1
//! Structures that represent SOCKS messages
2

            
3
use crate::{Error, Result};
4

            
5
use caret::caret_int;
6
use std::fmt;
7
use std::net::IpAddr;
8

            
9
#[cfg(feature = "arbitrary")]
10
use std::net::Ipv6Addr;
11

            
12
use tor_error::bad_api_usage;
13

            
14
#[cfg(feature = "arbitrary")]
15
use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured};
16

            
17
/// A supported SOCKS version.
18
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
19
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
20
#[non_exhaustive]
21
pub enum SocksVersion {
22
    /// Socks v4.
23
    V4,
24
    /// Socks v5.
25
    V5,
26
}
27

            
28
/// We tried to convert something other than '4' or '5' to a SOCKS version.
29
#[derive(Copy, Clone, Debug, Eq, PartialEq, thiserror::Error)]
30
#[error("{0} is not a recognized socks version")]
31
#[allow(clippy::exhaustive_structs)]
32
pub struct InvalidSocksVersion(pub u8);
33

            
34
impl TryFrom<u8> for SocksVersion {
35
    // Note: This is not and should not be `Error::BadProtocol`!
36
    // See the documentation on that variant for discussion of why.
37
    type Error = InvalidSocksVersion;
38

            
39
379
    fn try_from(v: u8) -> std::result::Result<SocksVersion, InvalidSocksVersion> {
40
379
        match v {
41
181
            4 => Ok(SocksVersion::V4),
42
198
            5 => Ok(SocksVersion::V5),
43
            // Note that we do *not* return BadProtocol in this case.
44
            // BadProtocol is only for the case when the first byte
45
            // of the SOCKS handshake is invalid.
46
            _ => Err(InvalidSocksVersion(v)),
47
        }
48
379
    }
49
}
50

            
51
impl fmt::Display for SocksVersion {
52
423
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53
423
        match self {
54
141
            SocksVersion::V4 => write!(f, "socks4"),
55
282
            SocksVersion::V5 => write!(f, "socks5"),
56
        }
57
423
    }
58
}
59

            
60
/// A completed SOCKS request, as negotiated on a SOCKS connection.
61
///
62
/// Once this request is done, we know where to connect.  Don't
63
/// discard this object immediately: Use it to report success or
64
/// failure.
65
#[derive(Clone, Debug)]
66
#[cfg_attr(test, derive(PartialEq, Eq))]
67
pub struct SocksRequest {
68
    /// Negotiated SOCKS protocol version.
69
    version: SocksVersion,
70
    /// The command requested by the SOCKS client.
71
    cmd: SocksCmd,
72
    /// The target address.
73
    addr: SocksAddr,
74
    /// The target port.
75
    port: u16,
76
    /// Authentication information.
77
    ///
78
    /// (Tor doesn't believe in SOCKS authentication, since it cannot
79
    /// possibly secure.  Instead, we use it for circuit isolation.)
80
    auth: SocksAuth,
81
}
82

            
83
#[cfg(feature = "arbitrary")]
84
impl<'a> Arbitrary<'a> for SocksRequest {
85
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
86
        let version = SocksVersion::arbitrary(u)?;
87
        let cmd = SocksCmd::arbitrary(u)?;
88
        let addr = SocksAddr::arbitrary(u)?;
89
        let port = u16::arbitrary(u)?;
90
        let auth = SocksAuth::arbitrary(u)?;
91

            
92
        SocksRequest::new(version, cmd, addr, port, auth)
93
            .map_err(|_| arbitrary::Error::IncorrectFormat)
94
    }
