summaryrefslogtreecommitdiffhomepage
path: root/libbb/obscure.c
blob: 24c4ac917119bd7011b3d1a967e233fb07c484f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/* vi: set sw=4 ts=4: */
/*
 * Mini weak password checker implementation for busybox
 *
 * Copyright (C) 2006 Tito Ragusa <farmatito@tiscali.it>
 *
 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
 */

/*	A good password:
	1)	should contain at least six characters (man passwd);
	2)	empty passwords are not permitted;
	3)	should contain a mix of four different types of characters
		upper case letters,
		lower case letters,
		numbers,
		special characters such as !@#$%^&*,;".
	This password types should not  be permitted:
	a)	pure numbers: birthdates, social security number, license plate, phone numbers;
	b)	words and all letters only passwords (uppercase, lowercase or mixed)
		as palindromes, consecutive or repetitive letters
		or adjacent letters on your keyboard;
	c)	username, real name, company name or (e-mail?) address
		in any form (as-is, reversed, capitalized, doubled, etc.).
		(we can check only against username, gecos and hostname)
	d)	common and obvious letter-number replacements
		(e.g. replace the letter O with number 0)
		such as "M1cr0$0ft" or "P@ssw0rd" (CAVEAT: we cannot check for them
		without the use of a dictionary).

	For each missing type of characters an increase of password length is
	requested.

	If user is root we warn only.

	CAVEAT: some older versions of crypt() truncates passwords to 8 chars,
	so that aaaaaaaa1Q$ is equal to aaaaaaaa making it possible to fool
	some of our checks. We don't test for this special case as newer versions
	of crypt do not truncate passwords.
*/

#include "libbb.h"

static int string_checker_helper(const char *p1, const char *p2) __attribute__ ((__pure__));

static int string_checker_helper(const char *p1, const char *p2)
{
	/* as sub-string */
	if (strcasestr(p2, p1) != NULL
	/* invert in case haystack is shorter than needle */
	 || strcasestr(p1, p2) != NULL
	/* as-is or capitalized */
	/* || strcasecmp(p1, p2) == 0 - 1st strcasestr should catch this too */
	) {
		return 1;
	}
	return 0;
}

static int string_checker(const char *p1, const char *p2)
{
	int size, i;
	/* check string */
	int ret = string_checker_helper(p1, p2);
	/* make our own copy */
	char *p = xstrdup(p1);

	/* reverse string */
	i = size = strlen(p1);
	while (--i >= 0) {
		*p++ = p1[i];
	}
	p -= size; /* restore pointer */

	/* check reversed string */
	ret |= string_checker_helper(p, p2);

	/* clean up */
	nuke_str(p);
	free(p);

	return ret;
}

#define CATEGORIES  4

#define LOWERCASE   1
#define UPPERCASE   2
#define NUMBERS     4
#define SPECIAL     8

#define LAST_CAT    8

static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw)
{
	unsigned length;
	unsigned size;
	unsigned mixed;
	unsigned c;
	unsigned i;
	const char *p;
	char *hostname;

	/* size */
	if (!new_p || (length = strlen(new_p)) < CONFIG_PASSWORD_MINLEN)
		return "too short";

	/* no username as-is, as sub-string, reversed, capitalized, doubled */
	if (string_checker(new_p, pw->pw_name)) {
		return "similar to username";
	}
#ifndef __BIONIC__
	/* no gecos as-is, as sub-string, reversed, capitalized, doubled */
	if (pw->pw_gecos[0] && string_checker(new_p, pw->pw_gecos)) {
		return "similar to gecos";
	}
#endif
	/* hostname as-is, as sub-string, reversed, capitalized, doubled */
	hostname = safe_gethostname();
	i = string_checker(new_p, hostname);
	free(hostname);
	if (i)
		return "similar to hostname";

	/* Should / Must contain a mix of: */
	mixed = 0;
	for (i = 0; i < length; i++) {
		if (islower(new_p[i])) {        /* a-z */
			mixed |= LOWERCASE;
		} else if (isupper(new_p[i])) { /* A-Z */
			mixed |= UPPERCASE;
		} else if (isdigit(new_p[i])) { /* 0-9 */
			mixed |= NUMBERS;
		} else  {                       /* special characters */
			mixed |= SPECIAL;
		}
		/* Count i'th char */
		c = 0;
		p = new_p;
		while (1) {
			p = strchr(p, new_p[i]);
			if (p == NULL) {
				break;
			}
			c++;
			p++;
			if (!*p) {
				break;
			}
		}
		/* More than 50% similar characters ? */
		if (c*2 >= length) {
			return "too many similar characters";
		}
	}

	size = CONFIG_PASSWORD_MINLEN + 2*CATEGORIES;
	for (i = 1; i <= LAST_CAT; i <<= 1)
		if (mixed & i)
			size -= 2;
	if (length < size)
		return "too weak";

	if (old_p && old_p[0]) {
		/* check vs. old password */
		if (string_checker(new_p, old_p)) {
			return "similar to old password";
		}
	}

	return NULL;
}

int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *pw)
{
	const char *msg;

	msg = obscure_msg(old, newval, pw);
	if (msg) {
		printf("Bad password: %s\n", msg);
		return 1;
	}
	return 0;
}