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