]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/general.c
Clean up and refactor configuration handling
[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 <rra@stanford.edu>
12  * Copyright 2006, 2007, 2009, 2012, 2013
13  *     The Board of Trustees of the Leland Stanford Junior Unversity
14  *
15  * See LICENSE for licensing terms.
16  */
17
18 #include <config.h>
19 #include <portable/kadmin.h>
20 #include <portable/krb5.h>
21 #include <portable/system.h>
22
23 #ifdef HAVE_CDB_H
24 # include <cdb.h>
25 #endif
26 #include <ctype.h>
27 #include <errno.h>
28
29 #include <plugin/internal.h>
30 #include <util/macros.h>
31
32
33 /*
34  * Initialize the module.  Ensure that the dictionary file exists and is
35  * readable and store the path in the module context.  Returns 0 on success,
36  * non-zero on failure.  This function returns failure only if it could not
37  * allocate memory or internal Kerberos calls that shouldn't fail do.
38  *
39  * The dictionary file should not include the trailing .pwd extension.
40  * Currently, we don't cope with a NULL dictionary path.
41  */
42 krb5_error_code
43 strength_init(krb5_context ctx, const char *dictionary,
44               krb5_pwqual_moddata *moddata)
45 {
46     krb5_pwqual_moddata data = NULL;
47     krb5_error_code code;
48
49     /* Allocate our internal data. */
50     data = calloc(1, sizeof(*data));
51     if (data == NULL)
52         return strength_error_system(ctx, "cannot allocate memory");
53     data->cdb_fd = -1;
54
55     /* Get minimum length information from krb5.conf. */
56     strength_config_number(ctx, "minimum_length", &data->minimum_length);
57
58     /* Get character class restrictions from krb5.conf. */
59     strength_config_boolean(ctx, "require_ascii_printable", &data->ascii);
60     strength_config_boolean(ctx, "require_non_letter", &data->nonletter);
61
62     /*
63      * Try to initialize CDB and CrackLib dictionaries.  Both functions handle
64      * their own configuration parsing and will do nothing if the
65      * corresponding dictionary is not configured.
66      */
67     code = strength_init_cracklib(ctx, data, dictionary);
68     if (code != 0)
69         goto fail;
70     code = strength_init_cdb(ctx, data);
71     if (code != 0)
72         goto fail;
73
74     /* Initialized.  Set moddata and return. */
75     *moddata = data;
76     return 0;
77
78 fail:
79     if (data != NULL)
80         strength_close(ctx, data);
81     *moddata = NULL;
82     return code;
83 }
84
85
86 /*
87  * Check if a password contains only printable ASCII characters.
88  */
89 static bool
90 only_printable_ascii(const char *password)
91 {
92     const char *p;
93
94     for (p = password; *p != '\0'; p++)
95         if (!isascii((unsigned char) *p) || !isprint((unsigned char) *p))
96             return false;
97     return true;
98 }
99
100
101 /*
102  * Check if a password contains only letters and spaces.
103  */
104 static bool
105 only_alpha_space(const char *password)
106 {
107     const char *p;
108
109     for (p = password; *p != '\0'; p++)
110         if (!isalpha((unsigned char) *p) && *p != ' ')
111             return false;
112     return true;
113 }
114
115
116 /*
117  * Check a given password.  Takes a Kerberos context, our module data, the
118  * password, the principal the password is for, and a buffer and buffer length
119  * into which to put any failure message.
120  */
121 krb5_error_code
122 strength_check(krb5_context ctx UNUSED, krb5_pwqual_moddata data,
123                const char *password, const char *principal)
124 {
125     char *user, *p;
126     const char *q;
127     size_t i, j;
128     char c;
129     krb5_error_code code;
130
131     /* Check minimum length first, since that's easy. */
132     if ((long) strlen(password) < data->minimum_length)
133         return strength_error_tooshort(ctx, ERROR_SHORT);
134
135     /*
136      * If desired, check whether the password contains non-ASCII or
137      * non-printable ASCII characters.
138      */
139     if (data->ascii && !only_printable_ascii(password))
140         return strength_error_generic(ctx, ERROR_ASCII);
141
142     /*
143      * If desired, ensure the password has a non-letter (and non-space)
144      * character.  This requires that people using phrases at least include a
145      * digit or punctuation to make phrase dictionary attacks or dictionary
146      * attacks via combinations of words harder.
147      */
148     if (data->nonletter && only_alpha_space(password))
149         return strength_error_class(ctx, ERROR_LETTER);
150
151     /*
152      * We get the principal (in krb5_unparse_name format) and we want to be
153      * sure that the password doesn't match the username, the username
154      * reversed, or the username with trailing digits.  We therefore have to
155      * copy the string so that we can manipulate it a bit.
156      */
157     if (strcasecmp(password, principal) == 0)
158         return strength_error_generic(ctx, ERROR_USERNAME);
159     user = strdup(principal);
160     if (user == NULL)
161         return strength_error_system(ctx, "cannot allocate memory");
162
163     /* Strip the realm off of the principal. */
164     for (p = user; p[0] != '\0'; p++) {
165         if (p[0] == '\\' && p[1] != '\0') {
166             p++;
167             continue;
168         }
169         if (p[0] == '@') {
170             p[0] = '\0';
171             break;
172         }
173     }
174
175     /*
176      * If the length of the password matches the length of the local portion
177      * of the principal, check for exact matches or reversed matches.
178      */
179     if (strlen(password) == strlen(user)) {
180         if (strcasecmp(password, user) == 0) {
181             free(user);
182             return strength_error_generic(ctx, ERROR_USERNAME);
183         }
184
185         /* Check against the reversed username. */
186         for (i = 0, j = strlen(user) - 1; i < j; i++, j--) {
187             c = user[i];
188             user[i] = user[j];
189             user[j] = c;
190         }
191         if (strcasecmp(password, user) == 0) {
192             free(user);
193             return strength_error_generic(ctx, ERROR_USERNAME);
194         }
195     }
196
197     /*
198      * If the length is greater, check whether the user just added trailing
199      * digits to the local portion of the principal to form the password.
200      */
201     if (strlen(password) > strlen(user))
202         if (strncasecmp(password, user, strlen(user)) == 0) {
203             q = password + strlen(user);
204             while (isdigit((unsigned char) *q))
205                 q++;
206             if (*q == '\0') {
207                 free(user);
208                 return strength_error_generic(ctx, ERROR_USERNAME);
209             }
210         }
211     free(user);
212
213     /* Check the password against CDB and CrackLib if configured. */
214     code = strength_check_cracklib(ctx, data, password);
215     if (code != 0)
216         return code;
217     code = strength_check_cdb(ctx, data, password);
218     if (code != 0)
219         return code;
220
221     /* Success.  Password accepted. */
222     return 0;
223 }
224
225
226 /*
227  * Cleanly shut down the password strength plugin.  The only thing we have to
228  * do is free the memory allocated for our internal data.
229  */
230 void
231 strength_close(krb5_context ctx UNUSED, krb5_pwqual_moddata data)
232 {
233     if (data != NULL) {
234         strength_close_cdb(ctx, data);
235         free(data->dictionary);
236         free(data);
237     }
238 }