Home | History | Annotate | Line # | Download | only in krb5
      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  */
     91 KRB5_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  */
    217 KRB5_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  */
    256 KRB5_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 
    366 have_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 
    451 done:
    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  */
    463 static krb5_error_code
    464 parse_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 
    489 done:
    490     if (reg_string != NULL)
    491         free(reg_string);
    492 
    493     return code;
    494 }
    495 
    496 static krb5_error_code
    497 parse_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 
    527 static krb5_error_code
    528 parse_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, &section->u.list);
    557         if (code) {
    558             RegCloseKey(subkey);
    559             return code;
    560         }
    561 
    562         code = parse_reg_subkeys(context, subkey, &section->u.list);
    563         if (code) {
    564             RegCloseKey(subkey);
    565             return code;
    566         }
    567 
    568         RegCloseKey(subkey);
    569     }
    570 
    571     return 0;
    572 }
    573 
    574 static krb5_error_code
    575 parse_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 
    593 static krb5_error_code
    594 load_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  */
    625 KRB5_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