Line data Source code
1 : /* Copyright (c) 2001-2004, Roger Dingledine.
2 : * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3 : * Copyright (c) 2007-2021, The Tor Project, Inc. */
4 : /* See LICENSE for licensing information */
5 :
6 : /*
7 : * Tests for confmgt.c's features that support multiple configuration
8 : * formats and configuration objects.
9 : */
10 :
11 : #define CONFMGT_PRIVATE
12 : #include "orconfig.h"
13 :
14 : #include "core/or/or.h"
15 : #include "lib/encoding/confline.h"
16 : #include "lib/confmgt/confmgt.h"
17 : #include "test/test.h"
18 : #include "test/log_test_helpers.h"
19 :
20 : /*
21 : * Set up a few objects: a pasture_cfg is toplevel; it has a llama_cfg and an
22 : * alpaca_cfg.
23 : */
24 :
25 : typedef struct {
26 : uint32_t magic;
27 : char *address;
28 : int opentopublic;
29 : config_suite_t *subobjs;
30 : } pasture_cfg_t;
31 :
32 : typedef struct {
33 : char *llamaname;
34 : int cuteness;
35 : uint32_t magic;
36 : int eats_meat; /* deprecated; llamas are never carnivorous. */
37 :
38 : char *description; // derived from other fields.
39 : } llama_cfg_t;
40 :
41 : typedef struct {
42 : uint32_t magic;
43 : int fuzziness;
44 : char *alpacaname;
45 : int n_wings; /* deprecated; alpacas don't have wings. */
46 :
47 : int square_fuzziness; /* Derived from fuzziness. */
48 : } alpaca_cfg_t;
49 :
50 : /*
51 : * Make the above into configuration objects.
52 : */
53 :
54 : static pasture_cfg_t pasture_cfg_t_dummy;
55 : static llama_cfg_t llama_cfg_t_dummy;
56 : static alpaca_cfg_t alpaca_cfg_t_dummy;
57 :
58 : #define PV(name, type, dflt) \
59 : CONFIG_VAR_ETYPE(pasture_cfg_t, #name, type, name, 0, dflt)
60 : #define LV(name, type, dflt) \
61 : CONFIG_VAR_ETYPE(llama_cfg_t, #name, type, name, 0, dflt)
62 : #define AV(name, type, dflt) \
63 : CONFIG_VAR_ETYPE(alpaca_cfg_t, #name, type, name, 0, dflt)
64 : static const config_var_t pasture_vars[] = {
65 : PV(address, STRING, NULL),
66 : PV(opentopublic, BOOL, "1"),
67 : END_OF_CONFIG_VARS
68 : };
69 : static const config_var_t llama_vars[] =
70 : {
71 : LV(llamaname, STRING, NULL),
72 : LV(eats_meat, BOOL, NULL),
73 : LV(cuteness, POSINT, "100"),
74 : END_OF_CONFIG_VARS
75 : };
76 : static const config_var_t alpaca_vars[] =
77 : {
78 : AV(alpacaname, STRING, NULL),
79 : AV(fuzziness, POSINT, "50"),
80 : AV(n_wings, POSINT, "0"),
81 : END_OF_CONFIG_VARS
82 : };
83 :
84 : static config_deprecation_t llama_deprecations[] = {
85 : { "eats_meat", "Llamas are herbivores." },
86 : {NULL,NULL}
87 : };
88 :
89 : static config_deprecation_t alpaca_deprecations[] = {
90 : { "n_wings", "Alpacas are quadrupeds." },
91 : {NULL,NULL}
92 : };
93 :
94 : static int clear_llama_cfg_called = 0;
95 : static void
96 12 : clear_llama_cfg(const config_mgr_t *mgr, void *llamacfg)
97 : {
98 12 : (void)mgr;
99 12 : llama_cfg_t *lc = llamacfg;
100 12 : tor_free(lc->description);
101 12 : ++clear_llama_cfg_called;
102 12 : }
103 :
104 : static config_abbrev_t llama_abbrevs[] = {
105 : { "gracia", "cuteness", 0, 0 },
106 : { "gentillesse", "cuteness", 0, 0 },
107 : { NULL, NULL, 0, 0 },
108 : };
109 :
110 : static int
111 3 : legacy_validate_pasture(const void *old_, void *obj, char **msg_out)
112 : {
113 3 : const pasture_cfg_t *old = old_;
114 3 : pasture_cfg_t *p = obj;
115 :
116 : // llamas can't find their way home if the letters are lowercase.
117 3 : if (p->address)
118 3 : tor_strupper(p->address);
119 :
120 3 : if (old && old->address &&
121 2 : (!p->address || strcmp(old->address, p->address))) {
122 1 : *msg_out = tor_strdup("You can't move a pasture.");
123 1 : return -1;
124 : }
125 :
126 : return 0;
127 : }
128 :
129 : static int
130 6 : validate_llama(const void *obj, char **msg_out)
131 : {
132 6 : const llama_cfg_t *llama = obj;
133 6 : tor_assert(llama->magic == 0x11aa11);
134 :
135 6 : if (! llama->llamaname || strlen(llama->llamaname) == 0) {
136 1 : *msg_out = tor_strdup("A llama has no name!?");
137 1 : return -1;
138 : }
139 :
140 5 : if (strspn(llama->llamaname, "0123456789") == strlen(llama->llamaname)) {
141 1 : *msg_out = tor_strdup("It is not a number; it is a free llama!");
142 1 : return -1;
143 : }
144 :
145 : return 0;
146 : }
147 :
148 : static int
149 3 : check_transition_alpaca(const void *old_, const void *new_, char **msg_out)
150 : {
151 3 : const alpaca_cfg_t *old_alpaca = old_;
152 3 : const alpaca_cfg_t *new_alpaca = new_;
153 :
154 3 : tor_assert(old_alpaca && new_alpaca);
155 3 : tor_assert(old_alpaca->magic == 0xa15aca);
156 3 : tor_assert(new_alpaca->magic == 0xa15aca);
157 :
158 3 : if (old_alpaca->fuzziness > new_alpaca->fuzziness) {
159 1 : *msg_out = tor_strdup("An alpaca only becomes more fuzzy over time.");
160 1 : return -1;
161 : }
162 :
163 : return 0;
164 : }
165 :
166 : static int
167 4 : post_normalize_llama(void *obj, char **msg_out)
168 : {
169 4 : (void)msg_out;
170 4 : llama_cfg_t *llama = obj;
171 4 : tor_assert(llama->magic == 0x11aa11);
172 4 : tor_assert(llama->llamaname); // we have already checked for a NULL name.
173 4 : tor_free(llama->description);
174 4 : tor_asprintf(&llama->description, "A llama called %s.", llama->llamaname);
175 4 : return 0;
176 : }
177 :
178 : static int
179 4 : pre_normalize_alpaca(void *obj, char **msg_out)
180 : {
181 4 : (void)msg_out;
182 4 : alpaca_cfg_t *alpaca = obj;
183 4 : tor_assert(alpaca->magic == 0xa15aca);
184 4 : alpaca->square_fuzziness = alpaca->fuzziness * alpaca->fuzziness;
185 4 : return 0;
186 : }
187 :
188 : static const config_format_t pasture_fmt = {
189 : sizeof(pasture_cfg_t),
190 : {
191 : "pasture_cfg_t",
192 : 8989,
193 : offsetof(pasture_cfg_t, magic)
194 : },
195 : .vars = pasture_vars,
196 : .has_config_suite = true,
197 : .config_suite_offset = offsetof(pasture_cfg_t, subobjs),
198 : .legacy_validate_fn = legacy_validate_pasture,
199 : };
200 :
201 : static const config_format_t llama_fmt = {
202 : sizeof(llama_cfg_t),
203 : {
204 : "llama_cfg_t",
205 : 0x11aa11,
206 : offsetof(llama_cfg_t, magic)
207 : },
208 : .vars = llama_vars,
209 : .deprecations = llama_deprecations,
210 : .abbrevs = llama_abbrevs,
211 : .clear_fn = clear_llama_cfg,
212 : .validate_fn = validate_llama,
213 : .post_normalize_fn = post_normalize_llama,
214 : };
215 :
216 : static const config_format_t alpaca_fmt = {
217 : sizeof(alpaca_cfg_t),
218 : {
219 : "alpaca_cfg_t",
220 : 0xa15aca,
221 : offsetof(alpaca_cfg_t, magic)
222 : },
223 : .vars = alpaca_vars,
224 : .deprecations = alpaca_deprecations,
225 : .pre_normalize_fn = pre_normalize_alpaca,
226 : .check_transition_fn = check_transition_alpaca,
227 : };
228 :
229 : #define LLAMA_IDX 0
230 : #define ALPACA_IDX 1
231 :
232 : static config_mgr_t *
233 7 : get_mgr(bool freeze)
234 : {
235 7 : config_mgr_t *mgr = config_mgr_new(&pasture_fmt);
236 7 : tt_int_op(LLAMA_IDX, OP_EQ, config_mgr_add_format(mgr, &llama_fmt));
237 7 : tt_int_op(ALPACA_IDX, OP_EQ, config_mgr_add_format(mgr, &alpaca_fmt));
238 7 : if (freeze)
239 7 : config_mgr_freeze(mgr);
240 : return mgr;
241 :
242 0 : done:
243 0 : config_mgr_free(mgr);
244 0 : return NULL;
245 : }
246 :
247 : static void
248 1 : test_confmgr_init(void *arg)
249 : {
250 1 : (void)arg;
251 1 : config_mgr_t *mgr = get_mgr(true);
252 1 : smartlist_t *vars = NULL;
253 1 : tt_ptr_op(mgr, OP_NE, NULL);
254 :
255 1 : vars = config_mgr_list_vars(mgr);
256 1 : tt_int_op(smartlist_len(vars), OP_EQ, 8); // 8 vars total.
257 :
258 1 : tt_str_op("cuteness", OP_EQ, config_find_option_name(mgr, "CUTENESS"));
259 1 : tt_str_op("cuteness", OP_EQ, config_find_option_name(mgr, "GRACIA"));
260 1 : smartlist_free(vars);
261 :
262 1 : vars = config_mgr_list_deprecated_vars(mgr); // 2 deprecated vars.
263 1 : tt_int_op(smartlist_len(vars), OP_EQ, 2);
264 1 : tt_assert(smartlist_contains_string(vars, "eats_meat"));
265 1 : tt_assert(smartlist_contains_string(vars, "n_wings"));
266 :
267 1 : tt_str_op("Llamas are herbivores.", OP_EQ,
268 : config_find_deprecation(mgr, "EATS_MEAT"));
269 1 : tt_str_op("Alpacas are quadrupeds.", OP_EQ,
270 : config_find_deprecation(mgr, "N_WINGS"));
271 :
272 1 : done:
273 1 : smartlist_free(vars);
274 1 : config_mgr_free(mgr);
275 1 : }
276 :
277 : static void
278 1 : test_confmgr_magic(void *args)
279 : {
280 1 : (void)args;
281 : // Every time we build a manager, it is supposed to get a different magic
282 : // number. Let's test that.
283 1 : config_mgr_t *mgr1 = get_mgr(true);
284 1 : config_mgr_t *mgr2 = get_mgr(true);
285 1 : config_mgr_t *mgr3 = get_mgr(true);
286 :
287 1 : pasture_cfg_t *p1 = NULL, *p2 = NULL, *p3 = NULL;
288 :
289 1 : tt_assert(mgr1);
290 1 : tt_assert(mgr2);
291 1 : tt_assert(mgr3);
292 :
293 1 : p1 = config_new(mgr1);
294 1 : p2 = config_new(mgr2);
295 1 : p3 = config_new(mgr3);
296 :
297 1 : tt_assert(p1);
298 1 : tt_assert(p2);
299 1 : tt_assert(p3);
300 :
301 : // By chance, two managers get the same magic with P=2^-32. Let's
302 : // make sure that at least two of them are different, so that our
303 : // odds of a false positive are 1/2^-64.
304 1 : tt_assert((p1->magic != p2->magic) || (p2->magic != p3->magic));
305 :
306 1 : done:
307 1 : config_free(mgr1, p1);
308 1 : config_free(mgr2, p2);
309 1 : config_free(mgr3, p3);
310 :
311 1 : config_mgr_free(mgr1);
312 1 : config_mgr_free(mgr2);
313 1 : config_mgr_free(mgr3);
314 1 : }
315 :
316 : static const char *simple_pasture =
317 : "LLamaname hugo\n"
318 : "Alpacaname daphne\n"
319 : "gentillesse 42\n"
320 : "address 123 Camelid ave\n";
321 :
322 : static void
323 1 : test_confmgr_parse(void *arg)
324 : {
325 1 : (void)arg;
326 1 : config_mgr_t *mgr = get_mgr(true);
327 1 : pasture_cfg_t *p = config_new(mgr);
328 1 : config_line_t *lines = NULL;
329 1 : char *msg = NULL;
330 :
331 1 : config_init(mgr, p); // set defaults.
332 :
333 1 : int r = config_get_lines(simple_pasture, &lines, 0);
334 1 : tt_int_op(r, OP_EQ, 0);
335 1 : r = config_assign(mgr, p, lines, 0, &msg);
336 1 : tt_int_op(r, OP_EQ, 0);
337 :
338 1 : tt_int_op(p->opentopublic, OP_EQ, 1);
339 1 : tt_str_op(p->address, OP_EQ, "123 Camelid ave");
340 :
341 : // We are using this API directly; modules outside confparse will, in the
342 : // future, not.
343 1 : const alpaca_cfg_t *ac = config_mgr_get_obj(mgr, p, ALPACA_IDX);
344 1 : const llama_cfg_t *lc = config_mgr_get_obj(mgr, p, LLAMA_IDX);
345 1 : tt_str_op(lc->llamaname, OP_EQ, "hugo");
346 1 : tt_str_op(ac->alpacaname, OP_EQ, "daphne");
347 1 : tt_int_op(lc->cuteness, OP_EQ, 42);
348 1 : tt_int_op(ac->fuzziness, OP_EQ, 50);
349 :
350 : // We set the description for the llama here, so that the clear function
351 : // can clear it. (Later we can do this in a verification function.)
352 1 : clear_llama_cfg_called = 0;
353 1 : llama_cfg_t *mut_lc = config_mgr_get_obj_mutable(mgr, p, LLAMA_IDX);
354 1 : mut_lc->description = tor_strdup("A llama named Hugo.");
355 1 : config_free(mgr, p);
356 1 : tt_int_op(clear_llama_cfg_called, OP_EQ, 1);
357 :
358 1 : done:
359 1 : config_free_lines(lines);
360 1 : config_free(mgr, p);
361 1 : config_mgr_free(mgr);
362 1 : tor_free(msg);
363 1 : }
364 :
365 : static void
366 1 : test_confmgr_dump(void *arg)
367 : {
368 1 : (void)arg;
369 1 : config_mgr_t *mgr = get_mgr(true);
370 1 : pasture_cfg_t *p = config_new(mgr);
371 1 : pasture_cfg_t *defaults = config_new(mgr);
372 1 : config_line_t *lines = NULL;
373 1 : char *msg = NULL;
374 1 : char *s = NULL;
375 :
376 1 : config_init(mgr, p); // set defaults.
377 1 : config_init(mgr, defaults); // set defaults.
378 :
379 1 : int r = config_get_lines(simple_pasture, &lines, 0);
380 1 : tt_int_op(r, OP_EQ, 0);
381 1 : r = config_assign(mgr, p, lines, 0, &msg);
382 1 : tt_int_op(r, OP_EQ, 0);
383 :
384 1 : s = config_dump(mgr, defaults, p, 1, 0);
385 1 : tt_str_op("address 123 Camelid ave\n"
386 : "alpacaname daphne\n"
387 : "cuteness 42\n"
388 : "llamaname hugo\n", OP_EQ, s);
389 :
390 1 : done:
391 1 : config_free_lines(lines);
392 1 : config_free(mgr, p);
393 1 : config_free(mgr, defaults);
394 1 : config_mgr_free(mgr);
395 :
396 1 : tor_free(msg);
397 1 : tor_free(s);
398 1 : }
399 :
400 : static pasture_cfg_t *
401 6 : parse_and_validate(config_mgr_t *mgr,
402 : const char *inp, const pasture_cfg_t *old, char **msg_out)
403 : {
404 6 : pasture_cfg_t *p = config_new(mgr);
405 6 : pasture_cfg_t *result = NULL;
406 6 : config_line_t *lines = NULL;
407 :
408 6 : config_init(mgr, p); // set defaults.
409 6 : int r = config_get_lines(inp, &lines, 0);
410 6 : tt_int_op(r, OP_EQ, 0);
411 6 : r = config_assign(mgr, p, lines, 0, msg_out);
412 6 : tt_int_op(r, OP_EQ, 0);
413 6 : tor_free(*msg_out); // sets it to NULL
414 6 : r = config_validate(mgr, old, p, msg_out);
415 6 : if (r < 0)
416 4 : goto done;
417 :
418 2 : tt_ptr_op(*msg_out, OP_EQ, NULL);
419 : result = p;
420 : p = NULL; // prevent free
421 6 : done:
422 6 : config_free(mgr, p);
423 6 : config_free_lines(lines);
424 6 : return result;
425 : }
426 :
427 : static void
428 1 : test_confmgr_validate(void *arg)
429 : {
430 1 : (void)arg;
431 1 : char *msg = NULL;
432 1 : config_mgr_t *mgr = get_mgr(true);
433 1 : pasture_cfg_t *p_orig, *p=NULL;
434 :
435 1 : p_orig = parse_and_validate(mgr, "Llamaname Quest\n"
436 : "Address 99 camelid way\n"
437 : "Fuzziness 8\n", NULL, &msg);
438 1 : tt_assert(p_orig);
439 :
440 : // Make sure normalization code was run.
441 1 : const alpaca_cfg_t *ac0 = config_mgr_get_obj(mgr, p_orig, ALPACA_IDX);
442 1 : const llama_cfg_t *lc0 = config_mgr_get_obj(mgr, p_orig, LLAMA_IDX);
443 1 : tt_int_op(ac0->fuzziness, OP_EQ, 8);
444 1 : tt_int_op(ac0->square_fuzziness, OP_EQ, 64);
445 1 : tt_str_op(lc0->description, OP_EQ, "A llama called Quest.");
446 1 : tt_str_op(p_orig->address, OP_EQ, "99 CAMELID WAY");
447 :
448 : // try a bad llamaname.
449 1 : p = parse_and_validate(mgr, "llamaname 123", p_orig, &msg);
450 1 : tt_assert(!p);
451 1 : tt_str_op(msg, OP_EQ, "It is not a number; it is a free llama!");
452 1 : tor_free(msg);
453 :
454 : // try a llamaname that would crash the post_normalize step, if it ran.
455 1 : p = parse_and_validate(mgr, "", p_orig, &msg);
456 1 : tt_assert(!p);
457 1 : tt_str_op(msg, OP_EQ, "A llama has no name!?");
458 1 : tor_free(msg);
459 :
460 : // Verify that a transition to a less fuzzy alpaca fails.
461 1 : p = parse_and_validate(mgr, "Llamaname Quest\n"
462 : "Address 99 camelid way\n"
463 : "Fuzziness 4\n", p_orig, &msg);
464 1 : tt_assert(!p);
465 1 : tt_str_op(msg, OP_EQ, "An alpaca only becomes more fuzzy over time.");
466 1 : tor_free(msg);
467 :
468 : // Try a transition to a more fuzzy alpaca; it should work fine.
469 1 : p = parse_and_validate(mgr, "Llamaname Mercutio\n"
470 : // the default fuzziness is 50
471 : "Address 99 camelid way\n", p_orig, &msg);
472 1 : tt_assert(p);
473 1 : config_free(mgr, p);
474 :
475 : // Verify that we can't move the pasture.
476 1 : p = parse_and_validate(mgr, "Llamaname Montague\n"
477 : // the default fuzziness is 50
478 : "Address 99 ungulate st\n", p_orig, &msg);
479 1 : tt_assert(!p);
480 1 : tt_str_op(msg, OP_EQ, "You can't move a pasture.");
481 :
482 1 : done:
483 1 : config_free(mgr, p);
484 1 : config_free(mgr, p_orig);
485 1 : config_mgr_free(mgr);
486 1 : tor_free(msg);
487 1 : }
488 :
489 : #define CONFMGR_TEST(name, flags) \
490 : { #name, test_confmgr_ ## name, flags, NULL, NULL }
491 :
492 : struct testcase_t confmgr_tests[] = {
493 : CONFMGR_TEST(init, 0),
494 : CONFMGR_TEST(magic, 0),
495 : CONFMGR_TEST(parse, 0),
496 : CONFMGR_TEST(dump, 0),
497 : CONFMGR_TEST(validate, 0),
498 : END_OF_TESTCASES
499 : };
|