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