1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
|
/*
* The general entry points for password strength checking.
*
* Provides the strength_init, strength_check, and strength_close entry points
* for doing password strength checking. These are the only interfaces that
* are called by the implementation-specific code, and all other checks are
* wrapped up in those interfaces.
*
* Developed by Derrick Brashear and Ken Hornstein of Sine Nomine Associates,
* on behalf of Stanford University
* Extensive modifications by Russ Allbery <eagle@eyrie.org>
* Copyright 2006, 2007, 2009, 2012, 2013, 2014
* The Board of Trustees of the Leland Stanford Junior University
*
* See LICENSE for licensing terms.
*/
#include <config.h>
#include <portable/krb5.h>
#include <portable/system.h>
#include <ctype.h>
#include <plugin/internal.h>
#include <util/macros.h>
/*
* Initialize the module. Ensure that the dictionary file exists and is
* readable and store the path in the module context. Returns 0 on success,
* non-zero on failure. This function returns failure only if it could not
* allocate memory or internal Kerberos calls that shouldn't fail do.
*
* The dictionary file should not include the trailing .pwd extension.
* Currently, we don't cope with a NULL dictionary path.
*/
krb5_error_code
strength_init(krb5_context ctx, const char *dictionary,
krb5_pwqual_moddata *moddata)
{
krb5_pwqual_moddata data = NULL;
krb5_error_code code;
/* Allocate our internal data. */
data = calloc(1, sizeof(*data));
if (data == NULL)
return strength_error_system(ctx, "cannot allocate memory");
data->cdb_fd = -1;
/* Get minimum length and character information from krb5.conf. */
strength_config_number(ctx, "minimum_different", &data->minimum_different);
strength_config_number(ctx, "minimum_length", &data->minimum_length);
/* Get simple character class restrictions from krb5.conf. */
strength_config_boolean(ctx, "require_ascii_printable", &data->ascii);
strength_config_boolean(ctx, "require_non_letter", &data->nonletter);
/* Get complex character class restrictions from krb5.conf. */
code = strength_config_classes(ctx, "require_classes", &data->rules);
if (code != 0)
goto fail;
/*
* Try to initialize CDB, CrackLib, and SQLite dictionaries. These
* functions handle their own configuration parsing and will do nothing if
* the corresponding dictionary is not configured.
*/
code = strength_init_cracklib(ctx, data, dictionary);
if (code != 0)
goto fail;
code = strength_init_cdb(ctx, data);
if (code != 0)
goto fail;
code = strength_init_sqlite(ctx, data);
if (code != 0)
goto fail;
/* Initialized. Set moddata and return. */
*moddata = data;
return 0;
fail:
if (data != NULL)
strength_close(ctx, data);
*moddata = NULL;
return code;
}
/*
* Check if a password contains only printable ASCII characters.
*/
static bool
only_printable_ascii(const char *password)
{
const char *p;
for (p = password; *p != '\0'; p++)
if (!isascii((unsigned char) *p) || !isprint((unsigned char) *p))
return false;
return true;
}
/*
* Check if a password contains only letters and spaces.
*/
static bool
only_alpha_space(const char *password)
{
const char *p;
for (p = password; *p != '\0'; p++)
if (!isalpha((unsigned char) *p) && *p != ' ')
return false;
return true;
}
/*
* Check if a password has a sufficient number of unique characters. Takes
* the password and the required number of characters.
*/
static bool
has_minimum_different(const char *password, long minimum)
{
size_t unique;
const char *p;
/* Special cases for passwords of length 0 and a minimum <= 1. */
if (password == NULL || password[0] == '\0')
return minimum <= 0;
if (minimum <= 1)
return true;
/*
* Count the number of unique characters by incrementing the count if each
* subsequent character is not found in the previous password characters.
* This algorithm is O(n^2), but passwords are short enough it shouldn't
* matter.
*/
unique = 1;
for (p = password + 1; *p != '\0'; p++)
if (memchr(password, *p, p - password) == NULL) {
unique++;
if (unique >= (size_t) minimum)
return true;
}
return false;
}
/*
* Check a given password. Takes a Kerberos context, our module data, the
* password, the principal the password is for, and a buffer and buffer length
* into which to put any failure message.
*/
krb5_error_code
strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
const char *principal, const char *password)
{
krb5_error_code code;
/* Check minimum length first, since that's easy. */
if ((long) strlen(password) < data->minimum_length)
return strength_error_tooshort(ctx, ERROR_SHORT);
/*
* If desired, check whether the password contains non-ASCII or
* non-printable ASCII characters.
*/
if (data->ascii && !only_printable_ascii(password))
return strength_error_generic(ctx, ERROR_ASCII);
/*
* If desired, ensure the password has a non-letter (and non-space)
* character. This requires that people using phrases at least include a
* digit or punctuation to make phrase dictionary attacks or dictionary
* attacks via combinations of words harder.
*/
if (data->nonletter && only_alpha_space(password))
return strength_error_class(ctx, ERROR_LETTER);
/* If desired, check for enough unique characters. */
if (data->minimum_different > 0)
if (!has_minimum_different(password, data->minimum_different))
return strength_error_class(ctx, ERROR_MINDIFF);
/*
* If desired, check that the password satisfies character class
* restrictions.
*/
code = strength_check_classes(ctx, data, password);
if (code != 0)
return code;
/* Check if the password is based on the principal in some way. */
code = strength_check_principal(ctx, data, principal, password);
if (code != 0)
return code;
/* Check the password against CDB, CrackLib, and SQLite if configured. */
code = strength_check_cracklib(ctx, data, password);
if (code != 0)
return code;
code = strength_check_cdb(ctx, data, password);
if (code != 0)
return code;
code = strength_check_sqlite(ctx, data, password);
if (code != 0)
return code;
/* Success. Password accepted. */
return 0;
}
/*
* Cleanly shut down the password strength plugin. The only thing we have to
* do is free the memory allocated for our internal data.
*/
void
strength_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
{
struct class_rule *last, *tmp;
if (data == NULL)
return;
strength_close_cdb(ctx, data);
strength_close_sqlite(ctx, data);
last = data->rules;
while (last != NULL) {
tmp = last;
last = last->next;
free(tmp);
}
free(data->dictionary);
free(data);
}
|