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