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