95
}
96

            
97
/// An address sent or received as part of a SOCKS handshake
98
#[derive(Clone, Debug, PartialEq, Eq)]
99
#[allow(clippy::exhaustive_enums)]
100
pub enum SocksAddr {
101
    /// A regular DNS hostname.
102
    Hostname(SocksHostname),
103
    /// An IP address.  (Tor doesn't like to receive these during SOCKS
104
    /// handshakes, since they usually indicate that the hostname lookup
105
    /// happened somewhere else.)
106
    Ip(IpAddr),
107
}
108

            
109
#[cfg(feature = "arbitrary")]
110
impl<'a> Arbitrary<'a> for SocksAddr {
111
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
112
        use std::net::Ipv4Addr;
113
        let b = u8::arbitrary(u)?;
114
        Ok(match b % 3 {
115
            0 => SocksAddr::Hostname(SocksHostname::arbitrary(u)?),
116
            1 => SocksAddr::Ip(IpAddr::V4(Ipv4Addr::arbitrary(u)?)),
117
            _ => SocksAddr::Ip(IpAddr::V6(Ipv6Addr::arbitrary(u)?)),
118
        })
119
    }
120
    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
121
        (1, Some(256))
122
    }
123
}
124

            
125
/// A hostname for use with SOCKS.  It is limited in length.
126
#[derive(Clone, Debug, PartialEq, Eq)]
127
pub struct SocksHostname(String);
128

            
129
#[cfg(feature = "arbitrary")]
130
impl<'a> Arbitrary<'a> for SocksHostname {
131
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
132
        String::arbitrary(u)?
133
            .try_into()
134
            .map_err(|_| arbitrary::Error::IncorrectFormat)
135
    }
136
    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
137
        (0, Some(255))
138
    }
