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