]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/config.c
Declare fast forward from 3.1-2
[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  * SPDX-License-Identifier: MIT
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_data);
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 = (unsigned int) 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,
191                                          "bad character class requirement in"
192                                          " configuration: %s",
193                                          spec);
194             goto fail;
195         }
196     }
197
198     /* Parse the required classes into a vector. */
199     classes = strength_vector_split_multi(spec, ",", NULL);
200     if (classes == NULL) {
201         code = strength_error_system(ctx, "cannot allocate memory");
202         goto fail;
203     }
204
205     /*
206      * Walk the list of required classes and set our flags, diagnosing an
207      * unknown character class.
208      */
209     for (i = 0; i < classes->count; i++) {
210         class = classes->strings[i];
211         if (strcmp(class, "upper") == 0)
212             (*rule)->upper = true;
213         else if (strcmp(class, "lower") == 0)
214             (*rule)->lower = true;
215         else if (strcmp(class, "digit") == 0)
216             (*rule)->digit = true;
217         else if (strcmp(class, "symbol") == 0)
218             (*rule)->symbol = true;
219         else if (isdigit((unsigned char) *class)) {
220             okay = parse_number(class, &(*rule)->num_classes, &end);
221             if (!okay || *end != '\0' || (*rule)->num_classes > MAX_CLASSES) {
222                 code = strength_error_config(ctx,
223                                              "bad character class minimum in"
224                                              " configuration: %s",
225                                              class);
226                 goto fail;
227             }
228         } else {
229             code = strength_error_config(ctx, "unknown character class %s",
230                                          class);
231             goto fail;
232         }
233     }
234     strength_vector_free(classes);
235     return 0;
236
237 fail:
238     strength_vector_free(classes);
239     free(*rule);
240     *rule = NULL;
241     return code;
242 }
243
244
245 /*
246  * Parse character class requirements from Kerberos appdefaults.  Takes the
247  * Kerberos context, the option, and the place to store the linked list of
248  * class requirements.
249  */
250 krb5_error_code
251 strength_config_classes(krb5_context ctx, const char *opt,
252                         struct class_rule **result)
253 {
254     struct vector *config = NULL;
255     struct class_rule *rules, *last, *tmp;
256     krb5_error_code code;
257     size_t i;
258
259     /* Get the basic configuration as a list. */
260     code = strength_config_list(ctx, opt, &config);
261     if (code != 0)
262         return code;
263     if (config == NULL || config->count == 0) {
264         *result = NULL;
265         return 0;
266     }
267
268     /* Each word in the list will be a class rule. */
269     code = parse_class(ctx, config->strings[0], &rules);
270     if (code != 0 || rules == NULL)
271         goto fail;
272     last = rules;
273     for (i = 1; i < config->count; i++) {
274         code = parse_class(ctx, config->strings[i], &last->next);
275         if (code != 0 || last->next == NULL)
276             goto fail;
277         last = last->next;
278     }
279
280     /* Success.  Free the vector and return the results. */
281     strength_vector_free(config);
282     *result = rules;
283     return 0;
284
285 fail:
286     last = rules;
287     while (last != NULL) {
288         tmp = last;
289         last = last->next;
290         free(tmp);
291     }
292     strength_vector_free(config);
293     return code;
294 }
295
296
297 /*
298  * Load a list option from Kerberos appdefaults.  Takes the Kerberos context,
299  * the option, and the result location.  The option is read as a string and
300  * the split on spaces and tabs into a list.
301  *
302  * This requires an annoying workaround because one cannot specify a default
303  * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls
304  * strdup on the default value.  There's also no way to determine if memory
305  * allocation failed while parsing or while setting the default value.
306  */
307 krb5_error_code
308 strength_config_list(krb5_context ctx, const char *opt, struct vector **result)
309 {
310     realm_type realm;
311     char *value = NULL;
312
313     /* Obtain the string from [appdefaults]. */
314     realm = default_realm(ctx);
315     krb5_appdefault_string(ctx, "krb5-strength", realm, opt, "", &value);
316     free_default_realm(ctx, realm);
317
318     /* If we got something back, store it in result. */
319     if (value != NULL) {
320         if (value[0] != '\0') {
321             *result = strength_vector_split_multi(value, " \t", *result);
322             if (*result == NULL)
323                 return strength_error_system(ctx, "cannot allocate memory");
324         }
325         krb5_free_string(ctx, value);
326     }
327     return 0;
328 }
329
330
331 /*
332  * Load a number option from Kerberos appdefaults.  Takes the Kerberos
333  * context, the option, and the result location.  The native interface doesn't
334  * support numbers, so we actually read a string and then convert.
335  */
336 void
337 strength_config_number(krb5_context ctx, const char *opt, long *result)
338 {
339     realm_type realm;
340     char *tmp = NULL;
341     char *end;
342     long value;
343
344     /* Obtain the setting in string form from [appdefaults]. */
345     realm = default_realm(ctx);
346     krb5_appdefault_string(ctx, "krb5-strength", realm, opt, "", &tmp);
347     free_default_realm(ctx, realm);
348
349     /*
350      * If we found anything, convert it to a number.  Currently, we ignore
351      * errors here.
352      */
353     if (tmp != NULL && tmp[0] != '\0') {
354         errno = 0;
355         value = strtol(tmp, &end, 10);
356         if (errno == 0 && *end == '\0')
357             *result = value;
358     }
359     if (tmp != NULL)
360         krb5_free_string(ctx, tmp);
361 }
362
363
364 /*
365  * Load a string option from Kerberos appdefaults.  Takes the Kerberos
366  * context, the option, and the result location.
367  *
368  * This requires an annoying workaround because one cannot specify a default
369  * value of NULL with MIT Kerberos, since MIT Kerberos unconditionally calls
370  * strdup on the default value.  There's also no way to determine if memory
371  * allocation failed while parsing or while setting the default value, so we
372  * don't return an error code.
373  */
374 void
375 strength_config_string(krb5_context ctx, const char *opt, char **result)
376 {
377     realm_type realm;
378     char *value = NULL;
379
380     /* Obtain the string from [appdefaults]. */
381     realm = default_realm(ctx);
382     krb5_appdefault_string(ctx, "krb5-strength", realm, opt, "", &value);
383     free_default_realm(ctx, realm);
384
385     /* If we got something back, store it in result. */
386     if (value != NULL) {
387         if (value[0] != '\0') {
388             free(*result);
389             *result = strdup(value);
390         }
391         krb5_free_string(ctx, value);
392     }
393 }