diff options
Diffstat (limited to 'src/app/config/config.c')
-rw-r--r-- | src/app/config/config.c | 8521 |
1 files changed, 8521 insertions, 0 deletions
diff --git a/src/app/config/config.c b/src/app/config/config.c new file mode 100644 index 0000000000..0b1b758d96 --- /dev/null +++ b/src/app/config/config.c @@ -0,0 +1,8521 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2019, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file 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 "app/config/config.h" +#include "app/config/confparse.h" +#include "app/config/statefile.h" +#include "app/main/main.h" +#include "core/mainloop/connection.h" +#include "core/mainloop/cpuworker.h" +#include "core/mainloop/mainloop.h" +#include "core/mainloop/netstatus.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 "core/or/connection_edge.h" +#include "core/or/connection_or.h" +#include "core/or/dos.h" +#include "core/or/policies.h" +#include "core/or/relay.h" +#include "core/or/scheduler.h" +#include "feature/client/addressmap.h" +#include "feature/client/bridges.h" +#include "feature/client/entrynodes.h" +#include "feature/client/transports.h" +#include "feature/control/control.h" +#include "feature/dirauth/bwauth.h" +#include "feature/dirauth/guardfraction.h" +#include "feature/dircache/consdiffmgr.h" +#include "feature/dircache/dirserv.h" +#include "feature/dircommon/voting_schedule.h" +#include "feature/hibernate/hibernate.h" +#include "feature/hs/hs_config.h" +#include "feature/nodelist/dirlist.h" +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/nickname.h" +#include "feature/nodelist/nodelist.h" +#include "feature/nodelist/routerlist.h" +#include "feature/nodelist/routerset.h" +#include "feature/relay/dns.h" +#include "feature/relay/ext_orport.h" +#include "feature/relay/routermode.h" +#include "feature/rend/rendclient.h" +#include "feature/rend/rendservice.h" +#include "lib/geoip/geoip.h" +#include "feature/stats/geoip_stats.h" +#include "feature/stats/predict_ports.h" +#include "feature/stats/rephist.h" +#include "lib/compress/compress.h" +#include "lib/crypt_ops/crypto_init.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "lib/crypt_ops/crypto_util.h" +#include "lib/encoding/confline.h" +#include "lib/log/git_revision.h" +#include "lib/net/resolve.h" +#include "lib/sandbox/sandbox.h" + +#ifdef ENABLE_NSS +#include "lib/crypt_ops/crypto_nss_mgt.h" +#else +#include "lib/crypt_ops/crypto_openssl_mgt.h" +#endif + +#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_SYS_PARAM_H +#include <sys/param.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/recommend_pkg.h" +#include "feature/dirauth/authmode.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(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) + +/** UINT64_MAX as a decimal string */ +#define UINT64_MAX_STRING "18446744073709551615" + +/** 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, AUTOBOOL, "auto"), + 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("HiddenServiceExportCircuitID", LINELIST_S, RendConfigLines, NULL), + VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"), + V(HidServAuth, LINELIST, NULL), + V(ClientOnionAuthDir, FILENAME, 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"), + OBSOLETE("Tor2webMode"), + OBSOLETE("Tor2webRendezvousPoints"), + 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", UINT64, OwningControllerFD, UINT64_MAX_STRING), + 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 options_free_cb(void *options); +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, + options_free_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); + } + global_options = old_options; + 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_opts(const or_options_t *options, const char *address) +{ + tor_assert(address); + if (!options) { + options = get_options(); + } + + if (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_opts(const or_options_t *options, const char *address) +{ + tor_assert(address); + if (!options) { + options = get_options(); + } + + if (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(); + 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) { + if (! start_daemon_has_been_called()) + crypto_prefork(); + /* No need to roll back, since you can't change the value. */ + if (start_daemon()) + crypto_postfork(); + } + +#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(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; + } + + /* We need to handle the group-readable flag for the cache directory + * specially, since the directory defaults to being the same as the + * DataDirectory. */ + int cache_dir_group_readable; + if (options->CacheDirectoryGroupReadable != -1) { + /* If the user specified a value, use their setting */ + cache_dir_group_readable = options->CacheDirectoryGroupReadable; + } else if (!strcmp(options->CacheDirectory, options->DataDirectory)) { + /* If the user left the value as "auto", and the cache is the same as the + * datadirectory, use the datadirectory setting. + */ + cache_dir_group_readable = options->DataDirectoryGroupReadable; + } else { + /* Otherwise, "auto" means "not group readable". */ + cache_dir_group_readable = 0; + } + if (check_and_create_data_directory(running_tor /* create */, + options->CacheDirectory, + cache_dir_group_readable, + 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); + } + + 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); + 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); + + 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."); + } + + /* 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 && hs_config_client_auth_all(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 != UINT64_MAX) { + 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; + } + } + + /* 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) || + 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)) || + !config_lines_eq(old_options->Bridges, options->Bridges)) { + /* 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 }, + { "--list-modules", 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-2019, 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); + } +} + +/** Print all compile-time modules and their enabled/disabled status. */ +static void +list_enabled_modules(void) +{ + printf("%s: %s\n", "dirauth", have_module_dirauth() ? "yes" : "no"); +} + +/** 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; +} + +/** Callback to free an or_options_t */ +static void +options_free_cb(void *options) +{ + or_options_free_(options); +} + +#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); + n += warn_if_option_path_is_relative("ClientOnionAuthDir", + options->ClientOnionAuthDir); + + 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) { + 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 (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) +{ + 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 (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"); + + /* need to check for relative paths after we populate + * options->DataDirectory (just above). */ + 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 (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."); + const char *ContactInfo = options->ContactInfo; + if (ContactInfo && !string_is_utf8(ContactInfo, strlen(ContactInfo))) + REJECT("ContactInfo config option must be UTF-8."); + + /* 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."); + +#ifdef HAVE_MODULE_DIRAUTH + 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); +#endif + + 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, 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\""); + } + } + } + + 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."); + } + + 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 (hs_config_client_auth_all(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. We will not become a Guard."); + } + } + 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, "--list-modules")) { + list_enabled_modules(); + 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()); +#ifdef ENABLE_OPENSSL + printf("OpenSSL \t\t%-15s\t\t%s\n", + crypto_openssl_get_header_version_str(), + crypto_openssl_get_version_str()); +#endif +#ifdef ENABLE_NSS + printf("NSS \t\t%-15s\t\t%s\n", + crypto_nss_get_header_version_str(), + crypto_nss_get_version_str()); +#endif + 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; + tor_addr_make_unspec(&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_INET) { + log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv4", + portname); + goto err; + } + if (bind_ipv6_only && tor_addr_family(&addr) != AF_INET6) { + log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv6", + 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 public relay to listen only on an IPv6 " + "address. Tor needs to listen on an IPv4 address too."); + 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_UINT64: 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) +{ + const or_options_t *options = get_options(); + const char *msg = ""; + int severity = options_need_geoip_info(options, &msg) ? LOG_WARN : LOG_INFO; + int r; + +#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; + } + r = geoip_load_file(family, fname, severity); + tor_free(free_fname); +#else /* !(defined(_WIN32)) */ + (void)default_fname; + r = geoip_load_file(family, fname, severity); +#endif /* defined(_WIN32) */ + + if (r < 0 && severity == LOG_WARN) { + log_warn(LD_GENERAL, "%s", msg); + } +} + +/** 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"); + /* Okay, now we need to maybe change our mind about what is in + * which country. We do this for IPv4 only since that's what we + * store in node->country. */ + refresh_all_country_info(); + } + 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); +} |