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