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