2 * Retrieve configuration settings from krb5.conf.
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.
8 * Written by Russ Allbery <eagle@eyrie.org>
9 * Copyright 2016 Russ Allbery <eagle@eyrie.org>
11 * The Board of Trustees of the Leland Stanford Junior University
13 * SPDX-License-Identifier: MIT
17 #include <portable/krb5.h>
18 #include <portable/system.h>
23 #include <plugin/internal.h>
24 #include <util/macros.h>
26 /* The representation of the realm differs between MIT and Kerberos. */
27 #ifdef HAVE_KRB5_REALM
28 typedef krb5_realm realm_type;
30 typedef krb5_data *realm_type;
33 /* Maximum number of character classes. */
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_*.
43 #ifdef HAVE_KRB5_REALM
46 default_realm(krb5_context ctx)
51 code = krb5_get_default_realm(ctx, &realm);
57 #else /* !HAVE_KRB5_REALM */
60 default_realm(krb5_context ctx)
64 krb5_data *realm_data;
66 realm_data = calloc(1, sizeof(krb5_data));
67 if (realm_data == NULL)
69 code = krb5_get_default_realm(ctx, &realm);
74 realm_data->magic = KV5M_DATA;
75 realm_data->data = strdup(realm);
76 if (realm_data->data == NULL) {
78 krb5_free_default_realm(ctx, realm);
81 realm_data->length = (unsigned int) strlen(realm);
82 krb5_free_default_realm(ctx, realm);
86 #endif /* !HAVE_KRB5_REALM */
90 * Free the default realm data in whatever form it was generated for the calls
91 * to krb5_appdefault_*.
93 #ifdef HAVE_KRB5_REALM
96 free_default_realm(krb5_context ctx UNUSED, realm_type realm)
98 krb5_free_default_realm(ctx, realm);
101 #else /* !HAVE_KRB5_REALM */
104 free_default_realm(krb5_context ctx UNUSED, realm_type realm)
110 #endif /* !HAVE_KRB5_REALM */
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.
120 parse_number(const char *string, unsigned long *result, const char **end)
125 value = strtoul(string, (char **) end, 10);
126 if (errno != 0 || *end == string)
134 * Load a boolean option from Kerberos appdefaults. Takes the Kerberos
135 * context, the option, and the result location.
138 strength_config_boolean(krb5_context ctx, const char *opt, bool *result)
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.
148 realm = default_realm(ctx);
149 krb5_appdefault_boolean(ctx, "krb5-strength", realm, opt, *result, &tmp);
151 free_default_realm(ctx, realm);
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
161 static krb5_error_code
162 parse_class(krb5_context ctx, const char *spec, struct class_rule **rule)
164 struct vector *classes = NULL;
166 krb5_error_code code;
167 const char *class, *end;
170 /* Create the basic rule structure. */
171 *rule = calloc(1, sizeof(struct class_rule));
173 return strength_error_system(ctx, "cannot allocate memory");
176 * If the rule starts with a digit and contains a '-', it starts
177 * with a range of affected password lengths. Parse that range.
179 if (isdigit((unsigned char) *spec) && strchr(spec, '-') != NULL) {
180 okay = parse_number(spec, &(*rule)->min, &end);
182 okay = (*end == '-');
184 okay = parse_number(end + 1, &(*rule)->max, &end);
186 okay = (*end == ':');
190 code = strength_error_config(ctx,
191 "bad character class requirement in"
192 " configuration: %s",
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");
206 * Walk the list of required classes and set our flags, diagnosing an
207 * unknown character class.
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",
229 code = strength_error_config(ctx, "unknown character class %s",
234 strength_vector_free(classes);
238 strength_vector_free(classes);
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.
251 strength_config_classes(krb5_context ctx, const char *opt,
252 struct class_rule **result)
254 struct vector *config = NULL;
255 struct class_rule *rules, *last, *tmp;
256 krb5_error_code code;
259 /* Get the basic configuration as a list. */
260 code = strength_config_list(ctx, opt, &config);
263 if (config == NULL || config->count == 0) {
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)
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)
280 /* Success. Free the vector and return the results. */
281 strength_vector_free(config);
287 while (last != NULL) {
292 strength_vector_free(config);
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.
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.
308 strength_config_list(krb5_context ctx, const char *opt, struct vector **result)
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);
318 /* If we got something back, store it in result. */
320 if (value[0] != '\0') {
321 *result = strength_vector_split_multi(value, " \t", *result);
323 return strength_error_system(ctx, "cannot allocate memory");
325 krb5_free_string(ctx, value);
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.
337 strength_config_number(krb5_context ctx, const char *opt, long *result)
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);
350 * If we found anything, convert it to a number. Currently, we ignore
353 if (tmp != NULL && tmp[0] != '\0') {
355 value = strtol(tmp, &end, 10);
356 if (errno == 0 && *end == '\0')
360 krb5_free_string(ctx, tmp);
365 * Load a string option from Kerberos appdefaults. Takes the Kerberos
366 * context, the option, and the result location.
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.
375 strength_config_string(krb5_context ctx, const char *opt, char **result)
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);
385 /* If we got something back, store it in result. */
387 if (value[0] != '\0') {
389 *result = strdup(value);
391 krb5_free_string(ctx, value);