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