]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/config.c
Add support for requiring a number of character classes
[kerberos/krb5-strength.git] / plugin / config.c
1 /*
2  * Retrieve configuration settings from krb5.conf.
3  *
4  * Provided here are functions to retrieve boolean, numeric, and string
5  * settings from krb5.conf.  This wraps the somewhat awkward
6  * krb5_appdefaults_* functions.
7  *
8  * Written by Russ Allbery <eagle@eyrie.org>
9  * Copyright 2013
10  *     The Board of Trustees of the Leland Stanford Junior University
11  *
12  * See LICENSE for licensing terms.
13  */
14
15 #include <config.h>
16 #include <portable/krb5.h>
17 #include <portable/system.h>
18
19 #include <ctype.h>
20 #include <errno.h>
21
22 #include <plugin/internal.h>
23 #include <util/macros.h>
24
25 /* maximum number of character classes */
26 #define MAX_CLASSES 4
27
28 /* The representation of the realm differs between MIT and Kerberos. */
29 #ifdef HAVE_KRB5_REALM
30 typedef krb5_realm realm_type;
31 #else
32 typedef krb5_data *realm_type;
33 #endif
34
35
36 /*
37  * Obtain the default realm and translate it into the format required by
38  * krb5_appdefault_*.  This is obnoxious for MIT Kerberos, which returns the
39  * default realm as a string but expects the realm as a krb5_data type when
40  * calling krb5_appdefault_*.
41  */
42 #ifdef HAVE_KRB5_REALM
43
44 static realm_type
45 default_realm(krb5_context ctx)
46 {
47     krb5_error_code code;
48     realm_type realm;
49
50     code = krb5_get_default_realm(ctx, &realm);
51     if (code != 0)
52         realm = NULL;
53     return realm;
54 }
55
56 #else /* !HAVE_KRB5_REALM */
57
58 static realm_type
59 default_realm(krb5_context ctx)
60 {
61     char *realm = NULL;
62     krb5_error_code code;
63     krb5_data *realm_data;
64
65     realm_data = calloc(1, sizeof(krb5_data));
66     if (realm_data == NULL)
67         return NULL;
68     code = krb5_get_default_realm(ctx, &realm);
69     if (code != 0) {
70         free(realm);
71         return NULL;
72     }
73     realm_data->magic = KV5M_DATA;
74     realm_data->data = strdup(realm);
75     if (realm_data->data == NULL) {
76         free(realm_data);
77         krb5_free_default_realm(ctx, realm);
78         return NULL;
79     }
80     realm_data->length = strlen(realm);
81     krb5_free_default_realm(ctx, realm);
82     return realm_data;
83 }
84
85 #endif /* !HAVE_KRB5_REALM */
86
87
88 /*
89  * Free the default realm data in whatever form it was generated for the calls
90  * to krb5_appdefault_*.
91  */
92 #ifdef HAVE_KRB5_REALM
93
94 static void
95 free_default_realm(krb5_context ctx UNUSED, realm_type realm)
96 {
97     krb5_free_default_realm(ctx, realm);
98 }
99
100 #else /* !HAVE_KRB5_REALM */
101
102 static void
103 free_default_realm(krb5_context ctx UNUSED, realm_type realm)
104 {
105     free(realm->data);
106     free(realm);
107 }
108
109 #endif /* !HAVE_KRB5_REALM */
110
111
112 /*
113  * Helper function to parse a number.  Takes the string to parse, the unsigned
114  * int in which to store the number, and the pointer to set to the first
115  * invalid character after the number.  Returns true if a number could be
116  * successfully parsed and false otherwise.
117  */
118 static bool
119 parse_number(const char *string, unsigned long *result, const char **end)
120 {
121     unsigned long value;
122
123     errno = 0;
124     value = strtoul(string, (char **) end, 10);
125     if (errno != 0 || *end == string)
126         return false;
127     *result = value;
128     return true;
129 }
130
131
132 /*
133  * Load a boolean option from Kerberos appdefaults.  Takes the Kerberos
134  * context, the option, and the result location.
135  */
136 void
137 strength_config_boolean(krb5_context ctx, const char *opt, bool *result)
138 {
139     realm_type realm;
140     int tmp;
141
142     /*
143      * The MIT version of krb5_appdefault_boolean takes an int * and the
144      * Heimdal version takes a krb5_boolean *, so hope that Heimdal always
145      * defines krb5_boolean to int or this will require more portability work.
146      */
147     realm = default_realm(ctx);
148     krb5_appdefault_boolean(ctx, "krb5-strength", realm, opt, *result, &tmp);
149     *result = tmp;
150     free_default_realm(ctx, realm);
151 }
152
153
154 /*
155  * Parse a single class specification.  Currently, this assumes that the class
156  * specification is a comma-separated list of required classes, and those
157  * classes are required for any length of password.  This will be enhanced
158  * later.
159  */
160 static krb5_error_code
161 parse_class(krb5_context ctx, const char *spec, struct class_rule **rule)
162 {
163     struct vector *classes = NULL;
164     size_t i;
165     krb5_error_code code;
166     const char *end;
167     bool okay;
168
169     /* Create the basic rule structure. */
170     *rule = calloc(1, sizeof(struct class_rule));
171     if (*rule == NULL)
172         return strength_error_system(ctx, "cannot allocate memory");
173
174     /*
175      * If the rule starts with a digit and contains a '-', it starts
176      * with a range of affected password lengths.  Parse that range.
177      */
178     if (isdigit((unsigned char) *spec) && strchr(spec, '-') != NULL) {
179         okay = parse_number(spec, &(*rule)->min, &end);
180         if (okay)
181             okay = (*end == '-');
182         if (okay)
183             okay = parse_number(end + 1, &(*rule)->max, &end);
184         if (okay)
185             okay = (*end == ':');
186         if (okay)
187             spec = end + 1;
188         else {
189             code = strength_error_config(ctx, "bad character class requirement"
190                                          " in configuration: %s", spec);
191             goto fail;
192         }
193     }
194
195     /* Parse the required classes into a vector. */
196     classes = strength_vector_split_multi(spec, ",", NULL);
197     if (classes == NULL) {
198         code = strength_error_system(ctx, "cannot allocate memory");
199         goto fail;
200     }
201
202     /*
203      * Walk the list of required classes and set our flags, diagnosing an
204      * unknown character class.
205      */
206     for (i = 0; i < classes->count; i++) {
207         if (strcmp(classes->strings[i], "upper") == 0)
208             (*rule)->upper = true;
209         else if (strcmp(classes->strings[i], "lower") == 0)
210             (*rule)->lower = true;
211         else if (strcmp(classes->strings[i], "digit") == 0)
212             (*rule)->digit = true;
213         else if (strcmp(classes->strings[i], "symbol") == 0)
214             (*rule)->symbol = true;
215         else if (isdigit((unsigned char) *classes->strings[i])) {
216             okay = parse_number(classes->strings[i],
217                                 &(*rule)->num_classes, &end);
218             if (!okay || *end != '\0' || (*rule)->num_classes > MAX_CLASSES) {
219                 code = strength_error_config(ctx, "bad character class"
220                                              " requirement in configuration:"
221                                              " %s",
222                                              classes->strings[i]);
223                 goto fail;
224             }
225         }
226         else {
227             code = strength_error_config(ctx, "unknown character class %s",
228                                          classes->strings[i]);
229             goto fail;
230         }
231     }
232     strength_vector_free(classes);
233     return 0;
234
235 fail:
236     strength_vector_free(classes);
237     free(*rule);
238     *rule = NULL;
239     return code;
240 }
241
242
243 /*
244  * Parse character class requirements from Kerberos appdefaults.  Takes the
245  * Kerberos context, the option, and the place to store the linked list of
246  * class requirements.
247  */
248 krb5_error_code
249 strength_config_classes(krb5_context ctx, const char *opt,
250                         struct class_rule **result)
251 {
252     struct vector *config = NULL;
253     struct class_rule *rules, *last, *tmp;
254     krb5_error_code code;
255     size_t i;
256
257     /* Get the basic configuration as a list. */
258     code = strength_config_list(ctx, opt, &config);
259     if (code != 0)
260         return code;
261     if (config == NULL || config->count == 0) {
262         *result = NULL;
263         return 0;
264     }
265
266     /* Each word in the list will be a class rule. */
267     code = parse_class(ctx, config->strings[0], &rules);
268     if (code != 0 || rules == NULL)
269         goto fail;
270     last = rules;
271     for (i = 1; i < config->count; i++) {
272         code = parse_class(ctx, config->strings[i], &last->next);
273         if (code != 0 || last->next == NULL)
274             goto fail;
275         last = last->next;
276     }
277
278     /* Success.  Free the vector and return the results. */
279     strength_vector_free(config);
280     *result = rules;
281     return 0;
282
283 fail:
284     last = rules;
285     while (last != NULL) {
286         tmp = last;
287         last = last->next;
288         free(tmp);
289     }
290     strength_vector_free(config);
291     return code;
292 }
293
294
295 /*
296  * Load a list option from Kerberos appdefaults.  Takes the Kerberos context,
297  * the option, and the result location.  The option is read as a string and
298  * the split on spaces and tabs into a list.
299  *
300  * This requires an annoying workaround because one cannot specify a default
301  * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls
302  * strdup on the default value.  There's also no way to determine if memory
303  * allocation failed while parsing or while setting the default value.
304  */
305 krb5_error_code
306 strength_config_list(krb5_context ctx, const char *opt,
307                      struct vector **result)
308 {
309     realm_type realm;
310     char *value = NULL;
311
312     /* Obtain the string from [appdefaults]. */
313     realm = default_realm(ctx);
314     krb5_appdefault_string(ctx, "krb5-strength", realm, opt, "", &value);
315     free_default_realm(ctx, realm);
316
317     /* If we got something back, store it in result. */
318     if (value != NULL) {
319         if (value[0] != '\0') {
320             *result = strength_vector_split_multi(value, " \t", *result);
321             if (*result == NULL)
322                 return strength_error_system(ctx, "cannot allocate memory");
323         }
324         krb5_free_string(ctx, value);
325     }
326     return 0;
327 }
328
329
330 /*
331  * Load a number option from Kerberos appdefaults.  Takes the Kerberos
332  * context, the option, and the result location.  The native interface doesn't
333  * support numbers, so we actually read a string and then convert.
334  */
335 void
336 strength_config_number(krb5_context ctx, const char *opt, long *result)
337 {
338     realm_type realm;
339     char *tmp = NULL;
340     char *end;
341     long value;
342
343     /* Obtain the setting in string form from [appdefaults]. */
344     realm = default_realm(ctx);
345     krb5_appdefault_string(ctx, "krb5-strength", realm, opt, "", &tmp);
346     free_default_realm(ctx, realm);
347
348     /*
349      * If we found anything, convert it to a number.  Currently, we ignore
350      * errors here.
351      */
352     if (tmp != NULL && tmp[0] != '\0') {
353         errno = 0;
354         value = strtol(tmp, &end, 10);
355         if (errno == 0 && *end == '\0')
356             *result = value;
357     }
358     if (tmp != NULL)
359         krb5_free_string(ctx, tmp);
360 }
361
362
363 /*
364  * Load a string option from Kerberos appdefaults.  Takes the Kerberos
365  * context, the option, and the result location.
366  *
367  * This requires an annoying workaround because one cannot specify a default
368  * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls
369  * strdup on the default value.  There's also no way to determine if memory
370  * allocation failed while parsing or while setting the default value, so we
371  * don't return an error code.
372  */
373 void
374 strength_config_string(krb5_context ctx, const char *opt, char **result)
375 {
376     realm_type realm;
377     char *value = NULL;
378
379     /* Obtain the string from [appdefaults]. */
380     realm = default_realm(ctx);
381     krb5_appdefault_string(ctx, "krb5-strength", realm, opt, "", &value);
382     free_default_realm(ctx, realm);
383
384     /* If we got something back, store it in result. */
385     if (value != NULL) {
386         if (value[0] != '\0') {
387             free(*result);
388             *result = strdup(value);
389         }
390         krb5_free_string(ctx, value);
391     }
392 }