summaryrefslogtreecommitdiffstats
path: root/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
diff options
context:
space:
mode:
authordes <des@FreeBSD.org>2002-04-04 15:50:47 +0000
committerdes <des@FreeBSD.org>2002-04-04 15:50:47 +0000
commit4532f219fa85b9c06b3f4caaa4dcbbdbf3bd32f5 (patch)
tree1dcc5978e4a3b724654b82d0e61f6dc3e94705ee /contrib/pam_modules/pam_passwdqc/passwdqc_check.c
downloadFreeBSD-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/passwdqc_check.c')
-rw-r--r--contrib/pam_modules/pam_passwdqc/passwdqc_check.c361
1 files changed, 361 insertions, 0 deletions
diff --git a/contrib/pam_modules/pam_passwdqc/passwdqc_check.c b/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
new file mode 100644
index 0000000..08e2b49
--- /dev/null
+++ b/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2000,2001 by Solar Designer. See LICENSE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <pwd.h>
+
+#include "passwdqc.h"
+
+#define REASON_ERROR \
+ "check failed"
+
+#define REASON_SAME \
+ "is the same as the old one"
+#define REASON_SIMILAR \
+ "is based on the old one"
+
+#define REASON_SHORT \
+ "too short"
+#define REASON_LONG \
+ "too long"
+
+#define REASON_SIMPLESHORT \
+ "not enough different characters or classes for this length"
+#define REASON_SIMPLE \
+ "not enough different characters or classes"
+
+#define REASON_PERSONAL \
+ "based on personal login information"
+
+#define REASON_WORD \
+ "based on a dictionary word and not a passphrase"
+
+#define FIXED_BITS 15
+
+typedef unsigned long fixed;
+
+/*
+ * Calculates the expected number of different characters for a random
+ * password of a given length. The result is rounded down. We use this
+ * with the _requested_ minimum length (so longer passwords don't have
+ * to meet this strict requirement for their length).
+ */
+static int expected_different(int charset, int length)
+{
+ fixed x, y, z;
+
+ x = ((fixed)(charset - 1) << FIXED_BITS) / charset;
+ y = x;
+ while (--length > 0) y = (y * x) >> FIXED_BITS;
+ z = (fixed)charset * (((fixed)1 << FIXED_BITS) - y);
+
+ return (int)(z >> FIXED_BITS);
+}
+
+/*
+ * A password is too simple if it is too short for its class, or doesn't
+ * contain enough different characters for its class, or doesn't contain
+ * enough words for a passphrase.
+ */
+static int is_simple(passwdqc_params_t *params, char *newpass)
+{
+ int length, classes, words, chars;
+ int digits, lowers, uppers, others, unknowns;
+ int c, p;
+
+ length = classes = words = chars = 0;
+ digits = lowers = uppers = others = unknowns = 0;
+ p = ' ';
+ while ((c = (unsigned char)newpass[length])) {
+ length++;
+
+ if (!isascii(c)) unknowns++; else
+ if (isdigit(c)) digits++; else
+ if (islower(c)) lowers++; else
+ if (isupper(c)) uppers++; else
+ others++;
+
+ if (isascii(c) && isalpha(c) && isascii(p) && !isalpha(p))
+ words++;
+ p = c;
+
+ if (!strchr(&newpass[length], c))
+ chars++;
+ }
+
+ if (!length) return 1;
+
+/* Upper case characters and digits used in common ways don't increase the
+ * strength of a password */
+ c = (unsigned char)newpass[0];
+ if (uppers && isascii(c) && isupper(c)) uppers--;
+ c = (unsigned char)newpass[length - 1];
+ if (digits && isascii(c) && isdigit(c)) digits--;
+
+/* Count the number of different character classes we've seen. We assume
+ * that there're no non-ASCII characters for digits. */
+ classes = 0;
+ if (digits) classes++;
+ if (lowers) classes++;
+ if (uppers) classes++;
+ if (others) classes++;
+ if (unknowns && (!classes || (digits && classes == 1))) classes++;
+
+ for (; classes > 0; classes--)
+ switch (classes) {
+ case 1:
+ if (length >= params->min[0] &&
+ chars >= expected_different(10, params->min[0]) - 1)
+ return 0;
+ return 1;
+
+ case 2:
+ if (length >= params->min[1] &&
+ chars >= expected_different(36, params->min[1]) - 1)
+ return 0;
+ if (!params->passphrase_words ||
+ words < params->passphrase_words)
+ continue;
+ if (length >= params->min[2] &&
+ chars >= expected_different(27, params->min[2]) - 1)
+ return 0;
+ continue;
+
+ case 3:
+ if (length >= params->min[3] &&
+ chars >= expected_different(62, params->min[3]) - 1)
+ return 0;
+ continue;
+
+ case 4:
+ if (length >= params->min[4] &&
+ chars >= expected_different(95, params->min[4]) - 1)
+ return 0;
+ continue;
+ }
+
+ return 1;
+}
+
+static char *unify(char *src)
+{
+ char *dst;
+ char *sptr, *dptr;
+ int c;
+
+ if (!(dst = malloc(strlen(src) + 1)))
+ return NULL;
+
+ sptr = src;
+ dptr = dst;
+ do {
+ c = (unsigned char)*sptr;
+ if (isascii(c) && isupper(c))
+ *dptr++ = tolower(c);
+ else
+ *dptr++ = *sptr;
+ } while (*sptr++);
+
+ return dst;
+}
+
+static char *reverse(char *src)
+{
+ char *dst;
+ char *sptr, *dptr;
+
+ if (!(dst = malloc(strlen(src) + 1)))
+ return NULL;
+
+ sptr = &src[strlen(src)];
+ dptr = dst;
+ while (sptr > src)
+ *dptr++ = *--sptr;
+ *dptr = '\0';
+
+ return dst;
+}
+
+static void clean(char *dst)
+{
+ if (dst) {
+ memset(dst, 0, strlen(dst));
+ free(dst);
+ }
+}
+
+/*
+ * Needle is based on haystack if both contain a long enough common
+ * substring and needle would be too simple for a password with the
+ * substring removed.
+ */
+static int is_based(passwdqc_params_t *params,
+ char *haystack, char *needle, char *original)
+{
+ char *scratch;
+ int length;
+ int i, j;
+ char *p;
+ int match;
+
+ if (!params->match_length) /* disabled */
+ return 0;
+
+ if (params->match_length < 0) /* misconfigured */
+ return 1;
+
+ if (strstr(haystack, needle)) /* based on haystack entirely */
+ return 1;
+
+ scratch = NULL;
+
+ length = strlen(needle);
+ for (i = 0; i <= length - params->match_length; i++)
+ for (j = params->match_length; i + j <= length; j++) {
+ match = 0;
+ for (p = haystack; *p; p++)
+ if (*p == needle[i] && !strncmp(p, &needle[i], j)) {
+ match = 1;
+ if (!scratch) {
+ if (!(scratch = malloc(length + 1)))
+ return 1;
+ }
+ memcpy(scratch, original, i);
+ memcpy(&scratch[i], &original[i + j],
+ length + 1 - (i + j));
+ if (is_simple(params, scratch)) {
+ clean(scratch);
+ return 1;
+ }
+ }
+ if (!match) break;
+ }
+
+ clean(scratch);
+
+ return 0;
+}
+
+/*
+ * This wordlist check is now the least important given the checks above
+ * and the support for passphrases (which are based on dictionary words,
+ * and checked by other means). It is still useful to trap simple short
+ * passwords (if short passwords are allowed) that are word-based, but
+ * passed the other checks due to uncommon capitalization, digits, and
+ * special characters. We (mis)use the same set of words that are used
+ * to generate random passwords. This list is much smaller than those
+ * used for password crackers, and it doesn't contain common passwords
+ * that aren't short English words. Perhaps support for large wordlists
+ * should still be added, even though this is now of little importance.
+ */
+static int is_word_based(passwdqc_params_t *params,
+ char *needle, char *original)
+{
+ char word[7];
+ char *unified;
+ int index;
+
+ word[6] = '\0';
+ for (index = 0; index < 0x1000; index++) {
+ memcpy(word, _passwdqc_wordset_4k[index], 6);
+ if (strlen(word) < params->match_length) continue;
+ unified = unify(word);
+ if (is_based(params, unified, needle, original)) {
+ clean(unified);
+ return 1;
+ }
+ clean(unified);
+ }
+
+ return 0;
+}
+
+char *_passwdqc_check(passwdqc_params_t *params,
+ char *newpass, char *oldpass, struct passwd *pw)
+{
+ char truncated[9], *reversed;
+ char *u_newpass, *u_reversed;
+ char *u_oldpass;
+ char *u_name, *u_gecos;
+ char *reason;
+ int length;
+
+ reversed = NULL;
+ u_newpass = u_reversed = NULL;
+ u_oldpass = NULL;
+ u_name = u_gecos = NULL;
+
+ reason = NULL;
+
+ if (oldpass && !strcmp(oldpass, newpass))
+ reason = REASON_SAME;
+
+ length = strlen(newpass);
+
+ if (!reason && length < params->min[4])
+ reason = REASON_SHORT;
+
+ if (!reason && length > params->max) {
+ if (params->max == 8) {
+ truncated[0] = '\0';
+ strncat(truncated, newpass, 8);
+ newpass = truncated;
+ if (oldpass && !strncmp(oldpass, newpass, 8))
+ reason = REASON_SAME;
+ } else
+ reason = REASON_LONG;
+ }
+
+ if (!reason && is_simple(params, newpass)) {
+ if (length < params->min[1] && params->min[1] <= params->max)
+ reason = REASON_SIMPLESHORT;
+ else
+ reason = REASON_SIMPLE;
+ }
+
+ if (!reason) {
+ if ((reversed = reverse(newpass))) {
+ u_newpass = unify(newpass);
+ u_reversed = unify(reversed);
+ if (oldpass)
+ u_oldpass = unify(oldpass);
+ if (pw) {
+ u_name = unify(pw->pw_name);
+ u_gecos = unify(pw->pw_gecos);
+ }
+ }
+ if (!reversed ||
+ !u_newpass || !u_reversed ||
+ (oldpass && !u_oldpass) ||
+ (pw && (!u_name || !u_gecos)))
+ reason = REASON_ERROR;
+ }
+
+ if (!reason && oldpass && params->similar_deny &&
+ (is_based(params, u_oldpass, u_newpass, newpass) ||
+ is_based(params, u_oldpass, u_reversed, reversed)))
+ reason = REASON_SIMILAR;
+
+ if (!reason && pw &&
+ (is_based(params, u_name, u_newpass, newpass) ||
+ is_based(params, u_name, u_reversed, reversed) ||
+ is_based(params, u_gecos, u_newpass, newpass) ||
+ is_based(params, u_gecos, u_reversed, reversed)))
+ reason = REASON_PERSONAL;
+
+ if (!reason && strlen(newpass) < params->min[2] &&
+ (is_word_based(params, u_newpass, newpass) ||
+ is_word_based(params, u_reversed, reversed)))
+ reason = REASON_WORD;
+
+ memset(truncated, 0, sizeof(truncated));
+ clean(reversed);
+ clean(u_newpass); clean(u_reversed);
+ clean(u_oldpass);
+ clean(u_name); clean(u_gecos);
+
+ return reason;
+}
OpenPOWER on IntegriCloud