Files
shadow/lib/obscure.c
2026-01-06 00:37:15 +01:00

240 lines
4.8 KiB
C

// 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 <alx@kernel.org>
// SPDX-License-Identifier: BSD-3-Clause
#include "config.h"
#ident "$Id$"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#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);
}