1
//! Support for shell expansion in [`general::SocketAddr`].
2

            
3
use crate::{CfgPath, CfgPathError};
4
use serde::{Deserialize, Serialize};
5
use std::{io, net, path::PathBuf, str::FromStr, sync::Arc};
6
use tor_general_addr::{general, unix};
7

            
8
/// A variation of [`general::SocketAddr`] that allows shell expansions in Unix paths.
9
///
10
/// The string representation for these addresses is the same as for [`general::SocketAddr`];
11
/// but the shell expansion syntax is the same as for [`CfgPath`].
12
///
13
/// Shell expansion is only supported _within_ paths: Even if the user has set `${HOME}`
14
/// to `127.0.0.1`, the address `inet:${HOME}:9999` is a syntax error.
15
///
16
/// In addition to the "inet:" and "unix:" schemas supported by `general::SocketAddr`,
17
/// This type also supports a "unix-literal" schema,
18
/// to indicate that no shell expansion should occur.
19
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
20
#[serde(into = "CfgAddrSerde", try_from = "CfgAddrSerde")]
21
pub struct CfgAddr(AddrInner);
22

            
23
/// Implementation type for `CfgAddr`.
24
///
25
/// This is a separate type because we can't define an public enum with private members.
26
#[derive(Clone, Debug, Eq, PartialEq)]
27
enum AddrInner {
28
    /// An internet address (which will not be expanded).
29
    Inet(net::SocketAddr),
30
    /// A unix domain socket path.
31
    Unix(CfgPath),
32
}
33

            
34
impl CfgAddr {
35
    /// Create a new [`CfgAddr`] that will produce an `AF_UNIX` address
36
    /// corresponding to the provided path.
37
    ///
38
    /// Note that not all platforms support AF\_UNIX addresses;
39
    /// on Windows, notably, expanding this path will produce an error.
40
20
    pub fn new_unix(path: CfgPath) -> Self {
41
20
        CfgAddr(AddrInner::Unix(path))
42
20
    }
43

            
44
    /// Return the [`general::SocketAddr`] produced by expanding this `CfgAddr`.
45
    #[cfg_attr(not(unix), expect(unused_variables))]
46
198
    pub fn address(
47
198
        &self,
48
198
        path_resolver: &crate::CfgPathResolver,
49
198
    ) -> Result<general::SocketAddr, CfgAddrError> {
50
198
        match &self.0 {
51
188
            AddrInner::Inet(socket_addr) => {
52
                // Easy case: This is an inet address.
53
188
                Ok((*socket_addr).into())
54
            }
55
10
            AddrInner::Unix(cfg_path) => {
56
                #[cfg(not(unix))]
57
                {
58
                    // Give this error early on non-unix platforms, so that we don't confuse the user.
59
                    Err(unix::NoAfUnixSocketSupport::default().into())
60
                }
61
                #[cfg(unix)]
62
                {
63
10
                    let addr = unix::SocketAddr::from_pathname(cfg_path.path(path_resolver)?)
64
10
                        .map_err(|e| CfgAddrError::ConstructAfUnixAddress(Arc::new(e)))?;
65
10
                    Ok(addr.into())
66
                }
67
            }
68
        }
69
198
    }
70

            
71
    /// Return true if this address is of a type to which variable substitutions will apply.
72
    ///
73
    /// Currently, substitutions apply to AF\_UNIX addresses but not to Inet addresses.
74
180
    pub fn substitutions_will_apply(&self) -> bool {
75
180
        match &self.0 {
76
180
            AddrInner::Inet(_) => false,
77
            AddrInner::Unix(_) => true,
78
        }
79
180
    }
80

            
81
    /// Helper: if possible, format this address as a String.
82
    ///
83
    /// (This will return Err(p) if this path is a literal unix domain socket path
84
    /// that can't be represented as a string.)
85
    //
86
    // This is a separate function so that it can form the basis of a "display_lossy"
87
    // implementation, assuming we need one.
88
6
    fn try_to_string(&self) -> Result<String, &PathBuf> {
89
        use crate::PathInner as PI;
90
        use AddrInner as AI;
91
6
        match &self.0 {
92
2
            AI::Inet(socket_addr) => Ok(format!("inet:{}", socket_addr)),
93
4
            AI::Unix(cfg_path) => match &cfg_path.0 {
94
2
                PI::Shell(s) => Ok(format!("unix:{}", s)),
95
2
                PI::Literal(path) => match path.literal.to_str() {
96
2
                    Some(literal_as_str) => Ok(format!("unix-literal:{}", literal_as_str)),
97
                    None => Err(&path.literal),
98
                },
99
            },
100
        }
101
6
    }
102
}
103

            
104
/// Error produced when trying to expand a [`CfgAddr`] into a [`general::SocketAddr`].
105
#[derive(Clone, Debug, thiserror::Error)]
106
#[non_exhaustive]
107
pub enum CfgAddrError {
108
    /// Tried to expand a `unix:` address on a platform where we don't support `AF_UNIX` addresses.
109
    #[error("No support for AF_UNIX addresses on this platform")]
110
    NoAfUnixSocketSupport(#[from] unix::NoAfUnixSocketSupport),
111
    /// Unable to expand the underlying `CfgPath`, likely due to syntax or missing variables.
112
    #[error("Could not expand path")]
113
    Path(#[from] CfgPathError),
114
    /// Unable to create an AF_UNIX address from a path.
115
    ///
116
    /// (This can happen if the path is too long, or contains internal NULs.)
117
    #[error("Could not construct AF_UNIX address")]
118
    ConstructAfUnixAddress(#[source] Arc<io::Error>),
119
}
120

            
121
impl FromStr for CfgAddr {
122
    type Err = general::AddrParseError;
123

            
124
224
    fn from_str(s: &str) -> Result<Self, Self::Err> {
125
        // NOTE: This logic is mostly duplicated from <FromStr for general::SocketAddr>;
126
        // I don't see an easy way to deduplicate it.
127
250
        if s.starts_with(|c: char| c.is_ascii_digit() || c == '[') {
128
            // This looks like an inet address, and cannot be a qualified address.
129
12
            Ok(s.parse::<net::SocketAddr>()?.into())
130
212
        } else if let Some((schema, remainder)) = s.split_once(':') {
131
212
            match schema {
132
212
                "unix" => {
133
10
                    let path = CfgPath::new(remainder.to_string());
134
10
                    Ok(CfgAddr::new_unix(path))
135
                }
136
202
                "unix-literal" => {
137
8
                    let path = CfgPath::new_literal(remainder.to_string());
138
8
                    Ok(CfgAddr::new_unix(path))
139
                }
140
194
                "inet" => Ok(remainder.parse::<net::SocketAddr>()?.into()),
141
2
                _ => Err(general::AddrParseError::UnrecognizedSchema(
142
2
                    schema.to_string(),
143
2
                )),
144
            }
145
        } else {
146
            Err(general::AddrParseError::NoSchema)
147
        }
148
224
    }
149
}
150

            
151
impl From<net::SocketAddr> for CfgAddr {
152
194
    fn from(value: net::SocketAddr) -> Self {
153
194
        CfgAddr(AddrInner::Inet(value))
154
194
    }
155
}
156
impl TryFrom<unix::SocketAddr> for CfgAddr {
157
    type Error = UnixAddrNotAPath;
158

            
159
    fn try_from(value: unix::SocketAddr) -> Result<Self, Self::Error> {
160
        // We don't need to check `#[cfg(unix)]` here:
161
        // if unix::SocketAddr is inhabited, then we can construct the Unix variant.
162
        Ok(Self::new_unix(CfgPath::new_literal(
163
            value.as_pathname().ok_or(UnixAddrNotAPath)?,
164
        )))
165
    }
166
}
167
// NOTE that we deliberately _don't_ implement From<Path> or From<CfgPath>;
168
// we want to keep open the possibility that there may be non-AF\_UNIX path-based
169
// addresses in the future!
170

            
171
/// Error returned when trying to convert a non-path `unix::SocketAddr` into a `CfgAddr` .
172
#[derive(Clone, Debug, Default, thiserror::Error)]
173
#[non_exhaustive]
174
#[error("Unix domain socket address was not a path.")]
175
pub struct UnixAddrNotAPath;
176

            
177
/// Serde helper: We convert CfgAddr through this format in order to serialize and deserialize it.
178
#[derive(Serialize, Deserialize)]
179
#[serde(untagged)]
180
enum CfgAddrSerde {
181
    /// We serialize most types as a string.
182
    Str(String),
183
    /// We have another format for representing AF\_UNIX address literals
184
    /// that can't be represented as a string.
185
    UnixLiteral {
186
        /// A path that won't be expanded.
187
        unix_literal: PathBuf,
188
    },
189
}
190

            
191
impl TryFrom<CfgAddrSerde> for CfgAddr {
192
    type Error = general::AddrParseError;
193

            
194
12
    fn try_from(value: CfgAddrSerde) -> Result<Self, Self::Error> {
195
        use CfgAddrSerde as S;
196
12
        match value {
197
12
            S::Str(s) => s.parse(),
198
            S::UnixLiteral { unix_literal } => {
199
                Ok(CfgAddr::new_unix(CfgPath::new_literal(unix_literal)))
200
            }
201
        }
202
12
    }
203
}
204
impl From<CfgAddr> for CfgAddrSerde {
205
6
    fn from(value: CfgAddr) -> Self {
206
6
        match value.try_to_string() {
207
6
            Ok(s) => CfgAddrSerde::Str(s),
208
            Err(unix_literal) => CfgAddrSerde::UnixLiteral {
209
                unix_literal: unix_literal.clone(),
210
            },
211
        }
212
6
    }
213
}
214

            
215
#[cfg(test)]
216
mod test {
217
    // @@ begin test lint list maintained by maint/add_warning @@
218
    #![allow(clippy::bool_assert_comparison)]
219
    #![allow(clippy::clone_on_copy)]
220
    #![allow(clippy::dbg_macro)]
221
    #![allow(clippy::mixed_attributes_style)]
222
    #![allow(clippy::print_stderr)]
223
    #![allow(clippy::print_stdout)]
224
    #![allow(clippy::single_char_pattern)]
225
    #![allow(clippy::unwrap_used)]
226
    #![allow(clippy::unchecked_time_subtraction)]
227
    #![allow(clippy::useless_vec)]
228
    #![allow(clippy::needless_pass_by_value)]
229
    #![allow(clippy::string_slice)] // See arti#2571
230
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
231

            
232
    use super::*;
233
    use assert_matches::assert_matches;
234
    use std::path::PathBuf;
235

            
236
    use crate::{CfgPathResolver, home};
237

            
238
    #[test]
239
    fn parse_inet_ok() {
240
        fn check(s: &str) {
241
            let resolv = CfgPathResolver::from_pairs([("FOO", "foo")]);
242
            let a: general::SocketAddr = CfgAddr::from_str(s).unwrap().address(&resolv).unwrap();
243
            assert_eq!(a, general::SocketAddr::from_str(s).unwrap());
244
        }
245

            
246
        check("127.0.0.1:9999");
247
        check("inet:127.0.0.1:9999");
248
        check("[2001:db8::413]:443");
249
        check("inet:[2001:db8::413]:443");
250
    }
251

            
252
    #[test]
253
    fn parse_inet_bad() {
254
        assert_matches!(
255
            CfgAddr::from_str("612"),
256
            Err(general::AddrParseError::InvalidInetAddress(_))
257
        );
258
        assert_matches!(
259
            CfgAddr::from_str("612unix:/home"),
260
            Err(general::AddrParseError::InvalidInetAddress(_))
261
        );
262
        assert_matches!(
263
            CfgAddr::from_str("127.0.0.1.1:99"),
264
            Err(general::AddrParseError::InvalidInetAddress(_))
265
        );
266
        assert_matches!(
267
            CfgAddr::from_str("inet:6"),
268
            Err(general::AddrParseError::InvalidInetAddress(_))
269
        );
270
        assert_matches!(
271
            CfgAddr::from_str("[[[[[]]]]]"),
272
            Err(general::AddrParseError::InvalidInetAddress(_))
273
        );
274
    }
275

            
276
    #[test]
277
    fn parse_bad_schemas() {
278
        assert_matches!(
279
            CfgAddr::from_str("uranian:umbra"),
280
            Err(general::AddrParseError::UnrecognizedSchema(_))
281
        );
282
    }
283

            
284
    #[test]
285
    #[cfg_attr(not(unix), expect(unused_variables))]
286
    fn unix_literal() {
287
        let resolv = CfgPathResolver::from_pairs([("USER_HOME", home().unwrap())]);
288
        let pb = PathBuf::from("${USER_HOME}/.local/socket");
289
        let a1 = CfgAddr::new_unix(CfgPath::new_literal(&pb));
290
        let a2 = CfgAddr::from_str("unix-literal:${USER_HOME}/.local/socket").unwrap();
291
        #[cfg(unix)]
292
        {
293
            assert_eq!(a1.address(&resolv).unwrap(), a2.address(&resolv).unwrap(),);
294
            match a1.address(&resolv).unwrap() {
295
                general::SocketAddr::Unix(socket_addr) => {
296
                    // can't use assert_eq because these types are not Debug.
297
                    assert!(socket_addr.as_pathname() == Some(pb.as_ref()));
298
                }
299
                _ => panic!("Expected a unix domain socket address"),
300
            }
301
        }
302
        #[cfg(not(unix))]
303
        assert_matches!(
304
            a1.address(&resolv),
305
            Err(CfgAddrError::NoAfUnixSocketSupport(_))
306
        );
307
    }
308

            
309
    #[cfg_attr(not(unix), expect(unused_variables))]
310
    fn try_unix(addr: &str, want: &str, path_resolver: &CfgPathResolver) {
311
        let p = CfgPath::new(want.to_string());
312
        let expansion = p.path(path_resolver).unwrap();
313
        let cfg_addr = CfgAddr::from_str(addr).unwrap();
314
        assert_matches!(&cfg_addr.0, AddrInner::Unix(_));
315
        #[cfg(unix)]
316
        {
317
            let gen_addr = cfg_addr.address(path_resolver).unwrap();
318
            let expected_addr = unix::SocketAddr::from_pathname(expansion).unwrap();
319
            assert_eq!(gen_addr, expected_addr.into());
320
        }
321
        #[cfg(not(unix))]
322
        {
323
            assert_matches!(
324
                cfg_addr.address(path_resolver),
325
                Err(CfgAddrError::NoAfUnixSocketSupport(_))
326
            );
327
        }
328
    }
329

            
330
    #[test]
331
    fn unix_no_substitution() {
332
        let resolver = CfgPathResolver::from_pairs([("FOO", "foo")]);
333
        try_unix("unix:/home/mayor/.socket", "/home/mayor/.socket", &resolver);
334
    }
335

            
336
    #[test]
337
    #[cfg(feature = "expand-paths")]
338
    fn unix_substitution() {
339
        let resolver = CfgPathResolver::from_pairs([("FOO", "foo")]);
340
        try_unix("unix:${FOO}/socket", "${FOO}/socket", &resolver);
341
    }
342

            
343
    #[test]
344
    fn serde() {
345
        fn testcase_with_provided_addr(json: &str, addr: &CfgAddr) {
346
            let a1: CfgAddr = serde_json::from_str(json).unwrap();
347
            assert_eq!(&a1, addr);
348
            let encoded = serde_json::to_string(&a1).unwrap();
349
            let a2: CfgAddr = serde_json::from_str(&encoded).unwrap();
350
            assert_eq!(&a2, addr);
351
        }
352
        fn testcase(json: &str, addr: &str) {
353
            let addr = CfgAddr::from_str(addr).unwrap();
354
            testcase_with_provided_addr(json, &addr);
355
        }
356

            
357
        testcase(r#" "inet:127.0.0.1:443" "#, "inet:127.0.0.1:443");
358
        testcase(r#" "unix:${HOME}/socket" "#, "unix:${HOME}/socket");
359
        testcase(
360
            r#" "unix-literal:${HOME}/socket" "#,
361
            "unix-literal:${HOME}/socket",
362
        );
363
    }
364
}