2 * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
4 * Copyright (c) Jan Rêkorajski, 1999.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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
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.)
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.
47 #include <sys/types.h>
51 #include <time.h> /* for time() */
57 #include <rpcsvc/yp_prot.h>
58 #include <rpcsvc/ypclnt.h>
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))
74 #include <security/_pam_macros.h>
76 /* indicate the following groups are defined */
78 #define PAM_SM_PASSWORD
80 #include <security/pam_modules.h>
81 #include <security/pam_ext.h>
82 #include <security/pam_modutil.h>
88 #if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
89 extern int getrpcport(const char *host, unsigned long prognum,
90 unsigned long versnum, unsigned int proto);
91 #endif /* GNU libc 2.1 */
94 * PAM framework looks for these entry-points to pass control to the
95 * password changing module.
98 #if defined(USE_LCKPWDF) && !defined(HAVE_LCKPWDF)
99 # include "./lckpwdf.-c"
102 extern char *bigcrypt(const char *key, const char *salt);
106 Gets in username (has to be done) from the calling program
107 Does authentication of user (only if we are not running as root)
108 Gets new password/checks for sanity
112 /* passwd/salt conversion macros */
114 #define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
115 #define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
119 #define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS"
120 #define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS"
122 #define MAX_PASSWD_TRIES 3
123 #define PW_TMPFILE "/etc/npasswd"
124 #define SH_TMPFILE "/etc/nshadow"
125 #ifndef CRACKLIB_DICTS
126 #define CRACKLIB_DICTS NULL
128 #define OPW_TMPFILE "/etc/security/nopasswd"
129 #define OLD_PASSWORDS_FILE "/etc/security/opasswd"
132 * i64c - convert an integer to a radix 64 character
134 static int i64c(int i)
144 if (i >= 2 && i <= 11)
145 return ('0' - 2 + i);
146 if (i >= 12 && i <= 37)
147 return ('A' - 12 + i);
148 if (i >= 38 && i <= 63)
149 return ('a' - 38 + i);
153 static char *crypt_md5_wrapper(const char *pass_new)
156 * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
157 * removed use of static variables (AGM)
162 unsigned char result[16];
163 char *cp = (char *) result;
164 unsigned char tmp[16];
169 gettimeofday(&tv, (struct timezone *) 0);
170 GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
172 GoodMD5Update(&ctx, (void *) &i, sizeof i);
174 GoodMD5Update(&ctx, (void *) &i, sizeof i);
175 GoodMD5Update(&ctx, result, sizeof result);
176 GoodMD5Final(tmp, &ctx);
177 strcpy(cp, "$1$"); /* magic for the MD5 */
179 for (i = 0; i < 8; i++)
180 *cp++ = i64c(tmp[i] & 077);
183 /* no longer need cleartext */
184 x = Goodcrypt_md5(pass_new, (const char *) result);
189 static char *getNISserver(pam_handle_t *pamh)
195 if ((err = yp_get_default_domain(&domainname)) != 0) {
196 pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
200 if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
201 pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
205 port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
207 pam_syslog(pamh, LOG_WARNING,
208 "yppasswdd not running on NIS master host");
211 if (port >= IPPORT_RESERVED) {
212 pam_syslog(pamh, LOG_WARNING,
213 "yppasswd daemon running on illegal port");
221 static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user, const char *fromwhat, const char *towhat)
223 int retval, child, fds[2];
224 void (*sighandler)(int) = NULL;
227 /* create a pipe for the password */
228 if (pipe(fds) != 0) {
229 D(("could not make pipe"));
233 if (off(UNIX_NOREAP, ctrl)) {
235 * This code arranges that the demise of the child does not cause
236 * the application to receive a signal it is not expecting - which
237 * may kill the application or worse.
239 * The "noreap" module argument is provided so that the admin can
240 * override this behavior.
242 sighandler = signal(SIGCHLD, SIG_DFL);
250 static char *envp[] = { NULL };
251 char *args[] = { NULL, NULL, NULL, NULL };
253 /* XXX - should really tidy up PAM here too */
256 /* reopen stdin as pipe */
258 dup2(fds[0], STDIN_FILENO);
260 if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
261 for (i=2; i < rlim.rlim_max; i++) {
262 if ((unsigned int)fds[0] != i)
267 if (SELINUX_ENABLED && geteuid() == 0) {
268 /* must set the real uid to 0 so the helper will not error
269 out if pam is called from setuid binary (su, sudo...) */
273 /* exec binary helper */
274 args[0] = x_strdup(CHKPWD_HELPER);
275 args[1] = x_strdup(user);
276 args[2] = x_strdup("shadow");
278 execve(CHKPWD_HELPER, args, envp);
280 /* should not get here: exit with error */
281 D(("helper binary is not available"));
282 exit(PAM_AUTHINFO_UNAVAIL);
283 } else if (child > 0) {
285 /* if the stored password is NULL */
288 pam_modutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
290 pam_modutil_write(fds[1], "", 1);
292 pam_modutil_write(fds[1], towhat, strlen(towhat)+1);
295 pam_modutil_write(fds[1], "", 1);
297 close(fds[0]); /* close here to avoid possible SIGPIPE above */
299 rc=waitpid(child, &retval, 0); /* wait for helper to complete */
301 pam_syslog(pamh, LOG_ERR, "unix_chkpwd waitpid returned %d: %m", rc);
302 retval = PAM_AUTH_ERR;
304 retval = WEXITSTATUS(retval);
310 retval = PAM_AUTH_ERR;
313 if (sighandler != NULL) {
314 (void) signal(SIGCHLD, sighandler); /* restore old signal handler */
321 static int check_old_password(const char *forwho, const char *newpass)
323 static char buf[16384];
324 char *s_luser, *s_uid, *s_npas, *s_pas;
325 int retval = PAM_SUCCESS;
328 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
332 while (fgets(buf, 16380, opwfile)) {
333 if (!strncmp(buf, forwho, strlen(forwho))) {
334 buf[strlen(buf) - 1] = '\0';
335 s_luser = strtok(buf, ":,");
336 s_uid = strtok(NULL, ":,");
337 s_npas = strtok(NULL, ":,");
338 s_pas = strtok(NULL, ":,");
339 while (s_pas != NULL) {
340 char *md5pass = Goodcrypt_md5(newpass, s_pas);
341 if (!strcmp(md5pass, s_pas)) {
342 _pam_delete(md5pass);
343 retval = PAM_AUTHTOK_ERR;
346 s_pas = strtok(NULL, ":,");
347 _pam_delete(md5pass);
357 static int save_old_password(pam_handle_t *pamh,
358 const char *forwho, const char *oldpass,
361 static char buf[16384];
362 static char nbuf[16384];
363 char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
365 FILE *pwfile, *opwfile;
369 struct passwd *pwd = NULL;
376 if (oldpass == NULL) {
380 oldmask = umask(077);
383 if (SELINUX_ENABLED) {
384 security_context_t passwd_context=NULL;
385 if (getfilecon("/etc/passwd",&passwd_context)<0) {
386 return PAM_AUTHTOK_ERR;
388 if (getfscreatecon(&prev_context)<0) {
389 freecon(passwd_context);
390 return PAM_AUTHTOK_ERR;
392 if (setfscreatecon(passwd_context)) {
393 freecon(passwd_context);
394 freecon(prev_context);
395 return PAM_AUTHTOK_ERR;
397 freecon(passwd_context);
400 pwfile = fopen(OPW_TMPFILE, "w");
402 if (pwfile == NULL) {
407 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
408 if (opwfile == NULL) {
414 if (fstat(fileno(opwfile), &st) == -1) {
421 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
427 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
434 while (fgets(buf, 16380, opwfile)) {
435 if (!strncmp(buf, forwho, strlen(forwho))) {
436 buf[strlen(buf) - 1] = '\0';
437 s_luser = strtok(buf, ":");
438 s_uid = strtok(NULL, ":");
439 s_npas = strtok(NULL, ":");
440 s_pas = strtok(NULL, ":");
441 npas = strtol(s_npas, NULL, 10) + 1;
442 while (npas > howmany) {
443 s_pas = strpbrk(s_pas, ",");
448 pass = crypt_md5_wrapper(oldpass);
450 snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
451 s_luser, s_uid, npas, pass);
453 snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
454 s_luser, s_uid, npas, s_pas, pass);
456 if (fputs(nbuf, pwfile) < 0) {
461 } else if (fputs(buf, pwfile) < 0) {
469 pwd = pam_modutil_getpwnam(pamh, forwho);
473 pass = crypt_md5_wrapper(oldpass);
474 snprintf(nbuf, sizeof(nbuf), "%s:%d:1:%s\n",
475 forwho, pwd->pw_uid, pass);
477 if (fputs(nbuf, pwfile) < 0) {
483 if (fclose(pwfile)) {
484 D(("error writing entries to old passwords file: %m"));
490 if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
494 if (SELINUX_ENABLED) {
495 if (setfscreatecon(prev_context)) {
499 freecon(prev_context);
507 return PAM_AUTHTOK_ERR;
511 static int _update_passwd(pam_handle_t *pamh,
512 const char *forwho, const char *towhat)
514 struct passwd *tmpent = NULL;
516 FILE *pwfile, *opwfile;
520 oldmask = umask(077);
522 if (SELINUX_ENABLED) {
523 security_context_t passwd_context=NULL;
524 if (getfilecon("/etc/passwd",&passwd_context)<0) {
525 return PAM_AUTHTOK_ERR;
527 if (getfscreatecon(&prev_context)<0) {
528 freecon(passwd_context);
529 return PAM_AUTHTOK_ERR;
531 if (setfscreatecon(passwd_context)) {
532 freecon(passwd_context);
533 freecon(prev_context);
534 return PAM_AUTHTOK_ERR;
536 freecon(passwd_context);
539 pwfile = fopen(PW_TMPFILE, "w");
541 if (pwfile == NULL) {
546 opwfile = fopen("/etc/passwd", "r");
547 if (opwfile == NULL) {
553 if (fstat(fileno(opwfile), &st) == -1) {
560 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
566 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
573 tmpent = fgetpwent(opwfile);
575 if (!strcmp(tmpent->pw_name, forwho)) {
578 const char *const_charp;
581 assigned_passwd.const_charp = towhat;
583 tmpent->pw_passwd = assigned_passwd.charp;
586 if (putpwent(tmpent, pwfile)) {
587 D(("error writing entry to password file: %m"));
591 tmpent = fgetpwent(opwfile);
595 if (fclose(pwfile)) {
596 D(("error writing entries to password file: %m"));
602 if (!rename(PW_TMPFILE, "/etc/passwd"))
603 pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
608 if (SELINUX_ENABLED) {
609 if (setfscreatecon(prev_context)) {
613 freecon(prev_context);
621 return PAM_AUTHTOK_ERR;
625 static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
627 struct spwd *spwdent = NULL, *stmpent = NULL;
629 FILE *pwfile, *opwfile;
633 spwdent = getspnam(forwho);
634 if (spwdent == NULL) {
635 return PAM_USER_UNKNOWN;
637 oldmask = umask(077);
640 if (SELINUX_ENABLED) {
641 security_context_t shadow_context=NULL;
642 if (getfilecon("/etc/shadow",&shadow_context)<0) {
643 return PAM_AUTHTOK_ERR;
645 if (getfscreatecon(&prev_context)<0) {
646 freecon(shadow_context);
647 return PAM_AUTHTOK_ERR;
649 if (setfscreatecon(shadow_context)) {
650 freecon(shadow_context);
651 freecon(prev_context);
652 return PAM_AUTHTOK_ERR;
654 freecon(shadow_context);
657 pwfile = fopen(SH_TMPFILE, "w");
659 if (pwfile == NULL) {
664 opwfile = fopen("/etc/shadow", "r");
665 if (opwfile == NULL) {
671 if (fstat(fileno(opwfile), &st) == -1) {
678 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
684 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
691 stmpent = fgetspent(opwfile);
694 if (!strcmp(stmpent->sp_namp, forwho)) {
695 stmpent->sp_pwdp = towhat;
696 stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
698 D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
701 if (putspent(stmpent, pwfile)) {
702 D(("error writing entry to shadow file: %m"));
707 stmpent = fgetspent(opwfile);
711 if (fclose(pwfile)) {
712 D(("error writing entries to shadow file: %m"));
718 if (!rename(SH_TMPFILE, "/etc/shadow"))
719 pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
725 if (SELINUX_ENABLED) {
726 if (setfscreatecon(prev_context)) {
730 freecon(prev_context);
739 return PAM_AUTHTOK_ERR;
743 static int _do_setpass(pam_handle_t* pamh, const char *forwho,
744 const char *fromwhat,
745 char *towhat, unsigned int ctrl, int remember)
747 struct passwd *pwd = NULL;
754 pwd = getpwnam(forwho);
757 retval = PAM_AUTHTOK_ERR;
761 if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
762 if ((master=getNISserver(pamh)) != NULL) {
763 struct timeval timeout;
764 struct yppasswd yppwd;
769 /* Unlock passwd file to avoid deadlock */
775 /* Initialize password information */
776 yppwd.newpw.pw_passwd = pwd->pw_passwd;
777 yppwd.newpw.pw_name = pwd->pw_name;
778 yppwd.newpw.pw_uid = pwd->pw_uid;
779 yppwd.newpw.pw_gid = pwd->pw_gid;
780 yppwd.newpw.pw_gecos = pwd->pw_gecos;
781 yppwd.newpw.pw_dir = pwd->pw_dir;
782 yppwd.newpw.pw_shell = pwd->pw_shell;
783 yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
784 yppwd.newpw.pw_passwd = towhat;
786 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
788 /* The yppasswd.x file said `unix authentication required',
789 * so I added it. This is the only reason it is in here.
790 * My yppasswdd doesn't use it, but maybe some others out there
793 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
794 clnt->cl_auth = authunix_create_default();
795 memset((char *) &status, '\0', sizeof(status));
798 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
799 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
800 (xdrproc_t) xdr_int, (char *) &status,
803 free (yppwd.oldpass);
806 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
809 D(("Error while changing NIS password.\n"));
811 D(("The password has%s been changed on %s.",
812 (err || status) ? " not" : "", master));
813 pam_syslog(pamh, LOG_NOTICE, "password%s changed for %s on %s",
814 (err || status) ? " not" : "", pwd->pw_name, master);
816 auth_destroy(clnt->cl_auth);
819 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
820 _("NIS password could not be changed."));
821 retval = PAM_TRY_AGAIN;
827 retval = PAM_TRY_AGAIN;
831 if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
835 /* These values for the number of attempts and the sleep time
836 are, of course, completely arbitrary.
837 My reading of the PAM docs is that, once pam_chauthtok() has been
838 called with PAM_UPDATE_AUTHTOK, we are obliged to take any
839 reasonable steps to make sure the token is updated; so retrying
840 for 1/10 sec. isn't overdoing it. */
841 while((retval = lckpwdf()) != 0 && i < 100) {
846 return PAM_AUTHTOK_LOCK_BUSY;
850 /* first, save old password */
851 if (save_old_password(pamh, forwho, fromwhat, remember)) {
852 retval = PAM_AUTHTOK_ERR;
855 if (on(UNIX_SHADOW, ctrl) || _unix_shadowed(pwd)) {
856 retval = _update_shadow(pamh, forwho, towhat);
858 if (retval != PAM_SUCCESS && SELINUX_ENABLED)
859 retval = _unix_run_shadow_binary(pamh, ctrl, forwho, fromwhat, towhat);
861 if (retval == PAM_SUCCESS)
862 if (!_unix_shadowed(pwd))
863 retval = _update_passwd(pamh, forwho, "x");
865 retval = _update_passwd(pamh, forwho, towhat);
878 static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
880 struct passwd *pwd = NULL; /* Password and shadow password */
881 struct spwd *spwdent = NULL; /* file entries for the user */
883 int retval = PAM_SUCCESS;
885 /* UNIX passwords area */
886 pwd = getpwnam(user); /* Get password file entry... */
888 return PAM_AUTHINFO_UNAVAIL; /* We don't need to do the rest... */
890 if (_unix_shadowed(pwd)) {
891 /* ...and shadow password file entry for this user, if shadowing
894 spwdent = getspnam(user);
898 if (spwdent == NULL && SELINUX_ENABLED )
899 spwdent = _unix_run_verify_binary(pamh, ctrl, user);
902 return PAM_AUTHINFO_UNAVAIL;
904 if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */
907 save_uid = geteuid();
908 seteuid (pwd->pw_uid);
909 spwdent = getspnam( user );
913 return PAM_AUTHINFO_UNAVAIL;
918 if (spwdent != NULL) {
919 /* We have the user's information, now let's check if their account
920 has expired (60 * 60 * 24 = number of seconds in a day) */
922 if (off(UNIX__IAMROOT, ctrl)) {
923 /* Get the current number of days since 1970 */
924 curdays = time(NULL) / (60 * 60 * 24);
925 if (curdays < spwdent->sp_lstchg) {
926 pam_syslog(pamh, LOG_DEBUG,
927 "account %s has password changed in future",
929 curdays = spwdent->sp_lstchg;
931 if ((curdays - spwdent->sp_lstchg < spwdent->sp_min)
932 && (spwdent->sp_min != -1))
934 * The last password change was too recent.
936 retval = PAM_AUTHTOK_ERR;
937 else if ((curdays - spwdent->sp_lstchg > spwdent->sp_max)
938 && (curdays - spwdent->sp_lstchg > spwdent->sp_inact)
939 && (curdays - spwdent->sp_lstchg >
940 spwdent->sp_max + spwdent->sp_inact)
941 && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1)
942 && (spwdent->sp_lstchg != 0))
944 * Their password change has been put off too long,
946 retval = PAM_ACCT_EXPIRED;
947 else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1)
948 && (spwdent->sp_lstchg != 0))
950 * OR their account has just plain expired
952 retval = PAM_ACCT_EXPIRED;
958 static int _pam_unix_approve_pass(pam_handle_t * pamh
960 ,const char *pass_old
961 ,const char *pass_new)
964 const char *remark = NULL;
965 int retval = PAM_SUCCESS;
967 D(("&new=%p, &old=%p", pass_old, pass_new));
968 D(("new=[%s]", pass_new));
969 D(("old=[%s]", pass_old));
971 if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
972 if (on(UNIX_DEBUG, ctrl)) {
973 pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
975 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
976 _("No password supplied") : _("Password unchanged"));
977 return PAM_AUTHTOK_ERR;
980 * if one wanted to hardwire authentication token strength
981 * checking this would be the place - AGM
984 retval = pam_get_item(pamh, PAM_USER, &user);
985 if (retval != PAM_SUCCESS) {
986 if (on(UNIX_DEBUG, ctrl)) {
987 pam_syslog(pamh, LOG_ERR, "Can not get username");
988 return PAM_AUTHTOK_ERR;
991 if (off(UNIX__IAMROOT, ctrl)) {
993 remark = FascistCheck (pass_new, CRACKLIB_DICTS);
994 D(("called cracklib [%s]", remark));
996 if (strlen(pass_new) < 6)
997 remark = _("You must choose a longer password");
998 D(("length check [%s]", remark));
1000 if (on(UNIX_REMEMBER_PASSWD, ctrl)) {
1001 if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR)
1002 remark = _("Password has been already used. Choose another.");
1003 if (retval == PAM_ABORT) {
1004 pam_syslog(pamh, LOG_ERR, "can't open %s file to check old passwords",
1005 OLD_PASSWORDS_FILE);
1011 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
1012 retval = PAM_AUTHTOK_ERR;
1018 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
1019 int argc, const char **argv)
1021 unsigned int ctrl, lctrl;
1025 /* <DO NOT free() THESE> */
1027 const void *pass_old, *pass_new;
1028 /* </DO NOT free() THESE> */
1032 ctrl = _set_ctrl(pamh, flags, &remember, argc, argv);
1035 * First get the name of a user
1037 retval = pam_get_user(pamh, &user, NULL);
1038 if (retval == PAM_SUCCESS) {
1040 * Various libraries at various times have had bugs related to
1041 * '+' or '-' as the first character of a user name. Don't take
1042 * any chances here. Require that the username starts with an
1043 * alphanumeric character.
1045 if (user == NULL || !isalnum(*user)) {
1046 pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
1047 return PAM_USER_UNKNOWN;
1049 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
1050 pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
1053 if (on(UNIX_DEBUG, ctrl))
1054 pam_syslog(pamh, LOG_DEBUG,
1055 "password - could not identify user");
1059 D(("Got username of %s", user));
1062 * Before we do anything else, check to make sure that the user's
1063 * info is in one of the databases we can modify from this module,
1064 * which currently is 'files' and 'nis'. We have to do this because
1065 * getpwnam() doesn't tell you *where* the information it gives you
1066 * came from, nor should it. That's our job.
1068 if (_unix_comesfromsource(pamh, user, 1, on(UNIX_NIS, ctrl)) == 0) {
1069 pam_syslog(pamh, LOG_DEBUG,
1070 "user \"%s\" does not exist in /etc/passwd%s",
1071 user, on(UNIX_NIS, ctrl) ? " or NIS" : "");
1072 return PAM_USER_UNKNOWN;
1075 _unix_getpwnam(pamh, user, 1, 1, &pwd);
1077 pam_syslog(pamh, LOG_DEBUG,
1078 "user \"%s\" has corrupted passwd entry",
1080 return PAM_USER_UNKNOWN;
1082 if (!_unix_shadowed(pwd) &&
1083 (strchr(pwd->pw_passwd, '*') != NULL)) {
1084 pam_syslog(pamh, LOG_DEBUG,
1085 "user \"%s\" does not have modifiable password",
1087 return PAM_USER_UNKNOWN;
1092 * This is not an AUTH module!
1094 if (on(UNIX__NONULL, ctrl))
1095 set(UNIX__NULLOK, ctrl);
1097 if (on(UNIX__PRELIM, ctrl)) {
1099 * obtain and verify the current password (OLDAUTHTOK) for
1104 D(("prelim check"));
1106 if (_unix_blankpasswd(pamh, ctrl, user)) {
1108 } else if (off(UNIX__IAMROOT, ctrl)) {
1110 /* instruct user what is happening */
1111 #define greeting "Changing password for "
1112 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
1113 if (Announce == NULL) {
1114 pam_syslog(pamh, LOG_CRIT,
1115 "password - out of memory");
1118 (void) strcpy(Announce, greeting);
1119 (void) strcpy(Announce + sizeof(greeting) - 1, user);
1123 set(UNIX__OLD_PASSWD, lctrl);
1124 retval = _unix_read_password(pamh, lctrl
1126 ,_("(current) UNIX password: ")
1132 if (retval != PAM_SUCCESS) {
1133 pam_syslog(pamh, LOG_NOTICE,
1134 "password - (old) token not obtained");
1137 /* verify that this is the password for this user */
1139 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
1141 D(("process run by root so do nothing this time around"));
1143 retval = PAM_SUCCESS; /* root doesn't have too */
1146 if (retval != PAM_SUCCESS) {
1147 D(("Authentication failed"));
1151 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
1153 if (retval != PAM_SUCCESS) {
1154 pam_syslog(pamh, LOG_CRIT,
1155 "failed to set PAM_OLDAUTHTOK");
1157 retval = _unix_verify_shadow(pamh,user, ctrl);
1158 if (retval == PAM_AUTHTOK_ERR) {
1159 if (off(UNIX__IAMROOT, ctrl))
1160 _make_remark(pamh, ctrl, PAM_ERROR_MSG,
1161 _("You must wait longer to change your password"));
1163 retval = PAM_SUCCESS;
1165 } else if (on(UNIX__UPDATE, ctrl)) {
1167 * tpass is used below to store the _pam_md() return; it
1168 * should be _pam_delete()'d.
1175 * obtain the proposed password
1181 * get the old token back. NULL was ok only if root [at this
1182 * point we assume that this has already been enforced on a
1183 * previous call to this function].
1186 if (off(UNIX_NOT_SET_PASS, ctrl)) {
1187 retval = pam_get_item(pamh, PAM_OLDAUTHTOK
1190 retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
1192 if (retval == PAM_NO_MODULE_DATA) {
1193 retval = PAM_SUCCESS;
1197 D(("pass_old [%s]", pass_old));
1199 if (retval != PAM_SUCCESS) {
1200 pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
1204 D(("get new password now"));
1208 if (on(UNIX_USE_AUTHTOK, lctrl)) {
1209 set(UNIX_USE_FIRST_PASS, lctrl);
1212 retval = PAM_AUTHTOK_ERR;
1213 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
1215 * use_authtok is to force the use of a previously entered
1216 * password -- needed for pluggable password strength checking
1219 retval = _unix_read_password(pamh, lctrl
1221 ,_("Enter new UNIX password: ")
1222 ,_("Retype new UNIX password: ")
1226 if (retval != PAM_SUCCESS) {
1227 if (on(UNIX_DEBUG, ctrl)) {
1228 pam_syslog(pamh, LOG_ALERT,
1229 "password - new password not obtained");
1231 pass_old = NULL; /* tidy up */
1234 D(("returned to _unix_chauthtok"));
1237 * At this point we know who the user is and what they
1238 * propose as their new password. Verify that the new
1239 * password is acceptable.
1242 if (*(const char *)pass_new == '\0') { /* "\0" password = NULL */
1245 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
1248 if (retval != PAM_SUCCESS) {
1249 pam_syslog(pamh, LOG_NOTICE,
1250 "new password not acceptable");
1251 pass_new = pass_old = NULL; /* tidy up */
1255 /* These values for the number of attempts and the sleep time
1256 are, of course, completely arbitrary.
1257 My reading of the PAM docs is that, once pam_chauthtok() has been
1258 called with PAM_UPDATE_AUTHTOK, we are obliged to take any
1259 reasonable steps to make sure the token is updated; so retrying
1260 for 1/10 sec. isn't overdoing it. */
1262 while((retval = lckpwdf()) != 0 && i < 100) {
1267 return PAM_AUTHTOK_LOCK_BUSY;
1272 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
1273 if (retval != PAM_SUCCESS) {
1274 pam_syslog(pamh, LOG_NOTICE, "user password changed by another process");
1282 retval = _unix_verify_shadow(pamh, user, ctrl);
1283 if (retval != PAM_SUCCESS) {
1284 pam_syslog(pamh, LOG_NOTICE, "user not authenticated 2");
1291 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
1292 if (retval != PAM_SUCCESS) {
1293 pam_syslog(pamh, LOG_NOTICE,
1294 "new password not acceptable 2");
1295 pass_new = pass_old = NULL; /* tidy up */
1303 * By reaching here we have approved the passwords and must now
1304 * rebuild the password database file.
1308 * First we encrypt the new password.
1311 if (on(UNIX_MD5_PASS, ctrl)) {
1312 tpass = crypt_md5_wrapper(pass_new);
1315 * Salt manipulation is stolen from Rick Faith's passwd
1316 * program. Sorry Rick :) -- alex
1323 salt[0] = bin_to_ascii(tm & 0x3f);
1324 salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
1327 if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) {
1329 * to avoid using the _extensions_ of the bigcrypt()
1330 * function we truncate the newly entered password
1331 * [Problems that followed from this are fixed as per
1334 char *temp = malloc(9);
1337 pam_syslog(pamh, LOG_CRIT,
1338 "out of memory for password");
1339 pass_new = pass_old = NULL; /* tidy up */
1345 /* copy first 8 bytes of password */
1346 strncpy(temp, pass_new, 8);
1349 /* no longer need cleartext */
1350 tpass = bigcrypt(temp, salt);
1352 _pam_delete(temp); /* tidy up */
1354 tpass = bigcrypt(pass_new, salt);
1358 D(("password processed"));
1360 /* update the password database(s) -- race conditions..? */
1362 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
1364 /* _do_setpass has called ulckpwdf for us */
1367 pass_old = pass_new = NULL;
1368 } else { /* something has broken with the module */
1369 pam_syslog(pamh, LOG_ALERT,
1370 "password received unknown request");
1374 D(("retval was %d", retval));
1380 /* static module data */
1382 struct pam_module _pam_unix_passwd_modstruct = {