xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/config_reg.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
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  */
47 char *
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  */
86 char *
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 
196 have_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 
281 done:
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  */
293 static krb5_error_code
294 parse_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 
319 done:
320     if (reg_string != NULL)
321         free(reg_string);
322 
323     return code;
324 }
325 
326 static krb5_error_code
327 parse_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 
357 static krb5_error_code
358 parse_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 
404 static krb5_error_code
405 parse_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  */
437 krb5_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