1/*	$NetBSD: config_reg.c,v 1.1.1.1 2011/04/13 18:15:32 elric 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#define REGPATH "SOFTWARE\\Kerberos"
41
42/**
43 * Parse a registry value as a string
44 *
45 * @see _krb5_parse_reg_value_as_multi_string()
46 */
47char *
48_krb5_parse_reg_value_as_string(krb5_context context,
49                                HKEY key, const char * valuename,
50                                DWORD type, DWORD cb_data)
51{
52    return _krb5_parse_reg_value_as_multi_string(context, key, valuename,
53                                                 type, cb_data, " ");
54}
55
56/**
57 * Parse a registry value as a multi string
58 *
59 * The following registry value types are handled:
60 *
61 * - REG_DWORD: The decimal string representation is used as the
62 *   value.
63 *
64 * - REG_SZ: The string is used as-is.
65 *
66 * - REG_EXPAND_SZ: Environment variables in the string are expanded
67 *   and the result is used as the value.
68 *
69 * - REG_MULTI_SZ: The list of strings is concatenated using the
70 *   separator.  No quoting is performed.
71 *
72 * Any other value type is rejected.
73 *
74 * @param [in]valuename Name of the registry value to be queried
75 * @param [in]type      Type of the value. REG_NONE if unknown
76 * @param [in]cbdata    Size of value. 0 if unknown.
77 * @param [in]separator Separator character for concatenating strings.
78 *
79 * @a type and @a cbdata are only considered valid if both are
80 * specified.
81 *
82 * @retval The registry value string, or NULL if there was an error.
83 * If NULL is returned, an error message has been set using
84 * krb5_set_error_message().
85 */
86char *
87_krb5_parse_reg_value_as_multi_string(krb5_context context,
88                                      HKEY key, const char * valuename,
89                                      DWORD type, DWORD cb_data, char *separator)
90{
91    LONG                rcode = ERROR_MORE_DATA;
92
93    BYTE                static_buffer[16384];
94    BYTE                *pbuffer = &static_buffer[0];
95    DWORD               cb_alloc = sizeof(static_buffer);
96    char                *ret_string = NULL;
97
98    /* If we know a type and cb_data from a previous call to
99     * RegEnumValue(), we use it.  Otherwise we use the
100     * static_buffer[] and query directly.  We do this to minimize the
101     * number of queries. */
102
103    if (type == REG_NONE || cb_data == 0) {
104
105        pbuffer = &static_buffer[0];
106        cb_alloc = cb_data = sizeof(static_buffer);
107        rcode = RegQueryValueExA(key, valuename, NULL, &type, pbuffer, &cb_data);
108
109        if (rcode == ERROR_SUCCESS &&
110
111            ((type != REG_SZ &&
112              type != REG_EXPAND_SZ) || cb_data + 1 <= sizeof(static_buffer)) &&
113
114            (type != REG_MULTI_SZ || cb_data + 2 <= sizeof(static_buffer)))
115            goto have_data;
116
117        if (rcode != ERROR_MORE_DATA && rcode != ERROR_SUCCESS)
118            return NULL;
119    }
120
121    /* Either we don't have the data or we aren't sure of the size
122     * (due to potentially missing terminating NULs). */
123
124    switch (type) {
125    case REG_DWORD:
126        if (cb_data != sizeof(DWORD)) {
127            if (context)
128                krb5_set_error_message(context, 0,
129                                       "Unexpected size while reading registry value %s",
130                                       valuename);
131            return NULL;
132        }
133        break;
134
135    case REG_SZ:
136    case REG_EXPAND_SZ:
137
138        if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0')
139            goto have_data;
140
141        cb_data += sizeof(char); /* Accout for potential missing NUL
142                                  * terminator. */
143        break;
144
145    case REG_MULTI_SZ:
146
147        if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0' &&
148            (cb_data == 1 || pbuffer[cb_data - 2] == '\0'))
149            goto have_data;
150
151        cb_data += sizeof(char) * 2; /* Potential missing double NUL
152                                      * terminator. */
153        break;
154
155    default:
156        if (context)
157            krb5_set_error_message(context, 0,
158                                   "Unexpected type while reading registry value %s",
159                                   valuename);
160        return NULL;
161    }
162
163    if (cb_data <= sizeof(static_buffer))
164        pbuffer = &static_buffer[0];
165    else {
166        pbuffer = malloc(cb_data);
167        if (pbuffer == NULL)
168            return NULL;
169    }
170
171    cb_alloc = cb_data;
172    rcode = RegQueryValueExA(key, valuename, NULL, NULL, pbuffer, &cb_data);
173
174    if (rcode != ERROR_SUCCESS) {
175
176        /* This can potentially be from a race condition. I.e. some
177         * other process or thread went and modified the registry
178         * value between the time we queried its size and queried for
179         * its value.  Ideally we would retry the query in a loop. */
180
181        if (context)
182            krb5_set_error_message(context, 0,
183                                   "Unexpected error while reading registry value %s",
184                                   valuename);
185        goto done;
186    }
187
188    if (cb_data > cb_alloc || cb_data == 0) {
189        if (context)
190            krb5_set_error_message(context, 0,
191                                   "Unexpected size while reading registry value %s",
192                                   valuename);
193        goto done;
194    }
195
196have_data:
197    switch (type) {
198    case REG_DWORD:
199        asprintf(&ret_string, "%d", *((DWORD *) pbuffer));
200        break;
201
202    case REG_SZ:
203    {
204        char * str = (char *) pbuffer;
205
206        if (str[cb_data - 1] != '\0') {
207            if (cb_data < cb_alloc)
208                str[cb_data] = '\0';
209            else
210                break;
211        }
212
213        if (pbuffer != static_buffer) {
214            ret_string = (char *) pbuffer;
215            pbuffer = NULL;
216        } else {
217            ret_string = strdup((char *) pbuffer);
218        }
219    }
220    break;
221
222    case REG_EXPAND_SZ:
223    {
224        char    *str = (char *) pbuffer;
225        char    expsz[32768];   /* Size of output buffer for
226                                 * ExpandEnvironmentStrings() is
227                                 * limited to 32K. */
228
229        if (str[cb_data - 1] != '\0') {
230            if (cb_data < cb_alloc)
231                str[cb_data] = '\0';
232            else
233                break;
234        }
235
236        if (ExpandEnvironmentStrings(str, expsz, sizeof(expsz)/sizeof(char)) != 0) {
237            ret_string = strdup(expsz);
238        } else {
239            if (context)
240                krb5_set_error_message(context, 0,
241                                       "Overflow while expanding environment strings "
242                                       "for registry value %s", valuename);
243        }
244    }
245    break;
246
247    case REG_MULTI_SZ:
248    {
249        char * str = (char *) pbuffer;
250        char * iter;
251
252        str[cb_alloc - 1] = '\0';
253        str[cb_alloc - 2] = '\0';
254
255        for (iter = str; *iter;) {
256            size_t len = strlen(iter);
257
258            iter += len;
259            if (iter[1] != '\0')
260                *iter++ = *separator;
261            else
262                break;
263        }
264
265        if (pbuffer != static_buffer) {
266            ret_string = str;
267            pbuffer = NULL;
268        } else {
269            ret_string = strdup(str);
270        }
271    }
272    break;
273
274    default:
275        if (context)
276            krb5_set_error_message(context, 0,
277                                   "Unexpected type while reading registry value %s",
278                                   valuename);
279    }
280
281done:
282    if (pbuffer != static_buffer && pbuffer != NULL)
283        free(pbuffer);
284
285    return ret_string;
286}
287
288/**
289 * Parse a registry value as a configuration value
290 *
291 * @see parse_reg_value_as_string()
292 */
293static krb5_error_code
294parse_reg_value(krb5_context context,
295                HKEY key, const char * valuename,
296                DWORD type, DWORD cbdata, krb5_config_section ** parent)
297{
298    char                *reg_string = NULL;
299    krb5_config_section *value;
300    krb5_error_code     code = 0;
301
302    reg_string = _krb5_parse_reg_value_as_string(context, key, valuename, type, cbdata);
303
304    if (reg_string == NULL)
305        return KRB5_CONFIG_BADFORMAT;
306
307    value = _krb5_config_get_entry(parent, valuename, krb5_config_string);
308    if (value == NULL) {
309        code = ENOMEM;
310        goto done;
311    }
312
313    if (value->u.string != NULL)
314        free(value->u.string);
315
316    value->u.string = reg_string;
317    reg_string = NULL;
318
319done:
320    if (reg_string != NULL)
321        free(reg_string);
322
323    return code;
324}
325
326static krb5_error_code
327parse_reg_values(krb5_context context,
328                 HKEY key,
329                 krb5_config_section ** parent)
330{
331    DWORD index;
332    LONG  rcode;
333
334    for (index = 0; ; index ++) {
335        char    name[16385];
336        DWORD   cch = sizeof(name)/sizeof(name[0]);
337        DWORD   type;
338        DWORD   cbdata = 0;
339        krb5_error_code code;
340
341        rcode = RegEnumValue(key, index, name, &cch, NULL,
342                             &type, NULL, &cbdata);
343        if (rcode != ERROR_SUCCESS)
344            break;
345
346        if (cbdata == 0)
347            continue;
348
349        code = parse_reg_value(context, key, name, type, cbdata, parent);
350        if (code != 0)
351            return code;
352    }
353
354    return 0;
355}
356
357static krb5_error_code
358parse_reg_subkeys(krb5_context context,
359                  HKEY key,
360                  krb5_config_section ** parent)
361{
362    DWORD index;
363    LONG  rcode;
364
365    for (index = 0; ; index ++) {
366        HKEY    subkey = NULL;
367        char    name[256];
368        DWORD   cch = sizeof(name)/sizeof(name[0]);
369        krb5_config_section     *section = NULL;
370        krb5_error_code         code;
371
372        rcode = RegEnumKeyEx(key, index, name, &cch, NULL, NULL, NULL, NULL);
373        if (rcode != ERROR_SUCCESS)
374            break;
375
376        rcode = RegOpenKeyEx(key, name, 0, KEY_READ, &subkey);
377        if (rcode != ERROR_SUCCESS)
378            continue;
379
380        section = _krb5_config_get_entry(parent, name, krb5_config_list);
381        if (section == NULL) {
382            RegCloseKey(subkey);
383            return ENOMEM;
384        }
385
386        code = parse_reg_values(context, subkey, &section->u.list);
387        if (code) {
388            RegCloseKey(subkey);
389            return code;
390        }
391
392        code = parse_reg_subkeys(context, subkey, &section->u.list);
393        if (code) {
394            RegCloseKey(subkey);
395            return code;
396        }
397
398        RegCloseKey(subkey);
399    }
400
401    return 0;
402}
403
404static krb5_error_code
405parse_reg_root(krb5_context context,
406               HKEY key,
407               krb5_config_section ** parent)
408{
409    krb5_config_section *libdefaults = NULL;
410    krb5_error_code     code = 0;
411
412    libdefaults = _krb5_config_get_entry(parent, "libdefaults", krb5_config_list);
413    if (libdefaults == NULL) {
414        krb5_set_error_message(context, ENOMEM, "Out of memory while parsing configuration");
415        return ENOMEM;
416    }
417
418    code = parse_reg_values(context, key, &libdefaults->u.list);
419    if (code)
420        return code;
421
422    return parse_reg_subkeys(context, key, parent);
423}
424
425/**
426 * Load configuration from registry
427 *
428 * The registry keys 'HKCU\Software\Heimdal' and
429 * 'HKLM\Software\Heimdal' are treated as krb5.conf files.  Each
430 * registry key corresponds to a configuration section (or bound list)
431 * and each value in a registry key is treated as a bound value.  The
432 * set of values that are directly under the Heimdal key are treated
433 * as if they were defined in the [libdefaults] section.
434 *
435 * @see parse_reg_value() for details about how each type of value is handled.
436 */
437krb5_error_code
438_krb5_load_config_from_registry(krb5_context context,
439                                krb5_config_section ** res)
440{
441    HKEY        key = NULL;
442    LONG        rcode;
443    krb5_error_code code = 0;
444
445    rcode = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGPATH, 0, KEY_READ, &key);
446    if (rcode == ERROR_SUCCESS) {
447        code = parse_reg_root(context, key, res);
448        RegCloseKey(key);
449        key = NULL;
450
451        if (code)
452            return code;
453    }
454
455    rcode = RegOpenKeyEx(HKEY_CURRENT_USER, REGPATH, 0, KEY_READ, &key);
456    if (rcode == ERROR_SUCCESS) {
457        code = parse_reg_root(context, key, res);
458        RegCloseKey(key);
459        key = NULL;
460
461        if (code)
462            return code;
463    }
464
465    return 0;
466}
467