resurrect password change support again
[heimdal.git] / lib / krb5 / config_reg.c
blob6ee6a6496f4b1f1e359a0ccb6d69dde91e54a09b
1 /***********************************************************************
2 * Copyright (c) 2010, Secure Endpoints Inc.
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28 * OF THE POSSIBILITY OF SUCH DAMAGE.
30 **********************************************************************/
32 #include "krb5_locl.h"
34 #ifndef _WIN32
35 #error config_reg.c is only for Windows
36 #endif
38 #include <shlwapi.h>
40 #ifndef MAX_DWORD
41 #define MAX_DWORD 0xFFFFFFFF
42 #endif
44 #define REGPATH_KERBEROS "SOFTWARE\\Kerberos"
45 #define REGPATH_HEIMDAL "SOFTWARE\\Heimdal"
47 /**
48 * Store a string as a registry value of the specified type
50 * The following registry types are handled:
52 * - REG_DWORD: The string is converted to a number.
54 * - REG_SZ: The string is stored as is.
56 * - REG_EXPAND_SZ: The string is stored as is.
58 * - REG_MULTI_SZ:
60 * . If a separator is specified, the input string is broken
61 * up into multiple strings and stored as a multi-sz.
63 * . If no separator is provided, the input string is stored
64 * as a multi-sz.
66 * - REG_NONE:
68 * . If the string is all numeric, it will be stored as a
69 * REG_DWORD.
71 * . Otherwise, the string is stored as a REG_SZ.
73 * Other types are rejected.
75 * If cb_data is MAX_DWORD, the string pointed to by data must be nul-terminated
76 * otherwise a buffer overrun will occur.
78 * @param [in]valuename Name of the registry value to be modified or created
79 * @param [in]type Type of the value. REG_NONE if unknown
80 * @param [in]data The input string to be stored in the registry.
81 * @param [in]cb_data Size of the input string in bytes. MAX_DWORD if unknown.
82 * @param [in]separator Separator character for parsing strings.
84 * @retval 0 if success or non-zero on error.
85 * If non-zero is returned, an error message has been set using
86 * krb5_set_error_message().
89 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
90 _krb5_store_string_to_reg_value(krb5_context context,
91 HKEY key, const char * valuename,
92 DWORD type, const char *data, DWORD cb_data,
93 const char * separator)
95 LONG rcode;
96 DWORD dwData;
97 BYTE static_buffer[16384];
98 BYTE *pbuffer = &static_buffer[0];
100 if (data == NULL)
102 if (context)
103 krb5_set_error_message(context, 0,
104 "'data' must not be NULL");
105 return -1;
108 if (cb_data == MAX_DWORD)
110 cb_data = (DWORD)strlen(data) + 1;
112 else if ((type == REG_MULTI_SZ && cb_data >= sizeof(static_buffer) - 1) ||
113 cb_data >= sizeof(static_buffer))
115 if (context)
116 krb5_set_error_message(context, 0, "cb_data too big");
117 return -1;
119 else if (data[cb_data-1] != '\0')
121 memcpy(static_buffer, data, cb_data);
122 static_buffer[cb_data++] = '\0';
123 if (type == REG_MULTI_SZ)
124 static_buffer[cb_data++] = '\0';
125 data = static_buffer;
128 if (type == REG_NONE)
131 * If input is all numeric, convert to DWORD and save as REG_DWORD.
132 * Otherwise, store as REG_SZ.
134 if ( StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) )
136 type = REG_DWORD;
137 } else {
138 type = REG_SZ;
142 switch (type) {
143 case REG_SZ:
144 case REG_EXPAND_SZ:
145 rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data);
146 if (rcode)
148 if (context)
149 krb5_set_error_message(context, 0,
150 "Unexpected error when setting registry value %s gle 0x%x",
151 valuename,
152 GetLastError());
153 return -1;
155 break;
156 case REG_MULTI_SZ:
157 if (separator && *separator)
159 char *cp;
161 if (data != static_buffer)
162 static_buffer[cb_data++] = '\0';
164 for ( cp = static_buffer; cp < static_buffer+cb_data; cp++)
166 if (*cp == *separator)
167 *cp = '\0';
170 rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data);
171 if (rcode)
173 if (context)
174 krb5_set_error_message(context, 0,
175 "Unexpected error when setting registry value %s gle 0x%x",
176 valuename,
177 GetLastError());
178 return -1;
181 break;
182 case REG_DWORD:
183 if ( !StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) )
185 if (context)
186 krb5_set_error_message(context, 0,
187 "Unexpected error when parsing %s as number gle 0x%x",
188 data,
189 GetLastError());
192 rcode = RegSetValueEx(key, valuename, 0, type, (BYTE *)&dwData, sizeof(DWORD));
193 if (rcode)
195 if (context)
196 krb5_set_error_message(context, 0,
197 "Unexpected error when setting registry value %s gle 0x%x",
198 valuename,
199 GetLastError());
200 return -1;
202 break;
203 default:
204 return -1;
207 return 0;
211 * Parse a registry value as a string
213 * @see _krb5_parse_reg_value_as_multi_string()
215 KRB5_LIB_FUNCTION char * KRB5_LIB_CALL
216 _krb5_parse_reg_value_as_string(krb5_context context,
217 HKEY key, const char * valuename,
218 DWORD type, DWORD cb_data)
220 return _krb5_parse_reg_value_as_multi_string(context, key, valuename,
221 type, cb_data, " ");
225 * Parse a registry value as a multi string
227 * The following registry value types are handled:
229 * - REG_DWORD: The decimal string representation is used as the
230 * value.
232 * - REG_SZ: The string is used as-is.
234 * - REG_EXPAND_SZ: Environment variables in the string are expanded
235 * and the result is used as the value.
237 * - REG_MULTI_SZ: The list of strings is concatenated using the
238 * separator. No quoting is performed.
240 * Any other value type is rejected.
242 * @param [in]valuename Name of the registry value to be queried
243 * @param [in]type Type of the value. REG_NONE if unknown
244 * @param [in]cbdata Size of value. 0 if unknown.
245 * @param [in]separator Separator character for concatenating strings.
247 * @a type and @a cbdata are only considered valid if both are
248 * specified.
250 * @retval The registry value string, or NULL if there was an error.
251 * If NULL is returned, an error message has been set using
252 * krb5_set_error_message().
254 KRB5_LIB_FUNCTION char * KRB5_LIB_CALL
255 _krb5_parse_reg_value_as_multi_string(krb5_context context,
256 HKEY key, const char * valuename,
257 DWORD type, DWORD cb_data, char *separator)
259 LONG rcode = ERROR_MORE_DATA;
261 BYTE static_buffer[16384];
262 BYTE *pbuffer = &static_buffer[0];
263 DWORD cb_alloc = sizeof(static_buffer);
264 char *ret_string = NULL;
266 /* If we know a type and cb_data from a previous call to
267 * RegEnumValue(), we use it. Otherwise we use the
268 * static_buffer[] and query directly. We do this to minimize the
269 * number of queries. */
271 if (type == REG_NONE || cb_data == 0) {
273 pbuffer = &static_buffer[0];
274 cb_alloc = cb_data = sizeof(static_buffer);
275 rcode = RegQueryValueExA(key, valuename, NULL, &type, pbuffer, &cb_data);
277 if (rcode == ERROR_SUCCESS &&
279 ((type != REG_SZ &&
280 type != REG_EXPAND_SZ) || cb_data + 1 <= sizeof(static_buffer)) &&
282 (type != REG_MULTI_SZ || cb_data + 2 <= sizeof(static_buffer)))
283 goto have_data;
285 if (rcode != ERROR_MORE_DATA && rcode != ERROR_SUCCESS)
286 return NULL;
289 /* Either we don't have the data or we aren't sure of the size
290 * (due to potentially missing terminating NULs). */
292 switch (type) {
293 case REG_DWORD:
294 if (cb_data != sizeof(DWORD)) {
295 if (context)
296 krb5_set_error_message(context, 0,
297 "Unexpected size while reading registry value %s",
298 valuename);
299 return NULL;
301 break;
303 case REG_SZ:
304 case REG_EXPAND_SZ:
306 if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0')
307 goto have_data;
309 cb_data += sizeof(char); /* Accout for potential missing NUL
310 * terminator. */
311 break;
313 case REG_MULTI_SZ:
315 if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0' &&
316 (cb_data == 1 || pbuffer[cb_data - 2] == '\0'))
317 goto have_data;
319 cb_data += sizeof(char) * 2; /* Potential missing double NUL
320 * terminator. */
321 break;
323 default:
324 if (context)
325 krb5_set_error_message(context, 0,
326 "Unexpected type while reading registry value %s",
327 valuename);
328 return NULL;
331 if (cb_data <= sizeof(static_buffer))
332 pbuffer = &static_buffer[0];
333 else {
334 pbuffer = malloc(cb_data);
335 if (pbuffer == NULL)
336 return NULL;
339 cb_alloc = cb_data;
340 rcode = RegQueryValueExA(key, valuename, NULL, NULL, pbuffer, &cb_data);
342 if (rcode != ERROR_SUCCESS) {
344 /* This can potentially be from a race condition. I.e. some
345 * other process or thread went and modified the registry
346 * value between the time we queried its size and queried for
347 * its value. Ideally we would retry the query in a loop. */
349 if (context)
350 krb5_set_error_message(context, 0,
351 "Unexpected error while reading registry value %s",
352 valuename);
353 goto done;
356 if (cb_data > cb_alloc || cb_data == 0) {
357 if (context)
358 krb5_set_error_message(context, 0,
359 "Unexpected size while reading registry value %s",
360 valuename);
361 goto done;
364 have_data:
365 switch (type) {
366 case REG_DWORD:
367 asprintf(&ret_string, "%d", *((DWORD *) pbuffer));
368 break;
370 case REG_SZ:
372 char * str = (char *) pbuffer;
374 if (str[cb_data - 1] != '\0') {
375 if (cb_data < cb_alloc)
376 str[cb_data] = '\0';
377 else
378 break;
381 if (pbuffer != static_buffer) {
382 ret_string = (char *) pbuffer;
383 pbuffer = NULL;
384 } else {
385 ret_string = strdup((char *) pbuffer);
388 break;
390 case REG_EXPAND_SZ:
392 char *str = (char *) pbuffer;
393 char expsz[32768]; /* Size of output buffer for
394 * ExpandEnvironmentStrings() is
395 * limited to 32K. */
397 if (str[cb_data - 1] != '\0') {
398 if (cb_data < cb_alloc)
399 str[cb_data] = '\0';
400 else
401 break;
404 if (ExpandEnvironmentStrings(str, expsz, sizeof(expsz)/sizeof(char)) != 0) {
405 ret_string = strdup(expsz);
406 } else {
407 if (context)
408 krb5_set_error_message(context, 0,
409 "Overflow while expanding environment strings "
410 "for registry value %s", valuename);
413 break;
415 case REG_MULTI_SZ:
417 char * str = (char *) pbuffer;
418 char * iter;
420 str[cb_alloc - 1] = '\0';
421 str[cb_alloc - 2] = '\0';
423 for (iter = str; *iter;) {
424 size_t len = strlen(iter);
426 iter += len;
427 if (iter[1] != '\0')
428 *iter++ = *separator;
429 else
430 break;
433 if (pbuffer != static_buffer) {
434 ret_string = str;
435 pbuffer = NULL;
436 } else {
437 ret_string = strdup(str);
440 break;
442 default:
443 if (context)
444 krb5_set_error_message(context, 0,
445 "Unexpected type while reading registry value %s",
446 valuename);
449 done:
450 if (pbuffer != static_buffer && pbuffer != NULL)
451 free(pbuffer);
453 return ret_string;
457 * Parse a registry value as a configuration value
459 * @see parse_reg_value_as_string()
461 static krb5_error_code
462 parse_reg_value(krb5_context context,
463 HKEY key, const char * valuename,
464 DWORD type, DWORD cbdata, krb5_config_section ** parent)
466 char *reg_string = NULL;
467 krb5_config_section *value;
468 krb5_error_code code = 0;
470 reg_string = _krb5_parse_reg_value_as_string(context, key, valuename, type, cbdata);
472 if (reg_string == NULL)
473 return KRB5_CONFIG_BADFORMAT;
475 value = _krb5_config_get_entry(parent, valuename, krb5_config_string);
476 if (value == NULL) {
477 code = ENOMEM;
478 goto done;
481 if (value->u.string != NULL)
482 free(value->u.string);
484 value->u.string = reg_string;
485 reg_string = NULL;
487 done:
488 if (reg_string != NULL)
489 free(reg_string);
491 return code;
494 static krb5_error_code
495 parse_reg_values(krb5_context context,
496 HKEY key,
497 krb5_config_section ** parent)
499 DWORD index;
500 LONG rcode;
502 for (index = 0; ; index ++) {
503 char name[16385];
504 DWORD cch = sizeof(name)/sizeof(name[0]);
505 DWORD type;
506 DWORD cbdata = 0;
507 krb5_error_code code;
509 rcode = RegEnumValue(key, index, name, &cch, NULL,
510 &type, NULL, &cbdata);
511 if (rcode != ERROR_SUCCESS)
512 break;
514 if (cbdata == 0)
515 continue;
517 code = parse_reg_value(context, key, name, type, cbdata, parent);
518 if (code != 0)
519 return code;
522 return 0;
525 static krb5_error_code
526 parse_reg_subkeys(krb5_context context,
527 HKEY key,
528 krb5_config_section ** parent)
530 DWORD index;
531 LONG rcode;
533 for (index = 0; ; index ++) {
534 HKEY subkey = NULL;
535 char name[256];
536 DWORD cch = sizeof(name)/sizeof(name[0]);
537 krb5_config_section *section = NULL;
538 krb5_error_code code;
540 rcode = RegEnumKeyEx(key, index, name, &cch, NULL, NULL, NULL, NULL);
541 if (rcode != ERROR_SUCCESS)
542 break;
544 rcode = RegOpenKeyEx(key, name, 0, KEY_READ, &subkey);
545 if (rcode != ERROR_SUCCESS)
546 continue;
548 section = _krb5_config_get_entry(parent, name, krb5_config_list);
549 if (section == NULL) {
550 RegCloseKey(subkey);
551 return ENOMEM;
554 code = parse_reg_values(context, subkey, &section->u.list);
555 if (code) {
556 RegCloseKey(subkey);
557 return code;
560 code = parse_reg_subkeys(context, subkey, &section->u.list);
561 if (code) {
562 RegCloseKey(subkey);
563 return code;
566 RegCloseKey(subkey);
569 return 0;
572 static krb5_error_code
573 parse_reg_root(krb5_context context,
574 HKEY key,
575 krb5_config_section ** parent)
577 krb5_config_section *libdefaults = NULL;
578 krb5_error_code code = 0;
580 libdefaults = _krb5_config_get_entry(parent, "libdefaults", krb5_config_list);
581 if (libdefaults == NULL)
582 return krb5_enomem(context);
584 code = parse_reg_values(context, key, &libdefaults->u.list);
585 if (code)
586 return code;
588 return parse_reg_subkeys(context, key, parent);
591 static krb5_error_code
592 load_config_from_regpath(krb5_context context,
593 HKEY hk_root,
594 const char* key_path,
595 krb5_config_section ** res)
597 HKEY key = NULL;
598 LONG rcode;
599 krb5_error_code code = 0;
601 rcode = RegOpenKeyEx(hk_root, key_path, 0, KEY_READ, &key);
602 if (rcode == ERROR_SUCCESS) {
603 code = parse_reg_root(context, key, res);
604 RegCloseKey(key);
605 key = NULL;
608 return code;
612 * Load configuration from registry
614 * The registry keys 'HKCU\Software\Heimdal' and
615 * 'HKLM\Software\Heimdal' are treated as krb5.conf files. Each
616 * registry key corresponds to a configuration section (or bound list)
617 * and each value in a registry key is treated as a bound value. The
618 * set of values that are directly under the Heimdal key are treated
619 * as if they were defined in the [libdefaults] section.
621 * @see parse_reg_value() for details about how each type of value is handled.
623 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
624 _krb5_load_config_from_registry(krb5_context context,
625 krb5_config_section ** res)
627 krb5_error_code code;
629 code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE,
630 REGPATH_KERBEROS, res);
631 if (code)
632 return code;
634 code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE,
635 REGPATH_HEIMDAL, res);
636 if (code)
637 return code;
639 code = load_config_from_regpath(context, HKEY_CURRENT_USER,
640 REGPATH_KERBEROS, res);
641 if (code)
642 return code;
644 code = load_config_from_regpath(context, HKEY_CURRENT_USER,
645 REGPATH_HEIMDAL, res);
646 if (code)
647 return code;
648 return 0;