]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/general.c
c491ea01b9522742e2fd2afc0ff4f5cb587a4ccd
[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     /*
64      * Try to initialize CDB, CrackLib, and SQLite dictionaries.  These
65      * functions handle their own configuration parsing and will do nothing if
66      * the corresponding dictionary is not configured.
67      */
68     code = strength_init_cracklib(ctx, data, dictionary);
69     if (code != 0)
70         goto fail;
71     code = strength_init_cdb(ctx, data);
72     if (code != 0)
73         goto fail;
74     code = strength_init_sqlite(ctx, data);
75     if (code != 0)
76         goto fail;
77
78     /* Initialized.  Set moddata and return. */
79     *moddata = data;
80     return 0;
81
82 fail:
83     if (data != NULL)
84         strength_close(ctx, data);
85     *moddata = NULL;
86     return code;
87 }
88
89
90 /*
91  * Check if a password contains only printable ASCII characters.
92  */
93 static bool
94 only_printable_ascii(const char *password)
95 {
96     const char *p;
97
98     for (p = password; *p != '\0'; p++)
99         if (!isascii((unsigned char) *p) || !isprint((unsigned char) *p))
100             return false;
101     return true;
102 }
103
104
105 /*
106  * Check if a password contains only letters and spaces.
107  */
108 static bool
109 only_alpha_space(const char *password)
110 {
111     const char *p;
112
113     for (p = password; *p != '\0'; p++)
114         if (!isalpha((unsigned char) *p) && *p != ' ')
115             return false;
116     return true;
117 }
118
119
120 /*
121  * Check if a password has a sufficient number of unique characters.  Takes
122  * the password and the required number of characters.
123  */
124 static bool
125 has_minimum_different(const char *password, long minimum)
126 {
127     size_t unique;
128     const char *p;
129
130     /* Special cases for passwords of length 0 and a minimum <= 1. */
131     if (password == NULL || password[0] == '\0')
132         return minimum <= 0;
133     if (minimum <= 1)
134         return true;
135
136     /*
137      * Count the number of unique characters by incrementing the count if each
138      * subsequent character is not found in the previous password characters.
139      * This algorithm is O(n^2), but passwords are short enough it shouldn't
140      * matter.
141      */
142     unique = 1;
143     for (p = password + 1; *p != '\0'; p++)
144         if (memchr(password, *p, p - password) == NULL) {
145             unique++;
146             if (unique >= (size_t) minimum)
147                 return true;
148         }
149     return false;
150 }
151
152
153 /*
154  * Check a given password.  Takes a Kerberos context, our module data, the
155  * password, the principal the password is for, and a buffer and buffer length
156  * into which to put any failure message.
157  */
158 krb5_error_code
159 strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
160                const char *principal, const char *password)
161 {
162     krb5_error_code code;
163
164     /* Check minimum length first, since that's easy. */
165     if ((long) strlen(password) < data->minimum_length)
166         return strength_error_tooshort(ctx, ERROR_SHORT);
167
168     /*
169      * If desired, check whether the password contains non-ASCII or
170      * non-printable ASCII characters.
171      */
172     if (data->ascii && !only_printable_ascii(password))
173         return strength_error_generic(ctx, ERROR_ASCII);
174
175     /*
176      * If desired, ensure the password has a non-letter (and non-space)
177      * character.  This requires that people using phrases at least include a
178      * digit or punctuation to make phrase dictionary attacks or dictionary
179      * attacks via combinations of words harder.
180      */
181     if (data->nonletter && only_alpha_space(password))
182         return strength_error_class(ctx, ERROR_LETTER);
183
184     /* If desired, check for enough unique characters. */
185     if (data->minimum_different > 0)
186         if (!has_minimum_different(password, data->minimum_different))
187             return strength_error_class(ctx, ERROR_MINDIFF);
188
189     /*
190      * If desired, check that the password satisfies character class
191      * restrictions.
192      */
193     code = strength_check_classes(ctx, data, password);
194     if (code != 0)
195         return code;
196
197     /* Check if the password is based on the principal in some way. */
198     code = strength_check_principal(ctx, data, principal, password);
199     if (code != 0)
200         return code;
201
202     /* Check the password against CDB, CrackLib, and SQLite if configured. */
203     code = strength_check_cracklib(ctx, data, password);
204     if (code != 0)
205         return code;
206     code = strength_check_cdb(ctx, data, password);
207     if (code != 0)
208         return code;
209     code = strength_check_sqlite(ctx, data, password);
210     if (code != 0)
211         return code;
212
213     /* Success.  Password accepted. */
214     return 0;
215 }
216
217
218 /*
219  * Cleanly shut down the password strength plugin.  The only thing we have to
220  * do is free the memory allocated for our internal data.
221  */
222 void
223 strength_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
224 {
225     struct class_rule *last, *tmp;
226
227     if (data == NULL)
228         return;
229     strength_close_cdb(ctx, data);
230     strength_close_sqlite(ctx, data);
231     last = data->rules;
232     while (last != NULL) {
233         tmp = last;
234         last = last->next;
235         free(tmp);
236     }
237     free(data->dictionary);
238     free(data);
239 }