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