139
}
140

            
141
/// Provided authentication from a SOCKS handshake
142
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
143
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
144
#[non_exhaustive]
145
pub enum SocksAuth {
146
    /// No authentication was provided
147
    NoAuth,
148
    /// Socks4 authentication (a string) was provided.
149
    Socks4(Vec<u8>),
150
    /// Socks5 username/password authentication was provided.
151
    Username(Vec<u8>, Vec<u8>),
152
}
153

            
154
caret_int! {
155
    /// Command from the socks client telling us what to do.
156
    #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
157
    pub struct SocksCmd(u8) {
158
        /// Connect to a remote TCP address:port.
159
        CONNECT = 1,
160
        /// Not supported in Tor.
161
        BIND = 2,
162
        /// Not supported in Tor.
163
        UDP_ASSOCIATE = 3,
164

            
165
        /// Lookup a hostname, return an IP address. (Tor only.)
166
        RESOLVE = 0xF0,
167
        /// Lookup an IP address, return a hostname. (Tor only.)
168
        RESOLVE_PTR = 0xF1,
169
    }
170
}
171

            
172
caret_int! {
173
    /// Possible reply status values from a SOCKS5 handshake.
174
    ///
175
    /// Note that the documentation for these values is kind of scant,
176
    /// and is limited to what the RFC says.  Note also that SOCKS4
177
    /// only represents success and failure.
178
    #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
179
    pub struct SocksStatus(u8) {
180
        /// RFC 1928: "succeeded"
181
        SUCCEEDED = 0x00,
182
        /// RFC 1928: "general SOCKS server failure"
183
        GENERAL_FAILURE = 0x01,
184
        /// RFC 1928: "connection not allowable by ruleset"
185
        ///
186
        /// (This is the only occurrence of 'ruleset' or even 'rule'
187
        /// in RFC 1928.)
188
        NOT_ALLOWED = 0x02,
189
        /// RFC 1928: "Network unreachable"
190
        NETWORK_UNREACHABLE = 0x03,
191
        /// RFC 1928: "Host unreachable"
192
        HOST_UNREACHABLE = 0x04,
193
        /// RFC 1928: "Connection refused"
194
        CONNECTION_REFUSED = 0x05,
195
        /// RFC 1928: "TTL expired"
196
        ///
197
        /// (This is the only occurrence of 'TTL' in RFC 1928.)
198
        TTL_EXPIRED = 0x06,
199
        /// RFC 1929: "Command not supported"
200
        COMMAND_NOT_SUPPORTED = 0x07,
201
        /// RFC 1929: "Address type not supported"
202
        ADDRTYPE_NOT_SUPPORTED = 0x08,
203
        /// Prop304: "Onion Service Descriptor Can Not be Found"
204
        HS_DESC_NOT_FOUND = 0xF0,
205
        /// Prop304: "Onion Service Descriptor Is Invalid"
206
        HS_DESC_INVALID = 0xF1,
207
        /// Prop304: "Onion Service Introduction Failed"
208
        HS_INTRO_FAILED = 0xF2,
209
        /// Prop304: "Onion Service Rendezvous Failed"
210
        HS_REND_FAILED = 0xF3,
211
        /// Prop304: "Onion Service Missing Client Authorization"
212
        HS_MISSING_CLIENT_AUTH = 0xF4,
213
        /// Prop304: "Onion Service Wrong Client Authorization"
214
        HS_WRONG_CLIENT_AUTH = 0xF5,
215
        /// "Onion service address is invalid"
216
        ///
217
        /// (Documented in `tor.1` but not yet specified.)
218
        HS_BAD_ADDRESS = 0xF6,
219
        /// "Onion Service Introduction Timed Out"
220
        ///
221
        /// (Documented in `tor.1` but not yet specified.)
222
        HS_INTRO_TIMEOUT = 0xF7
223
    }
224
}
225

            
226
impl SocksCmd {
227
    /// Return true if this is a supported command.
228
38
    fn recognized(self) -> bool {
229
1
        matches!(
230
38
            self,
231
            SocksCmd::CONNECT | SocksCmd::RESOLVE | SocksCmd::RESOLVE_PTR
232
        )
233
38
    }
234

            
235
    /// Return true if this is a command for which we require a port.
236
1
    fn requires_port(self) -> bool {
237
        matches!(
238
1
            self,
239
            SocksCmd::CONNECT | SocksCmd::BIND | SocksCmd::UDP_ASSOCIATE
240
        )
241
1
    }
242
}
243

            
244
impl SocksStatus {
245
    /// Convert this status into a value for use with SOCKS4 or SOCKS4a.
246
    #[cfg(feature = "proxy-handshake")]
247
12
    pub(crate) fn into_socks4_status(self) -> u8 {
248
12
        match self {
249
6
            SocksStatus::SUCCEEDED => 0x5A,
250
6
            _ => 0x5B,
251
        }
252
12
    }
253
    /// Create a status from a SOCKS4 or SOCKS4a reply code.
254
    #[cfg(feature = "client-handshake")]
255
12
    pub(crate) fn from_socks4_status(status: u8) -> Self {
256
12
        match status {
257
7
            0x5A => SocksStatus::SUCCEEDED,
258
5
            0x5B => SocksStatus::GENERAL_FAILURE,
259
            0x5C | 0x5D => SocksStatus::NOT_ALLOWED,
260
            _ => SocksStatus::GENERAL_FAILURE,
261
        }
262
12
    }
263
}
264

            
265
impl TryFrom<String> for SocksHostname {
266
    type Error = Error;
267
26
    fn try_from(s: String) -> Result<SocksHostname> {
268
26
        if s.len() > 255 {
269
            // This is only a limitation for Socks 5, but we enforce it in both
270
            // cases, for simplicity.
271
            Err(bad_api_usage!("hostname too long").into())
272
26
        } else if contains_zeros(s.as_bytes()) {
273
            // This is only a limitation for Socks 4, but we enforce it in both
274
            // cases, for simplicity.
275
            Err(Error::Syntax)
276
        } else {
277
26
            Ok(SocksHostname(s))
278
        }
279
26
    }
280
}
281

            
282
impl AsRef<str> for SocksHostname {
283
8
    fn as_ref(&self) -> &str {
284
8
        self.0.as_ref()
285
8
    }
286
}
287

            
288
impl SocksAuth {
289
    /// Check whether this authentication is well-formed and compatible with the
290
    /// provided SOCKS version.
291
    ///
292
    /// Return an error if not.
293
36
    fn validate(&self, version: SocksVersion) -> Result<()> {
294
36
        match self {
295
21
            SocksAuth::NoAuth => {}
296
8
            SocksAuth::Socks4(data) => {
297
8
                if version != SocksVersion::V4 || contains_zeros(data) {
298
                    return Err(Error::Syntax);
299
8
                }
300
            }
301
7
            SocksAuth::Username(user, pass) => {
302
7
                if version != SocksVersion::V5
303
7
                    || user.len() > u8::MAX as usize
304
7
                    || pass.len() > u8::MAX as usize
305
                {
306
                    return Err(Error::Syntax);
307
7
                }
308
            }
309
        }
310
36
        Ok(())
311
36
    }
312
}
313

            
314
/// Return true if b contains at least one zero.
315
///
316
/// Try to run in constant time.
317
36
fn contains_zeros(b: &[u8]) -> bool {
318
    use subtle::{Choice, ConstantTimeEq};
319
36
    let c: Choice = b
320
36
        .iter()
321
549
        .fold(Choice::from(0), |seen_any, byte| seen_any | byte.ct_eq(&0));
322
36
    c.unwrap_u8() != 0
323
36
}
324

            
325
impl SocksRequest {
326
    /// Create a SocksRequest with a given set of fields.
327
    ///
328
    /// Return an error if the inputs aren't supported or valid.
329
38
    pub fn new(
330
38
        version: SocksVersion,
331
38
        cmd: SocksCmd,
332
38
        addr: SocksAddr,
333
38
        port: u16,
334
38
        auth: SocksAuth,
335
38
    ) -> Result<Self> {
336
38
        if !cmd.recognized() {
337
1
            return Err(Error::NotImplemented(
338
1
                format!("SOCKS command {}", cmd).into(),
339
1
            ));
340
37
        }
341
37
        if port == 0 && cmd.requires_port() {
342
1
            return Err(Error::Syntax);
343
36
        }
344
36
        auth.validate(version)?;
345

            
346
36
        Ok(SocksRequest {
347
36
            version,
348
36
            cmd,
349
36
            addr,
350
36
            port,
351
36
            auth,
352
36
        })
353
38
    }
354

            
355
    /// Return the negotiated version (4 or 5).
356
55
    pub fn version(&self) -> SocksVersion {
357
55
        self.version
358
55
    }
359

            
360
    /// Return the command that the client requested.
361
31
    pub fn command(&self) -> SocksCmd {
362
31
        self.cmd
363
31
    }
364

            
365
    /// Return the 'authentication' information from this request.
366
36
    pub fn auth(&self) -> &SocksAuth {
367
36
        &self.auth
368
36
    }
369

            
370
    /// Return the requested port.
371
33
    pub fn port(&self) -> u16 {
372
33
        self.port
373
33
    }
374

            
375
    /// Return the requested address.
376
50
    pub fn addr(&self) -> &SocksAddr {
377
50
        &self.addr
378
50
    }
379
}
380

            
381
impl fmt::Display for SocksAddr {
382
    /// Format a string (a hostname or IP address) corresponding to this
383
    /// SocksAddr.
384
19
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385
19
        match self {
386
10
            SocksAddr::Ip(a) => write!(f, "{}", a),
387
9
            SocksAddr::Hostname(h) => write!(f, "{}", h.0),
388
        }
389
19
    }
