116d0e647SPeter Avalos /*
2*8c117293SSascha Wildner  * Copyright (c) 2000-2002,2010,2013,2016,2020 by Solar Designer.  See LICENSE.
316d0e647SPeter Avalos  */
416d0e647SPeter Avalos 
5*8c117293SSascha Wildner #ifdef _MSC_VER
6*8c117293SSascha Wildner #define _CRT_SECURE_NO_WARNINGS /* we use fopen(), sprintf(), strncat() */
7*8c117293SSascha Wildner #endif
8*8c117293SSascha Wildner 
9*8c117293SSascha Wildner #include <stdio.h>
1016d0e647SPeter Avalos #include <stdlib.h>
1116d0e647SPeter Avalos #include <string.h>
1216d0e647SPeter Avalos #include <ctype.h>
1316d0e647SPeter Avalos 
14*8c117293SSascha Wildner #include "passwdqc.h" /* also provides <pwd.h> or equivalent "struct passwd" */
15*8c117293SSascha Wildner #include "passwdqc_filter.h"
16*8c117293SSascha Wildner #include "wordset_4k.h"
17*8c117293SSascha Wildner 
18*8c117293SSascha Wildner #include "passwdqc_i18n.h"
1916d0e647SPeter Avalos 
2016d0e647SPeter Avalos #define REASON_ERROR \
21*8c117293SSascha Wildner 	_("check failed")
2216d0e647SPeter Avalos 
2316d0e647SPeter Avalos #define REASON_SAME \
24*8c117293SSascha Wildner 	_("is the same as the old one")
2516d0e647SPeter Avalos #define REASON_SIMILAR \
26*8c117293SSascha Wildner 	_("is based on the old one")
2716d0e647SPeter Avalos 
2816d0e647SPeter Avalos #define REASON_SHORT \
29*8c117293SSascha Wildner 	_("too short")
3016d0e647SPeter Avalos #define REASON_LONG \
31*8c117293SSascha Wildner 	_("too long")
3216d0e647SPeter Avalos 
3316d0e647SPeter Avalos #define REASON_SIMPLESHORT \
34*8c117293SSascha Wildner 	_("not enough different characters or classes for this length")
3516d0e647SPeter Avalos #define REASON_SIMPLE \
36*8c117293SSascha Wildner 	_("not enough different characters or classes")
3716d0e647SPeter Avalos 
3816d0e647SPeter Avalos #define REASON_PERSONAL \
39*8c117293SSascha Wildner 	_("based on personal login information")
4016d0e647SPeter Avalos 
4116d0e647SPeter Avalos #define REASON_WORD \
42*8c117293SSascha Wildner 	_("based on a dictionary word and not a passphrase")
43*8c117293SSascha Wildner 
44*8c117293SSascha Wildner #define REASON_SEQ \
45*8c117293SSascha Wildner 	_("based on a common sequence of characters and not a passphrase")
46*8c117293SSascha Wildner 
47*8c117293SSascha Wildner #define REASON_WORDLIST \
48*8c117293SSascha Wildner 	_("based on a word list entry")
49*8c117293SSascha Wildner 
50*8c117293SSascha Wildner #define REASON_DENYLIST \
51*8c117293SSascha Wildner 	_("is in deny list")
52*8c117293SSascha Wildner 
53*8c117293SSascha Wildner #define REASON_FILTER \
54*8c117293SSascha Wildner 	_("appears to be in a database")
5516d0e647SPeter Avalos 
5616d0e647SPeter Avalos #define FIXED_BITS			15
5716d0e647SPeter Avalos 
5816d0e647SPeter Avalos typedef unsigned long fixed;
5916d0e647SPeter Avalos 
6016d0e647SPeter Avalos /*
6116d0e647SPeter Avalos  * Calculates the expected number of different characters for a random
6216d0e647SPeter Avalos  * password of a given length.  The result is rounded down.  We use this
6316d0e647SPeter Avalos  * with the _requested_ minimum length (so longer passwords don't have
6416d0e647SPeter Avalos  * to meet this strict requirement for their length).
6516d0e647SPeter Avalos  */
expected_different(int charset,int length)6616d0e647SPeter Avalos static int expected_different(int charset, int length)
6716d0e647SPeter Avalos {
6816d0e647SPeter Avalos 	fixed x, y, z;
6916d0e647SPeter Avalos 
7016d0e647SPeter Avalos 	x = ((fixed)(charset - 1) << FIXED_BITS) / charset;
7116d0e647SPeter Avalos 	y = x;
72*8c117293SSascha Wildner 	while (--length > 0)
73*8c117293SSascha Wildner 		y = (y * x) >> FIXED_BITS;
7416d0e647SPeter Avalos 	z = (fixed)charset * (((fixed)1 << FIXED_BITS) - y);
7516d0e647SPeter Avalos 
7616d0e647SPeter Avalos 	return (int)(z >> FIXED_BITS);
7716d0e647SPeter Avalos }
7816d0e647SPeter Avalos 
7916d0e647SPeter Avalos /*
8016d0e647SPeter Avalos  * A password is too simple if it is too short for its class, or doesn't
8116d0e647SPeter Avalos  * contain enough different characters for its class, or doesn't contain
8216d0e647SPeter Avalos  * enough words for a passphrase.
83*8c117293SSascha Wildner  *
84*8c117293SSascha Wildner  * The biases are added to the length, and they may be positive or negative.
85*8c117293SSascha Wildner  * The passphrase length check uses passphrase_bias instead of bias so that
86*8c117293SSascha Wildner  * zero may be passed for this parameter when the (other) bias is non-zero
87*8c117293SSascha Wildner  * because of a dictionary word, which is perfectly normal for a passphrase.
88*8c117293SSascha Wildner  * The biases do not affect the number of different characters, character
89*8c117293SSascha Wildner  * classes, and word count.
9016d0e647SPeter Avalos  */
is_simple(const passwdqc_params_qc_t * params,const char * newpass,int bias,int passphrase_bias)91*8c117293SSascha Wildner static int is_simple(const passwdqc_params_qc_t *params, const char *newpass,
92*8c117293SSascha Wildner     int bias, int passphrase_bias)
9316d0e647SPeter Avalos {
9416d0e647SPeter Avalos 	int length, classes, words, chars;
9516d0e647SPeter Avalos 	int digits, lowers, uppers, others, unknowns;
9616d0e647SPeter Avalos 	int c, p;
9716d0e647SPeter Avalos 
9816d0e647SPeter Avalos 	length = classes = words = chars = 0;
9916d0e647SPeter Avalos 	digits = lowers = uppers = others = unknowns = 0;
10016d0e647SPeter Avalos 	p = ' ';
10116d0e647SPeter Avalos 	while ((c = (unsigned char)newpass[length])) {
10216d0e647SPeter Avalos 		length++;
10316d0e647SPeter Avalos 
104*8c117293SSascha Wildner 		if (!isascii(c))
105*8c117293SSascha Wildner 			unknowns++;
106*8c117293SSascha Wildner 		else if (isdigit(c))
107*8c117293SSascha Wildner 			digits++;
108*8c117293SSascha Wildner 		else if (islower(c))
109*8c117293SSascha Wildner 			lowers++;
110*8c117293SSascha Wildner 		else if (isupper(c))
111*8c117293SSascha Wildner 			uppers++;
112*8c117293SSascha Wildner 		else
11316d0e647SPeter Avalos 			others++;
11416d0e647SPeter Avalos 
115*8c117293SSascha Wildner /* A word starts when a letter follows a non-letter or when a non-ASCII
116*8c117293SSascha Wildner  * character follows a space character.  We treat all non-ASCII characters
117*8c117293SSascha Wildner  * as non-spaces, which is not entirely correct (there's the non-breaking
118*8c117293SSascha Wildner  * space character at 0xa0, 0x9a, or 0xff), but it should not hurt. */
119*8c117293SSascha Wildner 		if (isascii(p)) {
120*8c117293SSascha Wildner 			if (isascii(c)) {
121*8c117293SSascha Wildner 				if (isalpha(c) && !isalpha(p))
12216d0e647SPeter Avalos 					words++;
123*8c117293SSascha Wildner 			} else if (isspace(p))
124*8c117293SSascha Wildner 				words++;
125*8c117293SSascha Wildner 		}
12616d0e647SPeter Avalos 		p = c;
12716d0e647SPeter Avalos 
128*8c117293SSascha Wildner /* Count this character just once: when we're not going to see it anymore */
12916d0e647SPeter Avalos 		if (!strchr(&newpass[length], c))
13016d0e647SPeter Avalos 			chars++;
13116d0e647SPeter Avalos 	}
13216d0e647SPeter Avalos 
133*8c117293SSascha Wildner 	if (!length)
134*8c117293SSascha Wildner 		return 1;
13516d0e647SPeter Avalos 
13616d0e647SPeter Avalos /* Upper case characters and digits used in common ways don't increase the
13716d0e647SPeter Avalos  * strength of a password */
13816d0e647SPeter Avalos 	c = (unsigned char)newpass[0];
139*8c117293SSascha Wildner 	if (uppers && isascii(c) && isupper(c))
140*8c117293SSascha Wildner 		uppers--;
14116d0e647SPeter Avalos 	c = (unsigned char)newpass[length - 1];
142*8c117293SSascha Wildner 	if (digits && isascii(c) && isdigit(c))
143*8c117293SSascha Wildner 		digits--;
14416d0e647SPeter Avalos 
14516d0e647SPeter Avalos /* Count the number of different character classes we've seen.  We assume
14616d0e647SPeter Avalos  * that there are no non-ASCII characters for digits. */
14716d0e647SPeter Avalos 	classes = 0;
148*8c117293SSascha Wildner 	if (digits)
149*8c117293SSascha Wildner 		classes++;
150*8c117293SSascha Wildner 	if (lowers)
151*8c117293SSascha Wildner 		classes++;
152*8c117293SSascha Wildner 	if (uppers)
153*8c117293SSascha Wildner 		classes++;
154*8c117293SSascha Wildner 	if (others)
155*8c117293SSascha Wildner 		classes++;
156*8c117293SSascha Wildner 	if (unknowns && classes <= 1 && (!classes || digits || words >= 2))
157*8c117293SSascha Wildner 		classes++;
15816d0e647SPeter Avalos 
15916d0e647SPeter Avalos 	for (; classes > 0; classes--)
16016d0e647SPeter Avalos 	switch (classes) {
16116d0e647SPeter Avalos 	case 1:
162*8c117293SSascha Wildner 		if (length + bias >= params->min[0] &&
16316d0e647SPeter Avalos 		    chars >= expected_different(10, params->min[0]) - 1)
16416d0e647SPeter Avalos 			return 0;
16516d0e647SPeter Avalos 		return 1;
16616d0e647SPeter Avalos 
16716d0e647SPeter Avalos 	case 2:
168*8c117293SSascha Wildner 		if (length + bias >= params->min[1] &&
16916d0e647SPeter Avalos 		    chars >= expected_different(36, params->min[1]) - 1)
17016d0e647SPeter Avalos 			return 0;
17116d0e647SPeter Avalos 		if (!params->passphrase_words ||
17216d0e647SPeter Avalos 		    words < params->passphrase_words)
17316d0e647SPeter Avalos 			continue;
174*8c117293SSascha Wildner 		if (length + passphrase_bias >= params->min[2] &&
17516d0e647SPeter Avalos 		    chars >= expected_different(27, params->min[2]) - 1)
17616d0e647SPeter Avalos 			return 0;
17716d0e647SPeter Avalos 		continue;
17816d0e647SPeter Avalos 
17916d0e647SPeter Avalos 	case 3:
180*8c117293SSascha Wildner 		if (length + bias >= params->min[3] &&
18116d0e647SPeter Avalos 		    chars >= expected_different(62, params->min[3]) - 1)
18216d0e647SPeter Avalos 			return 0;
18316d0e647SPeter Avalos 		continue;
18416d0e647SPeter Avalos 
18516d0e647SPeter Avalos 	case 4:
186*8c117293SSascha Wildner 		if (length + bias >= params->min[4] &&
18716d0e647SPeter Avalos 		    chars >= expected_different(95, params->min[4]) - 1)
18816d0e647SPeter Avalos 			return 0;
18916d0e647SPeter Avalos 		continue;
19016d0e647SPeter Avalos 	}
19116d0e647SPeter Avalos 
19216d0e647SPeter Avalos 	return 1;
19316d0e647SPeter Avalos }
19416d0e647SPeter Avalos 
unify(char * dst,const char * src)195*8c117293SSascha Wildner static char *unify(char *dst, const char *src)
19616d0e647SPeter Avalos {
19716d0e647SPeter Avalos 	const char *sptr;
198*8c117293SSascha Wildner 	char *dptr;
19916d0e647SPeter Avalos 	int c;
20016d0e647SPeter Avalos 
201*8c117293SSascha Wildner 	if (!dst && !(dst = malloc(strlen(src) + 1)))
20216d0e647SPeter Avalos 		return NULL;
20316d0e647SPeter Avalos 
20416d0e647SPeter Avalos 	sptr = src;
20516d0e647SPeter Avalos 	dptr = dst;
20616d0e647SPeter Avalos 	do {
20716d0e647SPeter Avalos 		c = (unsigned char)*sptr;
20816d0e647SPeter Avalos 		if (isascii(c) && isupper(c))
209*8c117293SSascha Wildner 			c = tolower(c);
210*8c117293SSascha Wildner 		switch (c) {
211*8c117293SSascha Wildner 		case 'a': case '@':
212*8c117293SSascha Wildner 			c = '4'; break;
213*8c117293SSascha Wildner 		case 'e':
214*8c117293SSascha Wildner 			c = '3'; break;
215*8c117293SSascha Wildner /* Unfortunately, if we translate both 'i' and 'l' to '1', this would
216*8c117293SSascha Wildner  * associate these two letters with each other - e.g., "mile" would
217*8c117293SSascha Wildner  * match "MLLE", which is undesired.  To solve this, we'd need to test
218*8c117293SSascha Wildner  * different translations separately, which is not implemented yet. */
219*8c117293SSascha Wildner 		case 'i': case '|':
220*8c117293SSascha Wildner 			c = '!'; break;
221*8c117293SSascha Wildner 		case 'l':
222*8c117293SSascha Wildner 			c = '1'; break;
223*8c117293SSascha Wildner 		case 'o':
224*8c117293SSascha Wildner 			c = '0'; break;
225*8c117293SSascha Wildner 		case 's': case '$':
226*8c117293SSascha Wildner 			c = '5'; break;
227*8c117293SSascha Wildner 		case 't': case '+':
228*8c117293SSascha Wildner 			c = '7'; break;
229*8c117293SSascha Wildner 		}
230*8c117293SSascha Wildner 		*dptr++ = c;
23116d0e647SPeter Avalos 	} while (*sptr++);
23216d0e647SPeter Avalos 
23316d0e647SPeter Avalos 	return dst;
23416d0e647SPeter Avalos }
23516d0e647SPeter Avalos 
reverse(const char * src)23616d0e647SPeter Avalos static char *reverse(const char *src)
23716d0e647SPeter Avalos {
23816d0e647SPeter Avalos 	const char *sptr;
23916d0e647SPeter Avalos 	char *dst, *dptr;
24016d0e647SPeter Avalos 
24116d0e647SPeter Avalos 	if (!(dst = malloc(strlen(src) + 1)))
24216d0e647SPeter Avalos 		return NULL;
24316d0e647SPeter Avalos 
24416d0e647SPeter Avalos 	sptr = &src[strlen(src)];
24516d0e647SPeter Avalos 	dptr = dst;
24616d0e647SPeter Avalos 	while (sptr > src)
24716d0e647SPeter Avalos 		*dptr++ = *--sptr;
24816d0e647SPeter Avalos 	*dptr = '\0';
24916d0e647SPeter Avalos 
25016d0e647SPeter Avalos 	return dst;
25116d0e647SPeter Avalos }
25216d0e647SPeter Avalos 
clean(char * dst)25316d0e647SPeter Avalos static void clean(char *dst)
25416d0e647SPeter Avalos {
255*8c117293SSascha Wildner 	if (!dst)
256*8c117293SSascha Wildner 		return;
257*8c117293SSascha Wildner 	_passwdqc_memzero(dst, strlen(dst));
25816d0e647SPeter Avalos 	free(dst);
25916d0e647SPeter Avalos }
26016d0e647SPeter Avalos 
26116d0e647SPeter Avalos /*
26216d0e647SPeter Avalos  * Needle is based on haystack if both contain a long enough common
26316d0e647SPeter Avalos  * substring and needle would be too simple for a password with the
264*8c117293SSascha Wildner  * substring either removed with partial length credit for it added
265*8c117293SSascha Wildner  * or partially discounted for the purpose of the length check.
26616d0e647SPeter Avalos  */
is_based(const passwdqc_params_qc_t * params,const char * haystack,const char * needle,const char * original,int mode)267*8c117293SSascha Wildner static int is_based(const passwdqc_params_qc_t *params,
268*8c117293SSascha Wildner     const char *haystack, const char *needle, const char *original,
269*8c117293SSascha Wildner     int mode)
27016d0e647SPeter Avalos {
27116d0e647SPeter Avalos 	char *scratch;
27216d0e647SPeter Avalos 	int length;
27316d0e647SPeter Avalos 	int i, j;
27416d0e647SPeter Avalos 	const char *p;
275*8c117293SSascha Wildner 	int worst_bias;
27616d0e647SPeter Avalos 
27716d0e647SPeter Avalos 	if (!params->match_length)	/* disabled */
27816d0e647SPeter Avalos 		return 0;
27916d0e647SPeter Avalos 
28016d0e647SPeter Avalos 	if (params->match_length < 0)	/* misconfigured */
28116d0e647SPeter Avalos 		return 1;
28216d0e647SPeter Avalos 
28316d0e647SPeter Avalos 	scratch = NULL;
284*8c117293SSascha Wildner 	worst_bias = 0;
28516d0e647SPeter Avalos 
286*8c117293SSascha Wildner 	length = (int)strlen(needle);
28716d0e647SPeter Avalos 	for (i = 0; i <= length - params->match_length; i++)
28816d0e647SPeter Avalos 	for (j = params->match_length; i + j <= length; j++) {
289*8c117293SSascha Wildner 		int bias = 0, j1 = j - 1;
290*8c117293SSascha Wildner 		const char q0 = needle[i], *q1 = &needle[i + 1];
29116d0e647SPeter Avalos 		for (p = haystack; *p; p++)
292*8c117293SSascha Wildner 		if (*p == q0 && !strncmp(p + 1, q1, j1)) { /* or memcmp() */
293*8c117293SSascha Wildner 			if ((mode & 0xff) == 0) { /* remove & credit */
29416d0e647SPeter Avalos 				if (!scratch) {
29516d0e647SPeter Avalos 					if (!(scratch = malloc(length + 1)))
29616d0e647SPeter Avalos 						return 1;
29716d0e647SPeter Avalos 				}
298*8c117293SSascha Wildner 				/* remove j chars */
299*8c117293SSascha Wildner 				{
300*8c117293SSascha Wildner 					int pos = length - (i + j);
301*8c117293SSascha Wildner 					if (!(mode & 0x100)) /* not reversed */
302*8c117293SSascha Wildner 						pos = i;
303*8c117293SSascha Wildner 					memcpy(scratch, original, pos);
304*8c117293SSascha Wildner 					memcpy(&scratch[pos],
305*8c117293SSascha Wildner 					    &original[pos + j],
306*8c117293SSascha Wildner 					    length + 1 - (pos + j));
307*8c117293SSascha Wildner 				}
308*8c117293SSascha Wildner 				/* add credit for match_length - 1 chars */
309*8c117293SSascha Wildner 				bias = params->match_length - 1;
310*8c117293SSascha Wildner 				if (is_simple(params, scratch, bias, bias)) {
31116d0e647SPeter Avalos 					clean(scratch);
31216d0e647SPeter Avalos 					return 1;
31316d0e647SPeter Avalos 				}
314*8c117293SSascha Wildner 			} else { /* discount */
315*8c117293SSascha Wildner /* Require a 1 character longer match for substrings containing leetspeak
316*8c117293SSascha Wildner  * when matching against dictionary words */
317*8c117293SSascha Wildner 				bias = -1;
318*8c117293SSascha Wildner 				if ((mode & 0xff) == 1) { /* words */
319*8c117293SSascha Wildner 					int pos = i, end = i + j;
320*8c117293SSascha Wildner 					if (mode & 0x100) { /* reversed */
321*8c117293SSascha Wildner 						pos = length - end;
322*8c117293SSascha Wildner 						end = length - i;
32316d0e647SPeter Avalos 					}
324*8c117293SSascha Wildner 					for (; pos < end; pos++)
325*8c117293SSascha Wildner 					if (!isalpha((int)(unsigned char)
326*8c117293SSascha Wildner 					    original[pos])) {
327*8c117293SSascha Wildner 						if (j == params->match_length)
328*8c117293SSascha Wildner 							goto next_match_length;
329*8c117293SSascha Wildner 						bias = 0;
330*8c117293SSascha Wildner 						break;
331*8c117293SSascha Wildner 					}
332*8c117293SSascha Wildner 				}
333*8c117293SSascha Wildner 
334*8c117293SSascha Wildner 				/* discount j - (match_length + bias) chars */
335*8c117293SSascha Wildner 				bias += (int)params->match_length - j;
336*8c117293SSascha Wildner 				/* bias <= -1 */
337*8c117293SSascha Wildner 				if (bias < worst_bias) {
338*8c117293SSascha Wildner 					if (is_simple(params, original, bias,
339*8c117293SSascha Wildner 					    (mode & 0xff) == 1 ? 0 : bias))
340*8c117293SSascha Wildner 						return 1;
341*8c117293SSascha Wildner 					worst_bias = bias;
342*8c117293SSascha Wildner 				}
343*8c117293SSascha Wildner 			}
344*8c117293SSascha Wildner 		}
345*8c117293SSascha Wildner /* Zero bias implies that there were no matches for this length.  If so,
346*8c117293SSascha Wildner  * there's no reason to try the next substring length (it would result in
347*8c117293SSascha Wildner  * no matches as well).  We break out of the substring length loop and
348*8c117293SSascha Wildner  * proceed with all substring lengths for the next position in needle. */
349*8c117293SSascha Wildner 		if (!bias)
350*8c117293SSascha Wildner 			break;
351*8c117293SSascha Wildner next_match_length:
352*8c117293SSascha Wildner 		;
35316d0e647SPeter Avalos 	}
35416d0e647SPeter Avalos 
35516d0e647SPeter Avalos 	clean(scratch);
35616d0e647SPeter Avalos 
35716d0e647SPeter Avalos 	return 0;
35816d0e647SPeter Avalos }
35916d0e647SPeter Avalos 
360*8c117293SSascha Wildner #define READ_LINE_MAX 8192
361*8c117293SSascha Wildner #define READ_LINE_SIZE (READ_LINE_MAX + 2)
362*8c117293SSascha Wildner 
read_line(FILE * f,char * buf)363*8c117293SSascha Wildner static char *read_line(FILE *f, char *buf)
364*8c117293SSascha Wildner {
365*8c117293SSascha Wildner 	buf[READ_LINE_MAX] = '\n';
366*8c117293SSascha Wildner 
367*8c117293SSascha Wildner 	if (!fgets(buf, READ_LINE_SIZE, f))
368*8c117293SSascha Wildner 		return NULL;
369*8c117293SSascha Wildner 
370*8c117293SSascha Wildner 	if (buf[READ_LINE_MAX] != '\n') {
371*8c117293SSascha Wildner 		int c;
372*8c117293SSascha Wildner 		do {
373*8c117293SSascha Wildner 			c = getc(f);
374*8c117293SSascha Wildner 		} while (c != EOF && c != '\n');
375*8c117293SSascha Wildner 		if (ferror(f))
376*8c117293SSascha Wildner 			return NULL;
377*8c117293SSascha Wildner 	}
378*8c117293SSascha Wildner 
379*8c117293SSascha Wildner 	char *p;
380*8c117293SSascha Wildner 	if ((p = strpbrk(buf, "\r\n")))
381*8c117293SSascha Wildner 		*p = '\0';
382*8c117293SSascha Wildner 
383*8c117293SSascha Wildner 	return buf;
384*8c117293SSascha Wildner }
385*8c117293SSascha Wildner 
386*8c117293SSascha Wildner /*
387*8c117293SSascha Wildner  * Common sequences of characters.
388*8c117293SSascha Wildner  * We don't need to list any of the entire strings in reverse order because the
389*8c117293SSascha Wildner  * code checks the new password in both "unified" and "unified and reversed"
390*8c117293SSascha Wildner  * form against these strings (unifying them first indeed).  We also don't have
391*8c117293SSascha Wildner  * to include common repeats of characters (e.g., "777", "!!!", "1000") because
392*8c117293SSascha Wildner  * these are often taken care of by the requirement on the number of different
393*8c117293SSascha Wildner  * characters.
394*8c117293SSascha Wildner  */
395*8c117293SSascha Wildner const char * const seq[] = {
396*8c117293SSascha Wildner 	"0123456789",
397*8c117293SSascha Wildner 	"`1234567890-=",
398*8c117293SSascha Wildner 	"~!@#$%^&*()_+",
399*8c117293SSascha Wildner 	"abcdefghijklmnopqrstuvwxyz",
400*8c117293SSascha Wildner 	"a1b2c3d4e5f6g7h8i9j0",
401*8c117293SSascha Wildner 	"1a2b3c4d5e6f7g8h9i0j",
402*8c117293SSascha Wildner 	"abc123",
403*8c117293SSascha Wildner 	"qwertyuiop[]\\asdfghjkl;'zxcvbnm,./",
404*8c117293SSascha Wildner 	"qwertyuiop{}|asdfghjkl:\"zxcvbnm<>?",
405*8c117293SSascha Wildner 	"qwertyuiopasdfghjklzxcvbnm",
406*8c117293SSascha Wildner 	"1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]\\",
407*8c117293SSascha Wildner 	"!qaz@wsx#edc$rfv%tgb^yhn&ujm*ik<(ol>)p:?_{\"+}|",
408*8c117293SSascha Wildner 	"qazwsxedcrfvtgbyhnujmikolp",
409*8c117293SSascha Wildner 	"1q2w3e4r5t6y7u8i9o0p-[=]",
410*8c117293SSascha Wildner 	"q1w2e3r4t5y6u7i8o9p0[-]=\\",
411*8c117293SSascha Wildner 	"1qaz1qaz",
412*8c117293SSascha Wildner 	"1qaz!qaz", /* can't unify '1' and '!' - see comment in unify() */
413*8c117293SSascha Wildner 	"1qazzaq1",
414*8c117293SSascha Wildner 	"zaq!1qaz",
415*8c117293SSascha Wildner 	"zaq!2wsx"
416*8c117293SSascha Wildner };
417*8c117293SSascha Wildner 
41816d0e647SPeter Avalos /*
41916d0e647SPeter Avalos  * This wordlist check is now the least important given the checks above
42016d0e647SPeter Avalos  * and the support for passphrases (which are based on dictionary words,
42116d0e647SPeter Avalos  * and checked by other means).  It is still useful to trap simple short
42216d0e647SPeter Avalos  * passwords (if short passwords are allowed) that are word-based, but
42316d0e647SPeter Avalos  * passed the other checks due to uncommon capitalization, digits, and
42416d0e647SPeter Avalos  * special characters.  We (mis)use the same set of words that are used
42516d0e647SPeter Avalos  * to generate random passwords.  This list is much smaller than those
42616d0e647SPeter Avalos  * used for password crackers, and it doesn't contain common passwords
427*8c117293SSascha Wildner  * that aren't short English words.  We also support optional external
428*8c117293SSascha Wildner  * wordlist (for inexact matching) and deny list (for exact matching).
42916d0e647SPeter Avalos  */
is_word_based(const passwdqc_params_qc_t * params,const char * unified,const char * reversed,const char * original)430*8c117293SSascha Wildner static const char *is_word_based(const passwdqc_params_qc_t *params,
431*8c117293SSascha Wildner     const char *unified, const char *reversed, const char *original)
43216d0e647SPeter Avalos {
433*8c117293SSascha Wildner 	const char *reason = REASON_ERROR;
434*8c117293SSascha Wildner 	char word[WORDSET_4K_LENGTH_MAX + 1], *buf = NULL;
435*8c117293SSascha Wildner 	FILE *f = NULL;
436*8c117293SSascha Wildner 	unsigned int i;
43716d0e647SPeter Avalos 
438*8c117293SSascha Wildner 	word[WORDSET_4K_LENGTH_MAX] = '\0';
439*8c117293SSascha Wildner 	if (params->match_length)
440*8c117293SSascha Wildner 	for (i = 0; _passwdqc_wordset_4k[i][0]; i++) {
441*8c117293SSascha Wildner 		memcpy(word, _passwdqc_wordset_4k[i], WORDSET_4K_LENGTH_MAX);
442*8c117293SSascha Wildner 		int length = (int)strlen(word);
443*8c117293SSascha Wildner 		if (length < params->match_length)
444*8c117293SSascha Wildner 			continue;
445*8c117293SSascha Wildner 		if (!memcmp(word, _passwdqc_wordset_4k[i + 1], length))
446*8c117293SSascha Wildner 			continue;
447*8c117293SSascha Wildner 		unify(word, word);
448*8c117293SSascha Wildner 		if (is_based(params, word, unified, original, 1) ||
449*8c117293SSascha Wildner 		    is_based(params, word, reversed, original, 0x101)) {
450*8c117293SSascha Wildner 			reason = REASON_WORD;
451*8c117293SSascha Wildner 			goto out;
45216d0e647SPeter Avalos 		}
45316d0e647SPeter Avalos 	}
45416d0e647SPeter Avalos 
455*8c117293SSascha Wildner 	if (params->match_length)
456*8c117293SSascha Wildner 	for (i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) {
457*8c117293SSascha Wildner 		char *seq_i = unify(NULL, seq[i]);
458*8c117293SSascha Wildner 		if (!seq_i)
459*8c117293SSascha Wildner 			goto out;
460*8c117293SSascha Wildner 		if (is_based(params, seq_i, unified, original, 2) ||
461*8c117293SSascha Wildner 		    is_based(params, seq_i, reversed, original, 0x102)) {
462*8c117293SSascha Wildner 			clean(seq_i);
463*8c117293SSascha Wildner 			reason = REASON_SEQ;
464*8c117293SSascha Wildner 			goto out;
465*8c117293SSascha Wildner 		}
466*8c117293SSascha Wildner 		clean(seq_i);
46716d0e647SPeter Avalos 	}
46816d0e647SPeter Avalos 
469*8c117293SSascha Wildner 	if (params->match_length && params->match_length <= 4)
470*8c117293SSascha Wildner 	for (i = 1900; i <= 2039; i++) {
471*8c117293SSascha Wildner 		sprintf(word, "%u", i);
472*8c117293SSascha Wildner 		if (is_based(params, word, unified, original, 2) ||
473*8c117293SSascha Wildner 		    is_based(params, word, reversed, original, 0x102)) {
474*8c117293SSascha Wildner 			reason = REASON_SEQ;
475*8c117293SSascha Wildner 			goto out;
476*8c117293SSascha Wildner 		}
477*8c117293SSascha Wildner 	}
47816d0e647SPeter Avalos 
479*8c117293SSascha Wildner 	if (params->wordlist || params->denylist)
480*8c117293SSascha Wildner 		if (!(buf = malloc(READ_LINE_SIZE)))
481*8c117293SSascha Wildner 			goto out;
482*8c117293SSascha Wildner 
483*8c117293SSascha Wildner 	if (params->wordlist) {
484*8c117293SSascha Wildner 		if (!(f = fopen(params->wordlist, "r")))
485*8c117293SSascha Wildner 			goto out;
486*8c117293SSascha Wildner 		while (read_line(f, buf)) {
487*8c117293SSascha Wildner 			unify(buf, buf);
488*8c117293SSascha Wildner 			if (!strcmp(buf, unified) || !strcmp(buf, reversed))
489*8c117293SSascha Wildner 				goto out_wordlist;
490*8c117293SSascha Wildner 			if (!params->match_length ||
491*8c117293SSascha Wildner 			    strlen(buf) < (size_t)params->match_length)
492*8c117293SSascha Wildner 				continue;
493*8c117293SSascha Wildner 			if (is_based(params, buf, unified, original, 1) ||
494*8c117293SSascha Wildner 			    is_based(params, buf, reversed, original, 0x101)) {
495*8c117293SSascha Wildner out_wordlist:
496*8c117293SSascha Wildner 				reason = REASON_WORDLIST;
497*8c117293SSascha Wildner 				goto out;
498*8c117293SSascha Wildner 			}
499*8c117293SSascha Wildner 		}
500*8c117293SSascha Wildner 		if (ferror(f))
501*8c117293SSascha Wildner 			goto out;
502*8c117293SSascha Wildner 		fclose(f); f = NULL;
503*8c117293SSascha Wildner 	}
504*8c117293SSascha Wildner 
505*8c117293SSascha Wildner 	if (params->denylist) {
506*8c117293SSascha Wildner 		if (!(f = fopen(params->denylist, "r")))
507*8c117293SSascha Wildner 			goto out;
508*8c117293SSascha Wildner 		while (read_line(f, buf)) {
509*8c117293SSascha Wildner 			if (!strcmp(buf, original)) {
510*8c117293SSascha Wildner 				reason = REASON_DENYLIST;
511*8c117293SSascha Wildner 				goto out;
512*8c117293SSascha Wildner 			}
513*8c117293SSascha Wildner 		}
514*8c117293SSascha Wildner 		if (ferror(f))
515*8c117293SSascha Wildner 			goto out;
516*8c117293SSascha Wildner 	}
51716d0e647SPeter Avalos 
51816d0e647SPeter Avalos 	reason = NULL;
51916d0e647SPeter Avalos 
520*8c117293SSascha Wildner out:
521*8c117293SSascha Wildner 	if (f)
522*8c117293SSascha Wildner 		fclose(f);
523*8c117293SSascha Wildner 	if (buf) {
524*8c117293SSascha Wildner 		_passwdqc_memzero(buf, READ_LINE_SIZE);
525*8c117293SSascha Wildner 		free(buf);
526*8c117293SSascha Wildner 	}
527*8c117293SSascha Wildner 	_passwdqc_memzero(word, sizeof(word));
528*8c117293SSascha Wildner 	return reason;
529*8c117293SSascha Wildner }
53016d0e647SPeter Avalos 
passwdqc_check(const passwdqc_params_qc_t * params,const char * newpass,const char * oldpass,const struct passwd * pw)531*8c117293SSascha Wildner const char *passwdqc_check(const passwdqc_params_qc_t *params,
532*8c117293SSascha Wildner     const char *newpass, const char *oldpass, const struct passwd *pw)
533*8c117293SSascha Wildner {
534*8c117293SSascha Wildner 	char truncated[9];
535*8c117293SSascha Wildner 	char *u_newpass = NULL, *u_reversed = NULL;
536*8c117293SSascha Wildner 	char *u_oldpass = NULL;
537*8c117293SSascha Wildner 	char *u_name = NULL, *u_gecos = NULL, *u_dir = NULL;
538*8c117293SSascha Wildner 	const char *reason = REASON_ERROR;
53916d0e647SPeter Avalos 
540*8c117293SSascha Wildner 	size_t length = strlen(newpass);
541*8c117293SSascha Wildner 
542*8c117293SSascha Wildner 	if (length < (size_t)params->min[4]) {
54316d0e647SPeter Avalos 		reason = REASON_SHORT;
544*8c117293SSascha Wildner 		goto out;
545*8c117293SSascha Wildner 	}
54616d0e647SPeter Avalos 
547*8c117293SSascha Wildner 	if (length > 10000) {
548*8c117293SSascha Wildner 		reason = REASON_LONG;
549*8c117293SSascha Wildner 		goto out;
550*8c117293SSascha Wildner 	}
551*8c117293SSascha Wildner 
552*8c117293SSascha Wildner 	if (length > (size_t)params->max) {
55316d0e647SPeter Avalos 		if (params->max == 8) {
55416d0e647SPeter Avalos 			truncated[0] = '\0';
55516d0e647SPeter Avalos 			strncat(truncated, newpass, 8);
55616d0e647SPeter Avalos 			newpass = truncated;
557*8c117293SSascha Wildner 			length = 8;
558*8c117293SSascha Wildner 			if (oldpass && !strncmp(oldpass, newpass, 8)) {
55916d0e647SPeter Avalos 				reason = REASON_SAME;
560*8c117293SSascha Wildner 				goto out;
561*8c117293SSascha Wildner 			}
562*8c117293SSascha Wildner 		} else {
56316d0e647SPeter Avalos 			reason = REASON_LONG;
564*8c117293SSascha Wildner 			goto out;
565*8c117293SSascha Wildner 		}
56616d0e647SPeter Avalos 	}
56716d0e647SPeter Avalos 
568*8c117293SSascha Wildner 	if (oldpass && !strcmp(oldpass, newpass)) {
569*8c117293SSascha Wildner 		reason = REASON_SAME;
570*8c117293SSascha Wildner 		goto out;
571*8c117293SSascha Wildner 	}
572*8c117293SSascha Wildner 
573*8c117293SSascha Wildner 	if (is_simple(params, newpass, 0, 0)) {
57416d0e647SPeter Avalos 		reason = REASON_SIMPLE;
575*8c117293SSascha Wildner 		if (length < (size_t)params->min[1] &&
576*8c117293SSascha Wildner 		    params->min[1] <= params->max)
577*8c117293SSascha Wildner 			reason = REASON_SIMPLESHORT;
578*8c117293SSascha Wildner 		goto out;
57916d0e647SPeter Avalos 	}
58016d0e647SPeter Avalos 
581*8c117293SSascha Wildner 	if (!(u_newpass = unify(NULL, newpass)))
582*8c117293SSascha Wildner 		goto out; /* REASON_ERROR */
583*8c117293SSascha Wildner 	if (!(u_reversed = reverse(u_newpass)))
584*8c117293SSascha Wildner 		goto out;
585*8c117293SSascha Wildner 	if (oldpass && !(u_oldpass = unify(NULL, oldpass)))
586*8c117293SSascha Wildner 		goto out;
58716d0e647SPeter Avalos 	if (pw) {
588*8c117293SSascha Wildner 		if (!(u_name = unify(NULL, pw->pw_name)) ||
589*8c117293SSascha Wildner 		    !(u_gecos = unify(NULL, pw->pw_gecos)) ||
590*8c117293SSascha Wildner 		    !(u_dir = unify(NULL, pw->pw_dir)))
591*8c117293SSascha Wildner 			goto out;
59216d0e647SPeter Avalos 	}
59316d0e647SPeter Avalos 
594*8c117293SSascha Wildner 	if (oldpass && params->similar_deny &&
595*8c117293SSascha Wildner 	    (is_based(params, u_oldpass, u_newpass, newpass, 0) ||
596*8c117293SSascha Wildner 	     is_based(params, u_oldpass, u_reversed, newpass, 0x100))) {
59716d0e647SPeter Avalos 		reason = REASON_SIMILAR;
598*8c117293SSascha Wildner 		goto out;
599*8c117293SSascha Wildner 	}
60016d0e647SPeter Avalos 
601*8c117293SSascha Wildner 	if (pw &&
602*8c117293SSascha Wildner 	    (is_based(params, u_name, u_newpass, newpass, 0) ||
603*8c117293SSascha Wildner 	     is_based(params, u_name, u_reversed, newpass, 0x100) ||
604*8c117293SSascha Wildner 	     is_based(params, u_gecos, u_newpass, newpass, 0) ||
605*8c117293SSascha Wildner 	     is_based(params, u_gecos, u_reversed, newpass, 0x100) ||
606*8c117293SSascha Wildner 	     is_based(params, u_dir, u_newpass, newpass, 0) ||
607*8c117293SSascha Wildner 	     is_based(params, u_dir, u_reversed, newpass, 0x100))) {
60816d0e647SPeter Avalos 		reason = REASON_PERSONAL;
609*8c117293SSascha Wildner 		goto out;
610*8c117293SSascha Wildner 	}
61116d0e647SPeter Avalos 
612*8c117293SSascha Wildner 	reason = is_word_based(params, u_newpass, u_reversed, newpass);
61316d0e647SPeter Avalos 
614*8c117293SSascha Wildner 	if (!reason && params->filter) {
615*8c117293SSascha Wildner 		passwdqc_filter_t flt;
616*8c117293SSascha Wildner 		reason = REASON_ERROR;
617*8c117293SSascha Wildner 		if (passwdqc_filter_open(&flt, params->filter))
618*8c117293SSascha Wildner 			goto out;
619*8c117293SSascha Wildner 		int result = passwdqc_filter_lookup(&flt, newpass);
620*8c117293SSascha Wildner 		passwdqc_filter_close(&flt);
621*8c117293SSascha Wildner 		if (result < 0)
622*8c117293SSascha Wildner 			goto out;
623*8c117293SSascha Wildner 		reason = result ? REASON_FILTER : NULL;
624*8c117293SSascha Wildner 	}
625*8c117293SSascha Wildner 
626*8c117293SSascha Wildner out:
627*8c117293SSascha Wildner 	_passwdqc_memzero(truncated, sizeof(truncated));
628*8c117293SSascha Wildner 	clean(u_newpass);
629*8c117293SSascha Wildner 	clean(u_reversed);
63016d0e647SPeter Avalos 	clean(u_oldpass);
631*8c117293SSascha Wildner 	clean(u_name);
632*8c117293SSascha Wildner 	clean(u_gecos);
633*8c117293SSascha Wildner 	clean(u_dir);
63416d0e647SPeter Avalos 
63516d0e647SPeter Avalos 	return reason;
63616d0e647SPeter Avalos }
637