Line data Source code
1 : /* Copyright (c) 2001 Matej Pfajfar.
2 : * Copyright (c) 2001-2004, Roger Dingledine.
3 : * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4 : * Copyright (c) 2007-2021, The Tor Project, Inc. */
5 : /* See LICENSE for licensing information */
6 :
7 : /**
8 : * \file confline.c
9 : *
10 : * \brief Functions to manipulate a linked list of key-value pairs, of the
11 : * type used in Tor's configuration files.
12 : *
13 : * Tor uses the config_line_t type and its associated serialized format for
14 : * human-readable key-value pairs in many places, including its configuration,
15 : * its state files, its consensus cache, and so on.
16 : **/
17 :
18 : #include "lib/encoding/confline.h"
19 : #include "lib/encoding/cstring.h"
20 : #include "lib/log/log.h"
21 : #include "lib/log/util_bug.h"
22 : #include "lib/malloc/malloc.h"
23 : #include "lib/string/compat_ctype.h"
24 : #include "lib/string/compat_string.h"
25 : #include "lib/string/util_string.h"
26 :
27 : #include <string.h>
28 :
29 : /** Helper: allocate a new configuration option mapping 'key' to 'val',
30 : * append it to *<b>lst</b>. */
31 : void
32 1404 : config_line_append(config_line_t **lst,
33 : const char *key,
34 : const char *val)
35 : {
36 1404 : tor_assert(lst);
37 :
38 1404 : config_line_t *newline;
39 :
40 1404 : newline = tor_malloc_zero(sizeof(config_line_t));
41 1404 : newline->key = tor_strdup(key);
42 1404 : newline->value = tor_strdup(val);
43 1404 : newline->next = NULL;
44 2194 : while (*lst)
45 790 : lst = &((*lst)->next);
46 :
47 1404 : (*lst) = newline;
48 1404 : }
49 :
50 : /** Helper: allocate a new configuration option mapping 'key' to 'val',
51 : * and prepend it to *<b>lst</b> */
52 : void
53 482 : config_line_prepend(config_line_t **lst,
54 : const char *key,
55 : const char *val)
56 : {
57 482 : tor_assert(lst);
58 :
59 482 : config_line_t *newline;
60 :
61 482 : newline = tor_malloc_zero(sizeof(config_line_t));
62 482 : newline->key = tor_strdup(key);
63 482 : newline->value = tor_strdup(val);
64 482 : newline->next = *lst;
65 482 : *lst = newline;
66 482 : }
67 :
68 : /** Return the first line in <b>lines</b> whose key is exactly <b>key</b>, or
69 : * NULL if no such key exists.
70 : *
71 : * (In options parsing, this is for handling commandline-only options only;
72 : * other options should be looked up in the appropriate data structure.) */
73 : const config_line_t *
74 8518 : config_line_find(const config_line_t *lines,
75 : const char *key)
76 : {
77 8518 : const config_line_t *cl;
78 37740 : for (cl = lines; cl; cl = cl->next) {
79 34593 : if (!strcmp(cl->key, key))
80 5371 : return cl;
81 : }
82 : return NULL;
83 : }
84 :
85 : /** As config_line_find(), but perform a case-insensitive comparison. */
86 : const config_line_t *
87 0 : config_line_find_case(const config_line_t *lines,
88 : const char *key)
89 : {
90 0 : const config_line_t *cl;
91 0 : for (cl = lines; cl; cl = cl->next) {
92 0 : if (!strcasecmp(cl->key, key))
93 0 : return cl;
94 : }
95 : return NULL;
96 : }
97 :
98 : /** Auxiliary function that does all the work of config_get_lines.
99 : * <b>recursion_level</b> is the count of how many nested %includes we have.
100 : * <b>opened_lst</b> will have a list of opened files if provided.
101 : * Returns the a pointer to the last element of the <b>result</b> in
102 : * <b>last</b>. */
103 : int
104 1512 : config_get_lines_aux(const char *string, config_line_t **result, int extended,
105 : int allow_include, int *has_include,
106 : struct smartlist_t *opened_lst, int recursion_level,
107 : config_line_t **last,
108 : include_handler_fn handle_include)
109 : {
110 1512 : config_line_t *list = NULL, **next, *list_last = NULL;
111 1512 : char *k, *v;
112 1512 : const char *parse_err;
113 1512 : int include_used = 0;
114 :
115 1512 : if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) {
116 1 : log_warn(LD_CONFIG, "Error while parsing configuration: more than %d "
117 : "nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL);
118 1 : return -1;
119 : }
120 :
121 : next = &list;
122 4365 : do {
123 4365 : k = v = NULL;
124 4365 : string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err);
125 4365 : if (!string) {
126 6 : log_warn(LD_CONFIG, "Error while parsing configuration: %s",
127 : parse_err?parse_err:"<unknown>");
128 3 : config_free_lines(list);
129 3 : tor_free(k);
130 3 : tor_free(v);
131 3 : return -1;
132 : }
133 4362 : if (k && v) {
134 3724 : unsigned command = CONFIG_LINE_NORMAL;
135 3724 : if (extended) {
136 2179 : if (k[0] == '+') {
137 4 : char *k_new = tor_strdup(k+1);
138 4 : tor_free(k);
139 4 : k = k_new;
140 4 : command = CONFIG_LINE_APPEND;
141 2175 : } else if (k[0] == '/') {
142 6 : char *k_new = tor_strdup(k+1);
143 6 : tor_free(k);
144 6 : k = k_new;
145 6 : tor_free(v);
146 6 : v = tor_strdup("");
147 6 : command = CONFIG_LINE_CLEAR;
148 : }
149 : }
150 :
151 3724 : if (allow_include && !strcmp(k, "%include") && handle_include) {
152 125 : tor_free(k);
153 125 : include_used = 1;
154 125 : log_notice(LD_CONFIG, "Processing configuration path \"%s\" at "
155 : "recursion level %d.", v, recursion_level);
156 :
157 125 : config_line_t *include_list;
158 125 : if (handle_include(v, recursion_level, extended, &include_list,
159 : &list_last, opened_lst) < 0) {
160 35 : log_warn(LD_CONFIG, "Error reading included configuration "
161 : "file or directory: \"%s\".", v);
162 35 : config_free_lines(list);
163 35 : tor_free(v);
164 35 : return -1;
165 : }
166 90 : *next = include_list;
167 90 : if (list_last)
168 78 : next = &list_last->next;
169 90 : tor_free(v);
170 : } else {
171 : /* This list can get long, so we keep a pointer to the end of it
172 : * rather than using config_line_append over and over and getting
173 : * n^2 performance. */
174 3599 : *next = tor_malloc_zero(sizeof(**next));
175 3599 : (*next)->key = k;
176 3599 : (*next)->value = v;
177 3599 : (*next)->next = NULL;
178 3599 : (*next)->command = command;
179 3599 : list_last = *next;
180 3599 : next = &((*next)->next);
181 : }
182 : } else {
183 638 : tor_free(k);
184 638 : tor_free(v);
185 : }
186 4327 : } while (*string);
187 :
188 1473 : if (last) {
189 97 : *last = list_last;
190 : }
191 1473 : if (has_include) {
192 251 : *has_include = include_used;
193 : }
194 1473 : *result = list;
195 1473 : return 0;
196 : }
197 :
198 : /** Same as config_get_lines_include but does not allow %include */
199 : int
200 898 : config_get_lines(const char *string, config_line_t **result, int extended)
201 : {
202 898 : return config_get_lines_aux(string, result, extended, 0, NULL, NULL, 1,
203 : NULL, NULL);
204 : }
205 :
206 : /**
207 : * Free all the configuration lines on the linked list <b>front</b>.
208 : */
209 : void
210 782958 : config_free_lines_(config_line_t *front)
211 : {
212 782958 : config_line_t *tmp;
213 :
214 1024113 : while (front) {
215 241155 : tmp = front;
216 241155 : front = tmp->next;
217 :
218 241155 : tor_free(tmp->key);
219 241155 : tor_free(tmp->value);
220 241155 : tor_free(tmp);
221 : }
222 782958 : }
223 :
224 : /** Return a newly allocated deep copy of the lines in <b>inp</b>. */
225 : config_line_t *
226 13273 : config_lines_dup(const config_line_t *inp)
227 : {
228 13273 : return config_lines_dup_and_filter(inp, NULL);
229 : }
230 :
231 : /** Return a newly allocated deep copy of the lines in <b>inp</b>,
232 : * but only the ones whose keys begin with <b>key</b> (case-insensitive).
233 : * If <b>key</b> is NULL, do not filter. */
234 : config_line_t *
235 13278 : config_lines_dup_and_filter(const config_line_t *inp,
236 : const char *key)
237 : {
238 13278 : config_line_t *result = NULL;
239 13278 : config_line_t **next_out = &result;
240 16255 : while (inp) {
241 2977 : if (key && strcasecmpstart(inp->key, key)) {
242 5 : inp = inp->next;
243 5 : continue;
244 : }
245 2972 : *next_out = tor_malloc_zero(sizeof(config_line_t));
246 2972 : (*next_out)->key = tor_strdup(inp->key);
247 2972 : (*next_out)->value = tor_strdup(inp->value);
248 2972 : inp = inp->next;
249 2972 : next_out = &((*next_out)->next);
250 : }
251 13278 : (*next_out) = NULL;
252 13278 : return result;
253 : }
254 :
255 : /**
256 : * Given a linelist <b>inp</b> beginning with the key <b>header</b>, find the
257 : * next line with that key, and remove that instance and all following lines
258 : * from the list. Return the lines that were removed. Operate
259 : * case-insensitively.
260 : *
261 : * For example, if the header is "H", and <b>inp</b> contains "H, A, B, H, C,
262 : * H, D", this function will alter <b>inp</b> to contain only "H, A, B", and
263 : * return the elements "H, C, H, D" as a separate list.
264 : **/
265 : config_line_t *
266 38 : config_lines_partition(config_line_t *inp, const char *header)
267 : {
268 38 : if (BUG(inp == NULL))
269 0 : return NULL;
270 38 : if (BUG(strcasecmp(inp->key, header)))
271 0 : return NULL;
272 :
273 : /* Advance ptr until it points to the link to the next segment of this
274 : list. */
275 38 : config_line_t **ptr = &inp->next;
276 139 : while (*ptr && strcasecmp((*ptr)->key, header)) {
277 101 : ptr = &(*ptr)->next;
278 : }
279 38 : config_line_t *remainder = *ptr;
280 38 : *ptr = NULL;
281 38 : return remainder;
282 : }
283 :
284 : /** Return true iff a and b contain identical keys and values in identical
285 : * order. */
286 : int
287 5210 : config_lines_eq(const config_line_t *a, const config_line_t *b)
288 : {
289 5214 : while (a && b) {
290 5 : if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value))
291 : return 0;
292 4 : a = a->next;
293 4 : b = b->next;
294 : }
295 5209 : if (a || b)
296 162 : return 0;
297 : return 1;
298 : }
299 :
300 : /** Return the number of lines in <b>a</b> whose key is <b>key</b>. */
301 : int
302 9 : config_count_key(const config_line_t *a, const char *key)
303 : {
304 9 : int n = 0;
305 15 : while (a) {
306 6 : if (!strcasecmp(a->key, key)) {
307 3 : ++n;
308 : }
309 6 : a = a->next;
310 : }
311 9 : return n;
312 : }
313 :
314 : /** Given a string containing part of a configuration file or similar format,
315 : * advance past comments and whitespace and try to parse a single line. If we
316 : * parse a line successfully, set *<b>key_out</b> to a new string holding the
317 : * key portion and *<b>value_out</b> to a new string holding the value portion
318 : * of the line, and return a pointer to the start of the next line. If we run
319 : * out of data, return a pointer to the end of the string. If we encounter an
320 : * error, return NULL and set *<b>err_out</b> (if provided) to an error
321 : * message.
322 : */
323 : const char *
324 4414 : parse_config_line_from_str_verbose(const char *line, char **key_out,
325 : char **value_out,
326 : const char **err_out)
327 : {
328 : /*
329 : See torrc_format.txt for a description of the (silly) format this parses.
330 : */
331 4414 : const char *key, *val, *cp;
332 4414 : int continuation = 0;
333 :
334 4414 : tor_assert(key_out);
335 4414 : tor_assert(value_out);
336 :
337 4414 : *key_out = *value_out = NULL;
338 4414 : key = val = NULL;
339 : /* Skip until the first keyword. */
340 : while (1) {
341 5007 : while (TOR_ISSPACE(*line))
342 315 : ++line;
343 4692 : if (*line == '#') {
344 10220 : while (*line && *line != '\n')
345 9942 : ++line;
346 : } else {
347 : break;
348 : }
349 : }
350 :
351 4414 : if (!*line) { /* End of string? */
352 : *key_out = *value_out = NULL;
353 : return line;
354 : }
355 :
356 : /* Skip until the next space or \ followed by newline. */
357 53235 : key = line;
358 53235 : while (*line && !TOR_ISSPACE(*line) && *line != '#' &&
359 1 : ! (line[0] == '\\' && line[1] == '\n'))
360 49459 : ++line;
361 3776 : *key_out = tor_strndup(key, line-key);
362 :
363 : /* Skip until the value. */
364 7593 : while (*line == ' ' || *line == '\t')
365 3817 : ++line;
366 :
367 3776 : val = line;
368 :
369 : /* Find the end of the line. */
370 3776 : if (*line == '\"') { // XXX No continuation handling is done here
371 47 : if (!(line = unescape_string(line, value_out, NULL))) {
372 13 : if (err_out)
373 4 : *err_out = "Invalid escape sequence in quoted string";
374 13 : return NULL;
375 : }
376 40 : while (*line == ' ' || *line == '\t')
377 6 : ++line;
378 34 : if (*line == '\r' && *(++line) == '\n')
379 1 : ++line;
380 34 : if (*line && *line != '#' && *line != '\n') {
381 4 : if (err_out)
382 2 : *err_out = "Excess data after quoted string";
383 4 : return NULL;
384 : }
385 : } else {
386 : /* Look for the end of the line. */
387 59955 : while (*line && *line != '\n' && (*line != '#' || continuation)) {
388 56226 : if (*line == '\\' && line[1] == '\n') {
389 6 : continuation = 1;
390 6 : line += 2;
391 56220 : } else if (*line == '#') {
392 8 : do {
393 8 : ++line;
394 8 : } while (*line && *line != '\n');
395 1 : if (*line == '\n')
396 1 : ++line;
397 : } else {
398 56219 : ++line;
399 : }
400 : }
401 :
402 3729 : if (*line == '\n') {
403 3624 : cp = line++;
404 : } else {
405 3729 : cp = line;
406 : }
407 : /* Now back cp up to be the last nonspace character */
408 3960 : while (cp>val && TOR_ISSPACE(*(cp-1)))
409 231 : --cp;
410 :
411 3729 : tor_assert(cp >= val);
412 :
413 : /* Now copy out and decode the value. */
414 3729 : *value_out = tor_strndup(val, cp-val);
415 3729 : if (continuation) {
416 : char *v_out, *v_in;
417 : v_out = v_in = *value_out;
418 141 : while (*v_in) {
419 136 : if (*v_in == '#') {
420 8 : do {
421 8 : ++v_in;
422 8 : } while (*v_in && *v_in != '\n');
423 1 : if (*v_in == '\n')
424 1 : ++v_in;
425 135 : } else if (v_in[0] == '\\' && v_in[1] == '\n') {
426 6 : v_in += 2;
427 : } else {
428 129 : *v_out++ = *v_in++;
429 : }
430 : }
431 5 : *v_out = '\0';
432 : }
433 : }
434 :
435 3759 : if (*line == '#') {
436 174 : do {
437 174 : ++line;
438 174 : } while (*line && *line != '\n');
439 : }
440 3828 : while (TOR_ISSPACE(*line)) ++line;
441 :
442 : return line;
443 : }
|