busybox/libbb/obscure.c
<<
>>
Prefs
   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 source tree.
   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 sub-string */
  49        if (strcasestr(p2, p1) != NULL
  50        /* invert in case haystack is shorter than needle */
  51         || strcasestr(p1, p2) != NULL
  52        /* as-is or capitalized */
  53        /* || strcasecmp(p1, p2) == 0 - 1st strcasestr should catch this too */
  54        ) {
  55                return 1;
  56        }
  57        return 0;
  58}
  59
  60static int string_checker(const char *p1, const char *p2)
  61{
  62        int size, i;
  63        /* check string */
  64        int ret = string_checker_helper(p1, p2);
  65        /* make our own copy */
  66        char *p = xstrdup(p1);
  67
  68        /* reverse string */
  69        i = size = strlen(p1);
  70        while (--i >= 0) {
  71                *p++ = p1[i];
  72        }
  73        p -= size; /* restore pointer */
  74
  75        /* check reversed string */
  76        ret |= string_checker_helper(p, p2);
  77
  78        /* clean up */
  79        nuke_str(p);
  80        free(p);
  81
  82        return ret;
  83}
  84
  85#define CATEGORIES  4
  86
  87#define LOWERCASE   1
  88#define UPPERCASE   2
  89#define NUMBERS     4
  90#define SPECIAL     8
  91
  92#define LAST_CAT    8
  93
  94static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw)
  95{
  96        unsigned length;
  97        unsigned size;
  98        unsigned mixed;
  99        unsigned c;
 100        unsigned i;
 101        const char *p;
 102        char *hostname;
 103
 104        /* size */
 105        if (!new_p || (length = strlen(new_p)) < CONFIG_PASSWORD_MINLEN)
 106                return "too short";
 107
 108        /* no username as-is, as sub-string, reversed, capitalized, doubled */
 109        if (string_checker(new_p, pw->pw_name)) {
 110                return "similar to username";
 111        }
 112#ifndef __BIONIC__
 113        /* no gecos as-is, as sub-string, reversed, capitalized, doubled */
 114        if (pw->pw_gecos[0] && string_checker(new_p, pw->pw_gecos)) {
 115                return "similar to gecos";
 116        }
 117#endif
 118        /* hostname as-is, as sub-string, reversed, capitalized, doubled */
 119        hostname = safe_gethostname();
 120        i = string_checker(new_p, hostname);
 121        free(hostname);
 122        if (i)
 123                return "similar to hostname";
 124
 125        /* Should / Must contain a mix of: */
 126        mixed = 0;
 127        for (i = 0; i < length; i++) {
 128                if (islower(new_p[i])) {        /* a-z */
 129                        mixed |= LOWERCASE;
 130                } else if (isupper(new_p[i])) { /* A-Z */
 131                        mixed |= UPPERCASE;
 132                } else if (isdigit(new_p[i])) { /* 0-9 */
 133                        mixed |= NUMBERS;
 134                } else  {                       /* special characters */
 135                        mixed |= SPECIAL;
 136                }
 137                /* Count i'th char */
 138                c = 0;
 139                p = new_p;
 140                while (1) {
 141                        p = strchr(p, new_p[i]);
 142                        if (p == NULL) {
 143                                break;
 144                        }
 145                        c++;
 146                        p++;
 147                        if (!*p) {
 148                                break;
 149                        }
 150                }
 151                /* More than 50% similar characters ? */
 152                if (c*2 >= length) {
 153                        return "too many similar characters";
 154                }
 155        }
 156
 157        size = CONFIG_PASSWORD_MINLEN + 2*CATEGORIES;
 158        for (i = 1; i <= LAST_CAT; i <<= 1)
 159                if (mixed & i)
 160                        size -= 2;
 161        if (length < size)
 162                return "too weak";
 163
 164        if (old_p && old_p[0]) {
 165                /* check vs. old password */
 166                if (string_checker(new_p, old_p)) {
 167                        return "similar to old password";
 168                }
 169        }
 170
 171        return NULL;
 172}
 173
 174int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *pw)
 175{
 176        const char *msg;
 177
 178        msg = obscure_msg(old, newval, pw);
 179        if (msg) {
 180                printf("Bad password: %s\n", msg);
 181                return 1;
 182        }
 183        return 0;
 184}
 185
 186#if ENABLE_UNIT_TEST
 187
 188/* Test obscure_msg() instead of obscure() in order not to print anything. */
 189
 190static const struct passwd pw = {
 191        .pw_name = (char *)"johndoe",
 192        .pw_gecos = (char *)"John Doe",
 193};
 194
 195BBUNIT_DEFINE_TEST(obscure_weak_pass)
 196{
 197        /* Empty password */
 198        BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "", &pw));
 199        /* Pure numbers */
 200        BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "23577315", &pw));
 201        /* Similar to pw_name */
 202        BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "johndoe123%", &pw));
 203        /* Similar to pw_gecos, reversed */
 204        BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "eoD nhoJ^44@", &pw));
 205        /* Similar to the old password */
 206        BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "d4#21?'S", &pw));
 207        /* adjacent letters */
 208        BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "qwerty123", &pw));
 209        /* Many similar chars */
 210        BBUNIT_ASSERT_NOTNULL(obscure_msg("Ad4#21?'S|", "^33Daaaaaa1", &pw));
 211
 212        BBUNIT_ENDTEST;
 213}
 214
 215BBUNIT_DEFINE_TEST(obscure_strong_pass)
 216{
 217        BBUNIT_ASSERT_NULL(obscure_msg("Rt4##2&:'|", "}(^#rrSX3S*22", &pw));
 218
 219        BBUNIT_ENDTEST;
 220}
 221
 222#endif /* ENABLE_UNIT_TEST */
 223