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