/* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * @file type_defs.c * @brief Definitions for various low-level configuration types. * * This module creates a number of var_type_def_t objects, to be used by * typedvar.c in manipulating variables. * * The types here are common types that can be implemented with Tor's * low-level functionality. To define new types, see var_type_def_st.h. **/ #include "orconfig.h" #include "lib/conf/conftypes.h" #include "lib/confmgt/typedvar.h" #include "lib/confmgt/type_defs.h" #include "lib/confmgt/unitparse.h" #include "lib/cc/compat_compiler.h" #include "lib/conf/conftypes.h" #include "lib/container/smartlist.h" #include "lib/encoding/confline.h" #include "lib/encoding/time_fmt.h" #include "lib/log/escape.h" #include "lib/log/log.h" #include "lib/log/util_bug.h" #include "lib/malloc/malloc.h" #include "lib/string/parse_int.h" #include "lib/string/printf.h" #include "lib/confmgt/var_type_def_st.h" #include #include ////// // CONFIG_TYPE_STRING // CONFIG_TYPE_FILENAME // // These two types are the same for now, but they have different names. ////// static int string_parse(void *target, const char *value, char **errmsg, const void *params) { (void)params; (void)errmsg; char **p = (char**)target; *p = tor_strdup(value); return 0; } static char * string_encode(const void *value, const void *params) { (void)params; const char **p = (const char**)value; return *p ? tor_strdup(*p) : NULL; } static void string_clear(void *value, const void *params) { (void)params; char **p = (char**)value; tor_free(*p); // sets *p to NULL. } static const var_type_fns_t string_fns = { .parse = string_parse, .encode = string_encode, .clear = string_clear, }; ///// // CONFIG_TYPE_INT // CONFIG_TYPE_POSINT // // These types are implemented as int, possibly with a restricted range. ///// typedef struct int_type_params_t { int minval; int maxval; } int_parse_params_t; static const int_parse_params_t INT_PARSE_UNRESTRICTED = { .minval = INT_MIN, .maxval = INT_MAX, }; static const int_parse_params_t INT_PARSE_POSINT = { .minval = 0, .maxval = INT_MAX, }; static int int_parse(void *target, const char *value, char **errmsg, const void *params) { const int_parse_params_t *pp; if (params) { pp = params; } else { pp = &INT_PARSE_UNRESTRICTED; } int *p = target; int ok=0; *p = (int)tor_parse_long(value, 10, pp->minval, pp->maxval, &ok, NULL); if (!ok) { tor_asprintf(errmsg, "Integer %s is malformed or out of bounds.", value); return -1; } return 0; } static char * int_encode(const void *value, const void *params) { (void)params; int v = *(int*)value; char *result; tor_asprintf(&result, "%d", v); return result; } static void int_clear(void *value, const void *params) { (void)params; *(int*)value = 0; } static bool int_ok(const void *value, const void *params) { const int_parse_params_t *pp = params; if (pp) { int v = *(int*)value; return pp->minval <= v && v <= pp->maxval; } else { return true; } } static const var_type_fns_t int_fns = { .parse = int_parse, .encode = int_encode, .clear = int_clear, .ok = int_ok, }; ///// // CONFIG_TYPE_UINT64 // // This type is an unrestricted u64. ///// static int uint64_parse(void *target, const char *value, char **errmsg, const void *params) { (void)params; (void)errmsg; uint64_t *p = target; int ok=0; *p = tor_parse_uint64(value, 10, 0, UINT64_MAX, &ok, NULL); if (!ok) { tor_asprintf(errmsg, "Integer %s is malformed or out of bounds.", value); return -1; } return 0; } static char * uint64_encode(const void *value, const void *params) { (void)params; uint64_t v = *(uint64_t*)value; char *result; tor_asprintf(&result, "%"PRIu64, v); return result; } static void uint64_clear(void *value, const void *params) { (void)params; *(uint64_t*)value = 0; } static const var_type_fns_t uint64_fns = { .parse = uint64_parse, .encode = uint64_encode, .clear = uint64_clear, }; ///// // CONFIG_TYPE_INTERVAL // CONFIG_TYPE_MSEC_INTERVAL // CONFIG_TYPE_MEMUNIT // // These types are implemented using the config_parse_units() function. // The intervals are stored as ints, whereas memory units are stored as // uint64_ts. ///// static int units_parse_u64(void *target, const char *value, char **errmsg, const void *params) { const unit_table_t *table = params; tor_assert(table); uint64_t *v = (uint64_t*)target; int ok=1; *v = config_parse_units(value, table, &ok); if (!ok) { *errmsg = tor_strdup("Provided value is malformed or out of bounds."); return -1; } return 0; } static int units_parse_int(void *target, const char *value, char **errmsg, const void *params) { const unit_table_t *table = params; tor_assert(table); int *v = (int*)target; int ok=1; uint64_t u64 = config_parse_units(value, table, &ok); if (!ok) { *errmsg = tor_strdup("Provided value is malformed or out of bounds."); return -1; } if (u64 > INT_MAX) { tor_asprintf(errmsg, "Provided value %s is too large", value); return -1; } *v = (int) u64; return 0; } static bool units_ok_int(const void *value, const void *params) { (void)params; int v = *(int*)value; return v >= 0; } static const var_type_fns_t memunit_fns = { .parse = units_parse_u64, .encode = uint64_encode, // doesn't use params .clear = uint64_clear, // doesn't use params }; static const var_type_fns_t interval_fns = { .parse = units_parse_int, .encode = int_encode, // doesn't use params .clear = int_clear, // doesn't use params, .ok = units_ok_int // can't use int_ok, since that expects int params. }; ///// // CONFIG_TYPE_DOUBLE // // This is a nice simple double. ///// static int double_parse(void *target, const char *value, char **errmsg, const void *params) { (void)params; (void)errmsg; double *v = (double*)target; // XXXX This is the preexisting behavior, but we should detect errors here. *v = atof(value); return 0; } static char * double_encode(const void *value, const void *params) { (void)params; double v = *(double*)value; char *result; tor_asprintf(&result, "%f", v); return result; } static void double_clear(void *value, const void *params) { (void)params; double *v = (double *)value; *v = 0.0; } static const var_type_fns_t double_fns = { .parse = double_parse, .encode = double_encode, .clear = double_clear, }; ///// // CONFIG_TYPE_BOOL // CONFIG_TYPE_AUTOBOOL // // These types are implemented as a case-insensitive string-to-integer // mapping. ///// typedef struct enumeration_table_t { const char *name; int value; } enumeration_table_t; static int enum_parse(void *target, const char *value, char **errmsg, const void *params) { const enumeration_table_t *table = params; int *p = (int *)target; for (; table->name; ++table) { if (!strcasecmp(value, table->name)) { *p = table->value; return 0; } } tor_asprintf(errmsg, "Unrecognized value %s.", value); return -1; } static char * enum_encode(const void *value, const void *params) { int v = *(const int*)value; const enumeration_table_t *table = params; for (; table->name; ++table) { if (v == table->value) return tor_strdup(table->name); } return NULL; // error. } static void enum_clear(void *value, const void *params) { int *p = (int*)value; const enumeration_table_t *table = params; tor_assert(table->name); *p = table->value; } static bool enum_ok(const void *value, const void *params) { int v = *(const int*)value; const enumeration_table_t *table = params; for (; table->name; ++table) { if (v == table->value) return true; } return false; } static const enumeration_table_t enum_table_bool[] = { { "0", 0 }, { "1", 1 }, { NULL, 0 }, }; static const enumeration_table_t enum_table_autobool[] = { { "0", 0 }, { "1", 1 }, { "auto", -1 }, { NULL, 0 }, }; static const var_type_fns_t enum_fns = { .parse = enum_parse, .encode = enum_encode, .clear = enum_clear, .ok = enum_ok, }; ///// // CONFIG_TYPE_ISOTIME // // This is a time_t, encoded in ISO8601 format. ///// static int time_parse(void *target, const char *value, char **errmsg, const void *params) { (void) params; time_t *p = target; if (parse_iso_time(value, p) < 0) { tor_asprintf(errmsg, "Invalid time %s", escaped(value)); return -1; } return 0; } static char * time_encode(const void *value, const void *params) { (void)params; time_t v = *(const time_t *)value; char *result = tor_malloc(ISO_TIME_LEN+1); format_iso_time(result, v); return result; } static void time_clear(void *value, const void *params) { (void)params; time_t *t = value; *t = 0; } static const var_type_fns_t time_fns = { .parse = time_parse, .encode = time_encode, .clear = time_clear, }; ///// // CONFIG_TYPE_CSV // // This type is a comma-separated list of strings, stored in a smartlist_t. // An empty list may be encoded either as an empty smartlist, or as NULL. ///// static int csv_parse(void *target, const char *value, char **errmsg, const void *params) { (void)params; (void)errmsg; smartlist_t **sl = (smartlist_t**)target; *sl = smartlist_new(); smartlist_split_string(*sl, value, ",", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); return 0; } static char * csv_encode(const void *value, const void *params) { (void)params; const smartlist_t *sl = *(const smartlist_t **)value; if (! sl) return tor_strdup(""); return smartlist_join_strings(*(smartlist_t**)value, ",", 0, NULL); } static void csv_clear(void *value, const void *params) { (void)params; smartlist_t **sl = (smartlist_t**)value; if (!*sl) return; SMARTLIST_FOREACH(*sl, char *, cp, tor_free(cp)); smartlist_free(*sl); // clears pointer. } static const var_type_fns_t csv_fns = { .parse = csv_parse, .encode = csv_encode, .clear = csv_clear, }; ///// // CONFIG_TYPE_CSV_INTERVAL // // This type used to be a list of time intervals, used to determine a download // schedule. Now, only the first interval counts: everything after the first // comma is discarded. ///// static int legacy_csv_interval_parse(void *target, const char *value, char **errmsg, const void *params) { (void)params; /* We used to have entire smartlists here. But now that all of our * download schedules use exponential backoff, only the first part * matters. */ const char *comma = strchr(value, ','); const char *val = value; char *tmp = NULL; if (comma) { tmp = tor_strndup(val, comma - val); val = tmp; } int rv = units_parse_int(target, val, errmsg, &time_units); tor_free(tmp); return rv; } static const var_type_fns_t legacy_csv_interval_fns = { .parse = legacy_csv_interval_parse, .encode = int_encode, .clear = int_clear, }; ///// // CONFIG_TYPE_LINELIST // CONFIG_TYPE_LINELIST_S // CONFIG_TYPE_LINELIST_V // // A linelist is a raw config_line_t list. Order is preserved. // // The LINELIST type is used for homogeneous lists, where all the lines // have the same key. // // The LINELIST_S and LINELIST_V types are used for the case where multiple // lines of different keys are kept in a single list, to preserve their // relative order. The unified list is stored as a "virtual" variable whose // type is LINELIST_V; the individual sublists are treated as variables of // type LINELIST_S. // // A linelist may be fragile or non-fragile. Assigning a line to a fragile // linelist replaces the list with the line. If the line has the "APPEND" // command set on it, or if the list is non-fragile, the line is appended. // Either way, the new list is non-fragile. ///// static int linelist_kv_parse(void *target, const struct config_line_t *line, char **errmsg, const void *params) { (void)params; (void)errmsg; config_line_t **lines = target; if (*lines && (*lines)->fragile) { if (line->command == CONFIG_LINE_APPEND) { (*lines)->fragile = 0; } else { config_free_lines(*lines); // sets it to NULL } } config_line_append(lines, line->key, line->value); return 0; } static int linelist_kv_virt_noparse(void *target, const struct config_line_t *line, char **errmsg, const void *params) { (void)target; (void)line; (void)params; *errmsg = tor_strdup("Cannot assign directly to virtual option."); return -1; } static struct config_line_t * linelist_kv_encode(const char *key, const void *value, const void *params) { (void)key; (void)params; config_line_t *lines = *(config_line_t **)value; return config_lines_dup(lines); } static struct config_line_t * linelist_s_kv_encode(const char *key, const void *value, const void *params) { (void)params; config_line_t *lines = *(config_line_t **)value; return config_lines_dup_and_filter(lines, key); } static void linelist_clear(void *target, const void *params) { (void)params; config_line_t **lines = target; config_free_lines(*lines); // sets it to NULL } static bool linelist_eq(const void *a, const void *b, const void *params) { (void)params; const config_line_t *lines_a = *(const config_line_t **)a; const config_line_t *lines_b = *(const config_line_t **)b; return config_lines_eq(lines_a, lines_b); } static int linelist_copy(void *target, const void *value, const void *params) { (void)params; config_line_t **ptr = (config_line_t **)target; const config_line_t *val = *(const config_line_t **)value; config_free_lines(*ptr); *ptr = config_lines_dup(val); return 0; } static const var_type_fns_t linelist_fns = { .kv_parse = linelist_kv_parse, .kv_encode = linelist_kv_encode, .clear = linelist_clear, .eq = linelist_eq, .copy = linelist_copy, }; static const var_type_fns_t linelist_v_fns = { .kv_parse = linelist_kv_virt_noparse, .kv_encode = linelist_kv_encode, .clear = linelist_clear, .eq = linelist_eq, .copy = linelist_copy, }; static const var_type_fns_t linelist_s_fns = { .kv_parse = linelist_kv_parse, .kv_encode = linelist_s_kv_encode, .clear = linelist_clear, .eq = linelist_eq, .copy = linelist_copy, }; ///// // CONFIG_TYPE_ROUTERSET // // XXXX This type is not implemented here, since routerset_t is not available // XXXX to this module. ///// ///// // CONFIG_TYPE_OBSOLETE // // Used to indicate an obsolete option. // // XXXX This is not a type, and should be handled at a higher level of // XXXX abstraction. ///// static int ignore_parse(void *target, const char *value, char **errmsg, const void *params) { (void)target; (void)value; (void)errmsg; (void)params; // XXXX move this to a higher level, once such a level exists. log_warn(LD_GENERAL, "Skipping obsolete configuration option."); return 0; } static char * ignore_encode(const void *value, const void *params) { (void)value; (void)params; return NULL; } static const var_type_fns_t ignore_fns = { .parse = ignore_parse, .encode = ignore_encode, }; /** * Table mapping conf_type_t values to var_type_def_t objects. **/ static const var_type_def_t type_definitions_table[] = { [CONFIG_TYPE_STRING] = { "String", &string_fns, NULL }, [CONFIG_TYPE_FILENAME] = { "Filename", &string_fns, NULL }, [CONFIG_TYPE_INT] = { "SignedInteger", &int_fns, &INT_PARSE_UNRESTRICTED }, [CONFIG_TYPE_POSINT] = { "Integer", &int_fns, &INT_PARSE_POSINT }, [CONFIG_TYPE_UINT64] = { "Integer", &uint64_fns, NULL, }, [CONFIG_TYPE_MEMUNIT] = { "DataSize", &memunit_fns, &memory_units }, [CONFIG_TYPE_INTERVAL] = { "TimeInterval", &interval_fns, &time_units }, [CONFIG_TYPE_MSEC_INTERVAL] = { "TimeMsecInterval", &interval_fns, &time_msec_units }, [CONFIG_TYPE_DOUBLE] = { "Float", &double_fns, NULL }, [CONFIG_TYPE_BOOL] = { "Boolean", &enum_fns, &enum_table_bool }, [CONFIG_TYPE_AUTOBOOL] = { "Boolean+Auto", &enum_fns, &enum_table_autobool }, [CONFIG_TYPE_ISOTIME] = { "Time", &time_fns, NULL }, [CONFIG_TYPE_CSV] = { "CommaList", &csv_fns, NULL }, [CONFIG_TYPE_CSV_INTERVAL] = { "TimeInterval", &legacy_csv_interval_fns, NULL }, [CONFIG_TYPE_LINELIST] = { "LineList", &linelist_fns, NULL }, [CONFIG_TYPE_LINELIST_S] = { "Dependent", &linelist_s_fns, NULL }, [CONFIG_TYPE_LINELIST_V] = { "Virtual", &linelist_v_fns, NULL }, [CONFIG_TYPE_OBSOLETE] = { "Obsolete", &ignore_fns, NULL } }; /** * Return a pointer to the var_type_def_t object for the given * config_type_t value, or NULL if no such type definition exists. **/ const var_type_def_t * lookup_type_def(config_type_t type) { int t = type; tor_assert(t >= 0); if (t >= (int)ARRAY_LENGTH(type_definitions_table)) return NULL; return &type_definitions_table[t]; }