390
}
391

            
392
/// The reply from a SOCKS proxy.
393
#[derive(Debug, Clone)]
394
pub struct SocksReply {
395
    /// The provided status code
396
    status: SocksStatus,
397
    /// The provided address, if any.
398
    addr: SocksAddr,
399
    /// The provided port.
400
    port: u16,
401
}
402

            
403
impl SocksReply {
404
    /// Create a new SocksReply.
405
    #[cfg(feature = "client-handshake")]
406
24
    pub(crate) fn new(status: SocksStatus, addr: SocksAddr, port: u16) -> Self {
407
24
        Self { status, addr, port }
408
24
    }
409

            
410
    /// Return the status code from this socks reply.
411
24
    pub fn status(&self) -> SocksStatus {
412
24
        self.status
413
24
    }
414

            
415
    /// Return the address from this socks reply.
416
    ///
417
    /// The semantics of this address depend on the original socks command
418
    /// provided; see the SOCKS specification for more information.
419
    ///
420
    /// Note that some implementations (including Tor) will return `0.0.0.0` or
421
    /// `[::]` to indicate "no address given".
422
4
    pub fn addr(&self) -> &SocksAddr {
423
4
        &self.addr
424
4
    }
425

            
426
    /// Return the address from this socks reply.
427
    ///
428
    /// The semantics of this port depend on the original socks command
429
    /// provided; see the SOCKS specification for more information.
430
4
    pub fn port(&self) -> u16 {
431
4
        self.port
432
4
    }
433
}
434

            
435
#[cfg(test)]
436
mod test {
437
    // @@ begin test lint list maintained by maint/add_warning @@
438
    #![allow(clippy::bool_assert_comparison)]
439
    #![allow(clippy::clone_on_copy)]
440
    #![allow(clippy::dbg_macro)]
441
    #![allow(clippy::mixed_attributes_style)]
442
    #![allow(clippy::print_stderr)]
443
    #![allow(clippy::print_stdout)]
444
    #![allow(clippy::single_char_pattern)]
445
    #![allow(clippy::unwrap_used)]
446
    #![allow(clippy::unchecked_time_subtraction)]
447
    #![allow(clippy::useless_vec)]
448
    #![allow(clippy::needless_pass_by_value)]
449
    #![allow(clippy::string_slice)] // See arti#2571
450
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
451
    use super::*;
452

            
453
    #[test]
454
    fn display_sa() {
455
        let a = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
456
        assert_eq!(a.to_string(), "127.0.0.1");
457

            
458
        let a = SocksAddr::Ip(IpAddr::V6("f00::9999".parse().unwrap()));
459
        assert_eq!(a.to_string(), "f00::9999");
460

            
461
        let a = SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap());
462
        assert_eq!(a.to_string(), "www.torproject.org");
463
    }
