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