1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! List of directories that ships with Tor, for initial directory
//! operations.
//!
//! When a client doesn't have directory information yet, it uses a
//! "Fallback Directory" to retrieve its initial information about the
//! network.
//!
//! # Semver note
//!
//! The types in this module are re-exported from `arti-client` and
//! `tor-dirmgr`: any changes here must be reflected there.

mod set;

use base64ct::{Base64Unpadded, Encoding as _};
use derive_builder::Builder;
use tor_config::ConfigBuildError;
use tor_config::{define_list_builder_accessors, impl_standard_builder, list_builder::VecBuilder};
use tor_linkspec::{DirectChanMethodsHelper, OwnedChanTarget};
use tor_llcrypto::pk::ed25519::Ed25519Identity;
use tor_llcrypto::pk::rsa::RsaIdentity;

use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

use crate::dirstatus::DirStatus;
pub(crate) use set::FallbackState;
pub use set::{FallbackList, FallbackListBuilder};

/// A directory whose location ships with Tor (or arti), and which we
/// can use for bootstrapping when we don't know anything else about
/// the network.
//
// Note that we do *not* set serde(deny_unknown_fields) on this
// structure: we want our fallback directory configuration format to
// be future-proof against adding new info about each fallback.
#[derive(Debug, Clone, Builder, Eq, PartialEq)]
#[builder(build_fn(private, name = "build_unvalidated", error = "ConfigBuildError"))]
#[builder(derive(Debug, Serialize, Deserialize))]
pub struct FallbackDir {
    /// RSA identity for the directory relay
    rsa_identity: RsaIdentity,
    /// Ed25519 identity for the directory relay
    ed_identity: Ed25519Identity,
    /// List of ORPorts for the directory relay
    #[builder(sub_builder(fn_name = "build"), setter(custom))]
    orports: Vec<SocketAddr>,
}

impl_standard_builder! { FallbackDir: !Default }

define_list_builder_accessors! {
    struct FallbackDirBuilder {
        pub orports: [SocketAddr],
    }
}

impl FallbackDir {
    /// Return a copy of this FallbackDir as a [`FirstHop`](crate::FirstHop)
    pub fn as_guard(&self) -> crate::FirstHop {
        crate::FirstHop {
            sample: None,
            inner: crate::FirstHopInner::Chan(OwnedChanTarget::from_chan_target(self)),
        }
    }
}

impl FallbackDirBuilder {
    /// Make a new FallbackDirBuilder.
    ///
    /// You only need to use this if you're using a non-default set of
    /// fallback directories.
    pub fn new() -> Self {
        Self::default()
    }
    /// Builds a new `FallbackDir`.
    ///
    /// ### Errors
    ///
    /// Errors unless both of `rsa_identity`, `ed_identity`, and at least one `orport`,
    /// have been provided.
    pub fn build(&self) -> std::result::Result<FallbackDir, ConfigBuildError> {
        let built = self.build_unvalidated()?;
        if built.orports.is_empty() {
            return Err(ConfigBuildError::Invalid {
                field: "orport".to_string(),
                problem: "list was empty".to_string(),
            });
        }
        Ok(built)
    }
}

/// Return a list of the default fallback directories shipped with
/// arti.
pub(crate) fn default_fallbacks() -> Vec<FallbackDirBuilder> {
    /// Build a fallback directory; panic if input is bad.
    fn fallback(rsa: &str, ed: &str, ports: &[&str]) -> FallbackDirBuilder {
        let rsa = RsaIdentity::from_hex(rsa).expect("Bad hex in built-in fallback list");
        let ed = Base64Unpadded::decode_vec(ed).expect("Bad hex in built-in fallback list");
        let ed = Ed25519Identity::from_bytes(&ed).expect("Wrong length in built-in fallback list");
        let mut bld = FallbackDir::builder();
        bld.rsa_identity(rsa).ed_identity(ed);

        ports
            .iter()
            .map(|s| s.parse().expect("Bad socket address in fallbacklist"))
            .for_each(|p| {
                bld.orports().push(p);
            });

        bld
    }
    include!("../data/fallback_dirs.rs")
}

impl tor_linkspec::HasAddrs for FallbackDir {
    fn addrs(&self) -> &[SocketAddr] {
        &self.orports[..]
    }
}
impl tor_linkspec::HasRelayIdsLegacy for FallbackDir {
    fn ed_identity(&self) -> &Ed25519Identity {
        &self.ed_identity
    }
    fn rsa_identity(&self) -> &RsaIdentity {
        &self.rsa_identity
    }
}

impl DirectChanMethodsHelper for FallbackDir {}

impl tor_linkspec::ChanTarget for FallbackDir {}