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