1 /*	$NetBSD: config_reg.c,v 1.1.1.2 2014/04/24 12:45:49 pettai 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 int
_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             int i;
162             char *cp;
163 
164             if (data != static_buffer)
165                 static_buffer[cb_data++] = '\0';
166 
167             for ( cp = static_buffer; cp < static_buffer+cb_data; cp++)
168             {
169                 if (*cp == *separator)
170                     *cp = '\0';
171             }
172 
173             rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data);
174             if (rcode)
175             {
176                 if (context)
177                     krb5_set_error_message(context, 0,
178                                            "Unexpected error when setting registry value %s gle 0x%x",
179                                            valuename,
180                                            GetLastError());
181                 return -1;
182             }
183         }
184         break;
185     case REG_DWORD:
186         if ( !StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) )
187         {
188             if (context)
189                 krb5_set_error_message(context, 0,
190                                        "Unexpected error when parsing %s as number gle 0x%x",
191                                        data,
192                                        GetLastError());
193         }
194 
195         rcode = RegSetValueEx(key, valuename, 0, type, dwData, sizeof(DWORD));
196         if (rcode)
197         {
198             if (context)
199                 krb5_set_error_message(context, 0,
200                                        "Unexpected error when setting registry value %s gle 0x%x",
201                                        valuename,
202                                        GetLastError());
203             return -1;
204         }
205         break;
206     default:
207         return -1;
208     }
209 
210     return 0;
211 }
212 
213 /**
214  * Parse a registry value as a string
215  *
216  * @see _krb5_parse_reg_value_as_multi_string()
217  */
218 char *
_krb5_parse_reg_value_as_string(krb5_context context,HKEY key,const char * valuename,DWORD type,DWORD cb_data)219 _krb5_parse_reg_value_as_string(krb5_context context,
220                                 HKEY key, const char * valuename,
221                                 DWORD type, DWORD cb_data)
222 {
223     return _krb5_parse_reg_value_as_multi_string(context, key, valuename,
224                                                  type, cb_data, " ");
225 }
226 
227 /**
228  * Parse a registry value as a multi string
229  *
230  * The following registry value types are handled:
231  *
232  * - REG_DWORD: The decimal string representation is used as the
233  *   value.
234  *
235  * - REG_SZ: The string is used as-is.
236  *
237  * - REG_EXPAND_SZ: Environment variables in the string are expanded
238  *   and the result is used as the value.
239  *
240  * - REG_MULTI_SZ: The list of strings is concatenated using the
241  *   separator.  No quoting is performed.
242  *
243  * Any other value type is rejected.
244  *
245  * @param [in]valuename Name of the registry value to be queried
246  * @param [in]type      Type of the value. REG_NONE if unknown
247  * @param [in]cbdata    Size of value. 0 if unknown.
248  * @param [in]separator Separator character for concatenating strings.
249  *
250  * @a type and @a cbdata are only considered valid if both are
251  * specified.
252  *
253  * @retval The registry value string, or NULL if there was an error.
254  * If NULL is returned, an error message has been set using
255  * krb5_set_error_message().
256  */
257 char *
_krb5_parse_reg_value_as_multi_string(krb5_context context,HKEY key,const char * valuename,DWORD type,DWORD cb_data,char * separator)258 _krb5_parse_reg_value_as_multi_string(krb5_context context,
259                                       HKEY key, const char * valuename,
260                                       DWORD type, DWORD cb_data, char *separator)
261 {
262     LONG                rcode = ERROR_MORE_DATA;
263 
264     BYTE                static_buffer[16384];
265     BYTE                *pbuffer = &static_buffer[0];
266     DWORD               cb_alloc = sizeof(static_buffer);
267     char                *ret_string = NULL;
268 
269     /* If we know a type and cb_data from a previous call to
270      * RegEnumValue(), we use it.  Otherwise we use the
271      * static_buffer[] and query directly.  We do this to minimize the
272      * number of queries. */
273 
274     if (type == REG_NONE || cb_data == 0) {
275 
276         pbuffer = &static_buffer[0];
277         cb_alloc = cb_data = sizeof(static_buffer);
278         rcode = RegQueryValueExA(key, valuename, NULL, &type, pbuffer, &cb_data);
279 
280         if (rcode == ERROR_SUCCESS &&
281 
282             ((type != REG_SZ &&
283               type != REG_EXPAND_SZ) || cb_data + 1 <= sizeof(static_buffer)) &&
284 
285             (type != REG_MULTI_SZ || cb_data + 2 <= sizeof(static_buffer)))
286             goto have_data;
287 
288         if (rcode != ERROR_MORE_DATA && rcode != ERROR_SUCCESS)
289             return NULL;
290     }
291 
292     /* Either we don't have the data or we aren't sure of the size
293      * (due to potentially missing terminating NULs). */
294 
295     switch (type) {
296     case REG_DWORD:
297         if (cb_data != sizeof(DWORD)) {
298             if (context)
299                 krb5_set_error_message(context, 0,
300                                        "Unexpected size while reading registry value %s",
301                                        valuename);
302             return NULL;
303         }
304         break;
305 
306     case REG_SZ:
307     case REG_EXPAND_SZ:
308 
309         if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0')
310             goto have_data;
311 
312         cb_data += sizeof(char); /* Accout for potential missing NUL
313                                   * terminator. */
314         break;
315 
316     case REG_MULTI_SZ:
317 
318         if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0' &&
319             (cb_data == 1 || pbuffer[cb_data - 2] == '\0'))
320             goto have_data;
321 
322         cb_data += sizeof(char) * 2; /* Potential missing double NUL
323                                       * terminator. */
324         break;
325 
326     default:
327         if (context)
328             krb5_set_error_message(context, 0,
329                                    "Unexpected type while reading registry value %s",
330                                    valuename);
331         return NULL;
332     }
333 
334     if (cb_data <= sizeof(static_buffer))
335         pbuffer = &static_buffer[0];
336     else {
337         pbuffer = malloc(cb_data);
338         if (pbuffer == NULL)
339             return NULL;
340     }
341 
342     cb_alloc = cb_data;
343     rcode = RegQueryValueExA(key, valuename, NULL, NULL, pbuffer, &cb_data);
344 
345     if (rcode != ERROR_SUCCESS) {
346 
347         /* This can potentially be from a race condition. I.e. some
348          * other process or thread went and modified the registry
349          * value between the time we queried its size and queried for
350          * its value.  Ideally we would retry the query in a loop. */
351 
352         if (context)
353             krb5_set_error_message(context, 0,
354                                    "Unexpected error while reading registry value %s",
355                                    valuename);
356         goto done;
357     }
358 
359     if (cb_data > cb_alloc || cb_data == 0) {
360         if (context)
361             krb5_set_error_message(context, 0,
362                                    "Unexpected size while reading registry value %s",
363                                    valuename);
364         goto done;
365     }
366 
367 have_data:
368     switch (type) {
369     case REG_DWORD:
370         asprintf(&ret_string, "%d", *((DWORD *) pbuffer));
371         break;
372 
373     case REG_SZ:
374     {
375         char * str = (char *) pbuffer;
376 
377         if (str[cb_data - 1] != '\0') {
378             if (cb_data < cb_alloc)
379                 str[cb_data] = '\0';
380             else
381                 break;
382         }
383 
384         if (pbuffer != static_buffer) {
385             ret_string = (char *) pbuffer;
386             pbuffer = NULL;
387         } else {
388             ret_string = strdup((char *) pbuffer);
389         }
390     }
391     break;
392 
393     case REG_EXPAND_SZ:
394     {
395         char    *str = (char *) pbuffer;
396         char    expsz[32768];   /* Size of output buffer for
397                                  * ExpandEnvironmentStrings() is
398                                  * limited to 32K. */
399 
400         if (str[cb_data - 1] != '\0') {
401             if (cb_data < cb_alloc)
402                 str[cb_data] = '\0';
403             else
404                 break;
405         }
406 
407         if (ExpandEnvironmentStrings(str, expsz, sizeof(expsz)/sizeof(char)) != 0) {
408             ret_string = strdup(expsz);
409         } else {
410             if (context)
411                 krb5_set_error_message(context, 0,
412                                        "Overflow while expanding environment strings "
413                                        "for registry value %s", valuename);
414         }
415     }
416     break;
417 
418     case REG_MULTI_SZ:
419     {
420         char * str = (char *) pbuffer;
421         char * iter;
422 
423         str[cb_alloc - 1] = '\0';
424         str[cb_alloc - 2] = '\0';
425 
426         for (iter = str; *iter;) {
427             size_t len = strlen(iter);
428 
429             iter += len;
430             if (iter[1] != '\0')
431                 *iter++ = *separator;
432             else
433                 break;
434         }
435 
436         if (pbuffer != static_buffer) {
437             ret_string = str;
438             pbuffer = NULL;
439         } else {
440             ret_string = strdup(str);
441         }
442     }
443     break;
444 
445     default:
446         if (context)
447             krb5_set_error_message(context, 0,
448                                    "Unexpected type while reading registry value %s",
449                                    valuename);
450     }
451 
452 done:
453     if (pbuffer != static_buffer && pbuffer != NULL)
454         free(pbuffer);
455 
456     return ret_string;
457 }
458 
459 /**
460  * Parse a registry value as a configuration value
461  *
462  * @see parse_reg_value_as_string()
463  */
464 static krb5_error_code
parse_reg_value(krb5_context context,HKEY key,const char * valuename,DWORD type,DWORD cbdata,krb5_config_section ** parent)465 parse_reg_value(krb5_context context,
466                 HKEY key, const char * valuename,
467                 DWORD type, DWORD cbdata, krb5_config_section ** parent)
468 {
469     char                *reg_string = NULL;
470     krb5_config_section *value;
471     krb5_error_code     code = 0;
472 
473     reg_string = _krb5_parse_reg_value_as_string(context, key, valuename, type, cbdata);
474 
475     if (reg_string == NULL)
476         return KRB5_CONFIG_BADFORMAT;
477 
478     value = _krb5_config_get_entry(parent, valuename, krb5_config_string);
479     if (value == NULL) {
480         code = ENOMEM;
481         goto done;
482     }
483 
484     if (value->u.string != NULL)
485         free(value->u.string);
486 
487     value->u.string = reg_string;
488     reg_string = NULL;
489 
490 done:
491     if (reg_string != NULL)
492         free(reg_string);
493 
494     return code;
495 }
496 
497 static krb5_error_code
parse_reg_values(krb5_context context,HKEY key,krb5_config_section ** parent)498 parse_reg_values(krb5_context context,
499                  HKEY key,
500                  krb5_config_section ** parent)
501 {
502     DWORD index;
503     LONG  rcode;
504 
505     for (index = 0; ; index ++) {
506         char    name[16385];
507         DWORD   cch = sizeof(name)/sizeof(name[0]);
508         DWORD   type;
509         DWORD   cbdata = 0;
510         krb5_error_code code;
511 
512         rcode = RegEnumValue(key, index, name, &cch, NULL,
513                              &type, NULL, &cbdata);
514         if (rcode != ERROR_SUCCESS)
515             break;
516 
517         if (cbdata == 0)
518             continue;
519 
520         code = parse_reg_value(context, key, name, type, cbdata, parent);
521         if (code != 0)
522             return code;
523     }
524 
525     return 0;
526 }
527 
528 static krb5_error_code
parse_reg_subkeys(krb5_context context,HKEY key,krb5_config_section ** parent)529 parse_reg_subkeys(krb5_context context,
530                   HKEY key,
531                   krb5_config_section ** parent)
532 {
533     DWORD index;
534     LONG  rcode;
535 
536     for (index = 0; ; index ++) {
537         HKEY    subkey = NULL;
538         char    name[256];
539         DWORD   cch = sizeof(name)/sizeof(name[0]);
540         krb5_config_section     *section = NULL;
541         krb5_error_code         code;
542 
543         rcode = RegEnumKeyEx(key, index, name, &cch, NULL, NULL, NULL, NULL);
544         if (rcode != ERROR_SUCCESS)
545             break;
546 
547         rcode = RegOpenKeyEx(key, name, 0, KEY_READ, &subkey);
548         if (rcode != ERROR_SUCCESS)
549             continue;
550 
551         section = _krb5_config_get_entry(parent, name, krb5_config_list);
552         if (section == NULL) {
553             RegCloseKey(subkey);
554             return ENOMEM;
555         }
556 
557         code = parse_reg_values(context, subkey, &section->u.list);
558         if (code) {
559             RegCloseKey(subkey);
560             return code;
561         }
562 
563         code = parse_reg_subkeys(context, subkey, &section->u.list);
564         if (code) {
565             RegCloseKey(subkey);
566             return code;
567         }
568 
569         RegCloseKey(subkey);
570     }
571 
572     return 0;
573 }
574 
575 static krb5_error_code
parse_reg_root(krb5_context context,HKEY key,krb5_config_section ** parent)576 parse_reg_root(krb5_context context,
577                HKEY key,
578                krb5_config_section ** parent)
579 {
580     krb5_config_section *libdefaults = NULL;
581     krb5_error_code     code = 0;
582 
583     libdefaults = _krb5_config_get_entry(parent, "libdefaults", krb5_config_list);
584     if (libdefaults == NULL) {
585         krb5_set_error_message(context, ENOMEM, "Out of memory while parsing configuration");
586         return ENOMEM;
587     }
588 
589     code = parse_reg_values(context, key, &libdefaults->u.list);
590     if (code)
591         return code;
592 
593     return parse_reg_subkeys(context, key, parent);
594 }
595 
596 static krb5_error_code
load_config_from_regpath(krb5_context context,HKEY hk_root,const char * key_path,krb5_config_section ** res)597 load_config_from_regpath(krb5_context context,
598                          HKEY hk_root,
599                          const char* key_path,
600                          krb5_config_section ** res)
601 {
602     HKEY            key  = NULL;
603     LONG            rcode;
604     krb5_error_code code = 0;
605 
606     rcode = RegOpenKeyEx(hk_root, key_path, 0, KEY_READ, &key);
607     if (rcode == ERROR_SUCCESS) {
608         code = parse_reg_root(context, key, res);
609         RegCloseKey(key);
610         key = NULL;
611     }
612 
613     return code;
614 }
615 
616 /**
617  * Load configuration from registry
618  *
619  * The registry keys 'HKCU\Software\Heimdal' and
620  * 'HKLM\Software\Heimdal' are treated as krb5.conf files.  Each
621  * registry key corresponds to a configuration section (or bound list)
622  * and each value in a registry key is treated as a bound value.  The
623  * set of values that are directly under the Heimdal key are treated
624  * as if they were defined in the [libdefaults] section.
625  *
626  * @see parse_reg_value() for details about how each type of value is handled.
627  */
628 krb5_error_code
_krb5_load_config_from_registry(krb5_context context,krb5_config_section ** res)629 _krb5_load_config_from_registry(krb5_context context,
630                                 krb5_config_section ** res)
631 {
632     krb5_error_code code;
633 
634     code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE,
635                                     REGPATH_KERBEROS, res);
636     if (code)
637         return code;
638 
639     code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE,
640                                     REGPATH_HEIMDAL, res);
641     if (code)
642         return code;
643 
644     code = load_config_from_regpath(context, HKEY_CURRENT_USER,
645                                     REGPATH_KERBEROS, res);
646     if (code)
647         return code;
648 
649     code = load_config_from_regpath(context, HKEY_CURRENT_USER,
650                                     REGPATH_HEIMDAL, res);
651     if (code)
652         return code;
653     return 0;
654 }
655