From e745294c1839de3b0886c6cbe5d8601c56dd3dcf Mon Sep 17 00:00:00 2001 From: Tomas Mraz Date: Fri, 16 Sep 2011 19:02:43 +0200 Subject: Initial import into the repository. --- src/Makefile.am | 44 +++ src/check.c | 509 ++++++++++++++++++++++++++++++++++ src/generate.c | 211 ++++++++++++++ src/libpwquality.map | 15 + src/pam_cracklib.c | 755 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/pam_pwquality.c | 309 +++++++++++++++++++++ src/pwmake.c | 108 ++++++++ src/pwqprivate.h | 88 ++++++ src/pwquality.h | 139 ++++++++++ src/pwscore.c | 143 ++++++++++ src/settings.c | 350 ++++++++++++++++++++++++ 11 files changed, 2671 insertions(+) create mode 100644 src/Makefile.am create mode 100644 src/check.c create mode 100644 src/generate.c create mode 100644 src/libpwquality.map create mode 100644 src/pam_cracklib.c create mode 100644 src/pam_pwquality.c create mode 100644 src/pwmake.c create mode 100644 src/pwqprivate.h create mode 100644 src/pwquality.h create mode 100644 src/pwscore.c create mode 100644 src/settings.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..09db8ca --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,44 @@ +# +# Copyright (c) 2005, 2006, 2007, 2009 Thorsten Kukuk +# Copyright (c) 2011 Red Hat, Inc. +# Copyright (c) 2011 Tomas Mraz +# + +CLEANFILES = *~ + +EXTRA_DIST = libpwquality.map + +include_HEADERS = pwquality.h + +noinst_HEADERS = pwqprivate.h + +if HAVE_LD_VERSION_SCRIPT + libpwquality_version_script = -Wl,--version-script=$(srcdir)/libpwquality.map +else + libpwquality_version_script = +endif + +libpwquality_la_LDFLAGS = -no-undefined $(libpwquality_version_script) \ + -version-info @PWQUALITY_LT_CURRENT@:@PWQUALITY_LT_REVISION@:@PWQUALITY_LT_AGE@ + +libpwquality_la_LIBADD = @LIBCRACK@ + +libpwquality_la_SOURCES = generate.c check.c settings.c + +pam_pwquality_la_LDFLAGS = -no-undefined -avoid-version -module + +pam_pwquality_la_LIBADD = libpwquality.la -lpam + +pam_pwquality_la_SOURCES = pam_pwquality.c + +pwscore_SOURCES = pwscore.c + +pwscore_LDADD = libpwquality.la + +pwmake_SOURCES = pwmake.c + +pwmake_LDADD = libpwquality.la + +lib_LTLIBRARIES = libpwquality.la pam_pwquality.la + +bin_PROGRAMS = pwscore pwmake diff --git a/src/check.c b/src/check.c new file mode 100644 index 0000000..c02d1b4 --- /dev/null +++ b/src/check.c @@ -0,0 +1,509 @@ +/* + * libpwquality main API code for quality checking + * + * See the end of the file for Copyright and License Information + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "pwquality.h" +#include "pwqprivate.h" + +#ifdef MIN +#undef MIN +#endif +#define MIN(_a, _b) (((_a) < (_b)) ? (_a) : (_b)) + +/* Helper functions */ + +/* + * test for a palindrome - like `R A D A R' or `M A D A M' + */ +static int +palindrome(const char *new) +{ + int i, j; + + i = strlen (new); + + for (j = 0; j < i; j++) + if (new[i - j - 1] != new[j]) + return 0; + + return 1; +} + +/* + * Calculate how different two strings are in terms of the number of + * character removals, additions, and changes needed to go from one to + * the other + */ + +static int +distdifferent(const char *old, const char *new, + size_t i, size_t j) +{ + char c, d; + + if ((i == 0) || (strlen(old) < i)) { + c = 0; + } else { + c = old[i - 1]; + } + + if ((j == 0) || (strlen(new) < j)) { + d = 0; + } else { + d = new[j - 1]; + } + return (c != d); +} + +static int +distcalculate(int **distances, const char *old, const char *new, + size_t i, size_t j) +{ + int tmp = 0; + + if (distances[i][j] != -1) { + return distances[i][j]; + } + + tmp = distcalculate(distances, old, new, i - 1, j - 1); + tmp = MIN(tmp, distcalculate(distances, old, new, i, j - 1)); + tmp = MIN(tmp, distcalculate(distances, old, new, i - 1, j)); + tmp += distdifferent(old, new, i, j); + + distances[i][j] = tmp; + + return tmp; +} + +static int +distance(const char *old, const char *new) +{ + int **distances = NULL; + size_t m, n, i, j; + int r = -1; + + m = strlen(old); + n = strlen(new); + distances = malloc(sizeof(int*) * (m + 1)); + if (distances == NULL) + return -1; + + for (i = 0; i <= m; i++) { + distances[i] = malloc(sizeof(int) * (n + 1)); + if (distances[i] == NULL) + goto allocfail; + + for(j = 0; j <= n; j++) { + distances[i][j] = -1; + } + } + + for (i = 0; i <= m; i++) { + distances[i][0] = i; + } + + for (j = 0; j <= n; j++) { + distances[0][j] = j; + } + + r = distcalculate(distances, old, new, m, n); + +allocfail: + for (i = 0; i <= m; i++) { + if (distances[i]) { + memset(distances[i], 0, sizeof(int) * (n + 1)); + free(distances[i]); + } + } + free(distances); + + return r; +} + +static int +similar(pwquality_settings_t *pwq, + const char *old, const char *new) +{ + int dist; + + dist = distance(old, new); + + if (dist < 0) + return PWQ_ERROR_MEM_ALLOC; + + if (dist >= pwq->diff_ok) { + return 0; + } + + if (strlen(new) >= (strlen(old) * 2)) { + return 0; + } + + /* passwords are too similar */ + return PWQ_ERROR_TOO_SIMILAR; +} + +/* + * count classes of charecters + */ + +static int +numclass(pwquality_settings_t *pwq, + const char *new) +{ + int digits = 0; + int uppers = 0; + int lowers = 0; + int others = 0; + int total_class; + int i; + int retval; + + for (i = 0; new[i]; i++) { + if (isdigit(new[i])) + digits = 1; + else if (isupper(new[i])) + uppers = 1; + else if (islower(new[i])) + lowers = 1; + else + others = 1; + } + + total_class = digits + uppers + lowers + others; + + return total_class; +} + +/* + * a nice mix of characters + * the credit (if positive) is a maximum value that is subtracted from + * the minimum allowed size of the password if letters of the class are + * present in the password + */ +static int +simple(pwquality_settings_t *pwq, const char *new) +{ + int digits = 0; + int uppers = 0; + int lowers = 0; + int others = 0; + int size; + int i; + + for (i = 0; new[i]; i++) { + if (isdigit(new[i])) + digits++; + else if (isupper(new[i])) + uppers++; + else if (islower(new[i])) + lowers++; + else + others++; + } + + if ((pwq->dig_credit >= 0) && (digits > pwq->dig_credit)) + digits = pwq->dig_credit; + + if ((pwq->up_credit >= 0) && (uppers > pwq->up_credit)) + uppers = pwq->up_credit; + + if ((pwq->low_credit >= 0) && (lowers > pwq->low_credit)) + lowers = pwq->low_credit; + + if ((pwq->oth_credit >= 0) && (others > pwq->oth_credit)) + others = pwq->oth_credit; + + size = pwq->min_length; + + if (pwq->dig_credit >= 0) + size -= digits; + else if (digits < pwq->dig_credit * -1) + return PWQ_ERROR_MIN_DIGITS; + + if (pwq->up_credit >= 0) + size -= uppers; + else if (uppers < pwq->up_credit * -1) + return PWQ_ERROR_MIN_UPPERS; + + if (pwq->low_credit >= 0) + size -= lowers; + else if (lowers < pwq->low_credit * -1) + return PWQ_ERROR_MIN_LOWERS; + + if (pwq->oth_credit >= 0) + size -= others; + else if (others < pwq->oth_credit * -1) + return PWQ_ERROR_MIN_OTHERS; + + if (size <= i) + return 0; + + return PWQ_ERROR_MIN_LENGTH; +} + +/* + * too many same consecutive characters + */ + +static int +consecutive(pwquality_settings_t *pwq, const char *new) +{ + char c; + int i; + int same; + + if (pwq->max_repeat == 0) + return 0; + + for (i = 0; new[i]; i++) { + if (i > 0 && new[i] == c) { + ++same; + if (same > pwq->max_repeat) + return 1; + } else { + c = new[i]; + same = 1; + } + } + return 0; +} + +static char * +str_lower(char *string) +{ + char *cp; + + if (!string) + return NULL; + + for (cp = string; *cp; cp++) + *cp = tolower(*cp); + return string; +} + +static char * +x_strdup(const char *string) +{ + if (!string) + return NULL; + return strdup(string); +} + +static int +password_check(pwquality_settings_t *pwq, + const char *new, const char *old) +{ + int rv = 0; + char *oldmono = NULL, *newmono, *wrapped = NULL; + + newmono = str_lower(x_strdup(new)); + if (!newmono) + rv = PWQ_ERROR_MEM_ALLOC; + + if (!rv && old) { + oldmono = str_lower(x_strdup(old)); + if (oldmono) + wrapped = malloc(strlen(oldmono) * 2 + 1); + if (wrapped) { + strcpy (wrapped, oldmono); + strcat (wrapped, oldmono); + } else { + rv = PWQ_ERROR_MEM_ALLOC; + } + } + + if (!rv && palindrome(newmono)) + rv = PWQ_ERROR_PALINDROME; + + if (!rv && oldmono && strcmp(oldmono, newmono) == 0) + rv = PWQ_ERROR_CASE_CHANGES_ONLY; + + if (!rv && oldmono) + rv = similar(pwq, oldmono, newmono); + + if (!rv) + rv = simple(pwq, new); + + if (!rv && wrapped && strstr(wrapped, newmono)) + rv = PWQ_ERROR_ROTATED; + + if (!rv && numclass(pwq, new) < pwq->min_class) + rv = PWQ_ERROR_MIN_CLASSES; + + if (!rv && consecutive(pwq, new)) + rv = PWQ_ERROR_MAX_CONSECUTIVE; + + if (newmono) { + memset(newmono, 0, strlen(newmono)); + free(newmono); + } + + if (oldmono) { + memset(oldmono, 0, strlen(oldmono)); + free(oldmono); + } + + if (wrapped) { + memset(wrapped, 0, strlen(wrapped)); + free(wrapped); + } + + return rv; +} + +/* this algorithm is an arbitrary one, fine-tuned by testing */ +static int +password_score(pwquality_settings_t *pwq, const char *password) +{ + int len; + int score; + int i; + int j; + unsigned char freq[256]; + unsigned char *buf; + + len = strlen(password); + + if ((buf = malloc(len)) == NULL) + /* should get enough memory to obtain a nice score */ + return 0; + + score = (len - pwq->min_length) * 2; + + memcpy(buf, password, len); + + for (j = 0; j < 3; j++) { + + memset(freq, 0, sizeof(freq)); + + for (i = 0; i < len - j; i++) { + ++freq[buf[i]]; + if (i < len - j - 1) + buf[i] = abs(buf[i] - buf[i+1]); + } + + for (i = 0; i < sizeof(freq); i++) { + if (freq[i]) + ++score; + } + } + + memset(buf, 0, len); + free(buf); + + score += numclass(pwq, password) * 2; + + score = (score * 100)/(3 * pwq->min_length + + + PWQ_NUM_CLASSES * 2); + + score -= 50; + + if (score > 100) + score = 100; + if (score < 0) + score = 0; + + return score; +} + +/* check the password according to the settings + * it returns either score <0-100>, negative error number, + * and in case of PWQ_ERROR_CRACKLIB also auxiliary + * error message returned from cracklib; + * the old password is optional */ +int +pwquality_check(pwquality_settings_t *pwq, const char *password, + const char *oldpassword, const char **error) +{ + const char *msg; + int score; + + if (password == NULL || *password == '\0') { + return PWQ_ERROR_EMPTY_PASSWORD; + } + + if (oldpassword && strcmp(oldpassword, password) == 0) { + return PWQ_ERROR_SAME_PASSWORD; + } + + msg = FascistCheck(password, pwq->dict_path); + if (msg) { + if (error) + *error = msg; + return PWQ_ERROR_CRACKLIB_CHECK; + } + + score = password_check(pwq, password, oldpassword); + if (score == 0) { + score = password_score(pwq, password); + } + + return score; +} + +/* + * Copyright (c) Cristian Gafton , 1996. + * All rights reserved + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + * + * The following copyright was appended for the long password support + * added with the libpam 0.58 release: + * + * Modificaton Copyright (c) Philip W. Dalrymple III + * 1997. All rights reserved + * + * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/generate.c b/src/generate.c new file mode 100644 index 0000000..24c158b --- /dev/null +++ b/src/generate.c @@ -0,0 +1,211 @@ +/* + * libpwquality main API code for password generation + * + * See the end of the file for Copyright and License Information + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pwquality.h" +#include "pwqprivate.h" + +#ifndef PATH_DEV_URANDOM +#define PATH_DEV_URANDOM "/dev/urandom" +#endif + +static char vowels[] = { 'a', '4', 'A', 'e', 'E', '3', 'i', 'I', 'o', 'O', + '0', 'u', 'U', 'y', 'Y', '@' }; /* 4 bits */ +static char consonants1[] = { 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', + 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z', + 'B', 'D', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', + 'R', 'S' }; /* 5 bits */ +static char consonants2[] = { 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', + 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z', + 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', + 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z', + '1', '2', '5', '6', '7', '8', '9', '!', '#', '$', + '%', '^', '&', '*', '(', ')', '-', '+', '=', '[', + ']', ';', '.', ',' }; /* 6 bits */ + +static int +get_entropy_bits(char *buf, int nbits) +{ + int fd; + int rv; + int offset = 0; + int bytes = (nbits + 7) / 8; /* round up on division */ + + fd = open(PATH_DEV_URANDOM, O_RDONLY); + if (fd == -1) + return -1; + + while (bytes > 0) { + rv = read(fd, buf + offset, bytes); + + if (rv < 0) { + if (errno == EINTR) continue; + (void)close(fd); + return -1; + } + if (rv == 0) { + (void)close(fd); + return -1; + } + + offset += rv; + bytes -= rv; + } + + (void)close(fd); + return 0; +} + +static unsigned int +consume_entropy(char *buf, int bits, int *remaining, int *offset) +{ + unsigned int low = (unsigned char)buf[*offset/8]; + unsigned int high = 0; + + if (remaining) + *remaining -= bits; + + low >>= *offset % 8; + low &= (1 << bits) - 1; + + if (8 - *offset % 8 < bits) { + high = (unsigned char)buf[*offset/8 + 1]; + high &= (1 << (bits - (8 - *offset % 8))) - 1; + high <<= 8 - *offset % 8; + low |= high; + } + + *offset += bits; + return low; +} + +/* generate a random password according to the settings */ +int +pwquality_generate(pwquality_settings_t *pwq, int entropy_bits, char **password) +{ + char entropy[PWQ_MAX_ENTROPY_BITS/8 + 1]; + char *tmp; + int maxlen; + int try = 0; + + *password = NULL; + + if (entropy_bits > PWQ_MAX_ENTROPY_BITS) + entropy_bits = PWQ_MAX_ENTROPY_BITS; + + if (entropy_bits < PWQ_MIN_ENTROPY_BITS) + entropy_bits = PWQ_MIN_ENTROPY_BITS; + + /* overestimate here only 9 bits per syllable of 3 characters */ + maxlen = (entropy_bits + 8) / 9 * 3 + 1; + + tmp = malloc(maxlen); + if (tmp == NULL) { + return PWQ_ERROR_FATAL_FAILURE; + } + + do { + int offset = 0; + int remaining = entropy_bits; + char *ptr; + + memset(tmp, '\0', maxlen); + /* read one more byte for rounding overflow during generation + and for at most every 9th bit we also drop one bit */ + if (get_entropy_bits(entropy, entropy_bits + + (entropy_bits+8)/9 + 8) < 0) { + free(tmp); + return PWQ_ERROR_RNG; + } + + ptr = tmp; + while (remaining > 0) { + unsigned int idx; + + if (consume_entropy(entropy, 1, NULL, &offset)) { + idx = consume_entropy(entropy, 6, &remaining, &offset); + *ptr = consonants2[idx]; + ++ptr; + if (remaining < 0) + break; + } + + idx = consume_entropy(entropy, 4, &remaining, &offset); + *ptr = vowels[idx]; + ++ptr; + if (remaining < 0) + break; + + idx = consume_entropy(entropy, 5, &remaining, &offset); + *ptr = consonants1[idx]; + ++ptr; + } + } while (pwquality_check(pwq, tmp, NULL, NULL) < 0 && + ++try < PWQ_NUM_GENERATION_TRIES); + + /* clean up */ + memset(entropy, '\0', sizeof(entropy)); + + if (try >= PWQ_NUM_GENERATION_TRIES) { + free(tmp); + return PWQ_ERROR_GENERATION_FAILED; + } + + *password = tmp; + tmp = NULL; + return 0; +} + + +/* + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/libpwquality.map b/src/libpwquality.map new file mode 100644 index 0000000..231dd02 --- /dev/null +++ b/src/libpwquality.map @@ -0,0 +1,15 @@ +LIBPWQUALITY_1.0 { + global: + pwquality_default_settings; + pwquality_free_settings; + pwquality_read_config; + pwquality_set_option; + pwquality_set_int_value; + pwquality_set_str_value; + pwquality_get_int_value; + pwquality_get_str_value; + pwquality_generate; + pwquality_check; + local: + *; +}; diff --git a/src/pam_cracklib.c b/src/pam_cracklib.c new file mode 100644 index 0000000..a5e12cb --- /dev/null +++ b/src/pam_cracklib.c @@ -0,0 +1,755 @@ +/* + * libpwquality main API code + * + * See the end of the file for Copyright and License Information + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* For Translators: "%s%s" could be replaced with " " or "". */ +#define PROMPT1 _("New %s%spassword: ") +/* For Translators: "%s%s" could be replaced with " " or "". */ +#define PROMPT2 _("Retype new %s%spassword: ") +#define MISTYPED_PASS _("Sorry, passwords do not match.") + +#ifdef MIN +#undef MIN +#endif +#define MIN(_a, _b) (((_a) < (_b)) ? (_a) : (_b)) + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_PASSWORD + +#include +#include +#include + +/* argument parsing */ +#define PAM_DEBUG_ARG 0x0001 + +struct cracklib_options { + int retry_times; + int diff_ok; + int diff_ignore; + int min_length; + int dig_credit; + int up_credit; + int low_credit; + int oth_credit; + int min_class; + int max_repeat; + int reject_user; + const char *cracklib_dictpath; +}; + +#define CO_RETRY_TIMES 1 +#define CO_DIFF_OK 5 +#define CO_DIFF_IGNORE 23 +#define CO_MIN_LENGTH 9 +# define CO_MIN_LENGTH_BASE 5 +#define CO_DIG_CREDIT 1 +#define CO_UP_CREDIT 1 +#define CO_LOW_CREDIT 1 +#define CO_OTH_CREDIT 1 + +static int +_pam_parse (pam_handle_t *pamh, struct cracklib_options *opt, + int argc, const char **argv) +{ + int ctrl=0; + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + char *ep = NULL; + + /* generic options */ + + if (!strcmp(*argv,"debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strncmp(*argv,"type=",5)) + pam_set_item (pamh, PAM_AUTHTOK_TYPE, *argv+5); + else if (!strncmp(*argv,"retry=",6)) { + opt->retry_times = strtol(*argv+6,&ep,10); + if (!ep || (opt->retry_times < 1)) + opt->retry_times = CO_RETRY_TIMES; + } else if (!strncmp(*argv,"difok=",6)) { + opt->diff_ok = strtol(*argv+6,&ep,10); + if (!ep || (opt->diff_ok < 0)) + opt->diff_ok = CO_DIFF_OK; + } else if (!strncmp(*argv,"difignore=",10)) { + opt->diff_ignore = strtol(*argv+10,&ep,10); + if (!ep || (opt->diff_ignore < 0)) + opt->diff_ignore = CO_DIFF_IGNORE; + } else if (!strncmp(*argv,"minlen=",7)) { + opt->min_length = strtol(*argv+7,&ep,10); + if (!ep || (opt->min_length < CO_MIN_LENGTH_BASE)) + opt->min_length = CO_MIN_LENGTH_BASE; + } else if (!strncmp(*argv,"dcredit=",8)) { + opt->dig_credit = strtol(*argv+8,&ep,10); + if (!ep) + opt->dig_credit = 0; + } else if (!strncmp(*argv,"ucredit=",8)) { + opt->up_credit = strtol(*argv+8,&ep,10); + if (!ep) + opt->up_credit = 0; + } else if (!strncmp(*argv,"lcredit=",8)) { + opt->low_credit = strtol(*argv+8,&ep,10); + if (!ep) + opt->low_credit = 0; + } else if (!strncmp(*argv,"ocredit=",8)) { + opt->oth_credit = strtol(*argv+8,&ep,10); + if (!ep) + opt->oth_credit = 0; + } else if (!strncmp(*argv,"minclass=",9)) { + opt->min_class = strtol(*argv+9,&ep,10); + if (!ep) + opt->min_class = 0; + if (opt->min_class > 4) + opt->min_class = 4; + } else if (!strncmp(*argv,"maxrepeat=",10)) { + opt->max_repeat = strtol(*argv+10,&ep,10); + if (!ep) + opt->max_repeat = 0; + } else if (!strncmp(*argv,"reject_username",15)) { + opt->reject_user = 1; + } else if (!strncmp(*argv,"authtok_type",12)) { + /* for pam_get_authtok, ignore */; + } else if (!strncmp(*argv,"use_authtok",11)) { + /* for pam_get_authtok, ignore */; + } else if (!strncmp(*argv,"use_first_pass",14)) { + /* for pam_get_authtok, ignore */; + } else if (!strncmp(*argv,"try_first_pass",14)) { + /* for pam_get_authtok, ignore */; + } else if (!strncmp(*argv,"dictpath=",9)) { + opt->cracklib_dictpath = *argv+9; + if (!*(opt->cracklib_dictpath)) { + opt->cracklib_dictpath = CRACKLIB_DICTS; + } + } else { + pam_syslog(pamh,LOG_ERR,"pam_parse: unknown option; %s",*argv); + } + } + + return ctrl; +} + +/* Helper functions */ + +/* + * can't be a palindrome - like `R A D A R' or `M A D A M' + */ +static int palindrome(const char *new) +{ + int i, j; + + i = strlen (new); + + for (j = 0;j < i;j++) + if (new[i - j - 1] != new[j]) + return 0; + + return 1; +} + +/* + * Calculate how different two strings are in terms of the number of + * character removals, additions, and changes needed to go from one to + * the other + */ + +static int distdifferent(const char *old, const char *new, + size_t i, size_t j) +{ + char c, d; + + if ((i == 0) || (strlen(old) < i)) { + c = 0; + } else { + c = old[i - 1]; + } + if ((j == 0) || (strlen(new) < j)) { + d = 0; + } else { + d = new[j - 1]; + } + return (c != d); +} + +static int distcalculate(int **distances, const char *old, const char *new, + size_t i, size_t j) +{ + int tmp = 0; + + if (distances[i][j] != -1) { + return distances[i][j]; + } + + tmp = distcalculate(distances, old, new, i - 1, j - 1); + tmp = MIN(tmp, distcalculate(distances, old, new, i, j - 1)); + tmp = MIN(tmp, distcalculate(distances, old, new, i - 1, j)); + tmp += distdifferent(old, new, i, j); + + distances[i][j] = tmp; + + return tmp; +} + +static int distance(const char *old, const char *new) +{ + int **distances = NULL; + size_t m, n, i, j, r; + + m = strlen(old); + n = strlen(new); + distances = malloc(sizeof(int*) * (m + 1)); + + for (i = 0; i <= m; i++) { + distances[i] = malloc(sizeof(int) * (n + 1)); + for(j = 0; j <= n; j++) { + distances[i][j] = -1; + } + } + for (i = 0; i <= m; i++) { + distances[i][0] = i; + } + for (j = 0; j <= n; j++) { + distances[0][j] = j; + } + distances[0][0] = 0; + + r = distcalculate(distances, old, new, m, n); + + for (i = 0; i <= m; i++) { + memset(distances[i], 0, sizeof(int) * (n + 1)); + free(distances[i]); + } + free(distances); + + return r; +} + +static int similar(struct cracklib_options *opt, + const char *old, const char *new) +{ + if (distance(old, new) >= opt->diff_ok) { + return 0; + } + + if (strlen(new) >= (strlen(old) * 2)) { + return 0; + } + + /* passwords are too similar */ + return 1; +} + +/* + * enough classes of charecters + */ + +static int minclass (struct cracklib_options *opt, + const char *new) +{ + int digits = 0; + int uppers = 0; + int lowers = 0; + int others = 0; + int total_class; + int i; + int retval; + + D(( "called" )); + for (i = 0; new[i]; i++) + { + if (isdigit (new[i])) + digits = 1; + else if (isupper (new[i])) + uppers = 1; + else if (islower (new[i])) + lowers = 1; + else + others = 1; + } + + total_class = digits + uppers + lowers + others; + + D (("total class: %d\tmin_class: %d", total_class, opt->min_class)); + + if (total_class >= opt->min_class) + retval = 0; + else + retval = 1; + + return retval; +} + + +/* + * a nice mix of characters. + */ +static int simple(struct cracklib_options *opt, const char *new) +{ + int digits = 0; + int uppers = 0; + int lowers = 0; + int others = 0; + int size; + int i; + + for (i = 0;new[i];i++) { + if (isdigit (new[i])) + digits++; + else if (isupper (new[i])) + uppers++; + else if (islower (new[i])) + lowers++; + else + others++; + } + + /* + * The scam was this - a password of only one character type + * must be 8 letters long. Two types, 7, and so on. + * This is now changed, the base size and the credits or defaults + * see the docs on the module for info on these parameters, the + * defaults cause the effect to be the same as before the change + */ + + if ((opt->dig_credit >= 0) && (digits > opt->dig_credit)) + digits = opt->dig_credit; + + if ((opt->up_credit >= 0) && (uppers > opt->up_credit)) + uppers = opt->up_credit; + + if ((opt->low_credit >= 0) && (lowers > opt->low_credit)) + lowers = opt->low_credit; + + if ((opt->oth_credit >= 0) && (others > opt->oth_credit)) + others = opt->oth_credit; + + size = opt->min_length; + + if (opt->dig_credit >= 0) + size -= digits; + else if (digits < opt->dig_credit * -1) + return 1; + + if (opt->up_credit >= 0) + size -= uppers; + else if (uppers < opt->up_credit * -1) + return 1; + + if (opt->low_credit >= 0) + size -= lowers; + else if (lowers < opt->low_credit * -1) + return 1; + + if (opt->oth_credit >= 0) + size -= others; + else if (others < opt->oth_credit * -1) + return 1; + + if (size <= i) + return 0; + + return 1; +} + +static int consecutive(struct cracklib_options *opt, const char *new) +{ + char c; + int i; + int same; + + if (opt->max_repeat == 0) + return 0; + + for (i = 0; new[i]; i++) { + if (i > 0 && new[i] == c) { + ++same; + if (same > opt->max_repeat) + return 1; + } else { + c = new[i]; + same = 1; + } + } + return 0; +} + +static int usercheck(struct cracklib_options *opt, const char *new, + char *user) +{ + char *f, *b; + + if (!opt->reject_user) + return 0; + + if (strstr(new, user) != NULL) + return 1; + + /* now reverse the username, we can do that in place + as it is strdup-ed */ + f = user; + b = user+strlen(user)-1; + while (f < b) { + char c; + + c = *f; + *f = *b; + *b = c; + --b; + ++f; + } + + if (strstr(new, user) != NULL) + return 1; + return 0; +} + +static char * str_lower(char *string) +{ + char *cp; + + if (!string) + return NULL; + + for (cp = string; *cp; cp++) + *cp = tolower(*cp); + return string; +} + +static const char *password_check(struct cracklib_options *opt, + const char *old, const char *new, + const char *user) +{ + const char *msg = NULL; + char *oldmono = NULL, *newmono, *wrapped = NULL; + char *usermono = NULL; + + if (old && strcmp(new, old) == 0) { + msg = _("is the same as the old one"); + return msg; + } + + newmono = str_lower(x_strdup(new)); + if (!newmono) + msg = _("memory allocation error"); + + usermono = str_lower(x_strdup(user)); + if (!usermono) + msg = _("memory allocation error"); + + if (!msg && old) { + oldmono = str_lower(x_strdup(old)); + if (oldmono) + wrapped = malloc(strlen(oldmono) * 2 + 1); + if (wrapped) { + strcpy (wrapped, oldmono); + strcat (wrapped, oldmono); + } else { + msg = _("memory allocation error"); + } + } + + if (!msg && palindrome(newmono)) + msg = _("is a palindrome"); + + if (!msg && oldmono && strcmp(oldmono, newmono) == 0) + msg = _("case changes only"); + + if (!msg && oldmono && similar(opt, oldmono, newmono)) + msg = _("is too similar to the old one"); + + if (!msg && simple(opt, new)) + msg = _("is too simple"); + + if (!msg && wrapped && strstr(wrapped, newmono)) + msg = _("is rotated"); + + if (!msg && minclass (opt, new)) + msg = _("not enough character classes"); + + if (!msg && consecutive(opt, new)) + msg = _("contains too many same characters consecutively"); + + if (!msg && usercheck(opt, newmono, usermono)) + msg = _("contains the user name in some form"); + + free(usermono); + if (newmono) { + memset(newmono, 0, strlen(newmono)); + free(newmono); + } + if (oldmono) { + memset(oldmono, 0, strlen(oldmono)); + free(oldmono); + } + if (wrapped) { + memset(wrapped, 0, strlen(wrapped)); + free(wrapped); + } + + return msg; +} + + +static int _pam_unix_approve_pass(pam_handle_t *pamh, + unsigned int ctrl, + struct cracklib_options *opt, + const char *pass_old, + const char *pass_new) +{ + const char *msg = NULL; + const char *user; + int retval; + + if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_DEBUG, "bad authentication token"); + pam_error(pamh, "%s", pass_new == NULL ? + _("No password supplied"):_("Password unchanged")); + return PAM_AUTHTOK_ERR; + } + + retval = pam_get_user(pamh, &user, NULL); + if (retval != PAM_SUCCESS || user == NULL) { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh,LOG_ERR,"Can not get username"); + return PAM_AUTHTOK_ERR; + } + /* + * if one wanted to hardwire authentication token strength + * checking this would be the place + */ + msg = password_check(opt, pass_old, pass_new, user); + + if (msg) { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_NOTICE, + "new passwd fails strength check: %s", msg); + pam_error(pamh, _("BAD PASSWORD: %s"), msg); + return PAM_AUTHTOK_ERR; + }; + return PAM_SUCCESS; + +} + +/* The Main Thing (by Cristian Gafton, CEO at this module :-) + * (stolen from http://home.netscape.com) + */ +PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + unsigned int ctrl; + struct cracklib_options options; + + D(("called.")); + + memset(&options, 0, sizeof(options)); + options.retry_times = CO_RETRY_TIMES; + options.diff_ok = CO_DIFF_OK; + options.diff_ignore = CO_DIFF_IGNORE; + options.min_length = CO_MIN_LENGTH; + options.dig_credit = CO_DIG_CREDIT; + options.up_credit = CO_UP_CREDIT; + options.low_credit = CO_LOW_CREDIT; + options.oth_credit = CO_OTH_CREDIT; + options.cracklib_dictpath = CRACKLIB_DICTS; + + ctrl = _pam_parse(pamh, &options, argc, argv); + + if (flags & PAM_PRELIM_CHECK) { + /* Check for passwd dictionary */ + /* We cannot do that, since the original path is compiled + into the cracklib library and we don't know it. */ + return PAM_SUCCESS; + } else if (flags & PAM_UPDATE_AUTHTOK) { + int retval; + const void *oldtoken; + int tries; + + D(("do update")); + + + retval = pam_get_item (pamh, PAM_OLDAUTHTOK, &oldtoken); + if (retval != PAM_SUCCESS) { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh,LOG_ERR,"Can not get old passwd"); + oldtoken = NULL; + } + + tries = 0; + while (tries < options.retry_times) { + const char *crack_msg; + const char *newtoken = NULL; + + + tries++; + + /* Planned modus operandi: + * Get a passwd. + * Verify it against cracklib. + * If okay get it a second time. + * Check to be the same with the first one. + * set PAM_AUTHTOK and return + */ + + retval = pam_get_authtok_noverify (pamh, &newtoken, NULL); + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_ERR, "pam_get_authtok_noverify returned error: %s", + pam_strerror (pamh, retval)); + continue; + } else if (newtoken == NULL) { /* user aborted password change, quit */ + return PAM_AUTHTOK_ERR; + } + + D(("testing password")); + /* now test this passwd against cracklib */ + + D(("against cracklib")); + if ((crack_msg = FascistCheck (newtoken, options.cracklib_dictpath))) { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh,LOG_DEBUG,"bad password: %s",crack_msg); + pam_error (pamh, _("BAD PASSWORD: %s"), crack_msg); + if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) + { + pam_set_item (pamh, PAM_AUTHTOK, NULL); + retval = PAM_AUTHTOK_ERR; + continue; + } + } + + /* check it for strength too... */ + D(("for strength")); + retval = _pam_unix_approve_pass (pamh, ctrl, &options, + oldtoken, newtoken); + if (retval != PAM_SUCCESS) { + if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) + { + pam_set_item(pamh, PAM_AUTHTOK, NULL); + retval = PAM_AUTHTOK_ERR; + continue; + } + } + + retval = pam_get_authtok_verify (pamh, &newtoken, NULL); + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_ERR, "pam_get_authtok_verify returned error: %s", + pam_strerror (pamh, retval)); + pam_set_item(pamh, PAM_AUTHTOK, NULL); + continue; + } else if (newtoken == NULL) { /* user aborted password change, quit */ + return PAM_AUTHTOK_ERR; + } + + return PAM_SUCCESS; + } + + D(("returning because maxtries reached")); + + pam_set_item (pamh, PAM_AUTHTOK, NULL); + + /* if we have only one try, we can use the real reason, + else say that there were too many tries. */ + if (options.retry_times > 1) + return PAM_MAXTRIES; + else + return retval; + + } else { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_NOTICE, "UNKNOWN flags setting %02X",flags); + return PAM_SERVICE_ERR; + } + + /* Not reached */ + return PAM_SERVICE_ERR; +} + + + +#ifdef PAM_STATIC +/* static module data */ +struct pam_module _pam_cracklib_modstruct = { + "pam_cracklib", + NULL, + NULL, + NULL, + NULL, + NULL, + pam_sm_chauthtok +}; +#endif + +/* + * Copyright (c) Cristian Gafton , 1996. + * All rights reserved + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + * + * The following copyright was appended for the long password support + * added with the libpam 0.58 release: + * + * Modificaton Copyright (c) Philip W. Dalrymple III + * 1997. All rights reserved + * + * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/pam_pwquality.c b/src/pam_pwquality.c new file mode 100644 index 0000000..e3cd522 --- /dev/null +++ b/src/pam_pwquality.c @@ -0,0 +1,309 @@ +/* + * PAM module for password quality checking using libpwquality + * + * See the end of the file for Copyright and License Information + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pwquality.h" + +/* For Translators: "%s%s" could be replaced with " " or "". */ +#define PROMPT1 _("New %s%spassword: ") +/* For Translators: "%s%s" could be replaced with " " or "". */ +#define PROMPT2 _("Retype new %s%spassword: ") +#define MISTYPED_PASS _("Sorry, passwords do not match.") + +/* + * here, we make a definition for the externally accessible function + * in this file (this definition is required for static a module + * but strongly encouraged generally) it is used to instruct the + * modules include file to define the function prototypes. + */ + +#define PAM_SM_PASSWORD + +#include +#include +#include + +/* argument parsing */ +#define PAM_DEBUG_ARG 0x0001 + +struct module_options { + int retry_times; + pwquality_settings_t *pwq; +}; + +#define CO_RETRY_TIMES 1 + +static int +_pam_parse (pam_handle_t *pamh, struct module_options *opt, + int argc, const char **argv) +{ + int ctrl = 0; + pwquality_settings_t *pwq; + + pwq = pwquality_default_settings(); + if (pwq == NULL) + return -1; + + /* just log error here */ + if (pwquality_read_config(pwq, NULL)) + pam_syslog(pamh, LOG_ERR, + "Reading pwquality configuration file failed: %m"); + + /* step through arguments */ + for (ctrl = 0; argc-- > 0; ++argv) { + char *ep = NULL; + + if (!strcmp(*argv, "debug")) + ctrl |= PAM_DEBUG_ARG; + else if (!strncmp(*argv, "type=", 5)) + pam_set_item (pamh, PAM_AUTHTOK_TYPE, *argv+5); + else if (!strncmp(*argv, "retry=", 6)) { + opt->retry_times = strtol(*argv+6, &ep, 10); + if (!ep || (opt->retry_times < 1)) + opt->retry_times = CO_RETRY_TIMES; + } else if (!strncmp(*argv, "reject_username", 15)) { + /* ignored for compatibility with pam_cracklib */ + } else if (!strncmp(*argv, "authtok_type", 12)) { + /* for pam_get_authtok, ignore */; + } else if (!strncmp(*argv, "use_authtok", 11)) { + /* for pam_get_authtok, ignore */; + } else if (!strncmp(*argv, "use_first_pass", 14)) { + /* for pam_get_authtok, ignore */; + } else if (!strncmp(*argv, "try_first_pass", 14)) { + /* for pam_get_authtok, ignore */; + } else if (pwquality_set_option(pwq, *argv)) { + pam_syslog(pamh, LOG_ERR, + "pam_parse: unknown or broken option; %s", *argv); + } + } + + opt->pwq = pwq; + + return ctrl; +} + +static const char * +make_error_message(int rv, const char *crack_msg) +{ + switch(rv) { + case PWQ_ERROR_MEM_ALLOC: + return _("memory allocation error"); + case PWQ_ERROR_SAME_PASSWORD: + return _("is the same as the old one"); + case PWQ_ERROR_PALINDROME: + return _("is a palindrome"); + case PWQ_ERROR_CASE_CHANGES_ONLY: + return _("case changes only"); + case PWQ_ERROR_TOO_SIMILAR: + return _("is too similar to the old one"); + case PWQ_ERROR_MIN_DIGITS: + case PWQ_ERROR_MIN_UPPERS: + case PWQ_ERROR_MIN_LOWERS: + case PWQ_ERROR_MIN_OTHERS: + case PWQ_ERROR_MIN_LENGTH: + return _("is too simple"); + case PWQ_ERROR_ROTATED: + return _("is rotated"); + case PWQ_ERROR_MIN_CLASSES: + return _("not enough character classes"); + case PWQ_ERROR_MAX_CONSECUTIVE: + return _("contains too many same characters consecutively"); + case PWQ_ERROR_EMPTY_PASSWORD: + return _("No password supplied"); + case PWQ_ERROR_CRACKLIB_CHECK: + return crack_msg; + default: + return _("Error in service module"); + } +} + +PAM_EXTERN int +pam_sm_chauthtok(pam_handle_t *pamh, int flags, + int argc, const char **argv) +{ + int ctrl; + struct module_options options; + + memset(&options, 0, sizeof(options)); + options.retry_times = CO_RETRY_TIMES; + + ctrl = _pam_parse(pamh, &options, argc, argv); + if (ctrl < 0) + return PAM_BUF_ERR; + + if (flags & PAM_PRELIM_CHECK) { + /* Check for passwd dictionary + * We cannot do that, since the original path is compiled + * into the cracklib library and we don't know it. + */ + return PAM_SUCCESS; + } else if (flags & PAM_UPDATE_AUTHTOK) { + int retval; + const void *oldtoken; + int tries; + + retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &oldtoken); + if (retval != PAM_SUCCESS) { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_ERR, "Can not get old passwd"); + oldtoken = NULL; + } + + tries = 0; + while (tries < options.retry_times) { + const char *crack_msg; + const char *newtoken = NULL; + + tries++; + + /* Planned modus operandi: + * Get a passwd. + * Verify it against libpwquality. + * If okay get it a second time. + * Check to be the same with the first one. + * set PAM_AUTHTOK and return + */ + + retval = pam_get_authtok_noverify(pamh, &newtoken, NULL); + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_ERR, "pam_get_authtok_noverify returned error: %s", + pam_strerror(pamh, retval)); + continue; + } else if (newtoken == NULL) { /* user aborted password change, quit */ + return PAM_AUTHTOK_ERR; + } + + /* now test this passwd against libpwquality */ + retval = pwquality_check(options.pwq, newtoken, oldtoken, &crack_msg); + + if (retval < 0) { + const char *msg; + msg = make_error_message(retval, crack_msg); + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_DEBUG, "bad password: %s", msg); + pam_error(pamh, _("BAD PASSWORD: %s"), msg); + + if (getuid() || (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) { + pam_set_item(pamh, PAM_AUTHTOK, NULL); + retval = PAM_AUTHTOK_ERR; + continue; + } + } else { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_DEBUG, "password score: %d", retval); + } + + retval = pam_get_authtok_verify(pamh, &newtoken, NULL); + if (retval != PAM_SUCCESS) { + pam_syslog(pamh, LOG_ERR, "pam_get_authtok_verify returned error: %s", + pam_strerror(pamh, retval)); + pam_set_item(pamh, PAM_AUTHTOK, NULL); + continue; + } else if (newtoken == NULL) { /* user aborted password change, quit */ + return PAM_AUTHTOK_ERR; + } + + return PAM_SUCCESS; + } + + pam_set_item (pamh, PAM_AUTHTOK, NULL); + + /* if we have only one try, we can use the real reason, + * else say that there were too many tries. */ + if (options.retry_times > 1) + return PAM_MAXTRIES; + else + return retval; + } else { + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_NOTICE, "UNKNOWN flags setting %02X",flags); + } + + return PAM_SERVICE_ERR; +} + + + +#ifdef PAM_STATIC +/* static module data */ +struct pam_module _pam_pwquality_modstruct = { + "pam_pwquality", + NULL, + NULL, + NULL, + NULL, + NULL, + pam_sm_chauthtok +}; +#endif + +/* + * Copyright (c) Cristian Gafton , 1996. + * All rights reserved + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + * + * The following copyright was appended for the long password support + * added with the libpam 0.58 release: + * + * Modificaton Copyright (c) Philip W. Dalrymple III + * 1997. All rights reserved + * + * THE MODIFICATION THAT PROVIDES SUPPORT FOR LONG PASSWORD TYPE CHECKING TO + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/pwmake.c b/src/pwmake.c new file mode 100644 index 0000000..876ef1c --- /dev/null +++ b/src/pwmake.c @@ -0,0 +1,108 @@ +/* + * pwmake - a simple tool for password generation + * + * See the end of the file for Copyright and License Information + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "pwquality.h" + +int +usage(const char *progname) { + fprintf(stderr, _("Usage: %s \n"), progname); +} + +static const char * +make_error_message(int rv) +{ + switch(rv) { + case PWQ_ERROR_MEM_ALLOC: + return _("Memory allocation error"); + case PWQ_ERROR_RNG: + return _("Cannot obtain random numbers from the RNG device"); + case PWQ_ERROR_GENERATION_FAILED: + return _("Password generation failed - required entropy too low for settings"); + default: + return _("Unknown error"); + } +} + +/* score a password */ +int +main(int argc, char *argv[]) +{ + pwquality_settings_t *pwq; + char *password; + int rv; + int bits; + + if (argc != 2) { + usage(argv[0]); + exit(3); + } + + bits = atoi(argv[1]); + + pwq = pwquality_default_settings(); + if (pwq == NULL) { + fprintf(stderr, "Error: %s\n", _("Error: Memory allocation error")); + exit(2); + } + + pwquality_read_config(pwq, NULL); + + rv = pwquality_generate(pwq, bits, &password); + + if (rv != 0) { + fprintf(stderr, "Error: %s\n", make_error_message(rv)); + exit(1); + } + + printf("%s\n", password); + free(password); + return 0; +} + +/* + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/pwqprivate.h b/src/pwqprivate.h new file mode 100644 index 0000000..500c4e6 --- /dev/null +++ b/src/pwqprivate.h @@ -0,0 +1,88 @@ +/* + * libpwquality internal header + * + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * See the end of the file for the License Information + */ + +#ifndef PWQPRIVATE_H +#define PWQPRIVATE_H + +#include "pwquality.h" + +struct pwquality_settings { + int diff_ok; + int diff_ignore; + int min_length; + int dig_credit; + int up_credit; + int low_credit; + int oth_credit; + int min_class; + int max_repeat; + char *dict_path; + +}; + +struct setting_mapping { + const char *name; + int id; + int type; +}; + +#define PWQ_DEFAULT_DIFF_OK 5 +#define PWQ_DEFAULT_DIFF_IGNORE 23 +#define PWQ_DEFAULT_MIN_LENGTH 9 +#define PWQ_DEFAULT_DIG_CREDIT 1 +#define PWQ_DEFAULT_UP_CREDIT 1 +#define PWQ_DEFAULT_LOW_CREDIT 1 +#define PWQ_DEFAULT_OTH_CREDIT 1 + +#define PWQ_TYPE_INT 1 +#define PWQ_TYPE_STR 2 +#define PWQ_TYPE_SET 3 + +#define PWQ_BASE_MIN_LENGTH 6 /* used when lower than this value of min len is set */ +#define PWQ_NUM_CLASSES 4 +#define PWQ_NUM_GENERATION_TRIES 3 /* how many times to try to generate the random password if it fails the check */ + +#ifndef PWQUALITY_DEFAULT_CFGFILE +#define PWQUALITY_DEFAULT_CFGFILE "/etc/security/pwquality.conf" +#endif + +#endif /* PWQPRIVATE_H */ + +/* + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/pwquality.h b/src/pwquality.h new file mode 100644 index 0000000..5e7105b --- /dev/null +++ b/src/pwquality.h @@ -0,0 +1,139 @@ +/* + * libpwquality main API code header + * + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * See the end of the file for the License Information + */ + +#ifndef PWQUALITY_H +#define PWQUALITY_H + +#define PWQ_SETTING_DIFF_OK 1 +#define PWQ_SETTING_DIFF_IGNORE 2 +#define PWQ_SETTING_MIN_LENGTH 3 +#define PWQ_SETTING_DIG_CREDIT 4 +#define PWQ_SETTING_UP_CREDIT 5 +#define PWQ_SETTING_LOW_CREDIT 6 +#define PWQ_SETTING_OTH_CREDIT 7 +#define PWQ_SETTING_MIN_CLASS 8 +#define PWQ_SETTING_MAX_REPEAT 9 +#define PWQ_SETTING_DICT_PATH 10 + +#define PWQ_MAX_ENTROPY_BITS 256 +#define PWQ_MIN_ENTROPY_BITS 56 + +#define PWQ_ERROR_SUCCESS 0 /* implicit, not used in the library code */ +#define PWQ_ERROR_FATAL_FAILURE -1 +#define PWQ_ERROR_INTEGER -2 +#define PWQ_ERROR_CFGFILE_OPEN -3 +#define PWQ_ERROR_CFGFILE_MALFORMED -4 +#define PWQ_ERROR_UNKNOWN_SETTING -5 +#define PWQ_ERROR_NON_INT_SETTING -6 +#define PWQ_ERROR_NON_STR_SETTING -7 +#define PWQ_ERROR_MEM_ALLOC -8 +#define PWQ_ERROR_TOO_SIMILAR -9 +#define PWQ_ERROR_MIN_DIGITS -10 +#define PWQ_ERROR_MIN_UPPERS -11 +#define PWQ_ERROR_MIN_LOWERS -12 +#define PWQ_ERROR_MIN_OTHERS -13 +#define PWQ_ERROR_MIN_LENGTH -14 +#define PWQ_ERROR_PALINDROME -15 +#define PWQ_ERROR_CASE_CHANGES_ONLY -16 +#define PWQ_ERROR_ROTATED -17 +#define PWQ_ERROR_MIN_CLASSES -18 +#define PWQ_ERROR_MAX_CONSECUTIVE -19 +#define PWQ_ERROR_EMPTY_PASSWORD -20 +#define PWQ_ERROR_SAME_PASSWORD -21 +#define PWQ_ERROR_CRACKLIB_CHECK -22 +#define PWQ_ERROR_RNG -23 +#define PWQ_ERROR_GENERATION_FAILED -24 + +typedef struct pwquality_settings pwquality_settings_t; + +/* returns default pwquality settings to be used in other library calls */ +pwquality_settings_t * +pwquality_default_settings(void); + +/* frees pwquality settings data */ +void +pwquality_free_settings(pwquality_settings_t *pwq); + +/* parse the configuration file (if NULL then the default one) */ +int +pwquality_read_config(pwquality_settings_t *pwq, const char *cfgfile); + +/* useful for setting the options as configured on a pam module + * command line in form of = */ +int +pwquality_set_option(pwquality_settings_t *pwq, const char *option); + +/* set value of an integer setting */ +int +pwquality_set_int_value(pwquality_settings_t *pwq, int setting, int value); + +/* set value of a string setting */ +int +pwquality_set_str_value(pwquality_settings_t *pwq, int setting, + const char *value); + +/* get value of an integer setting, or -1 if setting unknown */ +int +pwquality_get_int_value(pwquality_settings_t *pwq, int setting); + +/* get value of a string setting, or NULL if setting unknown */ +const char * +pwquality_get_str_value(pwquality_settings_t *pwq, int setting); + +/* generate a random password of entropy_bits entropy and check it according to + * the settings */ +int +pwquality_generate(pwquality_settings_t *pwq, int entropy_bits, + char **password); + +/* check the password according to the settings + * it returns either score <0-100>, negative error number, + * and in case of PWQ_ERROR_CRACKLIB also auxiliary + * error message returned from cracklib + * The old password is optional and can be NULL. + * The score depends on PWQ_SETTING_MIN_LENGTH. If it is set higher, + * the score for the same passwords will be lower. */ +int +pwquality_check(pwquality_settings_t *pwq, const char *password, + const char *oldpassword, const char **error); + +#endif /* PWQUALITY_H */ + +/* + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/pwscore.c b/src/pwscore.c new file mode 100644 index 0000000..9046bbb --- /dev/null +++ b/src/pwscore.c @@ -0,0 +1,143 @@ +/* + * pwscore - a simple tool for password scoring + * + * See the end of the file for Copyright and License Information + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "pwquality.h" + +int +usage(const char *progname) { + fprintf(stderr, _("Usage: %s\n"), progname); + fprintf(stderr, _(" The command reads the password to be scored from the standard input.\n")); +} + +static const char * +make_error_message(int rv, const char *crack_msg) +{ + static char buf[200]; + + switch(rv) { + case PWQ_ERROR_MEM_ALLOC: + return _("Memory allocation error"); + case PWQ_ERROR_SAME_PASSWORD: + return _("The password is the same as the old one"); + case PWQ_ERROR_PALINDROME: + return _("The password is a palindrome"); + case PWQ_ERROR_CASE_CHANGES_ONLY: + return _("The password differs with case changes only"); + case PWQ_ERROR_TOO_SIMILAR: + return _("The password is too similar to the old one"); + case PWQ_ERROR_MIN_DIGITS: + return _("The password contains too few digits"); + case PWQ_ERROR_MIN_UPPERS: + return _("The password contains too few uppercase letters"); + case PWQ_ERROR_MIN_LOWERS: + return _("The password contains too few lowercase letters"); + case PWQ_ERROR_MIN_OTHERS: + return _("The password contains too few non-alphanumeric characters"); + case PWQ_ERROR_MIN_LENGTH: + return _("The password is too short"); + case PWQ_ERROR_ROTATED: + return _("The password is just rotated old one"); + case PWQ_ERROR_MIN_CLASSES: + return _("The password does not contain enough character classes"); + case PWQ_ERROR_MAX_CONSECUTIVE: + return _("The password contains too many same characters consecutively"); + case PWQ_ERROR_EMPTY_PASSWORD: + return _("No password supplied"); + case PWQ_ERROR_CRACKLIB_CHECK: + snprintf(buf, sizeof(buf), _("The password fails the dictionary check - %s"), crack_msg); + return buf; + default: + return _("Unknown error"); + } +} + + +/* score a password */ +int +main(int argc, char *argv[]) +{ + pwquality_settings_t *pwq; + int rv; + const char *crack_msg; + char buf[1024]; + size_t len; + + if (argc != 1) { + usage(argv[0]); + exit(3); + } + + if (fgets(buf, sizeof(buf), stdin) == NULL || (len = strlen(buf)) == 0) { + fprintf(stderr, "Error: Could not obtain the password to be scored\n"); + exit(4); + } + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + pwq = pwquality_default_settings(); + if (pwq == NULL) { + fprintf(stderr, _("Error: %s\n"), make_error_message(PWQ_ERROR_MEM_ALLOC, NULL)); + exit(2); + } + + pwquality_read_config(pwq, NULL); + + rv = pwquality_check(pwq, buf, NULL, &crack_msg); + + if (rv < 0) { + fprintf(stderr, _("Password quality check failed: %s\n"), + make_error_message(rv, crack_msg)); + exit(1); + } + + printf("%d\n", rv); + return 0; +} + +/* + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ diff --git a/src/settings.c b/src/settings.c new file mode 100644 index 0000000..70c0c2e --- /dev/null +++ b/src/settings.c @@ -0,0 +1,350 @@ +/* + * libpwquality main API code for reading and manipulation of settings + * + * See the end of the file for Copyright and License Information + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "pwquality.h" +#include "pwqprivate.h" + +/* returns default pwquality settings to be used in other library calls */ +pwquality_settings_t * +pwquality_default_settings(void) +{ + pwquality_settings_t *pwq; + + pwq = calloc(1, sizeof(*pwq)); + if (!pwq) + return NULL; + + pwq->diff_ok = PWQ_DEFAULT_DIFF_OK; + pwq->diff_ignore = PWQ_DEFAULT_DIFF_IGNORE; + pwq->min_length = PWQ_DEFAULT_MIN_LENGTH; + pwq->dig_credit = PWQ_DEFAULT_DIG_CREDIT; + pwq->up_credit = PWQ_DEFAULT_UP_CREDIT; + pwq->low_credit = PWQ_DEFAULT_LOW_CREDIT; + pwq->oth_credit = PWQ_DEFAULT_OTH_CREDIT; + /* pwq->dict_path = NULL; unneeded */ + + return pwq; +} + +/* frees pwquality settings data */ +void +pwquality_free_settings(pwquality_settings_t *pwq) +{ + if (pwq) { + free(pwq->dict_path); + free(pwq); + } +} + + +static const struct setting_mapping s_map[] = { + { "difok", PWQ_SETTING_DIFF_OK, PWQ_TYPE_INT}, + { "difignore", PWQ_SETTING_DIFF_IGNORE, PWQ_TYPE_INT}, + { "minlen", PWQ_SETTING_MIN_LENGTH, PWQ_TYPE_INT}, + { "dcredit", PWQ_SETTING_DIG_CREDIT, PWQ_TYPE_INT}, + { "ucredit", PWQ_SETTING_UP_CREDIT, PWQ_TYPE_INT}, + { "lcredit", PWQ_SETTING_LOW_CREDIT, PWQ_TYPE_INT}, + { "ocredit", PWQ_SETTING_OTH_CREDIT, PWQ_TYPE_INT}, + { "minclass", PWQ_SETTING_MIN_CLASS, PWQ_TYPE_INT}, + { "maxrepeat", PWQ_SETTING_MAX_REPEAT, PWQ_TYPE_INT}, + { "dictpath", PWQ_SETTING_DICT_PATH, PWQ_TYPE_STR} +}; + +/* set setting name with value */ +static int +set_name_value(pwquality_settings_t *pwq, const char *name, const char *value) +{ + int i; + long val; + char *endptr; + + for (i = 0; i < sizeof(s_map)/sizeof(s_map[0]); i++) { + if (strcasecmp(s_map[i].name, name) == 0) { + switch(s_map[i].type) { + case PWQ_TYPE_INT: + errno = 0; + val = strtol(value, &endptr, 10); + if (errno != 0 || *value == '\0' || + *endptr != '\0' || val >= INT_MAX || val <= INT_MIN) { + return PWQ_ERROR_INTEGER; + } + return pwquality_set_int_value(pwq, s_map[i].id, + (int)val); + case PWQ_TYPE_STR: + return pwquality_set_str_value(pwq, s_map[i].id, + value); + case PWQ_TYPE_SET: + return pwquality_set_int_value(pwq, s_map[i].id, + 1); + } + } + } + return PWQ_ERROR_UNKNOWN_SETTING; +} + +#define PWQSETTINGS_MAX_LINELEN 1023 + +/* parse the configuration file (if NULL then the default one) */ +int +pwquality_read_config(pwquality_settings_t *pwq, const char *cfgfile) +{ + FILE *f; + char linebuf[PWQSETTINGS_MAX_LINELEN+1]; + int rv = 0; + + if (cfgfile == NULL) + cfgfile = PWQUALITY_DEFAULT_CFGFILE; + + f = fopen(cfgfile, "r"); + if (f == NULL) + return PWQ_ERROR_CFGFILE_OPEN; + + while (fgets(linebuf, sizeof(linebuf), f) != NULL) { + size_t len; + char *ptr; + char *name; + + len = strlen(linebuf); + if (linebuf[len - 1] != '\n' && !feof(f)) { + (void) fclose(f); + return PWQ_ERROR_CFGFILE_MALFORMED; + } + + if ((ptr=strchr(linebuf, '#')) != NULL) { + *ptr = '\0'; + } else { + ptr = linebuf + len; + } + + /* drop terminating whitespace including the \n */ + do { + if (!isspace(*(ptr-1))) { + *ptr = '\0'; + break; + } + --ptr; + } while (ptr > linebuf); + + /* skip initial whitespace */ + for (ptr = linebuf; isspace(*ptr); ptr++); + if (*ptr == '\0') + continue; + + name = ptr; + while (*ptr != '\0') { + if (isspace(*ptr)) { + *ptr = '\0'; + ++ptr; + break; + } + ++ptr; + } + while (*ptr != '\0') { + if (!isspace(*ptr)) { + break; + } + ++ptr; + } + + if ((rv=set_name_value(pwq, name, ptr)) != 0) { + break; + } + } + + (void)fclose(f); + return rv; +} + +/* useful for setting the options as configured on a pam module + * command line in form of = */ +int +pwquality_set_option(pwquality_settings_t *pwq, const char *option) +{ + char name[80]; /* no options with name longer than that */ + const char *value; + size_t len; + + value = strchr(option, '='); + if (value == NULL) { + len = strlen(option); + value = option + len; /* just empty value */ + } else { + len = value - option; + ++value; + } + if (len > sizeof(name) - 1) + return PWQ_ERROR_UNKNOWN_SETTING; + + strncpy(name, option, len); + name[sizeof(name) - 1] = '\0'; + + return set_name_value(pwq, name, value); +} + +/* set value of an integer setting */ +int +pwquality_set_int_value(pwquality_settings_t *pwq, int setting, int value) +{ + switch(setting) { + case PWQ_SETTING_DIFF_OK: + pwq->diff_ok = value; + break; + case PWQ_SETTING_DIFF_IGNORE: + pwq->diff_ignore = value; + break; + case PWQ_SETTING_MIN_LENGTH: + if (value < PWQ_BASE_MIN_LENGTH) + value = PWQ_BASE_MIN_LENGTH; + pwq->min_length = value; + break; + case PWQ_SETTING_DIG_CREDIT: + pwq->dig_credit = value; + break; + case PWQ_SETTING_UP_CREDIT: + pwq->up_credit = value; + break; + case PWQ_SETTING_LOW_CREDIT: + pwq->low_credit = value; + break; + case PWQ_SETTING_OTH_CREDIT: + pwq->oth_credit = value; + break; + case PWQ_SETTING_MIN_CLASS: + if (value > PWQ_NUM_CLASSES) + value = PWQ_NUM_CLASSES; + pwq->min_class = value; + break; + case PWQ_SETTING_MAX_REPEAT: + pwq->max_repeat = value; + break; + default: + return PWQ_ERROR_NON_INT_SETTING; + } + + return 0; +} + +/* set value of a string setting */ +int +pwquality_set_str_value(pwquality_settings_t *pwq, int setting, + const char *value) +{ + char *dup; + + if (value == NULL || *value == '\0') { + dup = NULL; + } else { + dup = strdup(value); + if (dup == NULL) + return PWQ_ERROR_MEM_ALLOC; + } + + switch(setting) { + case PWQ_SETTING_DICT_PATH: + pwq->dict_path = dup; + break; + default: + return PWQ_ERROR_NON_STR_SETTING; + } + + return 0; +} + +/* get value of an integer setting, or -1 if setting unknown */ +int +pwquality_get_int_value(pwquality_settings_t *pwq, int setting) +{ + switch(setting) { + case PWQ_SETTING_DIFF_OK: + return pwq->diff_ok; + break; + case PWQ_SETTING_DIFF_IGNORE: + return pwq->diff_ignore; + break; + case PWQ_SETTING_MIN_LENGTH: + return pwq->min_length; + break; + case PWQ_SETTING_DIG_CREDIT: + return pwq->dig_credit; + break; + case PWQ_SETTING_UP_CREDIT: + return pwq->up_credit; + break; + case PWQ_SETTING_LOW_CREDIT: + return pwq->low_credit; + break; + case PWQ_SETTING_OTH_CREDIT: + return pwq->oth_credit; + break; + case PWQ_SETTING_MIN_CLASS: + return pwq->min_class; + break; + case PWQ_SETTING_MAX_REPEAT: + return pwq->max_repeat; + break; + default: + return -1; + } +} + +/* get value of a string setting, or NULL if setting unknown */ +const char * +pwquality_get_str_value(pwquality_settings_t *pwq, int setting) +{ + switch(setting) { + case PWQ_SETTING_DICT_PATH: + return pwq->dict_path; + break; + default: + return NULL; + } + +} + +/* + * Copyright (c) Red Hat, Inc, 2011 + * Copyright (c) Tomas Mraz , 2011 + * + * 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, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 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. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED `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 THE AUTHOR 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. + */ -- cgit v1.2.1