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 */
91KRB5_LIB_FUNCTION int KRB5_LIB_CALL
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 */
217KRB5_LIB_FUNCTION char * KRB5_LIB_CALL
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 */
256KRB5_LIB_FUNCTION char * KRB5_LIB_CALL
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
366have_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
451done:
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 */
463static krb5_error_code
464parse_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
489done:
490    if (reg_string != NULL)
491        free(reg_string);
492
493    return code;
494}
495
496static krb5_error_code
497parse_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
527static krb5_error_code
528parse_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, &section->u.list);
557        if (code) {
558            RegCloseKey(subkey);
559            return code;
560        }
561
562        code = parse_reg_subkeys(context, subkey, &section->u.list);
563        if (code) {
564            RegCloseKey(subkey);
565            return code;
566        }
567
568        RegCloseKey(subkey);
569    }
570
571    return 0;
572}
573
574static krb5_error_code
575parse_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
593static krb5_error_code
594load_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 */
625KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
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