tor-commits
Threads by month
- ----- 2025 -----
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
April 2019
- 18 participants
- 3314 discussions

19 Apr '19
commit add0f89c14b4aab6726e11acdcd864ee0c91543b
Author: Roger Dingledine <arma(a)torproject.org>
Date: Sat Mar 9 02:55:28 2019 -0500
relays shouldn't close idle rend circuits
Allow connections to single onion services to remain idle without being
disconnected.
Relays acting as rendezvous points for single onion services were
mistakenly closing idle established rendezvous circuits after 60 seconds,
thinking that they are unused directory-fetching circuits that had served
their purpose.
Fixes bug 29665; bugfix on 0.2.1.26.
---
changes/bug29665 | 7 +++++++
src/or/circuituse.c | 5 ++++-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/changes/bug29665 b/changes/bug29665
new file mode 100644
index 000000000..d89046faf
--- /dev/null
+++ b/changes/bug29665
@@ -0,0 +1,7 @@
+ o Minor bugfixes (single onion services):
+ - Allow connections to single onion services to remain idle without
+ being disconnected. Relays acting as rendezvous points for
+ single onion services were mistakenly closing idle established
+ rendezvous circuits after 60 seconds, thinking that they are unused
+ directory-fetching circuits that had served their purpose. Fixes
+ bug 29665; bugfix on 0.2.1.26.
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index 96cd3cd7e..7c0b60293 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -1381,10 +1381,13 @@ circuit_expire_old_circuits_serverside(time_t now)
or_circ = TO_OR_CIRCUIT(circ);
/* If the circuit has been idle for too long, and there are no streams
* on it, and it ends here, and it used a create_fast, mark it for close.
+ *
+ * Also if there is a rend_splice on it, it's a single onion service
+ * circuit and we should not close it.
*/
if (or_circ->is_first_hop && !circ->n_chan &&
!or_circ->n_streams && !or_circ->resolving_streams &&
- or_circ->p_chan &&
+ or_circ->p_chan && !or_circ->rend_splice &&
channel_when_last_xmit(or_circ->p_chan) <= cutoff) {
log_info(LD_CIRC, "Closing circ_id %u (empty %d secs ago)",
(unsigned)or_circ->p_circ_id,
1
0

[tor/release-0.4.0] Merge remote-tracking branch 'tor-github/pr/947' into maint-0.3.5
by teor@torproject.org 19 Apr '19
by teor@torproject.org 19 Apr '19
19 Apr '19
commit 4a66c2d63634529dc409e4756ba3ffa461444519
Merge: 8ff6ab0a3 a6d0420f7
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 11:47:50 2019 +1000
Merge remote-tracking branch 'tor-github/pr/947' into maint-0.3.5
changes/bug27199 | 3 +++
src/rust/Cargo.toml | 11 +++++++++++
2 files changed, 14 insertions(+)
1
0

[tor/release-0.4.0] Merge remote-tracking branch 'tor-github/pr/946' into maint-0.3.4
by teor@torproject.org 19 Apr '19
by teor@torproject.org 19 Apr '19
19 Apr '19
commit 116970dda7d04bfdc1cff0034d5ee9969a6754c4
Merge: b1762f8d1 5ab5c8ec1
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 11:47:10 2019 +1000
Merge remote-tracking branch 'tor-github/pr/946' into maint-0.3.4
changes/bug27199 | 3 +++
src/rust/Cargo.toml | 10 ++++++++++
2 files changed, 13 insertions(+)
1
0

19 Apr '19
commit 231036a110c1062e39b214b4b88fdc2a1eb46dc8
Merge: 742b5b32d cbce8dedd
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 12:00:41 2019 +1000
Merge branch 'maint-0.3.4' into maint-0.3.5
changes/bug29017 | 4 ++++
changes/bug29665 | 7 +++++++
doc/tor.1.txt | 2 +-
src/core/or/circuituse.c | 4 ++++
src/feature/relay/router.c | 11 +++++------
5 files changed, 21 insertions(+), 7 deletions(-)
diff --cc src/core/or/circuituse.c
index e306307c4,000000000..02bfa15fb
mode 100644,000000..100644
--- a/src/core/or/circuituse.c
+++ b/src/core/or/circuituse.c
@@@ -1,3132 -1,0 +1,3136 @@@
+/* 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 circuituse.c
+ * \brief Launch the right sort of circuits and attach the right streams to
+ * them.
+ *
+ * As distinct from circuitlist.c, which manages lookups to find circuits, and
+ * circuitbuild.c, which handles the logistics of circuit construction, this
+ * module keeps track of which streams can be attached to which circuits (in
+ * circuit_get_best()), and attaches streams to circuits (with
+ * circuit_try_attaching_streams(), connection_ap_handshake_attach_circuit(),
+ * and connection_ap_handshake_attach_chosen_circuit() ).
+ *
+ * This module also makes sure that we are building circuits for all of the
+ * predicted ports, using circuit_remove_handled_ports(),
+ * circuit_stream_is_being_handled(), and circuit_build_needed_cirs(). It
+ * handles launching circuits for specific targets using
+ * circuit_launch_by_extend_info().
+ *
+ * This is also where we handle expiring circuits that have been around for
+ * too long without actually completing, along with the circuit_build_timeout
+ * logic in circuitstats.c.
+ **/
+
+#include "core/or/or.h"
+#include "app/config/config.h"
+#include "core/mainloop/connection.h"
+#include "core/or/channel.h"
+#include "core/or/circuitbuild.h"
+#include "core/or/circuitlist.h"
+#include "core/or/circuitstats.h"
+#include "core/or/circuituse.h"
+#include "core/or/connection_edge.h"
+#include "core/or/policies.h"
+#include "feature/client/addressmap.h"
+#include "feature/client/bridges.h"
+#include "feature/client/circpathbias.h"
+#include "feature/client/entrynodes.h"
+#include "feature/control/control.h"
+#include "feature/dircommon/directory.h"
+#include "feature/hs/hs_circuit.h"
+#include "feature/hs/hs_client.h"
+#include "feature/hs/hs_common.h"
+#include "feature/hs/hs_ident.h"
+#include "feature/hs/hs_stats.h"
+#include "feature/nodelist/describe.h"
+#include "feature/nodelist/networkstatus.h"
+#include "feature/nodelist/nodelist.h"
+#include "feature/nodelist/routerlist.h"
+#include "feature/relay/routermode.h"
+#include "feature/relay/selftest.h"
+#include "feature/rend/rendclient.h"
+#include "feature/rend/rendcommon.h"
+#include "feature/rend/rendservice.h"
+#include "feature/stats/predict_ports.h"
+#include "lib/math/fp.h"
+#include "lib/time/tvdiff.h"
+
+#include "core/or/cpath_build_state_st.h"
+#include "feature/dircommon/dir_connection_st.h"
+#include "core/or/entry_connection_st.h"
+#include "core/or/extend_info_st.h"
+#include "core/or/or_circuit_st.h"
+#include "core/or/origin_circuit_st.h"
+#include "core/or/socks_request_st.h"
+
+static void circuit_expire_old_circuits_clientside(void);
+static void circuit_increment_failure_count(void);
+
+/** Check whether the hidden service destination of the stream at
+ * <b>edge_conn</b> is the same as the destination of the circuit at
+ * <b>origin_circ</b>. */
+static int
+circuit_matches_with_rend_stream(const edge_connection_t *edge_conn,
+ const origin_circuit_t *origin_circ)
+{
+ /* Check if this is a v2 rendezvous circ/stream */
+ if ((edge_conn->rend_data && !origin_circ->rend_data) ||
+ (!edge_conn->rend_data && origin_circ->rend_data) ||
+ (edge_conn->rend_data && origin_circ->rend_data &&
+ rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data),
+ rend_data_get_address(origin_circ->rend_data)))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ /* Check if this is a v3 rendezvous circ/stream */
+ if ((edge_conn->hs_ident && !origin_circ->hs_ident) ||
+ (!edge_conn->hs_ident && origin_circ->hs_ident) ||
+ (edge_conn->hs_ident && origin_circ->hs_ident &&
+ !ed25519_pubkey_eq(&edge_conn->hs_ident->identity_pk,
+ &origin_circ->hs_ident->identity_pk))) {
+ /* this circ is not for this conn */
+ return 0;
+ }
+
+ return 1;
+}
+
+/** Return 1 if <b>circ</b> could be returned by circuit_get_best().
+ * Else return 0.
+ */
+static int
+circuit_is_acceptable(const origin_circuit_t *origin_circ,
+ const entry_connection_t *conn,
+ int must_be_open, uint8_t purpose,
+ int need_uptime, int need_internal,
+ time_t now)
+{
+ const circuit_t *circ = TO_CIRCUIT(origin_circ);
+ const node_t *exitnode;
+ cpath_build_state_t *build_state;
+ tor_assert(circ);
+ tor_assert(conn);
+ tor_assert(conn->socks_request);
+
+ if (must_be_open && (circ->state != CIRCUIT_STATE_OPEN || !circ->n_chan))
+ return 0; /* ignore non-open circs */
+ if (circ->marked_for_close)
+ return 0;
+
+ /* if this circ isn't our purpose, skip. */
+ if (purpose == CIRCUIT_PURPOSE_C_REND_JOINED && !must_be_open) {
+ if (circ->purpose != CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
+ circ->purpose != CIRCUIT_PURPOSE_C_REND_READY &&
+ circ->purpose != CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED &&
+ circ->purpose != CIRCUIT_PURPOSE_C_REND_JOINED)
+ return 0;
+ } else if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
+ !must_be_open) {
+ if (circ->purpose != CIRCUIT_PURPOSE_C_INTRODUCING &&
+ circ->purpose != CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)
+ return 0;
+ } else {
+ if (purpose != circ->purpose)
+ return 0;
+ }
+
+ /* If this is a timed-out hidden service circuit, skip it. */
+ if (origin_circ->hs_circ_has_timed_out) {
+ return 0;
+ }
+
+ if (purpose == CIRCUIT_PURPOSE_C_GENERAL ||
+ purpose == CIRCUIT_PURPOSE_C_HSDIR_GET ||
+ purpose == CIRCUIT_PURPOSE_S_HSDIR_POST ||
+ purpose == CIRCUIT_PURPOSE_HS_VANGUARDS ||
+ purpose == CIRCUIT_PURPOSE_C_REND_JOINED) {
+ if (circ->timestamp_dirty &&
+ circ->timestamp_dirty+get_options()->MaxCircuitDirtiness <= now)
+ return 0;
+ }
+
+ if (origin_circ->unusable_for_new_conns)
+ return 0;
+
+ /* decide if this circ is suitable for this conn */
+
+ /* for rend circs, circ->cpath->prev is not the last router in the
+ * circuit, it's the magical extra service hop. so just check the nickname
+ * of the one we meant to finish at.
+ */
+ build_state = origin_circ->build_state;
+ exitnode = build_state_get_exit_node(build_state);
+
+ if (need_uptime && !build_state->need_uptime)
+ return 0;
+ if (need_internal != build_state->is_internal)
+ return 0;
+
+ if (purpose == CIRCUIT_PURPOSE_C_GENERAL ||
+ purpose == CIRCUIT_PURPOSE_S_HSDIR_POST ||
+ purpose == CIRCUIT_PURPOSE_C_HSDIR_GET) {
+ tor_addr_t addr;
+ const int family = tor_addr_parse(&addr, conn->socks_request->address);
+ if (!exitnode && !build_state->onehop_tunnel) {
+ log_debug(LD_CIRC,"Not considering circuit with unknown router.");
+ return 0; /* this circuit is screwed and doesn't know it yet,
+ * or is a rendezvous circuit. */
+ }
+ if (build_state->onehop_tunnel) {
+ if (!conn->want_onehop) {
+ log_debug(LD_CIRC,"Skipping one-hop circuit.");
+ return 0;
+ }
+ tor_assert(conn->chosen_exit_name);
+ if (build_state->chosen_exit) {
+ char digest[DIGEST_LEN];
+ if (hexdigest_to_digest(conn->chosen_exit_name, digest) < 0)
+ return 0; /* broken digest, we don't want it */
+ if (tor_memneq(digest, build_state->chosen_exit->identity_digest,
+ DIGEST_LEN))
+ return 0; /* this is a circuit to somewhere else */
+ if (tor_digest_is_zero(digest)) {
+ /* we don't know the digest; have to compare addr:port */
+ if (family < 0 ||
+ !tor_addr_eq(&build_state->chosen_exit->addr, &addr) ||
+ build_state->chosen_exit->port != conn->socks_request->port)
+ return 0;
+ }
+ }
+ } else {
+ if (conn->want_onehop) {
+ /* don't use three-hop circuits -- that could hurt our anonymity. */
+ return 0;
+ }
+ }
+ if (origin_circ->prepend_policy && family != -1) {
+ int r = compare_tor_addr_to_addr_policy(&addr,
+ conn->socks_request->port,
+ origin_circ->prepend_policy);
+ if (r == ADDR_POLICY_REJECTED)
+ return 0;
+ }
+ if (exitnode && !connection_ap_can_use_exit(conn, exitnode)) {
+ /* can't exit from this router */
+ return 0;
+ }
+ } else { /* not general: this might be a rend circuit */
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
+ if (!circuit_matches_with_rend_stream(edge_conn, origin_circ)) {
+ return 0;
+ }
+ }
+
+ if (!connection_edge_compatible_with_circuit(conn, origin_circ)) {
+ /* conn needs to be isolated from other conns that have already used
+ * origin_circ */
+ return 0;
+ }
+
+ return 1;
+}
+
+/** Return 1 if circuit <b>a</b> is better than circuit <b>b</b> for
+ * <b>conn</b>, and return 0 otherwise. Used by circuit_get_best.
+ */
+static int
+circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob,
+ const entry_connection_t *conn)
+{
+ const circuit_t *a = TO_CIRCUIT(oa);
+ const circuit_t *b = TO_CIRCUIT(ob);
+ const uint8_t purpose = ENTRY_TO_CONN(conn)->purpose;
+ int a_bits, b_bits;
+
+ /* If one of the circuits was allowed to live due to relaxing its timeout,
+ * it is definitely worse (it's probably a much slower path). */
+ if (oa->relaxed_timeout && !ob->relaxed_timeout)
+ return 0; /* ob is better. It's not relaxed. */
+ if (!oa->relaxed_timeout && ob->relaxed_timeout)
+ return 1; /* oa is better. It's not relaxed. */
+
+ switch (purpose) {
+ case CIRCUIT_PURPOSE_S_HSDIR_POST:
+ case CIRCUIT_PURPOSE_C_HSDIR_GET:
+ case CIRCUIT_PURPOSE_C_GENERAL:
+ /* if it's used but less dirty it's best;
+ * else if it's more recently created it's best
+ */
+ if (b->timestamp_dirty) {
+ if (a->timestamp_dirty &&
+ a->timestamp_dirty > b->timestamp_dirty)
+ return 1;
+ } else {
+ if (a->timestamp_dirty ||
+ timercmp(&a->timestamp_began, &b->timestamp_began, OP_GT))
+ return 1;
+ if (ob->build_state->is_internal)
+ /* XXXX++ what the heck is this internal thing doing here. I
+ * think we can get rid of it. circuit_is_acceptable() already
+ * makes sure that is_internal is exactly what we need it to
+ * be. -RD */
+ return 1;
+ }
+ break;
+ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT:
+ /* the closer it is to ack_wait the better it is */
+ if (a->purpose > b->purpose)
+ return 1;
+ break;
+ case CIRCUIT_PURPOSE_C_REND_JOINED:
+ /* the closer it is to rend_joined the better it is */
+ if (a->purpose > b->purpose)
+ return 1;
+ break;
+ }
+
+ /* XXXX Maybe this check should get a higher priority to avoid
+ * using up circuits too rapidly. */
+
+ a_bits = connection_edge_update_circuit_isolation(conn,
+ (origin_circuit_t*)oa, 1);
+ b_bits = connection_edge_update_circuit_isolation(conn,
+ (origin_circuit_t*)ob, 1);
+ /* if x_bits < 0, then we have not used x for anything; better not to dirty
+ * a connection if we can help it. */
+ if (a_bits < 0) {
+ return 0;
+ } else if (b_bits < 0) {
+ return 1;
+ }
+ a_bits &= ~ oa->isolation_flags_mixed;
+ a_bits &= ~ ob->isolation_flags_mixed;
+ if (n_bits_set_u8(a_bits) < n_bits_set_u8(b_bits)) {
+ /* The fewer new restrictions we need to make on a circuit for stream
+ * isolation, the better. */
+ return 1;
+ }
+
+ return 0;
+}
+
+/** Find the best circ that conn can use, preferably one which is
+ * dirty. Circ must not be too old.
+ *
+ * Conn must be defined.
+ *
+ * If must_be_open, ignore circs not in CIRCUIT_STATE_OPEN.
+ *
+ * circ_purpose specifies what sort of circuit we must have.
+ * It can be C_GENERAL, C_INTRODUCE_ACK_WAIT, or C_REND_JOINED.
+ *
+ * If it's REND_JOINED and must_be_open==0, then return the closest
+ * rendezvous-purposed circuit that you can find.
+ *
+ * If it's INTRODUCE_ACK_WAIT and must_be_open==0, then return the
+ * closest introduce-purposed circuit that you can find.
+ */
+static origin_circuit_t *
+circuit_get_best(const entry_connection_t *conn,
+ int must_be_open, uint8_t purpose,
+ int need_uptime, int need_internal)
+{
+ origin_circuit_t *best=NULL;
+ struct timeval now;
+ int intro_going_on_but_too_old = 0;
+
+ tor_assert(conn);
+
+ tor_assert(purpose == CIRCUIT_PURPOSE_C_GENERAL ||
+ purpose == CIRCUIT_PURPOSE_HS_VANGUARDS ||
+ purpose == CIRCUIT_PURPOSE_C_HSDIR_GET ||
+ purpose == CIRCUIT_PURPOSE_S_HSDIR_POST ||
+ purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT ||
+ purpose == CIRCUIT_PURPOSE_C_REND_JOINED);
+
+ tor_gettimeofday(&now);
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ origin_circuit_t *origin_circ;
+ if (!CIRCUIT_IS_ORIGIN(circ))
+ continue;
+ origin_circ = TO_ORIGIN_CIRCUIT(circ);
+
+ /* Log an info message if we're going to launch a new intro circ in
+ * parallel */
+ if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
+ !must_be_open && origin_circ->hs_circ_has_timed_out &&
+ !circ->marked_for_close) {
+ intro_going_on_but_too_old = 1;
+ continue;
+ }
+
+ if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose,
+ need_uptime,need_internal, (time_t)now.tv_sec))
+ continue;
+
+ /* now this is an acceptable circ to hand back. but that doesn't
+ * mean it's the *best* circ to hand back. try to decide.
+ */
+ if (!best || circuit_is_better(origin_circ,best,conn))
+ best = origin_circ;
+ }
+ SMARTLIST_FOREACH_END(circ);
+
+ if (!best && intro_going_on_but_too_old)
+ log_info(LD_REND|LD_CIRC, "There is an intro circuit being created "
+ "right now, but it has already taken quite a while. Starting "
+ "one in parallel.");
+
+ return best;
+}
+
+/** Return the number of not-yet-open general-purpose origin circuits. */
+static int
+count_pending_general_client_circuits(void)
+{
+ int count = 0;
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ if (circ->marked_for_close ||
+ circ->state == CIRCUIT_STATE_OPEN ||
+ !CIRCUIT_PURPOSE_COUNTS_TOWARDS_MAXPENDING(circ->purpose) ||
+ !CIRCUIT_IS_ORIGIN(circ))
+ continue;
+
+ ++count;
+ }
+ SMARTLIST_FOREACH_END(circ);
+
+ return count;
+}
+
+#if 0
+/** Check whether, according to the policies in <b>options</b>, the
+ * circuit <b>circ</b> makes sense. */
+/* XXXX currently only checks Exclude{Exit}Nodes; it should check more.
+ * Also, it doesn't have the right definition of an exit circuit. Also,
+ * it's never called. */
+int
+circuit_conforms_to_options(const origin_circuit_t *circ,
+ const or_options_t *options)
+{
+ const crypt_path_t *cpath, *cpath_next = NULL;
+
+ /* first check if it includes any excluded nodes */
+ for (cpath = circ->cpath; cpath_next != circ->cpath; cpath = cpath_next) {
+ cpath_next = cpath->next;
+ if (routerset_contains_extendinfo(options->ExcludeNodes,
+ cpath->extend_info))
+ return 0;
+ }
+
+ /* then consider the final hop */
+ if (routerset_contains_extendinfo(options->ExcludeExitNodes,
+ circ->cpath->prev->extend_info))
+ return 0;
+
+ return 1;
+}
+#endif /* 0 */
+
+/**
+ * Close all circuits that start at us, aren't open, and were born
+ * at least CircuitBuildTimeout seconds ago.
+ *
+ * TODO: This function is now partially redundant to
+ * circuit_build_times_handle_completed_hop(), but that function only
+ * covers circuits up to and including 3 hops that are still actually
+ * completing hops. However, circuit_expire_building() also handles longer
+ * circuits, as well as circuits that are completely stalled.
+ * In the future (after prop247/other path selection revamping), we probably
+ * want to eliminate this rats nest in favor of a simpler approach.
+ */
+void
+circuit_expire_building(void)
+{
+ /* circ_times.timeout_ms and circ_times.close_ms are from
+ * circuit_build_times_get_initial_timeout() if we haven't computed
+ * custom timeouts yet */
+ struct timeval general_cutoff, begindir_cutoff, fourhop_cutoff,
+ close_cutoff, extremely_old_cutoff, hs_extremely_old_cutoff,
+ cannibalized_cutoff, c_intro_cutoff, s_intro_cutoff, stream_cutoff;
+ const or_options_t *options = get_options();
+ struct timeval now;
+ cpath_build_state_t *build_state;
+ int any_opened_circs = 0;
+
+ tor_gettimeofday(&now);
+
+ /* Check to see if we have any opened circuits. If we don't,
+ * we want to be more lenient with timeouts, in case the
+ * user has relocated and/or changed network connections.
+ * See bug #3443. */
+ any_opened_circs = circuit_any_opened_circuits();
+
+#define SET_CUTOFF(target, msec) do { \
+ long ms = tor_lround(msec); \
+ struct timeval diff; \
+ diff.tv_sec = ms / 1000; \
+ diff.tv_usec = (int)((ms % 1000) * 1000); \
+ timersub(&now, &diff, &target); \
+ } while (0)
+
+ /**
+ * Because circuit build timeout is calculated only based on 3 hop
+ * general purpose circuit construction, we need to scale the timeout
+ * to make it properly apply to longer circuits, and circuits of
+ * certain usage types. The following diagram illustrates how we
+ * derive the scaling below. In short, we calculate the number
+ * of times our telescoping-based circuit construction causes cells
+ * to traverse each link for the circuit purpose types in question,
+ * and then assume each link is equivalent.
+ *
+ * OP --a--> A --b--> B --c--> C
+ * OP --a--> A --b--> B --c--> C --d--> D
+ *
+ * Let h = a = b = c = d
+ *
+ * Three hops (general_cutoff)
+ * RTTs = 3a + 2b + c
+ * RTTs = 6h
+ * Cannibalized:
+ * RTTs = a+b+c+d
+ * RTTs = 4h
+ * Four hops:
+ * RTTs = 4a + 3b + 2c + d
+ * RTTs = 10h
+ * Client INTRODUCE1+ACK: // XXX: correct?
+ * RTTs = 5a + 4b + 3c + 2d
+ * RTTs = 14h
+ * Server intro:
+ * RTTs = 4a + 3b + 2c
+ * RTTs = 9h
+ */
+ SET_CUTOFF(general_cutoff, get_circuit_build_timeout_ms());
+ SET_CUTOFF(begindir_cutoff, get_circuit_build_timeout_ms());
+
+ // TODO: We should probably use route_len_for_purpose() here instead,
+ // except that does not count the extra round trip for things like server
+ // intros and rends.
+
+ /* > 3hop circs seem to have a 1.0 second delay on their cannibalized
+ * 4th hop. */
+ SET_CUTOFF(fourhop_cutoff, get_circuit_build_timeout_ms() * (10/6.0) + 1000);
+
+ /* CIRCUIT_PURPOSE_C_ESTABLISH_REND behaves more like a RELAY cell.
+ * Use the stream cutoff (more or less). */
+ SET_CUTOFF(stream_cutoff, MAX(options->CircuitStreamTimeout,15)*1000 + 1000);
+
+ /* Be lenient with cannibalized circs. They already survived the official
+ * CBT, and they're usually not performance-critical. */
+ SET_CUTOFF(cannibalized_cutoff,
+ MAX(get_circuit_build_close_time_ms()*(4/6.0),
+ options->CircuitStreamTimeout * 1000) + 1000);
+
+ /* Intro circs have an extra round trip (and are also 4 hops long) */
+ SET_CUTOFF(c_intro_cutoff, get_circuit_build_timeout_ms() * (14/6.0) + 1000);
+
+ /* Server intro circs have an extra round trip */
+ SET_CUTOFF(s_intro_cutoff, get_circuit_build_timeout_ms() * (9/6.0) + 1000);
+
+ SET_CUTOFF(close_cutoff, get_circuit_build_close_time_ms());
+ SET_CUTOFF(extremely_old_cutoff, get_circuit_build_close_time_ms()*2 + 1000);
+
+ SET_CUTOFF(hs_extremely_old_cutoff,
+ MAX(get_circuit_build_close_time_ms()*2 + 1000,
+ options->SocksTimeout * 1000));
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *,victim) {
+ struct timeval cutoff;
+ bool fixed_time = circuit_build_times_disabled(get_options());
+
+ if (!CIRCUIT_IS_ORIGIN(victim) || /* didn't originate here */
+ victim->marked_for_close) /* don't mess with marked circs */
+ continue;
+
+ /* If we haven't yet started the first hop, it means we don't have
+ * any orconns available, and thus have not started counting time yet
+ * for this circuit. See circuit_deliver_create_cell() and uses of
+ * timestamp_began.
+ *
+ * Continue to wait in this case. The ORConn should timeout
+ * independently and kill us then.
+ */
+ if (TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_CLOSED) {
+ continue;
+ }
+
+ build_state = TO_ORIGIN_CIRCUIT(victim)->build_state;
+ if (build_state && build_state->onehop_tunnel)
+ cutoff = begindir_cutoff;
+ else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
+ cutoff = close_cutoff;
+ else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)
+ cutoff = c_intro_cutoff;
+ else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)
+ cutoff = s_intro_cutoff;
+ else if (victim->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND)
+ cutoff = stream_cutoff;
+ else if (victim->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING)
+ cutoff = close_cutoff;
+ else if (TO_ORIGIN_CIRCUIT(victim)->has_opened &&
+ victim->state != CIRCUIT_STATE_OPEN)
+ cutoff = cannibalized_cutoff;
+ else if (build_state && build_state->desired_path_len >= 4)
+ cutoff = fourhop_cutoff;
+ else
+ cutoff = general_cutoff;
+
+ if (TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out)
+ cutoff = hs_extremely_old_cutoff;
+
+ if (timercmp(&victim->timestamp_began, &cutoff, OP_GT))
+ continue; /* it's still young, leave it alone */
+
+ /* We need to double-check the opened state here because
+ * we don't want to consider opened 1-hop dircon circuits for
+ * deciding when to relax the timeout, but we *do* want to relax
+ * those circuits too if nothing else is opened *and* they still
+ * aren't either. */
+ if (!any_opened_circs && victim->state != CIRCUIT_STATE_OPEN) {
+ /* It's still young enough that we wouldn't close it, right? */
+ if (timercmp(&victim->timestamp_began, &close_cutoff, OP_GT)) {
+ if (!TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout) {
+ int first_hop_succeeded = TO_ORIGIN_CIRCUIT(victim)->cpath->state
+ == CPATH_STATE_OPEN;
+ if (!fixed_time) {
+ log_info(LD_CIRC,
+ "No circuits are opened. Relaxing timeout for circuit %d "
+ "(a %s %d-hop circuit in state %s with channel state %s).",
+ TO_ORIGIN_CIRCUIT(victim)->global_identifier,
+ circuit_purpose_to_string(victim->purpose),
+ TO_ORIGIN_CIRCUIT(victim)->build_state ?
+ TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len :
+ -1,
+ circuit_state_to_string(victim->state),
+ victim->n_chan ?
+ channel_state_to_string(victim->n_chan->state) : "none");
+ }
+
+ /* We count the timeout here for CBT, because technically this
+ * was a timeout, and the timeout value needs to reset if we
+ * see enough of them. Note this means we also need to avoid
+ * double-counting below, too. */
+ circuit_build_times_count_timeout(get_circuit_build_times_mutable(),
+ first_hop_succeeded);
+ TO_ORIGIN_CIRCUIT(victim)->relaxed_timeout = 1;
+ }
+ continue;
+ } else {
+ static ratelim_t relax_timeout_limit = RATELIM_INIT(3600);
+ const double build_close_ms = get_circuit_build_close_time_ms();
+ if (!fixed_time) {
+ log_fn_ratelim(&relax_timeout_limit, LOG_NOTICE, LD_CIRC,
+ "No circuits are opened. Relaxed timeout for circuit %d "
+ "(a %s %d-hop circuit in state %s with channel state %s) to "
+ "%ldms. However, it appears the circuit has timed out "
+ "anyway.",
+ TO_ORIGIN_CIRCUIT(victim)->global_identifier,
+ circuit_purpose_to_string(victim->purpose),
+ TO_ORIGIN_CIRCUIT(victim)->build_state ?
+ TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len :
+ -1,
+ circuit_state_to_string(victim->state),
+ victim->n_chan ?
+ channel_state_to_string(victim->n_chan->state) : "none",
+ (long)build_close_ms);
+ }
+ }
+ }
+
+#if 0
+ /* some debug logs, to help track bugs */
+ if (victim->purpose >= CIRCUIT_PURPOSE_C_INTRODUCING &&
+ victim->purpose <= CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
+ if (!victim->timestamp_dirty)
+ log_fn(LOG_DEBUG,"Considering %sopen purpose %d to %s (circid %d)."
+ "(clean).",
+ victim->state == CIRCUIT_STATE_OPEN ? "" : "non",
+ victim->purpose, victim->build_state->chosen_exit_name,
+ victim->n_circ_id);
+ else
+ log_fn(LOG_DEBUG,"Considering %sopen purpose %d to %s (circid %d). "
+ "%d secs since dirty.",
+ victim->state == CIRCUIT_STATE_OPEN ? "" : "non",
+ victim->purpose, victim->build_state->chosen_exit_name,
+ victim->n_circ_id,
+ (int)(now - victim->timestamp_dirty));
+ }
+#endif /* 0 */
+
+ /* if circ is !open, or if it's open but purpose is a non-finished
+ * intro or rend, then mark it for close */
+ if (victim->state == CIRCUIT_STATE_OPEN) {
+ switch (victim->purpose) {
+ default: /* most open circuits can be left alone. */
+ continue; /* yes, continue inside a switch refers to the nearest
+ * enclosing loop. C is smart. */
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ break; /* too old, need to die */
+ case CIRCUIT_PURPOSE_C_REND_READY:
+ /* it's a rend_ready circ -- has it already picked a query? */
+ /* c_rend_ready circs measure age since timestamp_dirty,
+ * because that's set when they switch purposes
+ */
+ if (TO_ORIGIN_CIRCUIT(victim)->rend_data ||
+ TO_ORIGIN_CIRCUIT(victim)->hs_ident ||
+ victim->timestamp_dirty > cutoff.tv_sec)
+ continue;
+ break;
+ case CIRCUIT_PURPOSE_PATH_BIAS_TESTING:
+ /* Open path bias testing circuits are given a long
+ * time to complete the test, but not forever */
+ TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED;
+ break;
+ case CIRCUIT_PURPOSE_C_INTRODUCING:
+ /* That purpose means that the intro point circuit has been opened
+ * successfully but the INTRODUCE1 cell hasn't been sent yet because
+ * the client is waiting for the rendezvous point circuit to open.
+ * Keep this circuit open while waiting for the rendezvous circuit.
+ * We let the circuit idle timeout take care of cleaning this
+ * circuit if it never used. */
+ continue;
+ case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+ case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED:
+ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT:
+ /* rend and intro circs become dirty each time they
+ * make an introduction attempt. so timestamp_dirty
+ * will reflect the time since the last attempt.
+ */
+ if (victim->timestamp_dirty > cutoff.tv_sec)
+ continue;
+ break;
+ }
+ } else { /* circuit not open, consider recording failure as timeout */
+ int first_hop_succeeded = TO_ORIGIN_CIRCUIT(victim)->cpath &&
+ TO_ORIGIN_CIRCUIT(victim)->cpath->state == CPATH_STATE_OPEN;
+
+ if (TO_ORIGIN_CIRCUIT(victim)->p_streams != NULL) {
+ log_warn(LD_BUG, "Circuit %d (purpose %d, %s) has timed out, "
+ "yet has attached streams!",
+ TO_ORIGIN_CIRCUIT(victim)->global_identifier,
+ victim->purpose,
+ circuit_purpose_to_string(victim->purpose));
+ tor_fragile_assert();
+ continue;
+ }
+
+ if (circuit_timeout_want_to_count_circ(TO_ORIGIN_CIRCUIT(victim)) &&
+ circuit_build_times_enough_to_compute(get_circuit_build_times())) {
+
+ log_info(LD_CIRC,
+ "Deciding to count the timeout for circuit %"PRIu32"\n",
+ TO_ORIGIN_CIRCUIT(victim)->global_identifier);
+
+ /* Circuits are allowed to last longer for measurement.
+ * Switch their purpose and wait. */
+ if (victim->purpose != CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT) {
+ circuit_build_times_mark_circ_as_measurement_only(TO_ORIGIN_CIRCUIT(
+ victim));
+ continue;
+ }
+
+ /*
+ * If the circuit build time is much greater than we would have cut
+ * it off at, we probably had a suspend event along this codepath,
+ * and we should discard the value.
+ */
+ if (timercmp(&victim->timestamp_began, &extremely_old_cutoff, OP_LT)) {
+ log_notice(LD_CIRC,
+ "Extremely large value for circuit build timeout: %lds. "
+ "Assuming clock jump. Purpose %d (%s)",
+ (long)(now.tv_sec - victim->timestamp_began.tv_sec),
+ victim->purpose,
+ circuit_purpose_to_string(victim->purpose));
+ } else if (circuit_build_times_count_close(
+ get_circuit_build_times_mutable(),
+ first_hop_succeeded,
+ (time_t)victim->timestamp_created.tv_sec)) {
+ circuit_build_times_set_timeout(get_circuit_build_times_mutable());
+ }
+ }
+ }
+
+ /* If this is a hidden service client circuit which is far enough along in
+ * connecting to its destination, and we haven't already flagged it as
+ * 'timed out', flag it so we'll launch another intro or rend circ, but
+ * don't mark it for close yet.
+ *
+ * (Circs flagged as 'timed out' are given a much longer timeout
+ * period above, so we won't close them in the next call to
+ * circuit_expire_building.) */
+ if (!(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out)) {
+ switch (victim->purpose) {
+ case CIRCUIT_PURPOSE_C_REND_READY:
+ /* We only want to spare a rend circ if it has been specified in
+ * an INTRODUCE1 cell sent to a hidden service. A circ's
+ * pending_final_cpath field is non-NULL iff it is a rend circ
+ * and we have tried to send an INTRODUCE1 cell specifying it.
+ * Thus, if the pending_final_cpath field *is* NULL, then we
+ * want to not spare it. */
+ if (TO_ORIGIN_CIRCUIT(victim)->build_state &&
+ TO_ORIGIN_CIRCUIT(victim)->build_state->pending_final_cpath ==
+ NULL)
+ break;
+ /* fallthrough! */
+ case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT:
+ case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED:
+ /* If we have reached this line, we want to spare the circ for now. */
+ log_info(LD_CIRC,"Marking circ %u (state %d:%s, purpose %d) "
+ "as timed-out HS circ",
+ (unsigned)victim->n_circ_id,
+ victim->state, circuit_state_to_string(victim->state),
+ victim->purpose);
+ TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1;
+ continue;
+ default:
+ break;
+ }
+ }
+
+ /* If this is a service-side rendezvous circuit which is far
+ * enough along in connecting to its destination, consider sparing
+ * it. */
+ if (!(TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out) &&
+ victim->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) {
+ log_info(LD_CIRC,"Marking circ %u (state %d:%s, purpose %d) "
+ "as timed-out HS circ; relaunching rendezvous attempt.",
+ (unsigned)victim->n_circ_id,
+ victim->state, circuit_state_to_string(victim->state),
+ victim->purpose);
+ TO_ORIGIN_CIRCUIT(victim)->hs_circ_has_timed_out = 1;
+ hs_circ_retry_service_rendezvous_point(TO_ORIGIN_CIRCUIT(victim));
+ continue;
+ }
+
+ if (victim->n_chan)
+ log_info(LD_CIRC,
+ "Abandoning circ %u %s:%u (state %d,%d:%s, purpose %d, "
+ "len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier,
+ channel_get_canonical_remote_descr(victim->n_chan),
+ (unsigned)victim->n_circ_id,
+ TO_ORIGIN_CIRCUIT(victim)->has_opened,
+ victim->state, circuit_state_to_string(victim->state),
+ victim->purpose,
+ TO_ORIGIN_CIRCUIT(victim)->build_state ?
+ TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len :
+ -1);
+ else
+ log_info(LD_CIRC,
+ "Abandoning circ %u %u (state %d,%d:%s, purpose %d, len %d)",
+ TO_ORIGIN_CIRCUIT(victim)->global_identifier,
+ (unsigned)victim->n_circ_id,
+ TO_ORIGIN_CIRCUIT(victim)->has_opened,
+ victim->state,
+ circuit_state_to_string(victim->state), victim->purpose,
+ TO_ORIGIN_CIRCUIT(victim)->build_state ?
+ TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len :
+ -1);
+
+ circuit_log_path(LOG_INFO,LD_CIRC,TO_ORIGIN_CIRCUIT(victim));
+ if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
+ circuit_mark_for_close(victim, END_CIRC_REASON_MEASUREMENT_EXPIRED);
+ else
+ circuit_mark_for_close(victim, END_CIRC_REASON_TIMEOUT);
+
+ pathbias_count_timeout(TO_ORIGIN_CIRCUIT(victim));
+ } SMARTLIST_FOREACH_END(victim);
+}
+
+/**
+ * Mark for close all circuits that start here, that were built through a
+ * guard we weren't sure if we wanted to use, and that have been waiting
+ * around for way too long.
+ */
+void
+circuit_expire_waiting_for_better_guard(void)
+{
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_origin_circuit_list(),
+ origin_circuit_t *, circ) {
+ if (TO_CIRCUIT(circ)->marked_for_close)
+ continue;
+ if (circ->guard_state == NULL)
+ continue;
+ if (entry_guard_state_should_expire(circ->guard_state))
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NONE);
+ } SMARTLIST_FOREACH_END(circ);
+}
+
+/** For debugging #8387: track when we last called
+ * circuit_expire_old_circuits_clientside. */
+static time_t last_expired_clientside_circuits = 0;
+
+/**
+ * As a diagnostic for bug 8387, log information about how many one-hop
+ * circuits we have around that have been there for at least <b>age</b>
+ * seconds. Log a few of them. Ignores Single Onion Service intro, it is
+ * expected to be long-term one-hop circuits.
+ */
+void
+circuit_log_ancient_one_hop_circuits(int age)
+{
+#define MAX_ANCIENT_ONEHOP_CIRCUITS_TO_LOG 10
+ time_t now = time(NULL);
+ time_t cutoff = now - age;
+ int n_found = 0;
+ smartlist_t *log_these = smartlist_new();
+ const or_options_t *options = get_options();
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ const origin_circuit_t *ocirc;
+ if (! CIRCUIT_IS_ORIGIN(circ))
+ continue;
+ if (circ->timestamp_created.tv_sec >= cutoff)
+ continue;
+ /* Single Onion Services deliberately make long term one-hop intro
+ * and rendezvous connections. Don't log the established ones. */
+ if (rend_service_allow_non_anonymous_connection(options) &&
+ (circ->purpose == CIRCUIT_PURPOSE_S_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED))
+ continue;
+ ocirc = CONST_TO_ORIGIN_CIRCUIT(circ);
+
+ if (ocirc->build_state && ocirc->build_state->onehop_tunnel) {
+ ++n_found;
+
+ if (smartlist_len(log_these) < MAX_ANCIENT_ONEHOP_CIRCUITS_TO_LOG)
+ smartlist_add(log_these, (origin_circuit_t*) ocirc);
+ }
+ }
+ SMARTLIST_FOREACH_END(circ);
+
+ if (n_found == 0)
+ goto done;
+
+ log_notice(LD_HEARTBEAT,
+ "Diagnostic for issue 8387: Found %d one-hop circuits more "
+ "than %d seconds old! Logging %d...",
+ n_found, age, smartlist_len(log_these));
+
+ SMARTLIST_FOREACH_BEGIN(log_these, const origin_circuit_t *, ocirc) {
+ char created[ISO_TIME_LEN+1];
+ int stream_num;
+ const edge_connection_t *conn;
+ char *dirty = NULL;
+ const circuit_t *circ = TO_CIRCUIT(ocirc);
+
+ format_local_iso_time(created,
+ (time_t)circ->timestamp_created.tv_sec);
+
+ if (circ->timestamp_dirty) {
+ char dirty_since[ISO_TIME_LEN+1];
+ format_local_iso_time(dirty_since, circ->timestamp_dirty);
+
+ tor_asprintf(&dirty, "Dirty since %s (%ld seconds vs %ld-second cutoff)",
+ dirty_since, (long)(now - circ->timestamp_dirty),
+ (long) options->MaxCircuitDirtiness);
+ } else {
+ dirty = tor_strdup("Not marked dirty");
+ }
+
+ log_notice(LD_HEARTBEAT, " #%d created at %s. %s, %s. %s for close. "
+ "Package window: %d. "
+ "%s for new conns. %s.",
+ ocirc_sl_idx,
+ created,
+ circuit_state_to_string(circ->state),
+ circuit_purpose_to_string(circ->purpose),
+ circ->marked_for_close ? "Marked" : "Not marked",
+ circ->package_window,
+ ocirc->unusable_for_new_conns ? "Not usable" : "usable",
+ dirty);
+ tor_free(dirty);
+
+ stream_num = 0;
+ for (conn = ocirc->p_streams; conn; conn = conn->next_stream) {
+ const connection_t *c = TO_CONN(conn);
+ char stream_created[ISO_TIME_LEN+1];
+ if (++stream_num >= 5)
+ break;
+
+ format_local_iso_time(stream_created, c->timestamp_created);
+
+ log_notice(LD_HEARTBEAT, " Stream#%d created at %s. "
+ "%s conn in state %s. "
+ "It is %slinked and %sreading from a linked connection %p. "
+ "Package window %d. "
+ "%s for close (%s:%d). Hold-open is %sset. "
+ "Has %ssent RELAY_END. %s on circuit.",
+ stream_num,
+ stream_created,
+ conn_type_to_string(c->type),
+ conn_state_to_string(c->type, c->state),
+ c->linked ? "" : "not ",
+ c->reading_from_linked_conn ? "": "not",
+ c->linked_conn,
+ conn->package_window,
+ c->marked_for_close ? "Marked" : "Not marked",
+ c->marked_for_close_file ? c->marked_for_close_file : "--",
+ c->marked_for_close,
+ c->hold_open_until_flushed ? "" : "not ",
+ conn->edge_has_sent_end ? "" : "not ",
+ conn->edge_blocked_on_circ ? "Blocked" : "Not blocked");
+ if (! c->linked_conn)
+ continue;
+
+ c = c->linked_conn;
+
+ log_notice(LD_HEARTBEAT, " Linked to %s connection in state %s "
+ "(Purpose %d). %s for close (%s:%d). Hold-open is %sset. ",
+ conn_type_to_string(c->type),
+ conn_state_to_string(c->type, c->state),
+ c->purpose,
+ c->marked_for_close ? "Marked" : "Not marked",
+ c->marked_for_close_file ? c->marked_for_close_file : "--",
+ c->marked_for_close,
+ c->hold_open_until_flushed ? "" : "not ");
+ }
+ } SMARTLIST_FOREACH_END(ocirc);
+
+ log_notice(LD_HEARTBEAT, "It has been %ld seconds since I last called "
+ "circuit_expire_old_circuits_clientside().",
+ (long)(now - last_expired_clientside_circuits));
+
+ done:
+ smartlist_free(log_these);
+}
+
+/** Remove any elements in <b>needed_ports</b> that are handled by an
+ * open or in-progress circuit.
+ */
+void
+circuit_remove_handled_ports(smartlist_t *needed_ports)
+{
+ int i;
+ uint16_t *port;
+
+ for (i = 0; i < smartlist_len(needed_ports); ++i) {
+ port = smartlist_get(needed_ports, i);
+ tor_assert(*port);
+ if (circuit_stream_is_being_handled(NULL, *port,
+ MIN_CIRCUITS_HANDLING_STREAM)) {
+ log_debug(LD_CIRC,"Port %d is already being handled; removing.", *port);
+ smartlist_del(needed_ports, i--);
+ tor_free(port);
+ } else {
+ log_debug(LD_CIRC,"Port %d is not handled.", *port);
+ }
+ }
+}
+
+/** Return 1 if at least <b>min</b> general-purpose non-internal circuits
+ * will have an acceptable exit node for exit stream <b>conn</b> if it
+ * is defined, else for "*:port".
+ * Else return 0.
+ */
+int
+circuit_stream_is_being_handled(entry_connection_t *conn,
+ uint16_t port, int min)
+{
+ const node_t *exitnode;
+ int num=0;
+ time_t now = time(NULL);
+ int need_uptime = smartlist_contains_int_as_string(
+ get_options()->LongLivedPorts,
+ conn ? conn->socks_request->port : port);
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ if (CIRCUIT_IS_ORIGIN(circ) &&
+ !circ->marked_for_close &&
+ circ->purpose == CIRCUIT_PURPOSE_C_GENERAL &&
+ (!circ->timestamp_dirty ||
+ circ->timestamp_dirty + get_options()->MaxCircuitDirtiness > now)) {
+ origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ cpath_build_state_t *build_state = origin_circ->build_state;
+ if (build_state->is_internal || build_state->onehop_tunnel)
+ continue;
+ if (origin_circ->unusable_for_new_conns)
+ continue;
+ if (origin_circ->isolation_values_set &&
+ (conn == NULL ||
+ !connection_edge_compatible_with_circuit(conn, origin_circ)))
+ continue;
+
+ exitnode = build_state_get_exit_node(build_state);
+ if (exitnode && (!need_uptime || build_state->need_uptime)) {
+ int ok;
+ if (conn) {
+ ok = connection_ap_can_use_exit(conn, exitnode);
+ } else {
+ addr_policy_result_t r;
+ r = compare_tor_addr_to_node_policy(NULL, port, exitnode);
+ ok = r != ADDR_POLICY_REJECTED && r != ADDR_POLICY_PROBABLY_REJECTED;
+ }
+ if (ok) {
+ if (++num >= min)
+ return 1;
+ }
+ }
+ }
+ }
+ SMARTLIST_FOREACH_END(circ);
+ return 0;
+}
+
+/** Don't keep more than this many unused open circuits around. */
+#define MAX_UNUSED_OPEN_CIRCUITS 14
+
+/* Return true if a circuit is available for use, meaning that it is open,
+ * clean, usable for new multi-hop connections, and a general purpose origin
+ * circuit.
+ * Accept any kind of circuit, return false if the above conditions are not
+ * met. */
+STATIC int
+circuit_is_available_for_use(const circuit_t *circ)
+{
+ const origin_circuit_t *origin_circ;
+ cpath_build_state_t *build_state;
+
+ if (!CIRCUIT_IS_ORIGIN(circ))
+ return 0; /* We first filter out only origin circuits before doing the
+ following checks. */
+ if (circ->marked_for_close)
+ return 0; /* Don't mess with marked circs */
+ if (circ->timestamp_dirty)
+ return 0; /* Only count clean circs */
+ if (circ->purpose != CIRCUIT_PURPOSE_C_GENERAL &&
+ circ->purpose != CIRCUIT_PURPOSE_HS_VANGUARDS)
+ return 0; /* We only pay attention to general purpose circuits.
+ General purpose circuits are always origin circuits. */
+
+ origin_circ = CONST_TO_ORIGIN_CIRCUIT(circ);
+ if (origin_circ->unusable_for_new_conns)
+ return 0;
+
+ build_state = origin_circ->build_state;
+ if (build_state->onehop_tunnel)
+ return 0;
+
+ return 1;
+}
+
+/* Return true if we need any more exit circuits.
+ * needs_uptime and needs_capacity are set only if we need more exit circuits.
+ * Check if we know of a port that's been requested recently and no circuit
+ * is currently available that can handle it. */
+STATIC int
+needs_exit_circuits(time_t now, int *needs_uptime, int *needs_capacity)
+{
+ return (!circuit_all_predicted_ports_handled(now, needs_uptime,
+ needs_capacity) &&
+ router_have_consensus_path() == CONSENSUS_PATH_EXIT);
+}
+
+/* Hidden services need at least this many internal circuits */
+#define SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS 3
+
+/* Return true if we need any more hidden service server circuits.
+ * HS servers only need an internal circuit. */
+STATIC int
+needs_hs_server_circuits(time_t now, int num_uptime_internal)
+{
+ if (!rend_num_services() && !hs_service_get_num_services()) {
+ /* No services, we don't need anything. */
+ goto no_need;
+ }
+
+ if (num_uptime_internal >= SUFFICIENT_UPTIME_INTERNAL_HS_SERVERS) {
+ /* We have sufficient amount of internal circuit. */
+ goto no_need;
+ }
+
+ if (router_have_consensus_path() == CONSENSUS_PATH_UNKNOWN) {
+ /* Consensus hasn't been checked or might be invalid so requesting
+ * internal circuits is not wise. */
+ goto no_need;
+ }
+
+ /* At this point, we need a certain amount of circuits and we will most
+ * likely use them for rendezvous so we note down the use of internal
+ * circuit for our prediction for circuit needing uptime and capacity. */
+ rep_hist_note_used_internal(now, 1, 1);
+
+ return 1;
+ no_need:
+ return 0;
+}
+
+/* We need at least this many internal circuits for hidden service clients */
+#define SUFFICIENT_INTERNAL_HS_CLIENTS 3
+
+/* We need at least this much uptime for internal circuits for hidden service
+ * clients */
+#define SUFFICIENT_UPTIME_INTERNAL_HS_CLIENTS 2
+
+/* Return true if we need any more hidden service client circuits.
+ * HS clients only need an internal circuit. */
+STATIC int
+needs_hs_client_circuits(time_t now, int *needs_uptime, int *needs_capacity,
+ int num_internal, int num_uptime_internal)
+{
+ int used_internal_recently = rep_hist_get_predicted_internal(now,
+ needs_uptime,
+ needs_capacity);
+ int requires_uptime = num_uptime_internal <
+ SUFFICIENT_UPTIME_INTERNAL_HS_CLIENTS &&
+ needs_uptime;
+
+ return (used_internal_recently &&
+ (requires_uptime || num_internal < SUFFICIENT_INTERNAL_HS_CLIENTS) &&
+ router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN);
+}
+
+/* This is how many circuits can be opened concurrently during the cbt learning
+ * phase. This number cannot exceed the tor-wide MAX_UNUSED_OPEN_CIRCUITS. */
+#define DFLT_CBT_UNUSED_OPEN_CIRCS (10)
+#define MIN_CBT_UNUSED_OPEN_CIRCS 0
+#define MAX_CBT_UNUSED_OPEN_CIRCS MAX_UNUSED_OPEN_CIRCUITS
+
+/* Return true if we need more circuits for a good build timeout.
+ * XXXX make the assumption that build timeout streams should be
+ * created whenever we can build internal circuits. */
+STATIC int
+needs_circuits_for_build(int num)
+{
+ if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) {
+ if (num < networkstatus_get_param(NULL, "cbtmaxopencircs",
+ DFLT_CBT_UNUSED_OPEN_CIRCS,
+ MIN_CBT_UNUSED_OPEN_CIRCS,
+ MAX_CBT_UNUSED_OPEN_CIRCS) &&
+ !circuit_build_times_disabled(get_options()) &&
+ circuit_build_times_needs_circuits_now(get_circuit_build_times())) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Launch the appropriate type of predicted circuit for hidden
+ * services, depending on our options.
+ */
+static void
+circuit_launch_predicted_hs_circ(int flags)
+{
+ /* K.I.S.S. implementation of bug #23101: If we are using
+ * vanguards or pinned middles, pre-build a specific purpose
+ * for HS circs. */
+ if (circuit_should_use_vanguards(CIRCUIT_PURPOSE_HS_VANGUARDS)) {
+ circuit_launch(CIRCUIT_PURPOSE_HS_VANGUARDS, flags);
+ } else {
+ /* If no vanguards, then no HS-specific prebuilt circuits are needed.
+ * Normal GENERAL circs are fine */
+ circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags);
+ }
+}
+
+/** Determine how many circuits we have open that are clean,
+ * Make sure it's enough for all the upcoming behaviors we predict we'll have.
+ * But put an upper bound on the total number of circuits.
+ */
+static void
+circuit_predict_and_launch_new(void)
+{
+ int num=0, num_internal=0, num_uptime_internal=0;
+ int hidserv_needs_uptime=0, hidserv_needs_capacity=1;
+ int port_needs_uptime=0, port_needs_capacity=1;
+ time_t now = time(NULL);
+ int flags = 0;
+
+ /* Count how many of each type of circuit we currently have. */
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ if (!circuit_is_available_for_use(circ))
+ continue;
+
+ num++;
+
+ cpath_build_state_t *build_state = TO_ORIGIN_CIRCUIT(circ)->build_state;
+ if (build_state->is_internal)
+ num_internal++;
+ if (build_state->need_uptime && build_state->is_internal)
+ num_uptime_internal++;
+ }
+ SMARTLIST_FOREACH_END(circ);
+
+ /* If that's enough, then stop now. */
+ if (num >= MAX_UNUSED_OPEN_CIRCUITS)
+ return;
+
+ if (needs_exit_circuits(now, &port_needs_uptime, &port_needs_capacity)) {
+ if (port_needs_uptime)
+ flags |= CIRCLAUNCH_NEED_UPTIME;
+ if (port_needs_capacity)
+ flags |= CIRCLAUNCH_NEED_CAPACITY;
+
+ log_info(LD_CIRC,
+ "Have %d clean circs (%d internal), need another exit circ.",
+ num, num_internal);
+ circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags);
+ return;
+ }
+
+ if (needs_hs_server_circuits(now, num_uptime_internal)) {
+ flags = (CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_NEED_UPTIME |
+ CIRCLAUNCH_IS_INTERNAL);
+
+ log_info(LD_CIRC,
+ "Have %d clean circs (%d internal), need another internal "
+ "circ for my hidden service.",
+ num, num_internal);
+ circuit_launch_predicted_hs_circ(flags);
+ return;
+ }
+
+ if (needs_hs_client_circuits(now, &hidserv_needs_uptime,
+ &hidserv_needs_capacity,
+ num_internal, num_uptime_internal))
+ {
+ if (hidserv_needs_uptime)
+ flags |= CIRCLAUNCH_NEED_UPTIME;
+ if (hidserv_needs_capacity)
+ flags |= CIRCLAUNCH_NEED_CAPACITY;
+ flags |= CIRCLAUNCH_IS_INTERNAL;
+
+ log_info(LD_CIRC,
+ "Have %d clean circs (%d uptime-internal, %d internal), need"
+ " another hidden service circ.",
+ num, num_uptime_internal, num_internal);
+
+ circuit_launch_predicted_hs_circ(flags);
+ return;
+ }
+
+ if (needs_circuits_for_build(num)) {
+ flags = CIRCLAUNCH_NEED_CAPACITY;
+ /* if there are no exits in the consensus, make timeout
+ * circuits internal */
+ if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL)
+ flags |= CIRCLAUNCH_IS_INTERNAL;
+
+ log_info(LD_CIRC,
+ "Have %d clean circs need another buildtime test circ.", num);
+ circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, flags);
+ return;
+ }
+}
+
+/** Build a new test circuit every 5 minutes */
+#define TESTING_CIRCUIT_INTERVAL 300
+
+/** This function is called once a second, if router_have_minimum_dir_info()
+ * is true. Its job is to make sure all services we offer have enough circuits
+ * available. Some services just want enough circuits for current tasks,
+ * whereas others want a minimum set of idle circuits hanging around.
+ */
+void
+circuit_build_needed_circs(time_t now)
+{
+ const or_options_t *options = get_options();
+
+ /* launch a new circ for any pending streams that need one
+ * XXXX make the assumption that (some) AP streams (i.e. HS clients)
+ * don't require an exit circuit, review in #13814.
+ * This allows HSs to function in a consensus without exits. */
+ if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN)
+ connection_ap_rescan_and_attach_pending();
+
+ circuit_expire_old_circs_as_needed(now);
+
+ if (!options->DisablePredictedCircuits)
+ circuit_predict_and_launch_new();
+}
+
+/**
+ * Called once a second either directly or from
+ * circuit_build_needed_circs(). As appropriate (once per NewCircuitPeriod)
+ * resets failure counts and expires old circuits.
+ */
+void
+circuit_expire_old_circs_as_needed(time_t now)
+{
+ static time_t time_to_expire_and_reset = 0;
+
+ if (time_to_expire_and_reset < now) {
+ circuit_reset_failure_count(1);
+ time_to_expire_and_reset = now + get_options()->NewCircuitPeriod;
+ if (proxy_mode(get_options()))
+ addressmap_clean(now);
+ circuit_expire_old_circuits_clientside();
+
+#if 0 /* disable for now, until predict-and-launch-new can cull leftovers */
+
+ /* If we ever re-enable, this has to move into
+ * circuit_build_needed_circs */
+
+ circ = circuit_get_youngest_clean_open(CIRCUIT_PURPOSE_C_GENERAL);
+ if (get_options()->RunTesting &&
+ circ &&
+ circ->timestamp_began.tv_sec + TESTING_CIRCUIT_INTERVAL < now) {
+ log_fn(LOG_INFO,"Creating a new testing circuit.");
+ circuit_launch(CIRCUIT_PURPOSE_C_GENERAL, 0);
+ }
+#endif /* 0 */
+ }
+}
+
+/** If the stream <b>conn</b> is a member of any of the linked
+ * lists of <b>circ</b>, then remove it from the list.
+ */
+void
+circuit_detach_stream(circuit_t *circ, edge_connection_t *conn)
+{
+ edge_connection_t *prevconn;
+
+ tor_assert(circ);
+ tor_assert(conn);
+
+ if (conn->base_.type == CONN_TYPE_AP) {
+ entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn);
+ entry_conn->may_use_optimistic_data = 0;
+ }
+ conn->cpath_layer = NULL; /* don't keep a stale pointer */
+ conn->on_circuit = NULL;
+
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
+ int removed = 0;
+ if (conn == origin_circ->p_streams) {
+ origin_circ->p_streams = conn->next_stream;
+ removed = 1;
+ } else {
+ for (prevconn = origin_circ->p_streams;
+ prevconn && prevconn->next_stream && prevconn->next_stream != conn;
+ prevconn = prevconn->next_stream)
+ ;
+ if (prevconn && prevconn->next_stream) {
+ prevconn->next_stream = conn->next_stream;
+ removed = 1;
+ }
+ }
+ if (removed) {
+ log_debug(LD_APP, "Removing stream %d from circ %u",
+ conn->stream_id, (unsigned)circ->n_circ_id);
+
+ /* If the stream was removed, and it was a rend stream, decrement the
+ * number of streams on the circuit associated with the rend service.
+ */
+ if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
+ hs_dec_rdv_stream_counter(origin_circ);
+ }
+ return;
+ }
+ } else {
+ or_circuit_t *or_circ = TO_OR_CIRCUIT(circ);
+ if (conn == or_circ->n_streams) {
+ or_circ->n_streams = conn->next_stream;
+ return;
+ }
+ if (conn == or_circ->resolving_streams) {
+ or_circ->resolving_streams = conn->next_stream;
+ return;
+ }
+
+ for (prevconn = or_circ->n_streams;
+ prevconn && prevconn->next_stream && prevconn->next_stream != conn;
+ prevconn = prevconn->next_stream)
+ ;
+ if (prevconn && prevconn->next_stream) {
+ prevconn->next_stream = conn->next_stream;
+ return;
+ }
+
+ for (prevconn = or_circ->resolving_streams;
+ prevconn && prevconn->next_stream && prevconn->next_stream != conn;
+ prevconn = prevconn->next_stream)
+ ;
+ if (prevconn && prevconn->next_stream) {
+ prevconn->next_stream = conn->next_stream;
+ return;
+ }
+ }
+
+ log_warn(LD_BUG,"Edge connection not in circuit's list.");
+ /* Don't give an error here; it's harmless. */
+ tor_fragile_assert();
+}
+
+/** Find each circuit that has been unused for too long, or dirty
+ * for too long and has no streams on it: mark it for close.
+ */
+static void
+circuit_expire_old_circuits_clientside(void)
+{
+ struct timeval cutoff, now;
+
+ tor_gettimeofday(&now);
+ last_expired_clientside_circuits = now.tv_sec;
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ if (circ->marked_for_close || !CIRCUIT_IS_ORIGIN(circ))
+ continue;
+
+ cutoff = now;
+ cutoff.tv_sec -= TO_ORIGIN_CIRCUIT(circ)->circuit_idle_timeout;
+
+ /* If the circuit has been dirty for too long, and there are no streams
+ * on it, mark it for close.
+ */
+ if (circ->timestamp_dirty &&
+ circ->timestamp_dirty + get_options()->MaxCircuitDirtiness <
+ now.tv_sec &&
+ !TO_ORIGIN_CIRCUIT(circ)->p_streams /* nothing attached */ ) {
+ log_debug(LD_CIRC, "Closing n_circ_id %u (dirty %ld sec ago, "
+ "purpose %d)",
+ (unsigned)circ->n_circ_id,
+ (long)(now.tv_sec - circ->timestamp_dirty),
+ circ->purpose);
+ /* Don't do this magic for testing circuits. Their death is governed
+ * by circuit_expire_building */
+ if (circ->purpose != CIRCUIT_PURPOSE_PATH_BIAS_TESTING)
+ circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
+ } else if (!circ->timestamp_dirty && circ->state == CIRCUIT_STATE_OPEN) {
+ if (timercmp(&circ->timestamp_began, &cutoff, OP_LT)) {
+ if (circ->purpose == CIRCUIT_PURPOSE_C_GENERAL ||
+ circ->purpose == CIRCUIT_PURPOSE_C_HSDIR_GET ||
+ circ->purpose == CIRCUIT_PURPOSE_S_HSDIR_POST ||
+ circ->purpose == CIRCUIT_PURPOSE_HS_VANGUARDS ||
+ circ->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT ||
+ circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
+ circ->purpose == CIRCUIT_PURPOSE_TESTING ||
+ (circ->purpose >= CIRCUIT_PURPOSE_C_INTRODUCING &&
+ circ->purpose <= CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) ||
+ circ->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) {
+ log_info(LD_CIRC,
+ "Closing circuit %"PRIu32
+ " that has been unused for %ld msec.",
+ TO_ORIGIN_CIRCUIT(circ)->global_identifier,
+ tv_mdiff(&circ->timestamp_began, &now));
+ circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
+ } else if (!TO_ORIGIN_CIRCUIT(circ)->is_ancient) {
+ /* Server-side rend joined circuits can end up really old, because
+ * they are reused by clients for longer than normal. The client
+ * controls their lifespan. (They never become dirty, because
+ * connection_exit_begin_conn() never marks anything as dirty.)
+ * Similarly, server-side intro circuits last a long time. */
+ if (circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED &&
+ circ->purpose != CIRCUIT_PURPOSE_S_INTRO) {
+ log_notice(LD_CIRC,
+ "Ancient non-dirty circuit %d is still around after "
+ "%ld milliseconds. Purpose: %d (%s)",
+ TO_ORIGIN_CIRCUIT(circ)->global_identifier,
+ tv_mdiff(&circ->timestamp_began, &now),
+ circ->purpose,
+ circuit_purpose_to_string(circ->purpose));
+ TO_ORIGIN_CIRCUIT(circ)->is_ancient = 1;
+ }
+ }
+ }
+ }
+ } SMARTLIST_FOREACH_END(circ);
+}
+
+/** How long do we wait before killing circuits with the properties
+ * described below?
+ *
+ * Probably we could choose a number here as low as 5 to 10 seconds,
+ * since these circs are used for begindir, and a) generally you either
+ * ask another begindir question right after or you don't for a long time,
+ * b) clients at least through 0.2.1.x choose from the whole set of
+ * directory mirrors at each choice, and c) re-establishing a one-hop
+ * circuit via create-fast is a light operation assuming the TLS conn is
+ * still there.
+ *
+ * I expect "b" to go away one day when we move to using directory
+ * guards, but I think "a" and "c" are good enough reasons that a low
+ * number is safe even then.
+ */
+#define IDLE_ONE_HOP_CIRC_TIMEOUT 60
+
+/** Find each non-origin circuit that has been unused for too long,
+ * has no streams on it, came from a client, and ends here: mark it
+ * for close.
+ */
+void
+circuit_expire_old_circuits_serverside(time_t now)
+{
+ or_circuit_t *or_circ;
+ time_t cutoff = now - IDLE_ONE_HOP_CIRC_TIMEOUT;
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ if (circ->marked_for_close || CIRCUIT_IS_ORIGIN(circ))
+ continue;
+ or_circ = TO_OR_CIRCUIT(circ);
+ /* If the circuit has been idle for too long, and there are no streams
+ * on it, and it ends here, and it used a create_fast, mark it for close.
++ *
++ * Also if there is a rend_splice on it, it's a single onion service
++ * circuit and we should not close it.
+ */
+ if (or_circ->p_chan && channel_is_client(or_circ->p_chan) &&
+ !circ->n_chan &&
+ !or_circ->n_streams && !or_circ->resolving_streams &&
++ !or_circ->rend_splice &&
+ channel_when_last_xmit(or_circ->p_chan) <= cutoff) {
+ log_info(LD_CIRC, "Closing circ_id %u (empty %d secs ago)",
+ (unsigned)or_circ->p_circ_id,
+ (int)(now - channel_when_last_xmit(or_circ->p_chan)));
+ circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
+ }
+ }
+ SMARTLIST_FOREACH_END(circ);
+}
+
+/** Number of testing circuits we want open before testing our bandwidth. */
+#define NUM_PARALLEL_TESTING_CIRCS 4
+
+/** True iff we've ever had enough testing circuits open to test our
+ * bandwidth. */
+static int have_performed_bandwidth_test = 0;
+
+/** Reset have_performed_bandwidth_test, so we'll start building
+ * testing circuits again so we can exercise our bandwidth. */
+void
+reset_bandwidth_test(void)
+{
+ have_performed_bandwidth_test = 0;
+}
+
+/** Return 1 if we've already exercised our bandwidth, or if we
+ * have fewer than NUM_PARALLEL_TESTING_CIRCS testing circuits
+ * established or on the way. Else return 0.
+ */
+int
+circuit_enough_testing_circs(void)
+{
+ int num = 0;
+
+ if (have_performed_bandwidth_test)
+ return 1;
+
+ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, circ) {
+ if (!circ->marked_for_close && CIRCUIT_IS_ORIGIN(circ) &&
+ circ->purpose == CIRCUIT_PURPOSE_TESTING &&
+ circ->state == CIRCUIT_STATE_OPEN)
+ num++;
+ }
+ SMARTLIST_FOREACH_END(circ);
+ return num >= NUM_PARALLEL_TESTING_CIRCS;
+}
+
+/** A testing circuit has completed. Take whatever stats we want.
+ * Noticing reachability is taken care of in onionskin_answer(),
+ * so there's no need to record anything here. But if we still want
+ * to do the bandwidth test, and we now have enough testing circuits
+ * open, do it.
+ */
+static void
+circuit_testing_opened(origin_circuit_t *circ)
+{
+ if (have_performed_bandwidth_test ||
+ !check_whether_orport_reachable(get_options())) {
+ /* either we've already done everything we want with testing circuits,
+ * or this testing circuit became open due to a fluke, e.g. we picked
+ * a last hop where we already had the connection open due to an
+ * outgoing local circuit. */
+ circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_AT_ORIGIN);
+ } else if (circuit_enough_testing_circs()) {
+ router_perform_bandwidth_test(NUM_PARALLEL_TESTING_CIRCS, time(NULL));
+ have_performed_bandwidth_test = 1;
+ } else
+ router_do_reachability_checks(1, 0);
+}
+
+/** A testing circuit has failed to build. Take whatever stats we want. */
+static void
+circuit_testing_failed(origin_circuit_t *circ, int at_last_hop)
+{
+ const or_options_t *options = get_options();
+ if (server_mode(options) && check_whether_orport_reachable(options))
+ return;
+
+ log_info(LD_GENERAL,
+ "Our testing circuit (to see if your ORPort is reachable) "
+ "has failed. I'll try again later.");
+
+ /* These aren't used yet. */
+ (void)circ;
+ (void)at_last_hop;
+}
+
+/** The circuit <b>circ</b> has just become open. Take the next
+ * step: for rendezvous circuits, we pass circ to the appropriate
+ * function in rendclient or rendservice. For general circuits, we
+ * call connection_ap_attach_pending, which looks for pending streams
+ * that could use circ.
+ */
+void
+circuit_has_opened(origin_circuit_t *circ)
+{
+ control_event_circuit_status(circ, CIRC_EVENT_BUILT, 0);
+
+ /* Remember that this circuit has finished building. Now if we start
+ * it building again later (e.g. by extending it), we will know not
+ * to consider its build time. */
+ circ->has_opened = 1;
+
+ switch (TO_CIRCUIT(circ)->purpose) {
+ case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+ hs_client_circuit_has_opened(circ);
+ /* Start building an intro circ if we don't have one yet. */
+ connection_ap_attach_pending(1);
+ /* This isn't a call to circuit_try_attaching_streams because a
+ * circuit in _C_ESTABLISH_REND state isn't connected to its
+ * hidden service yet, thus we can't attach streams to it yet,
+ * thus circuit_try_attaching_streams would always clear the
+ * circuit's isolation state. circuit_try_attaching_streams is
+ * called later, when the rend circ enters _C_REND_JOINED
+ * state. */
+ break;
+ case CIRCUIT_PURPOSE_C_INTRODUCING:
+ hs_client_circuit_has_opened(circ);
+ break;
+ case CIRCUIT_PURPOSE_C_GENERAL:
+ case CIRCUIT_PURPOSE_C_HSDIR_GET:
+ case CIRCUIT_PURPOSE_S_HSDIR_POST:
+ /* Tell any AP connections that have been waiting for a new
+ * circuit that one is ready. */
+ circuit_try_attaching_streams(circ);
+ break;
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ /* at the service, waiting for introductions */
+ hs_service_circuit_has_opened(circ);
+ break;
+ case CIRCUIT_PURPOSE_S_CONNECT_REND:
+ /* at the service, connecting to rend point */
+ hs_service_circuit_has_opened(circ);
+ break;
+ case CIRCUIT_PURPOSE_TESTING:
+ circuit_testing_opened(circ);
+ break;
+ /* default:
+ * This won't happen in normal operation, but might happen if the
+ * controller did it. Just let it slide. */
+ }
+}
+
+/** If the stream-isolation state of <b>circ</b> can be cleared, clear
+ * it. Return non-zero iff <b>circ</b>'s isolation state was cleared. */
+static int
+circuit_try_clearing_isolation_state(origin_circuit_t *circ)
+{
+ if (/* The circuit may have become non-open if it was cannibalized.*/
+ circ->base_.state == CIRCUIT_STATE_OPEN &&
+ /* If !isolation_values_set, there is nothing to clear. */
+ circ->isolation_values_set &&
+ /* It's not legal to clear a circuit's isolation info if it's ever had
+ * streams attached */
+ !circ->isolation_any_streams_attached) {
+ /* If we have any isolation information set on this circuit, and
+ * we didn't manage to attach any streams to it, then we can
+ * and should clear it and try again. */
+ circuit_clear_isolation(circ);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/** Called when a circuit becomes ready for streams to be attached to
+ * it. */
+void
+circuit_try_attaching_streams(origin_circuit_t *circ)
+{
+ /* Attach streams to this circuit if we can. */
+ connection_ap_attach_pending(1);
+
+ /* The call to circuit_try_clearing_isolation_state here will do
+ * nothing and return 0 if we didn't attach any streams to circ
+ * above. */
+ if (circuit_try_clearing_isolation_state(circ)) {
+ /* Maybe *now* we can attach some streams to this circuit. */
+ connection_ap_attach_pending(1);
+ }
+}
+
+/** Called whenever a circuit could not be successfully built.
+ */
+void
+circuit_build_failed(origin_circuit_t *circ)
+{
+ channel_t *n_chan = NULL;
+ /* we should examine circ and see if it failed because of
+ * the last hop or an earlier hop. then use this info below.
+ */
+ int failed_at_last_hop = 0;
+
+ /* First, check to see if this was a path failure, rather than build
+ * failure.
+ *
+ * Note that we deliberately use circuit_get_cpath_len() (and not
+ * circuit_get_cpath_opened_len()) because we only want to ensure
+ * that a full path is *chosen*. This is different than a full path
+ * being *built*. We only want to count *build* failures below.
+ *
+ * Path selection failures can happen spuriously for a number
+ * of reasons (such as aggressive/invalid user-specified path
+ * restrictions in the torrc, insufficient microdescriptors, and
+ * non-user reasons like exitpolicy issues), and so should not be
+ * counted as failures below.
+ */
+ if (circuit_get_cpath_len(circ) < circ->build_state->desired_path_len) {
+ static ratelim_t pathfail_limit = RATELIM_INIT(3600);
+ log_fn_ratelim(&pathfail_limit, LOG_NOTICE, LD_CIRC,
+ "Our circuit %u (id: %" PRIu32 ") died due to an invalid "
+ "selected path, purpose %s. This may be a torrc "
+ "configuration issue, or a bug.",
+ TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier,
+ circuit_purpose_to_string(TO_CIRCUIT(circ)->purpose));
+
+ /* If the path failed on an RP, retry it. */
+ if (TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND)
+ hs_circ_retry_service_rendezvous_point(circ);
+
+ /* In all other cases, just bail. The rest is just failure accounting
+ * that we don't want to do */
+ return;
+ }
+
+ /* If the last hop isn't open, and the second-to-last is, we failed
+ * at the last hop. */
+ if (circ->cpath &&
+ circ->cpath->prev->state != CPATH_STATE_OPEN &&
+ circ->cpath->prev->prev->state == CPATH_STATE_OPEN) {
+ failed_at_last_hop = 1;
+ }
+
+ /* Check if we failed at first hop */
+ if (circ->cpath &&
+ circ->cpath->state != CPATH_STATE_OPEN &&
+ ! circ->base_.received_destroy) {
+ /* We failed at the first hop for some reason other than a DESTROY cell.
+ * If there's an OR connection to blame, blame it. Also, avoid this relay
+ * for a while, and fail any one-hop directory fetches destined for it. */
+ const char *n_chan_ident = circ->cpath->extend_info->identity_digest;
+ tor_assert(n_chan_ident);
+ int already_marked = 0;
+ if (circ->base_.n_chan) {
+ n_chan = circ->base_.n_chan;
+
+ if (n_chan->is_bad_for_new_circs) {
+ /* We only want to blame this router when a fresh healthy
+ * connection fails. So don't mark this router as newly failed,
+ * since maybe this was just an old circuit attempt that's
+ * finally timing out now. Also, there's no need to blow away
+ * circuits/streams/etc, since the failure of an unhealthy conn
+ * doesn't tell us much about whether a healthy conn would
+ * succeed. */
+ already_marked = 1;
+ }
+ log_info(LD_OR,
+ "Our circuit %u (id: %" PRIu32 ") failed to get a response "
+ "from the first hop (%s). I'm going to try to rotate to a "
+ "better connection.",
+ TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier,
+ channel_get_canonical_remote_descr(n_chan));
+ n_chan->is_bad_for_new_circs = 1;
+ } else {
+ log_info(LD_OR,
+ "Our circuit %u (id: %" PRIu32 ") died before the first hop "
+ "with no connection",
+ TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier);
+ }
+ if (!already_marked) {
+ /*
+ * If we have guard state (new guard API) and our path selection
+ * code actually chose a full path, then blame the failure of this
+ * circuit on the guard.
+ */
+ if (circ->guard_state)
+ entry_guard_failed(&circ->guard_state);
+ /* if there are any one-hop streams waiting on this circuit, fail
+ * them now so they can retry elsewhere. */
+ connection_ap_fail_onehop(n_chan_ident, circ->build_state);
+ }
+ }
+
+ switch (circ->base_.purpose) {
+ case CIRCUIT_PURPOSE_C_HSDIR_GET:
+ case CIRCUIT_PURPOSE_S_HSDIR_POST:
+ case CIRCUIT_PURPOSE_C_GENERAL:
+ /* If we never built the circuit, note it as a failure. */
+ circuit_increment_failure_count();
+ if (failed_at_last_hop) {
+ /* Make sure any streams that demand our last hop as their exit
+ * know that it's unlikely to happen. */
+ circuit_discard_optional_exit_enclaves(circ->cpath->prev->extend_info);
+ }
+ break;
+ case CIRCUIT_PURPOSE_TESTING:
+ circuit_testing_failed(circ, failed_at_last_hop);
+ break;
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ /* at the service, waiting for introductions */
+ if (circ->base_.state != CIRCUIT_STATE_OPEN) {
+ circuit_increment_failure_count();
+ }
+ /* no need to care here, because the service will rebuild intro
+ * points periodically. */
+ break;
+ case CIRCUIT_PURPOSE_C_INTRODUCING:
+ /* at the client, connecting to intro point */
+ /* Don't increment failure count, since the service may have picked
+ * the introduction point maliciously */
+ /* The client will pick a new intro point when this one dies, if
+ * the stream in question still cares. No need to act here. */
+ break;
+ case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+ /* at the client, waiting for the service */
+ circuit_increment_failure_count();
+ /* the client will pick a new rend point when this one dies, if
+ * the stream in question still cares. No need to act here. */
+ break;
+ case CIRCUIT_PURPOSE_S_CONNECT_REND:
+ /* at the service, connecting to rend point */
+ /* Don't increment failure count, since the client may have picked
+ * the rendezvous point maliciously */
+ log_info(LD_REND,
+ "Couldn't connect to the client's chosen rend point %s "
+ "(%s hop failed).",
+ escaped(build_state_get_exit_nickname(circ->build_state)),
+ failed_at_last_hop?"last":"non-last");
+ hs_circ_retry_service_rendezvous_point(circ);
+ break;
+ /* default:
+ * This won't happen in normal operation, but might happen if the
+ * controller did it. Just let it slide. */
+ }
+}
+
+/** Number of consecutive failures so far; should only be touched by
+ * circuit_launch_new and circuit_*_failure_count.
+ */
+static int n_circuit_failures = 0;
+/** Before the last time we called circuit_reset_failure_count(), were
+ * there a lot of failures? */
+static int did_circs_fail_last_period = 0;
+
+/** Don't retry launching a new circuit if we try this many times with no
+ * success. */
+#define MAX_CIRCUIT_FAILURES 5
+
+/** Launch a new circuit; see circuit_launch_by_extend_info() for
+ * details on arguments. */
+origin_circuit_t *
+circuit_launch(uint8_t purpose, int flags)
+{
+ return circuit_launch_by_extend_info(purpose, NULL, flags);
+}
+
+/* Do we have enough descriptors to build paths?
+ * If need_exit is true, return 1 if we can build exit paths.
+ * (We need at least one Exit in the consensus to build exit paths.)
+ * If need_exit is false, return 1 if we can build internal paths.
+ */
+static int
+have_enough_path_info(int need_exit)
+{
+ if (need_exit)
+ return router_have_consensus_path() == CONSENSUS_PATH_EXIT;
+ else
+ return router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN;
+}
+
+/**
+ * Tell us if a circuit is a hidden service circuit.
+ */
+int
+circuit_purpose_is_hidden_service(uint8_t purpose)
+{
+ if (purpose == CIRCUIT_PURPOSE_HS_VANGUARDS) {
+ return 1;
+ }
+
+ /* Client-side purpose */
+ if (purpose >= CIRCUIT_PURPOSE_C_HS_MIN_ &&
+ purpose <= CIRCUIT_PURPOSE_C_HS_MAX_) {
+ return 1;
+ }
+
+ /* Service-side purpose */
+ if (purpose >= CIRCUIT_PURPOSE_S_HS_MIN_ &&
+ purpose <= CIRCUIT_PURPOSE_S_HS_MAX_) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Return true if this circuit purpose should use vanguards
+ * or pinned Layer2 or Layer3 guards.
+ *
+ * This function takes both the circuit purpose and the
+ * torrc options for pinned middles/vanguards into account
+ * (ie: the circuit must be a hidden service circuit and
+ * vanguards/pinned middles must be enabled for it to return
+ * true).
+ */
+int
+circuit_should_use_vanguards(uint8_t purpose)
+{
+ const or_options_t *options = get_options();
+
+ /* Only hidden service circuits use vanguards */
+ if (!circuit_purpose_is_hidden_service(purpose))
+ return 0;
+
+ /* Pinned middles are effectively vanguards */
+ if (options->HSLayer2Nodes || options->HSLayer3Nodes)
+ return 1;
+
+ return 0;
+}
+
+/**
+ * Return true for the set of conditions for which it is OK to use
+ * a cannibalized circuit.
+ *
+ * Don't cannibalize for onehops, or certain purposes.
+ */
+static int
+circuit_should_cannibalize_to_build(uint8_t purpose_to_build,
+ int has_extend_info,
+ int onehop_tunnel)
+{
+
+ /* Do not try to cannibalize if this is a one hop circuit. */
+ if (onehop_tunnel) {
+ return 0;
+ }
+
+ /* Don't try to cannibalize for general purpose circuits that do not
+ * specify a custom exit. */
+ if (purpose_to_build == CIRCUIT_PURPOSE_C_GENERAL && !has_extend_info) {
+ return 0;
+ }
+
+ /* Don't cannibalize for testing circuits. We want to see if they
+ * complete normally. Also don't cannibalize for vanguard-purpose
+ * circuits, since those are specially pre-built for later
+ * cannibalization by the actual specific circuit types that need
+ * vanguards.
+ */
+ if (purpose_to_build == CIRCUIT_PURPOSE_TESTING ||
+ purpose_to_build == CIRCUIT_PURPOSE_HS_VANGUARDS) {
+ return 0;
+ }
+
+ /* For vanguards, the server-side intro circ is not cannibalized
+ * because we pre-build 4 hop HS circuits, and it only needs a 3 hop
+ * circuit. It is also long-lived, so it is more important that
+ * it have lower latency than get built fast.
+ */
+ if (circuit_should_use_vanguards(purpose_to_build) &&
+ purpose_to_build == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/** Launch a new circuit with purpose <b>purpose</b> and exit node
+ * <b>extend_info</b> (or NULL to select a random exit node). If flags
+ * contains CIRCLAUNCH_NEED_UPTIME, choose among routers with high uptime. If
+ * CIRCLAUNCH_NEED_CAPACITY is set, choose among routers with high bandwidth.
+ * If CIRCLAUNCH_IS_INTERNAL is true, the last hop need not be an exit node.
+ * If CIRCLAUNCH_ONEHOP_TUNNEL is set, the circuit will have only one hop.
+ * Return the newly allocated circuit on success, or NULL on failure. */
+origin_circuit_t *
+circuit_launch_by_extend_info(uint8_t purpose,
+ extend_info_t *extend_info,
+ int flags)
+{
+ origin_circuit_t *circ;
+ int onehop_tunnel = (flags & CIRCLAUNCH_ONEHOP_TUNNEL) != 0;
+ int have_path = have_enough_path_info(! (flags & CIRCLAUNCH_IS_INTERNAL) );
+
+ /* Keep some stats about our attempts to launch HS rendezvous circuits */
+ if (purpose == CIRCUIT_PURPOSE_S_CONNECT_REND) {
+ hs_stats_note_service_rendezvous_launch();
+ }
+
+ if (!onehop_tunnel && (!router_have_minimum_dir_info() || !have_path)) {
+ log_debug(LD_CIRC,"Haven't %s yet; canceling "
+ "circuit launch.",
+ !router_have_minimum_dir_info() ?
+ "fetched enough directory info" :
+ "received a consensus with exits");
+ return NULL;
+ }
+
+ /* If we can/should cannibalize another circuit to build this one,
+ * then do so. */
+ if (circuit_should_cannibalize_to_build(purpose,
+ extend_info != NULL,
+ onehop_tunnel)) {
+ /* see if there are appropriate circs available to cannibalize. */
+ /* XXX if we're planning to add a hop, perhaps we want to look for
+ * internal circs rather than exit circs? -RD */
+ circ = circuit_find_to_cannibalize(purpose, extend_info, flags);
+ if (circ) {
+ uint8_t old_purpose = circ->base_.purpose;
+ struct timeval old_timestamp_began = circ->base_.timestamp_began;
+
+ log_info(LD_CIRC, "Cannibalizing circ %u (id: %" PRIu32 ") for "
+ "purpose %d (%s)",
+ TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier, purpose,
+ circuit_purpose_to_string(purpose));
+
+ if ((purpose == CIRCUIT_PURPOSE_S_CONNECT_REND ||
+ purpose == CIRCUIT_PURPOSE_C_INTRODUCING) &&
+ circ->path_state == PATH_STATE_BUILD_SUCCEEDED) {
+ /* Path bias: Cannibalized rends pre-emptively count as a
+ * successfully built but unused closed circuit. We don't
+ * wait until the extend (or the close) because the rend
+ * point could be malicious.
+ *
+ * Same deal goes for client side introductions. Clients
+ * can be manipulated to connect repeatedly to them
+ * (especially web clients).
+ *
+ * If we decide to probe the initial portion of these circs,
+ * (up to the adversary's final hop), we need to remove this,
+ * or somehow mark the circuit with a special path state.
+ */
+
+ /* This must be called before the purpose change */
+ pathbias_check_close(circ, END_CIRC_REASON_FINISHED);
+ }
+
+ circuit_change_purpose(TO_CIRCUIT(circ), purpose);
+ /* Reset the start date of this circ, else expire_building
+ * will see it and think it's been trying to build since it
+ * began.
+ *
+ * Technically, the code should reset this when the
+ * create cell is finally sent, but we're close enough
+ * here. */
+ tor_gettimeofday(&circ->base_.timestamp_began);
+
+ control_event_circuit_cannibalized(circ, old_purpose,
+ &old_timestamp_began);
+
+ switch (purpose) {
+ case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+ /* it's ready right now */
+ break;
+ case CIRCUIT_PURPOSE_C_INTRODUCING:
+ case CIRCUIT_PURPOSE_S_CONNECT_REND:
+ case CIRCUIT_PURPOSE_C_GENERAL:
+ case CIRCUIT_PURPOSE_S_HSDIR_POST:
+ case CIRCUIT_PURPOSE_C_HSDIR_GET:
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ /* need to add a new hop */
+ tor_assert(extend_info);
+ if (circuit_extend_to_new_exit(circ, extend_info) < 0)
+ return NULL;
+ break;
+ default:
+ log_warn(LD_BUG,
+ "unexpected purpose %d when cannibalizing a circ.",
+ purpose);
+ tor_fragile_assert();
+ return NULL;
+ }
+ return circ;
+ }
+ }
+
+ if (did_circs_fail_last_period &&
+ n_circuit_failures > MAX_CIRCUIT_FAILURES) {
+ /* too many failed circs in a row. don't try. */
+// log_fn(LOG_INFO,"%d failures so far, not trying.",n_circuit_failures);
+ return NULL;
+ }
+
+ /* try a circ. if it fails, circuit_mark_for_close will increment
+ * n_circuit_failures */
+ return circuit_establish_circuit(purpose, extend_info, flags);
+}
+
+/** Record another failure at opening a general circuit. When we have
+ * too many, we'll stop trying for the remainder of this minute.
+ */
+static void
+circuit_increment_failure_count(void)
+{
+ ++n_circuit_failures;
+ log_debug(LD_CIRC,"n_circuit_failures now %d.",n_circuit_failures);
+}
+
+/** Reset the failure count for opening general circuits. This means
+ * we will try MAX_CIRCUIT_FAILURES times more (if necessary) before
+ * stopping again.
+ */
+void
+circuit_reset_failure_count(int timeout)
+{
+ if (timeout && n_circuit_failures > MAX_CIRCUIT_FAILURES)
+ did_circs_fail_last_period = 1;
+ else
+ did_circs_fail_last_period = 0;
+ n_circuit_failures = 0;
+}
+
+/** Find an open circ that we're happy to use for <b>conn</b> and return 1. If
+ * there isn't one, and there isn't one on the way, launch one and return
+ * 0. If it will never work, return -1.
+ *
+ * Write the found or in-progress or launched circ into *circp.
+ */
+static int
+circuit_get_open_circ_or_launch(entry_connection_t *conn,
+ uint8_t desired_circuit_purpose,
+ origin_circuit_t **circp)
+{
+ origin_circuit_t *circ;
+ int check_exit_policy;
+ int need_uptime, need_internal;
+ int want_onehop;
+ const or_options_t *options = get_options();
+
+ tor_assert(conn);
+ tor_assert(circp);
+ if (ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_CIRCUIT_WAIT) {
+ connection_t *c = ENTRY_TO_CONN(conn);
+ log_err(LD_BUG, "Connection state mismatch: wanted "
+ "AP_CONN_STATE_CIRCUIT_WAIT, but got %d (%s)",
+ c->state, conn_state_to_string(c->type, c->state));
+ }
+ tor_assert(ENTRY_TO_CONN(conn)->state == AP_CONN_STATE_CIRCUIT_WAIT);
+
+ /* Will the exit policy of the exit node apply to this stream? */
+ check_exit_policy =
+ conn->socks_request->command == SOCKS_COMMAND_CONNECT &&
+ !conn->use_begindir &&
+ !connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn));
+
+ /* Does this connection want a one-hop circuit? */
+ want_onehop = conn->want_onehop;
+
+ /* Do we need a high-uptime circuit? */
+ need_uptime = !conn->want_onehop && !conn->use_begindir &&
+ smartlist_contains_int_as_string(options->LongLivedPorts,
+ conn->socks_request->port);
+
+ /* Do we need an "internal" circuit? */
+ if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL)
+ need_internal = 1;
+ else if (conn->use_begindir || conn->want_onehop)
+ need_internal = 1;
+ else
+ need_internal = 0;
+
+ /* We now know what kind of circuit we need. See if there is an
+ * open circuit that we can use for this stream */
+ circ = circuit_get_best(conn, 1 /* Insist on open circuits */,
+ desired_circuit_purpose,
+ need_uptime, need_internal);
+
+ if (circ) {
+ /* We got a circuit that will work for this stream! We can return it. */
+ *circp = circ;
+ return 1; /* we're happy */
+ }
+
+ /* Okay, there's no circuit open that will work for this stream. Let's
+ * see if there's an in-progress circuit or if we have to launch one */
+
+ /* Do we know enough directory info to build circuits at all? */
+ int have_path = have_enough_path_info(!need_internal);
+
+ if (!want_onehop && (!router_have_minimum_dir_info() || !have_path)) {
+ /* If we don't have enough directory information, we can't build
+ * multihop circuits.
+ */
+ if (!connection_get_by_type(CONN_TYPE_DIR)) {
+ int severity = LOG_NOTICE;
+ /* Retry some stuff that might help the connection work. */
+ /* If we are configured with EntryNodes or UseBridges */
+ if (entry_list_is_constrained(options)) {
+ /* Retry all our guards / bridges.
+ * guards_retry_optimistic() always returns true here. */
+ int rv = guards_retry_optimistic(options);
+ tor_assert_nonfatal_once(rv);
+ log_fn(severity, LD_APP|LD_DIR,
+ "Application request when we haven't %s. "
+ "Optimistically trying known %s again.",
+ !router_have_minimum_dir_info() ?
+ "used client functionality lately" :
+ "received a consensus with exits",
+ options->UseBridges ? "bridges" : "entrynodes");
+ } else {
+ /* Getting directory documents doesn't help much if we have a limited
+ * number of guards */
+ tor_assert_nonfatal(!options->UseBridges);
+ tor_assert_nonfatal(!options->EntryNodes);
+ /* Retry our directory fetches, so we have a fresh set of guard info */
+ log_fn(severity, LD_APP|LD_DIR,
+ "Application request when we haven't %s. "
+ "Optimistically trying directory fetches again.",
+ !router_have_minimum_dir_info() ?
+ "used client functionality lately" :
+ "received a consensus with exits");
+ routerlist_retry_directory_downloads(time(NULL));
+ }
+ }
+ /* Since we didn't have enough directory info, we can't attach now. The
+ * stream will be dealt with when router_have_minimum_dir_info becomes 1,
+ * or when all directory attempts fail and directory_all_unreachable()
+ * kills it.
+ */
+ return 0;
+ }
+
+ /* Check whether the exit policy of the chosen exit, or the exit policies
+ * of _all_ nodes, would forbid this node. */
+ if (check_exit_policy) {
+ if (!conn->chosen_exit_name) {
+ struct in_addr in;
+ tor_addr_t addr, *addrp=NULL;
+ if (tor_inet_aton(conn->socks_request->address, &in)) {
+ tor_addr_from_in(&addr, &in);
+ addrp = &addr;
+ }
+ if (router_exit_policy_all_nodes_reject(addrp,
+ conn->socks_request->port,
+ need_uptime)) {
+ log_notice(LD_APP,
+ "No Tor server allows exit to %s:%d. Rejecting.",
+ safe_str_client(conn->socks_request->address),
+ conn->socks_request->port);
+ return -1;
+ }
+ } else {
+ /* XXXX Duplicates checks in connection_ap_handshake_attach_circuit:
+ * refactor into a single function. */
+ const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0);
+ int opt = conn->chosen_exit_optional;
+ if (node && !connection_ap_can_use_exit(conn, node)) {
+ log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
+ "Requested exit point '%s' is excluded or "
+ "would refuse request. %s.",
+ conn->chosen_exit_name, opt ? "Trying others" : "Closing");
+ if (opt) {
+ conn->chosen_exit_optional = 0;
+ tor_free(conn->chosen_exit_name);
+ /* Try again. */
+ return circuit_get_open_circ_or_launch(conn,
+ desired_circuit_purpose,
+ circp);
+ }
+ return -1;
+ }
+ }
+ }
+
+ /* Now, check whether there already a circuit on the way that could handle
+ * this stream. This check matches the one above, but this time we
+ * do not require that the circuit will work. */
+ circ = circuit_get_best(conn, 0 /* don't insist on open circuits */,
+ desired_circuit_purpose,
+ need_uptime, need_internal);
+ if (circ)
+ log_debug(LD_CIRC, "one on the way!");
+
+ if (!circ) {
+ /* No open or in-progress circuit could handle this stream! We
+ * will have to launch one!
+ */
+
+ /* The chosen exit node, if there is one. */
+ extend_info_t *extend_info=NULL;
+ const int n_pending = count_pending_general_client_circuits();
+
+ /* Do we have too many pending circuits? */
+ if (n_pending >= options->MaxClientCircuitsPending) {
+ static ratelim_t delay_limit = RATELIM_INIT(10*60);
+ char *m;
+ if ((m = rate_limit_log(&delay_limit, approx_time()))) {
+ log_notice(LD_APP, "We'd like to launch a circuit to handle a "
+ "connection, but we already have %d general-purpose client "
+ "circuits pending. Waiting until some finish.%s",
+ n_pending, m);
+ tor_free(m);
+ }
+ return 0;
+ }
+
+ /* If this is a hidden service trying to start an introduction point,
+ * handle that case. */
+ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
+ /* need to pick an intro point */
+ extend_info = hs_client_get_random_intro_from_edge(edge_conn);
+ if (!extend_info) {
+ log_info(LD_REND, "No intro points: re-fetching service descriptor.");
+ if (edge_conn->rend_data) {
+ rend_client_refetch_v2_renddesc(edge_conn->rend_data);
+ } else {
+ hs_client_refetch_hsdesc(&edge_conn->hs_ident->identity_pk);
+ }
+ connection_ap_mark_as_waiting_for_renddesc(conn);
+ return 0;
+ }
+ log_info(LD_REND,"Chose %s as intro point for '%s'.",
+ extend_info_describe(extend_info),
+ (edge_conn->rend_data) ?
+ safe_str_client(rend_data_get_address(edge_conn->rend_data)) :
+ "service");
+ }
+
+ /* If we have specified a particular exit node for our
+ * connection, then be sure to open a circuit to that exit node.
+ */
+ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL ||
+ desired_circuit_purpose == CIRCUIT_PURPOSE_S_HSDIR_POST ||
+ desired_circuit_purpose == CIRCUIT_PURPOSE_C_HSDIR_GET) {
+ if (conn->chosen_exit_name) {
+ const node_t *r;
+ int opt = conn->chosen_exit_optional;
+ r = node_get_by_nickname(conn->chosen_exit_name, 0);
+ if (r && node_has_preferred_descriptor(r, conn->want_onehop ? 1 : 0)) {
+ /* We might want to connect to an IPv6 bridge for loading
+ descriptors so we use the preferred address rather than
+ the primary. */
+ extend_info = extend_info_from_node(r, conn->want_onehop ? 1 : 0);
+ if (!extend_info) {
+ log_warn(LD_CIRC,"Could not make a one-hop connection to %s. "
+ "Discarding this circuit.", conn->chosen_exit_name);
+ return -1;
+ }
+ } else { /* ! (r && node_has_preferred_descriptor(...)) */
+ log_debug(LD_DIR, "considering %d, %s",
+ want_onehop, conn->chosen_exit_name);
+ if (want_onehop && conn->chosen_exit_name[0] == '$') {
+ /* We're asking for a one-hop circuit to a router that
+ * we don't have a routerinfo about. Make up an extend_info. */
+ /* XXX prop220: we need to make chosen_exit_name able to
+ * encode both key formats. This is not absolutely critical
+ * since this is just for one-hop circuits, but we should
+ * still get it done */
+ char digest[DIGEST_LEN];
+ char *hexdigest = conn->chosen_exit_name+1;
+ tor_addr_t addr;
+ if (strlen(hexdigest) < HEX_DIGEST_LEN ||
+ base16_decode(digest,DIGEST_LEN,
+ hexdigest,HEX_DIGEST_LEN) != DIGEST_LEN) {
+ log_info(LD_DIR, "Broken exit digest on tunnel conn. Closing.");
+ return -1;
+ }
+ if (tor_addr_parse(&addr, conn->socks_request->address) < 0) {
+ log_info(LD_DIR, "Broken address %s on tunnel conn. Closing.",
+ escaped_safe_str_client(conn->socks_request->address));
+ return -1;
+ }
+ /* XXXX prop220 add a workaround for ed25519 ID below*/
+ extend_info = extend_info_new(conn->chosen_exit_name+1,
+ digest,
+ NULL, /* Ed25519 ID */
+ NULL, NULL, /* onion keys */
+ &addr, conn->socks_request->port);
+ } else { /* ! (want_onehop && conn->chosen_exit_name[0] == '$') */
+ /* We will need an onion key for the router, and we
+ * don't have one. Refuse or relax requirements. */
+ log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
+ "Requested exit point '%s' is not known. %s.",
+ conn->chosen_exit_name, opt ? "Trying others" : "Closing");
+ if (opt) {
+ conn->chosen_exit_optional = 0;
+ tor_free(conn->chosen_exit_name);
+ /* Try again with no requested exit */
+ return circuit_get_open_circ_or_launch(conn,
+ desired_circuit_purpose,
+ circp);
+ }
+ return -1;
+ }
+ }
+ }
+ } /* Done checking for general circutis with chosen exits. */
+
+ /* What purpose do we need to launch this circuit with? */
+ uint8_t new_circ_purpose;
+ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED)
+ new_circ_purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND;
+ else if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)
+ new_circ_purpose = CIRCUIT_PURPOSE_C_INTRODUCING;
+ else
+ new_circ_purpose = desired_circuit_purpose;
+
+ /* Determine what kind of a circuit to launch, and actually launch it. */
+ {
+ int flags = CIRCLAUNCH_NEED_CAPACITY;
+ if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL;
+ if (need_uptime) flags |= CIRCLAUNCH_NEED_UPTIME;
+ if (need_internal) flags |= CIRCLAUNCH_IS_INTERNAL;
+
+ /* If we are about to pick a v3 RP right now, make sure we pick a
+ * rendezvous point that supports the v3 protocol! */
+ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED &&
+ new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
+ ENTRY_TO_EDGE_CONN(conn)->hs_ident) {
+ flags |= CIRCLAUNCH_IS_V3_RP;
+ log_info(LD_GENERAL, "Getting rendezvous circuit to v3 service!");
+ }
+
+ circ = circuit_launch_by_extend_info(new_circ_purpose, extend_info,
+ flags);
+ }
+
+ extend_info_free(extend_info);
+
+ /* Now trigger things that need to happen when we launch circuits */
+
+ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL ||
+ desired_circuit_purpose == CIRCUIT_PURPOSE_C_HSDIR_GET ||
+ desired_circuit_purpose == CIRCUIT_PURPOSE_S_HSDIR_POST) {
+ /* We just caused a circuit to get built because of this stream.
+ * If this stream has caused a _lot_ of circuits to be built, that's
+ * a bad sign: we should tell the user. */
+ if (conn->num_circuits_launched < NUM_CIRCUITS_LAUNCHED_THRESHOLD &&
+ ++conn->num_circuits_launched == NUM_CIRCUITS_LAUNCHED_THRESHOLD)
+ log_info(LD_CIRC, "The application request to %s:%d has launched "
+ "%d circuits without finding one it likes.",
+ escaped_safe_str_client(conn->socks_request->address),
+ conn->socks_request->port,
+ conn->num_circuits_launched);
+ } else {
+ /* help predict this next time */
+ rep_hist_note_used_internal(time(NULL), need_uptime, 1);
+ if (circ) {
+ const edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn);
+ if (edge_conn->rend_data) {
+ /* write the service_id into circ */
+ circ->rend_data = rend_data_dup(edge_conn->rend_data);
+ } else if (edge_conn->hs_ident) {
+ circ->hs_ident =
+ hs_ident_circuit_new(&edge_conn->hs_ident->identity_pk,
+ HS_IDENT_CIRCUIT_INTRO);
+ }
+ if (circ->base_.purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND &&
+ circ->base_.state == CIRCUIT_STATE_OPEN)
+ circuit_has_opened(circ);
+ }
+ }
+ } /* endif (!circ) */
+
+ /* We either found a good circuit, or launched a new circuit, or failed to
+ * do so. Report success, and delay. */
+
+ if (circ) {
+ /* Mark the circuit with the isolation fields for this connection.
+ * When the circuit arrives, we'll clear these flags: this is
+ * just some internal bookkeeping to make sure that we have
+ * launched enough circuits.
+ */
+ connection_edge_update_circuit_isolation(conn, circ, 0);
+ } else {
+ log_info(LD_APP,
+ "No safe circuit (purpose %d) ready for edge "
+ "connection; delaying.",
+ desired_circuit_purpose);
+ }
+ *circp = circ;
+ return 0;
+}
+
+/** Return true iff <b>crypt_path</b> is one of the crypt_paths for
+ * <b>circ</b>. */
+static int
+cpath_is_on_circuit(origin_circuit_t *circ, crypt_path_t *crypt_path)
+{
+ crypt_path_t *cpath, *cpath_next = NULL;
+ for (cpath = circ->cpath; cpath_next != circ->cpath; cpath = cpath_next) {
+ cpath_next = cpath->next;
+ if (crypt_path == cpath)
+ return 1;
+ }
+ return 0;
+}
+
+/** Return true iff client-side optimistic data is supported. */
+static int
+optimistic_data_enabled(void)
+{
+ const or_options_t *options = get_options();
+ if (options->OptimisticData < 0) {
+ /* Note: this default was 0 before #18815 was merged. We can't take the
+ * parameter out of the consensus until versions before that are all
+ * obsolete. */
+ const int32_t enabled =
+ networkstatus_get_param(NULL, "UseOptimisticData", /*default*/ 1, 0, 1);
+ return (int)enabled;
+ }
+ return options->OptimisticData;
+}
+
+/** Attach the AP stream <b>apconn</b> to circ's linked list of
+ * p_streams. Also set apconn's cpath_layer to <b>cpath</b>, or to the last
+ * hop in circ's cpath if <b>cpath</b> is NULL.
+ */
+static void
+link_apconn_to_circ(entry_connection_t *apconn, origin_circuit_t *circ,
+ crypt_path_t *cpath)
+{
+ const node_t *exitnode = NULL;
+
+ /* add it into the linked list of streams on this circuit */
+ log_debug(LD_APP|LD_CIRC, "attaching new conn to circ. n_circ_id %u.",
+ (unsigned)circ->base_.n_circ_id);
+ /* reset it, so we can measure circ timeouts */
+ ENTRY_TO_CONN(apconn)->timestamp_last_read_allowed = time(NULL);
+ ENTRY_TO_EDGE_CONN(apconn)->next_stream = circ->p_streams;
+ ENTRY_TO_EDGE_CONN(apconn)->on_circuit = TO_CIRCUIT(circ);
+ /* assert_connection_ok(conn, time(NULL)); */
+ circ->p_streams = ENTRY_TO_EDGE_CONN(apconn);
+
+ if (connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(apconn))) {
+ /* We are attaching a stream to a rendezvous circuit. That means
+ * that an attempt to connect to a hidden service just
+ * succeeded. Tell rendclient.c. */
+ hs_client_note_connection_attempt_succeeded(ENTRY_TO_EDGE_CONN(apconn));
+ }
+
+ if (cpath) { /* we were given one; use it */
+ tor_assert(cpath_is_on_circuit(circ, cpath));
+ } else {
+ /* use the last hop in the circuit */
+ tor_assert(circ->cpath);
+ tor_assert(circ->cpath->prev);
+ tor_assert(circ->cpath->prev->state == CPATH_STATE_OPEN);
+ cpath = circ->cpath->prev;
+ }
+ ENTRY_TO_EDGE_CONN(apconn)->cpath_layer = cpath;
+
+ circ->isolation_any_streams_attached = 1;
+ connection_edge_update_circuit_isolation(apconn, circ, 0);
+
+ /* Compute the exitnode if possible, for logging below */
+ if (cpath->extend_info)
+ exitnode = node_get_by_id(cpath->extend_info->identity_digest);
+
+ /* See if we can use optimistic data on this circuit */
+ if (optimistic_data_enabled() &&
+ (circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL ||
+ circ->base_.purpose == CIRCUIT_PURPOSE_C_HSDIR_GET ||
+ circ->base_.purpose == CIRCUIT_PURPOSE_S_HSDIR_POST ||
+ circ->base_.purpose == CIRCUIT_PURPOSE_C_REND_JOINED))
+ apconn->may_use_optimistic_data = 1;
+ else
+ apconn->may_use_optimistic_data = 0;
+ log_info(LD_APP, "Looks like completed circuit to %s %s allow "
+ "optimistic data for connection to %s",
+ circ->base_.purpose == CIRCUIT_PURPOSE_C_GENERAL ?
+ /* node_describe() does the right thing if exitnode is NULL */
+ safe_str_client(node_describe(exitnode)) :
+ "hidden service",
+ apconn->may_use_optimistic_data ? "does" : "doesn't",
+ safe_str_client(apconn->socks_request->address));
+}
+
+/** Return true iff <b>address</b> is matched by one of the entries in
+ * TrackHostExits. */
+int
+hostname_in_track_host_exits(const or_options_t *options, const char *address)
+{
+ if (!options->TrackHostExits)
+ return 0;
+ SMARTLIST_FOREACH_BEGIN(options->TrackHostExits, const char *, cp) {
+ if (cp[0] == '.') { /* match end */
+ if (cp[1] == '\0' ||
+ !strcasecmpend(address, cp) ||
+ !strcasecmp(address, &cp[1]))
+ return 1;
+ } else if (strcasecmp(cp, address) == 0) {
+ return 1;
+ }
+ } SMARTLIST_FOREACH_END(cp);
+ return 0;
+}
+
+/** If an exit wasn't explicitly specified for <b>conn</b>, consider saving
+ * the exit that we *did* choose for use by future connections to
+ * <b>conn</b>'s destination.
+ */
+static void
+consider_recording_trackhost(const entry_connection_t *conn,
+ const origin_circuit_t *circ)
+{
+ const or_options_t *options = get_options();
+ char *new_address = NULL;
+ char fp[HEX_DIGEST_LEN+1];
+
+ /* Search the addressmap for this conn's destination. */
+ /* If they're not in the address map.. */
+ if (!options->TrackHostExits ||
+ addressmap_have_mapping(conn->socks_request->address,
+ options->TrackHostExitsExpire))
+ return; /* nothing to track, or already mapped */
+
+ if (!hostname_in_track_host_exits(options, conn->socks_request->address) ||
+ !circ->build_state->chosen_exit)
+ return;
+
+ /* write down the fingerprint of the chosen exit, not the nickname,
+ * because the chosen exit might not be named. */
+ base16_encode(fp, sizeof(fp),
+ circ->build_state->chosen_exit->identity_digest, DIGEST_LEN);
+
+ /* Add this exit/hostname pair to the addressmap. */
+ tor_asprintf(&new_address, "%s.%s.exit",
+ conn->socks_request->address, fp);
+
+ addressmap_register(conn->socks_request->address, new_address,
+ time(NULL) + options->TrackHostExitsExpire,
+ ADDRMAPSRC_TRACKEXIT, 0, 0);
+}
+
+/** Attempt to attach the connection <b>conn</b> to <b>circ</b>, and send a
+ * begin or resolve cell as appropriate. Return values are as for
+ * connection_ap_handshake_attach_circuit. The stream will exit from the hop
+ * indicated by <b>cpath</b>, or from the last hop in circ's cpath if
+ * <b>cpath</b> is NULL. */
+int
+connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn,
+ origin_circuit_t *circ,
+ crypt_path_t *cpath)
+{
+ connection_t *base_conn = ENTRY_TO_CONN(conn);
+ tor_assert(conn);
+ tor_assert(base_conn->state == AP_CONN_STATE_CIRCUIT_WAIT ||
+ base_conn->state == AP_CONN_STATE_CONTROLLER_WAIT);
+ tor_assert(conn->socks_request);
+ tor_assert(circ);
+ tor_assert(circ->base_.state == CIRCUIT_STATE_OPEN);
+
+ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
+
+ if (!circ->base_.timestamp_dirty ||
+ ((conn->entry_cfg.isolation_flags & ISO_SOCKSAUTH) &&
+ (conn->entry_cfg.socks_iso_keep_alive) &&
+ (conn->socks_request->usernamelen ||
+ conn->socks_request->passwordlen))) {
+ /* When stream isolation is in use and controlled by an application
+ * we are willing to keep using the stream. */
+ circ->base_.timestamp_dirty = approx_time();
+ }
+
+ pathbias_count_use_attempt(circ);
+
+ /* Now, actually link the connection. */
+ link_apconn_to_circ(conn, circ, cpath);
+
+ tor_assert(conn->socks_request);
+ if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
+ if (!conn->use_begindir)
+ consider_recording_trackhost(conn, circ);
+ if (connection_ap_handshake_send_begin(conn) < 0)
+ return -1;
+ } else {
+ if (connection_ap_handshake_send_resolve(conn) < 0)
+ return -1;
+ }
+
+ return 1;
+}
+
+/**
+ * Return an appropriate circuit purpose for non-rend streams.
+ * We don't handle rends here because a rend stream triggers two
+ * circuit builds with different purposes, so it is handled elsewhere.
+ *
+ * This function just figures out what type of hsdir activity this is,
+ * and tells us. Everything else is general.
+ */
+static int
+connection_ap_get_nonrend_circ_purpose(const entry_connection_t *conn)
+{
+ const connection_t *base_conn = ENTRY_TO_CONN(conn);
+ tor_assert_nonfatal(!connection_edge_is_rendezvous_stream(
+ ENTRY_TO_EDGE_CONN(conn)));
+
+ if (base_conn->linked_conn &&
+ base_conn->linked_conn->type == CONN_TYPE_DIR) {
+ /* Set a custom purpose for hsdir activity */
+ if (base_conn->linked_conn->purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2 ||
+ base_conn->linked_conn->purpose == DIR_PURPOSE_UPLOAD_HSDESC) {
+ return CIRCUIT_PURPOSE_S_HSDIR_POST;
+ } else if (base_conn->linked_conn->purpose
+ == DIR_PURPOSE_FETCH_RENDDESC_V2 ||
+ base_conn->linked_conn->purpose
+ == DIR_PURPOSE_FETCH_HSDESC) {
+ return CIRCUIT_PURPOSE_C_HSDIR_GET;
+ }
+ }
+
+ /* All other purposes are general for now */
+ return CIRCUIT_PURPOSE_C_GENERAL;
+}
+
+/** Try to find a safe live circuit for stream <b>conn</b>. If we find one,
+ * attach the stream, send appropriate cells, and return 1. Otherwise,
+ * try to launch new circuit(s) for the stream. If we can launch
+ * circuits, return 0. Otherwise, if we simply can't proceed with
+ * this stream, return -1. (conn needs to die, and is maybe already marked).
+ */
+/* XXXX this function should mark for close whenever it returns -1;
+ * its callers shouldn't have to worry about that. */
+int
+connection_ap_handshake_attach_circuit(entry_connection_t *conn)
+{
+ connection_t *base_conn = ENTRY_TO_CONN(conn);
+ int retval;
+ int conn_age;
+ int want_onehop;
+
+ tor_assert(conn);
+ tor_assert(base_conn->state == AP_CONN_STATE_CIRCUIT_WAIT);
+ tor_assert(conn->socks_request);
+ want_onehop = conn->want_onehop;
+
+ conn_age = (int)(time(NULL) - base_conn->timestamp_created);
+
+ /* Is this connection so old that we should give up on it? */
+ if (conn_age >= get_options()->SocksTimeout) {
+ int severity = (tor_addr_is_null(&base_conn->addr) && !base_conn->port) ?
+ LOG_INFO : LOG_NOTICE;
+ log_fn(severity, LD_APP,
+ "Tried for %d seconds to get a connection to %s:%d. Giving up.",
+ conn_age, safe_str_client(conn->socks_request->address),
+ conn->socks_request->port);
+ return -1;
+ }
+
+ /* We handle "general" (non-onion) connections much more straightforwardly.
+ */
+ if (!connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn))) {
+ /* we're a general conn */
+ origin_circuit_t *circ=NULL;
+
+ /* Are we linked to a dir conn that aims to fetch a consensus?
+ * We check here because the conn might no longer be needed. */
+ if (base_conn->linked_conn &&
+ base_conn->linked_conn->type == CONN_TYPE_DIR &&
+ base_conn->linked_conn->purpose == DIR_PURPOSE_FETCH_CONSENSUS) {
+
+ /* Yes we are. Is there a consensus fetch farther along than us? */
+ if (networkstatus_consensus_is_already_downloading(
+ TO_DIR_CONN(base_conn->linked_conn)->requested_resource)) {
+ /* We're doing the "multiple consensus fetch attempts" game from
+ * proposal 210, and we're late to the party. Just close this conn.
+ * The circuit and TLS conn that we made will time out after a while
+ * if nothing else wants to use them. */
+ log_info(LD_DIR, "Closing extra consensus fetch (to %s) since one "
+ "is already downloading.", base_conn->linked_conn->address);
+ return -1;
+ }
+ }
+
+ /* If we have a chosen exit, we need to use a circuit that's
+ * open to that exit. See what exit we meant, and whether we can use it.
+ */
+ if (conn->chosen_exit_name) {
+ const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0);
+ int opt = conn->chosen_exit_optional;
+ if (!node && !want_onehop) {
+ /* We ran into this warning when trying to extend a circuit to a
+ * hidden service directory for which we didn't have a router
+ * descriptor. See flyspray task 767 for more details. We should
+ * keep this in mind when deciding to use BEGIN_DIR cells for other
+ * directory requests as well. -KL*/
+ log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
+ "Requested exit point '%s' is not known. %s.",
+ conn->chosen_exit_name, opt ? "Trying others" : "Closing");
+ if (opt) {
+ /* If we are allowed to ignore the .exit request, do so */
+ conn->chosen_exit_optional = 0;
+ tor_free(conn->chosen_exit_name);
+ return 0;
+ }
+ return -1;
+ }
+ if (node && !connection_ap_can_use_exit(conn, node)) {
+ log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP,
+ "Requested exit point '%s' is excluded or "
+ "would refuse request. %s.",
+ conn->chosen_exit_name, opt ? "Trying others" : "Closing");
+ if (opt) {
+ /* If we are allowed to ignore the .exit request, do so */
+ conn->chosen_exit_optional = 0;
+ tor_free(conn->chosen_exit_name);
+ return 0;
+ }
+ return -1;
+ }
+ }
+
+ /* Find the circuit that we should use, if there is one. Otherwise
+ * launch it
+ */
+ retval = circuit_get_open_circ_or_launch(conn,
+ connection_ap_get_nonrend_circ_purpose(conn),
+ &circ);
+
+ if (retval < 1) {
+ /* We were either told "-1" (complete failure) or 0 (circuit in
+ * progress); we can't attach this stream yet. */
+ return retval;
+ }
+
+ log_debug(LD_APP|LD_CIRC,
+ "Attaching apconn to circ %u (stream %d sec old).",
+ (unsigned)circ->base_.n_circ_id, conn_age);
+ /* print the circ's path, so clients can figure out which circs are
+ * sucking. */
+ circuit_log_path(LOG_INFO,LD_APP|LD_CIRC,circ);
+
+ /* We have found a suitable circuit for our conn. Hurray. Do
+ * the attachment. */
+ return connection_ap_handshake_attach_chosen_circuit(conn, circ, NULL);
+
+ } else { /* we're a rendezvous conn */
+ origin_circuit_t *rendcirc=NULL, *introcirc=NULL;
+
+ tor_assert(!ENTRY_TO_EDGE_CONN(conn)->cpath_layer);
+
+ /* start by finding a rendezvous circuit for us */
+
+ retval = circuit_get_open_circ_or_launch(
+ conn, CIRCUIT_PURPOSE_C_REND_JOINED, &rendcirc);
+ if (retval < 0) return -1; /* failed */
+
+ if (retval > 0) {
+ tor_assert(rendcirc);
+ /* one is already established, attach */
+ log_info(LD_REND,
+ "rend joined circ %u (id: %" PRIu32 ") already here. "
+ "Attaching. (stream %d sec old)",
+ (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id,
+ rendcirc->global_identifier, conn_age);
+ /* Mark rendezvous circuits as 'newly dirty' every time you use
+ * them, since the process of rebuilding a rendezvous circ is so
+ * expensive. There is a tradeoff between linkability and
+ * feasibility, at this point.
+ */
+ rendcirc->base_.timestamp_dirty = time(NULL);
+
+ /* We've also attempted to use them. If they fail, we need to
+ * probe them for path bias */
+ pathbias_count_use_attempt(rendcirc);
+
+ link_apconn_to_circ(conn, rendcirc, NULL);
+ if (connection_ap_handshake_send_begin(conn) < 0)
+ return 0; /* already marked, let them fade away */
+ return 1;
+ }
+
+ /* At this point we need to re-check the state, since it's possible that
+ * our call to circuit_get_open_circ_or_launch() changed the connection's
+ * state from "CIRCUIT_WAIT" to "RENDDESC_WAIT" because we decided to
+ * re-fetch the descriptor.
+ */
+ if (ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_CIRCUIT_WAIT) {
+ log_info(LD_REND, "This connection is no longer ready to attach; its "
+ "state changed."
+ "(We probably have to re-fetch its descriptor.)");
+ return 0;
+ }
+
+ if (rendcirc && (rendcirc->base_.purpose ==
+ CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED)) {
+ log_info(LD_REND,
+ "pending-join circ %u (id: %" PRIu32 ") already here, with "
+ "intro ack. Stalling. (stream %d sec old)",
+ (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id,
+ rendcirc->global_identifier, conn_age);
+ return 0;
+ }
+
+ /* it's on its way. find an intro circ. */
+ retval = circuit_get_open_circ_or_launch(
+ conn, CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT, &introcirc);
+ if (retval < 0) return -1; /* failed */
+
+ if (retval > 0) {
+ /* one has already sent the intro. keep waiting. */
+ tor_assert(introcirc);
+ log_info(LD_REND, "Intro circ %u (id: %" PRIu32 ") present and "
+ "awaiting ACK. Rend circuit %u (id: %" PRIu32 "). "
+ "Stalling. (stream %d sec old)",
+ (unsigned) TO_CIRCUIT(introcirc)->n_circ_id,
+ introcirc->global_identifier,
+ rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0,
+ rendcirc ? rendcirc->global_identifier : 0,
+ conn_age);
+ return 0;
+ }
+
+ /* now rendcirc and introcirc are each either undefined or not finished */
+
+ if (rendcirc && introcirc &&
+ rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY) {
+ log_info(LD_REND,
+ "ready rend circ %u (id: %" PRIu32 ") already here. No"
+ "intro-ack yet on intro %u (id: %" PRIu32 "). "
+ "(stream %d sec old)",
+ (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id,
+ rendcirc->global_identifier,
+ (unsigned) TO_CIRCUIT(introcirc)->n_circ_id,
+ introcirc->global_identifier, conn_age);
+
+ tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING);
+ if (introcirc->base_.state == CIRCUIT_STATE_OPEN) {
+ int ret;
+ log_info(LD_REND, "Found open intro circ %u (id: %" PRIu32 "). "
+ "Rend circuit %u (id: %" PRIu32 "); Sending "
+ "introduction. (stream %d sec old)",
+ (unsigned) TO_CIRCUIT(introcirc)->n_circ_id,
+ introcirc->global_identifier,
+ (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id,
+ rendcirc->global_identifier, conn_age);
+ ret = hs_client_send_introduce1(introcirc, rendcirc);
+ switch (ret) {
+ case 0: /* success */
+ rendcirc->base_.timestamp_dirty = time(NULL);
+ introcirc->base_.timestamp_dirty = time(NULL);
+
+ pathbias_count_use_attempt(introcirc);
+ pathbias_count_use_attempt(rendcirc);
+
+ assert_circuit_ok(TO_CIRCUIT(rendcirc));
+ assert_circuit_ok(TO_CIRCUIT(introcirc));
+ return 0;
+ case -1: /* transient error */
+ return 0;
+ case -2: /* permanent error */
+ return -1;
+ default: /* oops */
+ tor_fragile_assert();
+ return -1;
+ }
+ }
+ }
+
+ log_info(LD_REND, "Intro %u (id: %" PRIu32 ") and rend circuit %u "
+ "(id: %" PRIu32 ") circuits are not both ready. "
+ "Stalling conn. (%d sec old)",
+ introcirc ? (unsigned) TO_CIRCUIT(introcirc)->n_circ_id : 0,
+ introcirc ? introcirc->global_identifier : 0,
+ rendcirc ? (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id : 0,
+ rendcirc ? rendcirc->global_identifier : 0, conn_age);
+ return 0;
+ }
+}
+
+/** Change <b>circ</b>'s purpose to <b>new_purpose</b>. */
+void
+circuit_change_purpose(circuit_t *circ, uint8_t new_purpose)
+{
+ uint8_t old_purpose;
+ /* Don't allow an OR circ to become an origin circ or vice versa. */
+ tor_assert(!!(CIRCUIT_IS_ORIGIN(circ)) ==
+ !!(CIRCUIT_PURPOSE_IS_ORIGIN(new_purpose)));
+
+ if (circ->purpose == new_purpose) return;
+
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ char old_purpose_desc[80] = "";
+
+ strncpy(old_purpose_desc, circuit_purpose_to_string(circ->purpose), 80-1);
+ old_purpose_desc[80-1] = '\0';
+
+ log_debug(LD_CIRC,
+ "changing purpose of origin circ %d "
+ "from \"%s\" (%d) to \"%s\" (%d)",
+ TO_ORIGIN_CIRCUIT(circ)->global_identifier,
+ old_purpose_desc,
+ circ->purpose,
+ circuit_purpose_to_string(new_purpose),
+ new_purpose);
+ }
+
+ old_purpose = circ->purpose;
+ circ->purpose = new_purpose;
+
+ if (CIRCUIT_IS_ORIGIN(circ)) {
+ control_event_circuit_purpose_changed(TO_ORIGIN_CIRCUIT(circ),
+ old_purpose);
+ }
+}
+
+/** Mark <b>circ</b> so that no more connections can be attached to it. */
+void
+mark_circuit_unusable_for_new_conns(origin_circuit_t *circ)
+{
+ const or_options_t *options = get_options();
+ tor_assert(circ);
+
+ /* XXXX This is a kludge; we're only keeping it around in case there's
+ * something that doesn't check unusable_for_new_conns, and to avoid
+ * deeper refactoring of our expiration logic. */
+ if (! circ->base_.timestamp_dirty)
+ circ->base_.timestamp_dirty = approx_time();
+ if (options->MaxCircuitDirtiness >= circ->base_.timestamp_dirty)
+ circ->base_.timestamp_dirty = 1; /* prevent underflow */
+ else
+ circ->base_.timestamp_dirty -= options->MaxCircuitDirtiness;
+
+ circ->unusable_for_new_conns = 1;
+}
+
+/**
+ * Add relay_body_len and RELAY_PAYLOAD_SIZE-relay_body_len to
+ * the valid delivered written fields and the overhead field,
+ * respectively.
+ */
+void
+circuit_sent_valid_data(origin_circuit_t *circ, uint16_t relay_body_len)
+{
+ if (!circ) return;
+
+ tor_assert_nonfatal(relay_body_len <= RELAY_PAYLOAD_SIZE);
+
+ circ->n_delivered_written_circ_bw =
+ tor_add_u32_nowrap(circ->n_delivered_written_circ_bw, relay_body_len);
+ circ->n_overhead_written_circ_bw =
+ tor_add_u32_nowrap(circ->n_overhead_written_circ_bw,
+ RELAY_PAYLOAD_SIZE-relay_body_len);
+}
+
+/**
+ * Add relay_body_len and RELAY_PAYLOAD_SIZE-relay_body_len to
+ * the valid delivered read field and the overhead field,
+ * respectively.
+ */
+void
+circuit_read_valid_data(origin_circuit_t *circ, uint16_t relay_body_len)
+{
+ if (!circ) return;
+
+ tor_assert_nonfatal(relay_body_len <= RELAY_PAYLOAD_SIZE);
+
+ circ->n_delivered_read_circ_bw =
+ tor_add_u32_nowrap(circ->n_delivered_read_circ_bw, relay_body_len);
+ circ->n_overhead_read_circ_bw =
+ tor_add_u32_nowrap(circ->n_overhead_read_circ_bw,
+ RELAY_PAYLOAD_SIZE-relay_body_len);
+}
diff --cc src/feature/relay/router.c
index b376046c8,000000000..dad2c6a50
mode 100644,000000..100644
--- a/src/feature/relay/router.c
+++ b/src/feature/relay/router.c
@@@ -1,3129 -1,0 +1,3128 @@@
+/* 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 */
+
+#define ROUTER_PRIVATE
+
+#include "core/or/or.h"
+#include "app/config/config.h"
+#include "app/config/statefile.h"
+#include "app/main/main.h"
+#include "core/mainloop/connection.h"
+#include "core/mainloop/mainloop.h"
+#include "core/mainloop/netstatus.h"
+#include "core/or/policies.h"
+#include "core/or/protover.h"
+#include "feature/client/transports.h"
+#include "feature/control/control.h"
+#include "feature/dirauth/process_descs.h"
+#include "feature/dircache/dirserv.h"
+#include "feature/dirclient/dirclient.h"
+#include "feature/dircommon/directory.h"
+#include "feature/dirparse/authcert_parse.h"
+#include "feature/dirparse/routerparse.h"
+#include "feature/dirparse/signing.h"
+#include "feature/hibernate/hibernate.h"
+#include "feature/keymgt/loadkey.h"
+#include "feature/nodelist/authcert.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/torcert.h"
+#include "feature/relay/dns.h"
+#include "feature/relay/router.h"
+#include "feature/relay/routerkeys.h"
+#include "feature/relay/routermode.h"
+#include "feature/relay/selftest.h"
+#include "lib/geoip/geoip.h"
+#include "feature/stats/geoip_stats.h"
+#include "feature/stats/rephist.h"
+#include "lib/crypt_ops/crypto_ed25519.h"
+#include "lib/crypt_ops/crypto_format.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/osinfo/uname.h"
+#include "lib/tls/tortls.h"
+
+#include "feature/dirauth/authmode.h"
+
+#include "app/config/or_state_st.h"
+#include "core/or/port_cfg_st.h"
+#include "feature/dirclient/dir_server_st.h"
+#include "feature/dircommon/dir_connection_st.h"
+#include "feature/nodelist/authority_cert_st.h"
+#include "feature/nodelist/extrainfo_st.h"
+#include "feature/nodelist/node_st.h"
+#include "feature/nodelist/routerinfo_st.h"
+#include "feature/nodelist/routerstatus_st.h"
+
+/**
+ * \file router.c
+ * \brief Miscellaneous relay functionality, including RSA key maintenance,
+ * generating and uploading server descriptors, picking an address to
+ * advertise, and so on.
+ *
+ * This module handles the job of deciding whether we are a Tor relay, and if
+ * so what kind. (Mostly through functions like server_mode() that inspect an
+ * or_options_t, but in some cases based on our own capabilities, such as when
+ * we are deciding whether to be a directory cache in
+ * router_has_bandwidth_to_be_dirserver().)
+ *
+ * Also in this module are the functions to generate our own routerinfo_t and
+ * extrainfo_t, and to encode those to signed strings for upload to the
+ * directory authorities.
+ *
+ * This module also handles key maintenance for RSA and Curve25519-ntor keys,
+ * and for our TLS context. (These functions should eventually move to
+ * routerkeys.c along with the code that handles Ed25519 keys now.)
+ **/
+
+/************************************************************/
+
+/*****
+ * Key management: ORs only.
+ *****/
+
+/** Private keys for this OR. There is also an SSL key managed by tortls.c.
+ */
+static tor_mutex_t *key_lock=NULL;
+static time_t onionkey_set_at=0; /**< When was onionkey last changed? */
+/** Current private onionskin decryption key: used to decode CREATE cells. */
+static crypto_pk_t *onionkey=NULL;
+/** Previous private onionskin decryption key: used to decode CREATE cells
+ * generated by clients that have an older version of our descriptor. */
+static crypto_pk_t *lastonionkey=NULL;
+/** Current private ntor secret key: used to perform the ntor handshake. */
+static curve25519_keypair_t curve25519_onion_key;
+/** Previous private ntor secret key: used to perform the ntor handshake
+ * with clients that have an older version of our descriptor. */
+static curve25519_keypair_t last_curve25519_onion_key;
+/** Private server "identity key": used to sign directory info and TLS
+ * certificates. Never changes. */
+static crypto_pk_t *server_identitykey=NULL;
+/** Digest of server_identitykey. */
+static char server_identitykey_digest[DIGEST_LEN];
+/** Private client "identity key": used to sign bridges' and clients'
+ * outbound TLS certificates. Regenerated on startup and on IP address
+ * change. */
+static crypto_pk_t *client_identitykey=NULL;
+/** Signing key used for v3 directory material; only set for authorities. */
+static crypto_pk_t *authority_signing_key = NULL;
+/** Key certificate to authenticate v3 directory material; only set for
+ * authorities. */
+static authority_cert_t *authority_key_certificate = NULL;
+
+/** For emergency V3 authority key migration: An extra signing key that we use
+ * with our old (obsolete) identity key for a while. */
+static crypto_pk_t *legacy_signing_key = NULL;
+/** For emergency V3 authority key migration: An extra certificate to
+ * authenticate legacy_signing_key with our obsolete identity key.*/
+static authority_cert_t *legacy_key_certificate = NULL;
+
+/* (Note that v3 authorities also have a separate "authority identity key",
+ * but this key is never actually loaded by the Tor process. Instead, it's
+ * used by tor-gencert to sign new signing keys and make new key
+ * certificates. */
+
+/** Return a readonly string with human readable description
+ * of <b>err</b>.
+ */
+const char *
+routerinfo_err_to_string(int err)
+{
+ switch (err) {
+ case TOR_ROUTERINFO_ERROR_NO_EXT_ADDR:
+ return "No known exit address yet";
+ case TOR_ROUTERINFO_ERROR_CANNOT_PARSE:
+ return "Cannot parse descriptor";
+ case TOR_ROUTERINFO_ERROR_NOT_A_SERVER:
+ return "Not running in server mode";
+ case TOR_ROUTERINFO_ERROR_DIGEST_FAILED:
+ return "Key digest failed";
+ case TOR_ROUTERINFO_ERROR_CANNOT_GENERATE:
+ return "Cannot generate descriptor";
+ case TOR_ROUTERINFO_ERROR_DESC_REBUILDING:
+ return "Descriptor still rebuilding - not ready yet";
+ }
+
+ log_warn(LD_BUG, "unknown routerinfo error %d - shouldn't happen", err);
+ tor_assert_unreached();
+
+ return "Unknown error";
+}
+
+/** Return true if we expect given error to be transient.
+ * Return false otherwise.
+ */
+int
+routerinfo_err_is_transient(int err)
+{
+ /**
+ * For simplicity, we consider all errors other than
+ * "not a server" transient - see discussion on
+ * https://trac.torproject.org/projects/tor/ticket/27034
+ */
+ return err != TOR_ROUTERINFO_ERROR_NOT_A_SERVER;
+}
+
+/** Replace the current onion key with <b>k</b>. Does not affect
+ * lastonionkey; to update lastonionkey correctly, call rotate_onion_key().
+ */
+static void
+set_onion_key(crypto_pk_t *k)
+{
+ if (onionkey && crypto_pk_eq_keys(onionkey, k)) {
+ /* k is already our onion key; free it and return */
+ crypto_pk_free(k);
+ return;
+ }
+ tor_mutex_acquire(key_lock);
+ crypto_pk_free(onionkey);
+ onionkey = k;
+ tor_mutex_release(key_lock);
+ mark_my_descriptor_dirty("set onion key");
+}
+
+/** Return the current onion key. Requires that the onion key has been
+ * loaded or generated. */
+crypto_pk_t *
+get_onion_key(void)
+{
+ tor_assert(onionkey);
+ return onionkey;
+}
+
+/** Store a full copy of the current onion key into *<b>key</b>, and a full
+ * copy of the most recent onion key into *<b>last</b>. Store NULL into
+ * a pointer if the corresponding key does not exist.
+ */
+void
+dup_onion_keys(crypto_pk_t **key, crypto_pk_t **last)
+{
+ tor_assert(key);
+ tor_assert(last);
+ tor_mutex_acquire(key_lock);
+ if (onionkey)
+ *key = crypto_pk_copy_full(onionkey);
+ else
+ *key = NULL;
+ if (lastonionkey)
+ *last = crypto_pk_copy_full(lastonionkey);
+ else
+ *last = NULL;
+ tor_mutex_release(key_lock);
+}
+
+/** Expire our old set of onion keys. This is done by setting
+ * last_curve25519_onion_key and lastonionkey to all zero's and NULL
+ * respectively.
+ *
+ * This function does not perform any grace period checks for the old onion
+ * keys.
+ */
+void
+expire_old_onion_keys(void)
+{
+ char *fname = NULL;
+
+ tor_mutex_acquire(key_lock);
+
+ /* Free lastonionkey and set it to NULL. */
+ if (lastonionkey) {
+ crypto_pk_free(lastonionkey);
+ lastonionkey = NULL;
+ }
+
+ /* We zero out the keypair. See the tor_mem_is_zero() check made in
+ * construct_ntor_key_map() below. */
+ memset(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key));
+
+ tor_mutex_release(key_lock);
+
+ fname = get_keydir_fname("secret_onion_key.old");
+ if (file_status(fname) == FN_FILE) {
+ if (tor_unlink(fname) != 0) {
+ log_warn(LD_FS, "Couldn't unlink old onion key file %s: %s",
+ fname, strerror(errno));
+ }
+ }
+ tor_free(fname);
+
+ fname = get_keydir_fname("secret_onion_key_ntor.old");
+ if (file_status(fname) == FN_FILE) {
+ if (tor_unlink(fname) != 0) {
+ log_warn(LD_FS, "Couldn't unlink old ntor onion key file %s: %s",
+ fname, strerror(errno));
+ }
+ }
+ tor_free(fname);
+}
+
+/** Return the current secret onion key for the ntor handshake. Must only
+ * be called from the main thread. */
+static const curve25519_keypair_t *
+get_current_curve25519_keypair(void)
+{
+ return &curve25519_onion_key;
+}
+/** Return a map from KEYID (the key itself) to keypairs for use in the ntor
+ * handshake. Must only be called from the main thread. */
+di_digest256_map_t *
+construct_ntor_key_map(void)
+{
+ di_digest256_map_t *m = NULL;
+
+ if (!tor_mem_is_zero((const char*)
+ curve25519_onion_key.pubkey.public_key,
+ CURVE25519_PUBKEY_LEN)) {
+ dimap_add_entry(&m,
+ curve25519_onion_key.pubkey.public_key,
+ tor_memdup(&curve25519_onion_key,
+ sizeof(curve25519_keypair_t)));
+ }
+ if (!tor_mem_is_zero((const char*)
+ last_curve25519_onion_key.pubkey.public_key,
+ CURVE25519_PUBKEY_LEN)) {
+ dimap_add_entry(&m,
+ last_curve25519_onion_key.pubkey.public_key,
+ tor_memdup(&last_curve25519_onion_key,
+ sizeof(curve25519_keypair_t)));
+ }
+
+ return m;
+}
+/** Helper used to deallocate a di_digest256_map_t returned by
+ * construct_ntor_key_map. */
+static void
+ntor_key_map_free_helper(void *arg)
+{
+ curve25519_keypair_t *k = arg;
+ memwipe(k, 0, sizeof(*k));
+ tor_free(k);
+}
+/** Release all storage from a keymap returned by construct_ntor_key_map. */
+void
+ntor_key_map_free_(di_digest256_map_t *map)
+{
+ if (!map)
+ return;
+ dimap_free(map, ntor_key_map_free_helper);
+}
+
+/** Return the time when the onion key was last set. This is either the time
+ * when the process launched, or the time of the most recent key rotation since
+ * the process launched.
+ */
+time_t
+get_onion_key_set_at(void)
+{
+ return onionkey_set_at;
+}
+
+/** Set the current server identity key to <b>k</b>.
+ */
+void
+set_server_identity_key(crypto_pk_t *k)
+{
+ crypto_pk_free(server_identitykey);
+ server_identitykey = k;
+ if (crypto_pk_get_digest(server_identitykey,
+ server_identitykey_digest) < 0) {
+ log_err(LD_BUG, "Couldn't compute our own identity key digest.");
+ tor_assert(0);
+ }
+}
+
+/** Make sure that we have set up our identity keys to match or not match as
+ * appropriate, and die with an assertion if we have not. */
+static void
+assert_identity_keys_ok(void)
+{
+ if (1)
+ return;
+ tor_assert(client_identitykey);
+ if (public_server_mode(get_options())) {
+ /* assert that we have set the client and server keys to be equal */
+ tor_assert(server_identitykey);
+ tor_assert(crypto_pk_eq_keys(client_identitykey, server_identitykey));
+ } else {
+ /* assert that we have set the client and server keys to be unequal */
+ if (server_identitykey)
+ tor_assert(!crypto_pk_eq_keys(client_identitykey, server_identitykey));
+ }
+}
+
+/** Returns the current server identity key; requires that the key has
+ * been set, and that we are running as a Tor server.
+ */
+crypto_pk_t *
+get_server_identity_key(void)
+{
+ tor_assert(server_identitykey);
+ tor_assert(server_mode(get_options()));
+ assert_identity_keys_ok();
+ return server_identitykey;
+}
+
+/** Return true iff we are a server and the server identity key
+ * has been set. */
+int
+server_identity_key_is_set(void)
+{
+ return server_mode(get_options()) && server_identitykey != NULL;
+}
+
+/** Set the current client identity key to <b>k</b>.
+ */
+void
+set_client_identity_key(crypto_pk_t *k)
+{
+ crypto_pk_free(client_identitykey);
+ client_identitykey = k;
+}
+
+/** Returns the current client identity key for use on outgoing TLS
+ * connections; requires that the key has been set.
+ */
+crypto_pk_t *
+get_tlsclient_identity_key(void)
+{
+ tor_assert(client_identitykey);
+ assert_identity_keys_ok();
+ return client_identitykey;
+}
+
+/** Return true iff the client identity key has been set. */
+int
+client_identity_key_is_set(void)
+{
+ return client_identitykey != NULL;
+}
+
+/** Return the key certificate for this v3 (voting) authority, or NULL
+ * if we have no such certificate. */
+MOCK_IMPL(authority_cert_t *,
+get_my_v3_authority_cert, (void))
+{
+ return authority_key_certificate;
+}
+
+/** Return the v3 signing key for this v3 (voting) authority, or NULL
+ * if we have no such key. */
+crypto_pk_t *
+get_my_v3_authority_signing_key(void)
+{
+ return authority_signing_key;
+}
+
+/** If we're an authority, and we're using a legacy authority identity key for
+ * emergency migration purposes, return the certificate associated with that
+ * key. */
+authority_cert_t *
+get_my_v3_legacy_cert(void)
+{
+ return legacy_key_certificate;
+}
+
+/** If we're an authority, and we're using a legacy authority identity key for
+ * emergency migration purposes, return that key. */
+crypto_pk_t *
+get_my_v3_legacy_signing_key(void)
+{
+ return legacy_signing_key;
+}
+
+/** Replace the previous onion key with the current onion key, and generate
+ * a new previous onion key. Immediately after calling this function,
+ * the OR should:
+ * - schedule all previous cpuworkers to shut down _after_ processing
+ * pending work. (This will cause fresh cpuworkers to be generated.)
+ * - generate and upload a fresh routerinfo.
+ */
+void
+rotate_onion_key(void)
+{
+ char *fname, *fname_prev;
+ crypto_pk_t *prkey = NULL;
+ or_state_t *state = get_or_state();
+ curve25519_keypair_t new_curve25519_keypair;
+ time_t now;
+ fname = get_keydir_fname("secret_onion_key");
+ fname_prev = get_keydir_fname("secret_onion_key.old");
+ /* There isn't much point replacing an old key with an empty file */
+ if (file_status(fname) == FN_FILE) {
+ if (replace_file(fname, fname_prev))
+ goto error;
+ }
+ if (!(prkey = crypto_pk_new())) {
+ log_err(LD_GENERAL,"Error constructing rotated onion key");
+ goto error;
+ }
+ if (crypto_pk_generate_key(prkey)) {
+ log_err(LD_BUG,"Error generating onion key");
+ goto error;
+ }
+ if (crypto_pk_write_private_key_to_filename(prkey, fname)) {
+ log_err(LD_FS,"Couldn't write generated onion key to \"%s\".", fname);
+ goto error;
+ }
+ tor_free(fname);
+ tor_free(fname_prev);
+ fname = get_keydir_fname("secret_onion_key_ntor");
+ fname_prev = get_keydir_fname("secret_onion_key_ntor.old");
+ if (curve25519_keypair_generate(&new_curve25519_keypair, 1) < 0)
+ goto error;
+ /* There isn't much point replacing an old key with an empty file */
+ if (file_status(fname) == FN_FILE) {
+ if (replace_file(fname, fname_prev))
+ goto error;
+ }
+ if (curve25519_keypair_write_to_file(&new_curve25519_keypair, fname,
+ "onion") < 0) {
+ log_err(LD_FS,"Couldn't write curve25519 onion key to \"%s\".",fname);
+ goto error;
+ }
+ log_info(LD_GENERAL, "Rotating onion key");
+ tor_mutex_acquire(key_lock);
+ crypto_pk_free(lastonionkey);
+ lastonionkey = onionkey;
+ onionkey = prkey;
+ memcpy(&last_curve25519_onion_key, &curve25519_onion_key,
+ sizeof(curve25519_keypair_t));
+ memcpy(&curve25519_onion_key, &new_curve25519_keypair,
+ sizeof(curve25519_keypair_t));
+ now = time(NULL);
+ state->LastRotatedOnionKey = onionkey_set_at = now;
+ tor_mutex_release(key_lock);
+ mark_my_descriptor_dirty("rotated onion key");
+ or_state_mark_dirty(state, get_options()->AvoidDiskWrites ? now+3600 : 0);
+ goto done;
+ error:
+ log_warn(LD_GENERAL, "Couldn't rotate onion key.");
+ if (prkey)
+ crypto_pk_free(prkey);
+ done:
+ memwipe(&new_curve25519_keypair, 0, sizeof(new_curve25519_keypair));
+ tor_free(fname);
+ tor_free(fname_prev);
+}
+
+/** Log greeting message that points to new relay lifecycle document the
+ * first time this function has been called.
+ */
+static void
+log_new_relay_greeting(void)
+{
+ static int already_logged = 0;
+
+ if (already_logged)
+ return;
+
+ tor_log(LOG_NOTICE, LD_GENERAL, "You are running a new relay. "
+ "Thanks for helping the Tor network! If you wish to know "
+ "what will happen in the upcoming weeks regarding its usage, "
+ "have a look at https://blog.torproject.org/blog/lifecycle-of"
+ "-a-new-relay");
+
+ already_logged = 1;
+}
+
+/** Load a curve25519 keypair from the file <b>fname</b>, writing it into
+ * <b>keys_out</b>. If the file isn't found, or is empty, and <b>generate</b>
+ * is true, create a new keypair and write it into the file. If there are
+ * errors, log them at level <b>severity</b>. Generate files using <b>tag</b>
+ * in their ASCII wrapper. */
+static int
+init_curve25519_keypair_from_file(curve25519_keypair_t *keys_out,
+ const char *fname,
+ int generate,
+ int severity,
+ const char *tag)
+{
+ switch (file_status(fname)) {
+ case FN_DIR:
+ case FN_ERROR:
+ tor_log(severity, LD_FS,"Can't read key from \"%s\"", fname);
+ goto error;
+ /* treat empty key files as if the file doesn't exist, and, if generate
+ * is set, replace the empty file in curve25519_keypair_write_to_file() */
+ case FN_NOENT:
+ case FN_EMPTY:
+ if (generate) {
+ if (!have_lockfile()) {
+ if (try_locking(get_options(), 0)<0) {
+ /* Make sure that --list-fingerprint only creates new keys
+ * if there is no possibility for a deadlock. */
+ tor_log(severity, LD_FS, "Another Tor process has locked \"%s\". "
+ "Not writing any new keys.", fname);
+ /*XXXX The 'other process' might make a key in a second or two;
+ * maybe we should wait for it. */
+ goto error;
+ }
+ }
+ log_info(LD_GENERAL, "No key found in \"%s\"; generating fresh key.",
+ fname);
+ if (curve25519_keypair_generate(keys_out, 1) < 0)
+ goto error;
+ if (curve25519_keypair_write_to_file(keys_out, fname, tag)<0) {
+ tor_log(severity, LD_FS,
+ "Couldn't write generated key to \"%s\".", fname);
+ memwipe(keys_out, 0, sizeof(*keys_out));
+ goto error;
+ }
+ } else {
+ log_info(LD_GENERAL, "No key found in \"%s\"", fname);
+ }
+ return 0;
+ case FN_FILE:
+ {
+ char *tag_in=NULL;
+ if (curve25519_keypair_read_from_file(keys_out, &tag_in, fname) < 0) {
+ tor_log(severity, LD_GENERAL,"Error loading private key.");
+ tor_free(tag_in);
+ goto error;
+ }
+ if (!tag_in || strcmp(tag_in, tag)) {
+ tor_log(severity, LD_GENERAL,"Unexpected tag %s on private key.",
+ escaped(tag_in));
+ tor_free(tag_in);
+ goto error;
+ }
+ tor_free(tag_in);
+ return 0;
+ }
+ default:
+ tor_assert(0);
+ }
+
+ error:
+ return -1;
+}
+
+/** Try to load the vote-signing private key and certificate for being a v3
+ * directory authority, and make sure they match. If <b>legacy</b>, load a
+ * legacy key/cert set for emergency key migration; otherwise load the regular
+ * key/cert set. On success, store them into *<b>key_out</b> and
+ * *<b>cert_out</b> respectively, and return 0. On failure, return -1. */
+static int
+load_authority_keyset(int legacy, crypto_pk_t **key_out,
+ authority_cert_t **cert_out)
+{
+ int r = -1;
+ char *fname = NULL, *cert = NULL;
+ const char *eos = NULL;
+ crypto_pk_t *signing_key = NULL;
+ authority_cert_t *parsed = NULL;
+
+ fname = get_keydir_fname(
+ legacy ? "legacy_signing_key" : "authority_signing_key");
+ signing_key = init_key_from_file(fname, 0, LOG_ERR, NULL);
+ if (!signing_key) {
+ log_warn(LD_DIR, "No version 3 directory key found in %s", fname);
+ goto done;
+ }
+ tor_free(fname);
+ fname = get_keydir_fname(
+ legacy ? "legacy_certificate" : "authority_certificate");
+ cert = read_file_to_str(fname, 0, NULL);
+ if (!cert) {
+ log_warn(LD_DIR, "Signing key found, but no certificate found in %s",
+ fname);
+ goto done;
+ }
+ parsed = authority_cert_parse_from_string(cert, &eos);
+ if (!parsed) {
+ log_warn(LD_DIR, "Unable to parse certificate in %s", fname);
+ goto done;
+ }
+ if (!crypto_pk_eq_keys(signing_key, parsed->signing_key)) {
+ log_warn(LD_DIR, "Stored signing key does not match signing key in "
+ "certificate");
+ goto done;
+ }
+
+ crypto_pk_free(*key_out);
+ authority_cert_free(*cert_out);
+
+ *key_out = signing_key;
+ *cert_out = parsed;
+ r = 0;
+ signing_key = NULL;
+ parsed = NULL;
+
+ done:
+ tor_free(fname);
+ tor_free(cert);
+ crypto_pk_free(signing_key);
+ authority_cert_free(parsed);
+ return r;
+}
+
+/** Load the v3 (voting) authority signing key and certificate, if they are
+ * present. Return -1 if anything is missing, mismatched, or unloadable;
+ * return 0 on success. */
+static int
+init_v3_authority_keys(void)
+{
+ if (load_authority_keyset(0, &authority_signing_key,
+ &authority_key_certificate)<0)
+ return -1;
+
+ if (get_options()->V3AuthUseLegacyKey &&
+ load_authority_keyset(1, &legacy_signing_key,
+ &legacy_key_certificate)<0)
+ return -1;
+
+ return 0;
+}
+
+/** If we're a v3 authority, check whether we have a certificate that's
+ * likely to expire soon. Warn if we do, but not too often. */
+void
+v3_authority_check_key_expiry(void)
+{
+ time_t now, expires;
+ static time_t last_warned = 0;
+ int badness, time_left, warn_interval;
+ if (!authdir_mode_v3(get_options()) || !authority_key_certificate)
+ return;
+
+ now = time(NULL);
+ expires = authority_key_certificate->expires;
+ time_left = (int)( expires - now );
+ if (time_left <= 0) {
+ badness = LOG_ERR;
+ warn_interval = 60*60;
+ } else if (time_left <= 24*60*60) {
+ badness = LOG_WARN;
+ warn_interval = 60*60;
+ } else if (time_left <= 24*60*60*7) {
+ badness = LOG_WARN;
+ warn_interval = 24*60*60;
+ } else if (time_left <= 24*60*60*30) {
+ badness = LOG_WARN;
+ warn_interval = 24*60*60*5;
+ } else {
+ return;
+ }
+
+ if (last_warned + warn_interval > now)
+ return;
+
+ if (time_left <= 0) {
+ tor_log(badness, LD_DIR, "Your v3 authority certificate has expired."
+ " Generate a new one NOW.");
+ } else if (time_left <= 24*60*60) {
+ tor_log(badness, LD_DIR, "Your v3 authority certificate expires in %d "
+ "hours; Generate a new one NOW.", time_left/(60*60));
+ } else {
+ tor_log(badness, LD_DIR, "Your v3 authority certificate expires in %d "
+ "days; Generate a new one soon.", time_left/(24*60*60));
+ }
+ last_warned = now;
+}
+
+/** Get the lifetime of an onion key in days. This value is defined by the
+ * network consesus parameter "onion-key-rotation-days". Always returns a value
+ * between <b>MIN_ONION_KEY_LIFETIME_DAYS</b> and
+ * <b>MAX_ONION_KEY_LIFETIME_DAYS</b>.
+ */
+static int
+get_onion_key_rotation_days_(void)
+{
+ return networkstatus_get_param(NULL,
+ "onion-key-rotation-days",
+ DEFAULT_ONION_KEY_LIFETIME_DAYS,
+ MIN_ONION_KEY_LIFETIME_DAYS,
+ MAX_ONION_KEY_LIFETIME_DAYS);
+}
+
+/** Get the current lifetime of an onion key in seconds. This value is defined
+ * by the network consesus parameter "onion-key-rotation-days", but the value
+ * is converted to seconds.
+ */
+int
+get_onion_key_lifetime(void)
+{
+ return get_onion_key_rotation_days_()*24*60*60;
+}
+
+/** Get the grace period of an onion key in seconds. This value is defined by
+ * the network consesus parameter "onion-key-grace-period-days", but the value
+ * is converted to seconds.
+ */
+int
+get_onion_key_grace_period(void)
+{
+ int grace_period;
+ grace_period = networkstatus_get_param(NULL,
+ "onion-key-grace-period-days",
+ DEFAULT_ONION_KEY_GRACE_PERIOD_DAYS,
+ MIN_ONION_KEY_GRACE_PERIOD_DAYS,
+ get_onion_key_rotation_days_());
+ return grace_period*24*60*60;
+}
+
+/** Set up Tor's TLS contexts, based on our configuration and keys. Return 0
+ * on success, and -1 on failure. */
+int
+router_initialize_tls_context(void)
+{
+ unsigned int flags = 0;
+ const or_options_t *options = get_options();
+ int lifetime = options->SSLKeyLifetime;
+ if (public_server_mode(options))
+ flags |= TOR_TLS_CTX_IS_PUBLIC_SERVER;
+ if (!lifetime) { /* we should guess a good ssl cert lifetime */
+
+ /* choose between 5 and 365 days, and round to the day */
+ unsigned int five_days = 5*24*3600;
+ unsigned int one_year = 365*24*3600;
+ lifetime = crypto_rand_int_range(five_days, one_year);
+ lifetime -= lifetime % (24*3600);
+
+ if (crypto_rand_int(2)) {
+ /* Half the time we expire at midnight, and half the time we expire
+ * one second before midnight. (Some CAs wobble their expiry times a
+ * bit in practice, perhaps to reduce collision attacks; see ticket
+ * 8443 for details about observed certs in the wild.) */
+ lifetime--;
+ }
+ }
+
+ /* It's ok to pass lifetime in as an unsigned int, since
+ * config_parse_interval() checked it. */
+ return tor_tls_context_init(flags,
+ get_tlsclient_identity_key(),
+ server_mode(options) ?
+ get_server_identity_key() : NULL,
+ (unsigned int)lifetime);
+}
+
+/** Compute fingerprint (or hashed fingerprint if hashed is 1) and write
+ * it to 'fingerprint' (or 'hashed-fingerprint'). Return 0 on success, or
+ * -1 if Tor should die,
+ */
+STATIC int
+router_write_fingerprint(int hashed)
+{
+ char *keydir = NULL, *cp = NULL;
+ const char *fname = hashed ? "hashed-fingerprint" :
+ "fingerprint";
+ char fingerprint[FINGERPRINT_LEN+1];
+ const or_options_t *options = get_options();
+ char *fingerprint_line = NULL;
+ int result = -1;
+
+ keydir = get_datadir_fname(fname);
+ log_info(LD_GENERAL,"Dumping %sfingerprint to \"%s\"...",
+ hashed ? "hashed " : "", keydir);
+ if (!hashed) {
+ if (crypto_pk_get_fingerprint(get_server_identity_key(),
+ fingerprint, 0) < 0) {
+ log_err(LD_GENERAL,"Error computing fingerprint");
+ goto done;
+ }
+ } else {
+ if (crypto_pk_get_hashed_fingerprint(get_server_identity_key(),
+ fingerprint) < 0) {
+ log_err(LD_GENERAL,"Error computing hashed fingerprint");
+ goto done;
+ }
+ }
+
+ tor_asprintf(&fingerprint_line, "%s %s\n", options->Nickname, fingerprint);
+
+ /* Check whether we need to write the (hashed-)fingerprint file. */
+
+ cp = read_file_to_str(keydir, RFTS_IGNORE_MISSING, NULL);
+ if (!cp || strcmp(cp, fingerprint_line)) {
+ if (write_str_to_file(keydir, fingerprint_line, 0)) {
+ log_err(LD_FS, "Error writing %sfingerprint line to file",
+ hashed ? "hashed " : "");
+ goto done;
+ }
+ }
+
+ log_notice(LD_GENERAL, "Your Tor %s identity key fingerprint is '%s %s'",
+ hashed ? "bridge's hashed" : "server's", options->Nickname,
+ fingerprint);
+
+ result = 0;
+ done:
+ tor_free(cp);
+ tor_free(keydir);
+ tor_free(fingerprint_line);
+ return result;
+}
+
+static int
+init_keys_common(void)
+{
+ if (!key_lock)
+ key_lock = tor_mutex_new();
+
+ /* There are a couple of paths that put us here before we've asked
+ * openssl to initialize itself. */
+ if (crypto_global_init(get_options()->HardwareAccel,
+ get_options()->AccelName,
+ get_options()->AccelDir)) {
+ log_err(LD_BUG, "Unable to initialize OpenSSL. Exiting.");
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+init_keys_client(void)
+{
+ crypto_pk_t *prkey;
+ if (init_keys_common() < 0)
+ return -1;
+
+ if (!(prkey = crypto_pk_new()))
+ return -1;
+ if (crypto_pk_generate_key(prkey)) {
+ crypto_pk_free(prkey);
+ return -1;
+ }
+ set_client_identity_key(prkey);
+ /* Create a TLS context. */
+ if (router_initialize_tls_context() < 0) {
+ log_err(LD_GENERAL,"Error creating TLS context for Tor client.");
+ return -1;
+ }
+ return 0;
+}
+
+/** Initialize all OR private keys, and the TLS context, as necessary.
+ * On OPs, this only initializes the tls context. Return 0 on success,
+ * or -1 if Tor should die.
+ */
+int
+init_keys(void)
+{
+ char *keydir;
+ const char *mydesc;
+ crypto_pk_t *prkey;
+ char digest[DIGEST_LEN];
+ char v3_digest[DIGEST_LEN];
+ const or_options_t *options = get_options();
+ dirinfo_type_t type;
+ time_t now = time(NULL);
+ dir_server_t *ds;
+ int v3_digest_set = 0;
+ authority_cert_t *cert = NULL;
+
+ /* OP's don't need persistent keys; just make up an identity and
+ * initialize the TLS context. */
+ if (!server_mode(options)) {
+ return init_keys_client();
+ }
+ if (init_keys_common() < 0)
+ return -1;
+
+ if (create_keys_directory(options) < 0)
+ return -1;
+
+ /* 1a. Read v3 directory authority key/cert information. */
+ memset(v3_digest, 0, sizeof(v3_digest));
+ if (authdir_mode_v3(options)) {
+ if (init_v3_authority_keys()<0) {
+ log_err(LD_GENERAL, "We're configured as a V3 authority, but we "
+ "were unable to load our v3 authority keys and certificate! "
+ "Use tor-gencert to generate them. Dying.");
+ return -1;
+ }
+ cert = get_my_v3_authority_cert();
+ if (cert) {
+ if (crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key,
+ v3_digest) < 0) {
+ log_err(LD_BUG, "Couldn't compute my v3 authority identity key "
+ "digest.");
+ return -1;
+ }
+ v3_digest_set = 1;
+ }
+ }
+
+ /* 1b. Read identity key. Make it if none is found. */
+ keydir = get_keydir_fname("secret_id_key");
+ log_info(LD_GENERAL,"Reading/making identity key \"%s\"...",keydir);
+ bool created = false;
+ prkey = init_key_from_file(keydir, 1, LOG_ERR, &created);
+ tor_free(keydir);
+ if (!prkey) return -1;
+ if (created)
+ log_new_relay_greeting();
+ set_server_identity_key(prkey);
+
+ /* 1c. If we are configured as a bridge, generate a client key;
+ * otherwise, set the server identity key as our client identity
+ * key. */
+ if (public_server_mode(options)) {
+ set_client_identity_key(crypto_pk_dup_key(prkey)); /* set above */
+ } else {
+ if (!(prkey = crypto_pk_new()))
+ return -1;
+ if (crypto_pk_generate_key(prkey)) {
+ crypto_pk_free(prkey);
+ return -1;
+ }
+ set_client_identity_key(prkey);
+ }
+
+ /* 1d. Load all ed25519 keys */
+ const int new_signing_key = load_ed_keys(options,now);
+ if (new_signing_key < 0)
+ return -1;
+
+ /* 2. Read onion key. Make it if none is found. */
+ keydir = get_keydir_fname("secret_onion_key");
+ log_info(LD_GENERAL,"Reading/making onion key \"%s\"...",keydir);
+ prkey = init_key_from_file(keydir, 1, LOG_ERR, &created);
+ if (created)
+ log_new_relay_greeting();
+ tor_free(keydir);
+ if (!prkey) return -1;
+ set_onion_key(prkey);
+ if (options->command == CMD_RUN_TOR) {
+ /* only mess with the state file if we're actually running Tor */
+ or_state_t *state = get_or_state();
+ if (state->LastRotatedOnionKey > 100 && state->LastRotatedOnionKey < now) {
+ /* We allow for some parsing slop, but we don't want to risk accepting
+ * values in the distant future. If we did, we might never rotate the
+ * onion key. */
+ onionkey_set_at = state->LastRotatedOnionKey;
+ } else {
+ /* We have no LastRotatedOnionKey set; either we just created the key
+ * or it's a holdover from 0.1.2.4-alpha-dev or earlier. In either case,
+ * start the clock ticking now so that we will eventually rotate it even
+ * if we don't stay up for the full lifetime of an onion key. */
+ state->LastRotatedOnionKey = onionkey_set_at = now;
+ or_state_mark_dirty(state, options->AvoidDiskWrites ?
+ time(NULL)+3600 : 0);
+ }
+ }
+
+ keydir = get_keydir_fname("secret_onion_key.old");
+ if (!lastonionkey && file_status(keydir) == FN_FILE) {
+ /* Load keys from non-empty files only.
+ * Missing old keys won't be replaced with freshly generated keys. */
+ prkey = init_key_from_file(keydir, 0, LOG_ERR, 0);
+ if (prkey)
+ lastonionkey = prkey;
+ }
+ tor_free(keydir);
+
+ {
+ /* 2b. Load curve25519 onion keys. */
+ int r;
+ keydir = get_keydir_fname("secret_onion_key_ntor");
+ r = init_curve25519_keypair_from_file(&curve25519_onion_key,
+ keydir, 1, LOG_ERR, "onion");
+ tor_free(keydir);
+ if (r<0)
+ return -1;
+
+ keydir = get_keydir_fname("secret_onion_key_ntor.old");
+ if (tor_mem_is_zero((const char *)
+ last_curve25519_onion_key.pubkey.public_key,
+ CURVE25519_PUBKEY_LEN) &&
+ file_status(keydir) == FN_FILE) {
+ /* Load keys from non-empty files only.
+ * Missing old keys won't be replaced with freshly generated keys. */
+ init_curve25519_keypair_from_file(&last_curve25519_onion_key,
+ keydir, 0, LOG_ERR, "onion");
+ }
+ tor_free(keydir);
+ }
+
+ /* 3. Initialize link key and TLS context. */
+ if (router_initialize_tls_context() < 0) {
+ log_err(LD_GENERAL,"Error initializing TLS context");
+ return -1;
+ }
+
+ /* 3b. Get an ed25519 link certificate. Note that we need to do this
+ * after we set up the TLS context */
+ if (generate_ed_link_cert(options, now, new_signing_key > 0) < 0) {
+ log_err(LD_GENERAL,"Couldn't make link cert");
+ return -1;
+ }
+
+ /* 4. Build our router descriptor. */
+ /* Must be called after keys are initialized. */
+ mydesc = router_get_my_descriptor();
+ if (authdir_mode_v3(options)) {
+ const char *m = NULL;
+ routerinfo_t *ri;
+ /* We need to add our own fingerprint so it gets recognized. */
+ if (dirserv_add_own_fingerprint(get_server_identity_key())) {
+ log_err(LD_GENERAL,"Error adding own fingerprint to set of relays");
+ return -1;
+ }
+ if (mydesc) {
+ was_router_added_t added;
+ ri = router_parse_entry_from_string(mydesc, NULL, 1, 0, NULL, NULL);
+ if (!ri) {
+ log_err(LD_GENERAL,"Generated a routerinfo we couldn't parse.");
+ return -1;
+ }
+ added = dirserv_add_descriptor(ri, &m, "self");
+ if (!WRA_WAS_ADDED(added)) {
+ if (!WRA_WAS_OUTDATED(added)) {
+ log_err(LD_GENERAL, "Unable to add own descriptor to directory: %s",
+ m?m:"<unknown error>");
+ return -1;
+ } else {
+ /* If the descriptor was outdated, that's ok. This can happen
+ * when some config options are toggled that affect workers, but
+ * we don't really need new keys yet so the descriptor doesn't
+ * change and the old one is still fresh. */
+ log_info(LD_GENERAL, "Couldn't add own descriptor to directory "
+ "after key init: %s This is usually not a problem.",
+ m?m:"<unknown error>");
+ }
+ }
+ }
+ }
+
+ /* 5. Dump fingerprint and possibly hashed fingerprint to files. */
+ if (router_write_fingerprint(0)) {
+ log_err(LD_FS, "Error writing fingerprint to file");
+ return -1;
+ }
+ if (!public_server_mode(options) && router_write_fingerprint(1)) {
+ log_err(LD_FS, "Error writing hashed fingerprint to file");
+ return -1;
+ }
+
+ if (!authdir_mode(options))
+ return 0;
+ /* 6. [authdirserver only] load approved-routers file */
+ if (dirserv_load_fingerprint_file() < 0) {
+ log_err(LD_GENERAL,"Error loading fingerprints");
+ return -1;
+ }
+ /* 6b. [authdirserver only] add own key to approved directories. */
+ crypto_pk_get_digest(get_server_identity_key(), digest);
+ type = ((options->V3AuthoritativeDir ?
+ (V3_DIRINFO|MICRODESC_DIRINFO|EXTRAINFO_DIRINFO) : NO_DIRINFO) |
+ (options->BridgeAuthoritativeDir ? BRIDGE_DIRINFO : NO_DIRINFO));
+
+ ds = router_get_trusteddirserver_by_digest(digest);
+ if (!ds) {
+ ds = trusted_dir_server_new(options->Nickname, NULL,
+ router_get_advertised_dir_port(options, 0),
+ router_get_advertised_or_port(options),
+ NULL,
+ digest,
+ v3_digest,
+ type, 0.0);
+ if (!ds) {
+ log_err(LD_GENERAL,"We want to be a directory authority, but we "
+ "couldn't add ourselves to the authority list. Failing.");
+ return -1;
+ }
+ dir_server_add(ds);
+ }
+ if (ds->type != type) {
+ log_warn(LD_DIR, "Configured authority type does not match authority "
+ "type in DirAuthority list. Adjusting. (%d v %d)",
+ type, ds->type);
+ ds->type = type;
+ }
+ if (v3_digest_set && (ds->type & V3_DIRINFO) &&
+ tor_memneq(v3_digest, ds->v3_identity_digest, DIGEST_LEN)) {
+ log_warn(LD_DIR, "V3 identity key does not match identity declared in "
+ "DirAuthority line. Adjusting.");
+ memcpy(ds->v3_identity_digest, v3_digest, DIGEST_LEN);
+ }
+
+ if (cert) { /* add my own cert to the list of known certs */
+ log_info(LD_DIR, "adding my own v3 cert");
+ if (trusted_dirs_load_certs_from_string(
+ cert->cache_info.signed_descriptor_body,
+ TRUSTED_DIRS_CERTS_SRC_SELF, 0,
+ NULL)<0) {
+ log_warn(LD_DIR, "Unable to parse my own v3 cert! Failing.");
+ return -1;
+ }
+ }
+
+ return 0; /* success */
+}
+
+/** The lower threshold of remaining bandwidth required to advertise (or
+ * automatically provide) directory services */
+/* XXX Should this be increased? */
+#define MIN_BW_TO_ADVERTISE_DIRSERVER 51200
+
+/** Return true iff we have enough configured bandwidth to advertise or
+ * automatically provide directory services from cache directory
+ * information. */
+int
+router_has_bandwidth_to_be_dirserver(const or_options_t *options)
+{
+ if (options->BandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) {
+ return 0;
+ }
+ if (options->RelayBandwidthRate > 0 &&
+ options->RelayBandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) {
+ return 0;
+ }
+ return 1;
+}
+
+/** Helper: Return 1 if we have sufficient resources for serving directory
+ * requests, return 0 otherwise.
+ * dir_port is either 0 or the configured DirPort number.
+ * If AccountingMax is set less than our advertised bandwidth, then don't
+ * serve requests. Likewise, if our advertised bandwidth is less than
+ * MIN_BW_TO_ADVERTISE_DIRSERVER, don't bother trying to serve requests.
+ */
+static int
+router_should_be_dirserver(const or_options_t *options, int dir_port)
+{
+ static int advertising=1; /* start out assuming we will advertise */
+ int new_choice=1;
+ const char *reason = NULL;
+
+ if (accounting_is_enabled(options) &&
+ get_options()->AccountingRule != ACCT_IN) {
+ /* Don't spend bytes for directory traffic if we could end up hibernating,
+ * but allow DirPort otherwise. Some relay operators set AccountingMax
+ * because they're confused or to get statistics. Directory traffic has a
+ * much larger effect on output than input so there is no reason to turn it
+ * off if using AccountingRule in. */
+ int interval_length = accounting_get_interval_length();
+ uint32_t effective_bw = get_effective_bwrate(options);
+ uint64_t acc_bytes;
+ if (!interval_length) {
+ log_warn(LD_BUG, "An accounting interval is not allowed to be zero "
+ "seconds long. Raising to 1.");
+ interval_length = 1;
+ }
+ log_info(LD_GENERAL, "Calculating whether to advertise %s: effective "
+ "bwrate: %u, AccountingMax: %"PRIu64", "
+ "accounting interval length %d",
+ dir_port ? "dirport" : "begindir",
+ effective_bw, (options->AccountingMax),
+ interval_length);
+
+ acc_bytes = options->AccountingMax;
+ if (get_options()->AccountingRule == ACCT_SUM)
+ acc_bytes /= 2;
+ if (effective_bw >=
+ acc_bytes / interval_length) {
+ new_choice = 0;
+ reason = "AccountingMax enabled";
+ }
+ } else if (! router_has_bandwidth_to_be_dirserver(options)) {
+ /* if we're advertising a small amount */
+ new_choice = 0;
+ reason = "BandwidthRate under 50KB";
+ }
+
+ if (advertising != new_choice) {
+ if (new_choice == 1) {
+ if (dir_port > 0)
+ log_notice(LD_DIR, "Advertising DirPort as %d", dir_port);
+ else
+ log_notice(LD_DIR, "Advertising directory service support");
+ } else {
+ tor_assert(reason);
+ log_notice(LD_DIR, "Not advertising Dir%s (Reason: %s)",
+ dir_port ? "Port" : "ectory Service support", reason);
+ }
+ advertising = new_choice;
+ }
+
+ return advertising;
+}
+
+/** Look at a variety of factors, and return 0 if we don't want to
+ * advertise the fact that we have a DirPort open or begindir support, else
+ * return 1.
+ *
+ * Where dir_port or supports_tunnelled_dir_requests are not relevant, they
+ * must be 0.
+ *
+ * Log a helpful message if we change our mind about whether to publish.
+ */
+static int
+decide_to_advertise_dir_impl(const or_options_t *options,
+ uint16_t dir_port,
+ int supports_tunnelled_dir_requests)
+{
+ /* Part one: reasons to publish or not publish that aren't
+ * worth mentioning to the user, either because they're obvious
+ * or because they're normal behavior. */
+
+ /* short circuit the rest of the function */
+ if (!dir_port && !supports_tunnelled_dir_requests)
+ return 0;
+ if (authdir_mode(options)) /* always publish */
+ return 1;
+ if (net_is_disabled())
+ return 0;
+ if (dir_port && !router_get_advertised_dir_port(options, dir_port))
+ return 0;
+ if (supports_tunnelled_dir_requests &&
+ !router_get_advertised_or_port(options))
+ return 0;
+
+ /* Part two: consider config options that could make us choose to
+ * publish or not publish that the user might find surprising. */
+ return router_should_be_dirserver(options, dir_port);
+}
+
+/** Front-end to decide_to_advertise_dir_impl(): return 0 if we don't want to
+ * advertise the fact that we have a DirPort open, else return the
+ * DirPort we want to advertise.
+ */
+int
+router_should_advertise_dirport(const or_options_t *options, uint16_t dir_port)
+{
+ /* supports_tunnelled_dir_requests is not relevant, pass 0 */
+ return decide_to_advertise_dir_impl(options, dir_port, 0) ? dir_port : 0;
+}
+
+/** Front-end to decide_to_advertise_dir_impl(): return 0 if we don't want to
+ * advertise the fact that we support begindir requests, else return 1.
+ */
+static int
+router_should_advertise_begindir(const or_options_t *options,
+ int supports_tunnelled_dir_requests)
+{
+ /* dir_port is not relevant, pass 0 */
+ return decide_to_advertise_dir_impl(options, 0,
+ supports_tunnelled_dir_requests);
+}
+
+/** Return true iff the combination of options in <b>options</b> and parameters
+ * in the consensus mean that we don't want to allow exits from circuits
+ * we got from addresses not known to be servers. */
+int
+should_refuse_unknown_exits(const or_options_t *options)
+{
+ if (options->RefuseUnknownExits != -1) {
+ return options->RefuseUnknownExits;
+ } else {
+ return networkstatus_get_param(NULL, "refuseunknownexits", 1, 0, 1);
+ }
+}
+
+/** Decide if we're a publishable server. We are a publishable server if:
+ * - We don't have the ClientOnly option set
+ * and
+ * - We have the PublishServerDescriptor option set to non-empty
+ * and
+ * - We have ORPort set
+ * and
+ * - We believe our ORPort and DirPort (if present) are reachable from
+ * the outside; or
+ * - We believe our ORPort is reachable from the outside, and we can't
+ * check our DirPort because the consensus has no exits; or
+ * - We are an authoritative directory server.
+ */
+static int
+decide_if_publishable_server(void)
+{
+ const or_options_t *options = get_options();
+
+ if (options->ClientOnly)
+ return 0;
+ if (options->PublishServerDescriptor_ == NO_DIRINFO)
+ return 0;
+ if (!server_mode(options))
+ return 0;
+ if (authdir_mode(options))
+ return 1;
+ if (!router_get_advertised_or_port(options))
+ return 0;
+ if (!check_whether_orport_reachable(options))
+ return 0;
+ if (router_have_consensus_path() == CONSENSUS_PATH_INTERNAL) {
+ /* All set: there are no exits in the consensus (maybe this is a tiny
+ * test network), so we can't check our DirPort reachability. */
+ return 1;
+ } else {
+ return check_whether_dirport_reachable(options);
+ }
+}
+
+/** Initiate server descriptor upload as reasonable (if server is publishable,
+ * etc). <b>force</b> is as for router_upload_dir_desc_to_dirservers.
+ *
+ * We need to rebuild the descriptor if it's dirty even if we're not
+ * uploading, because our reachability testing *uses* our descriptor to
+ * determine what IP address and ports to test.
+ */
+void
+consider_publishable_server(int force)
+{
+ int rebuilt;
+
+ if (!server_mode(get_options()))
+ return;
+
+ rebuilt = router_rebuild_descriptor(0);
+ if (decide_if_publishable_server()) {
+ set_server_advertised(1);
+ if (rebuilt == 0)
+ router_upload_dir_desc_to_dirservers(force);
+ } else {
+ set_server_advertised(0);
+ }
+}
+
+/** Return the port of the first active listener of type
+ * <b>listener_type</b>. */
+/** XXX not a very good interface. it's not reliable when there are
+ multiple listeners. */
+uint16_t
+router_get_active_listener_port_by_type_af(int listener_type,
+ sa_family_t family)
+{
+ /* Iterate all connections, find one of the right kind and return
+ the port. Not very sophisticated or fast, but effective. */
+ smartlist_t *conns = get_connection_array();
+ SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) {
+ if (conn->type == listener_type && !conn->marked_for_close &&
+ conn->socket_family == family) {
+ return conn->port;
+ }
+ } SMARTLIST_FOREACH_END(conn);
+
+ return 0;
+}
+
+/** Return the port that we should advertise as our ORPort; this is either
+ * the one configured in the ORPort option, or the one we actually bound to
+ * if ORPort is "auto".
+ */
+uint16_t
+router_get_advertised_or_port(const or_options_t *options)
+{
+ return router_get_advertised_or_port_by_af(options, AF_INET);
+}
+
+/** As router_get_advertised_or_port(), but allows an address family argument.
+ */
+uint16_t
+router_get_advertised_or_port_by_af(const or_options_t *options,
+ sa_family_t family)
+{
+ int port = get_first_advertised_port_by_type_af(CONN_TYPE_OR_LISTENER,
+ family);
+ (void)options;
+
+ /* If the port is in 'auto' mode, we have to use
+ router_get_listener_port_by_type(). */
+ if (port == CFG_AUTO_PORT)
+ return router_get_active_listener_port_by_type_af(CONN_TYPE_OR_LISTENER,
+ family);
+
+ return port;
+}
+
+/** Return the port that we should advertise as our DirPort;
+ * this is one of three possibilities:
+ * The one that is passed as <b>dirport</b> if the DirPort option is 0, or
+ * the one configured in the DirPort option,
+ * or the one we actually bound to if DirPort is "auto". */
+uint16_t
+router_get_advertised_dir_port(const or_options_t *options, uint16_t dirport)
+{
+ int dirport_configured = get_primary_dir_port();
+ (void)options;
+
+ if (!dirport_configured)
+ return dirport;
+
+ if (dirport_configured == CFG_AUTO_PORT)
+ return router_get_active_listener_port_by_type_af(CONN_TYPE_DIR_LISTENER,
+ AF_INET);
+
+ return dirport_configured;
+}
+
+/*
+ * OR descriptor generation.
+ */
+
+/** My routerinfo. */
+static routerinfo_t *desc_routerinfo = NULL;
+/** My extrainfo */
+static extrainfo_t *desc_extrainfo = NULL;
+/** Why did we most recently decide to regenerate our descriptor? Used to
+ * tell the authorities why we're sending it to them. */
+static const char *desc_gen_reason = "uninitialized reason";
+/** Since when has our descriptor been "clean"? 0 if we need to regenerate it
+ * now. */
+static time_t desc_clean_since = 0;
+/** Why did we mark the descriptor dirty? */
+static const char *desc_dirty_reason = "Tor just started";
+/** Boolean: do we need to regenerate the above? */
+static int desc_needs_upload = 0;
+
+/** OR only: If <b>force</b> is true, or we haven't uploaded this
+ * descriptor successfully yet, try to upload our signed descriptor to
+ * all the directory servers we know about.
+ */
+void
+router_upload_dir_desc_to_dirservers(int force)
+{
+ const routerinfo_t *ri;
+ extrainfo_t *ei;
+ char *msg;
+ size_t desc_len, extra_len = 0, total_len;
+ dirinfo_type_t auth = get_options()->PublishServerDescriptor_;
+
+ ri = router_get_my_routerinfo();
+ if (!ri) {
+ log_info(LD_GENERAL, "No descriptor; skipping upload");
+ return;
+ }
+ ei = router_get_my_extrainfo();
+ if (auth == NO_DIRINFO)
+ return;
+ if (!force && !desc_needs_upload)
+ return;
+
+ log_info(LD_OR, "Uploading relay descriptor to directory authorities%s",
+ force ? " (forced)" : "");
+
+ desc_needs_upload = 0;
+
+ desc_len = ri->cache_info.signed_descriptor_len;
+ extra_len = ei ? ei->cache_info.signed_descriptor_len : 0;
+ total_len = desc_len + extra_len + 1;
+ msg = tor_malloc(total_len);
+ memcpy(msg, ri->cache_info.signed_descriptor_body, desc_len);
+ if (ei) {
+ memcpy(msg+desc_len, ei->cache_info.signed_descriptor_body, extra_len);
+ }
+ msg[desc_len+extra_len] = 0;
+
+ directory_post_to_dirservers(DIR_PURPOSE_UPLOAD_DIR,
+ (auth & BRIDGE_DIRINFO) ?
+ ROUTER_PURPOSE_BRIDGE :
+ ROUTER_PURPOSE_GENERAL,
+ auth, msg, desc_len, extra_len);
+ tor_free(msg);
+}
+
+/** OR only: Check whether my exit policy says to allow connection to
+ * conn. Return 0 if we accept; non-0 if we reject.
+ */
+int
+router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port)
+{
+ const routerinfo_t *me = router_get_my_routerinfo();
+ if (!me) /* make sure routerinfo exists */
+ return -1;
+
+ /* make sure it's resolved to something. this way we can't get a
+ 'maybe' below. */
+ if (tor_addr_is_null(addr))
+ return -1;
+
+ /* look at router_get_my_routerinfo()->exit_policy for both the v4 and the
+ * v6 policies. The exit_policy field in router_get_my_routerinfo() is a
+ * bit unusual, in that it contains IPv6 and IPv6 entries. We don't want to
+ * look at router_get_my_routerinfo()->ipv6_exit_policy, since that's a port
+ * summary. */
+ if ((tor_addr_family(addr) == AF_INET ||
+ tor_addr_family(addr) == AF_INET6)) {
+ return compare_tor_addr_to_addr_policy(addr, port,
+ me->exit_policy) != ADDR_POLICY_ACCEPTED;
+#if 0
+ } else if (tor_addr_family(addr) == AF_INET6) {
+ return get_options()->IPv6Exit &&
+ desc_routerinfo->ipv6_exit_policy &&
+ compare_tor_addr_to_short_policy(addr, port,
+ me->ipv6_exit_policy) != ADDR_POLICY_ACCEPTED;
+#endif /* 0 */
+ } else {
+ return -1;
+ }
+}
+
+/** Return true iff my exit policy is reject *:*. Return -1 if we don't
+ * have a descriptor */
+MOCK_IMPL(int,
+router_my_exit_policy_is_reject_star,(void))
+{
+ const routerinfo_t *me = router_get_my_routerinfo();
+ if (!me) /* make sure routerinfo exists */
+ return -1;
+
+ return me->policy_is_reject_star;
+}
+
+/** Return true iff I'm a server and <b>digest</b> is equal to
+ * my server identity key digest. */
+int
+router_digest_is_me(const char *digest)
+{
+ return (server_identitykey &&
+ tor_memeq(server_identitykey_digest, digest, DIGEST_LEN));
+}
+
+/** Return my identity digest. */
+const uint8_t *
+router_get_my_id_digest(void)
+{
+ return (const uint8_t *)server_identitykey_digest;
+}
+
+/** Return true iff I'm a server and <b>digest</b> is equal to
+ * my identity digest. */
+int
+router_extrainfo_digest_is_me(const char *digest)
+{
+ extrainfo_t *ei = router_get_my_extrainfo();
+ if (!ei)
+ return 0;
+
+ return tor_memeq(digest,
+ ei->cache_info.signed_descriptor_digest,
+ DIGEST_LEN);
+}
+
+/** A wrapper around router_digest_is_me(). */
+int
+router_is_me(const routerinfo_t *router)
+{
+ return router_digest_is_me(router->cache_info.identity_digest);
+}
+
+/** Return a routerinfo for this OR, rebuilding a fresh one if
+ * necessary. Return NULL on error, or if called on an OP. */
+MOCK_IMPL(const routerinfo_t *,
+router_get_my_routerinfo,(void))
+{
+ return router_get_my_routerinfo_with_err(NULL);
+}
+
+/** Return routerinfo of this OR. Rebuild it from
+ * scratch if needed. Set <b>*err</b> to 0 on success or to
+ * appropriate TOR_ROUTERINFO_ERROR_* value on failure.
+ */
+MOCK_IMPL(const routerinfo_t *,
+router_get_my_routerinfo_with_err,(int *err))
+{
+ if (!server_mode(get_options())) {
+ if (err)
+ *err = TOR_ROUTERINFO_ERROR_NOT_A_SERVER;
+
+ return NULL;
+ }
+
+ if (!desc_clean_since) {
+ int rebuild_err = router_rebuild_descriptor(0);
+ if (rebuild_err < 0) {
+ if (err)
+ *err = rebuild_err;
+
+ return NULL;
+ }
+ }
+
+ if (!desc_routerinfo) {
+ if (err)
+ *err = TOR_ROUTERINFO_ERROR_DESC_REBUILDING;
+
+ return NULL;
+ }
+
+ if (err)
+ *err = 0;
+
+ return desc_routerinfo;
+}
+
+/** OR only: Return a signed server descriptor for this OR, rebuilding a fresh
+ * one if necessary. Return NULL on error.
+ */
+const char *
+router_get_my_descriptor(void)
+{
+ const char *body;
+ const routerinfo_t *me = router_get_my_routerinfo();
+ if (! me)
+ return NULL;
+ tor_assert(me->cache_info.saved_location == SAVED_NOWHERE);
+ body = signed_descriptor_get_body(&me->cache_info);
+ /* Make sure this is nul-terminated. */
+ tor_assert(!body[me->cache_info.signed_descriptor_len]);
+ log_debug(LD_GENERAL,"my desc is '%s'", body);
+ return body;
+}
+
+/** Return the extrainfo document for this OR, or NULL if we have none.
+ * Rebuilt it (and the server descriptor) if necessary. */
+extrainfo_t *
+router_get_my_extrainfo(void)
+{
+ if (!server_mode(get_options()))
+ return NULL;
+ if (router_rebuild_descriptor(0))
+ return NULL;
+ return desc_extrainfo;
+}
+
+/** Return a human-readable string describing what triggered us to generate
+ * our current descriptor, or NULL if we don't know. */
+const char *
+router_get_descriptor_gen_reason(void)
+{
+ return desc_gen_reason;
+}
+
+/** A list of nicknames that we've warned about including in our family
+ * declaration verbatim rather than as digests. */
+static smartlist_t *warned_nonexistent_family = NULL;
+
+static int router_guess_address_from_dir_headers(uint32_t *guess);
+
+/** Make a current best guess at our address, either because
+ * it's configured in torrc, or because we've learned it from
+ * dirserver headers. Place the answer in *<b>addr</b> and return
+ * 0 on success, else return -1 if we have no guess.
+ *
+ * If <b>cache_only</b> is true, just return any cached answers, and
+ * don't try to get any new answers.
+ */
+MOCK_IMPL(int,
+router_pick_published_address,(const or_options_t *options, uint32_t *addr,
+ int cache_only))
+{
+ /* First, check the cached output from resolve_my_address(). */
+ *addr = get_last_resolved_addr();
+ if (*addr)
+ return 0;
+
+ /* Second, consider doing a resolve attempt right here. */
+ if (!cache_only) {
+ if (resolve_my_address(LOG_INFO, options, addr, NULL, NULL) >= 0) {
+ log_info(LD_CONFIG,"Success: chose address '%s'.", fmt_addr32(*addr));
+ return 0;
+ }
+ }
+
+ /* Third, check the cached output from router_new_address_suggestion(). */
+ if (router_guess_address_from_dir_headers(addr) >= 0)
+ return 0;
+
+ /* We have no useful cached answers. Return failure. */
+ return -1;
+}
+
+/* Like router_check_descriptor_address_consistency, but specifically for the
+ * ORPort or DirPort.
+ * listener_type is either CONN_TYPE_OR_LISTENER or CONN_TYPE_DIR_LISTENER. */
+static void
+router_check_descriptor_address_port_consistency(uint32_t ipv4h_desc_addr,
+ int listener_type)
+{
+ tor_assert(listener_type == CONN_TYPE_OR_LISTENER ||
+ listener_type == CONN_TYPE_DIR_LISTENER);
+
+ /* The first advertised Port may be the magic constant CFG_AUTO_PORT.
+ */
+ int port_v4_cfg = get_first_advertised_port_by_type_af(listener_type,
+ AF_INET);
+ if (port_v4_cfg != 0 &&
+ !port_exists_by_type_addr32h_port(listener_type,
+ ipv4h_desc_addr, port_v4_cfg, 1)) {
+ const tor_addr_t *port_addr = get_first_advertised_addr_by_type_af(
+ listener_type,
+ AF_INET);
+ /* If we're building a descriptor with no advertised address,
+ * something is terribly wrong. */
+ tor_assert(port_addr);
+
+ tor_addr_t desc_addr;
+ char port_addr_str[TOR_ADDR_BUF_LEN];
+ char desc_addr_str[TOR_ADDR_BUF_LEN];
+
+ tor_addr_to_str(port_addr_str, port_addr, TOR_ADDR_BUF_LEN, 0);
+
+ tor_addr_from_ipv4h(&desc_addr, ipv4h_desc_addr);
+ tor_addr_to_str(desc_addr_str, &desc_addr, TOR_ADDR_BUF_LEN, 0);
+
+ const char *listener_str = (listener_type == CONN_TYPE_OR_LISTENER ?
+ "OR" : "Dir");
+ log_warn(LD_CONFIG, "The IPv4 %sPort address %s does not match the "
+ "descriptor address %s. If you have a static public IPv4 "
+ "address, use 'Address <IPv4>' and 'OutboundBindAddress "
+ "<IPv4>'. If you are behind a NAT, use two %sPort lines: "
+ "'%sPort <PublicPort> NoListen' and '%sPort <InternalPort> "
+ "NoAdvertise'.",
+ listener_str, port_addr_str, desc_addr_str, listener_str,
+ listener_str, listener_str);
+ }
+}
+
+/* Tor relays only have one IPv4 address in the descriptor, which is derived
+ * from the Address torrc option, or guessed using various methods in
+ * router_pick_published_address().
+ * Warn the operator if there is no ORPort on the descriptor address
+ * ipv4h_desc_addr.
+ * Warn the operator if there is no DirPort on the descriptor address.
+ * This catches a few common config errors:
+ * - operators who expect ORPorts and DirPorts to be advertised on the
+ * ports' listen addresses, rather than the torrc Address (or guessed
+ * addresses in the absence of an Address config). This includes
+ * operators who attempt to put their ORPort and DirPort on different
+ * addresses;
+ * - discrepancies between guessed addresses and configured listen
+ * addresses (when the Address option isn't set).
+ * If a listener is listening on all IPv4 addresses, it is assumed that it
+ * is listening on the configured Address, and no messages are logged.
+ * If an operators has specified NoAdvertise ORPorts in a NAT setting,
+ * no messages are logged, unless they have specified other advertised
+ * addresses.
+ * The message tells operators to configure an ORPort and DirPort that match
+ * the Address (using NoListen if needed).
+ */
+static void
+router_check_descriptor_address_consistency(uint32_t ipv4h_desc_addr)
+{
+ router_check_descriptor_address_port_consistency(ipv4h_desc_addr,
+ CONN_TYPE_OR_LISTENER);
+ router_check_descriptor_address_port_consistency(ipv4h_desc_addr,
+ CONN_TYPE_DIR_LISTENER);
+}
+
+/** Build a fresh routerinfo, signed server descriptor, and extra-info document
+ * for this OR. Set r to the generated routerinfo, e to the generated
+ * extra-info document. Return 0 on success, -1 on temporary error. Failure to
+ * generate an extra-info document is not an error and is indicated by setting
+ * e to NULL. Caller is responsible for freeing generated documents if 0 is
+ * returned.
+ */
+int
+router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
+{
+ routerinfo_t *ri;
+ extrainfo_t *ei;
+ uint32_t addr;
+ char platform[256];
+ int hibernating = we_are_hibernating();
+ const or_options_t *options = get_options();
+
+ if (router_pick_published_address(options, &addr, 0) < 0) {
+ log_warn(LD_CONFIG, "Don't know my address while generating descriptor");
+ return TOR_ROUTERINFO_ERROR_NO_EXT_ADDR;
+ }
+
+ /* Log a message if the address in the descriptor doesn't match the ORPort
+ * and DirPort addresses configured by the operator. */
+ router_check_descriptor_address_consistency(addr);
+
+ ri = tor_malloc_zero(sizeof(routerinfo_t));
+ ri->cache_info.routerlist_index = -1;
+ ri->nickname = tor_strdup(options->Nickname);
+ ri->addr = addr;
+ ri->or_port = router_get_advertised_or_port(options);
+ ri->dir_port = router_get_advertised_dir_port(options, 0);
+ ri->supports_tunnelled_dir_requests =
+ directory_permits_begindir_requests(options);
+ ri->cache_info.published_on = time(NULL);
+ /* get_onion_key() must invoke from main thread */
+ router_set_rsa_onion_pkey(get_onion_key(), &ri->onion_pkey,
+ &ri->onion_pkey_len);
+
+ ri->onion_curve25519_pkey =
+ tor_memdup(&get_current_curve25519_keypair()->pubkey,
+ sizeof(curve25519_public_key_t));
+
+ /* For now, at most one IPv6 or-address is being advertised. */
+ {
+ const port_cfg_t *ipv6_orport = NULL;
+ SMARTLIST_FOREACH_BEGIN(get_configured_ports(), const port_cfg_t *, p) {
+ if (p->type == CONN_TYPE_OR_LISTENER &&
+ ! p->server_cfg.no_advertise &&
+ ! p->server_cfg.bind_ipv4_only &&
+ tor_addr_family(&p->addr) == AF_INET6) {
+ /* Like IPv4, if the relay is configured using the default
+ * authorities, disallow internal IPs. Otherwise, allow them. */
+ const int default_auth = using_default_dir_authorities(options);
+ if (! tor_addr_is_internal(&p->addr, 0) || ! default_auth) {
+ ipv6_orport = p;
+ break;
+ } else {
+ char addrbuf[TOR_ADDR_BUF_LEN];
+ log_warn(LD_CONFIG,
+ "Unable to use configured IPv6 address \"%s\" in a "
+ "descriptor. Skipping it. "
+ "Try specifying a globally reachable address explicitly.",
+ tor_addr_to_str(addrbuf, &p->addr, sizeof(addrbuf), 1));
+ }
+ }
+ } SMARTLIST_FOREACH_END(p);
+ if (ipv6_orport) {
+ tor_addr_copy(&ri->ipv6_addr, &ipv6_orport->addr);
+ ri->ipv6_orport = ipv6_orport->port;
+ }
+ }
+
+ ri->identity_pkey = crypto_pk_dup_key(get_server_identity_key());
+ if (BUG(crypto_pk_get_digest(ri->identity_pkey,
+ ri->cache_info.identity_digest) < 0)) {
+ routerinfo_free(ri);
+ return TOR_ROUTERINFO_ERROR_DIGEST_FAILED;
+ }
+ ri->cache_info.signing_key_cert =
+ tor_cert_dup(get_master_signing_key_cert());
+
+ get_platform_str(platform, sizeof(platform));
+ ri->platform = tor_strdup(platform);
+
+ ri->protocol_list = tor_strdup(protover_get_supported_protocols());
+
+ /* compute ri->bandwidthrate as the min of various options */
+ ri->bandwidthrate = get_effective_bwrate(options);
+
+ /* and compute ri->bandwidthburst similarly */
+ ri->bandwidthburst = get_effective_bwburst(options);
+
+ /* Report bandwidth, unless we're hibernating or shutting down */
+ ri->bandwidthcapacity = hibernating ? 0 : rep_hist_bandwidth_assess();
+
+ if (dns_seems_to_be_broken() || has_dns_init_failed()) {
+ /* DNS is screwed up; don't claim to be an exit. */
+ policies_exit_policy_append_reject_star(&ri->exit_policy);
+ } else {
+ policies_parse_exit_policy_from_options(options,ri->addr,&ri->ipv6_addr,
+ &ri->exit_policy);
+ }
+ ri->policy_is_reject_star =
+ policy_is_reject_star(ri->exit_policy, AF_INET, 1) &&
+ policy_is_reject_star(ri->exit_policy, AF_INET6, 1);
+
+ if (options->IPv6Exit) {
+ char *p_tmp = policy_summarize(ri->exit_policy, AF_INET6);
+ if (p_tmp)
+ ri->ipv6_exit_policy = parse_short_policy(p_tmp);
+ tor_free(p_tmp);
+ }
+
+ if (options->MyFamily && ! options->BridgeRelay) {
+ if (!warned_nonexistent_family)
+ warned_nonexistent_family = smartlist_new();
+ ri->declared_family = smartlist_new();
+ config_line_t *family;
+ for (family = options->MyFamily; family; family = family->next) {
+ char *name = family->value;
+ const node_t *member;
+ if (!strcasecmp(name, options->Nickname))
+ continue; /* Don't list ourself, that's redundant */
+ else
+ member = node_get_by_nickname(name, 0);
+ if (!member) {
+ int is_legal = is_legal_nickname_or_hexdigest(name);
+ if (!smartlist_contains_string(warned_nonexistent_family, name) &&
+ !is_legal_hexdigest(name)) {
+ if (is_legal)
+ log_warn(LD_CONFIG,
+ "I have no descriptor for the router named \"%s\" in my "
+ "declared family; I'll use the nickname as is, but "
+ "this may confuse clients.", name);
+ else
+ log_warn(LD_CONFIG, "There is a router named \"%s\" in my "
+ "declared family, but that isn't a legal nickname. "
+ "Skipping it.", escaped(name));
+ smartlist_add_strdup(warned_nonexistent_family, name);
+ }
+ if (is_legal) {
+ smartlist_add_strdup(ri->declared_family, name);
+ }
+ } else if (router_digest_is_me(member->identity)) {
+ /* Don't list ourself in our own family; that's redundant */
+ /* XXX shouldn't be possible */
+ } else {
+ char *fp = tor_malloc(HEX_DIGEST_LEN+2);
+ fp[0] = '$';
+ base16_encode(fp+1,HEX_DIGEST_LEN+1,
+ member->identity, DIGEST_LEN);
+ smartlist_add(ri->declared_family, fp);
+ if (smartlist_contains_string(warned_nonexistent_family, name))
+ smartlist_string_remove(warned_nonexistent_family, name);
+ }
+ }
+
+ /* remove duplicates from the list */
+ smartlist_sort_strings(ri->declared_family);
+ smartlist_uniq_strings(ri->declared_family);
+ }
+
+ /* Now generate the extrainfo. */
+ ei = tor_malloc_zero(sizeof(extrainfo_t));
+ ei->cache_info.is_extrainfo = 1;
+ strlcpy(ei->nickname, get_options()->Nickname, sizeof(ei->nickname));
+ ei->cache_info.published_on = ri->cache_info.published_on;
+ ei->cache_info.signing_key_cert =
+ tor_cert_dup(get_master_signing_key_cert());
+
+ memcpy(ei->cache_info.identity_digest, ri->cache_info.identity_digest,
+ DIGEST_LEN);
+ if (extrainfo_dump_to_string(&ei->cache_info.signed_descriptor_body,
+ ei, get_server_identity_key(),
+ get_master_signing_keypair()) < 0) {
+ log_warn(LD_BUG, "Couldn't generate extra-info descriptor.");
+ extrainfo_free(ei);
+ ei = NULL;
+ } else {
+ ei->cache_info.signed_descriptor_len =
+ strlen(ei->cache_info.signed_descriptor_body);
+ router_get_extrainfo_hash(ei->cache_info.signed_descriptor_body,
+ ei->cache_info.signed_descriptor_len,
+ ei->cache_info.signed_descriptor_digest);
+ crypto_digest256((char*) ei->digest256,
+ ei->cache_info.signed_descriptor_body,
+ ei->cache_info.signed_descriptor_len,
+ DIGEST_SHA256);
+ }
+
+ /* Now finish the router descriptor. */
+ if (ei) {
+ memcpy(ri->cache_info.extra_info_digest,
+ ei->cache_info.signed_descriptor_digest,
+ DIGEST_LEN);
+ memcpy(ri->cache_info.extra_info_digest256,
+ ei->digest256,
+ DIGEST256_LEN);
+ } else {
+ /* ri was allocated with tor_malloc_zero, so there is no need to
+ * zero ri->cache_info.extra_info_digest here. */
+ }
+ if (! (ri->cache_info.signed_descriptor_body =
+ router_dump_router_to_string(ri, get_server_identity_key(),
+ get_onion_key(),
+ get_current_curve25519_keypair(),
+ get_master_signing_keypair())) ) {
+ log_warn(LD_BUG, "Couldn't generate router descriptor.");
+ routerinfo_free(ri);
+ extrainfo_free(ei);
+ return TOR_ROUTERINFO_ERROR_CANNOT_GENERATE;
+ }
+ ri->cache_info.signed_descriptor_len =
+ strlen(ri->cache_info.signed_descriptor_body);
+
+ ri->purpose =
+ options->BridgeRelay ? ROUTER_PURPOSE_BRIDGE : ROUTER_PURPOSE_GENERAL;
+ if (options->BridgeRelay) {
+ /* Bridges shouldn't be able to send their descriptors unencrypted,
+ anyway, since they don't have a DirPort, and always connect to the
+ bridge authority anonymously. But just in case they somehow think of
+ sending them on an unencrypted connection, don't allow them to try. */
+ ri->cache_info.send_unencrypted = 0;
+ if (ei)
+ ei->cache_info.send_unencrypted = 0;
+ } else {
+ ri->cache_info.send_unencrypted = 1;
+ if (ei)
+ ei->cache_info.send_unencrypted = 1;
+ }
+
+ router_get_router_hash(ri->cache_info.signed_descriptor_body,
+ strlen(ri->cache_info.signed_descriptor_body),
+ ri->cache_info.signed_descriptor_digest);
+
+ if (ei) {
+ tor_assert(!
+ routerinfo_incompatible_with_extrainfo(ri->identity_pkey, ei,
+ &ri->cache_info, NULL));
+ }
+
+ *r = ri;
+ *e = ei;
+ return 0;
+}
+
+/** If <b>force</b> is true, or our descriptor is out-of-date, rebuild a fresh
+ * routerinfo, signed server descriptor, and extra-info document for this OR.
+ * Return 0 on success, -1 on temporary error.
+ */
+int
+router_rebuild_descriptor(int force)
+{
+ int err = 0;
+ routerinfo_t *ri;
+ extrainfo_t *ei;
+ uint32_t addr;
+ const or_options_t *options = get_options();
+
+ if (desc_clean_since && !force)
+ return 0;
+
+ if (router_pick_published_address(options, &addr, 0) < 0 ||
+ router_get_advertised_or_port(options) == 0) {
+ /* Stop trying to rebuild our descriptor every second. We'll
+ * learn that it's time to try again when ip_address_changed()
+ * marks it dirty. */
+ desc_clean_since = time(NULL);
+ return TOR_ROUTERINFO_ERROR_DESC_REBUILDING;
+ }
+
+ log_info(LD_OR, "Rebuilding relay descriptor%s", force ? " (forced)" : "");
+
+ err = router_build_fresh_descriptor(&ri, &ei);
+ if (err < 0) {
+ return err;
+ }
+
+ routerinfo_free(desc_routerinfo);
+ desc_routerinfo = ri;
+ extrainfo_free(desc_extrainfo);
+ desc_extrainfo = ei;
+
+ desc_clean_since = time(NULL);
+ desc_needs_upload = 1;
+ desc_gen_reason = desc_dirty_reason;
+ if (BUG(desc_gen_reason == NULL)) {
+ desc_gen_reason = "descriptor was marked dirty earlier, for no reason.";
+ }
+ desc_dirty_reason = NULL;
+ control_event_my_descriptor_changed();
+ return 0;
+}
+
+/** If our router descriptor ever goes this long without being regenerated
+ * because something changed, we force an immediate regenerate-and-upload. */
+#define FORCE_REGENERATE_DESCRIPTOR_INTERVAL (18*60*60)
+
+/** If our router descriptor seems to be missing or unacceptable according
+ * to the authorities, regenerate and reupload it _this_ often. */
+#define FAST_RETRY_DESCRIPTOR_INTERVAL (90*60)
+
+/** Mark descriptor out of date if it's been "too long" since we last tried
+ * to upload one. */
+void
+mark_my_descriptor_dirty_if_too_old(time_t now)
+{
+ networkstatus_t *ns;
+ const routerstatus_t *rs;
+ const char *retry_fast_reason = NULL; /* Set if we should retry frequently */
+ const time_t slow_cutoff = now - FORCE_REGENERATE_DESCRIPTOR_INTERVAL;
+ const time_t fast_cutoff = now - FAST_RETRY_DESCRIPTOR_INTERVAL;
+
+ /* If it's already dirty, don't mark it. */
+ if (! desc_clean_since)
+ return;
+
+ /* If it's older than FORCE_REGENERATE_DESCRIPTOR_INTERVAL, it's always
+ * time to rebuild it. */
+ if (desc_clean_since < slow_cutoff) {
+ mark_my_descriptor_dirty("time for new descriptor");
+ return;
+ }
+ /* Now we see whether we want to be retrying frequently or no. The
+ * rule here is that we'll retry frequently if we aren't listed in the
+ * live consensus we have, or if the publication time of the
+ * descriptor listed for us in the consensus is very old. */
+ ns = networkstatus_get_live_consensus(now);
+ if (ns) {
+ rs = networkstatus_vote_find_entry(ns, server_identitykey_digest);
+ if (rs == NULL)
+ retry_fast_reason = "not listed in consensus";
+ else if (rs->published_on < slow_cutoff)
+ retry_fast_reason = "version listed in consensus is quite old";
+ }
+
+ if (retry_fast_reason && desc_clean_since < fast_cutoff)
+ mark_my_descriptor_dirty(retry_fast_reason);
+}
+
+/** Call when the current descriptor is out of date. */
+void
+mark_my_descriptor_dirty(const char *reason)
+{
+ const or_options_t *options = get_options();
+ if (BUG(reason == NULL)) {
+ reason = "marked descriptor dirty for unspecified reason";
+ }
+ if (server_mode(options) && options->PublishServerDescriptor_)
+ log_info(LD_OR, "Decided to publish new relay descriptor: %s", reason);
+ desc_clean_since = 0;
+ if (!desc_dirty_reason)
+ desc_dirty_reason = reason;
+}
+
+/** How frequently will we republish our descriptor because of large (factor
+ * of 2) shifts in estimated bandwidth? Note: We don't use this constant
+ * if our previous bandwidth estimate was exactly 0. */
+#define MAX_BANDWIDTH_CHANGE_FREQ (3*60*60)
+
+/** Maximum uptime to republish our descriptor because of large shifts in
+ * estimated bandwidth. */
+#define MAX_UPTIME_BANDWIDTH_CHANGE (24*60*60)
+
+/** By which factor bandwidth shifts have to change to be considered large. */
+#define BANDWIDTH_CHANGE_FACTOR 2
+
+/** Check whether bandwidth has changed a lot since the last time we announced
+ * bandwidth while the uptime is smaller than MAX_UPTIME_BANDWIDTH_CHANGE.
+ * If so, mark our descriptor dirty. */
+void
+check_descriptor_bandwidth_changed(time_t now)
+{
+ static time_t last_changed = 0;
+ uint64_t prev, cur;
+ const int hibernating = we_are_hibernating();
+
+ /* If the relay uptime is bigger than MAX_UPTIME_BANDWIDTH_CHANGE,
+ * the next regularly scheduled descriptor update (18h) will be enough */
+ if (get_uptime() > MAX_UPTIME_BANDWIDTH_CHANGE && !hibernating)
+ return;
+
+ const routerinfo_t *my_ri = router_get_my_routerinfo();
+
+ if (!my_ri)
+ return;
+
+ prev = my_ri->bandwidthcapacity;
+
+ /* Consider ourselves to have zero bandwidth if we're hibernating or
+ * shutting down. */
+ cur = hibernating ? 0 : rep_hist_bandwidth_assess();
+
+ if ((prev != cur && (!prev || !cur)) ||
+ cur > (prev * BANDWIDTH_CHANGE_FACTOR) ||
+ cur < (prev / BANDWIDTH_CHANGE_FACTOR) ) {
+ if (last_changed+MAX_BANDWIDTH_CHANGE_FREQ < now || !prev) {
+ log_info(LD_GENERAL,
+ "Measured bandwidth has changed; rebuilding descriptor.");
+ mark_my_descriptor_dirty("bandwidth has changed");
+ last_changed = now;
+ }
+ }
+}
+
+/** Note at log level severity that our best guess of address has changed from
+ * <b>prev</b> to <b>cur</b>. */
+static void
+log_addr_has_changed(int severity,
+ const tor_addr_t *prev,
+ const tor_addr_t *cur,
+ const char *source)
+{
+ char addrbuf_prev[TOR_ADDR_BUF_LEN];
+ char addrbuf_cur[TOR_ADDR_BUF_LEN];
+
+ if (BUG(!server_mode(get_options())))
+ return;
+
+ if (tor_addr_to_str(addrbuf_prev, prev, sizeof(addrbuf_prev), 1) == NULL)
+ strlcpy(addrbuf_prev, "???", TOR_ADDR_BUF_LEN);
+ if (tor_addr_to_str(addrbuf_cur, cur, sizeof(addrbuf_cur), 1) == NULL)
+ strlcpy(addrbuf_cur, "???", TOR_ADDR_BUF_LEN);
+
+ if (!tor_addr_is_null(prev))
+ log_fn(severity, LD_GENERAL,
+ "Our IP Address has changed from %s to %s; "
+ "rebuilding descriptor (source: %s).",
+ addrbuf_prev, addrbuf_cur, source);
+ else
+ log_notice(LD_GENERAL,
+ "Guessed our IP address as %s (source: %s).",
+ addrbuf_cur, source);
+}
+
+/** Check whether our own address as defined by the Address configuration
+ * has changed. This is for routers that get their address from a service
+ * like dyndns. If our address has changed, mark our descriptor dirty. */
+void
+check_descriptor_ipaddress_changed(time_t now)
+{
+ uint32_t prev, cur;
+ const or_options_t *options = get_options();
+ const char *method = NULL;
+ char *hostname = NULL;
+ const routerinfo_t *my_ri = router_get_my_routerinfo();
+
+ (void) now;
+
+ if (my_ri == NULL) /* make sure routerinfo exists */
+ return;
+
+ /* XXXX ipv6 */
+ prev = my_ri->addr;
+ if (resolve_my_address(LOG_INFO, options, &cur, &method, &hostname) < 0) {
+ log_info(LD_CONFIG,"options->Address didn't resolve into an IP.");
+ return;
+ }
+
+ if (prev != cur) {
+ char *source;
+ tor_addr_t tmp_prev, tmp_cur;
+
+ tor_addr_from_ipv4h(&tmp_prev, prev);
+ tor_addr_from_ipv4h(&tmp_cur, cur);
+
+ tor_asprintf(&source, "METHOD=%s%s%s", method,
+ hostname ? " HOSTNAME=" : "",
+ hostname ? hostname : "");
+
+ log_addr_has_changed(LOG_NOTICE, &tmp_prev, &tmp_cur, source);
+ tor_free(source);
+
+ ip_address_changed(0);
+ }
+
+ tor_free(hostname);
+}
+
+/** The most recently guessed value of our IP address, based on directory
+ * headers. */
+static tor_addr_t last_guessed_ip = TOR_ADDR_NULL;
+
+/** A directory server <b>d_conn</b> told us our IP address is
+ * <b>suggestion</b>.
+ * If this address is different from the one we think we are now, and
+ * if our computer doesn't actually know its IP address, then switch. */
+void
+router_new_address_suggestion(const char *suggestion,
+ const dir_connection_t *d_conn)
+{
+ tor_addr_t addr;
+ uint32_t cur = 0; /* Current IPv4 address. */
+ const or_options_t *options = get_options();
+
+ /* first, learn what the IP address actually is */
+ if (tor_addr_parse(&addr, suggestion) == -1) {
+ log_debug(LD_DIR, "Malformed X-Your-Address-Is header %s. Ignoring.",
+ escaped(suggestion));
+ return;
+ }
+
+ log_debug(LD_DIR, "Got X-Your-Address-Is: %s.", suggestion);
+
+ if (!server_mode(options)) {
+ tor_addr_copy(&last_guessed_ip, &addr);
+ return;
+ }
+
+ /* XXXX ipv6 */
+ cur = get_last_resolved_addr();
+ if (cur ||
+ resolve_my_address(LOG_INFO, options, &cur, NULL, NULL) >= 0) {
+ /* We're all set -- we already know our address. Great. */
+ tor_addr_from_ipv4h(&last_guessed_ip, cur); /* store it in case we
+ need it later */
+ return;
+ }
+ if (tor_addr_is_internal(&addr, 0)) {
+ /* Don't believe anybody who says our IP is, say, 127.0.0.1. */
+ return;
+ }
+ if (tor_addr_eq(&d_conn->base_.addr, &addr)) {
+ /* Don't believe anybody who says our IP is their IP. */
+ log_debug(LD_DIR, "A directory server told us our IP address is %s, "
+ "but they are just reporting their own IP address. Ignoring.",
+ suggestion);
+ return;
+ }
+
+ /* Okay. We can't resolve our own address, and X-Your-Address-Is is giving
+ * us an answer different from what we had the last time we managed to
+ * resolve it. */
+ if (!tor_addr_eq(&last_guessed_ip, &addr)) {
+ control_event_server_status(LOG_NOTICE,
+ "EXTERNAL_ADDRESS ADDRESS=%s METHOD=DIRSERV",
+ suggestion);
+ log_addr_has_changed(LOG_NOTICE, &last_guessed_ip, &addr,
+ d_conn->base_.address);
+ ip_address_changed(0);
+ tor_addr_copy(&last_guessed_ip, &addr); /* router_rebuild_descriptor()
+ will fetch it */
+ }
+}
+
+/** We failed to resolve our address locally, but we'd like to build
+ * a descriptor and publish / test reachability. If we have a guess
+ * about our address based on directory headers, answer it and return
+ * 0; else return -1. */
+static int
+router_guess_address_from_dir_headers(uint32_t *guess)
+{
+ if (!tor_addr_is_null(&last_guessed_ip)) {
+ *guess = tor_addr_to_ipv4h(&last_guessed_ip);
+ return 0;
+ }
+ return -1;
+}
+
+/** Set <b>platform</b> (max length <b>len</b>) to a NUL-terminated short
+ * string describing the version of Tor and the operating system we're
+ * currently running on.
+ */
+STATIC void
+get_platform_str(char *platform, size_t len)
+{
+ tor_snprintf(platform, len, "Tor %s on %s",
+ get_short_version(), get_uname());
+}
+
+/* XXX need to audit this thing and count fenceposts. maybe
+ * refactor so we don't have to keep asking if we're
+ * near the end of maxlen?
+ */
+#define DEBUG_ROUTER_DUMP_ROUTER_TO_STRING
+
+/** OR only: Given a routerinfo for this router, and an identity key to sign
+ * with, encode the routerinfo as a signed server descriptor and return a new
+ * string encoding the result, or NULL on failure.
+ */
+char *
+router_dump_router_to_string(routerinfo_t *router,
+ const crypto_pk_t *ident_key,
+ const crypto_pk_t *tap_key,
+ const curve25519_keypair_t *ntor_keypair,
+ const ed25519_keypair_t *signing_keypair)
+{
+ char *address = NULL;
+ char *onion_pkey = NULL; /* Onion key, PEM-encoded. */
+ crypto_pk_t *rsa_pubkey = NULL;
+ char *identity_pkey = NULL; /* Identity key, PEM-encoded. */
+ char digest[DIGEST256_LEN];
+ char published[ISO_TIME_LEN+1];
+ char fingerprint[FINGERPRINT_LEN+1];
+ char *extra_info_line = NULL;
+ size_t onion_pkeylen, identity_pkeylen;
+ char *family_line = NULL;
+ char *extra_or_address = NULL;
+ const or_options_t *options = get_options();
+ smartlist_t *chunks = NULL;
+ char *output = NULL;
+ const int emit_ed_sigs = signing_keypair &&
+ router->cache_info.signing_key_cert;
+ char *ed_cert_line = NULL;
+ char *rsa_tap_cc_line = NULL;
+ char *ntor_cc_line = NULL;
+ char *proto_line = NULL;
+
+ /* Make sure the identity key matches the one in the routerinfo. */
+ if (!crypto_pk_eq_keys(ident_key, router->identity_pkey)) {
+ log_warn(LD_BUG,"Tried to sign a router with a private key that didn't "
+ "match router's public key!");
+ goto err;
+ }
+ if (emit_ed_sigs) {
+ if (!router->cache_info.signing_key_cert->signing_key_included ||
+ !ed25519_pubkey_eq(&router->cache_info.signing_key_cert->signed_key,
+ &signing_keypair->pubkey)) {
+ log_warn(LD_BUG, "Tried to sign a router descriptor with a mismatched "
+ "ed25519 key chain %d",
+ router->cache_info.signing_key_cert->signing_key_included);
+ goto err;
+ }
+ }
+
+ /* record our fingerprint, so we can include it in the descriptor */
+ if (crypto_pk_get_fingerprint(router->identity_pkey, fingerprint, 1)<0) {
+ log_err(LD_BUG,"Error computing fingerprint");
+ goto err;
+ }
+
+ if (emit_ed_sigs) {
+ /* Encode ed25519 signing cert */
+ char ed_cert_base64[256];
+ char ed_fp_base64[ED25519_BASE64_LEN+1];
+ if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64),
+ (const char*)router->cache_info.signing_key_cert->encoded,
+ router->cache_info.signing_key_cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) < 0) {
+ log_err(LD_BUG,"Couldn't base64-encode signing key certificate!");
+ goto err;
+ }
+ if (ed25519_public_to_base64(ed_fp_base64,
+ &router->cache_info.signing_key_cert->signing_key)<0) {
+ log_err(LD_BUG,"Couldn't base64-encode identity key\n");
+ goto err;
+ }
+ tor_asprintf(&ed_cert_line, "identity-ed25519\n"
+ "-----BEGIN ED25519 CERT-----\n"
+ "%s"
+ "-----END ED25519 CERT-----\n"
+ "master-key-ed25519 %s\n",
+ ed_cert_base64, ed_fp_base64);
+ }
+
+ /* PEM-encode the onion key */
+ rsa_pubkey = router_get_rsa_onion_pkey(router->onion_pkey,
+ router->onion_pkey_len);
+ if (crypto_pk_write_public_key_to_string(rsa_pubkey,
+ &onion_pkey,&onion_pkeylen)<0) {
+ log_warn(LD_BUG,"write onion_pkey to string failed!");
+ goto err;
+ }
+
+ /* PEM-encode the identity key */
+ if (crypto_pk_write_public_key_to_string(router->identity_pkey,
+ &identity_pkey,&identity_pkeylen)<0) {
+ log_warn(LD_BUG,"write identity_pkey to string failed!");
+ goto err;
+ }
+
+ /* Cross-certify with RSA key */
+ if (tap_key && router->cache_info.signing_key_cert &&
+ router->cache_info.signing_key_cert->signing_key_included) {
+ char buf[256];
+ int tap_cc_len = 0;
+ uint8_t *tap_cc =
+ make_tap_onion_key_crosscert(tap_key,
+ &router->cache_info.signing_key_cert->signing_key,
+ router->identity_pkey,
+ &tap_cc_len);
+ if (!tap_cc) {
+ log_warn(LD_BUG,"make_tap_onion_key_crosscert failed!");
+ goto err;
+ }
+
+ if (base64_encode(buf, sizeof(buf), (const char*)tap_cc, tap_cc_len,
+ BASE64_ENCODE_MULTILINE) < 0) {
+ log_warn(LD_BUG,"base64_encode(rsa_crosscert) failed!");
+ tor_free(tap_cc);
+ goto err;
+ }
+ tor_free(tap_cc);
+
+ tor_asprintf(&rsa_tap_cc_line,
+ "onion-key-crosscert\n"
+ "-----BEGIN CROSSCERT-----\n"
+ "%s"
+ "-----END CROSSCERT-----\n", buf);
+ }
+
+ /* Cross-certify with onion keys */
+ if (ntor_keypair && router->cache_info.signing_key_cert &&
+ router->cache_info.signing_key_cert->signing_key_included) {
+ int sign = 0;
+ char buf[256];
+ /* XXXX Base the expiration date on the actual onion key expiration time?*/
+ tor_cert_t *cert =
+ make_ntor_onion_key_crosscert(ntor_keypair,
+ &router->cache_info.signing_key_cert->signing_key,
+ router->cache_info.published_on,
+ get_onion_key_lifetime(), &sign);
+ if (!cert) {
+ log_warn(LD_BUG,"make_ntor_onion_key_crosscert failed!");
+ goto err;
+ }
+ tor_assert(sign == 0 || sign == 1);
+
+ if (base64_encode(buf, sizeof(buf),
+ (const char*)cert->encoded, cert->encoded_len,
+ BASE64_ENCODE_MULTILINE)<0) {
+ log_warn(LD_BUG,"base64_encode(ntor_crosscert) failed!");
+ tor_cert_free(cert);
+ goto err;
+ }
+ tor_cert_free(cert);
+
+ tor_asprintf(&ntor_cc_line,
+ "ntor-onion-key-crosscert %d\n"
+ "-----BEGIN ED25519 CERT-----\n"
+ "%s"
+ "-----END ED25519 CERT-----\n", sign, buf);
+ }
+
+ /* Encode the publication time. */
+ format_iso_time(published, router->cache_info.published_on);
+
+ if (router->declared_family && smartlist_len(router->declared_family)) {
+ char *family = smartlist_join_strings(router->declared_family,
+ " ", 0, NULL);
+ tor_asprintf(&family_line, "family %s\n", family);
+ tor_free(family);
+ } else {
+ family_line = tor_strdup("");
+ }
+
+ if (!tor_digest_is_zero(router->cache_info.extra_info_digest)) {
+ char extra_info_digest[HEX_DIGEST_LEN+1];
+ base16_encode(extra_info_digest, sizeof(extra_info_digest),
+ router->cache_info.extra_info_digest, DIGEST_LEN);
+ if (!tor_digest256_is_zero(router->cache_info.extra_info_digest256)) {
+ char d256_64[BASE64_DIGEST256_LEN+1];
+ digest256_to_base64(d256_64, router->cache_info.extra_info_digest256);
+ tor_asprintf(&extra_info_line, "extra-info-digest %s %s\n",
+ extra_info_digest, d256_64);
+ } else {
+ tor_asprintf(&extra_info_line, "extra-info-digest %s\n",
+ extra_info_digest);
+ }
+ }
+
+ if (router->ipv6_orport &&
+ tor_addr_family(&router->ipv6_addr) == AF_INET6) {
+ char addr[TOR_ADDR_BUF_LEN];
+ const char *a;
+ a = tor_addr_to_str(addr, &router->ipv6_addr, sizeof(addr), 1);
+ if (a) {
+ tor_asprintf(&extra_or_address,
+ "or-address %s:%d\n", a, router->ipv6_orport);
+ log_debug(LD_OR, "My or-address line is <%s>", extra_or_address);
+ }
+ }
+
+ if (router->protocol_list) {
+ tor_asprintf(&proto_line, "proto %s\n", router->protocol_list);
+ } else {
+ proto_line = tor_strdup("");
+ }
+
+ address = tor_dup_ip(router->addr);
+ chunks = smartlist_new();
+
+ /* Generate the easy portion of the router descriptor. */
+ smartlist_add_asprintf(chunks,
+ "router %s %s %d 0 %d\n"
+ "%s"
+ "%s"
+ "platform %s\n"
+ "%s"
+ "published %s\n"
+ "fingerprint %s\n"
+ "uptime %ld\n"
+ "bandwidth %d %d %d\n"
+ "%s%s"
+ "onion-key\n%s"
+ "signing-key\n%s"
+ "%s%s"
+ "%s%s%s",
+ router->nickname,
+ address,
+ router->or_port,
+ router_should_advertise_dirport(options, router->dir_port),
+ ed_cert_line ? ed_cert_line : "",
+ extra_or_address ? extra_or_address : "",
+ router->platform,
+ proto_line,
+ published,
+ fingerprint,
+ get_uptime(),
+ (int) router->bandwidthrate,
+ (int) router->bandwidthburst,
+ (int) router->bandwidthcapacity,
+ extra_info_line ? extra_info_line : "",
+ (options->DownloadExtraInfo || options->V3AuthoritativeDir) ?
+ "caches-extra-info\n" : "",
+ onion_pkey, identity_pkey,
+ rsa_tap_cc_line ? rsa_tap_cc_line : "",
+ ntor_cc_line ? ntor_cc_line : "",
+ family_line,
+ we_are_hibernating() ? "hibernating 1\n" : "",
+ "hidden-service-dir\n");
+
+ if (options->ContactInfo && strlen(options->ContactInfo)) {
+ const char *ci = options->ContactInfo;
+ if (strchr(ci, '\n') || strchr(ci, '\r'))
+ ci = escaped(ci);
+ smartlist_add_asprintf(chunks, "contact %s\n", ci);
+ }
+
+ if (options->BridgeRelay) {
+ const char *bd;
+ if (options->BridgeDistribution && strlen(options->BridgeDistribution)) {
+ bd = options->BridgeDistribution;
+ } else {
+ bd = "any";
+ }
+ if (strchr(bd, '\n') || strchr(bd, '\r'))
+ bd = escaped(bd);
+ smartlist_add_asprintf(chunks, "bridge-distribution-request %s\n", bd);
+ }
+
+ if (router->onion_curve25519_pkey) {
+ char kbuf[128];
+ base64_encode(kbuf, sizeof(kbuf),
+ (const char *)router->onion_curve25519_pkey->public_key,
+ CURVE25519_PUBKEY_LEN, BASE64_ENCODE_MULTILINE);
+ smartlist_add_asprintf(chunks, "ntor-onion-key %s", kbuf);
+ } else {
+ /* Authorities will start rejecting relays without ntor keys in 0.2.9 */
+ log_err(LD_BUG, "A relay must have an ntor onion key");
+ goto err;
+ }
+
+ /* Write the exit policy to the end of 's'. */
+ if (!router->exit_policy || !smartlist_len(router->exit_policy)) {
+ smartlist_add_strdup(chunks, "reject *:*\n");
+ } else if (router->exit_policy) {
+ char *exit_policy = router_dump_exit_policy_to_string(router,1,0);
+
+ if (!exit_policy)
+ goto err;
+
+ smartlist_add_asprintf(chunks, "%s\n", exit_policy);
+ tor_free(exit_policy);
+ }
+
+ if (router->ipv6_exit_policy) {
+ char *p6 = write_short_policy(router->ipv6_exit_policy);
+ if (p6 && strcmp(p6, "reject 1-65535")) {
+ smartlist_add_asprintf(chunks,
+ "ipv6-policy %s\n", p6);
+ }
+ tor_free(p6);
+ }
+
+ if (router_should_advertise_begindir(options,
+ router->supports_tunnelled_dir_requests)) {
+ smartlist_add_strdup(chunks, "tunnelled-dir-server\n");
+ }
+
+ /* Sign the descriptor with Ed25519 */
+ if (emit_ed_sigs) {
+ smartlist_add_strdup(chunks, "router-sig-ed25519 ");
+ crypto_digest_smartlist_prefix(digest, DIGEST256_LEN,
+ ED_DESC_SIGNATURE_PREFIX,
+ chunks, "", DIGEST_SHA256);
+ ed25519_signature_t sig;
+ char buf[ED25519_SIG_BASE64_LEN+1];
+ if (ed25519_sign(&sig, (const uint8_t*)digest, DIGEST256_LEN,
+ signing_keypair) < 0)
+ goto err;
+ if (ed25519_signature_to_base64(buf, &sig) < 0)
+ goto err;
+
+ smartlist_add_asprintf(chunks, "%s\n", buf);
+ }
+
+ /* Sign the descriptor with RSA */
+ smartlist_add_strdup(chunks, "router-signature\n");
+
+ crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1);
+
+ {
+ char *sig;
+ if (!(sig = router_get_dirobj_signature(digest, DIGEST_LEN, ident_key))) {
+ log_warn(LD_BUG, "Couldn't sign router descriptor");
+ goto err;
+ }
+ smartlist_add(chunks, sig);
+ }
+
+ /* include a last '\n' */
+ smartlist_add_strdup(chunks, "\n");
+
+ output = smartlist_join_strings(chunks, "", 0, NULL);
+
+#ifdef DEBUG_ROUTER_DUMP_ROUTER_TO_STRING
+ {
+ char *s_dup;
+ const char *cp;
+ routerinfo_t *ri_tmp;
+ cp = s_dup = tor_strdup(output);
+ ri_tmp = router_parse_entry_from_string(cp, NULL, 1, 0, NULL, NULL);
+ if (!ri_tmp) {
+ log_err(LD_BUG,
+ "We just generated a router descriptor we can't parse.");
+ log_err(LD_BUG, "Descriptor was: <<%s>>", output);
+ goto err;
+ }
+ tor_free(s_dup);
+ routerinfo_free(ri_tmp);
+ }
+#endif /* defined(DEBUG_ROUTER_DUMP_ROUTER_TO_STRING) */
+
+ goto done;
+
+ err:
+ tor_free(output); /* sets output to NULL */
+ done:
+ if (chunks) {
+ SMARTLIST_FOREACH(chunks, char *, cp, tor_free(cp));
+ smartlist_free(chunks);
+ }
+ crypto_pk_free(rsa_pubkey);
+ tor_free(address);
+ tor_free(family_line);
+ tor_free(onion_pkey);
+ tor_free(identity_pkey);
+ tor_free(extra_or_address);
+ tor_free(ed_cert_line);
+ tor_free(rsa_tap_cc_line);
+ tor_free(ntor_cc_line);
+ tor_free(extra_info_line);
+ tor_free(proto_line);
+
+ return output;
+}
+
+/**
+ * OR only: Given <b>router</b>, produce a string with its exit policy.
+ * If <b>include_ipv4</b> is true, include IPv4 entries.
+ * If <b>include_ipv6</b> is true, include IPv6 entries.
+ */
+char *
+router_dump_exit_policy_to_string(const routerinfo_t *router,
+ int include_ipv4,
+ int include_ipv6)
+{
+ if ((!router->exit_policy) || (router->policy_is_reject_star)) {
+ return tor_strdup("reject *:*");
+ }
+
+ return policy_dump_to_string(router->exit_policy,
+ include_ipv4,
+ include_ipv6);
+}
+
+/** Load the contents of <b>filename</b>, find the last line starting with
+ * <b>end_line</b>, ensure that its timestamp is not more than 25 hours in
+ * the past or more than 1 hour in the future with respect to <b>now</b>,
+ * and write the file contents starting with that line to *<b>out</b>.
+ * Return 1 for success, 0 if the file does not exist or is empty, or -1
+ * if the file does not contain a line matching these criteria or other
+ * failure. */
+static int
+load_stats_file(const char *filename, const char *end_line, time_t now,
+ char **out)
+{
+ int r = -1;
+ char *fname = get_datadir_fname(filename);
+ char *contents, *start = NULL, *tmp, timestr[ISO_TIME_LEN+1];
+ time_t written;
+ switch (file_status(fname)) {
+ case FN_FILE:
+ /* X022 Find an alternative to reading the whole file to memory. */
+ if ((contents = read_file_to_str(fname, 0, NULL))) {
+ tmp = strstr(contents, end_line);
+ /* Find last block starting with end_line */
+ while (tmp) {
+ start = tmp;
+ tmp = strstr(tmp + 1, end_line);
+ }
+ if (!start)
+ goto notfound;
+ if (strlen(start) < strlen(end_line) + 1 + sizeof(timestr))
+ goto notfound;
+ strlcpy(timestr, start + 1 + strlen(end_line), sizeof(timestr));
+ if (parse_iso_time(timestr, &written) < 0)
+ goto notfound;
+ if (written < now - (25*60*60) || written > now + (1*60*60))
+ goto notfound;
+ *out = tor_strdup(start);
+ r = 1;
+ }
+ notfound:
+ tor_free(contents);
+ break;
+ /* treat empty stats files as if the file doesn't exist */
+ case FN_NOENT:
+ case FN_EMPTY:
+ r = 0;
+ break;
+ case FN_ERROR:
+ case FN_DIR:
+ default:
+ break;
+ }
+ tor_free(fname);
+ return r;
+}
+
+/** Write the contents of <b>extrainfo</b> and aggregated statistics to
+ * *<b>s_out</b>, signing them with <b>ident_key</b>. Return 0 on
+ * success, negative on failure. */
+int
+extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo,
+ crypto_pk_t *ident_key,
+ const ed25519_keypair_t *signing_keypair)
+{
+ const or_options_t *options = get_options();
+ char identity[HEX_DIGEST_LEN+1];
+ char published[ISO_TIME_LEN+1];
+ char digest[DIGEST_LEN];
+ char *bandwidth_usage;
+ int result;
+ static int write_stats_to_extrainfo = 1;
+ char sig[DIROBJ_MAX_SIG_LEN+1];
+ char *s = NULL, *pre, *contents, *cp, *s_dup = NULL;
+ time_t now = time(NULL);
+ smartlist_t *chunks = smartlist_new();
+ extrainfo_t *ei_tmp = NULL;
+ const int emit_ed_sigs = signing_keypair &&
+ extrainfo->cache_info.signing_key_cert;
+ char *ed_cert_line = NULL;
+
+ base16_encode(identity, sizeof(identity),
+ extrainfo->cache_info.identity_digest, DIGEST_LEN);
+ format_iso_time(published, extrainfo->cache_info.published_on);
+ bandwidth_usage = rep_hist_get_bandwidth_lines();
+ if (emit_ed_sigs) {
+ if (!extrainfo->cache_info.signing_key_cert->signing_key_included ||
+ !ed25519_pubkey_eq(&extrainfo->cache_info.signing_key_cert->signed_key,
+ &signing_keypair->pubkey)) {
+ log_warn(LD_BUG, "Tried to sign a extrainfo descriptor with a "
+ "mismatched ed25519 key chain %d",
+ extrainfo->cache_info.signing_key_cert->signing_key_included);
+ goto err;
+ }
+ char ed_cert_base64[256];
+ if (base64_encode(ed_cert_base64, sizeof(ed_cert_base64),
+ (const char*)extrainfo->cache_info.signing_key_cert->encoded,
+ extrainfo->cache_info.signing_key_cert->encoded_len,
+ BASE64_ENCODE_MULTILINE) < 0) {
+ log_err(LD_BUG,"Couldn't base64-encode signing key certificate!");
+ goto err;
+ }
+ tor_asprintf(&ed_cert_line, "identity-ed25519\n"
+ "-----BEGIN ED25519 CERT-----\n"
+ "%s"
+ "-----END ED25519 CERT-----\n", ed_cert_base64);
+ } else {
+ ed_cert_line = tor_strdup("");
+ }
+
+ tor_asprintf(&pre, "extra-info %s %s\n%spublished %s\n%s",
+ extrainfo->nickname, identity,
+ ed_cert_line,
+ published, bandwidth_usage);
+ smartlist_add(chunks, pre);
+
+ if (geoip_is_loaded(AF_INET))
+ smartlist_add_asprintf(chunks, "geoip-db-digest %s\n",
+ geoip_db_digest(AF_INET));
+ if (geoip_is_loaded(AF_INET6))
+ smartlist_add_asprintf(chunks, "geoip6-db-digest %s\n",
+ geoip_db_digest(AF_INET6));
+
+ if (options->ExtraInfoStatistics && write_stats_to_extrainfo) {
+ log_info(LD_GENERAL, "Adding stats to extra-info descriptor.");
+ if (options->DirReqStatistics &&
+ load_stats_file("stats"PATH_SEPARATOR"dirreq-stats",
+ "dirreq-stats-end", now, &contents) > 0) {
+ smartlist_add(chunks, contents);
+ }
+ if (options->HiddenServiceStatistics &&
+ load_stats_file("stats"PATH_SEPARATOR"hidserv-stats",
+ "hidserv-stats-end", now, &contents) > 0) {
+ smartlist_add(chunks, contents);
+ }
+ if (options->EntryStatistics &&
+ load_stats_file("stats"PATH_SEPARATOR"entry-stats",
+ "entry-stats-end", now, &contents) > 0) {
+ smartlist_add(chunks, contents);
+ }
+ if (options->CellStatistics &&
+ load_stats_file("stats"PATH_SEPARATOR"buffer-stats",
+ "cell-stats-end", now, &contents) > 0) {
+ smartlist_add(chunks, contents);
+ }
+ if (options->ExitPortStatistics &&
+ load_stats_file("stats"PATH_SEPARATOR"exit-stats",
+ "exit-stats-end", now, &contents) > 0) {
+ smartlist_add(chunks, contents);
+ }
+ if (options->ConnDirectionStatistics &&
+ load_stats_file("stats"PATH_SEPARATOR"conn-stats",
+ "conn-bi-direct", now, &contents) > 0) {
+ smartlist_add(chunks, contents);
+ }
- }
-
- if (options->PaddingStatistics) {
- contents = rep_hist_get_padding_count_lines();
- if (contents)
- smartlist_add(chunks, contents);
++ if (options->PaddingStatistics) {
++ contents = rep_hist_get_padding_count_lines();
++ if (contents)
++ smartlist_add(chunks, contents);
++ }
+ }
+
+ /* Add information about the pluggable transports we support. */
+ if (options->ServerTransportPlugin) {
+ char *pluggable_transports = pt_get_extra_info_descriptor_string();
+ if (pluggable_transports)
+ smartlist_add(chunks, pluggable_transports);
+ }
+
+ if (should_record_bridge_info(options) && write_stats_to_extrainfo) {
+ const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now);
+ if (bridge_stats) {
+ smartlist_add_strdup(chunks, bridge_stats);
+ }
+ }
+
+ if (emit_ed_sigs) {
+ char sha256_digest[DIGEST256_LEN];
+ smartlist_add_strdup(chunks, "router-sig-ed25519 ");
+ crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN,
+ ED_DESC_SIGNATURE_PREFIX,
+ chunks, "", DIGEST_SHA256);
+ ed25519_signature_t ed_sig;
+ char buf[ED25519_SIG_BASE64_LEN+1];
+ if (ed25519_sign(&ed_sig, (const uint8_t*)sha256_digest, DIGEST256_LEN,
+ signing_keypair) < 0)
+ goto err;
+ if (ed25519_signature_to_base64(buf, &ed_sig) < 0)
+ goto err;
+
+ smartlist_add_asprintf(chunks, "%s\n", buf);
+ }
+
+ smartlist_add_strdup(chunks, "router-signature\n");
+ s = smartlist_join_strings(chunks, "", 0, NULL);
+
+ while (strlen(s) > MAX_EXTRAINFO_UPLOAD_SIZE - DIROBJ_MAX_SIG_LEN) {
+ /* So long as there are at least two chunks (one for the initial
+ * extra-info line and one for the router-signature), we can keep removing
+ * things. */
+ if (smartlist_len(chunks) > 2) {
+ /* We remove the next-to-last element (remember, len-1 is the last
+ element), since we need to keep the router-signature element. */
+ int idx = smartlist_len(chunks) - 2;
+ char *e = smartlist_get(chunks, idx);
+ smartlist_del_keeporder(chunks, idx);
+ log_warn(LD_GENERAL, "We just generated an extra-info descriptor "
+ "with statistics that exceeds the 50 KB "
+ "upload limit. Removing last added "
+ "statistics.");
+ tor_free(e);
+ tor_free(s);
+ s = smartlist_join_strings(chunks, "", 0, NULL);
+ } else {
+ log_warn(LD_BUG, "We just generated an extra-info descriptors that "
+ "exceeds the 50 KB upload limit.");
+ goto err;
+ }
+ }
+
+ memset(sig, 0, sizeof(sig));
+ if (router_get_extrainfo_hash(s, strlen(s), digest) < 0 ||
+ router_append_dirobj_signature(sig, sizeof(sig), digest, DIGEST_LEN,
+ ident_key) < 0) {
+ log_warn(LD_BUG, "Could not append signature to extra-info "
+ "descriptor.");
+ goto err;
+ }
+ smartlist_add_strdup(chunks, sig);
+ tor_free(s);
+ s = smartlist_join_strings(chunks, "", 0, NULL);
+
+ cp = s_dup = tor_strdup(s);
+ ei_tmp = extrainfo_parse_entry_from_string(cp, NULL, 1, NULL, NULL);
+ if (!ei_tmp) {
+ if (write_stats_to_extrainfo) {
+ log_warn(LD_GENERAL, "We just generated an extra-info descriptor "
+ "with statistics that we can't parse. Not "
+ "adding statistics to this or any future "
+ "extra-info descriptors.");
+ write_stats_to_extrainfo = 0;
+ result = extrainfo_dump_to_string(s_out, extrainfo, ident_key,
+ signing_keypair);
+ goto done;
+ } else {
+ log_warn(LD_BUG, "We just generated an extrainfo descriptor we "
+ "can't parse.");
+ goto err;
+ }
+ }
+
+ *s_out = s;
+ s = NULL; /* prevent free */
+ result = 0;
+ goto done;
+
+ err:
+ result = -1;
+
+ done:
+ tor_free(s);
+ SMARTLIST_FOREACH(chunks, char *, chunk, tor_free(chunk));
+ smartlist_free(chunks);
+ tor_free(s_dup);
+ tor_free(ed_cert_line);
+ extrainfo_free(ei_tmp);
+ tor_free(bandwidth_usage);
+
+ return result;
+}
+
+/** Forget that we have issued any router-related warnings, so that we'll
+ * warn again if we see the same errors. */
+void
+router_reset_warnings(void)
+{
+ if (warned_nonexistent_family) {
+ SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp));
+ smartlist_clear(warned_nonexistent_family);
+ }
+}
+
+/** Release all static resources held in router.c */
+void
+router_free_all(void)
+{
+ crypto_pk_free(onionkey);
+ crypto_pk_free(lastonionkey);
+ crypto_pk_free(server_identitykey);
+ crypto_pk_free(client_identitykey);
+
+ tor_mutex_free(key_lock);
+ routerinfo_free(desc_routerinfo);
+ extrainfo_free(desc_extrainfo);
+ crypto_pk_free(authority_signing_key);
+ authority_cert_free(authority_key_certificate);
+ crypto_pk_free(legacy_signing_key);
+ authority_cert_free(legacy_key_certificate);
+
+ memwipe(&curve25519_onion_key, 0, sizeof(curve25519_onion_key));
+ memwipe(&last_curve25519_onion_key, 0, sizeof(last_curve25519_onion_key));
+
+ if (warned_nonexistent_family) {
+ SMARTLIST_FOREACH(warned_nonexistent_family, char *, cp, tor_free(cp));
+ smartlist_free(warned_nonexistent_family);
+ }
+}
+/* From the given RSA key object, convert it to ASN-1 encoded format and set
+ * the newly allocated object in onion_pkey_out. The length of the key is set
+ * in onion_pkey_len_out. */
+void
+router_set_rsa_onion_pkey(const crypto_pk_t *pk, char **onion_pkey_out,
+ size_t *onion_pkey_len_out)
+{
+ int len;
+ char buf[1024];
+
+ tor_assert(pk);
+ tor_assert(onion_pkey_out);
+ tor_assert(onion_pkey_len_out);
+
+ len = crypto_pk_asn1_encode(pk, buf, sizeof(buf));
+ if (BUG(len < 0)) {
+ goto done;
+ }
+
+ *onion_pkey_out = tor_memdup(buf, len);
+ *onion_pkey_len_out = len;
+
+ done:
+ return;
+}
+
+/* From an ASN-1 encoded onion pkey, return a newly allocated RSA key object.
+ * It is the caller responsability to free the returned object.
+ *
+ * Return NULL if the pkey is NULL, malformed or if the length is 0. */
+crypto_pk_t *
+router_get_rsa_onion_pkey(const char *pkey, size_t pkey_len)
+{
+ if (!pkey || pkey_len == 0) {
+ return NULL;
+ }
+ return crypto_pk_asn1_decode(pkey, pkey_len);
+}
1
0

