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