aboutsummaryrefslogtreecommitdiff
path: root/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/pam_modules/pam_passwdqc/passwdqc_check.c')
-rw-r--r--contrib/pam_modules/pam_passwdqc/passwdqc_check.c565
1 files changed, 420 insertions, 145 deletions
diff --git a/contrib/pam_modules/pam_passwdqc/passwdqc_check.c b/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
index 01265ff495e5..bb405af8cc48 100644
--- a/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
+++ b/contrib/pam_modules/pam_passwdqc/passwdqc_check.c
@@ -1,37 +1,57 @@
/*
- * Copyright (c) 2000-2002 by Solar Designer. See LICENSE.
+ * Copyright (c) 2000-2002,2010,2013,2016,2020 by Solar Designer. See LICENSE.
*/
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS /* we use fopen(), sprintf(), strncat() */
+#endif
+
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <pwd.h>
-#include "passwdqc.h"
+#include "passwdqc.h" /* also provides <pwd.h> or equivalent "struct passwd" */
+#include "passwdqc_filter.h"
+#include "wordset_4k.h"
+
+#include "passwdqc_i18n.h"
#define REASON_ERROR \
- "check failed"
+ _("check failed")
#define REASON_SAME \
- "is the same as the old one"
+ _("is the same as the old one")
#define REASON_SIMILAR \
- "is based on the old one"
+ _("is based on the old one")
#define REASON_SHORT \
- "too short"
+ _("too short")
#define REASON_LONG \
- "too long"
+ _("too long")
#define REASON_SIMPLESHORT \
- "not enough different characters or classes for this length"
+ _("not enough different characters or classes for this length")
#define REASON_SIMPLE \
- "not enough different characters or classes"
+ _("not enough different characters or classes")
#define REASON_PERSONAL \
- "based on personal login information"
+ _("based on personal login information")
#define REASON_WORD \
- "based on a dictionary word and not a passphrase"
+ _("based on a dictionary word and not a passphrase")
+
+#define REASON_SEQ \
+ _("based on a common sequence of characters and not a passphrase")
+
+#define REASON_WORDLIST \
+ _("based on a word list entry")
+
+#define REASON_DENYLIST \
+ _("is in deny list")
+
+#define REASON_FILTER \
+ _("appears to be in a database")
#define FIXED_BITS 15
@@ -39,7 +59,7 @@ typedef unsigned long fixed;
/*
* Calculates the expected number of different characters for a random
- * password of a given length. The result is rounded down. We use this
+ * password of a given length. The result is rounded down. We use this
* with the _requested_ minimum length (so longer passwords don't have
* to meet this strict requirement for their length).
*/
@@ -49,7 +69,8 @@ static int expected_different(int charset, int length)
x = ((fixed)(charset - 1) << FIXED_BITS) / charset;
y = x;
- while (--length > 0) y = (y * x) >> FIXED_BITS;
+ while (--length > 0)
+ y = (y * x) >> FIXED_BITS;
z = (fixed)charset * (((fixed)1 << FIXED_BITS) - y);
return (int)(z >> FIXED_BITS);
@@ -59,8 +80,16 @@ static int expected_different(int charset, int length)
* A password is too simple if it is too short for its class, or doesn't
* contain enough different characters for its class, or doesn't contain
* enough words for a passphrase.
+ *
+ * The biases are added to the length, and they may be positive or negative.
+ * The passphrase length check uses passphrase_bias instead of bias so that
+ * zero may be passed for this parameter when the (other) bias is non-zero
+ * because of a dictionary word, which is perfectly normal for a passphrase.
+ * The biases do not affect the number of different characters, character
+ * classes, and word count.
*/
-static int is_simple(passwdqc_params_t *params, const char *newpass)
+static int is_simple(const passwdqc_params_qc_t *params, const char *newpass,
+ int bias, int passphrase_bias)
{
int length, classes, words, chars;
int digits, lowers, uppers, others, unknowns;
@@ -72,66 +101,89 @@ static int is_simple(passwdqc_params_t *params, const char *newpass)
while ((c = (unsigned char)newpass[length])) {
length++;
- if (!isascii(c)) unknowns++; else
- if (isdigit(c)) digits++; else
- if (islower(c)) lowers++; else
- if (isupper(c)) uppers++; else
+ if (!isascii(c))
+ unknowns++;
+ else if (isdigit(c))
+ digits++;
+ else if (islower(c))
+ lowers++;
+ else if (isupper(c))
+ uppers++;
+ else
others++;
- if (isascii(c) && isalpha(c) && isascii(p) && !isalpha(p))
- words++;
+/* A word starts when a letter follows a non-letter or when a non-ASCII
+ * character follows a space character. We treat all non-ASCII characters
+ * as non-spaces, which is not entirely correct (there's the non-breaking
+ * space character at 0xa0, 0x9a, or 0xff), but it should not hurt. */
+ if (isascii(p)) {
+ if (isascii(c)) {
+ if (isalpha(c) && !isalpha(p))
+ words++;
+ } else if (isspace(p))
+ words++;
+ }
p = c;
+/* Count this character just once: when we're not going to see it anymore */
if (!strchr(&newpass[length], c))
chars++;
}
- if (!length) return 1;
+ if (!length)
+ return 1;
/* Upper case characters and digits used in common ways don't increase the
* strength of a password */
c = (unsigned char)newpass[0];
- if (uppers && isascii(c) && isupper(c)) uppers--;
+ if (uppers && isascii(c) && isupper(c))
+ uppers--;
c = (unsigned char)newpass[length - 1];
- if (digits && isascii(c) && isdigit(c)) digits--;
+ if (digits && isascii(c) && isdigit(c))
+ digits--;
-/* Count the number of different character classes we've seen. We assume
- * that there're no non-ASCII characters for digits. */
+/* Count the number of different character classes we've seen. We assume
+ * that there are no non-ASCII characters for digits. */
classes = 0;
- if (digits) classes++;
- if (lowers) classes++;
- if (uppers) classes++;
- if (others) classes++;
- if (unknowns && (!classes || (digits && classes == 1))) classes++;
+ if (digits)
+ classes++;
+ if (lowers)
+ classes++;
+ if (uppers)
+ classes++;
+ if (others)
+ classes++;
+ if (unknowns && classes <= 1 && (!classes || digits || words >= 2))
+ classes++;
for (; classes > 0; classes--)
switch (classes) {
case 1:
- if (length >= params->min[0] &&
+ if (length + bias >= params->min[0] &&
chars >= expected_different(10, params->min[0]) - 1)
return 0;
return 1;
case 2:
- if (length >= params->min[1] &&
+ if (length + bias >= params->min[1] &&
chars >= expected_different(36, params->min[1]) - 1)
return 0;
if (!params->passphrase_words ||
words < params->passphrase_words)
continue;
- if (length >= params->min[2] &&
+ if (length + passphrase_bias >= params->min[2] &&
chars >= expected_different(27, params->min[2]) - 1)
return 0;
continue;
case 3:
- if (length >= params->min[3] &&
+ if (length + bias >= params->min[3] &&
chars >= expected_different(62, params->min[3]) - 1)
return 0;
continue;
case 4:
- if (length >= params->min[4] &&
+ if (length + bias >= params->min[4] &&
chars >= expected_different(95, params->min[4]) - 1)
return 0;
continue;
@@ -140,13 +192,13 @@ static int is_simple(passwdqc_params_t *params, const char *newpass)
return 1;
}
-static char *unify(const char *src)
+static char *unify(char *dst, const char *src)
{
const char *sptr;
- char *dst, *dptr;
+ char *dptr;
int c;
- if (!(dst = malloc(strlen(src) + 1)))
+ if (!dst && !(dst = malloc(strlen(src) + 1)))
return NULL;
sptr = src;
@@ -154,9 +206,28 @@ static char *unify(const char *src)
do {
c = (unsigned char)*sptr;
if (isascii(c) && isupper(c))
- *dptr++ = tolower(c);
- else
- *dptr++ = *sptr;
+ c = tolower(c);
+ switch (c) {
+ case 'a': case '@':
+ c = '4'; break;
+ case 'e':
+ c = '3'; break;
+/* Unfortunately, if we translate both 'i' and 'l' to '1', this would
+ * associate these two letters with each other - e.g., "mile" would
+ * match "MLLE", which is undesired. To solve this, we'd need to test
+ * different translations separately, which is not implemented yet. */
+ case 'i': case '|':
+ c = '!'; break;
+ case 'l':
+ c = '1'; break;
+ case 'o':
+ c = '0'; break;
+ case 's': case '$':
+ c = '5'; break;
+ case 't': case '+':
+ c = '7'; break;
+ }
+ *dptr++ = c;
} while (*sptr++);
return dst;
@@ -181,25 +252,27 @@ static char *reverse(const char *src)
static void clean(char *dst)
{
- if (dst) {
- memset(dst, 0, strlen(dst));
- free(dst);
- }
+ if (!dst)
+ return;
+ _passwdqc_memzero(dst, strlen(dst));
+ free(dst);
}
/*
* Needle is based on haystack if both contain a long enough common
* substring and needle would be too simple for a password with the
- * substring removed.
+ * substring either removed with partial length credit for it added
+ * or partially discounted for the purpose of the length check.
*/
-static int is_based(passwdqc_params_t *params,
- const char *haystack, const char *needle, const char *original)
+static int is_based(const passwdqc_params_qc_t *params,
+ const char *haystack, const char *needle, const char *original,
+ int mode)
{
char *scratch;
int length;
int i, j;
const char *p;
- int match;
+ int worst_bias;
if (!params->match_length) /* disabled */
return 0;
@@ -207,31 +280,76 @@ static int is_based(passwdqc_params_t *params,
if (params->match_length < 0) /* misconfigured */
return 1;
- if (strstr(haystack, needle)) /* based on haystack entirely */
- return 1;
-
scratch = NULL;
+ worst_bias = 0;
- length = strlen(needle);
+ length = (int)strlen(needle);
for (i = 0; i <= length - params->match_length; i++)
for (j = params->match_length; i + j <= length; j++) {
- match = 0;
+ int bias = 0, j1 = j - 1;
+ const char q0 = needle[i], *q1 = &needle[i + 1];
for (p = haystack; *p; p++)
- if (*p == needle[i] && !strncmp(p, &needle[i], j)) {
- match = 1;
- if (!scratch) {
- if (!(scratch = malloc(length + 1)))
+ if (*p == q0 && !strncmp(p + 1, q1, j1)) { /* or memcmp() */
+ if ((mode & 0xff) == 0) { /* remove & credit */
+ if (!scratch) {
+ if (!(scratch = malloc(length + 1)))
+ return 1;
+ }
+ /* remove j chars */
+ {
+ int pos = length - (i + j);
+ if (!(mode & 0x100)) /* not reversed */
+ pos = i;
+ memcpy(scratch, original, pos);
+ memcpy(&scratch[pos],
+ &original[pos + j],
+ length + 1 - (pos + j));
+ }
+ /* add credit for match_length - 1 chars */
+ bias = params->match_length - 1;
+ if (is_simple(params, scratch, bias, bias)) {
+ clean(scratch);
return 1;
- }
- memcpy(scratch, original, i);
- memcpy(&scratch[i], &original[i + j],
- length + 1 - (i + j));
- if (is_simple(params, scratch)) {
- clean(scratch);
- return 1;
+ }
+ } else { /* discount */
+/* Require a 1 character longer match for substrings containing leetspeak
+ * when matching against dictionary words */
+ bias = -1;
+ if ((mode & 0xff) == 1) { /* words */
+ int pos = i, end = i + j;
+ if (mode & 0x100) { /* reversed */
+ pos = length - end;
+ end = length - i;
+ }
+ for (; pos < end; pos++)
+ if (!isalpha((int)(unsigned char)
+ original[pos])) {
+ if (j == params->match_length)
+ goto next_match_length;
+ bias = 0;
+ break;
+ }
+ }
+
+ /* discount j - (match_length + bias) chars */
+ bias += (int)params->match_length - j;
+ /* bias <= -1 */
+ if (bias < worst_bias) {
+ if (is_simple(params, original, bias,
+ (mode & 0xff) == 1 ? 0 : bias))
+ return 1;
+ worst_bias = bias;
+ }
}
}
- if (!match) break;
+/* Zero bias implies that there were no matches for this length. If so,
+ * there's no reason to try the next substring length (it would result in
+ * no matches as well). We break out of the substring length loop and
+ * proceed with all substring lengths for the next position in needle. */
+ if (!bias)
+ break;
+next_match_length:
+ ;
}
clean(scratch);
@@ -239,123 +357,280 @@ static int is_based(passwdqc_params_t *params,
return 0;
}
+#define READ_LINE_MAX 8192
+#define READ_LINE_SIZE (READ_LINE_MAX + 2)
+
+static char *read_line(FILE *f, char *buf)
+{
+ buf[READ_LINE_MAX] = '\n';
+
+ if (!fgets(buf, READ_LINE_SIZE, f))
+ return NULL;
+
+ if (buf[READ_LINE_MAX] != '\n') {
+ int c;
+ do {
+ c = getc(f);
+ } while (c != EOF && c != '\n');
+ if (ferror(f))
+ return NULL;
+ }
+
+ char *p;
+ if ((p = strpbrk(buf, "\r\n")))
+ *p = '\0';
+
+ return buf;
+}
+
+/*
+ * Common sequences of characters.
+ * We don't need to list any of the entire strings in reverse order because the
+ * code checks the new password in both "unified" and "unified and reversed"
+ * form against these strings (unifying them first indeed). We also don't have
+ * to include common repeats of characters (e.g., "777", "!!!", "1000") because
+ * these are often taken care of by the requirement on the number of different
+ * characters.
+ */
+const char * const seq[] = {
+ "0123456789",
+ "`1234567890-=",
+ "~!@#$%^&*()_+",
+ "abcdefghijklmnopqrstuvwxyz",
+ "a1b2c3d4e5f6g7h8i9j0",
+ "1a2b3c4d5e6f7g8h9i0j",
+ "abc123",
+ "qwertyuiop[]\\asdfghjkl;'zxcvbnm,./",
+ "qwertyuiop{}|asdfghjkl:\"zxcvbnm<>?",
+ "qwertyuiopasdfghjklzxcvbnm",
+ "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik,9ol.0p;/-['=]\\",
+ "!qaz@wsx#edc$rfv%tgb^yhn&ujm*ik<(ol>)p:?_{\"+}|",
+ "qazwsxedcrfvtgbyhnujmikolp",
+ "1q2w3e4r5t6y7u8i9o0p-[=]",
+ "q1w2e3r4t5y6u7i8o9p0[-]=\\",
+ "1qaz1qaz",
+ "1qaz!qaz", /* can't unify '1' and '!' - see comment in unify() */
+ "1qazzaq1",
+ "zaq!1qaz",
+ "zaq!2wsx"
+};
+
/*
* This wordlist check is now the least important given the checks above
* and the support for passphrases (which are based on dictionary words,
- * and checked by other means). It is still useful to trap simple short
+ * and checked by other means). It is still useful to trap simple short
* passwords (if short passwords are allowed) that are word-based, but
* passed the other checks due to uncommon capitalization, digits, and
- * special characters. We (mis)use the same set of words that are used
- * to generate random passwords. This list is much smaller than those
+ * special characters. We (mis)use the same set of words that are used
+ * to generate random passwords. This list is much smaller than those
* used for password crackers, and it doesn't contain common passwords
- * that aren't short English words. Perhaps support for large wordlists
- * should still be added, even though this is now of little importance.
+ * that aren't short English words. We also support optional external
+ * wordlist (for inexact matching) and deny list (for exact matching).
*/
-static int is_word_based(passwdqc_params_t *params,
- const char *needle, const char *original)
+static const char *is_word_based(const passwdqc_params_qc_t *params,
+ const char *unified, const char *reversed, const char *original)
{
- char word[7];
- char *unified;
- int i;
-
- word[6] = '\0';
- for (i = 0; i < 0x1000; i++) {
- memcpy(word, _passwdqc_wordset_4k[i], 6);
- if ((int)strlen(word) < params->match_length) continue;
- unified = unify(word);
- if (is_based(params, unified, needle, original)) {
- clean(unified);
- return 1;
+ const char *reason = REASON_ERROR;
+ char word[WORDSET_4K_LENGTH_MAX + 1], *buf = NULL;
+ FILE *f = NULL;
+ unsigned int i;
+
+ word[WORDSET_4K_LENGTH_MAX] = '\0';
+ if (params->match_length)
+ for (i = 0; _passwdqc_wordset_4k[i][0]; i++) {
+ memcpy(word, _passwdqc_wordset_4k[i], WORDSET_4K_LENGTH_MAX);
+ int length = (int)strlen(word);
+ if (length < params->match_length)
+ continue;
+ if (!memcmp(word, _passwdqc_wordset_4k[i + 1], length))
+ continue;
+ unify(word, word);
+ if (is_based(params, word, unified, original, 1) ||
+ is_based(params, word, reversed, original, 0x101)) {
+ reason = REASON_WORD;
+ goto out;
}
- clean(unified);
}
- return 0;
-}
+ if (params->match_length)
+ for (i = 0; i < sizeof(seq) / sizeof(seq[0]); i++) {
+ char *seq_i = unify(NULL, seq[i]);
+ if (!seq_i)
+ goto out;
+ if (is_based(params, seq_i, unified, original, 2) ||
+ is_based(params, seq_i, reversed, original, 0x102)) {
+ clean(seq_i);
+ reason = REASON_SEQ;
+ goto out;
+ }
+ clean(seq_i);
+ }
-const char *_passwdqc_check(passwdqc_params_t *params,
- const char *newpass, const char *oldpass, struct passwd *pw)
-{
- char truncated[9], *reversed;
- char *u_newpass, *u_reversed;
- char *u_oldpass;
- char *u_name, *u_gecos;
- const char *reason;
- int length;
+ if (params->match_length && params->match_length <= 4)
+ for (i = 1900; i <= 2039; i++) {
+ sprintf(word, "%u", i);
+ if (is_based(params, word, unified, original, 2) ||
+ is_based(params, word, reversed, original, 0x102)) {
+ reason = REASON_SEQ;
+ goto out;
+ }
+ }
- reversed = NULL;
- u_newpass = u_reversed = NULL;
- u_oldpass = NULL;
- u_name = u_gecos = NULL;
+ if (params->wordlist || params->denylist)
+ if (!(buf = malloc(READ_LINE_SIZE)))
+ goto out;
+
+ if (params->wordlist) {
+ if (!(f = fopen(params->wordlist, "r")))
+ goto out;
+ while (read_line(f, buf)) {
+ unify(buf, buf);
+ if (!strcmp(buf, unified) || !strcmp(buf, reversed))
+ goto out_wordlist;
+ if (!params->match_length ||
+ strlen(buf) < (size_t)params->match_length)
+ continue;
+ if (is_based(params, buf, unified, original, 1) ||
+ is_based(params, buf, reversed, original, 0x101)) {
+out_wordlist:
+ reason = REASON_WORDLIST;
+ goto out;
+ }
+ }
+ if (ferror(f))
+ goto out;
+ fclose(f); f = NULL;
+ }
+
+ if (params->denylist) {
+ if (!(f = fopen(params->denylist, "r")))
+ goto out;
+ while (read_line(f, buf)) {
+ if (!strcmp(buf, original)) {
+ reason = REASON_DENYLIST;
+ goto out;
+ }
+ }
+ if (ferror(f))
+ goto out;
+ }
reason = NULL;
- if (oldpass && !strcmp(oldpass, newpass))
- reason = REASON_SAME;
+out:
+ if (f)
+ fclose(f);
+ if (buf) {
+ _passwdqc_memzero(buf, READ_LINE_SIZE);
+ free(buf);
+ }
+ _passwdqc_memzero(word, sizeof(word));
+ return reason;
+}
- length = strlen(newpass);
+const char *passwdqc_check(const passwdqc_params_qc_t *params,
+ const char *newpass, const char *oldpass, const struct passwd *pw)
+{
+ char truncated[9];
+ char *u_newpass = NULL, *u_reversed = NULL;
+ char *u_oldpass = NULL;
+ char *u_name = NULL, *u_gecos = NULL, *u_dir = NULL;
+ const char *reason = REASON_ERROR;
+
+ size_t length = strlen(newpass);
- if (!reason && length < params->min[4])
+ if (length < (size_t)params->min[4]) {
reason = REASON_SHORT;
+ goto out;
+ }
+
+ if (length > 10000) {
+ reason = REASON_LONG;
+ goto out;
+ }
- if (!reason && length > params->max) {
+ if (length > (size_t)params->max) {
if (params->max == 8) {
truncated[0] = '\0';
strncat(truncated, newpass, 8);
newpass = truncated;
- if (oldpass && !strncmp(oldpass, newpass, 8))
+ length = 8;
+ if (oldpass && !strncmp(oldpass, newpass, 8)) {
reason = REASON_SAME;
- } else
+ goto out;
+ }
+ } else {
reason = REASON_LONG;
+ goto out;
+ }
+ }
+
+ if (oldpass && !strcmp(oldpass, newpass)) {
+ reason = REASON_SAME;
+ goto out;
}
- if (!reason && is_simple(params, newpass)) {
- if (length < params->min[1] && params->min[1] <= params->max)
+ if (is_simple(params, newpass, 0, 0)) {
+ reason = REASON_SIMPLE;
+ if (length < (size_t)params->min[1] &&
+ params->min[1] <= params->max)
reason = REASON_SIMPLESHORT;
- else
- reason = REASON_SIMPLE;
+ goto out;
}
- if (!reason) {
- if ((reversed = reverse(newpass))) {
- u_newpass = unify(newpass);
- u_reversed = unify(reversed);
- if (oldpass)
- u_oldpass = unify(oldpass);
- if (pw) {
- u_name = unify(pw->pw_name);
- u_gecos = unify(pw->pw_gecos);
- }
- }
- if (!reversed ||
- !u_newpass || !u_reversed ||
- (oldpass && !u_oldpass) ||
- (pw && (!u_name || !u_gecos)))
- reason = REASON_ERROR;
+ if (!(u_newpass = unify(NULL, newpass)))
+ goto out; /* REASON_ERROR */
+ if (!(u_reversed = reverse(u_newpass)))
+ goto out;
+ if (oldpass && !(u_oldpass = unify(NULL, oldpass)))
+ goto out;
+ if (pw) {
+ if (!(u_name = unify(NULL, pw->pw_name)) ||
+ !(u_gecos = unify(NULL, pw->pw_gecos)) ||
+ !(u_dir = unify(NULL, pw->pw_dir)))
+ goto out;
}
- if (!reason && oldpass && params->similar_deny &&
- (is_based(params, u_oldpass, u_newpass, newpass) ||
- is_based(params, u_oldpass, u_reversed, reversed)))
+ if (oldpass && params->similar_deny &&
+ (is_based(params, u_oldpass, u_newpass, newpass, 0) ||
+ is_based(params, u_oldpass, u_reversed, newpass, 0x100))) {
reason = REASON_SIMILAR;
+ goto out;
+ }
- if (!reason && pw &&
- (is_based(params, u_name, u_newpass, newpass) ||
- is_based(params, u_name, u_reversed, reversed) ||
- is_based(params, u_gecos, u_newpass, newpass) ||
- is_based(params, u_gecos, u_reversed, reversed)))
+ if (pw &&
+ (is_based(params, u_name, u_newpass, newpass, 0) ||
+ is_based(params, u_name, u_reversed, newpass, 0x100) ||
+ is_based(params, u_gecos, u_newpass, newpass, 0) ||
+ is_based(params, u_gecos, u_reversed, newpass, 0x100) ||
+ is_based(params, u_dir, u_newpass, newpass, 0) ||
+ is_based(params, u_dir, u_reversed, newpass, 0x100))) {
reason = REASON_PERSONAL;
+ goto out;
+ }
- if (!reason && (int)strlen(newpass) < params->min[2] &&
- (is_word_based(params, u_newpass, newpass) ||
- is_word_based(params, u_reversed, reversed)))
- reason = REASON_WORD;
+ reason = is_word_based(params, u_newpass, u_reversed, newpass);
+
+ if (!reason && params->filter) {
+ passwdqc_filter_t flt;
+ reason = REASON_ERROR;
+ if (passwdqc_filter_open(&flt, params->filter))
+ goto out;
+ int result = passwdqc_filter_lookup(&flt, newpass);
+ passwdqc_filter_close(&flt);
+ if (result < 0)
+ goto out;
+ reason = result ? REASON_FILTER : NULL;
+ }
- memset(truncated, 0, sizeof(truncated));
- clean(reversed);
- clean(u_newpass); clean(u_reversed);
+out:
+ _passwdqc_memzero(truncated, sizeof(truncated));
+ clean(u_newpass);
+ clean(u_reversed);
clean(u_oldpass);
- clean(u_name); clean(u_gecos);
+ clean(u_name);
+ clean(u_gecos);
+ clean(u_dir);
return reason;
}