19 Apr '19
commit e8b26bb71b3465bd770f6ecff5be1d4e9a26d460
Merge: 3444371dd 24b68b477
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 12:00:55 2019 +1000
Merge branch 'maint-0.4.0' into release-0.4.0
1
0

19 Apr '19
commit bf35c6ae7603e23dccde5fe272ffe7a540de0902
Merge: fc3b90b50 231036a11
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 12:00:46 2019 +1000
Merge branch 'maint-0.3.5' into release-0.3.5
changes/bug13221 | 5 +++++
changes/bug27199 | 3 +++
changes/bug28698 | 3 +++
changes/bug29017 | 4 ++++
changes/bug29144 | 5 +++++
changes/bug29665 | 7 +++++++
doc/tor.1.txt | 2 +-
src/app/config/config.c | 8 ++++----
src/core/mainloop/connection.c | 2 +-
src/core/or/circuituse.c | 34 ++++++++++++++++++++++------------
src/feature/relay/router.c | 11 +++++------
src/rust/Cargo.toml | 11 +++++++++++
12 files changed, 71 insertions(+), 24 deletions(-)
1
0

19 Apr '19
commit cbce8dedd3703424111928d68dfe8920900ce054
Merge: 116970dda 2460b4461
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 11:59:18 2019 +1000
Merge branch 'maint-0.2.9' into maint-0.3.4
"ours" merge, to avoid taking any changes from PR 792 in 0.3.4.
(We already merged PR 791 for 29665 into 0.3.4.)
1
0

