1/*********************************************************************** 2 * Copyright (c) 2010, Secure Endpoints Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * - Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in 14 * the documentation and/or other materials provided with the 15 * distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 22 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 28 * OF THE POSSIBILITY OF SUCH DAMAGE. 29 * 30 **********************************************************************/ 31 32#include "krb5_locl.h" 33 34#ifndef _WIN32 35#error config_reg.c is only for Windows 36#endif 37 38#include <shlwapi.h> 39 40#ifndef MAX_DWORD 41#define MAX_DWORD 0xFFFFFFFF 42#endif 43 44#define REGPATH_KERBEROS "SOFTWARE\\Kerberos" 45#define REGPATH_HEIMDAL "SOFTWARE\\Heimdal" 46 47/** 48 * Store a string as a registry value of the specified type 49 * 50 * The following registry types are handled: 51 * 52 * - REG_DWORD: The string is converted to a number. 53 * 54 * - REG_SZ: The string is stored as is. 55 * 56 * - REG_EXPAND_SZ: The string is stored as is. 57 * 58 * - REG_MULTI_SZ: 59 * 60 * . If a separator is specified, the input string is broken 61 * up into multiple strings and stored as a multi-sz. 62 * 63 * . If no separator is provided, the input string is stored 64 * as a multi-sz. 65 * 66 * - REG_NONE: 67 * 68 * . If the string is all numeric, it will be stored as a 69 * REG_DWORD. 70 * 71 * . Otherwise, the string is stored as a REG_SZ. 72 * 73 * Other types are rejected. 74 * 75 * If cb_data is MAX_DWORD, the string pointed to by data must be nul-terminated 76 * otherwise a buffer overrun will occur. 77 * 78 * @param [in]valuename Name of the registry value to be modified or created 79 * @param [in]type Type of the value. REG_NONE if unknown 80 * @param [in]data The input string to be stored in the registry. 81 * @param [in]cb_data Size of the input string in bytes. MAX_DWORD if unknown. 82 * @param [in]separator Separator character for parsing strings. 83 * 84 * @retval 0 if success or non-zero on error. 85 * If non-zero is returned, an error message has been set using 86 * krb5_set_error_message(). 87 * 88 */ 89int 90_krb5_store_string_to_reg_value(krb5_context context, 91 HKEY key, const char * valuename, 92 DWORD type, const char *data, DWORD cb_data, 93 const char * separator) 94{ 95 LONG rcode; 96 DWORD dwData; 97 BYTE static_buffer[16384]; 98 BYTE *pbuffer = &static_buffer[0]; 99 100 if (data == NULL) 101 { 102 if (context) 103 krb5_set_error_message(context, 0, 104 "'data' must not be NULL"); 105 return -1; 106 } 107 108 if (cb_data == MAX_DWORD) 109 { 110 cb_data = (DWORD)strlen(data) + 1; 111 } 112 else if ((type == REG_MULTI_SZ && cb_data >= sizeof(static_buffer) - 1) || 113 cb_data >= sizeof(static_buffer)) 114 { 115 if (context) 116 krb5_set_error_message(context, 0, "cb_data too big"); 117 return -1; 118 } 119 else if (data[cb_data-1] != '\0') 120 { 121 memcpy(static_buffer, data, cb_data); 122 static_buffer[cb_data++] = '\0'; 123 if (type == REG_MULTI_SZ) 124 static_buffer[cb_data++] = '\0'; 125 data = static_buffer; 126 } 127 128 if (type == REG_NONE) 129 { 130 /* 131 * If input is all numeric, convert to DWORD and save as REG_DWORD. 132 * Otherwise, store as REG_SZ. 133 */ 134 if ( StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) ) 135 { 136 type = REG_DWORD; 137 } else { 138 type = REG_SZ; 139 } 140 } 141 142 switch (type) { 143 case REG_SZ: 144 case REG_EXPAND_SZ: 145 rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data); 146 if (rcode) 147 { 148 if (context) 149 krb5_set_error_message(context, 0, 150 "Unexpected error when setting registry value %s gle 0x%x", 151 valuename, 152 GetLastError()); 153 return -1; 154 } 155 break; 156 case REG_MULTI_SZ: 157 if (separator && *separator) 158 { 159 int i; 160 char *cp; 161 162 if (data != static_buffer) 163 static_buffer[cb_data++] = '\0'; 164 165 for ( cp = static_buffer; cp < static_buffer+cb_data; cp++) 166 { 167 if (*cp == *separator) 168 *cp = '\0'; 169 } 170 171 rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data); 172 if (rcode) 173 { 174 if (context) 175 krb5_set_error_message(context, 0, 176 "Unexpected error when setting registry value %s gle 0x%x", 177 valuename, 178 GetLastError()); 179 return -1; 180 } 181 } 182 break; 183 case REG_DWORD: 184 if ( !StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) ) 185 { 186 if (context) 187 krb5_set_error_message(context, 0, 188 "Unexpected error when parsing %s as number gle 0x%x", 189 data, 190 GetLastError()); 191 } 192 193 rcode = RegSetValueEx(key, valuename, 0, type, dwData, sizeof(DWORD)); 194 if (rcode) 195 { 196 if (context) 197 krb5_set_error_message(context, 0, 198 "Unexpected error when setting registry value %s gle 0x%x", 199 valuename, 200 GetLastError()); 201 return -1; 202 } 203 break; 204 default: 205 return -1; 206 } 207 208 return 0; 209} 210 211/** 212 * Parse a registry value as a string 213 * 214 * @see _krb5_parse_reg_value_as_multi_string() 215 */ 216char * 217_krb5_parse_reg_value_as_string(krb5_context context, 218 HKEY key, const char * valuename, 219 DWORD type, DWORD cb_data) 220{ 221 return _krb5_parse_reg_value_as_multi_string(context, key, valuename, 222 type, cb_data, " "); 223} 224 225/** 226 * Parse a registry value as a multi string 227 * 228 * The following registry value types are handled: 229 * 230 * - REG_DWORD: The decimal string representation is used as the 231 * value. 232 * 233 * - REG_SZ: The string is used as-is. 234 * 235 * - REG_EXPAND_SZ: Environment variables in the string are expanded 236 * and the result is used as the value. 237 * 238 * - REG_MULTI_SZ: The list of strings is concatenated using the 239 * separator. No quoting is performed. 240 * 241 * Any other value type is rejected. 242 * 243 * @param [in]valuename Name of the registry value to be queried 244 * @param [in]type Type of the value. REG_NONE if unknown 245 * @param [in]cbdata Size of value. 0 if unknown. 246 * @param [in]separator Separator character for concatenating strings. 247 * 248 * @a type and @a cbdata are only considered valid if both are 249 * specified. 250 * 251 * @retval The registry value string, or NULL if there was an error. 252 * If NULL is returned, an error message has been set using 253 * krb5_set_error_message(). 254 */ 255char * 256_krb5_parse_reg_value_as_multi_string(krb5_context context, 257 HKEY key, const char * valuename, 258 DWORD type, DWORD cb_data, char *separator) 259{ 260 LONG rcode = ERROR_MORE_DATA; 261 262 BYTE static_buffer[16384]; 263 BYTE *pbuffer = &static_buffer[0]; 264 DWORD cb_alloc = sizeof(static_buffer); 265 char *ret_string = NULL; 266 267 /* If we know a type and cb_data from a previous call to 268 * RegEnumValue(), we use it. Otherwise we use the 269 * static_buffer[] and query directly. We do this to minimize the 270 * number of queries. */ 271 272 if (type == REG_NONE || cb_data == 0) { 273 274 pbuffer = &static_buffer[0]; 275 cb_alloc = cb_data = sizeof(static_buffer); 276 rcode = RegQueryValueExA(key, valuename, NULL, &type, pbuffer, &cb_data); 277 278 if (rcode == ERROR_SUCCESS && 279 280 ((type != REG_SZ && 281 type != REG_EXPAND_SZ) || cb_data + 1 <= sizeof(static_buffer)) && 282 283 (type != REG_MULTI_SZ || cb_data + 2 <= sizeof(static_buffer))) 284 goto have_data; 285 286 if (rcode != ERROR_MORE_DATA && rcode != ERROR_SUCCESS) 287 return NULL; 288 } 289 290 /* Either we don't have the data or we aren't sure of the size 291 * (due to potentially missing terminating NULs). */ 292 293 switch (type) { 294 case REG_DWORD: 295 if (cb_data != sizeof(DWORD)) { 296 if (context) 297 krb5_set_error_message(context, 0, 298 "Unexpected size while reading registry value %s", 299 valuename); 300 return NULL; 301 } 302 break; 303 304 case REG_SZ: 305 case REG_EXPAND_SZ: 306 307 if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0') 308 goto have_data; 309 310 cb_data += sizeof(char); /* Accout for potential missing NUL 311 * terminator. */ 312 break; 313 314 case REG_MULTI_SZ: 315 316 if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0' && 317 (cb_data == 1 || pbuffer[cb_data - 2] == '\0')) 318 goto have_data; 319 320 cb_data += sizeof(char) * 2; /* Potential missing double NUL 321 * terminator. */ 322 break; 323 324 default: 325 if (context) 326 krb5_set_error_message(context, 0, 327 "Unexpected type while reading registry value %s", 328 valuename); 329 return NULL; 330 } 331 332 if (cb_data <= sizeof(static_buffer)) 333 pbuffer = &static_buffer[0]; 334 else { 335 pbuffer = malloc(cb_data); 336 if (pbuffer == NULL) 337 return NULL; 338 } 339 340 cb_alloc = cb_data; 341 rcode = RegQueryValueExA(key, valuename, NULL, NULL, pbuffer, &cb_data); 342 343 if (rcode != ERROR_SUCCESS) { 344 345 /* This can potentially be from a race condition. I.e. some 346 * other process or thread went and modified the registry 347 * value between the time we queried its size and queried for 348 * its value. Ideally we would retry the query in a loop. */ 349 350 if (context) 351 krb5_set_error_message(context, 0, 352 "Unexpected error while reading registry value %s", 353 valuename); 354 goto done; 355 } 356 357 if (cb_data > cb_alloc || cb_data == 0) { 358 if (context) 359 krb5_set_error_message(context, 0, 360 "Unexpected size while reading registry value %s", 361 valuename); 362 goto done; 363 } 364 365have_data: 366 switch (type) { 367 case REG_DWORD: 368 asprintf(&ret_string, "%d", *((DWORD *) pbuffer)); 369 break; 370 371 case REG_SZ: 372 { 373 char * str = (char *) pbuffer; 374 375 if (str[cb_data - 1] != '\0') { 376 if (cb_data < cb_alloc) 377 str[cb_data] = '\0'; 378 else 379 break; 380 } 381 382 if (pbuffer != static_buffer) { 383 ret_string = (char *) pbuffer; 384 pbuffer = NULL; 385 } else { 386 ret_string = strdup((char *) pbuffer); 387 } 388 } 389 break; 390 391 case REG_EXPAND_SZ: 392 { 393 char *str = (char *) pbuffer; 394 char expsz[32768]; /* Size of output buffer for 395 * ExpandEnvironmentStrings() is 396 * limited to 32K. */ 397 398 if (str[cb_data - 1] != '\0') { 399 if (cb_data < cb_alloc) 400 str[cb_data] = '\0'; 401 else 402 break; 403 } 404 405 if (ExpandEnvironmentStrings(str, expsz, sizeof(expsz)/sizeof(char)) != 0) { 406 ret_string = strdup(expsz); 407 } else { 408 if (context) 409 krb5_set_error_message(context, 0, 410 "Overflow while expanding environment strings " 411 "for registry value %s", valuename); 412 } 413 } 414 break; 415 416 case REG_MULTI_SZ: 417 { 418 char * str = (char *) pbuffer; 419 char * iter; 420 421 str[cb_alloc - 1] = '\0'; 422 str[cb_alloc - 2] = '\0'; 423 424 for (iter = str; *iter;) { 425 size_t len = strlen(iter); 426 427 iter += len; 428 if (iter[1] != '\0') 429 *iter++ = *separator; 430 else 431 break; 432 } 433 434 if (pbuffer != static_buffer) { 435 ret_string = str; 436 pbuffer = NULL; 437 } else { 438 ret_string = strdup(str); 439 } 440 } 441 break; 442 443 default: 444 if (context) 445 krb5_set_error_message(context, 0, 446 "Unexpected type while reading registry value %s", 447 valuename); 448 } 449 450done: 451 if (pbuffer != static_buffer && pbuffer != NULL) 452 free(pbuffer); 453 454 return ret_string; 455} 456 457/** 458 * Parse a registry value as a configuration value 459 * 460 * @see parse_reg_value_as_string() 461 */ 462static krb5_error_code 463parse_reg_value(krb5_context context, 464 HKEY key, const char * valuename, 465 DWORD type, DWORD cbdata, krb5_config_section ** parent) 466{ 467 char *reg_string = NULL; 468 krb5_config_section *value; 469 krb5_error_code code = 0; 470 471 reg_string = _krb5_parse_reg_value_as_string(context, key, valuename, type, cbdata); 472 473 if (reg_string == NULL) 474 return KRB5_CONFIG_BADFORMAT; 475 476 value = _krb5_config_get_entry(parent, valuename, krb5_config_string); 477 if (value == NULL) { 478 code = ENOMEM; 479 goto done; 480 } 481 482 if (value->u.string != NULL) 483 free(value->u.string); 484 485 value->u.string = reg_string; 486 reg_string = NULL; 487 488done: 489 if (reg_string != NULL) 490 free(reg_string); 491 492 return code; 493} 494 495static krb5_error_code 496parse_reg_values(krb5_context context, 497 HKEY key, 498 krb5_config_section ** parent) 499{ 500 DWORD index; 501 LONG rcode; 502 503 for (index = 0; ; index ++) { 504 char name[16385]; 505 DWORD cch = sizeof(name)/sizeof(name[0]); 506 DWORD type; 507 DWORD cbdata = 0; 508 krb5_error_code code; 509 510 rcode = RegEnumValue(key, index, name, &cch, NULL, 511 &type, NULL, &cbdata); 512 if (rcode != ERROR_SUCCESS) 513 break; 514 515 if (cbdata == 0) 516 continue; 517 518 code = parse_reg_value(context, key, name, type, cbdata, parent); 519 if (code != 0) 520 return code; 521 } 522 523 return 0; 524} 525 526static krb5_error_code 527parse_reg_subkeys(krb5_context context, 528 HKEY key, 529 krb5_config_section ** parent) 530{ 531 DWORD index; 532 LONG rcode; 533 534 for (index = 0; ; index ++) { 535 HKEY subkey = NULL; 536 char name[256]; 537 DWORD cch = sizeof(name)/sizeof(name[0]); 538 krb5_config_section *section = NULL; 539 krb5_error_code code; 540 541 rcode = RegEnumKeyEx(key, index, name, &cch, NULL, NULL, NULL, NULL); 542 if (rcode != ERROR_SUCCESS) 543 break; 544 545 rcode = RegOpenKeyEx(key, name, 0, KEY_READ, &subkey); 546 if (rcode != ERROR_SUCCESS) 547 continue; 548 549 section = _krb5_config_get_entry(parent, name, krb5_config_list); 550 if (section == NULL) { 551 RegCloseKey(subkey); 552 return ENOMEM; 553 } 554 555 code = parse_reg_values(context, subkey, §ion->u.list); 556 if (code) { 557 RegCloseKey(subkey); 558 return code; 559 } 560 561 code = parse_reg_subkeys(context, subkey, §ion->u.list); 562 if (code) { 563 RegCloseKey(subkey); 564 return code; 565 } 566 567 RegCloseKey(subkey); 568 } 569 570 return 0; 571} 572 573static krb5_error_code 574parse_reg_root(krb5_context context, 575 HKEY key, 576 krb5_config_section ** parent) 577{ 578 krb5_config_section *libdefaults = NULL; 579 krb5_error_code code = 0; 580 581 libdefaults = _krb5_config_get_entry(parent, "libdefaults", krb5_config_list); 582 if (libdefaults == NULL) { 583 krb5_set_error_message(context, ENOMEM, "Out of memory while parsing configuration"); 584 return ENOMEM; 585 } 586 587 code = parse_reg_values(context, key, &libdefaults->u.list); 588 if (code) 589 return code; 590 591 return parse_reg_subkeys(context, key, parent); 592} 593 594static krb5_error_code 595load_config_from_regpath(krb5_context context, 596 HKEY hk_root, 597 const char* key_path, 598 krb5_config_section ** res) 599{ 600 HKEY key = NULL; 601 LONG rcode; 602 krb5_error_code code = 0; 603 604 rcode = RegOpenKeyEx(hk_root, key_path, 0, KEY_READ, &key); 605 if (rcode == ERROR_SUCCESS) { 606 code = parse_reg_root(context, key, res); 607 RegCloseKey(key); 608 key = NULL; 609 } 610 611 return code; 612} 613 614/** 615 * Load configuration from registry 616 * 617 * The registry keys 'HKCU\Software\Heimdal' and 618 * 'HKLM\Software\Heimdal' are treated as krb5.conf files. Each 619 * registry key corresponds to a configuration section (or bound list) 620 * and each value in a registry key is treated as a bound value. The 621 * set of values that are directly under the Heimdal key are treated 622 * as if they were defined in the [libdefaults] section. 623 * 624 * @see parse_reg_value() for details about how each type of value is handled. 625 */ 626krb5_error_code 627_krb5_load_config_from_registry(krb5_context context, 628 krb5_config_section ** res) 629{ 630 krb5_error_code code; 631 632 code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE, 633 REGPATH_KERBEROS, res); 634 if (code) 635 return code; 636 637 code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE, 638 REGPATH_HEIMDAL, res); 639 if (code) 640 return code; 641 642 code = load_config_from_regpath(context, HKEY_CURRENT_USER, 643 REGPATH_KERBEROS, res); 644 if (code) 645 return code; 646 647 code = load_config_from_regpath(context, HKEY_CURRENT_USER, 648 REGPATH_HEIMDAL, res); 649 if (code) 650 return code; 651 return 0; 652} 653