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