1
//! Configuration logic for tor-ptmgr.
2

            
3
use std::net::SocketAddr;
4

            
5
use derive_deftly::Deftly;
6
use tor_config::ConfigBuildError;
7
use tor_config::derive::prelude::*;
8
use tor_config_path::CfgPath;
9
use tor_linkspec::PtTransportName;
10

            
11
#[cfg(feature = "tor-channel-factory")]
12
use {crate::PtClientMethod, tor_socksproto::SocksVersion};
13

            
14
/// A single pluggable transport.
15
///
16
/// Pluggable transports are programs that transform and obfuscate traffic on
17
/// the network between a Tor client and a Tor bridge, so that an adversary
18
/// cannot recognize it as Tor traffic.
19
///
20
/// A pluggable transport can be either _managed_ (run as an external process
21
/// that we launch and monitor), or _unmanaged_ (running on a local port, not
22
/// controlled by Arti).
23
#[derive(Clone, Debug, Deftly, Eq, PartialEq)]
24
#[derive_deftly(TorConfig)]
25
#[deftly(tor_config(no_default_trait, pre_build = "Self::validate"))]
26
pub struct TransportConfig {
27
    /// Names of the transport protocols that we are willing to use from this transport.
28
    ///
29
    /// (These protocols are arbitrary identifiers that describe which protocols
30
    /// we want. They must match names that the binary knows how to provide.)
31
    //
32
    // NOTE(eta): This doesn't use the list builder stuff, because you're not likely to
33
    //            set this field more than once.
34
    #[deftly(tor_config(no_magic, no_default))]
35
    pub(crate) protocols: Vec<PtTransportName>,
36

            
37
    /// The path to the binary to run, if any.
38
    ///
39
    /// This needs to be the path to some executable file on disk.
40
    ///
41
    /// Present only for managed transports.
42
    #[deftly(tor_config(default, setter(strip_option)))]
43
    pub(crate) path: Option<CfgPath>,
44

            
45
    /// One or more command-line arguments to pass to the binary.
46
    ///
47
    /// Meaningful only for managed transports.
48
    // TODO: Should this be OsString? That's a pain to parse...
49
    //
50
    // NOTE(eta): This doesn't use the list builder stuff, because you're not likely to
51
    //            set this field more than once.
52
    #[deftly(tor_config(no_magic, default))]
53
    pub(crate) arguments: Vec<String>,
54

            
55
    /// The location at which to contact this transport.
56
    ///
57
    /// Present only for unmanaged transports.
58
    #[deftly(tor_config(default, setter(strip_option)))]
59
    pub(crate) proxy_addr: Option<SocketAddr>,
60

            
61
    /// If true, launch this transport on startup.  Otherwise, we launch
62
    /// it on demand.
63
    ///
64
    /// Meaningful only for managed transports.
65
    #[deftly(tor_config(default))]
66
    pub(crate) run_on_startup: bool,
67
}
68

            
69
impl TransportConfigBuilder {
70
    /// Inspect the list of protocols (ie, transport names)
71
    ///
72
    /// If none have yet been specified, returns an empty list.
73
175
    pub fn get_protocols(&self) -> &[PtTransportName] {
74
175
        self.protocols.as_deref().unwrap_or_default()
75
175
    }
76

            
77
    /// Make sure that this builder is internally consistent.
78
420
    fn validate(&self) -> Result<(), ConfigBuildError> {
79
        // `path` can only be set if the `managed-pts` feature is enabled
80
        #[cfg(not(feature = "managed-pts"))]
81
        if self.path.is_some() {
82
            return Err(ConfigBuildError::NoCompileTimeSupport {
83
                field: "path".into(),
84
                problem:
85
                    "Indicates a managed transport, but support is not enabled by cargo features"
86
                        .into(),
87
            });
88
        }
89

            
90
420
        match (&self.path, &self.proxy_addr) {
91
35
            (Some(_), Some(_)) => Err(ConfigBuildError::Inconsistent {
92
35
                fields: vec!["path".into(), "proxy_addr".into()],
93
35
                problem: "Cannot provide both path and proxy_addr".into(),
94
35
            }),
95
            // TODO: There is no ConfigBuildError for "one of two fields is missing."
96
            (None, None) => Err(ConfigBuildError::MissingField {
97
                field: "{path or proxy_addr}".into(),
98
            }),
99
            (None, Some(_)) => {
100
105
                if self.arguments.as_ref().is_some_and(|v| !v.is_empty()) {
101
                    Err(ConfigBuildError::Inconsistent {
102
                        fields: vec!["proxy_addr".into(), "arguments".into()],
103
                        problem: "Cannot provide arguments for an unmanaged transport".into(),
104
                    })
105
105
                } else if self.run_on_startup.is_some() {
106
                    Err(ConfigBuildError::Inconsistent {
107
                        fields: vec!["proxy_addr".into(), "run_on_startup".into()],
108
                        problem: "run_on_startup is meaningless for an unmanaged transport".into(),
109
                    })
110
                } else {
111
105
                    Ok(())
112
                }
113
            }
114
280
            (Some(_), None) => Ok(()),
115
        }
116
420
    }
117
}
118

            
119
/// The pluggable transport structure used internally. This is more type-safe than working with
120
/// `TransportConfig` directly, since we can't change `TransportConfig` as it's part of the public
121
/// API.
122
#[derive(Clone, Debug, Eq, PartialEq)]
123
pub(crate) enum TransportOptions {
124
    /// Options for a managed PT transport.
125
    #[cfg(feature = "managed-pts")]
126
    Managed(ManagedTransportOptions),
127
    /// Options for an unmanaged PT transport.
128
    Unmanaged(UnmanagedTransportOptions),
129
}
130

            
131
impl TryFrom<TransportConfig> for TransportOptions {
132
    type Error = tor_error::Bug;
133
    fn try_from(config: TransportConfig) -> Result<Self, Self::Error> {
134
        // We rely on the validation performed in `TransportConfigBuilder::validate` to ensure that
135
        // mutually exclusive options were not set. We could do validation again here, but it would
136
        // be error-prone to duplicate the validation logic. We also couldn't check things like if
137
        // `run_on_startup` was `Some`/`None`, since that's only available to the builder.
138

            
139
        if let Some(path) = config.path {
140
            cfg_if::cfg_if! {
141
                if #[cfg(feature = "managed-pts")] {
142
                    Ok(TransportOptions::Managed(ManagedTransportOptions {
143
                        protocols: config.protocols,
144
                        path,
145
                        arguments: config.arguments,
146
                        run_on_startup: config.run_on_startup,
147
                    }))
148
                } else {
149
                    let _ = path;
150
                    Err(tor_error::internal!(
151
                        "Path is set but 'managed-pts' feature is not enabled. How did this pass builder validation?"
152
                    ))
153
                }
154
            }
155
        } else if let Some(proxy_addr) = config.proxy_addr {
156
            Ok(TransportOptions::Unmanaged(UnmanagedTransportOptions {
157
                protocols: config.protocols,
158
                proxy_addr,
159
            }))
160
        } else {
161
            Err(tor_error::internal!(
162
                "Neither path nor proxy are set. How did this pass builder validation?"
163
            ))
164
        }
165
    }
