[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [tor/master] Merge branch 'maint-0.3.4'
commit 61e080e24a741a6b6978da272b0372a8391028db
Merge: 88bb80d5f a7bd20eba
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date: Wed Jul 18 15:09:28 2018 -0400
Merge branch 'maint-0.3.4'
changes/bug26485 | 4 ++++
src/app/config/config.c | 8 +++++++
src/feature/dirauth/dirvote.c | 49 +++++++++++++++++++++++++++++++++++----
src/feature/dircache/dirserv.h | 2 +-
src/test/test_dir.c | 52 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 110 insertions(+), 5 deletions(-)
diff --cc src/app/config/config.c
index 1f4f099be,000000000..4f52fa089
mode 100644,000000..100644
--- a/src/app/config/config.c
+++ b/src/app/config/config.c
@@@ -1,8504 -1,0 +1,8512 @@@
+
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file config.c
+ * \brief Code to interpret the user's configuration of Tor.
+ *
+ * This module handles torrc configuration file, including parsing it,
+ * combining it with torrc.defaults and the command line, allowing
+ * user changes to it (via editing and SIGHUP or via the control port),
+ * writing it back to disk (because of SAVECONF from the control port),
+ * and -- most importantly, acting on it.
+ *
+ * The module additionally has some tools for manipulating and
+ * inspecting values that are calculated as a result of the
+ * configured options.
+ *
+ * <h3>How to add new options</h3>
+ *
+ * To add new items to the torrc, there are a minimum of three places to edit:
+ * <ul>
+ * <li>The or_options_t structure in or.h, where the options are stored.
+ * <li>The option_vars_ array below in this module, which configures
+ * the names of the torrc options, their types, their multiplicities,
+ * and their mappings to fields in or_options_t.
+ * <li>The manual in doc/tor.1.txt, to document what the new option
+ * is, and how it works.
+ * </ul>
+ *
+ * Additionally, you might need to edit these places too:
+ * <ul>
+ * <li>options_validate() below, in case you want to reject some possible
+ * values of the new configuration option.
+ * <li>options_transition_allowed() below, in case you need to
+ * forbid some or all changes in the option while Tor is
+ * running.
+ * <li>options_transition_affects_workers(), in case changes in the option
+ * might require Tor to relaunch or reconfigure its worker threads.
+ * <li>options_transition_affects_descriptor(), in case changes in the
+ * option might require a Tor relay to build and publish a new server
+ * descriptor.
+ * <li>options_act() and/or options_act_reversible(), in case there's some
+ * action that needs to be taken immediately based on the option's
+ * value.
+ * </ul>
+ *
+ * <h3>Changing the value of an option</h3>
+ *
+ * Because of the SAVECONF command from the control port, it's a bad
+ * idea to change the value of any user-configured option in the
+ * or_options_t. If you want to sometimes do this anyway, we recommend
+ * that you create a secondary field in or_options_t; that you have the
+ * user option linked only to the secondary field; that you use the
+ * secondary field to initialize the one that Tor actually looks at; and that
+ * you use the one Tor looks as the one that you modify.
+ **/
+
+#define CONFIG_PRIVATE
+#include "core/or/or.h"
+#include "feature/client/bridges.h"
+#include "feature/client/addressmap.h"
+#include "core/or/channel.h"
+#include "core/or/circuitbuild.h"
+#include "core/or/circuitlist.h"
+#include "core/or/circuitmux.h"
+#include "core/or/circuitmux_ewma.h"
+#include "core/or/circuitstats.h"
+#include "lib/compress/compress.h"
+#include "app/config/config.h"
+#include "lib/encoding/confline.h"
+#include "core/mainloop/connection.h"
+#include "core/or/connection_edge.h"
+#include "core/or/connection_or.h"
+#include "feature/dircache/consdiffmgr.h"
+#include "feature/control/control.h"
+#include "app/config/confparse.h"
+#include "core/mainloop/cpuworker.h"
+#include "lib/crypt_ops/crypto_rand.h"
+#include "lib/crypt_ops/crypto_util.h"
+#include "feature/dircache/dirserv.h"
+#include "feature/relay/dns.h"
+#include "core/or/dos.h"
+#include "feature/client/entrynodes.h"
+#include "lib/log/git_revision.h"
+#include "feature/stats/geoip.h"
+#include "feature/hibernate/hibernate.h"
+#include "core/mainloop/main.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/nodelist.h"
+#include "core/or/policies.h"
+#include "core/or/relay.h"
+#include "feature/rend/rendclient.h"
+#include "feature/rend/rendservice.h"
+#include "feature/hs/hs_config.h"
+#include "feature/stats/rephist.h"
+#include "feature/relay/router.h"
+#include "lib/sandbox/sandbox.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/nodelist/routerset.h"
+#include "core/or/scheduler.h"
+#include "app/config/statefile.h"
+#include "feature/client/transports.h"
+#include "feature/relay/ext_orport.h"
+#include "feature/dircommon/voting_schedule.h"
+#include "lib/net/resolve.h"
+#ifdef _WIN32
+#include <shlobj.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "lib/meminfo/meminfo.h"
+#include "lib/osinfo/uname.h"
+#include "lib/process/daemon.h"
+#include "lib/process/pidfile.h"
+#include "lib/process/restrict.h"
+#include "lib/process/setuid.h"
+#include "lib/process/subprocess.h"
+#include "lib/net/gethostname.h"
+#include "lib/thread/numcpus.h"
+
+#include "lib/encoding/keyval.h"
+#include "lib/fs/conffile.h"
+#include "lib/evloop/procmon.h"
+
+#include "feature/dirauth/dirvote.h"
+#include "feature/dirauth/mode.h"
+
+#include "core/or/connection_st.h"
+#include "core/or/port_cfg_st.h"
+
+#ifdef HAVE_SYSTEMD
+# if defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__)
+/* Systemd's use of gcc's __INCLUDE_LEVEL__ extension macro appears to confuse
+ * Coverity. Here's a kludge to unconfuse it.
+ */
+# define __INCLUDE_LEVEL__ 2
+#endif /* defined(__COVERITY__) && !defined(__INCLUDE_LEVEL__) */
+#include <systemd/sd-daemon.h>
+#endif /* defined(HAVE_SYSTEMD) */
+
+/* Prefix used to indicate a Unix socket in a FooPort configuration. */
+static const char unix_socket_prefix[] = "unix:";
+/* Prefix used to indicate a Unix socket with spaces in it, in a FooPort
+ * configuration. */
+static const char unix_q_socket_prefix[] = "unix:\"";
+
+/* limits for TCP send and recv buffer size used for constrained sockets */
+#define MIN_CONSTRAINED_TCP_BUFFER 2048
+#define MAX_CONSTRAINED_TCP_BUFFER 262144 /* 256k */
+
+/** macro to help with the bulk rename of *DownloadSchedule to
+ * *DowloadInitialDelay . */
+#define DOWNLOAD_SCHEDULE(name) \
+ { #name "DownloadSchedule", #name "DownloadInitialDelay", 0, 1 }
+
+/** A list of abbreviations and aliases to map command-line options, obsolete
+ * option names, or alternative option names, to their current values. */
+static config_abbrev_t option_abbrevs_[] = {
+ PLURAL(AuthDirBadDirCC),
+ PLURAL(AuthDirBadExitCC),
+ PLURAL(AuthDirInvalidCC),
+ PLURAL(AuthDirRejectCC),
+ PLURAL(EntryNode),
+ PLURAL(ExcludeNode),
+ PLURAL(Tor2webRendezvousPoint),
+ PLURAL(FirewallPort),
+ PLURAL(LongLivedPort),
+ PLURAL(HiddenServiceNode),
+ PLURAL(HiddenServiceExcludeNode),
+ PLURAL(NumCPU),
+ PLURAL(RendNode),
+ PLURAL(RecommendedPackage),
+ PLURAL(RendExcludeNode),
+ PLURAL(StrictEntryNode),
+ PLURAL(StrictExitNode),
+ PLURAL(StrictNode),
+ { "l", "Log", 1, 0},
+ { "AllowUnverifiedNodes", "AllowInvalidNodes", 0, 0},
+ { "AutomapHostSuffixes", "AutomapHostsSuffixes", 0, 0},
+ { "AutomapHostOnResolve", "AutomapHostsOnResolve", 0, 0},
+ { "BandwidthRateBytes", "BandwidthRate", 0, 0},
+ { "BandwidthBurstBytes", "BandwidthBurst", 0, 0},
+ { "DirFetchPostPeriod", "StatusFetchPeriod", 0, 0},
+ { "DirServer", "DirAuthority", 0, 0}, /* XXXX later, make this warn? */
+ { "MaxConn", "ConnLimit", 0, 1},
+ { "MaxMemInCellQueues", "MaxMemInQueues", 0, 0},
+ { "ORBindAddress", "ORListenAddress", 0, 0},
+ { "DirBindAddress", "DirListenAddress", 0, 0},
+ { "SocksBindAddress", "SocksListenAddress", 0, 0},
+ { "UseHelperNodes", "UseEntryGuards", 0, 0},
+ { "NumHelperNodes", "NumEntryGuards", 0, 0},
+ { "UseEntryNodes", "UseEntryGuards", 0, 0},
+ { "NumEntryNodes", "NumEntryGuards", 0, 0},
+ { "ResolvConf", "ServerDNSResolvConfFile", 0, 1},
+ { "SearchDomains", "ServerDNSSearchDomains", 0, 1},
+ { "ServerDNSAllowBrokenResolvConf", "ServerDNSAllowBrokenConfig", 0, 0},
+ { "PreferTunnelledDirConns", "PreferTunneledDirConns", 0, 0},
+ { "BridgeAuthoritativeDirectory", "BridgeAuthoritativeDir", 0, 0},
+ { "HashedControlPassword", "__HashedControlSessionPassword", 1, 0},
+ { "VirtualAddrNetwork", "VirtualAddrNetworkIPv4", 0, 0},
+ { "SocksSocketsGroupWritable", "UnixSocksGroupWritable", 0, 1},
+ { "_HSLayer2Nodes", "HSLayer2Nodes", 0, 1 },
+ { "_HSLayer3Nodes", "HSLayer3Nodes", 0, 1 },
+
+ DOWNLOAD_SCHEDULE(ClientBootstrapConsensusAuthority),
+ DOWNLOAD_SCHEDULE(ClientBootstrapConsensusAuthorityOnly),
+ DOWNLOAD_SCHEDULE(ClientBootstrapConsensusFallback),
+ DOWNLOAD_SCHEDULE(TestingBridge),
+ DOWNLOAD_SCHEDULE(TestingBridgeBootstrap),
+ DOWNLOAD_SCHEDULE(TestingClient),
+ DOWNLOAD_SCHEDULE(TestingClientConsensus),
+ DOWNLOAD_SCHEDULE(TestingServer),
+ DOWNLOAD_SCHEDULE(TestingServerConsensus),
+
+ { NULL, NULL, 0, 0},
+};
+
+/** dummy instance of or_options_t, used for type-checking its
+ * members with CONF_CHECK_VAR_TYPE. */
+DUMMY_TYPECHECK_INSTANCE(or_options_t);
+
+/** An entry for config_vars: "The option <b>name</b> has type
+ * CONFIG_TYPE_<b>conftype</b>, and corresponds to
+ * or_options_t.<b>member</b>"
+ */
+#define VAR(name,conftype,member,initvalue) \
+ { name, CONFIG_TYPE_ ## conftype, offsetof(or_options_t, member), \
+ initvalue CONF_TEST_MEMBERS(or_options_t, conftype, member) }
+/** As VAR, but the option name and member name are the same. */
+#define V(member,conftype,initvalue) \
+ VAR(#member, conftype, member, initvalue)
+/** An entry for config_vars: "The option <b>name</b> is obsolete." */
+#ifdef TOR_UNIT_TESTS
+#define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL, {.INT=NULL} }
+#else
+#define OBSOLETE(name) { name, CONFIG_TYPE_OBSOLETE, 0, NULL }
+#endif
+
+/**
+ * Macro to declare *Port options. Each one comes in three entries.
+ * For example, most users should use "SocksPort" to configure the
+ * socks port, but TorBrowser wants to use __SocksPort so that it
+ * isn't stored by SAVECONF. The SocksPortLines virtual option is
+ * used to query both options from the controller.
+ */
+#define VPORT(member) \
+ VAR(#member "Lines", LINELIST_V, member ## _lines, NULL), \
+ VAR(#member, LINELIST_S, member ## _lines, NULL), \
+ VAR("__" #member, LINELIST_S, member ## _lines, NULL)
+
+/** Array of configuration options. Until we disallow nonstandard
+ * abbreviations, order is significant, since the first matching option will
+ * be chosen first.
+ */
+static config_var_t option_vars_[] = {
+ V(AccountingMax, MEMUNIT, "0 bytes"),
+ VAR("AccountingRule", STRING, AccountingRule_option, "max"),
+ V(AccountingStart, STRING, NULL),
+ V(Address, STRING, NULL),
+ OBSOLETE("AllowDotExit"),
+ OBSOLETE("AllowInvalidNodes"),
+ V(AllowNonRFC953Hostnames, BOOL, "0"),
+ OBSOLETE("AllowSingleHopCircuits"),
+ OBSOLETE("AllowSingleHopExits"),
+ V(AlternateBridgeAuthority, LINELIST, NULL),
+ V(AlternateDirAuthority, LINELIST, NULL),
+ OBSOLETE("AlternateHSAuthority"),
+ V(AssumeReachable, BOOL, "0"),
+ OBSOLETE("AuthDirBadDir"),
+ OBSOLETE("AuthDirBadDirCCs"),
+ V(AuthDirBadExit, LINELIST, NULL),
+ V(AuthDirBadExitCCs, CSV, ""),
+ V(AuthDirInvalid, LINELIST, NULL),
+ V(AuthDirInvalidCCs, CSV, ""),
+ V(AuthDirFastGuarantee, MEMUNIT, "100 KB"),
+ V(AuthDirGuardBWGuarantee, MEMUNIT, "2 MB"),
+ V(AuthDirPinKeys, BOOL, "1"),
+ V(AuthDirReject, LINELIST, NULL),
+ V(AuthDirRejectCCs, CSV, ""),
+ OBSOLETE("AuthDirRejectUnlisted"),
+ OBSOLETE("AuthDirListBadDirs"),
+ V(AuthDirListBadExits, BOOL, "0"),
+ V(AuthDirMaxServersPerAddr, UINT, "2"),
+ OBSOLETE("AuthDirMaxServersPerAuthAddr"),
+ V(AuthDirHasIPv6Connectivity, BOOL, "0"),
+ VAR("AuthoritativeDirectory", BOOL, AuthoritativeDir, "0"),
+ V(AutomapHostsOnResolve, BOOL, "0"),
+ V(AutomapHostsSuffixes, CSV, ".onion,.exit"),
+ V(AvoidDiskWrites, BOOL, "0"),
+ V(BandwidthBurst, MEMUNIT, "1 GB"),
+ V(BandwidthRate, MEMUNIT, "1 GB"),
+ V(BridgeAuthoritativeDir, BOOL, "0"),
+ VAR("Bridge", LINELIST, Bridges, NULL),
+ V(BridgePassword, STRING, NULL),
+ V(BridgeRecordUsageByCountry, BOOL, "1"),
+ V(BridgeRelay, BOOL, "0"),
+ V(BridgeDistribution, STRING, NULL),
+ VAR("CacheDirectory", FILENAME, CacheDirectory_option, NULL),
+ V(CacheDirectoryGroupReadable, BOOL, "0"),
+ V(CellStatistics, BOOL, "0"),
+ V(PaddingStatistics, BOOL, "1"),
+ V(LearnCircuitBuildTimeout, BOOL, "1"),
+ V(CircuitBuildTimeout, INTERVAL, "0"),
+ OBSOLETE("CircuitIdleTimeout"),
+ V(CircuitsAvailableTimeout, INTERVAL, "0"),
+ V(CircuitStreamTimeout, INTERVAL, "0"),
+ V(CircuitPriorityHalflife, DOUBLE, "-1.0"), /*negative:'Use default'*/
+ V(ClientDNSRejectInternalAddresses, BOOL,"1"),
+ V(ClientOnly, BOOL, "0"),
+ V(ClientPreferIPv6ORPort, AUTOBOOL, "auto"),
+ V(ClientPreferIPv6DirPort, AUTOBOOL, "auto"),
+ V(ClientRejectInternalAddresses, BOOL, "1"),
+ V(ClientTransportPlugin, LINELIST, NULL),
+ V(ClientUseIPv6, BOOL, "0"),
+ V(ClientUseIPv4, BOOL, "1"),
+ V(ConsensusParams, STRING, NULL),
+ V(ConnLimit, UINT, "1000"),
+ V(ConnDirectionStatistics, BOOL, "0"),
+ V(ConstrainedSockets, BOOL, "0"),
+ V(ConstrainedSockSize, MEMUNIT, "8192"),
+ V(ContactInfo, STRING, NULL),
+ OBSOLETE("ControlListenAddress"),
+ VPORT(ControlPort),
+ V(ControlPortFileGroupReadable,BOOL, "0"),
+ V(ControlPortWriteToFile, FILENAME, NULL),
+ V(ControlSocket, LINELIST, NULL),
+ V(ControlSocketsGroupWritable, BOOL, "0"),
+ V(UnixSocksGroupWritable, BOOL, "0"),
+ V(CookieAuthentication, BOOL, "0"),
+ V(CookieAuthFileGroupReadable, BOOL, "0"),
+ V(CookieAuthFile, STRING, NULL),
+ V(CountPrivateBandwidth, BOOL, "0"),
+ VAR("DataDirectory", FILENAME, DataDirectory_option, NULL),
+ V(DataDirectoryGroupReadable, BOOL, "0"),
+ V(DisableOOSCheck, BOOL, "1"),
+ V(DisableNetwork, BOOL, "0"),
+ V(DirAllowPrivateAddresses, BOOL, "0"),
+ V(TestingAuthDirTimeToLearnReachability, INTERVAL, "30 minutes"),
+ OBSOLETE("DirListenAddress"),
+ V(DirPolicy, LINELIST, NULL),
+ VPORT(DirPort),
+ V(DirPortFrontPage, FILENAME, NULL),
+ VAR("DirReqStatistics", BOOL, DirReqStatistics_option, "1"),
+ VAR("DirAuthority", LINELIST, DirAuthorities, NULL),
+ V(DirCache, BOOL, "1"),
+ /* A DirAuthorityFallbackRate of 0.1 means that 0.5% of clients try an
+ * authority when all fallbacks are up, and 2% try an authority when 25% of
+ * fallbacks are down. (We rebuild the list when 25% of fallbacks are down).
+ *
+ * We want to reduce load on authorities, but keep these two figures within
+ * an order of magnitude, so there isn't too much load shifting to
+ * authorities when fallbacks go down. */
+ V(DirAuthorityFallbackRate, DOUBLE, "0.1"),
+ V(DisableAllSwap, BOOL, "0"),
+ V(DisableDebuggerAttachment, BOOL, "1"),
+ OBSOLETE("DisableIOCP"),
+ OBSOLETE("DisableV2DirectoryInfo_"),
+ OBSOLETE("DynamicDHGroups"),
+ VPORT(DNSPort),
+ OBSOLETE("DNSListenAddress"),
+ /* DoS circuit creation options. */
+ V(DoSCircuitCreationEnabled, AUTOBOOL, "auto"),
+ V(DoSCircuitCreationMinConnections, UINT, "0"),
+ V(DoSCircuitCreationRate, UINT, "0"),
+ V(DoSCircuitCreationBurst, UINT, "0"),
+ V(DoSCircuitCreationDefenseType, INT, "0"),
+ V(DoSCircuitCreationDefenseTimePeriod, INTERVAL, "0"),
+ /* DoS connection options. */
+ V(DoSConnectionEnabled, AUTOBOOL, "auto"),
+ V(DoSConnectionMaxConcurrentCount, UINT, "0"),
+ V(DoSConnectionDefenseType, INT, "0"),
+ /* DoS single hop client options. */
+ V(DoSRefuseSingleHopClientRendezvous, AUTOBOOL, "auto"),
+ V(DownloadExtraInfo, BOOL, "0"),
+ V(TestingEnableConnBwEvent, BOOL, "0"),
+ V(TestingEnableCellStatsEvent, BOOL, "0"),
+ OBSOLETE("TestingEnableTbEmptyEvent"),
+ V(EnforceDistinctSubnets, BOOL, "1"),
+ V(EntryNodes, ROUTERSET, NULL),
+ V(EntryStatistics, BOOL, "0"),
+ V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "10 minutes"),
+ V(ExcludeNodes, ROUTERSET, NULL),
+ V(ExcludeExitNodes, ROUTERSET, NULL),
+ OBSOLETE("ExcludeSingleHopRelays"),
+ V(ExitNodes, ROUTERSET, NULL),
+ V(ExitPolicy, LINELIST, NULL),
+ V(ExitPolicyRejectPrivate, BOOL, "1"),
+ V(ExitPolicyRejectLocalInterfaces, BOOL, "0"),
+ V(ExitPortStatistics, BOOL, "0"),
+ V(ExtendAllowPrivateAddresses, BOOL, "0"),
+ V(ExitRelay, AUTOBOOL, "auto"),
+ VPORT(ExtORPort),
+ V(ExtORPortCookieAuthFile, STRING, NULL),
+ V(ExtORPortCookieAuthFileGroupReadable, BOOL, "0"),
+ V(ExtraInfoStatistics, BOOL, "1"),
+ V(ExtendByEd25519ID, AUTOBOOL, "auto"),
+ V(FallbackDir, LINELIST, NULL),
+
+ V(UseDefaultFallbackDirs, BOOL, "1"),
+
+ OBSOLETE("FallbackNetworkstatusFile"),
+ V(FascistFirewall, BOOL, "0"),
+ V(FirewallPorts, CSV, ""),
+ OBSOLETE("FastFirstHopPK"),
+ V(FetchDirInfoEarly, BOOL, "0"),
+ V(FetchDirInfoExtraEarly, BOOL, "0"),
+ V(FetchServerDescriptors, BOOL, "1"),
+ V(FetchHidServDescriptors, BOOL, "1"),
+ V(FetchUselessDescriptors, BOOL, "0"),
+ OBSOLETE("FetchV2Networkstatus"),
+ V(GeoIPExcludeUnknown, AUTOBOOL, "auto"),
+#ifdef _WIN32
+ V(GeoIPFile, FILENAME, "<default>"),
+ V(GeoIPv6File, FILENAME, "<default>"),
+#else
+ V(GeoIPFile, FILENAME,
+ SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip"),
+ V(GeoIPv6File, FILENAME,
+ SHARE_DATADIR PATH_SEPARATOR "tor" PATH_SEPARATOR "geoip6"),
+#endif /* defined(_WIN32) */
+ OBSOLETE("Group"),
+ V(GuardLifetime, INTERVAL, "0 minutes"),
+ V(HardwareAccel, BOOL, "0"),
+ V(HeartbeatPeriod, INTERVAL, "6 hours"),
+ V(MainloopStats, BOOL, "0"),
+ V(AccelName, STRING, NULL),
+ V(AccelDir, FILENAME, NULL),
+ V(HashedControlPassword, LINELIST, NULL),
+ OBSOLETE("HidServDirectoryV2"),
+ VAR("HiddenServiceDir", LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceDirGroupReadable", LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceOptions",LINELIST_V, RendConfigLines, NULL),
+ VAR("HiddenServicePort", LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceVersion",LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceAuthorizeClient",LINELIST_S,RendConfigLines, NULL),
+ VAR("HiddenServiceAllowUnknownPorts",LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceMaxStreams",LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceMaxStreamsCloseCircuit",LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
+ V(HidServAuth, LINELIST, NULL),
+ OBSOLETE("CloseHSClientCircuitsImmediatelyOnTimeout"),
+ OBSOLETE("CloseHSServiceRendCircuitsImmediatelyOnTimeout"),
+ V(HiddenServiceSingleHopMode, BOOL, "0"),
+ V(HiddenServiceNonAnonymousMode,BOOL, "0"),
+ V(HTTPProxy, STRING, NULL),
+ V(HTTPProxyAuthenticator, STRING, NULL),
+ V(HTTPSProxy, STRING, NULL),
+ V(HTTPSProxyAuthenticator, STRING, NULL),
+ VPORT(HTTPTunnelPort),
+ V(IPv6Exit, BOOL, "0"),
+ VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL),
+ V(ServerTransportListenAddr, LINELIST, NULL),
+ V(ServerTransportOptions, LINELIST, NULL),
+ V(SigningKeyLifetime, INTERVAL, "30 days"),
+ V(Socks4Proxy, STRING, NULL),
+ V(Socks5Proxy, STRING, NULL),
+ V(Socks5ProxyUsername, STRING, NULL),
+ V(Socks5ProxyPassword, STRING, NULL),
+ VAR("KeyDirectory", FILENAME, KeyDirectory_option, NULL),
+ V(KeyDirectoryGroupReadable, BOOL, "0"),
+ VAR("HSLayer2Nodes", ROUTERSET, HSLayer2Nodes, NULL),
+ VAR("HSLayer3Nodes", ROUTERSET, HSLayer3Nodes, NULL),
+ V(KeepalivePeriod, INTERVAL, "5 minutes"),
+ V(KeepBindCapabilities, AUTOBOOL, "auto"),
+ VAR("Log", LINELIST, Logs, NULL),
+ V(LogMessageDomains, BOOL, "0"),
+ V(LogTimeGranularity, MSEC_INTERVAL, "1 second"),
+ V(TruncateLogFile, BOOL, "0"),
+ V(SyslogIdentityTag, STRING, NULL),
+ V(AndroidIdentityTag, STRING, NULL),
+ V(LongLivedPorts, CSV,
+ "21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300"),
+ VAR("MapAddress", LINELIST, AddressMap, NULL),
+ V(MaxAdvertisedBandwidth, MEMUNIT, "1 GB"),
+ V(MaxCircuitDirtiness, INTERVAL, "10 minutes"),
+ V(MaxClientCircuitsPending, UINT, "32"),
+ V(MaxConsensusAgeForDiffs, INTERVAL, "0 seconds"),
+ VAR("MaxMemInQueues", MEMUNIT, MaxMemInQueues_raw, "0"),
+ OBSOLETE("MaxOnionsPending"),
+ V(MaxOnionQueueDelay, MSEC_INTERVAL, "1750 msec"),
+ V(MaxUnparseableDescSizeToLog, MEMUNIT, "10 MB"),
+ V(MinMeasuredBWsForAuthToIgnoreAdvertised, INT, "500"),
+ VAR("MyFamily", LINELIST, MyFamily_lines, NULL),
+ V(NewCircuitPeriod, INTERVAL, "30 seconds"),
+ OBSOLETE("NamingAuthoritativeDirectory"),
+ OBSOLETE("NATDListenAddress"),
+ VPORT(NATDPort),
+ V(Nickname, STRING, NULL),
+ OBSOLETE("PredictedPortsRelevanceTime"),
+ OBSOLETE("WarnUnsafeSocks"),
+ VAR("NodeFamily", LINELIST, NodeFamilies, NULL),
+ V(NoExec, BOOL, "0"),
+ V(NumCPUs, UINT, "0"),
+ V(NumDirectoryGuards, UINT, "0"),
+ V(NumEntryGuards, UINT, "0"),
+ V(NumPrimaryGuards, UINT, "0"),
+ V(OfflineMasterKey, BOOL, "0"),
+ OBSOLETE("ORListenAddress"),
+ VPORT(ORPort),
+ V(OutboundBindAddress, LINELIST, NULL),
+ V(OutboundBindAddressOR, LINELIST, NULL),
+ V(OutboundBindAddressExit, LINELIST, NULL),
+
+ OBSOLETE("PathBiasDisableRate"),
+ V(PathBiasCircThreshold, INT, "-1"),
+ V(PathBiasNoticeRate, DOUBLE, "-1"),
+ V(PathBiasWarnRate, DOUBLE, "-1"),
+ V(PathBiasExtremeRate, DOUBLE, "-1"),
+ V(PathBiasScaleThreshold, INT, "-1"),
+ OBSOLETE("PathBiasScaleFactor"),
+ OBSOLETE("PathBiasMultFactor"),
+ V(PathBiasDropGuards, AUTOBOOL, "0"),
+ OBSOLETE("PathBiasUseCloseCounts"),
+
+ V(PathBiasUseThreshold, INT, "-1"),
+ V(PathBiasNoticeUseRate, DOUBLE, "-1"),
+ V(PathBiasExtremeUseRate, DOUBLE, "-1"),
+ V(PathBiasScaleUseThreshold, INT, "-1"),
+
+ V(PathsNeededToBuildCircuits, DOUBLE, "-1"),
+ V(PerConnBWBurst, MEMUNIT, "0"),
+ V(PerConnBWRate, MEMUNIT, "0"),
+ V(PidFile, STRING, NULL),
+ V(TestingTorNetwork, BOOL, "0"),
+ V(TestingMinExitFlagThreshold, MEMUNIT, "0"),
+ V(TestingMinFastFlagThreshold, MEMUNIT, "0"),
+
+ V(TestingLinkCertLifetime, INTERVAL, "2 days"),
+ V(TestingAuthKeyLifetime, INTERVAL, "2 days"),
+ V(TestingLinkKeySlop, INTERVAL, "3 hours"),
+ V(TestingAuthKeySlop, INTERVAL, "3 hours"),
+ V(TestingSigningKeySlop, INTERVAL, "1 day"),
+
+ V(OptimisticData, AUTOBOOL, "auto"),
+ OBSOLETE("PortForwarding"),
+ OBSOLETE("PortForwardingHelper"),
+ OBSOLETE("PreferTunneledDirConns"),
+ V(ProtocolWarnings, BOOL, "0"),
+ V(PublishServerDescriptor, CSV, "1"),
+ V(PublishHidServDescriptors, BOOL, "1"),
+ V(ReachableAddresses, LINELIST, NULL),
+ V(ReachableDirAddresses, LINELIST, NULL),
+ V(ReachableORAddresses, LINELIST, NULL),
+ V(RecommendedVersions, LINELIST, NULL),
+ V(RecommendedClientVersions, LINELIST, NULL),
+ V(RecommendedServerVersions, LINELIST, NULL),
+ V(RecommendedPackages, LINELIST, NULL),
+ V(ReducedConnectionPadding, BOOL, "0"),
+ V(ConnectionPadding, AUTOBOOL, "auto"),
+ V(RefuseUnknownExits, AUTOBOOL, "auto"),
+ V(RejectPlaintextPorts, CSV, ""),
+ V(RelayBandwidthBurst, MEMUNIT, "0"),
+ V(RelayBandwidthRate, MEMUNIT, "0"),
+ V(RendPostPeriod, INTERVAL, "1 hour"),
+ V(RephistTrackTime, INTERVAL, "24 hours"),
+ V(RunAsDaemon, BOOL, "0"),
+ V(ReducedExitPolicy, BOOL, "0"),
+ OBSOLETE("RunTesting"), // currently unused
+ V(Sandbox, BOOL, "0"),
+ V(SafeLogging, STRING, "1"),
+ V(SafeSocks, BOOL, "0"),
+ V(ServerDNSAllowBrokenConfig, BOOL, "1"),
+ V(ServerDNSAllowNonRFC953Hostnames, BOOL,"0"),
+ V(ServerDNSDetectHijacking, BOOL, "1"),
+ V(ServerDNSRandomizeCase, BOOL, "1"),
+ V(ServerDNSResolvConfFile, STRING, NULL),
+ V(ServerDNSSearchDomains, BOOL, "0"),
+ V(ServerDNSTestAddresses, CSV,
+ "www.google.com,www.mit.edu,www.yahoo.com,www.slashdot.org"),
+ OBSOLETE("SchedulerLowWaterMark__"),
+ OBSOLETE("SchedulerHighWaterMark__"),
+ OBSOLETE("SchedulerMaxFlushCells__"),
+ V(KISTSchedRunInterval, MSEC_INTERVAL, "0 msec"),
+ V(KISTSockBufSizeFactor, DOUBLE, "1.0"),
+ V(Schedulers, CSV, "KIST,KISTLite,Vanilla"),
+ V(ShutdownWaitLength, INTERVAL, "30 seconds"),
+ OBSOLETE("SocksListenAddress"),
+ V(SocksPolicy, LINELIST, NULL),
+ VPORT(SocksPort),
+ V(SocksTimeout, INTERVAL, "2 minutes"),
+ V(SSLKeyLifetime, INTERVAL, "0"),
+ OBSOLETE("StrictEntryNodes"),
+ OBSOLETE("StrictExitNodes"),
+ V(StrictNodes, BOOL, "0"),
+ OBSOLETE("Support022HiddenServices"),
+ V(TestSocks, BOOL, "0"),
+ V(TokenBucketRefillInterval, MSEC_INTERVAL, "100 msec"),
+ V(Tor2webMode, BOOL, "0"),
+ V(Tor2webRendezvousPoints, ROUTERSET, NULL),
+ OBSOLETE("TLSECGroup"),
+ V(TrackHostExits, CSV, NULL),
+ V(TrackHostExitsExpire, INTERVAL, "30 minutes"),
+ OBSOLETE("TransListenAddress"),
+ VPORT(TransPort),
+ V(TransProxyType, STRING, "default"),
+ OBSOLETE("TunnelDirConns"),
+ V(UpdateBridgesFromAuthority, BOOL, "0"),
+ V(UseBridges, BOOL, "0"),
+ VAR("UseEntryGuards", BOOL, UseEntryGuards_option, "1"),
+ OBSOLETE("UseEntryGuardsAsDirGuards"),
+ V(UseGuardFraction, AUTOBOOL, "auto"),
+ V(UseMicrodescriptors, AUTOBOOL, "auto"),
+ OBSOLETE("UseNTorHandshake"),
+ V(User, STRING, NULL),
+ OBSOLETE("UserspaceIOCPBuffers"),
+ V(AuthDirSharedRandomness, BOOL, "1"),
+ V(AuthDirTestEd25519LinkKeys, BOOL, "1"),
+ OBSOLETE("V1AuthoritativeDirectory"),
+ OBSOLETE("V2AuthoritativeDirectory"),
+ VAR("V3AuthoritativeDirectory",BOOL, V3AuthoritativeDir, "0"),
+ V(TestingV3AuthInitialVotingInterval, INTERVAL, "30 minutes"),
+ V(TestingV3AuthInitialVoteDelay, INTERVAL, "5 minutes"),
+ V(TestingV3AuthInitialDistDelay, INTERVAL, "5 minutes"),
+ V(TestingV3AuthVotingStartOffset, INTERVAL, "0"),
+ V(V3AuthVotingInterval, INTERVAL, "1 hour"),
+ V(V3AuthVoteDelay, INTERVAL, "5 minutes"),
+ V(V3AuthDistDelay, INTERVAL, "5 minutes"),
+ V(V3AuthNIntervalsValid, UINT, "3"),
+ V(V3AuthUseLegacyKey, BOOL, "0"),
+ V(V3BandwidthsFile, FILENAME, NULL),
+ V(GuardfractionFile, FILENAME, NULL),
+ VAR("VersioningAuthoritativeDirectory",BOOL,VersioningAuthoritativeDir, "0"),
+ OBSOLETE("VoteOnHidServDirectoriesV2"),
+ V(VirtualAddrNetworkIPv4, STRING, "127.192.0.0/10"),
+ V(VirtualAddrNetworkIPv6, STRING, "[FE80::]/10"),
+ V(WarnPlaintextPorts, CSV, "23,109,110,143"),
+ OBSOLETE("UseFilteringSSLBufferevents"),
+ OBSOLETE("__UseFilteringSSLBufferevents"),
+ VAR("__ReloadTorrcOnSIGHUP", BOOL, ReloadTorrcOnSIGHUP, "1"),
+ VAR("__AllDirActionsPrivate", BOOL, AllDirActionsPrivate, "0"),
+ VAR("__DisablePredictedCircuits",BOOL,DisablePredictedCircuits, "0"),
+ VAR("__DisableSignalHandlers", BOOL, DisableSignalHandlers, "0"),
+ VAR("__LeaveStreamsUnattached",BOOL, LeaveStreamsUnattached, "0"),
+ VAR("__HashedControlSessionPassword", LINELIST, HashedControlSessionPassword,
+ NULL),
+ VAR("__OwningControllerProcess",STRING,OwningControllerProcess, NULL),
+ VAR("__OwningControllerFD",INT,OwningControllerFD, "-1"),
+ V(MinUptimeHidServDirectoryV2, INTERVAL, "96 hours"),
+ V(TestingServerDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingClientDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingServerConsensusDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingClientConsensusDownloadInitialDelay, CSV_INTERVAL, "0"),
+ /* With the ClientBootstrapConsensus*Download* below:
+ * Clients with only authorities will try:
+ * - at least 3 authorities over 10 seconds, then exponentially backoff,
+ * with the next attempt 3-21 seconds later,
+ * Clients with authorities and fallbacks will try:
+ * - at least 2 authorities and 4 fallbacks over 21 seconds, then
+ * exponentially backoff, with the next attempts 4-33 seconds later,
+ * Clients will also retry when an application request arrives.
+ * After a number of failed requests, clients retry every 3 days + 1 hour.
+ *
+ * Clients used to try 2 authorities over 10 seconds, then wait for
+ * 60 minutes or an application request.
+ *
+ * When clients have authorities and fallbacks available, they use these
+ * schedules: (we stagger the times to avoid thundering herds) */
+ V(ClientBootstrapConsensusAuthorityDownloadInitialDelay, CSV_INTERVAL, "6"),
+ V(ClientBootstrapConsensusFallbackDownloadInitialDelay, CSV_INTERVAL, "0"),
+ /* When clients only have authorities available, they use this schedule: */
+ V(ClientBootstrapConsensusAuthorityOnlyDownloadInitialDelay, CSV_INTERVAL,
+ "0"),
+ /* We don't want to overwhelm slow networks (or mirrors whose replies are
+ * blocked), but we also don't want to fail if only some mirrors are
+ * blackholed. Clients will try 3 directories simultaneously.
+ * (Relays never use simultaneous connections.) */
+ V(ClientBootstrapConsensusMaxInProgressTries, UINT, "3"),
+ /* When a client has any running bridges, check each bridge occasionally,
+ * whether or not that bridge is actually up. */
+ V(TestingBridgeDownloadInitialDelay, CSV_INTERVAL,"10800"),
+ /* When a client is just starting, or has no running bridges, check each
+ * bridge a few times quickly, and then try again later. These schedules
+ * are much longer than the other schedules, because we try each and every
+ * configured bridge with this schedule. */
+ V(TestingBridgeBootstrapDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"),
+ V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"),
+ OBSOLETE("TestingConsensusMaxDownloadTries"),
+ OBSOLETE("ClientBootstrapConsensusMaxDownloadTries"),
+ OBSOLETE("ClientBootstrapConsensusAuthorityOnlyMaxDownloadTries"),
+ OBSOLETE("TestingDescriptorMaxDownloadTries"),
+ OBSOLETE("TestingMicrodescMaxDownloadTries"),
+ OBSOLETE("TestingCertMaxDownloadTries"),
+ V(TestingDirAuthVoteExit, ROUTERSET, NULL),
+ V(TestingDirAuthVoteExitIsStrict, BOOL, "0"),
+ V(TestingDirAuthVoteGuard, ROUTERSET, NULL),
+ V(TestingDirAuthVoteGuardIsStrict, BOOL, "0"),
+ V(TestingDirAuthVoteHSDir, ROUTERSET, NULL),
+ V(TestingDirAuthVoteHSDirIsStrict, BOOL, "0"),
+ VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "0"),
+
+ END_OF_CONFIG_VARS
+};
+
+/** Override default values with these if the user sets the TestingTorNetwork
+ * option. */
+static const config_var_t testing_tor_network_defaults[] = {
+ V(DirAllowPrivateAddresses, BOOL, "1"),
+ V(EnforceDistinctSubnets, BOOL, "0"),
+ V(AssumeReachable, BOOL, "1"),
+ V(AuthDirMaxServersPerAddr, UINT, "0"),
+ V(ClientBootstrapConsensusAuthorityDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(ClientBootstrapConsensusFallbackDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(ClientBootstrapConsensusAuthorityOnlyDownloadInitialDelay, CSV_INTERVAL,
+ "0"),
+ V(ClientDNSRejectInternalAddresses, BOOL,"0"),
+ V(ClientRejectInternalAddresses, BOOL, "0"),
+ V(CountPrivateBandwidth, BOOL, "1"),
+ V(ExitPolicyRejectPrivate, BOOL, "0"),
+ V(ExtendAllowPrivateAddresses, BOOL, "1"),
+ V(V3AuthVotingInterval, INTERVAL, "5 minutes"),
+ V(V3AuthVoteDelay, INTERVAL, "20 seconds"),
+ V(V3AuthDistDelay, INTERVAL, "20 seconds"),
+ V(TestingV3AuthInitialVotingInterval, INTERVAL, "150 seconds"),
+ V(TestingV3AuthInitialVoteDelay, INTERVAL, "20 seconds"),
+ V(TestingV3AuthInitialDistDelay, INTERVAL, "20 seconds"),
+ V(TestingAuthDirTimeToLearnReachability, INTERVAL, "0 minutes"),
+ V(TestingEstimatedDescriptorPropagationTime, INTERVAL, "0 minutes"),
+ V(MinUptimeHidServDirectoryV2, INTERVAL, "0 minutes"),
+ V(TestingServerDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingClientDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingServerConsensusDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingClientConsensusDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingBridgeDownloadInitialDelay, CSV_INTERVAL, "10"),
+ V(TestingBridgeBootstrapDownloadInitialDelay, CSV_INTERVAL, "0"),
+ V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"),
+ V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"),
+ V(TestingEnableConnBwEvent, BOOL, "1"),
+ V(TestingEnableCellStatsEvent, BOOL, "1"),
+ VAR("___UsingTestNetworkDefaults", BOOL, UsingTestNetworkDefaults_, "1"),
+ V(RendPostPeriod, INTERVAL, "2 minutes"),
+
+ END_OF_CONFIG_VARS
+};
+
+#undef VAR
+#undef V
+#undef OBSOLETE
+
+static const config_deprecation_t option_deprecation_notes_[] = {
+ /* Deprecated since 0.3.2.0-alpha. */
+ { "HTTPProxy", "It only applies to direct unencrypted HTTP connections "
+ "to your directory server, which your Tor probably wasn't using." },
+ { "HTTPProxyAuthenticator", "HTTPProxy is deprecated in favor of HTTPSProxy "
+ "which should be used with HTTPSProxyAuthenticator." },
+ /* End of options deprecated since 0.3.2.1-alpha */
+
+ /* Options deprecated since 0.3.2.2-alpha */
+ { "ReachableDirAddresses", "It has no effect on relays, and has had no "
+ "effect on clients since 0.2.8." },
+ { "ClientPreferIPv6DirPort", "It has no effect on relays, and has had no "
+ "effect on clients since 0.2.8." },
+ /* End of options deprecated since 0.3.2.2-alpha. */
+
+ { NULL, NULL }
+};
+
+#ifdef _WIN32
+static char *get_windows_conf_root(void);
+#endif
+static int options_act_reversible(const or_options_t *old_options, char **msg);
+static int options_transition_allowed(const or_options_t *old,
+ const or_options_t *new,
+ char **msg);
+static int options_transition_affects_workers(
+ const or_options_t *old_options, const or_options_t *new_options);
+static int options_transition_affects_descriptor(
+ const or_options_t *old_options, const or_options_t *new_options);
+static int options_transition_affects_dirauth_timing(
+ const or_options_t *old_options, const or_options_t *new_options);
+static int normalize_nickname_list(config_line_t **normalized_out,
+ const config_line_t *lst, const char *name,
+ char **msg);
+static char *get_bindaddr_from_transport_listen_line(const char *line,
+ const char *transport);
+static int parse_ports(or_options_t *options, int validate_only,
+ char **msg_out, int *n_ports_out,
+ int *world_writable_control_socket);
+static int check_server_ports(const smartlist_t *ports,
+ const or_options_t *options,
+ int *num_low_ports_out);
+static int validate_data_directories(or_options_t *options);
+static int write_configuration_file(const char *fname,
+ const or_options_t *options);
+static int options_init_logs(const or_options_t *old_options,
+ or_options_t *options, int validate_only);
+
+static void init_libevent(const or_options_t *options);
+static int opt_streq(const char *s1, const char *s2);
+static int parse_outbound_addresses(or_options_t *options, int validate_only,
+ char **msg);
+static void config_maybe_load_geoip_files_(const or_options_t *options,
+ const or_options_t *old_options);
+static int options_validate_cb(void *old_options, void *options,
+ void *default_options,
+ int from_setconf, char **msg);
+static void cleanup_protocol_warning_severity_level(void);
+static void set_protocol_warning_severity_level(int warning_severity);
+
+/** Magic value for or_options_t. */
+#define OR_OPTIONS_MAGIC 9090909
+
+/** Configuration format for or_options_t. */
+STATIC config_format_t options_format = {
+ sizeof(or_options_t),
+ OR_OPTIONS_MAGIC,
+ offsetof(or_options_t, magic_),
+ option_abbrevs_,
+ option_deprecation_notes_,
+ option_vars_,
+ options_validate_cb,
+ NULL
+};
+
+/*
+ * Functions to read and write the global options pointer.
+ */
+
+/** Command-line and config-file options. */
+static or_options_t *global_options = NULL;
+/** The fallback options_t object; this is where we look for options not
+ * in torrc before we fall back to Tor's defaults. */
+static or_options_t *global_default_options = NULL;
+/** Name of most recently read torrc file. */
+static char *torrc_fname = NULL;
+/** Name of the most recently read torrc-defaults file.*/
+static char *torrc_defaults_fname = NULL;
+/** Configuration options set by command line. */
+static config_line_t *global_cmdline_options = NULL;
+/** Non-configuration options set by the command line */
+static config_line_t *global_cmdline_only_options = NULL;
+/** Boolean: Have we parsed the command line? */
+static int have_parsed_cmdline = 0;
+/** Contents of most recently read DirPortFrontPage file. */
+static char *global_dirfrontpagecontents = NULL;
+/** List of port_cfg_t for all configured ports. */
+static smartlist_t *configured_ports = NULL;
+/** True iff we're currently validating options, and any calls to
+ * get_options() are likely to be bugs. */
+static int in_option_validation = 0;
+/* True iff we've initialized libevent */
+static int libevent_initialized = 0;
+
+/** Return the contents of our frontpage string, or NULL if not configured. */
+MOCK_IMPL(const char*,
+get_dirportfrontpage, (void))
+{
+ return global_dirfrontpagecontents;
+}
+
+/** Returns the currently configured options. */
+MOCK_IMPL(or_options_t *,
+get_options_mutable, (void))
+{
+ tor_assert(global_options);
+ tor_assert_nonfatal(! in_option_validation);
+ return global_options;
+}
+
+/** Returns the currently configured options */
+MOCK_IMPL(const or_options_t *,
+get_options,(void))
+{
+ return get_options_mutable();
+}
+
+/** Change the current global options to contain <b>new_val</b> instead of
+ * their current value; take action based on the new value; free the old value
+ * as necessary. Returns 0 on success, -1 on failure.
+ */
+int
+set_options(or_options_t *new_val, char **msg)
+{
+ int i;
+ smartlist_t *elements;
+ config_line_t *line;
+ or_options_t *old_options = global_options;
+ global_options = new_val;
+ /* Note that we pass the *old* options below, for comparison. It
+ * pulls the new options directly out of global_options. */
+ if (options_act_reversible(old_options, msg)<0) {
+ tor_assert(*msg);
+ global_options = old_options;
+ return -1;
+ }
+ if (options_act(old_options) < 0) { /* acting on the options failed. die. */
+ if (! tor_event_loop_shutdown_is_pending()) {
+ log_err(LD_BUG,
+ "Acting on config options left us in a broken state. Dying.");
+ tor_shutdown_event_loop_and_exit(1);
+ }
+ return -1;
+ }
+ /* Issues a CONF_CHANGED event to notify controller of the change. If Tor is
+ * just starting up then the old_options will be undefined. */
+ if (old_options && old_options != global_options) {
+ elements = smartlist_new();
+ for (i=0; options_format.vars[i].name; ++i) {
+ const config_var_t *var = &options_format.vars[i];
+ const char *var_name = var->name;
+ if (var->type == CONFIG_TYPE_LINELIST_S ||
+ var->type == CONFIG_TYPE_OBSOLETE) {
+ continue;
+ }
+ if (!config_is_same(&options_format, new_val, old_options, var_name)) {
+ line = config_get_assigned_option(&options_format, new_val,
+ var_name, 1);
+
+ if (line) {
+ config_line_t *next;
+ for (; line; line = next) {
+ next = line->next;
+ smartlist_add(elements, line->key);
+ smartlist_add(elements, line->value);
+ tor_free(line);
+ }
+ } else {
+ smartlist_add_strdup(elements, options_format.vars[i].name);
+ smartlist_add(elements, NULL);
+ }
+ }
+ }
+ control_event_conf_changed(elements);
+ SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
+ smartlist_free(elements);
+ }
+
+ if (old_options != global_options) {
+ or_options_free(old_options);
+ /* If we are here it means we've successfully applied the new options and
+ * that the global options have been changed to the new values. We'll
+ * check if we need to remove or add periodic events. */
+ periodic_events_on_new_options(global_options);
+ }
+
+ return 0;
+}
+
+/** The version of this Tor process, as parsed. */
+static char *the_tor_version = NULL;
+/** A shorter version of this Tor process's version, for export in our router
+ * descriptor. (Does not include the git version, if any.) */
+static char *the_short_tor_version = NULL;
+
+/** Return the current Tor version. */
+const char *
+get_version(void)
+{
+ if (the_tor_version == NULL) {
+ if (strlen(tor_git_revision)) {
+ tor_asprintf(&the_tor_version, "%s (git-%s)", get_short_version(),
+ tor_git_revision);
+ } else {
+ the_tor_version = tor_strdup(get_short_version());
+ }
+ }
+ return the_tor_version;
+}
+
+/** Return the current Tor version, without any git tag. */
+const char *
+get_short_version(void)
+{
+
+ if (the_short_tor_version == NULL) {
+#ifdef TOR_BUILD_TAG
+ tor_asprintf(&the_short_tor_version, "%s (%s)", VERSION, TOR_BUILD_TAG);
+#else
+ the_short_tor_version = tor_strdup(VERSION);
+#endif
+ }
+ return the_short_tor_version;
+}
+
+/** Release additional memory allocated in options
+ */
+STATIC void
+or_options_free_(or_options_t *options)
+{
+ if (!options)
+ return;
+
+ routerset_free(options->ExcludeExitNodesUnion_);
+ if (options->NodeFamilySets) {
+ SMARTLIST_FOREACH(options->NodeFamilySets, routerset_t *,
+ rs, routerset_free(rs));
+ smartlist_free(options->NodeFamilySets);
+ }
+ if (options->SchedulerTypes_) {
+ SMARTLIST_FOREACH(options->SchedulerTypes_, int *, i, tor_free(i));
+ smartlist_free(options->SchedulerTypes_);
+ }
+ if (options->FilesOpenedByIncludes) {
+ SMARTLIST_FOREACH(options->FilesOpenedByIncludes, char *, f, tor_free(f));
+ smartlist_free(options->FilesOpenedByIncludes);
+ }
+ tor_free(options->DataDirectory);
+ tor_free(options->CacheDirectory);
+ tor_free(options->KeyDirectory);
+ tor_free(options->BridgePassword_AuthDigest_);
+ tor_free(options->command_arg);
+ tor_free(options->master_key_fname);
+ config_free_lines(options->MyFamily);
+ config_free(&options_format, options);
+}
+
+/** Release all memory and resources held by global configuration structures.
+ */
+void
+config_free_all(void)
+{
+ or_options_free(global_options);
+ global_options = NULL;
+ or_options_free(global_default_options);
+ global_default_options = NULL;
+
+ config_free_lines(global_cmdline_options);
+ global_cmdline_options = NULL;
+
+ config_free_lines(global_cmdline_only_options);
+ global_cmdline_only_options = NULL;
+
+ if (configured_ports) {
+ SMARTLIST_FOREACH(configured_ports,
+ port_cfg_t *, p, port_cfg_free(p));
+ smartlist_free(configured_ports);
+ configured_ports = NULL;
+ }
+
+ tor_free(torrc_fname);
+ tor_free(torrc_defaults_fname);
+ tor_free(global_dirfrontpagecontents);
+
+ tor_free(the_short_tor_version);
+ tor_free(the_tor_version);
+
+ cleanup_protocol_warning_severity_level();
+
+ have_parsed_cmdline = 0;
+ libevent_initialized = 0;
+}
+
+/** Make <b>address</b> -- a piece of information related to our operation as
+ * a client -- safe to log according to the settings in options->SafeLogging,
+ * and return it.
+ *
+ * (We return "[scrubbed]" if SafeLogging is "1", and address otherwise.)
+ */
+const char *
+safe_str_client(const char *address)
+{
+ tor_assert(address);
+ if (get_options()->SafeLogging_ == SAFELOG_SCRUB_ALL)
+ return "[scrubbed]";
+ else
+ return address;
+}
+
+/** Make <b>address</b> -- a piece of information of unspecified sensitivity
+ * -- safe to log according to the settings in options->SafeLogging, and
+ * return it.
+ *
+ * (We return "[scrubbed]" if SafeLogging is anything besides "0", and address
+ * otherwise.)
+ */
+const char *
+safe_str(const char *address)
+{
+ tor_assert(address);
+ if (get_options()->SafeLogging_ != SAFELOG_SCRUB_NONE)
+ return "[scrubbed]";
+ else
+ return address;
+}
+
+/** Equivalent to escaped(safe_str_client(address)). See reentrancy note on
+ * escaped(): don't use this outside the main thread, or twice in the same
+ * log statement. */
+const char *
+escaped_safe_str_client(const char *address)
+{
+ if (get_options()->SafeLogging_ == SAFELOG_SCRUB_ALL)
+ return "[scrubbed]";
+ else
+ return escaped(address);
+}
+
+/** Equivalent to escaped(safe_str(address)). See reentrancy note on
+ * escaped(): don't use this outside the main thread, or twice in the same
+ * log statement. */
+const char *
+escaped_safe_str(const char *address)
+{
+ if (get_options()->SafeLogging_ != SAFELOG_SCRUB_NONE)
+ return "[scrubbed]";
+ else
+ return escaped(address);
+}
+
+/**
+ * The severity level that should be used for warnings of severity
+ * LOG_PROTOCOL_WARN.
+ *
+ * We keep this outside the options, and we use an atomic_counter_t, in case
+ * one thread needs to use LOG_PROTOCOL_WARN while an option transition is
+ * happening in the main thread.
+ */
+static atomic_counter_t protocol_warning_severity_level;
+
+/** Return the severity level that should be used for warnings of severity
+ * LOG_PROTOCOL_WARN. */
+int
+get_protocol_warning_severity_level(void)
+{
+ return (int) atomic_counter_get(&protocol_warning_severity_level);
+}
+
+/** Set the protocol warning severity level to <b>severity</b>. */
+static void
+set_protocol_warning_severity_level(int warning_severity)
+{
+ atomic_counter_exchange(&protocol_warning_severity_level,
+ warning_severity);
+}
+
+/**
+ * Initialize the log warning severity level for protocol warnings. Call
+ * only once at startup.
+ */
+void
+init_protocol_warning_severity_level(void)
+{
+ atomic_counter_init(&protocol_warning_severity_level);
+ set_protocol_warning_severity_level(LOG_WARN);
+}
+
+/**
+ * Tear down protocol_warning_severity_level.
+ */
+static void
+cleanup_protocol_warning_severity_level(void)
+{
+ atomic_counter_destroy(&protocol_warning_severity_level);
+}
+
+/** List of default directory authorities */
+
+static const char *default_authorities[] = {
+#include "auth_dirs.inc"
+ NULL
+};
+
+/** List of fallback directory authorities. The list is generated by opt-in of
+ * relays that meet certain stability criteria.
+ */
+static const char *default_fallbacks[] = {
+#include "fallback_dirs.inc"
+ NULL
+};
+
+/** Add the default directory authorities directly into the trusted dir list,
+ * but only add them insofar as they share bits with <b>type</b>.
+ * Each authority's bits are restricted to the bits shared with <b>type</b>.
+ * If <b>type</b> is ALL_DIRINFO or NO_DIRINFO (zero), add all authorities. */
+STATIC void
+add_default_trusted_dir_authorities(dirinfo_type_t type)
+{
+ int i;
+ for (i=0; default_authorities[i]; i++) {
+ if (parse_dir_authority_line(default_authorities[i], type, 0)<0) {
+ log_err(LD_BUG, "Couldn't parse internal DirAuthority line %s",
+ default_authorities[i]);
+ }
+ }
+}
+
+/** Add the default fallback directory servers into the fallback directory
+ * server list. */
+MOCK_IMPL(void,
+add_default_fallback_dir_servers,(void))
+{
+ int i;
+ for (i=0; default_fallbacks[i]; i++) {
+ if (parse_dir_fallback_line(default_fallbacks[i], 0)<0) {
+ log_err(LD_BUG, "Couldn't parse internal FallbackDir line %s",
+ default_fallbacks[i]);
+ }
+ }
+}
+
+/** Look at all the config options for using alternate directory
+ * authorities, and make sure none of them are broken. Also, warn the
+ * user if we changed any dangerous ones.
+ */
+static int
+validate_dir_servers(or_options_t *options, or_options_t *old_options)
+{
+ config_line_t *cl;
+
+ if (options->DirAuthorities &&
+ (options->AlternateDirAuthority || options->AlternateBridgeAuthority)) {
+ log_warn(LD_CONFIG,
+ "You cannot set both DirAuthority and Alternate*Authority.");
+ return -1;
+ }
+
+ /* do we want to complain to the user about being partitionable? */
+ if ((options->DirAuthorities &&
+ (!old_options ||
+ !config_lines_eq(options->DirAuthorities,
+ old_options->DirAuthorities))) ||
+ (options->AlternateDirAuthority &&
+ (!old_options ||
+ !config_lines_eq(options->AlternateDirAuthority,
+ old_options->AlternateDirAuthority)))) {
+ log_warn(LD_CONFIG,
+ "You have used DirAuthority or AlternateDirAuthority to "
+ "specify alternate directory authorities in "
+ "your configuration. This is potentially dangerous: it can "
+ "make you look different from all other Tor users, and hurt "
+ "your anonymity. Even if you've specified the same "
+ "authorities as Tor uses by default, the defaults could "
+ "change in the future. Be sure you know what you're doing.");
+ }
+
+ /* Now go through the four ways you can configure an alternate
+ * set of directory authorities, and make sure none are broken. */
+ for (cl = options->DirAuthorities; cl; cl = cl->next)
+ if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0)
+ return -1;
+ for (cl = options->AlternateBridgeAuthority; cl; cl = cl->next)
+ if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0)
+ return -1;
+ for (cl = options->AlternateDirAuthority; cl; cl = cl->next)
+ if (parse_dir_authority_line(cl->value, NO_DIRINFO, 1)<0)
+ return -1;
+ for (cl = options->FallbackDir; cl; cl = cl->next)
+ if (parse_dir_fallback_line(cl->value, 1)<0)
+ return -1;
+ return 0;
+}
+
+/** Look at all the config options and assign new dir authorities
+ * as appropriate.
+ */
+int
+consider_adding_dir_servers(const or_options_t *options,
+ const or_options_t *old_options)
+{
+ config_line_t *cl;
+ int need_to_update =
+ !smartlist_len(router_get_trusted_dir_servers()) ||
+ !smartlist_len(router_get_fallback_dir_servers()) || !old_options ||
+ !config_lines_eq(options->DirAuthorities, old_options->DirAuthorities) ||
+ !config_lines_eq(options->FallbackDir, old_options->FallbackDir) ||
+ (options->UseDefaultFallbackDirs != old_options->UseDefaultFallbackDirs) ||
+ !config_lines_eq(options->AlternateBridgeAuthority,
+ old_options->AlternateBridgeAuthority) ||
+ !config_lines_eq(options->AlternateDirAuthority,
+ old_options->AlternateDirAuthority);
+
+ if (!need_to_update)
+ return 0; /* all done */
+
+ /* "You cannot set both DirAuthority and Alternate*Authority."
+ * Checking that this restriction holds allows us to simplify
+ * the unit tests. */
+ tor_assert(!(options->DirAuthorities &&
+ (options->AlternateDirAuthority
+ || options->AlternateBridgeAuthority)));
+
+ /* Start from a clean slate. */
+ clear_dir_servers();
+
+ if (!options->DirAuthorities) {
+ /* then we may want some of the defaults */
+ dirinfo_type_t type = NO_DIRINFO;
+ if (!options->AlternateBridgeAuthority) {
+ type |= BRIDGE_DIRINFO;
+ }
+ if (!options->AlternateDirAuthority) {
+ type |= V3_DIRINFO | EXTRAINFO_DIRINFO | MICRODESC_DIRINFO;
+ /* Only add the default fallback directories when the DirAuthorities,
+ * AlternateDirAuthority, and FallbackDir directory config options
+ * are set to their defaults, and when UseDefaultFallbackDirs is 1. */
+ if (!options->FallbackDir && options->UseDefaultFallbackDirs) {
+ add_default_fallback_dir_servers();
+ }
+ }
+ /* if type == NO_DIRINFO, we don't want to add any of the
+ * default authorities, because we've replaced them all */
+ if (type != NO_DIRINFO)
+ add_default_trusted_dir_authorities(type);
+ }
+
+ for (cl = options->DirAuthorities; cl; cl = cl->next)
+ if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0)
+ return -1;
+ for (cl = options->AlternateBridgeAuthority; cl; cl = cl->next)
+ if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0)
+ return -1;
+ for (cl = options->AlternateDirAuthority; cl; cl = cl->next)
+ if (parse_dir_authority_line(cl->value, NO_DIRINFO, 0)<0)
+ return -1;
+ for (cl = options->FallbackDir; cl; cl = cl->next)
+ if (parse_dir_fallback_line(cl->value, 0)<0)
+ return -1;
+ return 0;
+}
+
+/**
+ * Make sure that <b>directory</b> exists, with appropriate ownership and
+ * permissions (as modified by <b>group_readable</b>). If <b>create</b>,
+ * create the directory if it is missing. Return 0 on success.
+ * On failure, return -1 and set *<b>msg_out</b>.
+ */
+static int
+check_and_create_data_directory(int create,
+ const char *directory,
+ int group_readable,
+ const char *owner,
+ char **msg_out)
+{
+ cpd_check_t cpd_opts = create ? CPD_CREATE : CPD_CHECK;
+ if (group_readable)
+ cpd_opts |= CPD_GROUP_READ;
+ if (check_private_dir(directory,
+ cpd_opts,
+ owner) < 0) {
+ tor_asprintf(msg_out,
+ "Couldn't %s private data directory \"%s\"",
+ create ? "create" : "access",
+ directory);
+ return -1;
+ }
+
+#ifndef _WIN32
+ if (group_readable) {
+ /* Only new dirs created get new opts, also enforce group read. */
+ if (chmod(directory, 0750)) {
+ log_warn(LD_FS,"Unable to make %s group-readable: %s",
+ directory, strerror(errno));
+ }
+ }
+#endif /* !defined(_WIN32) */
+
+ return 0;
+}
+
+/**
+ * Ensure that our keys directory exists, with appropriate permissions.
+ * Return 0 on success, -1 on failure.
+ */
+int
+create_keys_directory(const or_options_t *options)
+{
+ /* Make sure DataDirectory exists, and is private. */
+ cpd_check_t cpd_opts = CPD_CREATE;
+ if (options->DataDirectoryGroupReadable)
+ cpd_opts |= CPD_GROUP_READ;
+ if (check_private_dir(options->DataDirectory, cpd_opts, options->User)) {
+ log_err(LD_OR, "Can't create/check datadirectory %s",
+ options->DataDirectory);
+ return -1;
+ }
+
+ /* Check the key directory. */
+ if (check_private_dir(options->KeyDirectory, CPD_CREATE, options->User)) {
+ return -1;
+ }
+ return 0;
+}
+
+/* Helps determine flags to pass to switch_id. */
+static int have_low_ports = -1;
+
+/** Fetch the active option list, and take actions based on it. All of the
+ * things we do should survive being done repeatedly. If present,
+ * <b>old_options</b> contains the previous value of the options.
+ *
+ * Return 0 if all goes well, return -1 if things went badly.
+ */
+static int
+options_act_reversible(const or_options_t *old_options, char **msg)
+{
+ smartlist_t *new_listeners = smartlist_new();
+ smartlist_t *replaced_listeners = smartlist_new();
+ or_options_t *options = get_options_mutable();
+ int running_tor = options->command == CMD_RUN_TOR;
+ int set_conn_limit = 0;
+ int r = -1;
+ int logs_marked = 0, logs_initialized = 0;
+ int old_min_log_level = get_min_log_level();
+
+ /* Daemonize _first_, since we only want to open most of this stuff in
+ * the subprocess. Libevent bases can't be reliably inherited across
+ * processes. */
+ if (running_tor && options->RunAsDaemon) {
+ /* No need to roll back, since you can't change the value. */
+ start_daemon();
+ }
+
+#ifdef HAVE_SYSTEMD
+ /* Our PID may have changed, inform supervisor */
+ sd_notifyf(0, "MAINPID=%ld\n", (long int)getpid());
+#endif
+
+#ifndef HAVE_SYS_UN_H
+ if (options->ControlSocket || options->ControlSocketsGroupWritable) {
+ *msg = tor_strdup("Unix domain sockets (ControlSocket) not supported "
+ "on this OS/with this build.");
+ goto rollback;
+ }
+#else /* !(!defined(HAVE_SYS_UN_H)) */
+ if (options->ControlSocketsGroupWritable && !options->ControlSocket) {
+ *msg = tor_strdup("Setting ControlSocketGroupWritable without setting"
+ "a ControlSocket makes no sense.");
+ goto rollback;
+ }
+#endif /* !defined(HAVE_SYS_UN_H) */
+
+ if (running_tor) {
+ int n_ports=0;
+ /* We need to set the connection limit before we can open the listeners. */
+ if (! sandbox_is_active()) {
+ if (set_max_file_descriptors((unsigned)options->ConnLimit,
+ &options->ConnLimit_) < 0) {
+ *msg = tor_strdup("Problem with ConnLimit value. "
+ "See logs for details.");
+ goto rollback;
+ }
+ set_conn_limit = 1;
+ } else {
+ tor_assert(old_options);
+ options->ConnLimit_ = old_options->ConnLimit_;
+ }
+
+ /* Set up libevent. (We need to do this before we can register the
+ * listeners as listeners.) */
+ if (running_tor && !libevent_initialized) {
+ init_libevent(options);
+ libevent_initialized = 1;
+
+ /* This has to come up after libevent is initialized. */
+ control_initialize_event_queue();
+
+ /*
+ * Initialize the scheduler - this has to come after
+ * options_init_from_torrc() sets up libevent - why yes, that seems
+ * completely sensible to hide the libevent setup in the option parsing
+ * code! It also needs to happen before init_keys(), so it needs to
+ * happen here too. How yucky. */
+ scheduler_init();
+ }
+
+ /* Adjust the port configuration so we can launch listeners. */
+ if (parse_ports(options, 0, msg, &n_ports, NULL)) {
+ if (!*msg)
+ *msg = tor_strdup("Unexpected problem parsing port config");
+ goto rollback;
+ }
+
+ /* Set the hibernation state appropriately.*/
+ consider_hibernation(time(NULL));
+
+ /* Launch the listeners. (We do this before we setuid, so we can bind to
+ * ports under 1024.) We don't want to rebind if we're hibernating or
+ * shutting down. If networking is disabled, this will close all but the
+ * control listeners, but disable those. */
+ if (!we_are_hibernating()) {
+ if (retry_all_listeners(replaced_listeners, new_listeners,
+ options->DisableNetwork) < 0) {
+ *msg = tor_strdup("Failed to bind one of the listener ports.");
+ goto rollback;
+ }
+ }
+ if (options->DisableNetwork) {
+ /* Aggressively close non-controller stuff, NOW */
+ log_notice(LD_NET, "DisableNetwork is set. Tor will not make or accept "
+ "non-control network connections. Shutting down all existing "
+ "connections.");
+ connection_mark_all_noncontrol_connections();
+ /* We can't complete circuits until the network is re-enabled. */
+ note_that_we_maybe_cant_complete_circuits();
+ }
+ }
+
+#if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H)
+ /* Open /dev/pf before dropping privileges. */
+ if (options->TransPort_set &&
+ options->TransProxyType_parsed == TPT_DEFAULT) {
+ if (get_pf_socket() < 0) {
+ *msg = tor_strdup("Unable to open /dev/pf for transparent proxy.");
+ goto rollback;
+ }
+ }
+#endif /* defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) */
+
+ /* Attempt to lock all current and future memory with mlockall() only once */
+ if (options->DisableAllSwap) {
+ if (tor_mlockall() == -1) {
+ *msg = tor_strdup("DisableAllSwap failure. Do you have proper "
+ "permissions?");
+ goto done;
+ }
+ }
+
+ /* Setuid/setgid as appropriate */
+ if (options->User) {
+ tor_assert(have_low_ports != -1);
+ unsigned switch_id_flags = 0;
+ if (options->KeepBindCapabilities == 1) {
+ switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
+ switch_id_flags |= SWITCH_ID_WARN_IF_NO_CAPS;
+ }
+ if (options->KeepBindCapabilities == -1 && have_low_ports) {
+ switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
+ }
+ if (switch_id(options->User, switch_id_flags) != 0) {
+ /* No need to roll back, since you can't change the value. */
+ *msg = tor_strdup("Problem with User value. See logs for details.");
+ goto done;
+ }
+ }
+
+ /* Ensure data directory is private; create if possible. */
+ /* It's okay to do this in "options_act_reversible()" even though it isn't
+ * actually reversible, since you can't change the DataDirectory while
+ * Tor is running. */
+ if (check_and_create_data_directory(running_tor /* create */,
+ options->DataDirectory,
+ options->DataDirectoryGroupReadable,
+ options->User,
+ msg) < 0) {
+ goto done;
+ }
+ if (check_and_create_data_directory(running_tor /* create */,
+ options->KeyDirectory,
+ options->KeyDirectoryGroupReadable,
+ options->User,
+ msg) < 0) {
+ goto done;
+ }
+ if (check_and_create_data_directory(running_tor /* create */,
+ options->CacheDirectory,
+ options->CacheDirectoryGroupReadable,
+ options->User,
+ msg) < 0) {
+ goto done;
+ }
+
+ /* Bail out at this point if we're not going to be a client or server:
+ * we don't run Tor itself. */
+ if (!running_tor)
+ goto commit;
+
+ mark_logs_temp(); /* Close current logs once new logs are open. */
+ logs_marked = 1;
+ /* Configure the tor_log(s) */
+ if (options_init_logs(old_options, options, 0)<0) {
+ *msg = tor_strdup("Failed to init Log options. See logs for details.");
+ goto rollback;
+ }
+ logs_initialized = 1;
+
+ commit:
+ r = 0;
+ if (logs_marked) {
+ log_severity_list_t *severity =
+ tor_malloc_zero(sizeof(log_severity_list_t));
+ close_temp_logs();
+ add_callback_log(severity, control_event_logmsg);
+ logs_set_pending_callback_callback(control_event_logmsg_pending);
+ control_adjust_event_log_severity();
+ tor_free(severity);
+ tor_log_update_sigsafe_err_fds();
+ }
+ if (logs_initialized) {
+ flush_log_messages_from_startup();
+ }
+
+ {
+ const char *badness = NULL;
+ int bad_safelog = 0, bad_severity = 0, new_badness = 0;
+ if (options->SafeLogging_ != SAFELOG_SCRUB_ALL) {
+ bad_safelog = 1;
+ if (!old_options || old_options->SafeLogging_ != options->SafeLogging_)
+ new_badness = 1;
+ }
+ if (get_min_log_level() >= LOG_INFO) {
+ bad_severity = 1;
+ if (get_min_log_level() != old_min_log_level)
+ new_badness = 1;
+ }
+ if (bad_safelog && bad_severity)
+ badness = "you disabled SafeLogging, and "
+ "you're logging more than \"notice\"";
+ else if (bad_safelog)
+ badness = "you disabled SafeLogging";
+ else
+ badness = "you're logging more than \"notice\"";
+ if (new_badness)
+ log_warn(LD_GENERAL, "Your log may contain sensitive information - %s. "
+ "Don't log unless it serves an important reason. "
+ "Overwrite the log afterwards.", badness);
+ }
+
+ SMARTLIST_FOREACH(replaced_listeners, connection_t *, conn,
+ {
+ int marked = conn->marked_for_close;
+ log_notice(LD_NET, "Closing old %s on %s:%d",
+ conn_type_to_string(conn->type), conn->address, conn->port);
+ connection_close_immediate(conn);
+ if (!marked) {
+ connection_mark_for_close(conn);
+ }
+ });
+
+ if (set_conn_limit) {
+ /*
+ * If we adjusted the conn limit, recompute the OOS threshold too
+ *
+ * How many possible sockets to keep in reserve? If we have lots of
+ * possible sockets, keep this below a limit and set ConnLimit_high_thresh
+ * very close to ConnLimit_, but if ConnLimit_ is low, shrink it in
+ * proportion.
+ *
+ * Somewhat arbitrarily, set socks_in_reserve to 5% of ConnLimit_, but
+ * cap it at 64.
+ */
+ int socks_in_reserve = options->ConnLimit_ / 20;
+ if (socks_in_reserve > 64) socks_in_reserve = 64;
+
+ options->ConnLimit_high_thresh = options->ConnLimit_ - socks_in_reserve;
+ options->ConnLimit_low_thresh = (options->ConnLimit_ / 4) * 3;
+ log_info(LD_GENERAL,
+ "Recomputed OOS thresholds: ConnLimit %d, ConnLimit_ %d, "
+ "ConnLimit_high_thresh %d, ConnLimit_low_thresh %d",
+ options->ConnLimit, options->ConnLimit_,
+ options->ConnLimit_high_thresh,
+ options->ConnLimit_low_thresh);
+
+ /* Give the OOS handler a chance with the new thresholds */
+ connection_check_oos(get_n_open_sockets(), 0);
+ }
+
+ goto done;
+
+ rollback:
+ r = -1;
+ tor_assert(*msg);
+
+ if (logs_marked) {
+ rollback_log_changes();
+ control_adjust_event_log_severity();
+ }
+
+ if (set_conn_limit && old_options)
+ set_max_file_descriptors((unsigned)old_options->ConnLimit,
+ &options->ConnLimit_);
+
+ SMARTLIST_FOREACH(new_listeners, connection_t *, conn,
+ {
+ log_notice(LD_NET, "Closing partially-constructed %s on %s:%d",
+ conn_type_to_string(conn->type), conn->address, conn->port);
+ connection_close_immediate(conn);
+ connection_mark_for_close(conn);
+ });
+
+ done:
+ smartlist_free(new_listeners);
+ smartlist_free(replaced_listeners);
+ return r;
+}
+
+/** If we need to have a GEOIP ip-to-country map to run with our configured
+ * options, return 1 and set *<b>reason_out</b> to a description of why. */
+int
+options_need_geoip_info(const or_options_t *options, const char **reason_out)
+{
+ int bridge_usage = should_record_bridge_info(options);
+ int routerset_usage =
+ routerset_needs_geoip(options->EntryNodes) ||
+ routerset_needs_geoip(options->ExitNodes) ||
+ routerset_needs_geoip(options->ExcludeExitNodes) ||
+ routerset_needs_geoip(options->ExcludeNodes) ||
+ routerset_needs_geoip(options->HSLayer2Nodes) ||
+ routerset_needs_geoip(options->HSLayer3Nodes) ||
+ routerset_needs_geoip(options->Tor2webRendezvousPoints);
+
+ if (routerset_usage && reason_out) {
+ *reason_out = "We've been configured to use (or avoid) nodes in certain "
+ "countries, and we need GEOIP information to figure out which ones they "
+ "are.";
+ } else if (bridge_usage && reason_out) {
+ *reason_out = "We've been configured to see which countries can access "
+ "us as a bridge, and we need GEOIP information to tell which countries "
+ "clients are in.";
+ }
+ return bridge_usage || routerset_usage;
+}
+
+/** Return the bandwidthrate that we are going to report to the authorities
+ * based on the config options. */
+uint32_t
+get_effective_bwrate(const or_options_t *options)
+{
+ uint64_t bw = options->BandwidthRate;
+ if (bw > options->MaxAdvertisedBandwidth)
+ bw = options->MaxAdvertisedBandwidth;
+ if (options->RelayBandwidthRate > 0 && bw > options->RelayBandwidthRate)
+ bw = options->RelayBandwidthRate;
+ /* ensure_bandwidth_cap() makes sure that this cast can't overflow. */
+ return (uint32_t)bw;
+}
+
+/** Return the bandwidthburst that we are going to report to the authorities
+ * based on the config options. */
+uint32_t
+get_effective_bwburst(const or_options_t *options)
+{
+ uint64_t bw = options->BandwidthBurst;
+ if (options->RelayBandwidthBurst > 0 && bw > options->RelayBandwidthBurst)
+ bw = options->RelayBandwidthBurst;
+ /* ensure_bandwidth_cap() makes sure that this cast can't overflow. */
+ return (uint32_t)bw;
+}
+
+/* Used in the various options_transition_affects* functions. */
+#define YES_IF_CHANGED_BOOL(opt) \
+ if (!CFG_EQ_BOOL(old_options, new_options, opt)) return 1;
+#define YES_IF_CHANGED_INT(opt) \
+ if (!CFG_EQ_INT(old_options, new_options, opt)) return 1;
+#define YES_IF_CHANGED_STRING(opt) \
+ if (!CFG_EQ_STRING(old_options, new_options, opt)) return 1;
+#define YES_IF_CHANGED_LINELIST(opt) \
+ if (!CFG_EQ_LINELIST(old_options, new_options, opt)) return 1;
+#define YES_IF_CHANGED_SMARTLIST(opt) \
+ if (!CFG_EQ_SMARTLIST(old_options, new_options, opt)) return 1;
+#define YES_IF_CHANGED_ROUTERSET(opt) \
+ if (!CFG_EQ_ROUTERSET(old_options, new_options, opt)) return 1;
+
+/**
+ * Return true if changing the configuration from <b>old</b> to <b>new</b>
+ * affects the guard subsystem.
+ */
+static int
+options_transition_affects_guards(const or_options_t *old_options,
+ const or_options_t *new_options)
+{
+ /* NOTE: Make sure this function stays in sync with
+ * node_passes_guard_filter */
+ tor_assert(old_options);
+ tor_assert(new_options);
+
+ YES_IF_CHANGED_BOOL(UseEntryGuards);
+ YES_IF_CHANGED_BOOL(UseBridges);
+ YES_IF_CHANGED_BOOL(ClientUseIPv4);
+ YES_IF_CHANGED_BOOL(ClientUseIPv6);
+ YES_IF_CHANGED_BOOL(FascistFirewall);
+ YES_IF_CHANGED_ROUTERSET(ExcludeNodes);
+ YES_IF_CHANGED_ROUTERSET(EntryNodes);
+ YES_IF_CHANGED_SMARTLIST(FirewallPorts);
+ YES_IF_CHANGED_LINELIST(Bridges);
+ YES_IF_CHANGED_LINELIST(ReachableORAddresses);
+ YES_IF_CHANGED_LINELIST(ReachableDirAddresses);
+
+ return 0;
+}
+
+/**
+ * Return true if changing the configuration from <b>old</b> to <b>new</b>
+ * affects the timing of the voting subsystem
+ */
+static int
+options_transition_affects_dirauth_timing(const or_options_t *old_options,
+ const or_options_t *new_options)
+{
+ tor_assert(old_options);
+ tor_assert(new_options);
+
+ if (authdir_mode_v3(old_options) != authdir_mode_v3(new_options))
+ return 1;
+ if (! authdir_mode_v3(new_options))
+ return 0;
+ YES_IF_CHANGED_INT(V3AuthVotingInterval);
+ YES_IF_CHANGED_INT(V3AuthVoteDelay);
+ YES_IF_CHANGED_INT(V3AuthDistDelay);
+ YES_IF_CHANGED_INT(TestingV3AuthInitialVotingInterval);
+ YES_IF_CHANGED_INT(TestingV3AuthInitialVoteDelay);
+ YES_IF_CHANGED_INT(TestingV3AuthInitialDistDelay);
+ YES_IF_CHANGED_INT(TestingV3AuthVotingStartOffset);
+
+ return 0;
+}
+
+/** Fetch the active option list, and take actions based on it. All of the
+ * things we do should survive being done repeatedly. If present,
+ * <b>old_options</b> contains the previous value of the options.
+ *
+ * Return 0 if all goes well, return -1 if it's time to die.
+ *
+ * Note: We haven't moved all the "act on new configuration" logic
+ * here yet. Some is still in do_hup() and other places.
+ */
+STATIC int
+options_act(const or_options_t *old_options)
+{
+ config_line_t *cl;
+ or_options_t *options = get_options_mutable();
+ int running_tor = options->command == CMD_RUN_TOR;
+ char *msg=NULL;
+ const int transition_affects_workers =
+ old_options && options_transition_affects_workers(old_options, options);
+ const int transition_affects_guards =
+ old_options && options_transition_affects_guards(old_options, options);
+
+ if (options->NoExec || options->Sandbox) {
+ tor_disable_spawning_background_processes();
+ }
+
+ /* disable ptrace and later, other basic debugging techniques */
+ {
+ /* Remember if we already disabled debugger attachment */
+ static int disabled_debugger_attach = 0;
+ /* Remember if we already warned about being configured not to disable
+ * debugger attachment */
+ static int warned_debugger_attach = 0;
+ /* Don't disable debugger attachment when we're running the unit tests. */
+ if (options->DisableDebuggerAttachment && !disabled_debugger_attach &&
+ running_tor) {
+ int ok = tor_disable_debugger_attach();
+ /* LCOV_EXCL_START the warned_debugger_attach is 0 can't reach inside. */
+ if (warned_debugger_attach && ok == 1) {
+ log_notice(LD_CONFIG, "Disabled attaching debuggers for unprivileged "
+ "users.");
+ }
+ /* LCOV_EXCL_STOP */
+ disabled_debugger_attach = (ok == 1);
+ } else if (!options->DisableDebuggerAttachment &&
+ !warned_debugger_attach) {
+ log_notice(LD_CONFIG, "Not disabling debugger attaching for "
+ "unprivileged users.");
+ warned_debugger_attach = 1;
+ }
+ }
+
+ /* Write control ports to disk as appropriate */
+ control_ports_write_to_file();
+
+ if (running_tor && !have_lockfile()) {
+ if (try_locking(options, 1) < 0)
+ return -1;
+ }
+
+ {
+ int warning_severity = options->ProtocolWarnings ? LOG_WARN : LOG_INFO;
+ set_protocol_warning_severity_level(warning_severity);
+ }
+
+ if (consider_adding_dir_servers(options, old_options) < 0) {
+ // XXXX This should get validated earlier, and committed here, to
+ // XXXX lower opportunities for reaching an error case.
+ return -1;
+ }
+
+ if (rend_non_anonymous_mode_enabled(options)) {
+ log_warn(LD_GENERAL, "This copy of Tor was compiled or configured to run "
+ "in a non-anonymous mode. It will provide NO ANONYMITY.");
+ }
+
+#ifdef ENABLE_TOR2WEB_MODE
+/* LCOV_EXCL_START */
+ // XXXX This should move into options_validate()
+ if (!options->Tor2webMode) {
+ log_err(LD_CONFIG, "This copy of Tor was compiled to run in "
+ "'tor2web mode'. It can only be run with the Tor2webMode torrc "
+ "option enabled.");
+ return -1;
+ }
+/* LCOV_EXCL_STOP */
+#else /* !(defined(ENABLE_TOR2WEB_MODE)) */
+ // XXXX This should move into options_validate()
+ if (options->Tor2webMode) {
+ log_err(LD_CONFIG, "This copy of Tor was not compiled to run in "
+ "'tor2web mode'. It cannot be run with the Tor2webMode torrc "
+ "option enabled. To enable Tor2webMode recompile with the "
+ "--enable-tor2web-mode option.");
+ return -1;
+ }
+#endif /* defined(ENABLE_TOR2WEB_MODE) */
+
+ /* If we are a bridge with a pluggable transport proxy but no
+ Extended ORPort, inform the user that they are missing out. */
+ if (server_mode(options) && options->ServerTransportPlugin &&
+ !options->ExtORPort_lines) {
+ log_notice(LD_CONFIG, "We use pluggable transports but the Extended "
+ "ORPort is disabled. Tor and your pluggable transports proxy "
+ "communicate with each other via the Extended ORPort so it "
+ "is suggested you enable it: it will also allow your Bridge "
+ "to collect statistics about its clients that use pluggable "
+ "transports. Please enable it using the ExtORPort torrc option "
+ "(e.g. set 'ExtORPort auto').");
+ }
+
+ if (options->Bridges) {
+ mark_bridge_list();
+ for (cl = options->Bridges; cl; cl = cl->next) {
+ bridge_line_t *bridge_line = parse_bridge_line(cl->value);
+ if (!bridge_line) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG,
+ "Previously validated Bridge line could not be added!");
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+ bridge_add_from_config(bridge_line);
+ }
+ sweep_bridge_list();
+ }
+
+ if (running_tor && hs_config_service_all(options, 0)<0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG,
+ "Previously validated hidden services line could not be added!");
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+
+ if (running_tor && rend_parse_service_authorization(options, 0) < 0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG, "Previously validated client authorization for "
+ "hidden services could not be added!");
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+
+ if (running_tor && !old_options && options->OwningControllerFD != -1) {
+#ifdef _WIN32
+ log_warn(LD_CONFIG, "OwningControllerFD is not supported on Windows. "
+ "If you need it, tell the Tor developers.");
+ return -1;
+#else
+ const unsigned ctrl_flags =
+ CC_LOCAL_FD_IS_OWNER |
+ CC_LOCAL_FD_IS_AUTHENTICATED;
+ tor_socket_t ctrl_sock = (tor_socket_t)options->OwningControllerFD;
+ if (control_connection_add_local_fd(ctrl_sock, ctrl_flags) < 0) {
+ log_warn(LD_CONFIG, "Could not add local controller connection with "
+ "given FD.");
+ return -1;
+ }
+#endif /* defined(_WIN32) */
+ }
+
+ /* Load state */
+ if (! or_state_loaded() && running_tor) {
+ if (or_state_load())
+ return -1;
+ rep_hist_load_mtbf_data(time(NULL));
+ }
+
+ /* If we have an ExtORPort, initialize its auth cookie. */
+ if (running_tor &&
+ init_ext_or_cookie_authentication(!!options->ExtORPort_lines) < 0) {
+ log_warn(LD_CONFIG,"Error creating Extended ORPort cookie file.");
+ return -1;
+ }
+
+ mark_transport_list();
+ pt_prepare_proxy_list_for_config_read();
+ if (!options->DisableNetwork) {
+ if (options->ClientTransportPlugin) {
+ for (cl = options->ClientTransportPlugin; cl; cl = cl->next) {
+ if (parse_transport_line(options, cl->value, 0, 0) < 0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG,
+ "Previously validated ClientTransportPlugin line "
+ "could not be added!");
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+ }
+ }
+
+ if (options->ServerTransportPlugin && server_mode(options)) {
+ for (cl = options->ServerTransportPlugin; cl; cl = cl->next) {
+ if (parse_transport_line(options, cl->value, 0, 1) < 0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG,
+ "Previously validated ServerTransportPlugin line "
+ "could not be added!");
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+ }
+ }
+ }
+ sweep_transport_list();
+ sweep_proxy_list();
+
+ /* Start the PT proxy configuration. By doing this configuration
+ here, we also figure out which proxies need to be restarted and
+ which not. */
+ if (pt_proxies_configuration_pending() && !net_is_disabled())
+ pt_configure_remaining_proxies();
+
+ /* Bail out at this point if we're not going to be a client or server:
+ * we want to not fork, and to log stuff to stderr. */
+ if (!running_tor)
+ return 0;
+
+ /* Finish backgrounding the process */
+ if (options->RunAsDaemon) {
+ /* We may be calling this for the n'th time (on SIGHUP), but it's safe. */
+ finish_daemon(options->DataDirectory);
+ }
+
+ /* See whether we need to enable/disable our once-a-second timer. */
+ reschedule_per_second_timer();
+
+ /* We want to reinit keys as needed before we do much of anything else:
+ keys are important, and other things can depend on them. */
+ if (transition_affects_workers ||
+ (options->V3AuthoritativeDir && (!old_options ||
+ !old_options->V3AuthoritativeDir))) {
+ if (init_keys() < 0) {
+ log_warn(LD_BUG,"Error initializing keys; exiting");
+ return -1;
+ }
+ }
+
+ /* Write our PID to the PID file. If we do not have write permissions we
+ * will log a warning and exit. */
+ if (options->PidFile && !sandbox_is_active()) {
+ if (write_pidfile(options->PidFile) < 0) {
+ log_err(LD_CONFIG, "Unable to write PIDFile %s",
+ escaped(options->PidFile));
+ return -1;
+ }
+ }
+
+ /* Register addressmap directives */
+ config_register_addressmaps(options);
+ parse_virtual_addr_network(options->VirtualAddrNetworkIPv4, AF_INET,0,NULL);
+ parse_virtual_addr_network(options->VirtualAddrNetworkIPv6, AF_INET6,0,NULL);
+
+ /* Update address policies. */
+ if (policies_parse_from_options(options) < 0) {
+ /* This should be impossible, but let's be sure. */
+ log_warn(LD_BUG,"Error parsing already-validated policy options.");
+ return -1;
+ }
+
+ if (server_mode(options)) {
+ static int cdm_initialized = 0;
+ if (cdm_initialized == 0) {
+ cdm_initialized = 1;
+ consdiffmgr_configure(NULL);
+ consdiffmgr_validate();
+ }
+ }
+
+ if (init_control_cookie_authentication(options->CookieAuthentication) < 0) {
+ log_warn(LD_CONFIG,"Error creating control cookie authentication file.");
+ return -1;
+ }
+
+ monitor_owning_controller_process(options->OwningControllerProcess);
+
+ /* reload keys as needed for rendezvous services. */
+ if (hs_service_load_all_keys() < 0) {
+ log_warn(LD_GENERAL,"Error loading rendezvous service keys");
+ return -1;
+ }
+
+ /* Inform the scheduler subsystem that a configuration changed happened. It
+ * might be a change of scheduler or parameter. */
+ scheduler_conf_changed();
+
+ /* Set up accounting */
+ if (accounting_parse_options(options, 0)<0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG,"Error in previously validated accounting options");
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+ if (accounting_is_enabled(options))
+ configure_accounting(time(NULL));
+
+ /* Change the cell EWMA settings */
+ cmux_ewma_set_options(options, networkstatus_get_latest_consensus());
+
+ /* Update the BridgePassword's hashed version as needed. We store this as a
+ * digest so that we can do side-channel-proof comparisons on it.
+ */
+ if (options->BridgePassword) {
+ char *http_authenticator;
+ http_authenticator = alloc_http_authenticator(options->BridgePassword);
+ if (!http_authenticator) {
+ // XXXX This should get validated in options_validate().
+ log_warn(LD_BUG, "Unable to allocate HTTP authenticator. Not setting "
+ "BridgePassword.");
+ return -1;
+ }
+ options->BridgePassword_AuthDigest_ = tor_malloc(DIGEST256_LEN);
+ crypto_digest256(options->BridgePassword_AuthDigest_,
+ http_authenticator, strlen(http_authenticator),
+ DIGEST_SHA256);
+ tor_free(http_authenticator);
+ }
+
+ if (parse_outbound_addresses(options, 0, &msg) < 0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG, "Failed parsing previously validated outbound "
+ "bind addresses: %s", msg);
+ tor_free(msg);
+ return -1;
+ // LCOV_EXCL_STOP
+ }
+
+ config_maybe_load_geoip_files_(options, old_options);
+
+ if (geoip_is_loaded(AF_INET) && options->GeoIPExcludeUnknown) {
+ /* ExcludeUnknown is true or "auto" */
+ const int is_auto = options->GeoIPExcludeUnknown == -1;
+ int changed;
+
+ changed = routerset_add_unknown_ccs(&options->ExcludeNodes, is_auto);
+ changed += routerset_add_unknown_ccs(&options->ExcludeExitNodes, is_auto);
+
+ if (changed)
+ routerset_add_unknown_ccs(&options->ExcludeExitNodesUnion_, is_auto);
+ }
+
+ /* Check for transitions that need action. */
+ if (old_options) {
+ int revise_trackexithosts = 0;
+ int revise_automap_entries = 0;
+ int abandon_circuits = 0;
+ if ((options->UseEntryGuards && !old_options->UseEntryGuards) ||
+ options->UseBridges != old_options->UseBridges ||
+ (options->UseBridges &&
+ !config_lines_eq(options->Bridges, old_options->Bridges)) ||
+ !routerset_equal(old_options->ExcludeNodes,options->ExcludeNodes) ||
+ !routerset_equal(old_options->ExcludeExitNodes,
+ options->ExcludeExitNodes) ||
+ !routerset_equal(old_options->EntryNodes, options->EntryNodes) ||
+ !routerset_equal(old_options->ExitNodes, options->ExitNodes) ||
+ !routerset_equal(old_options->HSLayer2Nodes,
+ options->HSLayer2Nodes) ||
+ !routerset_equal(old_options->HSLayer3Nodes,
+ options->HSLayer3Nodes) ||
+ !routerset_equal(old_options->Tor2webRendezvousPoints,
+ options->Tor2webRendezvousPoints) ||
+ options->StrictNodes != old_options->StrictNodes) {
+ log_info(LD_CIRC,
+ "Changed to using entry guards or bridges, or changed "
+ "preferred or excluded node lists. "
+ "Abandoning previous circuits.");
+ abandon_circuits = 1;
+ }
+
+ if (transition_affects_guards) {
+ if (guards_update_all()) {
+ abandon_circuits = 1;
+ }
+ }
+
+ if (abandon_circuits) {
+ circuit_mark_all_unused_circs();
+ circuit_mark_all_dirty_circs_as_unusable();
+ revise_trackexithosts = 1;
+ }
+
+ if (!smartlist_strings_eq(old_options->TrackHostExits,
+ options->TrackHostExits))
+ revise_trackexithosts = 1;
+
+ if (revise_trackexithosts)
+ addressmap_clear_excluded_trackexithosts(options);
+
+ if (!options->AutomapHostsOnResolve &&
+ old_options->AutomapHostsOnResolve) {
+ revise_automap_entries = 1;
+ } else {
+ if (!smartlist_strings_eq(old_options->AutomapHostsSuffixes,
+ options->AutomapHostsSuffixes))
+ revise_automap_entries = 1;
+ else if (!opt_streq(old_options->VirtualAddrNetworkIPv4,
+ options->VirtualAddrNetworkIPv4) ||
+ !opt_streq(old_options->VirtualAddrNetworkIPv6,
+ options->VirtualAddrNetworkIPv6))
+ revise_automap_entries = 1;
+ }
+
+ if (revise_automap_entries)
+ addressmap_clear_invalid_automaps(options);
+
+/* How long should we delay counting bridge stats after becoming a bridge?
+ * We use this so we don't count clients who used our bridge thinking it is
+ * a relay. If you change this, don't forget to change the log message
+ * below. It's 4 hours (the time it takes to stop being used by clients)
+ * plus some extra time for clock skew. */
+#define RELAY_BRIDGE_STATS_DELAY (6 * 60 * 60)
+
+ if (! bool_eq(options->BridgeRelay, old_options->BridgeRelay)) {
+ int was_relay = 0;
+ if (options->BridgeRelay) {
+ time_t int_start = time(NULL);
+ if (config_lines_eq(old_options->ORPort_lines,options->ORPort_lines)) {
+ int_start += RELAY_BRIDGE_STATS_DELAY;
+ was_relay = 1;
+ }
+ geoip_bridge_stats_init(int_start);
+ log_info(LD_CONFIG, "We are acting as a bridge now. Starting new "
+ "GeoIP stats interval%s.", was_relay ? " in 6 "
+ "hours from now" : "");
+ } else {
+ geoip_bridge_stats_term();
+ log_info(LD_GENERAL, "We are no longer acting as a bridge. "
+ "Forgetting GeoIP stats.");
+ }
+ }
+
+ if (transition_affects_workers) {
+ log_info(LD_GENERAL,
+ "Worker-related options changed. Rotating workers.");
+ const int server_mode_turned_on =
+ server_mode(options) && !server_mode(old_options);
+ const int dir_server_mode_turned_on =
+ dir_server_mode(options) && !dir_server_mode(old_options);
+
+ if (server_mode_turned_on || dir_server_mode_turned_on) {
+ cpu_init();
+ }
+
+ if (server_mode_turned_on) {
+ ip_address_changed(0);
+ if (have_completed_a_circuit() || !any_predicted_circuits(time(NULL)))
+ inform_testing_reachability();
+ }
+ cpuworkers_rotate_keyinfo();
+ if (dns_reset())
+ return -1;
+ } else {
+ if (dns_reset())
+ return -1;
+ }
+
+ if (options->PerConnBWRate != old_options->PerConnBWRate ||
+ options->PerConnBWBurst != old_options->PerConnBWBurst)
+ connection_or_update_token_buckets(get_connection_array(), options);
+
+ if (options->BandwidthRate != old_options->BandwidthRate ||
+ options->BandwidthBurst != old_options->BandwidthBurst ||
+ options->RelayBandwidthRate != old_options->RelayBandwidthRate ||
+ options->RelayBandwidthBurst != old_options->RelayBandwidthBurst)
+ connection_bucket_adjust(options);
+
+ if (options->MainloopStats != old_options->MainloopStats) {
+ reset_main_loop_counters();
+ }
+ }
+
+ /* Only collect directory-request statistics on relays and bridges. */
+ options->DirReqStatistics = options->DirReqStatistics_option &&
+ server_mode(options);
+ options->HiddenServiceStatistics =
+ options->HiddenServiceStatistics_option && server_mode(options);
+
+ if (options->CellStatistics || options->DirReqStatistics ||
+ options->EntryStatistics || options->ExitPortStatistics ||
+ options->ConnDirectionStatistics ||
+ options->HiddenServiceStatistics ||
+ options->BridgeAuthoritativeDir) {
+ time_t now = time(NULL);
+ int print_notice = 0;
+
+ /* Only collect other relay-only statistics on relays. */
+ if (!public_server_mode(options)) {
+ options->CellStatistics = 0;
+ options->EntryStatistics = 0;
+ options->ConnDirectionStatistics = 0;
+ options->ExitPortStatistics = 0;
+ }
+
+ if ((!old_options || !old_options->CellStatistics) &&
+ options->CellStatistics) {
+ rep_hist_buffer_stats_init(now);
+ print_notice = 1;
+ }
+ if ((!old_options || !old_options->DirReqStatistics) &&
+ options->DirReqStatistics) {
+ if (geoip_is_loaded(AF_INET)) {
+ geoip_dirreq_stats_init(now);
+ print_notice = 1;
+ } else {
+ /* disable statistics collection since we have no geoip file */
+ options->DirReqStatistics = 0;
+ if (options->ORPort_set)
+ log_notice(LD_CONFIG, "Configured to measure directory request "
+ "statistics, but no GeoIP database found. "
+ "Please specify a GeoIP database using the "
+ "GeoIPFile option.");
+ }
+ }
+ if ((!old_options || !old_options->EntryStatistics) &&
+ options->EntryStatistics && !should_record_bridge_info(options)) {
+ /* If we get here, we've started recording bridge info when we didn't
+ * do so before. Note that "should_record_bridge_info()" will
+ * always be false at this point, because of the earlier block
+ * that cleared EntryStatistics when public_server_mode() was false.
+ * We're leaving it in as defensive programming. */
+ if (geoip_is_loaded(AF_INET) || geoip_is_loaded(AF_INET6)) {
+ geoip_entry_stats_init(now);
+ print_notice = 1;
+ } else {
+ options->EntryStatistics = 0;
+ log_notice(LD_CONFIG, "Configured to measure entry node "
+ "statistics, but no GeoIP database found. "
+ "Please specify a GeoIP database using the "
+ "GeoIPFile option.");
+ }
+ }
+ if ((!old_options || !old_options->ExitPortStatistics) &&
+ options->ExitPortStatistics) {
+ rep_hist_exit_stats_init(now);
+ print_notice = 1;
+ }
+ if ((!old_options || !old_options->ConnDirectionStatistics) &&
+ options->ConnDirectionStatistics) {
+ rep_hist_conn_stats_init(now);
+ }
+ if ((!old_options || !old_options->HiddenServiceStatistics) &&
+ options->HiddenServiceStatistics) {
+ log_info(LD_CONFIG, "Configured to measure hidden service statistics.");
+ rep_hist_hs_stats_init(now);
+ }
+ if ((!old_options || !old_options->BridgeAuthoritativeDir) &&
+ options->BridgeAuthoritativeDir) {
+ rep_hist_desc_stats_init(now);
+ print_notice = 1;
+ }
+ if (print_notice)
+ log_notice(LD_CONFIG, "Configured to measure statistics. Look for "
+ "the *-stats files that will first be written to the "
+ "data directory in 24 hours from now.");
+ }
+
+ /* If we used to have statistics enabled but we just disabled them,
+ stop gathering them. */
+ if (old_options && old_options->CellStatistics &&
+ !options->CellStatistics)
+ rep_hist_buffer_stats_term();
+ if (old_options && old_options->DirReqStatistics &&
+ !options->DirReqStatistics)
+ geoip_dirreq_stats_term();
+ if (old_options && old_options->EntryStatistics &&
+ !options->EntryStatistics)
+ geoip_entry_stats_term();
+ if (old_options && old_options->HiddenServiceStatistics &&
+ !options->HiddenServiceStatistics)
+ rep_hist_hs_stats_term();
+ if (old_options && old_options->ExitPortStatistics &&
+ !options->ExitPortStatistics)
+ rep_hist_exit_stats_term();
+ if (old_options && old_options->ConnDirectionStatistics &&
+ !options->ConnDirectionStatistics)
+ rep_hist_conn_stats_term();
+ if (old_options && old_options->BridgeAuthoritativeDir &&
+ !options->BridgeAuthoritativeDir)
+ rep_hist_desc_stats_term();
+
+ /* Since our options changed, we might need to regenerate and upload our
+ * server descriptor.
+ */
+ if (!old_options ||
+ options_transition_affects_descriptor(old_options, options))
+ mark_my_descriptor_dirty("config change");
+
+ /* We may need to reschedule some directory stuff if our status changed. */
+ if (old_options) {
+ if (options_transition_affects_dirauth_timing(old_options, options)) {
+ voting_schedule_recalculate_timing(options, time(NULL));
+ reschedule_dirvote(options);
+ }
+ if (!bool_eq(directory_fetches_dir_info_early(options),
+ directory_fetches_dir_info_early(old_options)) ||
+ !bool_eq(directory_fetches_dir_info_later(options),
+ directory_fetches_dir_info_later(old_options))) {
+ /* Make sure update_router_have_minimum_dir_info() gets called. */
+ router_dir_info_changed();
+ /* We might need to download a new consensus status later or sooner than
+ * we had expected. */
+ update_consensus_networkstatus_fetch_time(time(NULL));
+ }
+ }
+
+ /* DoS mitigation subsystem only applies to public relay. */
+ if (public_server_mode(options)) {
+ /* If we are configured as a relay, initialize the subsystem. Even on HUP,
+ * this is safe to call as it will load data from the current options
+ * or/and the consensus. */
+ dos_init();
+ } else if (old_options && public_server_mode(old_options)) {
+ /* Going from relay to non relay, clean it up. */
+ dos_free_all();
+ }
+
+ /* Load the webpage we're going to serve every time someone asks for '/' on
+ our DirPort. */
+ tor_free(global_dirfrontpagecontents);
+ if (options->DirPortFrontPage) {
+ global_dirfrontpagecontents =
+ read_file_to_str(options->DirPortFrontPage, 0, NULL);
+ if (!global_dirfrontpagecontents) {
+ log_warn(LD_CONFIG,
+ "DirPortFrontPage file '%s' not found. Continuing anyway.",
+ options->DirPortFrontPage);
+ }
+ }
+
+ return 0;
+}
+
+typedef enum {
+ TAKES_NO_ARGUMENT = 0,
+ ARGUMENT_NECESSARY = 1,
+ ARGUMENT_OPTIONAL = 2
+} takes_argument_t;
+
+static const struct {
+ const char *name;
+ takes_argument_t takes_argument;
+} CMDLINE_ONLY_OPTIONS[] = {
+ { "-f", ARGUMENT_NECESSARY },
+ { "--allow-missing-torrc", TAKES_NO_ARGUMENT },
+ { "--defaults-torrc", ARGUMENT_NECESSARY },
+ { "--hash-password", ARGUMENT_NECESSARY },
+ { "--dump-config", ARGUMENT_OPTIONAL },
+ { "--list-fingerprint", TAKES_NO_ARGUMENT },
+ { "--keygen", TAKES_NO_ARGUMENT },
+ { "--key-expiration", ARGUMENT_OPTIONAL },
+ { "--newpass", TAKES_NO_ARGUMENT },
+ { "--no-passphrase", TAKES_NO_ARGUMENT },
+ { "--passphrase-fd", ARGUMENT_NECESSARY },
+ { "--verify-config", TAKES_NO_ARGUMENT },
+ { "--ignore-missing-torrc", TAKES_NO_ARGUMENT },
+ { "--quiet", TAKES_NO_ARGUMENT },
+ { "--hush", TAKES_NO_ARGUMENT },
+ { "--version", TAKES_NO_ARGUMENT },
+ { "--library-versions", TAKES_NO_ARGUMENT },
+ { "-h", TAKES_NO_ARGUMENT },
+ { "--help", TAKES_NO_ARGUMENT },
+ { "--list-torrc-options", TAKES_NO_ARGUMENT },
+ { "--list-deprecated-options",TAKES_NO_ARGUMENT },
+ { "--nt-service", TAKES_NO_ARGUMENT },
+ { "-nt-service", TAKES_NO_ARGUMENT },
+ { NULL, 0 },
+};
+
+/** Helper: Read a list of configuration options from the command line. If
+ * successful, or if ignore_errors is set, put them in *<b>result</b>, put the
+ * commandline-only options in *<b>cmdline_result</b>, and return 0;
+ * otherwise, return -1 and leave *<b>result</b> and <b>cmdline_result</b>
+ * alone. */
+int
+config_parse_commandline(int argc, char **argv, int ignore_errors,
+ config_line_t **result,
+ config_line_t **cmdline_result)
+{
+ config_line_t *param = NULL;
+
+ config_line_t *front = NULL;
+ config_line_t **new = &front;
+
+ config_line_t *front_cmdline = NULL;
+ config_line_t **new_cmdline = &front_cmdline;
+
+ char *s, *arg;
+ int i = 1;
+
+ while (i < argc) {
+ unsigned command = CONFIG_LINE_NORMAL;
+ takes_argument_t want_arg = ARGUMENT_NECESSARY;
+ int is_cmdline = 0;
+ int j;
+
+ for (j = 0; CMDLINE_ONLY_OPTIONS[j].name != NULL; ++j) {
+ if (!strcmp(argv[i], CMDLINE_ONLY_OPTIONS[j].name)) {
+ is_cmdline = 1;
+ want_arg = CMDLINE_ONLY_OPTIONS[j].takes_argument;
+ break;
+ }
+ }
+
+ s = argv[i];
+
+ /* Each keyword may be prefixed with one or two dashes. */
+ if (*s == '-')
+ s++;
+ if (*s == '-')
+ s++;
+ /* Figure out the command, if any. */
+ if (*s == '+') {
+ s++;
+ command = CONFIG_LINE_APPEND;
+ } else if (*s == '/') {
+ s++;
+ command = CONFIG_LINE_CLEAR;
+ /* A 'clear' command has no argument. */
+ want_arg = 0;
+ }
+
+ const int is_last = (i == argc-1);
+
+ if (want_arg == ARGUMENT_NECESSARY && is_last) {
+ if (ignore_errors) {
+ arg = tor_strdup("");
+ } else {
+ log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.",
+ argv[i]);
+ config_free_lines(front);
+ config_free_lines(front_cmdline);
+ return -1;
+ }
+ } else if (want_arg == ARGUMENT_OPTIONAL && is_last) {
+ arg = tor_strdup("");
+ } else {
+ arg = (want_arg != TAKES_NO_ARGUMENT) ? tor_strdup(argv[i+1]) :
+ tor_strdup("");
+ }
+
+ param = tor_malloc_zero(sizeof(config_line_t));
+ param->key = is_cmdline ? tor_strdup(argv[i]) :
+ tor_strdup(config_expand_abbrev(&options_format, s, 1, 1));
+ param->value = arg;
+ param->command = command;
+ param->next = NULL;
+ log_debug(LD_CONFIG, "command line: parsed keyword '%s', value '%s'",
+ param->key, param->value);
+
+ if (is_cmdline) {
+ *new_cmdline = param;
+ new_cmdline = &((*new_cmdline)->next);
+ } else {
+ *new = param;
+ new = &((*new)->next);
+ }
+
+ i += want_arg ? 2 : 1;
+ }
+ *cmdline_result = front_cmdline;
+ *result = front;
+ return 0;
+}
+
+/** Return true iff key is a valid configuration option. */
+int
+option_is_recognized(const char *key)
+{
+ const config_var_t *var = config_find_option(&options_format, key);
+ return (var != NULL);
+}
+
+/** Return the canonical name of a configuration option, or NULL
+ * if no such option exists. */
+const char *
+option_get_canonical_name(const char *key)
+{
+ const config_var_t *var = config_find_option(&options_format, key);
+ return var ? var->name : NULL;
+}
+
+/** Return a canonical list of the options assigned for key.
+ */
+config_line_t *
+option_get_assignment(const or_options_t *options, const char *key)
+{
+ return config_get_assigned_option(&options_format, options, key, 1);
+}
+
+/** Try assigning <b>list</b> to the global options. You do this by duping
+ * options, assigning list to the new one, then validating it. If it's
+ * ok, then throw out the old one and stick with the new one. Else,
+ * revert to old and return failure. Return SETOPT_OK on success, or
+ * a setopt_err_t on failure.
+ *
+ * If not success, point *<b>msg</b> to a newly allocated string describing
+ * what went wrong.
+ */
+setopt_err_t
+options_trial_assign(config_line_t *list, unsigned flags, char **msg)
+{
+ int r;
+ or_options_t *trial_options = config_dup(&options_format, get_options());
+
+ if ((r=config_assign(&options_format, trial_options,
+ list, flags, msg)) < 0) {
+ or_options_free(trial_options);
+ return r;
+ }
+
+ setopt_err_t rv;
+ or_options_t *cur_options = get_options_mutable();
+
+ in_option_validation = 1;
+
+ if (options_validate(cur_options, trial_options,
+ global_default_options, 1, msg) < 0) {
+ or_options_free(trial_options);
+ rv = SETOPT_ERR_PARSE; /*XXX make this a separate return value. */
+ goto done;
+ }
+
+ if (options_transition_allowed(cur_options, trial_options, msg) < 0) {
+ or_options_free(trial_options);
+ rv = SETOPT_ERR_TRANSITION;
+ goto done;
+ }
+ in_option_validation = 0;
+
+ if (set_options(trial_options, msg)<0) {
+ or_options_free(trial_options);
+ rv = SETOPT_ERR_SETTING;
+ goto done;
+ }
+
+ /* we liked it. put it in place. */
+ rv = SETOPT_OK;
+ done:
+ in_option_validation = 0;
+ return rv;
+}
+
+/** Print a usage message for tor. */
+static void
+print_usage(void)
+{
+ printf(
+"Copyright (c) 2001-2004, Roger Dingledine\n"
+"Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson\n"
+"Copyright (c) 2007-2018, The Tor Project, Inc.\n\n"
+"tor -f <torrc> [args]\n"
+"See man page for options, or https://www.torproject.org/ for "
+"documentation.\n");
+}
+
+/** Print all non-obsolete torrc options. */
+static void
+list_torrc_options(void)
+{
+ int i;
+ for (i = 0; option_vars_[i].name; ++i) {
+ const config_var_t *var = &option_vars_[i];
+ if (var->type == CONFIG_TYPE_OBSOLETE ||
+ var->type == CONFIG_TYPE_LINELIST_V)
+ continue;
+ printf("%s\n", var->name);
+ }
+}
+
+/** Print all deprecated but non-obsolete torrc options. */
+static void
+list_deprecated_options(void)
+{
+ const config_deprecation_t *d;
+ for (d = option_deprecation_notes_; d->name; ++d) {
+ printf("%s\n", d->name);
+ }
+}
+
+/** Last value actually set by resolve_my_address. */
+static uint32_t last_resolved_addr = 0;
+
+/** Accessor for last_resolved_addr from outside this file. */
+uint32_t
+get_last_resolved_addr(void)
+{
+ return last_resolved_addr;
+}
+
+/** Reset last_resolved_addr from outside this file. */
+void
+reset_last_resolved_addr(void)
+{
+ last_resolved_addr = 0;
+}
+
+/* Return true if <b>options</b> is using the default authorities, and false
+ * if any authority-related option has been overridden. */
+int
+using_default_dir_authorities(const or_options_t *options)
+{
+ return (!options->DirAuthorities && !options->AlternateDirAuthority);
+}
+
+/**
+ * Attempt getting our non-local (as judged by tor_addr_is_internal()
+ * function) IP address using following techniques, listed in
+ * order from best (most desirable, try first) to worst (least
+ * desirable, try if everything else fails).
+ *
+ * First, attempt using <b>options-\>Address</b> to get our
+ * non-local IP address.
+ *
+ * If <b>options-\>Address</b> represents a non-local IP address,
+ * consider it ours.
+ *
+ * If <b>options-\>Address</b> is a DNS name that resolves to
+ * a non-local IP address, consider this IP address ours.
+ *
+ * If <b>options-\>Address</b> is NULL, fall back to getting local
+ * hostname and using it in above-described ways to try and
+ * get our IP address.
+ *
+ * In case local hostname cannot be resolved to a non-local IP
+ * address, try getting an IP address of network interface
+ * in hopes it will be non-local one.
+ *
+ * Fail if one or more of the following is true:
+ * - DNS name in <b>options-\>Address</b> cannot be resolved.
+ * - <b>options-\>Address</b> is a local host address.
+ * - Attempt at getting local hostname fails.
+ * - Attempt at getting network interface address fails.
+ *
+ * Return 0 if all is well, or -1 if we can't find a suitable
+ * public IP address.
+ *
+ * If we are returning 0:
+ * - Put our public IP address (in host order) into *<b>addr_out</b>.
+ * - If <b>method_out</b> is non-NULL, set *<b>method_out</b> to a static
+ * string describing how we arrived at our answer.
+ * - "CONFIGURED" - parsed from IP address string in
+ * <b>options-\>Address</b>
+ * - "RESOLVED" - resolved from DNS name in <b>options-\>Address</b>
+ * - "GETHOSTNAME" - resolved from a local hostname.
+ * - "INTERFACE" - retrieved from a network interface.
+ * - If <b>hostname_out</b> is non-NULL, and we resolved a hostname to
+ * get our address, set *<b>hostname_out</b> to a newly allocated string
+ * holding that hostname. (If we didn't get our address by resolving a
+ * hostname, set *<b>hostname_out</b> to NULL.)
+ *
+ * XXXX ipv6
+ */
+int
+resolve_my_address(int warn_severity, const or_options_t *options,
+ uint32_t *addr_out,
+ const char **method_out, char **hostname_out)
+{
+ struct in_addr in;
+ uint32_t addr; /* host order */
+ char hostname[256];
+ const char *method_used;
+ const char *hostname_used;
+ int explicit_ip=1;
+ int explicit_hostname=1;
+ int from_interface=0;
+ char *addr_string = NULL;
+ const char *address = options->Address;
+ int notice_severity = warn_severity <= LOG_NOTICE ?
+ LOG_NOTICE : warn_severity;
+
+ tor_addr_t myaddr;
+ tor_assert(addr_out);
+
+ /*
+ * Step one: Fill in 'hostname' to be our best guess.
+ */
+
+ if (address && *address) {
+ strlcpy(hostname, address, sizeof(hostname));
+ } else { /* then we need to guess our address */
+ explicit_ip = 0; /* it's implicit */
+ explicit_hostname = 0; /* it's implicit */
+
+ if (tor_gethostname(hostname, sizeof(hostname)) < 0) {
+ log_fn(warn_severity, LD_NET,"Error obtaining local hostname");
+ return -1;
+ }
+ log_debug(LD_CONFIG, "Guessed local host name as '%s'", hostname);
+ }
+
+ /*
+ * Step two: Now that we know 'hostname', parse it or resolve it. If
+ * it doesn't parse or resolve, look at the interface address. Set 'addr'
+ * to be our (host-order) 32-bit answer.
+ */
+
+ if (tor_inet_aton(hostname, &in) == 0) {
+ /* then we have to resolve it */
+ explicit_ip = 0;
+ if (tor_lookup_hostname(hostname, &addr)) { /* failed to resolve */
+ uint32_t interface_ip; /* host order */
+
+ if (explicit_hostname) {
+ log_fn(warn_severity, LD_CONFIG,
+ "Could not resolve local Address '%s'. Failing.", hostname);
+ return -1;
+ }
+ log_fn(notice_severity, LD_CONFIG,
+ "Could not resolve guessed local hostname '%s'. "
+ "Trying something else.", hostname);
+ if (get_interface_address(warn_severity, &interface_ip)) {
+ log_fn(warn_severity, LD_CONFIG,
+ "Could not get local interface IP address. Failing.");
+ return -1;
+ }
+ from_interface = 1;
+ addr = interface_ip;
+ log_fn(notice_severity, LD_CONFIG, "Learned IP address '%s' for "
+ "local interface. Using that.", fmt_addr32(addr));
+ strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname));
+ } else { /* resolved hostname into addr */
+ tor_addr_from_ipv4h(&myaddr, addr);
+
+ if (!explicit_hostname &&
+ tor_addr_is_internal(&myaddr, 0)) {
+ tor_addr_t interface_ip;
+
+ log_fn(notice_severity, LD_CONFIG, "Guessed local hostname '%s' "
+ "resolves to a private IP address (%s). Trying something "
+ "else.", hostname, fmt_addr32(addr));
+
+ if (get_interface_address6(warn_severity, AF_INET, &interface_ip)<0) {
+ log_fn(warn_severity, LD_CONFIG,
+ "Could not get local interface IP address. Too bad.");
+ } else if (tor_addr_is_internal(&interface_ip, 0)) {
+ log_fn(notice_severity, LD_CONFIG,
+ "Interface IP address '%s' is a private address too. "
+ "Ignoring.", fmt_addr(&interface_ip));
+ } else {
+ from_interface = 1;
+ addr = tor_addr_to_ipv4h(&interface_ip);
+ log_fn(notice_severity, LD_CONFIG,
+ "Learned IP address '%s' for local interface."
+ " Using that.", fmt_addr32(addr));
+ strlcpy(hostname, "<guessed from interfaces>", sizeof(hostname));
+ }
+ }
+ }
+ } else {
+ addr = ntohl(in.s_addr); /* set addr so that addr_string is not
+ * illformed */
+ }
+
+ /*
+ * Step three: Check whether 'addr' is an internal IP address, and error
+ * out if it is and we don't want that.
+ */
+
+ tor_addr_from_ipv4h(&myaddr,addr);
+
+ addr_string = tor_dup_ip(addr);
+ if (tor_addr_is_internal(&myaddr, 0)) {
+ /* make sure we're ok with publishing an internal IP */
+ if (using_default_dir_authorities(options)) {
+ /* if they are using the default authorities, disallow internal IPs
+ * always. */
+ log_fn(warn_severity, LD_CONFIG,
+ "Address '%s' resolves to private IP address '%s'. "
+ "Tor servers that use the default DirAuthorities must have "
+ "public IP addresses.", hostname, addr_string);
+ tor_free(addr_string);
+ return -1;
+ }
+ if (!explicit_ip) {
+ /* even if they've set their own authorities, require an explicit IP if
+ * they're using an internal address. */
+ log_fn(warn_severity, LD_CONFIG, "Address '%s' resolves to private "
+ "IP address '%s'. Please set the Address config option to be "
+ "the IP address you want to use.", hostname, addr_string);
+ tor_free(addr_string);
+ return -1;
+ }
+ }
+
+ /*
+ * Step four: We have a winner! 'addr' is our answer for sure, and
+ * 'addr_string' is its string form. Fill out the various fields to
+ * say how we decided it.
+ */
+
+ log_debug(LD_CONFIG, "Resolved Address to '%s'.", addr_string);
+
+ if (explicit_ip) {
+ method_used = "CONFIGURED";
+ hostname_used = NULL;
+ } else if (explicit_hostname) {
+ method_used = "RESOLVED";
+ hostname_used = hostname;
+ } else if (from_interface) {
+ method_used = "INTERFACE";
+ hostname_used = NULL;
+ } else {
+ method_used = "GETHOSTNAME";
+ hostname_used = hostname;
+ }
+
+ *addr_out = addr;
+ if (method_out)
+ *method_out = method_used;
+ if (hostname_out)
+ *hostname_out = hostname_used ? tor_strdup(hostname_used) : NULL;
+
+ /*
+ * Step five: Check if the answer has changed since last time (or if
+ * there was no last time), and if so call various functions to keep
+ * us up-to-date.
+ */
+
+ if (last_resolved_addr && last_resolved_addr != *addr_out) {
+ /* Leave this as a notice, regardless of the requested severity,
+ * at least until dynamic IP address support becomes bulletproof. */
+ log_notice(LD_NET,
+ "Your IP address seems to have changed to %s "
+ "(METHOD=%s%s%s). Updating.",
+ addr_string, method_used,
+ hostname_used ? " HOSTNAME=" : "",
+ hostname_used ? hostname_used : "");
+ ip_address_changed(0);
+ }
+
+ if (last_resolved_addr != *addr_out) {
+ control_event_server_status(LOG_NOTICE,
+ "EXTERNAL_ADDRESS ADDRESS=%s METHOD=%s%s%s",
+ addr_string, method_used,
+ hostname_used ? " HOSTNAME=" : "",
+ hostname_used ? hostname_used : "");
+ }
+ last_resolved_addr = *addr_out;
+
+ /*
+ * And finally, clean up and return success.
+ */
+
+ tor_free(addr_string);
+ return 0;
+}
+
+/** Return true iff <b>addr</b> is judged to be on the same network as us, or
+ * on a private network.
+ */
+MOCK_IMPL(int,
+is_local_addr, (const tor_addr_t *addr))
+{
+ if (tor_addr_is_internal(addr, 0))
+ return 1;
+ /* Check whether ip is on the same /24 as we are. */
+ if (get_options()->EnforceDistinctSubnets == 0)
+ return 0;
+ if (tor_addr_family(addr) == AF_INET) {
+ uint32_t ip = tor_addr_to_ipv4h(addr);
+
+ /* It's possible that this next check will hit before the first time
+ * resolve_my_address actually succeeds. (For clients, it is likely that
+ * resolve_my_address will never be called at all). In those cases,
+ * last_resolved_addr will be 0, and so checking to see whether ip is on
+ * the same /24 as last_resolved_addr will be the same as checking whether
+ * it was on net 0, which is already done by tor_addr_is_internal.
+ */
+ if ((last_resolved_addr & (uint32_t)0xffffff00ul)
+ == (ip & (uint32_t)0xffffff00ul))
+ return 1;
+ }
+ return 0;
+}
+
+/** Return a new empty or_options_t. Used for testing. */
+or_options_t *
+options_new(void)
+{
+ return config_new(&options_format);
+}
+
+/** Set <b>options</b> to hold reasonable defaults for most options.
+ * Each option defaults to zero. */
+void
+options_init(or_options_t *options)
+{
+ config_init(&options_format, options);
+}
+
+/** Return a string containing a possible configuration file that would give
+ * the configuration in <b>options</b>. If <b>minimal</b> is true, do not
+ * include options that are the same as Tor's defaults.
+ */
+char *
+options_dump(const or_options_t *options, int how_to_dump)
+{
+ const or_options_t *use_defaults;
+ int minimal;
+ switch (how_to_dump) {
+ case OPTIONS_DUMP_MINIMAL:
+ use_defaults = global_default_options;
+ minimal = 1;
+ break;
+ case OPTIONS_DUMP_DEFAULTS:
+ use_defaults = NULL;
+ minimal = 1;
+ break;
+ case OPTIONS_DUMP_ALL:
+ use_defaults = NULL;
+ minimal = 0;
+ break;
+ default:
+ log_warn(LD_BUG, "Bogus value for how_to_dump==%d", how_to_dump);
+ return NULL;
+ }
+
+ return config_dump(&options_format, use_defaults, options, minimal, 0);
+}
+
+/** Return 0 if every element of sl is a string holding a decimal
+ * representation of a port number, or if sl is NULL.
+ * Otherwise set *msg and return -1. */
+static int
+validate_ports_csv(smartlist_t *sl, const char *name, char **msg)
+{
+ int i;
+ tor_assert(name);
+
+ if (!sl)
+ return 0;
+
+ SMARTLIST_FOREACH(sl, const char *, cp,
+ {
+ i = atoi(cp);
+ if (i < 1 || i > 65535) {
+ tor_asprintf(msg, "Port '%s' out of range in %s", cp, name);
+ return -1;
+ }
+ });
+ return 0;
+}
+
+/** If <b>value</b> exceeds ROUTER_MAX_DECLARED_BANDWIDTH, write
+ * a complaint into *<b>msg</b> using string <b>desc</b>, and return -1.
+ * Else return 0.
+ */
+static int
+ensure_bandwidth_cap(uint64_t *value, const char *desc, char **msg)
+{
+ if (*value > ROUTER_MAX_DECLARED_BANDWIDTH) {
+ /* This handles an understandable special case where somebody says "2gb"
+ * whereas our actual maximum is 2gb-1 (INT_MAX) */
+ --*value;
+ }
+ if (*value > ROUTER_MAX_DECLARED_BANDWIDTH) {
+ tor_asprintf(msg, "%s (%"PRIu64") must be at most %d",
+ desc, (*value),
+ ROUTER_MAX_DECLARED_BANDWIDTH);
+ return -1;
+ }
+ return 0;
+}
+
+/** Parse an authority type from <b>options</b>-\>PublishServerDescriptor
+ * and write it to <b>options</b>-\>PublishServerDescriptor_. Treat "1"
+ * as "v3" unless BridgeRelay is 1, in which case treat it as "bridge".
+ * Treat "0" as "".
+ * Return 0 on success or -1 if not a recognized authority type (in which
+ * case the value of PublishServerDescriptor_ is undefined). */
+static int
+compute_publishserverdescriptor(or_options_t *options)
+{
+ smartlist_t *list = options->PublishServerDescriptor;
+ dirinfo_type_t *auth = &options->PublishServerDescriptor_;
+ *auth = NO_DIRINFO;
+ if (!list) /* empty list, answer is none */
+ return 0;
+ SMARTLIST_FOREACH_BEGIN(list, const char *, string) {
+ if (!strcasecmp(string, "v1"))
+ log_warn(LD_CONFIG, "PublishServerDescriptor v1 has no effect, because "
+ "there are no v1 directory authorities anymore.");
+ else if (!strcmp(string, "1"))
+ if (options->BridgeRelay)
+ *auth |= BRIDGE_DIRINFO;
+ else
+ *auth |= V3_DIRINFO;
+ else if (!strcasecmp(string, "v2"))
+ log_warn(LD_CONFIG, "PublishServerDescriptor v2 has no effect, because "
+ "there are no v2 directory authorities anymore.");
+ else if (!strcasecmp(string, "v3"))
+ *auth |= V3_DIRINFO;
+ else if (!strcasecmp(string, "bridge"))
+ *auth |= BRIDGE_DIRINFO;
+ else if (!strcasecmp(string, "hidserv"))
+ log_warn(LD_CONFIG,
+ "PublishServerDescriptor hidserv is invalid. See "
+ "PublishHidServDescriptors.");
+ else if (!strcasecmp(string, "") || !strcmp(string, "0"))
+ /* no authority */;
+ else
+ return -1;
+ } SMARTLIST_FOREACH_END(string);
+ return 0;
+}
+
+/** Lowest allowable value for RendPostPeriod; if this is too low, hidden
+ * services can overload the directory system. */
+#define MIN_REND_POST_PERIOD (10*60)
+#define MIN_REND_POST_PERIOD_TESTING (5)
+
+/** Highest allowable value for CircuitsAvailableTimeout.
+ * If this is too large, client connections will stay open for too long,
+ * incurring extra padding overhead. */
+#define MAX_CIRCS_AVAILABLE_TIME (24*60*60)
+
+/** Highest allowable value for RendPostPeriod. */
+#define MAX_DIR_PERIOD ((7*24*60*60)/2)
+
+/** Lowest allowable value for MaxCircuitDirtiness; if this is too low, Tor
+ * will generate too many circuits and potentially overload the network. */
+#define MIN_MAX_CIRCUIT_DIRTINESS 10
+
+/** Highest allowable value for MaxCircuitDirtiness: prevents time_t
+ * overflows. */
+#define MAX_MAX_CIRCUIT_DIRTINESS (30*24*60*60)
+
+/** Lowest allowable value for CircuitStreamTimeout; if this is too low, Tor
+ * will generate too many circuits and potentially overload the network. */
+#define MIN_CIRCUIT_STREAM_TIMEOUT 10
+
+/** Lowest recommended value for CircuitBuildTimeout; if it is set too low
+ * and LearnCircuitBuildTimeout is off, the failure rate for circuit
+ * construction may be very high. In that case, if it is set below this
+ * threshold emit a warning.
+ * */
+#define RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT (10)
+
+static int
+options_validate_cb(void *old_options, void *options, void *default_options,
+ int from_setconf, char **msg)
+{
+ in_option_validation = 1;
+ int rv = options_validate(old_options, options, default_options,
+ from_setconf, msg);
+ in_option_validation = 0;
+ return rv;
+}
+
+#define REJECT(arg) \
+ STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END
+#if defined(__GNUC__) && __GNUC__ <= 3
+#define COMPLAIN(args...) \
+ STMT_BEGIN log_warn(LD_CONFIG, args); STMT_END
+#else
+#define COMPLAIN(args, ...) \
+ STMT_BEGIN log_warn(LD_CONFIG, args, ##__VA_ARGS__); STMT_END
+#endif /* defined(__GNUC__) && __GNUC__ <= 3 */
+
+/** Log a warning message iff <b>filepath</b> is not absolute.
+ * Warning message must contain option name <b>option</b> and
+ * an absolute path that <b>filepath</b> will resolve to.
+ *
+ * In case <b>filepath</b> is absolute, do nothing.
+ *
+ * Return 1 if there were relative paths; 0 otherwise.
+ */
+static int
+warn_if_option_path_is_relative(const char *option,
+ char *filepath)
+{
+ if (filepath && path_is_relative(filepath)) {
+ char *abs_path = make_path_absolute(filepath);
+ COMPLAIN("Path for %s (%s) is relative and will resolve to %s."
+ " Is this what you wanted?", option, filepath, abs_path);
+ tor_free(abs_path);
+ return 1;
+ }
+ return 0;
+}
+
+/** Scan <b>options</b> for occurrences of relative file/directory
+ * path and log a warning whenever it is found.
+ *
+ * Return 1 if there were relative paths; 0 otherwise.
+ */
+static int
+warn_about_relative_paths(or_options_t *options)
+{
+ tor_assert(options);
+ int n = 0;
+
+ n += warn_if_option_path_is_relative("CookieAuthFile",
+ options->CookieAuthFile);
+ n += warn_if_option_path_is_relative("ExtORPortCookieAuthFile",
+ options->ExtORPortCookieAuthFile);
+ n += warn_if_option_path_is_relative("DirPortFrontPage",
+ options->DirPortFrontPage);
+ n += warn_if_option_path_is_relative("V3BandwidthsFile",
+ options->V3BandwidthsFile);
+ n += warn_if_option_path_is_relative("ControlPortWriteToFile",
+ options->ControlPortWriteToFile);
+ n += warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile);
+ n += warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File);
+ n += warn_if_option_path_is_relative("Log",options->DebugLogFile);
+ n += warn_if_option_path_is_relative("AccelDir",options->AccelDir);
+ n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory);
+ n += warn_if_option_path_is_relative("PidFile",options->PidFile);
+
+ for (config_line_t *hs_line = options->RendConfigLines; hs_line;
+ hs_line = hs_line->next) {
+ if (!strcasecmp(hs_line->key, "HiddenServiceDir"))
+ n += warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value);
+ }
+ return n != 0;
+}
+
+/* Validate options related to the scheduler. From the Schedulers list, the
+ * SchedulerTypes_ list is created with int values so once we select the
+ * scheduler, which can happen anytime at runtime, we don't have to parse
+ * strings and thus be quick.
+ *
+ * Return 0 on success else -1 and msg is set with an error message. */
+static int
+options_validate_scheduler(or_options_t *options, char **msg)
+{
+ tor_assert(options);
+ tor_assert(msg);
+
+ if (!options->Schedulers || smartlist_len(options->Schedulers) == 0) {
+ REJECT("Empty Schedulers list. Either remove the option so the defaults "
+ "can be used or set at least one value.");
+ }
+ /* Ok, we do have scheduler types, validate them. */
+ options->SchedulerTypes_ = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(options->Schedulers, const char *, type) {
+ int *sched_type;
+ if (!strcasecmp("KISTLite", type)) {
+ sched_type = tor_malloc_zero(sizeof(int));
+ *sched_type = SCHEDULER_KIST_LITE;
+ smartlist_add(options->SchedulerTypes_, sched_type);
+ } else if (!strcasecmp("KIST", type)) {
+ sched_type = tor_malloc_zero(sizeof(int));
+ *sched_type = SCHEDULER_KIST;
+ smartlist_add(options->SchedulerTypes_, sched_type);
+ } else if (!strcasecmp("Vanilla", type)) {
+ sched_type = tor_malloc_zero(sizeof(int));
+ *sched_type = SCHEDULER_VANILLA;
+ smartlist_add(options->SchedulerTypes_, sched_type);
+ } else {
+ tor_asprintf(msg, "Unknown type %s in option Schedulers. "
+ "Possible values are KIST, KISTLite and Vanilla.",
+ escaped(type));
+ return -1;
+ }
+ } SMARTLIST_FOREACH_END(type);
+
+ if (options->KISTSockBufSizeFactor < 0) {
+ REJECT("KISTSockBufSizeFactor must be at least 0");
+ }
+
+ /* Don't need to validate that the Interval is less than anything because
+ * zero is valid and all negative values are valid. */
+ if (options->KISTSchedRunInterval > KIST_SCHED_RUN_INTERVAL_MAX) {
+ tor_asprintf(msg, "KISTSchedRunInterval must not be more than %d (ms)",
+ KIST_SCHED_RUN_INTERVAL_MAX);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Validate options related to single onion services.
+ * Modifies some options that are incompatible with single onion services.
+ * On failure returns -1, and sets *msg to an error string.
+ * Returns 0 on success. */
+STATIC int
+options_validate_single_onion(or_options_t *options, char **msg)
+{
+ /* The two single onion service options must have matching values. */
+ if (options->HiddenServiceSingleHopMode &&
+ !options->HiddenServiceNonAnonymousMode) {
+ REJECT("HiddenServiceSingleHopMode does not provide any server anonymity. "
+ "It must be used with HiddenServiceNonAnonymousMode set to 1.");
+ }
+ if (options->HiddenServiceNonAnonymousMode &&
+ !options->HiddenServiceSingleHopMode) {
+ REJECT("HiddenServiceNonAnonymousMode does not provide any server "
+ "anonymity. It must be used with HiddenServiceSingleHopMode set to "
+ "1.");
+ }
+
+ /* Now that we've checked that the two options are consistent, we can safely
+ * call the rend_service_* functions that abstract these options. */
+
+ /* If you run an anonymous client with an active Single Onion service, the
+ * client loses anonymity. */
+ const int client_port_set = (options->SocksPort_set ||
+ options->TransPort_set ||
+ options->NATDPort_set ||
+ options->DNSPort_set ||
+ options->HTTPTunnelPort_set);
+ if (rend_service_non_anonymous_mode_enabled(options) && client_port_set &&
+ !options->Tor2webMode) {
+ REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as "
+ "an anonymous client. Please set Socks/Trans/NATD/DNSPort to 0, or "
+ "revert HiddenServiceNonAnonymousMode to 0.");
+ }
+
+ /* If you run a hidden service in non-anonymous mode, the hidden service
+ * loses anonymity, even if SOCKSPort / Tor2web mode isn't used. */
+ if (!rend_service_non_anonymous_mode_enabled(options) &&
+ options->RendConfigLines && options->Tor2webMode) {
+ REJECT("Non-anonymous (Tor2web) mode is incompatible with using Tor as a "
+ "hidden service. Please remove all HiddenServiceDir lines, or use "
+ "a version of tor compiled without --enable-tor2web-mode, or use "
+ "HiddenServiceNonAnonymousMode.");
+ }
+
+ if (rend_service_allow_non_anonymous_connection(options)
+ && options->UseEntryGuards) {
+ /* Single Onion services only use entry guards when uploading descriptors;
+ * all other connections are one-hop. Further, Single Onions causes the
+ * hidden service code to do things which break the path bias
+ * detector, and it's far easier to turn off entry guards (and
+ * thus the path bias detector with it) than to figure out how to
+ * make path bias compatible with single onions.
+ */
+ log_notice(LD_CONFIG,
+ "HiddenServiceSingleHopMode is enabled; disabling "
+ "UseEntryGuards.");
+ options->UseEntryGuards = 0;
+ }
+
+ return 0;
+}
+
+/** Return 0 if every setting in <b>options</b> is reasonable, is a
+ * permissible transition from <b>old_options</b>, and none of the
+ * testing-only settings differ from <b>default_options</b> unless in
+ * testing mode. Else return -1. Should have no side effects, except for
+ * normalizing the contents of <b>options</b>.
+ *
+ * On error, tor_strdup an error explanation into *<b>msg</b>.
+ *
+ * XXX
+ * If <b>from_setconf</b>, we were called by the controller, and our
+ * Log line should stay empty. If it's 0, then give us a default log
+ * if there are no logs defined.
+ */
+STATIC int
+options_validate(or_options_t *old_options, or_options_t *options,
+ or_options_t *default_options, int from_setconf, char **msg)
+{
+ int i;
+ config_line_t *cl;
+ const char *uname = get_uname();
+ int n_ports=0;
+ int world_writable_control_socket=0;
+
+ tor_assert(msg);
+ *msg = NULL;
+
+ if (parse_ports(options, 1, msg, &n_ports,
+ &world_writable_control_socket) < 0)
+ return -1;
+
+ /* Set UseEntryGuards from the configured value, before we check it below.
+ * We change UseEntryGuards when it's incompatible with other options,
+ * but leave UseEntryGuards_option with the original value.
+ * Always use the value of UseEntryGuards, not UseEntryGuards_option. */
+ options->UseEntryGuards = options->UseEntryGuards_option;
+
+ if (warn_about_relative_paths(options) && options->RunAsDaemon) {
+ REJECT("You have specified at least one relative path (see above) "
+ "with the RunAsDaemon option. RunAsDaemon is not compatible "
+ "with relative paths.");
+ }
+
+ if (server_mode(options) &&
+ (!strcmpstart(uname, "Windows 95") ||
+ !strcmpstart(uname, "Windows 98") ||
+ !strcmpstart(uname, "Windows Me"))) {
+ log_warn(LD_CONFIG, "Tor is running as a server, but you are "
+ "running %s; this probably won't work. See "
+ "https://www.torproject.org/docs/faq.html#BestOSForRelay "
+ "for details.", uname);
+ }
+
+ if (parse_outbound_addresses(options, 1, msg) < 0)
+ return -1;
+
+ if (validate_data_directories(options)<0)
+ REJECT("Invalid DataDirectory");
+
+ if (options->Nickname == NULL) {
+ if (server_mode(options)) {
+ options->Nickname = tor_strdup(UNNAMED_ROUTER_NICKNAME);
+ }
+ } else {
+ if (!is_legal_nickname(options->Nickname)) {
+ tor_asprintf(msg,
+ "Nickname '%s', nicknames must be between 1 and 19 characters "
+ "inclusive, and must contain only the characters [a-zA-Z0-9].",
+ options->Nickname);
+ return -1;
+ }
+ }
+
+ if (server_mode(options) && !options->ContactInfo)
+ log_notice(LD_CONFIG, "Your ContactInfo config option is not set. "
+ "Please consider setting it, so we can contact you if your server is "
+ "misconfigured or something else goes wrong.");
+
+ /* Special case on first boot if no Log options are given. */
+ if (!options->Logs && !options->RunAsDaemon && !from_setconf) {
+ if (quiet_level == 0)
+ config_line_append(&options->Logs, "Log", "notice stdout");
+ else if (quiet_level == 1)
+ config_line_append(&options->Logs, "Log", "warn stdout");
+ }
+
+ /* Validate the tor_log(s) */
+ if (options_init_logs(old_options, options, 1)<0)
+ REJECT("Failed to validate Log options. See logs for details.");
+
+ if (authdir_mode(options)) {
+ /* confirm that our address isn't broken, so we can complain now */
+ uint32_t tmp;
+ if (resolve_my_address(LOG_WARN, options, &tmp, NULL, NULL) < 0)
+ REJECT("Failed to resolve/guess local address. See logs for details.");
+ }
+
+ if (server_mode(options) && options->RendConfigLines)
+ log_warn(LD_CONFIG,
+ "Tor is currently configured as a relay and a hidden service. "
+ "That's not very secure: you should probably run your hidden service "
+ "in a separate Tor process, at least -- see "
+ "https://trac.torproject.org/8742");
+
+ /* XXXX require that the only port not be DirPort? */
+ /* XXXX require that at least one port be listened-upon. */
+ if (n_ports == 0 && !options->RendConfigLines)
+ log_warn(LD_CONFIG,
+ "SocksPort, TransPort, NATDPort, DNSPort, and ORPort are all "
+ "undefined, and there aren't any hidden services configured. "
+ "Tor will still run, but probably won't do anything.");
+
+ options->TransProxyType_parsed = TPT_DEFAULT;
+#ifdef USE_TRANSPARENT
+ if (options->TransProxyType) {
+ if (!strcasecmp(options->TransProxyType, "default")) {
+ options->TransProxyType_parsed = TPT_DEFAULT;
+ } else if (!strcasecmp(options->TransProxyType, "pf-divert")) {
+#if !defined(OpenBSD) && !defined( DARWIN )
+ /* Later versions of OS X have pf */
+ REJECT("pf-divert is a OpenBSD-specific "
+ "and OS X/Darwin-specific feature.");
+#else
+ options->TransProxyType_parsed = TPT_PF_DIVERT;
+#endif /* !defined(OpenBSD) && !defined( DARWIN ) */
+ } else if (!strcasecmp(options->TransProxyType, "tproxy")) {
+#if !defined(__linux__)
+ REJECT("TPROXY is a Linux-specific feature.");
+#else
+ options->TransProxyType_parsed = TPT_TPROXY;
+#endif
+ } else if (!strcasecmp(options->TransProxyType, "ipfw")) {
+#ifndef KERNEL_MAY_SUPPORT_IPFW
+ /* Earlier versions of OS X have ipfw */
+ REJECT("ipfw is a FreeBSD-specific "
+ "and OS X/Darwin-specific feature.");
+#else
+ options->TransProxyType_parsed = TPT_IPFW;
+#endif /* !defined(KERNEL_MAY_SUPPORT_IPFW) */
+ } else {
+ REJECT("Unrecognized value for TransProxyType");
+ }
+
+ if (strcasecmp(options->TransProxyType, "default") &&
+ !options->TransPort_set) {
+ REJECT("Cannot use TransProxyType without any valid TransPort.");
+ }
+ }
+#else /* !(defined(USE_TRANSPARENT)) */
+ if (options->TransPort_set)
+ REJECT("TransPort is disabled in this build.");
+#endif /* defined(USE_TRANSPARENT) */
+
+ if (options->TokenBucketRefillInterval <= 0
+ || options->TokenBucketRefillInterval > 1000) {
+ REJECT("TokenBucketRefillInterval must be between 1 and 1000 inclusive.");
+ }
+
+ if (options->ExcludeExitNodes || options->ExcludeNodes) {
+ options->ExcludeExitNodesUnion_ = routerset_new();
+ routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeExitNodes);
+ routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeNodes);
+ }
+
+ if (options->NodeFamilies) {
+ options->NodeFamilySets = smartlist_new();
+ for (cl = options->NodeFamilies; cl; cl = cl->next) {
+ routerset_t *rs = routerset_new();
+ if (routerset_parse(rs, cl->value, cl->key) == 0) {
+ smartlist_add(options->NodeFamilySets, rs);
+ } else {
+ routerset_free(rs);
+ }
+ }
+ }
+
+ if (options->ExcludeNodes && options->StrictNodes) {
+ COMPLAIN("You have asked to exclude certain relays from all positions "
+ "in your circuits. Expect hidden services and other Tor "
+ "features to be broken in unpredictable ways.");
+ }
+
+ for (cl = options->RecommendedPackages; cl; cl = cl->next) {
+ if (! validate_recommended_package_line(cl->value)) {
+ log_warn(LD_CONFIG, "Invalid RecommendedPackage line %s will be ignored",
+ escaped(cl->value));
+ }
+ }
+
+ if (options->AuthoritativeDir) {
+ if (!options->ContactInfo && !options->TestingTorNetwork)
+ REJECT("Authoritative directory servers must set ContactInfo");
+ if (!options->RecommendedClientVersions)
+ options->RecommendedClientVersions =
+ config_lines_dup(options->RecommendedVersions);
+ if (!options->RecommendedServerVersions)
+ options->RecommendedServerVersions =
+ config_lines_dup(options->RecommendedVersions);
+ if (options->VersioningAuthoritativeDir &&
+ (!options->RecommendedClientVersions ||
+ !options->RecommendedServerVersions))
+ REJECT("Versioning authoritative dir servers must set "
+ "Recommended*Versions.");
++
++ char *t;
++ /* Call these functions to produce warnings only. */
++ t = format_recommended_version_list(options->RecommendedClientVersions, 1);
++ tor_free(t);
++ t = format_recommended_version_list(options->RecommendedServerVersions, 1);
++ tor_free(t);
++
+ if (options->UseEntryGuards) {
+ log_info(LD_CONFIG, "Authoritative directory servers can't set "
+ "UseEntryGuards. Disabling.");
+ options->UseEntryGuards = 0;
+ }
+ if (!options->DownloadExtraInfo && authdir_mode_v3(options)) {
+ log_info(LD_CONFIG, "Authoritative directories always try to download "
+ "extra-info documents. Setting DownloadExtraInfo.");
+ options->DownloadExtraInfo = 1;
+ }
+ if (!(options->BridgeAuthoritativeDir ||
+ options->V3AuthoritativeDir))
+ REJECT("AuthoritativeDir is set, but none of "
+ "(Bridge/V3)AuthoritativeDir is set.");
+ /* If we have a v3bandwidthsfile and it's broken, complain on startup */
+ if (options->V3BandwidthsFile && !old_options) {
+ dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL);
+ }
+ /* same for guardfraction file */
+ if (options->GuardfractionFile && !old_options) {
+ dirserv_read_guardfraction_file(options->GuardfractionFile, NULL);
+ }
+ }
+
+ if (options->AuthoritativeDir && !options->DirPort_set)
+ REJECT("Running as authoritative directory, but no DirPort set.");
+
+ if (options->AuthoritativeDir && !options->ORPort_set)
+ REJECT("Running as authoritative directory, but no ORPort set.");
+
+ if (options->AuthoritativeDir && options->ClientOnly)
+ REJECT("Running as authoritative directory, but ClientOnly also set.");
+
+ if (options->FetchDirInfoExtraEarly && !options->FetchDirInfoEarly)
+ REJECT("FetchDirInfoExtraEarly requires that you also set "
+ "FetchDirInfoEarly");
+
+ if (options->ConnLimit <= 0) {
+ tor_asprintf(msg,
+ "ConnLimit must be greater than 0, but was set to %d",
+ options->ConnLimit);
+ return -1;
+ }
+
+ if (options->PathsNeededToBuildCircuits >= 0.0) {
+ if (options->PathsNeededToBuildCircuits < 0.25) {
+ log_warn(LD_CONFIG, "PathsNeededToBuildCircuits is too low. Increasing "
+ "to 0.25");
+ options->PathsNeededToBuildCircuits = 0.25;
+ } else if (options->PathsNeededToBuildCircuits > 0.95) {
+ log_warn(LD_CONFIG, "PathsNeededToBuildCircuits is too high. Decreasing "
+ "to 0.95");
+ options->PathsNeededToBuildCircuits = 0.95;
+ }
+ }
+
+ if (options->MaxClientCircuitsPending <= 0 ||
+ options->MaxClientCircuitsPending > MAX_MAX_CLIENT_CIRCUITS_PENDING) {
+ tor_asprintf(msg,
+ "MaxClientCircuitsPending must be between 1 and %d, but "
+ "was set to %d", MAX_MAX_CLIENT_CIRCUITS_PENDING,
+ options->MaxClientCircuitsPending);
+ return -1;
+ }
+
+ if (validate_ports_csv(options->FirewallPorts, "FirewallPorts", msg) < 0)
+ return -1;
+
+ if (validate_ports_csv(options->LongLivedPorts, "LongLivedPorts", msg) < 0)
+ return -1;
+
+ if (validate_ports_csv(options->RejectPlaintextPorts,
+ "RejectPlaintextPorts", msg) < 0)
+ return -1;
+
+ if (validate_ports_csv(options->WarnPlaintextPorts,
+ "WarnPlaintextPorts", msg) < 0)
+ return -1;
+
+ if (options->FascistFirewall && !options->ReachableAddresses) {
+ if (options->FirewallPorts && smartlist_len(options->FirewallPorts)) {
+ /* We already have firewall ports set, so migrate them to
+ * ReachableAddresses, which will set ReachableORAddresses and
+ * ReachableDirAddresses if they aren't set explicitly. */
+ smartlist_t *instead = smartlist_new();
+ config_line_t *new_line = tor_malloc_zero(sizeof(config_line_t));
+ new_line->key = tor_strdup("ReachableAddresses");
+ /* If we're configured with the old format, we need to prepend some
+ * open ports. */
+ SMARTLIST_FOREACH(options->FirewallPorts, const char *, portno,
+ {
+ int p = atoi(portno);
+ if (p<0) continue;
+ smartlist_add_asprintf(instead, "*:%d", p);
+ });
+ new_line->value = smartlist_join_strings(instead,",",0,NULL);
+ /* These have been deprecated since 0.1.1.5-alpha-cvs */
+ log_notice(LD_CONFIG,
+ "Converting FascistFirewall and FirewallPorts "
+ "config options to new format: \"ReachableAddresses %s\"",
+ new_line->value);
+ options->ReachableAddresses = new_line;
+ SMARTLIST_FOREACH(instead, char *, cp, tor_free(cp));
+ smartlist_free(instead);
+ } else {
+ /* We do not have FirewallPorts set, so add 80 to
+ * ReachableDirAddresses, and 443 to ReachableORAddresses. */
+ if (!options->ReachableDirAddresses) {
+ config_line_t *new_line = tor_malloc_zero(sizeof(config_line_t));
+ new_line->key = tor_strdup("ReachableDirAddresses");
+ new_line->value = tor_strdup("*:80");
+ options->ReachableDirAddresses = new_line;
+ log_notice(LD_CONFIG, "Converting FascistFirewall config option "
+ "to new format: \"ReachableDirAddresses *:80\"");
+ }
+ if (!options->ReachableORAddresses) {
+ config_line_t *new_line = tor_malloc_zero(sizeof(config_line_t));
+ new_line->key = tor_strdup("ReachableORAddresses");
+ new_line->value = tor_strdup("*:443");
+ options->ReachableORAddresses = new_line;
+ log_notice(LD_CONFIG, "Converting FascistFirewall config option "
+ "to new format: \"ReachableORAddresses *:443\"");
+ }
+ }
+ }
+
+ /* Terminate Reachable*Addresses with reject *
+ */
+ for (i=0; i<3; i++) {
+ config_line_t **linep =
+ (i==0) ? &options->ReachableAddresses :
+ (i==1) ? &options->ReachableORAddresses :
+ &options->ReachableDirAddresses;
+ if (!*linep)
+ continue;
+ /* We need to end with a reject *:*, not an implicit accept *:* */
+ for (;;) {
+ linep = &((*linep)->next);
+ if (!*linep) {
+ *linep = tor_malloc_zero(sizeof(config_line_t));
+ (*linep)->key = tor_strdup(
+ (i==0) ? "ReachableAddresses" :
+ (i==1) ? "ReachableORAddresses" :
+ "ReachableDirAddresses");
+ (*linep)->value = tor_strdup("reject *:*");
+ break;
+ }
+ }
+ }
+
+ if ((options->ReachableAddresses ||
+ options->ReachableORAddresses ||
+ options->ReachableDirAddresses ||
+ options->ClientUseIPv4 == 0) &&
+ server_mode(options))
+ REJECT("Servers must be able to freely connect to the rest "
+ "of the Internet, so they must not set Reachable*Addresses "
+ "or FascistFirewall or FirewallPorts or ClientUseIPv4 0.");
+
+ if (options->UseBridges &&
+ server_mode(options))
+ REJECT("Servers must be able to freely connect to the rest "
+ "of the Internet, so they must not set UseBridges.");
+
+ /* If both of these are set, we'll end up with funny behavior where we
+ * demand enough entrynodes be up and running else we won't build
+ * circuits, yet we never actually use them. */
+ if (options->UseBridges && options->EntryNodes)
+ REJECT("You cannot set both UseBridges and EntryNodes.");
+
+ /* If we have UseBridges as 1 and UseEntryGuards as 0, we end up bypassing
+ * the use of bridges */
+ if (options->UseBridges && !options->UseEntryGuards)
+ REJECT("Setting UseBridges requires also setting UseEntryGuards.");
+
+ options->MaxMemInQueues =
+ compute_real_max_mem_in_queues(options->MaxMemInQueues_raw,
+ server_mode(options));
+ options->MaxMemInQueues_low_threshold = (options->MaxMemInQueues / 4) * 3;
+
+ if (!options->SafeLogging ||
+ !strcasecmp(options->SafeLogging, "0")) {
+ options->SafeLogging_ = SAFELOG_SCRUB_NONE;
+ } else if (!strcasecmp(options->SafeLogging, "relay")) {
+ options->SafeLogging_ = SAFELOG_SCRUB_RELAY;
+ } else if (!strcasecmp(options->SafeLogging, "1")) {
+ options->SafeLogging_ = SAFELOG_SCRUB_ALL;
+ } else {
+ tor_asprintf(msg,
+ "Unrecognized value '%s' in SafeLogging",
+ escaped(options->SafeLogging));
+ return -1;
+ }
+
+ if (compute_publishserverdescriptor(options) < 0) {
+ tor_asprintf(msg, "Unrecognized value in PublishServerDescriptor");
+ return -1;
+ }
+
+ if ((options->BridgeRelay
+ || options->PublishServerDescriptor_ & BRIDGE_DIRINFO)
+ && (options->PublishServerDescriptor_ & V3_DIRINFO)) {
+ REJECT("Bridges are not supposed to publish router descriptors to the "
+ "directory authorities. Please correct your "
+ "PublishServerDescriptor line.");
+ }
+
+ if (options->BridgeRelay && options->DirPort_set) {
+ log_warn(LD_CONFIG, "Can't set a DirPort on a bridge relay; disabling "
+ "DirPort");
+ config_free_lines(options->DirPort_lines);
+ options->DirPort_lines = NULL;
+ options->DirPort_set = 0;
+ }
+
+ if (server_mode(options) && options->ConnectionPadding != -1) {
+ REJECT("Relays must use 'auto' for the ConnectionPadding setting.");
+ }
+
+ if (server_mode(options) && options->ReducedConnectionPadding != 0) {
+ REJECT("Relays cannot set ReducedConnectionPadding. ");
+ }
+
+ if (options->BridgeDistribution) {
+ if (!options->BridgeRelay) {
+ REJECT("You set BridgeDistribution, but you didn't set BridgeRelay!");
+ }
+ if (check_bridge_distribution_setting(options->BridgeDistribution) < 0) {
+ REJECT("Invalid BridgeDistribution value.");
+ }
+ }
+
+ if (options->MinUptimeHidServDirectoryV2 < 0) {
+ log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at "
+ "least 0 seconds. Changing to 0.");
+ options->MinUptimeHidServDirectoryV2 = 0;
+ }
+
+ const int min_rendpostperiod =
+ options->TestingTorNetwork ?
+ MIN_REND_POST_PERIOD_TESTING : MIN_REND_POST_PERIOD;
+ if (options->RendPostPeriod < min_rendpostperiod) {
+ log_warn(LD_CONFIG, "RendPostPeriod option is too short; "
+ "raising to %d seconds.", min_rendpostperiod);
+ options->RendPostPeriod = min_rendpostperiod;
+ }
+
+ if (options->RendPostPeriod > MAX_DIR_PERIOD) {
+ log_warn(LD_CONFIG, "RendPostPeriod is too large; clipping to %ds.",
+ MAX_DIR_PERIOD);
+ options->RendPostPeriod = MAX_DIR_PERIOD;
+ }
+
+ /* Check the Single Onion Service options */
+ if (options_validate_single_onion(options, msg) < 0)
+ return -1;
+
+ if (options->CircuitsAvailableTimeout > MAX_CIRCS_AVAILABLE_TIME) {
+ // options_t is immutable for new code (the above code is older),
+ // so just make the user fix the value themselves rather than
+ // silently keep a shadow value lower than what they asked for.
+ REJECT("CircuitsAvailableTimeout is too large. Max is 24 hours.");
+ }
+
+#ifdef ENABLE_TOR2WEB_MODE
+ if (options->Tor2webMode && options->UseEntryGuards) {
+ /* tor2web mode clients do not (and should not) use entry guards
+ * in any meaningful way. Further, tor2web mode causes the hidden
+ * service client code to do things which break the path bias
+ * detector, and it's far easier to turn off entry guards (and
+ * thus the path bias detector with it) than to figure out how to
+ * make a piece of code which cannot possibly help tor2web mode
+ * users compatible with tor2web mode.
+ */
+ log_notice(LD_CONFIG,
+ "Tor2WebMode is enabled; disabling UseEntryGuards.");
+ options->UseEntryGuards = 0;
+ }
+#endif /* defined(ENABLE_TOR2WEB_MODE) */
+
+ if (options->Tor2webRendezvousPoints && !options->Tor2webMode) {
+ REJECT("Tor2webRendezvousPoints cannot be set without Tor2webMode.");
+ }
+
+ if (options->EntryNodes && !options->UseEntryGuards) {
+ REJECT("If EntryNodes is set, UseEntryGuards must be enabled.");
+ }
+
+ if (!(options->UseEntryGuards) &&
+ (options->RendConfigLines != NULL) &&
+ !rend_service_allow_non_anonymous_connection(options)) {
+ log_warn(LD_CONFIG,
+ "UseEntryGuards is disabled, but you have configured one or more "
+ "hidden services on this Tor instance. Your hidden services "
+ "will be very easy to locate using a well-known attack -- see "
+ "http://freehaven.net/anonbib/#hs-attack06 for details.");
+ }
+
+ if (options->NumPrimaryGuards && options->NumEntryGuards &&
+ options->NumEntryGuards > options->NumPrimaryGuards) {
+ REJECT("NumEntryGuards must not be greater than NumPrimaryGuards.");
+ }
+
+ if (options->EntryNodes &&
+ routerset_is_list(options->EntryNodes) &&
+ (routerset_len(options->EntryNodes) == 1) &&
+ (options->RendConfigLines != NULL)) {
+ tor_asprintf(msg,
+ "You have one single EntryNodes and at least one hidden service "
+ "configured. This is bad because it's very easy to locate your "
+ "entry guard which can then lead to the deanonymization of your "
+ "hidden service -- for more details, see "
+ "https://trac.torproject.org/projects/tor/ticket/14917. "
+ "For this reason, the use of one EntryNodes with an hidden "
+ "service is prohibited until a better solution is found.");
+ return -1;
+ }
+
+ /* Inform the hidden service operator that pinning EntryNodes can possibly
+ * be harmful for the service anonymity. */
+ if (options->EntryNodes &&
+ routerset_is_list(options->EntryNodes) &&
+ (options->RendConfigLines != NULL)) {
+ log_warn(LD_CONFIG,
+ "EntryNodes is set with multiple entries and at least one "
+ "hidden service is configured. Pinning entry nodes can possibly "
+ "be harmful to the service anonymity. Because of this, we "
+ "recommend you either don't do that or make sure you know what "
+ "you are doing. For more details, please look at "
+ "https://trac.torproject.org/projects/tor/ticket/21155.");
+ }
+
+ /* Single Onion Services: non-anonymous hidden services */
+ if (rend_service_non_anonymous_mode_enabled(options)) {
+ log_warn(LD_CONFIG,
+ "HiddenServiceNonAnonymousMode is set. Every hidden service on "
+ "this tor instance is NON-ANONYMOUS. If "
+ "the HiddenServiceNonAnonymousMode option is changed, Tor will "
+ "refuse to launch hidden services from the same directories, to "
+ "protect your anonymity against config errors. This setting is "
+ "for experimental use only.");
+ }
+
+ if (!options->LearnCircuitBuildTimeout && options->CircuitBuildTimeout &&
+ options->CircuitBuildTimeout < RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT) {
+ log_warn(LD_CONFIG,
+ "CircuitBuildTimeout is shorter (%d seconds) than the recommended "
+ "minimum (%d seconds), and LearnCircuitBuildTimeout is disabled. "
+ "If tor isn't working, raise this value or enable "
+ "LearnCircuitBuildTimeout.",
+ options->CircuitBuildTimeout,
+ RECOMMENDED_MIN_CIRCUIT_BUILD_TIMEOUT );
+ } else if (!options->LearnCircuitBuildTimeout &&
+ !options->CircuitBuildTimeout) {
+ int severity = LOG_NOTICE;
+ /* Be a little quieter if we've deliberately disabled
+ * LearnCircuitBuildTimeout. */
+ if (circuit_build_times_disabled_(options, 1)) {
+ severity = LOG_INFO;
+ }
+ log_fn(severity, LD_CONFIG, "You disabled LearnCircuitBuildTimeout, but "
+ "didn't specify a CircuitBuildTimeout. I'll pick a plausible "
+ "default.");
+ }
+
+ if (options->PathBiasNoticeRate > 1.0) {
+ tor_asprintf(msg,
+ "PathBiasNoticeRate is too high. "
+ "It must be between 0 and 1.0");
+ return -1;
+ }
+ if (options->PathBiasWarnRate > 1.0) {
+ tor_asprintf(msg,
+ "PathBiasWarnRate is too high. "
+ "It must be between 0 and 1.0");
+ return -1;
+ }
+ if (options->PathBiasExtremeRate > 1.0) {
+ tor_asprintf(msg,
+ "PathBiasExtremeRate is too high. "
+ "It must be between 0 and 1.0");
+ return -1;
+ }
+ if (options->PathBiasNoticeUseRate > 1.0) {
+ tor_asprintf(msg,
+ "PathBiasNoticeUseRate is too high. "
+ "It must be between 0 and 1.0");
+ return -1;
+ }
+ if (options->PathBiasExtremeUseRate > 1.0) {
+ tor_asprintf(msg,
+ "PathBiasExtremeUseRate is too high. "
+ "It must be between 0 and 1.0");
+ return -1;
+ }
+
+ if (options->MaxCircuitDirtiness < MIN_MAX_CIRCUIT_DIRTINESS) {
+ log_warn(LD_CONFIG, "MaxCircuitDirtiness option is too short; "
+ "raising to %d seconds.", MIN_MAX_CIRCUIT_DIRTINESS);
+ options->MaxCircuitDirtiness = MIN_MAX_CIRCUIT_DIRTINESS;
+ }
+
+ if (options->MaxCircuitDirtiness > MAX_MAX_CIRCUIT_DIRTINESS) {
+ log_warn(LD_CONFIG, "MaxCircuitDirtiness option is too high; "
+ "setting to %d days.", MAX_MAX_CIRCUIT_DIRTINESS/86400);
+ options->MaxCircuitDirtiness = MAX_MAX_CIRCUIT_DIRTINESS;
+ }
+
+ if (options->CircuitStreamTimeout &&
+ options->CircuitStreamTimeout < MIN_CIRCUIT_STREAM_TIMEOUT) {
+ log_warn(LD_CONFIG, "CircuitStreamTimeout option is too short; "
+ "raising to %d seconds.", MIN_CIRCUIT_STREAM_TIMEOUT);
+ options->CircuitStreamTimeout = MIN_CIRCUIT_STREAM_TIMEOUT;
+ }
+
+ if (options->HeartbeatPeriod &&
+ options->HeartbeatPeriod < MIN_HEARTBEAT_PERIOD) {
+ log_warn(LD_CONFIG, "HeartbeatPeriod option is too short; "
+ "raising to %d seconds.", MIN_HEARTBEAT_PERIOD);
+ options->HeartbeatPeriod = MIN_HEARTBEAT_PERIOD;
+ }
+
+ if (options->KeepalivePeriod < 1)
+ REJECT("KeepalivePeriod option must be positive.");
+
+ if (ensure_bandwidth_cap(&options->BandwidthRate,
+ "BandwidthRate", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->BandwidthBurst,
+ "BandwidthBurst", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->MaxAdvertisedBandwidth,
+ "MaxAdvertisedBandwidth", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->RelayBandwidthRate,
+ "RelayBandwidthRate", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->RelayBandwidthBurst,
+ "RelayBandwidthBurst", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->PerConnBWRate,
+ "PerConnBWRate", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->PerConnBWBurst,
+ "PerConnBWBurst", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->AuthDirFastGuarantee,
+ "AuthDirFastGuarantee", msg) < 0)
+ return -1;
+ if (ensure_bandwidth_cap(&options->AuthDirGuardBWGuarantee,
+ "AuthDirGuardBWGuarantee", msg) < 0)
+ return -1;
+
+ if (options->RelayBandwidthRate && !options->RelayBandwidthBurst)
+ options->RelayBandwidthBurst = options->RelayBandwidthRate;
+ if (options->RelayBandwidthBurst && !options->RelayBandwidthRate)
+ options->RelayBandwidthRate = options->RelayBandwidthBurst;
+
+ if (server_mode(options)) {
+ const unsigned required_min_bw =
+ public_server_mode(options) ?
+ RELAY_REQUIRED_MIN_BANDWIDTH : BRIDGE_REQUIRED_MIN_BANDWIDTH;
+ const char * const optbridge =
+ public_server_mode(options) ? "" : "bridge ";
+ if (options->BandwidthRate < required_min_bw) {
+ tor_asprintf(msg,
+ "BandwidthRate is set to %d bytes/second. "
+ "For %sservers, it must be at least %u.",
+ (int)options->BandwidthRate, optbridge,
+ required_min_bw);
+ return -1;
+ } else if (options->MaxAdvertisedBandwidth <
+ required_min_bw/2) {
+ tor_asprintf(msg,
+ "MaxAdvertisedBandwidth is set to %d bytes/second. "
+ "For %sservers, it must be at least %u.",
+ (int)options->MaxAdvertisedBandwidth, optbridge,
+ required_min_bw/2);
+ return -1;
+ }
+ if (options->RelayBandwidthRate &&
+ options->RelayBandwidthRate < required_min_bw) {
+ tor_asprintf(msg,
+ "RelayBandwidthRate is set to %d bytes/second. "
+ "For %sservers, it must be at least %u.",
+ (int)options->RelayBandwidthRate, optbridge,
+ required_min_bw);
+ return -1;
+ }
+ }
+
+ if (options->RelayBandwidthRate > options->RelayBandwidthBurst)
+ REJECT("RelayBandwidthBurst must be at least equal "
+ "to RelayBandwidthRate.");
+
+ if (options->BandwidthRate > options->BandwidthBurst)
+ REJECT("BandwidthBurst must be at least equal to BandwidthRate.");
+
+ /* if they set relaybandwidth* really high but left bandwidth*
+ * at the default, raise the defaults. */
+ if (options->RelayBandwidthRate > options->BandwidthRate)
+ options->BandwidthRate = options->RelayBandwidthRate;
+ if (options->RelayBandwidthBurst > options->BandwidthBurst)
+ options->BandwidthBurst = options->RelayBandwidthBurst;
+
+ if (accounting_parse_options(options, 1)<0)
+ REJECT("Failed to parse accounting options. See logs for details.");
+
+ if (options->AccountingMax) {
+ if (options->RendConfigLines && server_mode(options)) {
+ log_warn(LD_CONFIG, "Using accounting with a hidden service and an "
+ "ORPort is risky: your hidden service(s) and your public "
+ "address will all turn off at the same time, which may alert "
+ "observers that they are being run by the same party.");
+ } else if (config_count_key(options->RendConfigLines,
+ "HiddenServiceDir") > 1) {
+ log_warn(LD_CONFIG, "Using accounting with multiple hidden services is "
+ "risky: they will all turn off at the same time, which may "
+ "alert observers that they are being run by the same party.");
+ }
+ }
+
+ options->AccountingRule = ACCT_MAX;
+ if (options->AccountingRule_option) {
+ if (!strcmp(options->AccountingRule_option, "sum"))
+ options->AccountingRule = ACCT_SUM;
+ else if (!strcmp(options->AccountingRule_option, "max"))
+ options->AccountingRule = ACCT_MAX;
+ else if (!strcmp(options->AccountingRule_option, "in"))
+ options->AccountingRule = ACCT_IN;
+ else if (!strcmp(options->AccountingRule_option, "out"))
+ options->AccountingRule = ACCT_OUT;
+ else
+ REJECT("AccountingRule must be 'sum', 'max', 'in', or 'out'");
+ }
+
+ if (options->DirPort_set && !options->DirCache) {
+ REJECT("DirPort configured but DirCache disabled. DirPort requires "
+ "DirCache.");
+ }
+
+ if (options->BridgeRelay && !options->DirCache) {
+ REJECT("We're a bridge but DirCache is disabled. BridgeRelay requires "
+ "DirCache.");
+ }
+
+ if (server_mode(options)) {
+ char *dircache_msg = NULL;
+ if (have_enough_mem_for_dircache(options, 0, &dircache_msg)) {
+ log_warn(LD_CONFIG, "%s", dircache_msg);
+ tor_free(dircache_msg);
+ }
+ }
+
+ if (options->HTTPProxy) { /* parse it now */
+ if (tor_addr_port_lookup(options->HTTPProxy,
+ &options->HTTPProxyAddr, &options->HTTPProxyPort) < 0)
+ REJECT("HTTPProxy failed to parse or resolve. Please fix.");
+ if (options->HTTPProxyPort == 0) { /* give it a default */
+ options->HTTPProxyPort = 80;
+ }
+ }
+
+ if (options->HTTPProxyAuthenticator) {
+ if (strlen(options->HTTPProxyAuthenticator) >= 512)
+ REJECT("HTTPProxyAuthenticator is too long (>= 512 chars).");
+ }
+
+ if (options->HTTPSProxy) { /* parse it now */
+ if (tor_addr_port_lookup(options->HTTPSProxy,
+ &options->HTTPSProxyAddr, &options->HTTPSProxyPort) <0)
+ REJECT("HTTPSProxy failed to parse or resolve. Please fix.");
+ if (options->HTTPSProxyPort == 0) { /* give it a default */
+ options->HTTPSProxyPort = 443;
+ }
+ }
+
+ if (options->HTTPSProxyAuthenticator) {
+ if (strlen(options->HTTPSProxyAuthenticator) >= 512)
+ REJECT("HTTPSProxyAuthenticator is too long (>= 512 chars).");
+ }
+
+ if (options->Socks4Proxy) { /* parse it now */
+ if (tor_addr_port_lookup(options->Socks4Proxy,
+ &options->Socks4ProxyAddr,
+ &options->Socks4ProxyPort) <0)
+ REJECT("Socks4Proxy failed to parse or resolve. Please fix.");
+ if (options->Socks4ProxyPort == 0) { /* give it a default */
+ options->Socks4ProxyPort = 1080;
+ }
+ }
+
+ if (options->Socks5Proxy) { /* parse it now */
+ if (tor_addr_port_lookup(options->Socks5Proxy,
+ &options->Socks5ProxyAddr,
+ &options->Socks5ProxyPort) <0)
+ REJECT("Socks5Proxy failed to parse or resolve. Please fix.");
+ if (options->Socks5ProxyPort == 0) { /* give it a default */
+ options->Socks5ProxyPort = 1080;
+ }
+ }
+
+ /* Check if more than one exclusive proxy type has been enabled. */
+ if (!!options->Socks4Proxy + !!options->Socks5Proxy +
+ !!options->HTTPSProxy > 1)
+ REJECT("You have configured more than one proxy type. "
+ "(Socks4Proxy|Socks5Proxy|HTTPSProxy)");
+
+ /* Check if the proxies will give surprising behavior. */
+ if (options->HTTPProxy && !(options->Socks4Proxy ||
+ options->Socks5Proxy ||
+ options->HTTPSProxy)) {
+ log_warn(LD_CONFIG, "HTTPProxy configured, but no SOCKS proxy or "
+ "HTTPS proxy configured. Watch out: this configuration will "
+ "proxy unencrypted directory connections only.");
+ }
+
+ if (options->Socks5ProxyUsername) {
+ size_t len;
+
+ len = strlen(options->Socks5ProxyUsername);
+ if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE)
+ REJECT("Socks5ProxyUsername must be between 1 and 255 characters.");
+
+ if (!options->Socks5ProxyPassword)
+ REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
+
+ len = strlen(options->Socks5ProxyPassword);
+ if (len < 1 || len > MAX_SOCKS5_AUTH_FIELD_SIZE)
+ REJECT("Socks5ProxyPassword must be between 1 and 255 characters.");
+ } else if (options->Socks5ProxyPassword)
+ REJECT("Socks5ProxyPassword must be included with Socks5ProxyUsername.");
+
+ if (options->HashedControlPassword) {
+ smartlist_t *sl = decode_hashed_passwords(options->HashedControlPassword);
+ if (!sl) {
+ REJECT("Bad HashedControlPassword: wrong length or bad encoding");
+ } else {
+ SMARTLIST_FOREACH(sl, char*, cp, tor_free(cp));
+ smartlist_free(sl);
+ }
+ }
+
+ if (options->HashedControlSessionPassword) {
+ smartlist_t *sl = decode_hashed_passwords(
+ options->HashedControlSessionPassword);
+ if (!sl) {
+ REJECT("Bad HashedControlSessionPassword: wrong length or bad encoding");
+ } else {
+ SMARTLIST_FOREACH(sl, char*, cp, tor_free(cp));
+ smartlist_free(sl);
+ }
+ }
+
+ if (options->OwningControllerProcess) {
+ const char *validate_pspec_msg = NULL;
+ if (tor_validate_process_specifier(options->OwningControllerProcess,
+ &validate_pspec_msg)) {
+ tor_asprintf(msg, "Bad OwningControllerProcess: %s",
+ validate_pspec_msg);
+ return -1;
+ }
+ }
+
+ if ((options->ControlPort_set || world_writable_control_socket) &&
+ !options->HashedControlPassword &&
+ !options->HashedControlSessionPassword &&
+ !options->CookieAuthentication) {
+ log_warn(LD_CONFIG, "Control%s is %s, but no authentication method "
+ "has been configured. This means that any program on your "
+ "computer can reconfigure your Tor. That's bad! You should "
+ "upgrade your Tor controller as soon as possible.",
+ options->ControlPort_set ? "Port" : "Socket",
+ options->ControlPort_set ? "open" : "world writable");
+ }
+
+ if (options->CookieAuthFileGroupReadable && !options->CookieAuthFile) {
+ log_warn(LD_CONFIG, "CookieAuthFileGroupReadable is set, but will have "
+ "no effect: you must specify an explicit CookieAuthFile to "
+ "have it group-readable.");
+ }
+
+ if (options->MyFamily_lines && options->BridgeRelay) {
+ log_warn(LD_CONFIG, "Listing a family for a bridge relay is not "
+ "supported: it can reveal bridge fingerprints to censors. "
+ "You should also make sure you aren't listing this bridge's "
+ "fingerprint in any other MyFamily.");
+ }
+ if (normalize_nickname_list(&options->MyFamily,
+ options->MyFamily_lines, "MyFamily", msg))
+ return -1;
+ for (cl = options->NodeFamilies; cl; cl = cl->next) {
+ routerset_t *rs = routerset_new();
+ if (routerset_parse(rs, cl->value, cl->key)) {
+ routerset_free(rs);
+ return -1;
+ }
+ routerset_free(rs);
+ }
+
+ if (validate_addr_policies(options, msg) < 0)
+ return -1;
+
+ /* If FallbackDir is set, we don't UseDefaultFallbackDirs */
+ if (options->UseDefaultFallbackDirs && options->FallbackDir) {
+ log_info(LD_CONFIG, "You have set UseDefaultFallbackDirs 1 and "
+ "FallbackDir(s). Ignoring UseDefaultFallbackDirs, and "
+ "using the FallbackDir(s) you have set.");
+ }
+
+ if (validate_dir_servers(options, old_options) < 0)
+ REJECT("Directory authority/fallback line did not parse. See logs "
+ "for details.");
+
+ if (options->UseBridges && !options->Bridges)
+ REJECT("If you set UseBridges, you must specify at least one bridge.");
+
+ for (cl = options->Bridges; cl; cl = cl->next) {
+ bridge_line_t *bridge_line = parse_bridge_line(cl->value);
+ if (!bridge_line)
+ REJECT("Bridge line did not parse. See logs for details.");
+ bridge_line_free(bridge_line);
+ }
+
+ for (cl = options->ClientTransportPlugin; cl; cl = cl->next) {
+ if (parse_transport_line(options, cl->value, 1, 0) < 0)
+ REJECT("Invalid client transport line. See logs for details.");
+ }
+
+ for (cl = options->ServerTransportPlugin; cl; cl = cl->next) {
+ if (parse_transport_line(options, cl->value, 1, 1) < 0)
+ REJECT("Invalid server transport line. See logs for details.");
+ }
+
+ if (options->ServerTransportPlugin && !server_mode(options)) {
+ log_notice(LD_GENERAL, "Tor is not configured as a relay but you specified"
+ " a ServerTransportPlugin line (%s). The ServerTransportPlugin "
+ "line will be ignored.",
+ escaped(options->ServerTransportPlugin->value));
+ }
+
+ for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) {
+ /** If get_bindaddr_from_transport_listen_line() fails with
+ 'transport' being NULL, it means that something went wrong
+ while parsing the ServerTransportListenAddr line. */
+ char *bindaddr = get_bindaddr_from_transport_listen_line(cl->value, NULL);
+ if (!bindaddr)
+ REJECT("ServerTransportListenAddr did not parse. See logs for details.");
+ tor_free(bindaddr);
+ }
+
+ if (options->ServerTransportListenAddr && !options->ServerTransportPlugin) {
+ log_notice(LD_GENERAL, "You need at least a single managed-proxy to "
+ "specify a transport listen address. The "
+ "ServerTransportListenAddr line will be ignored.");
+ }
+
+ for (cl = options->ServerTransportOptions; cl; cl = cl->next) {
+ /** If get_options_from_transport_options_line() fails with
+ 'transport' being NULL, it means that something went wrong
+ while parsing the ServerTransportOptions line. */
+ smartlist_t *options_sl =
+ get_options_from_transport_options_line(cl->value, NULL);
+ if (!options_sl)
+ REJECT("ServerTransportOptions did not parse. See logs for details.");
+
+ SMARTLIST_FOREACH(options_sl, char *, cp, tor_free(cp));
+ smartlist_free(options_sl);
+ }
+
+ if (options->ConstrainedSockets) {
+ /* If the user wants to constrain socket buffer use, make sure the desired
+ * limit is between MIN|MAX_TCPSOCK_BUFFER in k increments. */
+ if (options->ConstrainedSockSize < MIN_CONSTRAINED_TCP_BUFFER ||
+ options->ConstrainedSockSize > MAX_CONSTRAINED_TCP_BUFFER ||
+ options->ConstrainedSockSize % 1024) {
+ tor_asprintf(msg,
+ "ConstrainedSockSize is invalid. Must be a value between %d and %d "
+ "in 1024 byte increments.",
+ MIN_CONSTRAINED_TCP_BUFFER, MAX_CONSTRAINED_TCP_BUFFER);
+ return -1;
+ }
+ if (options->DirPort_set) {
+ /* Providing cached directory entries while system TCP buffers are scarce
+ * will exacerbate the socket errors. Suggest that this be disabled. */
+ COMPLAIN("You have requested constrained socket buffers while also "
+ "serving directory entries via DirPort. It is strongly "
+ "suggested that you disable serving directory requests when "
+ "system TCP buffer resources are scarce.");
+ }
+ }
+
+ if (options->V3AuthVoteDelay + options->V3AuthDistDelay >=
+ options->V3AuthVotingInterval/2) {
+ /*
+ This doesn't work, but it seems like it should:
+ what code is preventing the interval being less than twice the lead-up?
+ if (options->TestingTorNetwork) {
+ if (options->V3AuthVoteDelay + options->V3AuthDistDelay >=
+ options->V3AuthVotingInterval) {
+ REJECT("V3AuthVoteDelay plus V3AuthDistDelay must be less than "
+ "V3AuthVotingInterval");
+ } else {
+ COMPLAIN("V3AuthVoteDelay plus V3AuthDistDelay is more than half "
+ "V3AuthVotingInterval. This may lead to "
+ "consensus instability, particularly if clocks drift.");
+ }
+ } else {
+ */
+ REJECT("V3AuthVoteDelay plus V3AuthDistDelay must be less than half "
+ "V3AuthVotingInterval");
+ /*
+ }
+ */
+ }
+
+ if (options->V3AuthVoteDelay < MIN_VOTE_SECONDS) {
+ if (options->TestingTorNetwork) {
+ if (options->V3AuthVoteDelay < MIN_VOTE_SECONDS_TESTING) {
+ REJECT("V3AuthVoteDelay is way too low.");
+ } else {
+ COMPLAIN("V3AuthVoteDelay is very low. "
+ "This may lead to failure to vote for a consensus.");
+ }
+ } else {
+ REJECT("V3AuthVoteDelay is way too low.");
+ }
+ }
+
+ if (options->V3AuthDistDelay < MIN_DIST_SECONDS) {
+ if (options->TestingTorNetwork) {
+ if (options->V3AuthDistDelay < MIN_DIST_SECONDS_TESTING) {
+ REJECT("V3AuthDistDelay is way too low.");
+ } else {
+ COMPLAIN("V3AuthDistDelay is very low. "
+ "This may lead to missing votes in a consensus.");
+ }
+ } else {
+ REJECT("V3AuthDistDelay is way too low.");
+ }
+ }
+
+ if (options->V3AuthNIntervalsValid < 2)
+ REJECT("V3AuthNIntervalsValid must be at least 2.");
+
+ if (options->V3AuthVotingInterval < MIN_VOTE_INTERVAL) {
+ if (options->TestingTorNetwork) {
+ if (options->V3AuthVotingInterval < MIN_VOTE_INTERVAL_TESTING) {
+ REJECT("V3AuthVotingInterval is insanely low.");
+ } else {
+ COMPLAIN("V3AuthVotingInterval is very low. "
+ "This may lead to failure to synchronise for a consensus.");
+ }
+ } else {
+ REJECT("V3AuthVotingInterval is insanely low.");
+ }
+ } else if (options->V3AuthVotingInterval > 24*60*60) {
+ REJECT("V3AuthVotingInterval is insanely high.");
+ } else if (((24*60*60) % options->V3AuthVotingInterval) != 0) {
+ COMPLAIN("V3AuthVotingInterval does not divide evenly into 24 hours.");
+ }
+
+ if (hs_config_service_all(options, 1) < 0)
+ REJECT("Failed to configure rendezvous options. See logs for details.");
+
+ /* Parse client-side authorization for hidden services. */
+ if (rend_parse_service_authorization(options, 1) < 0)
+ REJECT("Failed to configure client authorization for hidden services. "
+ "See logs for details.");
+
+ if (parse_virtual_addr_network(options->VirtualAddrNetworkIPv4,
+ AF_INET, 1, msg)<0)
+ return -1;
+ if (parse_virtual_addr_network(options->VirtualAddrNetworkIPv6,
+ AF_INET6, 1, msg)<0)
+ return -1;
+
+ if (options->TestingTorNetwork &&
+ !(options->DirAuthorities ||
+ (options->AlternateDirAuthority &&
+ options->AlternateBridgeAuthority))) {
+ REJECT("TestingTorNetwork may only be configured in combination with "
+ "a non-default set of DirAuthority or both of "
+ "AlternateDirAuthority and AlternateBridgeAuthority configured.");
+ }
+
+#define CHECK_DEFAULT(arg) \
+ STMT_BEGIN \
+ if (!options->TestingTorNetwork && \
+ !options->UsingTestNetworkDefaults_ && \
+ !config_is_same(&options_format,options, \
+ default_options,#arg)) { \
+ REJECT(#arg " may only be changed in testing Tor " \
+ "networks!"); \
+ } STMT_END
+ CHECK_DEFAULT(TestingV3AuthInitialVotingInterval);
+ CHECK_DEFAULT(TestingV3AuthInitialVoteDelay);
+ CHECK_DEFAULT(TestingV3AuthInitialDistDelay);
+ CHECK_DEFAULT(TestingV3AuthVotingStartOffset);
+ CHECK_DEFAULT(TestingAuthDirTimeToLearnReachability);
+ CHECK_DEFAULT(TestingEstimatedDescriptorPropagationTime);
+ CHECK_DEFAULT(TestingServerDownloadInitialDelay);
+ CHECK_DEFAULT(TestingClientDownloadInitialDelay);
+ CHECK_DEFAULT(TestingServerConsensusDownloadInitialDelay);
+ CHECK_DEFAULT(TestingClientConsensusDownloadInitialDelay);
+ CHECK_DEFAULT(TestingBridgeDownloadInitialDelay);
+ CHECK_DEFAULT(TestingBridgeBootstrapDownloadInitialDelay);
+ CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest);
+ CHECK_DEFAULT(TestingDirConnectionMaxStall);
+ CHECK_DEFAULT(TestingAuthKeyLifetime);
+ CHECK_DEFAULT(TestingLinkCertLifetime);
+ CHECK_DEFAULT(TestingSigningKeySlop);
+ CHECK_DEFAULT(TestingAuthKeySlop);
+ CHECK_DEFAULT(TestingLinkKeySlop);
+#undef CHECK_DEFAULT
+
+ if (!options->ClientDNSRejectInternalAddresses &&
+ !(options->DirAuthorities ||
+ (options->AlternateDirAuthority && options->AlternateBridgeAuthority)))
+ REJECT("ClientDNSRejectInternalAddresses used for default network.");
+ if (options->SigningKeyLifetime < options->TestingSigningKeySlop*2)
+ REJECT("SigningKeyLifetime is too short.");
+ if (options->TestingLinkCertLifetime < options->TestingAuthKeySlop*2)
+ REJECT("LinkCertLifetime is too short.");
+ if (options->TestingAuthKeyLifetime < options->TestingLinkKeySlop*2)
+ REJECT("TestingAuthKeyLifetime is too short.");
+
+ if (options->TestingV3AuthInitialVotingInterval
+ < MIN_VOTE_INTERVAL_TESTING_INITIAL) {
+ REJECT("TestingV3AuthInitialVotingInterval is insanely low.");
+ } else if (((30*60) % options->TestingV3AuthInitialVotingInterval) != 0) {
+ REJECT("TestingV3AuthInitialVotingInterval does not divide evenly into "
+ "30 minutes.");
+ }
+
+ if (options->TestingV3AuthInitialVoteDelay < MIN_VOTE_SECONDS_TESTING) {
+ REJECT("TestingV3AuthInitialVoteDelay is way too low.");
+ }
+
+ if (options->TestingV3AuthInitialDistDelay < MIN_DIST_SECONDS_TESTING) {
+ REJECT("TestingV3AuthInitialDistDelay is way too low.");
+ }
+
+ if (options->TestingV3AuthInitialVoteDelay +
+ options->TestingV3AuthInitialDistDelay >=
+ options->TestingV3AuthInitialVotingInterval) {
+ REJECT("TestingV3AuthInitialVoteDelay plus TestingV3AuthInitialDistDelay "
+ "must be less than TestingV3AuthInitialVotingInterval");
+ }
+
+ if (options->TestingV3AuthVotingStartOffset >
+ MIN(options->TestingV3AuthInitialVotingInterval,
+ options->V3AuthVotingInterval)) {
+ REJECT("TestingV3AuthVotingStartOffset is higher than the voting "
+ "interval.");
+ } else if (options->TestingV3AuthVotingStartOffset < 0) {
+ REJECT("TestingV3AuthVotingStartOffset must be non-negative.");
+ }
+
+ if (options->TestingAuthDirTimeToLearnReachability < 0) {
+ REJECT("TestingAuthDirTimeToLearnReachability must be non-negative.");
+ } else if (options->TestingAuthDirTimeToLearnReachability > 2*60*60) {
+ COMPLAIN("TestingAuthDirTimeToLearnReachability is insanely high.");
+ }
+
+ if (options->TestingEstimatedDescriptorPropagationTime < 0) {
+ REJECT("TestingEstimatedDescriptorPropagationTime must be non-negative.");
+ } else if (options->TestingEstimatedDescriptorPropagationTime > 60*60) {
+ COMPLAIN("TestingEstimatedDescriptorPropagationTime is insanely high.");
+ }
+
+ if (options->TestingClientMaxIntervalWithoutRequest < 1) {
+ REJECT("TestingClientMaxIntervalWithoutRequest is way too low.");
+ } else if (options->TestingClientMaxIntervalWithoutRequest > 3600) {
+ COMPLAIN("TestingClientMaxIntervalWithoutRequest is insanely high.");
+ }
+
+ if (options->TestingDirConnectionMaxStall < 5) {
+ REJECT("TestingDirConnectionMaxStall is way too low.");
+ } else if (options->TestingDirConnectionMaxStall > 3600) {
+ COMPLAIN("TestingDirConnectionMaxStall is insanely high.");
+ }
+
+ if (options->ClientBootstrapConsensusMaxInProgressTries < 1) {
+ REJECT("ClientBootstrapConsensusMaxInProgressTries must be greater "
+ "than 0.");
+ } else if (options->ClientBootstrapConsensusMaxInProgressTries
+ > 100) {
+ COMPLAIN("ClientBootstrapConsensusMaxInProgressTries is insanely "
+ "high.");
+ }
+
+ if (options->TestingEnableConnBwEvent &&
+ !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) {
+ REJECT("TestingEnableConnBwEvent may only be changed in testing "
+ "Tor networks!");
+ }
+
+ if (options->TestingEnableCellStatsEvent &&
+ !options->TestingTorNetwork && !options->UsingTestNetworkDefaults_) {
+ REJECT("TestingEnableCellStatsEvent may only be changed in testing "
+ "Tor networks!");
+ }
+
+ if (options->TestingTorNetwork) {
+ log_warn(LD_CONFIG, "TestingTorNetwork is set. This will make your node "
+ "almost unusable in the public Tor network, and is "
+ "therefore only advised if you are building a "
+ "testing Tor network!");
+ }
+
+ if (options->AccelName && !options->HardwareAccel)
+ options->HardwareAccel = 1;
+ if (options->AccelDir && !options->AccelName)
+ REJECT("Can't use hardware crypto accelerator dir without engine name.");
+
+ if (options->PublishServerDescriptor)
+ SMARTLIST_FOREACH(options->PublishServerDescriptor, const char *, pubdes, {
+ if (!strcmp(pubdes, "1") || !strcmp(pubdes, "0"))
+ if (smartlist_len(options->PublishServerDescriptor) > 1) {
+ COMPLAIN("You have passed a list of multiple arguments to the "
+ "PublishServerDescriptor option that includes 0 or 1. "
+ "0 or 1 should only be used as the sole argument. "
+ "This configuration will be rejected in a future release.");
+ break;
+ }
+ });
+
+ if (options->BridgeRelay == 1 && ! options->ORPort_set)
+ REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid "
+ "combination.");
+
+ if (options_validate_scheduler(options, msg) < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+#undef REJECT
+#undef COMPLAIN
+
+/* Given the value that the user has set for MaxMemInQueues, compute the
+ * actual maximum value. We clip this value if it's too low, and autodetect
+ * it if it's set to 0. */
+STATIC uint64_t
+compute_real_max_mem_in_queues(const uint64_t val, int log_guess)
+{
+ uint64_t result;
+
+ if (val == 0) {
+#define ONE_GIGABYTE (UINT64_C(1) << 30)
+#define ONE_MEGABYTE (UINT64_C(1) << 20)
+ /* The user didn't pick a memory limit. Choose a very large one
+ * that is still smaller than the system memory */
+ static int notice_sent = 0;
+ size_t ram = 0;
+ if (get_total_system_memory(&ram) < 0) {
+ /* We couldn't determine our total system memory! */
+#if SIZEOF_VOID_P >= 8
+ /* 64-bit system. Let's hope for 8 GB. */
+ result = 8 * ONE_GIGABYTE;
+#else
+ /* (presumably) 32-bit system. Let's hope for 1 GB. */
+ result = ONE_GIGABYTE;
+#endif /* SIZEOF_VOID_P >= 8 */
+ } else {
+ /* We detected the amount of memory available. */
+ uint64_t avail = 0;
+
+#if SIZEOF_SIZE_T > 4
+/* On a 64-bit platform, we consider 8GB "very large". */
+#define RAM_IS_VERY_LARGE(x) ((x) >= (8 * ONE_GIGABYTE))
+#else
+/* On a 32-bit platform, we can't have 8GB of ram. */
+#define RAM_IS_VERY_LARGE(x) (0)
+#endif
+
+ if (RAM_IS_VERY_LARGE(ram)) {
+ /* If we have 8 GB, or more, RAM available, we set the MaxMemInQueues
+ * to 0.4 * RAM. The idea behind this value is that the amount of RAM
+ * is more than enough for a single relay and should allow the relay
+ * operator to run two relays if they have additional bandwidth
+ * available.
+ */
+ avail = (ram / 5) * 2;
+ } else {
+ /* If we have less than 8 GB of RAM available, we use the "old" default
+ * for MaxMemInQueues of 0.75 * RAM.
+ */
+ avail = (ram / 4) * 3;
+ }
+
+ /* Make sure it's in range from 0.25 GB to 8 GB for 64-bit and 0.25 to 2
+ * GB for 32-bit. */
+ if (avail > MAX_DEFAULT_MEMORY_QUEUE_SIZE) {
+ /* If you want to use more than this much RAM, you need to configure
+ it yourself */
+ result = MAX_DEFAULT_MEMORY_QUEUE_SIZE;
+ } else if (avail < ONE_GIGABYTE / 4) {
+ result = ONE_GIGABYTE / 4;
+ } else {
+ result = avail;
+ }
+ }
+ if (log_guess && ! notice_sent) {
+ log_notice(LD_CONFIG, "%sMaxMemInQueues is set to %"PRIu64" MB. "
+ "You can override this by setting MaxMemInQueues by hand.",
+ ram ? "Based on detected system memory, " : "",
+ (result / ONE_MEGABYTE));
+ notice_sent = 1;
+ }
+ return result;
+ } else if (val < ONE_GIGABYTE / 4) {
+ log_warn(LD_CONFIG, "MaxMemInQueues must be at least 256 MB for now. "
+ "Ideally, have it as large as you can afford.");
+ return ONE_GIGABYTE / 4;
+ } else {
+ /* The value was fine all along */
+ return val;
+ }
+}
+
+/* If we have less than 300 MB suggest disabling dircache */
+#define DIRCACHE_MIN_MEM_MB 300
+#define DIRCACHE_MIN_MEM_BYTES (DIRCACHE_MIN_MEM_MB*ONE_MEGABYTE)
+#define STRINGIFY(val) #val
+
+/** Create a warning message for emitting if we are a dircache but may not have
+ * enough system memory, or if we are not a dircache but probably should be.
+ * Return -1 when a message is returned in *msg*, else return 0. */
+STATIC int
+have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem,
+ char **msg)
+{
+ *msg = NULL;
+ /* XXX We should possibly be looking at MaxMemInQueues here
+ * unconditionally. Or we should believe total_mem unconditionally. */
+ if (total_mem == 0) {
+ if (get_total_system_memory(&total_mem) < 0) {
+ total_mem = options->MaxMemInQueues >= SIZE_MAX ?
+ SIZE_MAX : (size_t)options->MaxMemInQueues;
+ }
+ }
+ if (options->DirCache) {
+ if (total_mem < DIRCACHE_MIN_MEM_BYTES) {
+ if (options->BridgeRelay) {
+ tor_asprintf(msg, "Running a Bridge with less than %d MB of memory "
+ "is not recommended.", DIRCACHE_MIN_MEM_MB);
+ } else {
+ tor_asprintf(msg, "Being a directory cache (default) with less than "
+ "%d MB of memory is not recommended and may consume "
+ "most of the available resources. Consider disabling "
+ "this functionality by setting the DirCache option "
+ "to 0.", DIRCACHE_MIN_MEM_MB);
+ }
+ }
+ } else {
+ if (total_mem >= DIRCACHE_MIN_MEM_BYTES) {
+ *msg = tor_strdup("DirCache is disabled and we are configured as a "
+ "relay. This may disqualify us from becoming a guard in the "
+ "future.");
+ }
+ }
+ return *msg == NULL ? 0 : -1;
+}
+#undef STRINGIFY
+
+/** Helper: return true iff s1 and s2 are both NULL, or both non-NULL
+ * equal strings. */
+static int
+opt_streq(const char *s1, const char *s2)
+{
+ return 0 == strcmp_opt(s1, s2);
+}
+
+/** Check if any of the previous options have changed but aren't allowed to. */
+static int
+options_transition_allowed(const or_options_t *old,
+ const or_options_t *new_val,
+ char **msg)
+{
+ if (!old)
+ return 0;
+
+#define BAD_CHANGE_TO(opt, how) do { \
+ *msg = tor_strdup("While Tor is running"how", changing " #opt \
+ " is not allowed"); \
+ return -1; \
+ } while (0)
+
+#define NO_CHANGE_BOOL(opt) \
+ if (! CFG_EQ_BOOL(old, new_val, opt)) BAD_CHANGE_TO(opt,"")
+#define NO_CHANGE_INT(opt) \
+ if (! CFG_EQ_INT(old, new_val, opt)) BAD_CHANGE_TO(opt,"")
+#define NO_CHANGE_STRING(opt) \
+ if (! CFG_EQ_STRING(old, new_val, opt)) BAD_CHANGE_TO(opt,"")
+
+ NO_CHANGE_STRING(PidFile);
+ NO_CHANGE_BOOL(RunAsDaemon);
+ NO_CHANGE_BOOL(Sandbox);
+ NO_CHANGE_STRING(DataDirectory);
+ NO_CHANGE_STRING(KeyDirectory);
+ NO_CHANGE_STRING(CacheDirectory);
+ NO_CHANGE_STRING(User);
+ NO_CHANGE_BOOL(KeepBindCapabilities);
+ NO_CHANGE_STRING(SyslogIdentityTag);
+ NO_CHANGE_STRING(AndroidIdentityTag);
+ NO_CHANGE_BOOL(HardwareAccel);
+ NO_CHANGE_STRING(AccelName);
+ NO_CHANGE_STRING(AccelDir);
+ NO_CHANGE_BOOL(TestingTorNetwork);
+ NO_CHANGE_BOOL(DisableAllSwap);
+ NO_CHANGE_INT(TokenBucketRefillInterval);
+ NO_CHANGE_BOOL(HiddenServiceSingleHopMode);
+ NO_CHANGE_BOOL(HiddenServiceNonAnonymousMode);
+ NO_CHANGE_BOOL(DisableDebuggerAttachment);
+ NO_CHANGE_BOOL(NoExec);
+ NO_CHANGE_INT(OwningControllerFD);
+ NO_CHANGE_BOOL(DisableSignalHandlers);
+
+ if (sandbox_is_active()) {
+#define SB_NOCHANGE_STR(opt) \
+ if (! CFG_EQ_STRING(old, new_val, opt)) \
+ BAD_CHANGE_TO(opt," with Sandbox active")
+#define SB_NOCHANGE_LINELIST(opt) \
+ if (! CFG_EQ_LINELIST(old, new_val, opt)) \
+ BAD_CHANGE_TO(opt," with Sandbox active")
+#define SB_NOCHANGE_INT(opt) \
+ if (! CFG_EQ_INT(old, new_val, opt)) \
+ BAD_CHANGE_TO(opt," with Sandbox active")
+
+ SB_NOCHANGE_STR(Address);
+ SB_NOCHANGE_STR(ServerDNSResolvConfFile);
+ SB_NOCHANGE_STR(DirPortFrontPage);
+ SB_NOCHANGE_STR(CookieAuthFile);
+ SB_NOCHANGE_STR(ExtORPortCookieAuthFile);
+ SB_NOCHANGE_LINELIST(Logs);
+ SB_NOCHANGE_INT(ConnLimit);
+
+ if (server_mode(old) != server_mode(new_val)) {
+ *msg = tor_strdup("Can't start/stop being a server while "
+ "Sandbox is active");
+ return -1;
+ }
+ }
+
+#undef SB_NOCHANGE_LINELIST
+#undef SB_NOCHANGE_STR
+#undef SB_NOCHANGE_INT
+#undef BAD_CHANGE_TO
+#undef NO_CHANGE_BOOL
+#undef NO_CHANGE_INT
+#undef NO_CHANGE_STRING
+ return 0;
+}
+
+/** Return 1 if any change from <b>old_options</b> to <b>new_options</b>
+ * will require us to rotate the CPU and DNS workers; else return 0. */
+static int
+options_transition_affects_workers(const or_options_t *old_options,
+ const or_options_t *new_options)
+{
+ YES_IF_CHANGED_STRING(DataDirectory);
+ YES_IF_CHANGED_INT(NumCPUs);
+ YES_IF_CHANGED_LINELIST(ORPort_lines);
+ YES_IF_CHANGED_BOOL(ServerDNSSearchDomains);
+ YES_IF_CHANGED_BOOL(SafeLogging_);
+ YES_IF_CHANGED_BOOL(ClientOnly);
+ YES_IF_CHANGED_BOOL(LogMessageDomains);
+ YES_IF_CHANGED_LINELIST(Logs);
+
+ if (server_mode(old_options) != server_mode(new_options) ||
+ public_server_mode(old_options) != public_server_mode(new_options) ||
+ dir_server_mode(old_options) != dir_server_mode(new_options))
+ return 1;
+
+ /* Nothing that changed matters. */
+ return 0;
+}
+
+/** Return 1 if any change from <b>old_options</b> to <b>new_options</b>
+ * will require us to generate a new descriptor; else return 0. */
+static int
+options_transition_affects_descriptor(const or_options_t *old_options,
+ const or_options_t *new_options)
+{
+ /* XXX We can be smarter here. If your DirPort isn't being
+ * published and you just turned it off, no need to republish. Etc. */
+
+ YES_IF_CHANGED_STRING(DataDirectory);
+ YES_IF_CHANGED_STRING(Nickname);
+ YES_IF_CHANGED_STRING(Address);
+ YES_IF_CHANGED_LINELIST(ExitPolicy);
+ YES_IF_CHANGED_BOOL(ExitRelay);
+ YES_IF_CHANGED_BOOL(ExitPolicyRejectPrivate);
+ YES_IF_CHANGED_BOOL(ExitPolicyRejectLocalInterfaces);
+ YES_IF_CHANGED_BOOL(IPv6Exit);
+ YES_IF_CHANGED_LINELIST(ORPort_lines);
+ YES_IF_CHANGED_LINELIST(DirPort_lines);
+ YES_IF_CHANGED_LINELIST(DirPort_lines);
+ YES_IF_CHANGED_BOOL(ClientOnly);
+ YES_IF_CHANGED_BOOL(DisableNetwork);
+ YES_IF_CHANGED_BOOL(PublishServerDescriptor_);
+ YES_IF_CHANGED_STRING(ContactInfo);
+ YES_IF_CHANGED_STRING(BridgeDistribution);
+ YES_IF_CHANGED_LINELIST(MyFamily);
+ YES_IF_CHANGED_STRING(AccountingStart);
+ YES_IF_CHANGED_INT(AccountingMax);
+ YES_IF_CHANGED_INT(AccountingRule);
+ YES_IF_CHANGED_BOOL(DirCache);
+ YES_IF_CHANGED_BOOL(AssumeReachable);
+
+ if (get_effective_bwrate(old_options) != get_effective_bwrate(new_options) ||
+ get_effective_bwburst(old_options) !=
+ get_effective_bwburst(new_options) ||
+ public_server_mode(old_options) != public_server_mode(new_options))
+ return 1;
+
+ return 0;
+}
+
+#ifdef _WIN32
+/** Return the directory on windows where we expect to find our application
+ * data. */
+static char *
+get_windows_conf_root(void)
+{
+ static int is_set = 0;
+ static char path[MAX_PATH*2+1];
+ TCHAR tpath[MAX_PATH] = {0};
+
+ LPITEMIDLIST idl;
+ IMalloc *m;
+ HRESULT result;
+
+ if (is_set)
+ return path;
+
+ /* Find X:\documents and settings\username\application data\ .
+ * We would use SHGetSpecialFolder path, but that wasn't added until IE4.
+ */
+#ifdef ENABLE_LOCAL_APPDATA
+#define APPDATA_PATH CSIDL_LOCAL_APPDATA
+#else
+#define APPDATA_PATH CSIDL_APPDATA
+#endif
+ if (!SUCCEEDED(SHGetSpecialFolderLocation(NULL, APPDATA_PATH, &idl))) {
+ getcwd(path,MAX_PATH);
+ is_set = 1;
+ log_warn(LD_CONFIG,
+ "I couldn't find your application data folder: are you "
+ "running an ancient version of Windows 95? Defaulting to \"%s\"",
+ path);
+ return path;
+ }
+ /* Convert the path from an "ID List" (whatever that is!) to a path. */
+ result = SHGetPathFromIDList(idl, tpath);
+#ifdef UNICODE
+ wcstombs(path,tpath,sizeof(path));
+ path[sizeof(path)-1] = '\0';
+#else
+ strlcpy(path,tpath,sizeof(path));
+#endif /* defined(UNICODE) */
+
+ /* Now we need to free the memory that the path-idl was stored in. In
+ * typical Windows fashion, we can't just call 'free()' on it. */
+ SHGetMalloc(&m);
+ if (m) {
+ m->lpVtbl->Free(m, idl);
+ m->lpVtbl->Release(m);
+ }
+ if (!SUCCEEDED(result)) {
+ return NULL;
+ }
+ strlcat(path,"\\tor",MAX_PATH);
+ is_set = 1;
+ return path;
+}
+#endif /* defined(_WIN32) */
+
+/** Return the default location for our torrc file (if <b>defaults_file</b> is
+ * false), or for the torrc-defaults file (if <b>defaults_file</b> is true). */
+static const char *
+get_default_conf_file(int defaults_file)
+{
+#ifdef DISABLE_SYSTEM_TORRC
+ (void) defaults_file;
+ return NULL;
+#elif defined(_WIN32)
+ if (defaults_file) {
+ static char defaults_path[MAX_PATH+1];
+ tor_snprintf(defaults_path, MAX_PATH, "%s\\torrc-defaults",
+ get_windows_conf_root());
+ return defaults_path;
+ } else {
+ static char path[MAX_PATH+1];
+ tor_snprintf(path, MAX_PATH, "%s\\torrc",
+ get_windows_conf_root());
+ return path;
+ }
+#else
+ return defaults_file ? CONFDIR "/torrc-defaults" : CONFDIR "/torrc";
+#endif /* defined(DISABLE_SYSTEM_TORRC) || ... */
+}
+
+/** Verify whether lst is a list of strings containing valid-looking
+ * comma-separated nicknames, or NULL. Will normalise <b>lst</b> to prefix '$'
+ * to any nickname or fingerprint that needs it. Also splits comma-separated
+ * list elements into multiple elements. Return 0 on success.
+ * Warn and return -1 on failure.
+ */
+static int
+normalize_nickname_list(config_line_t **normalized_out,
+ const config_line_t *lst, const char *name,
+ char **msg)
+{
+ if (!lst)
+ return 0;
+
+ config_line_t *new_nicknames = NULL;
+ config_line_t **new_nicknames_next = &new_nicknames;
+
+ const config_line_t *cl;
+ for (cl = lst; cl; cl = cl->next) {
+ const char *line = cl->value;
+ if (!line)
+ continue;
+
+ int valid_line = 1;
+ smartlist_t *sl = smartlist_new();
+ smartlist_split_string(sl, line, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK|SPLIT_STRIP_SPACE, 0);
+ SMARTLIST_FOREACH_BEGIN(sl, char *, s)
+ {
+ char *normalized = NULL;
+ if (!is_legal_nickname_or_hexdigest(s)) {
+ // check if first char is dollar
+ if (s[0] != '$') {
+ // Try again but with a dollar symbol prepended
+ char *prepended;
+ tor_asprintf(&prepended, "$%s", s);
+
+ if (is_legal_nickname_or_hexdigest(prepended)) {
+ // The nickname is valid when it's prepended, set it as the
+ // normalized version
+ normalized = prepended;
+ } else {
+ // Still not valid, free and fallback to error message
+ tor_free(prepended);
+ }
+ }
+
+ if (!normalized) {
+ tor_asprintf(msg, "Invalid nickname '%s' in %s line", s, name);
+ valid_line = 0;
+ break;
+ }
+ } else {
+ normalized = tor_strdup(s);
+ }
+
+ config_line_t *next = tor_malloc_zero(sizeof(*next));
+ next->key = tor_strdup(cl->key);
+ next->value = normalized;
+ next->next = NULL;
+
+ *new_nicknames_next = next;
+ new_nicknames_next = &next->next;
+ } SMARTLIST_FOREACH_END(s);
+
+ SMARTLIST_FOREACH(sl, char *, s, tor_free(s));
+ smartlist_free(sl);
+
+ if (!valid_line) {
+ config_free_lines(new_nicknames);
+ return -1;
+ }
+ }
+
+ *normalized_out = new_nicknames;
+
+ return 0;
+}
+
+/** Learn config file name from command line arguments, or use the default.
+ *
+ * If <b>defaults_file</b> is true, we're looking for torrc-defaults;
+ * otherwise, we're looking for the regular torrc_file.
+ *
+ * Set *<b>using_default_fname</b> to true if we're using the default
+ * configuration file name; or false if we've set it from the command line.
+ *
+ * Set *<b>ignore_missing_torrc</b> to true if we should ignore the resulting
+ * filename if it doesn't exist.
+ */
+static char *
+find_torrc_filename(config_line_t *cmd_arg,
+ int defaults_file,
+ int *using_default_fname, int *ignore_missing_torrc)
+{
+ char *fname=NULL;
+ config_line_t *p_index;
+ const char *fname_opt = defaults_file ? "--defaults-torrc" : "-f";
+ const char *ignore_opt = defaults_file ? NULL : "--ignore-missing-torrc";
+
+ if (defaults_file)
+ *ignore_missing_torrc = 1;
+
+ for (p_index = cmd_arg; p_index; p_index = p_index->next) {
+ if (!strcmp(p_index->key, fname_opt)) {
+ if (fname) {
+ log_warn(LD_CONFIG, "Duplicate %s options on command line.",
+ fname_opt);
+ tor_free(fname);
+ }
+ fname = expand_filename(p_index->value);
+
+ {
+ char *absfname;
+ absfname = make_path_absolute(fname);
+ tor_free(fname);
+ fname = absfname;
+ }
+
+ *using_default_fname = 0;
+ } else if (ignore_opt && !strcmp(p_index->key,ignore_opt)) {
+ *ignore_missing_torrc = 1;
+ }
+ }
+
+ if (*using_default_fname) {
+ /* didn't find one, try CONFDIR */
+ const char *dflt = get_default_conf_file(defaults_file);
+ file_status_t st = file_status(dflt);
+ if (dflt && (st == FN_FILE || st == FN_EMPTY)) {
+ fname = tor_strdup(dflt);
+ } else {
+#ifndef _WIN32
+ char *fn = NULL;
+ if (!defaults_file) {
+ fn = expand_filename("~/.torrc");
+ }
+ if (fn) {
+ file_status_t hmst = file_status(fn);
+ if (hmst == FN_FILE || hmst == FN_EMPTY || dflt == NULL) {
+ fname = fn;
+ } else {
+ tor_free(fn);
+ fname = tor_strdup(dflt);
+ }
+ } else {
+ fname = dflt ? tor_strdup(dflt) : NULL;
+ }
+#else /* !(!defined(_WIN32)) */
+ fname = dflt ? tor_strdup(dflt) : NULL;
+#endif /* !defined(_WIN32) */
+ }
+ }
+ return fname;
+}
+
+/** Read the torrc from standard input and return it as a string.
+ * Upon failure, return NULL.
+ */
+static char *
+load_torrc_from_stdin(void)
+{
+ size_t sz_out;
+
+ return read_file_to_str_until_eof(STDIN_FILENO,SIZE_MAX,&sz_out);
+}
+
+/** Load a configuration file from disk, setting torrc_fname or
+ * torrc_defaults_fname if successful.
+ *
+ * If <b>defaults_file</b> is true, load torrc-defaults; otherwise load torrc.
+ *
+ * Return the contents of the file on success, and NULL on failure.
+ */
+static char *
+load_torrc_from_disk(config_line_t *cmd_arg, int defaults_file)
+{
+ char *fname=NULL;
+ char *cf = NULL;
+ int using_default_torrc = 1;
+ int ignore_missing_torrc = 0;
+ char **fname_var = defaults_file ? &torrc_defaults_fname : &torrc_fname;
+
+ if (*fname_var == NULL) {
+ fname = find_torrc_filename(cmd_arg, defaults_file,
+ &using_default_torrc, &ignore_missing_torrc);
+ tor_free(*fname_var);
+ *fname_var = fname;
+ } else {
+ fname = *fname_var;
+ }
+ log_debug(LD_CONFIG, "Opening config file \"%s\"", fname?fname:"<NULL>");
+
+ /* Open config file */
+ file_status_t st = fname ? file_status(fname) : FN_EMPTY;
+ if (fname == NULL ||
+ !(st == FN_FILE || st == FN_EMPTY) ||
+ !(cf = read_file_to_str(fname,0,NULL))) {
+ if (using_default_torrc == 1 || ignore_missing_torrc) {
+ if (!defaults_file)
+ log_notice(LD_CONFIG, "Configuration file \"%s\" not present, "
+ "using reasonable defaults.", fname);
+ tor_free(fname); /* sets fname to NULL */
+ *fname_var = NULL;
+ cf = tor_strdup("");
+ } else {
+ log_warn(LD_CONFIG,
+ "Unable to open configuration file \"%s\".", fname);
+ goto err;
+ }
+ } else {
+ log_notice(LD_CONFIG, "Read configuration file \"%s\".", fname);
+ }
+
+ return cf;
+ err:
+ tor_free(fname);
+ *fname_var = NULL;
+ return NULL;
+}
+
+/** Read a configuration file into <b>options</b>, finding the configuration
+ * file location based on the command line. After loading the file
+ * call options_init_from_string() to load the config.
+ * Return 0 if success, -1 if failure, and 1 if we succeeded but should exit
+ * anyway. */
+int
+options_init_from_torrc(int argc, char **argv)
+{
+ char *cf=NULL, *cf_defaults=NULL;
+ int command;
+ int retval = -1;
+ char *command_arg = NULL;
+ char *errmsg=NULL;
+ config_line_t *p_index = NULL;
+ config_line_t *cmdline_only_options = NULL;
+
+ /* Go through command-line variables */
+ if (! have_parsed_cmdline) {
+ /* Or we could redo the list every time we pass this place.
+ * It does not really matter */
+ if (config_parse_commandline(argc, argv, 0, &global_cmdline_options,
+ &global_cmdline_only_options) < 0) {
+ goto err;
+ }
+ have_parsed_cmdline = 1;
+ }
+ cmdline_only_options = global_cmdline_only_options;
+
+ if (config_line_find(cmdline_only_options, "-h") ||
+ config_line_find(cmdline_only_options, "--help")) {
+ print_usage();
+ return 1;
+ }
+ if (config_line_find(cmdline_only_options, "--list-torrc-options")) {
+ /* For validating whether we've documented everything. */
+ list_torrc_options();
+ return 1;
+ }
+ if (config_line_find(cmdline_only_options, "--list-deprecated-options")) {
+ /* For validating whether what we have deprecated really exists. */
+ list_deprecated_options();
+ return 1;
+ }
+
+ if (config_line_find(cmdline_only_options, "--version")) {
+ printf("Tor version %s.\n",get_version());
+ return 1;
+ }
+
+ if (config_line_find(cmdline_only_options, "--library-versions")) {
+ printf("Tor version %s. \n", get_version());
+ printf("Library versions\tCompiled\t\tRuntime\n");
+ printf("Libevent\t\t%-15s\t\t%s\n",
+ tor_libevent_get_header_version_str(),
+ tor_libevent_get_version_str());
+ printf("OpenSSL \t\t%-15s\t\t%s\n",
+ crypto_openssl_get_header_version_str(),
+ crypto_openssl_get_version_str());
+ if (tor_compress_supports_method(ZLIB_METHOD)) {
+ printf("Zlib \t\t%-15s\t\t%s\n",
+ tor_compress_version_str(ZLIB_METHOD),
+ tor_compress_header_version_str(ZLIB_METHOD));
+ }
+ if (tor_compress_supports_method(LZMA_METHOD)) {
+ printf("Liblzma \t\t%-15s\t\t%s\n",
+ tor_compress_version_str(LZMA_METHOD),
+ tor_compress_header_version_str(LZMA_METHOD));
+ }
+ if (tor_compress_supports_method(ZSTD_METHOD)) {
+ printf("Libzstd \t\t%-15s\t\t%s\n",
+ tor_compress_version_str(ZSTD_METHOD),
+ tor_compress_header_version_str(ZSTD_METHOD));
+ }
+ //TODO: Hex versions?
+ return 1;
+ }
+
+ command = CMD_RUN_TOR;
+ for (p_index = cmdline_only_options; p_index; p_index = p_index->next) {
+ if (!strcmp(p_index->key,"--keygen")) {
+ command = CMD_KEYGEN;
+ } else if (!strcmp(p_index->key, "--key-expiration")) {
+ command = CMD_KEY_EXPIRATION;
+ command_arg = p_index->value;
+ } else if (!strcmp(p_index->key,"--list-fingerprint")) {
+ command = CMD_LIST_FINGERPRINT;
+ } else if (!strcmp(p_index->key, "--hash-password")) {
+ command = CMD_HASH_PASSWORD;
+ command_arg = p_index->value;
+ } else if (!strcmp(p_index->key, "--dump-config")) {
+ command = CMD_DUMP_CONFIG;
+ command_arg = p_index->value;
+ } else if (!strcmp(p_index->key, "--verify-config")) {
+ command = CMD_VERIFY_CONFIG;
+ }
+ }
+
+ if (command == CMD_HASH_PASSWORD) {
+ cf_defaults = tor_strdup("");
+ cf = tor_strdup("");
+ } else {
+ cf_defaults = load_torrc_from_disk(cmdline_only_options, 1);
+
+ const config_line_t *f_line = config_line_find(cmdline_only_options,
+ "-f");
+
+ const int read_torrc_from_stdin =
+ (f_line != NULL && strcmp(f_line->value, "-") == 0);
+
+ if (read_torrc_from_stdin) {
+ cf = load_torrc_from_stdin();
+ } else {
+ cf = load_torrc_from_disk(cmdline_only_options, 0);
+ }
+
+ if (!cf) {
+ if (config_line_find(cmdline_only_options, "--allow-missing-torrc")) {
+ cf = tor_strdup("");
+ } else {
+ goto err;
+ }
+ }
+ }
+
+ retval = options_init_from_string(cf_defaults, cf, command, command_arg,
+ &errmsg);
+
+ if (retval < 0)
+ goto err;
+
+ if (config_line_find(cmdline_only_options, "--no-passphrase")) {
+ if (command == CMD_KEYGEN) {
+ get_options_mutable()->keygen_force_passphrase = FORCE_PASSPHRASE_OFF;
+ } else {
+ log_err(LD_CONFIG, "--no-passphrase specified without --keygen!");
+ retval = -1;
+ goto err;
+ }
+ }
+
+ if (config_line_find(cmdline_only_options, "--newpass")) {
+ if (command == CMD_KEYGEN) {
+ get_options_mutable()->change_key_passphrase = 1;
+ } else {
+ log_err(LD_CONFIG, "--newpass specified without --keygen!");
+ retval = -1;
+ goto err;
+ }
+ }
+
+ {
+ const config_line_t *fd_line = config_line_find(cmdline_only_options,
+ "--passphrase-fd");
+ if (fd_line) {
+ if (get_options()->keygen_force_passphrase == FORCE_PASSPHRASE_OFF) {
+ log_err(LD_CONFIG, "--no-passphrase specified with --passphrase-fd!");
+ retval = -1;
+ goto err;
+ } else if (command != CMD_KEYGEN) {
+ log_err(LD_CONFIG, "--passphrase-fd specified without --keygen!");
+ retval = -1;
+ goto err;
+ } else {
+ const char *v = fd_line->value;
+ int ok = 1;
+ long fd = tor_parse_long(v, 10, 0, INT_MAX, &ok, NULL);
+ if (fd < 0 || ok == 0) {
+ log_err(LD_CONFIG, "Invalid --passphrase-fd value %s", escaped(v));
+ retval = -1;
+ goto err;
+ }
+ get_options_mutable()->keygen_passphrase_fd = (int)fd;
+ get_options_mutable()->use_keygen_passphrase_fd = 1;
+ get_options_mutable()->keygen_force_passphrase = FORCE_PASSPHRASE_ON;
+ }
+ }
+ }
+
+ {
+ const config_line_t *key_line = config_line_find(cmdline_only_options,
+ "--master-key");
+ if (key_line) {
+ if (command != CMD_KEYGEN) {
+ log_err(LD_CONFIG, "--master-key without --keygen!");
+ retval = -1;
+ goto err;
+ } else {
+ get_options_mutable()->master_key_fname = tor_strdup(key_line->value);
+ }
+ }
+ }
+
+ err:
+
+ tor_free(cf);
+ tor_free(cf_defaults);
+ if (errmsg) {
+ log_warn(LD_CONFIG,"%s", errmsg);
+ tor_free(errmsg);
+ }
+ return retval < 0 ? -1 : 0;
+}
+
+/** Load the options from the configuration in <b>cf</b>, validate
+ * them for consistency and take actions based on them.
+ *
+ * Return 0 if success, negative on error:
+ * * -1 for general errors.
+ * * -2 for failure to parse/validate,
+ * * -3 for transition not allowed
+ * * -4 for error while setting the new options
+ */
+setopt_err_t
+options_init_from_string(const char *cf_defaults, const char *cf,
+ int command, const char *command_arg,
+ char **msg)
+{
+ or_options_t *oldoptions, *newoptions, *newdefaultoptions=NULL;
+ config_line_t *cl;
+ int retval;
+ setopt_err_t err = SETOPT_ERR_MISC;
+ int cf_has_include = 0;
+ tor_assert(msg);
+
+ oldoptions = global_options; /* get_options unfortunately asserts if
+ this is the first time we run*/
+
+ newoptions = tor_malloc_zero(sizeof(or_options_t));
+ newoptions->magic_ = OR_OPTIONS_MAGIC;
+ options_init(newoptions);
+ newoptions->command = command;
+ newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL;
+
+ smartlist_t *opened_files = smartlist_new();
+ for (int i = 0; i < 2; ++i) {
+ const char *body = i==0 ? cf_defaults : cf;
+ if (!body)
+ continue;
+
+ /* get config lines, assign them */
+ retval = config_get_lines_include(body, &cl, 1,
+ body == cf ? &cf_has_include : NULL,
+ opened_files);
+ if (retval < 0) {
+ err = SETOPT_ERR_PARSE;
+ goto err;
+ }
+ retval = config_assign(&options_format, newoptions, cl,
+ CAL_WARN_DEPRECATIONS, msg);
+ config_free_lines(cl);
+ if (retval < 0) {
+ err = SETOPT_ERR_PARSE;
+ goto err;
+ }
+ if (i==0)
+ newdefaultoptions = config_dup(&options_format, newoptions);
+ }
+
+ if (newdefaultoptions == NULL) {
+ newdefaultoptions = config_dup(&options_format, global_default_options);
+ }
+
+ /* Go through command-line variables too */
+ retval = config_assign(&options_format, newoptions,
+ global_cmdline_options, CAL_WARN_DEPRECATIONS, msg);
+ if (retval < 0) {
+ err = SETOPT_ERR_PARSE;
+ goto err;
+ }
+
+ newoptions->IncludeUsed = cf_has_include;
+ newoptions->FilesOpenedByIncludes = opened_files;
+
+ /* If this is a testing network configuration, change defaults
+ * for a list of dependent config options, re-initialize newoptions
+ * with the new defaults, and assign all options to it second time. */
+ if (newoptions->TestingTorNetwork) {
+ /* XXXX this is a bit of a kludge. perhaps there's a better way to do
+ * this? We could, for example, make the parsing algorithm do two passes
+ * over the configuration. If it finds any "suite" options like
+ * TestingTorNetwork, it could change the defaults before its second pass.
+ * Not urgent so long as this seems to work, but at any sign of trouble,
+ * let's clean it up. -NM */
+
+ /* Change defaults. */
+ for (int i = 0; testing_tor_network_defaults[i].name; ++i) {
+ const config_var_t *new_var = &testing_tor_network_defaults[i];
+ config_var_t *old_var =
+ config_find_option_mutable(&options_format, new_var->name);
+ tor_assert(new_var);
+ tor_assert(old_var);
+ old_var->initvalue = new_var->initvalue;
+
+ if ((config_find_deprecation(&options_format, new_var->name))) {
+ log_warn(LD_GENERAL, "Testing options override the deprecated "
+ "option %s. Is that intentional?",
+ new_var->name);
+ }
+ }
+
+ /* Clear newoptions and re-initialize them with new defaults. */
+ or_options_free(newoptions);
+ or_options_free(newdefaultoptions);
+ newdefaultoptions = NULL;
+ newoptions = tor_malloc_zero(sizeof(or_options_t));
+ newoptions->magic_ = OR_OPTIONS_MAGIC;
+ options_init(newoptions);
+ newoptions->command = command;
+ newoptions->command_arg = command_arg ? tor_strdup(command_arg) : NULL;
+
+ /* Assign all options a second time. */
+ opened_files = smartlist_new();
+ for (int i = 0; i < 2; ++i) {
+ const char *body = i==0 ? cf_defaults : cf;
+ if (!body)
+ continue;
+
+ /* get config lines, assign them */
+ retval = config_get_lines_include(body, &cl, 1,
+ body == cf ? &cf_has_include : NULL,
+ opened_files);
+ if (retval < 0) {
+ err = SETOPT_ERR_PARSE;
+ goto err;
+ }
+ retval = config_assign(&options_format, newoptions, cl, 0, msg);
+ config_free_lines(cl);
+ if (retval < 0) {
+ err = SETOPT_ERR_PARSE;
+ goto err;
+ }
+ if (i==0)
+ newdefaultoptions = config_dup(&options_format, newoptions);
+ }
+ /* Assign command-line variables a second time too */
+ retval = config_assign(&options_format, newoptions,
+ global_cmdline_options, 0, msg);
+ if (retval < 0) {
+ err = SETOPT_ERR_PARSE;
+ goto err;
+ }
+ }
+
+ newoptions->IncludeUsed = cf_has_include;
+ in_option_validation = 1;
+ newoptions->FilesOpenedByIncludes = opened_files;
+
+ /* Validate newoptions */
+ if (options_validate(oldoptions, newoptions, newdefaultoptions,
+ 0, msg) < 0) {
+ err = SETOPT_ERR_PARSE; /*XXX make this a separate return value.*/
+ goto err;
+ }
+
+ if (options_transition_allowed(oldoptions, newoptions, msg) < 0) {
+ err = SETOPT_ERR_TRANSITION;
+ goto err;
+ }
+ in_option_validation = 0;
+
+ if (set_options(newoptions, msg)) {
+ err = SETOPT_ERR_SETTING;
+ goto err; /* frees and replaces old options */
+ }
+
+ or_options_free(global_default_options);
+ global_default_options = newdefaultoptions;
+
+ return SETOPT_OK;
+
+ err:
+ in_option_validation = 0;
+ if (opened_files) {
+ SMARTLIST_FOREACH(opened_files, char *, f, tor_free(f));
+ smartlist_free(opened_files);
+ }
+ // may have been set to opened_files, avoid double free
+ newoptions->FilesOpenedByIncludes = NULL;
+ or_options_free(newoptions);
+ or_options_free(newdefaultoptions);
+ if (*msg) {
+ char *old_msg = *msg;
+ tor_asprintf(msg, "Failed to parse/validate config: %s", old_msg);
+ tor_free(old_msg);
+ }
+ return err;
+}
+
+/** Return the location for our configuration file. May return NULL.
+ */
+const char *
+get_torrc_fname(int defaults_fname)
+{
+ const char *fname = defaults_fname ? torrc_defaults_fname : torrc_fname;
+
+ if (fname)
+ return fname;
+ else
+ return get_default_conf_file(defaults_fname);
+}
+
+/** Adjust the address map based on the MapAddress elements in the
+ * configuration <b>options</b>
+ */
+void
+config_register_addressmaps(const or_options_t *options)
+{
+ smartlist_t *elts;
+ config_line_t *opt;
+ const char *from, *to, *msg;
+
+ addressmap_clear_configured();
+ elts = smartlist_new();
+ for (opt = options->AddressMap; opt; opt = opt->next) {
+ smartlist_split_string(elts, opt->value, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
+ if (smartlist_len(elts) < 2) {
+ log_warn(LD_CONFIG,"MapAddress '%s' has too few arguments. Ignoring.",
+ opt->value);
+ goto cleanup;
+ }
+
+ from = smartlist_get(elts,0);
+ to = smartlist_get(elts,1);
+
+ if (to[0] == '.' || from[0] == '.') {
+ log_warn(LD_CONFIG,"MapAddress '%s' is ambiguous - address starts with a"
+ "'.'. Ignoring.",opt->value);
+ goto cleanup;
+ }
+
+ if (addressmap_register_auto(from, to, 0, ADDRMAPSRC_TORRC, &msg) < 0) {
+ log_warn(LD_CONFIG,"MapAddress '%s' failed: %s. Ignoring.", opt->value,
+ msg);
+ goto cleanup;
+ }
+
+ if (smartlist_len(elts) > 2)
+ log_warn(LD_CONFIG,"Ignoring extra arguments to MapAddress.");
+
+ cleanup:
+ SMARTLIST_FOREACH(elts, char*, cp, tor_free(cp));
+ smartlist_clear(elts);
+ }
+ smartlist_free(elts);
+}
+
+/** As addressmap_register(), but detect the wildcarded status of "from" and
+ * "to", and do not steal a reference to <b>to</b>. */
+/* XXXX move to connection_edge.c */
+int
+addressmap_register_auto(const char *from, const char *to,
+ time_t expires,
+ addressmap_entry_source_t addrmap_source,
+ const char **msg)
+{
+ int from_wildcard = 0, to_wildcard = 0;
+
+ *msg = "whoops, forgot the error message";
+
+ if (!strcmp(to, "*") || !strcmp(from, "*")) {
+ *msg = "can't remap from or to *";
+ return -1;
+ }
+ /* Detect asterisks in expressions of type: '*.example.com' */
+ if (!strncmp(from,"*.",2)) {
+ from += 2;
+ from_wildcard = 1;
+ }
+ if (!strncmp(to,"*.",2)) {
+ to += 2;
+ to_wildcard = 1;
+ }
+
+ if (to_wildcard && !from_wildcard) {
+ *msg = "can only use wildcard (i.e. '*.') if 'from' address "
+ "uses wildcard also";
+ return -1;
+ }
+
+ if (address_is_invalid_destination(to, 1)) {
+ *msg = "destination is invalid";
+ return -1;
+ }
+
+ addressmap_register(from, tor_strdup(to), expires, addrmap_source,
+ from_wildcard, to_wildcard);
+
+ return 0;
+}
+
+/**
+ * As add_file_log, but open the file as appropriate.
+ */
+STATIC int
+open_and_add_file_log(const log_severity_list_t *severity,
+ const char *filename, int truncate_log)
+{
+ int open_flags = O_WRONLY|O_CREAT;
+ open_flags |= truncate_log ? O_TRUNC : O_APPEND;
+
+ int fd = tor_open_cloexec(filename, open_flags, 0640);
+ if (fd < 0)
+ return -1;
+
+ return add_file_log(severity, filename, fd);
+}
+
+/**
+ * Initialize the logs based on the configuration file.
+ */
+static int
+options_init_logs(const or_options_t *old_options, or_options_t *options,
+ int validate_only)
+{
+ config_line_t *opt;
+ int ok;
+ smartlist_t *elts;
+ int run_as_daemon =
+#ifdef _WIN32
+ 0;
+#else
+ options->RunAsDaemon;
+#endif
+
+ if (options->LogTimeGranularity <= 0) {
+ log_warn(LD_CONFIG, "Log time granularity '%d' has to be positive.",
+ options->LogTimeGranularity);
+ return -1;
+ } else if (1000 % options->LogTimeGranularity != 0 &&
+ options->LogTimeGranularity % 1000 != 0) {
+ int granularity = options->LogTimeGranularity;
+ if (granularity < 40) {
+ do granularity++;
+ while (1000 % granularity != 0);
+ } else if (granularity < 1000) {
+ granularity = 1000 / granularity;
+ while (1000 % granularity != 0)
+ granularity--;
+ granularity = 1000 / granularity;
+ } else {
+ granularity = 1000 * ((granularity / 1000) + 1);
+ }
+ log_warn(LD_CONFIG, "Log time granularity '%d' has to be either a "
+ "divisor or a multiple of 1 second. Changing to "
+ "'%d'.",
+ options->LogTimeGranularity, granularity);
+ if (!validate_only)
+ set_log_time_granularity(granularity);
+ } else {
+ if (!validate_only)
+ set_log_time_granularity(options->LogTimeGranularity);
+ }
+
+ ok = 1;
+ elts = smartlist_new();
+
+ for (opt = options->Logs; opt; opt = opt->next) {
+ log_severity_list_t *severity;
+ const char *cfg = opt->value;
+ severity = tor_malloc_zero(sizeof(log_severity_list_t));
+ if (parse_log_severity_config(&cfg, severity) < 0) {
+ log_warn(LD_CONFIG, "Couldn't parse log levels in Log option 'Log %s'",
+ opt->value);
+ ok = 0; goto cleanup;
+ }
+
+ smartlist_split_string(elts, cfg, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2);
+
+ if (smartlist_len(elts) == 0)
+ smartlist_add_strdup(elts, "stdout");
+
+ if (smartlist_len(elts) == 1 &&
+ (!strcasecmp(smartlist_get(elts,0), "stdout") ||
+ !strcasecmp(smartlist_get(elts,0), "stderr"))) {
+ int err = smartlist_len(elts) &&
+ !strcasecmp(smartlist_get(elts,0), "stderr");
+ if (!validate_only) {
+ if (run_as_daemon) {
+ log_warn(LD_CONFIG,
+ "Can't log to %s with RunAsDaemon set; skipping stdout",
+ err?"stderr":"stdout");
+ } else {
+ add_stream_log(severity, err?"<stderr>":"<stdout>",
+ fileno(err?stderr:stdout));
+ }
+ }
+ goto cleanup;
+ }
+ if (smartlist_len(elts) == 1) {
+ if (!strcasecmp(smartlist_get(elts,0), "syslog")) {
+#ifdef HAVE_SYSLOG_H
+ if (!validate_only) {
+ add_syslog_log(severity, options->SyslogIdentityTag);
+ }
+#else
+ log_warn(LD_CONFIG, "Syslog is not supported on this system. Sorry.");
+#endif /* defined(HAVE_SYSLOG_H) */
+ goto cleanup;
+ }
+
+ if (!strcasecmp(smartlist_get(elts, 0), "android")) {
+#ifdef HAVE_ANDROID_LOG_H
+ if (!validate_only) {
+ add_android_log(severity, options->AndroidIdentityTag);
+ }
+#else
+ log_warn(LD_CONFIG, "Android logging is not supported"
+ " on this system. Sorry.");
+#endif // HAVE_ANDROID_LOG_H.
+ goto cleanup;
+ }
+ }
+
+ if (smartlist_len(elts) == 2 &&
+ !strcasecmp(smartlist_get(elts,0), "file")) {
+ if (!validate_only) {
+ char *fname = expand_filename(smartlist_get(elts, 1));
+ /* Truncate if TruncateLogFile is set and we haven't seen this option
+ line before. */
+ int truncate_log = 0;
+ if (options->TruncateLogFile) {
+ truncate_log = 1;
+ if (old_options) {
+ config_line_t *opt2;
+ for (opt2 = old_options->Logs; opt2; opt2 = opt2->next)
+ if (!strcmp(opt->value, opt2->value)) {
+ truncate_log = 0;
+ break;
+ }
+ }
+ }
+ if (open_and_add_file_log(severity, fname, truncate_log) < 0) {
+ log_warn(LD_CONFIG, "Couldn't open file for 'Log %s': %s",
+ opt->value, strerror(errno));
+ ok = 0;
+ }
+ tor_free(fname);
+ }
+ goto cleanup;
+ }
+
+ log_warn(LD_CONFIG, "Bad syntax on file Log option 'Log %s'",
+ opt->value);
+ ok = 0; goto cleanup;
+
+ cleanup:
+ SMARTLIST_FOREACH(elts, char*, cp, tor_free(cp));
+ smartlist_clear(elts);
+ tor_free(severity);
+ }
+ smartlist_free(elts);
+
+ if (ok && !validate_only)
+ logs_set_domain_logging(options->LogMessageDomains);
+
+ return ok?0:-1;
+}
+
+/** Given a smartlist of SOCKS arguments to be passed to a transport
+ * proxy in <b>args</b>, validate them and return -1 if they are
+ * corrupted. Return 0 if they seem OK. */
+static int
+validate_transport_socks_arguments(const smartlist_t *args)
+{
+ char *socks_string = NULL;
+ size_t socks_string_len;
+
+ tor_assert(args);
+ tor_assert(smartlist_len(args) > 0);
+
+ SMARTLIST_FOREACH_BEGIN(args, const char *, s) {
+ if (!string_is_key_value(LOG_WARN, s)) { /* items should be k=v items */
+ log_warn(LD_CONFIG, "'%s' is not a k=v item.", s);
+ return -1;
+ }
+ } SMARTLIST_FOREACH_END(s);
+
+ socks_string = pt_stringify_socks_args(args);
+ if (!socks_string)
+ return -1;
+
+ socks_string_len = strlen(socks_string);
+ tor_free(socks_string);
+
+ if (socks_string_len > MAX_SOCKS5_AUTH_SIZE_TOTAL) {
+ log_warn(LD_CONFIG, "SOCKS arguments can't be more than %u bytes (%lu).",
+ MAX_SOCKS5_AUTH_SIZE_TOTAL,
+ (unsigned long) socks_string_len);
+ return -1;
+ }
+
+ return 0;
+}
+
+/** Deallocate a bridge_line_t structure. */
+/* private */ void
+bridge_line_free_(bridge_line_t *bridge_line)
+{
+ if (!bridge_line)
+ return;
+
+ if (bridge_line->socks_args) {
+ SMARTLIST_FOREACH(bridge_line->socks_args, char*, s, tor_free(s));
+ smartlist_free(bridge_line->socks_args);
+ }
+ tor_free(bridge_line->transport_name);
+ tor_free(bridge_line);
+}
+
+/** Parse the contents of a string, <b>line</b>, containing a Bridge line,
+ * into a bridge_line_t.
+ *
+ * Validates that the IP:PORT, fingerprint, and SOCKS arguments (given to the
+ * Pluggable Transport, if a one was specified) are well-formed.
+ *
+ * Returns NULL If the Bridge line could not be validated, and returns a
+ * bridge_line_t containing the parsed information otherwise.
+ *
+ * Bridge line format:
+ * Bridge [transport] IP:PORT [id-fingerprint] [k=v] [k=v] ...
+ */
+/* private */ bridge_line_t *
+parse_bridge_line(const char *line)
+{
+ smartlist_t *items = NULL;
+ char *addrport=NULL, *fingerprint=NULL;
+ char *field=NULL;
+ bridge_line_t *bridge_line = tor_malloc_zero(sizeof(bridge_line_t));
+
+ items = smartlist_new();
+ smartlist_split_string(items, line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ if (smartlist_len(items) < 1) {
+ log_warn(LD_CONFIG, "Too few arguments to Bridge line.");
+ goto err;
+ }
+
+ /* first field is either a transport name or addrport */
+ field = smartlist_get(items, 0);
+ smartlist_del_keeporder(items, 0);
+
+ if (string_is_C_identifier(field)) {
+ /* It's a transport name. */
+ bridge_line->transport_name = field;
+ if (smartlist_len(items) < 1) {
+ log_warn(LD_CONFIG, "Too few items to Bridge line.");
+ goto err;
+ }
+ addrport = smartlist_get(items, 0); /* Next field is addrport then. */
+ smartlist_del_keeporder(items, 0);
+ } else {
+ addrport = field;
+ }
+
+ if (tor_addr_port_parse(LOG_INFO, addrport,
+ &bridge_line->addr, &bridge_line->port, 443)<0) {
+ log_warn(LD_CONFIG, "Error parsing Bridge address '%s'", addrport);
+ goto err;
+ }
+
+ /* If transports are enabled, next field could be a fingerprint or a
+ socks argument. If transports are disabled, next field must be
+ a fingerprint. */
+ if (smartlist_len(items)) {
+ if (bridge_line->transport_name) { /* transports enabled: */
+ field = smartlist_get(items, 0);
+ smartlist_del_keeporder(items, 0);
+
+ /* If it's a key=value pair, then it's a SOCKS argument for the
+ transport proxy... */
+ if (string_is_key_value(LOG_DEBUG, field)) {
+ bridge_line->socks_args = smartlist_new();
+ smartlist_add(bridge_line->socks_args, field);
+ } else { /* ...otherwise, it's the bridge fingerprint. */
+ fingerprint = field;
+ }
+
+ } else { /* transports disabled: */
+ fingerprint = smartlist_join_strings(items, "", 0, NULL);
+ }
+ }
+
+ /* Handle fingerprint, if it was provided. */
+ if (fingerprint) {
+ if (strlen(fingerprint) != HEX_DIGEST_LEN) {
+ log_warn(LD_CONFIG, "Key digest for Bridge is wrong length.");
+ goto err;
+ }
+ if (base16_decode(bridge_line->digest, DIGEST_LEN,
+ fingerprint, HEX_DIGEST_LEN) != DIGEST_LEN) {
+ log_warn(LD_CONFIG, "Unable to decode Bridge key digest.");
+ goto err;
+ }
+ }
+
+ /* If we are using transports, any remaining items in the smartlist
+ should be k=v values. */
+ if (bridge_line->transport_name && smartlist_len(items)) {
+ if (!bridge_line->socks_args)
+ bridge_line->socks_args = smartlist_new();
+
+ /* append remaining items of 'items' to 'socks_args' */
+ smartlist_add_all(bridge_line->socks_args, items);
+ smartlist_clear(items);
+
+ tor_assert(smartlist_len(bridge_line->socks_args) > 0);
+ }
+
+ if (bridge_line->socks_args) {
+ if (validate_transport_socks_arguments(bridge_line->socks_args) < 0)
+ goto err;
+ }
+
+ goto done;
+
+ err:
+ bridge_line_free(bridge_line);
+ bridge_line = NULL;
+
+ done:
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+ tor_free(addrport);
+ tor_free(fingerprint);
+
+ return bridge_line;
+}
+
+/** Read the contents of a ClientTransportPlugin or ServerTransportPlugin
+ * line from <b>line</b>, depending on the value of <b>server</b>. Return 0
+ * if the line is well-formed, and -1 if it isn't.
+ *
+ * If <b>validate_only</b> is 0, the line is well-formed, and the transport is
+ * needed by some bridge:
+ * - If it's an external proxy line, add the transport described in the line to
+ * our internal transport list.
+ * - If it's a managed proxy line, launch the managed proxy.
+ */
+
+STATIC int
+parse_transport_line(const or_options_t *options,
+ const char *line, int validate_only,
+ int server)
+{
+
+ smartlist_t *items = NULL;
+ int r;
+ const char *transports = NULL;
+ smartlist_t *transport_list = NULL;
+ char *type = NULL;
+ char *addrport = NULL;
+ tor_addr_t addr;
+ uint16_t port = 0;
+ int socks_ver = PROXY_NONE;
+
+ /* managed proxy options */
+ int is_managed = 0;
+ char **proxy_argv = NULL;
+ char **tmp = NULL;
+ int proxy_argc, i;
+ int is_useless_proxy = 1;
+
+ int line_length;
+
+ /* Split the line into space-separated tokens */
+ items = smartlist_new();
+ smartlist_split_string(items, line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ line_length = smartlist_len(items);
+
+ if (line_length < 3) {
+ log_warn(LD_CONFIG,
+ "Too few arguments on %sTransportPlugin line.",
+ server ? "Server" : "Client");
+ goto err;
+ }
+
+ /* Get the first line element, split it to commas into
+ transport_list (in case it's multiple transports) and validate
+ the transport names. */
+ transports = smartlist_get(items, 0);
+ transport_list = smartlist_new();
+ smartlist_split_string(transport_list, transports, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ SMARTLIST_FOREACH_BEGIN(transport_list, const char *, transport_name) {
+ /* validate transport names */
+ if (!string_is_C_identifier(transport_name)) {
+ log_warn(LD_CONFIG, "Transport name is not a C identifier (%s).",
+ transport_name);
+ goto err;
+ }
+
+ /* see if we actually need the transports provided by this proxy */
+ if (!validate_only && transport_is_needed(transport_name))
+ is_useless_proxy = 0;
+ } SMARTLIST_FOREACH_END(transport_name);
+
+ type = smartlist_get(items, 1);
+ if (!strcmp(type, "exec")) {
+ is_managed = 1;
+ } else if (server && !strcmp(type, "proxy")) {
+ /* 'proxy' syntax only with ServerTransportPlugin */
+ is_managed = 0;
+ } else if (!server && !strcmp(type, "socks4")) {
+ /* 'socks4' syntax only with ClientTransportPlugin */
+ is_managed = 0;
+ socks_ver = PROXY_SOCKS4;
+ } else if (!server && !strcmp(type, "socks5")) {
+ /* 'socks5' syntax only with ClientTransportPlugin */
+ is_managed = 0;
+ socks_ver = PROXY_SOCKS5;
+ } else {
+ log_warn(LD_CONFIG,
+ "Strange %sTransportPlugin type '%s'",
+ server ? "Server" : "Client", type);
+ goto err;
+ }
+
+ if (is_managed && options->Sandbox) {
+ log_warn(LD_CONFIG,
+ "Managed proxies are not compatible with Sandbox mode."
+ "(%sTransportPlugin line was %s)",
+ server ? "Server" : "Client", escaped(line));
+ goto err;
+ }
+
+ if (is_managed && options->NoExec) {
+ log_warn(LD_CONFIG,
+ "Managed proxies are not compatible with NoExec mode; ignoring."
+ "(%sTransportPlugin line was %s)",
+ server ? "Server" : "Client", escaped(line));
+ r = 0;
+ goto done;
+ }
+
+ if (is_managed) {
+ /* managed */
+
+ if (!server && !validate_only && is_useless_proxy) {
+ log_info(LD_GENERAL,
+ "Pluggable transport proxy (%s) does not provide "
+ "any needed transports and will not be launched.",
+ line);
+ }
+
+ /*
+ * If we are not just validating, use the rest of the line as the
+ * argv of the proxy to be launched. Also, make sure that we are
+ * only launching proxies that contribute useful transports.
+ */
+
+ if (!validate_only && (server || !is_useless_proxy)) {
+ proxy_argc = line_length - 2;
+ tor_assert(proxy_argc > 0);
+ proxy_argv = tor_calloc((proxy_argc + 1), sizeof(char *));
+ tmp = proxy_argv;
+
+ for (i = 0; i < proxy_argc; i++) {
+ /* store arguments */
+ *tmp++ = smartlist_get(items, 2);
+ smartlist_del_keeporder(items, 2);
+ }
+ *tmp = NULL; /* terminated with NULL, just like execve() likes it */
+
+ /* kickstart the thing */
+ if (server) {
+ pt_kickstart_server_proxy(transport_list, proxy_argv);
+ } else {
+ pt_kickstart_client_proxy(transport_list, proxy_argv);
+ }
+ }
+ } else {
+ /* external */
+
+ /* ClientTransportPlugins connecting through a proxy is managed only. */
+ if (!server && (options->Socks4Proxy || options->Socks5Proxy ||
+ options->HTTPSProxy)) {
+ log_warn(LD_CONFIG, "You have configured an external proxy with another "
+ "proxy type. (Socks4Proxy|Socks5Proxy|HTTPSProxy)");
+ goto err;
+ }
+
+ if (smartlist_len(transport_list) != 1) {
+ log_warn(LD_CONFIG,
+ "You can't have an external proxy with more than "
+ "one transport.");
+ goto err;
+ }
+
+ addrport = smartlist_get(items, 2);
+
+ if (tor_addr_port_lookup(addrport, &addr, &port) < 0) {
+ log_warn(LD_CONFIG,
+ "Error parsing transport address '%s'", addrport);
+ goto err;
+ }
+
+ if (!port) {
+ log_warn(LD_CONFIG,
+ "Transport address '%s' has no port.", addrport);
+ goto err;
+ }
+
+ if (!validate_only) {
+ log_info(LD_DIR, "%s '%s' at %s.",
+ server ? "Server transport" : "Transport",
+ transports, fmt_addrport(&addr, port));
+
+ if (!server) {
+ transport_add_from_config(&addr, port,
+ smartlist_get(transport_list, 0),
+ socks_ver);
+ }
+ }
+ }
+
+ r = 0;
+ goto done;
+
+ err:
+ r = -1;
+
+ done:
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+ if (transport_list) {
+ SMARTLIST_FOREACH(transport_list, char*, s, tor_free(s));
+ smartlist_free(transport_list);
+ }
+
+ return r;
+}
+
+/** Given a ServerTransportListenAddr <b>line</b>, return its
+ * <address:port> string. Return NULL if the line was not
+ * well-formed.
+ *
+ * If <b>transport</b> is set, return NULL if the line is not
+ * referring to <b>transport</b>.
+ *
+ * The returned string is allocated on the heap and it's the
+ * responsibility of the caller to free it. */
+static char *
+get_bindaddr_from_transport_listen_line(const char *line,const char *transport)
+{
+ smartlist_t *items = NULL;
+ const char *parsed_transport = NULL;
+ char *addrport = NULL;
+ tor_addr_t addr;
+ uint16_t port = 0;
+
+ items = smartlist_new();
+ smartlist_split_string(items, line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+
+ if (smartlist_len(items) < 2) {
+ log_warn(LD_CONFIG,"Too few arguments on ServerTransportListenAddr line.");
+ goto err;
+ }
+
+ parsed_transport = smartlist_get(items, 0);
+ addrport = tor_strdup(smartlist_get(items, 1));
+
+ /* If 'transport' is given, check if it matches the one on the line */
+ if (transport && strcmp(transport, parsed_transport))
+ goto err;
+
+ /* Validate addrport */
+ if (tor_addr_port_parse(LOG_WARN, addrport, &addr, &port, -1)<0) {
+ log_warn(LD_CONFIG, "Error parsing ServerTransportListenAddr "
+ "address '%s'", addrport);
+ goto err;
+ }
+
+ goto done;
+
+ err:
+ tor_free(addrport);
+ addrport = NULL;
+
+ done:
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+
+ return addrport;
+}
+
+/** Given a ServerTransportOptions <b>line</b>, return a smartlist
+ * with the options. Return NULL if the line was not well-formed.
+ *
+ * If <b>transport</b> is set, return NULL if the line is not
+ * referring to <b>transport</b>.
+ *
+ * The returned smartlist and its strings are allocated on the heap
+ * and it's the responsibility of the caller to free it. */
+smartlist_t *
+get_options_from_transport_options_line(const char *line,const char *transport)
+{
+ smartlist_t *items = smartlist_new();
+ smartlist_t *options = smartlist_new();
+ const char *parsed_transport = NULL;
+
+ smartlist_split_string(items, line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+
+ if (smartlist_len(items) < 2) {
+ log_warn(LD_CONFIG,"Too few arguments on ServerTransportOptions line.");
+ goto err;
+ }
+
+ parsed_transport = smartlist_get(items, 0);
+ /* If 'transport' is given, check if it matches the one on the line */
+ if (transport && strcmp(transport, parsed_transport))
+ goto err;
+
+ SMARTLIST_FOREACH_BEGIN(items, const char *, option) {
+ if (option_sl_idx == 0) /* skip the transport field (first field)*/
+ continue;
+
+ /* validate that it's a k=v value */
+ if (!string_is_key_value(LOG_WARN, option)) {
+ log_warn(LD_CONFIG, "%s is not a k=v value.", escaped(option));
+ goto err;
+ }
+
+ /* add it to the options smartlist */
+ smartlist_add_strdup(options, option);
+ log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option));
+ } SMARTLIST_FOREACH_END(option);
+
+ goto done;
+
+ err:
+ SMARTLIST_FOREACH(options, char*, s, tor_free(s));
+ smartlist_free(options);
+ options = NULL;
+
+ done:
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+
+ return options;
+}
+
+/** Given the name of a pluggable transport in <b>transport</b>, check
+ * the configuration file to see if the user has explicitly asked for
+ * it to listen on a specific port. Return a <address:port> string if
+ * so, otherwise NULL. */
+char *
+get_transport_bindaddr_from_config(const char *transport)
+{
+ config_line_t *cl;
+ const or_options_t *options = get_options();
+
+ for (cl = options->ServerTransportListenAddr; cl; cl = cl->next) {
+ char *bindaddr =
+ get_bindaddr_from_transport_listen_line(cl->value, transport);
+ if (bindaddr)
+ return bindaddr;
+ }
+
+ return NULL;
+}
+
+/** Given the name of a pluggable transport in <b>transport</b>, check
+ * the configuration file to see if the user has asked us to pass any
+ * parameters to the pluggable transport. Return a smartlist
+ * containing the parameters, otherwise NULL. */
+smartlist_t *
+get_options_for_server_transport(const char *transport)
+{
+ config_line_t *cl;
+ const or_options_t *options = get_options();
+
+ for (cl = options->ServerTransportOptions; cl; cl = cl->next) {
+ smartlist_t *options_sl =
+ get_options_from_transport_options_line(cl->value, transport);
+ if (options_sl)
+ return options_sl;
+ }
+
+ return NULL;
+}
+
+/** Read the contents of a DirAuthority line from <b>line</b>. If
+ * <b>validate_only</b> is 0, and the line is well-formed, and it
+ * shares any bits with <b>required_type</b> or <b>required_type</b>
+ * is NO_DIRINFO (zero), then add the dirserver described in the line
+ * (minus whatever bits it's missing) as a valid authority.
+ * Return 0 on success or filtering out by type,
+ * or -1 if the line isn't well-formed or if we can't add it. */
+STATIC int
+parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
+ int validate_only)
+{
+ smartlist_t *items = NULL;
+ int r;
+ char *addrport=NULL, *address=NULL, *nickname=NULL, *fingerprint=NULL;
+ tor_addr_port_t ipv6_addrport, *ipv6_addrport_ptr = NULL;
+ uint16_t dir_port = 0, or_port = 0;
+ char digest[DIGEST_LEN];
+ char v3_digest[DIGEST_LEN];
+ dirinfo_type_t type = 0;
+ double weight = 1.0;
+
+ memset(v3_digest, 0, sizeof(v3_digest));
+
+ items = smartlist_new();
+ smartlist_split_string(items, line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ if (smartlist_len(items) < 1) {
+ log_warn(LD_CONFIG, "No arguments on DirAuthority line.");
+ goto err;
+ }
+
+ if (is_legal_nickname(smartlist_get(items, 0))) {
+ nickname = smartlist_get(items, 0);
+ smartlist_del_keeporder(items, 0);
+ }
+
+ while (smartlist_len(items)) {
+ char *flag = smartlist_get(items, 0);
+ if (TOR_ISDIGIT(flag[0]))
+ break;
+ if (!strcasecmp(flag, "hs") ||
+ !strcasecmp(flag, "no-hs")) {
+ log_warn(LD_CONFIG, "The DirAuthority options 'hs' and 'no-hs' are "
+ "obsolete; you don't need them any more.");
+ } else if (!strcasecmp(flag, "bridge")) {
+ type |= BRIDGE_DIRINFO;
+ } else if (!strcasecmp(flag, "no-v2")) {
+ /* obsolete, but may still be contained in DirAuthority lines generated
+ by various tools */;
+ } else if (!strcasecmpstart(flag, "orport=")) {
+ int ok;
+ char *portstring = flag + strlen("orport=");
+ or_port = (uint16_t) tor_parse_long(portstring, 10, 1, 65535, &ok, NULL);
+ if (!ok)
+ log_warn(LD_CONFIG, "Invalid orport '%s' on DirAuthority line.",
+ portstring);
+ } else if (!strcmpstart(flag, "weight=")) {
+ int ok;
+ const char *wstring = flag + strlen("weight=");
+ weight = tor_parse_double(wstring, 0, (double)UINT64_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_CONFIG, "Invalid weight '%s' on DirAuthority line.",flag);
+ weight=1.0;
+ }
+ } else if (!strcasecmpstart(flag, "v3ident=")) {
+ char *idstr = flag + strlen("v3ident=");
+ if (strlen(idstr) != HEX_DIGEST_LEN ||
+ base16_decode(v3_digest, DIGEST_LEN,
+ idstr, HEX_DIGEST_LEN) != DIGEST_LEN) {
+ log_warn(LD_CONFIG, "Bad v3 identity digest '%s' on DirAuthority line",
+ flag);
+ } else {
+ type |= V3_DIRINFO|EXTRAINFO_DIRINFO|MICRODESC_DIRINFO;
+ }
+ } else if (!strcasecmpstart(flag, "ipv6=")) {
+ if (ipv6_addrport_ptr) {
+ log_warn(LD_CONFIG, "Redundant ipv6 addr/port on DirAuthority line");
+ } else {
+ if (tor_addr_port_parse(LOG_WARN, flag+strlen("ipv6="),
+ &ipv6_addrport.addr, &ipv6_addrport.port,
+ -1) < 0
+ || tor_addr_family(&ipv6_addrport.addr) != AF_INET6) {
+ log_warn(LD_CONFIG, "Bad ipv6 addr/port %s on DirAuthority line",
+ escaped(flag));
+ goto err;
+ }
+ ipv6_addrport_ptr = &ipv6_addrport;
+ }
+ } else {
+ log_warn(LD_CONFIG, "Unrecognized flag '%s' on DirAuthority line",
+ flag);
+ }
+ tor_free(flag);
+ smartlist_del_keeporder(items, 0);
+ }
+
+ if (smartlist_len(items) < 2) {
+ log_warn(LD_CONFIG, "Too few arguments to DirAuthority line.");
+ goto err;
+ }
+ addrport = smartlist_get(items, 0);
+ smartlist_del_keeporder(items, 0);
+
+ if (tor_addr_port_split(LOG_WARN, addrport, &address, &dir_port) < 0) {
+ log_warn(LD_CONFIG, "Error parsing DirAuthority address '%s'.", addrport);
+ goto err;
+ }
+
+ if (!string_is_valid_ipv4_address(address)) {
+ log_warn(LD_CONFIG, "Error parsing DirAuthority address '%s' "
+ "(invalid IPv4 address)", address);
+ goto err;
+ }
+
+ if (!dir_port) {
+ log_warn(LD_CONFIG, "Missing port in DirAuthority address '%s'",addrport);
+ goto err;
+ }
+
+ fingerprint = smartlist_join_strings(items, "", 0, NULL);
+ if (strlen(fingerprint) != HEX_DIGEST_LEN) {
+ log_warn(LD_CONFIG, "Key digest '%s' for DirAuthority is wrong length %d.",
+ fingerprint, (int)strlen(fingerprint));
+ goto err;
+ }
+ if (base16_decode(digest, DIGEST_LEN,
+ fingerprint, HEX_DIGEST_LEN) != DIGEST_LEN) {
+ log_warn(LD_CONFIG, "Unable to decode DirAuthority key digest.");
+ goto err;
+ }
+
+ if (!validate_only && (!required_type || required_type & type)) {
+ dir_server_t *ds;
+ if (required_type)
+ type &= required_type; /* pare down what we think of them as an
+ * authority for. */
+ log_debug(LD_DIR, "Trusted %d dirserver at %s:%d (%s)", (int)type,
+ address, (int)dir_port, (char*)smartlist_get(items,0));
+ if (!(ds = trusted_dir_server_new(nickname, address, dir_port, or_port,
+ ipv6_addrport_ptr,
+ digest, v3_digest, type, weight)))
+ goto err;
+ dir_server_add(ds);
+ }
+
+ r = 0;
+ goto done;
+
+ err:
+ r = -1;
+
+ done:
+ SMARTLIST_FOREACH(items, char*, s, tor_free(s));
+ smartlist_free(items);
+ tor_free(addrport);
+ tor_free(address);
+ tor_free(nickname);
+ tor_free(fingerprint);
+ return r;
+}
+
+/** Read the contents of a FallbackDir line from <b>line</b>. If
+ * <b>validate_only</b> is 0, and the line is well-formed, then add the
+ * dirserver described in the line as a fallback directory. Return 0 on
+ * success, or -1 if the line isn't well-formed or if we can't add it. */
+int
+parse_dir_fallback_line(const char *line,
+ int validate_only)
+{
+ int r = -1;
+ smartlist_t *items = smartlist_new(), *positional = smartlist_new();
+ int orport = -1;
+ uint16_t dirport;
+ tor_addr_t addr;
+ int ok;
+ char id[DIGEST_LEN];
+ char *address=NULL;
+ tor_addr_port_t ipv6_addrport, *ipv6_addrport_ptr = NULL;
+ double weight=1.0;
+
+ memset(id, 0, sizeof(id));
+ smartlist_split_string(items, line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, -1);
+ SMARTLIST_FOREACH_BEGIN(items, const char *, cp) {
+ const char *eq = strchr(cp, '=');
+ ok = 1;
+ if (! eq) {
+ smartlist_add(positional, (char*)cp);
+ continue;
+ }
+ if (!strcmpstart(cp, "orport=")) {
+ orport = (int)tor_parse_long(cp+strlen("orport="), 10,
+ 1, 65535, &ok, NULL);
+ } else if (!strcmpstart(cp, "id=")) {
+ ok = base16_decode(id, DIGEST_LEN, cp+strlen("id="),
+ strlen(cp)-strlen("id=")) == DIGEST_LEN;
+ } else if (!strcasecmpstart(cp, "ipv6=")) {
+ if (ipv6_addrport_ptr) {
+ log_warn(LD_CONFIG, "Redundant ipv6 addr/port on FallbackDir line");
+ } else {
+ if (tor_addr_port_parse(LOG_WARN, cp+strlen("ipv6="),
+ &ipv6_addrport.addr, &ipv6_addrport.port,
+ -1) < 0
+ || tor_addr_family(&ipv6_addrport.addr) != AF_INET6) {
+ log_warn(LD_CONFIG, "Bad ipv6 addr/port %s on FallbackDir line",
+ escaped(cp));
+ goto end;
+ }
+ ipv6_addrport_ptr = &ipv6_addrport;
+ }
+ } else if (!strcmpstart(cp, "weight=")) {
+ int num_ok;
+ const char *wstring = cp + strlen("weight=");
+ weight = tor_parse_double(wstring, 0, (double)UINT64_MAX, &num_ok, NULL);
+ if (!num_ok) {
+ log_warn(LD_CONFIG, "Invalid weight '%s' on FallbackDir line.", cp);
+ weight=1.0;
+ }
+ }
+
+ if (!ok) {
+ log_warn(LD_CONFIG, "Bad FallbackDir option %s", escaped(cp));
+ goto end;
+ }
+ } SMARTLIST_FOREACH_END(cp);
+
+ if (smartlist_len(positional) != 1) {
+ log_warn(LD_CONFIG, "Couldn't parse FallbackDir line %s", escaped(line));
+ goto end;
+ }
+
+ if (tor_digest_is_zero(id)) {
+ log_warn(LD_CONFIG, "Missing identity on FallbackDir line");
+ goto end;
+ }
+
+ if (orport <= 0) {
+ log_warn(LD_CONFIG, "Missing orport on FallbackDir line");
+ goto end;
+ }
+
+ if (tor_addr_port_split(LOG_INFO, smartlist_get(positional, 0),
+ &address, &dirport) < 0 ||
+ tor_addr_parse(&addr, address)<0) {
+ log_warn(LD_CONFIG, "Couldn't parse address:port %s on FallbackDir line",
+ (const char*)smartlist_get(positional, 0));
+ goto end;
+ }
+
+ if (!validate_only) {
+ dir_server_t *ds;
+ ds = fallback_dir_server_new(&addr, dirport, orport, ipv6_addrport_ptr,
+ id, weight);
+ if (!ds) {
+ log_warn(LD_CONFIG, "Couldn't create FallbackDir %s", escaped(line));
+ goto end;
+ }
+ dir_server_add(ds);
+ }
+
+ r = 0;
+
+ end:
+ SMARTLIST_FOREACH(items, char *, cp, tor_free(cp));
+ smartlist_free(items);
+ smartlist_free(positional);
+ tor_free(address);
+ return r;
+}
+
+/** Allocate and return a new port_cfg_t with reasonable defaults. */
+STATIC port_cfg_t *
+port_cfg_new(size_t namelen)
+{
+ tor_assert(namelen <= SIZE_T_CEILING - sizeof(port_cfg_t) - 1);
+ port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t) + namelen + 1);
+ cfg->entry_cfg.ipv4_traffic = 1;
+ cfg->entry_cfg.ipv6_traffic = 1;
+ cfg->entry_cfg.dns_request = 1;
+ cfg->entry_cfg.onion_traffic = 1;
+ cfg->entry_cfg.prefer_ipv6_virtaddr = 1;
+ return cfg;
+}
+
+/** Free all storage held in <b>port</b> */
+STATIC void
+port_cfg_free_(port_cfg_t *port)
+{
+ tor_free(port);
+}
+
+/** Warn for every port in <b>ports</b> of type <b>listener_type</b> that is
+ * on a publicly routable address. */
+static void
+warn_nonlocal_client_ports(const smartlist_t *ports,
+ const char *portname,
+ const int listener_type)
+{
+ SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) {
+ if (port->type != listener_type)
+ continue;
+ if (port->is_unix_addr) {
+ /* Unix sockets aren't accessible over a network. */
+ } else if (!tor_addr_is_internal(&port->addr, 1)) {
+ log_warn(LD_CONFIG, "You specified a public address '%s' for %sPort. "
+ "Other people on the Internet might find your computer and "
+ "use it as an open proxy. Please don't allow this unless you "
+ "have a good reason.",
+ fmt_addrport(&port->addr, port->port), portname);
+ } else if (!tor_addr_is_loopback(&port->addr)) {
+ log_notice(LD_CONFIG, "You configured a non-loopback address '%s' "
+ "for %sPort. This allows everybody on your local network to "
+ "use your machine as a proxy. Make sure this is what you "
+ "wanted.",
+ fmt_addrport(&port->addr, port->port), portname);
+ }
+ } SMARTLIST_FOREACH_END(port);
+}
+
+/** Warn for every Extended ORPort port in <b>ports</b> that is on a
+ * publicly routable address. */
+static void
+warn_nonlocal_ext_orports(const smartlist_t *ports, const char *portname)
+{
+ SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) {
+ if (port->type != CONN_TYPE_EXT_OR_LISTENER)
+ continue;
+ if (port->is_unix_addr)
+ continue;
+ /* XXX maybe warn even if address is RFC1918? */
+ if (!tor_addr_is_internal(&port->addr, 1)) {
+ log_warn(LD_CONFIG, "You specified a public address '%s' for %sPort. "
+ "This is not advised; this address is supposed to only be "
+ "exposed on localhost so that your pluggable transport "
+ "proxies can connect to it.",
+ fmt_addrport(&port->addr, port->port), portname);
+ }
+ } SMARTLIST_FOREACH_END(port);
+}
+
+/** Given a list of port_cfg_t in <b>ports</b>, warn if any controller port
+ * there is listening on any non-loopback address. If <b>forbid_nonlocal</b>
+ * is true, then emit a stronger warning and remove the port from the list.
+ */
+static void
+warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid_nonlocal)
+{
+ int warned = 0;
+ SMARTLIST_FOREACH_BEGIN(ports, port_cfg_t *, port) {
+ if (port->type != CONN_TYPE_CONTROL_LISTENER)
+ continue;
+ if (port->is_unix_addr)
+ continue;
+ if (!tor_addr_is_loopback(&port->addr)) {
+ if (forbid_nonlocal) {
+ if (!warned)
+ log_warn(LD_CONFIG,
+ "You have a ControlPort set to accept "
+ "unauthenticated connections from a non-local address. "
+ "This means that programs not running on your computer "
+ "can reconfigure your Tor, without even having to guess a "
+ "password. That's so bad that I'm closing your ControlPort "
+ "for you. If you need to control your Tor remotely, try "
+ "enabling authentication and using a tool like stunnel or "
+ "ssh to encrypt remote access.");
+ warned = 1;
+ port_cfg_free(port);
+ SMARTLIST_DEL_CURRENT(ports, port);
+ } else {
+ log_warn(LD_CONFIG, "You have a ControlPort set to accept "
+ "connections from a non-local address. This means that "
+ "programs not running on your computer can reconfigure your "
+ "Tor. That's pretty bad, since the controller "
+ "protocol isn't encrypted! Maybe you should just listen on "
+ "127.0.0.1 and use a tool like stunnel or ssh to encrypt "
+ "remote connections to your control port.");
+ return; /* No point in checking the rest */
+ }
+ }
+ } SMARTLIST_FOREACH_END(port);
+}
+
+/**
+ * Take a string (<b>line</b>) that begins with either an address:port, a
+ * port, or an AF_UNIX address, optionally quoted, prefixed with
+ * "unix:". Parse that line, and on success, set <b>addrport_out</b> to a new
+ * string containing the beginning portion (without prefix). Iff there was a
+ * unix: prefix, set <b>is_unix_out</b> to true. On success, also set
+ * <b>rest_out</b> to point to the part of the line after the address portion.
+ *
+ * Return 0 on success, -1 on failure.
+ */
+int
+port_cfg_line_extract_addrport(const char *line,
+ char **addrport_out,
+ int *is_unix_out,
+ const char **rest_out)
+{
+ tor_assert(line);
+ tor_assert(addrport_out);
+ tor_assert(is_unix_out);
+ tor_assert(rest_out);
+
+ line = eat_whitespace(line);
+
+ if (!strcmpstart(line, unix_q_socket_prefix)) {
+ // It starts with unix:"
+ size_t sz;
+ *is_unix_out = 1;
+ *addrport_out = NULL;
+ line += strlen(unix_socket_prefix); /*No q: Keep the quote */
+ *rest_out = unescape_string(line, addrport_out, &sz);
+ if (!*rest_out || (*addrport_out && sz != strlen(*addrport_out))) {
+ tor_free(*addrport_out);
+ return -1;
+ }
+ *rest_out = eat_whitespace(*rest_out);
+ return 0;
+ } else {
+ // Is there a unix: prefix?
+ if (!strcmpstart(line, unix_socket_prefix)) {
+ line += strlen(unix_socket_prefix);
+ *is_unix_out = 1;
+ } else {
+ *is_unix_out = 0;
+ }
+
+ const char *end = find_whitespace(line);
+ if (BUG(!end)) {
+ end = strchr(line, '\0'); // LCOV_EXCL_LINE -- this can't be NULL
+ }
+ tor_assert(end && end >= line);
+ *addrport_out = tor_strndup(line, end - line);
+ *rest_out = eat_whitespace(end);
+ return 0;
+ }
+}
+
+static void
+warn_client_dns_cache(const char *option, int disabling)
+{
+ if (disabling)
+ return;
+
+ warn_deprecated_option(option,
+ "Client-side DNS cacheing enables a wide variety of route-"
+ "capture attacks. If a single bad exit node lies to you about "
+ "an IP address, cacheing that address would make you visit "
+ "an address of the attacker's choice every time you connected "
+ "to your destination.");
+}
+
+/**
+ * Validate the configured bridge distribution method from a BridgeDistribution
+ * config line.
+ *
+ * The input <b>bd</b>, is a string taken from the BridgeDistribution config
+ * line (if present). If the option wasn't set, return 0 immediately. The
+ * BridgeDistribution option is then validated. Currently valid, recognised
+ * options are:
+ *
+ * - "none"
+ * - "any"
+ * - "https"
+ * - "email"
+ * - "moat"
+ * - "hyphae"
+ *
+ * If the option string is unrecognised, a warning will be logged and 0 is
+ * returned. If the option string contains an invalid character, -1 is
+ * returned.
+ **/
+STATIC int
+check_bridge_distribution_setting(const char *bd)
+{
+ if (bd == NULL)
+ return 0;
+
+ const char *RECOGNIZED[] = {
+ "none", "any", "https", "email", "moat", "hyphae"
+ };
+ unsigned i;
+ for (i = 0; i < ARRAY_LENGTH(RECOGNIZED); ++i) {
+ if (!strcmp(bd, RECOGNIZED[i]))
+ return 0;
+ }
+
+ const char *cp = bd;
+ // Method = (KeywordChar | "_") +
+ while (TOR_ISALNUM(*cp) || *cp == '-' || *cp == '_')
+ ++cp;
+
+ if (*cp == 0) {
+ log_warn(LD_CONFIG, "Unrecognized BridgeDistribution value %s. I'll "
+ "assume you know what you are doing...", escaped(bd));
+ return 0; // we reached the end of the string; all is well
+ } else {
+ return -1; // we found a bad character in the string.
+ }
+}
+
+/**
+ * Parse port configuration for a single port type.
+ *
+ * Read entries of the "FooPort" type from the list <b>ports</b>. Syntax is
+ * that FooPort can have any number of entries of the format
+ * "[Address:][Port] IsolationOptions".
+ *
+ * In log messages, describe the port type as <b>portname</b>.
+ *
+ * If no address is specified, default to <b>defaultaddr</b>. If no
+ * FooPort is given, default to defaultport (if 0, there is no default).
+ *
+ * If CL_PORT_NO_STREAM_OPTIONS is set in <b>flags</b>, do not allow stream
+ * isolation options in the FooPort entries.
+ *
+ * If CL_PORT_WARN_NONLOCAL is set in <b>flags</b>, warn if any of the
+ * ports are not on a local address. If CL_PORT_FORBID_NONLOCAL is set,
+ * this is a control port with no password set: don't even allow it.
+ *
+ * If CL_PORT_SERVER_OPTIONS is set in <b>flags</b>, do not allow stream
+ * isolation options in the FooPort entries; instead allow the
+ * server-port option set.
+ *
+ * If CL_PORT_TAKES_HOSTNAMES is set in <b>flags</b>, allow the options
+ * {No,}IPv{4,6}Traffic.
+ *
+ * On success, if <b>out</b> is given, add a new port_cfg_t entry to
+ * <b>out</b> for every port that the client should listen on. Return 0
+ * on success, -1 on failure.
+ */
+STATIC int
+parse_port_config(smartlist_t *out,
+ const config_line_t *ports,
+ const char *portname,
+ int listener_type,
+ const char *defaultaddr,
+ int defaultport,
+ const unsigned flags)
+{
+ smartlist_t *elts;
+ int retval = -1;
+ const unsigned is_control = (listener_type == CONN_TYPE_CONTROL_LISTENER);
+ const unsigned is_ext_orport = (listener_type == CONN_TYPE_EXT_OR_LISTENER);
+ const unsigned allow_no_stream_options = flags & CL_PORT_NO_STREAM_OPTIONS;
+ const unsigned use_server_options = flags & CL_PORT_SERVER_OPTIONS;
+ const unsigned warn_nonlocal = flags & CL_PORT_WARN_NONLOCAL;
+ const unsigned forbid_nonlocal = flags & CL_PORT_FORBID_NONLOCAL;
+ const unsigned default_to_group_writable =
+ flags & CL_PORT_DFLT_GROUP_WRITABLE;
+ const unsigned takes_hostnames = flags & CL_PORT_TAKES_HOSTNAMES;
+ const unsigned is_unix_socket = flags & CL_PORT_IS_UNIXSOCKET;
+ int got_zero_port=0, got_nonzero_port=0;
+ char *unix_socket_path = NULL;
+
+ /* If there's no FooPort, then maybe make a default one. */
+ if (! ports) {
+ if (defaultport && defaultaddr && out) {
+ port_cfg_t *cfg = port_cfg_new(is_unix_socket ? strlen(defaultaddr) : 0);
+ cfg->type = listener_type;
+ if (is_unix_socket) {
+ tor_addr_make_unspec(&cfg->addr);
+ memcpy(cfg->unix_addr, defaultaddr, strlen(defaultaddr) + 1);
+ cfg->is_unix_addr = 1;
+ } else {
+ cfg->port = defaultport;
+ tor_addr_parse(&cfg->addr, defaultaddr);
+ }
+ cfg->entry_cfg.session_group = SESSION_GROUP_UNSET;
+ cfg->entry_cfg.isolation_flags = ISO_DEFAULT;
+ smartlist_add(out, cfg);
+ }
+ return 0;
+ }
+
+ /* At last we can actually parse the FooPort lines. The syntax is:
+ * [Addr:](Port|auto) [Options].*/
+ elts = smartlist_new();
+ char *addrport = NULL;
+
+ for (; ports; ports = ports->next) {
+ tor_addr_t addr;
+ int port;
+ int sessiongroup = SESSION_GROUP_UNSET;
+ unsigned isolation = ISO_DEFAULT;
+ int prefer_no_auth = 0;
+ int socks_iso_keep_alive = 0;
+
+ uint16_t ptmp=0;
+ int ok;
+ /* This must be kept in sync with port_cfg_new's defaults */
+ int no_listen = 0, no_advertise = 0, all_addrs = 0,
+ bind_ipv4_only = 0, bind_ipv6_only = 0,
+ ipv4_traffic = 1, ipv6_traffic = 1, prefer_ipv6 = 0, dns_request = 1,
+ onion_traffic = 1,
+ cache_ipv4 = 0, use_cached_ipv4 = 0,
+ cache_ipv6 = 0, use_cached_ipv6 = 0,
+ prefer_ipv6_automap = 1, world_writable = 0, group_writable = 0,
+ relax_dirmode_check = 0,
+ has_used_unix_socket_only_option = 0;
+
+ int is_unix_tagged_addr = 0;
+ const char *rest_of_line = NULL;
+ if (port_cfg_line_extract_addrport(ports->value,
+ &addrport, &is_unix_tagged_addr, &rest_of_line)<0) {
+ log_warn(LD_CONFIG, "Invalid %sPort line with unparsable address",
+ portname);
+ goto err;
+ }
+ if (strlen(addrport) == 0) {
+ log_warn(LD_CONFIG, "Invalid %sPort line with no address", portname);
+ goto err;
+ }
+
+ /* Split the remainder... */
+ smartlist_split_string(elts, rest_of_line, NULL,
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+
+ /* Let's start to check if it's a Unix socket path. */
+ if (is_unix_tagged_addr) {
+#ifndef HAVE_SYS_UN_H
+ log_warn(LD_CONFIG, "Unix sockets not supported on this system.");
+ goto err;
+#endif
+ unix_socket_path = addrport;
+ addrport = NULL;
+ }
+
+ if (unix_socket_path &&
+ ! conn_listener_type_supports_af_unix(listener_type)) {
+ log_warn(LD_CONFIG, "%sPort does not support unix sockets", portname);
+ goto err;
+ }
+
+ if (unix_socket_path) {
+ port = 1;
+ } else if (is_unix_socket) {
+ if (BUG(!addrport))
+ goto err; // LCOV_EXCL_LINE unreachable, but coverity can't tell that
+ unix_socket_path = tor_strdup(addrport);
+ if (!strcmp(addrport, "0"))
+ port = 0;
+ else
+ port = 1;
+ } else if (!strcasecmp(addrport, "auto")) {
+ port = CFG_AUTO_PORT;
+ int af = tor_addr_parse(&addr, defaultaddr);
+ tor_assert(af >= 0);
+ } else if (!strcasecmpend(addrport, ":auto")) {
+ char *addrtmp = tor_strndup(addrport, strlen(addrport)-5);
+ port = CFG_AUTO_PORT;
+ if (tor_addr_port_lookup(addrtmp, &addr, &ptmp)<0 || ptmp) {
+ log_warn(LD_CONFIG, "Invalid address '%s' for %sPort",
+ escaped(addrport), portname);
+ tor_free(addrtmp);
+ goto err;
+ }
+ tor_free(addrtmp);
+ } else {
+ /* Try parsing integer port before address, because, who knows?
+ "9050" might be a valid address. */
+ port = (int) tor_parse_long(addrport, 10, 0, 65535, &ok, NULL);
+ if (ok) {
+ int af = tor_addr_parse(&addr, defaultaddr);
+ tor_assert(af >= 0);
+ } else if (tor_addr_port_lookup(addrport, &addr, &ptmp) == 0) {
+ if (ptmp == 0) {
+ log_warn(LD_CONFIG, "%sPort line has address but no port", portname);
+ goto err;
+ }
+ port = ptmp;
+ } else {
+ log_warn(LD_CONFIG, "Couldn't parse address %s for %sPort",
+ escaped(addrport), portname);
+ goto err;
+ }
+ }
+
+ if (unix_socket_path && default_to_group_writable)
+ group_writable = 1;
+
+ /* Now parse the rest of the options, if any. */
+ if (use_server_options) {
+ /* This is a server port; parse advertising options */
+ SMARTLIST_FOREACH_BEGIN(elts, char *, elt) {
+ if (!strcasecmp(elt, "NoAdvertise")) {
+ no_advertise = 1;
+ } else if (!strcasecmp(elt, "NoListen")) {
+ no_listen = 1;
+#if 0
+ /* not implemented yet. */
+ } else if (!strcasecmp(elt, "AllAddrs")) {
+
+ all_addrs = 1;
+#endif /* 0 */
+ } else if (!strcasecmp(elt, "IPv4Only")) {
+ bind_ipv4_only = 1;
+ } else if (!strcasecmp(elt, "IPv6Only")) {
+ bind_ipv6_only = 1;
+ } else {
+ log_warn(LD_CONFIG, "Unrecognized %sPort option '%s'",
+ portname, escaped(elt));
+ }
+ } SMARTLIST_FOREACH_END(elt);
+
+ if (no_advertise && no_listen) {
+ log_warn(LD_CONFIG, "Tried to set both NoListen and NoAdvertise "
+ "on %sPort line '%s'",
+ portname, escaped(ports->value));
+ goto err;
+ }
+ if (bind_ipv4_only && bind_ipv6_only) {
+ log_warn(LD_CONFIG, "Tried to set both IPv4Only and IPv6Only "
+ "on %sPort line '%s'",
+ portname, escaped(ports->value));
+ goto err;
+ }
+ if (bind_ipv4_only && tor_addr_family(&addr) == AF_INET6) {
+ log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv6",
+ portname);
+ goto err;
+ }
+ if (bind_ipv6_only && tor_addr_family(&addr) == AF_INET) {
+ log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv4",
+ portname);
+ goto err;
+ }
+ } else {
+ /* This is a client port; parse isolation options */
+ SMARTLIST_FOREACH_BEGIN(elts, char *, elt) {
+ int no = 0, isoflag = 0;
+ const char *elt_orig = elt;
+
+ if (!strcasecmpstart(elt, "SessionGroup=")) {
+ int group = (int)tor_parse_long(elt+strlen("SessionGroup="),
+ 10, 0, INT_MAX, &ok, NULL);
+ if (!ok || !allow_no_stream_options) {
+ log_warn(LD_CONFIG, "Invalid %sPort option '%s'",
+ portname, escaped(elt));
+ goto err;
+ }
+ if (sessiongroup >= 0) {
+ log_warn(LD_CONFIG, "Multiple SessionGroup options on %sPort",
+ portname);
+ goto err;
+ }
+ sessiongroup = group;
+ continue;
+ }
+
+ if (!strcasecmpstart(elt, "No")) {
+ no = 1;
+ elt += 2;
+ }
+
+ if (!strcasecmp(elt, "GroupWritable")) {
+ group_writable = !no;
+ has_used_unix_socket_only_option = 1;
+ continue;
+ } else if (!strcasecmp(elt, "WorldWritable")) {
+ world_writable = !no;
+ has_used_unix_socket_only_option = 1;
+ continue;
+ } else if (!strcasecmp(elt, "RelaxDirModeCheck")) {
+ relax_dirmode_check = !no;
+ has_used_unix_socket_only_option = 1;
+ continue;
+ }
+
+ if (allow_no_stream_options) {
+ log_warn(LD_CONFIG, "Unrecognized %sPort option '%s'",
+ portname, escaped(elt));
+ continue;
+ }
+
+ if (takes_hostnames) {
+ if (!strcasecmp(elt, "IPv4Traffic")) {
+ ipv4_traffic = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "IPv6Traffic")) {
+ ipv6_traffic = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "PreferIPv6")) {
+ prefer_ipv6 = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "DNSRequest")) {
+ dns_request = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "OnionTraffic")) {
+ onion_traffic = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "OnionTrafficOnly")) {
+ /* Only connect to .onion addresses. Equivalent to
+ * NoDNSRequest, NoIPv4Traffic, NoIPv6Traffic. The option
+ * NoOnionTrafficOnly is not supported, it's too confusing. */
+ if (no) {
+ log_warn(LD_CONFIG, "Unsupported %sPort option 'No%s'. Use "
+ "DNSRequest, IPv4Traffic, and/or IPv6Traffic instead.",
+ portname, escaped(elt));
+ } else {
+ ipv4_traffic = ipv6_traffic = dns_request = 0;
+ }
+ continue;
+ }
+ }
+ if (!strcasecmp(elt, "CacheIPv4DNS")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
+ cache_ipv4 = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "CacheIPv6DNS")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
+ cache_ipv6 = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "CacheDNS")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
+ cache_ipv4 = cache_ipv6 = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "UseIPv4Cache")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
+ use_cached_ipv4 = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "UseIPv6Cache")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
+ use_cached_ipv6 = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "UseDNSCache")) {
+ warn_client_dns_cache(elt, no); // since 0.2.9.2-alpha
+ use_cached_ipv4 = use_cached_ipv6 = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "PreferIPv6Automap")) {
+ prefer_ipv6_automap = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "PreferSOCKSNoAuth")) {
+ prefer_no_auth = ! no;
+ continue;
+ } else if (!strcasecmp(elt, "KeepAliveIsolateSOCKSAuth")) {
+ socks_iso_keep_alive = ! no;
+ continue;
+ }
+
+ if (!strcasecmpend(elt, "s"))
+ elt[strlen(elt)-1] = '\0'; /* kill plurals. */
+
+ if (!strcasecmp(elt, "IsolateDestPort")) {
+ isoflag = ISO_DESTPORT;
+ } else if (!strcasecmp(elt, "IsolateDestAddr")) {
+ isoflag = ISO_DESTADDR;
+ } else if (!strcasecmp(elt, "IsolateSOCKSAuth")) {
+ isoflag = ISO_SOCKSAUTH;
+ } else if (!strcasecmp(elt, "IsolateClientProtocol")) {
+ isoflag = ISO_CLIENTPROTO;
+ } else if (!strcasecmp(elt, "IsolateClientAddr")) {
+ isoflag = ISO_CLIENTADDR;
+ } else {
+ log_warn(LD_CONFIG, "Unrecognized %sPort option '%s'",
+ portname, escaped(elt_orig));
+ }
+
+ if (no) {
+ isolation &= ~isoflag;
+ } else {
+ isolation |= isoflag;
+ }
+ } SMARTLIST_FOREACH_END(elt);
+ }
+
+ if (port)
+ got_nonzero_port = 1;
+ else
+ got_zero_port = 1;
+
+ if (dns_request == 0 && listener_type == CONN_TYPE_AP_DNS_LISTENER) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with DNS disabled; that "
+ "won't work.", portname);
+ goto err;
+ }
+
+ if (ipv4_traffic == 0 && ipv6_traffic == 0 && onion_traffic == 0
+ && listener_type != CONN_TYPE_AP_DNS_LISTENER) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with all of IPv4 and "
+ "IPv6 and .onion disabled; that won't work.", portname);
+ goto err;
+ }
+
+ if (dns_request == 1 && ipv4_traffic == 0 && ipv6_traffic == 0
+ && listener_type != CONN_TYPE_AP_DNS_LISTENER) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with DNSRequest enabled, "
+ "but IPv4 and IPv6 disabled; DNS-based sites won't work.",
+ portname);
+ goto err;
+ }
+
+ if ( has_used_unix_socket_only_option && ! unix_socket_path) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with GroupWritable, "
+ "WorldWritable, or RelaxDirModeCheck, but it is not a "
+ "unix socket.", portname);
+ goto err;
+ }
+
+ if (!(isolation & ISO_SOCKSAUTH) && socks_iso_keep_alive) {
+ log_warn(LD_CONFIG, "You have a %sPort entry with both "
+ "NoIsolateSOCKSAuth and KeepAliveIsolateSOCKSAuth set.",
+ portname);
+ goto err;
+ }
+
+ if (unix_socket_path && (isolation & ISO_CLIENTADDR)) {
+ /* `IsolateClientAddr` is nonsensical in the context of AF_LOCAL.
+ * just silently remove the isolation flag.
+ */
+ isolation &= ~ISO_CLIENTADDR;
+ }
+
+ if (out && port) {
+ size_t namelen = unix_socket_path ? strlen(unix_socket_path) : 0;
+ port_cfg_t *cfg = port_cfg_new(namelen);
+ if (unix_socket_path) {
+ tor_addr_make_unspec(&cfg->addr);
+ memcpy(cfg->unix_addr, unix_socket_path, namelen + 1);
+ cfg->is_unix_addr = 1;
+ tor_free(unix_socket_path);
+ } else {
+ tor_addr_copy(&cfg->addr, &addr);
+ cfg->port = port;
+ }
+ cfg->type = listener_type;
+ cfg->is_world_writable = world_writable;
+ cfg->is_group_writable = group_writable;
+ cfg->relax_dirmode_check = relax_dirmode_check;
+ cfg->entry_cfg.isolation_flags = isolation;
+ cfg->entry_cfg.session_group = sessiongroup;
+ cfg->server_cfg.no_advertise = no_advertise;
+ cfg->server_cfg.no_listen = no_listen;
+ cfg->server_cfg.all_addrs = all_addrs;
+ cfg->server_cfg.bind_ipv4_only = bind_ipv4_only;
+ cfg->server_cfg.bind_ipv6_only = bind_ipv6_only;
+ cfg->entry_cfg.ipv4_traffic = ipv4_traffic;
+ cfg->entry_cfg.ipv6_traffic = ipv6_traffic;
+ cfg->entry_cfg.prefer_ipv6 = prefer_ipv6;
+ cfg->entry_cfg.dns_request = dns_request;
+ cfg->entry_cfg.onion_traffic = onion_traffic;
+ cfg->entry_cfg.cache_ipv4_answers = cache_ipv4;
+ cfg->entry_cfg.cache_ipv6_answers = cache_ipv6;
+ cfg->entry_cfg.use_cached_ipv4_answers = use_cached_ipv4;
+ cfg->entry_cfg.use_cached_ipv6_answers = use_cached_ipv6;
+ cfg->entry_cfg.prefer_ipv6_virtaddr = prefer_ipv6_automap;
+ cfg->entry_cfg.socks_prefer_no_auth = prefer_no_auth;
+ if (! (isolation & ISO_SOCKSAUTH))
+ cfg->entry_cfg.socks_prefer_no_auth = 1;
+ cfg->entry_cfg.socks_iso_keep_alive = socks_iso_keep_alive;
+
+ smartlist_add(out, cfg);
+ }
+ SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
+ smartlist_clear(elts);
+ tor_free(addrport);
+ tor_free(unix_socket_path);
+ }
+
+ if (warn_nonlocal && out) {
+ if (is_control)
+ warn_nonlocal_controller_ports(out, forbid_nonlocal);
+ else if (is_ext_orport)
+ warn_nonlocal_ext_orports(out, portname);
+ else
+ warn_nonlocal_client_ports(out, portname, listener_type);
+ }
+
+ if (got_zero_port && got_nonzero_port) {
+ log_warn(LD_CONFIG, "You specified a nonzero %sPort along with '%sPort 0' "
+ "in the same configuration. Did you mean to disable %sPort or "
+ "not?", portname, portname, portname);
+ goto err;
+ }
+
+ retval = 0;
+ err:
+ SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
+ smartlist_free(elts);
+ tor_free(unix_socket_path);
+ tor_free(addrport);
+ return retval;
+}
+
+/** Return the number of ports which are actually going to listen with type
+ * <b>listenertype</b>. Do not count no_listen ports. Only count unix
+ * sockets if count_sockets is true. */
+static int
+count_real_listeners(const smartlist_t *ports, int listenertype,
+ int count_sockets)
+{
+ int n = 0;
+ SMARTLIST_FOREACH_BEGIN(ports, port_cfg_t *, port) {
+ if (port->server_cfg.no_listen)
+ continue;
+ if (!count_sockets && port->is_unix_addr)
+ continue;
+ if (port->type != listenertype)
+ continue;
+ ++n;
+ } SMARTLIST_FOREACH_END(port);
+ return n;
+}
+
+/** Parse all ports from <b>options</b>. On success, set *<b>n_ports_out</b>
+ * to the number of ports that are listed, update the *Port_set values in
+ * <b>options</b>, and return 0. On failure, set *<b>msg</b> to a
+ * description of the problem and return -1.
+ *
+ * If <b>validate_only</b> is false, set configured_client_ports to the
+ * new list of ports parsed from <b>options</b>.
+ **/
+static int
+parse_ports(or_options_t *options, int validate_only,
+ char **msg, int *n_ports_out,
+ int *world_writable_control_socket)
+{
+ smartlist_t *ports;
+ int retval = -1;
+
+ ports = smartlist_new();
+
+ *n_ports_out = 0;
+
+ const unsigned gw_flag = options->UnixSocksGroupWritable ?
+ CL_PORT_DFLT_GROUP_WRITABLE : 0;
+ if (parse_port_config(ports,
+ options->SocksPort_lines,
+ "Socks", CONN_TYPE_AP_LISTENER,
+ "127.0.0.1", 9050,
+ ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL)
+ | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) {
+ *msg = tor_strdup("Invalid SocksPort configuration");
+ goto err;
+ }
+ if (parse_port_config(ports,
+ options->DNSPort_lines,
+ "DNS", CONN_TYPE_AP_DNS_LISTENER,
+ "127.0.0.1", 0,
+ CL_PORT_WARN_NONLOCAL|CL_PORT_TAKES_HOSTNAMES) < 0) {
+ *msg = tor_strdup("Invalid DNSPort configuration");
+ goto err;
+ }
+ if (parse_port_config(ports,
+ options->TransPort_lines,
+ "Trans", CONN_TYPE_AP_TRANS_LISTENER,
+ "127.0.0.1", 0,
+ CL_PORT_WARN_NONLOCAL) < 0) {
+ *msg = tor_strdup("Invalid TransPort configuration");
+ goto err;
+ }
+ if (parse_port_config(ports,
+ options->NATDPort_lines,
+ "NATD", CONN_TYPE_AP_NATD_LISTENER,
+ "127.0.0.1", 0,
+ CL_PORT_WARN_NONLOCAL) < 0) {
+ *msg = tor_strdup("Invalid NatdPort configuration");
+ goto err;
+ }
+ if (parse_port_config(ports,
+ options->HTTPTunnelPort_lines,
+ "HTTP Tunnel", CONN_TYPE_AP_HTTP_CONNECT_LISTENER,
+ "127.0.0.1", 0,
+ ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL)
+ | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) {
+ *msg = tor_strdup("Invalid HTTPTunnelPort configuration");
+ goto err;
+ }
+ {
+ unsigned control_port_flags = CL_PORT_NO_STREAM_OPTIONS |
+ CL_PORT_WARN_NONLOCAL;
+ const int any_passwords = (options->HashedControlPassword ||
+ options->HashedControlSessionPassword ||
+ options->CookieAuthentication);
+ if (! any_passwords)
+ control_port_flags |= CL_PORT_FORBID_NONLOCAL;
+ if (options->ControlSocketsGroupWritable)
+ control_port_flags |= CL_PORT_DFLT_GROUP_WRITABLE;
+
+ if (parse_port_config(ports,
+ options->ControlPort_lines,
+ "Control", CONN_TYPE_CONTROL_LISTENER,
+ "127.0.0.1", 0,
+ control_port_flags) < 0) {
+ *msg = tor_strdup("Invalid ControlPort configuration");
+ goto err;
+ }
+
+ if (parse_port_config(ports, options->ControlSocket,
+ "ControlSocket",
+ CONN_TYPE_CONTROL_LISTENER, NULL, 0,
+ control_port_flags | CL_PORT_IS_UNIXSOCKET) < 0) {
+ *msg = tor_strdup("Invalid ControlSocket configuration");
+ goto err;
+ }
+ }
+ if (! options->ClientOnly) {
+ if (parse_port_config(ports,
+ options->ORPort_lines,
+ "OR", CONN_TYPE_OR_LISTENER,
+ "0.0.0.0", 0,
+ CL_PORT_SERVER_OPTIONS) < 0) {
+ *msg = tor_strdup("Invalid ORPort configuration");
+ goto err;
+ }
+ if (parse_port_config(ports,
+ options->ExtORPort_lines,
+ "ExtOR", CONN_TYPE_EXT_OR_LISTENER,
+ "127.0.0.1", 0,
+ CL_PORT_SERVER_OPTIONS|CL_PORT_WARN_NONLOCAL) < 0) {
+ *msg = tor_strdup("Invalid ExtORPort configuration");
+ goto err;
+ }
+ if (parse_port_config(ports,
+ options->DirPort_lines,
+ "Dir", CONN_TYPE_DIR_LISTENER,
+ "0.0.0.0", 0,
+ CL_PORT_SERVER_OPTIONS) < 0) {
+ *msg = tor_strdup("Invalid DirPort configuration");
+ goto err;
+ }
+ }
+
+ int n_low_ports = 0;
+ if (check_server_ports(ports, options, &n_low_ports) < 0) {
+ *msg = tor_strdup("Misconfigured server ports");
+ goto err;
+ }
+ if (have_low_ports < 0)
+ have_low_ports = (n_low_ports > 0);
+
+ *n_ports_out = smartlist_len(ports);
+
+ retval = 0;
+
+ /* Update the *Port_set options. The !! here is to force a boolean out of
+ an integer. */
+ options->ORPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_OR_LISTENER, 0);
+ options->SocksPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_AP_LISTENER, 1);
+ options->TransPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1);
+ options->NATDPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1);
+ options->HTTPTunnelPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_AP_HTTP_CONNECT_LISTENER, 1);
+ /* Use options->ControlSocket to test if a control socket is set */
+ options->ControlPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0);
+ options->DirPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_DIR_LISTENER, 0);
+ options->DNSPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_AP_DNS_LISTENER, 1);
+ options->ExtORPort_set =
+ !! count_real_listeners(ports, CONN_TYPE_EXT_OR_LISTENER, 0);
+
+ if (world_writable_control_socket) {
+ SMARTLIST_FOREACH(ports, port_cfg_t *, p,
+ if (p->type == CONN_TYPE_CONTROL_LISTENER &&
+ p->is_unix_addr &&
+ p->is_world_writable) {
+ *world_writable_control_socket = 1;
+ break;
+ });
+ }
+
+ if (!validate_only) {
+ if (configured_ports) {
+ SMARTLIST_FOREACH(configured_ports,
+ port_cfg_t *, p, port_cfg_free(p));
+ smartlist_free(configured_ports);
+ }
+ configured_ports = ports;
+ ports = NULL; /* prevent free below. */
+ }
+
+ err:
+ if (ports) {
+ SMARTLIST_FOREACH(ports, port_cfg_t *, p, port_cfg_free(p));
+ smartlist_free(ports);
+ }
+ return retval;
+}
+
+/* Does port bind to IPv4? */
+static int
+port_binds_ipv4(const port_cfg_t *port)
+{
+ return tor_addr_family(&port->addr) == AF_INET ||
+ (tor_addr_family(&port->addr) == AF_UNSPEC
+ && !port->server_cfg.bind_ipv6_only);
+}
+
+/* Does port bind to IPv6? */
+static int
+port_binds_ipv6(const port_cfg_t *port)
+{
+ return tor_addr_family(&port->addr) == AF_INET6 ||
+ (tor_addr_family(&port->addr) == AF_UNSPEC
+ && !port->server_cfg.bind_ipv4_only);
+}
+
+/** Given a list of <b>port_cfg_t</b> in <b>ports</b>, check them for internal
+ * consistency and warn as appropriate. Set *<b>n_low_ports_out</b> to the
+ * number of sub-1024 ports we will be binding. */
+static int
+check_server_ports(const smartlist_t *ports,
+ const or_options_t *options,
+ int *n_low_ports_out)
+{
+ int n_orport_advertised = 0;
+ int n_orport_advertised_ipv4 = 0;
+ int n_orport_listeners = 0;
+ int n_dirport_advertised = 0;
+ int n_dirport_listeners = 0;
+ int n_low_port = 0;
+ int r = 0;
+
+ SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) {
+ if (port->type == CONN_TYPE_DIR_LISTENER) {
+ if (! port->server_cfg.no_advertise)
+ ++n_dirport_advertised;
+ if (! port->server_cfg.no_listen)
+ ++n_dirport_listeners;
+ } else if (port->type == CONN_TYPE_OR_LISTENER) {
+ if (! port->server_cfg.no_advertise) {
+ ++n_orport_advertised;
+ if (port_binds_ipv4(port))
+ ++n_orport_advertised_ipv4;
+ }
+ if (! port->server_cfg.no_listen)
+ ++n_orport_listeners;
+ } else {
+ continue;
+ }
+#ifndef _WIN32
+ if (!port->server_cfg.no_listen && port->port < 1024)
+ ++n_low_port;
+#endif
+ } SMARTLIST_FOREACH_END(port);
+
+ if (n_orport_advertised && !n_orport_listeners) {
+ log_warn(LD_CONFIG, "We are advertising an ORPort, but not actually "
+ "listening on one.");
+ r = -1;
+ }
+ if (n_orport_listeners && !n_orport_advertised) {
+ log_warn(LD_CONFIG, "We are listening on an ORPort, but not advertising "
+ "any ORPorts. This will keep us from building a %s "
+ "descriptor, and make us impossible to use.",
+ options->BridgeRelay ? "bridge" : "router");
+ r = -1;
+ }
+ if (n_dirport_advertised && !n_dirport_listeners) {
+ log_warn(LD_CONFIG, "We are advertising a DirPort, but not actually "
+ "listening on one.");
+ r = -1;
+ }
+ if (n_dirport_advertised > 1) {
+ log_warn(LD_CONFIG, "Can't advertise more than one DirPort.");
+ r = -1;
+ }
+ if (n_orport_advertised && !n_orport_advertised_ipv4 &&
+ !options->BridgeRelay) {
+ log_warn(LD_CONFIG, "Configured non-bridge only to listen on an IPv6 "
+ "address.");
+ r = -1;
+ }
+
+ if (n_low_port && options->AccountingMax &&
+ (!have_capability_support() || options->KeepBindCapabilities == 0)) {
+ const char *extra = "";
+ if (options->KeepBindCapabilities == 0 && have_capability_support())
+ extra = ", and you have disabled KeepBindCapabilities.";
+ log_warn(LD_CONFIG,
+ "You have set AccountingMax to use hibernation. You have also "
+ "chosen a low DirPort or OrPort%s."
+ "This combination can make Tor stop "
+ "working when it tries to re-attach the port after a period of "
+ "hibernation. Please choose a different port or turn off "
+ "hibernation unless you know this combination will work on your "
+ "platform.", extra);
+ }
+
+ if (n_low_ports_out)
+ *n_low_ports_out = n_low_port;
+
+ return r;
+}
+
+/** Return a list of port_cfg_t for client ports parsed from the
+ * options. */
+MOCK_IMPL(const smartlist_t *,
+get_configured_ports,(void))
+{
+ if (!configured_ports)
+ configured_ports = smartlist_new();
+ return configured_ports;
+}
+
+/** Return an address:port string representation of the address
+ * where the first <b>listener_type</b> listener waits for
+ * connections. Return NULL if we couldn't find a listener. The
+ * string is allocated on the heap and it's the responsibility of the
+ * caller to free it after use.
+ *
+ * This function is meant to be used by the pluggable transport proxy
+ * spawning code, please make sure that it fits your purposes before
+ * using it. */
+char *
+get_first_listener_addrport_string(int listener_type)
+{
+ static const char *ipv4_localhost = "127.0.0.1";
+ static const char *ipv6_localhost = "[::1]";
+ const char *address;
+ uint16_t port;
+ char *string = NULL;
+
+ if (!configured_ports)
+ return NULL;
+
+ SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) {
+ if (cfg->server_cfg.no_listen)
+ continue;
+
+ if (cfg->type == listener_type &&
+ tor_addr_family(&cfg->addr) != AF_UNSPEC) {
+
+ /* We found the first listener of the type we are interested in! */
+
+ /* If a listener is listening on INADDR_ANY, assume that it's
+ also listening on 127.0.0.1, and point the transport proxy
+ there: */
+ if (tor_addr_is_null(&cfg->addr))
+ address = tor_addr_is_v4(&cfg->addr) ? ipv4_localhost : ipv6_localhost;
+ else
+ address = fmt_and_decorate_addr(&cfg->addr);
+
+ /* If a listener is configured with port 'auto', we are forced
+ to iterate all listener connections and find out in which
+ port it ended up listening: */
+ if (cfg->port == CFG_AUTO_PORT) {
+ port = router_get_active_listener_port_by_type_af(listener_type,
+ tor_addr_family(&cfg->addr));
+ if (!port)
+ return NULL;
+ } else {
+ port = cfg->port;
+ }
+
+ tor_asprintf(&string, "%s:%u", address, port);
+
+ return string;
+ }
+
+ } SMARTLIST_FOREACH_END(cfg);
+
+ return NULL;
+}
+
+/** Return the first advertised port of type <b>listener_type</b> in
+ * <b>address_family</b>. Returns 0 when no port is found, and when passed
+ * AF_UNSPEC. */
+int
+get_first_advertised_port_by_type_af(int listener_type, int address_family)
+{
+ if (address_family == AF_UNSPEC)
+ return 0;
+
+ const smartlist_t *conf_ports = get_configured_ports();
+ SMARTLIST_FOREACH_BEGIN(conf_ports, const port_cfg_t *, cfg) {
+ if (cfg->type == listener_type &&
+ !cfg->server_cfg.no_advertise) {
+ if ((address_family == AF_INET && port_binds_ipv4(cfg)) ||
+ (address_family == AF_INET6 && port_binds_ipv6(cfg))) {
+ return cfg->port;
+ }
+ }
+ } SMARTLIST_FOREACH_END(cfg);
+ return 0;
+}
+
+/** Return the first advertised address of type <b>listener_type</b> in
+ * <b>address_family</b>. Returns NULL if there is no advertised address,
+ * and when passed AF_UNSPEC. */
+const tor_addr_t *
+get_first_advertised_addr_by_type_af(int listener_type, int address_family)
+{
+ if (address_family == AF_UNSPEC)
+ return NULL;
+ if (!configured_ports)
+ return NULL;
+ SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) {
+ if (cfg->type == listener_type &&
+ !cfg->server_cfg.no_advertise) {
+ if ((address_family == AF_INET && port_binds_ipv4(cfg)) ||
+ (address_family == AF_INET6 && port_binds_ipv6(cfg))) {
+ return &cfg->addr;
+ }
+ }
+ } SMARTLIST_FOREACH_END(cfg);
+ return NULL;
+}
+
+/** Return 1 if a port exists of type <b>listener_type</b> on <b>addr</b> and
+ * <b>port</b>. If <b>check_wildcard</b> is true, INADDR[6]_ANY and AF_UNSPEC
+ * addresses match any address of the appropriate family; and port -1 matches
+ * any port.
+ * To match auto ports, pass CFG_PORT_AUTO. (Does not match on the actual
+ * automatically chosen listener ports.) */
+int
+port_exists_by_type_addr_port(int listener_type, const tor_addr_t *addr,
+ int port, int check_wildcard)
+{
+ if (!configured_ports || !addr)
+ return 0;
+ SMARTLIST_FOREACH_BEGIN(configured_ports, const port_cfg_t *, cfg) {
+ if (cfg->type == listener_type) {
+ if (cfg->port == port || (check_wildcard && port == -1)) {
+ /* Exact match */
+ if (tor_addr_eq(&cfg->addr, addr)) {
+ return 1;
+ }
+ /* Skip wildcard matches if we're not doing them */
+ if (!check_wildcard) {
+ continue;
+ }
+ /* Wildcard matches IPv4 */
+ const int cfg_v4 = port_binds_ipv4(cfg);
+ const int cfg_any_v4 = tor_addr_is_null(&cfg->addr) && cfg_v4;
+ const int addr_v4 = tor_addr_family(addr) == AF_INET ||
+ tor_addr_family(addr) == AF_UNSPEC;
+ const int addr_any_v4 = tor_addr_is_null(&cfg->addr) && addr_v4;
+ if ((cfg_any_v4 && addr_v4) || (cfg_v4 && addr_any_v4)) {
+ return 1;
+ }
+ /* Wildcard matches IPv6 */
+ const int cfg_v6 = port_binds_ipv6(cfg);
+ const int cfg_any_v6 = tor_addr_is_null(&cfg->addr) && cfg_v6;
+ const int addr_v6 = tor_addr_family(addr) == AF_INET6 ||
+ tor_addr_family(addr) == AF_UNSPEC;
+ const int addr_any_v6 = tor_addr_is_null(&cfg->addr) && addr_v6;
+ if ((cfg_any_v6 && addr_v6) || (cfg_v6 && addr_any_v6)) {
+ return 1;
+ }
+ }
+ }
+ } SMARTLIST_FOREACH_END(cfg);
+ return 0;
+}
+
+/* Like port_exists_by_type_addr_port, but accepts a host-order IPv4 address
+ * instead. */
+int
+port_exists_by_type_addr32h_port(int listener_type, uint32_t addr_ipv4h,
+ int port, int check_wildcard)
+{
+ tor_addr_t ipv4;
+ tor_addr_from_ipv4h(&ipv4, addr_ipv4h);
+ return port_exists_by_type_addr_port(listener_type, &ipv4, port,
+ check_wildcard);
+}
+
+/** Allocate and return a good value for the DataDirectory based on
+ * <b>val</b>, which may be NULL. Return NULL on failure. */
+static char *
+get_data_directory(const char *val)
+{
+#ifdef _WIN32
+ if (val) {
+ return tor_strdup(val);
+ } else {
+ return tor_strdup(get_windows_conf_root());
+ }
+#else /* !(defined(_WIN32)) */
+ const char *d = val;
+ if (!d)
+ d = "~/.tor";
+
+ if (!strcmpstart(d, "~/")) {
+ char *fn = expand_filename(d);
+ if (!fn) {
+ log_warn(LD_CONFIG,"Failed to expand filename \"%s\".", d);
+ return NULL;
+ }
+ if (!val && !strcmp(fn,"/.tor")) {
+ /* If our homedir is /, we probably don't want to use it. */
+ /* Default to LOCALSTATEDIR/tor which is probably closer to what we
+ * want. */
+ log_warn(LD_CONFIG,
+ "Default DataDirectory is \"~/.tor\". This expands to "
+ "\"%s\", which is probably not what you want. Using "
+ "\"%s"PATH_SEPARATOR"tor\" instead", fn, LOCALSTATEDIR);
+ tor_free(fn);
+ fn = tor_strdup(LOCALSTATEDIR PATH_SEPARATOR "tor");
+ }
+ return fn;
+ }
+ return tor_strdup(d);
+#endif /* defined(_WIN32) */
+}
+
+/** Check and normalize the values of options->{Key,Data,Cache}Directory;
+ * return 0 if it is sane, -1 otherwise. */
+static int
+validate_data_directories(or_options_t *options)
+{
+ tor_free(options->DataDirectory);
+ options->DataDirectory = get_data_directory(options->DataDirectory_option);
+ if (!options->DataDirectory)
+ return -1;
+ if (strlen(options->DataDirectory) > (512-128)) {
+ log_warn(LD_CONFIG, "DataDirectory is too long.");
+ return -1;
+ }
+
+ tor_free(options->KeyDirectory);
+ if (options->KeyDirectory_option) {
+ options->KeyDirectory = get_data_directory(options->KeyDirectory_option);
+ if (!options->KeyDirectory)
+ return -1;
+ } else {
+ /* Default to the data directory's keys subdir */
+ tor_asprintf(&options->KeyDirectory, "%s"PATH_SEPARATOR"keys",
+ options->DataDirectory);
+ }
+
+ tor_free(options->CacheDirectory);
+ if (options->CacheDirectory_option) {
+ options->CacheDirectory = get_data_directory(
+ options->CacheDirectory_option);
+ if (!options->CacheDirectory)
+ return -1;
+ } else {
+ /* Default to the data directory. */
+ options->CacheDirectory = tor_strdup(options->DataDirectory);
+ }
+
+ return 0;
+}
+
+/** This string must remain the same forevermore. It is how we
+ * recognize that the torrc file doesn't need to be backed up. */
+#define GENERATED_FILE_PREFIX "# This file was generated by Tor; " \
+ "if you edit it, comments will not be preserved"
+/** This string can change; it tries to give the reader an idea
+ * that editing this file by hand is not a good plan. */
+#define GENERATED_FILE_COMMENT "# The old torrc file was renamed " \
+ "to torrc.orig.1 or similar, and Tor will ignore it"
+
+/** Save a configuration file for the configuration in <b>options</b>
+ * into the file <b>fname</b>. If the file already exists, and
+ * doesn't begin with GENERATED_FILE_PREFIX, rename it. Otherwise
+ * replace it. Return 0 on success, -1 on failure. */
+static int
+write_configuration_file(const char *fname, const or_options_t *options)
+{
+ char *old_val=NULL, *new_val=NULL, *new_conf=NULL;
+ int rename_old = 0, r;
+
+ if (!fname)
+ return -1;
+
+ switch (file_status(fname)) {
+ /* create backups of old config files, even if they're empty */
+ case FN_FILE:
+ case FN_EMPTY:
+ old_val = read_file_to_str(fname, 0, NULL);
+ if (!old_val || strcmpstart(old_val, GENERATED_FILE_PREFIX)) {
+ rename_old = 1;
+ }
+ tor_free(old_val);
+ break;
+ case FN_NOENT:
+ break;
+ case FN_ERROR:
+ case FN_DIR:
+ default:
+ log_warn(LD_CONFIG,
+ "Config file \"%s\" is not a file? Failing.", fname);
+ return -1;
+ }
+
+ if (!(new_conf = options_dump(options, OPTIONS_DUMP_MINIMAL))) {
+ log_warn(LD_BUG, "Couldn't get configuration string");
+ goto err;
+ }
+
+ tor_asprintf(&new_val, "%s\n%s\n\n%s",
+ GENERATED_FILE_PREFIX, GENERATED_FILE_COMMENT, new_conf);
+
+ if (rename_old) {
+ int i = 1;
+ char *fn_tmp = NULL;
+ while (1) {
+ tor_asprintf(&fn_tmp, "%s.orig.%d", fname, i);
+ if (file_status(fn_tmp) == FN_NOENT)
+ break;
+ tor_free(fn_tmp);
+ ++i;
+ }
+ log_notice(LD_CONFIG, "Renaming old configuration file to \"%s\"", fn_tmp);
+ if (tor_rename(fname, fn_tmp) < 0) {//XXXX sandbox doesn't allow
+ log_warn(LD_FS,
+ "Couldn't rename configuration file \"%s\" to \"%s\": %s",
+ fname, fn_tmp, strerror(errno));
+ tor_free(fn_tmp);
+ goto err;
+ }
+ tor_free(fn_tmp);
+ }
+
+ if (write_str_to_file(fname, new_val, 0) < 0)
+ goto err;
+
+ r = 0;
+ goto done;
+ err:
+ r = -1;
+ done:
+ tor_free(new_val);
+ tor_free(new_conf);
+ return r;
+}
+
+/**
+ * Save the current configuration file value to disk. Return 0 on
+ * success, -1 on failure.
+ **/
+int
+options_save_current(void)
+{
+ /* This fails if we can't write to our configuration file.
+ *
+ * If we try falling back to datadirectory or something, we have a better
+ * chance of saving the configuration, but a better chance of doing
+ * something the user never expected. */
+ return write_configuration_file(get_torrc_fname(0), get_options());
+}
+
+/** Return the number of cpus configured in <b>options</b>. If we are
+ * told to auto-detect the number of cpus, return the auto-detected number. */
+int
+get_num_cpus(const or_options_t *options)
+{
+ if (options->NumCPUs == 0) {
+ int n = compute_num_cpus();
+ return (n >= 1) ? n : 1;
+ } else {
+ return options->NumCPUs;
+ }
+}
+
+/**
+ * Initialize the libevent library.
+ */
+static void
+init_libevent(const or_options_t *options)
+{
+ tor_libevent_cfg cfg;
+
+ tor_assert(options);
+
+ configure_libevent_logging();
+ /* If the kernel complains that some method (say, epoll) doesn't
+ * exist, we don't care about it, since libevent will cope.
+ */
+ suppress_libevent_log_msg("Function not implemented");
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.num_cpus = get_num_cpus(options);
+ cfg.msec_per_tick = options->TokenBucketRefillInterval;
+
+ tor_libevent_initialize(&cfg);
+
+ suppress_libevent_log_msg(NULL);
+}
+
+/** Return a newly allocated string holding a filename relative to the
+ * directory in <b>options</b> specified by <b>roottype</b>.
+ * If <b>sub1</b> is present, it is the first path component after
+ * the data directory. If <b>sub2</b> is also present, it is the second path
+ * component after the data directory. If <b>suffix</b> is present, it
+ * is appended to the filename.
+ *
+ * Note: Consider using macros in config.h that wrap this function;
+ * you should probably never need to call it as-is.
+ */
+MOCK_IMPL(char *,
+options_get_dir_fname2_suffix,(const or_options_t *options,
+ directory_root_t roottype,
+ const char *sub1, const char *sub2,
+ const char *suffix))
+{
+ tor_assert(options);
+
+ const char *rootdir = NULL;
+ switch (roottype) {
+ case DIRROOT_DATADIR:
+ rootdir = options->DataDirectory;
+ break;
+ case DIRROOT_CACHEDIR:
+ rootdir = options->CacheDirectory;
+ break;
+ case DIRROOT_KEYDIR:
+ rootdir = options->KeyDirectory;
+ break;
+ default:
+ tor_assert_unreached();
+ break;
+ }
+ tor_assert(rootdir);
+
+ if (!suffix)
+ suffix = "";
+
+ char *fname = NULL;
+
+ if (sub1 == NULL) {
+ tor_asprintf(&fname, "%s%s", rootdir, suffix);
+ tor_assert(!sub2); /* If sub2 is present, sub1 must be present. */
+ } else if (sub2 == NULL) {
+ tor_asprintf(&fname, "%s"PATH_SEPARATOR"%s%s", rootdir, sub1, suffix);
+ } else {
+ tor_asprintf(&fname, "%s"PATH_SEPARATOR"%s"PATH_SEPARATOR"%s%s",
+ rootdir, sub1, sub2, suffix);
+ }
+
+ return fname;
+}
+
+/** Check wether the data directory has a private subdirectory
+ * <b>subdir</b>. If not, try to create it. Return 0 on success,
+ * -1 otherwise. */
+int
+check_or_create_data_subdir(const char *subdir)
+{
+ char *statsdir = get_datadir_fname(subdir);
+ int return_val = 0;
+
+ if (check_private_dir(statsdir, CPD_CREATE, get_options()->User) < 0) {
+ log_warn(LD_HIST, "Unable to create %s/ directory!", subdir);
+ return_val = -1;
+ }
+ tor_free(statsdir);
+ return return_val;
+}
+
+/** Create a file named <b>fname</b> with contents <b>str</b> in the
+ * subdirectory <b>subdir</b> of the data directory. <b>descr</b>
+ * should be a short description of the file's content and will be
+ * used for the warning message, if it's present and the write process
+ * fails. Return 0 on success, -1 otherwise.*/
+int
+write_to_data_subdir(const char* subdir, const char* fname,
+ const char* str, const char* descr)
+{
+ char *filename = get_datadir_fname2(subdir, fname);
+ int return_val = 0;
+
+ if (write_str_to_file(filename, str, 0) < 0) {
+ log_warn(LD_HIST, "Unable to write %s to disk!", descr ? descr : fname);
+ return_val = -1;
+ }
+ tor_free(filename);
+ return return_val;
+}
+
+/** Return a smartlist of ports that must be forwarded by
+ * tor-fw-helper. The smartlist contains the ports in a string format
+ * that is understandable by tor-fw-helper. */
+smartlist_t *
+get_list_of_ports_to_forward(void)
+{
+ smartlist_t *ports_to_forward = smartlist_new();
+ int port = 0;
+
+ /** XXX TODO tor-fw-helper does not support forwarding ports to
+ other hosts than the local one. If the user is binding to a
+ different IP address, tor-fw-helper won't work. */
+ port = router_get_advertised_or_port(get_options()); /* Get ORPort */
+ if (port)
+ smartlist_add_asprintf(ports_to_forward, "%d:%d", port, port);
+
+ port = router_get_advertised_dir_port(get_options(), 0); /* Get DirPort */
+ if (port)
+ smartlist_add_asprintf(ports_to_forward, "%d:%d", port, port);
+
+ /* Get ports of transport proxies */
+ {
+ smartlist_t *transport_ports = get_transport_proxy_ports();
+ if (transport_ports) {
+ smartlist_add_all(ports_to_forward, transport_ports);
+ smartlist_free(transport_ports);
+ }
+ }
+
+ if (!smartlist_len(ports_to_forward)) {
+ smartlist_free(ports_to_forward);
+ ports_to_forward = NULL;
+ }
+
+ return ports_to_forward;
+}
+
+/** Helper to implement GETINFO functions about configuration variables (not
+ * their values). Given a "config/names" question, set *<b>answer</b> to a
+ * new string describing the supported configuration variables and their
+ * types. */
+int
+getinfo_helper_config(control_connection_t *conn,
+ const char *question, char **answer,
+ const char **errmsg)
+{
+ (void) conn;
+ (void) errmsg;
+ if (!strcmp(question, "config/names")) {
+ smartlist_t *sl = smartlist_new();
+ int i;
+ for (i = 0; option_vars_[i].name; ++i) {
+ const config_var_t *var = &option_vars_[i];
+ const char *type;
+ /* don't tell controller about triple-underscore options */
+ if (!strncmp(option_vars_[i].name, "___", 3))
+ continue;
+ switch (var->type) {
+ case CONFIG_TYPE_STRING: type = "String"; break;
+ case CONFIG_TYPE_FILENAME: type = "Filename"; break;
+ case CONFIG_TYPE_UINT: type = "Integer"; break;
+ case CONFIG_TYPE_INT: type = "SignedInteger"; break;
+ case CONFIG_TYPE_PORT: type = "Port"; break;
+ case CONFIG_TYPE_INTERVAL: type = "TimeInterval"; break;
+ case CONFIG_TYPE_MSEC_INTERVAL: type = "TimeMsecInterval"; break;
+ case CONFIG_TYPE_MEMUNIT: type = "DataSize"; break;
+ case CONFIG_TYPE_DOUBLE: type = "Float"; break;
+ case CONFIG_TYPE_BOOL: type = "Boolean"; break;
+ case CONFIG_TYPE_AUTOBOOL: type = "Boolean+Auto"; break;
+ case CONFIG_TYPE_ISOTIME: type = "Time"; break;
+ case CONFIG_TYPE_ROUTERSET: type = "RouterList"; break;
+ case CONFIG_TYPE_CSV: type = "CommaList"; break;
+ /* This type accepts more inputs than TimeInterval, but it ignores
+ * everything after the first entry, so we may as well pretend
+ * it's a TimeInterval. */
+ case CONFIG_TYPE_CSV_INTERVAL: type = "TimeInterval"; break;
+ case CONFIG_TYPE_LINELIST: type = "LineList"; break;
+ case CONFIG_TYPE_LINELIST_S: type = "Dependent"; break;
+ case CONFIG_TYPE_LINELIST_V: type = "Virtual"; break;
+ default:
+ case CONFIG_TYPE_OBSOLETE:
+ type = NULL; break;
+ }
+ if (!type)
+ continue;
+ smartlist_add_asprintf(sl, "%s %s\n",var->name,type);
+ }
+ *answer = smartlist_join_strings(sl, "", 0, NULL);
+ SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
+ smartlist_free(sl);
+ } else if (!strcmp(question, "config/defaults")) {
+ smartlist_t *sl = smartlist_new();
+ int dirauth_lines_seen = 0, fallback_lines_seen = 0;
+ for (int i = 0; option_vars_[i].name; ++i) {
+ const config_var_t *var = &option_vars_[i];
+ if (var->initvalue != NULL) {
+ if (strcmp(option_vars_[i].name, "DirAuthority") == 0) {
+ /*
+ * Count dirauth lines we have a default for; we'll use the
+ * count later to decide whether to add the defaults manually
+ */
+ ++dirauth_lines_seen;
+ }
+ if (strcmp(option_vars_[i].name, "FallbackDir") == 0) {
+ /*
+ * Similarly count fallback lines, so that we can decided later
+ * to add the defaults manually.
+ */
+ ++fallback_lines_seen;
+ }
+ char *val = esc_for_log(var->initvalue);
+ smartlist_add_asprintf(sl, "%s %s\n",var->name,val);
+ tor_free(val);
+ }
+ }
+
+ if (dirauth_lines_seen == 0) {
+ /*
+ * We didn't see any directory authorities with default values,
+ * so add the list of default authorities manually.
+ */
+
+ /*
+ * default_authorities is defined earlier in this file and
+ * is a const char ** NULL-terminated array of dirauth config
+ * lines.
+ */
+ for (const char **i = default_authorities; *i != NULL; ++i) {
+ char *val = esc_for_log(*i);
+ smartlist_add_asprintf(sl, "DirAuthority %s\n", val);
+ tor_free(val);
+ }
+ }
+
+ if (fallback_lines_seen == 0 &&
+ get_options()->UseDefaultFallbackDirs == 1) {
+ /*
+ * We didn't see any explicitly configured fallback mirrors,
+ * so add the defaults to the list manually.
+ *
+ * default_fallbacks is included earlier in this file and
+ * is a const char ** NULL-terminated array of fallback config lines.
+ */
+ const char **i;
+
+ for (i = default_fallbacks; *i != NULL; ++i) {
+ char *val = esc_for_log(*i);
+ smartlist_add_asprintf(sl, "FallbackDir %s\n", val);
+ tor_free(val);
+ }
+ }
+
+ *answer = smartlist_join_strings(sl, "", 0, NULL);
+ SMARTLIST_FOREACH(sl, char *, c, tor_free(c));
+ smartlist_free(sl);
+ }
+ return 0;
+}
+
+/* Check whether an address has already been set against the options
+ * depending on address family and destination type. Any exsting
+ * value will lead to a fail, even if it is the same value. If not
+ * set and not only validating, copy it into this location too.
+ * Returns 0 on success or -1 if this address is already set.
+ */
+static int
+verify_and_store_outbound_address(sa_family_t family, tor_addr_t *addr,
+ outbound_addr_t type, or_options_t *options, int validate_only)
+{
+ if (type>=OUTBOUND_ADDR_MAX || (family!=AF_INET && family!=AF_INET6)) {
+ return -1;
+ }
+ int fam_index=0;
+ if (family==AF_INET6) {
+ fam_index=1;
+ }
+ tor_addr_t *dest=&options->OutboundBindAddresses[type][fam_index];
+ if (!tor_addr_is_null(dest)) {
+ return -1;
+ }
+ if (!validate_only) {
+ tor_addr_copy(dest, addr);
+ }
+ return 0;
+}
+
+/* Parse a list of address lines for a specific destination type.
+ * Will store them into the options if not validate_only. If a
+ * problem occurs, a suitable error message is store in msg.
+ * Returns 0 on success or -1 if any address is already set.
+ */
+static int
+parse_outbound_address_lines(const config_line_t *lines, outbound_addr_t type,
+ or_options_t *options, int validate_only, char **msg)
+{
+ tor_addr_t addr;
+ sa_family_t family;
+ while (lines) {
+ family = tor_addr_parse(&addr, lines->value);
+ if (verify_and_store_outbound_address(family, &addr, type,
+ options, validate_only)) {
+ if (msg)
+ tor_asprintf(msg, "Multiple%s%s outbound bind addresses "
+ "configured: %s",
+ family==AF_INET?" IPv4":(family==AF_INET6?" IPv6":""),
+ type==OUTBOUND_ADDR_OR?" OR":
+ (type==OUTBOUND_ADDR_EXIT?" exit":""), lines->value);
+ return -1;
+ }
+ lines = lines->next;
+ }
+ return 0;
+}
+
+/** Parse outbound bind address option lines. If <b>validate_only</b>
+ * is not 0 update OutboundBindAddresses in <b>options</b>.
+ * Only one address can be set for any of these values.
+ * On failure, set <b>msg</b> (if provided) to a newly allocated string
+ * containing a description of the problem and return -1.
+ */
+static int
+parse_outbound_addresses(or_options_t *options, int validate_only, char **msg)
+{
+ if (!validate_only) {
+ memset(&options->OutboundBindAddresses, 0,
+ sizeof(options->OutboundBindAddresses));
+ }
+
+ if (parse_outbound_address_lines(options->OutboundBindAddress,
+ OUTBOUND_ADDR_EXIT_AND_OR, options,
+ validate_only, msg) < 0) {
+ goto err;
+ }
+
+ if (parse_outbound_address_lines(options->OutboundBindAddressOR,
+ OUTBOUND_ADDR_OR, options, validate_only,
+ msg) < 0) {
+ goto err;
+ }
+
+ if (parse_outbound_address_lines(options->OutboundBindAddressExit,
+ OUTBOUND_ADDR_EXIT, options, validate_only,
+ msg) < 0) {
+ goto err;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+/** Load one of the geoip files, <a>family</a> determining which
+ * one. <a>default_fname</a> is used if on Windows and
+ * <a>fname</a> equals "<default>". */
+static void
+config_load_geoip_file_(sa_family_t family,
+ const char *fname,
+ const char *default_fname)
+{
+#ifdef _WIN32
+ char *free_fname = NULL; /* Used to hold any temporary-allocated value */
+ /* XXXX Don't use this "<default>" junk; make our filename options
+ * understand prefixes somehow. -NM */
+ if (!strcmp(fname, "<default>")) {
+ const char *conf_root = get_windows_conf_root();
+ tor_asprintf(&free_fname, "%s\\%s", conf_root, default_fname);
+ fname = free_fname;
+ }
+ geoip_load_file(family, fname);
+ tor_free(free_fname);
+#else /* !(defined(_WIN32)) */
+ (void)default_fname;
+ geoip_load_file(family, fname);
+#endif /* defined(_WIN32) */
+}
+
+/** Load geoip files for IPv4 and IPv6 if <a>options</a> and
+ * <a>old_options</a> indicate we should. */
+static void
+config_maybe_load_geoip_files_(const or_options_t *options,
+ const or_options_t *old_options)
+{
+ /* XXXX Reload GeoIPFile on SIGHUP. -NM */
+
+ if (options->GeoIPFile &&
+ ((!old_options || !opt_streq(old_options->GeoIPFile,
+ options->GeoIPFile))
+ || !geoip_is_loaded(AF_INET)))
+ config_load_geoip_file_(AF_INET, options->GeoIPFile, "geoip");
+ if (options->GeoIPv6File &&
+ ((!old_options || !opt_streq(old_options->GeoIPv6File,
+ options->GeoIPv6File))
+ || !geoip_is_loaded(AF_INET6)))
+ config_load_geoip_file_(AF_INET6, options->GeoIPv6File, "geoip6");
+}
+
+/** Initialize cookie authentication (used so far by the ControlPort
+ * and Extended ORPort).
+ *
+ * Allocate memory and create a cookie (of length <b>cookie_len</b>)
+ * in <b>cookie_out</b>.
+ * Then write it down to <b>fname</b> and prepend it with <b>header</b>.
+ *
+ * If <b>group_readable</b> is set, set <b>fname</b> to be readable
+ * by the default GID.
+ *
+ * If the whole procedure was successful, set
+ * <b>cookie_is_set_out</b> to True. */
+int
+init_cookie_authentication(const char *fname, const char *header,
+ int cookie_len, int group_readable,
+ uint8_t **cookie_out, int *cookie_is_set_out)
+{
+ char cookie_file_str_len = strlen(header) + cookie_len;
+ char *cookie_file_str = tor_malloc(cookie_file_str_len);
+ int retval = -1;
+
+ /* We don't want to generate a new cookie every time we call
+ * options_act(). One should be enough. */
+ if (*cookie_is_set_out) {
+ retval = 0; /* we are all set */
+ goto done;
+ }
+
+ /* If we've already set the cookie, free it before re-setting
+ it. This can happen if we previously generated a cookie, but
+ couldn't write it to a disk. */
+ if (*cookie_out)
+ tor_free(*cookie_out);
+
+ /* Generate the cookie */
+ *cookie_out = tor_malloc(cookie_len);
+ crypto_rand((char *)*cookie_out, cookie_len);
+
+ /* Create the string that should be written on the file. */
+ memcpy(cookie_file_str, header, strlen(header));
+ memcpy(cookie_file_str+strlen(header), *cookie_out, cookie_len);
+ if (write_bytes_to_file(fname, cookie_file_str, cookie_file_str_len, 1)) {
+ log_warn(LD_FS,"Error writing auth cookie to %s.", escaped(fname));
+ goto done;
+ }
+
+#ifndef _WIN32
+ if (group_readable) {
+ if (chmod(fname, 0640)) {
+ log_warn(LD_FS,"Unable to make %s group-readable.", escaped(fname));
+ }
+ }
+#else /* !(!defined(_WIN32)) */
+ (void) group_readable;
+#endif /* !defined(_WIN32) */
+
+ /* Success! */
+ log_info(LD_GENERAL, "Generated auth cookie file in '%s'.", escaped(fname));
+ *cookie_is_set_out = 1;
+ retval = 0;
+
+ done:
+ memwipe(cookie_file_str, 0, cookie_file_str_len);
+ tor_free(cookie_file_str);
+ return retval;
+}
+
+/**
+ * Return true if any option is set in <b>options</b> to make us behave
+ * as a client.
+ */
+int
+options_any_client_port_set(const or_options_t *options)
+{
+ return (options->SocksPort_set ||
+ options->TransPort_set ||
+ options->NATDPort_set ||
+ options->DNSPort_set ||
+ options->HTTPTunnelPort_set);
+}
diff --cc src/feature/dirauth/dirvote.c
index ce67c1bb9,000000000..753c42b80
mode 100644,000000..100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@@ -1,4568 -1,0 +1,4609 @@@
+/* Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#define DIRVOTE_PRIVATE
+#include "core/or/or.h"
+#include "app/config/config.h"
+#include "feature/dirauth/dircollate.h"
+#include "feature/dircache/directory.h"
+#include "feature/dircache/dirserv.h"
+#include "feature/nodelist/microdesc.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/nodelist.h"
+#include "feature/nodelist/parsecommon.h"
+#include "core/or/policies.h"
+#include "core/or/protover.h"
+#include "feature/stats/rephist.h"
+#include "feature/relay/router.h"
+#include "feature/relay/routerkeys.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/nodelist/routerparse.h"
+#include "feature/client/entrynodes.h" /* needed for guardfraction methods */
+#include "feature/nodelist/torcert.h"
+#include "feature/dircommon/voting_schedule.h"
+
+#include "feature/dirauth/dirvote.h"
+#include "feature/dirauth/mode.h"
+#include "feature/dirauth/shared_random_state.h"
+
+#include "feature/nodelist/authority_cert_st.h"
+#include "feature/dircache/cached_dir_st.h"
+#include "feature/dirclient/dir_server_st.h"
+#include "feature/nodelist/document_signature_st.h"
+#include "feature/nodelist/microdesc_st.h"
+#include "feature/nodelist/networkstatus_st.h"
+#include "feature/nodelist/networkstatus_voter_info_st.h"
+#include "feature/nodelist/node_st.h"
+#include "feature/dirauth/ns_detached_signatures_st.h"
+#include "feature/nodelist/routerinfo_st.h"
+#include "feature/nodelist/routerlist_st.h"
+#include "feature/dirauth/vote_microdesc_hash_st.h"
+#include "feature/nodelist/vote_routerstatus_st.h"
+#include "feature/dircommon/vote_timing_st.h"
+
+#include "lib/container/order.h"
+#include "lib/encoding/confline.h"
+#include "lib/crypt_ops/crypto_format.h"
+
+/**
+ * \file dirvote.c
+ * \brief Functions to compute directory consensus, and schedule voting.
+ *
+ * This module is the center of the consensus-voting based directory
+ * authority system. With this system, a set of authorities first
+ * publish vote based on their opinions of the network, and then compute
+ * a consensus from those votes. Each authority signs the consensus,
+ * and clients trust the consensus if enough known authorities have
+ * signed it.
+ *
+ * The code in this module is only invoked on directory authorities. It's
+ * responsible for:
+ *
+ * <ul>
+ * <li>Generating this authority's vote networkstatus, based on the
+ * authority's view of the network as represented in dirserv.c
+ * <li>Formatting the vote networkstatus objects.
+ * <li>Generating the microdescriptors that correspond to our own
+ * vote.
+ * <li>Sending votes to all the other authorities.
+ * <li>Trying to fetch missing votes from other authorities.
+ * <li>Computing the consensus from a set of votes, as well as
+ * a "detached signature" object for other authorities to fetch.
+ * <li>Collecting other authorities' signatures on the same consensus,
+ * until there are enough.
+ * <li>Publishing the consensus to the reset of the directory system.
+ * <li>Scheduling all of the above operations.
+ * </ul>
+ *
+ * The main entry points are in dirvote_act(), which handles scheduled
+ * actions; and dirvote_add_vote() and dirvote_add_signatures(), which
+ * handle uploaded and downloaded votes and signatures.
+ *
+ * (See dir-spec.txt from torspec.git for a complete specification of
+ * the directory protocol and voting algorithms.)
+ **/
+
+/** A consensus that we have built and are appending signatures to. Once it's
+ * time to publish it, it will become an active consensus if it accumulates
+ * enough signatures. */
+typedef struct pending_consensus_t {
+ /** The body of the consensus that we're currently building. Once we
+ * have it built, it goes into dirserv.c */
+ char *body;
+ /** The parsed in-progress consensus document. */
+ networkstatus_t *consensus;
+} pending_consensus_t;
+
+/* DOCDOC dirvote_add_signatures_to_all_pending_consensuses */
+static int dirvote_add_signatures_to_all_pending_consensuses(
+ const char *detached_signatures_body,
+ const char *source,
+ const char **msg_out);
+static int dirvote_add_signatures_to_pending_consensus(
+ pending_consensus_t *pc,
+ ns_detached_signatures_t *sigs,
+ const char *source,
+ int severity,
+ const char **msg_out);
+static char *list_v3_auth_ids(void);
+static void dirvote_fetch_missing_votes(void);
+static void dirvote_fetch_missing_signatures(void);
+static int dirvote_perform_vote(void);
+static void dirvote_clear_votes(int all_votes);
+static int dirvote_compute_consensuses(void);
+static int dirvote_publish_consensus(void);
+
+/* =====
+ * Certificate functions
+ * ===== */
+
+/** Allocate and return a new authority_cert_t with the same contents as
+ * <b>cert</b>. */
+STATIC authority_cert_t *
+authority_cert_dup(authority_cert_t *cert)
+{
+ authority_cert_t *out = tor_malloc(sizeof(authority_cert_t));
+ tor_assert(cert);
+
+ memcpy(out, cert, sizeof(authority_cert_t));
+ /* Now copy pointed-to things. */
+ out->cache_info.signed_descriptor_body =
+ tor_strndup(cert->cache_info.signed_descriptor_body,
+ cert->cache_info.signed_descriptor_len);
+ out->cache_info.saved_location = SAVED_NOWHERE;
+ out->identity_key = crypto_pk_dup_key(cert->identity_key);
+ out->signing_key = crypto_pk_dup_key(cert->signing_key);
+
+ return out;
+}
+
+/* =====
+ * Voting
+ * =====*/
+
+/* If <b>opt_value</b> is non-NULL, return "keyword opt_value\n" in a new
+ * string. Otherwise return a new empty string. */
+static char *
+format_line_if_present(const char *keyword, const char *opt_value)
+{
+ if (opt_value) {
+ char *result = NULL;
+ tor_asprintf(&result, "%s %s\n", keyword, opt_value);
+ return result;
+ } else {
+ return tor_strdup("");
+ }
+}
+
+/** Format the recommended/required-relay-client protocols lines for a vote in
+ * a newly allocated string, and return that string. */
+static char *
+format_protocols_lines_for_vote(const networkstatus_t *v3_ns)
+{
+ char *recommended_relay_protocols_line = NULL;
+ char *recommended_client_protocols_line = NULL;
+ char *required_relay_protocols_line = NULL;
+ char *required_client_protocols_line = NULL;
+
+ recommended_relay_protocols_line =
+ format_line_if_present("recommended-relay-protocols",
+ v3_ns->recommended_relay_protocols);
+ recommended_client_protocols_line =
+ format_line_if_present("recommended-client-protocols",
+ v3_ns->recommended_client_protocols);
+ required_relay_protocols_line =
+ format_line_if_present("required-relay-protocols",
+ v3_ns->required_relay_protocols);
+ required_client_protocols_line =
+ format_line_if_present("required-client-protocols",
+ v3_ns->required_client_protocols);
+
+ char *result = NULL;
+ tor_asprintf(&result, "%s%s%s%s",
+ recommended_relay_protocols_line,
+ recommended_client_protocols_line,
+ required_relay_protocols_line,
+ required_client_protocols_line);
+
+ tor_free(recommended_relay_protocols_line);
+ tor_free(recommended_client_protocols_line);
+ tor_free(required_relay_protocols_line);
+ tor_free(required_client_protocols_line);
+
+ return result;
+}
+
+/** Return a new string containing the string representation of the vote in
+ * <b>v3_ns</b>, signed with our v3 signing key <b>private_signing_key</b>.
+ * For v3 authorities. */
+STATIC char *
+format_networkstatus_vote(crypto_pk_t *private_signing_key,
+ networkstatus_t *v3_ns)
+{
+ smartlist_t *chunks = smartlist_new();
+ char *packages = NULL;
+ char fingerprint[FINGERPRINT_LEN+1];
+ char digest[DIGEST_LEN];
+ uint32_t addr;
+ char *protocols_lines = NULL;
+ char *client_versions_line = NULL, *server_versions_line = NULL;
+ char *shared_random_vote_str = NULL;
+ networkstatus_voter_info_t *voter;
+ char *status = NULL;
+
+ tor_assert(private_signing_key);
+ tor_assert(v3_ns->type == NS_TYPE_VOTE || v3_ns->type == NS_TYPE_OPINION);
+
+ voter = smartlist_get(v3_ns->voters, 0);
+
+ addr = voter->addr;
+
+ base16_encode(fingerprint, sizeof(fingerprint),
+ v3_ns->cert->cache_info.identity_digest, DIGEST_LEN);
+
+ client_versions_line = format_line_if_present("client-versions",
+ v3_ns->client_versions);
+ server_versions_line = format_line_if_present("server-versions",
+ v3_ns->server_versions);
+ protocols_lines = format_protocols_lines_for_vote(v3_ns);
+
+ if (v3_ns->package_lines) {
+ smartlist_t *tmp = smartlist_new();
+ SMARTLIST_FOREACH(v3_ns->package_lines, const char *, p,
+ if (validate_recommended_package_line(p))
+ smartlist_add_asprintf(tmp, "package %s\n", p));
+ smartlist_sort_strings(tmp);
+ packages = smartlist_join_strings(tmp, "", 0, NULL);
+ SMARTLIST_FOREACH(tmp, char *, cp, tor_free(cp));
+ smartlist_free(tmp);
+ } else {
+ packages = tor_strdup("");
+ }
+
+ /* Get shared random commitments/reveals line(s). */
+ shared_random_vote_str = sr_get_string_for_vote();
+
+ {
+ char published[ISO_TIME_LEN+1];
+ char va[ISO_TIME_LEN+1];
+ char fu[ISO_TIME_LEN+1];
+ char vu[ISO_TIME_LEN+1];
+ char *flags = smartlist_join_strings(v3_ns->known_flags, " ", 0, NULL);
+ /* XXXX Abstraction violation: should be pulling a field out of v3_ns.*/
+ char *flag_thresholds = dirserv_get_flag_thresholds_line();
+ char *params;
+ authority_cert_t *cert = v3_ns->cert;
+ char *methods =
+ make_consensus_method_list(MIN_SUPPORTED_CONSENSUS_METHOD,
+ MAX_SUPPORTED_CONSENSUS_METHOD, " ");
+ format_iso_time(published, v3_ns->published);
+ format_iso_time(va, v3_ns->valid_after);
+ format_iso_time(fu, v3_ns->fresh_until);
+ format_iso_time(vu, v3_ns->valid_until);
+
+ if (v3_ns->net_params)
+ params = smartlist_join_strings(v3_ns->net_params, " ", 0, NULL);
+ else
+ params = tor_strdup("");
+
+ tor_assert(cert);
+ smartlist_add_asprintf(chunks,
+ "network-status-version 3\n"
+ "vote-status %s\n"
+ "consensus-methods %s\n"
+ "published %s\n"
+ "valid-after %s\n"
+ "fresh-until %s\n"
+ "valid-until %s\n"
+ "voting-delay %d %d\n"
+ "%s%s" /* versions */
+ "%s" /* protocols */
+ "%s" /* packages */
+ "known-flags %s\n"
+ "flag-thresholds %s\n"
+ "params %s\n"
+ "dir-source %s %s %s %s %d %d\n"
+ "contact %s\n"
+ "%s", /* shared randomness information */
+ v3_ns->type == NS_TYPE_VOTE ? "vote" : "opinion",
+ methods,
+ published, va, fu, vu,
+ v3_ns->vote_seconds, v3_ns->dist_seconds,
+ client_versions_line,
+ server_versions_line,
+ protocols_lines,
+ packages,
+ flags,
+ flag_thresholds,
+ params,
+ voter->nickname, fingerprint, voter->address,
+ fmt_addr32(addr), voter->dir_port, voter->or_port,
+ voter->contact,
+ shared_random_vote_str ?
+ shared_random_vote_str : "");
+
+ tor_free(params);
+ tor_free(flags);
+ tor_free(flag_thresholds);
+ tor_free(methods);
+ tor_free(shared_random_vote_str);
+
+ if (!tor_digest_is_zero(voter->legacy_id_digest)) {
+ char fpbuf[HEX_DIGEST_LEN+1];
+ base16_encode(fpbuf, sizeof(fpbuf), voter->legacy_id_digest, DIGEST_LEN);
+ smartlist_add_asprintf(chunks, "legacy-dir-key %s\n", fpbuf);
+ }
+
+ smartlist_add(chunks, tor_strndup(cert->cache_info.signed_descriptor_body,
+ cert->cache_info.signed_descriptor_len));
+ }
+
+ SMARTLIST_FOREACH_BEGIN(v3_ns->routerstatus_list, vote_routerstatus_t *,
+ vrs) {
+ char *rsf;
+ vote_microdesc_hash_t *h;
+ rsf = routerstatus_format_entry(&vrs->status,
+ vrs->version, vrs->protocols,
+ NS_V3_VOTE,
+ ROUTERSTATUS_FORMAT_NO_CONSENSUS_METHOD,
+ vrs);
+ if (rsf)
+ smartlist_add(chunks, rsf);
+
+ for (h = vrs->microdesc; h; h = h->next) {
+ smartlist_add_strdup(chunks, h->microdesc_hash_line);
+ }
+ } SMARTLIST_FOREACH_END(vrs);
+
+ smartlist_add_strdup(chunks, "directory-footer\n");
+
+ /* The digest includes everything up through the space after
+ * directory-signature. (Yuck.) */
+ crypto_digest_smartlist(digest, DIGEST_LEN, chunks,
+ "directory-signature ", DIGEST_SHA1);
+
+ {
+ char signing_key_fingerprint[FINGERPRINT_LEN+1];
+ if (crypto_pk_get_fingerprint(private_signing_key,
+ signing_key_fingerprint, 0)<0) {
+ log_warn(LD_BUG, "Unable to get fingerprint for signing key");
+ goto err;
+ }
+
+ smartlist_add_asprintf(chunks, "directory-signature %s %s\n", fingerprint,
+ signing_key_fingerprint);
+ }
+
+ {
+ char *sig = router_get_dirobj_signature(digest, DIGEST_LEN,
+ private_signing_key);
+ if (!sig) {
+ log_warn(LD_BUG, "Unable to sign networkstatus vote.");
+ goto err;
+ }
+ smartlist_add(chunks, sig);
+ }
+
+ status = smartlist_join_strings(chunks, "", 0, NULL);
+
+ {
+ networkstatus_t *v;
+ if (!(v = networkstatus_parse_vote_from_string(status, NULL,
+ v3_ns->type))) {
+ log_err(LD_BUG,"Generated a networkstatus %s we couldn't parse: "
+ "<<%s>>",
+ v3_ns->type == NS_TYPE_VOTE ? "vote" : "opinion", status);
+ goto err;
+ }
+ networkstatus_vote_free(v);
+ }
+
+ goto done;
+
+ err:
+ tor_free(status);
+ done:
+ tor_free(client_versions_line);
+ tor_free(server_versions_line);
+ tor_free(protocols_lines);
+ tor_free(packages);
+
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_free(chunks);
+ return status;
+}
+
+/** Set *<b>timing_out</b> to the intervals at which we would like to vote.
+ * Note that these aren't the intervals we'll use to vote; they're the ones
+ * that we'll vote to use. */
+static void
+dirvote_get_preferred_voting_intervals(vote_timing_t *timing_out)
+{
+ const or_options_t *options = get_options();
+
+ tor_assert(timing_out);
+
+ timing_out->vote_interval = options->V3AuthVotingInterval;
+ timing_out->n_intervals_valid = options->V3AuthNIntervalsValid;
+ timing_out->vote_delay = options->V3AuthVoteDelay;
+ timing_out->dist_delay = options->V3AuthDistDelay;
+}
+
+/* =====
+ * Consensus generation
+ * ===== */
+
+/** If <b>vrs</b> has a hash made for the consensus method <b>method</b> with
+ * the digest algorithm <b>alg</b>, decode it and copy it into
+ * <b>digest256_out</b> and return 0. Otherwise return -1. */
+static int
+vote_routerstatus_find_microdesc_hash(char *digest256_out,
+ const vote_routerstatus_t *vrs,
+ int method,
+ digest_algorithm_t alg)
+{
+ /* XXXX only returns the sha256 method. */
+ const vote_microdesc_hash_t *h;
+ char mstr[64];
+ size_t mlen;
+ char dstr[64];
+
+ tor_snprintf(mstr, sizeof(mstr), "%d", method);
+ mlen = strlen(mstr);
+ tor_snprintf(dstr, sizeof(dstr), " %s=",
+ crypto_digest_algorithm_get_name(alg));
+
+ for (h = vrs->microdesc; h; h = h->next) {
+ const char *cp = h->microdesc_hash_line;
+ size_t num_len;
+ /* cp looks like \d+(,\d+)* (digesttype=val )+ . Let's hunt for mstr in
+ * the first part. */
+ while (1) {
+ num_len = strspn(cp, "1234567890");
+ if (num_len == mlen && fast_memeq(mstr, cp, mlen)) {
+ /* This is the line. */
+ char buf[BASE64_DIGEST256_LEN+1];
+ /* XXXX ignores extraneous stuff if the digest is too long. This
+ * seems harmless enough, right? */
+ cp = strstr(cp, dstr);
+ if (!cp)
+ return -1;
+ cp += strlen(dstr);
+ strlcpy(buf, cp, sizeof(buf));
+ return digest256_from_base64(digest256_out, buf);
+ }
+ if (num_len == 0 || cp[num_len] != ',')
+ break;
+ cp += num_len + 1;
+ }
+ }
+ return -1;
+}
+
+/** Given a vote <b>vote</b> (not a consensus!), return its associated
+ * networkstatus_voter_info_t. */
+static networkstatus_voter_info_t *
+get_voter(const networkstatus_t *vote)
+{
+ tor_assert(vote);
+ tor_assert(vote->type == NS_TYPE_VOTE);
+ tor_assert(vote->voters);
+ tor_assert(smartlist_len(vote->voters) == 1);
+ return smartlist_get(vote->voters, 0);
+}
+
+/** Temporary structure used in constructing a list of dir-source entries
+ * for a consensus. One of these is generated for every vote, and one more
+ * for every legacy key in each vote. */
+typedef struct dir_src_ent_t {
+ networkstatus_t *v;
+ const char *digest;
+ int is_legacy;
+} dir_src_ent_t;
+
+/** Helper for sorting networkstatus_t votes (not consensuses) by the
+ * hash of their voters' identity digests. */
+static int
+compare_votes_by_authority_id_(const void **_a, const void **_b)
+{
+ const networkstatus_t *a = *_a, *b = *_b;
+ return fast_memcmp(get_voter(a)->identity_digest,
+ get_voter(b)->identity_digest, DIGEST_LEN);
+}
+
+/** Helper: Compare the dir_src_ent_ts in *<b>_a</b> and *<b>_b</b> by
+ * their identity digests, and return -1, 0, or 1 depending on their
+ * ordering */
+static int
+compare_dir_src_ents_by_authority_id_(const void **_a, const void **_b)
+{
+ const dir_src_ent_t *a = *_a, *b = *_b;
+ const networkstatus_voter_info_t *a_v = get_voter(a->v),
+ *b_v = get_voter(b->v);
+ const char *a_id, *b_id;
+ a_id = a->is_legacy ? a_v->legacy_id_digest : a_v->identity_digest;
+ b_id = b->is_legacy ? b_v->legacy_id_digest : b_v->identity_digest;
+
+ return fast_memcmp(a_id, b_id, DIGEST_LEN);
+}
+
+/** Given a sorted list of strings <b>in</b>, add every member to <b>out</b>
+ * that occurs more than <b>min</b> times. */
+static void
+get_frequent_members(smartlist_t *out, smartlist_t *in, int min)
+{
+ char *cur = NULL;
+ int count = 0;
+ SMARTLIST_FOREACH_BEGIN(in, char *, cp) {
+ if (cur && !strcmp(cp, cur)) {
+ ++count;
+ } else {
+ if (count > min)
+ smartlist_add(out, cur);
+ cur = cp;
+ count = 1;
+ }
+ } SMARTLIST_FOREACH_END(cp);
+ if (count > min)
+ smartlist_add(out, cur);
+}
+
+/** Given a sorted list of strings <b>lst</b>, return the member that appears
+ * most. Break ties in favor of later-occurring members. */
+#define get_most_frequent_member(lst) \
+ smartlist_get_most_frequent_string(lst)
+
+/** Return 0 if and only if <b>a</b> and <b>b</b> are routerstatuses
+ * that come from the same routerinfo, with the same derived elements.
+ */
+static int
+compare_vote_rs(const vote_routerstatus_t *a, const vote_routerstatus_t *b)
+{
+ int r;
+ tor_assert(a);
+ tor_assert(b);
+
+ if ((r = fast_memcmp(a->status.identity_digest, b->status.identity_digest,
+ DIGEST_LEN)))
+ return r;
+ if ((r = fast_memcmp(a->status.descriptor_digest,
+ b->status.descriptor_digest,
+ DIGEST_LEN)))
+ return r;
+ /* If we actually reached this point, then the identities and
+ * the descriptor digests matched, so somebody is making SHA1 collisions.
+ */
+#define CMP_FIELD(utype, itype, field) do { \
+ utype aval = (utype) (itype) a->status.field; \
+ utype bval = (utype) (itype) b->status.field; \
+ utype u = bval - aval; \
+ itype r2 = (itype) u; \
+ if (r2 < 0) { \
+ return -1; \
+ } else if (r2 > 0) { \
+ return 1; \
+ } \
+ } while (0)
+
+ CMP_FIELD(uint64_t, int64_t, published_on);
+
+ if ((r = strcmp(b->status.nickname, a->status.nickname)))
+ return r;
+
+ CMP_FIELD(unsigned, int, addr);
+ CMP_FIELD(unsigned, int, or_port);
+ CMP_FIELD(unsigned, int, dir_port);
+
+ return 0;
+}
+
+/** Helper for sorting routerlists based on compare_vote_rs. */
+static int
+compare_vote_rs_(const void **_a, const void **_b)
+{
+ const vote_routerstatus_t *a = *_a, *b = *_b;
+ return compare_vote_rs(a,b);
+}
+
+/** Helper for sorting OR ports. */
+static int
+compare_orports_(const void **_a, const void **_b)
+{
+ const tor_addr_port_t *a = *_a, *b = *_b;
+ int r;
+
+ if ((r = tor_addr_compare(&a->addr, &b->addr, CMP_EXACT)))
+ return r;
+ if ((r = (((int) b->port) - ((int) a->port))))
+ return r;
+
+ return 0;
+}
+
+/** Given a list of vote_routerstatus_t, all for the same router identity,
+ * return whichever is most frequent, breaking ties in favor of more
+ * recently published vote_routerstatus_t and in case of ties there,
+ * in favor of smaller descriptor digest.
+ */
+static vote_routerstatus_t *
+compute_routerstatus_consensus(smartlist_t *votes, int consensus_method,
+ char *microdesc_digest256_out,
+ tor_addr_port_t *best_alt_orport_out)
+{
+ vote_routerstatus_t *most = NULL, *cur = NULL;
+ int most_n = 0, cur_n = 0;
+ time_t most_published = 0;
+
+ /* compare_vote_rs_() sorts the items by identity digest (all the same),
+ * then by SD digest. That way, if we have a tie that the published_on
+ * date cannot break, we use the descriptor with the smaller digest.
+ */
+ smartlist_sort(votes, compare_vote_rs_);
+ SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) {
+ if (cur && !compare_vote_rs(cur, rs)) {
+ ++cur_n;
+ } else {
+ if (cur && (cur_n > most_n ||
+ (cur_n == most_n &&
+ cur->status.published_on > most_published))) {
+ most = cur;
+ most_n = cur_n;
+ most_published = cur->status.published_on;
+ }
+ cur_n = 1;
+ cur = rs;
+ }
+ } SMARTLIST_FOREACH_END(rs);
+
+ if (cur_n > most_n ||
+ (cur && cur_n == most_n && cur->status.published_on > most_published)) {
+ most = cur;
+ // most_n = cur_n; // unused after this point.
+ // most_published = cur->status.published_on; // unused after this point.
+ }
+
+ tor_assert(most);
+
+ /* Vote on potential alternative (sets of) OR port(s) in the winning
+ * routerstatuses.
+ *
+ * XXX prop186 There's at most one alternative OR port (_the_ IPv6
+ * port) for now. */
+ if (best_alt_orport_out) {
+ smartlist_t *alt_orports = smartlist_new();
+ const tor_addr_port_t *most_alt_orport = NULL;
+
+ SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) {
+ tor_assert(rs);
+ if (compare_vote_rs(most, rs) == 0 &&
+ !tor_addr_is_null(&rs->status.ipv6_addr)
+ && rs->status.ipv6_orport) {
+ smartlist_add(alt_orports, tor_addr_port_new(&rs->status.ipv6_addr,
+ rs->status.ipv6_orport));
+ }
+ } SMARTLIST_FOREACH_END(rs);
+
+ smartlist_sort(alt_orports, compare_orports_);
+ most_alt_orport = smartlist_get_most_frequent(alt_orports,
+ compare_orports_);
+ if (most_alt_orport) {
+ memcpy(best_alt_orport_out, most_alt_orport, sizeof(tor_addr_port_t));
+ log_debug(LD_DIR, "\"a\" line winner for %s is %s",
+ most->status.nickname,
+ fmt_addrport(&most_alt_orport->addr, most_alt_orport->port));
+ }
+
+ SMARTLIST_FOREACH(alt_orports, tor_addr_port_t *, ap, tor_free(ap));
+ smartlist_free(alt_orports);
+ }
+
+ if (microdesc_digest256_out) {
+ smartlist_t *digests = smartlist_new();
+ const uint8_t *best_microdesc_digest;
+ SMARTLIST_FOREACH_BEGIN(votes, vote_routerstatus_t *, rs) {
+ char d[DIGEST256_LEN];
+ if (compare_vote_rs(rs, most))
+ continue;
+ if (!vote_routerstatus_find_microdesc_hash(d, rs, consensus_method,
+ DIGEST_SHA256))
+ smartlist_add(digests, tor_memdup(d, sizeof(d)));
+ } SMARTLIST_FOREACH_END(rs);
+ smartlist_sort_digests256(digests);
+ best_microdesc_digest = smartlist_get_most_frequent_digest256(digests);
+ if (best_microdesc_digest)
+ memcpy(microdesc_digest256_out, best_microdesc_digest, DIGEST256_LEN);
+ SMARTLIST_FOREACH(digests, char *, cp, tor_free(cp));
+ smartlist_free(digests);
+ }
+
+ return most;
+}
+
+/** Sorting helper: compare two strings based on their values as base-ten
+ * positive integers. (Non-integers are treated as prior to all integers, and
+ * compared lexically.) */
+static int
+cmp_int_strings_(const void **_a, const void **_b)
+{
+ const char *a = *_a, *b = *_b;
+ int ai = (int)tor_parse_long(a, 10, 1, INT_MAX, NULL, NULL);
+ int bi = (int)tor_parse_long(b, 10, 1, INT_MAX, NULL, NULL);
+ if (ai<bi) {
+ return -1;
+ } else if (ai==bi) {
+ if (ai == 0) /* Parsing failed. */
+ return strcmp(a, b);
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/** Given a list of networkstatus_t votes, determine and return the number of
+ * the highest consensus method that is supported by 2/3 of the voters. */
+static int
+compute_consensus_method(smartlist_t *votes)
+{
+ smartlist_t *all_methods = smartlist_new();
+ smartlist_t *acceptable_methods = smartlist_new();
+ smartlist_t *tmp = smartlist_new();
+ int min = (smartlist_len(votes) * 2) / 3;
+ int n_ok;
+ int result;
+ SMARTLIST_FOREACH(votes, networkstatus_t *, vote,
+ {
+ tor_assert(vote->supported_methods);
+ smartlist_add_all(tmp, vote->supported_methods);
+ smartlist_sort(tmp, cmp_int_strings_);
+ smartlist_uniq(tmp, cmp_int_strings_, NULL);
+ smartlist_add_all(all_methods, tmp);
+ smartlist_clear(tmp);
+ });
+
+ smartlist_sort(all_methods, cmp_int_strings_);
+ get_frequent_members(acceptable_methods, all_methods, min);
+ n_ok = smartlist_len(acceptable_methods);
+ if (n_ok) {
+ const char *best = smartlist_get(acceptable_methods, n_ok-1);
+ result = (int)tor_parse_long(best, 10, 1, INT_MAX, NULL, NULL);
+ } else {
+ result = 1;
+ }
+ smartlist_free(tmp);
+ smartlist_free(all_methods);
+ smartlist_free(acceptable_methods);
+ return result;
+}
+
+/** Return true iff <b>method</b> is a consensus method that we support. */
+static int
+consensus_method_is_supported(int method)
+{
+ return (method >= MIN_SUPPORTED_CONSENSUS_METHOD) &&
+ (method <= MAX_SUPPORTED_CONSENSUS_METHOD);
+}
+
+/** Return a newly allocated string holding the numbers between low and high
+ * (inclusive) that are supported consensus methods. */
+STATIC char *
+make_consensus_method_list(int low, int high, const char *separator)
+{
+ char *list;
+
+ int i;
+ smartlist_t *lst;
+ lst = smartlist_new();
+ for (i = low; i <= high; ++i) {
+ if (!consensus_method_is_supported(i))
+ continue;
+ smartlist_add_asprintf(lst, "%d", i);
+ }
+ list = smartlist_join_strings(lst, separator, 0, NULL);
+ tor_assert(list);
+ SMARTLIST_FOREACH(lst, char *, cp, tor_free(cp));
+ smartlist_free(lst);
+ return list;
+}
+
+/** Helper: given <b>lst</b>, a list of version strings such that every
+ * version appears once for every versioning voter who recommends it, return a
+ * newly allocated string holding the resulting client-versions or
+ * server-versions list. May change contents of <b>lst</b> */
+static char *
+compute_consensus_versions_list(smartlist_t *lst, int n_versioning)
+{
+ int min = n_versioning / 2;
+ smartlist_t *good = smartlist_new();
+ char *result;
++ SMARTLIST_FOREACH_BEGIN(lst, const char *, v) {
++ if (strchr(v, ' ')) {
++ log_warn(LD_DIR, "At least one authority has voted for a version %s "
++ "that contains a space. This probably wasn't intentional, and "
++ "is likely to cause trouble. Please tell them to stop it.",
++ escaped(v));
++ }
++ } SMARTLIST_FOREACH_END(v);
+ sort_version_list(lst, 0);
+ get_frequent_members(good, lst, min);
+ result = smartlist_join_strings(good, ",", 0, NULL);
+ smartlist_free(good);
+ return result;
+}
+
+/** Given a list of K=V values, return the int32_t value corresponding to
+ * KEYWORD=, or default_val if no such value exists, or if the value is
+ * corrupt.
+ */
+STATIC int32_t
+dirvote_get_intermediate_param_value(const smartlist_t *param_list,
+ const char *keyword,
+ int32_t default_val)
+{
+ unsigned int n_found = 0;
+ int32_t value = default_val;
+
+ SMARTLIST_FOREACH_BEGIN(param_list, const char *, k_v_pair) {
+ if (!strcmpstart(k_v_pair, keyword) && k_v_pair[strlen(keyword)] == '=') {
+ const char *integer_str = &k_v_pair[strlen(keyword)+1];
+ int ok;
+ value = (int32_t)
+ tor_parse_long(integer_str, 10, INT32_MIN, INT32_MAX, &ok, NULL);
+ if (BUG(! ok))
+ return default_val;
+ ++n_found;
+ }
+ } SMARTLIST_FOREACH_END(k_v_pair);
+
+ if (n_found == 1) {
+ return value;
+ } else {
+ tor_assert_nonfatal(n_found == 0);
+ return default_val;
+ }
+}
+
+/** Minimum number of directory authorities voting for a parameter to
+ * include it in the consensus, if consensus method 12 or later is to be
+ * used. See proposal 178 for details. */
+#define MIN_VOTES_FOR_PARAM 3
+
+/** Helper: given a list of valid networkstatus_t, return a new smartlist
+ * containing the contents of the consensus network parameter set.
+ */
+STATIC smartlist_t *
+dirvote_compute_params(smartlist_t *votes, int method, int total_authorities)
+{
+ int i;
+ int32_t *vals;
+
+ int cur_param_len;
+ const char *cur_param;
+ const char *eq;
+
+ const int n_votes = smartlist_len(votes);
+ smartlist_t *output;
+ smartlist_t *param_list = smartlist_new();
+ (void) method;
+
+ /* We require that the parameter lists in the votes are well-formed: that
+ is, that their keywords are unique and sorted, and that their values are
+ between INT32_MIN and INT32_MAX inclusive. This should be guaranteed by
+ the parsing code. */
+
+ vals = tor_calloc(n_votes, sizeof(int));
+
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ if (!v->net_params)
+ continue;
+ smartlist_add_all(param_list, v->net_params);
+ } SMARTLIST_FOREACH_END(v);
+
+ if (smartlist_len(param_list) == 0) {
+ tor_free(vals);
+ return param_list;
+ }
+
+ smartlist_sort_strings(param_list);
+ i = 0;
+ cur_param = smartlist_get(param_list, 0);
+ eq = strchr(cur_param, '=');
+ tor_assert(eq);
+ cur_param_len = (int)(eq+1 - cur_param);
+
+ output = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(param_list, const char *, param) {
+ /* resolve spurious clang shallow analysis null pointer errors */
+ tor_assert(param);
+
+ const char *next_param;
+ int ok=0;
+ eq = strchr(param, '=');
+ tor_assert(i<n_votes); /* Make sure we prevented vote-stuffing. */
+ vals[i++] = (int32_t)
+ tor_parse_long(eq+1, 10, INT32_MIN, INT32_MAX, &ok, NULL);
+ tor_assert(ok); /* Already checked these when parsing. */
+
+ if (param_sl_idx+1 == smartlist_len(param_list))
+ next_param = NULL;
+ else
+ next_param = smartlist_get(param_list, param_sl_idx+1);
+
+ if (!next_param || strncmp(next_param, param, cur_param_len)) {
+ /* We've reached the end of a series. */
+ /* Make sure enough authorities voted on this param, unless the
+ * the consensus method we use is too old for that. */
+ if (i > total_authorities/2 ||
+ i >= MIN_VOTES_FOR_PARAM) {
+ int32_t median = median_int32(vals, i);
+ char *out_string = tor_malloc(64+cur_param_len);
+ memcpy(out_string, param, cur_param_len);
+ tor_snprintf(out_string+cur_param_len,64, "%ld", (long)median);
+ smartlist_add(output, out_string);
+ }
+
+ i = 0;
+ if (next_param) {
+ eq = strchr(next_param, '=');
+ cur_param_len = (int)(eq+1 - next_param);
+ }
+ }
+ } SMARTLIST_FOREACH_END(param);
+
+ smartlist_free(param_list);
+ tor_free(vals);
+ return output;
+}
+
+#define RANGE_CHECK(a,b,c,d,e,f,g,mx) \
+ ((a) >= 0 && (a) <= (mx) && (b) >= 0 && (b) <= (mx) && \
+ (c) >= 0 && (c) <= (mx) && (d) >= 0 && (d) <= (mx) && \
+ (e) >= 0 && (e) <= (mx) && (f) >= 0 && (f) <= (mx) && \
+ (g) >= 0 && (g) <= (mx))
+
+#define CHECK_EQ(a, b, margin) \
+ ((a)-(b) >= 0 ? (a)-(b) <= (margin) : (b)-(a) <= (margin))
+
+typedef enum {
+ BW_WEIGHTS_NO_ERROR = 0,
+ BW_WEIGHTS_RANGE_ERROR = 1,
+ BW_WEIGHTS_SUMG_ERROR = 2,
+ BW_WEIGHTS_SUME_ERROR = 3,
+ BW_WEIGHTS_SUMD_ERROR = 4,
+ BW_WEIGHTS_BALANCE_MID_ERROR = 5,
+ BW_WEIGHTS_BALANCE_EG_ERROR = 6
+} bw_weights_error_t;
+
+/**
+ * Verify that any weightings satisfy the balanced formulas.
+ */
+static bw_weights_error_t
+networkstatus_check_weights(int64_t Wgg, int64_t Wgd, int64_t Wmg,
+ int64_t Wme, int64_t Wmd, int64_t Wee,
+ int64_t Wed, int64_t scale, int64_t G,
+ int64_t M, int64_t E, int64_t D, int64_t T,
+ int64_t margin, int do_balance) {
+ bw_weights_error_t berr = BW_WEIGHTS_NO_ERROR;
+
+ // Wed + Wmd + Wgd == 1
+ if (!CHECK_EQ(Wed + Wmd + Wgd, scale, margin)) {
+ berr = BW_WEIGHTS_SUMD_ERROR;
+ goto out;
+ }
+
+ // Wmg + Wgg == 1
+ if (!CHECK_EQ(Wmg + Wgg, scale, margin)) {
+ berr = BW_WEIGHTS_SUMG_ERROR;
+ goto out;
+ }
+
+ // Wme + Wee == 1
+ if (!CHECK_EQ(Wme + Wee, scale, margin)) {
+ berr = BW_WEIGHTS_SUME_ERROR;
+ goto out;
+ }
+
+ // Verify weights within range 0->1
+ if (!RANGE_CHECK(Wgg, Wgd, Wmg, Wme, Wmd, Wed, Wee, scale)) {
+ berr = BW_WEIGHTS_RANGE_ERROR;
+ goto out;
+ }
+
+ if (do_balance) {
+ // Wgg*G + Wgd*D == Wee*E + Wed*D, already scaled
+ if (!CHECK_EQ(Wgg*G + Wgd*D, Wee*E + Wed*D, (margin*T)/3)) {
+ berr = BW_WEIGHTS_BALANCE_EG_ERROR;
+ goto out;
+ }
+
+ // Wgg*G + Wgd*D == M*scale + Wmd*D + Wme*E + Wmg*G, already scaled
+ if (!CHECK_EQ(Wgg*G + Wgd*D, M*scale + Wmd*D + Wme*E + Wmg*G,
+ (margin*T)/3)) {
+ berr = BW_WEIGHTS_BALANCE_MID_ERROR;
+ goto out;
+ }
+ }
+
+ out:
+ if (berr) {
+ log_info(LD_DIR,
+ "Bw weight mismatch %d. G=%"PRId64" M=%"PRId64
+ " E=%"PRId64" D=%"PRId64" T=%"PRId64
+ " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
+ " Wgd=%d Wgg=%d Wme=%d Wmg=%d",
+ berr,
+ (G), (M), (E),
+ (D), (T),
+ (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
+ (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg);
+ }
+
+ return berr;
+}
+
+/**
+ * This function computes the bandwidth weights for consensus method 10.
+ *
+ * It returns true if weights could be computed, false otherwise.
+ */
+int
+networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G,
+ int64_t M, int64_t E, int64_t D,
+ int64_t T, int64_t weight_scale)
+{
+ bw_weights_error_t berr = 0;
+ int64_t Wgg = -1, Wgd = -1;
+ int64_t Wmg = -1, Wme = -1, Wmd = -1;
+ int64_t Wed = -1, Wee = -1;
+ const char *casename;
+
+ if (G <= 0 || M <= 0 || E <= 0 || D <= 0) {
+ log_warn(LD_DIR, "Consensus with empty bandwidth: "
+ "G=%"PRId64" M=%"PRId64" E=%"PRId64
+ " D=%"PRId64" T=%"PRId64,
+ (G), (M), (E),
+ (D), (T));
+ return 0;
+ }
+
+ /*
+ * Computed from cases in 3.8.3 of dir-spec.txt
+ *
+ * 1. Neither are scarce
+ * 2. Both Guard and Exit are scarce
+ * a. R+D <= S
+ * b. R+D > S
+ * 3. One of Guard or Exit is scarce
+ * a. S+D < T/3
+ * b. S+D >= T/3
+ */
+ if (3*E >= T && 3*G >= T) { // E >= T/3 && G >= T/3
+ /* Case 1: Neither are scarce. */
+ casename = "Case 1 (Wgd=Wmd=Wed)";
+ Wgd = weight_scale/3;
+ Wed = weight_scale/3;
+ Wmd = weight_scale/3;
+ Wee = (weight_scale*(E+G+M))/(3*E);
+ Wme = weight_scale - Wee;
+ Wmg = (weight_scale*(2*G-E-M))/(3*G);
+ Wgg = weight_scale - Wmg;
+
+ berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed,
+ weight_scale, G, M, E, D, T, 10, 1);
+
+ if (berr) {
+ log_warn(LD_DIR,
+ "Bw Weights error %d for %s v10. G=%"PRId64" M=%"PRId64
+ " E=%"PRId64" D=%"PRId64" T=%"PRId64
+ " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
+ " Wgd=%d Wgg=%d Wme=%d Wmg=%d weight_scale=%d",
+ berr, casename,
+ (G), (M), (E),
+ (D), (T),
+ (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
+ (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg, (int)weight_scale);
+ return 0;
+ }
+ } else if (3*E < T && 3*G < T) { // E < T/3 && G < T/3
+ int64_t R = MIN(E, G);
+ int64_t S = MAX(E, G);
+ /*
+ * Case 2: Both Guards and Exits are scarce
+ * Balance D between E and G, depending upon
+ * D capacity and scarcity.
+ */
+ if (R+D < S) { // Subcase a
+ Wgg = weight_scale;
+ Wee = weight_scale;
+ Wmg = 0;
+ Wme = 0;
+ Wmd = 0;
+ if (E < G) {
+ casename = "Case 2a (E scarce)";
+ Wed = weight_scale;
+ Wgd = 0;
+ } else { /* E >= G */
+ casename = "Case 2a (G scarce)";
+ Wed = 0;
+ Wgd = weight_scale;
+ }
+ } else { // Subcase b: R+D >= S
+ casename = "Case 2b1 (Wgg=weight_scale, Wmd=Wgd)";
+ Wee = (weight_scale*(E - G + M))/E;
+ Wed = (weight_scale*(D - 2*E + 4*G - 2*M))/(3*D);
+ Wme = (weight_scale*(G-M))/E;
+ Wmg = 0;
+ Wgg = weight_scale;
+ Wmd = (weight_scale - Wed)/2;
+ Wgd = (weight_scale - Wed)/2;
+
+ berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed,
+ weight_scale, G, M, E, D, T, 10, 1);
+
+ if (berr) {
+ casename = "Case 2b2 (Wgg=weight_scale, Wee=weight_scale)";
+ Wgg = weight_scale;
+ Wee = weight_scale;
+ Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
+ Wmd = (weight_scale*(D - 2*M + G + E))/(3*D);
+ Wme = 0;
+ Wmg = 0;
+
+ if (Wmd < 0) { // Can happen if M > T/3
+ casename = "Case 2b3 (Wmd=0)";
+ Wmd = 0;
+ log_warn(LD_DIR,
+ "Too much Middle bandwidth on the network to calculate "
+ "balanced bandwidth-weights. Consider increasing the "
+ "number of Guard nodes by lowering the requirements.");
+ }
+ Wgd = weight_scale - Wed - Wmd;
+ berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
+ Wed, weight_scale, G, M, E, D, T, 10, 1);
+ }
+ if (berr != BW_WEIGHTS_NO_ERROR &&
+ berr != BW_WEIGHTS_BALANCE_MID_ERROR) {
+ log_warn(LD_DIR,
+ "Bw Weights error %d for %s v10. G=%"PRId64" M=%"PRId64
+ " E=%"PRId64" D=%"PRId64" T=%"PRId64
+ " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
+ " Wgd=%d Wgg=%d Wme=%d Wmg=%d weight_scale=%d",
+ berr, casename,
+ (G), (M), (E),
+ (D), (T),
+ (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
+ (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg, (int)weight_scale);
+ return 0;
+ }
+ }
+ } else { // if (E < T/3 || G < T/3) {
+ int64_t S = MIN(E, G);
+ // Case 3: Exactly one of Guard or Exit is scarce
+ if (!(3*E < T || 3*G < T) || !(3*G >= T || 3*E >= T)) {
+ log_warn(LD_BUG,
+ "Bw-Weights Case 3 v10 but with G=%"PRId64" M="
+ "%"PRId64" E=%"PRId64" D=%"PRId64" T=%"PRId64,
+ (G), (M), (E),
+ (D), (T));
+ }
+
+ if (3*(S+D) < T) { // Subcase a: S+D < T/3
+ if (G < E) {
+ casename = "Case 3a (G scarce)";
+ Wgg = Wgd = weight_scale;
+ Wmd = Wed = Wmg = 0;
+ // Minor subcase, if E is more scarce than M,
+ // keep its bandwidth in place.
+ if (E < M) Wme = 0;
+ else Wme = (weight_scale*(E-M))/(2*E);
+ Wee = weight_scale-Wme;
+ } else { // G >= E
+ casename = "Case 3a (E scarce)";
+ Wee = Wed = weight_scale;
+ Wmd = Wgd = Wme = 0;
+ // Minor subcase, if G is more scarce than M,
+ // keep its bandwidth in place.
+ if (G < M) Wmg = 0;
+ else Wmg = (weight_scale*(G-M))/(2*G);
+ Wgg = weight_scale-Wmg;
+ }
+ } else { // Subcase b: S+D >= T/3
+ // D != 0 because S+D >= T/3
+ if (G < E) {
+ casename = "Case 3bg (G scarce, Wgg=weight_scale, Wmd == Wed)";
+ Wgg = weight_scale;
+ Wgd = (weight_scale*(D - 2*G + E + M))/(3*D);
+ Wmg = 0;
+ Wee = (weight_scale*(E+M))/(2*E);
+ Wme = weight_scale - Wee;
+ Wmd = (weight_scale - Wgd)/2;
+ Wed = (weight_scale - Wgd)/2;
+
+ berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
+ Wed, weight_scale, G, M, E, D, T, 10, 1);
+ } else { // G >= E
+ casename = "Case 3be (E scarce, Wee=weight_scale, Wmd == Wgd)";
+ Wee = weight_scale;
+ Wed = (weight_scale*(D - 2*E + G + M))/(3*D);
+ Wme = 0;
+ Wgg = (weight_scale*(G+M))/(2*G);
+ Wmg = weight_scale - Wgg;
+ Wmd = (weight_scale - Wed)/2;
+ Wgd = (weight_scale - Wed)/2;
+
+ berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee,
+ Wed, weight_scale, G, M, E, D, T, 10, 1);
+ }
+ if (berr) {
+ log_warn(LD_DIR,
+ "Bw Weights error %d for %s v10. G=%"PRId64" M=%"PRId64
+ " E=%"PRId64" D=%"PRId64" T=%"PRId64
+ " Wmd=%d Wme=%d Wmg=%d Wed=%d Wee=%d"
+ " Wgd=%d Wgg=%d Wme=%d Wmg=%d weight_scale=%d",
+ berr, casename,
+ (G), (M), (E),
+ (D), (T),
+ (int)Wmd, (int)Wme, (int)Wmg, (int)Wed, (int)Wee,
+ (int)Wgd, (int)Wgg, (int)Wme, (int)Wmg, (int)weight_scale);
+ return 0;
+ }
+ }
+ }
+
+ /* We cast down the weights to 32 bit ints on the assumption that
+ * weight_scale is ~= 10000. We need to ensure a rogue authority
+ * doesn't break this assumption to rig our weights */
+ tor_assert(0 < weight_scale && weight_scale <= INT32_MAX);
+
+ /*
+ * Provide Wgm=Wgg, Wmm=weight_scale, Wem=Wee, Weg=Wed. May later determine
+ * that middle nodes need different bandwidth weights for dirport traffic,
+ * or that weird exit policies need special weight, or that bridges
+ * need special weight.
+ *
+ * NOTE: This list is sorted.
+ */
+ smartlist_add_asprintf(chunks,
+ "bandwidth-weights Wbd=%d Wbe=%d Wbg=%d Wbm=%d "
+ "Wdb=%d "
+ "Web=%d Wed=%d Wee=%d Weg=%d Wem=%d "
+ "Wgb=%d Wgd=%d Wgg=%d Wgm=%d "
+ "Wmb=%d Wmd=%d Wme=%d Wmg=%d Wmm=%d\n",
+ (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale,
+ (int)weight_scale,
+ (int)weight_scale, (int)Wed, (int)Wee, (int)Wed, (int)Wee,
+ (int)weight_scale, (int)Wgd, (int)Wgg, (int)Wgg,
+ (int)weight_scale, (int)Wmd, (int)Wme, (int)Wmg, (int)weight_scale);
+
+ log_notice(LD_CIRC, "Computed bandwidth weights for %s with v10: "
+ "G=%"PRId64" M=%"PRId64" E=%"PRId64" D=%"PRId64
+ " T=%"PRId64,
+ casename,
+ (G), (M), (E),
+ (D), (T));
+ return 1;
+}
+
+/** Update total bandwidth weights (G/M/E/D/T) with the bandwidth of
+ * the router in <b>rs</b>. */
+static void
+update_total_bandwidth_weights(const routerstatus_t *rs,
+ int is_exit, int is_guard,
+ int64_t *G, int64_t *M, int64_t *E, int64_t *D,
+ int64_t *T)
+{
+ int default_bandwidth = rs->bandwidth_kb;
+ int guardfraction_bandwidth = 0;
+
+ if (!rs->has_bandwidth) {
+ log_info(LD_BUG, "Missing consensus bandwidth for router %s",
+ rs->nickname);
+ return;
+ }
+
+ /* If this routerstatus represents a guard that we have
+ * guardfraction information on, use it to calculate its actual
+ * bandwidth. From proposal236:
+ *
+ * Similarly, when calculating the bandwidth-weights line as in
+ * section 3.8.3 of dir-spec.txt, directory authorities should treat N
+ * as if fraction F of its bandwidth has the guard flag and (1-F) does
+ * not. So when computing the totals G,M,E,D, each relay N with guard
+ * visibility fraction F and bandwidth B should be added as follows:
+ *
+ * G' = G + F*B, if N does not have the exit flag
+ * M' = M + (1-F)*B, if N does not have the exit flag
+ *
+ * or
+ *
+ * D' = D + F*B, if N has the exit flag
+ * E' = E + (1-F)*B, if N has the exit flag
+ *
+ * In this block of code, we prepare the bandwidth values by setting
+ * the default_bandwidth to F*B and guardfraction_bandwidth to (1-F)*B.
+ */
+ if (rs->has_guardfraction) {
+ guardfraction_bandwidth_t guardfraction_bw;
+
+ tor_assert(is_guard);
+
+ guard_get_guardfraction_bandwidth(&guardfraction_bw,
+ rs->bandwidth_kb,
+ rs->guardfraction_percentage);
+
+ default_bandwidth = guardfraction_bw.guard_bw;
+ guardfraction_bandwidth = guardfraction_bw.non_guard_bw;
+ }
+
+ /* Now calculate the total bandwidth weights with or without
+ * guardfraction. Depending on the flags of the relay, add its
+ * bandwidth to the appropriate weight pool. If it's a guard and
+ * guardfraction is enabled, add its bandwidth to both pools as
+ * indicated by the previous comment.
+ */
+ *T += default_bandwidth;
+ if (is_exit && is_guard) {
+
+ *D += default_bandwidth;
+ if (rs->has_guardfraction) {
+ *E += guardfraction_bandwidth;
+ }
+
+ } else if (is_exit) {
+
+ *E += default_bandwidth;
+
+ } else if (is_guard) {
+
+ *G += default_bandwidth;
+ if (rs->has_guardfraction) {
+ *M += guardfraction_bandwidth;
+ }
+
+ } else {
+
+ *M += default_bandwidth;
+ }
+}
+
+/** Considering the different recommended/required protocols sets as a
+ * 4-element array, return the element from <b>vote</b> for that protocol
+ * set.
+ */
+static const char *
+get_nth_protocol_set_vote(int n, const networkstatus_t *vote)
+{
+ switch (n) {
+ case 0: return vote->recommended_client_protocols;
+ case 1: return vote->recommended_relay_protocols;
+ case 2: return vote->required_client_protocols;
+ case 3: return vote->required_relay_protocols;
+ default:
+ tor_assert_unreached();
+ return NULL;
+ }
+}
+
+/** Considering the different recommended/required protocols sets as a
+ * 4-element array, return a newly allocated string for the consensus value
+ * for the n'th set.
+ */
+static char *
+compute_nth_protocol_set(int n, int n_voters, const smartlist_t *votes)
+{
+ const char *keyword;
+ smartlist_t *proto_votes = smartlist_new();
+ int threshold;
+ switch (n) {
+ case 0:
+ keyword = "recommended-client-protocols";
+ threshold = CEIL_DIV(n_voters, 2);
+ break;
+ case 1:
+ keyword = "recommended-relay-protocols";
+ threshold = CEIL_DIV(n_voters, 2);
+ break;
+ case 2:
+ keyword = "required-client-protocols";
+ threshold = CEIL_DIV(n_voters * 2, 3);
+ break;
+ case 3:
+ keyword = "required-relay-protocols";
+ threshold = CEIL_DIV(n_voters * 2, 3);
+ break;
+ default:
+ tor_assert_unreached();
+ return NULL;
+ }
+
+ SMARTLIST_FOREACH_BEGIN(votes, const networkstatus_t *, ns) {
+ const char *v = get_nth_protocol_set_vote(n, ns);
+ if (v)
+ smartlist_add(proto_votes, (void*)v);
+ } SMARTLIST_FOREACH_END(ns);
+
+ char *protocols = protover_compute_vote(proto_votes, threshold);
+ smartlist_free(proto_votes);
+
+ char *result = NULL;
+ tor_asprintf(&result, "%s %s\n", keyword, protocols);
+ tor_free(protocols);
+
+ return result;
+}
+
+/** Given a list of vote networkstatus_t in <b>votes</b>, our public
+ * authority <b>identity_key</b>, our private authority <b>signing_key</b>,
+ * and the number of <b>total_authorities</b> that we believe exist in our
+ * voting quorum, generate the text of a new v3 consensus or microdescriptor
+ * consensus (depending on <b>flavor</b>), and return the value in a newly
+ * allocated string.
+ *
+ * Note: this function DOES NOT check whether the votes are from
+ * recognized authorities. (dirvote_add_vote does that.)
+ *
+ * <strong>WATCH OUT</strong>: You need to think before you change the
+ * behavior of this function, or of the functions it calls! If some
+ * authorities compute the consensus with a different algorithm than
+ * others, they will not reach the same result, and they will not all
+ * sign the same thing! If you really need to change the algorithm
+ * here, you should allocate a new "consensus_method" for the new
+ * behavior, and make the new behavior conditional on a new-enough
+ * consensus_method.
+ **/
+STATIC char *
+networkstatus_compute_consensus(smartlist_t *votes,
+ int total_authorities,
+ crypto_pk_t *identity_key,
+ crypto_pk_t *signing_key,
+ const char *legacy_id_key_digest,
+ crypto_pk_t *legacy_signing_key,
+ consensus_flavor_t flavor)
+{
+ smartlist_t *chunks;
+ char *result = NULL;
+ int consensus_method;
+ time_t valid_after, fresh_until, valid_until;
+ int vote_seconds, dist_seconds;
+ char *client_versions = NULL, *server_versions = NULL;
+ smartlist_t *flags;
+ const char *flavor_name;
+ uint32_t max_unmeasured_bw_kb = DEFAULT_MAX_UNMEASURED_BW_KB;
+ int64_t G, M, E, D, T; /* For bandwidth weights */
+ const routerstatus_format_type_t rs_format =
+ flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC;
+ char *params = NULL;
+ char *packages = NULL;
+ int added_weights = 0;
+ dircollator_t *collator = NULL;
+ smartlist_t *param_list = NULL;
+
+ tor_assert(flavor == FLAV_NS || flavor == FLAV_MICRODESC);
+ tor_assert(total_authorities >= smartlist_len(votes));
+ tor_assert(total_authorities > 0);
+
+ flavor_name = networkstatus_get_flavor_name(flavor);
+
+ if (!smartlist_len(votes)) {
+ log_warn(LD_DIR, "Can't compute a consensus from no votes.");
+ return NULL;
+ }
+ flags = smartlist_new();
+
+ consensus_method = compute_consensus_method(votes);
+ if (consensus_method_is_supported(consensus_method)) {
+ log_info(LD_DIR, "Generating consensus using method %d.",
+ consensus_method);
+ } else {
+ log_warn(LD_DIR, "The other authorities will use consensus method %d, "
+ "which I don't support. Maybe I should upgrade!",
+ consensus_method);
+ consensus_method = MAX_SUPPORTED_CONSENSUS_METHOD;
+ }
+
+ if (consensus_method >= MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE) {
+ /* It's smarter to initialize these weights to 1, so that later on,
+ * we can't accidentally divide by zero. */
+ G = M = E = D = 1;
+ T = 4;
+ } else {
+ /* ...but originally, they were set to zero. */
+ G = M = E = D = T = 0;
+ }
+
+ /* Compute medians of time-related things, and figure out how many
+ * routers we might need to talk about. */
+ {
+ int n_votes = smartlist_len(votes);
+ time_t *va_times = tor_calloc(n_votes, sizeof(time_t));
+ time_t *fu_times = tor_calloc(n_votes, sizeof(time_t));
+ time_t *vu_times = tor_calloc(n_votes, sizeof(time_t));
+ int *votesec_list = tor_calloc(n_votes, sizeof(int));
+ int *distsec_list = tor_calloc(n_votes, sizeof(int));
+ int n_versioning_clients = 0, n_versioning_servers = 0;
+ smartlist_t *combined_client_versions = smartlist_new();
+ smartlist_t *combined_server_versions = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ tor_assert(v->type == NS_TYPE_VOTE);
+ va_times[v_sl_idx] = v->valid_after;
+ fu_times[v_sl_idx] = v->fresh_until;
+ vu_times[v_sl_idx] = v->valid_until;
+ votesec_list[v_sl_idx] = v->vote_seconds;
+ distsec_list[v_sl_idx] = v->dist_seconds;
+ if (v->client_versions) {
+ smartlist_t *cv = smartlist_new();
+ ++n_versioning_clients;
+ smartlist_split_string(cv, v->client_versions, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ sort_version_list(cv, 1);
+ smartlist_add_all(combined_client_versions, cv);
+ smartlist_free(cv); /* elements get freed later. */
+ }
+ if (v->server_versions) {
+ smartlist_t *sv = smartlist_new();
+ ++n_versioning_servers;
+ smartlist_split_string(sv, v->server_versions, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ sort_version_list(sv, 1);
+ smartlist_add_all(combined_server_versions, sv);
+ smartlist_free(sv); /* elements get freed later. */
+ }
+ SMARTLIST_FOREACH(v->known_flags, const char *, cp,
+ smartlist_add_strdup(flags, cp));
+ } SMARTLIST_FOREACH_END(v);
+ valid_after = median_time(va_times, n_votes);
+ fresh_until = median_time(fu_times, n_votes);
+ valid_until = median_time(vu_times, n_votes);
+ vote_seconds = median_int(votesec_list, n_votes);
+ dist_seconds = median_int(distsec_list, n_votes);
+
+ tor_assert(valid_after +
+ (get_options()->TestingTorNetwork ?
+ MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) <= fresh_until);
+ tor_assert(fresh_until +
+ (get_options()->TestingTorNetwork ?
+ MIN_VOTE_INTERVAL_TESTING : MIN_VOTE_INTERVAL) <= valid_until);
+ tor_assert(vote_seconds >= MIN_VOTE_SECONDS);
+ tor_assert(dist_seconds >= MIN_DIST_SECONDS);
+
+ server_versions = compute_consensus_versions_list(combined_server_versions,
+ n_versioning_servers);
+ client_versions = compute_consensus_versions_list(combined_client_versions,
+ n_versioning_clients);
+ packages = compute_consensus_package_lines(votes);
+
+ SMARTLIST_FOREACH(combined_server_versions, char *, cp, tor_free(cp));
+ SMARTLIST_FOREACH(combined_client_versions, char *, cp, tor_free(cp));
+ smartlist_free(combined_server_versions);
+ smartlist_free(combined_client_versions);
+
+ smartlist_add_strdup(flags, "NoEdConsensus");
+
+ smartlist_sort_strings(flags);
+ smartlist_uniq_strings(flags);
+
+ tor_free(va_times);
+ tor_free(fu_times);
+ tor_free(vu_times);
+ tor_free(votesec_list);
+ tor_free(distsec_list);
+ }
+
+ chunks = smartlist_new();
+
+ {
+ char va_buf[ISO_TIME_LEN+1], fu_buf[ISO_TIME_LEN+1],
+ vu_buf[ISO_TIME_LEN+1];
+ char *flaglist;
+ format_iso_time(va_buf, valid_after);
+ format_iso_time(fu_buf, fresh_until);
+ format_iso_time(vu_buf, valid_until);
+ flaglist = smartlist_join_strings(flags, " ", 0, NULL);
+
+ smartlist_add_asprintf(chunks, "network-status-version 3%s%s\n"
+ "vote-status consensus\n",
+ flavor == FLAV_NS ? "" : " ",
+ flavor == FLAV_NS ? "" : flavor_name);
+
+ smartlist_add_asprintf(chunks, "consensus-method %d\n",
+ consensus_method);
+
+ smartlist_add_asprintf(chunks,
+ "valid-after %s\n"
+ "fresh-until %s\n"
+ "valid-until %s\n"
+ "voting-delay %d %d\n"
+ "client-versions %s\n"
+ "server-versions %s\n"
+ "%s" /* packages */
+ "known-flags %s\n",
+ va_buf, fu_buf, vu_buf,
+ vote_seconds, dist_seconds,
+ client_versions, server_versions,
+ packages,
+ flaglist);
+
+ tor_free(flaglist);
+ }
+
+ {
+ int num_dirauth = get_n_authorities(V3_DIRINFO);
+ int idx;
+ for (idx = 0; idx < 4; ++idx) {
+ char *proto_line = compute_nth_protocol_set(idx, num_dirauth, votes);
+ if (BUG(!proto_line))
+ continue;
+ smartlist_add(chunks, proto_line);
+ }
+ }
+
+ param_list = dirvote_compute_params(votes, consensus_method,
+ total_authorities);
+ if (smartlist_len(param_list)) {
+ params = smartlist_join_strings(param_list, " ", 0, NULL);
+ smartlist_add_strdup(chunks, "params ");
+ smartlist_add(chunks, params);
+ smartlist_add_strdup(chunks, "\n");
+ }
+
+ {
+ int num_dirauth = get_n_authorities(V3_DIRINFO);
+ /* Default value of this is 2/3 of the total number of authorities. For
+ * instance, if we have 9 dirauth, the default value is 6. The following
+ * calculation will round it down. */
+ int32_t num_srv_agreements =
+ dirvote_get_intermediate_param_value(param_list,
+ "AuthDirNumSRVAgreements",
+ (num_dirauth * 2) / 3);
+ /* Add the shared random value. */
+ char *srv_lines = sr_get_string_for_consensus(votes, num_srv_agreements);
+ if (srv_lines != NULL) {
+ smartlist_add(chunks, srv_lines);
+ }
+ }
+
+ /* Sort the votes. */
+ smartlist_sort(votes, compare_votes_by_authority_id_);
+ /* Add the authority sections. */
+ {
+ smartlist_t *dir_sources = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ dir_src_ent_t *e = tor_malloc_zero(sizeof(dir_src_ent_t));
+ e->v = v;
+ e->digest = get_voter(v)->identity_digest;
+ e->is_legacy = 0;
+ smartlist_add(dir_sources, e);
+ if (!tor_digest_is_zero(get_voter(v)->legacy_id_digest)) {
+ dir_src_ent_t *e_legacy = tor_malloc_zero(sizeof(dir_src_ent_t));
+ e_legacy->v = v;
+ e_legacy->digest = get_voter(v)->legacy_id_digest;
+ e_legacy->is_legacy = 1;
+ smartlist_add(dir_sources, e_legacy);
+ }
+ } SMARTLIST_FOREACH_END(v);
+ smartlist_sort(dir_sources, compare_dir_src_ents_by_authority_id_);
+
+ SMARTLIST_FOREACH_BEGIN(dir_sources, const dir_src_ent_t *, e) {
+ char fingerprint[HEX_DIGEST_LEN+1];
+ char votedigest[HEX_DIGEST_LEN+1];
+ networkstatus_t *v = e->v;
+ networkstatus_voter_info_t *voter = get_voter(v);
+
+ base16_encode(fingerprint, sizeof(fingerprint), e->digest, DIGEST_LEN);
+ base16_encode(votedigest, sizeof(votedigest), voter->vote_digest,
+ DIGEST_LEN);
+
+ smartlist_add_asprintf(chunks,
+ "dir-source %s%s %s %s %s %d %d\n",
+ voter->nickname, e->is_legacy ? "-legacy" : "",
+ fingerprint, voter->address, fmt_addr32(voter->addr),
+ voter->dir_port,
+ voter->or_port);
+ if (! e->is_legacy) {
+ smartlist_add_asprintf(chunks,
+ "contact %s\n"
+ "vote-digest %s\n",
+ voter->contact,
+ votedigest);
+ }
+ } SMARTLIST_FOREACH_END(e);
+ SMARTLIST_FOREACH(dir_sources, dir_src_ent_t *, e, tor_free(e));
+ smartlist_free(dir_sources);
+ }
+
+ {
+ char *max_unmeasured_param = NULL;
+ /* XXXX Extract this code into a common function. Or don't! see #19011 */
+ if (params) {
+ if (strcmpstart(params, "maxunmeasuredbw=") == 0)
+ max_unmeasured_param = params;
+ else
+ max_unmeasured_param = strstr(params, " maxunmeasuredbw=");
+ }
+ if (max_unmeasured_param) {
+ int ok = 0;
+ char *eq = strchr(max_unmeasured_param, '=');
+ if (eq) {
+ max_unmeasured_bw_kb = (uint32_t)
+ tor_parse_ulong(eq+1, 10, 1, UINT32_MAX, &ok, NULL);
+ if (!ok) {
+ log_warn(LD_DIR, "Bad element '%s' in max unmeasured bw param",
+ escaped(max_unmeasured_param));
+ max_unmeasured_bw_kb = DEFAULT_MAX_UNMEASURED_BW_KB;
+ }
+ }
+ }
+ }
+
+ /* Add the actual router entries. */
+ {
+ int *size; /* size[j] is the number of routerstatuses in votes[j]. */
+ int *flag_counts; /* The number of voters that list flag[j] for the
+ * currently considered router. */
+ int i;
+ smartlist_t *matching_descs = smartlist_new();
+ smartlist_t *chosen_flags = smartlist_new();
+ smartlist_t *versions = smartlist_new();
+ smartlist_t *protocols = smartlist_new();
+ smartlist_t *exitsummaries = smartlist_new();
+ uint32_t *bandwidths_kb = tor_calloc(smartlist_len(votes),
+ sizeof(uint32_t));
+ uint32_t *measured_bws_kb = tor_calloc(smartlist_len(votes),
+ sizeof(uint32_t));
+ uint32_t *measured_guardfraction = tor_calloc(smartlist_len(votes),
+ sizeof(uint32_t));
+ int num_bandwidths;
+ int num_mbws;
+ int num_guardfraction_inputs;
+
+ int *n_voter_flags; /* n_voter_flags[j] is the number of flags that
+ * votes[j] knows about. */
+ int *n_flag_voters; /* n_flag_voters[f] is the number of votes that care
+ * about flags[f]. */
+ int **flag_map; /* flag_map[j][b] is an index f such that flag_map[f]
+ * is the same flag as votes[j]->known_flags[b]. */
+ int *named_flag; /* Index of the flag "Named" for votes[j] */
+ int *unnamed_flag; /* Index of the flag "Unnamed" for votes[j] */
+ int n_authorities_measuring_bandwidth;
+
+ strmap_t *name_to_id_map = strmap_new();
+ char conflict[DIGEST_LEN];
+ char unknown[DIGEST_LEN];
+ memset(conflict, 0, sizeof(conflict));
+ memset(unknown, 0xff, sizeof(conflict));
+
+ size = tor_calloc(smartlist_len(votes), sizeof(int));
+ n_voter_flags = tor_calloc(smartlist_len(votes), sizeof(int));
+ n_flag_voters = tor_calloc(smartlist_len(flags), sizeof(int));
+ flag_map = tor_calloc(smartlist_len(votes), sizeof(int *));
+ named_flag = tor_calloc(smartlist_len(votes), sizeof(int));
+ unnamed_flag = tor_calloc(smartlist_len(votes), sizeof(int));
+ for (i = 0; i < smartlist_len(votes); ++i)
+ unnamed_flag[i] = named_flag[i] = -1;
+
+ /* Build the flag indexes. Note that no vote can have more than 64 members
+ * for known_flags, so no value will be greater than 63, so it's safe to
+ * do UINT64_C(1) << index on these values. But note also that
+ * named_flag and unnamed_flag are initialized to -1, so we need to check
+ * that they're actually set before doing UINT64_C(1) << index with
+ * them.*/
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ flag_map[v_sl_idx] = tor_calloc(smartlist_len(v->known_flags),
+ sizeof(int));
+ if (smartlist_len(v->known_flags) > MAX_KNOWN_FLAGS_IN_VOTE) {
+ log_warn(LD_BUG, "Somehow, a vote has %d entries in known_flags",
+ smartlist_len(v->known_flags));
+ }
+ SMARTLIST_FOREACH_BEGIN(v->known_flags, const char *, fl) {
+ int p = smartlist_string_pos(flags, fl);
+ tor_assert(p >= 0);
+ flag_map[v_sl_idx][fl_sl_idx] = p;
+ ++n_flag_voters[p];
+ if (!strcmp(fl, "Named"))
+ named_flag[v_sl_idx] = fl_sl_idx;
+ if (!strcmp(fl, "Unnamed"))
+ unnamed_flag[v_sl_idx] = fl_sl_idx;
+ } SMARTLIST_FOREACH_END(fl);
+ n_voter_flags[v_sl_idx] = smartlist_len(v->known_flags);
+ size[v_sl_idx] = smartlist_len(v->routerstatus_list);
+ } SMARTLIST_FOREACH_END(v);
+
+ /* Named and Unnamed get treated specially */
+ {
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ uint64_t nf;
+ if (named_flag[v_sl_idx]<0)
+ continue;
+ nf = UINT64_C(1) << named_flag[v_sl_idx];
+ SMARTLIST_FOREACH_BEGIN(v->routerstatus_list,
+ vote_routerstatus_t *, rs) {
+
+ if ((rs->flags & nf) != 0) {
+ const char *d = strmap_get_lc(name_to_id_map, rs->status.nickname);
+ if (!d) {
+ /* We have no name officially mapped to this digest. */
+ strmap_set_lc(name_to_id_map, rs->status.nickname,
+ rs->status.identity_digest);
+ } else if (d != conflict &&
+ fast_memcmp(d, rs->status.identity_digest, DIGEST_LEN)) {
+ /* Authorities disagree about this nickname. */
+ strmap_set_lc(name_to_id_map, rs->status.nickname, conflict);
+ } else {
+ /* It's already a conflict, or it's already this ID. */
+ }
+ }
+ } SMARTLIST_FOREACH_END(rs);
+ } SMARTLIST_FOREACH_END(v);
+
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ uint64_t uf;
+ if (unnamed_flag[v_sl_idx]<0)
+ continue;
+ uf = UINT64_C(1) << unnamed_flag[v_sl_idx];
+ SMARTLIST_FOREACH_BEGIN(v->routerstatus_list,
+ vote_routerstatus_t *, rs) {
+ if ((rs->flags & uf) != 0) {
+ const char *d = strmap_get_lc(name_to_id_map, rs->status.nickname);
+ if (d == conflict || d == unknown) {
+ /* Leave it alone; we know what it is. */
+ } else if (!d) {
+ /* We have no name officially mapped to this digest. */
+ strmap_set_lc(name_to_id_map, rs->status.nickname, unknown);
+ } else if (fast_memeq(d, rs->status.identity_digest, DIGEST_LEN)) {
+ /* Authorities disagree about this nickname. */
+ strmap_set_lc(name_to_id_map, rs->status.nickname, conflict);
+ } else {
+ /* It's mapped to a different name. */
+ }
+ }
+ } SMARTLIST_FOREACH_END(rs);
+ } SMARTLIST_FOREACH_END(v);
+ }
+
+ /* We need to know how many votes measure bandwidth. */
+ n_authorities_measuring_bandwidth = 0;
+ SMARTLIST_FOREACH(votes, const networkstatus_t *, v,
+ if (v->has_measured_bws) {
+ ++n_authorities_measuring_bandwidth;
+ }
+ );
+
+ /* Populate the collator */
+ collator = dircollator_new(smartlist_len(votes), total_authorities);
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ dircollator_add_vote(collator, v);
+ } SMARTLIST_FOREACH_END(v);
+
+ dircollator_collate(collator, consensus_method);
+
+ /* Now go through all the votes */
+ flag_counts = tor_calloc(smartlist_len(flags), sizeof(int));
+ const int num_routers = dircollator_n_routers(collator);
+ for (i = 0; i < num_routers; ++i) {
+ vote_routerstatus_t **vrs_lst =
+ dircollator_get_votes_for_router(collator, i);
+
+ vote_routerstatus_t *rs;
+ routerstatus_t rs_out;
+ const char *current_rsa_id = NULL;
+ const char *chosen_version;
+ const char *chosen_protocol_list;
+ const char *chosen_name = NULL;
+ int exitsummary_disagreement = 0;
+ int is_named = 0, is_unnamed = 0, is_running = 0, is_valid = 0;
+ int is_guard = 0, is_exit = 0, is_bad_exit = 0;
+ int naming_conflict = 0;
+ int n_listing = 0;
+ char microdesc_digest[DIGEST256_LEN];
+ tor_addr_port_t alt_orport = {TOR_ADDR_NULL, 0};
+
+ memset(flag_counts, 0, sizeof(int)*smartlist_len(flags));
+ smartlist_clear(matching_descs);
+ smartlist_clear(chosen_flags);
+ smartlist_clear(versions);
+ smartlist_clear(protocols);
+ num_bandwidths = 0;
+ num_mbws = 0;
+ num_guardfraction_inputs = 0;
+ int ed_consensus = 0;
+ const uint8_t *ed_consensus_val = NULL;
+
+ /* Okay, go through all the entries for this digest. */
+ for (int voter_idx = 0; voter_idx < smartlist_len(votes); ++voter_idx) {
+ if (vrs_lst[voter_idx] == NULL)
+ continue; /* This voter had nothing to say about this entry. */
+ rs = vrs_lst[voter_idx];
+ ++n_listing;
+
+ current_rsa_id = rs->status.identity_digest;
+
+ smartlist_add(matching_descs, rs);
+ if (rs->version && rs->version[0])
+ smartlist_add(versions, rs->version);
+
+ if (rs->protocols) {
+ /* We include this one even if it's empty: voting for an
+ * empty protocol list actually is meaningful. */
+ smartlist_add(protocols, rs->protocols);
+ }
+
+ /* Tally up all the flags. */
+ for (int flag = 0; flag < n_voter_flags[voter_idx]; ++flag) {
+ if (rs->flags & (UINT64_C(1) << flag))
+ ++flag_counts[flag_map[voter_idx][flag]];
+ }
+ if (named_flag[voter_idx] >= 0 &&
+ (rs->flags & (UINT64_C(1) << named_flag[voter_idx]))) {
+ if (chosen_name && strcmp(chosen_name, rs->status.nickname)) {
+ log_notice(LD_DIR, "Conflict on naming for router: %s vs %s",
+ chosen_name, rs->status.nickname);
+ naming_conflict = 1;
+ }
+ chosen_name = rs->status.nickname;
+ }
+
+ /* Count guardfraction votes and note down the values. */
+ if (rs->status.has_guardfraction) {
+ measured_guardfraction[num_guardfraction_inputs++] =
+ rs->status.guardfraction_percentage;
+ }
+
+ /* count bandwidths */
+ if (rs->has_measured_bw)
+ measured_bws_kb[num_mbws++] = rs->measured_bw_kb;
+
+ if (rs->status.has_bandwidth)
+ bandwidths_kb[num_bandwidths++] = rs->status.bandwidth_kb;
+
+ /* Count number for which ed25519 is canonical. */
+ if (rs->ed25519_reflects_consensus) {
+ ++ed_consensus;
+ if (ed_consensus_val) {
+ tor_assert(fast_memeq(ed_consensus_val, rs->ed25519_id,
+ ED25519_PUBKEY_LEN));
+ } else {
+ ed_consensus_val = rs->ed25519_id;
+ }
+ }
+ }
+
+ /* We don't include this router at all unless more than half of
+ * the authorities we believe in list it. */
+ if (n_listing <= total_authorities/2)
+ continue;
+
+ if (ed_consensus > 0) {
+ if (ed_consensus <= total_authorities / 2) {
+ log_warn(LD_BUG, "Not enough entries had ed_consensus set; how "
+ "can we have a consensus of %d?", ed_consensus);
+ }
+ }
+
+ /* The clangalyzer can't figure out that this will never be NULL
+ * if n_listing is at least 1 */
+ tor_assert(current_rsa_id);
+
+ /* Figure out the most popular opinion of what the most recent
+ * routerinfo and its contents are. */
+ memset(microdesc_digest, 0, sizeof(microdesc_digest));
+ rs = compute_routerstatus_consensus(matching_descs, consensus_method,
+ microdesc_digest, &alt_orport);
+ /* Copy bits of that into rs_out. */
+ memset(&rs_out, 0, sizeof(rs_out));
+ tor_assert(fast_memeq(current_rsa_id,
+ rs->status.identity_digest,DIGEST_LEN));
+ memcpy(rs_out.identity_digest, current_rsa_id, DIGEST_LEN);
+ memcpy(rs_out.descriptor_digest, rs->status.descriptor_digest,
+ DIGEST_LEN);
+ rs_out.addr = rs->status.addr;
+ rs_out.published_on = rs->status.published_on;
+ rs_out.dir_port = rs->status.dir_port;
+ rs_out.or_port = rs->status.or_port;
+ tor_addr_copy(&rs_out.ipv6_addr, &alt_orport.addr);
+ rs_out.ipv6_orport = alt_orport.port;
+ rs_out.has_bandwidth = 0;
+ rs_out.has_exitsummary = 0;
+
+ if (chosen_name && !naming_conflict) {
+ strlcpy(rs_out.nickname, chosen_name, sizeof(rs_out.nickname));
+ } else {
+ strlcpy(rs_out.nickname, rs->status.nickname, sizeof(rs_out.nickname));
+ }
+
+ {
+ const char *d = strmap_get_lc(name_to_id_map, rs_out.nickname);
+ if (!d) {
+ is_named = is_unnamed = 0;
+ } else if (fast_memeq(d, current_rsa_id, DIGEST_LEN)) {
+ is_named = 1; is_unnamed = 0;
+ } else {
+ is_named = 0; is_unnamed = 1;
+ }
+ }
+
+ /* Set the flags. */
+ smartlist_add(chosen_flags, (char*)"s"); /* for the start of the line. */
+ SMARTLIST_FOREACH_BEGIN(flags, const char *, fl) {
+ if (!strcmp(fl, "Named")) {
+ if (is_named)
+ smartlist_add(chosen_flags, (char*)fl);
+ } else if (!strcmp(fl, "Unnamed")) {
+ if (is_unnamed)
+ smartlist_add(chosen_flags, (char*)fl);
+ } else if (!strcmp(fl, "NoEdConsensus")) {
+ if (ed_consensus <= total_authorities/2)
+ smartlist_add(chosen_flags, (char*)fl);
+ } else {
+ if (flag_counts[fl_sl_idx] > n_flag_voters[fl_sl_idx]/2) {
+ smartlist_add(chosen_flags, (char*)fl);
+ if (!strcmp(fl, "Exit"))
+ is_exit = 1;
+ else if (!strcmp(fl, "Guard"))
+ is_guard = 1;
+ else if (!strcmp(fl, "Running"))
+ is_running = 1;
+ else if (!strcmp(fl, "BadExit"))
+ is_bad_exit = 1;
+ else if (!strcmp(fl, "Valid"))
+ is_valid = 1;
+ }
+ }
+ } SMARTLIST_FOREACH_END(fl);
+
+ /* Starting with consensus method 4 we do not list servers
+ * that are not running in a consensus. See Proposal 138 */
+ if (!is_running)
+ continue;
+
+ /* Starting with consensus method 24, we don't list servers
+ * that are not valid in a consensus. See Proposal 272 */
+ if (!is_valid)
+ continue;
+
+ /* Pick the version. */
+ if (smartlist_len(versions)) {
+ sort_version_list(versions, 0);
+ chosen_version = get_most_frequent_member(versions);
+ } else {
+ chosen_version = NULL;
+ }
+
+ /* Pick the protocol list */
+ if (smartlist_len(protocols)) {
+ smartlist_sort_strings(protocols);
+ chosen_protocol_list = get_most_frequent_member(protocols);
+ } else {
+ chosen_protocol_list = NULL;
+ }
+
+ /* If it's a guard and we have enough guardfraction votes,
+ calculate its consensus guardfraction value. */
+ if (is_guard && num_guardfraction_inputs > 2) {
+ rs_out.has_guardfraction = 1;
+ rs_out.guardfraction_percentage = median_uint32(measured_guardfraction,
+ num_guardfraction_inputs);
+ /* final value should be an integer percentage! */
+ tor_assert(rs_out.guardfraction_percentage <= 100);
+ }
+
+ /* Pick a bandwidth */
+ if (num_mbws > 2) {
+ rs_out.has_bandwidth = 1;
+ rs_out.bw_is_unmeasured = 0;
+ rs_out.bandwidth_kb = median_uint32(measured_bws_kb, num_mbws);
+ } else if (num_bandwidths > 0) {
+ rs_out.has_bandwidth = 1;
+ rs_out.bw_is_unmeasured = 1;
+ rs_out.bandwidth_kb = median_uint32(bandwidths_kb, num_bandwidths);
+ if (n_authorities_measuring_bandwidth > 2) {
+ /* Cap non-measured bandwidths. */
+ if (rs_out.bandwidth_kb > max_unmeasured_bw_kb) {
+ rs_out.bandwidth_kb = max_unmeasured_bw_kb;
+ }
+ }
+ }
+
+ /* Fix bug 2203: Do not count BadExit nodes as Exits for bw weights */
+ is_exit = is_exit && !is_bad_exit;
+
+ /* Update total bandwidth weights with the bandwidths of this router. */
+ {
+ update_total_bandwidth_weights(&rs_out,
+ is_exit, is_guard,
+ &G, &M, &E, &D, &T);
+ }
+
+ /* Ok, we already picked a descriptor digest we want to list
+ * previously. Now we want to use the exit policy summary from
+ * that descriptor. If everybody plays nice all the voters who
+ * listed that descriptor will have the same summary. If not then
+ * something is fishy and we'll use the most common one (breaking
+ * ties in favor of lexicographically larger one (only because it
+ * lets me reuse more existing code)).
+ *
+ * The other case that can happen is that no authority that voted
+ * for that descriptor has an exit policy summary. That's
+ * probably quite unlikely but can happen. In that case we use
+ * the policy that was most often listed in votes, again breaking
+ * ties like in the previous case.
+ */
+ {
+ /* Okay, go through all the votes for this router. We prepared
+ * that list previously */
+ const char *chosen_exitsummary = NULL;
+ smartlist_clear(exitsummaries);
+ SMARTLIST_FOREACH_BEGIN(matching_descs, vote_routerstatus_t *, vsr) {
+ /* Check if the vote where this status comes from had the
+ * proper descriptor */
+ tor_assert(fast_memeq(rs_out.identity_digest,
+ vsr->status.identity_digest,
+ DIGEST_LEN));
+ if (vsr->status.has_exitsummary &&
+ fast_memeq(rs_out.descriptor_digest,
+ vsr->status.descriptor_digest,
+ DIGEST_LEN)) {
+ tor_assert(vsr->status.exitsummary);
+ smartlist_add(exitsummaries, vsr->status.exitsummary);
+ if (!chosen_exitsummary) {
+ chosen_exitsummary = vsr->status.exitsummary;
+ } else if (strcmp(chosen_exitsummary, vsr->status.exitsummary)) {
+ /* Great. There's disagreement among the voters. That
+ * really shouldn't be */
+ exitsummary_disagreement = 1;
+ }
+ }
+ } SMARTLIST_FOREACH_END(vsr);
+
+ if (exitsummary_disagreement) {
+ char id[HEX_DIGEST_LEN+1];
+ char dd[HEX_DIGEST_LEN+1];
+ base16_encode(id, sizeof(dd), rs_out.identity_digest, DIGEST_LEN);
+ base16_encode(dd, sizeof(dd), rs_out.descriptor_digest, DIGEST_LEN);
+ log_warn(LD_DIR, "The voters disagreed on the exit policy summary "
+ " for router %s with descriptor %s. This really shouldn't"
+ " have happened.", id, dd);
+
+ smartlist_sort_strings(exitsummaries);
+ chosen_exitsummary = get_most_frequent_member(exitsummaries);
+ } else if (!chosen_exitsummary) {
+ char id[HEX_DIGEST_LEN+1];
+ char dd[HEX_DIGEST_LEN+1];
+ base16_encode(id, sizeof(dd), rs_out.identity_digest, DIGEST_LEN);
+ base16_encode(dd, sizeof(dd), rs_out.descriptor_digest, DIGEST_LEN);
+ log_warn(LD_DIR, "Not one of the voters that made us select"
+ "descriptor %s for router %s had an exit policy"
+ "summary", dd, id);
+
+ /* Ok, none of those voting for the digest we chose had an
+ * exit policy for us. Well, that kinda sucks.
+ */
+ smartlist_clear(exitsummaries);
+ SMARTLIST_FOREACH(matching_descs, vote_routerstatus_t *, vsr, {
+ if (vsr->status.has_exitsummary)
+ smartlist_add(exitsummaries, vsr->status.exitsummary);
+ });
+ smartlist_sort_strings(exitsummaries);
+ chosen_exitsummary = get_most_frequent_member(exitsummaries);
+
+ if (!chosen_exitsummary)
+ log_warn(LD_DIR, "Wow, not one of the voters had an exit "
+ "policy summary for %s. Wow.", id);
+ }
+
+ if (chosen_exitsummary) {
+ rs_out.has_exitsummary = 1;
+ /* yea, discards the const */
+ rs_out.exitsummary = (char *)chosen_exitsummary;
+ }
+ }
+
+ if (flavor == FLAV_MICRODESC &&
+ tor_digest256_is_zero(microdesc_digest)) {
+ /* With no microdescriptor digest, we omit the entry entirely. */
+ continue;
+ }
+
+ {
+ char *buf;
+ /* Okay!! Now we can write the descriptor... */
+ /* First line goes into "buf". */
+ buf = routerstatus_format_entry(&rs_out, NULL, NULL,
+ rs_format, consensus_method, NULL);
+ if (buf)
+ smartlist_add(chunks, buf);
+ }
+ /* Now an m line, if applicable. */
+ if (flavor == FLAV_MICRODESC &&
+ !tor_digest256_is_zero(microdesc_digest)) {
+ char m[BASE64_DIGEST256_LEN+1];
+ digest256_to_base64(m, microdesc_digest);
+ smartlist_add_asprintf(chunks, "m %s\n", m);
+ }
+ /* Next line is all flags. The "\n" is missing. */
+ smartlist_add(chunks,
+ smartlist_join_strings(chosen_flags, " ", 0, NULL));
+ /* Now the version line. */
+ if (chosen_version) {
+ smartlist_add_strdup(chunks, "\nv ");
+ smartlist_add_strdup(chunks, chosen_version);
+ }
+ smartlist_add_strdup(chunks, "\n");
+ if (chosen_protocol_list &&
+ consensus_method >= MIN_METHOD_FOR_RS_PROTOCOLS) {
+ smartlist_add_asprintf(chunks, "pr %s\n", chosen_protocol_list);
+ }
+ /* Now the weight line. */
+ if (rs_out.has_bandwidth) {
+ char *guardfraction_str = NULL;
+ int unmeasured = rs_out.bw_is_unmeasured;
+
+ /* If we have guardfraction info, include it in the 'w' line. */
+ if (rs_out.has_guardfraction) {
+ tor_asprintf(&guardfraction_str,
+ " GuardFraction=%u", rs_out.guardfraction_percentage);
+ }
+ smartlist_add_asprintf(chunks, "w Bandwidth=%d%s%s\n",
+ rs_out.bandwidth_kb,
+ unmeasured?" Unmeasured=1":"",
+ guardfraction_str ? guardfraction_str : "");
+
+ tor_free(guardfraction_str);
+ }
+
+ /* Now the exitpolicy summary line. */
+ if (rs_out.has_exitsummary && flavor == FLAV_NS) {
+ smartlist_add_asprintf(chunks, "p %s\n", rs_out.exitsummary);
+ }
+
+ /* And the loop is over and we move on to the next router */
+ }
+
+ tor_free(size);
+ tor_free(n_voter_flags);
+ tor_free(n_flag_voters);
+ for (i = 0; i < smartlist_len(votes); ++i)
+ tor_free(flag_map[i]);
+ tor_free(flag_map);
+ tor_free(flag_counts);
+ tor_free(named_flag);
+ tor_free(unnamed_flag);
+ strmap_free(name_to_id_map, NULL);
+ smartlist_free(matching_descs);
+ smartlist_free(chosen_flags);
+ smartlist_free(versions);
+ smartlist_free(protocols);
+ smartlist_free(exitsummaries);
+ tor_free(bandwidths_kb);
+ tor_free(measured_bws_kb);
+ tor_free(measured_guardfraction);
+ }
+
+ /* Mark the directory footer region */
+ smartlist_add_strdup(chunks, "directory-footer\n");
+
+ {
+ int64_t weight_scale = BW_WEIGHT_SCALE;
+ char *bw_weight_param = NULL;
+
+ // Parse params, extract BW_WEIGHT_SCALE if present
+ // DO NOT use consensus_param_bw_weight_scale() in this code!
+ // The consensus is not formed yet!
+ /* XXXX Extract this code into a common function. Or not: #19011. */
+ if (params) {
+ if (strcmpstart(params, "bwweightscale=") == 0)
+ bw_weight_param = params;
+ else
+ bw_weight_param = strstr(params, " bwweightscale=");
+ }
+
+ if (bw_weight_param) {
+ int ok=0;
+ char *eq = strchr(bw_weight_param, '=');
+ if (eq) {
+ weight_scale = tor_parse_long(eq+1, 10, 1, INT32_MAX, &ok,
+ NULL);
+ if (!ok) {
+ log_warn(LD_DIR, "Bad element '%s' in bw weight param",
+ escaped(bw_weight_param));
+ weight_scale = BW_WEIGHT_SCALE;
+ }
+ } else {
+ log_warn(LD_DIR, "Bad element '%s' in bw weight param",
+ escaped(bw_weight_param));
+ weight_scale = BW_WEIGHT_SCALE;
+ }
+ }
+
+ added_weights = networkstatus_compute_bw_weights_v10(chunks, G, M, E, D,
+ T, weight_scale);
+ }
+
+ /* Add a signature. */
+ {
+ char digest[DIGEST256_LEN];
+ char fingerprint[HEX_DIGEST_LEN+1];
+ char signing_key_fingerprint[HEX_DIGEST_LEN+1];
+ digest_algorithm_t digest_alg =
+ flavor == FLAV_NS ? DIGEST_SHA1 : DIGEST_SHA256;
+ size_t digest_len =
+ flavor == FLAV_NS ? DIGEST_LEN : DIGEST256_LEN;
+ const char *algname = crypto_digest_algorithm_get_name(digest_alg);
+ char *signature;
+
+ smartlist_add_strdup(chunks, "directory-signature ");
+
+ /* Compute the hash of the chunks. */
+ crypto_digest_smartlist(digest, digest_len, chunks, "", digest_alg);
+
+ /* Get the fingerprints */
+ crypto_pk_get_fingerprint(identity_key, fingerprint, 0);
+ crypto_pk_get_fingerprint(signing_key, signing_key_fingerprint, 0);
+
+ /* add the junk that will go at the end of the line. */
+ if (flavor == FLAV_NS) {
+ smartlist_add_asprintf(chunks, "%s %s\n", fingerprint,
+ signing_key_fingerprint);
+ } else {
+ smartlist_add_asprintf(chunks, "%s %s %s\n",
+ algname, fingerprint,
+ signing_key_fingerprint);
+ }
+ /* And the signature. */
+ if (!(signature = router_get_dirobj_signature(digest, digest_len,
+ signing_key))) {
+ log_warn(LD_BUG, "Couldn't sign consensus networkstatus.");
+ goto done;
+ }
+ smartlist_add(chunks, signature);
+
+ if (legacy_id_key_digest && legacy_signing_key) {
+ smartlist_add_strdup(chunks, "directory-signature ");
+ base16_encode(fingerprint, sizeof(fingerprint),
+ legacy_id_key_digest, DIGEST_LEN);
+ crypto_pk_get_fingerprint(legacy_signing_key,
+ signing_key_fingerprint, 0);
+ if (flavor == FLAV_NS) {
+ smartlist_add_asprintf(chunks, "%s %s\n", fingerprint,
+ signing_key_fingerprint);
+ } else {
+ smartlist_add_asprintf(chunks, "%s %s %s\n",
+ algname, fingerprint,
+ signing_key_fingerprint);
+ }
+
+ if (!(signature = router_get_dirobj_signature(digest, digest_len,
+ legacy_signing_key))) {
+ log_warn(LD_BUG, "Couldn't sign consensus networkstatus.");
+ goto done;
+ }
+ smartlist_add(chunks, signature);
+ }
+ }
+
+ result = smartlist_join_strings(chunks, "", 0, NULL);
+
+ {
+ networkstatus_t *c;
+ if (!(c = networkstatus_parse_vote_from_string(result, NULL,
+ NS_TYPE_CONSENSUS))) {
+ log_err(LD_BUG, "Generated a networkstatus consensus we couldn't "
+ "parse.");
+ tor_free(result);
+ goto done;
+ }
+ // Verify balancing parameters
+ if (added_weights) {
+ networkstatus_verify_bw_weights(c, consensus_method);
+ }
+ networkstatus_vote_free(c);
+ }
+
+ done:
+
+ dircollator_free(collator);
+ tor_free(client_versions);
+ tor_free(server_versions);
+ tor_free(packages);
+ SMARTLIST_FOREACH(flags, char *, cp, tor_free(cp));
+ smartlist_free(flags);
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_free(chunks);
+ SMARTLIST_FOREACH(param_list, char *, cp, tor_free(cp));
+ smartlist_free(param_list);
+
+ return result;
+}
+
+/** Given a list of networkstatus_t for each vote, return a newly allocated
+ * string containing the "package" lines for the vote. */
+STATIC char *
+compute_consensus_package_lines(smartlist_t *votes)
+{
+ const int n_votes = smartlist_len(votes);
+
+ /* This will be a map from "packagename version" strings to arrays
+ * of const char *, with the i'th member of the array corresponding to the
+ * package line from the i'th vote.
+ */
+ strmap_t *package_status = strmap_new();
+
+ SMARTLIST_FOREACH_BEGIN(votes, networkstatus_t *, v) {
+ if (! v->package_lines)
+ continue;
+ SMARTLIST_FOREACH_BEGIN(v->package_lines, const char *, line) {
+ if (! validate_recommended_package_line(line))
+ continue;
+
+ /* Skip 'cp' to the second space in the line. */
+ const char *cp = strchr(line, ' ');
+ if (!cp) continue;
+ ++cp;
+ cp = strchr(cp, ' ');
+ if (!cp) continue;
+
+ char *key = tor_strndup(line, cp - line);
+
+ const char **status = strmap_get(package_status, key);
+ if (!status) {
+ status = tor_calloc(n_votes, sizeof(const char *));
+ strmap_set(package_status, key, status);
+ }
+ status[v_sl_idx] = line; /* overwrite old value */
+ tor_free(key);
+ } SMARTLIST_FOREACH_END(line);
+ } SMARTLIST_FOREACH_END(v);
+
+ smartlist_t *entries = smartlist_new(); /* temporary */
+ smartlist_t *result_list = smartlist_new(); /* output */
+ STRMAP_FOREACH(package_status, key, const char **, values) {
+ int i, count=-1;
+ for (i = 0; i < n_votes; ++i) {
+ if (values[i])
+ smartlist_add(entries, (void*) values[i]);
+ }
+ smartlist_sort_strings(entries);
+ int n_voting_for_entry = smartlist_len(entries);
+ const char *most_frequent =
+ smartlist_get_most_frequent_string_(entries, &count);
+
+ if (n_voting_for_entry >= 3 && count > n_voting_for_entry / 2) {
+ smartlist_add_asprintf(result_list, "package %s\n", most_frequent);
+ }
+
+ smartlist_clear(entries);
+
+ } STRMAP_FOREACH_END;
+
+ smartlist_sort_strings(result_list);
+
+ char *result = smartlist_join_strings(result_list, "", 0, NULL);
+
+ SMARTLIST_FOREACH(result_list, char *, cp, tor_free(cp));
+ smartlist_free(result_list);
+ smartlist_free(entries);
+ strmap_free(package_status, tor_free_);
+
+ return result;
+}
+
+/** Given a consensus vote <b>target</b> and a set of detached signatures in
+ * <b>sigs</b> that correspond to the same consensus, check whether there are
+ * any new signatures in <b>src_voter_list</b> that should be added to
+ * <b>target</b>. (A signature should be added if we have no signature for that
+ * voter in <b>target</b> yet, or if we have no verifiable signature and the
+ * new signature is verifiable.) Return the number of signatures added or
+ * changed, or -1 if the document signed by <b>sigs</b> isn't the same
+ * document as <b>target</b>. */
+STATIC int
+networkstatus_add_detached_signatures(networkstatus_t *target,
+ ns_detached_signatures_t *sigs,
+ const char *source,
+ int severity,
+ const char **msg_out)
+{
+ int r = 0;
+ const char *flavor;
+ smartlist_t *siglist;
+ tor_assert(sigs);
+ tor_assert(target);
+ tor_assert(target->type == NS_TYPE_CONSENSUS);
+
+ flavor = networkstatus_get_flavor_name(target->flavor);
+
+ /* Do the times seem right? */
+ if (target->valid_after != sigs->valid_after) {
+ *msg_out = "Valid-After times do not match "
+ "when adding detached signatures to consensus";
+ return -1;
+ }
+ if (target->fresh_until != sigs->fresh_until) {
+ *msg_out = "Fresh-until times do not match "
+ "when adding detached signatures to consensus";
+ return -1;
+ }
+ if (target->valid_until != sigs->valid_until) {
+ *msg_out = "Valid-until times do not match "
+ "when adding detached signatures to consensus";
+ return -1;
+ }
+ siglist = strmap_get(sigs->signatures, flavor);
+ if (!siglist) {
+ *msg_out = "No signatures for given consensus flavor";
+ return -1;
+ }
+
+ /** Make sure all the digests we know match, and at least one matches. */
+ {
+ common_digests_t *digests = strmap_get(sigs->digests, flavor);
+ int n_matches = 0;
+ int alg;
+ if (!digests) {
+ *msg_out = "No digests for given consensus flavor";
+ return -1;
+ }
+ for (alg = DIGEST_SHA1; alg < N_COMMON_DIGEST_ALGORITHMS; ++alg) {
+ if (!tor_mem_is_zero(digests->d[alg], DIGEST256_LEN)) {
+ if (fast_memeq(target->digests.d[alg], digests->d[alg],
+ DIGEST256_LEN)) {
+ ++n_matches;
+ } else {
+ *msg_out = "Mismatched digest.";
+ return -1;
+ }
+ }
+ }
+ if (!n_matches) {
+ *msg_out = "No recognized digests for given consensus flavor";
+ }
+ }
+
+ /* For each voter in src... */
+ SMARTLIST_FOREACH_BEGIN(siglist, document_signature_t *, sig) {
+ char voter_identity[HEX_DIGEST_LEN+1];
+ networkstatus_voter_info_t *target_voter =
+ networkstatus_get_voter_by_id(target, sig->identity_digest);
+ authority_cert_t *cert = NULL;
+ const char *algorithm;
+ document_signature_t *old_sig = NULL;
+
+ algorithm = crypto_digest_algorithm_get_name(sig->alg);
+
+ base16_encode(voter_identity, sizeof(voter_identity),
+ sig->identity_digest, DIGEST_LEN);
+ log_info(LD_DIR, "Looking at signature from %s using %s", voter_identity,
+ algorithm);
+ /* If the target doesn't know about this voter, then forget it. */
+ if (!target_voter) {
+ log_info(LD_DIR, "We do not know any voter with ID %s", voter_identity);
+ continue;
+ }
+
+ old_sig = networkstatus_get_voter_sig_by_alg(target_voter, sig->alg);
+
+ /* If the target already has a good signature from this voter, then skip
+ * this one. */
+ if (old_sig && old_sig->good_signature) {
+ log_info(LD_DIR, "We already have a good signature from %s using %s",
+ voter_identity, algorithm);
+ continue;
+ }
+
+ /* Try checking the signature if we haven't already. */
+ if (!sig->good_signature && !sig->bad_signature) {
+ cert = authority_cert_get_by_digests(sig->identity_digest,
+ sig->signing_key_digest);
+ if (cert) {
+ /* Not checking the return value here, since we are going to look
+ * at the status of sig->good_signature in a moment. */
+ (void) networkstatus_check_document_signature(target, sig, cert);
+ }
+ }
+
+ /* If this signature is good, or we don't have any signature yet,
+ * then maybe add it. */
+ if (sig->good_signature || !old_sig || old_sig->bad_signature) {
+ log_info(LD_DIR, "Adding signature from %s with %s", voter_identity,
+ algorithm);
+ tor_log(severity, LD_DIR, "Added a signature for %s from %s.",
+ target_voter->nickname, source);
+ ++r;
+ if (old_sig) {
+ smartlist_remove(target_voter->sigs, old_sig);
+ document_signature_free(old_sig);
+ }
+ smartlist_add(target_voter->sigs, document_signature_dup(sig));
+ } else {
+ log_info(LD_DIR, "Not adding signature from %s", voter_identity);
+ }
+ } SMARTLIST_FOREACH_END(sig);
+
+ return r;
+}
+
+/** Return a newly allocated string containing all the signatures on
+ * <b>consensus</b> by all voters. If <b>for_detached_signatures</b> is true,
+ * then the signatures will be put in a detached signatures document, so
+ * prefix any non-NS-flavored signatures with "additional-signature" rather
+ * than "directory-signature". */
+static char *
+networkstatus_format_signatures(networkstatus_t *consensus,
+ int for_detached_signatures)
+{
+ smartlist_t *elements;
+ char buf[4096];
+ char *result = NULL;
+ int n_sigs = 0;
+ const consensus_flavor_t flavor = consensus->flavor;
+ const char *flavor_name = networkstatus_get_flavor_name(flavor);
+ const char *keyword;
+
+ if (for_detached_signatures && flavor != FLAV_NS)
+ keyword = "additional-signature";
+ else
+ keyword = "directory-signature";
+
+ elements = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(consensus->voters, networkstatus_voter_info_t *, v) {
+ SMARTLIST_FOREACH_BEGIN(v->sigs, document_signature_t *, sig) {
+ char sk[HEX_DIGEST_LEN+1];
+ char id[HEX_DIGEST_LEN+1];
+ if (!sig->signature || sig->bad_signature)
+ continue;
+ ++n_sigs;
+ base16_encode(sk, sizeof(sk), sig->signing_key_digest, DIGEST_LEN);
+ base16_encode(id, sizeof(id), sig->identity_digest, DIGEST_LEN);
+ if (flavor == FLAV_NS) {
+ smartlist_add_asprintf(elements,
+ "%s %s %s\n-----BEGIN SIGNATURE-----\n",
+ keyword, id, sk);
+ } else {
+ const char *digest_name =
+ crypto_digest_algorithm_get_name(sig->alg);
+ smartlist_add_asprintf(elements,
+ "%s%s%s %s %s %s\n-----BEGIN SIGNATURE-----\n",
+ keyword,
+ for_detached_signatures ? " " : "",
+ for_detached_signatures ? flavor_name : "",
+ digest_name, id, sk);
+ }
+ base64_encode(buf, sizeof(buf), sig->signature, sig->signature_len,
+ BASE64_ENCODE_MULTILINE);
+ strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf));
+ smartlist_add_strdup(elements, buf);
+ } SMARTLIST_FOREACH_END(sig);
+ } SMARTLIST_FOREACH_END(v);
+
+ result = smartlist_join_strings(elements, "", 0, NULL);
+ SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
+ smartlist_free(elements);
+ if (!n_sigs)
+ tor_free(result);
+ return result;
+}
+
+/** Return a newly allocated string holding the detached-signatures document
+ * corresponding to the signatures on <b>consensuses</b>, which must contain
+ * exactly one FLAV_NS consensus, and no more than one consensus for each
+ * other flavor. */
+STATIC char *
+networkstatus_get_detached_signatures(smartlist_t *consensuses)
+{
+ smartlist_t *elements;
+ char *result = NULL, *sigs = NULL;
+ networkstatus_t *consensus_ns = NULL;
+ tor_assert(consensuses);
+
+ SMARTLIST_FOREACH(consensuses, networkstatus_t *, ns, {
+ tor_assert(ns);
+ tor_assert(ns->type == NS_TYPE_CONSENSUS);
+ if (ns && ns->flavor == FLAV_NS)
+ consensus_ns = ns;
+ });
+ if (!consensus_ns) {
+ log_warn(LD_BUG, "No NS consensus given.");
+ return NULL;
+ }
+
+ elements = smartlist_new();
+
+ {
+ char va_buf[ISO_TIME_LEN+1], fu_buf[ISO_TIME_LEN+1],
+ vu_buf[ISO_TIME_LEN+1];
+ char d[HEX_DIGEST_LEN+1];
+
+ base16_encode(d, sizeof(d),
+ consensus_ns->digests.d[DIGEST_SHA1], DIGEST_LEN);
+ format_iso_time(va_buf, consensus_ns->valid_after);
+ format_iso_time(fu_buf, consensus_ns->fresh_until);
+ format_iso_time(vu_buf, consensus_ns->valid_until);
+
+ smartlist_add_asprintf(elements,
+ "consensus-digest %s\n"
+ "valid-after %s\n"
+ "fresh-until %s\n"
+ "valid-until %s\n", d, va_buf, fu_buf, vu_buf);
+ }
+
+ /* Get all the digests for the non-FLAV_NS consensuses */
+ SMARTLIST_FOREACH_BEGIN(consensuses, networkstatus_t *, ns) {
+ const char *flavor_name = networkstatus_get_flavor_name(ns->flavor);
+ int alg;
+ if (ns->flavor == FLAV_NS)
+ continue;
+
+ /* start with SHA256; we don't include SHA1 for anything but the basic
+ * consensus. */
+ for (alg = DIGEST_SHA256; alg < N_COMMON_DIGEST_ALGORITHMS; ++alg) {
+ char d[HEX_DIGEST256_LEN+1];
+ const char *alg_name =
+ crypto_digest_algorithm_get_name(alg);
+ if (tor_mem_is_zero(ns->digests.d[alg], DIGEST256_LEN))
+ continue;
+ base16_encode(d, sizeof(d), ns->digests.d[alg], DIGEST256_LEN);
+ smartlist_add_asprintf(elements, "additional-digest %s %s %s\n",
+ flavor_name, alg_name, d);
+ }
+ } SMARTLIST_FOREACH_END(ns);
+
+ /* Now get all the sigs for non-FLAV_NS consensuses */
+ SMARTLIST_FOREACH_BEGIN(consensuses, networkstatus_t *, ns) {
+ char *sigs_on_this_consensus;
+ if (ns->flavor == FLAV_NS)
+ continue;
+ sigs_on_this_consensus = networkstatus_format_signatures(ns, 1);
+ if (!sigs_on_this_consensus) {
+ log_warn(LD_DIR, "Couldn't format signatures");
+ goto err;
+ }
+ smartlist_add(elements, sigs_on_this_consensus);
+ } SMARTLIST_FOREACH_END(ns);
+
+ /* Now add the FLAV_NS consensus signatrures. */
+ sigs = networkstatus_format_signatures(consensus_ns, 1);
+ if (!sigs)
+ goto err;
+ smartlist_add(elements, sigs);
+
+ result = smartlist_join_strings(elements, "", 0, NULL);
+ err:
+ SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp));
+ smartlist_free(elements);
+ return result;
+}
+
+/** Return a newly allocated string holding a detached-signatures document for
+ * all of the in-progress consensuses in the <b>n_flavors</b>-element array at
+ * <b>pending</b>. */
+static char *
+get_detached_signatures_from_pending_consensuses(pending_consensus_t *pending,
+ int n_flavors)
+{
+ int flav;
+ char *signatures;
+ smartlist_t *c = smartlist_new();
+ for (flav = 0; flav < n_flavors; ++flav) {
+ if (pending[flav].consensus)
+ smartlist_add(c, pending[flav].consensus);
+ }
+ signatures = networkstatus_get_detached_signatures(c);
+ smartlist_free(c);
+ return signatures;
+}
+
+/**
+ * Entry point: Take whatever voting actions are pending as of <b>now</b>.
+ *
+ * Return the time at which the next action should be taken.
+ */
+time_t
+dirvote_act(const or_options_t *options, time_t now)
+{
+ if (!authdir_mode_v3(options))
+ return TIME_MAX;
+ tor_assert_nonfatal(voting_schedule.voting_starts);
+ /* If we haven't initialized this object through this codeflow, we need to
+ * recalculate the timings to match our vote. The reason to do that is if we
+ * have a voting schedule initialized 1 minute ago, the voting timings might
+ * not be aligned to what we should expect with "now". This is especially
+ * true for TestingTorNetwork using smaller timings. */
+ if (voting_schedule.created_on_demand) {
+ char *keys = list_v3_auth_ids();
+ authority_cert_t *c = get_my_v3_authority_cert();
+ log_notice(LD_DIR, "Scheduling voting. Known authority IDs are %s. "
+ "Mine is %s.",
+ keys, hex_str(c->cache_info.identity_digest, DIGEST_LEN));
+ tor_free(keys);
+ voting_schedule_recalculate_timing(options, now);
+ }
+
+#define IF_TIME_FOR_NEXT_ACTION(when_field, done_field) \
+ if (! voting_schedule.done_field) { \
+ if (voting_schedule.when_field > now) { \
+ return voting_schedule.when_field; \
+ } else {
+#define ENDIF \
+ } \
+ }
+
+ IF_TIME_FOR_NEXT_ACTION(voting_starts, have_voted) {
+ log_notice(LD_DIR, "Time to vote.");
+ dirvote_perform_vote();
+ voting_schedule.have_voted = 1;
+ } ENDIF
+ IF_TIME_FOR_NEXT_ACTION(fetch_missing_votes, have_fetched_missing_votes) {
+ log_notice(LD_DIR, "Time to fetch any votes that we're missing.");
+ dirvote_fetch_missing_votes();
+ voting_schedule.have_fetched_missing_votes = 1;
+ } ENDIF
+ IF_TIME_FOR_NEXT_ACTION(voting_ends, have_built_consensus) {
+ log_notice(LD_DIR, "Time to compute a consensus.");
+ dirvote_compute_consensuses();
+ /* XXXX We will want to try again later if we haven't got enough
+ * votes yet. Implement this if it turns out to ever happen. */
+ voting_schedule.have_built_consensus = 1;
+ } ENDIF
+ IF_TIME_FOR_NEXT_ACTION(fetch_missing_signatures,
+ have_fetched_missing_signatures) {
+ log_notice(LD_DIR, "Time to fetch any signatures that we're missing.");
+ dirvote_fetch_missing_signatures();
+ voting_schedule.have_fetched_missing_signatures = 1;
+ } ENDIF
+ IF_TIME_FOR_NEXT_ACTION(interval_starts,
+ have_published_consensus) {
+ log_notice(LD_DIR, "Time to publish the consensus and discard old votes");
+ dirvote_publish_consensus();
+ dirvote_clear_votes(0);
+ voting_schedule.have_published_consensus = 1;
+ /* Update our shared random state with the consensus just published. */
+ sr_act_post_consensus(
+ networkstatus_get_latest_consensus_by_flavor(FLAV_NS));
+ /* XXXX We will want to try again later if we haven't got enough
+ * signatures yet. Implement this if it turns out to ever happen. */
+ voting_schedule_recalculate_timing(options, now);
+ return voting_schedule.voting_starts;
+ } ENDIF
+
+ tor_assert_nonfatal_unreached();
+ return now + 1;
+
+#undef ENDIF
+#undef IF_TIME_FOR_NEXT_ACTION
+}
+
+/** A vote networkstatus_t and its unparsed body: held around so we can
+ * use it to generate a consensus (at voting_ends) and so we can serve it to
+ * other authorities that might want it. */
+typedef struct pending_vote_t {
+ cached_dir_t *vote_body;
+ networkstatus_t *vote;
+} pending_vote_t;
+
+/** List of pending_vote_t for the current vote. Before we've used them to
+ * build a consensus, the votes go here. */
+static smartlist_t *pending_vote_list = NULL;
+/** List of pending_vote_t for the previous vote. After we've used them to
+ * build a consensus, the votes go here for the next period. */
+static smartlist_t *previous_vote_list = NULL;
+
+/* DOCDOC pending_consensuses */
+static pending_consensus_t pending_consensuses[N_CONSENSUS_FLAVORS];
+
+/** The detached signatures for the consensus that we're currently
+ * building. */
+static char *pending_consensus_signatures = NULL;
+
+/** List of ns_detached_signatures_t: hold signatures that get posted to us
+ * before we have generated the consensus on our own. */
+static smartlist_t *pending_consensus_signature_list = NULL;
+
+/** Generate a networkstatus vote and post it to all the v3 authorities.
+ * (V3 Authority only) */
+static int
+dirvote_perform_vote(void)
+{
+ crypto_pk_t *key = get_my_v3_authority_signing_key();
+ authority_cert_t *cert = get_my_v3_authority_cert();
+ networkstatus_t *ns;
+ char *contents;
+ pending_vote_t *pending_vote;
+ time_t now = time(NULL);
+
+ int status;
+ const char *msg = "";
+
+ if (!cert || !key) {
+ log_warn(LD_NET, "Didn't find key/certificate to generate v3 vote");
+ return -1;
+ } else if (cert->expires < now) {
+ log_warn(LD_NET, "Can't generate v3 vote with expired certificate");
+ return -1;
+ }
+ if (!(ns = dirserv_generate_networkstatus_vote_obj(key, cert)))
+ return -1;
+
+ contents = format_networkstatus_vote(key, ns);
+ networkstatus_vote_free(ns);
+ if (!contents)
+ return -1;
+
+ pending_vote = dirvote_add_vote(contents, &msg, &status);
+ tor_free(contents);
+ if (!pending_vote) {
+ log_warn(LD_DIR, "Couldn't store my own vote! (I told myself, '%s'.)",
+ msg);
+ return -1;
+ }
+
+ directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_VOTE,
+ ROUTER_PURPOSE_GENERAL,
+ V3_DIRINFO,
+ pending_vote->vote_body->dir,
+ pending_vote->vote_body->dir_len, 0);
+ log_notice(LD_DIR, "Vote posted.");
+ return 0;
+}
+
+/** Send an HTTP request to every other v3 authority, for the votes of every
+ * authority for which we haven't received a vote yet in this period. (V3
+ * authority only) */
+static void
+dirvote_fetch_missing_votes(void)
+{
+ smartlist_t *missing_fps = smartlist_new();
+ char *resource;
+
+ SMARTLIST_FOREACH_BEGIN(router_get_trusted_dir_servers(),
+ dir_server_t *, ds) {
+ if (!(ds->type & V3_DIRINFO))
+ continue;
+ if (!dirvote_get_vote(ds->v3_identity_digest,
+ DGV_BY_ID|DGV_INCLUDE_PENDING)) {
+ char *cp = tor_malloc(HEX_DIGEST_LEN+1);
+ base16_encode(cp, HEX_DIGEST_LEN+1, ds->v3_identity_digest,
+ DIGEST_LEN);
+ smartlist_add(missing_fps, cp);
+ }
+ } SMARTLIST_FOREACH_END(ds);
+
+ if (!smartlist_len(missing_fps)) {
+ smartlist_free(missing_fps);
+ return;
+ }
+ {
+ char *tmp = smartlist_join_strings(missing_fps, " ", 0, NULL);
+ log_notice(LOG_NOTICE, "We're missing votes from %d authorities (%s). "
+ "Asking every other authority for a copy.",
+ smartlist_len(missing_fps), tmp);
+ tor_free(tmp);
+ }
+ resource = smartlist_join_strings(missing_fps, "+", 0, NULL);
+ directory_get_from_all_authorities(DIR_PURPOSE_FETCH_STATUS_VOTE,
+ 0, resource);
+ tor_free(resource);
+ SMARTLIST_FOREACH(missing_fps, char *, cp, tor_free(cp));
+ smartlist_free(missing_fps);
+}
+
+/** Send a request to every other authority for its detached signatures,
+ * unless we have signatures from all other v3 authorities already. */
+static void
+dirvote_fetch_missing_signatures(void)
+{
+ int need_any = 0;
+ int i;
+ for (i=0; i < N_CONSENSUS_FLAVORS; ++i) {
+ networkstatus_t *consensus = pending_consensuses[i].consensus;
+ if (!consensus ||
+ networkstatus_check_consensus_signature(consensus, -1) == 1) {
+ /* We have no consensus, or we have one that's signed by everybody. */
+ continue;
+ }
+ need_any = 1;
+ }
+ if (!need_any)
+ return;
+
+ directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES,
+ 0, NULL);
+}
+
+/** Release all storage held by pending consensuses (those waiting for
+ * signatures). */
+static void
+dirvote_clear_pending_consensuses(void)
+{
+ int i;
+ for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
+ pending_consensus_t *pc = &pending_consensuses[i];
+ tor_free(pc->body);
+
+ networkstatus_vote_free(pc->consensus);
+ pc->consensus = NULL;
+ }
+}
+
+/** Drop all currently pending votes, consensus, and detached signatures. */
+static void
+dirvote_clear_votes(int all_votes)
+{
+ if (!previous_vote_list)
+ previous_vote_list = smartlist_new();
+ if (!pending_vote_list)
+ pending_vote_list = smartlist_new();
+
+ /* All "previous" votes are now junk. */
+ SMARTLIST_FOREACH(previous_vote_list, pending_vote_t *, v, {
+ cached_dir_decref(v->vote_body);
+ v->vote_body = NULL;
+ networkstatus_vote_free(v->vote);
+ tor_free(v);
+ });
+ smartlist_clear(previous_vote_list);
+
+ if (all_votes) {
+ /* If we're dumping all the votes, we delete the pending ones. */
+ SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, v, {
+ cached_dir_decref(v->vote_body);
+ v->vote_body = NULL;
+ networkstatus_vote_free(v->vote);
+ tor_free(v);
+ });
+ } else {
+ /* Otherwise, we move them into "previous". */
+ smartlist_add_all(previous_vote_list, pending_vote_list);
+ }
+ smartlist_clear(pending_vote_list);
+
+ if (pending_consensus_signature_list) {
+ SMARTLIST_FOREACH(pending_consensus_signature_list, char *, cp,
+ tor_free(cp));
+ smartlist_clear(pending_consensus_signature_list);
+ }
+ tor_free(pending_consensus_signatures);
+ dirvote_clear_pending_consensuses();
+}
+
+/** Return a newly allocated string containing the hex-encoded v3 authority
+ identity digest of every recognized v3 authority. */
+static char *
+list_v3_auth_ids(void)
+{
+ smartlist_t *known_v3_keys = smartlist_new();
+ char *keys;
+ SMARTLIST_FOREACH(router_get_trusted_dir_servers(),
+ dir_server_t *, ds,
+ if ((ds->type & V3_DIRINFO) &&
+ !tor_digest_is_zero(ds->v3_identity_digest))
+ smartlist_add(known_v3_keys,
+ tor_strdup(hex_str(ds->v3_identity_digest, DIGEST_LEN))));
+ keys = smartlist_join_strings(known_v3_keys, ", ", 0, NULL);
+ SMARTLIST_FOREACH(known_v3_keys, char *, cp, tor_free(cp));
+ smartlist_free(known_v3_keys);
+ return keys;
+}
+
+/** Called when we have received a networkstatus vote in <b>vote_body</b>.
+ * Parse and validate it, and on success store it as a pending vote (which we
+ * then return). Return NULL on failure. Sets *<b>msg_out</b> and
+ * *<b>status_out</b> to an HTTP response and status code. (V3 authority
+ * only) */
+pending_vote_t *
+dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out)
+{
+ networkstatus_t *vote;
+ networkstatus_voter_info_t *vi;
+ dir_server_t *ds;
+ pending_vote_t *pending_vote = NULL;
+ const char *end_of_vote = NULL;
+ int any_failed = 0;
+ tor_assert(vote_body);
+ tor_assert(msg_out);
+ tor_assert(status_out);
+
+ if (!pending_vote_list)
+ pending_vote_list = smartlist_new();
+ *status_out = 0;
+ *msg_out = NULL;
+
+ again:
+ vote = networkstatus_parse_vote_from_string(vote_body, &end_of_vote,
+ NS_TYPE_VOTE);
+ if (!end_of_vote)
+ end_of_vote = vote_body + strlen(vote_body);
+ if (!vote) {
+ log_warn(LD_DIR, "Couldn't parse vote: length was %d",
+ (int)strlen(vote_body));
+ *msg_out = "Unable to parse vote";
+ goto err;
+ }
+ tor_assert(smartlist_len(vote->voters) == 1);
+ vi = get_voter(vote);
+ {
+ int any_sig_good = 0;
+ SMARTLIST_FOREACH(vi->sigs, document_signature_t *, sig,
+ if (sig->good_signature)
+ any_sig_good = 1);
+ tor_assert(any_sig_good);
+ }
+ ds = trusteddirserver_get_by_v3_auth_digest(vi->identity_digest);
+ if (!ds) {
+ char *keys = list_v3_auth_ids();
+ log_warn(LD_DIR, "Got a vote from an authority (nickname %s, address %s) "
+ "with authority key ID %s. "
+ "This key ID is not recognized. Known v3 key IDs are: %s",
+ vi->nickname, vi->address,
+ hex_str(vi->identity_digest, DIGEST_LEN), keys);
+ tor_free(keys);
+ *msg_out = "Vote not from a recognized v3 authority";
+ goto err;
+ }
+ tor_assert(vote->cert);
+ if (!authority_cert_get_by_digests(vote->cert->cache_info.identity_digest,
+ vote->cert->signing_key_digest)) {
+ /* Hey, it's a new cert! */
+ trusted_dirs_load_certs_from_string(
+ vote->cert->cache_info.signed_descriptor_body,
+ TRUSTED_DIRS_CERTS_SRC_FROM_VOTE, 1 /*flush*/,
+ NULL);
+ if (!authority_cert_get_by_digests(vote->cert->cache_info.identity_digest,
+ vote->cert->signing_key_digest)) {
+ log_warn(LD_BUG, "We added a cert, but still couldn't find it.");
+ }
+ }
+
+ /* Is it for the right period? */
+ if (vote->valid_after != voting_schedule.interval_starts) {
+ char tbuf1[ISO_TIME_LEN+1], tbuf2[ISO_TIME_LEN+1];
+ format_iso_time(tbuf1, vote->valid_after);
+ format_iso_time(tbuf2, voting_schedule.interval_starts);
+ log_warn(LD_DIR, "Rejecting vote from %s with valid-after time of %s; "
+ "we were expecting %s", vi->address, tbuf1, tbuf2);
+ *msg_out = "Bad valid-after time";
+ goto err;
+ }
+
+ /* Fetch any new router descriptors we just learned about */
+ update_consensus_router_descriptor_downloads(time(NULL), 1, vote);
+
+ /* Now see whether we already have a vote from this authority. */
+ SMARTLIST_FOREACH_BEGIN(pending_vote_list, pending_vote_t *, v) {
+ if (fast_memeq(v->vote->cert->cache_info.identity_digest,
+ vote->cert->cache_info.identity_digest,
+ DIGEST_LEN)) {
+ networkstatus_voter_info_t *vi_old = get_voter(v->vote);
+ if (fast_memeq(vi_old->vote_digest, vi->vote_digest, DIGEST_LEN)) {
+ /* Ah, it's the same vote. Not a problem. */
+ log_info(LD_DIR, "Discarding a vote we already have (from %s).",
+ vi->address);
+ if (*status_out < 200)
+ *status_out = 200;
+ goto discard;
+ } else if (v->vote->published < vote->published) {
+ log_notice(LD_DIR, "Replacing an older pending vote from this "
+ "directory (%s)", vi->address);
+ cached_dir_decref(v->vote_body);
+ networkstatus_vote_free(v->vote);
+ v->vote_body = new_cached_dir(tor_strndup(vote_body,
+ end_of_vote-vote_body),
+ vote->published);
+ v->vote = vote;
+ if (end_of_vote &&
+ !strcmpstart(end_of_vote, "network-status-version"))
+ goto again;
+
+ if (*status_out < 200)
+ *status_out = 200;
+ if (!*msg_out)
+ *msg_out = "OK";
+ return v;
+ } else {
+ *msg_out = "Already have a newer pending vote";
+ goto err;
+ }
+ }
+ } SMARTLIST_FOREACH_END(v);
+
+ /* This a valid vote, update our shared random state. */
+ sr_handle_received_commits(vote->sr_info.commits,
+ vote->cert->identity_key);
+
+ pending_vote = tor_malloc_zero(sizeof(pending_vote_t));
+ pending_vote->vote_body = new_cached_dir(tor_strndup(vote_body,
+ end_of_vote-vote_body),
+ vote->published);
+ pending_vote->vote = vote;
+ smartlist_add(pending_vote_list, pending_vote);
+
+ if (!strcmpstart(end_of_vote, "network-status-version ")) {
+ vote_body = end_of_vote;
+ goto again;
+ }
+
+ goto done;
+
+ err:
+ any_failed = 1;
+ if (!*msg_out)
+ *msg_out = "Error adding vote";
+ if (*status_out < 400)
+ *status_out = 400;
+
+ discard:
+ networkstatus_vote_free(vote);
+
+ if (end_of_vote && !strcmpstart(end_of_vote, "network-status-version ")) {
+ vote_body = end_of_vote;
+ goto again;
+ }
+
+ done:
+
+ if (*status_out < 200)
+ *status_out = 200;
+ if (!*msg_out) {
+ if (!any_failed && !pending_vote) {
+ *msg_out = "Duplicate discarded";
+ } else {
+ *msg_out = "ok";
+ }
+ }
+
+ return any_failed ? NULL : pending_vote;
+}
+
+/* Write the votes in <b>pending_vote_list</b> to disk. */
+static void
+write_v3_votes_to_disk(const smartlist_t *pending_votes)
+{
+ smartlist_t *votestrings = smartlist_new();
+ char *votefile = NULL;
+
+ SMARTLIST_FOREACH(pending_votes, pending_vote_t *, v,
+ {
+ sized_chunk_t *c = tor_malloc(sizeof(sized_chunk_t));
+ c->bytes = v->vote_body->dir;
+ c->len = v->vote_body->dir_len;
+ smartlist_add(votestrings, c); /* collect strings to write to disk */
+ });
+
+ votefile = get_datadir_fname("v3-status-votes");
+ write_chunks_to_file(votefile, votestrings, 0, 0);
+ log_debug(LD_DIR, "Wrote votes to disk (%s)!", votefile);
+
+ tor_free(votefile);
+ SMARTLIST_FOREACH(votestrings, sized_chunk_t *, c, tor_free(c));
+ smartlist_free(votestrings);
+}
+
+/** Try to compute a v3 networkstatus consensus from the currently pending
+ * votes. Return 0 on success, -1 on failure. Store the consensus in
+ * pending_consensus: it won't be ready to be published until we have
+ * everybody else's signatures collected too. (V3 Authority only) */
+static int
+dirvote_compute_consensuses(void)
+{
+ /* Have we got enough votes to try? */
+ int n_votes, n_voters, n_vote_running = 0;
+ smartlist_t *votes = NULL;
+ char *consensus_body = NULL, *signatures = NULL;
+ networkstatus_t *consensus = NULL;
+ authority_cert_t *my_cert;
+ pending_consensus_t pending[N_CONSENSUS_FLAVORS];
+ int flav;
+
+ memset(pending, 0, sizeof(pending));
+
+ if (!pending_vote_list)
+ pending_vote_list = smartlist_new();
+
+ /* Write votes to disk */
+ write_v3_votes_to_disk(pending_vote_list);
+
+ /* Setup votes smartlist */
+ votes = smartlist_new();
+ SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, v,
+ {
+ smartlist_add(votes, v->vote); /* collect votes to compute consensus */
+ });
+
+ /* See if consensus managed to achieve majority */
+ n_voters = get_n_authorities(V3_DIRINFO);
+ n_votes = smartlist_len(pending_vote_list);
+ if (n_votes <= n_voters/2) {
+ log_warn(LD_DIR, "We don't have enough votes to generate a consensus: "
+ "%d of %d", n_votes, n_voters/2+1);
+ goto err;
+ }
+ tor_assert(pending_vote_list);
+ SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, v, {
+ if (smartlist_contains_string(v->vote->known_flags, "Running"))
+ n_vote_running++;
+ });
+ if (!n_vote_running) {
+ /* See task 1066. */
+ log_warn(LD_DIR, "Nobody has voted on the Running flag. Generating "
+ "and publishing a consensus without Running nodes "
+ "would make many clients stop working. Not "
+ "generating a consensus!");
+ goto err;
+ }
+
+ if (!(my_cert = get_my_v3_authority_cert())) {
+ log_warn(LD_DIR, "Can't generate consensus without a certificate.");
+ goto err;
+ }
+
+ {
+ char legacy_dbuf[DIGEST_LEN];
+ crypto_pk_t *legacy_sign=NULL;
+ char *legacy_id_digest = NULL;
+ int n_generated = 0;
+ if (get_options()->V3AuthUseLegacyKey) {
+ authority_cert_t *cert = get_my_v3_legacy_cert();
+ legacy_sign = get_my_v3_legacy_signing_key();
+ if (cert) {
+ if (crypto_pk_get_digest(cert->identity_key, legacy_dbuf)) {
+ log_warn(LD_BUG,
+ "Unable to compute digest of legacy v3 identity key");
+ } else {
+ legacy_id_digest = legacy_dbuf;
+ }
+ }
+ }
+
+ for (flav = 0; flav < N_CONSENSUS_FLAVORS; ++flav) {
+ const char *flavor_name = networkstatus_get_flavor_name(flav);
+ consensus_body = networkstatus_compute_consensus(
+ votes, n_voters,
+ my_cert->identity_key,
+ get_my_v3_authority_signing_key(), legacy_id_digest, legacy_sign,
+ flav);
+
+ if (!consensus_body) {
+ log_warn(LD_DIR, "Couldn't generate a %s consensus at all!",
+ flavor_name);
+ continue;
+ }
+ consensus = networkstatus_parse_vote_from_string(consensus_body, NULL,
+ NS_TYPE_CONSENSUS);
+ if (!consensus) {
+ log_warn(LD_DIR, "Couldn't parse %s consensus we generated!",
+ flavor_name);
+ tor_free(consensus_body);
+ continue;
+ }
+
+ /* 'Check' our own signature, to mark it valid. */
+ networkstatus_check_consensus_signature(consensus, -1);
+
+ pending[flav].body = consensus_body;
+ pending[flav].consensus = consensus;
+ n_generated++;
+ consensus_body = NULL;
+ consensus = NULL;
+ }
+ if (!n_generated) {
+ log_warn(LD_DIR, "Couldn't generate any consensus flavors at all.");
+ goto err;
+ }
+ }
+
+ signatures = get_detached_signatures_from_pending_consensuses(
+ pending, N_CONSENSUS_FLAVORS);
+
+ if (!signatures) {
+ log_warn(LD_DIR, "Couldn't extract signatures.");
+ goto err;
+ }
+
+ dirvote_clear_pending_consensuses();
+ memcpy(pending_consensuses, pending, sizeof(pending));
+
+ tor_free(pending_consensus_signatures);
+ pending_consensus_signatures = signatures;
+
+ if (pending_consensus_signature_list) {
+ int n_sigs = 0;
+ /* we may have gotten signatures for this consensus before we built
+ * it ourself. Add them now. */
+ SMARTLIST_FOREACH_BEGIN(pending_consensus_signature_list, char *, sig) {
+ const char *msg = NULL;
+ int r = dirvote_add_signatures_to_all_pending_consensuses(sig,
+ "pending", &msg);
+ if (r >= 0)
+ n_sigs += r;
+ else
+ log_warn(LD_DIR,
+ "Could not add queued signature to new consensus: %s",
+ msg);
+ tor_free(sig);
+ } SMARTLIST_FOREACH_END(sig);
+ if (n_sigs)
+ log_notice(LD_DIR, "Added %d pending signatures while building "
+ "consensus.", n_sigs);
+ smartlist_clear(pending_consensus_signature_list);
+ }
+
+ log_notice(LD_DIR, "Consensus computed; uploading signature(s)");
+
+ directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_SIGNATURES,
+ ROUTER_PURPOSE_GENERAL,
+ V3_DIRINFO,
+ pending_consensus_signatures,
+ strlen(pending_consensus_signatures), 0);
+ log_notice(LD_DIR, "Signature(s) posted.");
+
+ smartlist_free(votes);
+ return 0;
+ err:
+ smartlist_free(votes);
+ tor_free(consensus_body);
+ tor_free(signatures);
+ networkstatus_vote_free(consensus);
+
+ return -1;
+}
+
+/** Helper: we just got the <b>detached_signatures_body</b> sent to us as
+ * signatures on the currently pending consensus. Add them to <b>pc</b>
+ * as appropriate. Return the number of signatures added. (?) */
+static int
+dirvote_add_signatures_to_pending_consensus(
+ pending_consensus_t *pc,
+ ns_detached_signatures_t *sigs,
+ const char *source,
+ int severity,
+ const char **msg_out)
+{
+ const char *flavor_name;
+ int r = -1;
+
+ /* Only call if we have a pending consensus right now. */
+ tor_assert(pc->consensus);
+ tor_assert(pc->body);
+ tor_assert(pending_consensus_signatures);
+
+ flavor_name = networkstatus_get_flavor_name(pc->consensus->flavor);
+ *msg_out = NULL;
+
+ {
+ smartlist_t *sig_list = strmap_get(sigs->signatures, flavor_name);
+ log_info(LD_DIR, "Have %d signatures for adding to %s consensus.",
+ sig_list ? smartlist_len(sig_list) : 0, flavor_name);
+ }
+ r = networkstatus_add_detached_signatures(pc->consensus, sigs,
+ source, severity, msg_out);
+ if (r >= 0) {
+ log_info(LD_DIR,"Added %d signatures to consensus.", r);
+ } else {
+ log_fn(LOG_PROTOCOL_WARN, LD_DIR,
+ "Unable to add signatures to consensus: %s",
+ *msg_out ? *msg_out : "(unknown)");
+ }
+
+ if (r >= 1) {
+ char *new_signatures =
+ networkstatus_format_signatures(pc->consensus, 0);
+ char *dst, *dst_end;
+ size_t new_consensus_len;
+ if (!new_signatures) {
+ *msg_out = "No signatures to add";
+ goto err;
+ }
+ new_consensus_len =
+ strlen(pc->body) + strlen(new_signatures) + 1;
+ pc->body = tor_realloc(pc->body, new_consensus_len);
+ dst_end = pc->body + new_consensus_len;
+ dst = strstr(pc->body, "directory-signature ");
+ tor_assert(dst);
+ strlcpy(dst, new_signatures, dst_end-dst);
+
+ /* We remove this block once it has failed to crash for a while. But
+ * unless it shows up in profiles, we're probably better leaving it in,
+ * just in case we break detached signature processing at some point. */
+ {
+ networkstatus_t *v = networkstatus_parse_vote_from_string(
+ pc->body, NULL,
+ NS_TYPE_CONSENSUS);
+ tor_assert(v);
+ networkstatus_vote_free(v);
+ }
+ *msg_out = "Signatures added";
+ tor_free(new_signatures);
+ } else if (r == 0) {
+ *msg_out = "Signatures ignored";
+ } else {
+ goto err;
+ }
+
+ goto done;
+ err:
+ if (!*msg_out)
+ *msg_out = "Unrecognized error while adding detached signatures.";
+ done:
+ return r;
+}
+
+static int
+dirvote_add_signatures_to_all_pending_consensuses(
+ const char *detached_signatures_body,
+ const char *source,
+ const char **msg_out)
+{
+ int r=0, i, n_added = 0, errors = 0;
+ ns_detached_signatures_t *sigs;
+ tor_assert(detached_signatures_body);
+ tor_assert(msg_out);
+ tor_assert(pending_consensus_signatures);
+
+ if (!(sigs = networkstatus_parse_detached_signatures(
+ detached_signatures_body, NULL))) {
+ *msg_out = "Couldn't parse detached signatures.";
+ goto err;
+ }
+
+ for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
+ int res;
+ int severity = i == FLAV_NS ? LOG_NOTICE : LOG_INFO;
+ pending_consensus_t *pc = &pending_consensuses[i];
+ if (!pc->consensus)
+ continue;
+ res = dirvote_add_signatures_to_pending_consensus(pc, sigs, source,
+ severity, msg_out);
+ if (res < 0)
+ errors++;
+ else
+ n_added += res;
+ }
+
+ if (errors && !n_added) {
+ r = -1;
+ goto err;
+ }
+
+ if (n_added && pending_consensuses[FLAV_NS].consensus) {
+ char *new_detached =
+ get_detached_signatures_from_pending_consensuses(
+ pending_consensuses, N_CONSENSUS_FLAVORS);
+ if (new_detached) {
+ tor_free(pending_consensus_signatures);
+ pending_consensus_signatures = new_detached;
+ }
+ }
+
+ r = n_added;
+ goto done;
+ err:
+ if (!*msg_out)
+ *msg_out = "Unrecognized error while adding detached signatures.";
+ done:
+ ns_detached_signatures_free(sigs);
+ /* XXXX NM Check how return is used. We can now have an error *and*
+ signatures added. */
+ return r;
+}
+
+/** Helper: we just got the <b>detached_signatures_body</b> sent to us as
+ * signatures on the currently pending consensus. Add them to the pending
+ * consensus (if we have one); otherwise queue them until we have a
+ * consensus. Return negative on failure, nonnegative on success. */
+int
+dirvote_add_signatures(const char *detached_signatures_body,
+ const char *source,
+ const char **msg)
+{
+ if (pending_consensuses[FLAV_NS].consensus) {
+ log_notice(LD_DIR, "Got a signature from %s. "
+ "Adding it to the pending consensus.", source);
+ return dirvote_add_signatures_to_all_pending_consensuses(
+ detached_signatures_body, source, msg);
+ } else {
+ log_notice(LD_DIR, "Got a signature from %s. "
+ "Queuing it for the next consensus.", source);
+ if (!pending_consensus_signature_list)
+ pending_consensus_signature_list = smartlist_new();
+ smartlist_add_strdup(pending_consensus_signature_list,
+ detached_signatures_body);
+ *msg = "Signature queued";
+ return 0;
+ }
+}
+
+/** Replace the consensus that we're currently serving with the one that we've
+ * been building. (V3 Authority only) */
+static int
+dirvote_publish_consensus(void)
+{
+ int i;
+
+ /* Now remember all the other consensuses as if we were a directory cache. */
+ for (i = 0; i < N_CONSENSUS_FLAVORS; ++i) {
+ pending_consensus_t *pending = &pending_consensuses[i];
+ const char *name;
+ name = networkstatus_get_flavor_name(i);
+ tor_assert(name);
+ if (!pending->consensus ||
+ networkstatus_check_consensus_signature(pending->consensus, 1)<0) {
+ log_warn(LD_DIR, "Not enough info to publish pending %s consensus",name);
+ continue;
+ }
+
+ if (networkstatus_set_current_consensus(pending->body, name, 0, NULL))
+ log_warn(LD_DIR, "Error publishing %s consensus", name);
+ else
+ log_notice(LD_DIR, "Published %s consensus", name);
+ }
+
+ return 0;
+}
+
+/** Release all static storage held in dirvote.c */
+void
+dirvote_free_all(void)
+{
+ dirvote_clear_votes(1);
+ /* now empty as a result of dirvote_clear_votes(). */
+ smartlist_free(pending_vote_list);
+ pending_vote_list = NULL;
+ smartlist_free(previous_vote_list);
+ previous_vote_list = NULL;
+
+ dirvote_clear_pending_consensuses();
+ tor_free(pending_consensus_signatures);
+ if (pending_consensus_signature_list) {
+ /* now empty as a result of dirvote_clear_votes(). */
+ smartlist_free(pending_consensus_signature_list);
+ pending_consensus_signature_list = NULL;
+ }
+}
+
+/* ====
+ * Access to pending items.
+ * ==== */
+
+/** Return the body of the consensus that we're currently trying to build. */
+MOCK_IMPL(const char *,
+dirvote_get_pending_consensus, (consensus_flavor_t flav))
+{
+ tor_assert(((int)flav) >= 0 && (int)flav < N_CONSENSUS_FLAVORS);
+ return pending_consensuses[flav].body;
+}
+
+/** Return the signatures that we know for the consensus that we're currently
+ * trying to build. */
+MOCK_IMPL(const char *,
+dirvote_get_pending_detached_signatures, (void))
+{
+ return pending_consensus_signatures;
+}
+
+/** Return a given vote specified by <b>fp</b>. If <b>by_id</b>, return the
+ * vote for the authority with the v3 authority identity key digest <b>fp</b>;
+ * if <b>by_id</b> is false, return the vote whose digest is <b>fp</b>. If
+ * <b>fp</b> is NULL, return our own vote. If <b>include_previous</b> is
+ * false, do not consider any votes for a consensus that's already been built.
+ * If <b>include_pending</b> is false, do not consider any votes for the
+ * consensus that's in progress. May return NULL if we have no vote for the
+ * authority in question. */
+const cached_dir_t *
+dirvote_get_vote(const char *fp, int flags)
+{
+ int by_id = flags & DGV_BY_ID;
+ const int include_pending = flags & DGV_INCLUDE_PENDING;
+ const int include_previous = flags & DGV_INCLUDE_PREVIOUS;
+
+ if (!pending_vote_list && !previous_vote_list)
+ return NULL;
+ if (fp == NULL) {
+ authority_cert_t *c = get_my_v3_authority_cert();
+ if (c) {
+ fp = c->cache_info.identity_digest;
+ by_id = 1;
+ } else
+ return NULL;
+ }
+ if (by_id) {
+ if (pending_vote_list && include_pending) {
+ SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, pv,
+ if (fast_memeq(get_voter(pv->vote)->identity_digest, fp, DIGEST_LEN))
+ return pv->vote_body);
+ }
+ if (previous_vote_list && include_previous) {
+ SMARTLIST_FOREACH(previous_vote_list, pending_vote_t *, pv,
+ if (fast_memeq(get_voter(pv->vote)->identity_digest, fp, DIGEST_LEN))
+ return pv->vote_body);
+ }
+ } else {
+ if (pending_vote_list && include_pending) {
+ SMARTLIST_FOREACH(pending_vote_list, pending_vote_t *, pv,
+ if (fast_memeq(pv->vote->digests.d[DIGEST_SHA1], fp, DIGEST_LEN))
+ return pv->vote_body);
+ }
+ if (previous_vote_list && include_previous) {
+ SMARTLIST_FOREACH(previous_vote_list, pending_vote_t *, pv,
+ if (fast_memeq(pv->vote->digests.d[DIGEST_SHA1], fp, DIGEST_LEN))
+ return pv->vote_body);
+ }
+ }
+ return NULL;
+}
+
+/** Construct and return a new microdescriptor from a routerinfo <b>ri</b>
+ * according to <b>consensus_method</b>.
+ **/
+STATIC microdesc_t *
+dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method)
+{
+ microdesc_t *result = NULL;
+ char *key = NULL, *summary = NULL, *family = NULL;
+ size_t keylen;
+ smartlist_t *chunks = smartlist_new();
+ char *output = NULL;
+
+ if (crypto_pk_write_public_key_to_string(ri->onion_pkey, &key, &keylen)<0)
+ goto done;
+ summary = policy_summarize(ri->exit_policy, AF_INET);
+ if (ri->declared_family)
+ family = smartlist_join_strings(ri->declared_family, " ", 0, NULL);
+
+ smartlist_add_asprintf(chunks, "onion-key\n%s", key);
+
+ if (ri->onion_curve25519_pkey) {
+ char kbuf[128];
+ base64_encode(kbuf, sizeof(kbuf),
+ (const char*)ri->onion_curve25519_pkey->public_key,
+ CURVE25519_PUBKEY_LEN, BASE64_ENCODE_MULTILINE);
+ smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf);
+ }
+
+ /* We originally put a lines in the micrdescriptors, but then we worked out
+ * that we needed them in the microdesc consensus. See #20916. */
+ if (consensus_method < MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC &&
+ !tor_addr_is_null(&ri->ipv6_addr) && ri->ipv6_orport)
+ smartlist_add_asprintf(chunks, "a %s\n",
+ fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport));
+
+ if (family)
+ smartlist_add_asprintf(chunks, "family %s\n", family);
+
+ if (summary && strcmp(summary, "reject 1-65535"))
+ smartlist_add_asprintf(chunks, "p %s\n", summary);
+
+ if (ri->ipv6_exit_policy) {
+ /* XXXX+++ This doesn't match proposal 208, which says these should
+ * be taken unchanged from the routerinfo. That's bogosity, IMO:
+ * the proposal should have said to do this instead.*/
+ char *p6 = write_short_policy(ri->ipv6_exit_policy);
+ if (p6 && strcmp(p6, "reject 1-65535"))
+ smartlist_add_asprintf(chunks, "p6 %s\n", p6);
+ tor_free(p6);
+ }
+
+ {
+ char idbuf[ED25519_BASE64_LEN+1];
+ const char *keytype;
+ if (ri->cache_info.signing_key_cert &&
+ ri->cache_info.signing_key_cert->signing_key_included) {
+ keytype = "ed25519";
+ ed25519_public_to_base64(idbuf,
+ &ri->cache_info.signing_key_cert->signing_key);
+ } else {
+ keytype = "rsa1024";
+ digest_to_base64(idbuf, ri->cache_info.identity_digest);
+ }
+ smartlist_add_asprintf(chunks, "id %s %s\n", keytype, idbuf);
+ }
+
+ output = smartlist_join_strings(chunks, "", 0, NULL);
+
+ {
+ smartlist_t *lst = microdescs_parse_from_string(output,
+ output+strlen(output), 0,
+ SAVED_NOWHERE, NULL);
+ if (smartlist_len(lst) != 1) {
+ log_warn(LD_DIR, "We generated a microdescriptor we couldn't parse.");
+ SMARTLIST_FOREACH(lst, microdesc_t *, md, microdesc_free(md));
+ smartlist_free(lst);
+ goto done;
+ }
+ result = smartlist_get(lst, 0);
+ smartlist_free(lst);
+ }
+
+ done:
+ tor_free(output);
+ tor_free(key);
+ tor_free(summary);
+ tor_free(family);
+ if (chunks) {
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_free(chunks);
+ }
+ return result;
+}
+
+/** Format the appropriate vote line to describe the microdescriptor <b>md</b>
+ * in a consensus vote document. Write it into the <b>out_len</b>-byte buffer
+ * in <b>out</b>. Return -1 on failure and the number of characters written
+ * on success. */
+static ssize_t
+dirvote_format_microdesc_vote_line(char *out_buf, size_t out_buf_len,
+ const microdesc_t *md,
+ int consensus_method_low,
+ int consensus_method_high)
+{
+ ssize_t ret = -1;
+ char d64[BASE64_DIGEST256_LEN+1];
+ char *microdesc_consensus_methods =
+ make_consensus_method_list(consensus_method_low,
+ consensus_method_high,
+ ",");
+ tor_assert(microdesc_consensus_methods);
+
+ if (digest256_to_base64(d64, md->digest)<0)
+ goto out;
+
+ if (tor_snprintf(out_buf, out_buf_len, "m %s sha256=%s\n",
+ microdesc_consensus_methods, d64)<0)
+ goto out;
+
+ ret = strlen(out_buf);
+
+ out:
+ tor_free(microdesc_consensus_methods);
+ return ret;
+}
+
+/** Array of start and end of consensus methods used for supported
+ microdescriptor formats. */
+static const struct consensus_method_range_t {
+ int low;
+ int high;
+} microdesc_consensus_methods[] = {
+ {MIN_SUPPORTED_CONSENSUS_METHOD, MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC - 1},
+ {MIN_METHOD_FOR_NO_A_LINES_IN_MICRODESC, MAX_SUPPORTED_CONSENSUS_METHOD},
+ {-1, -1}
+};
+
+/** Helper type used when generating the microdescriptor lines in a directory
+ * vote. */
+typedef struct microdesc_vote_line_t {
+ int low;
+ int high;
+ microdesc_t *md;
+ struct microdesc_vote_line_t *next;
+} microdesc_vote_line_t;
+
+/** Generate and return a linked list of all the lines that should appear to
+ * describe a router's microdescriptor versions in a directory vote.
+ * Add the generated microdescriptors to <b>microdescriptors_out</b>. */
+vote_microdesc_hash_t *
+dirvote_format_all_microdesc_vote_lines(const routerinfo_t *ri, time_t now,
+ smartlist_t *microdescriptors_out)
+{
+ const struct consensus_method_range_t *cmr;
+ microdesc_vote_line_t *entries = NULL, *ep;
+ vote_microdesc_hash_t *result = NULL;
+
+ /* Generate the microdescriptors. */
+ for (cmr = microdesc_consensus_methods;
+ cmr->low != -1 && cmr->high != -1;
+ cmr++) {
+ microdesc_t *md = dirvote_create_microdescriptor(ri, cmr->low);
+ if (md) {
+ microdesc_vote_line_t *e =
+ tor_malloc_zero(sizeof(microdesc_vote_line_t));
+ e->md = md;
+ e->low = cmr->low;
+ e->high = cmr->high;
+ e->next = entries;
+ entries = e;
+ }
+ }
+
+ /* Compress adjacent identical ones */
+ for (ep = entries; ep; ep = ep->next) {
+ while (ep->next &&
+ fast_memeq(ep->md->digest, ep->next->md->digest, DIGEST256_LEN) &&
+ ep->low == ep->next->high + 1) {
+ microdesc_vote_line_t *next = ep->next;
+ ep->low = next->low;
+ microdesc_free(next->md);
+ ep->next = next->next;
+ tor_free(next);
+ }
+ }
+
+ /* Format them into vote_microdesc_hash_t, and add to microdescriptors_out.*/
+ while ((ep = entries)) {
+ char buf[128];
+ vote_microdesc_hash_t *h;
+ if (dirvote_format_microdesc_vote_line(buf, sizeof(buf), ep->md,
+ ep->low, ep->high) >= 0) {
+ h = tor_malloc_zero(sizeof(vote_microdesc_hash_t));
+ h->microdesc_hash_line = tor_strdup(buf);
+ h->next = result;
+ result = h;
+ ep->md->last_listed = now;
+ smartlist_add(microdescriptors_out, ep->md);
+ }
+ entries = ep->next;
+ tor_free(ep);
+ }
+
+ return result;
+}
+
+/** Parse and extract all SR commits from <b>tokens</b> and place them in
+ * <b>ns</b>. */
+static void
+extract_shared_random_commits(networkstatus_t *ns, const smartlist_t *tokens)
+{
+ smartlist_t *chunks = NULL;
+
+ tor_assert(ns);
+ tor_assert(tokens);
+ /* Commits are only present in a vote. */
+ tor_assert(ns->type == NS_TYPE_VOTE);
+
+ ns->sr_info.commits = smartlist_new();
+
+ smartlist_t *commits = find_all_by_keyword(tokens, K_COMMIT);
+ /* It's normal that a vote might contain no commits even if it participates
+ * in the SR protocol. Don't treat it as an error. */
+ if (commits == NULL) {
+ goto end;
+ }
+
+ /* Parse the commit. We do NO validation of number of arguments or ordering
+ * for forward compatibility, it's the parse commit job to inform us if it's
+ * supported or not. */
+ chunks = smartlist_new();
+ SMARTLIST_FOREACH_BEGIN(commits, directory_token_t *, tok) {
+ /* Extract all arguments and put them in the chunks list. */
+ for (int i = 0; i < tok->n_args; i++) {
+ smartlist_add(chunks, tok->args[i]);
+ }
+ sr_commit_t *commit = sr_parse_commit(chunks);
+ smartlist_clear(chunks);
+ if (commit == NULL) {
+ /* Get voter identity so we can warn that this dirauth vote contains
+ * commit we can't parse. */
+ networkstatus_voter_info_t *voter = smartlist_get(ns->voters, 0);
+ tor_assert(voter);
+ log_warn(LD_DIR, "SR: Unable to parse commit %s from vote of voter %s.",
+ escaped(tok->object_body),
+ hex_str(voter->identity_digest,
+ sizeof(voter->identity_digest)));
+ /* Commitment couldn't be parsed. Continue onto the next commit because
+ * this one could be unsupported for instance. */
+ continue;
+ }
+ /* Add newly created commit object to the vote. */
+ smartlist_add(ns->sr_info.commits, commit);
+ } SMARTLIST_FOREACH_END(tok);
+
+ end:
+ smartlist_free(chunks);
+ smartlist_free(commits);
+}
+
+/* Using the given directory tokens in tokens, parse the shared random commits
+ * and put them in the given vote document ns.
+ *
+ * This also sets the SR participation flag if present in the vote. */
+void
+dirvote_parse_sr_commits(networkstatus_t *ns, const smartlist_t *tokens)
+{
+ /* Does this authority participates in the SR protocol? */
+ directory_token_t *tok = find_opt_by_keyword(tokens, K_SR_FLAG);
+ if (tok) {
+ ns->sr_info.participate = 1;
+ /* Get the SR commitments and reveals from the vote. */
+ extract_shared_random_commits(ns, tokens);
+ }
+}
+
+/* For the given vote, free the shared random commits if any. */
+void
+dirvote_clear_commits(networkstatus_t *ns)
+{
+ tor_assert(ns->type == NS_TYPE_VOTE);
+
+ if (ns->sr_info.commits) {
+ SMARTLIST_FOREACH(ns->sr_info.commits, sr_commit_t *, c,
+ sr_commit_free(c));
+ smartlist_free(ns->sr_info.commits);
+ }
+}
+
+/* The given url is the /tor/status-vote GET directory request. Populates the
+ * items list with strings that we can compress on the fly and dir_items with
+ * cached_dir_t objects that have a precompressed deflated version. */
+void
+dirvote_dirreq_get_status_vote(const char *url, smartlist_t *items,
+ smartlist_t *dir_items)
+{
+ int current;
+
+ url += strlen("/tor/status-vote/");
+ current = !strcmpstart(url, "current/");
+ url = strchr(url, '/');
+ tor_assert(url);
+ ++url;
+ if (!strcmp(url, "consensus")) {
+ const char *item;
+ tor_assert(!current); /* we handle current consensus specially above,
+ * since it wants to be spooled. */
+ if ((item = dirvote_get_pending_consensus(FLAV_NS)))
+ smartlist_add(items, (char*)item);
+ } else if (!current && !strcmp(url, "consensus-signatures")) {
+ /* XXXX the spec says that we should implement
+ * current/consensus-signatures too. It doesn't seem to be needed,
+ * though. */
+ const char *item;
+ if ((item=dirvote_get_pending_detached_signatures()))
+ smartlist_add(items, (char*)item);
+ } else if (!strcmp(url, "authority")) {
+ const cached_dir_t *d;
+ int flags = DGV_BY_ID |
+ (current ? DGV_INCLUDE_PREVIOUS : DGV_INCLUDE_PENDING);
+ if ((d=dirvote_get_vote(NULL, flags)))
+ smartlist_add(dir_items, (cached_dir_t*)d);
+ } else {
+ const cached_dir_t *d;
+ smartlist_t *fps = smartlist_new();
+ int flags;
+ if (!strcmpstart(url, "d/")) {
+ url += 2;
+ flags = DGV_INCLUDE_PENDING | DGV_INCLUDE_PREVIOUS;
+ } else {
+ flags = DGV_BY_ID |
+ (current ? DGV_INCLUDE_PREVIOUS : DGV_INCLUDE_PENDING);
+ }
+ dir_split_resource_into_fingerprints(url, fps, NULL,
+ DSR_HEX|DSR_SORT_UNIQ);
+ SMARTLIST_FOREACH(fps, char *, fp, {
+ if ((d = dirvote_get_vote(fp, flags)))
+ smartlist_add(dir_items, (cached_dir_t*)d);
+ tor_free(fp);
+ });
+ smartlist_free(fps);
+ }
+}
+
+/** Get the best estimate of a router's bandwidth for dirauth purposes,
+ * preferring measured to advertised values if available. */
+static uint32_t
+dirserv_get_bandwidth_for_router_kb(const routerinfo_t *ri)
+{
+ uint32_t bw_kb = 0;
+ /*
+ * Yeah, measured bandwidths in measured_bw_line_t are (implicitly
+ * signed) longs and the ones router_get_advertised_bandwidth() returns
+ * are uint32_t.
+ */
+ long mbw_kb = 0;
+
+ if (ri) {
+ /*
+ * * First try to see if we have a measured bandwidth; don't bother with
+ * as_of_out here, on the theory that a stale measured bandwidth is still
+ * better to trust than an advertised one.
+ */
+ if (dirserv_query_measured_bw_cache_kb(ri->cache_info.identity_digest,
+ &mbw_kb, NULL)) {
+ /* Got one! */
+ bw_kb = (uint32_t)mbw_kb;
+ } else {
+ /* If not, fall back to advertised */
+ bw_kb = router_get_advertised_bandwidth(ri) / 1000;
+ }
+ }
+
+ return bw_kb;
+}
+
+/** Helper for sorting: compares two routerinfos first by address, and then by
+ * descending order of "usefulness". (An authority is more useful than a
+ * non-authority; a running router is more useful than a non-running router;
+ * and a router with more bandwidth is more useful than one with less.)
+ **/
+static int
+compare_routerinfo_by_ip_and_bw_(const void **a, const void **b)
+{
+ routerinfo_t *first = *(routerinfo_t **)a, *second = *(routerinfo_t **)b;
+ int first_is_auth, second_is_auth;
+ uint32_t bw_kb_first, bw_kb_second;
+ const node_t *node_first, *node_second;
+ int first_is_running, second_is_running;
+
+ /* we return -1 if first should appear before second... that is,
+ * if first is a better router. */
+ if (first->addr < second->addr)
+ return -1;
+ else if (first->addr > second->addr)
+ return 1;
+
+ /* Potentially, this next bit could cause k n lg n memeq calls. But in
+ * reality, we will almost never get here, since addresses will usually be
+ * different. */
+
+ first_is_auth =
+ router_digest_is_trusted_dir(first->cache_info.identity_digest);
+ second_is_auth =
+ router_digest_is_trusted_dir(second->cache_info.identity_digest);
+
+ if (first_is_auth && !second_is_auth)
+ return -1;
+ else if (!first_is_auth && second_is_auth)
+ return 1;
+
+ node_first = node_get_by_id(first->cache_info.identity_digest);
+ node_second = node_get_by_id(second->cache_info.identity_digest);
+ first_is_running = node_first && node_first->is_running;
+ second_is_running = node_second && node_second->is_running;
+
+ if (first_is_running && !second_is_running)
+ return -1;
+ else if (!first_is_running && second_is_running)
+ return 1;
+
+ bw_kb_first = dirserv_get_bandwidth_for_router_kb(first);
+ bw_kb_second = dirserv_get_bandwidth_for_router_kb(second);
+
+ if (bw_kb_first > bw_kb_second)
+ return -1;
+ else if (bw_kb_first < bw_kb_second)
+ return 1;
+
+ /* They're equal! Compare by identity digest, so there's a
+ * deterministic order and we avoid flapping. */
+ return fast_memcmp(first->cache_info.identity_digest,
+ second->cache_info.identity_digest,
+ DIGEST_LEN);
+}
+
+/** Given a list of routerinfo_t in <b>routers</b>, return a new digestmap_t
+ * whose keys are the identity digests of those routers that we're going to
+ * exclude for Sybil-like appearance. */
+static digestmap_t *
+get_possible_sybil_list(const smartlist_t *routers)
+{
+ const or_options_t *options = get_options();
+ digestmap_t *omit_as_sybil;
+ smartlist_t *routers_by_ip = smartlist_new();
+ uint32_t last_addr;
+ int addr_count;
+ /* Allow at most this number of Tor servers on a single IP address, ... */
+ int max_with_same_addr = options->AuthDirMaxServersPerAddr;
+ if (max_with_same_addr <= 0)
+ max_with_same_addr = INT_MAX;
+
+ smartlist_add_all(routers_by_ip, routers);
+ smartlist_sort(routers_by_ip, compare_routerinfo_by_ip_and_bw_);
+ omit_as_sybil = digestmap_new();
+
+ last_addr = 0;
+ addr_count = 0;
+ SMARTLIST_FOREACH_BEGIN(routers_by_ip, routerinfo_t *, ri) {
+ if (last_addr != ri->addr) {
+ last_addr = ri->addr;
+ addr_count = 1;
+ } else if (++addr_count > max_with_same_addr) {
+ digestmap_set(omit_as_sybil, ri->cache_info.identity_digest, ri);
+ }
+ } SMARTLIST_FOREACH_END(ri);
+
+ smartlist_free(routers_by_ip);
+ return omit_as_sybil;
+}
+
+/** Given a platform string as in a routerinfo_t (possibly null), return a
+ * newly allocated version string for a networkstatus document, or NULL if the
+ * platform doesn't give a Tor version. */
+static char *
+version_from_platform(const char *platform)
+{
+ if (platform && !strcmpstart(platform, "Tor ")) {
+ const char *eos = find_whitespace(platform+4);
+ if (eos && !strcmpstart(eos, " (r")) {
+ /* XXXX Unify this logic with the other version extraction
+ * logic in routerparse.c. */
+ eos = find_whitespace(eos+1);
+ }
+ if (eos) {
+ return tor_strndup(platform, eos-platform);
+ }
+ }
+ return NULL;
+}
+
+/** Given a (possibly empty) list of config_line_t, each line of which contains
+ * a list of comma-separated version numbers surrounded by optional space,
+ * allocate and return a new string containing the version numbers, in order,
+ * separated by commas. Used to generate Recommended(Client|Server)?Versions
+ */
- static char *
- format_versions_list(config_line_t *ln)
++char *
++format_recommended_version_list(const config_line_t *ln, int warn)
+{
+ smartlist_t *versions;
+ char *result;
+ versions = smartlist_new();
+ for ( ; ln; ln = ln->next) {
+ smartlist_split_string(versions, ln->value, ",",
+ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ }
++
++ /* Handle the case where a dirauth operator has accidentally made some
++ * versions space-separated instead of comma-separated. */
++ smartlist_t *more_versions = smartlist_new();
++ SMARTLIST_FOREACH_BEGIN(versions, char *, v) {
++ if (strchr(v, ' ')) {
++ if (warn)
++ log_warn(LD_DIRSERV, "Unexpected space in versions list member %s. "
++ "(These are supposed to be comma-separated; I'll pretend you "
++ "used commas instead.)", escaped(v));
++ SMARTLIST_DEL_CURRENT(versions, v);
++ smartlist_split_string(more_versions, v, NULL,
++ SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
++ tor_free(v);
++ }
++ } SMARTLIST_FOREACH_END(v);
++ smartlist_add_all(versions, more_versions);
++ smartlist_free(more_versions);
++
++ /* Check to make sure everything looks like a version. */
++ if (warn) {
++ SMARTLIST_FOREACH_BEGIN(versions, const char *, v) {
++ tor_version_t ver;
++ if (tor_version_parse(v, &ver) < 0) {
++ log_warn(LD_DIRSERV, "Recommended version %s does not look valid. "
++ " (I'll include it anyway, since you told me to.)",
++ escaped(v));
++ }
++ } SMARTLIST_FOREACH_END(v);
++ }
++
+ sort_version_list(versions, 1);
+ result = smartlist_join_strings(versions,",",0,NULL);
+ SMARTLIST_FOREACH(versions,char *,s,tor_free(s));
+ smartlist_free(versions);
+ return result;
+}
+
+/** If there are entries in <b>routers</b> with exactly the same ed25519 keys,
+ * remove the older one. If they are exactly the same age, remove the one
+ * with the greater descriptor digest. May alter the order of the list. */
+static void
+routers_make_ed_keys_unique(smartlist_t *routers)
+{
+ routerinfo_t *ri2;
+ digest256map_t *by_ed_key = digest256map_new();
+
+ SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
+ ri->omit_from_vote = 0;
+ if (ri->cache_info.signing_key_cert == NULL)
+ continue; /* No ed key */
+ const uint8_t *pk = ri->cache_info.signing_key_cert->signing_key.pubkey;
+ if ((ri2 = digest256map_get(by_ed_key, pk))) {
+ /* Duplicate; must omit one. Set the omit_from_vote flag in whichever
+ * one has the earlier published_on. */
+ const time_t ri_pub = ri->cache_info.published_on;
+ const time_t ri2_pub = ri2->cache_info.published_on;
+ if (ri2_pub < ri_pub ||
+ (ri2_pub == ri_pub &&
+ fast_memcmp(ri->cache_info.signed_descriptor_digest,
+ ri2->cache_info.signed_descriptor_digest,DIGEST_LEN)<0)) {
+ digest256map_set(by_ed_key, pk, ri);
+ ri2->omit_from_vote = 1;
+ } else {
+ ri->omit_from_vote = 1;
+ }
+ } else {
+ /* Add to map */
+ digest256map_set(by_ed_key, pk, ri);
+ }
+ } SMARTLIST_FOREACH_END(ri);
+
+ digest256map_free(by_ed_key, NULL);
+
+ /* Now remove every router where the omit_from_vote flag got set. */
+ SMARTLIST_FOREACH_BEGIN(routers, const routerinfo_t *, ri) {
+ if (ri->omit_from_vote) {
+ SMARTLIST_DEL_CURRENT(routers, ri);
+ }
+ } SMARTLIST_FOREACH_END(ri);
+}
+
+/** Routerstatus <b>rs</b> is part of a group of routers that are on
+ * too narrow an IP-space. Clear out its flags since we don't want it be used
+ * because of its Sybil-like appearance.
+ *
+ * Leave its BadExit flag alone though, since if we think it's a bad exit,
+ * we want to vote that way in case all the other authorities are voting
+ * Running and Exit.
+ */
+static void
+clear_status_flags_on_sybil(routerstatus_t *rs)
+{
+ rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast =
+ rs->is_flagged_running = rs->is_named = rs->is_valid =
+ rs->is_hs_dir = rs->is_v2_dir = rs->is_possible_guard = 0;
+ /* FFFF we might want some mechanism to check later on if we
+ * missed zeroing any flags: it's easy to add a new flag but
+ * forget to add it to this clause. */
+}
+
+/** Return a new networkstatus_t* containing our current opinion. (For v3
+ * authorities) */
+networkstatus_t *
+dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
+ authority_cert_t *cert)
+{
+ const or_options_t *options = get_options();
+ networkstatus_t *v3_out = NULL;
+ uint32_t addr;
+ char *hostname = NULL, *client_versions = NULL, *server_versions = NULL;
+ const char *contact;
+ smartlist_t *routers, *routerstatuses;
+ char identity_digest[DIGEST_LEN];
+ char signing_key_digest[DIGEST_LEN];
+ int listbadexits = options->AuthDirListBadExits;
+ routerlist_t *rl = router_get_routerlist();
+ time_t now = time(NULL);
+ time_t cutoff = now - ROUTER_MAX_AGE_TO_PUBLISH;
+ networkstatus_voter_info_t *voter = NULL;
+ vote_timing_t timing;
+ digestmap_t *omit_as_sybil = NULL;
+ const int vote_on_reachability = running_long_enough_to_decide_unreachable();
+ smartlist_t *microdescriptors = NULL;
+
+ tor_assert(private_key);
+ tor_assert(cert);
+
+ if (crypto_pk_get_digest(private_key, signing_key_digest)<0) {
+ log_err(LD_BUG, "Error computing signing key digest");
+ return NULL;
+ }
+ if (crypto_pk_get_digest(cert->identity_key, identity_digest)<0) {
+ log_err(LD_BUG, "Error computing identity key digest");
+ return NULL;
+ }
+ if (resolve_my_address(LOG_WARN, options, &addr, NULL, &hostname)<0) {
+ log_warn(LD_NET, "Couldn't resolve my hostname");
+ return NULL;
+ }
+ if (!hostname || !strchr(hostname, '.')) {
+ tor_free(hostname);
+ hostname = tor_dup_ip(addr);
+ }
+
+ if (options->VersioningAuthoritativeDir) {
- client_versions = format_versions_list(options->RecommendedClientVersions);
- server_versions = format_versions_list(options->RecommendedServerVersions);
++ client_versions =
++ format_recommended_version_list(options->RecommendedClientVersions, 0);
++ server_versions =
++ format_recommended_version_list(options->RecommendedServerVersions, 0);
+ }
+
+ contact = get_options()->ContactInfo;
+ if (!contact)
+ contact = "(none)";
+
+ /*
+ * Do this so dirserv_compute_performance_thresholds() and
+ * set_routerstatus_from_routerinfo() see up-to-date bandwidth info.
+ */
+ if (options->V3BandwidthsFile) {
+ dirserv_read_measured_bandwidths(options->V3BandwidthsFile, NULL);
+ } else {
+ /*
+ * No bandwidths file; clear the measured bandwidth cache in case we had
+ * one last time around.
+ */
+ if (dirserv_get_measured_bw_cache_size() > 0) {
+ dirserv_clear_measured_bw_cache();
+ }
+ }
+
+ /* precompute this part, since we need it to decide what "stable"
+ * means. */
+ SMARTLIST_FOREACH(rl->routers, routerinfo_t *, ri, {
+ dirserv_set_router_is_running(ri, now);
+ });
+
+ routers = smartlist_new();
+ smartlist_add_all(routers, rl->routers);
+ routers_make_ed_keys_unique(routers);
+ /* After this point, don't use rl->routers; use 'routers' instead. */
+ routers_sort_by_identity(routers);
+ omit_as_sybil = get_possible_sybil_list(routers);
+
+ DIGESTMAP_FOREACH(omit_as_sybil, sybil_id, void *, ignore) {
+ (void) ignore;
+ rep_hist_make_router_pessimal(sybil_id, now);
+ } DIGESTMAP_FOREACH_END;
+
+ /* Count how many have measured bandwidths so we know how to assign flags;
+ * this must come before dirserv_compute_performance_thresholds() */
+ dirserv_count_measured_bws(routers);
+
+ dirserv_compute_performance_thresholds(omit_as_sybil);
+
+ routerstatuses = smartlist_new();
+ microdescriptors = smartlist_new();
+
+ SMARTLIST_FOREACH_BEGIN(routers, routerinfo_t *, ri) {
+ /* If it has a protover list and contains a protocol name greater than
+ * MAX_PROTOCOL_NAME_LENGTH, skip it. */
+ if (ri->protocol_list &&
+ protover_contains_long_protocol_names(ri->protocol_list)) {
+ continue;
+ }
+ if (ri->cache_info.published_on >= cutoff) {
+ routerstatus_t *rs;
+ vote_routerstatus_t *vrs;
+ node_t *node = node_get_mutable_by_id(ri->cache_info.identity_digest);
+ if (!node)
+ continue;
+
+ vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
+ rs = &vrs->status;
+ set_routerstatus_from_routerinfo(rs, node, ri, now,
+ listbadexits);
+
+ if (ri->cache_info.signing_key_cert) {
+ memcpy(vrs->ed25519_id,
+ ri->cache_info.signing_key_cert->signing_key.pubkey,
+ ED25519_PUBKEY_LEN);
+ }
+
+ if (digestmap_get(omit_as_sybil, ri->cache_info.identity_digest))
+ clear_status_flags_on_sybil(rs);
+
+ if (!vote_on_reachability)
+ rs->is_flagged_running = 0;
+
+ vrs->version = version_from_platform(ri->platform);
+ if (ri->protocol_list) {
+ vrs->protocols = tor_strdup(ri->protocol_list);
+ } else {
+ vrs->protocols = tor_strdup(
+ protover_compute_for_old_tor(vrs->version));
+ }
+ vrs->microdesc = dirvote_format_all_microdesc_vote_lines(ri, now,
+ microdescriptors);
+
+ smartlist_add(routerstatuses, vrs);
+ }
+ } SMARTLIST_FOREACH_END(ri);
+
+ {
+ smartlist_t *added =
+ microdescs_add_list_to_cache(get_microdesc_cache(),
+ microdescriptors, SAVED_NOWHERE, 0);
+ smartlist_free(added);
+ smartlist_free(microdescriptors);
+ }
+
+ smartlist_free(routers);
+ digestmap_free(omit_as_sybil, NULL);
+
+ /* Apply guardfraction information to routerstatuses. */
+ if (options->GuardfractionFile) {
+ dirserv_read_guardfraction_file(options->GuardfractionFile,
+ routerstatuses);
+ }
+
+ /* This pass through applies the measured bw lines to the routerstatuses */
+ if (options->V3BandwidthsFile) {
+ dirserv_read_measured_bandwidths(options->V3BandwidthsFile,
+ routerstatuses);
+ } else {
+ /*
+ * No bandwidths file; clear the measured bandwidth cache in case we had
+ * one last time around.
+ */
+ if (dirserv_get_measured_bw_cache_size() > 0) {
+ dirserv_clear_measured_bw_cache();
+ }
+ }
+
+ v3_out = tor_malloc_zero(sizeof(networkstatus_t));
+
+ v3_out->type = NS_TYPE_VOTE;
+ dirvote_get_preferred_voting_intervals(&timing);
+ v3_out->published = now;
+ {
+ char tbuf[ISO_TIME_LEN+1];
+ networkstatus_t *current_consensus =
+ networkstatus_get_live_consensus(now);
+ long last_consensus_interval; /* only used to pick a valid_after */
+ if (current_consensus)
+ last_consensus_interval = current_consensus->fresh_until -
+ current_consensus->valid_after;
+ else
+ last_consensus_interval = options->TestingV3AuthInitialVotingInterval;
+ v3_out->valid_after =
+ voting_schedule_get_start_of_next_interval(now,
+ (int)last_consensus_interval,
+ options->TestingV3AuthVotingStartOffset);
+ format_iso_time(tbuf, v3_out->valid_after);
+ log_notice(LD_DIR,"Choosing valid-after time in vote as %s: "
+ "consensus_set=%d, last_interval=%d",
+ tbuf, current_consensus?1:0, (int)last_consensus_interval);
+ }
+ v3_out->fresh_until = v3_out->valid_after + timing.vote_interval;
+ v3_out->valid_until = v3_out->valid_after +
+ (timing.vote_interval * timing.n_intervals_valid);
+ v3_out->vote_seconds = timing.vote_delay;
+ v3_out->dist_seconds = timing.dist_delay;
+ tor_assert(v3_out->vote_seconds > 0);
+ tor_assert(v3_out->dist_seconds > 0);
+ tor_assert(timing.n_intervals_valid > 0);
+
+ v3_out->client_versions = client_versions;
+ v3_out->server_versions = server_versions;
+
+ /* These are hardwired, to avoid disaster. */
+ v3_out->recommended_relay_protocols =
+ tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=4 LinkAuth=1 Microdesc=1-2 Relay=2");
+ v3_out->recommended_client_protocols =
+ tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=4 LinkAuth=1 Microdesc=1-2 Relay=2");
+ v3_out->required_client_protocols =
+ tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=4 LinkAuth=1 Microdesc=1-2 Relay=2");
+ v3_out->required_relay_protocols =
+ tor_strdup("Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 "
+ "Link=3-4 LinkAuth=1 Microdesc=1 Relay=1-2");
+
+ /* We are not allowed to vote to require anything we don't have. */
+ tor_assert(protover_all_supported(v3_out->required_relay_protocols, NULL));
+ tor_assert(protover_all_supported(v3_out->required_client_protocols, NULL));
+
+ /* We should not recommend anything we don't have. */
+ tor_assert_nonfatal(protover_all_supported(
+ v3_out->recommended_relay_protocols, NULL));
+ tor_assert_nonfatal(protover_all_supported(
+ v3_out->recommended_client_protocols, NULL));
+
+ v3_out->package_lines = smartlist_new();
+ {
+ config_line_t *cl;
+ for (cl = get_options()->RecommendedPackages; cl; cl = cl->next) {
+ if (validate_recommended_package_line(cl->value))
+ smartlist_add_strdup(v3_out->package_lines, cl->value);
+ }
+ }
+
+ v3_out->known_flags = smartlist_new();
+ smartlist_split_string(v3_out->known_flags,
+ "Authority Exit Fast Guard Stable V2Dir Valid HSDir",
+ 0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+ if (vote_on_reachability)
+ smartlist_add_strdup(v3_out->known_flags, "Running");
+ if (listbadexits)
+ smartlist_add_strdup(v3_out->known_flags, "BadExit");
+ smartlist_sort_strings(v3_out->known_flags);
+
+ if (options->ConsensusParams) {
+ v3_out->net_params = smartlist_new();
+ smartlist_split_string(v3_out->net_params,
+ options->ConsensusParams, NULL, 0, 0);
+ smartlist_sort_strings(v3_out->net_params);
+ }
+
+ voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t));
+ voter->nickname = tor_strdup(options->Nickname);
+ memcpy(voter->identity_digest, identity_digest, DIGEST_LEN);
+ voter->sigs = smartlist_new();
+ voter->address = hostname;
+ voter->addr = addr;
+ voter->dir_port = router_get_advertised_dir_port(options, 0);
+ voter->or_port = router_get_advertised_or_port(options);
+ voter->contact = tor_strdup(contact);
+ if (options->V3AuthUseLegacyKey) {
+ authority_cert_t *c = get_my_v3_legacy_cert();
+ if (c) {
+ if (crypto_pk_get_digest(c->identity_key, voter->legacy_id_digest)) {
+ log_warn(LD_BUG, "Unable to compute digest of legacy v3 identity key");
+ memset(voter->legacy_id_digest, 0, DIGEST_LEN);
+ }
+ }
+ }
+
+ v3_out->voters = smartlist_new();
+ smartlist_add(v3_out->voters, voter);
+ v3_out->cert = authority_cert_dup(cert);
+ v3_out->routerstatus_list = routerstatuses;
+ /* Note: networkstatus_digest is unset; it won't get set until we actually
+ * format the vote. */
+
+ return v3_out;
+}
diff --cc src/feature/dircache/dirserv.h
index 3b4a64609,000000000..d399c4109
mode 100644,000000..100644
--- a/src/feature/dircache/dirserv.h
+++ b/src/feature/dircache/dirserv.h
@@@ -1,239 -1,0 +1,239 @@@
+/* Copyright (c) 2001 Matej Pfajfar.
+ * Copyright (c) 2001-2004, Roger Dingledine.
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file dirserv.h
+ * \brief Header file for dirserv.c.
+ **/
+
+#ifndef TOR_DIRSERV_H
+#define TOR_DIRSERV_H
+
+struct ed25519_public_key_t;
+
+#include "lib/testsupport/testsupport.h"
+
+/** An enum to describe what format we're generating a routerstatus line in.
+ */
+typedef enum {
+ /** For use in a v2 opinion */
+ NS_V2,
+ /** For use in a consensus networkstatus document (ns flavor) */
+ NS_V3_CONSENSUS,
+ /** For use in a vote networkstatus document */
+ NS_V3_VOTE,
+ /** For passing to the controlport in response to a GETINFO request */
+ NS_CONTROL_PORT,
+ /** For use in a consensus networkstatus document (microdesc flavor) */
+ NS_V3_CONSENSUS_MICRODESC
+} routerstatus_format_type_t;
+
+/** What fraction (1 over this number) of the relay ID space do we
+ * (as a directory authority) launch connections to at each reachability
+ * test? */
+#define REACHABILITY_MODULO_PER_TEST 128
+
+/** How often (in seconds) do we launch reachability tests? */
+#define REACHABILITY_TEST_INTERVAL 10
+
+/** How many seconds apart are the reachability tests for a given relay? */
+#define REACHABILITY_TEST_CYCLE_PERIOD \
+ (REACHABILITY_TEST_INTERVAL*REACHABILITY_MODULO_PER_TEST)
+
+/** Maximum length of an exit policy summary. */
+#define MAX_EXITPOLICY_SUMMARY_LEN 1000
+
+/** Maximum allowable length of a version line in a networkstatus. */
+#define MAX_V_LINE_LEN 128
+
+/** Ways to convert a spoolable_resource_t to a bunch of bytes. */
+typedef enum dir_spool_source_t {
+ DIR_SPOOL_SERVER_BY_DIGEST=1, DIR_SPOOL_SERVER_BY_FP,
+ DIR_SPOOL_EXTRA_BY_DIGEST, DIR_SPOOL_EXTRA_BY_FP,
+ DIR_SPOOL_MICRODESC,
+ DIR_SPOOL_NETWORKSTATUS,
+ DIR_SPOOL_CONSENSUS_CACHE_ENTRY,
+} dir_spool_source_t;
+#define dir_spool_source_bitfield_t ENUM_BF(dir_spool_source_t)
+
+/** Object to remember the identity of an object that we are spooling,
+ * or about to spool, in response to a directory request.
+ *
+ * (Why do we spool? Because some directory responses are very large,
+ * and we don't want to just shove the complete answer into the output
+ * buffer: that would take a ridiculous amount of RAM.)
+ *
+ * If the spooled resource is relatively small (like microdescriptors,
+ * descriptors, etc), we look them up by ID as needed, and add the whole
+ * thing onto the output buffer at once. If the spooled reseource is
+ * big (like networkstatus documents), we reference-count it, and add it
+ * a few K at a time.
+ */
+typedef struct spooled_resource_t {
+ /**
+ * If true, we add the entire object to the outbuf. If false,
+ * we spool the object a few K at a time.
+ */
+ unsigned spool_eagerly : 1;
+ /**
+ * Tells us what kind of object to get, and how to look it up.
+ */
+ dir_spool_source_bitfield_t spool_source : 7;
+ /**
+ * Tells us the specific object to spool.
+ */
+ uint8_t digest[DIGEST256_LEN];
+ /**
+ * A large object that we're spooling. Holds a reference count. Only
+ * used when spool_eagerly is false.
+ */
+ struct cached_dir_t *cached_dir_ref;
+ /**
+ * A different kind of large object that we might be spooling. Also
+ * reference-counted. Also only used when spool_eagerly is false.
+ */
+ struct consensus_cache_entry_t *consensus_cache_entry;
+ const uint8_t *cce_body;
+ size_t cce_len;
+ /**
+ * The current offset into cached_dir or cce_body. Only used when
+ * spool_eagerly is false */
+ off_t cached_dir_offset;
+} spooled_resource_t;
+
+#ifdef DIRSERV_PRIVATE
+typedef struct measured_bw_line_t {
+ char node_id[DIGEST_LEN];
+ char node_hex[MAX_HEX_NICKNAME_LEN+1];
+ long int bw_kb;
+} measured_bw_line_t;
+#endif /* defined(DIRSERV_PRIVATE) */
+
+int connection_dirserv_flushed_some(dir_connection_t *conn);
+
+int dirserv_add_own_fingerprint(crypto_pk_t *pk);
+int dirserv_load_fingerprint_file(void);
+void dirserv_free_fingerprint_list(void);
+enum was_router_added_t dirserv_add_multiple_descriptors(
+ const char *desc, uint8_t purpose,
+ const char *source,
+ const char **msg);
+enum was_router_added_t dirserv_add_descriptor(routerinfo_t *ri,
+ const char **msg,
+ const char *source);
+void dirserv_set_router_is_running(routerinfo_t *router, time_t now);
+int list_server_status_v1(smartlist_t *routers, char **router_status_out,
+ int for_controller);
+char *dirserv_get_flag_thresholds_line(void);
+void dirserv_compute_bridge_flag_thresholds(void);
+
+int directory_fetches_from_authorities(const or_options_t *options);
+int directory_fetches_dir_info_early(const or_options_t *options);
+int directory_fetches_dir_info_later(const or_options_t *options);
+int directory_caches_unknown_auth_certs(const or_options_t *options);
+int directory_caches_dir_info(const or_options_t *options);
+int directory_permits_begindir_requests(const or_options_t *options);
+int directory_too_idle_to_fetch_descriptors(const or_options_t *options,
+ time_t now);
+
+cached_dir_t *dirserv_get_consensus(const char *flavor_name);
+void dirserv_set_cached_consensus_networkstatus(const char *consensus,
+ const char *flavor_name,
+ const common_digests_t *digests,
+ const uint8_t *sha3_as_signed,
+ time_t published);
+void dirserv_clear_old_networkstatuses(time_t cutoff);
+int dirserv_get_routerdesc_spool(smartlist_t *spools_out, const char *key,
+ dir_spool_source_t source,
+ int conn_is_encrypted,
+ const char **msg_out);
+int dirserv_get_routerdescs(smartlist_t *descs_out, const char *key,
+ const char **msg);
+void dirserv_orconn_tls_done(const tor_addr_t *addr,
+ uint16_t or_port,
+ const char *digest_rcvd,
+ const struct ed25519_public_key_t *ed_id_rcvd);
+int dirserv_should_launch_reachability_test(const routerinfo_t *ri,
+ const routerinfo_t *ri_old);
+void dirserv_single_reachability_test(time_t now, routerinfo_t *router);
+void dirserv_test_reachability(time_t now);
+int authdir_wants_to_reject_router(routerinfo_t *ri, const char **msg,
+ int complain,
+ int *valid_out);
+uint32_t dirserv_router_get_status(const routerinfo_t *router,
+ const char **msg,
+ int severity);
+void dirserv_set_node_flags_from_authoritative_status(node_t *node,
+ uint32_t authstatus);
+
+int dirserv_would_reject_router(const routerstatus_t *rs);
+char *routerstatus_format_entry(
+ const routerstatus_t *rs,
+ const char *version,
+ const char *protocols,
+ routerstatus_format_type_t format,
+ int consensus_method,
+ const vote_routerstatus_t *vrs);
+void dirserv_free_all(void);
+void cached_dir_decref(cached_dir_t *d);
+cached_dir_t *new_cached_dir(char *s, time_t published);
-
++char *format_recommended_version_list(const config_line_t *line, int warn);
+int validate_recommended_package_line(const char *line);
+int dirserv_query_measured_bw_cache_kb(const char *node_id,
+ long *bw_out,
+ time_t *as_of_out);
+void dirserv_clear_measured_bw_cache(void);
+int dirserv_has_measured_bw(const char *node_id);
+int dirserv_get_measured_bw_cache_size(void);
+void dirserv_count_measured_bws(const smartlist_t *routers);
+int running_long_enough_to_decide_unreachable(void);
+void dirserv_compute_performance_thresholds(digestmap_t *omit_as_sybil);
+
+#ifdef DIRSERV_PRIVATE
+
+STATIC void dirserv_set_routerstatus_testing(routerstatus_t *rs);
+
+/* Put the MAX_MEASUREMENT_AGE #define here so unit tests can see it */
+#define MAX_MEASUREMENT_AGE (3*24*60*60) /* 3 days */
+
+STATIC int measured_bw_line_parse(measured_bw_line_t *out, const char *line,
+ int line_is_after_headers);
+
+STATIC int measured_bw_line_apply(measured_bw_line_t *parsed_line,
+ smartlist_t *routerstatuses);
+
+STATIC void dirserv_cache_measured_bw(const measured_bw_line_t *parsed_line,
+ time_t as_of);
+STATIC void dirserv_expire_measured_bw_cache(time_t now);
+
+STATIC int
+dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
+ smartlist_t *vote_routerstatuses);
+#endif /* defined(DIRSERV_PRIVATE) */
+
+int dirserv_read_measured_bandwidths(const char *from_file,
+ smartlist_t *routerstatuses);
+
+int dirserv_read_guardfraction_file(const char *fname,
+ smartlist_t *vote_routerstatuses);
+
+spooled_resource_t *spooled_resource_new(dir_spool_source_t source,
+ const uint8_t *digest,
+ size_t digestlen);
+spooled_resource_t *spooled_resource_new_from_cache_entry(
+ struct consensus_cache_entry_t *entry);
+void spooled_resource_free_(spooled_resource_t *spooled);
+#define spooled_resource_free(sp) \
+ FREE_AND_NULL(spooled_resource_t, spooled_resource_free_, (sp))
+void dirserv_spool_remove_missing_and_guess_size(dir_connection_t *conn,
+ time_t cutoff,
+ int compression,
+ size_t *size_out,
+ int *n_expired_out);
+void dirserv_spool_sort(dir_connection_t *conn);
+void dir_conn_clear_spool(dir_connection_t *conn);
+
+#endif /* !defined(TOR_DIRSERV_H) */
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits