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

            
62
#[cfg(feature = "arbitrary")]
63
impl<'a> Arbitrary<'a> for SocksRequest {
64
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
65
        let version = SocksVersion::arbitrary(u)?;
66
        let cmd = SocksCmd::arbitrary(u)?;
67
        let addr = SocksAddr::arbitrary(u)?;
68
        let port = u16::arbitrary(u)?;
69
        let auth = SocksAuth::arbitrary(u)?;
70

            
71
        SocksRequest::new(version, cmd, addr, port, auth)
72
            .map_err(|_| arbitrary::Error::IncorrectFormat)
73
    }
74
}
75

            
76
/// An address sent or received as part of a SOCKS handshake
77
#[derive(Clone, Debug, PartialEq, Eq)]
78
#[allow(clippy::exhaustive_enums)]
79
pub enum SocksAddr {
80
    /// A regular DNS hostname.
81
    Hostname(SocksHostname),
82
    /// An IP address.  (Tor doesn't like to receive these during SOCKS
83
    /// handshakes, since they usually indicate that the hostname lookup
84
    /// happened somewhere else.)
85
    Ip(IpAddr),
86
}
87

            
88
#[cfg(feature = "arbitrary")]
89
impl<'a> Arbitrary<'a> for SocksAddr {
90
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
91
        use std::net::Ipv4Addr;
92
        let b = u8::arbitrary(u)?;
93
        Ok(match b % 3 {
94
            0 => SocksAddr::Hostname(SocksHostname::arbitrary(u)?),
95
            1 => SocksAddr::Ip(IpAddr::V4(Ipv4Addr::arbitrary(u)?)),
96
            _ => SocksAddr::Ip(IpAddr::V6(Ipv6Addr::arbitrary(u)?)),
97
        })
98
    }
99
    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
100
        (1, Some(256))
101
    }
102
}
103

            
104
/// A hostname for use with SOCKS.  It is limited in length.
105
#[derive(Clone, Debug, PartialEq, Eq)]
106
pub struct SocksHostname(String);
107

            
108
#[cfg(feature = "arbitrary")]
109
impl<'a> Arbitrary<'a> for SocksHostname {
110
    fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
111
        String::arbitrary(u)?
112
            .try_into()
113
            .map_err(|_| arbitrary::Error::IncorrectFormat)
114
    }
115
    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
116
        (0, Some(255))
117
    }
118
}
119

            
120
/// Provided authentication from a SOCKS handshake
121
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
122
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
123
#[non_exhaustive]
124
pub enum SocksAuth {
125
    /// No authentication was provided
126
    NoAuth,
127
    /// Socks4 authentication (a string) was provided.
128
    Socks4(Vec<u8>),
129
    /// Socks5 username/password authentication was provided.
130
    Username(Vec<u8>, Vec<u8>),
131
}
132

            
133
caret_int! {
134
    /// Command from the socks client telling us what to do.
135
    #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
136
    pub struct SocksCmd(u8) {
137
        /// Connect to a remote TCP address:port.
138
        CONNECT = 1,
139
        /// Not supported in Tor.
140
        BIND = 2,
141
        /// Not supported in Tor.
142
        UDP_ASSOCIATE = 3,
143

            
144
        /// Lookup a hostname, return an IP address. (Tor only.)
145
        RESOLVE = 0xF0,
146
        /// Lookup an IP address, return a hostname. (Tor only.)
147
        RESOLVE_PTR = 0xF1,
148
    }
149
}
150

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

            
205
impl SocksCmd {
206
    /// Return true if this is a supported command.
207
74
    fn recognized(self) -> bool {
208
2
        matches!(
209
74
            self,
210
            SocksCmd::CONNECT | SocksCmd::RESOLVE | SocksCmd::RESOLVE_PTR
211
        )
212
74
    }
213

            
214
    /// Return true if this is a command for which we require a port.
215
2
    fn requires_port(self) -> bool {
216
        matches!(
217
2
            self,
218
            SocksCmd::CONNECT | SocksCmd::BIND | SocksCmd::UDP_ASSOCIATE
219
        )
220
2
    }
221
}
222

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

            
244
impl TryFrom<String> for SocksHostname {
245
    type Error = Error;
246
52
    fn try_from(s: String) -> Result<SocksHostname> {
247
52
        if s.len() > 255 {
248
            // This is only a limitation for Socks 5, but we enforce it in both
249
            // cases, for simplicity.
250
            Err(bad_api_usage!("hostname too long").into())
251
52
        } else if contains_zeros(s.as_bytes()) {
252
            // This is only a limitation for Socks 4, but we enforce it in both
253
            // cases, for simplicity.
254
            Err(Error::Syntax)
255
        } else {
256
52
            Ok(SocksHostname(s))
257
        }
258
52
    }
259
}
260

            
261
impl AsRef<str> for SocksHostname {
262
16
    fn as_ref(&self) -> &str {
263
16
        self.0.as_ref()
264
16
    }
265
}
266

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

            
293
/// Return true if b contains at least one zero.
294
///
295
/// Try to run in constant time.
296
72
fn contains_zeros(b: &[u8]) -> bool {
297
    use subtle::{Choice, ConstantTimeEq};
298
72
    let c: Choice = b
299
72
        .iter()
300
1134
        .fold(Choice::from(0), |seen_any, byte| seen_any | byte.ct_eq(&0));
301
72
    c.unwrap_u8() != 0
302
72
}
303

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

            
325
70
        Ok(SocksRequest {
326
70
            version,
327
70
            cmd,
328
70
            addr,
329
70
            port,
330
70
            auth,
331
70
        })
