diff options
Diffstat (limited to 'third_party/heimdal/lib/base/config_reg.c')
-rw-r--r-- | third_party/heimdal/lib/base/config_reg.c | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/base/config_reg.c b/third_party/heimdal/lib/base/config_reg.c new file mode 100644 index 0000000..cb24e50 --- /dev/null +++ b/third_party/heimdal/lib/base/config_reg.c @@ -0,0 +1,658 @@ +/*********************************************************************** + * Copyright (c) 2010, Secure Endpoints Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + **********************************************************************/ + +#include "baselocl.h" + +#ifndef _WIN32 +#error config_reg.c is only for Windows +#endif + +#include <shlwapi.h> + +#ifndef MAX_DWORD +#define MAX_DWORD 0xFFFFFFFF +#endif + +/** + * Store a string as a registry value of the specified type + * + * The following registry types are handled: + * + * - REG_DWORD: The string is converted to a number. + * + * - REG_SZ: The string is stored as is. + * + * - REG_EXPAND_SZ: The string is stored as is. + * + * - REG_MULTI_SZ: + * + * . If a separator is specified, the input string is broken + * up into multiple strings and stored as a multi-sz. + * + * . If no separator is provided, the input string is stored + * as a multi-sz. + * + * - REG_NONE: + * + * . If the string is all numeric, it will be stored as a + * REG_DWORD. + * + * . Otherwise, the string is stored as a REG_SZ. + * + * Other types are rejected. + * + * If cb_data is MAX_DWORD, the string pointed to by data must be nul-terminated + * otherwise a buffer overrun will occur. + * + * @param [in]valuename Name of the registry value to be modified or created + * @param [in]type Type of the value. REG_NONE if unknown + * @param [in]data The input string to be stored in the registry. + * @param [in]cb_data Size of the input string in bytes. MAX_DWORD if unknown. + * @param [in]separator Separator character for parsing strings. + * + * @retval 0 if success or non-zero on error. + * If non-zero is returned, an error message has been set using + * heim_set_error_message(). + * + */ +int +heim_store_string_to_reg_value(heim_context context, + HKEY key, const char *valuename, + DWORD type, const char *data, DWORD cb_data, + const char *separator) +{ + LONG rcode; + int dwData; + BYTE static_buffer[16384]; + + if (data == NULL) + { + if (context) + heim_set_error_message(context, 0, + "'data' must not be NULL"); + return -1; + } + + if (cb_data == MAX_DWORD) + { + cb_data = (DWORD)strlen(data) + 1; + } + else if ((type == REG_MULTI_SZ && cb_data >= sizeof(static_buffer) - 1) || + cb_data >= sizeof(static_buffer)) + { + if (context) + heim_set_error_message(context, 0, "cb_data too big"); + return -1; + } + else if (data[cb_data-1] != '\0') + { + memcpy(static_buffer, data, cb_data); + static_buffer[cb_data++] = '\0'; + if (type == REG_MULTI_SZ) + static_buffer[cb_data++] = '\0'; + data = static_buffer; + } + + if (type == REG_NONE) + { + /* + * If input is all numeric, convert to DWORD and save as REG_DWORD. + * Otherwise, store as REG_SZ. + */ + if ( StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) ) + { + type = REG_DWORD; + } else { + type = REG_SZ; + } + } + + switch (type) { + case REG_SZ: + case REG_EXPAND_SZ: + rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data); + if (rcode) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when setting registry value %s gle 0x%x", + valuename, + GetLastError()); + return -1; + } + break; + case REG_MULTI_SZ: + if (separator && *separator) + { + char *cp; + + if (data != static_buffer) + static_buffer[cb_data++] = '\0'; + + for ( cp = static_buffer; cp < static_buffer+cb_data; cp++) + { + if (*cp == *separator) + *cp = '\0'; + } + + rcode = RegSetValueEx(key, valuename, 0, type, data, cb_data); + if (rcode) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when setting registry value %s gle 0x%x", + valuename, + GetLastError()); + return -1; + } + } + break; + case REG_DWORD: + if ( !StrToIntExA( data, STIF_SUPPORT_HEX, &dwData) ) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when parsing %s as number gle 0x%x", + data, + GetLastError()); + } + + rcode = RegSetValueEx(key, valuename, 0, type, (BYTE *)&dwData, sizeof(DWORD)); + if (rcode) + { + if (context) + heim_set_error_message(context, 0, + "Unexpected error when setting registry value %s gle 0x%x", + valuename, + GetLastError()); + return -1; + } + break; + default: + return -1; + } + + return 0; +} + +/** + * Parse a registry value as a string + * + * @see heim_parse_reg_value_as_multi_string() + */ +char * +heim_parse_reg_value_as_string(heim_context context, + HKEY key, const char * valuename, + DWORD type, DWORD cb_data) +{ + return heim_parse_reg_value_as_multi_string(context, key, valuename, + type, cb_data, " "); +} + +/** + * Parse a registry value as a multi string + * + * The following registry value types are handled: + * + * - REG_DWORD: The decimal string representation is used as the + * value. + * + * - REG_SZ: The string is used as-is. + * + * - REG_EXPAND_SZ: Environment variables in the string are expanded + * and the result is used as the value. + * + * - REG_MULTI_SZ: The list of strings is concatenated using the + * separator. No quoting is performed. + * + * Any other value type is rejected. + * + * @param [in]valuename Name of the registry value to be queried + * @param [in]type Type of the value. REG_NONE if unknown + * @param [in]cbdata Size of value. 0 if unknown. + * @param [in]separator Separator character for concatenating strings. + * + * @a type and @a cbdata are only considered valid if both are + * specified. + * + * @retval The registry value string, or NULL if there was an error. + * If NULL is returned, an error message has been set using + * heim_set_error_message(). + */ +char * +heim_parse_reg_value_as_multi_string(heim_context context, + HKEY key, const char * valuename, + DWORD type, DWORD cb_data, char *separator) +{ + LONG rcode = ERROR_MORE_DATA; + + BYTE static_buffer[16384]; + BYTE *pbuffer = &static_buffer[0]; + DWORD cb_alloc = sizeof(static_buffer); + char *ret_string = NULL; + + /* If we know a type and cb_data from a previous call to + * RegEnumValue(), we use it. Otherwise we use the + * static_buffer[] and query directly. We do this to minimize the + * number of queries. */ + + if (type == REG_NONE || cb_data == 0) { + + pbuffer = &static_buffer[0]; + cb_alloc = cb_data = sizeof(static_buffer); + rcode = RegQueryValueExA(key, valuename, NULL, &type, pbuffer, &cb_data); + + if (rcode == ERROR_SUCCESS && + + ((type != REG_SZ && + type != REG_EXPAND_SZ) || cb_data + 1 <= sizeof(static_buffer)) && + + (type != REG_MULTI_SZ || cb_data + 2 <= sizeof(static_buffer))) + goto have_data; + + if (rcode != ERROR_MORE_DATA && rcode != ERROR_SUCCESS) + return NULL; + } + + /* Either we don't have the data or we aren't sure of the size + * (due to potentially missing terminating NULs). */ + + switch (type) { + case REG_DWORD: + if (cb_data != sizeof(DWORD)) { + if (context) + heim_set_error_message(context, 0, + "Unexpected size while reading registry value %s", + valuename); + return NULL; + } + break; + + case REG_SZ: + case REG_EXPAND_SZ: + + if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0') + goto have_data; + + cb_data += sizeof(char); /* Accout for potential missing NUL + * terminator. */ + break; + + case REG_MULTI_SZ: + + if (rcode == ERROR_SUCCESS && cb_data > 0 && pbuffer[cb_data - 1] == '\0' && + (cb_data == 1 || pbuffer[cb_data - 2] == '\0')) + goto have_data; + + cb_data += sizeof(char) * 2; /* Potential missing double NUL + * terminator. */ + break; + + default: + if (context) + heim_set_error_message(context, 0, + "Unexpected type while reading registry value %s", + valuename); + return NULL; + } + + if (cb_data <= sizeof(static_buffer)) + pbuffer = &static_buffer[0]; + else { + pbuffer = malloc(cb_data); + if (pbuffer == NULL) + return NULL; + } + + cb_alloc = cb_data; + rcode = RegQueryValueExA(key, valuename, NULL, NULL, pbuffer, &cb_data); + + if (rcode != ERROR_SUCCESS) { + + /* This can potentially be from a race condition. I.e. some + * other process or thread went and modified the registry + * value between the time we queried its size and queried for + * its value. Ideally we would retry the query in a loop. */ + + if (context) + heim_set_error_message(context, 0, + "Unexpected error while reading registry value %s", + valuename); + goto done; + } + + if (cb_data > cb_alloc || cb_data == 0) { + if (context) + heim_set_error_message(context, 0, + "Unexpected size while reading registry value %s", + valuename); + goto done; + } + +have_data: + switch (type) { + case REG_DWORD: + asprintf(&ret_string, "%d", *((DWORD *) pbuffer)); + break; + + case REG_SZ: + { + char * str = (char *) pbuffer; + + if (str[cb_data - 1] != '\0') { + if (cb_data < cb_alloc) + str[cb_data] = '\0'; + else + break; + } + + if (pbuffer != static_buffer) { + ret_string = (char *) pbuffer; + pbuffer = NULL; + } else { + ret_string = strdup((char *) pbuffer); + } + } + break; + + case REG_EXPAND_SZ: + { + char *str = (char *) pbuffer; + char expsz[32768]; /* Size of output buffer for + * ExpandEnvironmentStrings() is + * limited to 32K. */ + + if (str[cb_data - 1] != '\0') { + if (cb_data < cb_alloc) + str[cb_data] = '\0'; + else + break; + } + + if (ExpandEnvironmentStrings(str, expsz, sizeof(expsz)/sizeof(char)) != 0) { + ret_string = strdup(expsz); + } else { + if (context) + heim_set_error_message(context, 0, + "Overflow while expanding environment strings " + "for registry value %s", valuename); + } + } + break; + + case REG_MULTI_SZ: + { + char * str = (char *) pbuffer; + char * iter; + + str[cb_alloc - 1] = '\0'; + str[cb_alloc - 2] = '\0'; + + for (iter = str; *iter;) { + size_t len = strlen(iter); + + iter += len; + if (iter[1] != '\0') + *iter++ = *separator; + else + break; + } + + if (pbuffer != static_buffer) { + ret_string = str; + pbuffer = NULL; + } else { + ret_string = strdup(str); + } + } + break; + + default: + if (context) + heim_set_error_message(context, 0, + "Unexpected type while reading registry value %s", + valuename); + } + +done: + if (pbuffer != static_buffer && pbuffer != NULL) + free(pbuffer); + + return ret_string; +} + +/** + * Parse a registry value as a configuration value + * + * @see parse_reg_value_as_string() + */ +static heim_error_code +parse_reg_value(heim_context context, + HKEY key, const char * valuename, + DWORD type, DWORD cbdata, heim_config_section ** parent) +{ + char *reg_string = NULL; + heim_config_section *value; + heim_error_code code = 0; + + reg_string = heim_parse_reg_value_as_string(context, key, valuename, type, cbdata); + + if (reg_string == NULL) + return HEIM_ERR_CONFIG_BADFORMAT; + + value = heim_config_get_entry(parent, valuename, heim_config_string); + if (value == NULL) { + code = ENOMEM; + goto done; + } + + if (value->u.string != NULL) + free(value->u.string); + + value->u.string = reg_string; + reg_string = NULL; + +done: + if (reg_string != NULL) + free(reg_string); + + return code; +} + +static heim_error_code +parse_reg_values(heim_context context, + HKEY key, + heim_config_section ** parent) +{ + DWORD index; + LONG rcode; + + for (index = 0; ; index ++) { + char name[16385]; + DWORD cch = sizeof(name)/sizeof(name[0]); + DWORD type; + DWORD cbdata = 0; + heim_error_code code; + + rcode = RegEnumValue(key, index, name, &cch, NULL, + &type, NULL, &cbdata); + if (rcode != ERROR_SUCCESS) + break; + + if (cbdata == 0) + continue; + + code = parse_reg_value(context, key, name, type, cbdata, parent); + if (code != 0) + return code; + } + + return 0; +} + +static heim_error_code +parse_reg_subkeys(heim_context context, + HKEY key, + heim_config_section ** parent) +{ + DWORD index; + LONG rcode; + + for (index = 0; ; index ++) { + HKEY subkey = NULL; + char name[256]; + DWORD cch = sizeof(name)/sizeof(name[0]); + heim_config_section *section = NULL; + heim_error_code code; + + rcode = RegEnumKeyEx(key, index, name, &cch, NULL, NULL, NULL, NULL); + if (rcode != ERROR_SUCCESS) + break; + + rcode = RegOpenKeyEx(key, name, 0, KEY_READ, &subkey); + if (rcode != ERROR_SUCCESS) + continue; + + section = heim_config_get_entry(parent, name, heim_config_list); + if (section == NULL) { + RegCloseKey(subkey); + return ENOMEM; + } + + code = parse_reg_values(context, subkey, §ion->u.list); + if (code) { + RegCloseKey(subkey); + return code; + } + + code = parse_reg_subkeys(context, subkey, §ion->u.list); + if (code) { + RegCloseKey(subkey); + return code; + } + + RegCloseKey(subkey); + } + + return 0; +} + +static heim_error_code +parse_reg_root(heim_context context, + HKEY key, + heim_config_section ** parent) +{ + heim_config_section *libdefaults = NULL; + heim_error_code code = 0; + + libdefaults = heim_config_get_entry(parent, "libdefaults", heim_config_list); + if (libdefaults == NULL) + return heim_enomem(context); + + code = parse_reg_values(context, key, &libdefaults->u.list); + if (code) + return code; + + return parse_reg_subkeys(context, key, parent); +} + +static heim_error_code +load_config_from_regpath(heim_context context, + HKEY hk_root, + const char* key_path, + heim_config_section ** res) +{ + HKEY key = NULL; + LONG rcode; + heim_error_code code = 0; + + rcode = RegOpenKeyEx(hk_root, key_path, 0, KEY_READ, &key); + if (rcode == ERROR_SUCCESS) { + code = parse_reg_root(context, key, res); + RegCloseKey(key); + key = NULL; + } + + return code; +} + +/** + * Load configuration from registry + * + * The registry keys 'HKCU\Software\Heimdal' and + * 'HKLM\Software\Heimdal' are treated as krb5.conf files. Each + * registry key corresponds to a configuration section (or bound list) + * and each value in a registry key is treated as a bound value. The + * set of values that are directly under the Heimdal key are treated + * as if they were defined in the [libdefaults] section. + * + * @see parse_reg_value() for details about how each type of value is handled. + */ +heim_error_code +heim_load_config_from_registry(heim_context context, + const char *path0, + const char *path1, + heim_config_section **res) +{ + heim_error_code code; + + if (!path0 && !path1) + return EINVAL; + + if (path0) { + code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE, + path0, res); + if (code) + return code; + } + + if (path1) { + code = load_config_from_regpath(context, HKEY_LOCAL_MACHINE, + path1, res); + if (code) + return code; + } + + if (path0) { + code = load_config_from_regpath(context, HKEY_CURRENT_USER, + path0, res); + if (code) + return code; + } + + if (path0) { + code = load_config_from_regpath(context, HKEY_CURRENT_USER, + path1, res); + if (code) + return code; + } + return 0; +} |