Files
heimdal/lib/base/config_reg.c
Nicolas Williams ea90ca8666 Move some infra bits of lib/krb5/ to lib/base/ (2)
This is the second of two commits in a series that must be picked together.

This series of two commits moves parts of lib/krb5/ infrastructure
functionality to lib/base/, leaving behind wrappers.

Some parts of libkrb5 are entirely generic or easily made so, and could
be useful in various parts of Heimdal that are not specific to the krb5
API, such as:

 - lib/gssapi/  (especially since the integration of NegoEx)
 - lib/hx509/
 - bx509d       (which should really move out of kdc/)

For the above we need to move these bits of lib/krb5/:

 - lib/krb5/config_file.c   (all of it, leaving forwardings behind)
 - lib/krb5/config_reg.c    (all of it)
 - lib/krb5/plugin.c        (all of it, leaving forwardings behind)
 - lib/krb5/log.c           (all of it, ditto)
 - lib/krb5/heim_err.et     (all of it)

And because of those two, these too must also move:

 - lib/krb5/expand_path.c   (all of it, leaving forwardings behind)
 - lib/krb5/warn.c          (just the warning functions, ditto)

The changes to the moved files are mostly quite straightforward and are
best reviewed with --word-diff=color.

We're also creating a heim_context and a heim API to go with it.  But
it's as thin as possible, with as little state as necessary to enable
this move.  Functions for dealing with error messages use callbacks.

Moving plugin.c does have one knock-on effect on all users of the old
krb5 plugin API (which remains), which is that a global search and
replace of struct krb5_plugin_data to struct heim_plugin_data was
needed, though the layout and size of that structure doesn't change, so
the ABI doesn't either.

As well, we now build lib/vers/ and lib/com_err/ before lib/base/ so as
to be able to move lib/krb5/heim_err.et to lib/base/ so that we can make
use of HEIM_ERR_* in lib/base/, specifically in the files that moved.

Once this is all done we'll be able to use config files and plugins in
lib/hx509/, we'll be able to move bx509d out of kdc/, and so on.

Most if not all of the new functions in lib/base/ are Heimdal-private,
thus calling conventions for them are not declared.

Status:

 - builds and passes CIs (Travis, Appveyor)
 - ran make check-valgrind and no new leaks or other memory errors
 - ready for review

HOW TO REVIEW:

     $ # Review file moves:
     $ git log --stat -n1 HEAD^
     $
     $ # Review changes to moved files using --word-diff=color
     $ git log -p -b -w --word-diff=color HEAD^..HEAD   \
               lib/base/config_file.c                   \
               lib/base/config_reg.c                    \
               lib/base/expand_path.c                   \
               lib/base/warn.c                          \
               lib/krb5/config_file.c                   \
               lib/krb5/config_reg.c                    \
               lib/krb5/expand_path.c                   \
               lib/krb5/warn.c
     $
     $ # Review the whole thing, possibly adding -b and/or -w, and
     $ # maybe --word-diff=color:
     $ git log -p origin/master..HEAD
     $ git log -p -b -w origin/master..HEAD
     $ git log -p -b -w --word-diff=color origin/master..HEAD

TBD (future commits):

 - make lib/gssapi use the new heimbase functions
 - move kx509/bx509d common code to lib/hx509/ or other approp. location
 - move bx509d out of kdc/
2020-03-02 10:56:13 -06:00

660 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;
DWORD dwData;
BYTE static_buffer[16384];
BYTE *pbuffer = &static_buffer[0];
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, &section->u.list);
if (code) {
RegCloseKey(subkey);
return code;
}
code = parse_reg_subkeys(context, subkey, &section->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;
}