659 lines
19 KiB
C
659 lines
19 KiB
C
/***********************************************************************
|
|
* 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;
|
|
}
|