*
* Copyright (C) 2006 Tito Ragusa <farmatito@tiscali.it>
*
- * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
*/
/* A good password:
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
+ 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
+ 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).
of crypt do not truncate passwords.
*/
-#include <ctype.h>
-#include <unistd.h>
-#include <string.h>
-
#include "libbb.h"
-
-/* passwords should consist of 6 (to 8 characters) */
-#define MINLEN 6
-
-
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-is or capitalized */
- if (strcasecmp(p1, p2) == 0
/* as sub-string */
- || strcasestr(p2, p1) != NULL
+ if (strcasestr(p2, p1) != NULL
/* invert in case haystack is shorter than needle */
- || strcasestr(p1, p2) != NULL)
+ || 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;
+ int size, i;
/* check string */
int ret = string_checker_helper(p1, p2);
- /* Make our own copy */
- char *p = bb_xstrdup(p1);
- /* reverse string */
- size = strlen(p);
+ /* make our own copy */
+ char *p = xstrdup(p1);
- while (size--) {
- *p = p1[size];
- p++;
+ /* reverse string */
+ i = size = strlen(p1);
+ while (--i >= 0) {
+ *p++ = p1[i];
}
- /* restore pointer */
- p -= strlen(p1);
+ p -= size; /* restore pointer */
+
/* check reversed string */
ret |= string_checker_helper(p, p2);
+
/* clean up */
- memset(p, 0, strlen(p1));
+ nuke_str(p);
free(p);
+
return ret;
}
-#define LOWERCASE 1
-#define UPPERCASE 2
-#define NUMBERS 4
-#define SPECIAL 8
+#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)
+static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw)
{
- int i;
- int c;
- int length;
- int mixed = 0;
- /* Add 1 for each type of characters to the minlen of password */
- int size = MINLEN + 8;
+ unsigned length;
+ unsigned size;
+ unsigned mixed;
+ unsigned c;
+ unsigned i;
const char *p;
- char hostname[255];
+ char *hostname;
/* size */
- if (!new_p || (length = strlen(new_p)) < MINLEN)
- return("too short");
-
+ 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 (string_checker(new_p, pw->pw_gecos)) {
+ 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 */
- if (gethostname(hostname, 255) == 0) {
- hostname[254] = '\0';
- if (string_checker(new_p, hostname)) {
- return "similar to hostname";
- }
- }
+ 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 { /* special characters */
mixed |= SPECIAL;
}
- /* More than 50% similar characters ? */
+ /* Count i'th char */
c = 0;
p = new_p;
while (1) {
- if ((p = strchr(p, new_p[i])) == NULL) {
+ p = strchr(p, new_p[i]);
+ if (p == NULL) {
break;
}
c++;
- if (!++p) {
- break; /* move past the matched char if possible */
+ p++;
+ if (!*p) {
+ break;
}
}
-
- if (c >= (length / 2)) {
+ /* More than 50% similar characters ? */
+ if (c*2 >= length) {
return "too many similar characters";
}
}
- for(i=0;i<4;i++)
- if (mixed & (1<<i)) size -= 2;
+
+ 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] != '\0') {
+
+ 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 obscure(const char *old, const char *newval, const struct passwd *pwdp)
+int FAST_FUNC obscure(const char *old, const char *newval, const struct passwd *pw)
{
const char *msg;
- if ((msg = obscure_msg(old, newval, pwdp))) {
- printf("Bad password: %s.\n", msg);
- /* If user is root warn only */
- return (getuid())? 1 : 0;
+ 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 */