test_parseconf.sh (21920B)
1 #!/bin/sh 2 # Copyright 2019, The Tor Project, Inc. 3 # See LICENSE for licensing information 4 5 # Integration test script for verifying that Tor configurations are parsed as 6 # we expect. 7 # 8 # Valid configurations are tested with --dump-config, which parses and 9 # validates the configuration before writing it out. We then make sure that 10 # the result is what we expect, before parsing and dumping it again to make 11 # sure that there is no change. Optionally, we can also test the log messages 12 # with --verify-config. 13 # 14 # Invalid configurations are tested with --verify-config, which parses 15 # and validates the configuration. We capture its output and make sure that 16 # it contains the error message we expect. 17 # 18 # When tor is compiled with different libraries or modules, some 19 # configurations may have different results. We can specify these result 20 # variants using additional result files. 21 22 # This script looks for its test cases as individual directories in 23 # src/test/conf_examples/. Each test may have these files: 24 # 25 # Configuration Files 26 # 27 # torrc -- Usually needed. This file is passed to Tor on the command line 28 # with the "-f" flag. (If you omit it, you'll test Tor's behavior when 29 # it receives a nonexistent configuration file.) 30 # 31 # torrc.defaults -- Optional. If present, it is passed to Tor on the command 32 # line with the --defaults-torrc option. If this file is absent, an empty 33 # file is passed instead to prevent Tor from reading the system defaults. 34 # 35 # cmdline -- Optional. If present, it contains command-line arguments that 36 # will be passed to Tor. 37 # 38 # (included torrc files or directories) -- Optional. Additional files can be 39 # included in configuration, using the "%include" directive. Files or 40 # directories can be included in any of the config files listed above. 41 # Include paths should be specified relative to the test case directory. 42 # 43 # Result Files 44 # 45 # expected -- If this file is present, then it should be the expected result 46 # of "--dump-config short" for this test case. Exactly one of 47 # "expected" or "error" must be present, or the test will fail. 48 # 49 # expected_log -- Optional. If this file is present, then it contains a regex 50 # that must be matched by some line in the output of "--verify-config", 51 # which must succeed. Only used if "expected" is also present. 52 # 53 # error -- If this file is present, then it contains a regex that must be 54 # matched by some line in the output of "--verify-config", which must 55 # fail. Exactly one of "expected" or "error" must be present, or the 56 # test will fail. 57 # 58 # {expected,expected_log,error}_${TOR_LIBS_ENABLED}* -- If this file is 59 # present, then the outcome is different when some optional libraries are 60 # enabled. If there is no result file matching the exact list of enabled 61 # libraries, the script searches for result files with one or more of 62 # those libraries disabled. The search terminates at the standard result 63 # file. If expected* is present, the script also searches for 64 # expected_log*. 65 # 66 # For example: 67 # A test that succeeds, regardless of any enabled libraries: 68 # - expected 69 # A test that has a different result if the nss library is enabled 70 # (but the same result if any other library is enabled). We also check 71 # the log output in this test: 72 # - expected 73 # - expected_log 74 # - expected_nss 75 # - expected_log_nss 76 # A test that fails if the lzma and zstd modules are *not* enabled: 77 # - error 78 # - expected_lzma_zstd 79 # 80 # {expected,expected_log,error}*_no_${TOR_MODULES_DISABLED} -- If this file is 81 # present, then the outcome is different when some modules are disabled. 82 # If there is no result file matching the exact list of disabled modules, 83 # the standard result file is used. If expected* is present, the script 84 # also searches for expected_log*. 85 # 86 # For example: 87 # A test that succeeds, regardless of any disabled modules: 88 # - expected 89 # A test that has a different result if the relay module is disabled 90 # (but the same result if just the dirauth module is disabled): 91 # - expected 92 # - expected_no_relay_dirauth 93 # A test that fails if the dirauth module is disabled: 94 # - expected 95 # - error_no_dirauth 96 # - error_no_relay_dirauth 97 # (Disabling the relay module also disables dirauth module. But we don't 98 # want to encode that knowledge in this test script, so we supply a 99 # separate result file for every combination of disabled modules that 100 # has a different result.) 101 # 102 # This logic ignores modules that are not listed by --list-modules 103 # (dircache) and some that do not currently affect config parsing (pow). 104 105 umask 077 106 set -e 107 108 MYNAME="$0" 109 110 # emulate realpath(), in case coreutils or equivalent is not installed. 111 abspath() { 112 f="$*" 113 if test -d "$f"; then 114 dir="$f" 115 base="" 116 else 117 dir="$(dirname "$f")" 118 base="/$(basename "$f")" 119 fi 120 dir="$(cd "$dir" && pwd)" 121 echo "$dir$base" 122 } 123 124 # find the tor binary 125 if test $# -ge 1; then 126 TOR_BINARY="$1" 127 shift 128 else 129 TOR_BINARY="${TESTING_TOR_BINARY:-./src/app/tor}" 130 fi 131 132 TOR_BINARY="$(abspath "$TOR_BINARY")" 133 134 echo "Using Tor binary '$TOR_BINARY'." 135 136 # make a safe space for temporary files 137 DATA_DIR=$(mktemp -d -t tor_parseconf_tests.XXXXXX) 138 trap 'rm -rf "$DATA_DIR"' 0 139 140 # This is where we look for examples 141 EXAMPLEDIR="$(dirname "$0")"/conf_examples 142 143 case "$(uname -s)" in 144 CYGWIN*) WINDOWS=1;; 145 MINGW*) WINDOWS=1;; 146 MSYS*) WINDOWS=1;; 147 *) WINDOWS=0;; 148 esac 149 150 #### 151 # BUG WORKAROUND FOR 31757: 152 # On Appveyor, it seems that Tor sometimes randomly fails to produce 153 # output with --dump-config. Whil we are figuring this out, do not treat 154 # windows errors as hard failures. 155 #### 156 if test "$WINDOWS" = 1; then 157 EXITCODE=0 158 else 159 EXITCODE=1 160 fi 161 162 FINAL_EXIT=0 163 NEXT_TEST= 164 165 # Log a failure message to stderr, using $@ as a printf string and arguments 166 # Set NEXT_TEST to "yes" and FINAL_EXIT to $EXITCODE. 167 fail_printf() 168 { 169 printf "FAIL: " >&2 170 # The first argument is a printf string, so this warning is spurious 171 # shellcheck disable=SC2059 172 printf "$@" >&2 173 NEXT_TEST="yes" 174 FINAL_EXIT=$EXITCODE 175 } 176 177 # Log a failure message to stderr, using $@ as a printf string and arguments 178 # Exit with status $EXITCODE. 179 die_printf() 180 { 181 printf "FAIL: CRITICAL error in '%s':" "$MYNAME" >&2 182 # The first argument is a printf string, so this warning is spurious 183 # shellcheck disable=SC2059 184 printf "$@" >&2 185 exit $EXITCODE 186 } 187 188 if test "$WINDOWS" = 1; then 189 FILTER="dos2unix" 190 else 191 FILTER="cat" 192 fi 193 194 EMPTY="${DATA_DIR}/EMPTY" 195 touch "$EMPTY" || die_printf "Couldn't create empty file '%s'.\\n" \ 196 "$EMPTY" 197 NON_EMPTY="${DATA_DIR}/NON_EMPTY" 198 echo "This pattern should not match any log messages" \ 199 > "$NON_EMPTY" || die_printf "Couldn't create non-empty file '%s'.\\n" \ 200 "$NON_EMPTY" 201 202 STANDARD_LIBS="libevent\\|openssl\\|zlib" 203 MODULES_WITHOUT_CONFIG_TESTS="dircache\\|pow" 204 205 # Lib names are restricted to [a-z0-9]* at the moment 206 # We don't actually want to support foreign accents here 207 # shellcheck disable=SC2018,SC2019 208 TOR_LIBS_ENABLED="$("$TOR_BINARY" --verify-config \ 209 -f "$EMPTY" --defaults-torrc "$EMPTY" \ 210 | sed -n 's/.* Tor .* running on .* with\(.*\) and .* .* as libc\./\1/p' \ 211 | tr 'A-Z' 'a-z' | tr ',' '\n' \ 212 | grep -v "$STANDARD_LIBS" | grep -v "n/a" \ 213 | sed 's/\( and\)* \(lib\)*\([a-z0-9]*\) .*/\3/' \ 214 | sort | tr '\n' '_')" 215 # Remove the last underscore, if there is one 216 TOR_LIBS_ENABLED=${TOR_LIBS_ENABLED%_} 217 218 # If we ever have more than 3 optional libraries, we'll need more code here 219 TOR_LIBS_ENABLED_COUNT="$(echo "$TOR_LIBS_ENABLED_SEARCH" \ 220 | tr ' ' '\n' | wc -l)" 221 if test "$TOR_LIBS_ENABLED_COUNT" -gt 3; then 222 die_printf "Can not handle more than 3 optional libraries.\\n" 223 fi 224 # Brute-force the combinations of libraries 225 TOR_LIBS_ENABLED_SEARCH_3="$(echo "$TOR_LIBS_ENABLED" \ 226 | sed -n \ 227 's/^\([^_]*\)_\([^_]*\)_\([^_]*\)$/_\1_\2 _\1_\3 _\2_\3 _\1 _\2 _\3/p')" 228 TOR_LIBS_ENABLED_SEARCH_2="$(echo "$TOR_LIBS_ENABLED" \ 229 | sed -n 's/^\([^_]*\)_\([^_]*\)$/_\1 _\2/p')" 230 TOR_LIBS_ENABLED_SEARCH="_$TOR_LIBS_ENABLED \ 231 $TOR_LIBS_ENABLED_SEARCH_3 \ 232 $TOR_LIBS_ENABLED_SEARCH_2" 233 TOR_LIBS_ENABLED_SEARCH="$(echo "$TOR_LIBS_ENABLED_SEARCH" | tr ' ' '\n' \ 234 | grep -v '^_*$' | tr '\n' ' ')" 235 236 TOR_MODULES_DISABLED="$("$TOR_BINARY" --list-modules | grep ': no' \ 237 | grep -v "$MODULES_WITHOUT_CONFIG_TESTS" \ 238 | cut -d ':' -f1 | sort | tr '\n' '_')" 239 # Remove the last underscore, if there is one 240 TOR_MODULES_DISABLED=${TOR_MODULES_DISABLED%_} 241 242 echo "Tor is configured with:" 243 echo "Optional Libraries: ${TOR_LIBS_ENABLED:-(None)}" 244 if test "$TOR_LIBS_ENABLED"; then 245 echo "Optional Library Search List: $TOR_LIBS_ENABLED_SEARCH" 246 fi 247 echo "Disabled Modules: ${TOR_MODULES_DISABLED:-(None)}" 248 249 # Yes, unix uses "0" for a successful command 250 TRUE=0 251 FALSE=1 252 253 # Run tor --verify-config on the torrc $1, and defaults torrc $2, which may 254 # be $EMPTY. Pass tor the extra command line arguments $3, which will be 255 # passed unquoted. 256 # Send tor's standard output to stderr. 257 log_verify_config() 258 { 259 # show the command we're about to execute 260 # log_verify_config() is only called when we've failed 261 printf "Tor --verify-config said:\\n" >&2 262 printf "$ %s %s %s %s %s %s %s\\n" \ 263 "$TOR_BINARY" --verify-config \ 264 -f "$1" \ 265 --defaults-torrc "$2" \ 266 "$3" \ 267 >&2 268 # We need cmdline unquoted 269 # shellcheck disable=SC2086 270 "$TOR_BINARY" --verify-config \ 271 -f "$1" \ 272 --defaults-torrc "$2" \ 273 $3 \ 274 >&2 \ 275 || true 276 } 277 278 # Run "tor --dump-config short" on the torrc $1, and defaults torrc $2, which 279 # may be $EMPTY. Pass tor the extra command line arguments $3, which will be 280 # passed unquoted. Send tor's standard output to $4. 281 # 282 # Set $FULL_TOR_CMD to the tor command line that was executed. 283 # 284 # If tor fails, fail_printf() using the file name $5, and context $6, 285 # which may be an empty string. Then run log_verify_config(). 286 dump_config() 287 { 288 if test "$6"; then 289 CONTEXT=" $6" 290 else 291 CONTEXT="" 292 fi 293 294 # keep the command we're about to execute, and show if it we fail 295 FULL_TOR_CMD=$(printf "$ %s %s %s %s %s %s %s %s" \ 296 "$TOR_BINARY" --dump-config short \ 297 -f "$1" \ 298 --defaults-torrc "$2" \ 299 "$3" 300 ) 301 # We need cmdline unquoted 302 # shellcheck disable=SC2086 303 if ! "$TOR_BINARY" --dump-config short \ 304 -f "$1" \ 305 --defaults-torrc "$2" \ 306 $3 \ 307 > "$4"; then 308 fail_printf "'%s': Tor --dump-config reported an error%s:\\n%s\\n" \ 309 "$5" \ 310 "$CONTEXT" \ 311 "$FULL_TOR_CMD" 312 log_verify_config "$1" \ 313 "$2" \ 314 "$3" 315 fi 316 } 317 318 # Run "$FILTER" on the input $1. 319 # Send the standard output to $2. 320 # If tor fails, log a failure message using the file name $3, and context $4, 321 # which may be an empty string. 322 filter() 323 { 324 if test "$4"; then 325 CONTEXT=" $4" 326 else 327 CONTEXT="" 328 fi 329 330 "$FILTER" "$1" \ 331 > "$2" \ 332 || fail_printf "'%s': Filter '%s' reported an error%s.\\n" \ 333 "$3" \ 334 "$FILTER" \ 335 "$CONTEXT" 336 } 337 338 # Compare the expected file $1, and output file $2. 339 # 340 # If they are different, fail. Log the differences between the files. 341 # Run log_verify_config() with torrc $3, defaults torrc $4, and command 342 # line $5, to log Tor's error messages. 343 # 344 # If the file contents are identical, returns true. Otherwise, return false. 345 # 346 # Log failure messages using fail_printf(), with the expected file name, 347 # context $6, which may be an empty string, and the tor command line $7. 348 check_diff() 349 { 350 if test "$6"; then 351 CONTEXT=" $6" 352 else 353 CONTEXT="" 354 fi 355 356 if cmp "$1" "$2" > /dev/null; then 357 return "$TRUE" 358 else 359 fail_printf "'%s': Tor --dump-config said%s:\\n%s\\n" \ 360 "$1" \ 361 "$CONTEXT" \ 362 "$7" 363 diff -u "$1" "$2" >&2 \ 364 || true 365 log_verify_config "$3" \ 366 "$4" \ 367 "$5" 368 return "$FALSE" 369 fi 370 } 371 372 # Run "tor --dump-config short" on the torrc $1, and defaults torrc $2, which 373 # may be $EMPTY. Pass tor the extra command line arguments $3, which will be 374 # passed unquoted. Send tor's standard output to $4, after running $FILTER 375 # on it. 376 # 377 # If tor fails, run log_verify_config(). 378 # 379 # Compare the expected file $5, and output file. If they are different, fail. 380 # If this is the first step that failed in this test, run log_verify_config(). 381 # 382 # If the file contents are identical, returns true. Otherwise, return false, 383 # and log the differences between the files. 384 # 385 # Log failure messages using fail_printf(), with the expected file name, and 386 # context $6, which may be an empty string. 387 check_dump_config() 388 { 389 OUTPUT="$4" 390 OUTPUT_RAW="${OUTPUT}_raw" 391 392 FULL_TOR_CMD= 393 dump_config "$1" \ 394 "$2" \ 395 "$3" \ 396 "$OUTPUT_RAW" \ 397 "$5" \ 398 "$6" 399 400 filter "$OUTPUT_RAW" \ 401 "$OUTPUT" \ 402 "$5" \ 403 "$6" 404 405 if check_diff "$5" \ 406 "$OUTPUT" \ 407 "$1" \ 408 "$2" \ 409 "$3" \ 410 "$6" \ 411 "$FULL_TOR_CMD"; then 412 return "$TRUE" 413 else 414 return "$FALSE" 415 fi 416 } 417 418 # Check if $1 is an empty file. 419 # If it is, fail_printf() using $2 as the type of file. 420 # Returns true if the file is empty, false otherwise. 421 check_empty_pattern() 422 { 423 if ! test -s "$1"; then 424 fail_printf "%s file '%s' is empty, and will match any output.\\n" \ 425 "$2" \ 426 "$1" 427 return "$TRUE" 428 else 429 return "$FALSE" 430 fi 431 } 432 433 # Run tor --verify-config on the torrc $1, and defaults torrc $2, which may 434 # be $EMPTY. Pass tor the extra command line arguments $3, which will be 435 # passed unquoted. Send tor's standard output to $4. 436 # 437 # Set $FULL_TOR_CMD to the tor command line that was executed. 438 # 439 # If tor's exit status does not match the boolean $5, fail_printf() 440 # using the file name $6, and context $7, which is required. 441 verify_config() 442 { 443 RESULT=$TRUE 444 445 # keep the command we're about to execute, and show if it we fail 446 FULL_TOR_CMD=$(printf "$ %s %s %s %s %s %s %s" \ 447 "$TOR_BINARY" --verify-config \ 448 -f "$1" \ 449 --defaults-torrc "$2" \ 450 "$3" 451 ) 452 # We need cmdline unquoted 453 # shellcheck disable=SC2086 454 "$TOR_BINARY" --verify-config \ 455 -f "$1" \ 456 --defaults-torrc "$2" \ 457 $3 \ 458 > "$4" || RESULT=$FALSE 459 460 # Convert the actual and expected results to boolean, and compare 461 if test $((! (! RESULT))) -ne $((! (! $5))); then 462 fail_printf "'%s': Tor --verify-config did not %s:\\n%s\\n" \ 463 "$6" \ 464 "$7" \ 465 "$FULL_TOR_CMD" 466 cat "$4" >&2 467 fi 468 } 469 470 # Check for the patterns in the match file $1, in the output file $2. 471 # Uses grep with the entire contents of the match file as the pattern. 472 # (Not "grep -f".) 473 # 474 # If the pattern does not match any lines in the output file, fail. 475 # Log the pattern, and the entire contents of the output file. 476 # 477 # Log failure messages using fail_printf(), with the match file name, 478 # context $3, and tor command line $4, which are required. 479 check_pattern() 480 { 481 expect_log="$(cat "$1")" 482 if ! grep "$expect_log" "$2" > /dev/null; then 483 fail_printf "Expected %s '%s':\\n%s\\n" \ 484 "$3" \ 485 "$1" \ 486 "$expect_log" 487 printf "Tor --verify-config said:\\n%s\\n" \ 488 "$4" >&2 489 cat "$2" >&2 490 fi 491 } 492 493 # Run tor --verify-config on the torrc $1, and defaults torrc $2, which may 494 # be $EMPTY. Pass tor the extra command line arguments $3, which will be 495 # passed unquoted. Send tor's standard output to $4. 496 # 497 # If tor's exit status does not match the boolean $5, fail. 498 # 499 # Check for the patterns in the match file $6, in the output file. 500 # Uses grep with the entire contents of the match file as the pattern. 501 # (Not "grep -f".) The match file must not be empty. 502 # 503 # If the pattern does not match any lines in the output file, fail. 504 # Log the pattern, and the entire contents of the output file. 505 # 506 # Log failure messages using fail_printf(), with the match file name, 507 # and context $7, which is required. 508 check_verify_config() 509 { 510 if check_empty_pattern "$6" "$7"; then 511 return 512 fi 513 514 FULL_TOR_CMD= 515 verify_config "$1" \ 516 "$2" \ 517 "$3" \ 518 "$4" \ 519 "$5" \ 520 "$6" \ 521 "$7" 522 523 check_pattern "$6" \ 524 "$4" \ 525 "$7" \ 526 "$FULL_TOR_CMD" 527 } 528 529 for dir in "${EXAMPLEDIR}"/*; do 530 NEXT_TEST= 531 532 if ! test -d "$dir"; then 533 # Only count directories. 534 continue 535 fi 536 537 testname="$(basename "${dir}")" 538 # We use printf since "echo -n" is not standard 539 printf "%s: " \ 540 "$testname" 541 542 PREV_DIR="$(pwd)" 543 cd "$dir" 544 545 if test -f "./torrc.defaults"; then 546 DEFAULTS="./torrc.defaults" 547 else 548 DEFAULTS="${DATA_DIR}/EMPTY" 549 fi 550 551 if test -f "./cmdline"; then 552 CMDLINE="$(cat ./cmdline)" 553 else 554 CMDLINE="" 555 fi 556 557 EXPECTED= 558 EXPECTED_LOG= 559 ERROR= 560 # Search for a custom result file for any combination of enabled optional 561 # libraries 562 # The libs in the list are [A-Za-z0-9_]* and space-separated. 563 # shellcheck disable=SC2086 564 for lib_suffix in $TOR_LIBS_ENABLED_SEARCH ""; do 565 # Search for a custom result file for any disabled modules 566 for mod_suffix in "_no_${TOR_MODULES_DISABLED}" ""; do 567 suffix="${lib_suffix}${mod_suffix}" 568 569 if test -f "./expected${suffix}"; then 570 571 # Check for broken configs 572 if test -f "./error${suffix}"; then 573 fail_printf "Found both '%s' and '%s'.%s\\n" \ 574 "${dir}/expected${suffix}" \ 575 "${dir}/error${suffix}" \ 576 "(Only one of these files should exist.)" 577 break 578 fi 579 580 EXPECTED="./expected${suffix}" 581 if test -f "./expected_log${suffix}"; then 582 EXPECTED_LOG="./expected_log${suffix}" 583 fi 584 break 585 586 elif test -f "./error${suffix}"; then 587 ERROR="./error${suffix}" 588 break 589 fi 590 done 591 592 # Exit as soon as the inner loop finds a file, or fails 593 if test -f "$EXPECTED" || test -f "$ERROR" || test "$NEXT_TEST"; then 594 break 595 fi 596 done 597 598 if test "$NEXT_TEST"; then 599 # The test failed inside the file search loop: go to the next test 600 continue 601 elif test -f "$EXPECTED"; then 602 # This case should succeed: run dump-config and see if it does. 603 604 if check_dump_config "./torrc" \ 605 "$DEFAULTS" \ 606 "$CMDLINE" \ 607 "${DATA_DIR}/output.${testname}" \ 608 "$EXPECTED" \ 609 ""; then 610 # Check round-trip. 611 check_dump_config "${DATA_DIR}/output.${testname}" \ 612 "$EMPTY" \ 613 "" \ 614 "${DATA_DIR}/output_2.${testname}" \ 615 "$EXPECTED" \ 616 "on round-trip" || true 617 fi 618 619 if test -f "$EXPECTED_LOG"; then 620 # This case should succeed: run verify-config and see if it does. 621 622 check_verify_config "./torrc" \ 623 "$DEFAULTS" \ 624 "$CMDLINE" \ 625 "${DATA_DIR}/output_log.${testname}" \ 626 "$TRUE" \ 627 "$EXPECTED_LOG" \ 628 "log success" 629 else 630 printf "\\nNOTICE: Missing '%s_log' file:\\n" \ 631 "$EXPECTED" >&2 632 log_verify_config "./torrc" \ 633 "$DEFAULTS" \ 634 "$CMDLINE" 635 fi 636 637 elif test -f "$ERROR"; then 638 # This case should fail: run verify-config and see if it does. 639 640 check_verify_config "./torrc" \ 641 "$DEFAULTS" \ 642 "$CMDLINE" \ 643 "${DATA_DIR}/output.${testname}" \ 644 "$FALSE" \ 645 "$ERROR" \ 646 "log error" 647 else 648 # This case is not actually configured with a success or a failure. 649 # call that an error. 650 fail_printf "Did not find ${dir}/*expected or ${dir}/*error.\\n" 651 fi 652 653 if test -z "$NEXT_TEST"; then 654 echo "OK" 655 fi 656 657 cd "$PREV_DIR" 658 659 done 660 661 exit "$FINAL_EXIT"