diff options
144 files changed, 3065 insertions, 1665 deletions
diff --git a/.travis.yml b/.travis.yml index bda1f323e5..8e258aef26 100644 --- a/.travis.yml +++ b/.travis.yml @@ -216,7 +216,7 @@ script: ## We run `make check` because that's what https://jenkins.torproject.org does. - if [[ "$DISTCHECK" == "" && "$TEST_STEM" == "" ]]; then make check; fi ## Diagnostic for bug 29437: kill stem if it hangs for 15 minutes - - if [[ "$TEST_STEM" != "" ]]; then timelimit -p -t 540 -T 30 make src/app/tor test-stem; fi + - if [[ "$TEST_STEM" != "" ]]; then make src/app/tor; timelimit -p -t 540 -s USR1 -T 30 -S ABRT python3 "$STEM_SOURCE_DIR"/run_tests.py --tor src/app/tor --integ --log notice --target RUN_ALL; fi - if [[ "$DISTCHECK" != "" && "$TEST_STEM" == "" ]]; then make distcheck DISTCHECK_CONFIGURE_FLAGS="$CONFIGURE_FLAGS"; fi ## If this build was one that produced coverage, upload it. - if [[ "$COVERAGE_OPTIONS" != "" ]]; then coveralls -b . --exclude src/test --exclude src/trunnel --gcov-options '\-p' || echo "Coverage failed"; fi @@ -230,6 +230,7 @@ after_failure: ## `make distcheck` puts it somewhere different. - if [[ "$DISTCHECK" != "" ]]; then make show-distdir-testlog || echo "make failed"; fi - if [[ "$DISTCHECK" != "" ]]; then make show-distdir-core || echo "make failed"; fi + - if [[ "$TEST_STEM" != "" ]]; then cat "$STEM_SOURCE_DIR"/test/data/tor_log || echo "cat failed"; fi before_cache: ## Delete all gcov files. diff --git a/Makefile.am b/Makefile.am index de11696965..827cf3dc9b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -226,7 +226,7 @@ shellcheck: if command -v shellcheck; then \ find $(top_srcdir)/scripts/ -name "*.sh" -exec shellcheck {} +; \ if [ -d "$(top_srcdir)/scripts/test" ]; then \ - shellcheck $(top_srcdir)/scripts/test/cov-diff $(top_builddir)/scripts/test/coverage; \ + shellcheck $(top_srcdir)/scripts/test/cov-diff $(top_srcdir)/scripts/test/coverage; \ fi; \ fi diff --git a/changes/bug24490 b/changes/bug24490 new file mode 100644 index 0000000000..cf9281c878 --- /dev/null +++ b/changes/bug24490 @@ -0,0 +1,5 @@ + o Minor bugfixes (bridge authority): + - We set bridges as running when we dump the bridge status to a file. + Previously, we set bridges as running in a GETINFO controller, but + these shouldn't modify vital data structures. Fixes bug 24490; + bugfix on 0.2.0.13-alpha. Patch by Neel Chauhan diff --git a/changes/bug27199 b/changes/bug27199 new file mode 100644 index 0000000000..f9d2a422f9 --- /dev/null +++ b/changes/bug27199 @@ -0,0 +1,3 @@ + o Minor bugfixes (rust): + - Abort on panic in all build profiles, instead of potentially unwinding + into C code. Fixes bug 27199; bugfix on 0.3.3.1-alpha. diff --git a/changes/bug28269 b/changes/bug28269 new file mode 100644 index 0000000000..bdfe9e1aae --- /dev/null +++ b/changes/bug28269 @@ -0,0 +1,7 @@ + o Minor bugfixes (onion services): + - If we are launching repeated HSFETCH queries and are rate-limited, + we introduce a new controller response QUERY_RATE_LIMITED instead + of QUERY_NO_HSDIR, while keeping the latter for when onion service + directories are missing a descriptor. Previously, we returned + QUERY_NO_HSDIR for both cases. Fixes bug 28269; bugfix on + 0.3.1.1-alpha. Patch by Neel Chauhan diff --git a/changes/bug29613 b/changes/bug29613 new file mode 100644 index 0000000000..e966973255 --- /dev/null +++ b/changes/bug29613 @@ -0,0 +1,5 @@ + o Minor bugfixes (relay): + - If we are are a relay and have IPv6Exit to 1 while ExitRelay is + auto, we act as if ExitRelay is 1. Previously, we ignored IPv6Exit + if ExitRelay was 0 or auto. Fixes bug 29613; bugfix on 0.3.5.1-alpha. + Patch by Neel Chauhan. diff --git a/changes/bug29640 b/changes/bug29640 new file mode 100644 index 0000000000..81adeae32a --- /dev/null +++ b/changes/bug29640 @@ -0,0 +1,4 @@ + o Minor bugfixes (documentation): + - Improve the monotonic time module and function documentation. Explain + what "monotonic" actually means, and document some results that have + surprised people. Fixes bug 29640; bugfix on 0.2.9.1-alpha. diff --git a/changes/bug30001 b/changes/bug30001 new file mode 100644 index 0000000000..52e58872ef --- /dev/null +++ b/changes/bug30001 @@ -0,0 +1,7 @@ + o Minor features (testing): + - Use the approx_time() function when setting the "Expires" header + in directory replies, to make them more testable. Needed for + ticket 30001. + o Minor bug fixes (testing): + - Check the time in the "Expires" header with approx_time(). + Fixes bug 30001; bugfix on 0.4.0.4-rc. diff --git a/changes/bug30151 b/changes/bug30151 new file mode 100644 index 0000000000..8ac9a320a0 --- /dev/null +++ b/changes/bug30151 @@ -0,0 +1,5 @@ + o Minor bugfixes (tor-resolve): + - Fix a memory leak in tor-resolve that could happen if Tor gave it a + malformed SOCKS response. (Memory leaks in tor-resolve don't actually + matter, but it's good to fix them anyway.) Fixes bug 30151; bugfix on + 0.4.0.1-alpha. diff --git a/changes/bug30189 b/changes/bug30189 new file mode 100644 index 0000000000..f8c932a5f9 --- /dev/null +++ b/changes/bug30189 @@ -0,0 +1,4 @@ + o Minor bugfixes (compilation, unusual configuration): + - Avoid failures when building with ALL_BUGS_ARE_FAILED due to + missing declarations of abort(), and prevent other such failures + in the future. Fixes bug 30189; bugfix on 0.3.4.1-alpha. diff --git a/changes/bug30190 b/changes/bug30190 new file mode 100644 index 0000000000..e2352c3b9c --- /dev/null +++ b/changes/bug30190 @@ -0,0 +1,3 @@ + o Minor bugfixes (lib): + do not log a warning for OpenSSL versions that should be compatible + Fixes bug 30190; bugfix on 0.2.4.2-alpha diff --git a/changes/bug30263 b/changes/bug30263 new file mode 100644 index 0000000000..ba81c1b8a1 --- /dev/null +++ b/changes/bug30263 @@ -0,0 +1,3 @@ + o Minor bugfixes (shellcheck): + - Stop looking for scripts in the build directory during + "make shellcheck". Fixes bug 30263; bugfix on 0.4.0.1-alpha. diff --git a/changes/diagnostic_28223_redux b/changes/diagnostic_28223_redux new file mode 100644 index 0000000000..0d7499832e --- /dev/null +++ b/changes/diagnostic_28223_redux @@ -0,0 +1,4 @@ + o Minor features (diagnostic): + - Add more diagnostic log messages in an attempt to solve + the issue of NUL bytes appearing in a microdescriptor cache. + Related to ticket 28223. diff --git a/changes/ticket27821 b/changes/ticket27821 new file mode 100644 index 0000000000..158f308fbf --- /dev/null +++ b/changes/ticket27821 @@ -0,0 +1,3 @@ + o Minor features (HTTP tunnel): + - Return an informative web page when the HTTPTunnelPort is used as an + HTTP proxy. Closes ticket 27821, patch by "eighthave". diff --git a/changes/ticket29660 b/changes/ticket29660 new file mode 100644 index 0000000000..84b8059106 --- /dev/null +++ b/changes/ticket29660 @@ -0,0 +1,5 @@ + o Code simplification and refactoring: + - Remove redundant return values in crypto_format, and the associated + return value checks elsewhere in the code. Make the implementations in + crypto_format consistent, and remove redundant code. + Resolves ticket 29660. diff --git a/changes/ticket29732 b/changes/ticket29732 new file mode 100644 index 0000000000..bb72361c48 --- /dev/null +++ b/changes/ticket29732 @@ -0,0 +1,5 @@ + o Minor features (testing): + - Tor's unit test code now contains a standard set of functions to + replace the PRNG with a deterministic or reproducible version for + testing. Previously, various tests implemented this in various ways. + Implements ticket 29732. diff --git a/changes/ticket29984 b/changes/ticket29984 new file mode 100644 index 0000000000..8631dff27b --- /dev/null +++ b/changes/ticket29984 @@ -0,0 +1,5 @@ + o Minor bugfixes (controller protocol): + - Teach the controller parser to correctly distinguish an object + preceded by an argument list from one without. Previously, it + couldn't distinguish an argument list from the first line of a + multiline object. Fixes bug 29984; bugfix on 0.2.3.8-alpha. diff --git a/changes/ticket30033 b/changes/ticket30033 new file mode 100644 index 0000000000..3f66d049c8 --- /dev/null +++ b/changes/ticket30033 @@ -0,0 +1,4 @@ + o Minor features (developer tooling): + - Call pre-commit git hook from pre-push hook to make sure we're + running documentation and code style checks before pushing to remote + git repository. Implements feature 30033. diff --git a/changes/ticket30051 b/changes/ticket30051 new file mode 100644 index 0000000000..87b6d7611f --- /dev/null +++ b/changes/ticket30051 @@ -0,0 +1,5 @@ + o Minor features (developer tooling): + - Call practracker from pre-push and pre-commit git hooks to let a + developer know if they made any code style violations in their last + commit. This should help preventing code style violations appearing + upstream. Closes ticket 30051. diff --git a/changes/ticket30075 b/changes/ticket30075 new file mode 100644 index 0000000000..288abd7674 --- /dev/null +++ b/changes/ticket30075 @@ -0,0 +1,3 @@ + o Removed features: + - Remove the obsolete script at contrib/dist/tor.sh.in. Resolves issue + 30075. diff --git a/changes/ticket30077 b/changes/ticket30077 new file mode 100644 index 0000000000..9be014730e --- /dev/null +++ b/changes/ticket30077 @@ -0,0 +1,2 @@ + o Code simplification and refactoring (shell scripts): + - Fix shellcheck warnings in fuzz_multi.sh. Resolves issue 30077. diff --git a/changes/ticket30091 b/changes/ticket30091 new file mode 100644 index 0000000000..968ea01f4a --- /dev/null +++ b/changes/ticket30091 @@ -0,0 +1,4 @@ + o Major features (controller protocol): + - Controller commands are now parsed using a generalized parsing + subsystem. Previously, each controller command was responsible for + parsing its own input. Closes ticket 30091. diff --git a/changes/ticket30114 b/changes/ticket30114 new file mode 100644 index 0000000000..a80f7f4dcf --- /dev/null +++ b/changes/ticket30114 @@ -0,0 +1,3 @@ + o Minor features (git scripts): + - In git-pull-all.sh, also fetch the latest tor-github pull requests. + Implements ticket 30114. diff --git a/changes/ticket30117 b/changes/ticket30117 new file mode 100644 index 0000000000..5b6e6dabf7 --- /dev/null +++ b/changes/ticket30117 @@ -0,0 +1,4 @@ + o Testing (continuous integration): + - In Travis, tell timelimit to use stem's backtrace signals. And launch + python directly from timelimit, so python receives the signals from + timelimit, rather than make. Closes ticket 30117. diff --git a/changes/ticket30149 b/changes/ticket30149 new file mode 100644 index 0000000000..a21687ac2f --- /dev/null +++ b/changes/ticket30149 @@ -0,0 +1,3 @@ + o Code simplification and refactoring: + - Add several assertions in an attempt to fix some Coverity warnings. + Closes ticket 30149. diff --git a/changes/ticket30176 b/changes/ticket30176 new file mode 100644 index 0000000000..da23760ce5 --- /dev/null +++ b/changes/ticket30176 @@ -0,0 +1,4 @@ + o Minor features (defense in depth): + - In smartlist_remove_keeporder(), set any pointers that become + unused to NULL, in case a bug causes them to be used later. Closes + ticket 30176. Patch from Tobias Stoeckmann. diff --git a/changes/ticket30234 b/changes/ticket30234 new file mode 100644 index 0000000000..5a0076bad2 --- /dev/null +++ b/changes/ticket30234 @@ -0,0 +1,2 @@ + o Testing (continuous integration): + - In Travis, show stem's tor log after failure. Closes ticket 30234. diff --git a/changes/ticket30261 b/changes/ticket30261 new file mode 100644 index 0000000000..e4a2643c88 --- /dev/null +++ b/changes/ticket30261 @@ -0,0 +1,4 @@ + o Documentation: + - Document how to find git commits and tags for bug fixes in + CodingStandards.md. And update some changes file documentation. + Closes ticket 30261. diff --git a/changes/ticket30293 b/changes/ticket30293 new file mode 100644 index 0000000000..c74b6cd346 --- /dev/null +++ b/changes/ticket30293 @@ -0,0 +1,5 @@ + o Code simplification and refactoring: + - Start move responsibility for knowing about periodic events to the + appropriate subsystems, so that the mainloop doesn't need to know all + the periodic events in the rest of the codebase. Implements tickets + 30293 and 30294. diff --git a/changes/ticket30307 b/changes/ticket30307 new file mode 100644 index 0000000000..abcacb6085 --- /dev/null +++ b/changes/ticket30307 @@ -0,0 +1,4 @@ + o Major features (performance): + - Update our node selection algorithm to exclude nodes in linear time. + Previously, the algorithm was quadratic, which could slow down heavily + used onion services. Closes ticket 30307. diff --git a/changes/ticket30308 b/changes/ticket30308 new file mode 100644 index 0000000000..b78e6b3e9f --- /dev/null +++ b/changes/ticket30308 @@ -0,0 +1,5 @@ + o Minor bugfixes (performance): + - When checking a node for bridge status, use a fast check to make sure + that its identity is set. Previously, we used a constant-time check, + which is not necessary when verifying a BUG() condition that causes + a stack trace. Fixes bug 30308; bugfix on 0.3.5.1-alpha. diff --git a/configure.ac b/configure.ac index 0b80669f03..3ea578bbba 100644 --- a/configure.ac +++ b/configure.ac @@ -2459,7 +2459,6 @@ AC_CONFIG_FILES([ config.rust contrib/dist/suse/tor.sh contrib/operator-tools/tor.logrotate - contrib/dist/tor.sh contrib/dist/torctl contrib/dist/tor.service src/config/torrc.sample diff --git a/contrib/dist/tor.sh.in b/contrib/dist/tor.sh.in deleted file mode 100644 index 92f890681f..0000000000 --- a/contrib/dist/tor.sh.in +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/sh -# -# tor The Onion Router -# -# Startup/shutdown script for tor. This is a wrapper around torctl; -# torctl does the actual work in a relatively system-independent, or at least -# distribution-independent, way, and this script deals with fitting the -# whole thing into the conventions of the particular system at hand. -# This particular script is written for Red Hat/Fedora Linux, and may -# also work on Mandrake, but not SuSE. -# -# These next couple of lines "declare" tor for the "chkconfig" program, -# originally from SGI, used on Red Hat/Fedora and probably elsewhere. -# -# chkconfig: 2345 90 10 -# description: Onion Router - A low-latency anonymous proxy -# - -PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin -DAEMON=/usr/sbin/tor -NAME=tor -DESC="tor daemon" -TORPIDDIR=/var/run/tor -TORPID=$TORPIDDIR/tor.pid -WAITFORDAEMON=60 -ARGS="" - -# Library functions -if [ -f /etc/rc.d/init.d/functions ]; then - . /etc/rc.d/init.d/functions -elif [ -f /etc/init.d/functions ]; then - . /etc/init.d/functions -fi - -TORCTL=@BINDIR@/torctl - -# torctl will use these environment variables -TORUSER=@TORUSER@ -export TORUSER - -if [ -x /bin/su ] ; then - SUPROG=/bin/su -elif [ -x /sbin/su ] ; then - SUPROG=/sbin/su -elif [ -x /usr/bin/su ] ; then - SUPROG=/usr/bin/su -elif [ -x /usr/sbin/su ] ; then - SUPROG=/usr/sbin/su -else - SUPROG=/bin/su -fi - -# Raise ulimit based on number of file descriptors available (thanks, Debian) - -if [ -r /proc/sys/fs/file-max ]; then - system_max=`cat /proc/sys/fs/file-max` - if [ "$system_max" -gt "80000" ] ; then - MAX_FILEDESCRIPTORS=32768 - elif [ "$system_max" -gt "40000" ] ; then - MAX_FILEDESCRIPTORS=16384 - elif [ "$system_max" -gt "10000" ] ; then - MAX_FILEDESCRIPTORS=8192 - else - MAX_FILEDESCRIPTORS=1024 - cat << EOF - -Warning: Your system has very few filedescriptors available in total. - -Maybe you should try raising that by adding 'fs.file-max=100000' to your -/etc/sysctl.conf file. Feel free to pick any number that you deem appropriate. -Then run 'sysctl -p'. See /proc/sys/fs/file-max for the current value, and -file-nr in the same directory for how many of those are used at the moment. - -EOF - fi -else - MAX_FILEDESCRIPTORS=8192 -fi - -NICE="" - -case "$1" in - - start) - if [ -n "$MAX_FILEDESCRIPTORS" ]; then - echo -n "Raising maximum number of filedescriptors (ulimit -n) to $MAX_FILEDESCRIPTORS" - if ulimit -n "$MAX_FILEDESCRIPTORS" ; then - echo "." - else - echo ": FAILED." - fi - fi - - action $"Starting tor:" $TORCTL start - RETVAL=$? - ;; - - stop) - action $"Stopping tor:" $TORCTL stop - RETVAL=$? - ;; - - restart) - action $"Restarting tor:" $TORCTL restart - RETVAL=$? - ;; - - reload) - action $"Reloading tor:" $TORCTL reload - RETVAL=$? - ;; - - status) - $TORCTL status - RETVAL=$? - ;; - - *) - echo "Usage: $0 (start|stop|restart|reload|status)" - RETVAL=1 -esac - -exit $RETVAL diff --git a/contrib/include.am b/contrib/include.am index 742bc58163..9f4775632c 100644 --- a/contrib/include.am +++ b/contrib/include.am @@ -4,7 +4,6 @@ EXTRA_DIST+= \ contrib/client-tools/torify \ contrib/dist/rc.subr \ contrib/dist/suse/tor.sh.in \ - contrib/dist/tor.sh \ contrib/dist/torctl \ contrib/dist/tor.service.in \ contrib/operator-tools/tor-exit-notice.html \ diff --git a/doc/HACKING/CodingStandards.md b/doc/HACKING/CodingStandards.md index 4f229348e4..74db2a39a3 100644 --- a/doc/HACKING/CodingStandards.md +++ b/doc/HACKING/CodingStandards.md @@ -110,12 +110,41 @@ it's a bugfix, mention what bug it fixes and when the bug was introduced. To find out which Git tag the change was introduced in, you can use `git describe --contains <sha1 of commit>`. -If at all possible, try to create this file in the same commit where you are -making the change. Please give it a distinctive name that no other branch will -use for the lifetime of your change. To verify the format of the changes file, -you can use `make check-changes`. This is run automatically as part of -`make check` -- if it fails, we must fix it before we release. These -checks are implemented in `scripts/maint/lintChanges.py`. +If you don't know the commit, you can search the git diffs (-S) for the first +instance of the feature (--reverse). + +For example, for #30224, we wanted to know when the bridge-distribution-request +feature was introduced into Tor: + $ git log -S bridge-distribution-request --reverse + commit ebab521525 + Author: Roger Dingledine <arma@torproject.org> + Date: Sun Nov 13 02:39:16 2016 -0500 + + Add new BridgeDistribution config option + + $ git describe --contains ebab521525 + tor-0.3.2.3-alpha~15^2~4 + +If you need to know all the Tor versions that contain a commit, use: + $ git tag --contains 9f2efd02a1 | sort -V + tor-0.2.5.16 + tor-0.2.8.17 + tor-0.2.9.14 + tor-0.2.9.15 + ... + tor-0.3.0.13 + tor-0.3.1.9 + tor-0.3.1.10 + ... + +If at all possible, try to create the changes file in the same commit where +you are making the change. Please give it a distinctive name that no other +branch will use for the lifetime of your change. We usually use "ticketNNNNN" +or "bugNNNNN", where NNNNN is the ticket number. To verify the format of the +changes file, you can use `make check-changes`. This is run automatically as +part of `make check` -- if it fails, we must fix it as soon as possible, so +that our CI passes. These checks are implemented in +`scripts/maint/lintChanges.py`. Changes file style guide: * Changes files begin with " o Header (subheading):". The header diff --git a/doc/tor.1.txt b/doc/tor.1.txt index f992172405..cbbc3515bb 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1935,13 +1935,14 @@ is non-zero): exit according to the ExitPolicy option, the ReducedExitPolicy option, or the default ExitPolicy (if no other exit policy option is specified). + + - If ExitRelay is set to 0, no traffic is allowed to - exit, and the ExitPolicy and ReducedExitPolicy options are ignored. + + If ExitRelay is set to 0, no traffic is allowed to exit, and the + ExitPolicy, ReducedExitPolicy, and IPv6Exit options are ignored. + + - If ExitRelay is set to "auto", then Tor checks the ExitPolicy and - ReducedExitPolicy options. If either is set, Tor behaves as if ExitRelay - were set to 1. If neither exit policy option is set, Tor behaves as if - ExitRelay were set to 0. (Default: auto) + If ExitRelay is set to "auto", then Tor checks the ExitPolicy, + ReducedExitPolicy, and IPv6Exit options. If at least one of these options + is set, Tor behaves as if ExitRelay were set to 1. If none of these exit + policy options are set, Tor behaves as if ExitRelay were set to 0. + (Default: auto) [[ExitPolicy]] **ExitPolicy** __policy__,__policy__,__...__:: Set an exit policy for this server. Each policy is of the form @@ -2136,8 +2137,9 @@ is non-zero): (Default: 0) [[IPv6Exit]] **IPv6Exit** **0**|**1**:: - If set, and we are an exit node, allow clients to use us for IPv6 - traffic. (Default: 0) + If set, and we are an exit node, allow clients to use us for IPv6 traffic. + When this option is set and ExitRelay is auto, we act as if ExitRelay + is 1. (Default: 0) [[MaxOnionQueueDelay]] **MaxOnionQueueDelay** __NUM__ [**msec**|**second**]:: If we have more onionskins queued for processing than we can process in diff --git a/scripts/git/git-pull-all.sh b/scripts/git/git-pull-all.sh index 0a4898a111..5d1d58e4bf 100755 --- a/scripts/git/git-pull-all.sh +++ b/scripts/git/git-pull-all.sh @@ -174,6 +174,19 @@ function fetch_origin fi } +# Fetch tor-github pull requests. No arguments. +function fetch_tor_github +{ + local cmd="git fetch tor-github" + printf " %s Fetching tor-github..." "$MARKER" + if [ $DRY_RUN -eq 0 ]; then + msg=$( eval "$cmd" 2>&1 ) + validate_ret $? "$msg" + else + printf "\\n %s\\n" "${IWTH}$cmd${CNRM}" + fi +} + ############### # Entry point # ############### @@ -188,8 +201,11 @@ while getopts "n" opt; do esac done -# First, fetch the origin. +# First, fetch tor-github. goto_repo "$ORIGIN_PATH" +fetch_tor_github + +# Then, fetch the origin. fetch_origin # Go over all configured worktree. diff --git a/scripts/git/pre-commit.git-hook b/scripts/git/pre-commit.git-hook index 65fa99f4c4..b285776c04 100755 --- a/scripts/git/pre-commit.git-hook +++ b/scripts/git/pre-commit.git-hook @@ -12,7 +12,7 @@ cd "$workdir" || exit 1 set -e -if [ ! -z "ls ./changes/*" ]; then +if [ -n "$(ls ./changes/)" ]; then python scripts/maint/lintChanges.py ./changes/* fi @@ -26,7 +26,7 @@ if [ -d src/lib ]; then src/test/*.[ch] \ src/test/*/*.[ch] \ src/tools/*.[ch] -elif [ -d src/common]; then +elif [ -d src/common ]; then # This was the layout before 0.3.5 perl scripts/maint/checkSpace.pl -C \ src/common/*/*.[ch] \ @@ -39,3 +39,7 @@ fi if test -e scripts/maint/checkIncludes.py; then python scripts/maint/checkIncludes.py fi + +if [ -e scripts/maint/practracker/practracker.py ]; then + python3 ./scripts/maint/practracker/practracker.py "$workdir" +fi diff --git a/scripts/git/pre-push.git-hook b/scripts/git/pre-push.git-hook index e7a72efa08..740180d6f6 100755 --- a/scripts/git/pre-push.git-hook +++ b/scripts/git/pre-push.git-hook @@ -27,6 +27,19 @@ ref_is_upstream_branch() { fi } +workdir=$(git rev-parse --show-toplevel) +if [ -x "$workdir/.git/hooks/pre-commit" ]; then + if ! "$workdir"/.git/hooks/pre-commit; then + exit 1 + fi +fi + +if [ -e scripts/maint/practracker/practracker.py ]; then + if ! python3 ./scripts/maint/practracker/practracker.py "$workdir"; then + exit 1 + fi +fi + # shellcheck disable=SC2034 while read -r local_ref local_sha remote_ref remote_sha do diff --git a/scripts/maint/practracker/exceptions.txt b/scripts/maint/practracker/exceptions.txt index b84396272d..d90ed1f4bd 100644 --- a/scripts/maint/practracker/exceptions.txt +++ b/scripts/maint/practracker/exceptions.txt @@ -29,8 +29,8 @@ # # Remember: It is better to fix the problem than to add a new exception! -problem file-size /src/app/config/config.c 8491 -problem include-count /src/app/config/config.c 86 +problem file-size /src/app/config/config.c 8492 +problem include-count /src/app/config/config.c 87 problem function-size /src/app/config/config.c:options_act_reversible() 296 problem function-size /src/app/config/config.c:options_act() 588 problem function-size /src/app/config/config.c:resolve_my_address() 192 @@ -54,9 +54,9 @@ problem function-size /src/app/main/main.c:sandbox_init_filter() 291 problem function-size /src/app/main/main.c:run_tor_main_loop() 105 problem function-size /src/app/main/ntmain.c:nt_service_install() 125 problem include-count /src/app/main/shutdown.c 52 -problem file-size /src/core/mainloop/connection.c 5558 +problem file-size /src/core/mainloop/connection.c 5559 problem include-count /src/core/mainloop/connection.c 61 -problem function-size /src/core/mainloop/connection.c:connection_free_minimal() 184 +problem function-size /src/core/mainloop/connection.c:connection_free_minimal() 185 problem function-size /src/core/mainloop/connection.c:connection_listener_new() 328 problem function-size /src/core/mainloop/connection.c:connection_handle_listener_read() 161 problem function-size /src/core/mainloop/connection.c:connection_connect_sockaddr() 103 @@ -78,7 +78,7 @@ problem function-size /src/core/or/channeltls.c:channel_tls_process_versions_cel problem function-size /src/core/or/channeltls.c:channel_tls_process_netinfo_cell() 214 problem function-size /src/core/or/channeltls.c:channel_tls_process_certs_cell() 246 problem function-size /src/core/or/channeltls.c:channel_tls_process_authenticate_cell() 202 -problem file-size /src/core/or/circuitbuild.c 3060 +problem file-size /src/core/or/circuitbuild.c 3061 problem include-count /src/core/or/circuitbuild.c 53 problem function-size /src/core/or/circuitbuild.c:get_unique_circ_id_by_chan() 128 problem function-size /src/core/or/circuitbuild.c:circuit_extend() 147 @@ -102,7 +102,7 @@ problem function-size /src/core/or/circuituse.c:circuit_get_open_circ_or_launch( problem function-size /src/core/or/circuituse.c:connection_ap_handshake_attach_circuit() 244 problem function-size /src/core/or/command.c:command_process_create_cell() 156 problem function-size /src/core/or/command.c:command_process_relay_cell() 132 -problem file-size /src/core/or/connection_edge.c 4550 +problem file-size /src/core/or/connection_edge.c 4575 problem include-count /src/core/or/connection_edge.c 64 problem function-size /src/core/or/connection_edge.c:connection_ap_expire_beginning() 117 problem function-size /src/core/or/connection_edge.c:connection_ap_handshake_rewrite() 192 @@ -117,7 +117,7 @@ problem include-count /src/core/or/connection_or.c 51 problem function-size /src/core/or/connection_or.c:connection_or_group_set_badness_() 105 problem function-size /src/core/or/connection_or.c:connection_or_client_learned_peer_id() 144 problem function-size /src/core/or/connection_or.c:connection_or_compute_authenticate_cell_body() 235 -problem file-size /src/core/or/policies.c 3163 +problem file-size /src/core/or/policies.c 3171 problem function-size /src/core/or/policies.c:policy_summarize() 107 problem function-size /src/core/or/protover.c:protover_all_supported() 117 problem file-size /src/core/or/relay.c 3173 @@ -152,7 +152,7 @@ problem function-size /src/feature/control/control_cmd.c:handle_control_add_onio problem function-size /src/feature/control/control_cmd.c:add_onion_helper_keyarg() 125 problem function-size /src/feature/control/control_cmd.c:handle_control_command() 104 problem function-size /src/feature/control/control_events.c:control_event_stream_status() 119 -problem include-count /src/feature/control/control_getinfo.c 52 +problem include-count /src/feature/control/control_getinfo.c 53 problem function-size /src/feature/control/control_getinfo.c:getinfo_helper_misc() 109 problem function-size /src/feature/control/control_getinfo.c:getinfo_helper_dir() 304 problem function-size /src/feature/control/control_getinfo.c:getinfo_helper_events() 236 @@ -187,7 +187,7 @@ problem function-size /src/feature/dirclient/dirclient.c:handle_response_fetch_c problem function-size /src/feature/dircommon/consdiff.c:gen_ed_diff() 204 problem function-size /src/feature/dircommon/consdiff.c:apply_ed_diff() 159 problem function-size /src/feature/dirparse/authcert_parse.c:authority_cert_parse_from_string() 182 -problem function-size /src/feature/dirparse/microdesc_parse.c:microdescs_parse_from_string() 154 +problem function-size /src/feature/dirparse/microdesc_parse.c:microdescs_parse_from_string() 169 problem function-size /src/feature/dirparse/ns_parse.c:routerstatus_parse_entry_from_string() 286 problem function-size /src/feature/dirparse/ns_parse.c:networkstatus_verify_bw_weights() 389 problem function-size /src/feature/dirparse/ns_parse.c:networkstatus_parse_vote_from_string() 638 @@ -214,11 +214,11 @@ problem function-size /src/feature/nodelist/authcert.c:trusted_dirs_load_certs_f problem function-size /src/feature/nodelist/authcert.c:authority_certs_fetch_missing() 296 problem function-size /src/feature/nodelist/fmt_routerstatus.c:routerstatus_format_entry() 166 problem function-size /src/feature/nodelist/microdesc.c:microdesc_cache_rebuild() 134 -problem include-count /src/feature/nodelist/networkstatus.c 61 +problem include-count /src/feature/nodelist/networkstatus.c 62 problem function-size /src/feature/nodelist/networkstatus.c:networkstatus_check_consensus_signature() 176 problem function-size /src/feature/nodelist/networkstatus.c:networkstatus_set_current_consensus() 293 problem function-size /src/feature/nodelist/node_select.c:router_pick_directory_server_impl() 123 -problem function-size /src/feature/nodelist/node_select.c:compute_weighted_bandwidths() 205 +problem function-size /src/feature/nodelist/node_select.c:compute_weighted_bandwidths() 206 problem function-size /src/feature/nodelist/node_select.c:router_pick_trusteddirserver_impl() 114 problem function-size /src/feature/nodelist/nodelist.c:compute_frac_paths_available() 193 problem file-size /src/feature/nodelist/routerlist.c 3234 @@ -287,5 +287,5 @@ problem function-size /src/lib/tls/tortls_openssl.c:tor_tls_context_new() 171 problem function-size /src/lib/tls/x509_nss.c:tor_tls_create_certificate_internal() 126 problem function-size /src/tools/tor-gencert.c:parse_commandline() 111 problem function-size /src/tools/tor-resolve.c:build_socks5_resolve_request() 104 -problem function-size /src/tools/tor-resolve.c:do_resolve() 173 +problem function-size /src/tools/tor-resolve.c:do_resolve() 175 problem function-size /src/tools/tor-resolve.c:main() 112 diff --git a/src/app/config/config.c b/src/app/config/config.c index 46dc15b069..81a83e2c5f 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -156,6 +156,7 @@ #include "lib/evloop/procmon.h" #include "feature/dirauth/dirvote.h" +#include "feature/dirauth/dirauth_periodic.h" #include "feature/dirauth/recommend_pkg.h" #include "feature/dirauth/authmode.h" diff --git a/src/app/main/shutdown.c b/src/app/main/shutdown.c index 314e33f228..9239a0cf0f 100644 --- a/src/app/main/shutdown.c +++ b/src/app/main/shutdown.c @@ -18,7 +18,6 @@ #include "app/main/shutdown.h" #include "app/main/subsysmgr.h" #include "core/mainloop/connection.h" -#include "core/mainloop/mainloop.h" #include "core/mainloop/mainloop_pubsub.h" #include "core/or/channeltls.h" #include "core/or/circuitlist.h" @@ -126,7 +125,6 @@ tor_free_all(int postfork) } geoip_free_all(); geoip_stats_free_all(); - dirvote_free_all(); routerlist_free_all(); networkstatus_free_all(); addressmap_free_all(); @@ -176,7 +174,6 @@ tor_free_all(int postfork) /* stuff in main.c */ tor_mainloop_disconnect_pubsub(); - tor_mainloop_free_all(); if (!postfork) { release_lockfile(); diff --git a/src/app/main/subsystem_list.c b/src/app/main/subsystem_list.c index 3834176182..00effe01aa 100644 --- a/src/app/main/subsystem_list.c +++ b/src/app/main/subsystem_list.c @@ -8,6 +8,7 @@ #include "lib/cc/compat_compiler.h" #include "lib/cc/torint.h" +#include "core/mainloop/mainloop_sys.h" #include "core/or/ocirc_event_sys.h" #include "core/or/orconn_event_sys.h" #include "feature/control/btrack_sys.h" @@ -23,6 +24,8 @@ #include "lib/wallclock/wallclock_sys.h" #include "lib/process/process_sys.h" +#include "feature/dirauth/dirauth_sys.h" + #include <stddef.h> /** @@ -44,6 +47,12 @@ const subsys_fns_t *tor_subsystems[] = { &sys_orconn_event, /* -33 */ &sys_ocirc_event, /* -32 */ &sys_btrack, /* -30 */ + + &sys_mainloop, /* 5 */ + +#ifdef HAVE_MODULE_DIRAUTH + &sys_dirauth, /* 70 */ +#endif }; const unsigned n_tor_subsystems = ARRAY_LENGTH(tor_subsystems); diff --git a/src/config/torrc.sample.in b/src/config/torrc.sample.in index c2ae707e93..9d514e6bda 100644 --- a/src/config/torrc.sample.in +++ b/src/config/torrc.sample.in @@ -174,13 +174,11 @@ ## Uncomment this if you want your relay to be an exit, with the default ## exit policy (or whatever exit policy you set below). -## (If ReducedExitPolicy or ExitPolicy are set, relays are exits. -## If neither exit policy option is set, relays are non-exits.) +## (If ReducedExitPolicy, ExitPolicy, or IPv6Exit are set, relays are exits. +## If none of these options are set, relays are non-exits.) #ExitRelay 1 ## Uncomment this if you want your relay to allow IPv6 exit traffic. -## You must also set ExitRelay, ReducedExitPolicy, or ExitPolicy to make your -## relay into an exit. ## (Relays do not allow any exit traffic by default.) #IPv6Exit 1 diff --git a/src/core/include.am b/src/core/include.am index 9824601725..4ec42182a6 100644 --- a/src/core/include.am +++ b/src/core/include.am @@ -24,6 +24,7 @@ LIBTOR_APP_A_SOURCES = \ src/core/mainloop/cpuworker.c \ src/core/mainloop/mainloop.c \ src/core/mainloop/mainloop_pubsub.c \ + src/core/mainloop/mainloop_sys.c \ src/core/mainloop/netstatus.c \ src/core/mainloop/periodic.c \ src/core/or/address_set.c \ @@ -175,6 +176,8 @@ LIBTOR_APP_TESTING_A_SOURCES = $(LIBTOR_APP_A_SOURCES) # The Directory Authority module. MODULE_DIRAUTH_SOURCES = \ src/feature/dirauth/authmode.c \ + src/feature/dirauth/dirauth_periodic.c \ + src/feature/dirauth/dirauth_sys.c \ src/feature/dirauth/dircollate.c \ src/feature/dirauth/dirvote.c \ src/feature/dirauth/shared_random.c \ @@ -222,6 +225,7 @@ noinst_HEADERS += \ src/core/mainloop/cpuworker.h \ src/core/mainloop/mainloop.h \ src/core/mainloop/mainloop_pubsub.h \ + src/core/mainloop/mainloop_sys.h \ src/core/mainloop/netstatus.h \ src/core/mainloop/periodic.h \ src/core/or/addr_policy_st.h \ @@ -298,6 +302,7 @@ noinst_HEADERS += \ src/feature/control/control.h \ src/feature/control/control_auth.h \ src/feature/control/control_cmd.h \ + src/feature/control/control_cmd_args_st.h \ src/feature/control/control_connection_st.h \ src/feature/control/control_events.h \ src/feature/control/control_fmt.h \ @@ -306,6 +311,8 @@ noinst_HEADERS += \ src/feature/control/getinfo_geoip.h \ src/feature/dirauth/authmode.h \ src/feature/dirauth/bwauth.h \ + src/feature/dirauth/dirauth_periodic.h \ + src/feature/dirauth/dirauth_sys.h \ src/feature/dirauth/dircollate.h \ src/feature/dirauth/dirvote.h \ src/feature/dirauth/dsigs_parse.h \ diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 51c19b4c4c..30504e4edb 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -697,6 +697,7 @@ connection_free_minimal(connection_t *conn) control_connection_t *control_conn = TO_CONTROL_CONN(conn); tor_free(control_conn->safecookie_client_hash); tor_free(control_conn->incoming_cmd); + tor_free(control_conn->current_cmd); if (control_conn->ephemeral_onion_services) { SMARTLIST_FOREACH(control_conn->ephemeral_onion_services, char *, cp, { memwipe(cp, 0, strlen(cp)); diff --git a/src/core/mainloop/mainloop.c b/src/core/mainloop/mainloop.c index c9f2b0d896..30dad956ae 100644 --- a/src/core/mainloop/mainloop.c +++ b/src/core/mainloop/mainloop.c @@ -75,7 +75,6 @@ #include "feature/control/control.h" #include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" -#include "feature/dirauth/reachability.h" #include "feature/dircache/consdiffmgr.h" #include "feature/dircache/dirserv.h" #include "feature/dircommon/directory.h" @@ -106,9 +105,6 @@ #include <event2/event.h> -#include "feature/dirauth/dirvote.h" -#include "feature/dirauth/authmode.h" - #include "core/or/cell_st.h" #include "core/or/entry_connection_st.h" #include "feature/nodelist/networkstatus_st.h" @@ -1346,7 +1342,6 @@ static int periodic_events_initialized = 0; #define CALLBACK(name) \ static int name ## _callback(time_t, const or_options_t *) CALLBACK(add_entropy); -CALLBACK(check_authority_cert); CALLBACK(check_canonical_channels); CALLBACK(check_descriptor); CALLBACK(check_dns_honesty); @@ -1356,14 +1351,11 @@ CALLBACK(check_for_reachability_bw); CALLBACK(check_onion_keys_expiry_time); CALLBACK(clean_caches); CALLBACK(clean_consdiffmgr); -CALLBACK(dirvote); -CALLBACK(downrate_stability); CALLBACK(expire_old_ciruits_serverside); CALLBACK(fetch_networkstatus); CALLBACK(heartbeat); CALLBACK(hs_service); CALLBACK(launch_descriptor_fetches); -CALLBACK(launch_reachability_tests); CALLBACK(prune_old_routers); CALLBACK(reachability_warnings); CALLBACK(record_bridge_stats); @@ -1373,7 +1365,6 @@ CALLBACK(retry_dns); CALLBACK(retry_listeners); CALLBACK(rotate_onion_key); CALLBACK(rotate_x509_certificate); -CALLBACK(save_stability); CALLBACK(save_state); CALLBACK(write_bridge_ns); CALLBACK(write_stats_file); @@ -1387,7 +1378,7 @@ CALLBACK(second_elapsed); PERIODIC_EVENT(name, PERIODIC_EVENT_ROLE_ ## r, f) #define FL(name) (PERIODIC_EVENT_FLAG_ ## name) -STATIC periodic_event_item_t periodic_events[] = { +STATIC periodic_event_item_t mainloop_periodic_events[] = { /* Everyone needs to run these. They need to have very long timeouts for * that to be safe. */ @@ -1428,15 +1419,6 @@ STATIC periodic_event_item_t periodic_events[] = { CALLBACK(retry_dns, ROUTER, 0), CALLBACK(rotate_onion_key, ROUTER, 0), - /* Authorities (bridge and directory) only. */ - CALLBACK(downrate_stability, AUTHORITIES, 0), - CALLBACK(launch_reachability_tests, AUTHORITIES, FL(NEED_NET)), - CALLBACK(save_stability, AUTHORITIES, 0), - - /* Directory authority only. */ - CALLBACK(check_authority_cert, DIRAUTH, 0), - CALLBACK(dirvote, DIRAUTH, FL(NEED_NET)), - /* Relay only. */ CALLBACK(check_canonical_channels, RELAY, FL(NEED_NET)), CALLBACK(check_dns_honesty, RELAY, FL(NEED_NET)), @@ -1470,7 +1452,6 @@ STATIC periodic_event_item_t periodic_events[] = { * can access them by name. We also keep them inside periodic_events[] * so that we can implement "reset all timers" in a reasonable way. */ static periodic_event_item_t *check_descriptor_event=NULL; -static periodic_event_item_t *dirvote_event=NULL; static periodic_event_item_t *fetch_networkstatus_event=NULL; static periodic_event_item_t *launch_descriptor_fetches_event=NULL; static periodic_event_item_t *check_dns_honesty_event=NULL; @@ -1485,24 +1466,7 @@ static periodic_event_item_t *prune_old_routers_event=NULL; void reset_all_main_loop_timers(void) { - int i; - for (i = 0; periodic_events[i].name; ++i) { - periodic_event_reschedule(&periodic_events[i]); - } -} - -/** Return the member of periodic_events[] whose name is <b>name</b>. - * Return NULL if no such event is found. - */ -static periodic_event_item_t * -find_periodic_event(const char *name) -{ - int i; - for (i = 0; periodic_events[i].name; ++i) { - if (strcmp(name, periodic_events[i].name) == 0) - return &periodic_events[i]; - } - return NULL; + periodic_events_reset_all(); } /** Return a bitmask of the roles this tor instance is configured for using @@ -1565,9 +1529,9 @@ initialize_periodic_events_cb(evutil_socket_t fd, short events, void *data) rescan_periodic_events(get_options()); } -/** Set up all the members of periodic_events[], and configure them all to be - * launched from a callback. */ -STATIC void +/** Set up all the members of mainloop_periodic_events[], and configure them + * all to be launched from a callback. */ +void initialize_periodic_events(void) { if (periodic_events_initialized) @@ -1575,37 +1539,33 @@ initialize_periodic_events(void) periodic_events_initialized = 1; - /* Set up all periodic events. We'll launch them by roles. */ - int i; - for (i = 0; periodic_events[i].name; ++i) { - periodic_event_setup(&periodic_events[i]); + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_events_register(&mainloop_periodic_events[i]); } + /* Set up all periodic events. We'll launch them by roles. */ + #define NAMED_CALLBACK(name) \ - STMT_BEGIN name ## _event = find_periodic_event( #name ); STMT_END + STMT_BEGIN name ## _event = periodic_events_find( #name ); STMT_END NAMED_CALLBACK(check_descriptor); NAMED_CALLBACK(prune_old_routers); - NAMED_CALLBACK(dirvote); NAMED_CALLBACK(fetch_networkstatus); NAMED_CALLBACK(launch_descriptor_fetches); NAMED_CALLBACK(check_dns_honesty); NAMED_CALLBACK(save_state); - - struct timeval one_second = { 1, 0 }; - initialize_periodic_events_event = tor_evtimer_new( - tor_libevent_get_base(), - initialize_periodic_events_cb, NULL); - event_add(initialize_periodic_events_event, &one_second); } STATIC void teardown_periodic_events(void) { - int i; - for (i = 0; periodic_events[i].name; ++i) { - periodic_event_destroy(&periodic_events[i]); - } + periodic_events_disconnect_all(); + check_descriptor_event = NULL; + fetch_networkstatus_event = NULL; + launch_descriptor_fetches_event = NULL; + check_dns_honesty_event = NULL; + save_state_event = NULL; + prune_old_routers_event = NULL; periodic_events_initialized = 0; } @@ -1640,40 +1600,7 @@ rescan_periodic_events(const or_options_t *options) { tor_assert(options); - /* Avoid scanning the event list if we haven't initialized it yet. This is - * particularly useful for unit tests in order to avoid initializing main - * loop events everytime. */ - if (!periodic_events_initialized) { - return; - } - - int roles = get_my_roles(options); - - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; - - int enable = !!(item->roles & roles); - - /* Handle the event flags. */ - if (net_is_disabled() && - (item->flags & PERIODIC_EVENT_FLAG_NEED_NET)) { - enable = 0; - } - - /* Enable the event if needed. It is safe to enable an event that was - * already enabled. Same goes for disabling it. */ - if (enable) { - log_debug(LD_GENERAL, "Launching periodic event %s", item->name); - periodic_event_enable(item); - } else { - log_debug(LD_GENERAL, "Disabling periodic event %s", item->name); - if (item->flags & PERIODIC_EVENT_FLAG_RUN_ON_DISABLE) { - periodic_event_schedule_and_disable(item); - } else { - periodic_event_disable(item); - } - } - } + periodic_events_rescan_by_roles(get_my_roles(options), net_is_disabled()); } /* We just got new options globally set, see if we need to enabled or disable @@ -1681,13 +1608,7 @@ rescan_periodic_events(const or_options_t *options) void periodic_events_on_new_options(const or_options_t *options) { - /* Only if we've already initialized the events, rescan the list which will - * enable or disable events depending on our roles. This will be called at - * bootup and we don't want this function to initialize the events because - * they aren't set up at this stage. */ - if (periodic_events_initialized) { - rescan_periodic_events(options); - } + rescan_periodic_events(options); } /** @@ -1770,29 +1691,6 @@ mainloop_schedule_shutdown(int delay_sec) mainloop_event_schedule(scheduled_shutdown_ev, &delay_tv); } -#define LONGEST_TIMER_PERIOD (30 * 86400) -/** Helper: Return the number of seconds between <b>now</b> and <b>next</b>, - * clipped to the range [1 second, LONGEST_TIMER_PERIOD]. */ -static inline int -safe_timer_diff(time_t now, time_t next) -{ - if (next > now) { - /* There were no computers at signed TIME_MIN (1902 on 32-bit systems), - * and nothing that could run Tor. It's a bug if 'next' is around then. - * On 64-bit systems with signed TIME_MIN, TIME_MIN is before the Big - * Bang. We cannot extrapolate past a singularity, but there was probably - * nothing that could run Tor then, either. - **/ - tor_assert(next > TIME_MIN + LONGEST_TIMER_PERIOD); - - if (next - LONGEST_TIMER_PERIOD > now) - return LONGEST_TIMER_PERIOD; - return (int)(next - now); - } else { - return 1; - } -} - /** Perform regular maintenance tasks. This function gets run once per * second. */ @@ -2062,102 +1960,6 @@ check_network_participation_callback(time_t now, const or_options_t *options) } /** - * Periodic callback: if we're an authority, make sure we test - * the routers on the network for reachability. - */ -static int -launch_reachability_tests_callback(time_t now, const or_options_t *options) -{ - if (authdir_mode_tests_reachability(options) && - !net_is_disabled()) { - /* try to determine reachability of the other Tor relays */ - dirserv_test_reachability(now); - } - return REACHABILITY_TEST_INTERVAL; -} - -/** - * Periodic callback: if we're an authority, discount the stability - * information (and other rephist information) that's older. - */ -static int -downrate_stability_callback(time_t now, const or_options_t *options) -{ - (void)options; - /* 1d. Periodically, we discount older stability information so that new - * stability info counts more, and save the stability information to disk as - * appropriate. */ - time_t next = rep_hist_downrate_old_runs(now); - return safe_timer_diff(now, next); -} - -/** - * Periodic callback: if we're an authority, record our measured stability - * information from rephist in an mtbf file. - */ -static int -save_stability_callback(time_t now, const or_options_t *options) -{ - if (authdir_mode_tests_reachability(options)) { - if (rep_hist_record_mtbf_data(now, 1)<0) { - log_warn(LD_GENERAL, "Couldn't store mtbf data."); - } - } -#define SAVE_STABILITY_INTERVAL (30*60) - return SAVE_STABILITY_INTERVAL; -} - -/** - * Periodic callback: if we're an authority, check on our authority - * certificate (the one that authenticates our authority signing key). - */ -static int -check_authority_cert_callback(time_t now, const or_options_t *options) -{ - (void)now; - (void)options; - /* 1e. Periodically, if we're a v3 authority, we check whether our cert is - * close to expiring and warn the admin if it is. */ - v3_authority_check_key_expiry(); -#define CHECK_V3_CERTIFICATE_INTERVAL (5*60) - return CHECK_V3_CERTIFICATE_INTERVAL; -} - -/** - * Scheduled callback: Run directory-authority voting functionality. - * - * The schedule is a bit complicated here, so dirvote_act() manages the - * schedule itself. - **/ -static int -dirvote_callback(time_t now, const or_options_t *options) -{ - if (!authdir_mode_v3(options)) { - tor_assert_nonfatal_unreached(); - return 3600; - } - - time_t next = dirvote_act(options, now); - if (BUG(next == TIME_MAX)) { - /* This shouldn't be returned unless we called dirvote_act() without - * being an authority. If it happens, maybe our configuration will - * fix itself in an hour or so? */ - return 3600; - } - return safe_timer_diff(now, next); -} - -/** Reschedule the directory-authority voting event. Run this whenever the - * schedule has changed. */ -void -reschedule_dirvote(const or_options_t *options) -{ - if (periodic_events_initialized && authdir_mode_v3(options)) { - periodic_event_reschedule(dirvote_event); - } -} - -/** * Periodic callback: If our consensus is too old, recalculate whether * we can actually use it. */ @@ -2798,8 +2600,7 @@ dns_servers_relaunch_checks(void) { if (server_mode(get_options())) { dns_reset_correctness_checks(); - if (periodic_events_initialized) { - tor_assert(check_dns_honesty_event); + if (check_dns_honesty_event) { periodic_event_reschedule(check_dns_honesty_event); } } @@ -2809,8 +2610,6 @@ dns_servers_relaunch_checks(void) void initialize_mainloop_events(void) { - initialize_periodic_events(); - if (!schedule_active_linked_connections_event) { schedule_active_linked_connections_event = mainloop_event_postloop_new(schedule_active_linked_connections_cb, NULL); @@ -2828,9 +2627,17 @@ do_main_loop(void) /* initialize the periodic events first, so that code that depends on the * events being present does not assert. */ - initialize_periodic_events(); + tor_assert(periodic_events_initialized); initialize_mainloop_events(); + periodic_events_connect_all(); + + struct timeval one_second = { 1, 0 }; + initialize_periodic_events_event = tor_evtimer_new( + tor_libevent_get_base(), + initialize_periodic_events_cb, NULL); + event_add(initialize_periodic_events_event, &one_second); + #ifdef HAVE_SYSTEMD_209 uint64_t watchdog_delay; /* set up systemd watchdog notification. */ diff --git a/src/core/mainloop/mainloop.h b/src/core/mainloop/mainloop.h index 6ed93fa900..3a611f81aa 100644 --- a/src/core/mainloop/mainloop.h +++ b/src/core/mainloop/mainloop.h @@ -62,7 +62,6 @@ void reset_all_main_loop_timers(void); void reschedule_descriptor_update_check(void); void reschedule_directory_downloads(void); void reschedule_or_state_save(void); -void reschedule_dirvote(const or_options_t *options); void mainloop_schedule_postloop_cleanup(void); void rescan_periodic_events(const or_options_t *options); MOCK_DECL(void, schedule_rescan_periodic_events,(void)); @@ -90,6 +89,7 @@ void mainloop_schedule_shutdown(int delay_sec); void tor_init_connection_lists(void); void initialize_mainloop_events(void); +void initialize_periodic_events(void); void tor_mainloop_free_all(void); struct token_bucket_rw_t; @@ -102,7 +102,6 @@ extern struct token_bucket_rw_t global_relayed_bucket; #ifdef MAINLOOP_PRIVATE STATIC int run_main_loop_until_done(void); STATIC void close_closeable_connections(void); -STATIC void initialize_periodic_events(void); STATIC void teardown_periodic_events(void); STATIC int get_my_roles(const or_options_t *); STATIC int check_network_participation_callback(time_t now, @@ -113,7 +112,7 @@ extern smartlist_t *connection_array; /* We need the periodic_event_item_t definition. */ #include "core/mainloop/periodic.h" -extern periodic_event_item_t periodic_events[]; +extern periodic_event_item_t mainloop_periodic_events[]; #endif #endif /* defined(MAIN_PRIVATE) */ diff --git a/src/core/mainloop/mainloop_sys.c b/src/core/mainloop/mainloop_sys.c new file mode 100644 index 0000000000..fbd5a40327 --- /dev/null +++ b/src/core/mainloop/mainloop_sys.c @@ -0,0 +1,32 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "core/or/or.h" +#include "core/mainloop/mainloop_sys.h" +#include "core/mainloop/mainloop.h" + +#include "lib/subsys/subsys.h" + +static int +subsys_mainloop_initialize(void) +{ + initialize_periodic_events(); + return 0; +} + +static void +subsys_mainloop_shutdown(void) +{ + tor_mainloop_free_all(); +} + +const struct subsys_fns_t sys_mainloop = { + .name = "mainloop", + .supported = true, + .level = 5, + .initialize = subsys_mainloop_initialize, + .shutdown = subsys_mainloop_shutdown, +}; diff --git a/src/core/mainloop/mainloop_sys.h b/src/core/mainloop/mainloop_sys.h new file mode 100644 index 0000000000..14c567278c --- /dev/null +++ b/src/core/mainloop/mainloop_sys.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef MAINLOOP_SYS_H +#define MAINLOOP_SYS_H + +extern const struct subsys_fns_t sys_mainloop; + +#endif diff --git a/src/core/mainloop/periodic.c b/src/core/mainloop/periodic.c index c0363b15ea..dbc4553a73 100644 --- a/src/core/mainloop/periodic.c +++ b/src/core/mainloop/periodic.c @@ -6,9 +6,22 @@ * * \brief Generic backend for handling periodic events. * - * The events in this module are used by main.c to track items that need + * The events in this module are used to track items that need * to fire once every N seconds, possibly picking a new interval each time - * that they fire. See periodic_events[] in main.c for examples. + * that they fire. See periodic_events[] in mainloop.c for examples. + * + * This module manages a global list of periodic_event_item_t objects, + * each corresponding to a single event. To register an event, pass it to + * periodic_events_register() when initializing your subsystem. + * + * Registering an event makes the periodic event subsystem know about it, but + * doesn't cause the event to get created immediately. Before the event can + * be started, periodic_event_connect_all() must be called by mainloop.c to + * connect all the events to Libevent. + * + * We expect that periodic_event_item_t objects will be statically allocated; + * we set them up and tear them down here, but we don't take ownership of + * them. */ #include "core/or/or.h" @@ -24,6 +37,12 @@ */ static const int MAX_INTERVAL = 10 * 365 * 86400; +/** + * Global list of periodic events that have been registered with + * <b>periodic_event_register</a>. + **/ +static smartlist_t *the_periodic_events = NULL; + /** Set the event <b>event</b> to run in <b>next_interval</b> seconds from * now. */ static void @@ -87,15 +106,16 @@ periodic_event_dispatch(mainloop_event_t *ev, void *data) void periodic_event_reschedule(periodic_event_item_t *event) { - /* Don't reschedule a disabled event. */ - if (periodic_event_is_enabled(event)) { + /* Don't reschedule a disabled or uninitialized event. */ + if (event->ev && periodic_event_is_enabled(event)) { periodic_event_set_interval(event, 1); } } -/** Initializes the libevent backend for a periodic event. */ +/** Connects a periodic event to the Libevent backend. Does not launch the + * event immediately. */ void -periodic_event_setup(periodic_event_item_t *event) +periodic_event_connect(periodic_event_item_t *event) { if (event->ev) { /* Already setup? This is a bug */ log_err(LD_BUG, "Initial dispatch should only be done once."); @@ -113,7 +133,7 @@ void periodic_event_launch(periodic_event_item_t *event) { if (! event->ev) { /* Not setup? This is a bug */ - log_err(LD_BUG, "periodic_event_launch without periodic_event_setup"); + log_err(LD_BUG, "periodic_event_launch without periodic_event_connect"); tor_assert(0); } /* Event already enabled? This is a bug */ @@ -127,9 +147,9 @@ periodic_event_launch(periodic_event_item_t *event) periodic_event_dispatch(event->ev, event); } -/** Release all storage associated with <b>event</b> */ -void -periodic_event_destroy(periodic_event_item_t *event) +/** Disconnect and unregister the periodic event in <b>event</b> */ +static void +periodic_event_disconnect(periodic_event_item_t *event) { if (!event) return; @@ -184,3 +204,161 @@ periodic_event_schedule_and_disable(periodic_event_item_t *event) mainloop_event_activate(event->ev); } + +/** + * Add <b>item</b> to the list of periodic events. + * + * Note that <b>item</b> should be statically allocated: we do not + * take ownership of it. + **/ +void +periodic_events_register(periodic_event_item_t *item) +{ + if (!the_periodic_events) + the_periodic_events = smartlist_new(); + + if (BUG(smartlist_contains(the_periodic_events, item))) + return; + + smartlist_add(the_periodic_events, item); +} + +/** + * Make all registered periodic events connect to the libevent backend. + */ +void +periodic_events_connect_all(void) +{ + if (! the_periodic_events) + return; + + SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) { + if (item->ev) + continue; + periodic_event_connect(item); + } SMARTLIST_FOREACH_END(item); +} + +/** + * Reset all the registered periodic events so we'll do all our actions again + * as if we just started up. + * + * Useful if our clock just moved back a long time from the future, + * so we don't wait until that future arrives again before acting. + */ +void +periodic_events_reset_all(void) +{ + if (! the_periodic_events) + return; + + SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) { + if (!item->ev) + continue; + + periodic_event_reschedule(item); + } SMARTLIST_FOREACH_END(item); +} + +/** + * Return the registered periodic event whose name is <b>name</b>. + * Return NULL if no such event is found. + */ +periodic_event_item_t * +periodic_events_find(const char *name) +{ + if (! the_periodic_events) + return NULL; + + SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) { + if (strcmp(name, item->name) == 0) + return item; + } SMARTLIST_FOREACH_END(item); + return NULL; +} + +/** + * Start or stop registered periodic events, depending on our current set of + * roles. + * + * Invoked when our list of roles, or the net_disabled flag has changed. + **/ +void +periodic_events_rescan_by_roles(int roles, bool net_disabled) +{ + if (! the_periodic_events) + return; + + SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) { + if (!item->ev) + continue; + + int enable = !!(item->roles & roles); + + /* Handle the event flags. */ + if (net_disabled && + (item->flags & PERIODIC_EVENT_FLAG_NEED_NET)) { + enable = 0; + } + + /* Enable the event if needed. It is safe to enable an event that was + * already enabled. Same goes for disabling it. */ + if (enable) { + log_debug(LD_GENERAL, "Launching periodic event %s", item->name); + periodic_event_enable(item); + } else { + log_debug(LD_GENERAL, "Disabling periodic event %s", item->name); + if (item->flags & PERIODIC_EVENT_FLAG_RUN_ON_DISABLE) { + periodic_event_schedule_and_disable(item); + } else { + periodic_event_disable(item); + } + } + } SMARTLIST_FOREACH_END(item); +} + +/** + * Invoked at shutdown: disconnect and unregister all periodic events. + * + * Does not free the periodic_event_item_t object themselves, because we do + * not own them. + */ +void +periodic_events_disconnect_all(void) +{ + if (! the_periodic_events) + return; + + SMARTLIST_FOREACH_BEGIN(the_periodic_events, periodic_event_item_t *, item) { + periodic_event_disconnect(item); + } SMARTLIST_FOREACH_END(item); + + smartlist_free(the_periodic_events); +} + +#define LONGEST_TIMER_PERIOD (30 * 86400) +/** Helper: Return the number of seconds between <b>now</b> and <b>next</b>, + * clipped to the range [1 second, LONGEST_TIMER_PERIOD]. + * + * We use this to answer the question, "how many seconds is it from now until + * next" in periodic timer callbacks. Don't use it for other purposes + **/ +int +safe_timer_diff(time_t now, time_t next) +{ + if (next > now) { + /* There were no computers at signed TIME_MIN (1902 on 32-bit systems), + * and nothing that could run Tor. It's a bug if 'next' is around then. + * On 64-bit systems with signed TIME_MIN, TIME_MIN is before the Big + * Bang. We cannot extrapolate past a singularity, but there was probably + * nothing that could run Tor then, either. + **/ + tor_assert(next > TIME_MIN + LONGEST_TIMER_PERIOD); + + if (next - LONGEST_TIMER_PERIOD > now) + return LONGEST_TIMER_PERIOD; + return (int)(next - now); + } else { + return 1; + } +} diff --git a/src/core/mainloop/periodic.h b/src/core/mainloop/periodic.h index 344fc9ad25..a9aa461969 100644 --- a/src/core/mainloop/periodic.h +++ b/src/core/mainloop/periodic.h @@ -83,11 +83,20 @@ periodic_event_is_enabled(const periodic_event_item_t *item) } void periodic_event_launch(periodic_event_item_t *event); -void periodic_event_setup(periodic_event_item_t *event); -void periodic_event_destroy(periodic_event_item_t *event); +void periodic_event_connect(periodic_event_item_t *event); +//void periodic_event_disconnect(periodic_event_item_t *event); void periodic_event_reschedule(periodic_event_item_t *event); void periodic_event_enable(periodic_event_item_t *event); void periodic_event_disable(periodic_event_item_t *event); void periodic_event_schedule_and_disable(periodic_event_item_t *event); +void periodic_events_register(periodic_event_item_t *item); +void periodic_events_connect_all(void); +void periodic_events_reset_all(void); +periodic_event_item_t *periodic_events_find(const char *name); +void periodic_events_rescan_by_roles(int roles, bool net_disabled); +void periodic_events_disconnect_all(void); + +int safe_timer_diff(time_t now, time_t next); + #endif /* !defined(TOR_PERIODIC_H) */ diff --git a/src/core/or/circuitbuild.c b/src/core/or/circuitbuild.c index f8e87bf026..cfe0a97bcf 100644 --- a/src/core/or/circuitbuild.c +++ b/src/core/or/circuitbuild.c @@ -1683,7 +1683,8 @@ route_len_for_purpose(uint8_t purpose, extend_info_t *exit_ei) * to handle the desired path length, return -1. */ STATIC int -new_route_len(uint8_t purpose, extend_info_t *exit_ei, smartlist_t *nodes) +new_route_len(uint8_t purpose, extend_info_t *exit_ei, + const smartlist_t *nodes) { int routelen; @@ -2345,7 +2346,7 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit_ei) * particular router. See bug #25885.) */ MOCK_IMPL(STATIC int, -count_acceptable_nodes, (smartlist_t *nodes, int direct)) +count_acceptable_nodes, (const smartlist_t *nodes, int direct)) { int num=0; diff --git a/src/core/or/circuitbuild.h b/src/core/or/circuitbuild.h index b19bc41235..b45bc816a3 100644 --- a/src/core/or/circuitbuild.h +++ b/src/core/or/circuitbuild.h @@ -83,8 +83,8 @@ void circuit_upgrade_circuits_from_guard_wait(void); #ifdef CIRCUITBUILD_PRIVATE STATIC circid_t get_unique_circ_id_by_chan(channel_t *chan); STATIC int new_route_len(uint8_t purpose, extend_info_t *exit_ei, - smartlist_t *nodes); -MOCK_DECL(STATIC int, count_acceptable_nodes, (smartlist_t *nodes, + const smartlist_t *nodes); +MOCK_DECL(STATIC int, count_acceptable_nodes, (const smartlist_t *nodes, int direct)); STATIC int onion_extend_cpath(origin_circuit_t *circ); diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c index 071a8c91ed..33ba723971 100644 --- a/src/core/or/connection_edge.c +++ b/src/core/or/connection_edge.c @@ -2810,6 +2810,31 @@ connection_ap_process_natd(entry_connection_t *conn) return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); } +static const char HTTP_CONNECT_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 405 Method Not Allowed\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>This is an HTTP CONNECT tunnel, not an full HTTP Proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>This is an HTTP CONNECT tunnel, not an HTTP proxy.</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use this Tor port as\n" + "an HTTP proxy.\n" + "</p><p>\n" + "This is not correct: This port is configured as a CONNECT tunnel, not\n" + "an HTTP proxy. Please configure your client accordingly. You can also\n" + "use HTTPS, then the client should automatically use HTTP CONNECT." + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "</p>\n" + "</body>\n" + "</html>\n"; + /** Called on an HTTP CONNECT entry connection when some bytes have arrived, * but we have not yet received a full HTTP CONNECT request. Try to parse an * HTTP CONNECT request from the connection's inbuf. On success, set up the @@ -2850,7 +2875,7 @@ connection_ap_process_http_connect(entry_connection_t *conn) tor_assert(command); tor_assert(addrport); if (strcasecmp(command, "connect")) { - errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; + errmsg = HTTP_CONNECT_IS_NOT_AN_HTTP_PROXY_MSG; goto err; } diff --git a/src/core/or/policies.c b/src/core/or/policies.c index a6d66d36de..f59894ea8f 100644 --- a/src/core/or/policies.c +++ b/src/core/or/policies.c @@ -1164,6 +1164,15 @@ authdir_policy_badexit_address(uint32_t addr, uint16_t port) #define REJECT(arg) \ STMT_BEGIN *msg = tor_strdup(arg); goto err; STMT_END +/** Check <b>or_options</b> to determine whether or not we are using the + * default options for exit policy. Return true if so, false otherwise. */ +static int +policy_using_default_exit_options(const or_options_t *or_options) +{ + return (or_options->ExitPolicy == NULL && or_options->ExitRelay == -1 && + or_options->ReducedExitPolicy == 0 && or_options->IPv6Exit == 0); +} + /** Config helper: If there's any problem with the policy configuration * options in <b>options</b>, return -1 and set <b>msg</b> to a newly * allocated description of the error. Else return 0. */ @@ -1182,9 +1191,8 @@ validate_addr_policies(const or_options_t *options, char **msg) static int warned_about_nonexit = 0; - if (public_server_mode(options) && - !warned_about_nonexit && options->ExitPolicy == NULL && - options->ExitRelay == -1 && options->ReducedExitPolicy == 0) { + if (public_server_mode(options) && !warned_about_nonexit && + policy_using_default_exit_options(options)) { warned_about_nonexit = 1; log_notice(LD_CONFIG, "By default, Tor does not run as an exit relay. " "If you want to be an exit relay, " @@ -2141,9 +2149,9 @@ policies_parse_exit_policy_from_options(const or_options_t *or_options, int rv = 0; /* Short-circuit for non-exit relays, or for relays where we didn't specify - * ExitPolicy or ReducedExitPolicy and ExitRelay is auto. */ - if (or_options->ExitRelay == 0 || (or_options->ExitPolicy == NULL && - or_options->ExitRelay == -1 && or_options->ReducedExitPolicy == 0)) { + * ExitPolicy or ReducedExitPolicy or IPv6Exit and ExitRelay is auto. */ + if (or_options->ExitRelay == 0 || + policy_using_default_exit_options(or_options)) { append_exit_policy_string(result, "reject *4:*"); append_exit_policy_string(result, "reject *6:*"); return 0; diff --git a/src/feature/client/bridges.c b/src/feature/client/bridges.c index 05f89ad36c..ea1aff9519 100644 --- a/src/feature/client/bridges.c +++ b/src/feature/client/bridges.c @@ -348,7 +348,7 @@ int node_is_a_configured_bridge(const node_t *node) { /* First, let's try searching for a bridge with matching identity. */ - if (BUG(tor_digest_is_zero(node->identity))) + if (BUG(tor_mem_is_zero(node->identity, DIGEST_LEN))) return 0; if (find_bridge_by_digest(node->identity) != NULL) diff --git a/src/feature/client/circpathbias.c b/src/feature/client/circpathbias.c index 1743ab5a81..e6af649ba7 100644 --- a/src/feature/client/circpathbias.c +++ b/src/feature/client/circpathbias.c @@ -176,6 +176,7 @@ pathbias_get_scale_threshold(const or_options_t *options) static double pathbias_get_scale_ratio(const or_options_t *options) { + (void) options; /* * The scale factor is the denominator for our scaling * of circuit counts for our path bias window. @@ -185,7 +186,8 @@ pathbias_get_scale_ratio(const or_options_t *options) */ int denominator = networkstatus_get_param(NULL, "pb_scalefactor", 2, 2, INT32_MAX); - (void) options; + tor_assert(denominator > 0); + /** * The mult factor is the numerator for our scaling * of circuit counts for our path bias window. It diff --git a/src/feature/control/control.c b/src/feature/control/control.c index 41e21c0a14..23ef83ef95 100644 --- a/src/feature/control/control.c +++ b/src/feature/control/control.c @@ -33,6 +33,7 @@ **/ #define CONTROL_MODULE_PRIVATE +#define CONTROL_PRIVATE #include "core/or/or.h" #include "app/config/config.h" @@ -274,6 +275,44 @@ peek_connection_has_http_command(connection_t *conn) return peek_buf_has_http_command(conn->inbuf); } +/** + * Helper: take a nul-terminated command of given length, and find where the + * command starts and the arguments begin. Separate them, allocate a new + * string in <b>current_cmd_out</b> for the command, and return a pointer + * to the arguments. + **/ +STATIC char * +control_split_incoming_command(char *incoming_cmd, + size_t *data_len, + char **current_cmd_out) +{ + const bool is_multiline = *data_len && incoming_cmd[0] == '+'; + size_t cmd_len = 0; + while (cmd_len < *data_len + && !TOR_ISSPACE(incoming_cmd[cmd_len])) + ++cmd_len; + + *current_cmd_out = tor_memdup_nulterm(incoming_cmd, cmd_len); + char *args = incoming_cmd+cmd_len; + tor_assert(*data_len>=cmd_len); + *data_len -= cmd_len; + if (is_multiline) { + // Only match horizontal space: any line after the first is data, + // not arguments. + while ((*args == '\t' || *args == ' ') && *data_len) { + ++args; + --*data_len; + } + } else { + while (TOR_ISSPACE(*args) && *data_len) { + ++args; + --*data_len; + } + } + + return args; +} + static const char CONTROLPORT_IS_NOT_AN_HTTP_PROXY_MSG[] = "HTTP/1.0 501 Tor ControlPort is not an HTTP proxy" "\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n" @@ -308,7 +347,6 @@ connection_control_process_inbuf(control_connection_t *conn) { size_t data_len; uint32_t cmd_data_len; - int cmd_len; char *args; tor_assert(conn); @@ -400,22 +438,15 @@ connection_control_process_inbuf(control_connection_t *conn) /* Otherwise, read another line. */ } data_len = conn->incoming_cmd_cur_len; + /* Okay, we now have a command sitting on conn->incoming_cmd. See if we * recognize it. */ - cmd_len = 0; - while ((size_t)cmd_len < data_len - && !TOR_ISSPACE(conn->incoming_cmd[cmd_len])) - ++cmd_len; - - conn->incoming_cmd[cmd_len]='\0'; - args = conn->incoming_cmd+cmd_len+1; - tor_assert(data_len>(size_t)cmd_len); - data_len -= (cmd_len+1); /* skip the command and NUL we added after it */ - while (TOR_ISSPACE(*args)) { - ++args; - --data_len; - } + tor_free(conn->current_cmd); + args = control_split_incoming_command(conn->incoming_cmd, &data_len, + &conn->current_cmd); + if (BUG(!conn->current_cmd)) + return -1; /* If the connection is already closing, ignore further commands */ if (TO_CONN(conn)->marked_for_close) { @@ -423,14 +454,14 @@ connection_control_process_inbuf(control_connection_t *conn) } /* Otherwise, Quit is always valid. */ - if (!strcasecmp(conn->incoming_cmd, "QUIT")) { + if (!strcasecmp(conn->current_cmd, "QUIT")) { connection_write_str_to_buf("250 closing connection\r\n", conn); connection_mark_and_flush(TO_CONN(conn)); return 0; } if (conn->base_.state == CONTROL_CONN_STATE_NEEDAUTH && - !is_valid_initial_command(conn, conn->incoming_cmd)) { + !is_valid_initial_command(conn, conn->current_cmd)) { connection_write_str_to_buf("514 Authentication required.\r\n", conn); connection_mark_for_close(TO_CONN(conn)); return 0; diff --git a/src/feature/control/control.h b/src/feature/control/control.h index 3083837931..8d3595d2ed 100644 --- a/src/feature/control/control.h +++ b/src/feature/control/control.h @@ -60,4 +60,10 @@ int get_cached_network_liveness(void); void set_cached_network_liveness(int liveness); #endif /* defined(CONTROL_MODULE_PRIVATE) */ +#ifdef CONTROL_PRIVATE +STATIC char *control_split_incoming_command(char *incoming_cmd, + size_t *data_len, + char **current_cmd_out); +#endif + #endif /* !defined(TOR_CONTROL_H) */ diff --git a/src/feature/control/control_auth.c b/src/feature/control/control_auth.c index 927115a308..a86442c21f 100644 --- a/src/feature/control/control_auth.c +++ b/src/feature/control/control_auth.c @@ -11,12 +11,16 @@ #include "app/config/config.h" #include "core/mainloop/connection.h" #include "feature/control/control.h" +#include "feature/control/control_cmd.h" #include "feature/control/control_auth.h" +#include "feature/control/control_cmd_args_st.h" #include "feature/control/control_connection_st.h" #include "feature/control/control_fmt.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "lib/encoding/confline.h" +#include "lib/encoding/kvline.h" +#include "lib/encoding/qstring.h" #include "lib/crypt_ops/crypto_s2k.h" @@ -116,12 +120,19 @@ decode_hashed_passwords(config_line_t *passwords) return NULL; } +const control_cmd_syntax_t authchallenge_syntax = { + .min_args = 1, + .max_args = 1, + .accept_keywords=true, + .kvline_flags=KV_OMIT_KEYS|KV_QUOTED_QSTRING, + .store_raw_body=true +}; + /** Called when we get an AUTHCHALLENGE command. */ int -handle_control_authchallenge(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_authchallenge(control_connection_t *conn, + const control_cmd_args_t *args) { - const char *cp = body; char *client_nonce; size_t client_nonce_len; char server_hash[DIGEST256_LEN]; @@ -129,63 +140,50 @@ handle_control_authchallenge(control_connection_t *conn, uint32_t len, char server_nonce[SAFECOOKIE_SERVER_NONCE_LEN]; char server_nonce_encoded[(2*SAFECOOKIE_SERVER_NONCE_LEN) + 1]; - cp += strspn(cp, " \t\n\r"); - if (!strcasecmpstart(cp, "SAFECOOKIE")) { - cp += strlen("SAFECOOKIE"); - } else { + if (strcasecmp(smartlist_get(args->args, 0), "SAFECOOKIE")) { connection_write_str_to_buf("513 AUTHCHALLENGE only supports SAFECOOKIE " "authentication\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; + goto fail; } - if (!authentication_cookie_is_set) { connection_write_str_to_buf("515 Cookie authentication is disabled\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; + goto fail; + } + if (args->kwargs == NULL || args->kwargs->next != NULL) { + /* connection_write_str_to_buf("512 AUTHCHALLENGE requires exactly " + "2 arguments.\r\n", conn); + */ + connection_printf_to_buf(conn, + "512 AUTHCHALLENGE dislikes argument list %s\r\n", + escaped(args->raw_body)); + goto fail; + } + if (strcmp(args->kwargs->key, "")) { + connection_write_str_to_buf("512 AUTHCHALLENGE does not accept keyword " + "arguments.\r\n", conn); + goto fail; } - cp += strspn(cp, " \t\n\r"); - if (*cp == '"') { - const char *newcp = - decode_escaped_string(cp, len - (cp - body), - &client_nonce, &client_nonce_len); - if (newcp == NULL) { - connection_write_str_to_buf("513 Invalid quoted client nonce\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - return -1; - } - cp = newcp; + bool contains_quote = strchr(args->raw_body, '\"'); + if (contains_quote) { + /* The nonce was quoted */ + client_nonce = tor_strdup(args->kwargs->value); + client_nonce_len = strlen(client_nonce); } else { - size_t client_nonce_encoded_len = strspn(cp, "0123456789ABCDEFabcdef"); - - client_nonce_len = client_nonce_encoded_len / 2; - client_nonce = tor_malloc_zero(client_nonce_len); - - if (base16_decode(client_nonce, client_nonce_len, - cp, client_nonce_encoded_len) - != (int) client_nonce_len) { + /* The nonce was should be in hex. */ + const char *hex_nonce = args->kwargs->value; + client_nonce_len = strlen(hex_nonce) / 2; + client_nonce = tor_malloc(client_nonce_len); + if (base16_decode(client_nonce, client_nonce_len, hex_nonce, + strlen(hex_nonce)) != (int)client_nonce_len) { connection_write_str_to_buf("513 Invalid base16 client nonce\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); tor_free(client_nonce); - return -1; + goto fail; } - - cp += client_nonce_encoded_len; } - cp += strspn(cp, " \t\n\r"); - if (*cp != '\0' || - cp != body + len) { - connection_write_str_to_buf("513 Junk at end of AUTHCHALLENGE command\r\n", - conn); - connection_mark_for_close(TO_CONN(conn)); - tor_free(client_nonce); - return -1; - } crypto_rand(server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); /* Now compute and send the server-to-controller response, and the @@ -233,38 +231,56 @@ handle_control_authchallenge(control_connection_t *conn, uint32_t len, tor_free(client_nonce); return 0; + fail: + connection_mark_for_close(TO_CONN(conn)); + return -1; } +const control_cmd_syntax_t authenticate_syntax = { + .max_args = 0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_KEYS|KV_QUOTED_QSTRING, + .store_raw_body=true +}; + /** Called when we get an AUTHENTICATE message. Check whether the * authentication is valid, and if so, update the connection's state to * OPEN. Reply with DONE or ERROR. */ int -handle_control_authenticate(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_authenticate(control_connection_t *conn, + const control_cmd_args_t *args) { - int used_quoted_string = 0; + bool used_quoted_string = false; const or_options_t *options = get_options(); const char *errstr = "Unknown error"; char *password; size_t password_len; - const char *cp; - int i; int bad_cookie=0, bad_password=0; smartlist_t *sl = NULL; - if (!len) { + if (args->kwargs == NULL) { password = tor_strdup(""); password_len = 0; - } else if (TOR_ISXDIGIT(body[0])) { - cp = body; - while (TOR_ISXDIGIT(*cp)) - ++cp; - i = (int)(cp - body); - tor_assert(i>0); - password_len = i/2; - password = tor_malloc(password_len + 1); - if (base16_decode(password, password_len+1, body, i) + } else if (args->kwargs->next) { + connection_write_str_to_buf( + "512 Too many arguments to AUTHENTICATE.\r\n", conn); + connection_mark_for_close(TO_CONN(conn)); + return 0; + } else if (strcmp(args->kwargs->key, "")) { + connection_write_str_to_buf( + "512 AUTHENTICATE does not accept keyword arguments.\r\n", conn); + connection_mark_for_close(TO_CONN(conn)); + return 0; + } else if (strchr(args->raw_body, '\"')) { + used_quoted_string = true; + password = tor_strdup(args->kwargs->value); + password_len = strlen(password); + } else { + const char *hex_passwd = args->kwargs->value; + password_len = strlen(hex_passwd) / 2; + password = tor_malloc(password_len+1); + if (base16_decode(password, password_len+1, hex_passwd, strlen(hex_passwd)) != (int) password_len) { connection_write_str_to_buf( "551 Invalid hexadecimal encoding. Maybe you tried a plain text " @@ -274,14 +290,6 @@ handle_control_authenticate(control_connection_t *conn, uint32_t len, tor_free(password); return 0; } - } else { - if (!decode_escaped_string(body, len, &password, &password_len)) { - connection_write_str_to_buf("551 Invalid quoted string. You need " - "to put the password in double quotes.\r\n", conn); - connection_mark_for_close(TO_CONN(conn)); - return 0; - } - used_quoted_string = 1; } if (conn->safecookie_client_hash != NULL) { diff --git a/src/feature/control/control_auth.h b/src/feature/control/control_auth.h index f436482e4a..246e18ccbc 100644 --- a/src/feature/control/control_auth.h +++ b/src/feature/control/control_auth.h @@ -12,16 +12,21 @@ #ifndef TOR_CONTROL_AUTH_H #define TOR_CONTROL_AUTH_H +struct control_cmd_args_t; +struct control_cmd_syntax_t; + int init_control_cookie_authentication(int enabled); char *get_controller_cookie_file_name(void); struct config_line_t; smartlist_t *decode_hashed_passwords(struct config_line_t *passwords); -int handle_control_authchallenge(control_connection_t *conn, uint32_t len, - const char *body); +int handle_control_authchallenge(control_connection_t *conn, + const struct control_cmd_args_t *args); int handle_control_authenticate(control_connection_t *conn, - uint32_t cmd_data_len, - const char *args); + const struct control_cmd_args_t *args); void control_auth_free_all(void); +extern const struct control_cmd_syntax_t authchallenge_syntax; +extern const struct control_cmd_syntax_t authenticate_syntax; + #endif /* !defined(TOR_CONTROL_AUTH_H) */ diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c index 95cf0d561e..9afa734d86 100644 --- a/src/feature/control/control_cmd.c +++ b/src/feature/control/control_cmd.c @@ -40,11 +40,13 @@ #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" #include "lib/encoding/confline.h" +#include "lib/encoding/kvline.h" #include "core/or/cpath_build_state_st.h" #include "core/or/entry_connection_st.h" #include "core/or/origin_circuit_st.h" #include "core/or/socks_request_st.h" +#include "feature/control/control_cmd_args_st.h" #include "feature/control/control_connection_st.h" #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" @@ -52,39 +54,238 @@ #include "feature/rend/rend_encoded_v2_service_descriptor_st.h" #include "feature/rend/rend_service_descriptor_st.h" -static int control_setconf_helper(control_connection_t *conn, uint32_t len, - char *body, +static int control_setconf_helper(control_connection_t *conn, + const control_cmd_args_t *args, int use_defaults); /** Yield true iff <b>s</b> is the state of a control_connection_t that has * finished authentication and is accepting commands. */ #define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN) +/** + * Release all storage held in <b>args</b> + **/ +void +control_cmd_args_free_(control_cmd_args_t *args) +{ + if (! args) + return; + + if (args->args) { + SMARTLIST_FOREACH(args->args, char *, c, tor_free(c)); + smartlist_free(args->args); + } + config_free_lines(args->kwargs); + tor_free(args->cmddata); + + tor_free(args); +} + +/** Erase all memory held in <b>args</b>. */ +void +control_cmd_args_wipe(control_cmd_args_t *args) +{ + if (!args) + return; + + if (args->args) { + SMARTLIST_FOREACH(args->args, char *, c, memwipe(c, 0, strlen(c))); + } + for (config_line_t *line = args->kwargs; line; line = line->next) { + memwipe(line->key, 0, strlen(line->key)); + memwipe(line->value, 0, strlen(line->value)); + } + if (args->cmddata) + memwipe(args->cmddata, 0, args->cmddata_len); +} + +/** + * Return true iff any element of the NULL-terminated <b>array</b> matches + * <b>kwd</b>. Case-insensitive. + **/ +static bool +string_array_contains_keyword(const char **array, const char *kwd) +{ + for (unsigned i = 0; array[i]; ++i) { + if (! strcasecmp(array[i], kwd)) + return true; + } + return false; +} + +/** Helper for argument parsing: check whether the keyword arguments just + * parsed in <b>result</b> were well-formed according to <b>syntax</b>. + * + * On success, return 0. On failure, return -1 and set *<b>error_out</b> + * to a newly allocated error string. + **/ +static int +kvline_check_keyword_args(const control_cmd_args_t *result, + const control_cmd_syntax_t *syntax, + char **error_out) +{ + if (result->kwargs == NULL) { + tor_asprintf(error_out, "Cannot parse keyword argument(s)"); + return -1; + } + + if (! syntax->allowed_keywords) { + /* All keywords are permitted. */ + return 0; + } + + /* Check for unpermitted arguments */ + const config_line_t *line; + for (line = result->kwargs; line; line = line->next) { + if (! string_array_contains_keyword(syntax->allowed_keywords, + line->key)) { + tor_asprintf(error_out, "Unrecognized keyword argument %s", + escaped(line->key)); + return -1; + } + } + + return 0; +} + +/** + * Helper: parse the arguments to a command according to <b>syntax</b>. On + * success, set *<b>error_out</b> to NULL and return a newly allocated + * control_cmd_args_t. On failure, set *<b>error_out</b> to newly allocated + * error string, and return NULL. + **/ +STATIC control_cmd_args_t * +control_cmd_parse_args(const char *command, + const control_cmd_syntax_t *syntax, + size_t body_len, + const char *body, + char **error_out) +{ + *error_out = NULL; + control_cmd_args_t *result = tor_malloc_zero(sizeof(control_cmd_args_t)); + const char *cmdline; + char *cmdline_alloc = NULL; + tor_assert(syntax->max_args < INT_MAX || syntax->max_args == UINT_MAX); + + result->command = command; + + if (syntax->store_raw_body) { + tor_assert(body[body_len] == 0); + result->raw_body = body; + } + + const char *eol = memchr(body, '\n', body_len); + if (syntax->want_cmddata) { + if (! eol || (eol+1) == body+body_len) { + *error_out = tor_strdup("Empty body"); + goto err; + } + cmdline_alloc = tor_memdup_nulterm(body, eol-body); + cmdline = cmdline_alloc; + ++eol; + result->cmddata_len = read_escaped_data(eol, (body+body_len)-eol, + &result->cmddata); + } else { + if (eol && (eol+1) != body+body_len) { + *error_out = tor_strdup("Unexpected body"); + goto err; + } + cmdline = body; + } + + result->args = smartlist_new(); + smartlist_split_string(result->args, cmdline, " ", + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, + (int)(syntax->max_args+1)); + size_t n_args = smartlist_len(result->args); + if (n_args < syntax->min_args) { + tor_asprintf(error_out, "Need at least %u argument(s)", + syntax->min_args); + goto err; + } else if (n_args > syntax->max_args && ! syntax->accept_keywords) { + tor_asprintf(error_out, "Cannot accept more than %u argument(s)", + syntax->max_args); + goto err; + } + + if (n_args > syntax->max_args) { + /* We have extra arguments after the positional arguments, and we didn't + treat them as an error, so they must count as keyword arguments: Either + K=V pairs, or flags, or both. */ + tor_assert(n_args == syntax->max_args + 1); + tor_assert(syntax->accept_keywords); + char *remainder = smartlist_pop_last(result->args); + result->kwargs = kvline_parse(remainder, syntax->kvline_flags); + tor_free(remainder); + if (kvline_check_keyword_args(result, syntax, error_out) < 0) { + goto err; + } + } + + tor_assert_nonfatal(*error_out == NULL); + goto done; + err: + tor_assert_nonfatal(*error_out != NULL); + control_cmd_args_free(result); + done: + tor_free(cmdline_alloc); + return result; +} + +/** + * Return true iff <b>lines</b> contains <b>flags</b> as a no-value + * (keyword-only) entry. + **/ +static bool +config_lines_contain_flag(const config_line_t *lines, const char *flag) +{ + const config_line_t *line = config_line_find_case(lines, flag); + return line && !strcmp(line->value, ""); +} + +static const control_cmd_syntax_t setconf_syntax = { + .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS|KV_QUOTED, +}; + /** Called when we receive a SETCONF message: parse the body and try * to update our configuration. Reply with a DONE or ERROR message. * Modifies the contents of body.*/ static int -handle_control_setconf(control_connection_t *conn, uint32_t len, char *body) +handle_control_setconf(control_connection_t *conn, + const control_cmd_args_t *args) { - return control_setconf_helper(conn, len, body, 0); + return control_setconf_helper(conn, args, 0); } +static const control_cmd_syntax_t resetconf_syntax = { + .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS|KV_QUOTED, +}; + /** Called when we receive a RESETCONF message: parse the body and try * to update our configuration. Reply with a DONE or ERROR message. * Modifies the contents of body. */ static int -handle_control_resetconf(control_connection_t *conn, uint32_t len, char *body) +handle_control_resetconf(control_connection_t *conn, + const control_cmd_args_t *args) { - return control_setconf_helper(conn, len, body, 1); + return control_setconf_helper(conn, args, 1); } +static const control_cmd_syntax_t getconf_syntax = { + .max_args=UINT_MAX +}; + /** Called when we receive a GETCONF message. Parse the request, and * reply with a CONFVALUE or an ERROR message */ static int -handle_control_getconf(control_connection_t *conn, uint32_t body_len, - const char *body) +handle_control_getconf(control_connection_t *conn, + const control_cmd_args_t *args) { - smartlist_t *questions = smartlist_new(); + const smartlist_t *questions = args->args; smartlist_t *answers = smartlist_new(); smartlist_t *unrecognized = smartlist_new(); char *msg = NULL; @@ -92,9 +293,6 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, const or_options_t *options = get_options(); int i, len; - (void) body_len; /* body is NUL-terminated; so we can ignore len. */ - smartlist_split_string(questions, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { if (!option_is_recognized(q)) { smartlist_add(unrecognized, (char*) q); @@ -139,8 +337,6 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp)); smartlist_free(answers); - SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); - smartlist_free(questions); smartlist_free(unrecognized); tor_free(msg); @@ -148,17 +344,21 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, return 0; } +static const control_cmd_syntax_t loadconf_syntax = { + .want_cmddata = true +}; + /** Called when we get a +LOADCONF message. */ static int -handle_control_loadconf(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_loadconf(control_connection_t *conn, + const control_cmd_args_t *args) { setopt_err_t retval; char *errstring = NULL; const char *msg = NULL; - (void) len; - retval = options_init_from_string(NULL, body, CMD_RUN_TOR, NULL, &errstring); + retval = options_init_from_string(NULL, args->cmddata, + CMD_RUN_TOR, NULL, &errstring); if (retval != SETOPT_OK) log_warn(LD_CONTROL, @@ -194,20 +394,20 @@ handle_control_loadconf(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t setevents_syntax = { + .max_args = UINT_MAX +}; + /** Called when we get a SETEVENTS message: update conn->event_mask, * and reply with DONE or ERROR. */ static int -handle_control_setevents(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_setevents(control_connection_t *conn, + const control_cmd_args_t *args) { int event_code; event_mask_t event_mask = 0; - smartlist_t *events = smartlist_new(); + const smartlist_t *events = args->args; - (void) len; - - smartlist_split_string(events, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(events, const char *, ev) { if (!strcasecmp(ev, "EXTENDED") || @@ -229,16 +429,12 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, if (event_code == -1) { connection_printf_to_buf(conn, "552 Unrecognized event \"%s\"\r\n", ev); - SMARTLIST_FOREACH(events, char *, e, tor_free(e)); - smartlist_free(events); return 0; } } event_mask |= (((event_mask_t)1) << event_code); } SMARTLIST_FOREACH_END(ev); - SMARTLIST_FOREACH(events, char *, e, tor_free(e)); - smartlist_free(events); conn->event_mask = event_mask; @@ -247,15 +443,19 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t saveconf_syntax = { + .max_args = 0, + .accept_keywords = true, + .kvline_flags=KV_OMIT_VALS, +}; + /** Called when we get a SAVECONF command. Try to flush the current options to * disk, and report success or failure. */ static int -handle_control_saveconf(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_saveconf(control_connection_t *conn, + const control_cmd_args_t *args) { - (void) len; - - int force = !strcmpstart(body, "FORCE"); + bool force = config_lines_contain_flag(args->kwargs, "FORCE"); const or_options_t *options = get_options(); if ((!force && options->IncludeUsed) || options_save_current() < 0) { connection_write_str_to_buf( @@ -266,23 +466,23 @@ handle_control_saveconf(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t signal_syntax = { + .min_args = 1, + .max_args = 1, +}; + /** Called when we get a SIGNAL command. React to the provided signal, and * report success or failure. (If the signal results in a shutdown, success * may not be reported.) */ static int -handle_control_signal(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_signal(control_connection_t *conn, + const control_cmd_args_t *args) { int sig = -1; int i; - int n = 0; - char *s; - (void) len; - - while (body[n] && ! TOR_ISSPACE(body[n])) - ++n; - s = tor_strndup(body, n); + tor_assert(smartlist_len(args->args) == 1); + const char *s = smartlist_get(args->args, 0); for (i = 0; signal_table[i].signal_name != NULL; ++i) { if (!strcasecmp(s, signal_table[i].signal_name)) { @@ -294,7 +494,6 @@ handle_control_signal(control_connection_t *conn, uint32_t len, if (sig < 0) connection_printf_to_buf(conn, "552 Unrecognized signal code \"%s\"\r\n", s); - tor_free(s); if (sig < 0) return 0; @@ -308,15 +507,18 @@ handle_control_signal(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t takeownership_syntax = { + .max_args = UINT_MAX, // This should probably become zero. XXXXX +}; + /** Called when we get a TAKEOWNERSHIP command. Mark this connection * as an owning connection, so that we will exit if the connection * closes. */ static int -handle_control_takeownership(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_takeownership(control_connection_t *conn, + const control_cmd_args_t *args) { - (void)len; - (void)body; + (void)args; conn->is_owning_control_connection = 1; @@ -328,15 +530,18 @@ handle_control_takeownership(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t dropownership_syntax = { + .max_args = UINT_MAX, // This should probably become zero. XXXXX +}; + /** Called when we get a DROPOWNERSHIP command. Mark this connection * as a non-owning connection, so that we will not exit if the connection * closes. */ static int -handle_control_dropownership(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_dropownership(control_connection_t *conn, + const control_cmd_args_t *args) { - (void)len; - (void)body; + (void)args; conn->is_owning_control_connection = 0; @@ -381,73 +586,17 @@ get_stream(const char *id) * contents of body. */ static int -control_setconf_helper(control_connection_t *conn, uint32_t len, char *body, +control_setconf_helper(control_connection_t *conn, + const control_cmd_args_t *args, int use_defaults) { setopt_err_t opt_err; - config_line_t *lines=NULL; - char *start = body; char *errstring = NULL; const unsigned flags = CAL_CLEAR_FIRST | (use_defaults ? CAL_USE_DEFAULTS : 0); - char *config; - smartlist_t *entries = smartlist_new(); - - /* We have a string, "body", of the format '(key(=val|="val")?)' entries - * separated by space. break it into a list of configuration entries. */ - while (*body) { - char *eq = body; - char *key; - char *entry; - while (!TOR_ISSPACE(*eq) && *eq != '=') - ++eq; - key = tor_strndup(body, eq-body); - body = eq+1; - if (*eq == '=') { - char *val=NULL; - size_t val_len=0; - if (*body != '\"') { - char *val_start = body; - while (!TOR_ISSPACE(*body)) - body++; - val = tor_strndup(val_start, body-val_start); - val_len = strlen(val); - } else { - body = (char*)extract_escaped_string(body, (len - (body-start)), - &val, &val_len); - if (!body) { - connection_write_str_to_buf("551 Couldn't parse string\r\n", conn); - SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); - smartlist_free(entries); - tor_free(key); - return 0; - } - } - tor_asprintf(&entry, "%s %s", key, val); - tor_free(key); - tor_free(val); - } else { - entry = key; - } - smartlist_add(entries, entry); - while (TOR_ISSPACE(*body)) - ++body; - } - - smartlist_add_strdup(entries, ""); - config = smartlist_join_strings(entries, "\n", 0, NULL); - SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); - smartlist_free(entries); - - if (config_get_lines(config, &lines, 0) < 0) { - log_warn(LD_CONTROL,"Controller gave us config lines we can't parse."); - connection_write_str_to_buf("551 Couldn't parse configuration\r\n", - conn); - tor_free(config); - return 0; - } - tor_free(config); + // We need a copy here, since confparse.c wants to canonicalize cases. + config_line_t *lines = config_lines_dup(args->kwargs); opt_err = options_trial_assign(lines, flags, &errstring); { @@ -492,30 +641,27 @@ address_is_invalid_mapaddress_target(const char *addr) return address_is_invalid_destination(addr, 1); } +static const control_cmd_syntax_t mapaddress_syntax = { + .max_args=1, + .accept_keywords=true, +}; + /** Called when we get a MAPADDRESS command; try to bind all listed addresses, * and report success or failure. */ static int -handle_control_mapaddress(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_mapaddress(control_connection_t *conn, + const control_cmd_args_t *args) { - smartlist_t *elts; - smartlist_t *lines; smartlist_t *reply; char *r; size_t sz; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - lines = smartlist_new(); - elts = smartlist_new(); reply = smartlist_new(); - smartlist_split_string(lines, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(lines, char *, line) { - tor_strlower(line); - smartlist_split_string(elts, line, "=", 0, 2); - if (smartlist_len(elts) == 2) { - const char *from = smartlist_get(elts,0); - const char *to = smartlist_get(elts,1); + const config_line_t *line; + for (line = args->kwargs; line; line = line->next) { + const char *from = line->key; + const char *to = line->value; + { if (address_is_invalid_mapaddress_target(to)) { smartlist_add_asprintf(reply, "512-syntax error: invalid address '%s'", to); @@ -530,10 +676,10 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, type, tor_strdup(to)); if (!address) { smartlist_add_asprintf(reply, - "451-resource exhausted: skipping '%s'", line); + "451-resource exhausted: skipping '%s=%s'", from,to); log_warn(LD_CONTROL, "Unable to allocate address for '%s' in MapAddress msg", - safe_str_client(line)); + safe_str_client(to)); } else { smartlist_add_asprintf(reply, "250-%s=%s", address, to); } @@ -543,27 +689,16 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, ADDRMAPSRC_CONTROLLER, &msg) < 0) { smartlist_add_asprintf(reply, "512-syntax error: invalid address mapping " - " '%s': %s", line, msg); + " '%s=%s': %s", from, to, msg); log_warn(LD_CONTROL, - "Skipping invalid argument '%s' in MapAddress msg: %s", - line, msg); + "Skipping invalid argument '%s=%s' in MapAddress msg: %s", + from, to, msg); } else { - smartlist_add_asprintf(reply, "250-%s", line); + smartlist_add_asprintf(reply, "250-%s=%s", from, to); } } - } else { - smartlist_add_asprintf(reply, "512-syntax error: mapping '%s' is " - "not of expected form 'foo=bar'.", line); - log_info(LD_CONTROL, "Skipping MapAddress '%s': wrong " - "number of items.", - safe_str_client(line)); } - SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); - smartlist_clear(elts); - } SMARTLIST_FOREACH_END(line); - SMARTLIST_FOREACH(lines, char *, cp, tor_free(cp)); - smartlist_free(lines); - smartlist_free(elts); + } if (smartlist_len(reply)) { ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; @@ -596,94 +731,61 @@ circuit_purpose_from_string(const char *string) return CIRCUIT_PURPOSE_UNKNOWN; } -/** Return a newly allocated smartlist containing the arguments to the command - * waiting in <b>body</b>. If there are fewer than <b>min_args</b> arguments, - * or if <b>max_args</b> is nonnegative and there are more than - * <b>max_args</b> arguments, send a 512 error to the controller, using - * <b>command</b> as the command name in the error message. */ -static smartlist_t * -getargs_helper(const char *command, control_connection_t *conn, - const char *body, int min_args, int max_args) -{ - smartlist_t *args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - if (smartlist_len(args) < min_args) { - connection_printf_to_buf(conn, "512 Missing argument to %s\r\n",command); - goto err; - } else if (max_args >= 0 && smartlist_len(args) > max_args) { - connection_printf_to_buf(conn, "512 Too many arguments to %s\r\n",command); - goto err; - } - return args; - err: - SMARTLIST_FOREACH(args, char *, s, tor_free(s)); - smartlist_free(args); - return NULL; -} - -/** Helper. Return the first element of <b>sl</b> at index <b>start_at</b> or - * higher that starts with <b>prefix</b>, case-insensitive. Return NULL if no - * such element exists. */ -static const char * -find_element_starting_with(smartlist_t *sl, int start_at, const char *prefix) -{ - int i; - for (i = start_at; i < smartlist_len(sl); ++i) { - const char *elt = smartlist_get(sl, i); - if (!strcasecmpstart(elt, prefix)) - return elt; - } - return NULL; -} - -/** Helper. Return true iff s is an argument that we should treat as a - * key-value pair. */ -static int -is_keyval_pair(const char *s) -{ - /* An argument is a key-value pair if it has an =, and it isn't of the form - * $fingeprint=name */ - return strchr(s, '=') && s[0] != '$'; -} +static const control_cmd_syntax_t extendcircuit_syntax = { + .min_args=1, + .max_args=1, // see note in function + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS +}; /** Called when we get an EXTENDCIRCUIT message. Try to extend the listed * circuit, and report success or failure. */ static int -handle_control_extendcircuit(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_extendcircuit(control_connection_t *conn, + const control_cmd_args_t *args) { - smartlist_t *router_nicknames=NULL, *nodes=NULL; + smartlist_t *router_nicknames=smartlist_new(), *nodes=NULL; origin_circuit_t *circ = NULL; - int zero_circ; uint8_t intended_purpose = CIRCUIT_PURPOSE_C_GENERAL; - smartlist_t *args; - (void) len; - - router_nicknames = smartlist_new(); - - args = getargs_helper("EXTENDCIRCUIT", conn, body, 1, -1); - if (!args) - goto done; + const config_line_t *kwargs = args->kwargs; + const char *circ_id = smartlist_get(args->args, 0); + const char *path_str = NULL; + char *path_str_alloc = NULL; + + /* The syntax for this command is unfortunate. The second argument is + optional, and is a comma-separated list long-format fingerprints, which + can (historically!) contain an equals sign. + + Here we check the second argument to see if it's a path, and if so we + remove it from the kwargs list and put it in path_str. + */ + if (kwargs) { + const config_line_t *arg1 = kwargs; + if (!strcmp(arg1->value, "")) { + path_str = arg1->key; + kwargs = kwargs->next; + } else if (arg1->key[0] == '$') { + tor_asprintf(&path_str_alloc, "%s=%s", arg1->key, arg1->value); + path_str = path_str_alloc; + kwargs = kwargs->next; + } + } - zero_circ = !strcmp("0", (char*)smartlist_get(args,0)); + const config_line_t *purpose_line = config_line_find_case(kwargs, "PURPOSE"); + bool zero_circ = !strcmp("0", circ_id); - if (zero_circ) { - const char *purp = find_element_starting_with(args, 1, "PURPOSE="); - - if (purp) { - intended_purpose = circuit_purpose_from_string(purp); - if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - goto done; - } + if (purpose_line) { + intended_purpose = circuit_purpose_from_string(purpose_line->value); + if (intended_purpose == CIRCUIT_PURPOSE_UNKNOWN) { + connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", + purpose_line->value); + goto done; } + } - if ((smartlist_len(args) == 1) || - (smartlist_len(args) >= 2 && is_keyval_pair(smartlist_get(args, 1)))) { - // "EXTENDCIRCUIT 0" || EXTENDCIRCUIT 0 foo=bar" + if (zero_circ) { + if (!path_str) { + // "EXTENDCIRCUIT 0" with no path. circ = circuit_launch(intended_purpose, CIRCLAUNCH_NEED_CAPACITY); if (!circ) { connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); @@ -691,37 +793,24 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, connection_printf_to_buf(conn, "250 EXTENDED %lu\r\n", (unsigned long)circ->global_identifier); } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); goto done; } - // "EXTENDCIRCUIT 0 router1,router2" || - // "EXTENDCIRCUIT 0 router1,router2 PURPOSE=foo" } - if (!zero_circ && !(circ = get_circ(smartlist_get(args,0)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); + if (!zero_circ && !(circ = get_circ(circ_id))) { + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", circ_id); goto done; } - if (smartlist_len(args) < 2) { - connection_printf_to_buf(conn, - "512 syntax error: not enough arguments.\r\n"); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); + if (!path_str) { + connection_printf_to_buf(conn, "512 syntax error: path required.\r\n"); goto done; } - smartlist_split_string(router_nicknames, smartlist_get(args,1), ",", 0, 0); - - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); + smartlist_split_string(router_nicknames, path_str, ",", 0, 0); nodes = smartlist_new(); - int first_node = zero_circ; + bool first_node = zero_circ; SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { const node_t *node = node_get_by_nickname(n, 0); if (!node) { @@ -733,8 +822,9 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, goto done; } smartlist_add(nodes, (void*)node); - first_node = 0; + first_node = false; } SMARTLIST_FOREACH_END(n); + if (!smartlist_len(nodes)) { connection_write_str_to_buf("512 No router names provided\r\n", conn); goto done; @@ -800,39 +890,40 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, SMARTLIST_FOREACH(router_nicknames, char *, n, tor_free(n)); smartlist_free(router_nicknames); smartlist_free(nodes); + tor_free(path_str_alloc); return 0; } +static const control_cmd_syntax_t setcircuitpurpose_syntax = { + .max_args=1, + .accept_keywords=true, +}; + /** Called when we get a SETCIRCUITPURPOSE message. If we can find the * circuit and it's a valid purpose, change it. */ static int handle_control_setcircuitpurpose(control_connection_t *conn, - uint32_t len, const char *body) + const control_cmd_args_t *args) { origin_circuit_t *circ = NULL; uint8_t new_purpose; - smartlist_t *args; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - - args = getargs_helper("SETCIRCUITPURPOSE", conn, body, 2, -1); - if (!args) - goto done; + const char *circ_id = smartlist_get(args->args,0); - if (!(circ = get_circ(smartlist_get(args,0)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); + if (!(circ = get_circ(circ_id))) { + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", circ_id); goto done; } { - const char *purp = find_element_starting_with(args,1,"PURPOSE="); + const config_line_t *purp = config_line_find_case(args->kwargs, "PURPOSE"); if (!purp) { connection_write_str_to_buf("552 No purpose given\r\n", conn); goto done; } - new_purpose = circuit_purpose_from_string(purp); + new_purpose = circuit_purpose_from_string(purp->value); if (new_purpose == CIRCUIT_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", purp); + connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", + purp->value); goto done; } } @@ -841,54 +932,50 @@ handle_control_setcircuitpurpose(control_connection_t *conn, connection_write_str_to_buf("250 OK\r\n", conn); done: - if (args) { - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - } return 0; } +static const char *attachstream_keywords[] = { + "HOP", NULL +}; +static const control_cmd_syntax_t attachstream_syntax = { + .min_args=2, .max_args=2, + .accept_keywords=true, + .allowed_keywords=attachstream_keywords +}; + /** Called when we get an ATTACHSTREAM message. Try to attach the requested * stream, and report success or failure. */ static int -handle_control_attachstream(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_attachstream(control_connection_t *conn, + const control_cmd_args_t *args) { entry_connection_t *ap_conn = NULL; origin_circuit_t *circ = NULL; - int zero_circ; - smartlist_t *args; crypt_path_t *cpath=NULL; int hop=0, hop_line_ok=1; - (void) len; + const char *stream_id = smartlist_get(args->args, 0); + const char *circ_id = smartlist_get(args->args, 1); + int zero_circ = !strcmp(circ_id, "0"); + const config_line_t *hoparg = config_line_find_case(args->kwargs, "HOP"); - args = getargs_helper("ATTACHSTREAM", conn, body, 2, -1); - if (!args) + if (!(ap_conn = get_stream(stream_id))) { + connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", stream_id); + return 0; + } else if (!zero_circ && !(circ = get_circ(circ_id))) { + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", circ_id); return 0; - - zero_circ = !strcmp("0", (char*)smartlist_get(args,1)); - - if (!(ap_conn = get_stream(smartlist_get(args, 0)))) { - connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - } else if (!zero_circ && !(circ = get_circ(smartlist_get(args, 1)))) { - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 1)); } else if (circ) { - const char *hopstring = find_element_starting_with(args,2,"HOP="); - if (hopstring) { - hopstring += strlen("HOP="); - hop = (int) tor_parse_ulong(hopstring, 10, 0, INT_MAX, + if (hoparg) { + hop = (int) tor_parse_ulong(hoparg->value, 10, 0, INT_MAX, &hop_line_ok, NULL); if (!hop_line_ok) { /* broken hop line */ - connection_printf_to_buf(conn, "552 Bad value hop=%s\r\n", hopstring); + connection_printf_to_buf(conn, "552 Bad value hop=%s\r\n", + hoparg->value); + return 0; } } } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!ap_conn || (!zero_circ && !circ) || !hop_line_ok) - return 0; if (ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONTROLLER_WAIT && ENTRY_TO_CONN(ap_conn)->state != AP_CONN_STATE_CONNECT_WAIT && @@ -943,59 +1030,49 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, return 0; } +static const char *postdescriptor_keywords[] = { + "cache", "purpose", NULL, +}; + +static const control_cmd_syntax_t postdescriptor_syntax = { + .max_args = 0, + .accept_keywords = true, + .allowed_keywords = postdescriptor_keywords, + .want_cmddata = true, +}; + /** Called when we get a POSTDESCRIPTOR message. Try to learn the provided * descriptor, and report success or failure. */ static int -handle_control_postdescriptor(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_postdescriptor(control_connection_t *conn, + const control_cmd_args_t *args) { - char *desc; const char *msg=NULL; uint8_t purpose = ROUTER_PURPOSE_GENERAL; int cache = 0; /* eventually, we may switch this to 1 */ + const config_line_t *line; - const char *cp = memchr(body, '\n', len); - - if (cp == NULL) { - connection_printf_to_buf(conn, "251 Empty body\r\n"); - return 0; + line = config_line_find_case(args->kwargs, "purpose"); + if (line) { + purpose = router_purpose_from_string(line->value); + connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", + line->value); + goto done; } - ++cp; - - char *cmdline = tor_memdup_nulterm(body, cp-body); - smartlist_t *args = smartlist_new(); - smartlist_split_string(args, cmdline, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, char *, option) { - if (!strcasecmpstart(option, "purpose=")) { - option += strlen("purpose="); - purpose = router_purpose_from_string(option); - if (purpose == ROUTER_PURPOSE_UNKNOWN) { - connection_printf_to_buf(conn, "552 Unknown purpose \"%s\"\r\n", - option); - goto done; - } - } else if (!strcasecmpstart(option, "cache=")) { - option += strlen("cache="); - if (!strcasecmp(option, "no")) - cache = 0; - else if (!strcasecmp(option, "yes")) - cache = 1; - else { - connection_printf_to_buf(conn, "552 Unknown cache request \"%s\"\r\n", - option); - goto done; - } - } else { /* unrecognized argument? */ - connection_printf_to_buf(conn, - "512 Unexpected argument \"%s\" to postdescriptor\r\n", option); + line = config_line_find_case(args->kwargs, "cache"); + if (line) { + if (!strcasecmp(line->value, "no")) + cache = 0; + else if (!strcasecmp(line->value, "yes")) + cache = 1; + else { + connection_printf_to_buf(conn, "552 Unknown cache request \"%s\"\r\n", + line->value); goto done; } - } SMARTLIST_FOREACH_END(option); - - read_escaped_data(cp, len-(cp-body), &desc); + } - switch (router_load_single_router(desc, purpose, cache, &msg)) { + switch (router_load_single_router(args->cmddata, purpose, cache, &msg)) { case -1: if (!msg) msg = "Could not parse descriptor"; connection_printf_to_buf(conn, "554 %s\r\n", msg); @@ -1009,29 +1086,25 @@ handle_control_postdescriptor(control_connection_t *conn, uint32_t len, break; } - tor_free(desc); done: - SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); - smartlist_free(args); - tor_free(cmdline); return 0; } +static const control_cmd_syntax_t redirectstream_syntax = { + .min_args = 2, + .max_args = UINT_MAX, // XXX should be 3. +}; + /** Called when we receive a REDIRECTSTERAM command. Try to change the target * address of the named AP stream, and report success or failure. */ static int -handle_control_redirectstream(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_redirectstream(control_connection_t *conn, + const control_cmd_args_t *cmd_args) { entry_connection_t *ap_conn = NULL; char *new_addr = NULL; uint16_t new_port = 0; - smartlist_t *args; - (void) len; - - args = getargs_helper("REDIRECTSTREAM", conn, body, 2, -1); - if (!args) - return 0; + const smartlist_t *args = cmd_args->args; if (!(ap_conn = get_stream(smartlist_get(args, 0))) || !ap_conn->socks_request) { @@ -1051,8 +1124,6 @@ handle_control_redirectstream(control_connection_t *conn, uint32_t len, } } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); if (!new_addr) return 0; @@ -1065,23 +1136,26 @@ handle_control_redirectstream(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t closestream_syntax = { + .min_args = 2, + .max_args = UINT_MAX, /* XXXX This is the original behavior, but + * maybe we should change the spec. */ +}; + /** Called when we get a CLOSESTREAM command; try to close the named stream * and report success or failure. */ static int -handle_control_closestream(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_closestream(control_connection_t *conn, + const control_cmd_args_t *cmd_args) { entry_connection_t *ap_conn=NULL; uint8_t reason=0; - smartlist_t *args; int ok; - (void) len; + const smartlist_t *args = cmd_args->args; - args = getargs_helper("CLOSESTREAM", conn, body, 2, -1); - if (!args) - return 0; + tor_assert(smartlist_len(args) >= 2); - else if (!(ap_conn = get_stream(smartlist_get(args, 0)))) + if (!(ap_conn = get_stream(smartlist_get(args, 0)))) connection_printf_to_buf(conn, "552 Unknown stream \"%s\"\r\n", (char*)smartlist_get(args, 0)); else { @@ -1093,8 +1167,6 @@ handle_control_closestream(control_connection_t *conn, uint32_t len, ap_conn = NULL; } } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); if (!ap_conn) return 0; @@ -1103,38 +1175,29 @@ handle_control_closestream(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t closecircuit_syntax = { + .min_args=1, .max_args=1, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS, + // XXXX we might want to exclude unrecognized flags, but for now we + // XXXX just ignore them for backward compatibility. +}; + /** Called when we get a CLOSECIRCUIT command; try to close the named circuit * and report success or failure. */ static int -handle_control_closecircuit(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_closecircuit(control_connection_t *conn, + const control_cmd_args_t *args) { + const char *circ_id = smartlist_get(args->args, 0); origin_circuit_t *circ = NULL; - int safe = 0; - smartlist_t *args; - (void) len; - args = getargs_helper("CLOSECIRCUIT", conn, body, 1, -1); - if (!args) + if (!(circ=get_circ(circ_id))) { + connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", circ_id); return 0; - - if (!(circ=get_circ(smartlist_get(args, 0)))) - connection_printf_to_buf(conn, "552 Unknown circuit \"%s\"\r\n", - (char*)smartlist_get(args, 0)); - else { - int i; - for (i=1; i < smartlist_len(args); ++i) { - if (!strcasecmp(smartlist_get(args, i), "IfUnused")) - safe = 1; - else - log_info(LD_CONTROL, "Skipping unknown option %s", - (char*)smartlist_get(args,i)); - } } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); - if (!circ) - return 0; + + bool safe = config_lines_contain_flag(args->kwargs, "IfUnused"); if (!safe || !circ->p_streams) { circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_REQUESTED); @@ -1144,36 +1207,43 @@ handle_control_closecircuit(control_connection_t *conn, uint32_t len, return 0; } +static const control_cmd_syntax_t resolve_syntax = { + .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS, +}; + /** Called when we get a RESOLVE command: start trying to resolve * the listed addresses. */ static int -handle_control_resolve(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_resolve(control_connection_t *conn, + const control_cmd_args_t *args) { - smartlist_t *args, *failed; + smartlist_t *failed; int is_reverse = 0; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ if (!(conn->event_mask & (((event_mask_t)1)<<EVENT_ADDRMAP))) { log_warn(LD_CONTROL, "Controller asked us to resolve an address, but " "isn't listening for ADDRMAP events. It probably won't see " "the answer."); } - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + { - const char *modearg = find_element_starting_with(args, 0, "mode="); - if (modearg && !strcasecmp(modearg, "mode=reverse")) + const config_line_t *modearg = config_line_find_case(args->kwargs, "mode"); + if (modearg && !strcasecmp(modearg->value, "reverse")) is_reverse = 1; } failed = smartlist_new(); - SMARTLIST_FOREACH(args, const char *, arg, { - if (!is_keyval_pair(arg)) { - if (dnsserv_launch_request(arg, is_reverse, conn)<0) - smartlist_add(failed, (char*)arg); - } - }); + for (const config_line_t *line = args->kwargs; line; line = line->next) { + if (!strlen(line->value)) { + const char *addr = line->key; + if (dnsserv_launch_request(addr, is_reverse, conn)<0) + smartlist_add(failed, (char*)addr); + } else { + // XXXX arguably we should reject unrecognized keyword arguments, + // XXXX but the old implementation didn't do that. + } + } send_control_done(conn); SMARTLIST_FOREACH(failed, const char *, arg, { @@ -1181,25 +1251,24 @@ handle_control_resolve(control_connection_t *conn, uint32_t len, "internal", 0); }); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); smartlist_free(failed); return 0; } +static const control_cmd_syntax_t protocolinfo_syntax = { + .max_args = UINT_MAX +}; + /** Called when we get a PROTOCOLINFO command: send back a reply. */ static int -handle_control_protocolinfo(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_protocolinfo(control_connection_t *conn, + const control_cmd_args_t *cmd_args) { const char *bad_arg = NULL; - smartlist_t *args; - (void)len; + const smartlist_t *args = cmd_args->args; conn->have_sent_protocolinfo = 1; - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + SMARTLIST_FOREACH(args, const char *, arg, { int ok; tor_parse_long(arg, 10, 0, LONG_MAX, &ok, NULL); @@ -1255,24 +1324,21 @@ handle_control_protocolinfo(control_connection_t *conn, uint32_t len, tor_free(esc_cfile); } done: - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); return 0; } +static const control_cmd_syntax_t usefeature_syntax = { + .max_args = UINT_MAX +}; + /** Called when we get a USEFEATURE command: parse the feature list, and * set up the control_connection's options properly. */ static int handle_control_usefeature(control_connection_t *conn, - uint32_t len, - const char *body) + const control_cmd_args_t *cmd_args) { - smartlist_t *args; + const smartlist_t *args = cmd_args->args; int bad = 0; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { if (!strcasecmp(arg, "VERBOSE_NAMES")) ; @@ -1290,22 +1356,19 @@ handle_control_usefeature(control_connection_t *conn, send_control_done(conn); } - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); return 0; } +static const control_cmd_syntax_t dropguards_syntax = { + .max_args = 0, +}; + /** Implementation for the DROPGUARDS command. */ static int handle_control_dropguards(control_connection_t *conn, - uint32_t len, - const char *body) + const control_cmd_args_t *args) { - smartlist_t *args; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = smartlist_new(); - smartlist_split_string(args, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + (void) args; /* We don't take arguments. */ static int have_warned = 0; if (! have_warned) { @@ -1315,42 +1378,39 @@ handle_control_dropguards(control_connection_t *conn, have_warned = 1; } - if (smartlist_len(args)) { - connection_printf_to_buf(conn, "512 Too many arguments to DROPGUARDS\r\n"); - } else { - remove_all_entry_guards(); - send_control_done(conn); - } + remove_all_entry_guards(); + send_control_done(conn); - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); return 0; } +static const char *hsfetch_keywords[] = { + "SERVER", NULL, +}; +static const control_cmd_syntax_t hsfetch_syntax = { + .min_args = 1, .max_args = 1, + .accept_keywords = true, + .allowed_keywords = hsfetch_keywords, + .want_cmddata = true, +}; + /** Implementation for the HSFETCH command. */ static int -handle_control_hsfetch(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_hsfetch(control_connection_t *conn, + const control_cmd_args_t *args) + { - int i; - char digest[DIGEST_LEN], *hsaddress = NULL, *arg1 = NULL, *desc_id = NULL; - smartlist_t *args = NULL, *hsdirs = NULL; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - static const char *hsfetch_command = "HSFETCH"; + char digest[DIGEST_LEN], *desc_id = NULL; + smartlist_t *hsdirs = NULL; static const char *v2_str = "v2-"; const size_t v2_str_len = strlen(v2_str); rend_data_t *rend_query = NULL; ed25519_public_key_t v3_pk; uint32_t version; - - /* Make sure we have at least one argument, the HSAddress. */ - args = getargs_helper(hsfetch_command, conn, body, 1, -1); - if (!args) { - goto exit; - } + const char *hsaddress = NULL; /* Extract the first argument (either HSAddress or DescID). */ - arg1 = smartlist_get(args, 0); + const char *arg1 = smartlist_get(args->args, 0); /* Test if it's an HS address without the .onion part. */ if (rend_valid_v2_service_id(arg1)) { hsaddress = arg1; @@ -1374,33 +1434,24 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, goto done; } - static const char *opt_server = "SERVER="; - - /* Skip first argument because it's the HSAddress or DescID. */ - for (i = 1; i < smartlist_len(args); ++i) { - const char *arg = smartlist_get(args, i); - const node_t *node; - - if (!strcasecmpstart(arg, opt_server)) { - const char *server; + for (const config_line_t *line = args->kwargs; line; line = line->next) { + if (!strcasecmp(line->key, "SERVER")) { + const char *server = line->value; - server = arg + strlen(opt_server); - node = node_get_by_hex_id(server, 0); + const node_t *node = node_get_by_hex_id(server, 0); if (!node) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", server); goto done; } if (!hsdirs) { - /* Stores routerstatus_t object for each specified server. */ + /* Stores routerstatus_t cmddata for each specified server. */ hsdirs = smartlist_new(); } /* Valid server, add it to our local list. */ smartlist_add(hsdirs, node->rs); } else { - connection_printf_to_buf(conn, "513 Unexpected argument \"%s\"\r\n", - arg); - goto done; + tor_assert_nonfatal_unreached(); } } @@ -1416,9 +1467,8 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, /* Using a descriptor ID, we force the user to provide at least one * hsdir server using the SERVER= option. */ if (desc_id && (!hsdirs || !smartlist_len(hsdirs))) { - connection_printf_to_buf(conn, "512 %s option is required\r\n", - opt_server); - goto done; + connection_printf_to_buf(conn, "512 SERVER option is required\r\n"); + goto done; } /* We are about to trigger HSDir fetch so send the OK now because after @@ -1436,96 +1486,75 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, } done: - SMARTLIST_FOREACH(args, char *, cp, tor_free(cp)); - smartlist_free(args); /* Contains data pointer that we don't own thus no cleanup. */ smartlist_free(hsdirs); rend_data_free(rend_query); - exit: return 0; } +static const char *hspost_keywords[] = { + "SERVER", "HSADDRESS", NULL +}; +static const control_cmd_syntax_t hspost_syntax = { + .min_args = 0, .max_args = 0, + .accept_keywords = true, + .want_cmddata = true, + .allowed_keywords = hspost_keywords +}; + /** Implementation for the HSPOST command. */ static int handle_control_hspost(control_connection_t *conn, - uint32_t len, - const char *body) + const control_cmd_args_t *args) { - static const char *opt_server = "SERVER="; - static const char *opt_hsaddress = "HSADDRESS="; smartlist_t *hs_dirs = NULL; - const char *encoded_desc = body; - size_t encoded_desc_len = len; + const char *encoded_desc = args->cmddata; + size_t encoded_desc_len = args->cmddata_len; const char *onion_address = NULL; + const config_line_t *line; - char *cp = memchr(body, '\n', len); - if (cp == NULL) { - connection_printf_to_buf(conn, "251 Empty body\r\n"); - return 0; - } - char *argline = tor_strndup(body, cp-body); - - smartlist_t *args = smartlist_new(); - - /* If any SERVER= or HSADDRESS= options were specified, try to parse - * the options line. */ - if (!strcasecmpstart(argline, opt_server) || - !strcasecmpstart(argline, opt_hsaddress)) { - /* encoded_desc begins after a newline character */ - cp = cp + 1; - encoded_desc = cp; - encoded_desc_len = len-(cp-body); - - smartlist_split_string(args, argline, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { - if (!strcasecmpstart(arg, opt_server)) { - const char *server = arg + strlen(opt_server); - const node_t *node = node_get_by_hex_id(server, 0); - - if (!node || !node->rs) { - connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", - server); - goto done; - } - /* Valid server, add it to our local list. */ - if (!hs_dirs) - hs_dirs = smartlist_new(); - smartlist_add(hs_dirs, node->rs); - } else if (!strcasecmpstart(arg, opt_hsaddress)) { - const char *address = arg + strlen(opt_hsaddress); - if (!hs_address_is_valid(address)) { - connection_printf_to_buf(conn, "512 Malformed onion address\r\n"); - goto done; - } - onion_address = address; - } else { - connection_printf_to_buf(conn, "512 Unexpected argument \"%s\"\r\n", - arg); + for (line = args->kwargs; line; line = line->next) { + if (!strcasecmpstart(line->key, "SERVER")) { + const char *server = line->value; + const node_t *node = node_get_by_hex_id(server, 0); + + if (!node || !node->rs) { + connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", + server); goto done; } - } SMARTLIST_FOREACH_END(arg); + /* Valid server, add it to our local list. */ + if (!hs_dirs) + hs_dirs = smartlist_new(); + smartlist_add(hs_dirs, node->rs); + } else if (!strcasecmpstart(line->key, "HSADDRESS")) { + const char *address = line->value; + if (!hs_address_is_valid(address)) { + connection_printf_to_buf(conn, "512 Malformed onion address\r\n"); + goto done; + } + onion_address = address; + } else { + tor_assert_nonfatal_unreached(); + } } /* Handle the v3 case. */ if (onion_address) { - char *desc_str = NULL; - read_escaped_data(encoded_desc, encoded_desc_len, &desc_str); - if (hs_control_hspost_command(desc_str, onion_address, hs_dirs) < 0) { + if (hs_control_hspost_command(encoded_desc, onion_address, hs_dirs) < 0) { connection_printf_to_buf(conn, "554 Invalid descriptor\r\n"); } else { send_control_done(conn); } - tor_free(desc_str); goto done; } /* From this point on, it is only v2. */ - /* Read the dot encoded descriptor, and parse it. */ + /* parse it. */ rend_encoded_v2_service_descriptor_t *desc = tor_malloc_zero(sizeof(rend_encoded_v2_service_descriptor_t)); - read_escaped_data(encoded_desc, encoded_desc_len, &desc->desc_str); + desc->desc_str = tor_memdup_nulterm(encoded_desc, encoded_desc_len); rend_service_descriptor_t *parsed = NULL; char *intro_content = NULL; @@ -1559,10 +1588,7 @@ handle_control_hspost(control_connection_t *conn, tor_free(intro_content); rend_encoded_v2_service_descriptor_free(desc); done: - tor_free(argline); smartlist_free(hs_dirs); /* Contents belong to the rend service code. */ - SMARTLIST_FOREACH(args, char *, arg, tor_free(arg)); - smartlist_free(args); return 0; } @@ -1626,21 +1652,21 @@ get_detached_onion_services(void) return detached_onion_services; } +static const char *add_onion_keywords[] = { + "Port", "Flags", "MaxStreams", "ClientAuth", NULL +}; +static const control_cmd_syntax_t add_onion_syntax = { + .min_args = 1, .max_args = 1, + .accept_keywords = true, + .allowed_keywords = add_onion_keywords +}; + /** Called when we get a ADD_ONION command; parse the body, and set up * the new ephemeral Onion Service. */ static int handle_control_add_onion(control_connection_t *conn, - uint32_t len, - const char *body) + const control_cmd_args_t *args) { - smartlist_t *args; - int arg_len; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = getargs_helper("ADD_ONION", conn, body, 2, -1); - if (!args) - return 0; - arg_len = smartlist_len(args); - /* Parse all of the arguments that do not involve handling cryptographic * material first, since there's no reason to touch that at all if any of * the other arguments are malformed. @@ -1653,36 +1679,28 @@ handle_control_add_onion(control_connection_t *conn, int max_streams = 0; int max_streams_close_circuit = 0; rend_auth_type_t auth_type = REND_NO_AUTH; - /* Default to adding an anonymous hidden service if no flag is given */ int non_anonymous = 0; - for (int i = 1; i < arg_len; i++) { - static const char *port_prefix = "Port="; - static const char *flags_prefix = "Flags="; - static const char *max_s_prefix = "MaxStreams="; - static const char *auth_prefix = "ClientAuth="; - - const char *arg = smartlist_get(args, (int)i); - if (!strcasecmpstart(arg, port_prefix)) { - /* "Port=VIRTPORT[,TARGET]". */ - const char *port_str = arg + strlen(port_prefix); + const config_line_t *arg; + for (arg = args->kwargs; arg; arg = arg->next) { + if (!strcasecmp(arg->key, "Port")) { + /* "Port=VIRTPORT[,TARGET]". */ rend_service_port_config_t *cfg = - rend_service_parse_port_config(port_str, ",", NULL); + rend_service_parse_port_config(arg->value, ",", NULL); if (!cfg) { connection_printf_to_buf(conn, "512 Invalid VIRTPORT/TARGET\r\n"); goto out; } smartlist_add(port_cfgs, cfg); - } else if (!strcasecmpstart(arg, max_s_prefix)) { + } else if (!strcasecmp(arg->key, "MaxStreams")) { /* "MaxStreams=[0..65535]". */ - const char *max_s_str = arg + strlen(max_s_prefix); int ok = 0; - max_streams = (int)tor_parse_long(max_s_str, 10, 0, 65535, &ok, NULL); + max_streams = (int)tor_parse_long(arg->value, 10, 0, 65535, &ok, NULL); if (!ok) { connection_printf_to_buf(conn, "512 Invalid MaxStreams\r\n"); goto out; } - } else if (!strcasecmpstart(arg, flags_prefix)) { + } else if (!strcasecmp(arg->key, "Flags")) { /* "Flags=Flag[,Flag]", where Flag can be: * * 'DiscardPK' - If tor generates the keypair, do not include it in * the response. @@ -1705,8 +1723,7 @@ handle_control_add_onion(control_connection_t *conn, smartlist_t *flags = smartlist_new(); int bad = 0; - smartlist_split_string(flags, arg + strlen(flags_prefix), ",", - SPLIT_IGNORE_BLANK, 0); + smartlist_split_string(flags, arg->value, ",", SPLIT_IGNORE_BLANK, 0); if (smartlist_len(flags) < 1) { connection_printf_to_buf(conn, "512 Invalid 'Flags' argument\r\n"); bad = 1; @@ -1735,11 +1752,12 @@ handle_control_add_onion(control_connection_t *conn, smartlist_free(flags); if (bad) goto out; - } else if (!strcasecmpstart(arg, auth_prefix)) { + + } else if (!strcasecmp(arg->key, "ClientAuth")) { char *err_msg = NULL; int created = 0; rend_authorized_client_t *client = - add_onion_helper_clientauth(arg + strlen(auth_prefix), + add_onion_helper_clientauth(arg->value, &created, &err_msg); if (!client) { if (err_msg) { @@ -1772,7 +1790,7 @@ handle_control_add_onion(control_connection_t *conn, smartlist_add(auth_created_clients, client); } } else { - connection_printf_to_buf(conn, "513 Invalid argument\r\n"); + tor_assert_nonfatal_unreached(); goto out; } } @@ -1813,7 +1831,8 @@ handle_control_add_onion(control_connection_t *conn, char *key_new_blob = NULL; char *err_msg = NULL; - if (add_onion_helper_keyarg(smartlist_get(args, 0), discard_pk, + const char *onionkey = smartlist_get(args->args, 0); + if (add_onion_helper_keyarg(onionkey, discard_pk, &key_new_alg, &key_new_blob, &pk, &hs_version, &err_msg) < 0) { if (err_msg) { @@ -1915,12 +1934,6 @@ handle_control_add_onion(control_connection_t *conn, // Do not free entries; they are the same as auth_clients smartlist_free(auth_created_clients); } - - SMARTLIST_FOREACH(args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(args); return 0; } @@ -2120,19 +2133,19 @@ add_onion_helper_clientauth(const char *arg, int *created, char **err_msg) return client; } +static const control_cmd_syntax_t del_onion_syntax = { + .min_args = 1, .max_args = 1, +}; + /** Called when we get a DEL_ONION command; parse the body, and remove * the existing ephemeral Onion Service. */ static int handle_control_del_onion(control_connection_t *conn, - uint32_t len, - const char *body) + const control_cmd_args_t *cmd_args) { int hs_version = 0; - smartlist_t *args; - (void) len; /* body is nul-terminated; it's safe to ignore the length */ - args = getargs_helper("DEL_ONION", conn, body, 1, 1); - if (!args) - return 0; + smartlist_t *args = cmd_args->args; + tor_assert(smartlist_len(args) == 1); const char *service_id = smartlist_get(args, 0); if (rend_valid_v2_service_id(service_id)) { @@ -2198,118 +2211,202 @@ handle_control_del_onion(control_connection_t *conn, } out: - SMARTLIST_FOREACH(args, char *, cp, { - memwipe(cp, 0, strlen(cp)); - tor_free(cp); - }); - smartlist_free(args); return 0; } -int -handle_control_command(control_connection_t *conn, - uint32_t cmd_data_len, - char *args) +static const control_cmd_syntax_t obsolete_syntax = { + .max_args = UINT_MAX +}; + +/** + * Called when we get an obsolete command: tell the controller that it is + * obsolete. + */ +static int +handle_control_obsolete(control_connection_t *conn, + const control_cmd_args_t *args) { - /* XXXX Why is this not implemented as a table like the GETINFO - * items are? Even handling the plus signs at the beginnings of - * commands wouldn't be very hard with proper macros. */ + (void)args; + char *command = tor_strdup(conn->current_cmd); + tor_strupper(command); + connection_printf_to_buf(conn, "511 %s is obsolete.\r\n", command); + tor_free(command); + return 0; +} - if (!strcasecmp(conn->incoming_cmd, "SETCONF")) { - if (handle_control_setconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "RESETCONF")) { - if (handle_control_resetconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "GETCONF")) { - if (handle_control_getconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+LOADCONF")) { - if (handle_control_loadconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETEVENTS")) { - if (handle_control_setevents(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "AUTHENTICATE")) { - if (handle_control_authenticate(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SAVECONF")) { - if (handle_control_saveconf(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SIGNAL")) { - if (handle_control_signal(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "TAKEOWNERSHIP")) { - if (handle_control_takeownership(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DROPOWNERSHIP")) { - if (handle_control_dropownership(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "MAPADDRESS")) { - if (handle_control_mapaddress(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "GETINFO")) { - if (handle_control_getinfo(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "EXTENDCIRCUIT")) { - if (handle_control_extendcircuit(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETCIRCUITPURPOSE")) { - if (handle_control_setcircuitpurpose(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "SETROUTERPURPOSE")) { - connection_write_str_to_buf("511 SETROUTERPURPOSE is obsolete.\r\n", conn); - } else if (!strcasecmp(conn->incoming_cmd, "ATTACHSTREAM")) { - if (handle_control_attachstream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+POSTDESCRIPTOR")) { - if (handle_control_postdescriptor(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "REDIRECTSTREAM")) { - if (handle_control_redirectstream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "CLOSESTREAM")) { - if (handle_control_closestream(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "CLOSECIRCUIT")) { - if (handle_control_closecircuit(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "USEFEATURE")) { - if (handle_control_usefeature(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "RESOLVE")) { - if (handle_control_resolve(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "PROTOCOLINFO")) { - if (handle_control_protocolinfo(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "AUTHCHALLENGE")) { - if (handle_control_authchallenge(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DROPGUARDS")) { - if (handle_control_dropguards(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "HSFETCH")) { - if (handle_control_hsfetch(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "+HSPOST")) { - if (handle_control_hspost(conn, cmd_data_len, args)) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "ADD_ONION")) { - int ret = handle_control_add_onion(conn, cmd_data_len, args); - memwipe(args, 0, cmd_data_len); /* Scrub the private key. */ - if (ret) - return -1; - } else if (!strcasecmp(conn->incoming_cmd, "DEL_ONION")) { - int ret = handle_control_del_onion(conn, cmd_data_len, args); - memwipe(args, 0, cmd_data_len); /* Scrub the service id/pk. */ - if (ret) - return -1; +/** + * Function pointer to a handler function for a controller command. + **/ +typedef int (*handler_fn_t) (control_connection_t *conn, + const control_cmd_args_t *args); + +/** + * Definition for a controller command. + */ +typedef struct control_cmd_def_t { + /** + * The name of the command. If the command is multiline, the name must + * begin with "+". This is not case-sensitive. */ + const char *name; + /** + * A function to execute the command. + */ + handler_fn_t handler; + /** + * Zero or more CMD_FL_* flags, or'd together. + */ + unsigned flags; + /** + * For parsed command: a syntax description. + */ + const control_cmd_syntax_t *syntax; +} control_cmd_def_t; + +/** + * Indicates that the command's arguments are sensitive, and should be + * memwiped after use. + */ +#define CMD_FL_WIPE (1u<<0) + +/** Macro: declare a command with a one-line argument, a given set of flags, + * and a syntax definition. + **/ +#define ONE_LINE(name, flags) \ + { \ + #name, \ + handle_control_ ##name, \ + flags, \ + &name##_syntax, \ + } + +/** + * Macro: declare a command with a multi-line argument and a given set of + * flags. + **/ +#define MULTLINE(name, flags) \ + { "+"#name, \ + handle_control_ ##name, \ + flags, \ + &name##_syntax \ + } + +/** + * Macro: declare an obsolete command. (Obsolete commands give a different + * error than non-existent ones.) + **/ +#define OBSOLETE(name) \ + { #name, \ + handle_control_obsolete, \ + 0, \ + &obsolete_syntax, \ + } + +/** + * An array defining all the recognized controller commands. + **/ +static const control_cmd_def_t CONTROL_COMMANDS[] = +{ + ONE_LINE(setconf, 0), + ONE_LINE(resetconf, 0), + ONE_LINE(getconf, 0), + MULTLINE(loadconf, 0), + ONE_LINE(setevents, 0), + ONE_LINE(authenticate, CMD_FL_WIPE), + ONE_LINE(saveconf, 0), + ONE_LINE(signal, 0), + ONE_LINE(takeownership, 0), + ONE_LINE(dropownership, 0), + ONE_LINE(mapaddress, 0), + ONE_LINE(getinfo, 0), + ONE_LINE(extendcircuit, 0), + ONE_LINE(setcircuitpurpose, 0), + OBSOLETE(setrouterpurpose), + ONE_LINE(attachstream, 0), + MULTLINE(postdescriptor, 0), + ONE_LINE(redirectstream, 0), + ONE_LINE(closestream, 0), + ONE_LINE(closecircuit, 0), + ONE_LINE(usefeature, 0), + ONE_LINE(resolve, 0), + ONE_LINE(protocolinfo, 0), + ONE_LINE(authchallenge, CMD_FL_WIPE), + ONE_LINE(dropguards, 0), + ONE_LINE(hsfetch, 0), + MULTLINE(hspost, 0), + ONE_LINE(add_onion, CMD_FL_WIPE), + ONE_LINE(del_onion, CMD_FL_WIPE), +}; + +/** + * The number of entries in CONTROL_COMMANDS. + **/ +static const size_t N_CONTROL_COMMANDS = ARRAY_LENGTH(CONTROL_COMMANDS); + +/** + * Run a single control command, as defined by a control_cmd_def_t, + * with a given set of arguments. + */ +static int +handle_single_control_command(const control_cmd_def_t *def, + control_connection_t *conn, + uint32_t cmd_data_len, + char *args) +{ + int rv = 0; + + control_cmd_args_t *parsed_args; + char *err=NULL; + tor_assert(def->syntax); + parsed_args = control_cmd_parse_args(conn->current_cmd, + def->syntax, + cmd_data_len, args, + &err); + if (!parsed_args) { + connection_printf_to_buf(conn, + "512 Bad arguments to %s: %s\r\n", + conn->current_cmd, err?err:""); + tor_free(err); } else { - connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", - conn->incoming_cmd); + if (BUG(err)) + tor_free(err); + if (def->handler(conn, parsed_args)) + rv = 0; + + if (def->flags & CMD_FL_WIPE) + control_cmd_args_wipe(parsed_args); + + control_cmd_args_free(parsed_args); } + if (def->flags & CMD_FL_WIPE) + memwipe(args, 0, cmd_data_len); + + return rv; +} + +/** + * Run a given controller command, as selected by the current_cmd field of + * <b>conn</b>. + */ +int +handle_control_command(control_connection_t *conn, + uint32_t cmd_data_len, + char *args) +{ + tor_assert(conn); + tor_assert(args); + tor_assert(args[cmd_data_len] == '\0'); + + for (unsigned i = 0; i < N_CONTROL_COMMANDS; ++i) { + const control_cmd_def_t *def = &CONTROL_COMMANDS[i]; + if (!strcasecmp(conn->current_cmd, def->name)) { + return handle_single_control_command(def, conn, cmd_data_len, args); + } + } + + connection_printf_to_buf(conn, "510 Unrecognized command \"%s\"\r\n", + conn->current_cmd); + return 0; } diff --git a/src/feature/control/control_cmd.h b/src/feature/control/control_cmd.h index a417e10da3..5c3d1a1cec 100644 --- a/src/feature/control/control_cmd.h +++ b/src/feature/control/control_cmd.h @@ -12,11 +12,68 @@ #ifndef TOR_CONTROL_CMD_H #define TOR_CONTROL_CMD_H +#include "lib/malloc/malloc.h" + int handle_control_command(control_connection_t *conn, uint32_t cmd_data_len, char *args); void control_cmd_free_all(void); +typedef struct control_cmd_args_t control_cmd_args_t; +void control_cmd_args_free_(control_cmd_args_t *args); +void control_cmd_args_wipe(control_cmd_args_t *args); + +#define control_cmd_args_free(v) \ + FREE_AND_NULL(control_cmd_args_t, control_cmd_args_free_, (v)) + +/** + * Definition for the syntax of a controller command, as parsed by + * control_cmd_parse_args. + * + * WORK IN PROGRESS: This structure is going to get more complex as this + * branch goes on. + **/ +typedef struct control_cmd_syntax_t { + /** + * Lowest number of positional arguments that this command accepts. + * 0 for "it's okay not to have positional arguments." + **/ + unsigned int min_args; + /** + * Highest number of positional arguments that this command accepts. + * UINT_MAX for no limit. + **/ + unsigned int max_args; + /** + * If true, we should parse options after the positional arguments + * as a set of unordered flags and key=value arguments. + * + * Requires that max_args is not UINT_MAX. + **/ + bool accept_keywords; + /** + * If accept_keywords is true, then only the keywords listed in this + * (NULL-terminated) array are valid keywords for this command. + **/ + const char **allowed_keywords; + /** + * If accept_keywords is true, this option is passed to kvline_parse() as + * its flags. + **/ + unsigned kvline_flags; + /** + * True iff this command wants to be followed by a multiline object. + **/ + bool want_cmddata; + /** + * True iff this command needs access to the raw body of the input. + * + * This should not be needed for pure commands; it is purely a legacy + * option. + **/ + bool store_raw_body; +} control_cmd_syntax_t; + #ifdef CONTROL_CMD_PRIVATE #include "lib/crypt_ops/crypto_ed25519.h" @@ -39,6 +96,13 @@ STATIC int add_onion_helper_keyarg(const char *arg, int discard_pk, STATIC rend_authorized_client_t *add_onion_helper_clientauth(const char *arg, int *created, char **err_msg_out); +STATIC control_cmd_args_t *control_cmd_parse_args( + const char *command, + const control_cmd_syntax_t *syntax, + size_t body_len, + const char *body, + char **error_out); + #endif /* defined(CONTROL_CMD_PRIVATE) */ #ifdef CONTROL_MODULE_PRIVATE diff --git a/src/feature/control/control_cmd_args_st.h b/src/feature/control/control_cmd_args_st.h new file mode 100644 index 0000000000..8d7a4f55b3 --- /dev/null +++ b/src/feature/control/control_cmd_args_st.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file control_cmd_args_st.h + * \brief Definition for control_cmd_args_t + **/ + +#ifndef TOR_CONTROL_CMD_ST_H +#define TOR_CONTROL_CMD_ST_H + +struct smartlist_t; +struct config_line_t; + +/** + * Parsed arguments for a control command. + * + * WORK IN PROGRESS: This structure is going to get more complex as this + * branch goes on. + **/ +struct control_cmd_args_t { + /** + * The command itself, as provided by the controller. Not owned by this + * structure. + **/ + const char *command; + /** + * Positional arguments to the command. + **/ + struct smartlist_t *args; + /** + * Keyword arguments to the command. + **/ + struct config_line_t *kwargs; + /** + * Number of bytes in <b>cmddata</b>; 0 if <b>cmddata</b> is not set. + **/ + size_t cmddata_len; + /** + * A multiline object passed with this command. + **/ + char *cmddata; + /** + * If set, a nul-terminated string containing the raw unparsed arguments. + **/ + const char *raw_body; +}; + +#endif /* !defined(TOR_CONTROL_CMD_ST_H) */ diff --git a/src/feature/control/control_connection_st.h b/src/feature/control/control_connection_st.h index 177a916257..cace6bb36f 100644 --- a/src/feature/control/control_connection_st.h +++ b/src/feature/control/control_connection_st.h @@ -40,7 +40,8 @@ struct control_connection_t { /** A control command that we're reading from the inbuf, but which has not * yet arrived completely. */ char *incoming_cmd; + /** The control command that we are currently processing. */ + char *current_cmd; }; #endif - diff --git a/src/feature/control/control_fmt.c b/src/feature/control/control_fmt.c index 71f9d82163..b2ab4f10bb 100644 --- a/src/feature/control/control_fmt.c +++ b/src/feature/control/control_fmt.c @@ -305,94 +305,6 @@ send_control_done(control_connection_t *conn) connection_write_str_to_buf("250 OK\r\n", conn); } -/** If the first <b>in_len_max</b> characters in <b>start</b> contain a - * double-quoted string with escaped characters, return the length of that - * string (as encoded, including quotes). Otherwise return -1. */ -static inline int -get_escaped_string_length(const char *start, size_t in_len_max, - int *chars_out) -{ - const char *cp, *end; - int chars = 0; - - if (*start != '\"') - return -1; - - cp = start+1; - end = start+in_len_max; - - /* Calculate length. */ - while (1) { - if (cp >= end) { - return -1; /* Too long. */ - } else if (*cp == '\\') { - if (++cp == end) - return -1; /* Can't escape EOS. */ - ++cp; - ++chars; - } else if (*cp == '\"') { - break; - } else { - ++cp; - ++chars; - } - } - if (chars_out) - *chars_out = chars; - return (int)(cp - start+1); -} - -/** As decode_escaped_string, but does not decode the string: copies the - * entire thing, including quotation marks. */ -const char * -extract_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len) -{ - int length = get_escaped_string_length(start, in_len_max, NULL); - if (length<0) - return NULL; - *out_len = length; - *out = tor_strndup(start, *out_len); - return start+length; -} - -/** Given a pointer to a string starting at <b>start</b> containing - * <b>in_len_max</b> characters, decode a string beginning with one double - * quote, containing any number of non-quote characters or characters escaped - * with a backslash, and ending with a final double quote. Place the resulting - * string (unquoted, unescaped) into a newly allocated string in *<b>out</b>; - * store its length in <b>out_len</b>. On success, return a pointer to the - * character immediately following the escaped string. On failure, return - * NULL. */ -const char * -decode_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len) -{ - const char *cp, *end; - char *outp; - int len, n_chars = 0; - - len = get_escaped_string_length(start, in_len_max, &n_chars); - if (len<0) - return NULL; - - end = start+len-1; /* Index of last quote. */ - tor_assert(*end == '\"'); - outp = *out = tor_malloc(len+1); - *out_len = n_chars; - - cp = start+1; - while (cp < end) { - if (*cp == '\\') - ++cp; - *outp++ = *cp++; - } - *outp = '\0'; - tor_assert((outp - *out) == (int)*out_len); - - return end+1; -} - /** Return a longname the node whose identity is <b>id_digest</b>. If * node_get_by_id() returns NULL, base 16 encoding of <b>id_digest</b> is * returned instead. diff --git a/src/feature/control/control_fmt.h b/src/feature/control/control_fmt.h index 74545eb309..8bbbaa95d0 100644 --- a/src/feature/control/control_fmt.h +++ b/src/feature/control/control_fmt.h @@ -25,10 +25,6 @@ char *circuit_describe_status_for_controller(origin_circuit_t *circ); size_t write_escaped_data(const char *data, size_t len, char **out); size_t read_escaped_data(const char *data, size_t len, char **out); -const char *extract_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len); -const char *decode_escaped_string(const char *start, size_t in_len_max, - char **out, size_t *out_len); void send_control_done(control_connection_t *conn); MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest)); diff --git a/src/feature/control/control_getinfo.c b/src/feature/control/control_getinfo.c index a7a85f2fdf..5c6a0d4aa2 100644 --- a/src/feature/control/control_getinfo.c +++ b/src/feature/control/control_getinfo.c @@ -55,6 +55,7 @@ #include "core/or/origin_circuit_st.h" #include "core/or/socks_request_st.h" #include "feature/control/control_connection_st.h" +#include "feature/control/control_cmd_args_st.h" #include "feature/dircache/cached_dir_st.h" #include "feature/nodelist/extrainfo_st.h" #include "feature/nodelist/microdesc_st.h" @@ -1584,21 +1585,22 @@ handle_getinfo_helper(control_connection_t *control_conn, return 0; /* unrecognized */ } +const control_cmd_syntax_t getinfo_syntax = { + .max_args = UINT_MAX, +}; + /** Called when we receive a GETINFO command. Try to fetch all requested * information, and reply with information or error message. */ int -handle_control_getinfo(control_connection_t *conn, uint32_t len, - const char *body) +handle_control_getinfo(control_connection_t *conn, + const control_cmd_args_t *args) { - smartlist_t *questions = smartlist_new(); + const smartlist_t *questions = args->args; smartlist_t *answers = smartlist_new(); smartlist_t *unrecognized = smartlist_new(); char *ans = NULL; int i; - (void) len; /* body is NUL-terminated, so it's safe to ignore the length. */ - smartlist_split_string(questions, body, " ", - SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(questions, const char *, q) { const char *errmsg = NULL; @@ -1653,8 +1655,6 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, done: SMARTLIST_FOREACH(answers, char *, cp, tor_free(cp)); smartlist_free(answers); - SMARTLIST_FOREACH(questions, char *, cp, tor_free(cp)); - smartlist_free(questions); SMARTLIST_FOREACH(unrecognized, char *, cp, tor_free(cp)); smartlist_free(unrecognized); diff --git a/src/feature/control/control_getinfo.h b/src/feature/control/control_getinfo.h index d5a2feb3e0..2d56586f6d 100644 --- a/src/feature/control/control_getinfo.h +++ b/src/feature/control/control_getinfo.h @@ -12,8 +12,12 @@ #ifndef TOR_CONTROL_GETINFO_H #define TOR_CONTROL_GETINFO_H -int handle_control_getinfo(control_connection_t *conn, uint32_t len, - const char *body); +struct control_cmd_syntax_t; +struct control_cmd_args_t; +extern const struct control_cmd_syntax_t getinfo_syntax; + +int handle_control_getinfo(control_connection_t *conn, + const struct control_cmd_args_t *args); #ifdef CONTROL_GETINFO_PRIVATE STATIC int getinfo_helper_onions( diff --git a/src/feature/control/fmt_serverstatus.c b/src/feature/control/fmt_serverstatus.c index a1ddd2119a..d224a1d234 100644 --- a/src/feature/control/fmt_serverstatus.c +++ b/src/feature/control/fmt_serverstatus.c @@ -66,11 +66,9 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, smartlist_t *rs_entries; time_t now = time(NULL); time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH; - const or_options_t *options = get_options(); /* We include v2 dir auths here too, because they need to answer * controllers. Eventually we'll deprecate this whole function; * see also networkstatus_getinfo_by_purpose(). */ - int authdir = authdir_mode_publishes_statuses(options); tor_assert(router_status_out); rs_entries = smartlist_new(); @@ -78,10 +76,7 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) { const node_t *node = node_get_by_id(ri->cache_info.identity_digest); tor_assert(node); - if (authdir) { - /* Update router status in routerinfo_t. */ - dirserv_set_router_is_running(ri, now); - } + if (for_controller) { char name_buf[MAX_VERBOSE_NICKNAME_LEN+2]; char *cp = name_buf; diff --git a/src/feature/dirauth/bwauth.c b/src/feature/dirauth/bwauth.c index 1cfd8119df..e60c8b86bd 100644 --- a/src/feature/dirauth/bwauth.c +++ b/src/feature/dirauth/bwauth.c @@ -199,9 +199,32 @@ dirserv_get_credible_bandwidth_kb(const routerinfo_t *ri) } /** - * Read the measured bandwidth list file, apply it to the list of - * vote_routerstatus_t and store all the headers in <b>bw_file_headers</b>. + * Read the measured bandwidth list <b>from_file</b>: + * - store all the headers in <b>bw_file_headers</b>, + * - apply bandwidth lines to the list of vote_routerstatus_t in + * <b>routerstatuses</b>, + * - cache bandwidth lines for dirserv_get_bandwidth_for_router(), + * - expire old entries in the measured bandwidth cache, and + * - store the DIGEST_SHA256 of the contents of the file in <b>digest_out</b>. + * * Returns -1 on error, 0 otherwise. + * + * If the file can't be read, or is empty: + * - <b>bw_file_headers</b> is empty, + * - <b>routerstatuses</b> is not modified, + * - the measured bandwidth cache is not modified, and + * - <b>digest_out</b> is the zero-byte digest. + * + * Otherwise, if there is an error later in the file: + * - <b>bw_file_headers</b> contains all the headers up to the error, + * - <b>routerstatuses</b> is updated with all the relay lines up to the error, + * - the measured bandwidth cache is updated with all the relay lines up to + * the error, + * - if the timestamp is valid and recent, old entries in the measured + * bandwidth cache are expired, and + * - <b>digest_out</b> is the digest up to the first read error (if any). + * The digest is taken over all the readable file contents, even if the + * file is outdated or unparseable. */ int dirserv_read_measured_bandwidths(const char *from_file, @@ -223,15 +246,12 @@ dirserv_read_measured_bandwidths(const char *from_file, size_t n = 0; crypto_digest_t *digest = crypto_digest256_new(DIGEST_SHA256); - /* Initialise line, so that we can't possibly run off the end. */ - if (fp == NULL) { log_warn(LD_CONFIG, "Can't open bandwidth file at configured location: %s", from_file); goto err; } - /* If fgets fails, line is either unmodified, or indeterminate. */ if (tor_getline(&line,&n,fp) <= 0) { log_warn(LD_DIRSERV, "Empty bandwidth file"); goto err; @@ -345,6 +365,9 @@ dirserv_read_measured_bandwidths(const char *from_file, * the header block yet. If we encounter an incomplete bw line, return -1 but * don't warn since there could be additional header lines coming. If we * encounter a proper bw line, return 0 (and we got past the headers). + * + * If the line contains "vote=0", stop parsing it, and return -1, so that the + * line is ignored during voting. */ STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line, diff --git a/src/feature/dirauth/dirauth_periodic.c b/src/feature/dirauth/dirauth_periodic.c new file mode 100644 index 0000000000..cfbb156b9f --- /dev/null +++ b/src/feature/dirauth/dirauth_periodic.c @@ -0,0 +1,142 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "core/or/or.h" + +#include "app/config/or_options_st.h" +#include "core/mainloop/netstatus.h" +#include "feature/dirauth/reachability.h" +#include "feature/stats/rephist.h" + +#include "feature/dirauth/dirvote.h" +#include "feature/dirauth/dirauth_periodic.h" +#include "feature/dirauth/authmode.h" + +#include "core/mainloop/periodic.h" + +#define DECLARE_EVENT(name, roles, flags) \ + static periodic_event_item_t name ## _event = \ + PERIODIC_EVENT(name, \ + PERIODIC_EVENT_ROLE_##roles, \ + flags) + +#define FL(name) (PERIODIC_EVENT_FLAG_##name) + +/** + * Periodic callback: if we're an authority, check on our authority + * certificate (the one that authenticates our authority signing key). + */ +static int +check_authority_cert_callback(time_t now, const or_options_t *options) +{ + (void)now; + (void)options; + /* 1e. Periodically, if we're a v3 authority, we check whether our cert is + * close to expiring and warn the admin if it is. */ + v3_authority_check_key_expiry(); +#define CHECK_V3_CERTIFICATE_INTERVAL (5*60) + return CHECK_V3_CERTIFICATE_INTERVAL; +} + +DECLARE_EVENT(check_authority_cert, DIRAUTH, 0); + +/** + * Scheduled callback: Run directory-authority voting functionality. + * + * The schedule is a bit complicated here, so dirvote_act() manages the + * schedule itself. + **/ +static int +dirvote_callback(time_t now, const or_options_t *options) +{ + if (!authdir_mode_v3(options)) { + tor_assert_nonfatal_unreached(); + return 3600; + } + + time_t next = dirvote_act(options, now); + if (BUG(next == TIME_MAX)) { + /* This shouldn't be returned unless we called dirvote_act() without + * being an authority. If it happens, maybe our configuration will + * fix itself in an hour or so? */ + return 3600; + } + return safe_timer_diff(now, next); +} + +DECLARE_EVENT(dirvote, DIRAUTH, FL(NEED_NET)); + +/** Reschedule the directory-authority voting event. Run this whenever the + * schedule has changed. */ +void +reschedule_dirvote(const or_options_t *options) +{ + if (authdir_mode_v3(options)) { + periodic_event_reschedule(&dirvote_event); + } +} + +/** + * Periodic callback: if we're an authority, record our measured stability + * information from rephist in an mtbf file. + */ +static int +save_stability_callback(time_t now, const or_options_t *options) +{ + if (authdir_mode_tests_reachability(options)) { + if (rep_hist_record_mtbf_data(now, 1)<0) { + log_warn(LD_GENERAL, "Couldn't store mtbf data."); + } + } +#define SAVE_STABILITY_INTERVAL (30*60) + return SAVE_STABILITY_INTERVAL; +} + +DECLARE_EVENT(save_stability, AUTHORITIES, 0); + +/** + * Periodic callback: if we're an authority, make sure we test + * the routers on the network for reachability. + */ +static int +launch_reachability_tests_callback(time_t now, const or_options_t *options) +{ + if (authdir_mode_tests_reachability(options) && + !net_is_disabled()) { + /* try to determine reachability of the other Tor relays */ + dirserv_test_reachability(now); + } + return REACHABILITY_TEST_INTERVAL; +} + +DECLARE_EVENT(launch_reachability_tests, AUTHORITIES, FL(NEED_NET)); + +/** + * Periodic callback: if we're an authority, discount the stability + * information (and other rephist information) that's older. + */ +static int +downrate_stability_callback(time_t now, const or_options_t *options) +{ + (void)options; + /* 1d. Periodically, we discount older stability information so that new + * stability info counts more, and save the stability information to disk as + * appropriate. */ + time_t next = rep_hist_downrate_old_runs(now); + return safe_timer_diff(now, next); +} + +DECLARE_EVENT(downrate_stability, AUTHORITIES, 0); + +void +dirauth_register_periodic_events(void) +{ + periodic_events_register(&downrate_stability_event); + periodic_events_register(&launch_reachability_tests_event); + periodic_events_register(&save_stability_event); + periodic_events_register(&check_authority_cert_event); + periodic_events_register(&dirvote_event); +} diff --git a/src/feature/dirauth/dirauth_periodic.h b/src/feature/dirauth/dirauth_periodic.h new file mode 100644 index 0000000000..de14cbb3c8 --- /dev/null +++ b/src/feature/dirauth/dirauth_periodic.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef DIRVOTE_PERIODIC_H +#define DIRVOTE_PERIODIC_H + +#ifdef HAVE_MODULE_DIRAUTH + +void dirauth_register_periodic_events(void); +void reschedule_dirvote(const or_options_t *options); + +#else + +static inline void +reschedule_dirvote(const or_options_t *options) +{ + (void)options; +} + +#endif + +#endif diff --git a/src/feature/dirauth/dirauth_sys.c b/src/feature/dirauth/dirauth_sys.c new file mode 100644 index 0000000000..bb482f2685 --- /dev/null +++ b/src/feature/dirauth/dirauth_sys.c @@ -0,0 +1,33 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "core/or/or.h" + +#include "feature/dirauth/dirauth_sys.h" +#include "feature/dirauth/dirvote.h" +#include "feature/dirauth/dirauth_periodic.h" +#include "lib/subsys/subsys.h" + +static int +subsys_dirauth_initialize(void) +{ + dirauth_register_periodic_events(); + return 0; +} + +static void +subsys_dirauth_shutdown(void) +{ + dirvote_free_all(); +} + +const struct subsys_fns_t sys_dirauth = { + .name = "dirauth", + .supported = true, + .level = 70, + .initialize = subsys_dirauth_initialize, + .shutdown = subsys_dirauth_shutdown, +}; diff --git a/src/feature/dirauth/dirauth_sys.h b/src/feature/dirauth/dirauth_sys.h new file mode 100644 index 0000000000..e10f4c9589 --- /dev/null +++ b/src/feature/dirauth/dirauth_sys.h @@ -0,0 +1,12 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef DIRAUTH_SYS_H +#define DIRAUTH_SYS_H + +extern const struct subsys_fns_t sys_dirauth; + +#endif diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c index 29f5d04509..1f861d2417 100644 --- a/src/feature/dirauth/dirvote.c +++ b/src/feature/dirauth/dirvote.c @@ -3914,8 +3914,7 @@ dirvote_format_microdesc_vote_line(char *out_buf, size_t out_buf_len, ","); tor_assert(microdesc_consensus_methods); - if (digest256_to_base64(d64, md->digest)<0) - goto out; + digest256_to_base64(d64, md->digest); if (tor_snprintf(out_buf, out_buf_len, "m %s sha256=%s\n", microdesc_consensus_methods, d64)<0) diff --git a/src/feature/dirauth/voteflags.c b/src/feature/dirauth/voteflags.c index 0a53c588d6..957ebe4a4f 100644 --- a/src/feature/dirauth/voteflags.c +++ b/src/feature/dirauth/voteflags.c @@ -29,6 +29,7 @@ #include "feature/nodelist/node_st.h" #include "feature/nodelist/routerinfo_st.h" +#include "feature/nodelist/routerlist_st.h" #include "feature/nodelist/vote_routerstatus_st.h" #include "lib/container/order.h" @@ -238,7 +239,7 @@ dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil) uint32_t *uptimes, *bandwidths_kb, *bandwidths_excluding_exits_kb; long *tks; double *mtbfs, *wfus; - smartlist_t *nodelist; + const smartlist_t *nodelist; time_t now = time(NULL); const or_options_t *options = get_options(); @@ -658,3 +659,20 @@ dirserv_set_routerstatus_testing(routerstatus_t *rs) rs->is_hs_dir = 0; } } + +/** Use dirserv_set_router_is_running() to set bridges as running if they're + * reachable. + * + * This function is called from set_bridge_running_callback() when running as + * a bridge authority. + */ +void +dirserv_set_bridges_running(time_t now) +{ + routerlist_t *rl = router_get_routerlist(); + + SMARTLIST_FOREACH_BEGIN(rl->routers, routerinfo_t *, ri) { + if (ri->purpose == ROUTER_PURPOSE_BRIDGE) + dirserv_set_router_is_running(ri, now); + } SMARTLIST_FOREACH_END(ri); +} diff --git a/src/feature/dirauth/voteflags.h b/src/feature/dirauth/voteflags.h index cca6f53746..18b29a5183 100644 --- a/src/feature/dirauth/voteflags.h +++ b/src/feature/dirauth/voteflags.h @@ -25,6 +25,8 @@ void set_routerstatus_from_routerinfo(routerstatus_t *rs, void dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil); +void dirserv_set_bridges_running(time_t now); + #ifdef VOTEFLAGS_PRIVATE /** Any descriptor older than this age causes the authorities to set the * StaleDesc flag. */ diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index 06bd298aca..1b36f716f4 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -124,7 +124,7 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, long cache_lifetime) { char date[RFC1123_TIME_LEN+1]; - time_t now = time(NULL); + time_t now = approx_time(); buf_t *buf = buf_new_with_capacity(1024); tor_assert(conn); diff --git a/src/feature/dirparse/microdesc_parse.c b/src/feature/dirparse/microdesc_parse.c index 3b11e65ca0..22cc1e272e 100644 --- a/src/feature/dirparse/microdesc_parse.c +++ b/src/feature/dirparse/microdesc_parse.c @@ -160,7 +160,22 @@ microdescs_parse_from_string(const char *s, const char *eos, if (tokenize_string(area, s, start_of_next_microdesc, tokens, microdesc_token_table, flags)) { - log_warn(LD_DIR, "Unparseable microdescriptor"); + const char *location; + switch (where) { + case SAVED_NOWHERE: + location = "download or generated string"; + break; + case SAVED_IN_CACHE: + location = "cache"; + break; + case SAVED_IN_JOURNAL: + location = "journal"; + break; + default: + location = "unknown location"; + break; + } + log_warn(LD_DIR, "Unparseable microdescriptor found in %s", location); goto next; } diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index c34271efca..7aec6d80bb 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -165,9 +165,7 @@ purge_hid_serv_request(const ed25519_public_key_t *identity_pk) * some point and we don't care about those anymore. */ hs_build_blinded_pubkey(identity_pk, NULL, 0, hs_get_time_period_num(0), &blinded_pk); - if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &blinded_pk); /* Purge last hidden service request from cache for this blinded key. */ hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk); } @@ -354,7 +352,6 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, ed25519_public_key_t blinded_pubkey; char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; hs_ident_dir_conn_t hs_conn_dir_ident; - int retval; tor_assert(hsdir); tor_assert(onion_identity_pk); @@ -363,10 +360,7 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, current_time_period, &blinded_pubkey); /* ...and base64 it. */ - retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); - if (BUG(retval < 0)) { - return HS_CLIENT_FETCH_ERROR; - } + ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); /* Copy onion pk to a dir_ident so that we attach it to the dir conn */ hs_ident_dir_conn_init(onion_identity_pk, &blinded_pubkey, @@ -405,7 +399,6 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, STATIC routerstatus_t * pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) { - int retval; char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; uint64_t current_time_period = hs_get_time_period_num(0); smartlist_t *responsible_hsdirs = NULL; @@ -418,10 +411,7 @@ pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) hs_build_blinded_pubkey(onion_identity_pk, NULL, 0, current_time_period, &blinded_pubkey); /* ...and base64 it. */ - retval = ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); - if (BUG(retval < 0)) { - return NULL; - } + ed25519_public_to_base64(base64_blinded_pubkey, &blinded_pubkey); /* Get responsible hsdirs of service for this time period */ responsible_hsdirs = smartlist_new(); @@ -434,7 +424,7 @@ pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) /* Pick an HSDir from the responsible ones. The ownership of * responsible_hsdirs is given to this function so no need to free it. */ - hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey); + hsdir_rs = hs_pick_hsdir(responsible_hsdirs, base64_blinded_pubkey, NULL); return hsdir_rs; } diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c index b2227432d2..d4736c2862 100644 --- a/src/feature/hs/hs_common.c +++ b/src/feature/hs/hs_common.c @@ -1589,20 +1589,25 @@ hs_purge_last_hid_serv_requests(void) /** Given the list of responsible HSDirs in <b>responsible_dirs</b>, pick the * one that we should use to fetch a descriptor right now. Take into account * previous failed attempts at fetching this descriptor from HSDirs using the - * string identifier <b>req_key_str</b>. + * string identifier <b>req_key_str</b>. We return whether we are rate limited + * into *<b>is_rate_limited_out</b> if it is not NULL. * * Steals ownership of <b>responsible_dirs</b>. * * Return the routerstatus of the chosen HSDir if successful, otherwise return * NULL if no HSDirs are worth trying right now. */ routerstatus_t * -hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) +hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str, + bool *is_rate_limited_out) { smartlist_t *usable_responsible_dirs = smartlist_new(); const or_options_t *options = get_options(); routerstatus_t *hs_dir; time_t now = time(NULL); int excluded_some; + bool rate_limited = false; + int rate_limited_count = 0; + int responsible_dirs_count = smartlist_len(responsible_dirs); tor_assert(req_key_str); @@ -1622,6 +1627,7 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) if (last + hs_hsdir_requery_period(options) >= now || !node || !node_has_preferred_descriptor(node, 0)) { SMARTLIST_DEL_CURRENT(responsible_dirs, dir); + rate_limited_count++; continue; } if (!routerset_contains_node(options->ExcludeNodes, node)) { @@ -1629,6 +1635,10 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) } } SMARTLIST_FOREACH_END(dir); + if (rate_limited_count > 0 || responsible_dirs_count > 0) { + rate_limited = rate_limited_count == responsible_dirs_count; + } + excluded_some = smartlist_len(usable_responsible_dirs) < smartlist_len(responsible_dirs); @@ -1640,9 +1650,10 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) smartlist_free(responsible_dirs); smartlist_free(usable_responsible_dirs); if (!hs_dir) { + const char *warn_str = (rate_limited) ? "we are rate limited." : + "we requested them all recently without success"; log_info(LD_REND, "Could not pick one of the responsible hidden " - "service directories, because we requested them all " - "recently without success."); + "service directories, because %s.", warn_str); if (options->StrictNodes && excluded_some) { log_warn(LD_REND, "Could not pick a hidden service directory for the " "requested hidden service: they are all either down or " @@ -1654,6 +1665,10 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str) hs_lookup_last_hid_serv_request(hs_dir, req_key_str, now, 1); } + if (is_rate_limited_out != NULL) { + *is_rate_limited_out = rate_limited; + } + return hs_dir; } diff --git a/src/feature/hs/hs_common.h b/src/feature/hs/hs_common.h index abf39fa431..3009780d90 100644 --- a/src/feature/hs/hs_common.h +++ b/src/feature/hs/hs_common.h @@ -241,7 +241,8 @@ void hs_get_responsible_hsdirs(const struct ed25519_public_key_t *blinded_pk, int use_second_hsdir_index, int for_fetching, smartlist_t *responsible_dirs); routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs, - const char *req_key_str); + const char *req_key_str, + bool *is_rate_limited_out); time_t hs_hsdir_requery_period(const or_options_t *options); time_t hs_lookup_last_hid_serv_request(routerstatus_t *hs_dir, diff --git a/src/feature/hs/hs_control.c b/src/feature/hs/hs_control.c index 20a1061609..abb421345c 100644 --- a/src/feature/hs/hs_control.c +++ b/src/feature/hs/hs_control.c @@ -74,10 +74,7 @@ hs_control_desc_event_failed(const hs_ident_dir_conn_t *ident, tor_assert(reason); /* Build onion address and encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, - &ident->blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &ident->blinded_pk); hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); control_event_hsv3_descriptor_failed(onion_address, base64_blinded_pk, @@ -99,10 +96,7 @@ hs_control_desc_event_received(const hs_ident_dir_conn_t *ident, tor_assert(hsdir_id_digest); /* Build onion address and encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, - &ident->blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &ident->blinded_pk); hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); control_event_hsv3_descriptor_received(onion_address, base64_blinded_pk, @@ -123,9 +117,7 @@ hs_control_desc_event_created(const char *onion_address, tor_assert(blinded_pk); /* Build base64 encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, blinded_pk); /* Version 3 doesn't use the replica number in its descriptor ID computation * so we pass negative value so the control port subsystem can ignore it. */ @@ -151,9 +143,7 @@ hs_control_desc_event_upload(const char *onion_address, tor_assert(hsdir_index); /* Build base64 encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, blinded_pk); control_event_hs_descriptor_upload(onion_address, hsdir_id_digest, base64_blinded_pk, @@ -196,10 +186,7 @@ hs_control_desc_event_content(const hs_ident_dir_conn_t *ident, tor_assert(hsdir_id_digest); /* Build onion address and encoded blinded key. */ - IF_BUG_ONCE(ed25519_public_to_base64(base64_blinded_pk, - &ident->blinded_pk) < 0) { - return; - } + ed25519_public_to_base64(base64_blinded_pk, &ident->blinded_pk); hs_build_address(&ident->identity_pk, HS_VERSION_THREE, onion_address); control_event_hs_descriptor_content(onion_address, base64_blinded_pk, diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c index 8f7bdf86ef..b526da6661 100644 --- a/src/feature/hs/hs_descriptor.c +++ b/src/feature/hs/hs_descriptor.c @@ -403,9 +403,7 @@ encode_enc_key(const hs_desc_intro_point_t *ip) tor_assert(ip); /* Base64 encode the encryption key for the "enc-key" field. */ - if (curve25519_public_to_base64(key_b64, &ip->enc_key) < 0) { - goto done; - } + curve25519_public_to_base64(key_b64, &ip->enc_key); if (tor_cert_encode_ed22519(ip->enc_key_cert, &encoded_cert) < 0) { goto done; } @@ -421,7 +419,7 @@ encode_enc_key(const hs_desc_intro_point_t *ip) } /* Encode an introduction point onion key. Return a newly allocated string - * with it. On failure, return NULL. */ + * with it. Can not fail. */ static char * encode_onion_key(const hs_desc_intro_point_t *ip) { @@ -431,12 +429,9 @@ encode_onion_key(const hs_desc_intro_point_t *ip) tor_assert(ip); /* Base64 encode the encryption key for the "onion-key" field. */ - if (curve25519_public_to_base64(key_b64, &ip->onion_key) < 0) { - goto done; - } + curve25519_public_to_base64(key_b64, &ip->onion_key); tor_asprintf(&encoded, "%s ntor %s", str_ip_onion_key, key_b64); - done: return encoded; } @@ -797,8 +792,8 @@ get_inner_encrypted_layer_plaintext(const hs_descriptor_t *desc) /* Create the middle layer of the descriptor, which includes the client auth * data and the encrypted inner layer (provided as a base64 string at * <b>layer2_b64_ciphertext</b>). Return a newly-allocated string with the - * layer plaintext, or NULL if an error occurred. It's the responsibility of - * the caller to free the returned string. */ + * layer plaintext. It's the responsibility of the caller to free the returned + * string. Can not fail. */ static char * get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, const char *layer2_b64_ciphertext) @@ -817,10 +812,7 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, tor_assert(!tor_mem_is_zero((char *) ephemeral_pubkey->public_key, CURVE25519_PUBKEY_LEN)); - if (curve25519_public_to_base64(ephemeral_key_base64, - ephemeral_pubkey) < 0) { - goto done; - } + curve25519_public_to_base64(ephemeral_key_base64, ephemeral_pubkey); smartlist_add_asprintf(lines, "%s %s\n", str_desc_auth_key, ephemeral_key_base64); @@ -845,7 +837,6 @@ get_outer_encrypted_layer_plaintext(const hs_descriptor_t *desc, layer1_str = smartlist_join_strings(lines, "", 0, NULL); - done: /* We need to memwipe all lines because it contains the ephemeral key */ SMARTLIST_FOREACH(lines, char *, a, memwipe(a, 0, strlen(a))); SMARTLIST_FOREACH(lines, char *, a, tor_free(a)); @@ -1091,11 +1082,7 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_free(encoded_str); goto err; } - if (ed25519_signature_to_base64(ed_sig_b64, &sig) < 0) { - log_warn(LD_BUG, "Can't base64 encode descriptor signature!"); - tor_free(encoded_str); - goto err; - } + ed25519_signature_to_base64(ed_sig_b64, &sig); /* Create the signature line. */ smartlist_add_asprintf(lines, "%s %s", str_signature, ed_sig_b64); } diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c index b4f05b63a0..36922561a0 100644 --- a/src/feature/nodelist/microdesc.c +++ b/src/feature/nodelist/microdesc.c @@ -70,6 +70,8 @@ struct microdesc_cache_t { }; static microdesc_cache_t *get_microdesc_cache_noload(void); +static void warn_if_nul_found(const char *inp, size_t len, int64_t offset, + const char *activity); /** Helper: computes a hash of <b>md</b> to place it in a hash table. */ static inline unsigned int @@ -223,6 +225,8 @@ dump_microdescriptor(int fd, microdesc_t *md, size_t *annotation_len_out) } md->off = tor_fd_getpos(fd); + warn_if_nul_found(md->body, md->bodylen, (int64_t) md->off, + "dumping a microdescriptor"); written = write_all_to_fd(fd, md->body, md->bodylen); if (written != (ssize_t)md->bodylen) { written = written < 0 ? 0 : written; @@ -482,6 +486,27 @@ microdesc_cache_clear(microdesc_cache_t *cache) cache->bytes_dropped = 0; } +static void +warn_if_nul_found(const char *inp, size_t len, int64_t offset, + const char *activity) +{ + const char *nul_found = memchr(inp, 0, len); + if (BUG(nul_found)) { + log_warn(LD_BUG, "Found unexpected NUL while %s, offset %"PRId64 + "at position %"TOR_PRIuSZ"/%"TOR_PRIuSZ".", + activity, offset, (nul_found - inp), len); + const char *start_excerpt_at, *eos = inp + len; + if ((nul_found - inp) >= 16) + start_excerpt_at = nul_found - 16; + else + start_excerpt_at = inp; + size_t excerpt_len = MIN(32, eos - start_excerpt_at); + char tmp[65]; + base16_encode(tmp, sizeof(tmp), start_excerpt_at, excerpt_len); + log_warn(LD_BUG, " surrounding string: %s", tmp); + } +} + /** Reload the contents of <b>cache</b> from disk. If it is empty, load it * for the first time. Return 0 on success, -1 on failure. */ int @@ -499,6 +524,7 @@ microdesc_cache_reload(microdesc_cache_t *cache) mm = cache->cache_content = tor_mmap_file(cache->cache_fname); if (mm) { + warn_if_nul_found(mm->data, mm->size, 0, "scanning microdesc cache"); added = microdescs_add_to_cache(cache, mm->data, mm->data+mm->size, SAVED_IN_CACHE, 0, -1, NULL); if (added) { @@ -511,6 +537,8 @@ microdesc_cache_reload(microdesc_cache_t *cache) RFTS_IGNORE_MISSING, &st); if (journal_content) { cache->journal_len = (size_t) st.st_size; + warn_if_nul_found(journal_content, cache->journal_len, 0, + "reading microdesc journal"); added = microdescs_add_to_cache(cache, journal_content, journal_content+st.st_size, SAVED_IN_JOURNAL, 0, -1, NULL); diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c index ee22e16323..fdadfc3039 100644 --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@ -82,6 +82,7 @@ #include "lib/crypt_ops/crypto_rand.h" #include "lib/crypt_ops/crypto_util.h" +#include "feature/dirauth/dirauth_periodic.h" #include "feature/dirauth/dirvote.h" #include "feature/dirauth/authmode.h" #include "feature/dirauth/shared_random.h" @@ -2381,7 +2382,6 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) smartlist_t *statuses; const uint8_t purpose = router_purpose_from_string(purpose_string); routerstatus_t rs; - const int bridge_auth = authdir_mode_bridge(get_options()); if (purpose == ROUTER_PURPOSE_UNKNOWN) { log_info(LD_DIR, "Unrecognized purpose '%s' when listing router statuses.", @@ -2398,9 +2398,6 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) continue; if (ri->purpose != purpose) continue; - /* TODO: modifying the running flag in a getinfo is a bad idea */ - if (bridge_auth && ri->purpose == ROUTER_PURPOSE_BRIDGE) - dirserv_set_router_is_running(ri, now); /* then generate and write out status lines for each of them */ set_routerstatus_from_routerinfo(&rs, node, ri, now, 0); smartlist_add(statuses, networkstatus_getinfo_helper_single(&rs)); @@ -2412,11 +2409,12 @@ networkstatus_getinfo_by_purpose(const char *purpose_string, time_t now) return answer; } -/** Write out router status entries for all our bridge descriptors. */ +/** Write out router status entries for all our bridge descriptors. Here, we + * also mark routers as running. */ void networkstatus_dump_bridge_status_to_file(time_t now) { - char *status = networkstatus_getinfo_by_purpose("bridge", now); + char *status; char *fname = NULL; char *thresholds = NULL; char *published_thresholds_and_status = NULL; @@ -2425,6 +2423,9 @@ networkstatus_dump_bridge_status_to_file(time_t now) char fingerprint[FINGERPRINT_LEN+1]; char *fingerprint_line = NULL; + dirserv_set_bridges_running(now); + status = networkstatus_getinfo_by_purpose("bridge", now); + if (me && crypto_pk_get_fingerprint(me->identity_pkey, fingerprint, 0) >= 0) { tor_asprintf(&fingerprint_line, "fingerprint %s\n", fingerprint); diff --git a/src/feature/nodelist/node_select.c b/src/feature/nodelist/node_select.c index e31abb247f..719b4b1b27 100644 --- a/src/feature/nodelist/node_select.c +++ b/src/feature/nodelist/node_select.c @@ -30,6 +30,7 @@ #include "feature/nodelist/routerset.h" #include "feature/relay/router.h" #include "feature/relay/routermode.h" +#include "lib/container/bitarray.h" #include "lib/crypt_ops/crypto_rand.h" #include "lib/math/fp.h" @@ -585,6 +586,7 @@ compute_weighted_bandwidths(const smartlist_t *sl, } weight_scale = networkstatus_get_weight_scale_param(NULL); + tor_assert(weight_scale >= 1); if (rule == WEIGHT_FOR_GUARD) { Wg = networkstatus_get_bw_weight(NULL, "Wgg", -1); @@ -825,6 +827,58 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) nodelist_add_node_and_family(sl, node); } +/** + * Remove every node_t that appears in <b>excluded</b> from <b>sl</b>. + * + * Behaves like smartlist_subtract, but uses nodelist_idx values to deliver + * linear performance when smartlist_subtract would be quadratic. + **/ +static void +nodelist_subtract(smartlist_t *sl, const smartlist_t *excluded) +{ + const smartlist_t *nodelist = nodelist_get_list(); + const int nodelist_len = smartlist_len(nodelist); + bitarray_t *excluded_idx = bitarray_init_zero(nodelist_len); + + /* We haven't used nodelist_idx in this way previously, so I'm going to be + * paranoid in this code, and check that nodelist_idx is correct for every + * node before we use it. If we fail, we fall back to smartlist_subtract(). + */ + + /* Set the excluded_idx bit corresponding to every excluded node... + */ + SMARTLIST_FOREACH_BEGIN(excluded, const node_t *, node) { + const int idx = node->nodelist_idx; + if (BUG(idx < 0) || BUG(idx >= nodelist_len) || + BUG(node != smartlist_get(nodelist, idx))) { + goto internal_error; + } + bitarray_set(excluded_idx, idx); + } SMARTLIST_FOREACH_END(node); + + /* Then remove them from sl. + */ + SMARTLIST_FOREACH_BEGIN(sl, const node_t *, node) { + const int idx = node->nodelist_idx; + if (BUG(idx < 0) || BUG(idx >= nodelist_len) || + BUG(node != smartlist_get(nodelist, idx))) { + goto internal_error; + } + if (bitarray_is_set(excluded_idx, idx)) { + SMARTLIST_DEL_CURRENT(sl, node); + } + } SMARTLIST_FOREACH_END(node); + + bitarray_free(excluded_idx); + return; + + internal_error: + log_warn(LD_BUG, "Internal error prevented us from using the fast method " + "for subtracting nodelists. Falling back to the quadratic way."); + smartlist_subtract(sl, excluded); + bitarray_free(excluded_idx); +} + /** Return a random running node from the nodelist. Never * pick a node that is in * <b>excludedsmartlist</b>, or which matches <b>excludedset</b>, @@ -859,6 +913,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, const int direct_conn = (flags & CRN_DIRECT_CONN) != 0; const int rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0; + const smartlist_t *node_list = nodelist_get_list(); smartlist_t *sl=smartlist_new(), *excludednodes=smartlist_new(); const node_t *choice = NULL; @@ -869,17 +924,17 @@ router_choose_random_node(smartlist_t *excludedsmartlist, rule = weight_for_exit ? WEIGHT_FOR_EXIT : (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID); - SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { + SMARTLIST_FOREACH_BEGIN(node_list, const node_t *, node) { if (node_allows_single_hop_exits(node)) { /* Exclude relays that allow single hop exit circuits. This is an * obsolete option since 0.2.9.2-alpha and done by default in * 0.3.1.0-alpha. */ - smartlist_add(excludednodes, node); + smartlist_add(excludednodes, (node_t*)node); } else if (rendezvous_v3 && !node_supports_v3_rendezvous_point(node)) { /* Exclude relays that do not support to rendezvous for a hidden service * version 3. */ - smartlist_add(excludednodes, node); + smartlist_add(excludednodes, (node_t*)node); } } SMARTLIST_FOREACH_END(node); @@ -896,19 +951,11 @@ router_choose_random_node(smartlist_t *excludedsmartlist, "We found %d running nodes.", smartlist_len(sl)); - smartlist_subtract(sl,excludednodes); - log_debug(LD_CIRC, - "We removed %d excludednodes, leaving %d nodes.", - smartlist_len(excludednodes), - smartlist_len(sl)); - if (excludedsmartlist) { - smartlist_subtract(sl,excludedsmartlist); - log_debug(LD_CIRC, - "We removed %d excludedsmartlist, leaving %d nodes.", - smartlist_len(excludedsmartlist), - smartlist_len(sl)); + smartlist_add_all(excludednodes, excludedsmartlist); } + nodelist_subtract(sl, excludednodes); + if (excludedset) { routerset_subtract_nodes(sl,excludedset); log_debug(LD_CIRC, diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index f878d47fd7..8aa4915107 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -944,7 +944,7 @@ nodelist_ensure_freshness(networkstatus_t *ns) /** Return a list of a node_t * for every node we know about. The caller * MUST NOT modify the list. (You can set and clear flags in the nodes if * you must, but you must not add or remove nodes.) */ -MOCK_IMPL(smartlist_t *, +MOCK_IMPL(const smartlist_t *, nodelist_get_list,(void)) { init_nodelist(); @@ -1939,7 +1939,7 @@ node_set_country(node_t *node) void nodelist_refresh_countries(void) { - smartlist_t *nodes = nodelist_get_list(); + const smartlist_t *nodes = nodelist_get_list(); SMARTLIST_FOREACH(nodes, node_t *, node, node_set_country(node)); } diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index a3d65347a8..84ab5f7a54 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -101,7 +101,7 @@ const struct curve25519_public_key_t *node_get_curve25519_onion_key( const node_t *node); crypto_pk_t *node_get_rsa_onion_key(const node_t *node); -MOCK_DECL(smartlist_t *, nodelist_get_list, (void)); +MOCK_DECL(const smartlist_t *, nodelist_get_list, (void)); /* Temporary during transition to multiple addresses. */ void node_get_addr(const node_t *node, tor_addr_t *addr_out); diff --git a/src/feature/nodelist/routerset.c b/src/feature/nodelist/routerset.c index 55e2756959..e801fd81b1 100644 --- a/src/feature/nodelist/routerset.c +++ b/src/feature/nodelist/routerset.c @@ -378,7 +378,7 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, } else { /* We need to iterate over the routerlist to get all the ones of the * right kind. */ - smartlist_t *nodes = nodelist_get_list(); + const smartlist_t *nodes = nodelist_get_list(); SMARTLIST_FOREACH(nodes, const node_t *, node, { if (running_only && !node->is_running) continue; diff --git a/src/feature/relay/onion_queue.c b/src/feature/relay/onion_queue.c index 696905cf5e..c37745cf33 100644 --- a/src/feature/relay/onion_queue.c +++ b/src/feature/relay/onion_queue.c @@ -212,10 +212,12 @@ num_ntors_per_tap(void) #define MIN_NUM_NTORS_PER_TAP 1 #define MAX_NUM_NTORS_PER_TAP 100000 - return networkstatus_get_param(NULL, "NumNTorsPerTAP", - DEFAULT_NUM_NTORS_PER_TAP, - MIN_NUM_NTORS_PER_TAP, - MAX_NUM_NTORS_PER_TAP); + int result = networkstatus_get_param(NULL, "NumNTorsPerTAP", + DEFAULT_NUM_NTORS_PER_TAP, + MIN_NUM_NTORS_PER_TAP, + MAX_NUM_NTORS_PER_TAP); + tor_assert(result > 0); + return result; } /** Choose which onion queue we'll pull from next. If one is empty choose diff --git a/src/feature/relay/router.c b/src/feature/relay/router.c index e5cf72ad18..ac4b3b7a02 100644 --- a/src/feature/relay/router.c +++ b/src/feature/relay/router.c @@ -2728,11 +2728,8 @@ router_dump_router_to_string(routerinfo_t *router, log_err(LD_BUG,"Couldn't base64-encode signing key certificate!"); goto err; } - if (ed25519_public_to_base64(ed_fp_base64, - &router->cache_info.signing_key_cert->signing_key)<0) { - log_err(LD_BUG,"Couldn't base64-encode identity key\n"); - goto err; - } + ed25519_public_to_base64(ed_fp_base64, + &router->cache_info.signing_key_cert->signing_key); tor_asprintf(&ed_cert_line, "identity-ed25519\n" "-----BEGIN ED25519 CERT-----\n" "%s" @@ -2977,8 +2974,7 @@ router_dump_router_to_string(routerinfo_t *router, if (ed25519_sign(&sig, (const uint8_t*)digest, DIGEST256_LEN, signing_keypair) < 0) goto err; - if (ed25519_signature_to_base64(buf, &sig) < 0) - goto err; + ed25519_signature_to_base64(buf, &sig); smartlist_add_asprintf(chunks, "%s\n", buf); } @@ -3252,8 +3248,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, if (ed25519_sign(&ed_sig, (const uint8_t*)sha256_digest, DIGEST256_LEN, signing_keypair) < 0) goto err; - if (ed25519_signature_to_base64(buf, &ed_sig) < 0) - goto err; + ed25519_signature_to_base64(buf, &ed_sig); smartlist_add_asprintf(chunks, "%s\n", buf); } diff --git a/src/feature/rend/rendclient.c b/src/feature/rend/rendclient.c index 5a8b234544..f84d221b1a 100644 --- a/src/feature/rend/rendclient.c +++ b/src/feature/rend/rendclient.c @@ -469,16 +469,19 @@ directory_get_from_hs_dir(const char *desc_id, /* Automatically pick an hs dir if none given. */ if (!rs_hsdir) { + bool rate_limited = false; + /* Determine responsible dirs. Even if we can't get all we want, work with * the ones we have. If it's empty, we'll notice in hs_pick_hsdir(). */ smartlist_t *responsible_dirs = smartlist_new(); hid_serv_get_responsible_directories(responsible_dirs, desc_id); - hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32); + hs_dir = hs_pick_hsdir(responsible_dirs, desc_id_base32, &rate_limited); if (!hs_dir) { /* No suitable hs dir can be found, stop right now. */ - control_event_hsv2_descriptor_failed(rend_query, NULL, - "QUERY_NO_HSDIR"); + const char *query_response = (rate_limited) ? "QUERY_RATE_LIMITED" : + "QUERY_NO_HSDIR"; + control_event_hsv2_descriptor_failed(rend_query, NULL, query_response); control_event_hs_descriptor_content(rend_data_get_address(rend_query), desc_id_base32, NULL, NULL); return 0; diff --git a/src/lib/crypt_ops/crypto_curve25519.h b/src/lib/crypt_ops/crypto_curve25519.h index 061a7a3505..cd23169cd5 100644 --- a/src/lib/crypt_ops/crypto_curve25519.h +++ b/src/lib/crypt_ops/crypto_curve25519.h @@ -76,8 +76,8 @@ STATIC int curve25519_basepoint_impl(uint8_t *output, const uint8_t *secret); int curve25519_public_from_base64(curve25519_public_key_t *pkey, const char *input); -int curve25519_public_to_base64(char *output, - const curve25519_public_key_t *pkey); +void curve25519_public_to_base64(char *output, + const curve25519_public_key_t *pkey); void curve25519_set_impl_params(int use_ed); void curve25519_init(void); diff --git a/src/lib/crypt_ops/crypto_format.c b/src/lib/crypt_ops/crypto_format.c index 84f73e5272..e11b391194 100644 --- a/src/lib/crypt_ops/crypto_format.c +++ b/src/lib/crypt_ops/crypto_format.c @@ -131,20 +131,27 @@ crypto_read_tagged_contents_from_file(const char *fname, return r; } -/** Encode <b>pkey</b> as a base64-encoded string, without trailing "=" +/** Encode <b>pkey</b> as a base64-encoded string, including trailing "=" * characters, in the buffer <b>output</b>, which must have at least - * CURVE25519_BASE64_PADDED_LEN+1 bytes available. Return 0 on success, -1 on - * failure. */ -int + * CURVE25519_BASE64_PADDED_LEN+1 bytes available. + * Can not fail. + * + * Careful! CURVE25519_BASE64_PADDED_LEN is one byte longer than + * ED25519_BASE64_LEN. + */ +void curve25519_public_to_base64(char *output, const curve25519_public_key_t *pkey) { char buf[128]; - base64_encode(buf, sizeof(buf), - (const char*)pkey->public_key, CURVE25519_PUBKEY_LEN, 0); - buf[CURVE25519_BASE64_PADDED_LEN] = '\0'; + int n = base64_encode(buf, sizeof(buf), + (const char*)pkey->public_key, + CURVE25519_PUBKEY_LEN, 0); + /* These asserts should always succeed, unless there is a bug in + * base64_encode(). */ + tor_assert(n == CURVE25519_BASE64_PADDED_LEN); + tor_assert(buf[CURVE25519_BASE64_PADDED_LEN] == '\0'); memcpy(output, buf, CURVE25519_BASE64_PADDED_LEN+1); - return 0; } /** Try to decode a base64-encoded curve25519 public key from <b>input</b> @@ -181,8 +188,7 @@ ed25519_fmt(const ed25519_public_key_t *pkey) if (ed25519_public_key_is_zero(pkey)) { strlcpy(formatted, "<unset>", sizeof(formatted)); } else { - int r = ed25519_public_to_base64(formatted, pkey); - tor_assert(!r); + ed25519_public_to_base64(formatted, pkey); } } else { strlcpy(formatted, "<null>", sizeof(formatted)); @@ -202,28 +208,35 @@ ed25519_public_from_base64(ed25519_public_key_t *pkey, /** Encode the public key <b>pkey</b> into the buffer at <b>output</b>, * which must have space for ED25519_BASE64_LEN bytes of encoded key, - * plus one byte for a terminating NUL. Return 0 on success, -1 on failure. + * plus one byte for a terminating NUL. + * Can not fail. + * + * Careful! ED25519_BASE64_LEN is one byte shorter than + * CURVE25519_BASE64_PADDED_LEN. */ -int +void ed25519_public_to_base64(char *output, const ed25519_public_key_t *pkey) { - return digest256_to_base64(output, (const char *)pkey->pubkey); + digest256_to_base64(output, (const char *)pkey->pubkey); } /** Encode the signature <b>sig</b> into the buffer at <b>output</b>, * which must have space for ED25519_SIG_BASE64_LEN bytes of encoded signature, - * plus one byte for a terminating NUL. Return 0 on success, -1 on failure. + * plus one byte for a terminating NUL. + * Can not fail. */ -int +void ed25519_signature_to_base64(char *output, const ed25519_signature_t *sig) { char buf[256]; int n = base64_encode_nopad(buf, sizeof(buf), sig->sig, ED25519_SIG_LEN); + /* These asserts should always succeed, unless there is a bug in + * base64_encode_nopad(). */ tor_assert(n == ED25519_SIG_BASE64_LEN); + tor_assert(buf[ED25519_SIG_BASE64_LEN] == '\0'); memcpy(output, buf, ED25519_SIG_BASE64_LEN+1); - return 0; } /** Try to decode the string <b>input</b> into an ed25519 signature. On @@ -233,16 +246,11 @@ int ed25519_signature_from_base64(ed25519_signature_t *sig, const char *input) { - if (strlen(input) != ED25519_SIG_BASE64_LEN) return -1; - char buf[ED25519_SIG_BASE64_LEN+3]; - memcpy(buf, input, ED25519_SIG_BASE64_LEN); - buf[ED25519_SIG_BASE64_LEN+0] = '='; - buf[ED25519_SIG_BASE64_LEN+1] = '='; - buf[ED25519_SIG_BASE64_LEN+2] = 0; char decoded[128]; - int n = base64_decode(decoded, sizeof(decoded), buf, strlen(buf)); + int n = base64_decode(decoded, sizeof(decoded), input, + ED25519_SIG_BASE64_LEN); if (n < 0 || n != ED25519_SIG_LEN) return -1; memcpy(sig->sig, decoded, ED25519_SIG_LEN); @@ -250,24 +258,26 @@ ed25519_signature_from_base64(ed25519_signature_t *sig, return 0; } -/** Base64 encode DIGEST_LINE bytes from <b>digest</b>, remove the trailing = +/** Base64 encode DIGEST_LEN bytes from <b>digest</b>, remove the trailing = * characters, and store the nul-terminated result in the first - * BASE64_DIGEST_LEN+1 bytes of <b>d64</b>. */ -/* XXXX unify with crypto_format.c code */ -int + * BASE64_DIGEST_LEN+1 bytes of <b>d64</b>. + * Can not fail. */ +void digest_to_base64(char *d64, const char *digest) { char buf[256]; - base64_encode(buf, sizeof(buf), digest, DIGEST_LEN, 0); - buf[BASE64_DIGEST_LEN] = '\0'; + int n = base64_encode_nopad(buf, sizeof(buf), + (const uint8_t *)digest, DIGEST_LEN); + /* These asserts should always succeed, unless there is a bug in + * base64_encode_nopad(). */ + tor_assert(n == BASE64_DIGEST_LEN); + tor_assert(buf[BASE64_DIGEST_LEN] == '\0'); memcpy(d64, buf, BASE64_DIGEST_LEN+1); - return 0; } /** Given a base64 encoded, nul-terminated digest in <b>d64</b> (without * trailing newline or = characters), decode it and store the result in the * first DIGEST_LEN bytes at <b>digest</b>. */ -/* XXXX unify with crypto_format.c code */ int digest_from_base64(char *digest, const char *d64) { @@ -279,22 +289,24 @@ digest_from_base64(char *digest, const char *d64) /** Base64 encode DIGEST256_LINE bytes from <b>digest</b>, remove the * trailing = characters, and store the nul-terminated result in the first - * BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>. */ - /* XXXX unify with crypto_format.c code */ -int + * BASE64_DIGEST256_LEN+1 bytes of <b>d64</b>. + * Can not fail. */ +void digest256_to_base64(char *d64, const char *digest) { char buf[256]; - base64_encode(buf, sizeof(buf), digest, DIGEST256_LEN, 0); - buf[BASE64_DIGEST256_LEN] = '\0'; + int n = base64_encode_nopad(buf, sizeof(buf), + (const uint8_t *)digest, DIGEST256_LEN); + /* These asserts should always succeed, unless there is a bug in + * base64_encode_nopad(). */ + tor_assert(n == BASE64_DIGEST256_LEN); + tor_assert(buf[BASE64_DIGEST256_LEN] == '\0'); memcpy(d64, buf, BASE64_DIGEST256_LEN+1); - return 0; } /** Given a base64 encoded, nul-terminated digest in <b>d64</b> (without * trailing newline or = characters), decode it and store the result in the * first DIGEST256_LEN bytes at <b>digest</b>. */ -/* XXXX unify with crypto_format.c code */ int digest256_from_base64(char *digest, const char *d64) { diff --git a/src/lib/crypt_ops/crypto_format.h b/src/lib/crypt_ops/crypto_format.h index fe852e6a61..b4b3aa189c 100644 --- a/src/lib/crypt_ops/crypto_format.h +++ b/src/lib/crypt_ops/crypto_format.h @@ -33,18 +33,18 @@ ssize_t crypto_read_tagged_contents_from_file(const char *fname, int ed25519_public_from_base64(struct ed25519_public_key_t *pkey, const char *input); -int ed25519_public_to_base64(char *output, - const struct ed25519_public_key_t *pkey); +void ed25519_public_to_base64(char *output, + const struct ed25519_public_key_t *pkey); const char *ed25519_fmt(const struct ed25519_public_key_t *pkey); int ed25519_signature_from_base64(struct ed25519_signature_t *sig, const char *input); -int ed25519_signature_to_base64(char *output, - const struct ed25519_signature_t *sig); +void ed25519_signature_to_base64(char *output, + const struct ed25519_signature_t *sig); -int digest_to_base64(char *d64, const char *digest); +void digest_to_base64(char *d64, const char *digest); int digest_from_base64(char *digest, const char *d64); -int digest256_to_base64(char *d64, const char *digest); +void digest256_to_base64(char *d64, const char *digest); int digest256_from_base64(char *digest, const char *d64); #endif /* !defined(TOR_CRYPTO_FORMAT_H) */ diff --git a/src/lib/crypt_ops/crypto_openssl_mgt.c b/src/lib/crypt_ops/crypto_openssl_mgt.c index 60e4ea795e..c97815f9a4 100644 --- a/src/lib/crypt_ops/crypto_openssl_mgt.c +++ b/src/lib/crypt_ops/crypto_openssl_mgt.c @@ -213,6 +213,14 @@ crypto_openssl_early_init(void) !strcmp(version_str, OPENSSL_VERSION_TEXT)) { log_info(LD_CRYPTO, "OpenSSL version matches version from headers " "(%lx: %s).", version_num, version_str); + } else if ((version_num & 0xffff0000) == + (OPENSSL_VERSION_NUMBER & 0xffff0000)) { + log_notice(LD_CRYPTO, + "We compiled with OpenSSL %lx: %s and we " + "are running with OpenSSL %lx: %s. " + "These two versions should be binary compatible.", + (unsigned long)OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_TEXT, + version_num, version_str); } else { log_warn(LD_CRYPTO, "OpenSSL version from headers does not match the " "version we're running with. If you get weird crashes, that " diff --git a/src/lib/crypt_ops/crypto_rand.h b/src/lib/crypt_ops/crypto_rand.h index c51d6a4480..528f238fa5 100644 --- a/src/lib/crypt_ops/crypto_rand.h +++ b/src/lib/crypt_ops/crypto_rand.h @@ -92,6 +92,10 @@ void crypto_rand_fast_shutdown(void); #if defined(TOR_UNIT_TESTS) /* Used for white-box testing */ size_t crypto_fast_rng_get_bytes_used_per_stream(void); +/* For deterministic prng implementations */ +void crypto_fast_rng_disable_reseed(crypto_fast_rng_t *rng); +/* To override the prng for testing. */ +crypto_fast_rng_t *crypto_replace_thread_fast_rng(crypto_fast_rng_t *rng); #endif #ifdef CRYPTO_RAND_PRIVATE diff --git a/src/lib/crypt_ops/crypto_rand_fast.c b/src/lib/crypt_ops/crypto_rand_fast.c index 01817c618f..b71ade81bd 100644 --- a/src/lib/crypt_ops/crypto_rand_fast.c +++ b/src/lib/crypt_ops/crypto_rand_fast.c @@ -95,8 +95,13 @@ CTASSERT(KEY_BITS == 128 || KEY_BITS == 192 || KEY_BITS == 256); struct crypto_fast_rng_t { /** How many more fills does this buffer have before we should mix - * in the output of crypto_rand()? */ - uint16_t n_till_reseed; + * in the output of crypto_strongest_rand()? + * + * This value may be negative if unit tests are enabled. If so, it + * indicates that we should never mix in extra data from + * crypto_strongest_rand(). + */ + int16_t n_till_reseed; /** How many bytes are remaining in cbuf.bytes? */ uint16_t bytes_left; #ifdef CHECK_PID @@ -181,6 +186,18 @@ crypto_fast_rng_new_from_seed(const uint8_t *seed) return result; } +#ifdef TOR_UNIT_TESTS +/** + * Unit tests only: prevent a crypto_fast_rng_t from ever mixing in more + * entropy. + */ +void +crypto_fast_rng_disable_reseed(crypto_fast_rng_t *rng) +{ + rng->n_till_reseed = -1; +} +#endif + /** * Helper: create a crypto_cipher_t object from SEED_LEN bytes of * input. The first KEY_LEN bytes are used as the stream cipher's key, @@ -193,6 +210,26 @@ cipher_from_seed(const uint8_t *seed) } /** + * Helper: mix additional entropy into <b>rng</b> by using our XOF to mix the + * old value for the seed with some additional bytes from + * crypto_strongest_rand(). + **/ +static void +crypto_fast_rng_add_entopy(crypto_fast_rng_t *rng) +{ + crypto_xof_t *xof = crypto_xof_new(); + crypto_xof_add_bytes(xof, rng->buf.seed, SEED_LEN); + { + uint8_t seedbuf[SEED_LEN]; + crypto_strongest_rand(seedbuf, SEED_LEN); + crypto_xof_add_bytes(xof, seedbuf, SEED_LEN); + memwipe(seedbuf, 0, SEED_LEN); + } + crypto_xof_squeeze_bytes(xof, rng->buf.seed, SEED_LEN); + crypto_xof_free(xof); +} + +/** * Helper: refill the seed bytes and output buffer of <b>rng</b>, using * the input seed bytes as input (key and IV) for the stream cipher. * @@ -202,22 +239,19 @@ cipher_from_seed(const uint8_t *seed) static void crypto_fast_rng_refill(crypto_fast_rng_t *rng) { - if (rng->n_till_reseed-- == 0) { - /* It's time to reseed the RNG. We'll do this by using our XOF to mix the - * old value for the seed with some additional bytes from - * crypto_strongest_rand(). */ - crypto_xof_t *xof = crypto_xof_new(); - crypto_xof_add_bytes(xof, rng->buf.seed, SEED_LEN); - { - uint8_t seedbuf[SEED_LEN]; - crypto_strongest_rand(seedbuf, SEED_LEN); - crypto_xof_add_bytes(xof, seedbuf, SEED_LEN); - memwipe(seedbuf, 0, SEED_LEN); - } - crypto_xof_squeeze_bytes(xof, rng->buf.seed, SEED_LEN); - crypto_xof_free(xof); - + rng->n_till_reseed--; + if (rng->n_till_reseed == 0) { + /* It's time to reseed the RNG. */ + crypto_fast_rng_add_entopy(rng); rng->n_till_reseed = RESEED_AFTER; + } else if (rng->n_till_reseed < 0) { +#ifdef TOR_UNIT_TESTS + /* Reseeding is disabled for testing; never do it on this prng. */ + rng->n_till_reseed = -1; +#else + /* If testing is disabled, this shouldn't be able to become negative. */ + tor_assert_unreached(); +#endif } /* Now fill rng->buf with output from our stream cipher, initialized from * that seed value. */ @@ -363,6 +397,20 @@ destroy_thread_fast_rng(void) tor_threadlocal_set(&thread_rng, NULL); } +#ifdef TOR_UNIT_TESTS +/** + * Replace the current thread's rng with <b>rng</b>. For use by the + * unit tests only. Returns the previous thread rng. + **/ +crypto_fast_rng_t * +crypto_replace_thread_fast_rng(crypto_fast_rng_t *rng) +{ + crypto_fast_rng_t *old_rng = tor_threadlocal_get(&thread_rng); + tor_threadlocal_set(&thread_rng, rng); + return old_rng; +} +#endif + /** * Initialize the global thread-local key that will be used to keep track * of per-thread fast RNG instances. Called from the crypto subsystem's diff --git a/src/lib/encoding/binascii.c b/src/lib/encoding/binascii.c index 187df34243..fc64e014e7 100644 --- a/src/lib/encoding/binascii.c +++ b/src/lib/encoding/binascii.c @@ -321,8 +321,10 @@ base64_encode(char *dest, size_t destlen, const char *src, size_t srclen, return (int) enclen; } -/** As base64_encode, but do not add any internal spaces or external padding - * to the output stream. */ +/** As base64_encode, but do not add any internal spaces, and remove external + * padding from the output stream. + * dest must be at least base64_encode_size(srclen, 0), including space for + * the removed external padding. */ int base64_encode_nopad(char *dest, size_t destlen, const uint8_t *src, size_t srclen) diff --git a/src/lib/encoding/confline.c b/src/lib/encoding/confline.c index 8110f3dd9c..fdb575e03f 100644 --- a/src/lib/encoding/confline.c +++ b/src/lib/encoding/confline.c @@ -82,6 +82,19 @@ config_line_find(const config_line_t *lines, return NULL; } +/** As config_line_find(), but perform a case-insensitive comparison. */ +const config_line_t * +config_line_find_case(const config_line_t *lines, + const char *key) +{ + const config_line_t *cl; + for (cl = lines; cl; cl = cl->next) { + if (!strcasecmp(cl->key, key)) + return cl; + } + return NULL; +} + /** Auxiliary function that does all the work of config_get_lines. * <b>recursion_level</b> is the count of how many nested %includes we have. * <b>opened_lst</b> will have a list of opened files if provided. diff --git a/src/lib/encoding/confline.h b/src/lib/encoding/confline.h index 3d9ae8a662..56ea36bf61 100644 --- a/src/lib/encoding/confline.h +++ b/src/lib/encoding/confline.h @@ -48,6 +48,8 @@ config_line_t *config_lines_dup_and_filter(const config_line_t *inp, const char *key); const config_line_t *config_line_find(const config_line_t *lines, const char *key); +const config_line_t *config_line_find_case(const config_line_t *lines, + const char *key); int config_lines_eq(config_line_t *a, config_line_t *b); int config_count_key(const config_line_t *a, const char *key); void config_free_lines_(config_line_t *front); diff --git a/src/lib/encoding/include.am b/src/lib/encoding/include.am index 83e9211b6f..8272e4e5fa 100644 --- a/src/lib/encoding/include.am +++ b/src/lib/encoding/include.am @@ -11,6 +11,7 @@ src_lib_libtor_encoding_a_SOURCES = \ src/lib/encoding/keyval.c \ src/lib/encoding/kvline.c \ src/lib/encoding/pem.c \ + src/lib/encoding/qstring.c \ src/lib/encoding/time_fmt.c src_lib_libtor_encoding_testing_a_SOURCES = \ @@ -25,4 +26,5 @@ noinst_HEADERS += \ src/lib/encoding/keyval.h \ src/lib/encoding/kvline.h \ src/lib/encoding/pem.h \ + src/lib/encoding/qstring.h \ src/lib/encoding/time_fmt.h diff --git a/src/lib/encoding/kvline.c b/src/lib/encoding/kvline.c index 307adc3f12..d4a8f510ba 100644 --- a/src/lib/encoding/kvline.c +++ b/src/lib/encoding/kvline.c @@ -16,6 +16,7 @@ #include "lib/encoding/confline.h" #include "lib/encoding/cstring.h" #include "lib/encoding/kvline.h" +#include "lib/encoding/qstring.h" #include "lib/malloc/malloc.h" #include "lib/string/compat_ctype.h" #include "lib/string/printf.h" @@ -54,6 +55,15 @@ line_has_no_key(const config_line_t *line) } /** + * Return true iff the value in <b>line</b> is not set. + **/ +static bool +line_has_no_val(const config_line_t *line) +{ + return line->value == NULL || strlen(line->value) == 0; +} + +/** * Return true iff the all the lines in <b>line</b> can be encoded * using <b>flags</b>. **/ @@ -98,14 +108,25 @@ kvline_can_encode_lines(const config_line_t *line, unsigned flags) * If KV_OMIT_KEYS is set in <b>flags</b>, then pairs with empty keys are * allowed, and are encoded as 'Value'. Otherwise, such pairs are not * allowed. + * + * If KV_OMIT_VALS is set in <b>flags</b>, then an empty value is + * encoded as 'Key', not as 'Key=' or 'Key=""'. Mutually exclusive with + * KV_OMIT_KEYS. + * + * KV_QUOTED_QSTRING is not supported. */ char * kvline_encode(const config_line_t *line, unsigned flags) { + tor_assert(! (flags & KV_QUOTED_QSTRING)); + if (!kvline_can_encode_lines(line, flags)) return NULL; + tor_assert((flags & (KV_OMIT_KEYS|KV_OMIT_VALS)) != + (KV_OMIT_KEYS|KV_OMIT_VALS)); + smartlist_t *elements = smartlist_new(); for (; line; line = line->next) { @@ -126,7 +147,10 @@ kvline_encode(const config_line_t *line, } } - if (esc) { + if ((flags & KV_OMIT_VALS) && line_has_no_val(line)) { + eq = ""; + v = ""; + } else if (esc) { tmp = esc_for_log(line->value); v = tmp; } else { @@ -151,17 +175,30 @@ kvline_encode(const config_line_t *line, * allocated list of pairs on success, or NULL on failure. * * If KV_QUOTED is set in <b>flags</b>, then (double-)quoted values are - * allowed. Otherwise, such values are not allowed. + * allowed and handled as C strings. Otherwise, such values are not allowed. * * If KV_OMIT_KEYS is set in <b>flags</b>, then values without keys are * allowed. Otherwise, such values are not allowed. + * + * If KV_OMIT_VALS is set in <b>flags</b>, then keys without values are + * allowed. Otherwise, such keys are not allowed. Mutually exclusive with + * KV_OMIT_KEYS. + * + * If KV_QUOTED_QSTRING is set in <b>flags</b>, then double-quoted values + * are allowed and handled as QuotedStrings per qstring.c. Do not add + * new users of this flag. */ config_line_t * kvline_parse(const char *line, unsigned flags) { + tor_assert((flags & (KV_OMIT_KEYS|KV_OMIT_VALS)) != + (KV_OMIT_KEYS|KV_OMIT_VALS)); + const char *cp = line, *cplast = NULL; - bool omit_keys = (flags & KV_OMIT_KEYS) != 0; - bool quoted = (flags & KV_QUOTED) != 0; + const bool omit_keys = (flags & KV_OMIT_KEYS) != 0; + const bool omit_vals = (flags & KV_OMIT_VALS) != 0; + const bool quoted = (flags & (KV_QUOTED|KV_QUOTED_QSTRING)) != 0; + const bool c_quoted = (flags & (KV_QUOTED)) != 0; config_line_t *result = NULL; config_line_t **next_line = &result; @@ -171,27 +208,33 @@ kvline_parse(const char *line, unsigned flags) while (*cp) { key = val = NULL; + /* skip all spaces */ { size_t idx = strspn(cp, " \t\r\v\n"); cp += idx; } if (BUG(cp == cplast)) { - /* If we didn't parse anything, this code is broken. */ + /* If we didn't parse anything since the last loop, this code is + * broken. */ goto err; // LCOV_EXCL_LINE } cplast = cp; if (! *cp) break; /* End of string; we're done. */ - /* Possible formats are K=V, K="V", V, and "V", depending on flags. */ + /* Possible formats are K=V, K="V", K, V, and "V", depending on flags. */ - /* Find the key. */ + /* Find where the key ends */ if (*cp != '\"') { size_t idx = strcspn(cp, " \t\r\v\n="); if (cp[idx] == '=') { key = tor_memdup_nulterm(cp, idx); cp += idx + 1; + } else if (omit_vals) { + key = tor_memdup_nulterm(cp, idx); + cp += idx; + goto commit; } else { if (!omit_keys) goto err; @@ -203,7 +246,11 @@ kvline_parse(const char *line, unsigned flags) if (!quoted) goto err; size_t len=0; - cp = unescape_string(cp, &val, &len); + if (c_quoted) { + cp = unescape_string(cp, &val, &len); + } else { + cp = decode_qstring(cp, strlen(cp), &val, &len); + } if (cp == NULL || len != strlen(val)) { // The string contains a NUL or is badly coded. goto err; @@ -214,6 +261,7 @@ kvline_parse(const char *line, unsigned flags) cp += idx; } + commit: if (key && strlen(key) == 0) { /* We don't allow empty keys. */ goto err; @@ -221,13 +269,15 @@ kvline_parse(const char *line, unsigned flags) *next_line = tor_malloc_zero(sizeof(config_line_t)); (*next_line)->key = key ? key : tor_strdup(""); - (*next_line)->value = val; + (*next_line)->value = val ? val : tor_strdup(""); next_line = &(*next_line)->next; key = val = NULL; } - if (!kvline_can_encode_lines(result, flags)) { - goto err; + if (! (flags & KV_QUOTED_QSTRING)) { + if (!kvline_can_encode_lines(result, flags)) { + goto err; + } } return result; diff --git a/src/lib/encoding/kvline.h b/src/lib/encoding/kvline.h index 4eed30a223..dea2ce1809 100644 --- a/src/lib/encoding/kvline.h +++ b/src/lib/encoding/kvline.h @@ -17,6 +17,8 @@ struct config_line_t; #define KV_QUOTED (1u<<0) #define KV_OMIT_KEYS (1u<<1) +#define KV_OMIT_VALS (1u<<2) +#define KV_QUOTED_QSTRING (1u<<3) struct config_line_t *kvline_parse(const char *line, unsigned flags); char *kvline_encode(const struct config_line_t *line, unsigned flags); diff --git a/src/lib/encoding/qstring.c b/src/lib/encoding/qstring.c new file mode 100644 index 0000000000..a92d28c706 --- /dev/null +++ b/src/lib/encoding/qstring.c @@ -0,0 +1,90 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file qstring.c + * \brief Implement QuotedString parsing. + * + * Note that this is only used for controller authentication; do not + * create new users for this. Instead, prefer the cstring.c functions. + **/ + +#include "orconfig.h" +#include "lib/encoding/qstring.h" +#include "lib/malloc/malloc.h" +#include "lib/log/util_bug.h" + +/** If the first <b>in_len_max</b> characters in <b>start</b> contain a + * QuotedString, return the length of that + * string (as encoded, including quotes). Otherwise return -1. */ +static inline int +get_qstring_length(const char *start, size_t in_len_max, + int *chars_out) +{ + const char *cp, *end; + int chars = 0; + + if (*start != '\"') + return -1; + + cp = start+1; + end = start+in_len_max; + + /* Calculate length. */ + while (1) { + if (cp >= end) { + return -1; /* Too long. */ + } else if (*cp == '\\') { + if (++cp == end) + return -1; /* Can't escape EOS. */ + ++cp; + ++chars; + } else if (*cp == '\"') { + break; + } else { + ++cp; + ++chars; + } + } + if (chars_out) + *chars_out = chars; + return (int)(cp - start+1); +} + +/** Given a pointer to a string starting at <b>start</b> containing + * <b>in_len_max</b> characters, decode a string beginning with one double + * quote, containing any number of non-quote characters or characters escaped + * with a backslash, and ending with a final double quote. Place the resulting + * string (unquoted, unescaped) into a newly allocated string in *<b>out</b>; + * store its length in <b>out_len</b>. On success, return a pointer to the + * character immediately following the escaped string. On failure, return + * NULL. */ +const char * +decode_qstring(const char *start, size_t in_len_max, + char **out, size_t *out_len) +{ + const char *cp, *end; + char *outp; + int len, n_chars = 0; + + len = get_qstring_length(start, in_len_max, &n_chars); + if (len<0) + return NULL; + + end = start+len-1; /* Index of last quote. */ + tor_assert(*end == '\"'); + outp = *out = tor_malloc(len+1); + *out_len = n_chars; + + cp = start+1; + while (cp < end) { + if (*cp == '\\') + ++cp; + *outp++ = *cp++; + } + *outp = '\0'; + tor_assert((outp - *out) == (int)*out_len); + + return end+1; +} diff --git a/src/lib/encoding/qstring.h b/src/lib/encoding/qstring.h new file mode 100644 index 0000000000..fe15b655f1 --- /dev/null +++ b/src/lib/encoding/qstring.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file qstring.h + * \brief Header for qstring.c + */ + +#ifndef TOR_ENCODING_QSTRING_H +#define TOR_ENCODING_QSTRING_H + +#include <stddef.h> + +const char *decode_qstring(const char *start, size_t in_len_max, + char **out, size_t *out_len); + +#endif diff --git a/src/lib/fdio/fdio.c b/src/lib/fdio/fdio.c index 6c87af791d..078af6a9ba 100644 --- a/src/lib/fdio/fdio.c +++ b/src/lib/fdio/fdio.c @@ -17,12 +17,16 @@ #ifdef _WIN32 #include <windows.h> #endif +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif #include "lib/fdio/fdio.h" #include "lib/cc/torint.h" #include "lib/err/torerr.h" #include <stdlib.h> +#include <stdio.h> /** @{ */ /** Some old versions of Unix didn't define constants for these values, diff --git a/src/lib/log/util_bug.c b/src/lib/log/util_bug.c index 65ab7bc9c6..76b97c1a08 100644 --- a/src/lib/log/util_bug.c +++ b/src/lib/log/util_bug.c @@ -19,6 +19,7 @@ #include "lib/string/printf.h" #include <string.h> +#include <stdlib.h> #ifdef TOR_UNIT_TESTS static void (*failed_assertion_cb)(void) = NULL; @@ -159,6 +160,19 @@ tor_bug_occurred_(const char *fname, unsigned int line, #endif } +/** + * Call the abort() function to kill the current process with a fatal + * error. + * + * (This is a separate function so that we declare it in util_bug.h without + * including stdlib in all the users of util_bug.h) + **/ +void +tor_abort_(void) +{ + abort(); +} + #ifdef _WIN32 /** Take a filename and return a pointer to its final element. This * function is called on __FILE__ to fix a MSVC nit where __FILE__ diff --git a/src/lib/log/util_bug.h b/src/lib/log/util_bug.h index 63c5309c98..2e220b7286 100644 --- a/src/lib/log/util_bug.h +++ b/src/lib/log/util_bug.h @@ -106,7 +106,7 @@ } else { \ tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, #expr, \ fmt, ##__VA_ARGS__); \ - abort(); \ + tor_abort_(); \ } STMT_END #endif /* defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) */ @@ -114,7 +114,7 @@ STMT_BEGIN { \ tor_assertion_failed_(SHORT_FILE__, __LINE__, __func__, \ "line should be unreached", NULL); \ - abort(); \ + tor_abort_(); \ } STMT_END /* Non-fatal bug assertions. The "unreached" variants mean "this line should @@ -149,7 +149,7 @@ #define BUG(cond) \ (ASSERT_PREDICT_UNLIKELY_(cond) ? \ (tor_assertion_failed_(SHORT_FILE__,__LINE__,__func__,"!("#cond")"), \ - abort(), 1) \ + tor_abort_(), 1) \ : 0) #elif defined(TOR_UNIT_TESTS) && defined(DISABLE_ASSERTS_IN_UNIT_TESTS) #define tor_assert_nonfatal_unreached() STMT_NIL @@ -246,6 +246,8 @@ void tor_bug_occurred_(const char *fname, unsigned int line, const char *func, const char *expr, int once, const char *fmt, ...); +void tor_abort_(void) ATTR_NORETURN; + #ifdef _WIN32 #define SHORT_FILE__ (tor_fix_source_file(__FILE__)) const char *tor_fix_source_file(const char *fname); diff --git a/src/lib/math/prob_distr.h b/src/lib/math/prob_distr.h index 2eb935e4a8..8fccf8d015 100644 --- a/src/lib/math/prob_distr.h +++ b/src/lib/math/prob_distr.h @@ -53,7 +53,7 @@ struct dist { * We define this conditionally to suppress false positives from * Coverity, which gets confused by the sizeof business. */ -#ifdef __COVERITY___ +#ifdef __COVERITY__ #define TYPE_CHECK_OBJ(OPS, OBJ, TYPE) 0 #else #define TYPE_CHECK_OBJ(OPS, OBJ, TYPE) \ diff --git a/src/lib/smartlist_core/smartlist_core.c b/src/lib/smartlist_core/smartlist_core.c index 5947e76271..6b0a305a93 100644 --- a/src/lib/smartlist_core/smartlist_core.c +++ b/src/lib/smartlist_core/smartlist_core.c @@ -177,6 +177,8 @@ smartlist_remove_keeporder(smartlist_t *sl, const void *element) sl->list[i++] = sl->list[j]; } } + memset(sl->list + sl->num_used, 0, + sizeof(void *) * (num_used_orig - sl->num_used)); } /** If <b>sl</b> is nonempty, remove and return the final element. Otherwise, diff --git a/src/lib/time/compat_time.c b/src/lib/time/compat_time.c index c6625c7806..70802770cc 100644 --- a/src/lib/time/compat_time.c +++ b/src/lib/time/compat_time.c @@ -164,6 +164,8 @@ static int64_t last_tick_count = 0; * to be monotonic; increments them as appropriate so that they actually * _are_ monotonic. * + * The returned time may be the same as the previous returned time. + * * Caller must hold lock. */ STATIC int64_t ratchet_performance_counter(int64_t count_raw) @@ -202,6 +204,8 @@ static struct timeval timeofday_offset = { 0, 0 }; * supposed to be monotonic; increments them as appropriate so that they * actually _are_ monotonic. * + * The returned time may be the same as the previous returned time. + * * Caller must hold lock. */ STATIC void ratchet_timeval(const struct timeval *timeval_raw, struct timeval *out) @@ -270,7 +274,9 @@ monotime_init_internal(void) } /** - * Set "out" to the most recent monotonic time value + * Set "out" to the most recent monotonic time value. + * + * The returned time may be the same as the previous returned time. */ void monotime_get(monotime_t *out) @@ -302,6 +308,8 @@ monotime_coarse_get(monotime_coarse_t *out) /** * Return the number of nanoseconds between <b>start</b> and <b>end</b>. + * + * The returned value may be equal to zero. */ int64_t monotime_diff_nsec(const monotime_t *start, diff --git a/src/lib/time/compat_time.h b/src/lib/time/compat_time.h index 2cd4b3bee3..360d92e5c9 100644 --- a/src/lib/time/compat_time.h +++ b/src/lib/time/compat_time.h @@ -15,11 +15,29 @@ * of tens of milliseconds. */ -/* Q: Should you use monotime or monotime_coarse as your source? +/* Q: When should I use monotonic time? + * + * A: If you need a time that never decreases, use monotonic time. If you need + * to send a time to a user or another process, or store a time, use the + * wall-clock time. + * + * Q: Should you use monotime or monotime_coarse as your source? * * A: Generally, you get better precision with monotime, but better * performance with monotime_coarse. * + * Q: What is a "monotonic" time, exactly? + * + * A: Monotonic times are strictly non-decreasing. The difference between any + * previous monotonic time, and the current monotonic time, is always greater + * than *or equal to* zero. + * Zero deltas happen more often: + * - on Windows (due to an OS bug), + * - when using monotime_coarse, or on systems with low-resolution timers, + * - on platforms where we emulate monotonic time using wall-clock time, and + * - when using time units that are larger than nanoseconds (due to + * truncation on division). + * * Q: Should you use monotime_t or monotime_coarse_t directly? Should you use * usec? msec? "stamp units?" * @@ -95,7 +113,7 @@ * All, "timestamp units": Cheap everywhere: it never divides. * * Q: This is only somewhat related, but how much precision could I hope for - * from a libevent time.? + * from a libevent time? * * A: Actually, it's _very_ related if you're timing in order to have a * timeout happen. @@ -182,26 +200,36 @@ void monotime_init(void); void monotime_get(monotime_t *out); /** * Return the number of nanoseconds between <b>start</b> and <b>end</b>. + * The returned value may be equal to zero. */ int64_t monotime_diff_nsec(const monotime_t *start, const monotime_t *end); /** * Return the number of microseconds between <b>start</b> and <b>end</b>. + * The returned value may be equal to zero. + * Fractional units are truncated, not rounded. */ int64_t monotime_diff_usec(const monotime_t *start, const monotime_t *end); /** * Return the number of milliseconds between <b>start</b> and <b>end</b>. + * The returned value may be equal to zero. + * Fractional units are truncated, not rounded. */ int64_t monotime_diff_msec(const monotime_t *start, const monotime_t *end); /** * Return the number of nanoseconds since the timer system was initialized. + * The returned value may be equal to zero. */ uint64_t monotime_absolute_nsec(void); /** * Return the number of microseconds since the timer system was initialized. + * The returned value may be equal to zero. + * Fractional units are truncated, not rounded. */ MOCK_DECL(uint64_t, monotime_absolute_usec,(void)); /** * Return the number of milliseconds since the timer system was initialized. + * The returned value may be equal to zero. + * Fractional units are truncated, not rounded. */ uint64_t monotime_absolute_msec(void); @@ -225,6 +253,9 @@ void monotime_add_msec(monotime_t *out, const monotime_t *val, uint32_t msec); * Set <b>out</b> to the current coarse time. */ void monotime_coarse_get(monotime_coarse_t *out); +/** + * Like monotime_absolute_*(), but faster on some platforms. + */ uint64_t monotime_coarse_absolute_nsec(void); uint64_t monotime_coarse_absolute_usec(void); uint64_t monotime_coarse_absolute_msec(void); @@ -248,18 +279,27 @@ uint32_t monotime_coarse_to_stamp(const monotime_coarse_t *t); /** * Convert a difference, expressed in the units of monotime_coarse_to_stamp, * into an approximate number of milliseconds. + * + * The returned value may be equal to zero. + * Fractional units are truncated, not rounded. */ uint64_t monotime_coarse_stamp_units_to_approx_msec(uint64_t units); uint64_t monotime_msec_to_approx_coarse_stamp_units(uint64_t msec); uint32_t monotime_coarse_get_stamp(void); #if defined(MONOTIME_COARSE_TYPE_IS_DIFFERENT) +/** + * Like monotime_diff_*(), but faster on some platforms. + */ int64_t monotime_coarse_diff_nsec(const monotime_coarse_t *start, const monotime_coarse_t *end); int64_t monotime_coarse_diff_usec(const monotime_coarse_t *start, const monotime_coarse_t *end); int64_t monotime_coarse_diff_msec(const monotime_coarse_t *start, const monotime_coarse_t *end); +/** + * Like monotime_*(), but faster on some platforms. + */ void monotime_coarse_zero(monotime_coarse_t *out); int monotime_coarse_is_zero(const monotime_coarse_t *val); void monotime_coarse_add_msec(monotime_coarse_t *out, @@ -278,6 +318,9 @@ void monotime_coarse_add_msec(monotime_coarse_t *out, * * Requires that the difference fit into an int32_t; not for use with * large time differences. + * + * The returned value may be equal to zero. + * Fractional units are truncated, not rounded. */ int32_t monotime_coarse_diff_msec32_(const monotime_coarse_t *start, const monotime_coarse_t *end); @@ -287,6 +330,9 @@ int32_t monotime_coarse_diff_msec32_(const monotime_coarse_t *start, * * Requires that the difference fit into an int32_t; not for use with * large time differences. + * + * The returned value may be equal to zero. + * Fractional units are truncated, not rounded. */ static inline int32_t monotime_coarse_diff_msec32(const monotime_coarse_t *start, diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 83f9629660..de8693ea33 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -10,6 +10,17 @@ members = [ "tor_util", ] +# Can remove panic="abort" when this issue is fixed: +# https://github.com/rust-lang/rust/issues/52652 +[profile.dev] +panic = "abort" + [profile.release] debug = true panic = "abort" + +[profile.test] +panic = "abort" + +[profile.bench] +panic = "abort" diff --git a/src/test/fuzz/fuzz_multi.sh b/src/test/fuzz/fuzz_multi.sh index b4a17ed8cb..406ab498d9 100755 --- a/src/test/fuzz/fuzz_multi.sh +++ b/src/test/fuzz/fuzz_multi.sh @@ -1,3 +1,5 @@ +#!/bin/sh + MEMLIMIT_BYTES=21990500990976 N_CPUS=1 @@ -6,9 +8,9 @@ if [ $# -ge 1 ]; then shift fi -FILTER=echo +FILTER="echo" -for i in `seq -w "$N_CPUS"`; do +for i in $(seq -w "$N_CPUS"); do if [ "$i" -eq 1 ]; then if [ "$N_CPUS" -eq 1 ]; then INSTANCE="" diff --git a/src/test/fuzz/fuzz_strops.c b/src/test/fuzz/fuzz_strops.c index a37cbb5be8..459b4e21aa 100644 --- a/src/test/fuzz/fuzz_strops.c +++ b/src/test/fuzz/fuzz_strops.c @@ -235,6 +235,18 @@ fuzz_main(const uint8_t *stdin_buf, size_t data_size) kv_flags = 0; ENCODE_ROUNDTRIP(kv_enc, kv_dec, config_free_lines_); break; + case 7: + kv_flags = KV_OMIT_VALS; + ENCODE_ROUNDTRIP(kv_enc, kv_dec, config_free_lines_); + break; + case 8: + kv_flags = KV_QUOTED; + ENCODE_ROUNDTRIP(kv_enc, kv_dec, config_free_lines_); + break; + case 9: + kv_flags = KV_QUOTED|KV_OMIT_VALS; + ENCODE_ROUNDTRIP(kv_enc, kv_dec, config_free_lines_); + break; } return 0; diff --git a/src/test/include.am b/src/test/include.am index 497aa320a4..022cdbe035 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -89,6 +89,7 @@ src_test_test_SOURCES += \ src/test/log_test_helpers.c \ src/test/hs_test_helpers.c \ src/test/rend_test_helpers.c \ + src/test/rng_test_helpers.c \ src/test/test.c \ src/test/test_accounting.c \ src/test/test_addr.c \ @@ -211,6 +212,7 @@ endif src_test_test_slow_SOURCES = if UNITTESTS_ENABLED src_test_test_slow_SOURCES += \ + src/test/rng_test_helpers.c \ src/test/test_slow.c \ src/test/test_crypto_slow.c \ src/test/test_process_slow.c \ @@ -319,6 +321,7 @@ noinst_HEADERS+= \ src/test/hs_test_helpers.h \ src/test/log_test_helpers.h \ src/test/rend_test_helpers.h \ + src/test/rng_test_helpers.h \ src/test/test.h \ src/test/ptr_helpers.h \ src/test/test_helpers.h \ diff --git a/src/test/rng_test_helpers.c b/src/test/rng_test_helpers.c new file mode 100644 index 0000000000..262d380bda --- /dev/null +++ b/src/test/rng_test_helpers.c @@ -0,0 +1,226 @@ +/* Copyright (c) 2018-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file rng_test_helpers.c + * \brief Helpers for overriding PRNGs during unit tests. + * + * We define two PRNG overrides: a "reproducible PRNG" where the seed is + * chosen randomly but the stream can be replayed later on in case a bug is + * found, and a "deterministic PRNG" where the seed is fixed in the unit + * tests. + * + * Obviously, this code is testing-only. + */ + +#include "orconfig.h" +#include "core/or/or.h" + +#include "lib/crypt_ops/crypto_rand.h" + +#include "test/rng_test_helpers.h" + +#ifndef TOR_UNIT_TESTS +#error "No. Never link this code into Tor proper." +#endif + +/** + * True iff the RNG is currently replaced. Prevents double-replacement. + **/ +static bool rng_is_replaced = false; + +/** + * Mutex to protect deterministic prng. + * + * Note that if you actually _use_ the prng from two threads at the same time, + * the results will probably be nondeterministic anyway. + */ +static tor_mutex_t *rng_mutex = NULL; + +/** + * Cached old value for the thread prng. + **/ +static crypto_fast_rng_t *stored_fast_rng = NULL; + +/** replacement for crypto_strongest_rand that delegates to crypto_rand. */ +static void +mock_crypto_strongest_rand(uint8_t *out, size_t len) +{ + crypto_rand((char *)out, len); +} + +/* This is the seed of the deterministic randomness. */ +static uint8_t rng_seed[16]; +static crypto_xof_t *rng_xof = NULL; + +/** + * Print the seed for our PRNG to stdout. We use this when we're + **/ +void +testing_dump_reproducible_rng_seed(void) +{ + printf("\n" + "Seed: %s\n", + hex_str((const char*)rng_seed, sizeof(rng_seed))); +} + +/** Produce deterministic randomness for the stochastic tests using the global + * rng_xof output. + * + * This function produces deterministic data over multiple calls iff it's + * called in the same call order with the same 'n' parameter. + * If not, outputs will deviate. */ +static void +crypto_rand_deterministic(char *out, size_t n) +{ + tor_assert(rng_xof); + tor_mutex_acquire(rng_mutex); + crypto_xof_squeeze_bytes(rng_xof, (uint8_t*)out, n); + tor_mutex_release(rng_mutex); +} + +/** + * Implementation helper: override our crypto_rand() PRNG with a given seed of + * length <b>seed_len</b>. Overlong seeds are truncated; short ones are + * padded. + **/ +static void +enable_deterministic_rng_impl(const uint8_t *seed, size_t seed_len) +{ + tor_assert(!rng_is_replaced); + tor_assert(crypto_rand == crypto_rand__real); + + memset(rng_seed, 0, sizeof(rng_seed)); + memcpy(rng_seed, seed, MIN(seed_len, sizeof(rng_seed))); + + rng_mutex = tor_mutex_new(); + + crypto_xof_free(rng_xof); + rng_xof = crypto_xof_new(); + crypto_xof_add_bytes(rng_xof, rng_seed, sizeof(rng_seed)); + MOCK(crypto_rand, crypto_rand_deterministic); + MOCK(crypto_strongest_rand_, mock_crypto_strongest_rand); + + uint8_t fast_rng_seed[CRYPTO_FAST_RNG_SEED_LEN]; + memset(fast_rng_seed, 0xff, sizeof(fast_rng_seed)); + memcpy(fast_rng_seed, rng_seed, MIN(sizeof(rng_seed), + sizeof(fast_rng_seed))); + crypto_fast_rng_t *fast_rng = crypto_fast_rng_new_from_seed(fast_rng_seed); + crypto_fast_rng_disable_reseed(fast_rng); + stored_fast_rng = crypto_replace_thread_fast_rng(fast_rng); + + rng_is_replaced = true; +} + +/** + * Replace our get_thread_fast_rng(), crypto_rand() and + * crypto_strongest_rand() prngs with a variant that generates all of its + * output deterministically from a randomly chosen seed. In the event of an + * error, you can log the seed later on with + * testing_dump_reproducible_rng_seed. + **/ +void +testing_enable_reproducible_rng(void) +{ + uint8_t seed[16]; + crypto_rand((char*)seed, sizeof(seed)); + enable_deterministic_rng_impl(seed, sizeof(seed)); +} + +/** + * Replace our get_thread_fast_rng(), crypto_rand() and + * crypto_strongest_rand() prngs with a variant that generates all of its + * output deterministically from a fixed seed. This variant is mainly useful + * for cases when we don't want coverage to change between runs. + * + * USAGE NOTE: Test correctness SHOULD NOT depend on the specific output of + * this "rng". If you need a specific output, use + * testing_enable_prefilled_rng() instead. + **/ +void +testing_enable_deterministic_rng(void) +{ + static const uint8_t quotation[] = + "What will it be? A tree? A weed? " + "Each one is started from a seed."; // -- Mary Ann Hoberman + enable_deterministic_rng_impl(quotation, sizeof(quotation)); +} + +static uint8_t *prefilled_rng_buffer = NULL; +static size_t prefilled_rng_buflen; +static size_t prefilled_rng_idx; + +/** + * crypto_rand() replacement that returns canned data. + **/ +static void +crypto_rand_prefilled(char *out, size_t n) +{ + tor_mutex_acquire(rng_mutex); + while (n) { + size_t n_to_copy = MIN(prefilled_rng_buflen - prefilled_rng_idx, n); + memcpy(out, prefilled_rng_buffer + prefilled_rng_idx, n_to_copy); + out += n_to_copy; + n -= n_to_copy; + prefilled_rng_idx += n_to_copy; + + if (prefilled_rng_idx == prefilled_rng_buflen) { + prefilled_rng_idx = 0; + } + } + tor_mutex_release(rng_mutex); +} + +/** + * Replace our crypto_rand() and crypto_strongest_rand() prngs with a variant + * that yields output from a buffer. If it reaches the end of the buffer, it + * starts over. + * + * Note: the get_thread_fast_rng() prng is not replaced by this; we'll need + * more code to support that. + **/ +void +testing_enable_prefilled_rng(const void *buffer, size_t buflen) +{ + tor_assert(buflen > 0); + rng_mutex = tor_mutex_new(); + + prefilled_rng_buffer = tor_memdup(buffer, buflen); + prefilled_rng_buflen = buflen; + prefilled_rng_idx = 0; + + MOCK(crypto_rand, crypto_rand_prefilled); + MOCK(crypto_strongest_rand_, mock_crypto_strongest_rand); +} + +/** + * Reset the position in the prefilled RNG buffer to the start. + */ +void +testing_prefilled_rng_reset(void) +{ + tor_mutex_acquire(rng_mutex); + prefilled_rng_idx = 0; + tor_mutex_release(rng_mutex); +} + +/** + * Undo the overrides for our PRNG. To be used at the end of testing. + * + * Note that this function should be safe to call even if the rng has not + * yet been replaced. + **/ +void +testing_disable_rng_override(void) +{ + crypto_xof_free(rng_xof); + tor_free(prefilled_rng_buffer); + UNMOCK(crypto_rand); + UNMOCK(crypto_strongest_rand_); + tor_mutex_free(rng_mutex); + + crypto_fast_rng_t *rng = crypto_replace_thread_fast_rng(stored_fast_rng); + crypto_fast_rng_free(rng); + + rng_is_replaced = false; +} diff --git a/src/test/rng_test_helpers.h b/src/test/rng_test_helpers.h new file mode 100644 index 0000000000..907099450d --- /dev/null +++ b/src/test/rng_test_helpers.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2017-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_RNG_TEST_HELPERS_H +#define TOR_RNG_TEST_HELPERS_H + +#include "core/or/or.h" + +void testing_enable_deterministic_rng(void); +void testing_enable_reproducible_rng(void); +void testing_enable_prefilled_rng(const void *buffer, size_t buflen); + +void testing_prefilled_rng_reset(void); + +void testing_disable_rng_override(void); + +#define testing_disable_reproducible_rng() \ + testing_disable_rng_override() +#define testing_disable_deterministic_rng() \ + testing_disable_rng_override() +#define testing_disable_prefilled_rng() \ + testing_disable_rng_override() + +void testing_dump_reproducible_rng_seed(void); + +#endif /* !defined(TOR_RNG_TEST_HELPERS_H) */ diff --git a/src/test/test-network.sh b/src/test/test-network.sh index 4d56e83806..5ef995f1a4 100755 --- a/src/test/test-network.sh +++ b/src/test/test-network.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # This script calls the equivalent script in chutney/tools @@ -18,32 +18,14 @@ ECHO="${ECHO:-echo}" # Output is prefixed with the name of the script myname=$(basename "$0") -# Save the arguments before we destroy them -# This might not preserve arguments with spaces in them -ORIGINAL_ARGS=( "$@" ) - # We need to find CHUTNEY_PATH, so that we can call the version of this script # in chutney/tools with the same arguments. We also need to respect --quiet. -until [ -z "$1" ] -do - case "$1" in - --chutney-path) - CHUTNEY_PATH="$2" - shift - ;; - --tor-path) - TOR_DIR="$2" - shift - ;; - --quiet) - ECHO=true - ;; - *) - # maybe chutney's test-network.sh can handle it - ;; - esac - shift -done +CHUTNEY_PATH=$(echo "$@" | awk -F '--chutney-path ' '{sub(" .*","",$2); print $2}') +TOR_DIR=$(echo "$@" | awk -F '--tor-dir ' '{sub(" .*","",$2); print $2}') + +if echo "$@" | grep -e "--quiet" > /dev/null; then + ECHO=true +fi # optional: $TOR_DIR is the tor build directory # it's used to find the location of tor binaries @@ -99,7 +81,7 @@ if [ -d "$CHUTNEY_PATH" ] && [ -x "$TEST_NETWORK" ]; then # this may fail if some arguments have spaces in them # if so, set CHUTNEY_PATH before calling test-network.sh, and spaces # will be handled correctly - exec "$TEST_NETWORK" "${ORIGINAL_ARGS[@]}" # $ORIGINAL_ARGS + exec "$TEST_NETWORK" "$@" else $ECHO "$myname: Could not find tools/test-network.sh in CHUTNEY_PATH." $ECHO "$myname: Please update your chutney using 'git pull'." diff --git a/src/test/test.c b/src/test/test.c index fbc30fb64e..be5cb12b1e 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -12,6 +12,7 @@ #include "lib/crypt_ops/crypto_dh.h" #include "lib/crypt_ops/crypto_rand.h" #include "app/config/or_state_st.h" +#include "test/rng_test_helpers.h" #include <stdio.h> #ifdef HAVE_FCNTL_H @@ -354,18 +355,6 @@ test_onion_queues(void *arg) tor_free(onionskin); } -static crypto_cipher_t *crypto_rand_aes_cipher = NULL; - -// Mock replacement for crypto_rand: Generates bytes from a provided AES_CTR -// cipher in <b>crypto_rand_aes_cipher</b>. -static void -crypto_rand_deterministic_aes(char *out, size_t n) -{ - tor_assert(crypto_rand_aes_cipher); - memset(out, 0, n); - crypto_cipher_crypt_inplace(crypto_rand_aes_cipher, out, n); -} - static void test_circuit_timeout(void *arg) { @@ -397,8 +386,7 @@ test_circuit_timeout(void *arg) // Use a deterministic RNG here, or else we'll get nondeterministic // coverage in some of the circuitstats functions. - MOCK(crypto_rand, crypto_rand_deterministic_aes); - crypto_rand_aes_cipher = crypto_cipher_new("xyzzyplughplover"); + testing_enable_deterministic_rng(); circuitbuild_running_unit_tests(); #define timeout0 (build_time_t)(30*1000.0) @@ -534,8 +522,8 @@ test_circuit_timeout(void *arg) circuit_build_times_free_timeouts(&final); or_state_free(state); teardown_periodic_events(); - UNMOCK(crypto_rand); - crypto_cipher_free(crypto_rand_aes_cipher); + + testing_disable_deterministic_rng(); } /** Test encoding and parsing of rendezvous service descriptors. */ diff --git a/src/test/test_addr.c b/src/test/test_addr.c index fb8df5f0fb..3a1a7b6997 100644 --- a/src/test/test_addr.c +++ b/src/test/test_addr.c @@ -11,6 +11,7 @@ #include "feature/client/addressmap.h" #include "test/log_test_helpers.h" #include "lib/net/resolve.h" +#include "test/rng_test_helpers.h" #ifdef HAVE_SYS_UN_H #include <sys/un.h> @@ -945,27 +946,6 @@ test_virtaddrmap(void *data) ; } -static const char *canned_data = NULL; -static size_t canned_data_len = 0; - -/* Mock replacement for crypto_rand() that returns canned data from - * canned_data above. */ -static void -crypto_canned(char *ptr, size_t n) -{ - if (canned_data_len) { - size_t to_copy = MIN(n, canned_data_len); - memcpy(ptr, canned_data, to_copy); - canned_data += to_copy; - canned_data_len -= to_copy; - n -= to_copy; - ptr += to_copy; - } - if (n) { - crypto_rand_unmocked(ptr, n); - } -} - static void test_virtaddrmap_persist(void *data) { @@ -973,6 +953,8 @@ test_virtaddrmap_persist(void *data) const char *a, *b, *c; tor_addr_t addr; char *ones = NULL; + const char *canned_data; + size_t canned_data_len; addressmap_init(); @@ -991,7 +973,7 @@ test_virtaddrmap_persist(void *data) "1234567890" // the second call returns this. "abcdefghij"; // the third call returns this. canned_data_len = 30; - MOCK(crypto_rand, crypto_canned); + testing_enable_prefilled_rng(canned_data, canned_data_len); a = addressmap_register_virtual_address(RESOLVED_TYPE_HOSTNAME, tor_strdup("quuxit.baz")); @@ -1001,9 +983,9 @@ test_virtaddrmap_persist(void *data) tt_assert(b); tt_str_op(a, OP_EQ, "gezdgnbvgy3tqojq.virtual"); tt_str_op(b, OP_EQ, "mfrggzdfmztwq2lk.virtual"); + testing_disable_prefilled_rng(); // Now try something to get us an ipv4 address - UNMOCK(crypto_rand); tt_int_op(0,OP_EQ, parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, NULL)); a = addressmap_register_virtual_address(RESOLVED_TYPE_IPV4, @@ -1020,22 +1002,23 @@ test_virtaddrmap_persist(void *data) // Try some canned entropy and verify all the we discard duplicates, // addresses that end with 0, and addresses that end with 255. - MOCK(crypto_rand, crypto_canned); canned_data = "\x01\x02\x03\x04" // okay "\x01\x02\x03\x04" // duplicate "\x03\x04\x00\x00" // bad ending 1 "\x05\x05\x00\xff" // bad ending 2 "\x05\x06\x07\xf0"; // okay canned_data_len = 20; + testing_enable_prefilled_rng(canned_data, canned_data_len); + a = addressmap_register_virtual_address(RESOLVED_TYPE_IPV4, tor_strdup("wumble.onion")); b = addressmap_register_virtual_address(RESOLVED_TYPE_IPV4, tor_strdup("wumpus.onion")); tt_str_op(a, OP_EQ, "192.168.3.4"); tt_str_op(b, OP_EQ, "192.168.7.240"); + testing_disable_prefilled_rng(); // Now try IPv6! - UNMOCK(crypto_rand); tt_int_op(0,OP_EQ, parse_virtual_addr_network("1010:F000::/20", AF_INET6, 0, NULL)); a = addressmap_register_virtual_address(RESOLVED_TYPE_IPV6, @@ -1051,7 +1034,7 @@ test_virtaddrmap_persist(void *data) tt_assert(!strcmpstart(b, "[1010:f")); // Try IPv6 with canned entropy, to make sure we detect duplicates. - MOCK(crypto_rand, crypto_canned); + canned_data = "acanthopterygian" // okay "cinematographist" // okay "acanthopterygian" // duplicate @@ -1060,6 +1043,8 @@ test_virtaddrmap_persist(void *data) "cinematographist" // duplicate "coadministration"; // okay canned_data_len = 16 * 7; + testing_enable_prefilled_rng(canned_data, canned_data_len); + a = addressmap_register_virtual_address(RESOLVED_TYPE_IPV6, tor_strdup("wuffle.baz")); b = addressmap_register_virtual_address(RESOLVED_TYPE_IPV6, @@ -1072,9 +1057,11 @@ test_virtaddrmap_persist(void *data) // Try address exhaustion: make sure we can actually fail if we // get too many already-existing addresses. + testing_disable_prefilled_rng(); canned_data_len = 128*1024; canned_data = ones = tor_malloc(canned_data_len); memset(ones, 1, canned_data_len); + testing_enable_prefilled_rng(canned_data, canned_data_len); // There is some chance this one will fail if a previous random // allocation gave out the address already. a = addressmap_register_virtual_address(RESOLVED_TYPE_IPV4, @@ -1091,7 +1078,7 @@ test_virtaddrmap_persist(void *data) expect_single_log_msg_containing("Ran out of virtual addresses!"); done: - UNMOCK(crypto_rand); + testing_disable_prefilled_rng(); tor_free(ones); addressmap_free_all(); teardown_capture_of_logs(); diff --git a/src/test/test_circuitbuild.c b/src/test/test_circuitbuild.c index 27f2cd1ca5..47218a559a 100644 --- a/src/test/test_circuitbuild.c +++ b/src/test/test_circuitbuild.c @@ -21,7 +21,7 @@ static smartlist_t dummy_nodes; static extend_info_t dummy_ei; static int -mock_count_acceptable_nodes(smartlist_t *nodes, int direct) +mock_count_acceptable_nodes(const smartlist_t *nodes, int direct) { (void)nodes; diff --git a/src/test/test_config.c b/src/test/test_config.c index 72649dd9b1..6cfb7b764b 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -5886,6 +5886,61 @@ test_config_kvline_parse(void *arg) tt_assert(lines); tt_str_op(lines->key, OP_EQ, "AB"); tt_str_op(lines->value, OP_EQ, ""); + config_free_lines(lines); + + lines = kvline_parse("AB=", KV_OMIT_VALS); + tt_assert(lines); + tt_str_op(lines->key, OP_EQ, "AB"); + tt_str_op(lines->value, OP_EQ, ""); + config_free_lines(lines); + + lines = kvline_parse(" AB ", KV_OMIT_VALS); + tt_assert(lines); + tt_str_op(lines->key, OP_EQ, "AB"); + tt_str_op(lines->value, OP_EQ, ""); + config_free_lines(lines); + + lines = kvline_parse("AB", KV_OMIT_VALS); + tt_assert(lines); + tt_str_op(lines->key, OP_EQ, "AB"); + tt_str_op(lines->value, OP_EQ, ""); + enc = kvline_encode(lines, KV_OMIT_VALS); + tt_str_op(enc, OP_EQ, "AB"); + tor_free(enc); + config_free_lines(lines); + + lines = kvline_parse("AB=CD", KV_OMIT_VALS); + tt_assert(lines); + tt_str_op(lines->key, OP_EQ, "AB"); + tt_str_op(lines->value, OP_EQ, "CD"); + enc = kvline_encode(lines, KV_OMIT_VALS); + tt_str_op(enc, OP_EQ, "AB=CD"); + tor_free(enc); + config_free_lines(lines); + + lines = kvline_parse("AB=CD DE FGH=I", KV_OMIT_VALS); + tt_assert(lines); + tt_str_op(lines->key, OP_EQ, "AB"); + tt_str_op(lines->value, OP_EQ, "CD"); + tt_str_op(lines->next->key, OP_EQ, "DE"); + tt_str_op(lines->next->value, OP_EQ, ""); + tt_str_op(lines->next->next->key, OP_EQ, "FGH"); + tt_str_op(lines->next->next->value, OP_EQ, "I"); + enc = kvline_encode(lines, KV_OMIT_VALS); + tt_str_op(enc, OP_EQ, "AB=CD DE FGH=I"); + tor_free(enc); + config_free_lines(lines); + + lines = kvline_parse("AB=\"CD E\" DE FGH=\"I\"", KV_OMIT_VALS|KV_QUOTED); + tt_assert(lines); + tt_str_op(lines->key, OP_EQ, "AB"); + tt_str_op(lines->value, OP_EQ, "CD E"); + tt_str_op(lines->next->key, OP_EQ, "DE"); + tt_str_op(lines->next->value, OP_EQ, ""); + tt_str_op(lines->next->next->key, OP_EQ, "FGH"); + tt_str_op(lines->next->next->value, OP_EQ, "I"); + enc = kvline_encode(lines, KV_OMIT_VALS|KV_QUOTED); + tt_str_op(enc, OP_EQ, "AB=\"CD E\" DE FGH=I"); done: config_free_lines(lines); diff --git a/src/test/test_containers.c b/src/test/test_containers.c index 7892a08853..67ba457975 100644 --- a/src/test/test_containers.c +++ b/src/test/test_containers.c @@ -1006,6 +1006,10 @@ test_container_smartlist_remove(void *arg) tt_ptr_op(smartlist_get(sl, 1), OP_EQ, &array[2]); tt_ptr_op(smartlist_get(sl, 2), OP_EQ, &array[1]); tt_ptr_op(smartlist_get(sl, 3), OP_EQ, &array[2]); + /* Ordinary code should never look at this pointer; we're doing it here + * to make sure that we really cleared the pointer we removed. + */ + tt_ptr_op(sl->list[4], OP_EQ, NULL); done: smartlist_free(sl); diff --git a/src/test/test_controller.c b/src/test/test_controller.c index f3af6d2ec0..ee48d656bd 100644 --- a/src/test/test_controller.c +++ b/src/test/test_controller.c @@ -18,12 +18,189 @@ #include "test/test.h" #include "test/test_helpers.h" #include "lib/net/resolve.h" +#include "lib/encoding/confline.h" +#include "lib/encoding/kvline.h" #include "feature/control/control_connection_st.h" +#include "feature/control/control_cmd_args_st.h" #include "feature/dirclient/download_status_st.h" #include "feature/nodelist/microdesc_st.h" #include "feature/nodelist/node_st.h" +typedef struct { + const char *input; + const char *expected_parse; + const char *expected_error; +} parser_testcase_t; + +typedef struct { + const control_cmd_syntax_t *syntax; + size_t n_testcases; + const parser_testcase_t *testcases; +} parse_test_params_t; + +static char * +control_cmd_dump_args(const control_cmd_args_t *result) +{ + buf_t *buf = buf_new(); + buf_add_string(buf, "{ args=["); + if (result->args) { + if (smartlist_len(result->args)) { + buf_add_string(buf, " "); + } + SMARTLIST_FOREACH_BEGIN(result->args, const char *, s) { + const bool last = (s_sl_idx == smartlist_len(result->args)-1); + buf_add_printf(buf, "%s%s ", + escaped(s), + last ? "" : ","); + } SMARTLIST_FOREACH_END(s); + } + buf_add_string(buf, "]"); + if (result->cmddata) { + buf_add_string(buf, ", obj="); + buf_add_string(buf, escaped(result->cmddata)); + } + if (result->kwargs) { + buf_add_string(buf, ", { "); + const config_line_t *line; + for (line = result->kwargs; line; line = line->next) { + const bool last = (line->next == NULL); + buf_add_printf(buf, "%s=%s%s ", line->key, escaped(line->value), + last ? "" : ","); + } + buf_add_string(buf, "}"); + } + buf_add_string(buf, " }"); + + char *encoded = buf_extract(buf, NULL); + buf_free(buf); + return encoded; +} + +static void +test_controller_parse_cmd(void *arg) +{ + const parse_test_params_t *params = arg; + control_cmd_args_t *result = NULL; + char *error = NULL; + char *encoded = NULL; + + for (size_t i = 0; i < params->n_testcases; ++i) { + const parser_testcase_t *t = ¶ms->testcases[i]; + result = control_cmd_parse_args("EXAMPLE", + params->syntax, + strlen(t->input), + t->input, + &error); + // A valid test should expect exactly one parse or error. + tt_int_op((t->expected_parse == NULL), OP_NE, + (t->expected_error == NULL)); + // We get a result or an error, not both. + tt_int_op((result == NULL), OP_EQ, (error != NULL)); + // We got the one we expected. + tt_int_op((result == NULL), OP_EQ, (t->expected_parse == NULL)); + + if (result) { + encoded = control_cmd_dump_args(result); + tt_str_op(encoded, OP_EQ, t->expected_parse); + } else { + tt_str_op(error, OP_EQ, t->expected_error); + } + + tor_free(error); + tor_free(encoded); + control_cmd_args_free(result); + } + + done: + tor_free(error); + tor_free(encoded); + control_cmd_args_free(result); +} + +#define OK(inp, out) \ + { inp "\r\n", out, NULL } +#define ERR(inp, err) \ + { inp "\r\n", NULL, err } + +#define TESTPARAMS(syntax, array) \ + { &syntax, \ + ARRAY_LENGTH(array), \ + array } + +static const parser_testcase_t one_to_three_tests[] = { + ERR("", "Need at least 1 argument(s)"), + ERR(" \t", "Need at least 1 argument(s)"), + OK("hello", "{ args=[ \"hello\" ] }"), + OK("hello world", "{ args=[ \"hello\", \"world\" ] }"), + OK("hello world", "{ args=[ \"hello\", \"world\" ] }"), + OK(" hello world", "{ args=[ \"hello\", \"world\" ] }"), + OK(" hello world ", "{ args=[ \"hello\", \"world\" ] }"), + OK("hello there world", "{ args=[ \"hello\", \"there\", \"world\" ] }"), + ERR("why hello there world", "Cannot accept more than 3 argument(s)"), + ERR("hello\r\nworld.\r\n.", "Unexpected body"), +}; + +static const control_cmd_syntax_t one_to_three_syntax = { + .min_args=1, .max_args=3 +}; + +static const parse_test_params_t parse_one_to_three_params = + TESTPARAMS( one_to_three_syntax, one_to_three_tests ); + +// = +static const parser_testcase_t no_args_one_obj_tests[] = { + ERR("Hi there!\r\n.", "Cannot accept more than 0 argument(s)"), + ERR("", "Empty body"), + OK("\r\n", "{ args=[], obj=\"\\n\" }"), + OK("\r\nHello world\r\n", "{ args=[], obj=\"Hello world\\n\\n\" }"), + OK("\r\nHello\r\nworld\r\n", "{ args=[], obj=\"Hello\\nworld\\n\\n\" }"), + OK("\r\nHello\r\n..\r\nworld\r\n", + "{ args=[], obj=\"Hello\\n.\\nworld\\n\\n\" }"), +}; +static const control_cmd_syntax_t no_args_one_obj_syntax = { + .min_args=0, .max_args=0, + .want_cmddata=true, +}; +static const parse_test_params_t parse_no_args_one_obj_params = + TESTPARAMS( no_args_one_obj_syntax, no_args_one_obj_tests ); + +static const parser_testcase_t no_args_kwargs_tests[] = { + OK("", "{ args=[] }"), + OK(" ", "{ args=[] }"), + OK("hello there=world", "{ args=[], { hello=\"\", there=\"world\" } }"), + OK("hello there=world today", + "{ args=[], { hello=\"\", there=\"world\", today=\"\" } }"), + ERR("=Foo", "Cannot parse keyword argument(s)"), +}; +static const control_cmd_syntax_t no_args_kwargs_syntax = { + .min_args=0, .max_args=0, + .accept_keywords=true, + .kvline_flags=KV_OMIT_VALS +}; +static const parse_test_params_t parse_no_args_kwargs_params = + TESTPARAMS( no_args_kwargs_syntax, no_args_kwargs_tests ); + +static const char *one_arg_kwargs_allow_keywords[] = { + "Hello", "world", NULL +}; +static const parser_testcase_t one_arg_kwargs_tests[] = { + ERR("", "Need at least 1 argument(s)"), + OK("Hi", "{ args=[ \"Hi\" ] }"), + ERR("hello there=world", "Unrecognized keyword argument \"there\""), + OK("Hi HELLO=foo", "{ args=[ \"Hi\" ], { HELLO=\"foo\" } }"), + OK("Hi world=\"bar baz\" hello ", + "{ args=[ \"Hi\" ], { world=\"bar baz\", hello=\"\" } }"), +}; +static const control_cmd_syntax_t one_arg_kwargs_syntax = { + .min_args=1, .max_args=1, + .accept_keywords=true, + .allowed_keywords=one_arg_kwargs_allow_keywords, + .kvline_flags=KV_OMIT_VALS|KV_QUOTED, +}; +static const parse_test_params_t parse_one_arg_kwargs_params = + TESTPARAMS( one_arg_kwargs_syntax, one_arg_kwargs_tests ); + static void test_add_onion_helper_keyarg_v3(void *arg) { @@ -1546,7 +1723,7 @@ test_current_time(void *arg) static size_t n_nodelist_get_list = 0; static smartlist_t *nodes = NULL; -static smartlist_t * +static const smartlist_t * mock_nodelist_get_list(void) { n_nodelist_get_list++; @@ -1617,7 +1794,15 @@ test_getinfo_md_all(void *arg) return; } +#define PARSER_TEST(type) \ + { "parse/" #type, test_controller_parse_cmd, 0, &passthrough_setup, \ + (void*)&parse_ ## type ## _params } + struct testcase_t controller_tests[] = { + PARSER_TEST(one_to_three), + PARSER_TEST(no_args_one_obj), + PARSER_TEST(no_args_kwargs), + PARSER_TEST(one_arg_kwargs), { "add_onion_helper_keyarg_v2", test_add_onion_helper_keyarg_v2, 0, NULL, NULL }, { "add_onion_helper_keyarg_v3", test_add_onion_helper_keyarg_v3, 0, diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index a5c17b3e6a..08dfb6bcdd 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -2075,7 +2075,7 @@ test_crypto_curve25519_encode(void *arg) curve25519_secret_key_generate(&seckey, 0); curve25519_public_key_generate(&key1, &seckey); - tt_int_op(0, OP_EQ, curve25519_public_to_base64(buf, &key1)); + curve25519_public_to_base64(buf, &key1); tt_int_op(CURVE25519_BASE64_PADDED_LEN, OP_EQ, strlen(buf)); tt_int_op(0, OP_EQ, curve25519_public_from_base64(&key2, buf)); @@ -2455,13 +2455,13 @@ test_crypto_ed25519_encode(void *arg) /* Test roundtrip. */ tt_int_op(0, OP_EQ, ed25519_keypair_generate(&kp, 0)); - tt_int_op(0, OP_EQ, ed25519_public_to_base64(buf, &kp.pubkey)); + ed25519_public_to_base64(buf, &kp.pubkey); tt_int_op(ED25519_BASE64_LEN, OP_EQ, strlen(buf)); tt_int_op(0, OP_EQ, ed25519_public_from_base64(&pk, buf)); tt_mem_op(kp.pubkey.pubkey, OP_EQ, pk.pubkey, ED25519_PUBKEY_LEN); tt_int_op(0, OP_EQ, ed25519_sign(&sig1, (const uint8_t*)"ABC", 3, &kp)); - tt_int_op(0, OP_EQ, ed25519_signature_to_base64(buf, &sig1)); + ed25519_signature_to_base64(buf, &sig1); tt_int_op(0, OP_EQ, ed25519_signature_from_base64(&sig2, buf)); tt_mem_op(sig1.sig, OP_EQ, sig2.sig, ED25519_SIG_LEN); diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 6518977b6f..17d6db1e4d 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -995,9 +995,7 @@ test_dir_formats_rsa_ed25519(void *arg) smartlist_add_strdup(chunks, "master-key-ed25519 "); { char k[ED25519_BASE64_LEN+1]; - tt_int_op(ed25519_public_to_base64(k, - &r2->cache_info.signing_key_cert->signing_key), - OP_GE, 0); + ed25519_public_to_base64(k, &r2->cache_info.signing_key_cert->signing_key); smartlist_add_strdup(chunks, k); smartlist_add_strdup(chunks, "\n"); } diff --git a/src/test/test_dir_handle_get.c b/src/test/test_dir_handle_get.c index c3a17e7309..e57bd02584 100644 --- a/src/test/test_dir_handle_get.c +++ b/src/test/test_dir_handle_get.c @@ -2526,7 +2526,7 @@ test_dir_handle_get_status_vote_next_bandwidth(void* data) /* Check cache lifetime */ char expbuf[RFC1123_TIME_LEN+1]; - time_t now = time(NULL); + time_t now = approx_time(); /* BANDWIDTH_CACHE_LIFETIME is defined in dircache.c. */ format_rfc1123_time(expbuf, (time_t)(now + 30*60)); char *expires = NULL; diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 729795b674..c43b21c673 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -67,7 +67,7 @@ static networkstatus_t *dummy_consensus = NULL; static smartlist_t *big_fake_net_nodes = NULL; -static smartlist_t * +static const smartlist_t * bfn_mock_nodelist_get_list(void) { return big_fake_net_nodes; @@ -197,6 +197,7 @@ big_fake_network_setup(const struct testcase_t *testcase) n->md->exit_policy = parse_short_policy("accept 443"); } + n->nodelist_idx = smartlist_len(big_fake_net_nodes); smartlist_add(big_fake_net_nodes, n); } diff --git a/src/test/test_extorport.c b/src/test/test_extorport.c index f5d16af921..cfdd11d161 100644 --- a/src/test/test_extorport.c +++ b/src/test/test_extorport.c @@ -18,6 +18,7 @@ #include "test/test.h" #include "test/test_helpers.h" +#include "test/rng_test_helpers.h" #ifdef HAVE_SYS_STAT_H #include <sys/stat.h> @@ -303,16 +304,6 @@ test_ext_or_cookie_auth(void *arg) } static void -crypto_rand_return_tse_str(char *to, size_t n) -{ - if (n != 32) { - TT_FAIL(("Asked for %d bytes, not 32", (int)n)); - return; - } - memcpy(to, "te road There is always another ", 32); -} - -static void test_ext_or_cookie_auth_testvec(void *arg) { char *reply=NULL, *client_hash=NULL; @@ -326,7 +317,7 @@ test_ext_or_cookie_auth_testvec(void *arg) memcpy(ext_or_auth_cookie, "Gliding wrapt in a brown mantle," , 32); ext_or_auth_cookie_is_set = 1; - MOCK(crypto_rand, crypto_rand_return_tse_str); + testing_enable_prefilled_rng("te road There is always another ", 32); tt_int_op(0, OP_EQ, handle_client_auth_nonce(client_nonce, 32, &client_hash, &reply, @@ -351,7 +342,7 @@ test_ext_or_cookie_auth_testvec(void *arg) "33b3cd77ff79bd80c2074bbf438119a2"); done: - UNMOCK(crypto_rand); + testing_disable_prefilled_rng(); tor_free(reply); tor_free(client_hash); tor_free(mem_op_hex_tmp); @@ -414,9 +405,9 @@ do_ext_or_handshake(or_connection_t *conn) CONTAINS("\x01\x00", 2); WRITE("\x01", 1); WRITE("But when I look ahead up the whi", 32); - MOCK(crypto_rand, crypto_rand_return_tse_str); + testing_enable_prefilled_rng("te road There is always another ", 32); tt_int_op(0, OP_EQ, connection_ext_or_process_inbuf(conn)); - UNMOCK(crypto_rand); + testing_disable_prefilled_rng(); tt_int_op(TO_CONN(conn)->state, OP_EQ, EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH); CONTAINS("\xec\x80\xed\x6e\x54\x6d\x3b\x36\xfd\xfc\x22\xfe\x13\x15\x41\x6b" @@ -481,9 +472,9 @@ test_ext_or_handshake(void *arg) tt_int_op(0, OP_EQ, connection_ext_or_process_inbuf(conn)); /* send the rest of the nonce. */ WRITE("ahead up the whi", 16); - MOCK(crypto_rand, crypto_rand_return_tse_str); + testing_enable_prefilled_rng("te road There is always another ", 32); tt_int_op(0, OP_EQ, connection_ext_or_process_inbuf(conn)); - UNMOCK(crypto_rand); + testing_disable_prefilled_rng(); /* We should get the right reply from the server. */ CONTAINS("\xec\x80\xed\x6e\x54\x6d\x3b\x36\xfd\xfc\x22\xfe\x13\x15\x41\x6b" "\x02\x9f\x1a\xde\x76\x10\xd9\x10\x87\x8b\x62\xee\xb7\x40\x38\x21" @@ -582,7 +573,7 @@ test_ext_or_handshake(void *arg) done: UNMOCK(connection_write_to_buf_impl_); - UNMOCK(crypto_rand); + testing_disable_prefilled_rng(); if (conn) connection_free_minimal(TO_CONN(conn)); #undef CONTAINS diff --git a/src/test/test_helpers.c b/src/test/test_helpers.c index 13de1e154b..489c257761 100644 --- a/src/test/test_helpers.c +++ b/src/test/test_helpers.c @@ -78,7 +78,7 @@ helper_setup_fake_routerlist(void) { int retval; routerlist_t *our_routerlist = NULL; - smartlist_t *our_nodelist = NULL; + const smartlist_t *our_nodelist = NULL; /* Read the file that contains our test descriptors. */ diff --git a/src/test/test_hs.c b/src/test/test_hs.c index aeb3387471..5d3327c777 100644 --- a/src/test/test_hs.c +++ b/src/test/test_hs.c @@ -323,6 +323,16 @@ test_hs_desc_event(void *arg) tt_str_op(received_msg,OP_EQ, expected_msg); tor_free(received_msg); + /* test HSDir rate limited */ + rend_query.auth_type = REND_NO_AUTH; + control_event_hsv2_descriptor_failed(&rend_query.base_, NULL, + "QUERY_RATE_LIMITED"); + expected_msg = "650 HS_DESC FAILED "STR_HS_ADDR" NO_AUTH " \ + "UNKNOWN REASON=QUERY_RATE_LIMITED\r\n"; + tt_assert(received_msg); + tt_str_op(received_msg,OP_EQ, expected_msg); + tor_free(received_msg); + /* Test invalid content with no HSDir fingerprint. */ char *exp_msg; control_event_hs_descriptor_content(rend_query.onion_address, diff --git a/src/test/test_hs_cache.c b/src/test/test_hs_cache.c index 9182829116..2187c2be39 100644 --- a/src/test/test_hs_cache.c +++ b/src/test/test_hs_cache.c @@ -238,14 +238,13 @@ helper_fetch_desc_from_hsdir(const ed25519_public_key_t *blinded_key) { char hsdir_cache_key[ED25519_BASE64_LEN+1]; - retval = ed25519_public_to_base64(hsdir_cache_key, - blinded_key); - tt_int_op(retval, OP_EQ, 0); + ed25519_public_to_base64(hsdir_cache_key, blinded_key); tor_asprintf(&hsdir_query_str, GET("/tor/hs/3/%s"), hsdir_cache_key); } /* Simulate an HTTP GET request to the HSDir */ conn = dir_connection_new(AF_INET); + tt_assert(conn); tor_addr_from_ipv4h(&conn->base_.addr, 0x7f000001); TO_CONN(conn)->linked = 1;/* Pretend the conn is encrypted :) */ retval = directory_handle_command_get(conn, hsdir_query_str, diff --git a/src/test/test_hs_common.c b/src/test/test_hs_common.c index eb7f3bfbb0..bb41f1f870 100644 --- a/src/test/test_hs_common.c +++ b/src/test/test_hs_common.c @@ -275,7 +275,7 @@ test_start_time_of_next_time_period(void *arg) static void cleanup_nodelist(void) { - smartlist_t *nodelist = nodelist_get_list(); + const smartlist_t *nodelist = nodelist_get_list(); SMARTLIST_FOREACH_BEGIN(nodelist, node_t *, node) { tor_free(node->md); node->md = NULL; diff --git a/src/test/test_hs_control.c b/src/test/test_hs_control.c index 481ef1eb39..7cedc987bb 100644 --- a/src/test/test_hs_control.c +++ b/src/test/test_hs_control.c @@ -107,8 +107,7 @@ test_hs_desc_event(void *arg) memset(&blinded_pk, 'B', sizeof(blinded_pk)); memset(&hsdir_rs, 0, sizeof(hsdir_rs)); memcpy(hsdir_rs.identity_digest, HSDIR_EXIST_ID, DIGEST_LEN); - ret = ed25519_public_to_base64(base64_blinded_pk, &blinded_pk); - tt_int_op(ret, OP_EQ, 0); + ed25519_public_to_base64(base64_blinded_pk, &blinded_pk); memcpy(&ident.identity_pk, &identity_kp.pubkey, sizeof(ed25519_public_key_t)); memcpy(&ident.blinded_pk, &blinded_pk, sizeof(blinded_pk)); diff --git a/src/test/test_hs_descriptor.c b/src/test/test_hs_descriptor.c index 09c6c3e700..5a3fd46dbe 100644 --- a/src/test/test_hs_descriptor.c +++ b/src/test/test_hs_descriptor.c @@ -21,6 +21,7 @@ #include "test/hs_test_helpers.h" #include "test/test_helpers.h" #include "test/log_test_helpers.h" +#include "test/rng_test_helpers.h" #ifdef HAVE_CFLAG_WOVERLENGTH_STRINGS DISABLE_GCC_WARNING(overlength-strings) @@ -30,13 +31,6 @@ DISABLE_GCC_WARNING(overlength-strings) #include "test_hs_descriptor.inc" ENABLE_GCC_WARNING(overlength-strings) -/* Mock function to fill all bytes with 1 */ -static void -mock_crypto_strongest_rand(uint8_t *out, size_t out_len) -{ - memset(out, 1, out_len); -} - /* Test certificate encoding put in a descriptor. */ static void test_cert_encoding(void *arg) @@ -739,8 +733,7 @@ test_desc_signature(void *arg) ret = ed25519_sign_prefixed(&sig, (const uint8_t *) data, strlen(data), "Tor onion service descriptor sig v3", &kp); tt_int_op(ret, OP_EQ, 0); - ret = ed25519_signature_to_base64(sig_b64, &sig); - tt_int_op(ret, OP_EQ, 0); + ed25519_signature_to_base64(sig_b64, &sig); /* Build the descriptor that should be valid. */ tor_asprintf(&desc, "%ssignature %s\n", data, sig_b64); ret = desc_sig_is_valid(sig_b64, &kp.pubkey, desc, strlen(desc)); @@ -800,7 +793,7 @@ test_build_authorized_client(void *arg) client_pubkey_b16, strlen(client_pubkey_b16)); - MOCK(crypto_strongest_rand_, mock_crypto_strongest_rand); + testing_enable_prefilled_rng("\x01", 1); hs_desc_build_authorized_client(subcredential, &client_auth_pk, &auth_ephemeral_sk, @@ -816,7 +809,7 @@ test_build_authorized_client(void *arg) done: tor_free(desc_client); tor_free(mem_op_hex_tmp); - UNMOCK(crypto_strongest_rand_); + testing_disable_prefilled_rng(); } struct testcase_t hs_descriptor[] = { diff --git a/src/test/test_periodic_event.c b/src/test/test_periodic_event.c index ebac20838f..961a8be698 100644 --- a/src/test/test_periodic_event.c +++ b/src/test/test_periodic_event.c @@ -51,12 +51,13 @@ test_pe_initialize(void *arg) * need to run the main loop and then wait for a second delaying the unit * tests. Instead, we'll test the callback work indepedently elsewhere. */ initialize_periodic_events(); + periodic_events_connect_all(); set_network_participation(false); rescan_periodic_events(get_options()); /* Validate that all events have been set up. */ - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; tt_assert(item->ev); tt_assert(item->fn); tt_u64_op(item->last_action_time, OP_EQ, 0); @@ -89,8 +90,8 @@ test_pe_launch(void *arg) /* Hack: We'll set a dumb fn() of each events so they don't get called when * dispatching them. We just want to test the state of the callbacks, not * the whole code path. */ - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; item->fn = dumb_event_fn; } @@ -110,14 +111,15 @@ test_pe_launch(void *arg) #endif initialize_periodic_events(); + periodic_events_connect_all(); /* Now that we've initialized, rescan the list to launch. */ periodic_events_on_new_options(options); int mask = PERIODIC_EVENT_ROLE_CLIENT|PERIODIC_EVENT_ROLE_ALL| PERIODIC_EVENT_ROLE_NET_PARTICIPANT; - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; int should_be_enabled = !!(item->roles & mask); tt_int_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled); // enabled or not, the event has not yet been run. @@ -134,8 +136,8 @@ test_pe_launch(void *arg) PERIODIC_EVENT_ROLE_RELAY|PERIODIC_EVENT_ROLE_DIRSERVER| PERIODIC_EVENT_ROLE_ALL|PERIODIC_EVENT_ROLE_NET_PARTICIPANT); - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; /* Only Client role should be disabled. */ if (item->roles == PERIODIC_EVENT_ROLE_CLIENT) { tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0); @@ -156,8 +158,8 @@ test_pe_launch(void *arg) set_network_participation(false); periodic_events_on_new_options(options); - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; int should_be_enabled = (item->roles & PERIODIC_EVENT_ROLE_ALL) && !(item->flags & PERIODIC_EVENT_FLAG_NEED_NET); tt_int_op(periodic_event_is_enabled(item), OP_EQ, should_be_enabled); @@ -177,8 +179,8 @@ test_pe_launch(void *arg) * trigger a rescan of the event disabling the HS service event. */ to_remove = &service; - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; tt_int_op(periodic_event_is_enabled(item), OP_EQ, (item->roles != PERIODIC_EVENT_ROLE_CONTROLEV)); } @@ -300,12 +302,13 @@ test_pe_hs_service(void *arg) consider_hibernation(time(NULL)); /* Initialize the events so we can enable them */ initialize_periodic_events(); + periodic_events_connect_all(); /* Hack: We'll set a dumb fn() of each events so they don't get called when * dispatching them. We just want to test the state of the callbacks, not * the whole code path. */ - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; item->fn = dumb_event_fn; } @@ -318,8 +321,8 @@ test_pe_hs_service(void *arg) * trigger a rescan of the event disabling the HS service event. */ to_remove = &service; - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; if (item->roles & PERIODIC_EVENT_ROLE_HS_SERVICE) { tt_int_op(periodic_event_is_enabled(item), OP_EQ, 1); } @@ -329,8 +332,8 @@ test_pe_hs_service(void *arg) /* Remove the service from the global map, it should trigger a rescan and * disable the HS service events. */ remove_service(get_hs_service_map(), &service); - for (int i = 0; periodic_events[i].name; ++i) { - periodic_event_item_t *item = &periodic_events[i]; + for (int i = 0; mainloop_periodic_events[i].name; ++i) { + periodic_event_item_t *item = &mainloop_periodic_events[i]; if (item->roles & PERIODIC_EVENT_ROLE_HS_SERVICE) { tt_int_op(periodic_event_is_enabled(item), OP_EQ, 0); } diff --git a/src/test/test_prob_distr.c b/src/test/test_prob_distr.c index 37cfdae7d9..747c3d98e6 100644 --- a/src/test/test_prob_distr.c +++ b/src/test/test_prob_distr.c @@ -33,6 +33,7 @@ #include "lib/math/prob_distr.h" #include "lib/math/fp.h" #include "lib/crypt_ops/crypto_rand.h" +#include "test/rng_test_helpers.h" #include <float.h> #include <math.h> @@ -1117,49 +1118,14 @@ test_psi_dist_sample(const struct dist *dist) } } -/* This is the seed of the deterministic randomness */ -static uint8_t rng_seed[16]; -static crypto_xof_t *rng_xof = NULL; - -/** Initialize the seed of the deterministic randomness. */ -static void -init_deterministic_rand(void) -{ - crypto_rand((char*)rng_seed, sizeof(rng_seed)); - crypto_xof_free(rng_xof); - rng_xof = crypto_xof_new(); - crypto_xof_add_bytes(rng_xof, rng_seed, sizeof(rng_seed)); -} - -static void -teardown_deterministic_rand(void) -{ - crypto_xof_free(rng_xof); -} - static void dump_seed(void) { printf("\n" "NOTE: This is a stochastic test, and we expect it to fail from\n" "time to time, with some low probability. If you see it fail more\n" - "than one trial in 100, though, please tell us.\n\n" - "Seed: %s\n", - hex_str((const char*)rng_seed, sizeof(rng_seed))); -} - -/** Produce deterministic randomness for the stochastic tests using the global - * deterministic_rand_counter seed - * - * This function produces deterministic data over multiple calls iff it's - * called in the same call order with the same 'n' parameter (which is the - * case for the psi test). If not, outputs will deviate. */ -static void -crypto_rand_deterministic(char *out, size_t n) -{ - /* Use a XOF to squeeze bytes out of that silly counter */ - tor_assert(rng_xof); - crypto_xof_squeeze_bytes(rng_xof, (uint8_t*)out, n); + "than one trial in 100, though, please tell us.\n\n"); + testing_dump_reproducible_rng_seed(); } static void @@ -1199,8 +1165,7 @@ test_stochastic_uniform(void *arg) }; bool ok = true, tests_failed = true; - init_deterministic_rand(); - MOCK(crypto_rand, crypto_rand_deterministic); + testing_enable_reproducible_rng(); ok &= test_psi_dist_sample(&uniform01.base); ok &= test_psi_dist_sample(&uniform_pos.base); @@ -1217,8 +1182,7 @@ test_stochastic_uniform(void *arg) if (tests_failed) { dump_seed(); } - teardown_deterministic_rand(); - UNMOCK(crypto_rand); + testing_disable_reproducible_rng(); } static bool @@ -1288,8 +1252,7 @@ test_stochastic_genpareto(void *arg) bool tests_failed = true; (void) arg; - init_deterministic_rand(); - MOCK(crypto_rand, crypto_rand_deterministic); + testing_enable_reproducible_rng(); ok = test_stochastic_genpareto_impl(0, 1, -0.25); tt_assert(ok); @@ -1312,8 +1275,7 @@ test_stochastic_genpareto(void *arg) if (tests_failed) { dump_seed(); } - teardown_deterministic_rand(); - UNMOCK(crypto_rand); + testing_disable_reproducible_rng(); } static void @@ -1324,8 +1286,7 @@ test_stochastic_geometric(void *arg) (void) arg; - init_deterministic_rand(); - MOCK(crypto_rand, crypto_rand_deterministic); + testing_enable_reproducible_rng(); ok = test_stochastic_geometric_impl(0.1); tt_assert(ok); @@ -1342,8 +1303,7 @@ test_stochastic_geometric(void *arg) if (tests_failed) { dump_seed(); } - teardown_deterministic_rand(); - UNMOCK(crypto_rand); + testing_disable_reproducible_rng(); } static void @@ -1353,8 +1313,7 @@ test_stochastic_logistic(void *arg) bool tests_failed = true; (void) arg; - init_deterministic_rand(); - MOCK(crypto_rand, crypto_rand_deterministic); + testing_enable_reproducible_rng(); ok = test_stochastic_logistic_impl(0, 1); tt_assert(ok); @@ -1371,8 +1330,7 @@ test_stochastic_logistic(void *arg) if (tests_failed) { dump_seed(); } - teardown_deterministic_rand(); - UNMOCK(crypto_rand); + testing_disable_reproducible_rng(); } static void @@ -1382,8 +1340,7 @@ test_stochastic_log_logistic(void *arg) bool tests_failed = true; (void) arg; - init_deterministic_rand(); - MOCK(crypto_rand, crypto_rand_deterministic); + testing_enable_reproducible_rng(); ok = test_stochastic_log_logistic_impl(1, 1); tt_assert(ok); @@ -1400,8 +1357,7 @@ test_stochastic_log_logistic(void *arg) if (tests_failed) { dump_seed(); } - teardown_deterministic_rand(); - UNMOCK(crypto_rand); + testing_disable_reproducible_rng(); } static void @@ -1411,8 +1367,7 @@ test_stochastic_weibull(void *arg) bool tests_failed = true; (void) arg; - init_deterministic_rand(); - MOCK(crypto_rand, crypto_rand_deterministic); + testing_enable_reproducible_rng(); ok = test_stochastic_weibull_impl(1, 0.5); tt_assert(ok); @@ -1431,7 +1386,7 @@ test_stochastic_weibull(void *arg) if (tests_failed) { dump_seed(); } - teardown_deterministic_rand(); + testing_disable_reproducible_rng(); UNMOCK(crypto_rand); } diff --git a/src/test/test_routerset.c b/src/test/test_routerset.c index c45f0e1595..cc73e6c20a 100644 --- a/src/test/test_routerset.c +++ b/src/test/test_routerset.c @@ -1765,7 +1765,7 @@ NS(node_get_by_nickname)(const char *nickname, unsigned flags) * Structural test for routerset_get_all_nodes, when the nodelist has no nodes. */ -NS_DECL(smartlist_t *, nodelist_get_list, (void)); +NS_DECL(const smartlist_t *, nodelist_get_list, (void)); static smartlist_t *NS(mock_smartlist); @@ -1795,7 +1795,7 @@ NS(test_main)(void *arg) ; } -smartlist_t * +const smartlist_t * NS(nodelist_get_list)(void) { CALLED(nodelist_get_list)++; @@ -1811,7 +1811,7 @@ NS(nodelist_get_list)(void) * the running_only flag is set, but the nodes are not running. */ -NS_DECL(smartlist_t *, nodelist_get_list, (void)); +NS_DECL(const smartlist_t *, nodelist_get_list, (void)); static smartlist_t *NS(mock_smartlist); static node_t NS(mock_node); @@ -1844,7 +1844,7 @@ NS(test_main)(void *arg) ; } -smartlist_t * +const smartlist_t * NS(nodelist_get_list)(void) { CALLED(nodelist_get_list)++; diff --git a/src/tools/tor-resolve.c b/src/tools/tor-resolve.c index 98b3a4a74c..5d97696c18 100644 --- a/src/tools/tor-resolve.c +++ b/src/tools/tor-resolve.c @@ -424,6 +424,7 @@ do_resolve(const char *hostname, if (parsed < 2) { log_err(LD_NET, "Failed to parse SOCKS5 method selection " "message"); + socks5_server_method_free(m); goto err; } |