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