[tor/release-0.4.0] Merge remote-tracking branch 'tor-github/pr/726' into maint-0.3.5
by teor@torproject.org 19 Apr '19
by teor@torproject.org 19 Apr '19
19 Apr '19
commit cb084de5e5dddee69abd55e3f4ed3a5ce9ebc9a2
Merge: 2ae67fee4 1b9e77349
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 11:51:05 2019 +1000
Merge remote-tracking branch 'tor-github/pr/726' into maint-0.3.5
changes/bug13221 | 5 +++++
src/app/config/config.c | 8 ++++----
2 files changed, 9 insertions(+), 4 deletions(-)
diff --cc src/app/config/config.c
index e06c77e43,000000000..2a504d306
mode 100644,000000..100644
--- a/src/app/config/config.c
+++ b/src/app/config/config.c
@@@ -1,8507 -1,0 +1,8507 @@@
+/* 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))) {
+ /* Make sure update_router_have_minimum_dir_info() gets called. */
+ router_dir_info_changed();
+ /* We might need to download a new consensus status later or sooner than
+ * we had expected. */
+ update_consensus_networkstatus_fetch_time(time(NULL));
+ }
+ }
+
+ /* DoS mitigation subsystem only applies to public relay. */
+ if (public_server_mode(options)) {
+ /* If we are configured as a relay, initialize the subsystem. Even on HUP,
+ * this is safe to call as it will load data from the current options
+ * or/and the consensus. */
+ dos_init();
+ } else if (old_options && public_server_mode(old_options)) {
+ /* Going from relay to non relay, clean it up. */
+ dos_free_all();
+ }
+
+ /* Load the webpage we're going to serve every time someone asks for '/' on
+ our DirPort. */
+ tor_free(global_dirfrontpagecontents);
+ if (options->DirPortFrontPage) {
+ global_dirfrontpagecontents =
+ read_file_to_str(options->DirPortFrontPage, 0, NULL);
+ if (!global_dirfrontpagecontents) {
+ log_warn(LD_CONFIG,
+ "DirPortFrontPage file '%s' not found. Continuing anyway.",
+ options->DirPortFrontPage);
+ }
+ }
+
+ return 0;
+}
+
+typedef enum {
+ TAKES_NO_ARGUMENT = 0,
+ ARGUMENT_NECESSARY = 1,
+ ARGUMENT_OPTIONAL = 2
+} takes_argument_t;
+
+static const struct {
+ const char *name;
+ takes_argument_t takes_argument;
+} CMDLINE_ONLY_OPTIONS[] = {
+ { "-f", ARGUMENT_NECESSARY },
+ { "--allow-missing-torrc", TAKES_NO_ARGUMENT },
+ { "--defaults-torrc", ARGUMENT_NECESSARY },
+ { "--hash-password", ARGUMENT_NECESSARY },
+ { "--dump-config", ARGUMENT_OPTIONAL },
+ { "--list-fingerprint", TAKES_NO_ARGUMENT },
+ { "--keygen", TAKES_NO_ARGUMENT },
+ { "--key-expiration", ARGUMENT_OPTIONAL },
+ { "--newpass", TAKES_NO_ARGUMENT },
+ { "--no-passphrase", TAKES_NO_ARGUMENT },
+ { "--passphrase-fd", ARGUMENT_NECESSARY },
+ { "--verify-config", TAKES_NO_ARGUMENT },
+ { "--ignore-missing-torrc", TAKES_NO_ARGUMENT },
+ { "--quiet", TAKES_NO_ARGUMENT },
+ { "--hush", TAKES_NO_ARGUMENT },
+ { "--version", TAKES_NO_ARGUMENT },
+ { "--library-versions", TAKES_NO_ARGUMENT },
+ { "-h", TAKES_NO_ARGUMENT },
+ { "--help", TAKES_NO_ARGUMENT },
+ { "--list-torrc-options", TAKES_NO_ARGUMENT },
+ { "--list-deprecated-options",TAKES_NO_ARGUMENT },
+ { "--nt-service", TAKES_NO_ARGUMENT },
+ { "-nt-service", TAKES_NO_ARGUMENT },
+ { NULL, 0 },
+};
+
+/** Helper: Read a list of configuration options from the command line. If
+ * successful, or if ignore_errors is set, put them in *<b>result</b>, put the
+ * commandline-only options in *<b>cmdline_result</b>, and return 0;
+ * otherwise, return -1 and leave *<b>result</b> and <b>cmdline_result</b>
+ * alone. */
+int
+config_parse_commandline(int argc, char **argv, int ignore_errors,
+ config_line_t **result,
+ config_line_t **cmdline_result)
+{
+ config_line_t *param = NULL;
+
+ config_line_t *front = NULL;
+ config_line_t **new = &front;
+
+ config_line_t *front_cmdline = NULL;
+ config_line_t **new_cmdline = &front_cmdline;
+
+ char *s, *arg;
+ int i = 1;
+
+ while (i < argc) {
+ unsigned command = CONFIG_LINE_NORMAL;
+ takes_argument_t want_arg = ARGUMENT_NECESSARY;
+ int is_cmdline = 0;
+ int j;
+
+ for (j = 0; CMDLINE_ONLY_OPTIONS[j].name != NULL; ++j) {
+ if (!strcmp(argv[i], CMDLINE_ONLY_OPTIONS[j].name)) {
+ is_cmdline = 1;
+ want_arg = CMDLINE_ONLY_OPTIONS[j].takes_argument;
+ break;
+ }
+ }
+
+ s = argv[i];
+
+ /* Each keyword may be prefixed with one or two dashes. */
+ if (*s == '-')
+ s++;
+ if (*s == '-')
+ s++;
+ /* Figure out the command, if any. */
+ if (*s == '+') {
+ s++;
+ command = CONFIG_LINE_APPEND;
+ } else if (*s == '/') {
+ s++;
+ command = CONFIG_LINE_CLEAR;
+ /* A 'clear' command has no argument. */
+ want_arg = 0;
+ }
+
+ const int is_last = (i == argc-1);
+
+ if (want_arg == ARGUMENT_NECESSARY && is_last) {
+ if (ignore_errors) {
+ arg = tor_strdup("");
+ } else {
+ log_warn(LD_CONFIG,"Command-line option '%s' with no value. Failing.",
+ argv[i]);
+ config_free_lines(front);
+ config_free_lines(front_cmdline);
+ return -1;
+ }
+ } else if (want_arg == ARGUMENT_OPTIONAL && is_last) {
+ arg = tor_strdup("");
+ } else {
+ arg = (want_arg != TAKES_NO_ARGUMENT) ? tor_strdup(argv[i+1]) :
+ tor_strdup("");
+ }
+
+ param = tor_malloc_zero(sizeof(config_line_t));
+ param->key = is_cmdline ? tor_strdup(argv[i]) :
+ tor_strdup(config_expand_abbrev(&options_format, s, 1, 1));
+ param->value = arg;
+ param->command = command;
+ param->next = NULL;
+ log_debug(LD_CONFIG, "command line: parsed keyword '%s', value '%s'",
+ param->key, param->value);
+
+ if (is_cmdline) {
+ *new_cmdline = param;
+ new_cmdline = &((*new_cmdline)->next);
+ } else {
+ *new = param;
+ new = &((*new)->next);
+ }
+
+ i += want_arg ? 2 : 1;
+ }
+ *cmdline_result = front_cmdline;
+ *result = front;
+ return 0;
+}
+
+/** Return true iff key is a valid configuration option. */
+int
+option_is_recognized(const char *key)
+{
+ const config_var_t *var = config_find_option(&options_format, key);
+ return (var != NULL);
+}
+
+/** Return the canonical name of a configuration option, or NULL
+ * if no such option exists. */
+const char *
+option_get_canonical_name(const char *key)
+{
+ const config_var_t *var = config_find_option(&options_format, key);
+ return var ? var->name : NULL;
+}
+
+/** Return a canonical list of the options assigned for key.
+ */
+config_line_t *
+option_get_assignment(const or_options_t *options, const char *key)
+{
+ return config_get_assigned_option(&options_format, options, key, 1);
+}
+
+/** Try assigning <b>list</b> to the global options. You do this by duping
+ * options, assigning list to the new one, then validating it. If it's
+ * ok, then throw out the old one and stick with the new one. Else,
+ * revert to old and return failure. Return SETOPT_OK on success, or
+ * a setopt_err_t on failure.
+ *
+ * If not success, point *<b>msg</b> to a newly allocated string describing
+ * what went wrong.
+ */
+setopt_err_t
+options_trial_assign(config_line_t *list, unsigned flags, char **msg)
+{
+ int r;
+ or_options_t *trial_options = config_dup(&options_format, get_options());
+
+ if ((r=config_assign(&options_format, trial_options,
+ list, flags, msg)) < 0) {
+ or_options_free(trial_options);
+ return r;
+ }
+
+ setopt_err_t rv;
+ or_options_t *cur_options = get_options_mutable();
+
+ in_option_validation = 1;
+
+ if (options_validate(cur_options, trial_options,
+ global_default_options, 1, msg) < 0) {
+ or_options_free(trial_options);
+ rv = SETOPT_ERR_PARSE; /*XXX make this a separate return value. */
+ goto done;
+ }
+
+ if (options_transition_allowed(cur_options, trial_options, msg) < 0) {
+ or_options_free(trial_options);
+ rv = SETOPT_ERR_TRANSITION;
+ goto done;
+ }
+ in_option_validation = 0;
+
+ if (set_options(trial_options, msg)<0) {
+ or_options_free(trial_options);
+ rv = SETOPT_ERR_SETTING;
+ goto done;
+ }
+
+ /* we liked it. put it in place. */
+ rv = SETOPT_OK;
+ done:
+ in_option_validation = 0;
+ return rv;
+}
+
+/** Print a usage message for tor. */
+static void
+print_usage(void)
+{
+ printf(
+"Copyright (c) 2001-2004, Roger Dingledine\n"
+"Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson\n"
+"Copyright (c) 2007-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);
+ }
+}
+
+/** 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, "--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_INET6) {
- log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv6",
++ 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_INET) {
- log_warn(LD_CONFIG, "Could not interpret %sPort address as IPv4",
++ 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);
+}
1
0

[tor/release-0.4.0] Merge remote-tracking branch 'tor-github/pr/638' into maint-0.3.4
by teor@torproject.org 19 Apr '19
by teor@torproject.org 19 Apr '19
19 Apr '19
commit b1762f8d1254a89cd181ecf41db666d9bc75fa34
Merge: 8154b33f9 85c598cbc
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 11:44:38 2019 +1000
Merge remote-tracking branch 'tor-github/pr/638' into maint-0.3.4
changes/bug29017 | 4 ++++
doc/tor.1.txt | 2 +-
src/or/router.c | 11 +++++------
3 files changed, 10 insertions(+), 7 deletions(-)
1
0

[tor/release-0.4.0] Merge remote-tracking branch 'tor-github/pr/745' into maint-0.3.5
by teor@torproject.org 19 Apr '19
by teor@torproject.org 19 Apr '19
19 Apr '19
commit 2ae67fee4244ff3ea1e9f3345f2cfde48cf64435
Merge: 4a66c2d63 389ee834b
Author: teor <teor(a)torproject.org>
Date: Fri Apr 19 11:48:41 2019 +1000
Merge remote-tracking branch 'tor-github/pr/745' into maint-0.3.5
changes/bug29144 | 5 +++++
src/core/mainloop/connection.c | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
1
0