Added Steve Langasek <vorlon@netexpress.net> pam_smbpass PAM module code.
[jra/samba/.git] / source3 / pam_smbpass / pam_smb_passwd.c
1 /* Unix NT password database implementation, version 0.7.5.
2  *
3  * This program is free software; you can redistribute it and/or modify it under
4  * the terms of the GNU General Public License as published by the Free
5  * Software Foundation; either version 2 of the License, or (at your option)
6  * any later version.
7  *
8  * This program is distributed in the hope that it will be useful, but WITHOUT
9  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
11  * more details.
12  *
13  * You should have received a copy of the GNU General Public License along with
14  * this program; if not, write to the Free Software Foundation, Inc., 675
15  * Mass Ave, Cambridge, MA 02139, USA.
16 */
17
18 /* indicate the following groups are defined */
19 #define PAM_SM_PASSWORD
20
21 #include "includes.h"
22
23 #ifndef LINUX
24
25 /* This is only used in the Sun implementation. */
26 #include <security/pam_appl.h>
27
28 #endif  /* LINUX */
29
30 #include <security/pam_modules.h>
31
32 #include "general.h" 
33
34 #include "support.h"
35
36 int smb_update_db( pam_handle_t *pamh, int ctrl, const char *user
37                    , const char *pass_new )
38 {
39  char           c;
40  int            retval, i;
41  pstring        err_str;
42  pstring        msg_str;
43
44     err_str[0] = '\0';
45     msg_str[0] = '\0';
46
47     retval = local_password_change( user, 0, pass_new, err_str, sizeof(err_str),
48                                     msg_str, sizeof(msg_str) );
49
50     if (!retval) {
51         if (*err_str) {
52             err_str[PSTRING_LEN-1] = '\0';
53             make_remark( pamh, ctrl, PAM_ERROR_MSG, err_str );
54         }
55
56         /* FIXME: what value is appropriate here? */
57         retval = PAM_AUTHTOK_ERR;
58     } else {
59         if (*msg_str) {
60             msg_str[PSTRING_LEN-1] = '\0';
61             make_remark( pamh, ctrl, PAM_TEXT_INFO, msg_str );
62         }
63         retval = PAM_SUCCESS;
64     }
65
66     return retval;      
67
68 }
69
70
71 /* data tokens */
72
73 #define _SMB_OLD_AUTHTOK  "-SMB-OLD-PASS"
74 #define _SMB_NEW_AUTHTOK  "-SMB-NEW-PASS"
75
76 /*
77  * FUNCTION: pam_sm_chauthtok()
78  *
79  * This function is called twice by the PAM library, once with
80  * PAM_PRELIM_CHECK set, and then again with PAM_UPDATE_AUTHTOK set.  With
81  * Linux-PAM, these two passes generally involve first checking the old
82  * token and then changing the token.  This is what we do here.
83  *
84  * Having obtained a new password. The function updates the
85  * SMB_PASSWD_FILE file (normally, $(LIBDIR)/smbpasswd).
86  */
87
88 int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
89                      int argc, const char **argv)
90 {
91     unsigned int ctrl;
92     int retval;
93
94     extern BOOL in_client;
95
96     struct smb_passwd *smb_pwent=NULL;
97     const char *user;
98     const char *pass_old, *pass_new;
99
100     /* Samba initialization. */
101     setup_logging( "pam_smbpass", False );
102     charset_initialise();
103     in_client = True;
104
105     ctrl = set_ctrl(flags, argc, argv);
106
107     /*
108      * First get the name of a user.  No need to do anything if we can't
109      * determine this.
110      */
111
112     retval = pam_get_user( pamh, &user, "Username: " );
113     if (retval != PAM_SUCCESS) {
114         if (on( SMB_DEBUG, ctrl )) {
115             _log_err( LOG_DEBUG, "password: could not identify user" );
116         }
117         return retval;
118     }
119     if (on( SMB_DEBUG, ctrl )) {
120         _log_err( LOG_DEBUG, "username [%s] obtained", user );
121     }
122
123     if (!initialize_password_db()) {
124         _log_err( LOG_ALERT, "Cannot access samba password database" );
125         return PAM_AUTHINFO_UNAVAIL;
126     }
127
128     /* obtain user record */
129     smb_pwent = getsmbpwnam(user);
130
131     if (smb_pwent == NULL) {
132         _log_err( LOG_ALERT, "Failed to find entry for user %s.", user );
133         return PAM_USER_UNKNOWN;
134     }
135
136     if (flags & PAM_PRELIM_CHECK) {
137         /*
138          * obtain and verify the current password (OLDAUTHTOK) for
139          * the user.
140          */
141
142         char *Announce;
143
144         if (_smb_blankpasswd( ctrl, smb_pwent )) {
145
146             return PAM_SUCCESS;
147
148         }
149
150         /* Password change by root, or for an expired token, doesn't
151            require authentication.  Is this a good choice? */
152         if (getuid() != 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK)) {
153
154             /* tell user what is happening */
155 #define greeting "Changing password for "
156             Announce = (char *) malloc(sizeof(greeting)+strlen(user));
157             if (Announce == NULL) {
158                 _log_err(LOG_CRIT, "password: out of memory");
159                 return PAM_BUF_ERR;
160             }
161             strncpy( Announce, greeting, sizeof(greeting) );
162             strncpy( Announce+sizeof(greeting)-1, user, strlen(user)+1 );
163 #undef greeting
164
165             set( SMB__OLD_PASSWD, ctrl );
166             retval = _smb_read_password( pamh, ctrl
167                                          , Announce
168                                          , "Current SMB password: "
169                                          , NULL
170                                          , _SMB_OLD_AUTHTOK
171                                          , &pass_old );
172             free( Announce );
173
174             if (retval != PAM_SUCCESS) {
175                 _log_err( LOG_NOTICE
176                           , "password - (old) token not obtained" );
177                 return retval;
178             }
179
180             /* verify that this is the password for this user */
181
182             retval = _smb_verify_password( pamh, smb_pwent, pass_old, ctrl );
183
184         } else {
185             pass_old = NULL;
186             retval = PAM_SUCCESS;           /* root doesn't have to */
187         }
188
189         pass_old = NULL;
190         return retval;
191
192     } else if (flags & PAM_UPDATE_AUTHTOK) {
193
194         if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) {
195             /* NOTE: there is currently no support for password expiring
196                under Samba. Support will be added here when it becomes
197                available. */
198             return PAM_SUCCESS;
199         }
200         /*
201          * obtain the proposed password
202          */
203
204         /*
205          * get the old token back. NULL was ok only if root [at this
206          * point we assume that this has already been enforced on a
207          * previous call to this function].
208          */
209
210         if (off( SMB_NOT_SET_PASS, ctrl )) {
211             retval = pam_get_item( pamh, PAM_OLDAUTHTOK,
212                                    (const void **)&pass_old );
213         } else {
214             retval = pam_get_data( pamh, _SMB_OLD_AUTHTOK,
215                                    (const void **)&pass_old );
216             if (retval == PAM_NO_MODULE_DATA) {
217                 pass_old = NULL;
218                 retval = PAM_SUCCESS;
219             }
220         }
221
222         if (retval != PAM_SUCCESS) {
223             _log_err( LOG_NOTICE, "password: user not authenticated" );
224             return retval;
225         }
226
227         /*
228          * use_authtok is to force the use of a previously entered
229          * password -- needed for pluggable password strength checking
230          * or other module stacking
231          */
232
233         if (on( SMB_USE_AUTHTOK, ctrl )) {
234             set( SMB_USE_FIRST_PASS, ctrl );
235         }
236
237         retval = _smb_read_password( pamh, ctrl
238                                       , NULL
239                                       , "Enter new SMB password: "
240                                       , "Retype new SMB password: "
241                                       , _SMB_NEW_AUTHTOK
242                                       , &pass_new );
243
244         if (retval != PAM_SUCCESS) {
245             if (on( SMB_DEBUG, ctrl )) {
246                 _log_err( LOG_ALERT
247                           , "password: new password not obtained" );
248             }
249             pass_old = NULL;                               /* tidy up */
250             return retval;
251         }
252
253         /*
254          * At this point we know who the user is and what they
255          * propose as their new password. Verify that the new
256          * password is acceptable.
257          */ 
258
259         if (pass_new[0] == '\0') {     /* "\0" password = NULL */
260             pass_new = NULL;
261         }
262
263         retval = _pam_smb_approve_pass(pamh, ctrl, pass_old, pass_new);
264
265         if (retval != PAM_SUCCESS) {
266             _log_err(LOG_NOTICE, "new password not acceptable");
267             pass_new = pass_old = NULL;               /* tidy up */
268             return retval;
269         }
270
271         /*
272          * By reaching here we have approved the passwords and must now
273          * rebuild the smb password file.
274          */
275
276         /* update the password database */
277
278         retval = smb_update_db(pamh, ctrl, user, pass_new);
279         if (retval == PAM_SUCCESS) {
280             /* password updated */
281             _log_err( LOG_NOTICE, "password for (%s/%d) changed by (%s/%d)"
282                       , user, smb_pwent->smb_userid, uidtoname( getuid() )
283                       , getuid() );
284         } else {
285             _log_err( LOG_ERR, "password change failed for user %s"
286                       , user );
287         }
288
289         pass_old = pass_new = NULL;
290         smb_pwent = NULL;
291
292     } else {            /* something has broken with the library */
293
294         _log_err( LOG_ALERT, "password received unknown request" );
295         retval = PAM_ABORT;
296
297     }
298
299     return retval;
300 }
301
302 /* static module data */
303 #ifdef PAM_STATIC
304 struct pam_module _pam_smbpass_passwd_modstruct = {
305      "pam_smbpass",
306      NULL,
307      NULL,
308      NULL,
309      NULL,
310      NULL,
311      pam_sm_chauthtok
312 };
313 #endif
314