mirror of
https://github.com/sheumann/hush.git
synced 2024-08-14 11:28:58 +00:00
This set of patches adds a simple unit-testing framework to Busybox unit-tests: add some helper macros for unit-test framework implementation unit-tests: implement the unit-testing framework unit-tests: add basic documentation on writing the unit test cases unit-tests: modify the Makefile 'test' target to run unit-tests too unit-tests: add two example test cases unit-tests: modify the existing strrstr test code to use the unit-test framework Signed-off-by: Bartosz Golaszewski <bartekgola@gmail.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
223 lines
5.6 KiB
C
223 lines
5.6 KiB
C
/* 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;
|
|
}
|
|
|
|
#if ENABLE_UNIT_TEST
|
|
|
|
/* Test obscure_msg() instead of obscure() in order not to print anything. */
|
|
|
|
static const struct passwd pw = {
|
|
.pw_name = (char *)"johndoe",
|
|
.pw_gecos = (char *)"John Doe",
|
|
};
|
|
|
|
BBUNIT_DEFINE_TEST(obscure_weak_pass)
|
|
{
|
|
/* Empty password */
|
|
BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "", &pw));
|
|
/* Pure numbers */
|
|
BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "23577315", &pw));
|
|
/* Similar to pw_name */
|
|
BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "johndoe123%", &pw));
|
|
/* Similar to pw_gecos, reversed */
|
|
BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "eoD nhoJ^44@", &pw));
|
|
/* Similar to the old password */
|
|
BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "d4#21?'S", &pw));
|
|
/* adjacent letters */
|
|
BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "qwerty123", &pw));
|
|
/* Many similar chars */
|
|
BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "^33Daaaaaa1", &pw));
|
|
|
|
BBUNIT_ENDTEST;
|
|
}
|
|
|
|
BBUNIT_DEFINE_TEST(obscure_strong_pass)
|
|
{
|
|
BBUNIT_ASSERT_NULL(obscure_msg("Rt4##2&:'|", "}(^#rrSX3S*22", &pw));
|
|
|
|
BBUNIT_ENDTEST;
|
|
}
|
|
|
|
#endif /* ENABLE_UNIT_TEST */
|