1
//! Connect point types, and the code to parse them and resolve them.
2

            
3
use serde::Deserialize;
4
use std::{fmt::Debug, path::PathBuf, str::FromStr};
5
use tor_config_path::{
6
    CfgPath, CfgPathError, CfgPathResolver,
7
    addr::{CfgAddr, CfgAddrError},
8
};
9
use tor_general_addr::general;
10

            
11
use crate::HasClientErrorAction;
12

            
13
/// A connect point, as deserialized from TOML.
14
///
15
/// Connect points tell an RPC client how to reach an RPC server,
16
/// and tell an RPC server where and how to listen for connections for RPC clients.
17
///
18
/// This type may have members containing symbolic paths, such as
19
/// `${USER_HOME}` or `${ARTI_LOCAL_STATE}`.
20
/// To convert these paths to a usable format,
21
/// invoke [`ParsedConnectPoint::resolve()`] on this object.
22
#[derive(Clone, Debug)]
23
pub struct ParsedConnectPoint(ConnectPointEnum<Unresolved>);
24

            
25
/// A connect point, with all paths resolved.
26
///
27
/// Connect points tell an RPC client how to reach an RPC server,
28
/// and tell an RPC server where and how to listen for connections for RPC clients.
29
///
30
/// This type is returned by [`ParsedConnectPoint::resolve()`],
31
/// and can be used to connect or bind.
32
#[derive(Clone, Debug)]
33
pub struct ResolvedConnectPoint(pub(crate) ConnectPointEnum<Resolved>);
34

            
35
impl ParsedConnectPoint {
36
    /// Try to resolve all symbolic paths in this connect point,
37
    /// using the rules of [`CfgPath`] and [`CfgAddr`].
38
4
    pub fn resolve(
39
4
        &self,
40
4
        resolver: &CfgPathResolver,
41
4
    ) -> Result<ResolvedConnectPoint, ResolveError> {
42
        use ConnectPointEnum as CPE;
43
4
        Ok(ResolvedConnectPoint(match &self.0 {
44
4
            CPE::Connect(connect) => CPE::Connect(connect.resolve(resolver)?),
45
            CPE::Builtin(builtin) => CPE::Builtin(builtin.clone()),
46
        }))
47
4
    }
48
}
49

            
50
impl FromStr for ParsedConnectPoint {
51
    type Err = ParseError;
52

            
53
58
    fn from_str(s: &str) -> Result<Self, Self::Err> {
54
58
        let de: ConnectPointDe = toml::from_str(s).map_err(ParseError::InvalidConnectPoint)?;
55
52
        Ok(ParsedConnectPoint(de.try_into()?))
56
58
    }
57
}
58

            
59
/// A failure from [`ParsedConnectPoint::from_str()`].
60
#[derive(Clone, Debug, thiserror::Error)]
61
#[non_exhaustive]
62
pub enum ParseError {
63
    /// The input was not valid toml, or was an invalid connect point.
64
    #[error("Invalid connect point")]
65
    InvalidConnectPoint(#[source] toml::de::Error),
66
    /// The input had sections or members
67
    /// that are not allowed to appear in the same connect point.
68
    #[error("Conflicting members in connect point")]
69
    ConflictingMembers,
70
    /// The input was valid toml, but did not have any recognized
71
    /// connect point section.
72
    #[error("Unrecognized format on connect point")]
73
    UnrecognizedFormat,
74
}
75
impl HasClientErrorAction for ParseError {
76
    fn client_action(&self) -> crate::ClientErrorAction {
77
        use crate::ClientErrorAction as A;
78
        match self {
79
            ParseError::InvalidConnectPoint(_) => A::Abort,
80
            ParseError::ConflictingMembers => A::Abort,
81
            ParseError::UnrecognizedFormat => A::Decline,
82
        }
83
    }
84
}
85

            
86
/// A failure from [`ParsedConnectPoint::resolve()`].
87
#[derive(Clone, Debug, thiserror::Error)]
88
#[non_exhaustive]
89
pub enum ResolveError {
90
    /// There was a path in the connect point that we couldn't resolve.
91
    #[error("Unable to resolve variables in path")]
92
    InvalidPath(#[from] CfgPathError),
93
    /// There was an address in the connect point that we couldn't resolve.
94
    #[error("Unable to resolve variables in address")]
95
    InvalidAddr(#[from] CfgAddrError),
96
    /// After substitution, we couldn't expand the path to a string.
97
    #[error("Cannot represent expanded path as string")]
98
    PathNotString,
99
    /// Address is not a loopback address.
100
    #[error("Tried to bind or connect to a non-loopback TCP address")]
101
    AddressNotLoopback,
102
    /// Authorization mechanism not compatible with address family
103
    #[error("Authorization type not compatible with address family")]
104
    AuthNotCompatible,
105
    /// Authorization mechanism not recognized
106
    #[error("Authorization type not recognized as a supported type")]
107
    AuthNotRecognized,
108
    /// Address type not supported by the RPC connect point subsystem.
109
    ///
110
    /// (This can only happen if somebody adds new variants to `general::SocketAddr`.)
111
    #[error("Address type not recognized")]
112
    AddressTypeNotRecognized,
113
    /// The name of a file or AF_UNIX socket address was a relative path.
114
    #[error("Path was not absolute")]
115
    PathNotAbsolute,
116
}
117
impl HasClientErrorAction for ResolveError {
118
    fn client_action(&self) -> crate::ClientErrorAction {
119
        use crate::ClientErrorAction as A;
120
        match self {
121
            ResolveError::InvalidPath(e) => e.client_action(),
122
            ResolveError::InvalidAddr(e) => e.client_action(),
123
            ResolveError::PathNotString => A::Decline,
124
            ResolveError::AddressNotLoopback => A::Decline,
125
            ResolveError::AuthNotCompatible => A::Abort,
126
            ResolveError::AuthNotRecognized => A::Decline,
127
            ResolveError::AddressTypeNotRecognized => A::Decline,
128
            ResolveError::PathNotAbsolute => A::Abort,
129
        }
130
    }
131
}
132

            
133
/// Implementation type for a connect point.
134
///
135
/// This type is hidden so that the enum fields remain private.
136
/// It is parameterized on a [`Addresses`] trait,
137
/// to indicate whether it is in resolved or unresolved form.
138
#[derive(Clone, Debug)]
139
pub(crate) enum ConnectPointEnum<R: Addresses> {
140
    /// Connect by opening a socket to a [`general::SocketAddr`]
141
    Connect(Connect<R>),
142
    /// Connect by some built-in mechanism.
143
    ///
144
    /// (Or, in the case of Abort, do not connect at all.)
145
    Builtin(Builtin),
146
}
147

            
148
/// Trait to hold types that vary depending on whether a connect point is resolved or not.
149
//
150
// Note: We could use instead separate `PATH` and `ADDR` parameters,
151
// but this approach makes specifying bounds significantly easier.
152
pub(crate) trait Addresses {
153
    /// Type to represent addresses that we can open a socket to.
154
    type SocketAddr: Clone + std::fmt::Debug;
155
    /// Type to represent paths on the filesystem.
156
    type Path: Clone + std::fmt::Debug;
157
}
158

            
159
/// Representation of a connect point as deserialized.
160
///
161
/// We could instead deserialize [`ConnectPointEnum`] directly,
162
/// but that would restrict our error-handling:
163
/// the `toml` crate doesn't make it easy to distinguish
164
/// one kind of parse error from another.
165
///
166
/// TODO We should revisit this choice when we add more variants
167
/// or more auxiliary tables.
168
#[derive(Deserialize, Clone, Debug)]
169
struct ConnectPointDe {
170
    /// A "connect" table.
171
    connect: Option<Connect<Unresolved>>,
172
    /// A "builtin" table.
173
    builtin: Option<Builtin>,
174
}
175
impl TryFrom<ConnectPointDe> for ConnectPointEnum<Unresolved> {
176
    type Error = ParseError;
177

            
178
52
    fn try_from(value: ConnectPointDe) -> Result<Self, Self::Error> {
179
4
        match value {
180
            ConnectPointDe {
181
46
                connect: Some(c),
182
                builtin: None,
183
46
            } => Ok(ConnectPointEnum::Connect(c)),
184
            ConnectPointDe {
185
                connect: None,
186
2
                builtin: Some(b),
187
2
            } => Ok(ConnectPointEnum::Builtin(b)),
188
            ConnectPointDe {
189
                connect: Some(_),
190
                builtin: Some(_),
191
2
            } => Err(ParseError::ConflictingMembers),
192
            // This didn't have either recognized section,
193
            // so it is likely itn an unrecognized format.
194
2
            _ => Err(ParseError::UnrecognizedFormat),
195
        }
196
52
    }
197
}
198

            
199
/// A "builtin" connect point.
200
///
201
/// This represents an approach to connecting that is handled purely
202
/// within arti.  In the future, this might include "embedded" or "owned";
203
/// but for now, it only includes "abort".
204
#[derive(Deserialize, Clone, Debug)]
205
pub(crate) struct Builtin {
206
    /// Actual strategy of built-in behavior to implement.
207
    pub(crate) builtin: BuiltinVariant,
208
}
209

            
210
/// A particular built-in strategy.
211
#[derive(Deserialize, Clone, Debug)]
212
#[serde(rename_all = "lowercase")]
213
pub(crate) enum BuiltinVariant {
214
    /// This connect point must fail,
215
    /// and no subsequent connect points may be tried.
216
    Abort,
217
}
218

            
219
#[derive(Deserialize, Clone, Debug)]
220
#[serde(bound = "R::Path : Deserialize<'de>, AddrWithStr<R::SocketAddr> : Deserialize<'de>")]
221
#[allow(clippy::missing_docs_in_private_items)]
222
pub(crate) struct Connect<R: Addresses> {
223
    /// The address of the socket at which the client should try to reach the RPC server,
224
    /// and which the RPC server should bind.
225
    pub(crate) socket: AddrWithStr<R::SocketAddr>,
226
    /// The address of the socket which the RPC server believes it is actually listening at.
227
    ///
228
    /// If absent, defaults to `socket`.
229
    ///
230
    /// This value is only needs to be different from `socket`
231
    /// in cases where cookie authentication is in use,
232
    /// and the client is sandboxed somehow (such as behind a NAT, or inside a container).
233
    pub(crate) socket_canonical: Option<AddrWithStr<R::SocketAddr>>,
234
    /// The authentication that the client should try to use,
235
    /// and which the server should require.
236
    pub(crate) auth: Auth<R>,
237
}
238

            
239
impl Connect<Unresolved> {
240
    /// Convert all symbolic paths within this Connect to their resolved forms.
241
4
    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Connect<Resolved>, ResolveError> {
242
4
        let socket = self.socket.resolve(resolver)?;
243
4
        let socket_canonical = self
244
4
            .socket_canonical
245
4
            .as_ref()
246
6
            .map(|sc| sc.resolve(resolver))
247
4
            .transpose()?;
248
4
        let auth = self.auth.resolve(resolver)?;
249
4
        Connect {
250
4
            socket,
251
4
            socket_canonical,
252
4
            auth,
253
4
        }
254
4
        .validate()
255
4
    }
256
}
257

            
258
impl Connect<Resolved> {
259
    /// Return this `Connect` only if its parts are valid and compatible.
260
4
    fn validate(self) -> Result<Self, ResolveError> {
261
        use general::SocketAddr::{Inet, Unix};
262
4
        match (self.socket.as_ref(), &self.auth) {
263
4
            (Inet(addr), _) if !addr.ip().is_loopback() => {
264
                return Err(ResolveError::AddressNotLoopback);
265
            }
266
            (Inet(_), Auth::None) => return Err(ResolveError::AuthNotCompatible),
267
4
            (_, Auth::Unrecognized(_)) => return Err(ResolveError::AuthNotRecognized),
268
            (Inet(_), Auth::Cookie { .. }) => {}
269
            (Unix(_), _) => {}
270
            (_, _) => return Err(ResolveError::AddressTypeNotRecognized),
271
        };
272
        self.check_absolute_paths()?;
273
        Ok(self)
274
4
    }
275

            
276
    /// Return an error if some path in this `Connect` is not absolute.
277
    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
278
        sockaddr_check_absolute(self.socket.as_ref())?;
279
        if let Some(sa) = &self.socket_canonical {
280
            sockaddr_check_absolute(sa.as_ref())?;
281
        }
282
        self.auth.check_absolute_paths()?;
283
        Ok(())
284
    }
285
}
286

            
287
/// An authentication method for RPC implementations to use,
288
/// along with its related parameters.
289
#[derive(Deserialize, Clone, Debug)]
290
#[serde(rename_all = "lowercase")]
291
pub(crate) enum Auth<R: Addresses> {
292
    /// No authentication is needed or should be expected.
293
    None,
294
    /// Cookie-based authentication should be used.
295
    Cookie {
296
        /// Path to the cookie file.
297
        path: R::Path,
298
    },
299
    /// Unrecognized authentication method.
300
    ///
301
    /// (Serde will deserialize into this whenever the auth field
302
    /// is something unrecognized.)
303
    #[serde(untagged)]
304
    Unrecognized(toml::Value),
305
}
306

            
307
impl Auth<Unresolved> {
308
    /// Convert all symbolic paths within this `Auth` to their resolved forms.
309
4
    fn resolve(&self, resolver: &CfgPathResolver) -> Result<Auth<Resolved>, ResolveError> {
310
4
        match self {
311
            Auth::None => Ok(Auth::None),
312
            Auth::Cookie { path } => Ok(Auth::Cookie {
313
                path: path.path(resolver)?,
314
            }),
315
4
            Auth::Unrecognized(x) => Ok(Auth::Unrecognized(x.clone())),
316
        }
317
4
    }
318
}
319

            
320
impl Auth<Resolved> {
321
    /// Return an error if any path in `self` is not absolute..
322
    fn check_absolute_paths(&self) -> Result<(), ResolveError> {
323
        match self {
324
            Auth::None => Ok(()),
325
            Auth::Cookie { path } => {
326
                if path.is_absolute() {
327
                    Ok(())
328
                } else {
329
                    Err(ResolveError::PathNotAbsolute)
330
                }
331
            }
332
            Auth::Unrecognized(_) => Ok(()),
333
        }
334
    }
335
}
336

            
337
/// Type parameters for unresolved connect points
338
//
339
// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
340
#[derive(Clone, Debug)]
341
struct Unresolved;
342
impl Addresses for Unresolved {
343
    type SocketAddr = CfgAddr;
344
    type Path = CfgPath;
345
}
346

            
347
/// Type parameters for resolved connect points
348
//
349
// This derive should be needless, but it permits derive(Clone,Debug) elsewhere.
350
#[derive(Clone, Debug)]
351
pub(crate) struct Resolved;
352
impl Addresses for Resolved {
353
    type SocketAddr = general::SocketAddr;
354
    type Path = PathBuf;
355
}
356

            
357
/// Represent an address type along with the string it was decoded from.
358
///
359
/// We use this type in connect points because, for some kinds of authentication,
360
/// we need the literal input string that created the address.
361
#[derive(Clone, Debug, derive_more::AsRef, serde_with::DeserializeFromStr)]
362
pub(crate) struct AddrWithStr<A>
363
where
364
    A: Clone + Debug,
365
{
366
    /// The string representation of the address.
367
    ///
368
    /// For inet addresses, this is the value that appeared in the configuration.
369
    /// For unix domain sockets, this is the value that appeared in the configuration,
370
    /// after shell expansion.
371
    string: String,
372
    /// The address itself.
373
    #[as_ref]
374
    addr: A,
375
}
376
impl<A> AddrWithStr<A>
377
where
378
    A: Clone + Debug,
379
{
380
    /// Return the string representation of this address,
381
    /// for use in the authentication handshake.
382
    pub(crate) fn as_str(&self) -> &str {
383
        self.string.as_str()
384
    }
385
}
386
impl AddrWithStr<CfgAddr> {
387
    /// Convert an `AddrWithStr<CfgAddr>` into its substituted form.
388
8
    pub(crate) fn resolve(
389
8
        &self,
390
8
        resolver: &CfgPathResolver,
391
8
    ) -> Result<AddrWithStr<general::SocketAddr>, ResolveError> {
392
8
        let AddrWithStr { string, addr } = self;
393
8
        let substituted = addr.substitutions_will_apply();
394
8
        let addr = addr.address(resolver)?;
395
8
        let string = if substituted {
396
            addr.try_to_string().ok_or(ResolveError::PathNotString)?
397
        } else {
398
8
            string.clone()
399
        };
400
8
        Ok(AddrWithStr { string, addr })
401
8
    }
402
}
403
impl<A> FromStr for AddrWithStr<A>
404
where
405
    A: Clone + Debug + FromStr,
406
{
407
    type Err = <A as FromStr>::Err;
408

            
409
80
    fn from_str(s: &str) -> Result<Self, Self::Err> {
410
80
        let addr = s.parse()?;
411
80
        let string = s.to_owned();
412
80
        Ok(Self { string, addr })
413
80
    }
414
}
415

            
416
/// Return true if `s` is an absolute address.
417
///
418
/// All IP addresses are considered absolute.
419
fn sockaddr_check_absolute(s: &general::SocketAddr) -> Result<(), ResolveError> {
420
    match s {
421
        general::SocketAddr::Inet(_) => Ok(()),
422
        general::SocketAddr::Unix(sa) => match sa.as_pathname() {
423
            Some(p) if !p.is_absolute() => Err(ResolveError::PathNotAbsolute),
424
            _ => Ok(()),
425
        },
426
        _ => Err(ResolveError::AddressTypeNotRecognized),
427
    }
428
}
429

            
430
#[cfg(test)]
431
mod test {
432
    // @@ begin test lint list maintained by maint/add_warning @@
433
    #![allow(clippy::bool_assert_comparison)]
434
    #![allow(clippy::clone_on_copy)]
435
    #![allow(clippy::dbg_macro)]
436
    #![allow(clippy::mixed_attributes_style)]
437
    #![allow(clippy::print_stderr)]
438
    #![allow(clippy::print_stdout)]
439
    #![allow(clippy::single_char_pattern)]
440
    #![allow(clippy::unwrap_used)]
441
    #![allow(clippy::unchecked_time_subtraction)]
442
    #![allow(clippy::useless_vec)]
443
    #![allow(clippy::needless_pass_by_value)]
444
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
445

            
446
    use super::*;
447
    use assert_matches::assert_matches;
448

            
449
    fn parse(s: &str) -> ParsedConnectPoint {
450
        s.parse().unwrap()
451
    }
452

            
453
    #[test]
454
    fn examples() {
455
        let _e1 = parse(
456
            r#"
457
[builtin]
458
builtin = "abort"
459
"#,
460
        );
461

            
462
        let _e2 = parse(
463
            r#"
464
[connect]
465
socket = "unix:/var/run/arti/rpc_socket"
466
auth = "none"
467
"#,
468
        );
469

            
470
        let _e3 = parse(
471
            r#"
472
[connect]
473
socket = "inet:[::1]:9191"
474
socket_canonical = "inet:[::1]:2020"
475

            
476
auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
477
"#,
478
        );
479

            
480
        let _e4 = parse(
481
            r#"
482
[connect]
483
socket = "inet:[::1]:9191"
484
socket_canonical = "inet:[::1]:2020"
485

            
486
[connect.auth.cookie]
487
path = "/home/user/.arti_rpc/cookie"
488
"#,
489
        );
490
    }
491

            
492
    #[test]
493
    fn parse_errors() {
494
        let r: Result<ParsedConnectPoint, _> = "not a toml string".parse();
495
        assert_matches!(r, Err(ParseError::InvalidConnectPoint(_)));
496

            
497
        let r: Result<ParsedConnectPoint, _> = "[squidcakes]".parse();
498
        assert_matches!(r, Err(ParseError::UnrecognizedFormat));
499

            
500
        let r: Result<ParsedConnectPoint, _> = r#"
501
[builtin]
502
builtin = "abort"
503

            
504
[connect]
505
socket = "inet:[::1]:9191"
506
socket_canonical = "inet:[::1]:2020"
507

            
508
auth = { cookie = { path = "/home/user/.arti_rpc/cookie" } }
509
"#
510
        .parse();
511
        assert_matches!(r, Err(ParseError::ConflictingMembers));
512
    }
513

            
514
    #[test]
515
    fn resolve_errors() {
516
        let resolver = CfgPathResolver::default();
517

            
518
        let r: ParsedConnectPoint = r#"
519
[connect]
520
socket = "inet:[::1]:9191"
521
socket_canonical = "inet:[::1]:2020"
522

            
523
[connect.auth.esp]
524
telekinetic_handshake = 3
525
"#
526
        .parse()
527
        .unwrap();
528
        let err = r.resolve(&resolver).err();
529
        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
530

            
531
        let r: ParsedConnectPoint = r#"
532
[connect]
533
socket = "inet:[::1]:9191"
534
socket_canonical = "inet:[::1]:2020"
535

            
536
auth = "foo"
537
"#
538
        .parse()
539
        .unwrap();
540
        let err = r.resolve(&resolver).err();
541
        assert_matches!(err, Some(ResolveError::AuthNotRecognized));
542
    }
543
}