]> granicus.if.org Git - linux-pam/blob - modules/pam_unix/pam_unix_passwd.c
Relevant BUGIDs: 2487654
[linux-pam] / modules / pam_unix / pam_unix_passwd.c
1 /*
2  * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
3  * Copyright (C) 1996.
4  * Copyright (c) Jan Rêkorajski, 1999.
5  * Copyright (c) Red Hat, Inc., 2007, 2008.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, and the entire permission notice in its entirety,
12  *    including the disclaimer of warranties.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the author may not be used to endorse or promote
17  *    products derived from this software without specific prior
18  *    written permission.
19  *
20  * ALTERNATIVELY, this product may be distributed under the terms of
21  * the GNU Public License, in which case the provisions of the GPL are
22  * required INSTEAD OF the above restrictions.  (This clause is
23  * necessary due to a potential bad interaction between the GPL and
24  * the restrictions contained in a BSD-style copyright.)
25  *
26  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
27  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
30  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36  * OF THE POSSIBILITY OF SUCH DAMAGE.
37  */
38
39 #include "config.h"
40
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <stdarg.h>
44 #include <string.h>
45 #include <malloc.h>
46 #include <unistd.h>
47 #include <errno.h>
48 #include <sys/types.h>
49 #include <pwd.h>
50 #include <syslog.h>
51 #include <shadow.h>
52 #include <time.h>               /* for time() */
53 #include <fcntl.h>
54 #include <ctype.h>
55 #include <sys/time.h>
56 #include <sys/stat.h>
57 #include <rpc/rpc.h>
58 #include <rpcsvc/yp_prot.h>
59 #include <rpcsvc/ypclnt.h>
60
61 #include <signal.h>
62 #include <errno.h>
63 #include <sys/wait.h>
64 #ifdef WITH_SELINUX
65 static int selinux_enabled=-1;
66 #include <selinux/selinux.h>
67 #define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
68 #endif
69
70 #include <security/_pam_macros.h>
71
72 /* indicate the following groups are defined */
73
74 #define PAM_SM_PASSWORD
75
76 #include <security/pam_modules.h>
77 #include <security/pam_ext.h>
78 #include <security/pam_modutil.h>
79
80 #include "yppasswd.h"
81 #include "md5.h"
82 #include "support.h"
83 #include "passverify.h"
84 #include "bigcrypt.h"
85
86 #if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
87 extern int getrpcport(const char *host, unsigned long prognum,
88                       unsigned long versnum, unsigned int proto);
89 #endif                          /* GNU libc 2.1 */
90
91 /*
92    How it works:
93    Gets in username (has to be done) from the calling program
94    Does authentication of user (only if we are not running as root)
95    Gets new password/checks for sanity
96    Sets it.
97  */
98
99 /* data tokens */
100
101 #define _UNIX_OLD_AUTHTOK       "-UN*X-OLD-PASS"
102 #define _UNIX_NEW_AUTHTOK       "-UN*X-NEW-PASS"
103
104 #define MAX_PASSWD_TRIES        3
105
106 static char *getNISserver(pam_handle_t *pamh)
107 {
108         char *master;
109         char *domainname;
110         int port, err;
111
112         if ((err = yp_get_default_domain(&domainname)) != 0) {
113                 pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
114                          yperr_string(err));
115                 return NULL;
116         }
117         if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
118                 pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
119                          yperr_string(err));
120                 return NULL;
121         }
122         port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
123         if (port == 0) {
124                 pam_syslog(pamh, LOG_WARNING,
125                          "yppasswdd not running on NIS master host");
126                 return NULL;
127         }
128         if (port >= IPPORT_RESERVED) {
129                 pam_syslog(pamh, LOG_WARNING,
130                          "yppasswd daemon running on illegal port");
131                 return NULL;
132         }
133         return master;
134 }
135
136 #ifdef WITH_SELINUX
137
138 static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user,
139     const char *fromwhat, const char *towhat, int remember)
140 {
141     int retval, child, fds[2];
142     struct sigaction newsa, oldsa;
143
144     D(("called."));
145     /* create a pipe for the password */
146     if (pipe(fds) != 0) {
147         D(("could not make pipe"));
148         return PAM_AUTH_ERR;
149     }
150
151     if (off(UNIX_NOREAP, ctrl)) {
152         /*
153          * This code arranges that the demise of the child does not cause
154          * the application to receive a signal it is not expecting - which
155          * may kill the application or worse.
156          *
157          * The "noreap" module argument is provided so that the admin can
158          * override this behavior.
159          */
160         memset(&newsa, '\0', sizeof(newsa));
161         newsa.sa_handler = SIG_DFL;
162         sigaction(SIGCHLD, &newsa, &oldsa);
163     }
164
165     /* fork */
166     child = fork();
167     if (child == 0) {
168         int i=0;
169         struct rlimit rlim;
170         static char *envp[] = { NULL };
171         char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
172         char buffer[16];
173
174         /* XXX - should really tidy up PAM here too */
175
176         /* reopen stdin as pipe */
177         dup2(fds[0], STDIN_FILENO);
178
179         if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
180           if (rlim.rlim_max >= MAX_FD_NO)
181             rlim.rlim_max = MAX_FD_NO;
182           for (i=0; i < (int)rlim.rlim_max; i++) {
183             if (i != STDIN_FILENO)
184                    close(i);
185           }
186         }
187
188         if (SELINUX_ENABLED && geteuid() == 0) {
189           /* must set the real uid to 0 so the helper will not error
190              out if pam is called from setuid binary (su, sudo...) */
191           setuid(0);
192         }
193
194         /* exec binary helper */
195         args[0] = x_strdup(UPDATE_HELPER);
196         args[1] = x_strdup(user);
197         args[2] = x_strdup("update");
198         if (on(UNIX_SHADOW, ctrl))
199                 args[3] = x_strdup("1");
200         else
201                 args[3] = x_strdup("0");
202
203         snprintf(buffer, sizeof(buffer), "%d", remember);
204         args[4] = x_strdup(buffer);
205         
206         execve(UPDATE_HELPER, args, envp);
207
208         /* should not get here: exit with error */
209         D(("helper binary is not available"));
210         exit(PAM_AUTHINFO_UNAVAIL);
211     } else if (child > 0) {
212         /* wait for child */
213         /* if the stored password is NULL */
214         int rc=0;
215         if (fromwhat)
216           pam_modutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
217         else
218           pam_modutil_write(fds[1], "", 1);
219         if (towhat) {
220           pam_modutil_write(fds[1], towhat, strlen(towhat)+1);
221         }
222         else
223           pam_modutil_write(fds[1], "", 1);
224
225         close(fds[0]);       /* close here to avoid possible SIGPIPE above */
226         close(fds[1]);
227         rc=waitpid(child, &retval, 0);  /* wait for helper to complete */
228         if (rc<0) {
229           pam_syslog(pamh, LOG_ERR, "unix_update waitpid failed: %m");
230           retval = PAM_AUTHTOK_ERR;
231         } else if (!WIFEXITED(retval)) {
232           pam_syslog(pamh, LOG_ERR, "unix_update abnormal exit: %d", retval);
233           retval = PAM_AUTHTOK_ERR;
234         } else {
235           retval = WEXITSTATUS(retval);
236         }
237     } else {
238         D(("fork failed"));
239         close(fds[0]);
240         close(fds[1]);
241         retval = PAM_AUTH_ERR;
242     }
243
244     if (off(UNIX_NOREAP, ctrl)) {
245         sigaction(SIGCHLD, &oldsa, NULL);   /* restore old signal handler */
246     }
247
248     return retval;
249 }
250 #endif
251
252 static int check_old_password(const char *forwho, const char *newpass)
253 {
254         static char buf[16384];
255         char *s_luser, *s_uid, *s_npas, *s_pas;
256         int retval = PAM_SUCCESS;
257         FILE *opwfile;
258
259         opwfile = fopen(OLD_PASSWORDS_FILE, "r");
260         if (opwfile == NULL)
261                 return PAM_ABORT;
262
263         while (fgets(buf, 16380, opwfile)) {
264                 if (!strncmp(buf, forwho, strlen(forwho))) {
265                         char *sptr;
266                         buf[strlen(buf) - 1] = '\0';
267                         s_luser = strtok_r(buf, ":,", &sptr);
268                         s_uid = strtok_r(NULL, ":,", &sptr);
269                         s_npas = strtok_r(NULL, ":,", &sptr);
270                         s_pas = strtok_r(NULL, ":,", &sptr);
271                         while (s_pas != NULL) {
272                                 char *md5pass = Goodcrypt_md5(newpass, s_pas);
273                                 if (!strcmp(md5pass, s_pas)) {
274                                         _pam_delete(md5pass);
275                                         retval = PAM_AUTHTOK_ERR;
276                                         break;
277                                 }
278                                 s_pas = strtok_r(NULL, ":,", &sptr);
279                                 _pam_delete(md5pass);
280                         }
281                         break;
282                 }
283         }
284         fclose(opwfile);
285
286         return retval;
287 }
288
289 static int _do_setpass(pam_handle_t* pamh, const char *forwho,
290                        const char *fromwhat,
291                        char *towhat, unsigned int ctrl, int remember)
292 {
293         struct passwd *pwd = NULL;
294         int retval = 0;
295         int unlocked = 0;
296         char *master = NULL;
297
298         D(("called"));
299
300         pwd = getpwnam(forwho);
301
302         if (pwd == NULL) {
303                 retval = PAM_AUTHTOK_ERR;
304                 goto done;
305         }
306
307         if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
308             if ((master=getNISserver(pamh)) != NULL) {
309                 struct timeval timeout;
310                 struct yppasswd yppwd;
311                 CLIENT *clnt;
312                 int status;
313                 enum clnt_stat err;
314
315                 /* Unlock passwd file to avoid deadlock */
316                 unlock_pwdf();
317                 unlocked = 1;
318
319                 /* Initialize password information */
320                 yppwd.newpw.pw_passwd = pwd->pw_passwd;
321                 yppwd.newpw.pw_name = pwd->pw_name;
322                 yppwd.newpw.pw_uid = pwd->pw_uid;
323                 yppwd.newpw.pw_gid = pwd->pw_gid;
324                 yppwd.newpw.pw_gecos = pwd->pw_gecos;
325                 yppwd.newpw.pw_dir = pwd->pw_dir;
326                 yppwd.newpw.pw_shell = pwd->pw_shell;
327                 yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
328                 yppwd.newpw.pw_passwd = towhat;
329
330                 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
331
332                 /* The yppasswd.x file said `unix authentication required',
333                  * so I added it. This is the only reason it is in here.
334                  * My yppasswdd doesn't use it, but maybe some others out there
335                  * do.                                        --okir
336                  */
337                 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
338                 clnt->cl_auth = authunix_create_default();
339                 memset((char *) &status, '\0', sizeof(status));
340                 timeout.tv_sec = 25;
341                 timeout.tv_usec = 0;
342                 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
343                                 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
344                                 (xdrproc_t) xdr_int, (char *) &status,
345                                 timeout);
346
347                 free (yppwd.oldpass);
348
349                 if (err) {
350                         _make_remark(pamh, ctrl, PAM_TEXT_INFO,
351                                 clnt_sperrno(err));
352                 } else if (status) {
353                         D(("Error while changing NIS password.\n"));
354                 }
355                 D(("The password has%s been changed on %s.",
356                    (err || status) ? " not" : "", master));
357                 pam_syslog(pamh, LOG_NOTICE, "password%s changed for %s on %s",
358                          (err || status) ? " not" : "", pwd->pw_name, master);
359
360                 auth_destroy(clnt->cl_auth);
361                 clnt_destroy(clnt);
362                 if (err || status) {
363                         _make_remark(pamh, ctrl, PAM_TEXT_INFO,
364                                 _("NIS password could not be changed."));
365                         retval = PAM_TRY_AGAIN;
366                 }
367 #ifdef DEBUG
368                 sleep(5);
369 #endif
370             } else {
371                     retval = PAM_TRY_AGAIN;
372             }
373         }
374
375         if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
376                 if(unlocked) {
377                         if (lock_pwdf() != PAM_SUCCESS) {
378                                 return PAM_AUTHTOK_LOCK_BUSY;
379                         }
380                 }
381 #ifdef WITH_SELINUX
382                 if (unix_selinux_confined())
383                           return _unix_run_update_binary(pamh, ctrl, forwho, fromwhat, towhat, remember);
384 #endif
385                 /* first, save old password */
386                 if (save_old_password(pamh, forwho, fromwhat, remember)) {
387                         retval = PAM_AUTHTOK_ERR;
388                         goto done;
389                 }
390                 if (on(UNIX_SHADOW, ctrl) || is_pwd_shadowed(pwd)) {
391                         retval = unix_update_shadow(pamh, forwho, towhat);
392                         if (retval == PAM_SUCCESS)
393                                 if (!is_pwd_shadowed(pwd))
394                                         retval = unix_update_passwd(pamh, forwho, "x");
395                 } else {
396                         retval = unix_update_passwd(pamh, forwho, towhat);
397                 }
398         }
399
400
401 done:
402         unlock_pwdf();
403
404         return retval;
405 }
406
407 static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
408 {
409         struct passwd *pwent = NULL;    /* Password and shadow password */
410         struct spwd *spent = NULL;      /* file entries for the user */
411         int daysleft;
412         int retval;
413
414         retval = get_account_info(pamh, user, &pwent, &spent);
415         if (retval == PAM_USER_UNKNOWN) {
416                 return retval;
417         }
418
419         if (retval == PAM_SUCCESS && spent == NULL)
420                 return PAM_SUCCESS;
421
422         if (retval == PAM_UNIX_RUN_HELPER) {
423                 retval = _unix_run_verify_binary(pamh, ctrl, user, &daysleft);
424                 if (retval == PAM_AUTH_ERR || retval == PAM_USER_UNKNOWN)
425                         return retval;
426         }
427         else if (retval == PAM_SUCCESS)
428                 retval = check_shadow_expiry(pamh, spent, &daysleft);
429
430         if (on(UNIX__IAMROOT, ctrl) || retval == PAM_NEW_AUTHTOK_REQD)
431                 return PAM_SUCCESS;
432
433         return retval;
434 }
435
436 static int _pam_unix_approve_pass(pam_handle_t * pamh
437                                   ,unsigned int ctrl
438                                   ,const char *pass_old
439                                   ,const char *pass_new)
440 {
441         const void *user;
442         const char *remark = NULL;
443         int retval = PAM_SUCCESS;
444
445         D(("&new=%p, &old=%p", pass_old, pass_new));
446         D(("new=[%s]", pass_new));
447         D(("old=[%s]", pass_old));
448
449         if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
450                 if (on(UNIX_DEBUG, ctrl)) {
451                         pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
452                 }
453                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
454                         _("No password supplied") : _("Password unchanged"));
455                 return PAM_AUTHTOK_ERR;
456         }
457         /*
458          * if one wanted to hardwire authentication token strength
459          * checking this would be the place - AGM
460          */
461
462         retval = pam_get_item(pamh, PAM_USER, &user);
463         if (retval != PAM_SUCCESS) {
464                 if (on(UNIX_DEBUG, ctrl)) {
465                         pam_syslog(pamh, LOG_ERR, "Can not get username");
466                         return PAM_AUTHTOK_ERR;
467                 }
468         }
469         if (off(UNIX__IAMROOT, ctrl)) {
470                 if (strlen(pass_new) < 6)
471                   remark = _("You must choose a longer password");
472                 D(("length check [%s]", remark));
473                 if (on(UNIX_REMEMBER_PASSWD, ctrl)) {
474                         if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR)
475                           remark = _("Password has been already used. Choose another.");
476                         if (retval == PAM_ABORT) {
477                                 pam_syslog(pamh, LOG_ERR, "can't open %s file to check old passwords",
478                                         OLD_PASSWORDS_FILE);
479                                 return retval;
480                         }
481                 }
482         }
483         if (remark) {
484                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
485                 retval = PAM_AUTHTOK_ERR;
486         }
487         return retval;
488 }
489
490
491 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
492                                 int argc, const char **argv)
493 {
494         unsigned int ctrl, lctrl;
495         int retval;
496         int remember = -1;
497         int rounds = -1;
498
499         /* <DO NOT free() THESE> */
500         const char *user;
501         const void *pass_old, *pass_new;
502         /* </DO NOT free() THESE> */
503
504         D(("called."));
505
506         ctrl = _set_ctrl(pamh, flags, &remember, &rounds, argc, argv);
507
508         /*
509          * First get the name of a user
510          */
511         retval = pam_get_user(pamh, &user, NULL);
512         if (retval == PAM_SUCCESS) {
513                 /*
514                  * Various libraries at various times have had bugs related to
515                  * '+' or '-' as the first character of a user name. Don't
516                  * allow them.
517                  */
518                 if (user == NULL || user[0] == '-' || user[0] == '+') {
519                         pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
520                         return PAM_USER_UNKNOWN;
521                 }
522                 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
523                         pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
524                                  user);
525         } else {
526                 if (on(UNIX_DEBUG, ctrl))
527                         pam_syslog(pamh, LOG_DEBUG,
528                                  "password - could not identify user");
529                 return retval;
530         }
531
532         D(("Got username of %s", user));
533
534         /*
535          * Before we do anything else, check to make sure that the user's
536          * info is in one of the databases we can modify from this module,
537          * which currently is 'files' and 'nis'.  We have to do this because
538          * getpwnam() doesn't tell you *where* the information it gives you
539          * came from, nor should it.  That's our job.
540          */
541         if (_unix_comesfromsource(pamh, user, 1, on(UNIX_NIS, ctrl)) == 0) {
542                 pam_syslog(pamh, LOG_DEBUG,
543                          "user \"%s\" does not exist in /etc/passwd%s",
544                          user, on(UNIX_NIS, ctrl) ? " or NIS" : "");
545                 return PAM_USER_UNKNOWN;
546         } else {
547                 struct passwd *pwd;
548                 _unix_getpwnam(pamh, user, 1, 1, &pwd);
549                 if (pwd == NULL) {
550                         pam_syslog(pamh, LOG_DEBUG,
551                                 "user \"%s\" has corrupted passwd entry",
552                                 user);
553                         return PAM_USER_UNKNOWN;
554                 }
555         }
556
557         /*
558          * This is not an AUTH module!
559          */
560         if (on(UNIX__NONULL, ctrl))
561                 set(UNIX__NULLOK, ctrl);
562
563         if (on(UNIX__PRELIM, ctrl)) {
564                 /*
565                  * obtain and verify the current password (OLDAUTHTOK) for
566                  * the user.
567                  */
568                 char *Announce;
569
570                 D(("prelim check"));
571
572                 if (_unix_blankpasswd(pamh, ctrl, user)) {
573                         return PAM_SUCCESS;
574                 } else if (off(UNIX__IAMROOT, ctrl)) {
575                         /* instruct user what is happening */
576                         if (asprintf(&Announce, _("Changing password for %s."),
577                                 user) < 0) {
578                                 pam_syslog(pamh, LOG_CRIT,
579                                          "password - out of memory");
580                                 return PAM_BUF_ERR;
581                         }
582
583                         lctrl = ctrl;
584                         set(UNIX__OLD_PASSWD, lctrl);
585                         retval = _unix_read_password(pamh, lctrl
586                                                      ,Announce
587                                              ,_("(current) UNIX password: ")
588                                                      ,NULL
589                                                      ,_UNIX_OLD_AUTHTOK
590                                              ,&pass_old);
591                         free(Announce);
592
593                         if (retval != PAM_SUCCESS) {
594                                 pam_syslog(pamh, LOG_NOTICE,
595                                     "password - (old) token not obtained");
596                                 return retval;
597                         }
598                         /* verify that this is the password for this user */
599
600                         retval = _unix_verify_password(pamh, user, pass_old, ctrl);
601                 } else {
602                         D(("process run by root so do nothing this time around"));
603                         pass_old = NULL;
604                         retval = PAM_SUCCESS;   /* root doesn't have too */
605                 }
606
607                 if (retval != PAM_SUCCESS) {
608                         D(("Authentication failed"));
609                         pass_old = NULL;
610                         return retval;
611                 }
612                 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
613                 pass_old = NULL;
614                 if (retval != PAM_SUCCESS) {
615                         pam_syslog(pamh, LOG_CRIT,
616                                  "failed to set PAM_OLDAUTHTOK");
617                 }
618                 retval = _unix_verify_shadow(pamh,user, ctrl);
619                 if (retval == PAM_AUTHTOK_ERR) {
620                         if (off(UNIX__IAMROOT, ctrl))
621                                 _make_remark(pamh, ctrl, PAM_ERROR_MSG,
622                                              _("You must wait longer to change your password"));
623                         else
624                                 retval = PAM_SUCCESS;
625                 }
626         } else if (on(UNIX__UPDATE, ctrl)) {
627                 /*
628                  * tpass is used below to store the _pam_md() return; it
629                  * should be _pam_delete()'d.
630                  */
631
632                 char *tpass = NULL;
633                 int retry = 0;
634
635                 /*
636                  * obtain the proposed password
637                  */
638
639                 D(("do update"));
640
641                 /*
642                  * get the old token back. NULL was ok only if root [at this
643                  * point we assume that this has already been enforced on a
644                  * previous call to this function].
645                  */
646
647                 if (off(UNIX_NOT_SET_PASS, ctrl)) {
648                         retval = pam_get_item(pamh, PAM_OLDAUTHTOK
649                                               ,&pass_old);
650                 } else {
651                         retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
652                                               ,&pass_old);
653                         if (retval == PAM_NO_MODULE_DATA) {
654                                 retval = PAM_SUCCESS;
655                                 pass_old = NULL;
656                         }
657                 }
658                 D(("pass_old [%s]", pass_old));
659
660                 if (retval != PAM_SUCCESS) {
661                         pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
662                         return retval;
663                 }
664
665                 D(("get new password now"));
666
667                 lctrl = ctrl;
668
669                 if (on(UNIX_USE_AUTHTOK, lctrl)) {
670                         set(UNIX_USE_FIRST_PASS, lctrl);
671                 }
672                 retry = 0;
673                 retval = PAM_AUTHTOK_ERR;
674                 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
675                         /*
676                          * use_authtok is to force the use of a previously entered
677                          * password -- needed for pluggable password strength checking
678                          */
679
680                         retval = _unix_read_password(pamh, lctrl
681                                                      ,NULL
682                                              ,_("Enter new UNIX password: ")
683                                             ,_("Retype new UNIX password: ")
684                                                      ,_UNIX_NEW_AUTHTOK
685                                              ,&pass_new);
686
687                         if (retval != PAM_SUCCESS) {
688                                 if (on(UNIX_DEBUG, ctrl)) {
689                                         pam_syslog(pamh, LOG_ALERT,
690                                                  "password - new password not obtained");
691                                 }
692                                 pass_old = NULL;        /* tidy up */
693                                 return retval;
694                         }
695                         D(("returned to _unix_chauthtok"));
696
697                         /*
698                          * At this point we know who the user is and what they
699                          * propose as their new password. Verify that the new
700                          * password is acceptable.
701                          */
702
703                         if (*(const char *)pass_new == '\0') {  /* "\0" password = NULL */
704                                 pass_new = NULL;
705                         }
706                         retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
707                         
708                         if (retval != PAM_SUCCESS && off(UNIX_NOT_SET_PASS, ctrl)) {
709                                 pam_set_item(pamh, PAM_AUTHTOK, NULL);
710                         }
711                 }
712
713                 if (retval != PAM_SUCCESS) {
714                         pam_syslog(pamh, LOG_NOTICE,
715                                  "new password not acceptable");
716                         pass_new = pass_old = NULL;     /* tidy up */
717                         return retval;
718                 }
719                 if (lock_pwdf() != PAM_SUCCESS) {
720                         return PAM_AUTHTOK_LOCK_BUSY;
721                 }
722
723                 if (pass_old) {
724                         retval = _unix_verify_password(pamh, user, pass_old, ctrl);
725                         if (retval != PAM_SUCCESS) {
726                                 pam_syslog(pamh, LOG_NOTICE, "user password changed by another process");
727                                 unlock_pwdf();
728                                 return retval;
729                         }
730                 }
731
732                 retval = _unix_verify_shadow(pamh, user, ctrl);
733                 if (retval != PAM_SUCCESS) {
734                         pam_syslog(pamh, LOG_NOTICE, "user shadow entry expired");
735                         unlock_pwdf();
736                         return retval;
737                 }
738
739                 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
740                 if (retval != PAM_SUCCESS) {
741                         pam_syslog(pamh, LOG_NOTICE,
742                                  "new password not acceptable 2");
743                         pass_new = pass_old = NULL;     /* tidy up */
744                         unlock_pwdf();
745                         return retval;
746                 }
747
748                 /*
749                  * By reaching here we have approved the passwords and must now
750                  * rebuild the password database file.
751                  */
752
753                 /*
754                  * First we encrypt the new password.
755                  */
756
757                 tpass = create_password_hash(pamh, pass_new, ctrl, rounds);
758                 if (tpass == NULL) {
759                         pam_syslog(pamh, LOG_CRIT,
760                                 "out of memory for password");
761                         pass_new = pass_old = NULL;     /* tidy up */
762                         unlock_pwdf();
763                         return PAM_BUF_ERR;
764                 }
765
766                 D(("password processed"));
767
768                 /* update the password database(s) -- race conditions..? */
769
770                 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
771                                      remember);
772                 /* _do_setpass has called unlock_pwdf for us */
773
774                 _pam_delete(tpass);
775                 pass_old = pass_new = NULL;
776         } else {                /* something has broken with the module */
777                 pam_syslog(pamh, LOG_ALERT,
778                          "password received unknown request");
779                 retval = PAM_ABORT;
780         }
781
782         D(("retval was %d", retval));
783
784         return retval;
785 }
786
787
788 /* static module data */
789 #ifdef PAM_STATIC
790 struct pam_module _pam_unix_passwd_modstruct = {
791     "pam_unix_passwd",
792     NULL,
793     NULL,
794     NULL,
795     NULL,
796     NULL,
797     pam_sm_chauthtok,
798 };
799 #endif