- add testcase for grep bug (http://busybox.net/bugs/view.php?id=887)
[oweals/busybox.git] / libbb / obscure.c
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 tarball for details.
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 <ctype.h>
43 #include <unistd.h>
44 #include <string.h>
45 #include <strings.h>
46
47 #include "libbb.h"
48
49
50 /* passwords should consist of 6 (to 8 characters) */
51 #define MINLEN 6
52
53
54 static int string_checker_helper(const char *p1, const char *p2) __attribute__ ((__pure__));
55
56 static int string_checker_helper(const char *p1, const char *p2)
57 {
58         /* as-is or capitalized */
59         if (strcasecmp(p1, p2) == 0
60         /* as sub-string */
61         || strcasestr(p2, p1) != NULL
62         /* invert in case haystack is shorter than needle */
63         || strcasestr(p1, p2) != NULL)
64                 return 1;
65         return 0;
66 }
67
68 static int string_checker(const char *p1, const char *p2)
69 {
70         int size;
71         /* check string */
72         int ret = string_checker_helper(p1, p2);
73         /* Make our own copy */
74         char *p = bb_xstrdup(p1);
75         /* reverse string */
76         size = strlen(p);
77
78         while (size--) {
79                 *p = p1[size];
80                 p++;
81         }
82         /* restore pointer */
83         p -= strlen(p1);
84         /* check reversed string */
85         ret |= string_checker_helper(p, p2);
86         /* clean up */
87         memset(p, 0, strlen(p1));
88         free(p);
89         return ret;
90 }
91
92 #define LOWERCASE          1
93 #define UPPERCASE          2
94 #define NUMBERS            4
95 #define SPECIAL            8
96
97 static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw)
98 {
99         int i;
100         int c;
101         int length;
102         int mixed = 0;
103         /* Add 1 for each type of characters to the minlen of password */
104         int size = MINLEN + 8;
105         const char *p;
106         char hostname[255];
107
108         /* size */
109         if (!new_p || (length = strlen(new_p)) < MINLEN)
110                 return("too short");
111
112         /* no username as-is, as sub-string, reversed, capitalized, doubled */
113         if (string_checker(new_p, pw->pw_name)) {
114                 return "similar to username";
115         }
116         /* no gecos as-is, as sub-string, reversed, capitalized, doubled */
117         if (string_checker(new_p, pw->pw_gecos)) {
118                 return "similar to gecos";
119         }
120         /* hostname as-is, as sub-string, reversed, capitalized, doubled */
121         if (gethostname(hostname, 255) == 0) {
122                 hostname[254] = '\0';
123                 if (string_checker(new_p, hostname)) {
124                         return "similar to hostname";
125                 }
126         }
127
128         /* Should / Must contain a mix of: */
129         for (i = 0; i < length; i++) {
130                 if (islower(new_p[i])) {        /* a-z */
131                         mixed |= LOWERCASE;
132                 } else if (isupper(new_p[i])) { /* A-Z */
133                         mixed |= UPPERCASE;
134                 } else if (isdigit(new_p[i])) { /* 0-9 */
135                         mixed |= NUMBERS;
136                 } else  {                       /* special characters */
137                         mixed |= SPECIAL;
138                 }
139                 /* More than 50% similar characters ? */
140                 c = 0;
141                 p = new_p;
142                 while (1) {
143                         if ((p = strchr(p, new_p[i])) == NULL) {
144                                 break;
145                         }
146                         c++;
147                         if (!++p) {
148                                 break; /* move past the matched char if possible */
149                         }
150                 }
151
152                 if (c >= (length / 2)) {
153                         return "too many similar characters";
154                 }
155         }
156         for (i=0; i<4; i++)
157                 if (mixed & (1<<i)) size -= 2;
158         if (length < size)
159                 return "too weak";
160
161         if (old_p && old_p[0] != '\0') {
162                 /* check vs. old password */
163                 if (string_checker(new_p, old_p)) {
164                         return "similar to old password";
165                 }
166         }
167         return NULL;
168 }
169
170 int obscure(const char *old, const char *newval, const struct passwd *pwdp)
171 {
172         const char *msg;
173
174         if ((msg = obscure_msg(old, newval, pwdp))) {
175                 printf("Bad password: %s.\n", msg);
176                 /* If user is root warn only */
177                 return (getuid())? 1 : 0;
178         }
179         return 0;
180 }