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>
83 #include <security/pam_appl.h>
84 #endif /* LINUX_PAM */
86 #include <security/_pam_modutil.h>
92 #if !((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))
93 extern int getrpcport(const char *host, unsigned long prognum,
94 unsigned long versnum, unsigned int proto);
95 #endif /* GNU libc 2.1 */
98 * PAM framework looks for these entry-points to pass control to the
99 * password changing module.
102 #if defined(USE_LCKPWDF) && !defined(HAVE_LCKPWDF)
103 # include "./lckpwdf.-c"
106 extern char *bigcrypt(const char *key, const char *salt);
110 Gets in username (has to be done) from the calling program
111 Does authentication of user (only if we are not running as root)
112 Gets new password/checks for sanity
116 /* passwd/salt conversion macros */
118 #define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
119 #define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
123 #define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS"
124 #define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS"
126 #define MAX_PASSWD_TRIES 3
127 #define PW_TMPFILE "/etc/npasswd"
128 #define SH_TMPFILE "/etc/nshadow"
129 #ifndef CRACKLIB_DICTS
130 #define CRACKLIB_DICTS NULL
132 #define OPW_TMPFILE "/etc/security/nopasswd"
133 #define OLD_PASSWORDS_FILE "/etc/security/opasswd"
136 * i64c - convert an integer to a radix 64 character
138 static int i64c(int i)
148 if (i >= 2 && i <= 11)
149 return ('0' - 2 + i);
150 if (i >= 12 && i <= 37)
151 return ('A' - 12 + i);
152 if (i >= 38 && i <= 63)
153 return ('a' - 38 + i);
157 static char *crypt_md5_wrapper(const char *pass_new)
160 * Code lifted from Marek Michalkiewicz's shadow suite. (CG)
161 * removed use of static variables (AGM)
166 unsigned char result[16];
167 char *cp = (char *) result;
168 unsigned char tmp[16];
173 gettimeofday(&tv, (struct timezone *) 0);
174 GoodMD5Update(&ctx, (void *) &tv, sizeof tv);
176 GoodMD5Update(&ctx, (void *) &i, sizeof i);
178 GoodMD5Update(&ctx, (void *) &i, sizeof i);
179 GoodMD5Update(&ctx, result, sizeof result);
180 GoodMD5Final(tmp, &ctx);
181 strcpy(cp, "$1$"); /* magic for the MD5 */
183 for (i = 0; i < 8; i++)
184 *cp++ = i64c(tmp[i] & 077);
187 /* no longer need cleartext */
188 x = Goodcrypt_md5(pass_new, (const char *) result);
193 static char *getNISserver(pam_handle_t *pamh)
199 if ((err = yp_get_default_domain(&domainname)) != 0) {
200 _log_err(LOG_WARNING, pamh, "can't get local yp domain: %s\n",
204 if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
205 _log_err(LOG_WARNING, pamh, "can't find the master ypserver: %s\n",
209 port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
211 _log_err(LOG_WARNING, pamh,
212 "yppasswdd not running on NIS master host\n");
215 if (port >= IPPORT_RESERVED) {
216 _log_err(LOG_WARNING, pamh,
217 "yppasswd daemon running on illegal port.\n");
225 static int _unix_run_shadow_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user, const char *fromwhat, const char *towhat)
227 int retval, child, fds[2];
228 void (*sighandler)(int) = NULL;
231 /* create a pipe for the password */
232 if (pipe(fds) != 0) {
233 D(("could not make pipe"));
237 if (off(UNIX_NOREAP, ctrl)) {
239 * This code arranges that the demise of the child does not cause
240 * the application to receive a signal it is not expecting - which
241 * may kill the application or worse.
243 * The "noreap" module argument is provided so that the admin can
244 * override this behavior.
246 sighandler = signal(SIGCHLD, SIG_DFL);
254 static char *envp[] = { NULL };
255 char *args[] = { NULL, NULL, NULL, NULL };
257 /* XXX - should really tidy up PAM here too */
260 /* reopen stdin as pipe */
262 dup2(fds[0], STDIN_FILENO);
264 if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
265 for (i=2; i < rlim.rlim_max; i++) {
266 if ((unsigned int)fds[0] != i)
270 /* exec binary helper */
271 args[0] = x_strdup(CHKPWD_HELPER);
272 args[1] = x_strdup(user);
273 args[2] = x_strdup("shadow");
275 execve(CHKPWD_HELPER, args, envp);
277 /* should not get here: exit with error */
278 D(("helper binary is not available"));
279 exit(PAM_AUTHINFO_UNAVAIL);
280 } else if (child > 0) {
282 /* if the stored password is NULL */
285 _pammodutil_write(fds[1], fromwhat, strlen(fromwhat)+1);
287 _pammodutil_write(fds[1], "", 1);
289 _pammodutil_write(fds[1], towhat, strlen(towhat)+1);
292 _pammodutil_write(fds[1], "", 1);
294 close(fds[0]); /* close here to avoid possible SIGPIPE above */
296 rc=waitpid(child, &retval, 0); /* wait for helper to complete */
298 _log_err(LOG_ERR, pamh, "unix_chkpwd waitpid returned %d: %s", rc, strerror(errno));
299 retval = PAM_AUTH_ERR;
301 retval = WEXITSTATUS(retval);
307 retval = PAM_AUTH_ERR;
310 if (sighandler != NULL) {
311 (void) signal(SIGCHLD, sighandler); /* restore old signal handler */
318 static int check_old_password(const char *forwho, const char *newpass)
320 static char buf[16384];
321 char *s_luser, *s_uid, *s_npas, *s_pas;
322 int retval = PAM_SUCCESS;
325 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
329 while (fgets(buf, 16380, opwfile)) {
330 if (!strncmp(buf, forwho, strlen(forwho))) {
331 buf[strlen(buf) - 1] = '\0';
332 s_luser = strtok(buf, ":,");
333 s_uid = strtok(NULL, ":,");
334 s_npas = strtok(NULL, ":,");
335 s_pas = strtok(NULL, ":,");
336 while (s_pas != NULL) {
337 char *md5pass = Goodcrypt_md5(newpass, s_pas);
338 if (!strcmp(md5pass, s_pas)) {
339 _pam_delete(md5pass);
340 retval = PAM_AUTHTOK_ERR;
343 s_pas = strtok(NULL, ":,");
344 _pam_delete(md5pass);
354 static int save_old_password(pam_handle_t *pamh,
355 const char *forwho, const char *oldpass,
358 static char buf[16384];
359 static char nbuf[16384];
360 char *s_luser, *s_uid, *s_npas, *s_pas, *pass;
362 FILE *pwfile, *opwfile;
366 struct passwd *pwd = NULL;
373 if (oldpass == NULL) {
377 oldmask = umask(077);
380 if (SELINUX_ENABLED) {
381 security_context_t passwd_context=NULL;
382 if (getfilecon("/etc/passwd",&passwd_context)<0) {
383 return PAM_AUTHTOK_ERR;
385 if (getfscreatecon(&prev_context)<0) {
386 freecon(passwd_context);
387 return PAM_AUTHTOK_ERR;
389 if (setfscreatecon(passwd_context)) {
390 freecon(passwd_context);
391 freecon(prev_context);
392 return PAM_AUTHTOK_ERR;
394 freecon(passwd_context);
397 pwfile = fopen(OPW_TMPFILE, "w");
399 if (pwfile == NULL) {
404 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
405 if (opwfile == NULL) {
411 if (fstat(fileno(opwfile), &st) == -1) {
418 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
424 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
431 while (fgets(buf, 16380, opwfile)) {
432 if (!strncmp(buf, forwho, strlen(forwho))) {
433 buf[strlen(buf) - 1] = '\0';
434 s_luser = strtok(buf, ":");
435 s_uid = strtok(NULL, ":");
436 s_npas = strtok(NULL, ":");
437 s_pas = strtok(NULL, ":");
438 npas = strtol(s_npas, NULL, 10) + 1;
439 while (npas > howmany) {
440 s_pas = strpbrk(s_pas, ",");
445 pass = crypt_md5_wrapper(oldpass);
447 snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n",
448 s_luser, s_uid, npas, pass);
450 snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n",
451 s_luser, s_uid, npas, s_pas, pass);
453 if (fputs(nbuf, pwfile) < 0) {
458 } else if (fputs(buf, pwfile) < 0) {
466 pwd = _pammodutil_getpwnam(pamh, forwho);
470 pass = crypt_md5_wrapper(oldpass);
471 snprintf(nbuf, sizeof(nbuf), "%s:%d:1:%s\n",
472 forwho, pwd->pw_uid, pass);
474 if (fputs(nbuf, pwfile) < 0) {
480 if (fclose(pwfile)) {
481 D(("error writing entries to old passwords file: %s\n",
488 if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE))
492 if (SELINUX_ENABLED) {
493 if (setfscreatecon(prev_context)) {
497 freecon(prev_context);
505 return PAM_AUTHTOK_ERR;
509 static int _update_passwd(pam_handle_t *pamh,
510 const char *forwho, const char *towhat)
512 struct passwd *tmpent = NULL;
514 FILE *pwfile, *opwfile;
518 oldmask = umask(077);
520 if (SELINUX_ENABLED) {
521 security_context_t passwd_context=NULL;
522 if (getfilecon("/etc/passwd",&passwd_context)<0) {
523 return PAM_AUTHTOK_ERR;
525 if (getfscreatecon(&prev_context)<0) {
526 freecon(passwd_context);
527 return PAM_AUTHTOK_ERR;
529 if (setfscreatecon(passwd_context)) {
530 freecon(passwd_context);
531 freecon(prev_context);
532 return PAM_AUTHTOK_ERR;
534 freecon(passwd_context);
537 pwfile = fopen(PW_TMPFILE, "w");
539 if (pwfile == NULL) {
544 opwfile = fopen("/etc/passwd", "r");
545 if (opwfile == NULL) {
551 if (fstat(fileno(opwfile), &st) == -1) {
558 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
564 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
571 tmpent = fgetpwent(opwfile);
573 if (!strcmp(tmpent->pw_name, forwho)) {
576 const char *const_charp;
579 assigned_passwd.const_charp = towhat;
581 tmpent->pw_passwd = assigned_passwd.charp;
584 if (putpwent(tmpent, pwfile)) {
585 D(("error writing entry to password file: %s\n", strerror(errno)));
589 tmpent = fgetpwent(opwfile);
593 if (fclose(pwfile)) {
594 D(("error writing entries to password file: %s\n", strerror(errno)));
600 if (!rename(PW_TMPFILE, "/etc/passwd"))
601 _log_err(LOG_NOTICE, pamh, "password changed for %s", forwho);
606 if (SELINUX_ENABLED) {
607 if (setfscreatecon(prev_context)) {
611 freecon(prev_context);
619 return PAM_AUTHTOK_ERR;
623 static int _update_shadow(pam_handle_t *pamh, const char *forwho, char *towhat)
625 struct spwd *spwdent = NULL, *stmpent = NULL;
627 FILE *pwfile, *opwfile;
631 spwdent = getspnam(forwho);
632 if (spwdent == NULL) {
633 return PAM_USER_UNKNOWN;
635 oldmask = umask(077);
638 if (SELINUX_ENABLED) {
639 security_context_t shadow_context=NULL;
640 if (getfilecon("/etc/shadow",&shadow_context)<0) {
641 return PAM_AUTHTOK_ERR;
643 if (getfscreatecon(&prev_context)<0) {
644 freecon(shadow_context);
645 return PAM_AUTHTOK_ERR;
647 if (setfscreatecon(shadow_context)) {
648 freecon(shadow_context);
649 freecon(prev_context);
650 return PAM_AUTHTOK_ERR;
652 freecon(shadow_context);
655 pwfile = fopen(SH_TMPFILE, "w");
657 if (pwfile == NULL) {
662 opwfile = fopen("/etc/shadow", "r");
663 if (opwfile == NULL) {
669 if (fstat(fileno(opwfile), &st) == -1) {
676 if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) {
682 if (fchmod(fileno(pwfile), st.st_mode) == -1) {
689 stmpent = fgetspent(opwfile);
692 if (!strcmp(stmpent->sp_namp, forwho)) {
693 stmpent->sp_pwdp = towhat;
694 stmpent->sp_lstchg = time(NULL) / (60 * 60 * 24);
696 D(("Set password %s for %s", stmpent->sp_pwdp, forwho));
699 if (putspent(stmpent, pwfile)) {
700 D(("error writing entry to shadow file: %s\n", strerror(errno)));
705 stmpent = fgetspent(opwfile);
709 if (fclose(pwfile)) {
710 D(("error writing entries to shadow file: %s\n", strerror(errno)));
716 if (!rename(SH_TMPFILE, "/etc/shadow"))
717 _log_err(LOG_NOTICE, pamh, "password changed for %s", forwho);
723 if (SELINUX_ENABLED) {
724 if (setfscreatecon(prev_context)) {
728 freecon(prev_context);
737 return PAM_AUTHTOK_ERR;
741 static int _do_setpass(pam_handle_t* pamh, const char *forwho,
742 const char *fromwhat,
743 char *towhat, unsigned int ctrl, int remember)
745 struct passwd *pwd = NULL;
752 pwd = getpwnam(forwho);
755 retval = PAM_AUTHTOK_ERR;
759 if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
760 if ((master=getNISserver(pamh)) != NULL) {
761 struct timeval timeout;
762 struct yppasswd yppwd;
767 /* Unlock passwd file to avoid deadlock */
773 /* Initialize password information */
774 yppwd.newpw.pw_passwd = pwd->pw_passwd;
775 yppwd.newpw.pw_name = pwd->pw_name;
776 yppwd.newpw.pw_uid = pwd->pw_uid;
777 yppwd.newpw.pw_gid = pwd->pw_gid;
778 yppwd.newpw.pw_gecos = pwd->pw_gecos;
779 yppwd.newpw.pw_dir = pwd->pw_dir;
780 yppwd.newpw.pw_shell = pwd->pw_shell;
781 yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
782 yppwd.newpw.pw_passwd = towhat;
784 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
786 /* The yppasswd.x file said `unix authentication required',
787 * so I added it. This is the only reason it is in here.
788 * My yppasswdd doesn't use it, but maybe some others out there
791 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
792 clnt->cl_auth = authunix_create_default();
793 memset((char *) &status, '\0', sizeof(status));
796 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
797 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
798 (xdrproc_t) xdr_int, (char *) &status,
801 free (yppwd.oldpass);
804 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
807 D(("Error while changing NIS password.\n"));
809 D(("The password has%s been changed on %s.",
810 (err || status) ? " not" : "", master));
811 _log_err(LOG_NOTICE, pamh, "password%s changed for %s on %s",
812 (err || status) ? " not" : "", pwd->pw_name, master);
814 auth_destroy(clnt->cl_auth);
817 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
818 _("NIS password could not be changed."));
819 retval = PAM_TRY_AGAIN;
825 retval = PAM_TRY_AGAIN;
829 if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
833 /* These values for the number of attempts and the sleep time
834 are, of course, completely arbitrary.
835 My reading of the PAM docs is that, once pam_chauthtok() has been
836 called with PAM_UPDATE_AUTHTOK, we are obliged to take any
837 reasonable steps to make sure the token is updated; so retrying
838 for 1/10 sec. isn't overdoing it. */
839 while((retval = lckpwdf()) != 0 && i < 100) {
844 return PAM_AUTHTOK_LOCK_BUSY;
848 /* first, save old password */
849 if (save_old_password(pamh, forwho, fromwhat, remember)) {
850 retval = PAM_AUTHTOK_ERR;
853 if (on(UNIX_SHADOW, ctrl) || _unix_shadowed(pwd)) {
854 retval = _update_shadow(pamh, forwho, towhat);
856 if (retval != PAM_SUCCESS && SELINUX_ENABLED)
857 retval = _unix_run_shadow_binary(pamh, ctrl, forwho, fromwhat, towhat);
859 if (retval == PAM_SUCCESS)
860 if (!_unix_shadowed(pwd))
861 retval = _update_passwd(pamh, forwho, "x");
863 retval = _update_passwd(pamh, forwho, towhat);
876 static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
878 struct passwd *pwd = NULL; /* Password and shadow password */
879 struct spwd *spwdent = NULL; /* file entries for the user */
881 int retval = PAM_SUCCESS;
883 /* UNIX passwords area */
884 pwd = getpwnam(user); /* Get password file entry... */
886 return PAM_AUTHINFO_UNAVAIL; /* We don't need to do the rest... */
888 if (_unix_shadowed(pwd)) {
889 /* ...and shadow password file entry for this user, if shadowing
892 spwdent = getspnam(user);
896 if (spwdent == NULL && SELINUX_ENABLED )
897 spwdent = _unix_run_verify_binary(pamh, ctrl, user);
900 return PAM_AUTHINFO_UNAVAIL;
902 if (strcmp(pwd->pw_passwd,"*NP*") == 0) { /* NIS+ */
905 save_uid = geteuid();
906 seteuid (pwd->pw_uid);
907 spwdent = getspnam( user );
911 return PAM_AUTHINFO_UNAVAIL;
916 if (spwdent != NULL) {
917 /* We have the user's information, now let's check if their account
918 has expired (60 * 60 * 24 = number of seconds in a day) */
920 if (off(UNIX__IAMROOT, ctrl)) {
921 /* Get the current number of days since 1970 */
922 curdays = time(NULL) / (60 * 60 * 24);
923 if ((curdays < (spwdent->sp_lstchg + spwdent->sp_min))
924 && (spwdent->sp_min != -1))
925 retval = PAM_AUTHTOK_ERR;
926 else if ((curdays > (spwdent->sp_lstchg + spwdent->sp_max + spwdent->sp_inact))
927 && (spwdent->sp_max != -1) && (spwdent->sp_inact != -1)
928 && (spwdent->sp_lstchg != 0))
930 * Their password change has been put off too long,
932 retval = PAM_ACCT_EXPIRED;
933 else if ((curdays > spwdent->sp_expire) && (spwdent->sp_expire != -1)
934 && (spwdent->sp_lstchg != 0))
936 * OR their account has just plain expired
938 retval = PAM_ACCT_EXPIRED;
944 static int _pam_unix_approve_pass(pam_handle_t * pamh
946 ,const char *pass_old
947 ,const char *pass_new)
950 const char *remark = NULL;
951 int retval = PAM_SUCCESS;
953 D(("&new=%p, &old=%p", pass_old, pass_new));
954 D(("new=[%s]", pass_new));
955 D(("old=[%s]", pass_old));
957 if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
958 if (on(UNIX_DEBUG, ctrl)) {
959 _log_err(LOG_DEBUG, pamh, "bad authentication token");
961 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
962 _("No password supplied") : _("Password unchanged"));
963 return PAM_AUTHTOK_ERR;
966 * if one wanted to hardwire authentication token strength
967 * checking this would be the place - AGM
970 retval = pam_get_item(pamh, PAM_USER, &user);
971 if (retval != PAM_SUCCESS) {
972 if (on(UNIX_DEBUG, ctrl)) {
973 _log_err(LOG_ERR, pamh, "Can not get username");
974 return PAM_AUTHTOK_ERR;
977 if (off(UNIX__IAMROOT, ctrl)) {
979 remark = FascistCheck (pass_new, CRACKLIB_DICTS);
980 D(("called cracklib [%s]", remark));
982 if (strlen(pass_new) < 6)
983 remark = _("You must choose a longer password");
984 D(("length check [%s]", remark));
986 if (on(UNIX_REMEMBER_PASSWD, ctrl)) {
987 if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR)
988 remark = _("Password has been already used. Choose another.");
989 if (retval == PAM_ABORT) {
990 _log_err(LOG_ERR, pamh, "can't open %s file to check old passwords",
997 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
998 retval = PAM_AUTHTOK_ERR;
1004 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
1005 int argc, const char **argv)
1007 unsigned int ctrl, lctrl;
1011 /* <DO NOT free() THESE> */
1013 const void *pass_old, *pass_new;
1014 /* </DO NOT free() THESE> */
1018 ctrl = _set_ctrl(pamh, flags, &remember, argc, argv);
1021 * First get the name of a user
1023 retval = pam_get_user(pamh, &user, NULL);
1024 if (retval == PAM_SUCCESS) {
1026 * Various libraries at various times have had bugs related to
1027 * '+' or '-' as the first character of a user name. Don't take
1028 * any chances here. Require that the username starts with an
1029 * alphanumeric character.
1031 if (user == NULL || !isalnum(*user)) {
1032 _log_err(LOG_ERR, pamh, "bad username [%s]", user);
1033 return PAM_USER_UNKNOWN;
1035 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
1036 _log_err(LOG_DEBUG, pamh, "username [%s] obtained",
1039 if (on(UNIX_DEBUG, ctrl))
1040 _log_err(LOG_DEBUG, pamh,
1041 "password - could not identify user");
1045 D(("Got username of %s", user));
1048 * Before we do anything else, check to make sure that the user's
1049 * info is in one of the databases we can modify from this module,
1050 * which currently is 'files' and 'nis'. We have to do this because
1051 * getpwnam() doesn't tell you *where* the information it gives you
1052 * came from, nor should it. That's our job.
1054 if (_unix_comesfromsource(pamh, user, 1, 1) == 0) {
1055 _log_err(LOG_DEBUG, pamh,
1056 "user \"%s\" does not exist in /etc/passwd or NIS",
1058 return PAM_USER_UNKNOWN;
1061 _unix_getpwnam(pamh, user, 1, 1, &pwd);
1063 _log_err(LOG_DEBUG, pamh,
1064 "user \"%s\" has corrupted passwd entry",
1066 return PAM_USER_UNKNOWN;
1068 if (!_unix_shadowed(pwd) &&
1069 (strchr(pwd->pw_passwd, '*') != NULL)) {
1070 _log_err(LOG_DEBUG, pamh,
1071 "user \"%s\" does not have modifiable password",
1073 return PAM_USER_UNKNOWN;
1078 * This is not an AUTH module!
1080 if (on(UNIX__NONULL, ctrl))
1081 set(UNIX__NULLOK, ctrl);
1083 if (on(UNIX__PRELIM, ctrl)) {
1085 * obtain and verify the current password (OLDAUTHTOK) for
1090 D(("prelim check"));
1092 if (_unix_blankpasswd(pamh, ctrl, user)) {
1094 } else if (off(UNIX__IAMROOT, ctrl)) {
1096 /* instruct user what is happening */
1097 #define greeting "Changing password for "
1098 Announce = (char *) malloc(sizeof(greeting) + strlen(user));
1099 if (Announce == NULL) {
1100 _log_err(LOG_CRIT, pamh,
1101 "password - out of memory");
1104 (void) strcpy(Announce, greeting);
1105 (void) strcpy(Announce + sizeof(greeting) - 1, user);
1109 set(UNIX__OLD_PASSWD, lctrl);
1110 retval = _unix_read_password(pamh, lctrl
1112 ,"(current) UNIX password: "
1118 if (retval != PAM_SUCCESS) {
1119 _log_err(LOG_NOTICE, pamh
1120 ,"password - (old) token not obtained");
1123 /* verify that this is the password for this user */
1125 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
1127 D(("process run by root so do nothing this time around"));
1129 retval = PAM_SUCCESS; /* root doesn't have too */
1132 if (retval != PAM_SUCCESS) {
1133 D(("Authentication failed"));
1137 retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old);
1139 if (retval != PAM_SUCCESS) {
1140 _log_err(LOG_CRIT, pamh,
1141 "failed to set PAM_OLDAUTHTOK");
1143 retval = _unix_verify_shadow(pamh,user, ctrl);
1144 if (retval == PAM_AUTHTOK_ERR) {
1145 if (off(UNIX__IAMROOT, ctrl))
1146 _make_remark(pamh, ctrl, PAM_ERROR_MSG,
1147 _("You must wait longer to change your password"));
1149 retval = PAM_SUCCESS;
1151 } else if (on(UNIX__UPDATE, ctrl)) {
1153 * tpass is used below to store the _pam_md() return; it
1154 * should be _pam_delete()'d.
1161 * obtain the proposed password
1167 * get the old token back. NULL was ok only if root [at this
1168 * point we assume that this has already been enforced on a
1169 * previous call to this function].
1172 if (off(UNIX_NOT_SET_PASS, ctrl)) {
1173 retval = pam_get_item(pamh, PAM_OLDAUTHTOK
1176 retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK
1178 if (retval == PAM_NO_MODULE_DATA) {
1179 retval = PAM_SUCCESS;
1183 D(("pass_old [%s]", pass_old));
1185 if (retval != PAM_SUCCESS) {
1186 _log_err(LOG_NOTICE, pamh, "user not authenticated");
1190 D(("get new password now"));
1194 if (on(UNIX_USE_AUTHTOK, lctrl)) {
1195 set(UNIX_USE_FIRST_PASS, lctrl);
1198 retval = PAM_AUTHTOK_ERR;
1199 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
1201 * use_authtok is to force the use of a previously entered
1202 * password -- needed for pluggable password strength checking
1205 retval = _unix_read_password(pamh, lctrl
1207 ,"Enter new UNIX password: "
1208 ,"Retype new UNIX password: "
1212 if (retval != PAM_SUCCESS) {
1213 if (on(UNIX_DEBUG, ctrl)) {
1214 _log_err(LOG_ALERT, pamh
1215 ,"password - new password not obtained");
1217 pass_old = NULL; /* tidy up */
1220 D(("returned to _unix_chauthtok"));
1223 * At this point we know who the user is and what they
1224 * propose as their new password. Verify that the new
1225 * password is acceptable.
1228 if (*(const char *)pass_new == '\0') { /* "\0" password = NULL */
1231 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
1234 if (retval != PAM_SUCCESS) {
1235 _log_err(LOG_NOTICE, pamh,
1236 "new password not acceptable");
1237 pass_new = pass_old = NULL; /* tidy up */
1241 /* These values for the number of attempts and the sleep time
1242 are, of course, completely arbitrary.
1243 My reading of the PAM docs is that, once pam_chauthtok() has been
1244 called with PAM_UPDATE_AUTHTOK, we are obliged to take any
1245 reasonable steps to make sure the token is updated; so retrying
1246 for 1/10 sec. isn't overdoing it. */
1248 while((retval = lckpwdf()) != 0 && i < 100) {
1253 return PAM_AUTHTOK_LOCK_BUSY;
1258 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
1259 if (retval != PAM_SUCCESS) {
1260 _log_err(LOG_NOTICE, pamh, "user password changed by another process");
1268 retval = _unix_verify_shadow(pamh, user, ctrl);
1269 if (retval != PAM_SUCCESS) {
1270 _log_err(LOG_NOTICE, pamh, "user not authenticated 2");
1277 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new);
1278 if (retval != PAM_SUCCESS) {
1279 _log_err(LOG_NOTICE, pamh,
1280 "new password not acceptable 2");
1281 pass_new = pass_old = NULL; /* tidy up */
1289 * By reaching here we have approved the passwords and must now
1290 * rebuild the password database file.
1294 * First we encrypt the new password.
1297 if (on(UNIX_MD5_PASS, ctrl)) {
1298 tpass = crypt_md5_wrapper(pass_new);
1301 * Salt manipulation is stolen from Rick Faith's passwd
1302 * program. Sorry Rick :) -- alex
1309 salt[0] = bin_to_ascii(tm & 0x3f);
1310 salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
1313 if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) {
1315 * to avoid using the _extensions_ of the bigcrypt()
1316 * function we truncate the newly entered password
1317 * [Problems that followed from this are fixed as per
1320 char *temp = malloc(9);
1323 _log_err(LOG_CRIT, pamh,
1324 "out of memory for password");
1325 pass_new = pass_old = NULL; /* tidy up */
1331 /* copy first 8 bytes of password */
1332 strncpy(temp, pass_new, 8);
1335 /* no longer need cleartext */
1336 tpass = bigcrypt(temp, salt);
1338 _pam_delete(temp); /* tidy up */
1340 tpass = bigcrypt(pass_new, salt);
1344 D(("password processed"));
1346 /* update the password database(s) -- race conditions..? */
1348 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
1350 /* _do_setpass has called ulckpwdf for us */
1353 pass_old = pass_new = NULL;
1354 } else { /* something has broken with the module */
1355 _log_err(LOG_ALERT, pamh,
1356 "password received unknown request");
1360 D(("retval was %d", retval));
1366 /* static module data */
1368 struct pam_module _pam_unix_passwd_modstruct = {