test_confmgr.c (13238B)
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 clear_llama_cfg(const config_mgr_t *mgr, void *llamacfg) 97 { 98 (void)mgr; 99 llama_cfg_t *lc = llamacfg; 100 tor_free(lc->description); 101 ++clear_llama_cfg_called; 102 } 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 legacy_validate_pasture(const void *old_, void *obj, char **msg_out) 112 { 113 const pasture_cfg_t *old = old_; 114 pasture_cfg_t *p = obj; 115 116 // llamas can't find their way home if the letters are lowercase. 117 if (p->address) 118 tor_strupper(p->address); 119 120 if (old && old->address && 121 (!p->address || strcmp(old->address, p->address))) { 122 *msg_out = tor_strdup("You can't move a pasture."); 123 return -1; 124 } 125 126 return 0; 127 } 128 129 static int 130 validate_llama(const void *obj, char **msg_out) 131 { 132 const llama_cfg_t *llama = obj; 133 tor_assert(llama->magic == 0x11aa11); 134 135 if (! llama->llamaname || strlen(llama->llamaname) == 0) { 136 *msg_out = tor_strdup("A llama has no name!?"); 137 return -1; 138 } 139 140 if (strspn(llama->llamaname, "0123456789") == strlen(llama->llamaname)) { 141 *msg_out = tor_strdup("It is not a number; it is a free llama!"); 142 return -1; 143 } 144 145 return 0; 146 } 147 148 static int 149 check_transition_alpaca(const void *old_, const void *new_, char **msg_out) 150 { 151 const alpaca_cfg_t *old_alpaca = old_; 152 const alpaca_cfg_t *new_alpaca = new_; 153 154 tor_assert(old_alpaca && new_alpaca); 155 tor_assert(old_alpaca->magic == 0xa15aca); 156 tor_assert(new_alpaca->magic == 0xa15aca); 157 158 if (old_alpaca->fuzziness > new_alpaca->fuzziness) { 159 *msg_out = tor_strdup("An alpaca only becomes more fuzzy over time."); 160 return -1; 161 } 162 163 return 0; 164 } 165 166 static int 167 post_normalize_llama(void *obj, char **msg_out) 168 { 169 (void)msg_out; 170 llama_cfg_t *llama = obj; 171 tor_assert(llama->magic == 0x11aa11); 172 tor_assert(llama->llamaname); // we have already checked for a NULL name. 173 tor_free(llama->description); 174 tor_asprintf(&llama->description, "A llama called %s.", llama->llamaname); 175 return 0; 176 } 177 178 static int 179 pre_normalize_alpaca(void *obj, char **msg_out) 180 { 181 (void)msg_out; 182 alpaca_cfg_t *alpaca = obj; 183 tor_assert(alpaca->magic == 0xa15aca); 184 alpaca->square_fuzziness = alpaca->fuzziness * alpaca->fuzziness; 185 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 get_mgr(bool freeze) 234 { 235 config_mgr_t *mgr = config_mgr_new(&pasture_fmt); 236 tt_int_op(LLAMA_IDX, OP_EQ, config_mgr_add_format(mgr, &llama_fmt)); 237 tt_int_op(ALPACA_IDX, OP_EQ, config_mgr_add_format(mgr, &alpaca_fmt)); 238 if (freeze) 239 config_mgr_freeze(mgr); 240 return mgr; 241 242 done: 243 config_mgr_free(mgr); 244 return NULL; 245 } 246 247 static void 248 test_confmgr_init(void *arg) 249 { 250 (void)arg; 251 config_mgr_t *mgr = get_mgr(true); 252 smartlist_t *vars = NULL; 253 tt_ptr_op(mgr, OP_NE, NULL); 254 255 vars = config_mgr_list_vars(mgr); 256 tt_int_op(smartlist_len(vars), OP_EQ, 8); // 8 vars total. 257 258 tt_str_op("cuteness", OP_EQ, config_find_option_name(mgr, "CUTENESS")); 259 tt_str_op("cuteness", OP_EQ, config_find_option_name(mgr, "GRACIA")); 260 smartlist_free(vars); 261 262 vars = config_mgr_list_deprecated_vars(mgr); // 2 deprecated vars. 263 tt_int_op(smartlist_len(vars), OP_EQ, 2); 264 tt_assert(smartlist_contains_string(vars, "eats_meat")); 265 tt_assert(smartlist_contains_string(vars, "n_wings")); 266 267 tt_str_op("Llamas are herbivores.", OP_EQ, 268 config_find_deprecation(mgr, "EATS_MEAT")); 269 tt_str_op("Alpacas are quadrupeds.", OP_EQ, 270 config_find_deprecation(mgr, "N_WINGS")); 271 272 done: 273 smartlist_free(vars); 274 config_mgr_free(mgr); 275 } 276 277 static void 278 test_confmgr_magic(void *args) 279 { 280 (void)args; 281 // Every time we build a manager, it is supposed to get a different magic 282 // number. Let's test that. 283 config_mgr_t *mgr1 = get_mgr(true); 284 config_mgr_t *mgr2 = get_mgr(true); 285 config_mgr_t *mgr3 = get_mgr(true); 286 287 pasture_cfg_t *p1 = NULL, *p2 = NULL, *p3 = NULL; 288 289 tt_assert(mgr1); 290 tt_assert(mgr2); 291 tt_assert(mgr3); 292 293 p1 = config_new(mgr1); 294 p2 = config_new(mgr2); 295 p3 = config_new(mgr3); 296 297 tt_assert(p1); 298 tt_assert(p2); 299 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 tt_assert((p1->magic != p2->magic) || (p2->magic != p3->magic)); 305 306 done: 307 config_free(mgr1, p1); 308 config_free(mgr2, p2); 309 config_free(mgr3, p3); 310 311 config_mgr_free(mgr1); 312 config_mgr_free(mgr2); 313 config_mgr_free(mgr3); 314 } 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 test_confmgr_parse(void *arg) 324 { 325 (void)arg; 326 config_mgr_t *mgr = get_mgr(true); 327 pasture_cfg_t *p = config_new(mgr); 328 config_line_t *lines = NULL; 329 char *msg = NULL; 330 331 config_init(mgr, p); // set defaults. 332 333 int r = config_get_lines(simple_pasture, &lines, 0); 334 tt_int_op(r, OP_EQ, 0); 335 r = config_assign(mgr, p, lines, 0, &msg); 336 tt_int_op(r, OP_EQ, 0); 337 338 tt_int_op(p->opentopublic, OP_EQ, 1); 339 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 const alpaca_cfg_t *ac = config_mgr_get_obj(mgr, p, ALPACA_IDX); 344 const llama_cfg_t *lc = config_mgr_get_obj(mgr, p, LLAMA_IDX); 345 tt_str_op(lc->llamaname, OP_EQ, "hugo"); 346 tt_str_op(ac->alpacaname, OP_EQ, "daphne"); 347 tt_int_op(lc->cuteness, OP_EQ, 42); 348 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 clear_llama_cfg_called = 0; 353 llama_cfg_t *mut_lc = config_mgr_get_obj_mutable(mgr, p, LLAMA_IDX); 354 mut_lc->description = tor_strdup("A llama named Hugo."); 355 config_free(mgr, p); 356 tt_int_op(clear_llama_cfg_called, OP_EQ, 1); 357 358 done: 359 config_free_lines(lines); 360 config_free(mgr, p); 361 config_mgr_free(mgr); 362 tor_free(msg); 363 } 364 365 static void 366 test_confmgr_dump(void *arg) 367 { 368 (void)arg; 369 config_mgr_t *mgr = get_mgr(true); 370 pasture_cfg_t *p = config_new(mgr); 371 pasture_cfg_t *defaults = config_new(mgr); 372 config_line_t *lines = NULL; 373 char *msg = NULL; 374 char *s = NULL; 375 376 config_init(mgr, p); // set defaults. 377 config_init(mgr, defaults); // set defaults. 378 379 int r = config_get_lines(simple_pasture, &lines, 0); 380 tt_int_op(r, OP_EQ, 0); 381 r = config_assign(mgr, p, lines, 0, &msg); 382 tt_int_op(r, OP_EQ, 0); 383 384 s = config_dump(mgr, defaults, p, 1, 0); 385 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 done: 391 config_free_lines(lines); 392 config_free(mgr, p); 393 config_free(mgr, defaults); 394 config_mgr_free(mgr); 395 396 tor_free(msg); 397 tor_free(s); 398 } 399 400 static pasture_cfg_t * 401 parse_and_validate(config_mgr_t *mgr, 402 const char *inp, const pasture_cfg_t *old, char **msg_out) 403 { 404 pasture_cfg_t *p = config_new(mgr); 405 pasture_cfg_t *result = NULL; 406 config_line_t *lines = NULL; 407 408 config_init(mgr, p); // set defaults. 409 int r = config_get_lines(inp, &lines, 0); 410 tt_int_op(r, OP_EQ, 0); 411 r = config_assign(mgr, p, lines, 0, msg_out); 412 tt_int_op(r, OP_EQ, 0); 413 tor_free(*msg_out); // sets it to NULL 414 r = config_validate(mgr, old, p, msg_out); 415 if (r < 0) 416 goto done; 417 418 tt_ptr_op(*msg_out, OP_EQ, NULL); 419 result = p; 420 p = NULL; // prevent free 421 done: 422 config_free(mgr, p); 423 config_free_lines(lines); 424 return result; 425 } 426 427 static void 428 test_confmgr_validate(void *arg) 429 { 430 (void)arg; 431 char *msg = NULL; 432 config_mgr_t *mgr = get_mgr(true); 433 pasture_cfg_t *p_orig, *p=NULL; 434 435 p_orig = parse_and_validate(mgr, "Llamaname Quest\n" 436 "Address 99 camelid way\n" 437 "Fuzziness 8\n", NULL, &msg); 438 tt_assert(p_orig); 439 440 // Make sure normalization code was run. 441 const alpaca_cfg_t *ac0 = config_mgr_get_obj(mgr, p_orig, ALPACA_IDX); 442 const llama_cfg_t *lc0 = config_mgr_get_obj(mgr, p_orig, LLAMA_IDX); 443 tt_int_op(ac0->fuzziness, OP_EQ, 8); 444 tt_int_op(ac0->square_fuzziness, OP_EQ, 64); 445 tt_str_op(lc0->description, OP_EQ, "A llama called Quest."); 446 tt_str_op(p_orig->address, OP_EQ, "99 CAMELID WAY"); 447 448 // try a bad llamaname. 449 p = parse_and_validate(mgr, "llamaname 123", p_orig, &msg); 450 tt_assert(!p); 451 tt_str_op(msg, OP_EQ, "It is not a number; it is a free llama!"); 452 tor_free(msg); 453 454 // try a llamaname that would crash the post_normalize step, if it ran. 455 p = parse_and_validate(mgr, "", p_orig, &msg); 456 tt_assert(!p); 457 tt_str_op(msg, OP_EQ, "A llama has no name!?"); 458 tor_free(msg); 459 460 // Verify that a transition to a less fuzzy alpaca fails. 461 p = parse_and_validate(mgr, "Llamaname Quest\n" 462 "Address 99 camelid way\n" 463 "Fuzziness 4\n", p_orig, &msg); 464 tt_assert(!p); 465 tt_str_op(msg, OP_EQ, "An alpaca only becomes more fuzzy over time."); 466 tor_free(msg); 467 468 // Try a transition to a more fuzzy alpaca; it should work fine. 469 p = parse_and_validate(mgr, "Llamaname Mercutio\n" 470 // the default fuzziness is 50 471 "Address 99 camelid way\n", p_orig, &msg); 472 tt_assert(p); 473 config_free(mgr, p); 474 475 // Verify that we can't move the pasture. 476 p = parse_and_validate(mgr, "Llamaname Montague\n" 477 // the default fuzziness is 50 478 "Address 99 ungulate st\n", p_orig, &msg); 479 tt_assert(!p); 480 tt_str_op(msg, OP_EQ, "You can't move a pasture."); 481 482 done: 483 config_free(mgr, p); 484 config_free(mgr, p_orig); 485 config_mgr_free(mgr); 486 tor_free(msg); 487 } 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 };