File: general.c

package info (click to toggle)
krb5-strength 3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 2,668 kB
  • ctags: 876
  • sloc: sh: 11,907; ansic: 8,234; perl: 1,208; makefile: 167
file content (239 lines) | stat: -rw-r--r-- 7,212 bytes parent folder | download
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);
}