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
impl TryFrom<u8> for SocksVersion {
29
    type Error = Error;
30
754
    fn try_from(v: u8) -> Result<SocksVersion> {
31
754
        match v {
32
362
            4 => Ok(SocksVersion::V4),
33
392
            5 => Ok(SocksVersion::V5),
34
            _ => Err(Error::BadProtocol(v)),
35
        }
36
754
    }
37
}
38

            
39
impl fmt::Display for SocksVersion {
40
423
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41
423
        match self {
42
141
            SocksVersion::V4 => write!(f, "socks4"),
43
282
            SocksVersion::V5 => write!(f, "socks5"),
44
        }
45
423
    }
46
}
47

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

            
71
#[cfg(feature = "arbitrary")]
72
impl<'a> Arbitrary<'a> for SocksRequest {
73
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
74
        let version = SocksVersion::arbitrary(u)?;
75
        let cmd = SocksCmd::arbitrary(u)?;
76
        let addr = SocksAddr::arbitrary(u)?;
77
        let port = u16::arbitrary(u)?;
78
        let auth = SocksAuth::arbitrary(u)?;
79

            
80
        SocksRequest::new(version, cmd, addr, port, auth)
81
            .map_err(|_| arbitrary::Error::IncorrectFormat)
82
    }
83
}
84

            
85
/// An address sent or received as part of a SOCKS handshake
86
#[derive(Clone, Debug, PartialEq, Eq)]
87
#[allow(clippy::exhaustive_enums)]
88
pub enum SocksAddr {
89
    /// A regular DNS hostname.
90
    Hostname(SocksHostname),
91
    /// An IP address.  (Tor doesn't like to receive these during SOCKS
92
    /// handshakes, since they usually indicate that the hostname lookup
93
    /// happened somewhere else.)
94
    Ip(IpAddr),
95
}
96

            
97
#[cfg(feature = "arbitrary")]
98
impl<'a> Arbitrary<'a> for SocksAddr {
99
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
100
        use std::net::Ipv4Addr;
101
        let b = u8::arbitrary(u)?;
102
        Ok(match b % 3 {
103
            0 => SocksAddr::Hostname(SocksHostname::arbitrary(u)?),
104
            1 => SocksAddr::Ip(IpAddr::V4(Ipv4Addr::arbitrary(u)?)),
105
            _ => SocksAddr::Ip(IpAddr::V6(Ipv6Addr::arbitrary(u)?)),
106
        })
107
    }
108
    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
109
        (1, Some(256))
110
    }
111
}
112

            
113
/// A hostname for use with SOCKS.  It is limited in length.
114
#[derive(Clone, Debug, PartialEq, Eq)]
115
pub struct SocksHostname(String);
116

            
117
#[cfg(feature = "arbitrary")]
118
impl<'a> Arbitrary<'a> for SocksHostname {
119
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
120
        String::arbitrary(u)?
121
            .try_into()
122
            .map_err(|_| arbitrary::Error::IncorrectFormat)
123
    }
124
    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
125
        (0, Some(255))
126
    }