464

            
465
    #[test]
466
    fn ok_request() {
467
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
468
        let r = SocksRequest::new(
469
            SocksVersion::V4,
470
            SocksCmd::CONNECT,
471
            localhost_v4.clone(),
472
            1024,
473
            SocksAuth::NoAuth,
474
        )
475
        .unwrap();
476
        assert_eq!(r.version(), SocksVersion::V4);
477
        assert_eq!(r.command(), SocksCmd::CONNECT);
478
        assert_eq!(r.addr(), &localhost_v4);
479
        assert_eq!(r.auth(), &SocksAuth::NoAuth);
480
    }
481

            
482
    #[test]
483
    fn bad_request() {
484
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
485

            
486
        let e = SocksRequest::new(
487
            SocksVersion::V4,
488
            SocksCmd::BIND,
489
            localhost_v4.clone(),
490
            1024,
491
            SocksAuth::NoAuth,
492
        );
493
        assert!(matches!(e, Err(Error::NotImplemented(_))));
494

            
495
        let e = SocksRequest::new(
496
            SocksVersion::V4,
497
            SocksCmd::CONNECT,
498
            localhost_v4,
499
            0,
500
            SocksAuth::NoAuth,
501
        );
502
        assert!(matches!(e, Err(Error::Syntax)));
503
    }
504

            
505
    #[test]
506
    fn test_contains_zeros() {
507
        assert!(contains_zeros(b"Hello\0world"));
508
        assert!(!contains_zeros(b"Hello world"));
509
    }
510
}