123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- /* 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 */
|