1/* $NetBSD$ */ 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, §ion->u.list); 387 if (code) { 388 RegCloseKey(subkey); 389 return code; 390 } 391 392 code = parse_reg_subkeys(context, subkey, §ion->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