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>
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 */
95 * PAM framework looks for these entry-points to pass control to the
96 * password changing module.
99 #if defined(USE_LCKPWDF) && !defined(HAVE_LCKPWDF)
100 # include "./lckpwdf.-c"
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
111 /* passwd/salt conversion macros */
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)+'.')
118 #define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS"
119 #define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS"
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
127 #define OPW_TMPFILE "/etc/security/nopasswd"
128 #define OLD_PASSWORDS_FILE "/etc/security/opasswd"
131 * i64c - convert an integer to a radix 64 character
133 static int i64c(int i)
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);
152 static char *crypt_md5_wrapper(const char *pass_new)
155 * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
156 * removed use of static variables (AGM)
161 unsigned char result[16];
162 char *cp = (char *) result;
163 unsigned char tmp[16];
168 gettimeofday(&tv, (struct timezone *) 0);
169 GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
171 GoodMD5Update(&ctx, (void *) &i, sizeof i);
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 */
178 for (i = 0; i < 8; i++)
179 *cp++ = i64c(tmp[i] & 077);
182 /* no longer need cleartext */
183 x = Goodcrypt_md5(pass_new, (const char *) result);
188 static char *getNISserver(pam_handle_t *pamh)
194 if ((err = yp_get_default_domain(&domainname)) != 0) {
195 pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
199 if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
200 pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
204 port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
206 pam_syslog(pamh, LOG_WARNING,
207 "yppasswdd not running on NIS master host");
210 if (port >= IPPORT_RESERVED) {
211 pam_syslog(pamh, LOG_WARNING,
212 "yppasswd daemon running on illegal port");
220 static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user, const char *fromwhat, const char *towhat)
222 int retval, child, fds[2];
223 void (*sighandler)(int) = NULL;
226 /* create a pipe for the password */
227 if (pipe(fds) != 0) {
228 D(("could not make pipe"));
232 if (off(UNIX_NOREAP, ctrl)) {
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.
238 * The "noreap" module argument is provided so that the admin can
239 * override this behavior.
241 sighandler = signal(SIGCHLD, SIG_DFL);
249 static char *envp[] = { NULL };
250 char *args[] = { NULL, NULL, NULL, NULL };
252 /* XXX - should really tidy up PAM here too */
255 /* reopen stdin as pipe */
257 dup2(fds[0], STDIN_FILENO);
259 if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
260 for (i=2; i < rlim.rlim_max; i++) {
261 if ((unsigned int)fds[0] != i)
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...) */
272 /* exec binary helper */
273 args[0] = x_strdup(CHKPWD_HELPER);
274 args[1] = x_strdup(user);
275 args[2] = x_strdup("shadow");
277 execve(CHKPWD_HELPER, args, envp);
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) {
284 /* if the stored password is NULL */
287 pam_modutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
289 pam_modutil_write(fds[1], "", 1);
291 pam_modutil_write(fds[1], towhat, strlen(towhat)+1);
294 pam_modutil_write(fds[1], "", 1);
296 close(fds[0]); /* close here to avoid possible SIGPIPE above */
298 rc=waitpid(child, &retval, 0); /* wait for helper to complete */
300 pam_syslog(pamh, LOG_ERR, "unix_chkpwd waitpid returned %d: %m", rc);
301 retval = PAM_AUTH_ERR;
303 retval = WEXITSTATUS(retval);
309 retval = PAM_AUTH_ERR;
312 if (sighandler != SIG_ERR) {
313 (void) signal(SIGCHLD, sighandler); /* restore old signal handler */
320 static int check_old_password(const char *forwho, const char *newpass)
322 static char buf[16384];
323 char *s_luser, *s_uid, *s_npas, *s_pas;
324 int retval = PAM_SUCCESS;
327 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
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;
345 s_pas = strtok(NULL, ":,");
346 _pam_delete(md5pass);
356 static int save_old_password(pam_handle_t *pamh,
357 const char *forwho, const char *oldpass,
360 static char buf[16384];
361 static char nbuf[16384];
362 char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
364 FILE *pwfile, *opwfile;
368 struct passwd *pwd = NULL;
375 if (oldpass == NULL) {
379 oldmask = umask(077);
382 if (SELINUX_ENABLED) {
383 security_context_t passwd_context=NULL;
384 if (getfilecon("/etc/passwd",&passwd_context)<0) {
385 return PAM_AUTHTOK_ERR;
387 if (getfscreatecon(&prev_context)<0) {
388 freecon(passwd_context);
389 return PAM_AUTHTOK_ERR;
391 if (setfscreatecon(passwd_context)) {
392 freecon(passwd_context);
393 freecon(prev_context);
394 return PAM_AUTHTOK_ERR;
396 freecon(passwd_context);
399 pwfile = fopen(OPW_TMPFILE, "w");
401 if (pwfile == NULL) {
406 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
407 if (opwfile == NULL) {
413 if (fstat(fileno(opwfile), &st) == -1) {
420 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
426 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
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, ",");
447 pass = crypt_md5_wrapper(oldpass);
449 snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
450 s_luser, s_uid, npas, pass);
452 snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
453 s_luser, s_uid, npas, s_pas, pass);
455 if (fputs(nbuf, pwfile) < 0) {
460 } else if (fputs(buf, pwfile) < 0) {
468 pwd = pam_modutil_getpwnam(pamh, forwho);
472 pass = crypt_md5_wrapper(oldpass);
473 snprintf(nbuf, sizeof(nbuf), "%s:%lu:1:%s\n",
474 forwho, (unsigned long)pwd->pw_uid, pass);
476 if (fputs(nbuf, pwfile) < 0) {
482 if (fclose(pwfile)) {
483 D(("error writing entries to old passwords file: %m"));
489 if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
493 if (SELINUX_ENABLED) {
494 if (setfscreatecon(prev_context)) {
498 freecon(prev_context);
506 return PAM_AUTHTOK_ERR;
510 static int _update_passwd(pam_handle_t *pamh,
511 const char *forwho, const char *towhat)
513 struct passwd *tmpent = NULL;
515 FILE *pwfile, *opwfile;
519 oldmask = umask(077);
521 if (SELINUX_ENABLED) {
522 security_context_t passwd_context=NULL;
523 if (getfilecon("/etc/passwd",&passwd_context)<0) {
524 return PAM_AUTHTOK_ERR;
526 if (getfscreatecon(&prev_context)<0) {
527 freecon(passwd_context);
528 return PAM_AUTHTOK_ERR;
530 if (setfscreatecon(passwd_context)) {
531 freecon(passwd_context);
532 freecon(prev_context);
533 return PAM_AUTHTOK_ERR;
535 freecon(passwd_context);
538 pwfile = fopen(PW_TMPFILE, "w");
540 if (pwfile == NULL) {
545 opwfile = fopen("/etc/passwd", "r");
546 if (opwfile == NULL) {
552 if (fstat(fileno(opwfile), &st) == -1) {
559 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
565 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
572 tmpent = fgetpwent(opwfile);
574 if (!strcmp(tmpent->pw_name, forwho)) {
577 const char *const_charp;
580 assigned_passwd.const_charp = towhat;
582 tmpent->pw_passwd = assigned_passwd.charp;
585 if (putpwent(tmpent, pwfile)) {
586 D(("error writing entry to password file: %m"));
590 tmpent = fgetpwent(opwfile);
594 if (fclose(pwfile)) {
595 D(("error writing entries to password file: %m"));
601 if (!rename(PW_TMPFILE, "/etc/passwd"))
602 pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
607 if (SELINUX_ENABLED) {
608 if (setfscreatecon(prev_context)) {
612 freecon(prev_context);
620 return PAM_AUTHTOK_ERR;
624 static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
626 struct spwd *spwdent = NULL, *stmpent = NULL;
628 FILE *pwfile, *opwfile;
632 spwdent = getspnam(forwho);
633 if (spwdent == NULL) {
634 return PAM_USER_UNKNOWN;
636 oldmask = umask(077);
639 if (SELINUX_ENABLED) {
640 security_context_t shadow_context=NULL;
641 if (getfilecon("/etc/shadow",&shadow_context)<0) {
642 return PAM_AUTHTOK_ERR;
644 if (getfscreatecon(&prev_context)<0) {
645 freecon(shadow_context);
646 return PAM_AUTHTOK_ERR;
648 if (setfscreatecon(shadow_context)) {
649 freecon(shadow_context);
650 freecon(prev_context);
651 return PAM_AUTHTOK_ERR;
653 freecon(shadow_context);
656 pwfile = fopen(SH_TMPFILE, "w");
658 if (pwfile == NULL) {
663 opwfile = fopen("/etc/shadow", "r");
664 if (opwfile == NULL) {
670 if (fstat(fileno(opwfile), &st) == -1) {
677 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
683 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
690 stmpent = fgetspent(opwfile);
693 if (!strcmp(stmpent->sp_namp, forwho)) {
694 stmpent->sp_pwdp = towhat;
695 stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
697 D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
700 if (putspent(stmpent, pwfile)) {
701 D(("error writing entry to shadow file: %m"));
706 stmpent = fgetspent(opwfile);
710 if (fclose(pwfile)) {
711 D(("error writing entries to shadow file: %m"));
717 if (!rename(SH_TMPFILE, "/etc/shadow"))
718 pam_syslog(pamh, LOG_NOTICE, "password changed for %s", forwho);
724 if (SELINUX_ENABLED) {
725 if (setfscreatecon(prev_context)) {
729 freecon(prev_context);
738 return PAM_AUTHTOK_ERR;
742 static int _do_setpass(pam_handle_t* pamh, const char *forwho,
743 const char *fromwhat,
744 char *towhat, unsigned int ctrl, int remember)
746 struct passwd *pwd = NULL;
753 pwd = getpwnam(forwho);
756 retval = PAM_AUTHTOK_ERR;
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;
768 /* Unlock passwd file to avoid deadlock */
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;
785 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
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
792 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
793 clnt->cl_auth = authunix_create_default();
794 memset((char *) &status, '\0', sizeof(status));
797 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
798 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
799 (xdrproc_t) xdr_int, (char *) &status,
802 free (yppwd.oldpass);
805 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
808 D(("Error while changing NIS password.\n"));
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);
815 auth_destroy(clnt->cl_auth);
818 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
819 _("NIS password could not be changed."));
820 retval = PAM_TRY_AGAIN;
826 retval = PAM_TRY_AGAIN;
830 if (_unix_comesfromsource(pamh, forwho, 1, 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) {
845 return PAM_AUTHTOK_LOCK_BUSY;
849 /* first, save old password */
850 if (save_old_password(pamh, forwho, fromwhat, remember)) {
851 retval = PAM_AUTHTOK_ERR;
854 if (on(UNIX_SHADOW, ctrl) || _unix_shadowed(pwd)) {
855 retval = _update_shadow(pamh, forwho, towhat);
857 if (retval != PAM_SUCCESS && SELINUX_ENABLED)
858 retval = _unix_run_shadow_binary(pamh, ctrl, forwho, fromwhat, towhat);
860 if (retval == PAM_SUCCESS)
861 if (!_unix_shadowed(pwd))
862 retval = _update_passwd(pamh, forwho, "x");
864 retval = _update_passwd(pamh, forwho, towhat);
877 static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
879 struct passwd *pwd = NULL; /* Password and shadow password */
880 struct spwd *spwdent = NULL; /* file entries for the user */
882 int retval = PAM_SUCCESS;
884 /* UNIX passwords area */
885 pwd = getpwnam(user); /* Get password file entry... */
887 return PAM_AUTHINFO_UNAVAIL; /* We don't need to do the rest... */
889 if (_unix_shadowed(pwd)) {
890 /* ...and shadow password file entry for this user, if shadowing
893 spwdent = getspnam(user);
897 if (spwdent == NULL && SELINUX_ENABLED )
898 spwdent = _unix_run_verify_binary(pamh, ctrl, user);
901 return PAM_AUTHINFO_UNAVAIL;
903 if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */
906 save_uid = geteuid();
907 seteuid (pwd->pw_uid);
908 spwdent = getspnam( user );
912 return PAM_AUTHINFO_UNAVAIL;
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) */
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",
928 curdays = spwdent->sp_lstchg;
930 if ((curdays - spwdent->sp_lstchg < spwdent->sp_min)
931 && (spwdent->sp_min != -1))
933 * The last password change was too recent.
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))
943 * Their password change has been put off too long,
945 retval = PAM_ACCT_EXPIRED;
946 else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1)
947 && (spwdent->sp_lstchg != 0))
949 * OR their account has just plain expired
951 retval = PAM_ACCT_EXPIRED;
957 static int _pam_unix_approve_pass(pam_handle_t * pamh
959 ,const char *pass_old
960 ,const char *pass_new)
963 const char *remark = NULL;
964 int retval = PAM_SUCCESS;
966 D(("&new=%p, &old=%p", pass_old, pass_new));
967 D(("new=[%s]", pass_new));
968 D(("old=[%s]", pass_old));
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");
974 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
975 _("No password supplied") : _("Password unchanged"));
976 return PAM_AUTHTOK_ERR;
979 * if one wanted to hardwire authentication token strength
980 * checking this would be the place - AGM
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;
990 if (off(UNIX__IAMROOT, ctrl)) {
992 remark = FascistCheck (pass_new, CRACKLIB_DICTS);
993 D(("called cracklib [%s]", remark));
995 if (strlen(pass_new) < 6)
996 remark = _("You must choose a longer password");
997 D(("length check [%s]", remark));
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);
1010 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
1011 retval = PAM_AUTHTOK_ERR;
1017 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
1018 int argc, const char **argv)
1020 unsigned int ctrl, lctrl;
1024 /* <DO NOT free() THESE> */
1026 const void *pass_old, *pass_new;
1027 /* </DO NOT free() THESE> */
1031 ctrl = _set_ctrl(pamh, flags, &remember, argc, argv);
1034 * First get the name of a user
1036 retval = pam_get_user(pamh, &user, NULL);
1037 if (retval == PAM_SUCCESS) {
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.
1044 if (user == NULL || !isalnum(*user)) {
1045 pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
1046 return PAM_USER_UNKNOWN;
1048 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
1049 pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
1052 if (on(UNIX_DEBUG, ctrl))
1053 pam_syslog(pamh, LOG_DEBUG,
1054 "password - could not identify user");
1058 D(("Got username of %s", user));
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.
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;
1074 _unix_getpwnam(pamh, user, 1, 1, &pwd);
1076 pam_syslog(pamh, LOG_DEBUG,
1077 "user \"%s\" has corrupted passwd entry",
1079 return PAM_USER_UNKNOWN;
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",
1086 return PAM_USER_UNKNOWN;
1091 * This is not an AUTH module!
1093 if (on(UNIX__NONULL, ctrl))
1094 set(UNIX__NULLOK, ctrl);
1096 if (on(UNIX__PRELIM, ctrl)) {
1098 * obtain and verify the current password (OLDAUTHTOK) for
1103 D(("prelim check"));
1105 if (_unix_blankpasswd(pamh, ctrl, user)) {
1107 } else if (off(UNIX__IAMROOT, ctrl)) {
1108 /* instruct user what is happening */
1109 if (asprintf(&Announce, _("Changing password for %s."),
1111 pam_syslog(pamh, LOG_CRIT,
1112 "password - out of memory");
1117 set(UNIX__OLD_PASSWD, lctrl);
1118 retval = _unix_read_password(pamh, lctrl
1120 ,_("(current) UNIX password: ")
1126 if (retval != PAM_SUCCESS) {
1127 pam_syslog(pamh, LOG_NOTICE,
1128 "password - (old) token not obtained");
1131 /* verify that this is the password for this user */
1133 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
1135 D(("process run by root so do nothing this time around"));
1137 retval = PAM_SUCCESS; /* root doesn't have too */
1140 if (retval != PAM_SUCCESS) {
1141 D(("Authentication failed"));
1145 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
1147 if (retval != PAM_SUCCESS) {
1148 pam_syslog(pamh, LOG_CRIT,
1149 "failed to set PAM_OLDAUTHTOK");
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"));
1157 retval = PAM_SUCCESS;
1159 } else if (on(UNIX__UPDATE, ctrl)) {
1161 * tpass is used below to store the _pam_md() return; it
1162 * should be _pam_delete()'d.
1169 * obtain the proposed password
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].
1180 if (off(UNIX_NOT_SET_PASS, ctrl)) {
1181 retval = pam_get_item(pamh, PAM_OLDAUTHTOK
1184 retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
1186 if (retval == PAM_NO_MODULE_DATA) {
1187 retval = PAM_SUCCESS;
1191 D(("pass_old [%s]", pass_old));
1193 if (retval != PAM_SUCCESS) {
1194 pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
1198 D(("get new password now"));
1202 if (on(UNIX_USE_AUTHTOK, lctrl)) {
1203 set(UNIX_USE_FIRST_PASS, lctrl);
1206 retval = PAM_AUTHTOK_ERR;
1207 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
1209 * use_authtok is to force the use of a previously entered
1210 * password -- needed for pluggable password strength checking
1213 retval = _unix_read_password(pamh, lctrl
1215 ,_("Enter new UNIX password: ")
1216 ,_("Retype new UNIX password: ")
1220 if (retval != PAM_SUCCESS) {
1221 if (on(UNIX_DEBUG, ctrl)) {
1222 pam_syslog(pamh, LOG_ALERT,
1223 "password - new password not obtained");
1225 pass_old = NULL; /* tidy up */
1228 D(("returned to _unix_chauthtok"));
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.
1236 if (*(const char *)pass_new == '\0') { /* "\0" password = NULL */
1239 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
1242 if (retval != PAM_SUCCESS) {
1243 pam_syslog(pamh, LOG_NOTICE,
1244 "new password not acceptable");
1245 pass_new = pass_old = NULL; /* tidy up */
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. */
1256 while((retval = lckpwdf()) != 0 && i < 100) {
1261 return PAM_AUTHTOK_LOCK_BUSY;
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");
1276 retval = _unix_verify_shadow(pamh, user, ctrl);
1277 if (retval != PAM_SUCCESS) {
1278 pam_syslog(pamh, LOG_NOTICE, "user not authenticated 2");
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 */
1297 * By reaching here we have approved the passwords and must now
1298 * rebuild the password database file.
1302 * First we encrypt the new password.
1305 if (on(UNIX_MD5_PASS, ctrl)) {
1306 tpass = crypt_md5_wrapper(pass_new);
1309 * Salt manipulation is stolen from Rick Faith's passwd
1310 * program. Sorry Rick :) -- alex
1317 salt[0] = bin_to_ascii(tm & 0x3f);
1318 salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
1321 if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) {
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
1328 char *temp = malloc(9);
1331 pam_syslog(pamh, LOG_CRIT,
1332 "out of memory for password");
1333 pass_new = pass_old = NULL; /* tidy up */
1339 /* copy first 8 bytes of password */
1340 strncpy(temp, pass_new, 8);
1343 /* no longer need cleartext */
1344 tpass = bigcrypt(temp, salt);
1346 _pam_delete(temp); /* tidy up */
1348 tpass = bigcrypt(pass_new, salt);
1352 D(("password processed"));
1354 /* update the password database(s) -- race conditions..? */
1356 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
1358 /* _do_setpass has called ulckpwdf for us */
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");
1368 D(("retval was %d", retval));
1374 /* static module data */
1376 struct pam_module _pam_unix_passwd_modstruct = {