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