blob: ad17d1ff1b27c6051ee7ef18920c0eb701ef8525 [file] [log] [blame]
Eric Andersen27f64e12002-06-23 04:24:25 +00001/* vi: set sw=4 ts=4: */
2/*
Rob Landleya13cca92006-04-02 18:57:20 +00003 * Mini weak password checker implementation for busybox
Eric Andersen27f64e12002-06-23 04:24:25 +00004 *
Rob Landleya13cca92006-04-02 18:57:20 +00005 * Copyright (C) 2006 Tito Ragusa <farmatito@tiscali.it>
Eric Andersen27f64e12002-06-23 04:24:25 +00006 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02007 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
Eric Andersen27f64e12002-06-23 04:24:25 +00008 */
9
Rob Landleya13cca92006-04-02 18:57:20 +000010/* 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)
Bernhard Reutner-Fischera2a647d2006-05-19 12:30:00 +000021 as palindromes, consecutive or repetitive letters
Rob Landleya13cca92006-04-02 18:57:20 +000022 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)
Bernhard Reutner-Fischera2a647d2006-05-19 12:30:00 +000026 d) common and obvious letter-number replacements
Rob Landleya13cca92006-04-02 18:57:20 +000027 (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).
Eric Andersen27f64e12002-06-23 04:24:25 +000030
Rob Landleya13cca92006-04-02 18:57:20 +000031 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
Eric Andersen27f64e12002-06-23 04:24:25 +000042#include "libbb.h"
43
Rob Landleya13cca92006-04-02 18:57:20 +000044static int string_checker_helper(const char *p1, const char *p2) __attribute__ ((__pure__));
45
46static int string_checker_helper(const char *p1, const char *p2)
Eric Andersen27f64e12002-06-23 04:24:25 +000047{
Rob Landleya13cca92006-04-02 18:57:20 +000048 /* as sub-string */
Bob Dunlopd71c7702010-08-17 16:01:16 +020049 if (strcasestr(p2, p1) != NULL
Rob Landleya13cca92006-04-02 18:57:20 +000050 /* invert in case haystack is shorter than needle */
Bob Dunlopd71c7702010-08-17 16:01:16 +020051 || strcasestr(p1, p2) != NULL
52 /* as-is or capitalized */
53 /* || strcasecmp(p1, p2) == 0 - 1st strcasestr should catch this too */
54 ) {
Rob Landleya13cca92006-04-02 18:57:20 +000055 return 1;
Bob Dunlopd71c7702010-08-17 16:01:16 +020056 }
Rob Landleya13cca92006-04-02 18:57:20 +000057 return 0;
Eric Andersen27f64e12002-06-23 04:24:25 +000058}
59
Rob Landleya13cca92006-04-02 18:57:20 +000060static int string_checker(const char *p1, const char *p2)
Eric Andersen27f64e12002-06-23 04:24:25 +000061{
Bob Dunlopd71c7702010-08-17 16:01:16 +020062 int size, i;
Rob Landleya13cca92006-04-02 18:57:20 +000063 /* check string */
64 int ret = string_checker_helper(p1, p2);
Bob Dunlopd71c7702010-08-17 16:01:16 +020065 /* make our own copy */
Rob Landleyd921b2e2006-08-03 15:41:12 +000066 char *p = xstrdup(p1);
Rob Landleya13cca92006-04-02 18:57:20 +000067
Bob Dunlopd71c7702010-08-17 16:01:16 +020068 /* reverse string */
69 i = size = strlen(p1);
70 while (--i >= 0) {
71 *p++ = p1[i];
Rob Landleya13cca92006-04-02 18:57:20 +000072 }
Bob Dunlopd71c7702010-08-17 16:01:16 +020073 p -= size; /* restore pointer */
74
Rob Landleya13cca92006-04-02 18:57:20 +000075 /* check reversed string */
76 ret |= string_checker_helper(p, p2);
Bob Dunlopd71c7702010-08-17 16:01:16 +020077
Rob Landleya13cca92006-04-02 18:57:20 +000078 /* clean up */
Denys Vlasenkoeb9f4852014-04-16 19:51:34 +020079 nuke_str(p);
Rob Landleya13cca92006-04-02 18:57:20 +000080 free(p);
Bob Dunlopd71c7702010-08-17 16:01:16 +020081
Rob Landleya13cca92006-04-02 18:57:20 +000082 return ret;
83}
84
Bob Dunlopd71c7702010-08-17 16:01:16 +020085#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
Rob Landleya13cca92006-04-02 18:57:20 +000093
Bernhard Reutner-Fischera2a647d2006-05-19 12:30:00 +000094static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw)
Rob Landleya13cca92006-04-02 18:57:20 +000095{
Bob Dunlopd71c7702010-08-17 16:01:16 +020096 unsigned length;
97 unsigned size;
98 unsigned mixed;
99 unsigned c;
100 unsigned i;
Rob Landleya13cca92006-04-02 18:57:20 +0000101 const char *p;
Denis Vlasenko6f1713f2008-02-25 23:23:58 +0000102 char *hostname;
Eric Andersen27f64e12002-06-23 04:24:25 +0000103
Rob Landleya13cca92006-04-02 18:57:20 +0000104 /* size */
"Robert P. J. Day"087b9d62006-07-02 18:35:39 +0000105 if (!new_p || (length = strlen(new_p)) < CONFIG_PASSWORD_MINLEN)
Denis Vlasenko079f8af2006-11-27 16:49:31 +0000106 return "too short";
Bernhard Reutner-Fischera2a647d2006-05-19 12:30:00 +0000107
Rob Landleya13cca92006-04-02 18:57:20 +0000108 /* 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 }
Denys Vlasenko8f6ce092011-07-08 08:34:28 +0200112#ifndef __BIONIC__
Rob Landleya13cca92006-04-02 18:57:20 +0000113 /* no gecos as-is, as sub-string, reversed, capitalized, doubled */
Bob Dunlopd71c7702010-08-17 16:01:16 +0200114 if (pw->pw_gecos[0] && string_checker(new_p, pw->pw_gecos)) {
Rob Landleya13cca92006-04-02 18:57:20 +0000115 return "similar to gecos";
116 }
Denys Vlasenko8f6ce092011-07-08 08:34:28 +0200117#endif
Rob Landleya13cca92006-04-02 18:57:20 +0000118 /* hostname as-is, as sub-string, reversed, capitalized, doubled */
Denis Vlasenko6f1713f2008-02-25 23:23:58 +0000119 hostname = safe_gethostname();
120 i = string_checker(new_p, hostname);
121 free(hostname);
122 if (i)
123 return "similar to hostname";
Eric Andersen27f64e12002-06-23 04:24:25 +0000124
Rob Landleya13cca92006-04-02 18:57:20 +0000125 /* Should / Must contain a mix of: */
Bob Dunlopd71c7702010-08-17 16:01:16 +0200126 mixed = 0;
Rob Landleya13cca92006-04-02 18:57:20 +0000127 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 }
Bob Dunlopd71c7702010-08-17 16:01:16 +0200137 /* Count i'th char */
Rob Landleya13cca92006-04-02 18:57:20 +0000138 c = 0;
139 p = new_p;
140 while (1) {
Denis Vlasenko6bef3d12007-11-06 03:05:54 +0000141 p = strchr(p, new_p[i]);
142 if (p == NULL) {
Rob Landleya13cca92006-04-02 18:57:20 +0000143 break;
144 }
145 c++;
Bob Dunlopd71c7702010-08-17 16:01:16 +0200146 p++;
147 if (!*p) {
148 break;
Rob Landleya13cca92006-04-02 18:57:20 +0000149 }
150 }
Bob Dunlopd71c7702010-08-17 16:01:16 +0200151 /* More than 50% similar characters ? */
152 if (c*2 >= length) {
Rob Landleya13cca92006-04-02 18:57:20 +0000153 return "too many similar characters";
154 }
Eric Andersen3124a9e2003-07-30 07:57:06 +0000155 }
Bob Dunlopd71c7702010-08-17 16:01:16 +0200156
157 size = CONFIG_PASSWORD_MINLEN + 2*CATEGORIES;
Denys Vlasenkofd27fa82010-08-17 17:21:36 +0200158 for (i = 1; i <= LAST_CAT; i <<= 1)
Bob Dunlopd71c7702010-08-17 16:01:16 +0200159 if (mixed & i)
160 size -= 2;
Rob Landleya13cca92006-04-02 18:57:20 +0000161 if (length < size)
162 return "too weak";
Bernhard Reutner-Fischera2a647d2006-05-19 12:30:00 +0000163
Bob Dunlopd71c7702010-08-17 16:01:16 +0200164 if (old_p && old_p[0]) {
Rob Landleya13cca92006-04-02 18:57:20 +0000165 /* check vs. old password */
166 if (string_checker(new_p, old_p)) {
167 return "similar to old password";
168 }
169 }
Bob Dunlopd71c7702010-08-17 16:01:16 +0200170
Rob Landleya13cca92006-04-02 18:57:20 +0000171 return NULL;
Eric Andersen27f64e12002-06-23 04:24:25 +0000172}
173
Denis Vlasenkodefc1ea2008-06-27 02:52:20 +0000174int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *pw)
Eric Andersen27f64e12002-06-23 04:24:25 +0000175{
Rob Landleya13cca92006-04-02 18:57:20 +0000176 const char *msg;
Eric Andersen27f64e12002-06-23 04:24:25 +0000177
Denis Vlasenkoab24e182006-11-30 16:41:15 +0000178 msg = obscure_msg(old, newval, pw);
179 if (msg) {
180 printf("Bad password: %s\n", msg);
181 return 1;
Eric Andersen27f64e12002-06-23 04:24:25 +0000182 }
Eric Andersen27f64e12002-06-23 04:24:25 +0000183 return 0;
184}
Bartosz Golaszewski3ed81cf2014-06-22 16:30:41 +0200185
186#if ENABLE_UNIT_TEST
187
188/* Test obscure_msg() instead of obscure() in order not to print anything. */
189
190static const struct passwd pw = {
191 .pw_name = (char *)"johndoe",
192 .pw_gecos = (char *)"John Doe",
193};
194
195BBUNIT_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
215BBUNIT_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 */