127
}
128

            
129
/// Provided authentication from a SOCKS handshake
130
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
131
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
132
#[non_exhaustive]
133
pub enum SocksAuth {
134
    /// No authentication was provided
135
    NoAuth,
136
    /// Socks4 authentication (a string) was provided.
137
    Socks4(Vec<u8>),
138
    /// Socks5 username/password authentication was provided.
139
    Username(Vec<u8>, Vec<u8>),
140
}
141

            
142
caret_int! {
143
    /// Command from the socks client telling us what to do.
144
    #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
145
    pub struct SocksCmd(u8) {
146
        /// Connect to a remote TCP address:port.
147
        CONNECT = 1,
148
        /// Not supported in Tor.
149
        BIND = 2,
150
        /// Not supported in Tor.
151
        UDP_ASSOCIATE = 3,
152

            
153
        /// Lookup a hostname, return an IP address. (Tor only.)
154
        RESOLVE = 0xF0,
155
        /// Lookup an IP address, return a hostname. (Tor only.)
156
        RESOLVE_PTR = 0xF1,
157
    }
158
}
159

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

            
214
impl SocksCmd {
215
    /// Return true if this is a supported command.
216
74
    fn recognized(self) -> bool {
217
2
        matches!(
218
74
            self,
219
            SocksCmd::CONNECT | SocksCmd::RESOLVE | SocksCmd::RESOLVE_PTR
220
        )
221
74
    }
222

            
223
    /// Return true if this is a command for which we require a port.
224
2
    fn requires_port(self) -> bool {
225
        matches!(
226
2
            self,
227
            SocksCmd::CONNECT | SocksCmd::BIND | SocksCmd::UDP_ASSOCIATE
228
        )
229
2
    }
230
}
231

            
232
impl SocksStatus {
233
    /// Convert this status into a value for use with SOCKS4 or SOCKS4a.
234
    #[cfg(feature = "proxy-handshake")]
235
24
    pub(crate) fn into_socks4_status(self) -> u8 {
236
24
        match self {
237
12
            SocksStatus::SUCCEEDED => 0x5A,
238
12
            _ => 0x5B,
239
        }
240
24
    }
241
    /// Create a status from a SOCKS4 or SOCKS4a reply code.
242
    #[cfg(feature = "client-handshake")]
243
24
    pub(crate) fn from_socks4_status(status: u8) -> Self {
244
24
        match status {
245
14
            0x5A => SocksStatus::SUCCEEDED,
246
10
            0x5B => SocksStatus::GENERAL_FAILURE,
247
            0x5C | 0x5D => SocksStatus::NOT_ALLOWED,
248
            _ => SocksStatus::GENERAL_FAILURE,
249
        }
250
24
    }
251
}
252

            
253
impl TryFrom<String> for SocksHostname {
254
    type Error = Error;
255
52
    fn try_from(s: String) -> Result<SocksHostname> {
256
52
        if s.len() > 255 {
257
            // This is only a limitation for Socks 5, but we enforce it in both
258
            // cases, for simplicity.
259
            Err(bad_api_usage!("hostname too long").into())
260
52
        } else if contains_zeros(s.as_bytes()) {
261
            // This is only a limitation for Socks 4, but we enforce it in both
262
            // cases, for simplicity.
263
            Err(Error::Syntax)
264
        } else {
265
52
            Ok(SocksHostname(s))
266
        }
267
52
    }
268
}
269

            
270
impl AsRef<str> for SocksHostname {
271
16
    fn as_ref(&self) -> &str {
272
16
        self.0.as_ref()
273
16
    }
274
}
275

            
276
impl SocksAuth {
277
    /// Check whether this authentication is well-formed and compatible with the
278
    /// provided SOCKS version.
279
    ///
280
    /// Return an error if not.
281
70
    fn validate(&self, version: SocksVersion) -> Result<()> {
282
70
        match self {
283
40
            SocksAuth::NoAuth => {}
284
16
            SocksAuth::Socks4(data) => {
285
16
                if version != SocksVersion::V4 || contains_zeros(data) {
286
                    return Err(Error::Syntax);
287
16
                }
288
            }
289
14
            SocksAuth::Username(user, pass) => {
290
14
                if version != SocksVersion::V5
291
14
                    || user.len() > u8::MAX as usize
292
14
                    || pass.len() > u8::MAX as usize
293
                {
294
                    return Err(Error::Syntax);
295
14
                }
296
            }
297
        }
298
70
        Ok(())
299
70
    }
300
}
301

            
302
/// Return true if b contains at least one zero.
303
///
304
/// Try to run in constant time.
305
72
fn contains_zeros(b: &[u8]) -> bool {
306
    use subtle::{Choice, ConstantTimeEq};
307
72
    let c: Choice = b
308
72
        .iter()
309
1134
        .fold(Choice::from(0), |seen_any, byte| seen_any | byte.ct_eq(&0));
310
72
    c.unwrap_u8() != 0
311
72
}
312

            
313
impl SocksRequest {
314
    /// Create a SocksRequest with a given set of fields.
315
    ///
316
    /// Return an error if the inputs aren't supported or valid.
317
74
    pub fn new(
318
74
        version: SocksVersion,
319
74
        cmd: SocksCmd,
320
74
        addr: SocksAddr,
321
74
        port: u16,
322
74
        auth: SocksAuth,
323
74
    ) -> Result<Self> {
324
74
        if !cmd.recognized() {
325
2
            return Err(Error::NotImplemented(
326
2
                format!("SOCKS command {}", cmd).into(),
327
2
            ));
328
72
        }
329
72
        if port == 0 && cmd.requires_port() {
330
2
            return Err(Error::Syntax);
331
70
        }
332
70
        auth.validate(version)?;
333

            
334
70
        Ok(SocksRequest {
335
70
            version,
336
70
            cmd,
337
70
            addr,
338
70
            port,
339
70
            auth,
340
70
        })
341
74
    }
342

            
343
    /// Return the negotiated version (4 or 5).
344
106
    pub fn version(&self) -> SocksVersion {
345
106
        self.version
346
106
    }
347

            
348
    /// Return the command that the client requested.
349
60
    pub fn command(&self) -> SocksCmd {
350
60
        self.cmd
351
60
    }
352

            
353
    /// Return the 'authentication' information from this request.
354
70
    pub fn auth(&self) -> &SocksAuth {
355
70
        &self.auth
356
70
    }
357

            
358
    /// Return the requested port.
359
64
    pub fn port(&self) -> u16 {
360
64
        self.port
361
64
    }
362

            
363
    /// Return the requested address.
364
74
    pub fn addr(&self) -> &SocksAddr {
365
74
        &self.addr
366
74
    }
367
}
368

            
369
impl fmt::Display for SocksAddr {
370
    /// Format a string (a hostname or IP address) corresponding to this
371
    /// SocksAddr.
372
36
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373
36
        match self {
374
18
            SocksAddr::Ip(a) => write!(f, "{}", a),
375
18
            SocksAddr::Hostname(h) => write!(f, "{}", h.0),
376
        }
377
36
    }