332
74
    }
333

            
334
    /// Return the negotiated version (4 or 5).
335
106
    pub fn version(&self) -> SocksVersion {
336
106
        self.version
337
106
    }
338

            
339
    /// Return the command that the client requested.
340
60
    pub fn command(&self) -> SocksCmd {
341
60
        self.cmd
342
60
    }
343

            
344
    /// Return the 'authentication' information from this request.
345
70
    pub fn auth(&self) -> &SocksAuth {
346
70
        &self.auth
347
70
    }
348

            
349
    /// Return the requested port.
350
64
    pub fn port(&self) -> u16 {
351
64
        self.port
352
64
    }
353

            
354
    /// Return the requested address.
355
74
    pub fn addr(&self) -> &SocksAddr {
356
74
        &self.addr
357
74
    }
358
}
359

            
360
impl fmt::Display for SocksAddr {
361
    /// Format a string (a hostname or IP address) corresponding to this
362
    /// SocksAddr.
363
36
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364
36
        match self {
365
18
            SocksAddr::Ip(a) => write!(f, "{}", a),
366
18
            SocksAddr::Hostname(h) => write!(f, "{}", h.0),
367
        }
368
36
    }
369
}
370

            
371
/// The reply from a SOCKS proxy.
372
#[derive(Debug, Clone)]
373
pub struct SocksReply {
374
    /// The provided status code
375
    status: SocksStatus,
376
    /// The provided address, if any.
377
    addr: SocksAddr,
378
    /// The provided port.
379
    port: u16,
380
}
381

            
382
impl SocksReply {
383
    /// Create a new SocksReply.
384
    #[cfg(feature = "client-handshake")]
385
48
    pub(crate) fn new(status: SocksStatus, addr: SocksAddr, port: u16) -> Self {
386
48
        Self { status, addr, port }
387
48
    }
388

            
389
    /// Return the status code from this socks reply.
390
48
    pub fn status(&self) -> SocksStatus {
391
48
        self.status
392
48
    }
393

            
394
    /// Return the address from this socks reply.
395
    ///
396
    /// The semantics of this address depend on the original socks command
397
    /// provided; see the SOCKS specification for more information.
398
    ///
399
    /// Note that some implementations (including Tor) will return `0.0.0.0` or
400
    /// `[::]` to indicate "no address given".
401
8
    pub fn addr(&self) -> &SocksAddr {
402
8
        &self.addr
403
8
    }
404

            
405
    /// Return the address from this socks reply.
406
    ///
407
    /// The semantics of this port depend on the original socks command
408
    /// provided; see the SOCKS specification for more information.
409
8
    pub fn port(&self) -> u16 {
410
8
        self.port
411
8
    }
412
}
413

            
414
#[cfg(test)]
415
mod test {
416
    // @@ begin test lint list maintained by maint/add_warning @@
417
    #![allow(clippy::bool_assert_comparison)]
418
    #![allow(clippy::clone_on_copy)]
419
    #![allow(clippy::dbg_macro)]
420
    #![allow(clippy::mixed_attributes_style)]
421
    #![allow(clippy::print_stderr)]
422
    #![allow(clippy::print_stdout)]
423
    #![allow(clippy::single_char_pattern)]
424
    #![allow(clippy::unwrap_used)]
425
    #![allow(clippy::unchecked_duration_subtraction)]
426
    #![allow(clippy::useless_vec)]
427
    #![allow(clippy::needless_pass_by_value)]
428
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
429
    use super::*;
430

            
431
    #[test]
432
    fn display_sa() {
433
        let a = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
434
        assert_eq!(a.to_string(), "127.0.0.1");
435

            
436
        let a = SocksAddr::Ip(IpAddr::V6("f00::9999".parse().unwrap()));
437
        assert_eq!(a.to_string(), "f00::9999");
438

            
439
        let a = SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap());
440
        assert_eq!(a.to_string(), "www.torproject.org");
441
    }
442

            
443
    #[test]
444
    fn ok_request() {
445
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
446
        let r = SocksRequest::new(
447
            SocksVersion::V4,
448
            SocksCmd::CONNECT,
449
            localhost_v4.clone(),
450
            1024,
451
            SocksAuth::NoAuth,
452
        )
453
        .unwrap();
454
        assert_eq!(r.version(), SocksVersion::V4);
455
        assert_eq!(r.command(), SocksCmd::CONNECT);
456
        assert_eq!(r.addr(), &localhost_v4);
457
        assert_eq!(r.auth(), &SocksAuth::NoAuth);
458
    }
459

            
460
    #[test]
461
    fn bad_request() {
462
        let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap()));
463

            
464
        let e = SocksRequest::new(
465
            SocksVersion::V4,
466
            SocksCmd::BIND,
467
            localhost_v4.clone(),
468
            1024,
469
            SocksAuth::NoAuth,
470
        );
471
        assert!(matches!(e, Err(Error::NotImplemented(_))));
472

            
473
        let e = SocksRequest::new(
474
            SocksVersion::V4,
475
            SocksCmd::CONNECT,
476
            localhost_v4,
477
            0,
478
            SocksAuth::NoAuth,
479
        );
480
        assert!(matches!(e, Err(Error::Syntax)));
481
    }
482

            
483
    #[test]
484
    fn test_contains_zeros() {
485
        assert!(contains_zeros(b"Hello\0world"));
486
        assert!(!contains_zeros(b"Hello world"));
487
    }
488
}