diff options
author | des <des@FreeBSD.org> | 2002-04-04 15:50:47 +0000 |
---|---|---|
committer | des <des@FreeBSD.org> | 2002-04-04 15:50:47 +0000 |
commit | 4532f219fa85b9c06b3f4caaa4dcbbdbf3bd32f5 (patch) | |
tree | 1dcc5978e4a3b724654b82d0e61f6dc3e94705ee /contrib/pam_modules/pam_passwdqc/pam_passwdqc.c | |
download | FreeBSD-src-4532f219fa85b9c06b3f4caaa4dcbbdbf3bd32f5.zip FreeBSD-src-4532f219fa85b9c06b3f4caaa4dcbbdbf3bd32f5.tar.gz |
Vendor import of Solar Designer's pam_passwdqc module.
Diffstat (limited to 'contrib/pam_modules/pam_passwdqc/pam_passwdqc.c')
-rw-r--r-- | contrib/pam_modules/pam_passwdqc/pam_passwdqc.c | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/contrib/pam_modules/pam_passwdqc/pam_passwdqc.c b/contrib/pam_modules/pam_passwdqc/pam_passwdqc.c new file mode 100644 index 0000000..7cc0c81 --- /dev/null +++ b/contrib/pam_modules/pam_passwdqc/pam_passwdqc.c @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2000,2001 by Solar Designer. See LICENSE. + */ + +#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE_EXTENDED +#define _XOPEN_VERSION 500 +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <limits.h> +#include <unistd.h> +#include <pwd.h> +#ifdef HAVE_SHADOW +#include <shadow.h> +#endif + +#define PAM_SM_PASSWORD +#ifndef LINUX_PAM +#include <security/pam_appl.h> +#endif +#include <security/pam_modules.h> + +#include "pam_macros.h" + +#if !defined(PAM_EXTERN) && !defined(PAM_STATIC) +#define PAM_EXTERN extern +#endif + +#if !defined(PAM_AUTHTOK_RECOVER_ERR) && defined(PAM_AUTHTOK_RECOVERY_ERR) +#define PAM_AUTHTOK_RECOVER_ERR PAM_AUTHTOK_RECOVERY_ERR +#endif + +#if defined(__sun__) && !defined(LINUX_PAM) +#define linux_const /* Sun's PAM doesn't use const here */ +#else +#define linux_const const +#endif +typedef linux_const void *pam_item_t; + +#include "passwdqc.h" + +#define F_ENFORCE_MASK 0x00000003 +#define F_ENFORCE_USERS 0x00000001 +#define F_ENFORCE_ROOT 0x00000002 +#define F_ENFORCE_EVERYONE F_ENFORCE_MASK +#define F_NON_UNIX 0x00000004 +#define F_ASK_OLDAUTHTOK_MASK 0x00000030 +#define F_ASK_OLDAUTHTOK_PRELIM 0x00000010 +#define F_ASK_OLDAUTHTOK_UPDATE 0x00000020 +#define F_CHECK_OLDAUTHTOK 0x00000040 +#define F_USE_FIRST_PASS 0x00000100 +#define F_USE_AUTHTOK 0x00000200 + +typedef struct { + passwdqc_params_t qc; + int flags; + int retry; +} params_t; + +static params_t defaults = { + { + {INT_MAX, 24, 12, 8, 7}, /* min */ + 40, /* max */ + 3, /* passphrase_words */ + 4, /* match_length */ + 1, /* similar_deny */ + 42 /* random_bits */ + }, + F_ENFORCE_EVERYONE, /* flags */ + 3 /* retry */ +}; + +#define PROMPT_OLDPASS \ + "Enter current password: " +#define PROMPT_NEWPASS1 \ + "Enter new password: " +#define PROMPT_NEWPASS2 \ + "Re-type new password: " + +#define MESSAGE_MISCONFIGURED \ + "System configuration error. Please contact your administrator." +#define MESSAGE_INVALID_OPTION \ + "pam_passwdqc: Invalid option: \"%s\"." +#define MESSAGE_INTRO_PASSWORD \ + "\nYou can now choose the new password.\n" +#define MESSAGE_INTRO_BOTH \ + "\nYou can now choose the new password or passphrase.\n" +#define MESSAGE_EXPLAIN_PASSWORD_1 \ + "A valid password should be a mix of upper and lower case letters,\n" \ + "digits and other characters. You can use a%s %d character long\n" \ + "password with characters from at least 3 of these 4 classes.\n" \ + "Characters that form a common pattern are discarded by the check.\n" +#define MESSAGE_EXPLAIN_PASSWORD_2 \ + "A valid password should be a mix of upper and lower case letters,\n" \ + "digits and other characters. You can use a%s %d character long\n" \ + "password with characters from at least 3 of these 4 classes, or\n" \ + "a%s %d character long password containing characters from all the\n" \ + "classes. Characters that form a common pattern are discarded by\n" \ + "the check.\n" +#define MESSAGE_EXPLAIN_PASSPHRASE \ + "A passphrase should be of at least %d words, %d to %d characters\n" \ + "long and contain enough different characters.\n" +#define MESSAGE_RANDOM \ + "Alternatively, if noone else can see your terminal now, you can\n" \ + "pick this as your password: \"%s\".\n" +#define MESSAGE_RANDOMONLY \ + "This system is configured to permit randomly generated passwords\n" \ + "only. If noone else can see your terminal now, you can pick this\n" \ + "as your password: \"%s\". Otherwise, come back later.\n" +#define MESSAGE_RANDOMFAILED \ + "This system is configured to use randomly generated passwords\n" \ + "only, but the attempt to generate a password has failed. This\n" \ + "could happen for a number of reasons: you could have requested\n" \ + "an impossible password length, or the access to kernel random\n" \ + "number pool could have failed." +#define MESSAGE_TOOLONG \ + "This password may be too long for some services. Choose another." +#define MESSAGE_TRUNCATED \ + "Warning: your longer password will be truncated to 8 characters." +#define MESSAGE_WEAKPASS \ + "Weak password: %s." +#define MESSAGE_NOTRANDOM \ + "Sorry, you've mistyped the password that was generated for you." +#define MESSAGE_MISTYPED \ + "Sorry, passwords do not match." +#define MESSAGE_RETRY \ + "Try again." + +static int converse(pam_handle_t *pamh, int style, char *text, + struct pam_response **resp) +{ + struct pam_conv *conv; + struct pam_message msg, *pmsg; + int status; + + status = pam_get_item(pamh, PAM_CONV, (pam_item_t *)&conv); + if (status != PAM_SUCCESS) + return status; + + pmsg = &msg; + msg.msg_style = style; + msg.msg = text; + + *resp = NULL; + return conv->conv(1, (linux_const struct pam_message **)&pmsg, resp, + conv->appdata_ptr); +} + +#ifdef __GNUC__ +__attribute__ ((format (printf, 3, 4))) +#endif +static int say(pam_handle_t *pamh, int style, const char *format, ...) +{ + va_list args; + char buffer[0x800]; + int needed; + struct pam_response *resp; + int status; + + va_start(args, format); + needed = vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + + if (needed > 0 && needed < sizeof(buffer)) { + status = converse(pamh, style, buffer, &resp); + _pam_overwrite(buffer); + } else { + status = PAM_ABORT; + memset(buffer, 0, sizeof(buffer)); + } + + return status; +} + +static int check_max(params_t *params, pam_handle_t *pamh, char *newpass) +{ + if (strlen(newpass) > params->qc.max) { + if (params->qc.max != 8) { + say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG); + return -1; + } + say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED); + } + + return 0; +} + +static int parse(params_t *params, pam_handle_t *pamh, + int argc, const char **argv) +{ + const char *p; + int i; + unsigned long v; + + while (argc) { + if (!strncmp(*argv, "min=", 4)) { + p = *argv + 4; + for (i = 0; i < 5; i++) { + if (!strncmp(p, "disabled", 8)) { + v = INT_MAX; + p += 8; + } else + v = strtoul(p, (char **)&p, 10); + if (i < 4 && *p++ != ',') break; + if (v > INT_MAX) break; + if (i && v > params->qc.min[i - 1]) break; + params->qc.min[i] = v; + } + if (*p) break; + } else + if (!strncmp(*argv, "max=", 4)) { + v = strtoul(*argv + 4, (char **)&p, 10); + if (*p || v < 8 || v > INT_MAX) break; + params->qc.max = v; + } else + if (!strncmp(*argv, "passphrase=", 11)) { + v = strtoul(*argv + 11, (char **)&p, 10); + if (*p || v > INT_MAX) break; + params->qc.passphrase_words = v; + } else + if (!strncmp(*argv, "match=", 6)) { + v = strtoul(*argv + 6, (char **)&p, 10); + if (*p || v > INT_MAX) break; + params->qc.match_length = v; + } else + if (!strncmp(*argv, "similar=", 8)) { + if (!strcmp(*argv + 8, "permit")) + params->qc.similar_deny = 0; + else + if (!strcmp(*argv + 8, "deny")) + params->qc.similar_deny = 1; + else + break; + } else + if (!strncmp(*argv, "random=", 7)) { + v = strtoul(*argv + 7, (char **)&p, 10); + if (!strcmp(p, ",only")) { + p += 5; + params->qc.min[4] = INT_MAX; + } + if (*p || v > INT_MAX) break; + params->qc.random_bits = v; + } else + if (!strncmp(*argv, "enforce=", 8)) { + params->flags &= ~F_ENFORCE_MASK; + if (!strcmp(*argv + 8, "users")) + params->flags |= F_ENFORCE_USERS; + else + if (!strcmp(*argv + 8, "everyone")) + params->flags |= F_ENFORCE_EVERYONE; + else + if (strcmp(*argv + 8, "none")) + break; + } else + if (!strcmp(*argv, "non-unix")) { + if (params->flags & F_CHECK_OLDAUTHTOK) break; + params->flags |= F_NON_UNIX; + } else + if (!strncmp(*argv, "retry=", 6)) { + v = strtoul(*argv + 6, (char **)&p, 10); + if (*p || v > INT_MAX) break; + params->retry = v; + } else + if (!strncmp(*argv, "ask_oldauthtok", 14)) { + params->flags &= ~F_ASK_OLDAUTHTOK_MASK; + if (params->flags & F_USE_FIRST_PASS) break; + if (!strcmp(*argv + 14, "=update")) + params->flags |= F_ASK_OLDAUTHTOK_UPDATE; + else + if (!(*argv)[14]) + params->flags |= F_ASK_OLDAUTHTOK_PRELIM; + else + break; + } else + if (!strcmp(*argv, "check_oldauthtok")) { + if (params->flags & F_NON_UNIX) break; + params->flags |= F_CHECK_OLDAUTHTOK; + } else + if (!strcmp(*argv, "use_first_pass")) { + if (params->flags & F_ASK_OLDAUTHTOK_MASK) break; + params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK; + } else + if (!strcmp(*argv, "use_authtok")) { + params->flags |= F_USE_AUTHTOK; + } else + break; + argc--; argv++; + } + + if (argc) { + say(pamh, PAM_ERROR_MSG, getuid() != 0 ? + MESSAGE_MISCONFIGURED : MESSAGE_INVALID_OPTION, *argv); + return PAM_ABORT; + } + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + params_t params; + struct pam_response *resp; + struct passwd *pw, fake_pw; +#ifdef HAVE_SHADOW + struct spwd *spw; +#endif + char *user, *oldpass, *newpass, *randompass; + char *reason; + int ask_oldauthtok; + int randomonly, enforce, retries_left, retry_wanted; + int status; + + params = defaults; + status = parse(¶ms, pamh, argc, argv); + if (status != PAM_SUCCESS) + return status; + + ask_oldauthtok = 0; + if (flags & PAM_PRELIM_CHECK) { + if (params.flags & F_ASK_OLDAUTHTOK_PRELIM) + ask_oldauthtok = 1; + } else + if (flags & PAM_UPDATE_AUTHTOK) { + if (params.flags & F_ASK_OLDAUTHTOK_UPDATE) + ask_oldauthtok = 1; + } else + return PAM_SERVICE_ERR; + + if (ask_oldauthtok && getuid() != 0) { + status = converse(pamh, PAM_PROMPT_ECHO_OFF, + PROMPT_OLDPASS, &resp); + + if (status == PAM_SUCCESS) { + if (resp && resp->resp) { + status = pam_set_item(pamh, + PAM_OLDAUTHTOK, resp->resp); + _pam_drop_reply(resp, 1); + } else + status = PAM_AUTHTOK_RECOVER_ERR; + } + + if (status != PAM_SUCCESS) + return status; + } + + if (flags & PAM_PRELIM_CHECK) + return status; + + status = pam_get_item(pamh, PAM_USER, (pam_item_t *)&user); + if (status != PAM_SUCCESS) + return status; + + status = pam_get_item(pamh, PAM_OLDAUTHTOK, (pam_item_t *)&oldpass); + if (status != PAM_SUCCESS) + return status; + + if (params.flags & F_NON_UNIX) { + pw = &fake_pw; + pw->pw_name = user; + pw->pw_gecos = ""; + } else { + pw = getpwnam(user); + endpwent(); + if (!pw) + return PAM_USER_UNKNOWN; + if ((params.flags & F_CHECK_OLDAUTHTOK) && getuid() != 0) { + if (!oldpass) + status = PAM_AUTH_ERR; + else +#ifdef HAVE_SHADOW + if (!strcmp(pw->pw_passwd, "x")) { + spw = getspnam(user); + endspent(); + if (spw) { + if (strcmp(crypt(oldpass, spw->sp_pwdp), + spw->sp_pwdp)) + status = PAM_AUTH_ERR; + memset(spw->sp_pwdp, 0, + strlen(spw->sp_pwdp)); + } else + status = PAM_AUTH_ERR; + } else +#endif + if (strcmp(crypt(oldpass, pw->pw_passwd), + pw->pw_passwd)) + status = PAM_AUTH_ERR; + } + memset(pw->pw_passwd, 0, strlen(pw->pw_passwd)); + if (status != PAM_SUCCESS) + return status; + } + + randomonly = params.qc.min[4] > params.qc.max; + + if (getuid() != 0) + enforce = params.flags & F_ENFORCE_USERS; + else + enforce = params.flags & F_ENFORCE_ROOT; + + if (params.flags & F_USE_AUTHTOK) { + status = pam_get_item(pamh, PAM_AUTHTOK, + (pam_item_t *)&newpass); + if (status != PAM_SUCCESS) + return status; + if (!newpass || (check_max(¶ms, pamh, newpass) && enforce)) + return PAM_AUTHTOK_RECOVER_ERR; + reason = _passwdqc_check(¶ms.qc, newpass, oldpass, pw); + if (reason) { + say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason); + if (enforce) + status = PAM_AUTHTOK_RECOVER_ERR; + } + return status; + } + + retries_left = params.retry; + +retry: + retry_wanted = 0; + + if (!randomonly && + params.qc.passphrase_words && params.qc.min[2] <= params.qc.max) + status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH); + else + status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD); + if (status != PAM_SUCCESS) + return status; + + if (!randomonly && params.qc.min[3] <= params.qc.min[4]) + status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_1, + params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "", + params.qc.min[3]); + else + if (!randomonly) + status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_2, + params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "", + params.qc.min[3], + params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "", + params.qc.min[4]); + if (status != PAM_SUCCESS) + return status; + + if (!randomonly && + params.qc.passphrase_words && + params.qc.min[2] <= params.qc.max) { + status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE, + params.qc.passphrase_words, + params.qc.min[2], params.qc.max); + if (status != PAM_SUCCESS) + return status; + } + + randompass = _passwdqc_random(¶ms.qc); + if (randompass) { + status = say(pamh, PAM_TEXT_INFO, randomonly ? + MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass); + if (status != PAM_SUCCESS) { + _pam_overwrite(randompass); + randompass = NULL; + } + } else + if (randomonly) { + say(pamh, PAM_ERROR_MSG, getuid() != 0 ? + MESSAGE_MISCONFIGURED : MESSAGE_RANDOMFAILED); + return PAM_AUTHTOK_RECOVER_ERR; + } + + status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp); + if (status == PAM_SUCCESS && (!resp || !resp->resp)) + status = PAM_AUTHTOK_RECOVER_ERR; + + if (status != PAM_SUCCESS) { + if (randompass) _pam_overwrite(randompass); + return status; + } + + newpass = strdup(resp->resp); + + _pam_drop_reply(resp, 1); + + if (!newpass) { + if (randompass) _pam_overwrite(randompass); + return PAM_AUTHTOK_RECOVER_ERR; + } + + if (check_max(¶ms, pamh, newpass) && enforce) { + status = PAM_AUTHTOK_RECOVER_ERR; + retry_wanted = 1; + } + + reason = NULL; + if (status == PAM_SUCCESS && + (!randompass || !strstr(newpass, randompass)) && + (randomonly || + (reason = _passwdqc_check(¶ms.qc, newpass, oldpass, pw)))) { + if (randomonly) + say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM); + else + say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason); + if (enforce) { + status = PAM_AUTHTOK_RECOVER_ERR; + retry_wanted = 1; + } + } + + if (status == PAM_SUCCESS) + status = converse(pamh, PAM_PROMPT_ECHO_OFF, + PROMPT_NEWPASS2, &resp); + if (status == PAM_SUCCESS) { + if (resp && resp->resp) { + if (strcmp(newpass, resp->resp)) { + status = say(pamh, + PAM_ERROR_MSG, MESSAGE_MISTYPED); + if (status == PAM_SUCCESS) { + status = PAM_AUTHTOK_RECOVER_ERR; + retry_wanted = 1; + } + } + _pam_drop_reply(resp, 1); + } else + status = PAM_AUTHTOK_RECOVER_ERR; + } + + if (status == PAM_SUCCESS) + status = pam_set_item(pamh, PAM_AUTHTOK, newpass); + + if (randompass) _pam_overwrite(randompass); + _pam_overwrite(newpass); + free(newpass); + + if (retry_wanted && --retries_left > 0) { + status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY); + if (status == PAM_SUCCESS) + goto retry; + } + + return status; +} + +#ifdef PAM_STATIC +struct pam_module _pam_passwdqc_modstruct = { + "pam_passwdqc", + NULL, + NULL, + NULL, + NULL, + NULL, + pam_sm_chauthtok +}; +#endif |