mfiutil(8): Use MAN instead of MAN8.
[dragonfly.git] / contrib / pam_passwdqc / passwdqc_check.c
blob2d5f7143f7ae268ecc18c8ea5945741ab0f924d3
1 /*
2 * Copyright (c) 2000-2002 by Solar Designer. See LICENSE.
3 */
5 #include <stdlib.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <pwd.h>
10 #include "passwdqc.h"
12 #define REASON_ERROR \
13 "check failed"
15 #define REASON_SAME \
16 "is the same as the old one"
17 #define REASON_SIMILAR \
18 "is based on the old one"
20 #define REASON_SHORT \
21 "too short"
22 #define REASON_LONG \
23 "too long"
25 #define REASON_SIMPLESHORT \
26 "not enough different characters or classes for this length"
27 #define REASON_SIMPLE \
28 "not enough different characters or classes"
30 #define REASON_PERSONAL \
31 "based on personal login information"
33 #define REASON_WORD \
34 "based on a dictionary word and not a passphrase"
36 #define FIXED_BITS 15
38 typedef unsigned long fixed;
41 * Calculates the expected number of different characters for a random
42 * password of a given length. The result is rounded down. We use this
43 * with the _requested_ minimum length (so longer passwords don't have
44 * to meet this strict requirement for their length).
46 static int expected_different(int charset, int length)
48 fixed x, y, z;
50 x = ((fixed)(charset - 1) << FIXED_BITS) / charset;
51 y = x;
52 while (--length > 0) y = (y * x) >> FIXED_BITS;
53 z = (fixed)charset * (((fixed)1 << FIXED_BITS) - y);
55 return (int)(z >> FIXED_BITS);
59 * A password is too simple if it is too short for its class, or doesn't
60 * contain enough different characters for its class, or doesn't contain
61 * enough words for a passphrase.
63 static int is_simple(passwdqc_params_t *params, const char *newpass)
65 int length, classes, words, chars;
66 int digits, lowers, uppers, others, unknowns;
67 int c, p;
69 length = classes = words = chars = 0;
70 digits = lowers = uppers = others = unknowns = 0;
71 p = ' ';
72 while ((c = (unsigned char)newpass[length])) {
73 length++;
75 if (!isascii(c)) unknowns++; else
76 if (isdigit(c)) digits++; else
77 if (islower(c)) lowers++; else
78 if (isupper(c)) uppers++; else
79 others++;
81 if (isascii(c) && isalpha(c) && isascii(p) && !isalpha(p))
82 words++;
83 p = c;
85 if (!strchr(&newpass[length], c))
86 chars++;
89 if (!length) return 1;
91 /* Upper case characters and digits used in common ways don't increase the
92 * strength of a password */
93 c = (unsigned char)newpass[0];
94 if (uppers && isascii(c) && isupper(c)) uppers--;
95 c = (unsigned char)newpass[length - 1];
96 if (digits && isascii(c) && isdigit(c)) digits--;
98 /* Count the number of different character classes we've seen. We assume
99 * that there are no non-ASCII characters for digits. */
100 classes = 0;
101 if (digits) classes++;
102 if (lowers) classes++;
103 if (uppers) classes++;
104 if (others) classes++;
105 if (unknowns && (!classes || (digits && classes == 1))) classes++;
107 for (; classes > 0; classes--)
108 switch (classes) {
109 case 1:
110 if (length >= params->min[0] &&
111 chars >= expected_different(10, params->min[0]) - 1)
112 return 0;
113 return 1;
115 case 2:
116 if (length >= params->min[1] &&
117 chars >= expected_different(36, params->min[1]) - 1)
118 return 0;
119 if (!params->passphrase_words ||
120 words < params->passphrase_words)
121 continue;
122 if (length >= params->min[2] &&
123 chars >= expected_different(27, params->min[2]) - 1)
124 return 0;
125 continue;
127 case 3:
128 if (length >= params->min[3] &&
129 chars >= expected_different(62, params->min[3]) - 1)
130 return 0;
131 continue;
133 case 4:
134 if (length >= params->min[4] &&
135 chars >= expected_different(95, params->min[4]) - 1)
136 return 0;
137 continue;
140 return 1;
143 static char *unify(const char *src)
145 const char *sptr;
146 char *dst, *dptr;
147 int c;
149 if (!(dst = malloc(strlen(src) + 1)))
150 return NULL;
152 sptr = src;
153 dptr = dst;
154 do {
155 c = (unsigned char)*sptr;
156 if (isascii(c) && isupper(c))
157 *dptr++ = tolower(c);
158 else
159 *dptr++ = *sptr;
160 } while (*sptr++);
162 return dst;
165 static char *reverse(const char *src)
167 const char *sptr;
168 char *dst, *dptr;
170 if (!(dst = malloc(strlen(src) + 1)))
171 return NULL;
173 sptr = &src[strlen(src)];
174 dptr = dst;
175 while (sptr > src)
176 *dptr++ = *--sptr;
177 *dptr = '\0';
179 return dst;
182 static void clean(char *dst)
184 if (dst) {
185 memset(dst, 0, strlen(dst));
186 free(dst);
191 * Needle is based on haystack if both contain a long enough common
192 * substring and needle would be too simple for a password with the
193 * substring removed.
195 static int is_based(passwdqc_params_t *params,
196 const char *haystack, const char *needle, const char *original)
198 char *scratch;
199 int length;
200 int i, j;
201 const char *p;
202 int match;
204 if (!params->match_length) /* disabled */
205 return 0;
207 if (params->match_length < 0) /* misconfigured */
208 return 1;
210 if (strstr(haystack, needle)) /* based on haystack entirely */
211 return 1;
213 scratch = NULL;
215 length = strlen(needle);
216 for (i = 0; i <= length - params->match_length; i++)
217 for (j = params->match_length; i + j <= length; j++) {
218 match = 0;
219 for (p = haystack; *p; p++)
220 if (*p == needle[i] && !strncmp(p, &needle[i], j)) {
221 match = 1;
222 if (!scratch) {
223 if (!(scratch = malloc(length + 1)))
224 return 1;
226 memcpy(scratch, original, i);
227 memcpy(&scratch[i], &original[i + j],
228 length + 1 - (i + j));
229 if (is_simple(params, scratch)) {
230 clean(scratch);
231 return 1;
234 if (!match) break;
237 clean(scratch);
239 return 0;
243 * This wordlist check is now the least important given the checks above
244 * and the support for passphrases (which are based on dictionary words,
245 * and checked by other means). It is still useful to trap simple short
246 * passwords (if short passwords are allowed) that are word-based, but
247 * passed the other checks due to uncommon capitalization, digits, and
248 * special characters. We (mis)use the same set of words that are used
249 * to generate random passwords. This list is much smaller than those
250 * used for password crackers, and it doesn't contain common passwords
251 * that aren't short English words. Perhaps support for large wordlists
252 * should still be added, even though this is now of little importance.
254 static int is_word_based(passwdqc_params_t *params,
255 const char *needle, const char *original)
257 char word[7];
258 char *unified;
259 int i;
261 word[6] = '\0';
262 for (i = 0; i < 0x1000; i++) {
263 memcpy(word, _passwdqc_wordset_4k[i], 6);
264 if ((int)strlen(word) < params->match_length) continue;
265 unified = unify(word);
266 if (is_based(params, unified, needle, original)) {
267 clean(unified);
268 return 1;
270 clean(unified);
273 return 0;
276 const char *_passwdqc_check(passwdqc_params_t *params,
277 const char *newpass, const char *oldpass, struct passwd *pw)
279 char truncated[9], *reversed;
280 char *u_newpass, *u_reversed;
281 char *u_oldpass;
282 char *u_name, *u_gecos;
283 const char *reason;
284 int length;
286 reversed = NULL;
287 u_newpass = u_reversed = NULL;
288 u_oldpass = NULL;
289 u_name = u_gecos = NULL;
291 reason = NULL;
293 if (oldpass && !strcmp(oldpass, newpass))
294 reason = REASON_SAME;
296 length = strlen(newpass);
298 if (!reason && length < params->min[4])
299 reason = REASON_SHORT;
301 if (!reason && length > params->max) {
302 if (params->max == 8) {
303 truncated[0] = '\0';
304 strncat(truncated, newpass, 8);
305 newpass = truncated;
306 if (oldpass && !strncmp(oldpass, newpass, 8))
307 reason = REASON_SAME;
308 } else
309 reason = REASON_LONG;
312 if (!reason && is_simple(params, newpass)) {
313 if (length < params->min[1] && params->min[1] <= params->max)
314 reason = REASON_SIMPLESHORT;
315 else
316 reason = REASON_SIMPLE;
319 if (!reason) {
320 if ((reversed = reverse(newpass))) {
321 u_newpass = unify(newpass);
322 u_reversed = unify(reversed);
323 if (oldpass)
324 u_oldpass = unify(oldpass);
325 if (pw) {
326 u_name = unify(pw->pw_name);
327 u_gecos = unify(pw->pw_gecos);
330 if (!reversed ||
331 !u_newpass || !u_reversed ||
332 (oldpass && !u_oldpass) ||
333 (pw && (!u_name || !u_gecos)))
334 reason = REASON_ERROR;
337 if (!reason && oldpass && params->similar_deny &&
338 (is_based(params, u_oldpass, u_newpass, newpass) ||
339 is_based(params, u_oldpass, u_reversed, reversed)))
340 reason = REASON_SIMILAR;
342 if (!reason && pw &&
343 (is_based(params, u_name, u_newpass, newpass) ||
344 is_based(params, u_name, u_reversed, reversed) ||
345 is_based(params, u_gecos, u_newpass, newpass) ||
346 is_based(params, u_gecos, u_reversed, reversed)))
347 reason = REASON_PERSONAL;
349 if (!reason && (int)strlen(newpass) < params->min[2] &&
350 (is_word_based(params, u_newpass, newpass) ||
351 is_word_based(params, u_reversed, reversed)))
352 reason = REASON_WORD;
354 memset(truncated, 0, sizeof(truncated));
355 clean(reversed);
356 clean(u_newpass); clean(u_reversed);
357 clean(u_oldpass);
358 clean(u_name); clean(u_gecos);
360 return reason;