1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini weak password checker implementation for busybox
4  *
5  * Copyright (C) 2006 Tito Ragusa <farmatito@tiscali.it>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9 
10 /*	A good password:
11 	1)	should contain at least six characters (man passwd);
12 	2)	empty passwords are not permitted;
13 	3)	should contain a mix of four different types of characters
14 		upper case letters,
15 		lower case letters,
16 		numbers,
17 		special characters such as !@#$%^&*,;".
18 	This password types should not  be permitted:
19 	a)	pure numbers: birthdates, social security number, license plate, phone numbers;
20 	b)	words and all letters only passwords (uppercase, lowercase or mixed)
21 		as palindromes, consecutive or repetitive letters
22 		or adjacent letters on your keyboard;
23 	c)	username, real name, company name or (e-mail?) address
24 		in any form (as-is, reversed, capitalized, doubled, etc.).
25 		(we can check only against username, gecos and hostname)
26 	d)	common and obvious letter-number replacements
27 		(e.g. replace the letter O with number 0)
28 		such as "M1cr0$0ft" or "P@ssw0rd" (CAVEAT: we cannot check for them
29 		without the use of a dictionary).
30 
31 	For each missing type of characters an increase of password length is
32 	requested.
33 
34 	If user is root we warn only.
35 
36 	CAVEAT: some older versions of crypt() truncates passwords to 8 chars,
37 	so that aaaaaaaa1Q$ is equal to aaaaaaaa making it possible to fool
38 	some of our checks. We don't test for this special case as newer versions
39 	of crypt do not truncate passwords.
40 */
41 
42 #include "libbb.h"
43 
44 static int string_checker_helper(const char *p1, const char *p2) __attribute__ ((__pure__));
45 
string_checker_helper(const char * p1,const char * p2)46 static int string_checker_helper(const char *p1, const char *p2)
47 {
48 	/* as sub-string */
49 	if (strcasestr(p2, p1) != NULL
50 	/* invert in case haystack is shorter than needle */
51 	 || strcasestr(p1, p2) != NULL
52 	/* as-is or capitalized */
53 	/* || strcasecmp(p1, p2) == 0 - 1st strcasestr should catch this too */
54 	) {
55 		return 1;
56 	}
57 	return 0;
58 }
59 
string_checker(const char * p1,const char * p2)60 static int string_checker(const char *p1, const char *p2)
61 {
62 	int size, i;
63 	/* check string */
64 	int ret = string_checker_helper(p1, p2);
65 	/* make our own copy */
66 	char *p = xstrdup(p1);
67 
68 	/* reverse string */
69 	i = size = strlen(p1);
70 	while (--i >= 0) {
71 		*p++ = p1[i];
72 	}
73 	p -= size; /* restore pointer */
74 
75 	/* check reversed string */
76 	ret |= string_checker_helper(p, p2);
77 
78 	/* clean up */
79 	nuke_str(p);
80 	free(p);
81 
82 	return ret;
83 }
84 
85 #define CATEGORIES  4
86 
87 #define LOWERCASE   1
88 #define UPPERCASE   2
89 #define NUMBERS     4
90 #define SPECIAL     8
91 
92 #define LAST_CAT    8
93 
obscure_msg(const char * old_p,const char * new_p,const struct passwd * pw)94 static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw)
95 {
96 	unsigned length;
97 	unsigned size;
98 	unsigned mixed;
99 	unsigned c;
100 	unsigned i;
101 	const char *p;
102 	char *hostname;
103 
104 	/* size */
105 	if (!new_p || (length = strlen(new_p)) < CONFIG_PASSWORD_MINLEN)
106 		return "too short";
107 
108 	/* no username as-is, as sub-string, reversed, capitalized, doubled */
109 	if (string_checker(new_p, pw->pw_name)) {
110 		return "similar to username";
111 	}
112 #ifndef __BIONIC__
113 	/* no gecos as-is, as sub-string, reversed, capitalized, doubled */
114 	if (pw->pw_gecos[0] && string_checker(new_p, pw->pw_gecos)) {
115 		return "similar to gecos";
116 	}
117 #endif
118 	/* hostname as-is, as sub-string, reversed, capitalized, doubled */
119 	hostname = safe_gethostname();
120 	i = string_checker(new_p, hostname);
121 	free(hostname);
122 	if (i)
123 		return "similar to hostname";
124 
125 	/* Should / Must contain a mix of: */
126 	mixed = 0;
127 	for (i = 0; i < length; i++) {
128 		if (islower(new_p[i])) {        /* a-z */
129 			mixed |= LOWERCASE;
130 		} else if (isupper(new_p[i])) { /* A-Z */
131 			mixed |= UPPERCASE;
132 		} else if (isdigit(new_p[i])) { /* 0-9 */
133 			mixed |= NUMBERS;
134 		} else  {                       /* special characters */
135 			mixed |= SPECIAL;
136 		}
137 		/* Count i'th char */
138 		c = 0;
139 		p = new_p;
140 		while (1) {
141 			p = strchr(p, new_p[i]);
142 			if (p == NULL) {
143 				break;
144 			}
145 			c++;
146 			p++;
147 			if (!*p) {
148 				break;
149 			}
150 		}
151 		/* More than 50% similar characters ? */
152 		if (c*2 >= length) {
153 			return "too many similar characters";
154 		}
155 	}
156 
157 	size = CONFIG_PASSWORD_MINLEN + 2*CATEGORIES;
158 	for (i = 1; i <= LAST_CAT; i <<= 1)
159 		if (mixed & i)
160 			size -= 2;
161 	if (length < size)
162 		return "too weak";
163 
164 	if (old_p && old_p[0]) {
165 		/* check vs. old password */
166 		if (string_checker(new_p, old_p)) {
167 			return "similar to old password";
168 		}
169 	}
170 
171 	return NULL;
172 }
173 
obscure(const char * old,const char * newval,const struct passwd * pw)174 int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *pw)
175 {
176 	const char *msg;
177 
178 	msg = obscure_msg(old, newval, pw);
179 	if (msg) {
180 		printf("Bad password: %s\n", msg);
181 		return 1;
182 	}
183 	return 0;
184 }
185 
186 #if ENABLE_UNIT_TEST
187 
188 /* Test obscure_msg() instead of obscure() in order not to print anything. */
189 
190 static const struct passwd pw = {
191 	.pw_name = (char *)"johndoe",
192 	.pw_gecos = (char *)"John Doe",
193 };
194 
BBUNIT_DEFINE_TEST(obscure_weak_pass)195 BBUNIT_DEFINE_TEST(obscure_weak_pass)
196 {
197 	/* Empty password */
198 	BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "", &pw));
199 	/* Pure numbers */
200 	BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "23577315", &pw));
201 	/* Similar to pw_name */
202 	BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "johndoe123%", &pw));
203 	/* Similar to pw_gecos, reversed */
204 	BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "eoD nhoJ^44@", &pw));
205 	/* Similar to the old password */
206 	BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "d4#21?'S", &pw));
207 	/* adjacent letters */
208 	BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "qwerty123", &pw));
209 	/* Many similar chars */
210 	BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "^33Daaaaaa1", &pw));
211 
212 	BBUNIT_ENDTEST;
213 }
214 
BBUNIT_DEFINE_TEST(obscure_strong_pass)215 BBUNIT_DEFINE_TEST(obscure_strong_pass)
216 {
217 	BBUNIT_ASSERT_NULL(obscure_msg("Rt4##2&:'|", "}(^#rrSX3S*22", &pw));
218 
219 	BBUNIT_ENDTEST;
220 }
221 
222 #endif /* ENABLE_UNIT_TEST */
223