]> 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  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, and the entire permission notice in its entirety,
11  *    including the disclaimer of warranties.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The name of the author may not be used to endorse or promote
16  *    products derived from this software without specific prior
17  *    written permission.
18  *
19  * ALTERNATIVELY, this product may be distributed under the terms of
20  * the GNU Public License, in which case the provisions of the GPL are
21  * required INSTEAD OF the above restrictions.  (This clause is
22  * necessary due to a potential bad interaction between the GPL and
23  * the restrictions contained in a BSD-style copyright.)
24  *
25  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
26  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
29  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
35  * OF THE POSSIBILITY OF SUCH DAMAGE.
36  */
37
38 #include <security/_pam_aconf.h>
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <stdarg.h>
43 #include <string.h>
44 #include <malloc.h>
45 #include <unistd.h>
46 #include <errno.h>
47 #include <sys/types.h>
48 #include <pwd.h>
49 #include <syslog.h>
50 #include <shadow.h>
51 #include <time.h>               /* for time() */
52 #include <fcntl.h>
53 #include <ctype.h>
54 #include <sys/time.h>
55 #include <sys/stat.h>
56 #include <rpc/rpc.h>
57 #include <rpcsvc/yp_prot.h>
58 #include <rpcsvc/ypclnt.h>
59
60 #ifdef USE_CRACKLIB
61 #include <crack.h>
62 #endif
63
64 #include <security/_pam_macros.h>
65
66 /* indicate the following groups are defined */
67
68 #define PAM_SM_PASSWORD
69
70 #include <security/pam_modules.h>
71
72 #ifndef LINUX_PAM
73 #include <security/pam_appl.h>
74 #endif                          /* LINUX_PAM */
75
76 #include <security/_pam_modutil.h>
77
78 #include "yppasswd.h"
79 #include "md5.h"
80 #include "support.h"
81
82 #if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
83 extern int getrpcport(const char *host, unsigned long prognum,
84                       unsigned long versnum, unsigned int proto);
85 #endif                          /* GNU libc 2.1 */
86
87 /*
88  * PAM framework looks for these entry-points to pass control to the
89  * password changing module.
90  */
91
92 #ifdef NEED_LCKPWDF
93 # include "./lckpwdf.-c"
94 #endif
95
96 extern char *bigcrypt(const char *key, const char *salt);
97
98 /*
99    How it works:
100    Gets in username (has to be done) from the calling program
101    Does authentication of user (only if we are not running as root)
102    Gets new password/checks for sanity
103    Sets it.
104  */
105
106 /* passwd/salt conversion macros */
107
108 #define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
109 #define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
110
111 /* data tokens */
112
113 #define _UNIX_OLD_AUTHTOK       "-UN*X-OLD-PASS"
114 #define _UNIX_NEW_AUTHTOK       "-UN*X-NEW-PASS"
115
116 #define MAX_PASSWD_TRIES        3
117 #define PW_TMPFILE              "/etc/npasswd"
118 #define SH_TMPFILE              "/etc/nshadow"
119 #define CRACKLIB_DICTS          "/usr/share/dict/cracklib_dict"
120 #define OPW_TMPFILE             "/etc/security/nopasswd"
121 #define OLD_PASSWORDS_FILE      "/etc/security/opasswd"
122
123 /*
124  * i64c - convert an integer to a radix 64 character
125  */
126 static int i64c(int i)
127 {
128         if (i < 0)
129                 return ('.');
130         else if (i > 63)
131                 return ('z');
132         if (i == 0)
133                 return ('.');
134         if (i == 1)
135                 return ('/');
136         if (i >= 2 && i <= 11)
137                 return ('0' - 2 + i);
138         if (i >= 12 && i <= 37)
139                 return ('A' - 12 + i);
140         if (i >= 38 && i <= 63)
141                 return ('a' - 38 + i);
142         return ('\0');
143 }
144
145 static char *crypt_md5_wrapper(const char *pass_new)
146 {
147         /*
148          * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
149          * removed use of static variables (AGM)
150          */
151
152         struct timeval tv;
153         MD5_CTX ctx;
154         unsigned char result[16];
155         char *cp = (char *) result;
156         unsigned char tmp[16];
157         int i;
158         char *x = NULL;
159
160         GoodMD5Init(&ctx);
161         gettimeofday(&tv, (struct timezone *) 0);
162         GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
163         i = getpid();
164         GoodMD5Update(&ctx, (void *) &i, sizeof i);
165         i = clock();
166         GoodMD5Update(&ctx, (void *) &i, sizeof i);
167         GoodMD5Update(&ctx, result, sizeof result);
168         GoodMD5Final(tmp, &ctx);
169         strcpy(cp, "$1$");      /* magic for the MD5 */
170         cp += strlen(cp);
171         for (i = 0; i < 8; i++)
172                 *cp++ = i64c(tmp[i] & 077);
173         *cp = '\0';
174
175         /* no longer need cleartext */
176         x = Goodcrypt_md5(pass_new, (const char *) result);
177
178         return x;
179 }
180
181 static char *getNISserver(pam_handle_t *pamh)
182 {
183         char *master;
184         char *domainname;
185         int port, err;
186
187         if ((err = yp_get_default_domain(&domainname)) != 0) {
188                 _log_err(LOG_WARNING, pamh, "can't get local yp domain: %s\n",
189                          yperr_string(err));
190                 return NULL;
191         }
192         if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
193                 _log_err(LOG_WARNING, pamh, "can't find the master ypserver: %s\n",
194                          yperr_string(err));
195                 return NULL;
196         }
197         port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
198         if (port == 0) {
199                 _log_err(LOG_WARNING, pamh,
200                          "yppasswdd not running on NIS master host\n");
201                 return NULL;
202         }
203         if (port >= IPPORT_RESERVED) {
204                 _log_err(LOG_WARNING, pamh,
205                          "yppasswd daemon running on illegal port.\n");
206                 return NULL;
207         }
208         return master;
209 }
210
211 static int check_old_password(const char *forwho, const char *newpass)
212 {
213         static char buf[16384];
214         char *s_luser, *s_uid, *s_npas, *s_pas;
215         int retval = PAM_SUCCESS;
216         FILE *opwfile;
217
218         opwfile = fopen(OLD_PASSWORDS_FILE, "r");
219         if (opwfile == NULL)
220                 return PAM_AUTHTOK_ERR;
221
222         while (fgets(buf, 16380, opwfile)) {
223                 if (!strncmp(buf, forwho, strlen(forwho))) {
224                         buf[strlen(buf) - 1] = '\0';
225                         s_luser = strtok(buf, ":,");
226                         s_uid = strtok(NULL, ":,");
227                         s_npas = strtok(NULL, ":,");
228                         s_pas = strtok(NULL, ":,");
229                         while (s_pas != NULL) {
230                                 char *md5pass = Goodcrypt_md5(newpass, s_pas);
231                                 if (!strcmp(md5pass, s_pas)) {
232                                         _pam_delete(md5pass);
233                                         retval = PAM_AUTHTOK_ERR;
234                                         break;
235                                 }
236                                 s_pas = strtok(NULL, ":,");
237                                 _pam_delete(md5pass);
238                         }
239                         break;
240                 }
241         }
242         fclose(opwfile);
243
244         return retval;
245 }
246
247 static int save_old_password(pam_handle_t *pamh,
248                              const char *forwho, const char *oldpass,
249                              int howmany)
250 {
251     static char buf[16384];
252     static char nbuf[16384];
253     char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
254     int npas;
255     FILE *pwfile, *opwfile;
256     int err = 0;
257     int oldmask;
258     int found = 0;
259     struct passwd *pwd = NULL;
260
261     if (howmany < 0) {
262         return PAM_SUCCESS;
263     }
264
265     if (oldpass == NULL) {
266         return PAM_SUCCESS;
267     }
268
269     oldmask = umask(077);
270     pwfile = fopen(OPW_TMPFILE, "w");
271     umask(oldmask);
272     if (pwfile == NULL) {
273         return PAM_AUTHTOK_ERR;
274     }
275
276     opwfile = fopen(OLD_PASSWORDS_FILE, "r");
277     if (opwfile == NULL) {
278         fclose(pwfile);
279         return PAM_AUTHTOK_ERR;
280     }
281
282     chown(OPW_TMPFILE, 0, 0);
283     chmod(OPW_TMPFILE, 0600);
284
285     while (fgets(buf, 16380, opwfile)) {
286         if (!strncmp(buf, forwho, strlen(forwho))) {
287             buf[strlen(buf) - 1] = '\0';
288             s_luser = strtok(buf, ":");
289             s_uid = strtok(NULL, ":");
290             s_npas = strtok(NULL, ":");
291             s_pas = strtok(NULL, ":");
292             npas = strtol(s_npas, NULL, 10) + 1;
293             while (npas > howmany) {
294                 s_pas = strpbrk(s_pas, ",");
295                 if (s_pas != NULL)
296                     s_pas++;
297                 npas--;
298             }
299             pass = crypt_md5_wrapper(oldpass);
300             if (s_pas == NULL)
301                 snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
302                          s_luser, s_uid, npas, pass);
303             else
304                 snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
305                          s_luser, s_uid, npas, s_pas, pass);
306             _pam_delete(pass);
307             if (fputs(nbuf, pwfile) < 0) {
308                 err = 1;
309                 break;
310             }
311             found = 1;
312         } else if (fputs(buf, pwfile) < 0) {
313             err = 1;
314             break;
315         }
316     }
317     fclose(opwfile);
318
319     if (!found) {
320         pwd = _pammodutil_getpwnam(pamh, forwho);
321         if (pwd == NULL) {
322             err = 1;
323         } else {
324             pass = crypt_md5_wrapper(oldpass);
325             snprintf(nbuf, sizeof(nbuf), "%s:%d:1:%s\n",
326                      forwho, pwd->pw_uid, pass);
327             _pam_delete(pass);
328             if (fputs(nbuf, pwfile) < 0) {
329                 err = 1;
330             }
331         }
332     }
333
334     if (fclose(pwfile)) {
335         D(("error writing entries to old passwords file: %s\n",
336            strerror(errno)));
337         err = 1;
338     }
339
340     if (!err) {
341         rename(OPW_TMPFILE, OLD_PASSWORDS_FILE);
342         return PAM_SUCCESS;
343     } else {
344         unlink(OPW_TMPFILE);
345         return PAM_AUTHTOK_ERR;
346     }
347 }
348
349 static int _update_passwd(pam_handle_t *pamh,
350                           const char *forwho, const char *towhat)
351 {
352     struct passwd *tmpent = NULL;
353     struct stat st;
354     FILE *pwfile, *opwfile;
355     int err = 1;
356     int oldmask;
357
358     oldmask = umask(077);
359     pwfile = fopen(PW_TMPFILE, "w");
360     umask(oldmask);
361     if (pwfile == NULL) {
362         return PAM_AUTHTOK_ERR;
363     }
364
365     opwfile = fopen("/etc/passwd", "r");
366     if (opwfile == NULL) {
367         fclose(pwfile);
368         return PAM_AUTHTOK_ERR;
369     }
370
371     if (fstat(fileno(opwfile), &st) == -1) {
372         chown(PW_TMPFILE, 0, 0);
373         chmod(PW_TMPFILE, 0644);
374     } else {
375         chown(PW_TMPFILE, st.st_uid, st.st_gid);
376         chmod(PW_TMPFILE, st.st_mode);
377     }
378     tmpent = fgetpwent(opwfile);
379     while (tmpent) {
380         if (!strcmp(tmpent->pw_name, forwho)) {
381             /* To shut gcc up */
382             union {
383                 const char *const_charp;
384                 char *charp;
385             } assigned_passwd;
386             assigned_passwd.const_charp = towhat;
387                         
388             tmpent->pw_passwd = assigned_passwd.charp;
389             err = 0;
390         }
391         if (putpwent(tmpent, pwfile)) {
392             D(("error writing entry to password file: %s\n", strerror(errno)));
393             err = 1;
394             break;
395         }
396         tmpent = fgetpwent(opwfile);
397     }
398     fclose(opwfile);
399
400     if (fclose(pwfile)) {
401         D(("error writing entries to password file: %s\n", strerror(errno)));
402         err = 1;
403     }
404
405     if (!err) {
406         rename(PW_TMPFILE, "/etc/passwd");
407         _log_err(LOG_NOTICE, pamh, "password changed for %s", forwho);
408         return PAM_SUCCESS;
409     } else {
410         unlink(PW_TMPFILE);
411         return PAM_AUTHTOK_ERR;
412     }
413 }
414
415 static int _update_shadow(const char *forwho, char *towhat)
416 {
417     struct spwd *spwdent = NULL, *stmpent = NULL;
418     struct stat st;
419     FILE *pwfile, *opwfile;
420     int err = 1;
421     int oldmask;
422
423     spwdent = getspnam(forwho);
424     if (spwdent == NULL) {
425         return PAM_USER_UNKNOWN;
426     }
427     oldmask = umask(077);
428     pwfile = fopen(SH_TMPFILE, "w");
429     umask(oldmask);
430     if (pwfile == NULL) {
431         return PAM_AUTHTOK_ERR;
432     }
433
434     opwfile = fopen("/etc/shadow", "r");
435     if (opwfile == NULL) {
436         fclose(pwfile);
437         return PAM_AUTHTOK_ERR;
438     }
439
440     if (fstat(fileno(opwfile), &st) == -1) {
441         chown(SH_TMPFILE, 0, 0);
442         chmod(SH_TMPFILE, 0600);
443     } else {
444         chown(SH_TMPFILE, st.st_uid, st.st_gid);
445         chmod(SH_TMPFILE, st.st_mode);
446     }
447     stmpent = fgetspent(opwfile);
448     while (stmpent) {
449
450         if (!strcmp(stmpent->sp_namp, forwho)) {
451             stmpent->sp_pwdp = towhat;
452             stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
453             err = 0;
454             D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
455         }
456
457         if (putspent(stmpent, pwfile)) {
458             D(("error writing entry to shadow file: %s\n", strerror(errno)));
459             err = 1;
460             break;
461         }
462
463         stmpent = fgetspent(opwfile);
464     }
465     fclose(opwfile);
466
467     if (fclose(pwfile)) {
468         D(("error writing entries to shadow file: %s\n", strerror(errno)));
469         err = 1;
470     }
471
472     if (!err) {
473         rename(SH_TMPFILE, "/etc/shadow");
474         return PAM_SUCCESS;
475     } else {
476         unlink(SH_TMPFILE);
477         return PAM_AUTHTOK_ERR;
478     }
479 }
480
481 static int _do_setpass(pam_handle_t* pamh, const char *forwho, char *fromwhat,
482                        char *towhat, unsigned int ctrl, int remember)
483 {
484         struct passwd *pwd = NULL;
485         int retval = 0;
486
487         D(("called"));
488
489         pwd = getpwnam(forwho);
490         if (pwd == NULL)
491                 return PAM_AUTHTOK_ERR;
492
493         if (on(UNIX_NIS, ctrl)) {
494                 struct timeval timeout;
495                 struct yppasswd yppwd;
496                 CLIENT *clnt;
497                 char *master;
498                 int status;
499                 int err = 0;
500
501                 /* Make RPC call to NIS server */
502                 if ((master = getNISserver(pamh)) == NULL)
503                         return PAM_TRY_AGAIN;
504
505                 /* Initialize password information */
506                 yppwd.newpw.pw_passwd = pwd->pw_passwd;
507                 yppwd.newpw.pw_name = pwd->pw_name;
508                 yppwd.newpw.pw_uid = pwd->pw_uid;
509                 yppwd.newpw.pw_gid = pwd->pw_gid;
510                 yppwd.newpw.pw_gecos = pwd->pw_gecos;
511                 yppwd.newpw.pw_dir = pwd->pw_dir;
512                 yppwd.newpw.pw_shell = pwd->pw_shell;
513                 yppwd.oldpass = fromwhat;
514                 yppwd.newpw.pw_passwd = towhat;
515
516                 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
517
518                 /* The yppasswd.x file said `unix authentication required',
519                  * so I added it. This is the only reason it is in here.
520                  * My yppasswdd doesn't use it, but maybe some others out there
521                  * do.                                        --okir
522                  */
523                 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
524                 clnt->cl_auth = authunix_create_default();
525                 memset((char *) &status, '\0', sizeof(status));
526                 timeout.tv_sec = 25;
527                 timeout.tv_usec = 0;
528                 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
529                                 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
530                                 (xdrproc_t) xdr_int, (char *) &status,
531                                 timeout);
532
533                 if (err) {
534                         clnt_perrno(err);
535                         retval = PAM_TRY_AGAIN;
536                 } else if (status) {
537                         D(("Error while changing NIS password.\n"));
538                         retval = PAM_TRY_AGAIN;
539                 }
540                 D(("The password has%s been changed on %s.",
541                    (err || status) ? " not" : "", master));
542                 _log_err(LOG_NOTICE, pamh, "password%s changed for %s on %s",
543                          (err || status) ? " not" : "", pwd->pw_name, master);
544
545                 auth_destroy(clnt->cl_auth);
546                 clnt_destroy(clnt);
547                 if ((err || status) != 0) {
548                         retval = PAM_TRY_AGAIN;
549                 }
550 #ifdef DEBUG
551                 sleep(5);
552 #endif
553                 return retval;
554         }
555         /* first, save old password */
556         if (save_old_password(pamh, forwho, fromwhat, remember)) {
557                 return PAM_AUTHTOK_ERR;
558         }
559
560 #ifdef USE_LCKPWDF
561         /*
562          * These values for the number of attempts and the sleep time
563          * are, of course, completely arbitrary.
564          *
565          * My reading of the PAM docs is that, once pam_chauthtok()
566          * has been called with PAM_UPDATE_AUTHTOK, we are obliged to
567          * take any reasonable steps to make sure the token is
568          * updated; so retrying for 1/10 sec. isn't overdoing it.
569          */
570
571         retval = lckpwdf();
572         if (retval != 0) {
573             return PAM_AUTHTOK_LOCK_BUSY;
574         }
575 #endif /* def USE_LCKPWDF */
576         
577         if (on(UNIX_SHADOW, ctrl) || (strcmp(pwd->pw_passwd, "x") == 0)) {
578                 retval = _update_shadow(forwho, towhat);
579                 if (retval == PAM_SUCCESS)
580                         retval = _update_passwd(pamh, forwho, "x");
581         } else {
582                 retval = _update_passwd(pamh, forwho, towhat);
583         }
584
585 #ifdef USE_LCKPWDF
586         ulckpwdf();
587 #endif /* def USE_LCKPWDF */
588
589         return retval;
590 }
591
592 static int _unix_verify_shadow(const char *user, unsigned int ctrl)
593 {
594         struct passwd *pwd = NULL;      /* Password and shadow password */
595         struct spwd *spwdent = NULL;    /* file entries for the user */
596         time_t curdays;
597         int retval = PAM_SUCCESS;
598
599         /* UNIX passwords area */
600         pwd = getpwnam(user);   /* Get password file entry... */
601         if (pwd == NULL)
602                 return PAM_AUTHINFO_UNAVAIL;    /* We don't need to do the rest... */
603
604         if (strcmp(pwd->pw_passwd, "x") == 0) {
605                 /* ...and shadow password file entry for this user, if shadowing
606                    is enabled */
607                 setspent();
608                 spwdent = getspnam(user);
609                 endspent();
610
611                 if (spwdent == NULL)
612                         return PAM_AUTHINFO_UNAVAIL;
613         } else {
614                 if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */                 
615                         uid_t save_uid;
616
617                         save_uid = geteuid();
618                         seteuid (pwd->pw_uid);
619                         spwdent = getspnam( user );
620                         seteuid (save_uid);
621
622                         if (spwdent == NULL)
623                                 return PAM_AUTHINFO_UNAVAIL;
624                 } else
625                         spwdent = NULL;
626         }
627
628         if (spwdent != NULL) {
629                 /* We have the user's information, now let's check if their account
630                    has expired (60 * 60 * 24 = number of seconds in a day) */
631
632                 if (off(UNIX__IAMROOT, ctrl)) {
633                         /* Get the current number of days since 1970 */
634                         curdays = time(NULL) / (60 * 60 * 24);
635                         if ((curdays < (spwdent->sp_lstchg + spwdent->sp_min))
636                             && (spwdent->sp_min != -1))
637                                 retval = PAM_AUTHTOK_ERR;
638                         else if ((curdays > (spwdent->sp_lstchg + spwdent->sp_max + spwdent->sp_inact))
639                                  && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1)
640                                  && (spwdent->sp_lstchg != 0))
641                                 /*
642                                  * Their password change has been put off too long,
643                                  */
644                                 retval = PAM_ACCT_EXPIRED;
645                         else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1)
646                                  && (spwdent->sp_lstchg != 0))
647                                 /*
648                                  * OR their account has just plain expired
649                                  */
650                                 retval = PAM_ACCT_EXPIRED;
651                 }
652         }
653         return retval;
654 }
655
656 static int _pam_unix_approve_pass(pam_handle_t * pamh
657                                   ,unsigned int ctrl
658                                   ,const char *pass_old
659                                   ,const char *pass_new)
660 {
661         const char *user;
662         const char *remark = NULL;
663         int retval = PAM_SUCCESS;
664
665         D(("&new=%p, &old=%p", pass_old, pass_new));
666         D(("new=[%s]", pass_new));
667         D(("old=[%s]", pass_old));
668
669         if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
670                 if (on(UNIX_DEBUG, ctrl)) {
671                         _log_err(LOG_DEBUG, pamh, "bad authentication token");
672                 }
673                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
674                           "No password supplied" : "Password unchanged");
675                 return PAM_AUTHTOK_ERR;
676         }
677         /*
678          * if one wanted to hardwire authentication token strength
679          * checking this would be the place - AGM
680          */
681
682         retval = pam_get_item(pamh, PAM_USER, (const void **) &user);
683         if (retval != PAM_SUCCESS) {
684                 if (on(UNIX_DEBUG, ctrl)) {
685                         _log_err(LOG_ERR, pamh, "Can not get username");
686                         return PAM_AUTHTOK_ERR;
687                 }
688         }
689         if (off(UNIX__IAMROOT, ctrl)) {
690 #ifdef USE_CRACKLIB
691                 remark = FascistCheck(pass_new, CRACKLIB_DICTS);
692                 D(("called cracklib [%s]", remark));
693 #else
694                 if (strlen(pass_new) < 6)
695                         remark = "You must choose a longer password";
696                 D(("lenth check [%s]", remark));
697 #endif
698                 if (on(UNIX_REMEMBER_PASSWD, ctrl))
699                         if ((retval = check_old_password(user, pass_new)) != PAM_SUCCESS)
700                                 remark = "Password has been already used. Choose another.";
701         }
702         if (remark) {
703                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
704                 retval = PAM_AUTHTOK_ERR;
705         }
706         return retval;
707 }
708
709
710 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
711                                 int argc, const char **argv)
712 {
713         unsigned int ctrl, lctrl;
714         int retval;
715         int remember = -1;
716
717         /* <DO NOT free() THESE> */
718         const char *user;
719         char *pass_old, *pass_new;
720         /* </DO NOT free() THESE> */
721
722         D(("called."));
723
724         ctrl = _set_ctrl(pamh, flags, &remember, argc, argv);
725
726         /*
727          * First get the name of a user
728          */
729         retval = pam_get_user(pamh, &user, NULL);
730         if (retval == PAM_SUCCESS) {
731                 /*
732                  * Various libraries at various times have had bugs related to
733                  * '+' or '-' as the first character of a user name. Don't take
734                  * any chances here. Require that the username starts with an
735                  * alphanumeric character.
736                  */
737                 if (user == NULL || !isalnum(*user)) {
738                         _log_err(LOG_ERR, pamh, "bad username [%s]", user);
739                         return PAM_USER_UNKNOWN;
740                 }
741                 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
742                         _log_err(LOG_DEBUG, pamh, "username [%s] obtained",
743                                  user);
744         } else {
745                 if (on(UNIX_DEBUG, ctrl))
746                         _log_err(LOG_DEBUG, pamh,
747                                  "password - could not identify user");
748                 return retval;
749         }
750
751         D(("Got username of %s", user));
752
753         /*
754          * This is not an AUTH module!
755          */
756         if (on(UNIX__NONULL, ctrl))
757                 set(UNIX__NULLOK, ctrl);
758
759         if (on(UNIX__PRELIM, ctrl)) {
760                 /*
761                  * obtain and verify the current password (OLDAUTHTOK) for
762                  * the user.
763                  */
764                 char *Announce;
765
766                 D(("prelim check"));
767
768                 if (_unix_blankpasswd(pamh, ctrl, user)) {
769                         return PAM_SUCCESS;
770                 } else if (off(UNIX__IAMROOT, ctrl)) {
771
772                         /* instruct user what is happening */
773 #define greeting "Changing password for "
774                         Announce = (char *) malloc(sizeof(greeting) + strlen(user));
775                         if (Announce == NULL) {
776                                 _log_err(LOG_CRIT, pamh,
777                                          "password - out of memory");
778                                 return PAM_BUF_ERR;
779                         }
780                         (void) strcpy(Announce, greeting);
781                         (void) strcpy(Announce + sizeof(greeting) - 1, user);
782 #undef greeting
783
784                         lctrl = ctrl;
785                         set(UNIX__OLD_PASSWD, lctrl);
786                         retval = _unix_read_password(pamh, lctrl
787                                                      ,Announce
788                                              ,"(current) UNIX password: "
789                                                      ,NULL
790                                                      ,_UNIX_OLD_AUTHTOK
791                                              ,(const char **) &pass_old);
792                         free(Announce);
793
794                         if (retval != PAM_SUCCESS) {
795                                 _log_err(LOG_NOTICE, pamh
796                                  ,"password - (old) token not obtained");
797                                 return retval;
798                         }
799                         /* verify that this is the password for this user */
800
801                         retval = _unix_verify_password(pamh, user, pass_old, ctrl);
802                 } else {
803                         D(("process run by root so do nothing this time around"));
804                         pass_old = NULL;
805                         retval = PAM_SUCCESS;   /* root doesn't have too */
806                 }
807
808                 if (retval != PAM_SUCCESS) {
809                         D(("Authentication failed"));
810                         pass_old = NULL;
811                         return retval;
812                 }
813                 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
814                 pass_old = NULL;
815                 if (retval != PAM_SUCCESS) {
816                         _log_err(LOG_CRIT, pamh,
817                                  "failed to set PAM_OLDAUTHTOK");
818                 }
819                 retval = _unix_verify_shadow(user, ctrl);
820                 if (retval == PAM_AUTHTOK_ERR) {
821                         if (off(UNIX__IAMROOT, ctrl))
822                                 _make_remark(pamh, ctrl, PAM_ERROR_MSG,
823                                             "You must wait longer to change your password");
824                         else
825                                 retval = PAM_SUCCESS;
826                 }
827         } else if (on(UNIX__UPDATE, ctrl)) {
828                 /*
829                  * tpass is used below to store the _pam_md() return; it
830                  * should be _pam_delete()'d.
831                  */
832
833                 char *tpass = NULL;
834                 int retry = 0;
835
836                 /*
837                  * obtain the proposed password
838                  */
839
840                 D(("do update"));
841
842                 /*
843                  * get the old token back. NULL was ok only if root [at this
844                  * point we assume that this has already been enforced on a
845                  * previous call to this function].
846                  */
847
848                 if (off(UNIX_NOT_SET_PASS, ctrl)) {
849                         retval = pam_get_item(pamh, PAM_OLDAUTHTOK
850                                               ,(const void **) &pass_old);
851                 } else {
852                         retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
853                                               ,(const void **) &pass_old);
854                         if (retval == PAM_NO_MODULE_DATA) {
855                                 retval = PAM_SUCCESS;
856                                 pass_old = NULL;
857                         }
858                 }
859                 D(("pass_old [%s]", pass_old));
860
861                 if (retval != PAM_SUCCESS) {
862                         _log_err(LOG_NOTICE, pamh, "user not authenticated");
863                         return retval;
864                 }
865                 retval = _unix_verify_shadow(user, ctrl);
866                 if (retval != PAM_SUCCESS) {
867                         _log_err(LOG_NOTICE, pamh, "user not authenticated 2");
868                         return retval;
869                 }
870                 D(("get new password now"));
871
872                 lctrl = ctrl;
873
874                 if (on(UNIX_USE_AUTHTOK, lctrl)) {
875                         set(UNIX_USE_FIRST_PASS, lctrl);
876                 }
877                 retry = 0;
878                 retval = PAM_AUTHTOK_ERR;
879                 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
880                         /*
881                          * use_authtok is to force the use of a previously entered
882                          * password -- needed for pluggable password strength checking
883                          */
884
885                         retval = _unix_read_password(pamh, lctrl
886                                                      ,NULL
887                                              ,"Enter new UNIX password: "
888                                             ,"Retype new UNIX password: "
889                                                      ,_UNIX_NEW_AUTHTOK
890                                              ,(const char **) &pass_new);
891
892                         if (retval != PAM_SUCCESS) {
893                                 if (on(UNIX_DEBUG, ctrl)) {
894                                         _log_err(LOG_ALERT, pamh
895                                                  ,"password - new password not obtained");
896                                 }
897                                 pass_old = NULL;        /* tidy up */
898                                 return retval;
899                         }
900                         D(("returned to _unix_chauthtok"));
901
902                         /*
903                          * At this point we know who the user is and what they
904                          * propose as their new password. Verify that the new
905                          * password is acceptable.
906                          */
907
908                         if (pass_new[0] == '\0') {      /* "\0" password = NULL */
909                                 pass_new = NULL;
910                         }
911                         retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
912                 }
913
914                 if (retval != PAM_SUCCESS) {
915                         _log_err(LOG_NOTICE, pamh,
916                                  "new password not acceptable");
917                         pass_new = pass_old = NULL;     /* tidy up */
918                         return retval;
919                 }
920                 /*
921                  * By reaching here we have approved the passwords and must now
922                  * rebuild the password database file.
923                  */
924
925                 /*
926                  * First we encrypt the new password.
927                  */
928
929                 if (on(UNIX_MD5_PASS, ctrl)) {
930                         tpass = crypt_md5_wrapper(pass_new);
931                 } else {
932                         /*
933                          * Salt manipulation is stolen from Rick Faith's passwd
934                          * program.  Sorry Rick :) -- alex
935                          */
936
937                         time_t tm;
938                         char salt[3];
939
940                         time(&tm);
941                         salt[0] = bin_to_ascii(tm & 0x3f);
942                         salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
943                         salt[2] = '\0';
944
945                         if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) {
946                                 /* 
947                                  * to avoid using the _extensions_ of the bigcrypt()
948                                  * function we truncate the newly entered password
949                                  * [Problems that followed from this are fixed as per
950                                  *  Bug 521314.]
951                                  */
952                                 char *temp = malloc(9);
953
954                                 if (temp == NULL) {
955                                         _log_err(LOG_CRIT, pamh,
956                                                  "out of memory for password");
957                                         pass_new = pass_old = NULL;     /* tidy up */
958                                         return PAM_BUF_ERR;
959                                 }
960                                 /* copy first 8 bytes of password */
961                                 strncpy(temp, pass_new, 8);
962                                 temp[8] = '\0';
963
964                                 /* no longer need cleartext */
965                                 tpass = bigcrypt(temp, salt);
966
967                                 _pam_delete(temp);      /* tidy up */
968                         } else {
969                                 tpass = bigcrypt(pass_new, salt);
970                         }
971                 }
972
973                 D(("password processed"));
974
975                 /* update the password database(s) -- race conditions..? */
976
977                 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
978                                      remember);
979
980                 _pam_delete(tpass);
981                 pass_old = pass_new = NULL;
982         } else {                /* something has broken with the module */
983                 _log_err(LOG_ALERT, pamh,
984                          "password received unknown request");
985                 retval = PAM_ABORT;
986         }
987
988         D(("retval was %d", retval));
989
990         return retval;
991 }
992
993
994 /* static module data */
995 #ifdef PAM_STATIC
996 struct pam_module _pam_unix_passwd_modstruct = {
997     "pam_unix_passwd",
998     NULL,
999     NULL,
1000     NULL,
1001     NULL,
1002     NULL,
1003     pam_sm_chauthtok,
1004 };
1005 #endif
1006