ash: builtin: Mark more regular built-ins
[oweals/busybox.git] / libbb / obscure.c
index aa15e409717f2fc40d423cf87fd9c5d080326b7a..ad17d1ff1b27c6051ee7ef18920c0eb701ef8525 100644 (file)
 /* vi: set sw=4 ts=4: */
 /*
- * Copyright 1989 - 1994, Julianne Frances Haugh <jockgrrl@austin.rr.com>
- * All rights reserved.
+ * Mini weak password checker implementation for busybox
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of Julianne F. Haugh nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
+ * Copyright (C) 2006 Tito Ragusa <farmatito@tiscali.it>
  *
- * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
 
-/*
- * This version of obscure.c contains modifications to support "cracklib"
- * by Alec Muffet (alec.muffett@uk.sun.com).  You must obtain the Cracklib
- * library source code for this function to operate.
- */
+/*     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 <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
 #include "libbb.h"
 
-/*
- * can't be a palindrome - like `R A D A R' or `M A D A M'
- */
-
-static int palindrome(const char *newval)
-{
-       int i, j;
-
-       i = strlen(newval);
-
-       for (j = 0; j < i; j++)
-               if (newval[i - j - 1] != newval[j])
-                       return 0;
-
-       return 1;
-}
-
-/*
- * more than half of the characters are different ones.
- */
+static int string_checker_helper(const char *p1, const char *p2) __attribute__ ((__pure__));
 
-static int similiar(const char *old, const char *newval)
+static int string_checker_helper(const char *p1, const char *p2)
 {
-       int i, j;
-
-       for (i = j = 0; newval[i] && old[i]; i++)
-               if (strchr(newval, old[i]))
-                       j++;
-
-       if (i >= j * 2)
-               return 0;
-
-       return 1;
+       /* 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;
 }
 
-/*
- * a nice mix of characters.
- */
-
-static int simple(const char *newval)
+static int string_checker(const char *p1, const char *p2)
 {
-       int digits = 0;
-       int uppers = 0;
-       int lowers = 0;
-       int others = 0;
-       int c;
-       int size;
-       int i;
-
-       for (i = 0; (c = *newval++) != 0; i++) {
-               if (isdigit(c))
-                       digits = c;
-               else if (isupper(c))
-                       uppers = c;
-               else if (islower(c))
-                       lowers = c;
-               else
-                       others = c;
+       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 */
 
-       /*
-        * The scam is this - a password of only one character type
-        * must be 8 letters long.  Two types, 7, and so on.
-        */
-
-       size = 9;
-       if (digits)
-               size--;
-       if (uppers)
-               size--;
-       if (lowers)
-               size--;
-       if (others)
-               size--;
-
-       if (size <= i)
-               return 0;
-
-       return 1;
-}
+       /* check reversed string */
+       ret |= string_checker_helper(p, p2);
 
-static char *str_lower(char *string)
-{
-       char *cp;
+       /* clean up */
+       nuke_str(p);
+       free(p);
 
-       for (cp = string; *cp; cp++)
-               *cp = tolower(*cp);
-       return string;
+       return ret;
 }
 
-static const char *
-password_check(const char *old, const char *newval, const struct passwd *pwdp)
-{
-       const char *msg;
-       char *newmono, *wrapped;
-       int lenwrap;
-
-       if (strcmp(newval, old) == 0)
-               return "no change";
-       if (simple(newval))
-               return "too simple";
+#define CATEGORIES  4
 
-       msg = NULL;
-       newmono = str_lower(bb_xstrdup(newval));
-       lenwrap = strlen(old);
-       wrapped = (char *) xmalloc(lenwrap * 2 + 1);
-       str_lower(strcpy(wrapped, old));
+#define LOWERCASE   1
+#define UPPERCASE   2
+#define NUMBERS     4
+#define SPECIAL     8
 
-       if (palindrome(newmono))
-               msg = "a palindrome";
+#define LAST_CAT    8
 
-       else if (strcmp(wrapped, newmono) == 0)
-               msg = "case changes only";
-
-       else if (similiar(wrapped, newmono))
-               msg = "too similiar";
+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";
 
-       else {
-               safe_strncpy(wrapped + lenwrap, wrapped, lenwrap + 1);
-               if (strstr(wrapped, newmono))
-                       msg = "rotated";
+       /* 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";
+               }
        }
 
-       bzero(newmono, strlen(newmono));
-       bzero(wrapped, lenwrap * 2);
-       free(newmono);
-       free(wrapped);
+       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 msg;
+       return NULL;
 }
 
-static const char *
-obscure_msg(const char *old, const char *newval, const struct passwd *pwdp)
+int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *pw)
 {
-       int maxlen, oldlen, newlen;
-       char *new1, *old1;
        const char *msg;
 
-       oldlen = strlen(old);
-       newlen = strlen(newval);
+       msg = obscure_msg(old, newval, pw);
+       if (msg) {
+               printf("Bad password: %s\n", msg);
+               return 1;
+       }
+       return 0;
+}
 
-#if 0                                                  /* why not check the password when set for the first time?  --marekm */
-       if (old[0] == '\0')
-               /* return (1); */
-               return NULL;
-#endif
+#if ENABLE_UNIT_TEST
 
-       if (newlen < 5)
-               return "too short";
+/* Test obscure_msg() instead of obscure() in order not to print anything. */
 
-       /*
-        * Remaining checks are optional.
-        */
-       /* Not for us -- Sean
-        *if (!getdef_bool("OBSCURE_CHECKS_ENAB"))
-        *      return NULL;
-        */
-       msg = password_check(old, newval, pwdp);
-       if (msg)
-               return msg;
-
-       /* The traditional crypt() truncates passwords to 8 chars.  It is
-          possible to circumvent the above checks by choosing an easy
-          8-char password and adding some random characters to it...
-          Example: "password$%^&*123".  So check it again, this time
-          truncated to the maximum length.  Idea from npasswd.  --marekm */
-
-       maxlen = 8;
-       if (oldlen <= maxlen && newlen <= maxlen)
-               return NULL;
-
-       new1 = (char *) bb_xstrdup(newval);
-       old1 = (char *) bb_xstrdup(old);
-       if (newlen > maxlen)
-               new1[maxlen] = '\0';
-       if (oldlen > maxlen)
-               old1[maxlen] = '\0';
-
-       msg = password_check(old1, new1, pwdp);
-
-       bzero(new1, newlen);
-       bzero(old1, oldlen);
-       free(new1);
-       free(old1);
-
-       return msg;
-}
+static const struct passwd pw = {
+       .pw_name = (char *)"johndoe",
+       .pw_gecos = (char *)"John Doe",
+};
 
-/*
- * Obscure - see if password is obscure enough.
- *
- *     The programmer is encouraged to add as much complexity to this
- *     routine as desired.  Included are some of my favorite ways to
- *     check passwords.
- */
+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;
+}
 
-extern int obscure(const char *old, const char *newval, const struct passwd *pwdp)
+BBUNIT_DEFINE_TEST(obscure_strong_pass)
 {
-       const char *msg = obscure_msg(old, newval, pwdp);
+       BBUNIT_ASSERT_NULL(obscure_msg("Rt4##2&:'|", "}(^#rrSX3S*22", &pw));
 
-       /*  if (msg) { */
-       if (msg != NULL) {
-               printf("Bad password: %s.\n", msg);
-               /* return 0; */
-               return 1;
-       }
-       /* return 1; */
-       return 0;
+       BBUNIT_ENDTEST;
 }
+
+#endif /* ENABLE_UNIT_TEST */