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