// SPDX-FileCopyrightText: 1989-1994, Julianne Frances Haugh // SPDX-FileCopyrightText: 1996-1999, Marek Michałkiewicz // SPDX-FileCopyrightText: 2003-2005, Tomasz Kłoczko // SPDX-FileCopyrightText: 2007-2010, Nicolas François // SPDX-FileCopyrightText: 2025, Alejandro Colomar // SPDX-License-Identifier: BSD-3-Clause #include "config.h" #ident "$Id$" #include #include #include #include "attr.h" #include "prototypes.h" #include "defines.h" #include "getdef.h" #include "string/ctype/strtoascii/strtolower.h" #include "string/memset/memzero.h" #include "string/sprintf/aprintf.h" #include "string/strcmp/streq.h" #include "string/strdup/strdup.h" #if WITH_LIBBSD == 0 #include "freezero.h" #endif /* WITH_LIBBSD */ /* * can't be a palindrome - like `R A D A R' or `M A D A M' */ static bool palindrome(const char *new) { size_t i, j; i = strlen (new); for (j = 0; j < i; j++) { if (new[i - j - 1] != new[j]) { return false; } } return true; } /* * more than half of the characters are different ones. */ static bool similar (/*@notnull@*/const char *old, /*@notnull@*/const char *new) { int i, j; /* * XXX - sometimes this fails when changing from a simple password * to a really long one (MD5). For now, I just return success if * the new password is long enough. Please feel free to suggest * something better... --marekm */ if (strlen (new) >= 8) { return false; } for (i = j = 0; ('\0' != new[i]) && ('\0' != old[i]); i++) { if (strchr(new, old[i])) j++; } if (i >= j * 2) { return false; } return true; } static /*@observer@*//*@null@*/const char *password_check ( /*@notnull@*/const char *old, /*@notnull@*/const char *new) { const char *msg = NULL; char *oldmono, *newmono, *wrapped; if (streq(new, old)) { return _("no change"); } newmono = strtolower(xstrdup(new)); oldmono = strtolower(xstrdup(old)); wrapped = xaprintf("%s%s", oldmono, oldmono); if (palindrome(newmono)) { msg = _("a palindrome"); } else if (streq(oldmono, newmono)) { msg = _("case changes only"); } else if (similar (oldmono, newmono)) { msg = _("too similar"); } else if (strstr (wrapped, newmono) != NULL) { msg = _("rotated"); } free(strzero(newmono)); free(strzero(oldmono)); free(strzero(wrapped)); return msg; } static /*@observer@*//*@null@*/const char *obscure_msg ( /*@notnull@*/const char *old, /*@notnull@*/const char *new) { int maxlen, minlen; size_t oldlen, newlen; char *new1, *old1; const char *msg; oldlen = strlen (old); newlen = strlen (new); obscure_get_range(&minlen, &maxlen); if (newlen < (size_t) minlen) { return _("too short"); } /* * Remaining checks are optional. */ if (!getdef_bool ("OBSCURE_CHECKS_ENAB")) { return NULL; } msg = password_check(old, new); if (NULL != msg) { return msg; } if (maxlen == -1) { return NULL; } /* 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 */ if ( (oldlen <= (size_t) maxlen) && (newlen <= (size_t) maxlen)) { return NULL; } new1 = xstrdup (new); old1 = xstrdup (old); if (newlen > (size_t) maxlen) stpcpy(&new1[maxlen], ""); if (oldlen > (size_t) maxlen) stpcpy(&old1[maxlen], ""); msg = password_check(old1, new1); freezero (new1, newlen); freezero (old1, oldlen); return msg; } /* * 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. */ bool obscure(const char *old, const char *new) { const char *msg = obscure_msg(old, new); if (NULL != msg) { printf (_("Bad password: %s. "), msg); return false; } return true; } /* * obscure_get_range - retrieve min and max password lengths * * Returns minimum and maximum allowed lengths of a password * to pass obscure checks. */ void obscure_get_range(int *minlen, int *maxlen) { int val; const char *method; /* Minimum length is 0, even if -1 is configured. */ val = getdef_num("PASS_MIN_LEN", 0); *minlen = val == -1 ? 0 : val; /* Maximum password length check is optional. */ *maxlen = -1; if (!getdef_bool("OBSCURE_CHECKS_ENAB")) { return; } method = getdef_str ("ENCRYPT_METHOD"); if (NULL == method) { if (getdef_bool ("MD5_CRYPT_ENAB")) { return; } } else { if ( streq(method, "MD5") #ifdef USE_SHA_CRYPT || streq(method, "SHA256") || streq(method, "SHA512") #endif #ifdef USE_BCRYPT || streq(method, "BCRYPT") #endif #ifdef USE_YESCRYPT || streq(method, "YESCRYPT") #endif ) { return; } } *maxlen = getdef_num ("PASS_MAX_LEN", 8); }