378
}
379

            
380
/// The reply from a SOCKS proxy.
381
#[derive(Debug, Clone)]
382
pub struct SocksReply {
383
    /// The provided status code
384
    status: SocksStatus,
385
    /// The provided address, if any.
386
    addr: SocksAddr,
387
    /// The provided port.
388
    port: u16,
389
}
390

            
391
impl SocksReply {
392
    /// Create a new SocksReply.
393
    #[cfg(feature = "client-handshake")]
394
48
    pub(crate) fn new(status: SocksStatus, addr: SocksAddr, port: u16) -> Self {
395
48
        Self { status, addr, port }
396
48
    }
397

            
398
    /// Return the status code from this socks reply.
399
48
    pub fn status(&self) -> SocksStatus {
400
48
        self.status
401
48
    }
402

            
403
    /// Return the address from this socks reply.
404
    ///
405
    /// The semantics of this address depend on the original socks command
406
    /// provided; see the SOCKS specification for more information.
407
    ///
408
    /// Note that some implementations (including Tor) will return `0.0.0.0` or
409
    /// `[::]` to indicate "no address given".
410
8
    pub fn addr(&self) -> &SocksAddr {
411
8
        &self.addr
412
8
    }
413

            
414
    /// Return the address from this socks reply.
415
    ///
416
    /// The semantics of this port depend on the original socks command
417
    /// provided; see the SOCKS specification for more information.
418
8
    pub fn port(&self) -> u16 {
419
8
        self.port
420
8
    }
421
}
422

            
423
#[cfg(test)]
424
mod test {
425
    // @@ begin test lint list maintained by maint/add_warning @@
426
    #![allow(clippy::bool_assert_comparison)]
427
    #![allow(clippy::clone_on_copy)]
428
    #![allow(clippy::dbg_macro)]
429
    #![allow(clippy::mixed_attributes_style)]
430
    #![allow(clippy::print_stderr)]
431
    #![allow(clippy::print_stdout)]
432
    #![allow(clippy::single_char_pattern)]
433
    #![allow(clippy::unwrap_used)]
434
    #![allow(clippy::unchecked_time_subtraction)]
435
    #![allow(clippy::useless_vec)]
436
    #![allow(clippy::needless_pass_by_value)]
437
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
438
    use super::*;
439

            
440
    #[test]
441
    fn display_sa() {
442
        let a = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
443
        assert_eq!(a.to_string(), "127.0.0.1");
444

            
445
        let a = SocksAddr::Ip(IpAddr::V6("f00::9999".parse().unwrap()));
446
        assert_eq!(a.to_string(), "f00::9999");
447

            
448
        let a = SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap());
449
        assert_eq!(a.to_string(), "www.torproject.org");
450
    }
451

            
452
    #[test]
453
    fn ok_request() {
454
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
455
        let r = SocksRequest::new(
456
            SocksVersion::V4,
457
            SocksCmd::CONNECT,
458
            localhost_v4.clone(),
459
            1024,
460
            SocksAuth::NoAuth,
461
        )
462
        .unwrap();
463
        assert_eq!(r.version(), SocksVersion::V4);
464
        assert_eq!(r.command(), SocksCmd::CONNECT);
465
        assert_eq!(r.addr(), &localhost_v4);
466
        assert_eq!(r.auth(), &SocksAuth::NoAuth);
467
    }
468

            
469
    #[test]
470
    fn bad_request() {
471
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
472

            
473
        let e = SocksRequest::new(
474
            SocksVersion::V4,
475
            SocksCmd::BIND,
476
            localhost_v4.clone(),
477
            1024,
478
            SocksAuth::NoAuth,
479
        );
480
        assert!(matches!(e, Err(Error::NotImplemented(_))));
481

            
482
        let e = SocksRequest::new(
483
            SocksVersion::V4,
484
            SocksCmd::CONNECT,
485
            localhost_v4,
486
            0,
487
            SocksAuth::NoAuth,
488
        );
489
        assert!(matches!(e, Err(Error::Syntax)));
490
    }
491

            
492
    #[test]
493
    fn test_contains_zeros() {
494
        assert!(contains_zeros(b"Hello\0world"));
495
        assert!(!contains_zeros(b"Hello world"));
496
    }
497
}