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