166
}
167

            
168
/// A pluggable transport that is run as an external process that we launch and monitor.
169
#[cfg(feature = "managed-pts")]
170
#[derive(Clone, Debug, Eq, PartialEq)]
171
pub(crate) struct ManagedTransportOptions {
172
    /// See [TransportConfig::protocols].
173
    pub(crate) protocols: Vec<PtTransportName>,
174

            
175
    /// See [TransportConfig::path].
176
    pub(crate) path: CfgPath,
177

            
178
    /// See [TransportConfig::arguments].
179
    pub(crate) arguments: Vec<String>,
180

            
181
    /// See [TransportConfig::run_on_startup].
182
    pub(crate) run_on_startup: bool,
183
}
184

            
185
/// A pluggable transport running on a local port, not controlled by Arti.
186
#[derive(Clone, Debug, Eq, PartialEq)]
187
pub(crate) struct UnmanagedTransportOptions {
188
    /// See [TransportConfig::protocols].
189
    pub(crate) protocols: Vec<PtTransportName>,
190

            
191
    /// See [TransportConfig::proxy_addr].
192
    pub(crate) proxy_addr: SocketAddr,
193
}
194

            
195
impl UnmanagedTransportOptions {
196
    /// A client method that can be used to contact this transport.
197
    #[cfg(feature = "tor-channel-factory")]
198
    pub(crate) fn cmethod(&self) -> PtClientMethod {
199
        PtClientMethod {
200
            // TODO: Someday we might want to support other protocols;
201
            // but for now, let's see if we can get away with just socks5.
202
            kind: SocksVersion::V5,
203
            endpoint: self.proxy_addr,
204
        }
205
    }
206

            
207
    /// Return true if this transport is configured on localhost.
208
    pub(crate) fn is_localhost(&self) -> bool {
209
        self.proxy_addr.ip().is_loopback()
210
    }
211
}