PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, int argc, const char **argv) { unsigned int ctrl, lctrl; int retval, i; int remember = -1; /* <DO NOT free() THESE> */ const char *user; char *pass_old, *pass_new; /* </DO NOT free() THESE> */ D(("called.")); #ifdef USE_LCKPWDF /* our current locking system requires that we lock the entire password database. This avoids both livelock and deadlock. */ /* These values for the number of attempts and the sleep time are, of course, completely arbitrary. My reading of the PAM docs is that, once pam_chauthtok() has been called with PAM_UPDATE_AUTHTOK, we are obliged to take any reasonable steps to make sure the token is updated; so retrying for 1/10 sec. isn't overdoing it. The other possibility is to call lckpwdf() on the first pam_chauthtok() pass, and hold the lock until released in the second pass--but is this guaranteed to work? -SRL */ i=0; while((retval = lckpwdf()) != 0 && i < 100) { usleep(1000); } if(retval != 0) { return PAM_AUTHTOK_LOCK_BUSY; } #endif ctrl = _set_ctrl(pamh, flags, &remember, argc, argv); /* * First get the name of a user */ retval = pam_get_user(pamh, &user, "Username: "******"bad username [%s]", user); #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_USER_UNKNOWN; } if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl)) _log_err(LOG_DEBUG, pamh, "username [%s] obtained", user); } else { if (on(UNIX_DEBUG, ctrl)) _log_err(LOG_DEBUG, pamh, "password - could not identify user"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } D(("Got username of %s", user)); /* * This is not an AUTH module! */ if (on(UNIX__NONULL, ctrl)) set(UNIX__NULLOK, ctrl); if (on(UNIX__PRELIM, ctrl)) { /* * obtain and verify the current password (OLDAUTHTOK) for * the user. */ char *Announce; D(("prelim check")); if (_unix_blankpasswd(ctrl, user)) { #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_SUCCESS; } else if (off(UNIX__IAMROOT, ctrl)) { /* instruct user what is happening */ #define greeting "Changing password for " Announce = (char *) malloc(sizeof(greeting) + strlen(user)); if (Announce == NULL) { _log_err(LOG_CRIT, pamh, "password - out of memory"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_BUF_ERR; } (void) strcpy(Announce, greeting); (void) strcpy(Announce + sizeof(greeting) - 1, user); #undef greeting lctrl = ctrl; set(UNIX__OLD_PASSWD, lctrl); retval = _unix_read_password(pamh, lctrl ,Announce ,"(current) UNIX password: "******"password - (old) token not obtained"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } /* verify that this is the password for this user */ retval = _unix_verify_password(pamh, user, pass_old, ctrl); } else { D(("process run by root so do nothing this time around")); pass_old = NULL; retval = PAM_SUCCESS; /* root doesn't have too */ } if (retval != PAM_SUCCESS) { D(("Authentication failed")); pass_old = NULL; #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); pass_old = NULL; if (retval != PAM_SUCCESS) { _log_err(LOG_CRIT, pamh, "failed to set PAM_OLDAUTHTOK"); } retval = _unix_verify_shadow(user, ctrl); if (retval == PAM_AUTHTOK_ERR) { if (off(UNIX__IAMROOT, ctrl)) _make_remark(pamh, ctrl, PAM_ERROR_MSG, "You must wait longer to change your password"); else retval = PAM_SUCCESS; } } else if (on(UNIX__UPDATE, ctrl)) { /* * tpass is used below to store the _pam_md() return; it * should be _pam_delete()'d. */ char *tpass = NULL; int retry = 0; /* * obtain the proposed password */ D(("do update")); /* * get the old token back. NULL was ok only if root [at this * point we assume that this has already been enforced on a * previous call to this function]. */ if (off(UNIX_NOT_SET_PASS, ctrl)) { retval = pam_get_item(pamh, PAM_OLDAUTHTOK ,(const void **) &pass_old); } else { retval = pam_get_data(pamh, _UNIX_OLD_AUTHTOK ,(const void **) &pass_old); if (retval == PAM_NO_MODULE_DATA) { retval = PAM_SUCCESS; pass_old = NULL; } } D(("pass_old [%s]", pass_old)); if (retval != PAM_SUCCESS) { _log_err(LOG_NOTICE, pamh, "user not authenticated"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } retval = _unix_verify_shadow(user, ctrl); if (retval != PAM_SUCCESS) { _log_err(LOG_NOTICE, pamh, "user not authenticated 2"); #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } D(("get new password now")); lctrl = ctrl; if (on(UNIX_USE_AUTHTOK, lctrl)) { set(UNIX_USE_FIRST_PASS, lctrl); } retry = 0; retval = PAM_AUTHTOK_ERR; while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { /* * use_authtok is to force the use of a previously entered * password -- needed for pluggable password strength checking */ retval = _unix_read_password(pamh, lctrl ,NULL ,"Enter new UNIX password: "******"Retype new UNIX password: "******"password - new password not obtained"); } pass_old = NULL; /* tidy up */ #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } D(("returned to _unix_chauthtok")); /* * At this point we know who the user is and what they * propose as their new password. Verify that the new * password is acceptable. */ if (pass_new[0] == '\0') { /* "\0" password = NULL */ pass_new = NULL; } retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new); } if (retval != PAM_SUCCESS) { _log_err(LOG_NOTICE, pamh, "new password not acceptable"); _pam_overwrite(pass_new); _pam_overwrite(pass_old); pass_new = pass_old = NULL; /* tidy up */ #ifdef USE_LCKPWDF ulckpwdf(); #endif return retval; } /* * By reaching here we have approved the passwords and must now * rebuild the password database file. */ /* * First we encrypt the new password. */ if (on(UNIX_MD5_PASS, ctrl)) { tpass = crypt_md5_wrapper(pass_new); } else { /* * Salt manipulation is stolen from Rick Faith's passwd * program. Sorry Rick :) -- alex */ time_t tm; char salt[3]; time(&tm); salt[0] = bin_to_ascii(tm & 0x3f); salt[1] = bin_to_ascii((tm >> 6) & 0x3f); salt[2] = '\0'; if (off(UNIX_BIGCRYPT, ctrl) && strlen(pass_new) > 8) { /* * to avoid using the _extensions_ of the bigcrypt() * function we truncate the newly entered password */ char *temp = malloc(9); char *e; if (temp == NULL) { _log_err(LOG_CRIT, pamh, "out of memory for password"); _pam_overwrite(pass_new); _pam_overwrite(pass_old); pass_new = pass_old = NULL; /* tidy up */ #ifdef USE_LCKPWDF ulckpwdf(); #endif return PAM_BUF_ERR; } /* copy first 8 bytes of password */ strncpy(temp, pass_new, 8); temp[8] = '\0'; /* no longer need cleartext */ e = bigcrypt(temp, salt); tpass = x_strdup(e); _pam_overwrite(e); _pam_delete(temp); /* tidy up */ } else { char *e; /* no longer need cleartext */ e = bigcrypt(pass_new, salt); tpass = x_strdup(e); _pam_overwrite(e); } } D(("password processed")); /* update the password database(s) -- race conditions..? */ retval = _do_setpass(pamh, user, pass_old, tpass, ctrl, remember); _pam_overwrite(pass_new); _pam_overwrite(pass_old); _pam_delete(tpass); pass_old = pass_new = NULL; } else { /* something has broken with the module */
int save_old_password(pam_handle_t *pamh, const char *forwho, const char *oldpass, int howmany) #endif { static char buf[16384]; static char nbuf[16384]; char *s_luser, *s_uid, *s_npas, *s_pas, *pass; int npas; FILE *pwfile, *opwfile; int err = 0; int oldmask; int found = 0; struct passwd *pwd = NULL; struct stat st; #ifdef WITH_SELINUX security_context_t prev_context=NULL; #endif if (howmany < 0) { return PAM_SUCCESS; } if (oldpass == NULL) { return PAM_SUCCESS; } oldmask = umask(077); #ifdef WITH_SELINUX if (SELINUX_ENABLED) { security_context_t passwd_context=NULL; if (getfilecon("/etc/passwd",&passwd_context)<0) { return PAM_AUTHTOK_ERR; }; if (getfscreatecon(&prev_context)<0) { freecon(passwd_context); return PAM_AUTHTOK_ERR; } if (setfscreatecon(passwd_context)) { freecon(passwd_context); freecon(prev_context); return PAM_AUTHTOK_ERR; } freecon(passwd_context); } #endif pwfile = fopen(OPW_TMPFILE, "w"); umask(oldmask); if (pwfile == NULL) { err = 1; goto done; } opwfile = fopen(OLD_PASSWORDS_FILE, "r"); if (opwfile == NULL) { fclose(pwfile); err = 1; goto done; } if (fstat(fileno(opwfile), &st) == -1) { fclose(opwfile); fclose(pwfile); err = 1; goto done; } if (fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) { fclose(opwfile); fclose(pwfile); err = 1; goto done; } if (fchmod(fileno(pwfile), st.st_mode) == -1) { fclose(opwfile); fclose(pwfile); err = 1; goto done; } while (fgets(buf, 16380, opwfile)) { if (!strncmp(buf, forwho, strlen(forwho))) { char *sptr = NULL; found = 1; if (howmany == 0) continue; buf[strlen(buf) - 1] = '\0'; s_luser = strtok_r(buf, ":", &sptr); s_uid = strtok_r(NULL, ":", &sptr); s_npas = strtok_r(NULL, ":", &sptr); s_pas = strtok_r(NULL, ":", &sptr); npas = strtol(s_npas, NULL, 10) + 1; while (npas > howmany) { s_pas = strpbrk(s_pas, ","); if (s_pas != NULL) s_pas++; npas--; } pass = crypt_md5_wrapper(oldpass); if (s_pas == NULL) snprintf(nbuf, sizeof(nbuf), "%s:%s:%d:%s\n", s_luser, s_uid, npas, pass); else snprintf(nbuf, sizeof(nbuf),"%s:%s:%d:%s,%s\n", s_luser, s_uid, npas, s_pas, pass); _pam_delete(pass); if (fputs(nbuf, pwfile) < 0) { err = 1; break; } } else if (fputs(buf, pwfile) < 0) { err = 1; break; } } fclose(opwfile); if (!found) { pwd = pam_modutil_getpwnam(pamh, forwho); if (pwd == NULL) { err = 1; } else { pass = crypt_md5_wrapper(oldpass); snprintf(nbuf, sizeof(nbuf), "%s:%lu:1:%s\n", forwho, (unsigned long)pwd->pw_uid, pass); _pam_delete(pass); if (fputs(nbuf, pwfile) < 0) { err = 1; } } } if (fflush(pwfile) || fsync(fileno(pwfile))) { D(("fflush or fsync error writing entries to old passwords file: %m")); err = 1; } if (fclose(pwfile)) { D(("fclose error writing entries to old passwords file: %m")); err = 1; } done: if (!err) { if (rename(OPW_TMPFILE, OLD_PASSWORDS_FILE)) err = 1; } #ifdef WITH_SELINUX if (SELINUX_ENABLED) { if (setfscreatecon(prev_context)) { err = 1; } if (prev_context) freecon(prev_context); prev_context=NULL; } #endif if (!err) { return PAM_SUCCESS; } else { unlink(OPW_TMPFILE); return PAM_AUTHTOK_ERR; } }
static int save_old_password(const char *forwho, const char *oldpass, int howmany) { static char buf[16384]; static char nbuf[16384]; char *s_luser, *s_uid, *s_npas, *s_pas, *pass; int retval = 0, npas; FILE *pwfile, *opwfile; int err = 0; int oldmask; int found = 0; struct passwd *pwd = NULL; if (howmany < 0) return retval; if (oldpass == NULL) return retval; oldmask = umask(077); pwfile = fopen(OPW_TMPFILE, "w"); umask(oldmask); opwfile = fopen(OLD_PASSWORDS_FILE, "r"); if (pwfile == NULL || opwfile == NULL) return PAM_AUTHTOK_ERR; chown(OPW_TMPFILE, 0, 0); chmod(OPW_TMPFILE, 0600); while (fgets(buf, 16380, opwfile)) { if (!strncmp(buf, forwho, strlen(forwho))) { buf[strlen(buf) - 1] = '\0'; s_luser = strtok(buf, ":"); s_uid = strtok(NULL, ":"); s_npas = strtok(NULL, ":"); s_pas = strtok(NULL, ":"); npas = strtol(s_npas, NULL, 10) + 1; while (npas > howmany) { s_pas = strpbrk(s_pas, ","); if (s_pas != NULL) s_pas++; npas--; } pass = crypt_md5_wrapper(oldpass); if (s_pas == NULL) sprintf(nbuf, "%s:%s:%d:%s\n", s_luser, s_uid, npas, pass); else sprintf(nbuf, "%s:%s:%d:%s,%s\n", s_luser, s_uid, npas, s_pas, pass); if (fputs(nbuf, pwfile) < 0) { retval = PAM_AUTHTOK_ERR; err = 1; break; } found = 1; } else if (fputs(buf, pwfile) < 0) { retval = PAM_AUTHTOK_ERR; err = 1; break; } } fclose(opwfile); if (!found) { pwd = getpwnam(forwho); if (pwd == NULL) { retval = PAM_AUTHTOK_ERR; err = 1; } else { pass = crypt_md5_wrapper(oldpass); sprintf(nbuf, "%s:%d:1:%s\n", forwho, pwd->pw_uid, pass); if (fputs(nbuf, pwfile) < 0) { retval = PAM_AUTHTOK_ERR; err = 1; } } } if (fclose(pwfile)) { fprintf(stderr, "error writing entries to old passwords file: %s\n", strerror(errno)); retval = PAM_AUTHTOK_ERR; err = 1; } if (!err) rename(OPW_TMPFILE, OLD_PASSWORDS_FILE); else unlink(OPW_TMPFILE); return retval; }
PAMH_ARG_DECL(char * create_password_hash, const char *password, unsigned int ctrl, int rounds) { const char *algoid; char salt[64]; /* contains rounds number + max 16 bytes of salt + algo id */ char *sp; if (on(UNIX_MD5_PASS, ctrl)) { /* algoid = "$1" */ return crypt_md5_wrapper(password); } else if (on(UNIX_BLOWFISH_PASS, ctrl)) { algoid = "$2a$"; } else if (on(UNIX_SHA256_PASS, ctrl)) { algoid = "$5$"; } else if (on(UNIX_SHA512_PASS, ctrl)) { algoid = "$6$"; } else { /* must be crypt/bigcrypt */ char tmppass[9]; char *crypted; crypt_make_salt(salt, 2); if (off(UNIX_BIGCRYPT, ctrl) && strlen(password) > 8) { strncpy(tmppass, password, sizeof(tmppass)-1); tmppass[sizeof(tmppass)-1] = '\0'; password = tmppass; } crypted = bigcrypt(password, salt); memset(tmppass, '\0', sizeof(tmppass)); password = NULL; return crypted; } #ifdef HAVE_CRYPT_GENSALT_R if (on(UNIX_BLOWFISH_PASS, ctrl)) { char entropy[17]; crypt_make_salt(entropy, sizeof(entropy) - 1); sp = crypt_gensalt_r (algoid, rounds, entropy, sizeof(entropy), salt, sizeof(salt)); } else { #endif sp = stpcpy(salt, algoid); if (on(UNIX_ALGO_ROUNDS, ctrl)) { sp += snprintf(sp, sizeof(salt) - 3, "rounds=%u$", rounds); } crypt_make_salt(sp, 8); /* For now be conservative so the resulting hashes * are not too long. 8 bytes of salt prevents dictionary * attacks well enough. */ #ifdef HAVE_CRYPT_GENSALT_R } #endif sp = crypt(password, salt); if (strncmp(algoid, sp, strlen(algoid)) != 0) { /* libxcrypt/libc doesn't know the algorithm, use MD5 */ pam_syslog(pamh, LOG_ERR, "Algo %s not supported by the crypto backend, " "falling back to MD5\n", on(UNIX_BLOWFISH_PASS, ctrl) ? "blowfish" : on(UNIX_SHA256_PASS, ctrl) ? "sha256" : on(UNIX_SHA512_PASS, ctrl) ? "sha512" : algoid); memset(sp, '\0', strlen(sp)); return crypt_md5_wrapper(password); } return x_strdup(sp); }