]> granicus.if.org Git - linux-pam/blob - modules/pam_unix/pam_unix_passwd.c
Relevant BUGIDs:
[linux-pam] / modules / pam_unix / pam_unix_passwd.c
1 /*
2  * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
3  * Copyright (C) 1996.
4  * Copyright (c) Jan Rêkorajski, 1999.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, and the entire permission notice in its entirety,
11  *    including the disclaimer of warranties.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. The name of the author may not be used to endorse or promote
16  *    products derived from this software without specific prior
17  *    written permission.
18  *
19  * ALTERNATIVELY, this product may be distributed under the terms of
20  * the GNU Public License, in which case the provisions of the GPL are
21  * required INSTEAD OF the above restrictions.  (This clause is
22  * necessary due to a potential bad interaction between the GPL and
23  * the restrictions contained in a BSD-style copyright.)
24  *
25  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
26  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
29  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
35  * OF THE POSSIBILITY OF SUCH DAMAGE.
36  */
37
38 #include "config.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 #include <signal.h>
61 #include <errno.h>
62 #include <sys/wait.h>
63 #ifdef WITH_SELINUX
64 static int selinux_enabled=-1;
65 #include <selinux/selinux.h>
66 static security_context_t prev_context=NULL;
67 #define SELINUX_ENABLED (selinux_enabled!=-1 ? selinux_enabled : (selinux_enabled=is_selinux_enabled()>0))
68 #endif
69
70 #ifdef USE_CRACKLIB
71 #include <crack.h>
72 #endif
73
74 #include <security/_pam_macros.h>
75
76 /* indicate the following groups are defined */
77
78 #define PAM_SM_PASSWORD
79
80 #include <security/pam_modules.h>
81 #include <security/pam_ext.h>
82 #include <security/pam_modutil.h>
83
84 #include "yppasswd.h"
85 #include "md5.h"
86 #include "support.h"
87 #include "bigcrypt.h"
88
89 #if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
90 extern int getrpcport(const char *host, unsigned long prognum,
91                       unsigned long versnum, unsigned int proto);
92 #endif                          /* GNU libc 2.1 */
93
94 /*
95  * PAM framework looks for these entry-points to pass control to the
96  * password changing module.
97  */
98
99 #if defined(USE_LCKPWDF) && !defined(HAVE_LCKPWDF)
100 # include "./lckpwdf.-c"
101 #endif
102
103 /*
104    How it works:
105    Gets in username (has to be done) from the calling program
106    Does authentication of user (only if we are not running as root)
107    Gets new password/checks for sanity
108    Sets it.
109  */
110
111 /* passwd/salt conversion macros */
112
113 #define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
114 #define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
115
116 /* data tokens */
117
118 #define _UNIX_OLD_AUTHTOK       "-UN*X-OLD-PASS"
119 #define _UNIX_NEW_AUTHTOK       "-UN*X-NEW-PASS"
120
121 #define MAX_PASSWD_TRIES        3
122 #define PW_TMPFILE              "/etc/npasswd"
123 #define SH_TMPFILE              "/etc/nshadow"
124 #ifndef CRACKLIB_DICTS
125 #define CRACKLIB_DICTS          NULL
126 #endif
127 #define OPW_TMPFILE             "/etc/security/nopasswd"
128 #define OLD_PASSWORDS_FILE      "/etc/security/opasswd"
129
130 /*
131  * i64c - convert an integer to a radix 64 character
132  */
133 static int i64c(int i)
134 {
135         if (i < 0)
136                 return ('.');
137         else if (i > 63)
138                 return ('z');
139         if (i == 0)
140                 return ('.');
141         if (i == 1)
142                 return ('/');
143         if (i >= 2 && i <= 11)
144                 return ('0' - 2 + i);
145         if (i >= 12 && i <= 37)
146                 return ('A' - 12 + i);
147         if (i >= 38 && i <= 63)
148                 return ('a' - 38 + i);
149         return ('\0');
150 }
151
152 static char *crypt_md5_wrapper(const char *pass_new)
153 {
154         /*
155          * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
156          * removed use of static variables (AGM)
157          */
158
159         struct timeval tv;
160         MD5_CTX ctx;
161         unsigned char result[16];
162         char *cp = (char *) result;
163         unsigned char tmp[16];
164         int i;
165         char *x = NULL;
166
167         GoodMD5Init(&ctx);
168         gettimeofday(&tv, (struct timezone *) 0);
169         GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
170         i = getpid();
171         GoodMD5Update(&ctx, (void *) &i, sizeof i);
172         i = clock();
173         GoodMD5Update(&ctx, (void *) &i, sizeof i);
174         GoodMD5Update(&ctx, result, sizeof result);
175         GoodMD5Final(tmp, &ctx);
176         strcpy(cp, "$1$");      /* magic for the MD5 */
177         cp += strlen(cp);
178         for (i = 0; i < 8; i++)
179                 *cp++ = i64c(tmp[i] & 077);
180         *cp = '\0';
181
182         /* no longer need cleartext */
183         x = Goodcrypt_md5(pass_new, (const char *) result);
184
185         return x;
186 }
187
188 static char *getNISserver(pam_handle_t *pamh)
189 {
190         char *master;
191         char *domainname;
192         int port, err;
193
194         if ((err = yp_get_default_domain(&domainname)) != 0) {
195                 pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
196                          yperr_string(err));
197                 return NULL;
198         }
199         if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
200                 pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
201                          yperr_string(err));
202                 return NULL;
203         }
204         port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
205         if (port == 0) {
206                 pam_syslog(pamh, LOG_WARNING,
207                          "yppasswdd not running on NIS master host");
208                 return NULL;
209         }
210         if (port >= IPPORT_RESERVED) {
211                 pam_syslog(pamh, LOG_WARNING,
212                          "yppasswd daemon running on illegal port");
213                 return NULL;
214         }
215         return master;
216 }
217
218 #ifdef WITH_SELINUX
219
220 static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user, const char *fromwhat, const char *towhat)
221 {
222     int retval, child, fds[2];
223     void (*sighandler)(int) = NULL;
224
225     D(("called."));
226     /* create a pipe for the password */
227     if (pipe(fds) != 0) {
228         D(("could not make pipe"));
229         return PAM_AUTH_ERR;
230     }
231
232     if (off(UNIX_NOREAP, ctrl)) {
233         /*
234          * This code arranges that the demise of the child does not cause
235          * the application to receive a signal it is not expecting - which
236          * may kill the application or worse.
237          *
238          * The "noreap" module argument is provided so that the admin can
239          * override this behavior.
240          */
241         sighandler = signal(SIGCHLD, SIG_DFL);
242     }
243
244     /* fork */
245     child = fork();
246     if (child == 0) {
247         size_t i=0;
248         struct rlimit rlim;
249         static char *envp[] = { NULL };
250         char *args[] = { NULL, NULL, NULL, NULL };
251
252         /* XXX - should really tidy up PAM here too */
253
254         close(0); close(1);
255         /* reopen stdin as pipe */
256         close(fds[1]);
257         dup2(fds[0], STDIN_FILENO);
258
259         if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
260           for (i=2; i < rlim.rlim_max; i++) {
261             if ((unsigned int)fds[0] != i)
262                    close(i);
263           }
264         }
265
266         if (SELINUX_ENABLED && geteuid() == 0) {
267           /* must set the real uid to 0 so the helper will not error
268              out if pam is called from setuid binary (su, sudo...) */
269           setuid(0);
270         }
271
272         /* exec binary helper */
273         args[0] = x_strdup(CHKPWD_HELPER);
274         args[1] = x_strdup(user);
275         args[2] = x_strdup("shadow");
276
277         execve(CHKPWD_HELPER, args, envp);
278
279         /* should not get here: exit with error */
280         D(("helper binary is not available"));
281         exit(PAM_AUTHINFO_UNAVAIL);
282     } else if (child > 0) {
283         /* wait for child */
284         /* if the stored password is NULL */
285         int rc=0;
286         if (fromwhat)
287           pam_modutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
288         else
289           pam_modutil_write(fds[1], "", 1);
290         if (towhat) {
291           pam_modutil_write(fds[1], towhat, strlen(towhat)+1);
292         }
293         else
294           pam_modutil_write(fds[1], "", 1);
295
296         close(fds[0]);       /* close here to avoid possible SIGPIPE above */
297         close(fds[1]);
298         rc=waitpid(child, &retval, 0);  /* wait for helper to complete */
299         if (rc<0) {
300           pam_syslog(pamh, LOG_ERR, "unix_chkpwd waitpid returned %d: %m", rc);
301           retval = PAM_AUTH_ERR;
302         } else {
303           retval = WEXITSTATUS(retval);
304         }
305     } else {
306         D(("fork failed"));
307         close(fds[0]);
308         close(fds[1]);
309         retval = PAM_AUTH_ERR;
310     }
311
312     if (sighandler != SIG_ERR) {
313         (void) signal(SIGCHLD, sighandler);   /* restore old signal handler */
314     }
315
316     return retval;
317 }
318 #endif
319
320 static int check_old_password(const char *forwho, const char *newpass)
321 {
322         static char buf[16384];
323         char *s_luser, *s_uid, *s_npas, *s_pas;
324         int retval = PAM_SUCCESS;
325         FILE *opwfile;
326
327         opwfile = fopen(OLD_PASSWORDS_FILE, "r");
328         if (opwfile == NULL)
329                 return PAM_ABORT;
330
331         while (fgets(buf, 16380, opwfile)) {
332                 if (!strncmp(buf, forwho, strlen(forwho))) {
333                         buf[strlen(buf) - 1] = '\0';
334                         s_luser = strtok(buf, ":,");
335                         s_uid = strtok(NULL, ":,");
336                         s_npas = strtok(NULL, ":,");
337                         s_pas = strtok(NULL, ":,");
338                         while (s_pas != NULL) {
339                                 char *md5pass = Goodcrypt_md5(newpass, s_pas);
340                                 if (!strcmp(md5pass, s_pas)) {
341                                         _pam_delete(md5pass);
342                                         retval = PAM_AUTHTOK_ERR;
343                                         break;
344                                 }
345                                 s_pas = strtok(NULL, ":,");
346                                 _pam_delete(md5pass);
347                         }
348                         break;
349                 }
350         }
351         fclose(opwfile);
352
353         return retval;
354 }
355
356 static int save_old_password(pam_handle_t *pamh,
357                              const char *forwho, const char *oldpass,
358                              int howmany)
359 {
360     static char buf[16384];
361     static char nbuf[16384];
362     char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
363     int npas;
364     FILE *pwfile, *opwfile;
365     int err = 0;
366     int oldmask;
367     int found = 0;
368     struct passwd *pwd = NULL;
369     struct stat st;
370
371     if (howmany < 0) {
372         return PAM_SUCCESS;
373     }
374
375     if (oldpass == NULL) {
376         return PAM_SUCCESS;
377     }
378
379     oldmask = umask(077);
380
381 #ifdef WITH_SELINUX
382     if (SELINUX_ENABLED) {
383       security_context_t passwd_context=NULL;
384       if (getfilecon("/etc/passwd",&passwd_context)<0) {
385         return PAM_AUTHTOK_ERR;
386       };
387       if (getfscreatecon(&prev_context)<0) {
388         freecon(passwd_context);
389         return PAM_AUTHTOK_ERR;
390       }
391       if (setfscreatecon(passwd_context)) {
392         freecon(passwd_context);
393         freecon(prev_context);
394         return PAM_AUTHTOK_ERR;
395       }
396       freecon(passwd_context);
397     }
398 #endif
399     pwfile = fopen(OPW_TMPFILE, "w");
400     umask(oldmask);
401     if (pwfile == NULL) {
402       err = 1;
403       goto done;
404     }
405
406     opwfile = fopen(OLD_PASSWORDS_FILE, "r");
407     if (opwfile == NULL) {
408         fclose(pwfile);
409       err = 1;
410       goto done;
411     }
412
413     if (fstat(fileno(opwfile), &st) == -1) {
414         fclose(opwfile);
415         fclose(pwfile);
416         err = 1;
417         goto done;
418     }
419
420     if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
421         fclose(opwfile);
422         fclose(pwfile);
423         err = 1;
424         goto done;
425     }
426     if (fchmod(fileno(pwfile), st.st_mode) == -1) {
427         fclose(opwfile);
428         fclose(pwfile);
429         err = 1;
430         goto done;
431     }
432
433     while (fgets(buf, 16380, opwfile)) {
434         if (!strncmp(buf, forwho, strlen(forwho))) {
435             buf[strlen(buf) - 1] = '\0';
436             s_luser = strtok(buf, ":");
437             s_uid = strtok(NULL, ":");
438             s_npas = strtok(NULL, ":");
439             s_pas = strtok(NULL, ":");
440             npas = strtol(s_npas, NULL, 10) + 1;
441             while (npas > howmany) {
442                 s_pas = strpbrk(s_pas, ",");
443                 if (s_pas != NULL)
444                     s_pas++;
445                 npas--;
446             }
447             pass = crypt_md5_wrapper(oldpass);
448             if (s_pas == NULL)
449                 snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
450                          s_luser, s_uid, npas, pass);
451             else
452                 snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
453                          s_luser, s_uid, npas, s_pas, pass);
454             _pam_delete(pass);
455             if (fputs(nbuf, pwfile) < 0) {
456                 err = 1;
457                 break;
458             }
459             found = 1;
460         } else if (fputs(buf, pwfile) < 0) {
461             err = 1;
462             break;
463         }
464     }
465     fclose(opwfile);
466
467     if (!found) {
468         pwd = pam_modutil_getpwnam(pamh, forwho);
469         if (pwd == NULL) {
470             err = 1;
471         } else {
472             pass = crypt_md5_wrapper(oldpass);
473             snprintf(nbuf, sizeof(nbuf), "%s:%lu:1:%s\n",
474                      forwho, (unsigned long)pwd->pw_uid, pass);
475             _pam_delete(pass);
476             if (fputs(nbuf, pwfile) < 0) {
477                 err = 1;
478             }
479         }
480     }
481
482     if (fclose(pwfile)) {
483         D(("error writing entries to old passwords file: %m"));
484         err = 1;
485     }
486
487 done:
488     if (!err) {
489         if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
490             err = 1;
491     }
492 #ifdef WITH_SELINUX
493     if (SELINUX_ENABLED) {
494       if (setfscreatecon(prev_context)) {
495         err = 1;
496       }
497       if (prev_context)
498         freecon(prev_context);
499       prev_context=NULL;
500     }
501 #endif
502     if (!err) {
503         return PAM_SUCCESS;
504     } else {
505         unlink(OPW_TMPFILE);
506         return PAM_AUTHTOK_ERR;
507     }
508 }
509
510 static int _update_passwd(pam_handle_t *pamh,
511                           const char *forwho, const char *towhat)
512 {
513     struct passwd *tmpent = NULL;
514     struct stat st;
515     FILE *pwfile, *opwfile;
516     int err = 1;
517     int oldmask;
518
519     oldmask = umask(077);
520 #ifdef WITH_SELINUX
521     if (SELINUX_ENABLED) {
522       security_context_t passwd_context=NULL;
523       if (getfilecon("/etc/passwd",&passwd_context)<0) {
524         return PAM_AUTHTOK_ERR;
525       };
526       if (getfscreatecon(&prev_context)<0) {
527         freecon(passwd_context);
528         return PAM_AUTHTOK_ERR;
529       }
530       if (setfscreatecon(passwd_context)) {
531         freecon(passwd_context);
532         freecon(prev_context);
533         return PAM_AUTHTOK_ERR;
534       }
535       freecon(passwd_context);
536     }
537 #endif
538     pwfile = fopen(PW_TMPFILE, "w");
539     umask(oldmask);
540     if (pwfile == NULL) {
541       err = 1;
542       goto done;
543     }
544
545     opwfile = fopen("/etc/passwd", "r");
546     if (opwfile == NULL) {
547         fclose(pwfile);
548         err = 1;
549         goto done;
550     }
551
552     if (fstat(fileno(opwfile), &st) == -1) {
553         fclose(opwfile);
554         fclose(pwfile);
555         err = 1;
556         goto done;
557     }
558
559     if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
560         fclose(opwfile);
561         fclose(pwfile);
562         err = 1;
563         goto done;
564     }
565     if (fchmod(fileno(pwfile), st.st_mode) == -1) {
566         fclose(opwfile);
567         fclose(pwfile);
568         err = 1;
569         goto done;
570     }
571
572     tmpent = fgetpwent(opwfile);
573     while (tmpent) {
574         if (!strcmp(tmpent->pw_name, forwho)) {
575             /* To shut gcc up */
576             union {
577                 const char *const_charp;
578                 char *charp;
579             } assigned_passwd;
580             assigned_passwd.const_charp = towhat;
581
582             tmpent->pw_passwd = assigned_passwd.charp;
583             err = 0;
584         }
585         if (putpwent(tmpent, pwfile)) {
586             D(("error writing entry to password file: %m"));
587             err = 1;
588             break;
589         }
590         tmpent = fgetpwent(opwfile);
591     }
592     fclose(opwfile);
593
594     if (fclose(pwfile)) {
595         D(("error writing entries to password file: %m"));
596         err = 1;
597     }
598
599 done:
600     if (!err) {
601         if (!rename(PW_TMPFILE, "/etc/passwd"))
602             pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
603         else
604             err = 1;
605     }
606 #ifdef WITH_SELINUX
607     if (SELINUX_ENABLED) {
608       if (setfscreatecon(prev_context)) {
609         err = 1;
610       }
611       if (prev_context)
612         freecon(prev_context);
613       prev_context=NULL;
614     }
615 #endif
616     if (!err) {
617         return PAM_SUCCESS;
618     } else {
619         unlink(PW_TMPFILE);
620         return PAM_AUTHTOK_ERR;
621     }
622 }
623
624 static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
625 {
626     struct spwd *spwdent = NULL, *stmpent = NULL;
627     struct stat st;
628     FILE *pwfile, *opwfile;
629     int err = 1;
630     int oldmask;
631
632     spwdent = getspnam(forwho);
633     if (spwdent == NULL) {
634         return PAM_USER_UNKNOWN;
635     }
636     oldmask = umask(077);
637
638 #ifdef WITH_SELINUX
639     if (SELINUX_ENABLED) {
640       security_context_t shadow_context=NULL;
641       if (getfilecon("/etc/shadow",&shadow_context)<0) {
642         return PAM_AUTHTOK_ERR;
643       };
644       if (getfscreatecon(&prev_context)<0) {
645         freecon(shadow_context);
646         return PAM_AUTHTOK_ERR;
647       }
648       if (setfscreatecon(shadow_context)) {
649         freecon(shadow_context);
650         freecon(prev_context);
651         return PAM_AUTHTOK_ERR;
652       }
653       freecon(shadow_context);
654     }
655 #endif
656     pwfile = fopen(SH_TMPFILE, "w");
657     umask(oldmask);
658     if (pwfile == NULL) {
659         err = 1;
660         goto done;
661     }
662
663     opwfile = fopen("/etc/shadow", "r");
664     if (opwfile == NULL) {
665         fclose(pwfile);
666         err = 1;
667         goto done;
668     }
669
670     if (fstat(fileno(opwfile), &st) == -1) {
671         fclose(opwfile);
672         fclose(pwfile);
673         err = 1;
674         goto done;
675     }
676
677     if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
678         fclose(opwfile);
679         fclose(pwfile);
680         err = 1;
681         goto done;
682     }
683     if (fchmod(fileno(pwfile), st.st_mode) == -1) {
684         fclose(opwfile);
685         fclose(pwfile);
686         err = 1;
687         goto done;
688     }
689
690     stmpent = fgetspent(opwfile);
691     while (stmpent) {
692
693         if (!strcmp(stmpent->sp_namp, forwho)) {
694             stmpent->sp_pwdp = towhat;
695             stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
696             err = 0;
697             D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
698         }
699
700         if (putspent(stmpent, pwfile)) {
701             D(("error writing entry to shadow file: %m"));
702             err = 1;
703             break;
704         }
705
706         stmpent = fgetspent(opwfile);
707     }
708     fclose(opwfile);
709
710     if (fclose(pwfile)) {
711         D(("error writing entries to shadow file: %m"));
712         err = 1;
713     }
714
715  done:
716     if (!err) {
717         if (!rename(SH_TMPFILE, "/etc/shadow"))
718             pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
719         else
720             err = 1;
721     }
722
723 #ifdef WITH_SELINUX
724     if (SELINUX_ENABLED) {
725       if (setfscreatecon(prev_context)) {
726         err = 1;
727       }
728       if (prev_context)
729         freecon(prev_context);
730       prev_context=NULL;
731     }
732 #endif
733
734     if (!err) {
735         return PAM_SUCCESS;
736     } else {
737         unlink(SH_TMPFILE);
738         return PAM_AUTHTOK_ERR;
739     }
740 }
741
742 static int _do_setpass(pam_handle_t* pamh, const char *forwho,
743                        const char *fromwhat,
744                        char *towhat, unsigned int ctrl, int remember)
745 {
746         struct passwd *pwd = NULL;
747         int retval = 0;
748         int unlocked = 0;
749         char *master = NULL;
750
751         D(("called"));
752
753         pwd = getpwnam(forwho);
754
755         if (pwd == NULL) {
756                 retval = PAM_AUTHTOK_ERR;
757                 goto done;
758         }
759
760         if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
761             if ((master=getNISserver(pamh)) != NULL) {
762                 struct timeval timeout;
763                 struct yppasswd yppwd;
764                 CLIENT *clnt;
765                 int status;
766                 enum clnt_stat err;
767
768                 /* Unlock passwd file to avoid deadlock */
769 #ifdef USE_LCKPWDF
770                 ulckpwdf();
771 #endif
772                 unlocked = 1;
773
774                 /* Initialize password information */
775                 yppwd.newpw.pw_passwd = pwd->pw_passwd;
776                 yppwd.newpw.pw_name = pwd->pw_name;
777                 yppwd.newpw.pw_uid = pwd->pw_uid;
778                 yppwd.newpw.pw_gid = pwd->pw_gid;
779                 yppwd.newpw.pw_gecos = pwd->pw_gecos;
780                 yppwd.newpw.pw_dir = pwd->pw_dir;
781                 yppwd.newpw.pw_shell = pwd->pw_shell;
782                 yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
783                 yppwd.newpw.pw_passwd = towhat;
784
785                 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
786
787                 /* The yppasswd.x file said `unix authentication required',
788                  * so I added it. This is the only reason it is in here.
789                  * My yppasswdd doesn't use it, but maybe some others out there
790                  * do.                                        --okir
791                  */
792                 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
793                 clnt->cl_auth = authunix_create_default();
794                 memset((char *) &status, '\0', sizeof(status));
795                 timeout.tv_sec = 25;
796                 timeout.tv_usec = 0;
797                 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
798                                 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
799                                 (xdrproc_t) xdr_int, (char *) &status,
800                                 timeout);
801
802                 free (yppwd.oldpass);
803
804                 if (err) {
805                         _make_remark(pamh, ctrl, PAM_TEXT_INFO,
806                                 clnt_sperrno(err));
807                 } else if (status) {
808                         D(("Error while changing NIS password.\n"));
809                 }
810                 D(("The password has%s been changed on %s.",
811                    (err || status) ? " not" : "", master));
812                 pam_syslog(pamh, LOG_NOTICE, "password%s changed for %s on %s",
813                          (err || status) ? " not" : "", pwd->pw_name, master);
814
815                 auth_destroy(clnt->cl_auth);
816                 clnt_destroy(clnt);
817                 if (err || status) {
818                         _make_remark(pamh, ctrl, PAM_TEXT_INFO,
819                                 _("NIS password could not be changed."));
820                         retval = PAM_TRY_AGAIN;
821                 }
822 #ifdef DEBUG
823                 sleep(5);
824 #endif
825             } else {
826                     retval = PAM_TRY_AGAIN;
827             }
828         }
829
830         if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
831 #ifdef USE_LCKPWDF
832                 if(unlocked) {
833                         int i = 0;
834                         /* These values for the number of attempts and the sleep time
835                            are, of course, completely arbitrary.
836                            My reading of the PAM docs is that, once pam_chauthtok() has been
837                            called with PAM_UPDATE_AUTHTOK, we are obliged to take any
838                            reasonable steps to make sure the token is updated; so retrying
839                            for 1/10 sec. isn't overdoing it. */
840                         while((retval = lckpwdf()) != 0 && i < 100) {
841                                 usleep(1000);
842                                 i++;
843                         }
844                         if(retval != 0) {
845                                 return PAM_AUTHTOK_LOCK_BUSY;
846                         }
847                 }
848 #endif
849                 /* first, save old password */
850                 if (save_old_password(pamh, forwho, fromwhat, remember)) {
851                         retval = PAM_AUTHTOK_ERR;
852                         goto done;
853                 }
854                 if (on(UNIX_SHADOW, ctrl) || _unix_shadowed(pwd)) {
855                         retval = _update_shadow(pamh, forwho, towhat);
856 #ifdef WITH_SELINUX
857                         if (retval != PAM_SUCCESS && SELINUX_ENABLED)
858                           retval = _unix_run_shadow_binary(pamh, ctrl, forwho, fromwhat, towhat);
859 #endif
860                         if (retval == PAM_SUCCESS)
861                                 if (!_unix_shadowed(pwd))
862                                         retval = _update_passwd(pamh, forwho, "x");
863                 } else {
864                         retval = _update_passwd(pamh, forwho, towhat);
865                 }
866         }
867
868
869 done:
870 #ifdef USE_LCKPWDF
871         ulckpwdf();
872 #endif
873
874         return retval;
875 }
876
877 static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
878 {
879         struct passwd *pwd = NULL;      /* Password and shadow password */
880         struct spwd *spwdent = NULL;    /* file entries for the user */
881         time_t curdays;
882         int retval = PAM_SUCCESS;
883
884         /* UNIX passwords area */
885         pwd = getpwnam(user);   /* Get password file entry... */
886         if (pwd == NULL)
887                 return PAM_AUTHINFO_UNAVAIL;    /* We don't need to do the rest... */
888
889         if (_unix_shadowed(pwd)) {
890                 /* ...and shadow password file entry for this user, if shadowing
891                    is enabled */
892                 setspent();
893                 spwdent = getspnam(user);
894                 endspent();
895
896 #ifdef WITH_SELINUX
897                 if (spwdent == NULL && SELINUX_ENABLED )
898                     spwdent = _unix_run_verify_binary(pamh, ctrl, user);
899 #endif
900                 if (spwdent == NULL)
901                         return PAM_AUTHINFO_UNAVAIL;
902         } else {
903                 if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */
904                         uid_t save_uid;
905
906                         save_uid = geteuid();
907                         seteuid (pwd->pw_uid);
908                         spwdent = getspnam( user );
909                         seteuid (save_uid);
910
911                         if (spwdent == NULL)
912                                 return PAM_AUTHINFO_UNAVAIL;
913                 } else
914                         spwdent = NULL;
915         }
916
917         if (spwdent != NULL) {
918                 /* We have the user's information, now let's check if their account
919                    has expired (60 * 60 * 24 = number of seconds in a day) */
920
921                 if (off(UNIX__IAMROOT, ctrl)) {
922                         /* Get the current number of days since 1970 */
923                         curdays = time(NULL) / (60 * 60 * 24);
924                         if (curdays < spwdent->sp_lstchg) {
925                                 pam_syslog(pamh, LOG_DEBUG,
926                                         "account %s has password changed in future",
927                                         user);
928                                 curdays = spwdent->sp_lstchg;
929                         }
930                         if ((curdays - spwdent->sp_lstchg < spwdent->sp_min)
931                                  && (spwdent->sp_min != -1))
932                                 /*
933                                  * The last password change was too recent.
934                                  */
935                                 retval = PAM_AUTHTOK_ERR;
936                         else if ((curdays - spwdent->sp_lstchg > spwdent->sp_max)
937                                  && (curdays - spwdent->sp_lstchg > spwdent->sp_inact)
938                                  && (curdays - spwdent->sp_lstchg >
939                                      spwdent->sp_max + spwdent->sp_inact)
940                                  && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1)
941                                  && (spwdent->sp_lstchg != 0))
942                                 /*
943                                  * Their password change has been put off too long,
944                                  */
945                                 retval = PAM_ACCT_EXPIRED;
946                         else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1)
947                                  && (spwdent->sp_lstchg != 0))
948                                 /*
949                                  * OR their account has just plain expired
950                                  */
951                                 retval = PAM_ACCT_EXPIRED;
952                 }
953         }
954         return retval;
955 }
956
957 static int _pam_unix_approve_pass(pam_handle_t * pamh
958                                   ,unsigned int ctrl
959                                   ,const char *pass_old
960                                   ,const char *pass_new)
961 {
962         const void *user;
963         const char *remark = NULL;
964         int retval = PAM_SUCCESS;
965
966         D(("&new=%p, &old=%p", pass_old, pass_new));
967         D(("new=[%s]", pass_new));
968         D(("old=[%s]", pass_old));
969
970         if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
971                 if (on(UNIX_DEBUG, ctrl)) {
972                         pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
973                 }
974                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
975                         _("No password supplied") : _("Password unchanged"));
976                 return PAM_AUTHTOK_ERR;
977         }
978         /*
979          * if one wanted to hardwire authentication token strength
980          * checking this would be the place - AGM
981          */
982
983         retval = pam_get_item(pamh, PAM_USER, &user);
984         if (retval != PAM_SUCCESS) {
985                 if (on(UNIX_DEBUG, ctrl)) {
986                         pam_syslog(pamh, LOG_ERR, "Can not get username");
987                         return PAM_AUTHTOK_ERR;
988                 }
989         }
990         if (off(UNIX__IAMROOT, ctrl)) {
991 #ifdef USE_CRACKLIB
992                 remark = FascistCheck (pass_new, CRACKLIB_DICTS);
993                 D(("called cracklib [%s]", remark));
994 #else
995                 if (strlen(pass_new) < 6)
996                   remark = _("You must choose a longer password");
997                 D(("length check [%s]", remark));
998 #endif
999                 if (on(UNIX_REMEMBER_PASSWD, ctrl)) {
1000                         if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR)
1001                           remark = _("Password has been already used. Choose another.");
1002                         if (retval == PAM_ABORT) {
1003                                 pam_syslog(pamh, LOG_ERR, "can't open %s file to check old passwords",
1004                                         OLD_PASSWORDS_FILE);
1005                                 return retval;
1006                         }
1007                 }
1008         }
1009         if (remark) {
1010                 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
1011                 retval = PAM_AUTHTOK_ERR;
1012         }
1013         return retval;
1014 }
1015
1016
1017 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
1018                                 int argc, const char **argv)
1019 {
1020         unsigned int ctrl, lctrl;
1021         int retval, i;
1022         int remember = -1;
1023
1024         /* <DO NOT free() THESE> */
1025         const char *user;
1026         const void *pass_old, *pass_new;
1027         /* </DO NOT free() THESE> */
1028
1029         D(("called."));
1030
1031         ctrl = _set_ctrl(pamh, flags, &remember, argc, argv);
1032
1033         /*
1034          * First get the name of a user
1035          */
1036         retval = pam_get_user(pamh, &user, NULL);
1037         if (retval == PAM_SUCCESS) {
1038                 /*
1039                  * Various libraries at various times have had bugs related to
1040                  * '+' or '-' as the first character of a user name. Don't take
1041                  * any chances here. Require that the username starts with an
1042                  * alphanumeric character.
1043                  */
1044                 if (user == NULL || !isalnum(*user)) {
1045                         pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
1046                         return PAM_USER_UNKNOWN;
1047                 }
1048                 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
1049                         pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
1050                                  user);
1051         } else {
1052                 if (on(UNIX_DEBUG, ctrl))
1053                         pam_syslog(pamh, LOG_DEBUG,
1054                                  "password - could not identify user");
1055                 return retval;
1056         }
1057
1058         D(("Got username of %s", user));
1059
1060         /*
1061          * Before we do anything else, check to make sure that the user's
1062          * info is in one of the databases we can modify from this module,
1063          * which currently is 'files' and 'nis'.  We have to do this because
1064          * getpwnam() doesn't tell you *where* the information it gives you
1065          * came from, nor should it.  That's our job.
1066          */
1067         if (_unix_comesfromsource(pamh, user, 1, on(UNIX_NIS, ctrl)) == 0) {
1068                 pam_syslog(pamh, LOG_DEBUG,
1069                          "user \"%s\" does not exist in /etc/passwd%s",
1070                          user, on(UNIX_NIS, ctrl) ? " or NIS" : "");
1071                 return PAM_USER_UNKNOWN;
1072         } else {
1073                 struct passwd *pwd;
1074                 _unix_getpwnam(pamh, user, 1, 1, &pwd);
1075                 if (pwd == NULL) {
1076                         pam_syslog(pamh, LOG_DEBUG,
1077                                 "user \"%s\" has corrupted passwd entry",
1078                                 user);
1079                         return PAM_USER_UNKNOWN;
1080                 }
1081                 if (!_unix_shadowed(pwd) &&
1082                     (strchr(pwd->pw_passwd, '*') != NULL)) {
1083                         pam_syslog(pamh, LOG_DEBUG,
1084                                 "user \"%s\" does not have modifiable password",
1085                                 user);
1086                         return PAM_USER_UNKNOWN;
1087                 }
1088         }
1089
1090         /*
1091          * This is not an AUTH module!
1092          */
1093         if (on(UNIX__NONULL, ctrl))
1094                 set(UNIX__NULLOK, ctrl);
1095
1096         if (on(UNIX__PRELIM, ctrl)) {
1097                 /*
1098                  * obtain and verify the current password (OLDAUTHTOK) for
1099                  * the user.
1100                  */
1101                 char *Announce;
1102
1103                 D(("prelim check"));
1104
1105                 if (_unix_blankpasswd(pamh, ctrl, user)) {
1106                         return PAM_SUCCESS;
1107                 } else if (off(UNIX__IAMROOT, ctrl)) {
1108                         /* instruct user what is happening */
1109                         if (asprintf(&Announce, _("Changing password for %s."),
1110                                 user) < 0) {
1111                                 pam_syslog(pamh, LOG_CRIT,
1112                                          "password - out of memory");
1113                                 return PAM_BUF_ERR;
1114                         }
1115
1116                         lctrl = ctrl;
1117                         set(UNIX__OLD_PASSWD, lctrl);
1118                         retval = _unix_read_password(pamh, lctrl
1119                                                      ,Announce
1120                                              ,_("(current) UNIX password: ")
1121                                                      ,NULL
1122                                                      ,_UNIX_OLD_AUTHTOK
1123                                              ,&pass_old);
1124                         free(Announce);
1125
1126                         if (retval != PAM_SUCCESS) {
1127                                 pam_syslog(pamh, LOG_NOTICE,
1128                                     "password - (old) token not obtained");
1129                                 return retval;
1130                         }
1131                         /* verify that this is the password for this user */
1132
1133                         retval = _unix_verify_password(pamh, user, pass_old, ctrl);
1134                 } else {
1135                         D(("process run by root so do nothing this time around"));
1136                         pass_old = NULL;
1137                         retval = PAM_SUCCESS;   /* root doesn't have too */
1138                 }
1139
1140                 if (retval != PAM_SUCCESS) {
1141                         D(("Authentication failed"));
1142                         pass_old = NULL;
1143                         return retval;
1144                 }
1145                 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
1146                 pass_old = NULL;
1147                 if (retval != PAM_SUCCESS) {
1148                         pam_syslog(pamh, LOG_CRIT,
1149                                  "failed to set PAM_OLDAUTHTOK");
1150                 }
1151                 retval = _unix_verify_shadow(pamh,user, ctrl);
1152                 if (retval == PAM_AUTHTOK_ERR) {
1153                         if (off(UNIX__IAMROOT, ctrl))
1154                                 _make_remark(pamh, ctrl, PAM_ERROR_MSG,
1155                                              _("You must wait longer to change your password"));
1156                         else
1157                                 retval = PAM_SUCCESS;
1158                 }
1159         } else if (on(UNIX__UPDATE, ctrl)) {
1160                 /*
1161                  * tpass is used below to store the _pam_md() return; it
1162                  * should be _pam_delete()'d.
1163                  */
1164
1165                 char *tpass = NULL;
1166                 int retry = 0;
1167
1168                 /*
1169                  * obtain the proposed password
1170                  */
1171
1172                 D(("do update"));
1173
1174                 /*
1175                  * get the old token back. NULL was ok only if root [at this
1176                  * point we assume that this has already been enforced on a
1177                  * previous call to this function].
1178                  */
1179
1180                 if (off(UNIX_NOT_SET_PASS, ctrl)) {
1181                         retval = pam_get_item(pamh, PAM_OLDAUTHTOK
1182                                               ,&pass_old);
1183                 } else {
1184                         retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
1185                                               ,&pass_old);
1186                         if (retval == PAM_NO_MODULE_DATA) {
1187                                 retval = PAM_SUCCESS;
1188                                 pass_old = NULL;
1189                         }
1190                 }
1191                 D(("pass_old [%s]", pass_old));
1192
1193                 if (retval != PAM_SUCCESS) {
1194                         pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
1195                         return retval;
1196                 }
1197
1198                 D(("get new password now"));
1199
1200                 lctrl = ctrl;
1201
1202                 if (on(UNIX_USE_AUTHTOK, lctrl)) {
1203                         set(UNIX_USE_FIRST_PASS, lctrl);
1204                 }
1205                 retry = 0;
1206                 retval = PAM_AUTHTOK_ERR;
1207                 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
1208                         /*
1209                          * use_authtok is to force the use of a previously entered
1210                          * password -- needed for pluggable password strength checking
1211                          */
1212
1213                         retval = _unix_read_password(pamh, lctrl
1214                                                      ,NULL
1215                                              ,_("Enter new UNIX password: ")
1216                                             ,_("Retype new UNIX password: ")
1217                                                      ,_UNIX_NEW_AUTHTOK
1218                                              ,&pass_new);
1219
1220                         if (retval != PAM_SUCCESS) {
1221                                 if (on(UNIX_DEBUG, ctrl)) {
1222                                         pam_syslog(pamh, LOG_ALERT,
1223                                                  "password - new password not obtained");
1224                                 }
1225                                 pass_old = NULL;        /* tidy up */
1226                                 return retval;
1227                         }
1228                         D(("returned to _unix_chauthtok"));
1229
1230                         /*
1231                          * At this point we know who the user is and what they
1232                          * propose as their new password. Verify that the new
1233                          * password is acceptable.
1234                          */
1235
1236                         if (*(const char *)pass_new == '\0') {  /* "\0" password = NULL */
1237                                 pass_new = NULL;
1238                         }
1239                         retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
1240                 }
1241
1242                 if (retval != PAM_SUCCESS) {
1243                         pam_syslog(pamh, LOG_NOTICE,
1244                                  "new password not acceptable");
1245                         pass_new = pass_old = NULL;     /* tidy up */
1246                         return retval;
1247                 }
1248 #ifdef USE_LCKPWDF
1249                 /* These values for the number of attempts and the sleep time
1250                    are, of course, completely arbitrary.
1251                    My reading of the PAM docs is that, once pam_chauthtok() has been
1252                    called with PAM_UPDATE_AUTHTOK, we are obliged to take any
1253                    reasonable steps to make sure the token is updated; so retrying
1254                    for 1/10 sec. isn't overdoing it. */
1255                 i=0;
1256                 while((retval = lckpwdf()) != 0 && i < 100) {
1257                         usleep(1000);
1258                         i++;
1259                 }
1260                 if(retval != 0) {
1261                         return PAM_AUTHTOK_LOCK_BUSY;
1262                 }
1263 #endif
1264
1265                 if (pass_old) {
1266                         retval = _unix_verify_password(pamh, user, pass_old, ctrl);
1267                         if (retval != PAM_SUCCESS) {
1268                                 pam_syslog(pamh, LOG_NOTICE, "user password changed by another process");
1269 #ifdef USE_LCKPWDF
1270                                 ulckpwdf();
1271 #endif
1272                                 return retval;
1273                         }
1274                 }
1275
1276                 retval = _unix_verify_shadow(pamh, user, ctrl);
1277                 if (retval != PAM_SUCCESS) {
1278                         pam_syslog(pamh, LOG_NOTICE, "user not authenticated 2");
1279 #ifdef USE_LCKPWDF
1280                         ulckpwdf();
1281 #endif
1282                         return retval;
1283                 }
1284
1285                 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
1286                 if (retval != PAM_SUCCESS) {
1287                         pam_syslog(pamh, LOG_NOTICE,
1288                                  "new password not acceptable 2");
1289                         pass_new = pass_old = NULL;     /* tidy up */
1290 #ifdef USE_LCKPWDF
1291                         ulckpwdf();
1292 #endif
1293                         return retval;
1294                 }
1295
1296                 /*
1297                  * By reaching here we have approved the passwords and must now
1298                  * rebuild the password database file.
1299                  */
1300
1301                 /*
1302                  * First we encrypt the new password.
1303                  */
1304
1305                 if (on(UNIX_MD5_PASS, ctrl)) {
1306                         tpass = crypt_md5_wrapper(pass_new);
1307                 } else {
1308                         /*
1309                          * Salt manipulation is stolen from Rick Faith's passwd
1310                          * program.  Sorry Rick :) -- alex
1311                          */
1312
1313                         time_t tm;
1314                         char salt[3];
1315
1316                         time(&tm);
1317                         salt[0] = bin_to_ascii(tm & 0x3f);
1318                         salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
1319                         salt[2] = '\0';
1320
1321                         if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) {
1322                                 /*
1323                                  * to avoid using the _extensions_ of the bigcrypt()
1324                                  * function we truncate the newly entered password
1325                                  * [Problems that followed from this are fixed as per
1326                                  *  Bug 521314.]
1327                                  */
1328                                 char *temp = malloc(9);
1329
1330                                 if (temp == NULL) {
1331                                         pam_syslog(pamh, LOG_CRIT,
1332                                                  "out of memory for password");
1333                                         pass_new = pass_old = NULL;     /* tidy up */
1334 #ifdef USE_LCKPWDF
1335                                         ulckpwdf();
1336 #endif
1337                                         return PAM_BUF_ERR;
1338                                 }
1339                                 /* copy first 8 bytes of password */
1340                                 strncpy(temp, pass_new, 8);
1341                                 temp[8] = '\0';
1342
1343                                 /* no longer need cleartext */
1344                                 tpass = bigcrypt(temp, salt);
1345
1346                                 _pam_delete(temp);      /* tidy up */
1347                         } else {
1348                                 tpass = bigcrypt(pass_new, salt);
1349                         }
1350                 }
1351
1352                 D(("password processed"));
1353
1354                 /* update the password database(s) -- race conditions..? */
1355
1356                 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
1357                                      remember);
1358                 /* _do_setpass has called ulckpwdf for us */
1359
1360                 _pam_delete(tpass);
1361                 pass_old = pass_new = NULL;
1362         } else {                /* something has broken with the module */
1363                 pam_syslog(pamh, LOG_ALERT,
1364                          "password received unknown request");
1365                 retval = PAM_ABORT;
1366         }
1367
1368         D(("retval was %d", retval));
1369
1370         return retval;
1371 }
1372
1373
1374 /* static module data */
1375 #ifdef PAM_STATIC
1376 struct pam_module _pam_unix_passwd_modstruct = {
1377     "pam_unix_passwd",
1378     NULL,
1379     NULL,
1380     NULL,
1381     NULL,
1382     NULL,
1383     pam_sm_chauthtok,
1384 };
1385 #endif