1 /* $NetBSD: config_reg.c,v 1.2 2017/01/28 21:31:49 christos Exp $ */
2
3 /***********************************************************************
4 * Copyright (c) 2010, Secure Endpoints Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * - Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * - Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 **********************************************************************/
33
34 #include "krb5_locl.h"
35
36 #ifndef _WIN32
37 #error config_reg.c is only for Windows
38 #endif
39
40 #include <shlwapi.h>
41
42 #ifndef MAX_DWORD
43 #define MAX_DWORD 0xFFFFFFFF
44 #endif
45
46 #define REGPATH_KERBEROS "SOFTWARE\\Kerberos"
47 #define REGPATH_HEIMDAL "SOFTWARE\\Heimdal"
48
49 /**
50 * Store a string as a registry value of the specified type
51 *
52 * The following registry types are handled:
53 *
54 * - REG_DWORD: The string is converted to a number.
55 *
56 * - REG_SZ: The string is stored as is.
57 *
58 * - REG_EXPAND_SZ: The string is stored as is.
59 *
60 * - REG_MULTI_SZ:
61 *
62 * . If a separator is specified, the input string is broken
63 * up into multiple strings and stored as a multi-sz.
64 *
65 * . If no separator is provided, the input string is stored
66 * as a multi-sz.
67 *
68 * - REG_NONE:
69 *
70 * . If the string is all numeric, it will be stored as a
71 * REG_DWORD.
72 *
73 * . Otherwise, the string is stored as a REG_SZ.
74 *
75 * Other types are rejected.
76 *
77 * If cb_data is MAX_DWORD, the string pointed to by data must be nul-terminated
78 * otherwise a buffer overrun will occur.
79 *
80 * @param [in]valuename Name of the registry value to be modified or created
81 * @param [in]type Type of the value. REG_NONE if unknown
82 * @param [in]data The input string to be stored in the registry.
83 * @param [in]cb_data Size of the input string in bytes. MAX_DWORD if unknown.
84 * @param [in]separator Separator character for parsing strings.
85 *
86 * @retval 0 if success or non-zero on error.
87 * If non-zero is returned, an error message has been set using
88 * krb5_set_error_message().
89 *
90 */
91 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
_krb5_store_string_to_reg_value(krb5_context context,HKEY key,const char * valuename,DWORD type,const char * data,DWORD cb_data,const char * separator)92 _krb5_store_string_to_reg_value(krb5_context context,
93 HKEY key, const char * valuename,
94 DWORD type, const char *data, DWORD cb_data,
95 const char * separator)
96 {
97 LONG rcode;
98 DWORD dwData;
99 BYTE static_buffer[16384];
100 BYTE *pbuffer = &static_buffer[0];
101
102 if (data == NULL)
103 {
104 if (context)
105 krb5_set_error_message(context, 0,
106 "'data' must not be NULL");
107 return -1;
108 }
109
110 if (cb_data == MAX_DWORD)
111 {
112 cb_data = (DWORD)strlen(data) + 1;
113 }
114 else if ((type == REG_MULTI_SZ && cb_data >= sizeof(static_buffer) - 1) ||
115 cb_data >= sizeof(static_buffer))
116 {
117 if (context)
118 krb5_set_error_message(context, 0, "cb_data too big");
119 return -1;
120 }
121 else if (data[cb_data-1] != '\0')
122 {
123 memcpy(static_buffer, data, cb_data);
124 static_buffer[cb_data++] = '\0';
125 if (type == REG_MULTI_SZ)
126 static_buffer[cb_data++] = '\0';
127 data = static_buffer;
128 }
129
130 if (type == REG_NONE)
131 {
132 /*
133 * If input is all numeric, convert to DWORD and save as REG_DWORD.
134 * Otherwise, store as REG_SZ.
135 */
136 if ( StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) )
137 {
138 type = REG_DWORD;
139 } else {
140 type = REG_SZ;
141 }
142 }
143
144 switch (type) {
145 case REG_SZ:
146 case REG_EXPAND_SZ:
147 rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data);
148 if (rcode)
149 {
150 if (context)
151 krb5_set_error_message(context, 0,
152 "Unexpected error when setting registry value %s gle 0x%x",
153 valuename,
154 GetLastError());
155 return -1;
156 }
157 break;
158 case REG_MULTI_SZ:
159 if (separator && *separator)
160 {
161 char *cp;
162
163 if (data != static_buffer)
164 static_buffer[cb_data++] = '\0';
165
166 for ( cp = static_buffer; cp < static_buffer+cb_data; cp++)
167 {
168 if (*cp == *separator)
169 *cp = '\0';
170 }
171
172 rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data);
173 if (rcode)
174 {
175 if (context)
176 krb5_set_error_message(context, 0,
177 "Unexpected error when setting registry value %s gle 0x%x",
178 valuename,
179 GetLastError());
180 return -1;
181 }
182 }
183 break;
184 case REG_DWORD:
185 if ( !StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) )
186 {
187 if (context)
188 krb5_set_error_message(context, 0,
189 "Unexpected error when parsing %s as number gle 0x%x",
190 data,
191 GetLastError());
192 }
193
194 rcode = RegSetValueEx(key, valuename, 0, type, (BYTE *)&dwData, sizeof(DWORD));
195 if (rcode)
196 {
197 if (context)
198 krb5_set_error_message(context, 0,
199 "Unexpected error when setting registry value %s gle 0x%x",
200 valuename,
201 GetLastError());
202 return -1;
203 }
204 break;
205 default:
206 return -1;
207 }
208
209 return 0;
210 }
211
212 /**
213 * Parse a registry value as a string
214 *
215 * @see _krb5_parse_reg_value_as_multi_string()
216 */
217 KRB5_LIB_FUNCTION char * KRB5_LIB_CALL
_krb5_parse_reg_value_as_string(krb5_context context,HKEY key,const char * valuename,DWORD type,DWORD cb_data)218 _krb5_parse_reg_value_as_string(krb5_context context,
219 HKEY key, const char * valuename,
220 DWORD type, DWORD cb_data)
221 {
222 return _krb5_parse_reg_value_as_multi_string(context, key, valuename,
223 type, cb_data, " ");
224 }
225
226 /**
227 * Parse a registry value as a multi string
228 *
229 * The following registry value types are handled:
230 *
231 * - REG_DWORD: The decimal string representation is used as the
232 * value.
233 *
234 * - REG_SZ: The string is used as-is.
235 *
236 * - REG_EXPAND_SZ: Environment variables in the string are expanded
237 * and the result is used as the value.
238 *
239 * - REG_MULTI_SZ: The list of strings is concatenated using the
240 * separator. No quoting is performed.
241 *
242 * Any other value type is rejected.
243 *
244 * @param [in]valuename Name of the registry value to be queried
245 * @param [in]type Type of the value. REG_NONE if unknown
246 * @param [in]cbdata Size of value. 0 if unknown.
247 * @param [in]separator Separator character for concatenating strings.
248 *
249 * @a type and @a cbdata are only considered valid if both are
250 * specified.
251 *
252 * @retval The registry value string, or NULL if there was an error.
253 * If NULL is returned, an error message has been set using
254 * krb5_set_error_message().
255 */
256 KRB5_LIB_FUNCTION char * KRB5_LIB_CALL
_krb5_parse_reg_value_as_multi_string(krb5_context context,HKEY key,const char * valuename,DWORD type,DWORD cb_data,char * separator)257 _krb5_parse_reg_value_as_multi_string(krb5_context context,
258 HKEY key, const char * valuename,
259 DWORD type, DWORD cb_data, char *separator)
260 {
261 LONG rcode = ERROR_MORE_DATA;
262
263 BYTE static_buffer[16384];
264 BYTE *pbuffer = &static_buffer[0];
265 DWORD cb_alloc = sizeof(static_buffer);
266 char *ret_string = NULL;
267
268 /* If we know a type and cb_data from a previous call to
269 * RegEnumValue(), we use it. Otherwise we use the
270 * static_buffer[] and query directly. We do this to minimize the
271 * number of queries. */
272
273 if (type == REG_NONE || cb_data == 0) {
274
275 pbuffer = &static_buffer[0];
276 cb_alloc = cb_data = sizeof(static_buffer);
277 rcode = RegQueryValueExA(key, valuename, NULL, &type, pbuffer, &cb_data);
278
279 if (rcode == ERROR_SUCCESS &&
280
281 ((type != REG_SZ &&
282 type != REG_EXPAND_SZ) || cb_data + 1 <= sizeof(static_buffer)) &&
283
284 (type != REG_MULTI_SZ || cb_data + 2 <= sizeof(static_buffer)))
285 goto have_data;
286
287 if (rcode != ERROR_MORE_DATA && rcode != ERROR_SUCCESS)
288 return NULL;
289 }
290
291 /* Either we don't have the data or we aren't sure of the size
292 * (due to potentially missing terminating NULs). */
293
294 switch (type) {
295 case REG_DWORD:
296 if (cb_data != sizeof(DWORD)) {
297 if (context)
298 krb5_set_error_message(context, 0,
299 "Unexpected size while reading registry value %s",
300 valuename);
301 return NULL;
302 }
303 break;
304
305 case REG_SZ:
306 case REG_EXPAND_SZ:
307
308 if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0')
309 goto have_data;
310
311 cb_data += sizeof(char); /* Accout for potential missing NUL
312 * terminator. */
313 break;
314
315 case REG_MULTI_SZ:
316
317 if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0' &&
318 (cb_data == 1 || pbuffer[cb_data - 2] == '\0'))
319 goto have_data;
320
321 cb_data += sizeof(char) * 2; /* Potential missing double NUL
322 * terminator. */
323 break;
324
325 default:
326 if (context)
327 krb5_set_error_message(context, 0,
328 "Unexpected type while reading registry value %s",
329 valuename);
330 return NULL;
331 }
332
333 if (cb_data <= sizeof(static_buffer))
334 pbuffer = &static_buffer[0];
335 else {
336 pbuffer = malloc(cb_data);
337 if (pbuffer == NULL)
338 return NULL;
339 }
340
341 cb_alloc = cb_data;
342 rcode = RegQueryValueExA(key, valuename, NULL, NULL, pbuffer, &cb_data);
343
344 if (rcode != ERROR_SUCCESS) {
345
346 /* This can potentially be from a race condition. I.e. some
347 * other process or thread went and modified the registry
348 * value between the time we queried its size and queried for
349 * its value. Ideally we would retry the query in a loop. */
350
351 if (context)
352 krb5_set_error_message(context, 0,
353 "Unexpected error while reading registry value %s",
354 valuename);
355 goto done;
356 }
357
358 if (cb_data > cb_alloc || cb_data == 0) {
359 if (context)
360 krb5_set_error_message(context, 0,
361 "Unexpected size while reading registry value %s",
362 valuename);
363 goto done;
364 }
365
366 have_data:
367 switch (type) {
368 case REG_DWORD:
369 asprintf(&ret_string, "%d", *((DWORD *) pbuffer));
370 break;
371
372 case REG_SZ:
373 {
374 char * str = (char *) pbuffer;
375
376 if (str[cb_data - 1] != '\0') {
377 if (cb_data < cb_alloc)
378 str[cb_data] = '\0';
379 else
380 break;
381 }
382
383 if (pbuffer != static_buffer) {
384 ret_string = (char *) pbuffer;
385 pbuffer = NULL;
386 } else {
387 ret_string = strdup((char *) pbuffer);
388 }
389 }
390 break;
391
392 case REG_EXPAND_SZ:
393 {
394 char *str = (char *) pbuffer;
395 char expsz[32768]; /* Size of output buffer for
396 * ExpandEnvironmentStrings() is
397 * limited to 32K. */
398
399 if (str[cb_data - 1] != '\0') {
400 if (cb_data < cb_alloc)
401 str[cb_data] = '\0';
402 else
403 break;
404 }
405
406 if (ExpandEnvironmentStrings(str, expsz, sizeof(expsz)/sizeof(char)) != 0) {
407 ret_string = strdup(expsz);
408 } else {
409 if (context)
410 krb5_set_error_message(context, 0,
411 "Overflow while expanding environment strings "
412 "for registry value %s", valuename);
413 }
414 }
415 break;
416
417 case REG_MULTI_SZ:
418 {
419 char * str = (char *) pbuffer;
420 char * iter;
421
422 str[cb_alloc - 1] = '\0';
423 str[cb_alloc - 2] = '\0';
424
425 for (iter = str; *iter;) {
426 size_t len = strlen(iter);
427
428 iter += len;
429 if (iter[1] != '\0')
430 *iter++ = *separator;
431 else
432 break;
433 }
434
435 if (pbuffer != static_buffer) {
436 ret_string = str;
437 pbuffer = NULL;
438 } else {
439 ret_string = strdup(str);
440 }
441 }
442 break;
443
444 default:
445 if (context)
446 krb5_set_error_message(context, 0,
447 "Unexpected type while reading registry value %s",
448 valuename);
449 }
450
451 done:
452 if (pbuffer != static_buffer && pbuffer != NULL)
453 free(pbuffer);
454
455 return ret_string;
456 }
457
458 /**
459 * Parse a registry value as a configuration value
460 *
461 * @see parse_reg_value_as_string()
462 */
463 static krb5_error_code
parse_reg_value(krb5_context context,HKEY key,const char * valuename,DWORD type,DWORD cbdata,krb5_config_section ** parent)464 parse_reg_value(krb5_context context,
465 HKEY key, const char * valuename,
466 DWORD type, DWORD cbdata, krb5_config_section ** parent)
467 {
468 char *reg_string = NULL;
469 krb5_config_section *value;
470 krb5_error_code code = 0;
471
472 reg_string = _krb5_parse_reg_value_as_string(context, key, valuename, type, cbdata);
473
474 if (reg_string == NULL)
475 return KRB5_CONFIG_BADFORMAT;
476
477 value = _krb5_config_get_entry(parent, valuename, krb5_config_string);
478 if (value == NULL) {
479 code = ENOMEM;
480 goto done;
481 }
482
483 if (value->u.string != NULL)
484 free(value->u.string);
485
486 value->u.string = reg_string;
487 reg_string = NULL;
488
489 done:
490 if (reg_string != NULL)
491 free(reg_string);
492
493 return code;
494 }
495
496 static krb5_error_code
parse_reg_values(krb5_context context,HKEY key,krb5_config_section ** parent)497 parse_reg_values(krb5_context context,
498 HKEY key,
499 krb5_config_section ** parent)
500 {
501 DWORD index;
502 LONG rcode;
503
504 for (index = 0; ; index ++) {
505 char name[16385];
506 DWORD cch = sizeof(name)/sizeof(name[0]);
507 DWORD type;
508 DWORD cbdata = 0;
509 krb5_error_code code;
510
511 rcode = RegEnumValue(key, index, name, &cch, NULL,
512 &type, NULL, &cbdata);
513 if (rcode != ERROR_SUCCESS)
514 break;
515
516 if (cbdata == 0)
517 continue;
518
519 code = parse_reg_value(context, key, name, type, cbdata, parent);
520 if (code != 0)
521 return code;
522 }
523
524 return 0;
525 }
526
527 static krb5_error_code
parse_reg_subkeys(krb5_context context,HKEY key,krb5_config_section ** parent)528 parse_reg_subkeys(krb5_context context,
529 HKEY key,
530 krb5_config_section ** parent)
531 {
532 DWORD index;
533 LONG rcode;
534
535 for (index = 0; ; index ++) {
536 HKEY subkey = NULL;
537 char name[256];
538 DWORD cch = sizeof(name)/sizeof(name[0]);
539 krb5_config_section *section = NULL;
540 krb5_error_code code;
541
542 rcode = RegEnumKeyEx(key, index, name, &cch, NULL, NULL, NULL, NULL);
543 if (rcode != ERROR_SUCCESS)
544 break;
545
546 rcode = RegOpenKeyEx(key, name, 0, KEY_READ, &subkey);
547 if (rcode != ERROR_SUCCESS)
548 continue;
549
550 section = _krb5_config_get_entry(parent, name, krb5_config_list);
551 if (section == NULL) {
552 RegCloseKey(subkey);
553 return ENOMEM;
554 }
555
556 code = parse_reg_values(context, subkey, §ion->u.list);
557 if (code) {
558 RegCloseKey(subkey);
559 return code;
560 }
561
562 code = parse_reg_subkeys(context, subkey, §ion->u.list);
563 if (code) {
564 RegCloseKey(subkey);
565 return code;
566 }
567
568 RegCloseKey(subkey);
569 }
570
571 return 0;
572 }
573
574 static krb5_error_code
parse_reg_root(krb5_context context,HKEY key,krb5_config_section ** parent)575 parse_reg_root(krb5_context context,
576 HKEY key,
577 krb5_config_section ** parent)
578 {
579 krb5_config_section *libdefaults = NULL;
580 krb5_error_code code = 0;
581
582 libdefaults = _krb5_config_get_entry(parent, "libdefaults", krb5_config_list);
583 if (libdefaults == NULL)
584 return krb5_enomem(context);
585
586 code = parse_reg_values(context, key, &libdefaults->u.list);
587 if (code)
588 return code;
589
590 return parse_reg_subkeys(context, key, parent);
591 }
592
593 static krb5_error_code
load_config_from_regpath(krb5_context context,HKEY hk_root,const char * key_path,krb5_config_section ** res)594 load_config_from_regpath(krb5_context context,
595 HKEY hk_root,
596 const char* key_path,
597 krb5_config_section ** res)
598 {
599 HKEY key = NULL;
600 LONG rcode;
601 krb5_error_code code = 0;
602
603 rcode = RegOpenKeyEx(hk_root, key_path, 0, KEY_READ, &key);
604 if (rcode == ERROR_SUCCESS) {
605 code = parse_reg_root(context, key, res);
606 RegCloseKey(key);
607 key = NULL;
608 }
609
610 return code;
611 }
612
613 /**
614 * Load configuration from registry
615 *
616 * The registry keys 'HKCU\Software\Heimdal' and
617 * 'HKLM\Software\Heimdal' are treated as krb5.conf files. Each
618 * registry key corresponds to a configuration section (or bound list)
619 * and each value in a registry key is treated as a bound value. The
620 * set of values that are directly under the Heimdal key are treated
621 * as if they were defined in the [libdefaults] section.
622 *
623 * @see parse_reg_value() for details about how each type of value is handled.
624 */
625 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_load_config_from_registry(krb5_context context,krb5_config_section ** res)626 _krb5_load_config_from_registry(krb5_context context,
627 krb5_config_section ** res)
628 {
629 krb5_error_code code;
630
631 code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE,
632 REGPATH_KERBEROS, res);
633 if (code)
634 return code;
635
636 code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE,
637 REGPATH_HEIMDAL, res);
638 if (code)
639 return code;
640
641 code = load_config_from_regpath(context, HKEY_CURRENT_USER,
642 REGPATH_KERBEROS, res);
643 if (code)
644 return code;
645
646 code = load_config_from_regpath(context, HKEY_CURRENT_USER,
647 REGPATH_HEIMDAL, res);
648 if (code)
649 return code;
650 return 0;
651 }
652