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