tor_socksproto/
msg.rs

1//! Structures that represent SOCKS messages
2
3use crate::{Error, Result};
4
5use caret::caret_int;
6use std::fmt;
7use std::net::IpAddr;
8
9#[cfg(feature = "arbitrary")]
10use std::net::Ipv6Addr;
11
12use tor_error::bad_api_usage;
13
14#[cfg(feature = "arbitrary")]
15use 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]
21pub enum SocksVersion {
22    /// Socks v4.
23    V4,
24    /// Socks v5.
25    V5,
26}
27
28impl TryFrom<u8> for SocksVersion {
29    type Error = Error;
30    fn try_from(v: u8) -> Result<SocksVersion> {
31        match v {
32            4 => Ok(SocksVersion::V4),
33            5 => Ok(SocksVersion::V5),
34            _ => Err(Error::BadProtocol(v)),
35        }
36    }
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))]
46pub 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")]
63impl<'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)]
79pub 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")]
89impl<'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)]
106pub struct SocksHostname(String);
107
108#[cfg(feature = "arbitrary")]
109impl<'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]
124pub 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
133caret_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
151caret_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
205impl SocksCmd {
206    /// Return true if this is a supported command.
207    fn recognized(self) -> bool {
208        matches!(
209            self,
210            SocksCmd::CONNECT | SocksCmd::RESOLVE | SocksCmd::RESOLVE_PTR
211        )
212    }
213
214    /// Return true if this is a command for which we require a port.
215    fn requires_port(self) -> bool {
216        matches!(
217            self,
218            SocksCmd::CONNECT | SocksCmd::BIND | SocksCmd::UDP_ASSOCIATE
219        )
220    }
221}
222
223impl SocksStatus {
224    /// Convert this status into a value for use with SOCKS4 or SOCKS4a.
225    #[cfg(feature = "proxy-handshake")]
226    pub(crate) fn into_socks4_status(self) -> u8 {
227        match self {
228            SocksStatus::SUCCEEDED => 0x5A,
229            _ => 0x5B,
230        }
231    }
232    /// Create a status from a SOCKS4 or SOCKS4a reply code.
233    #[cfg(feature = "client-handshake")]
234    pub(crate) fn from_socks4_status(status: u8) -> Self {
235        match status {
236            0x5A => SocksStatus::SUCCEEDED,
237            0x5B => SocksStatus::GENERAL_FAILURE,
238            0x5C | 0x5D => SocksStatus::NOT_ALLOWED,
239            _ => SocksStatus::GENERAL_FAILURE,
240        }
241    }
242}
243
244impl TryFrom<String> for SocksHostname {
245    type Error = Error;
246    fn try_from(s: String) -> Result<SocksHostname> {
247        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        } 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            Ok(SocksHostname(s))
257        }
258    }
259}
260
261impl AsRef<str> for SocksHostname {
262    fn as_ref(&self) -> &str {
263        self.0.as_ref()
264    }
265}
266
267impl 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    fn validate(&self, version: SocksVersion) -> Result<()> {
273        match self {
274            SocksAuth::NoAuth => {}
275            SocksAuth::Socks4(data) => {
276                if version != SocksVersion::V4 || contains_zeros(data) {
277                    return Err(Error::Syntax);
278                }
279            }
280            SocksAuth::Username(user, pass) => {
281                if version != SocksVersion::V5
282                    || user.len() > u8::MAX as usize
283                    || pass.len() > u8::MAX as usize
284                {
285                    return Err(Error::Syntax);
286                }
287            }
288        }
289        Ok(())
290    }
291}
292
293/// Return true if b contains at least one zero.
294///
295/// Try to run in constant time.
296fn contains_zeros(b: &[u8]) -> bool {
297    use subtle::{Choice, ConstantTimeEq};
298    let c: Choice = b
299        .iter()
300        .fold(Choice::from(0), |seen_any, byte| seen_any | byte.ct_eq(&0));
301    c.unwrap_u8() != 0
302}
303
304impl 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    pub fn new(
309        version: SocksVersion,
310        cmd: SocksCmd,
311        addr: SocksAddr,
312        port: u16,
313        auth: SocksAuth,
314    ) -> Result<Self> {
315        if !cmd.recognized() {
316            return Err(Error::NotImplemented(
317                format!("SOCKS command {}", cmd).into(),
318            ));
319        }
320        if port == 0 && cmd.requires_port() {
321            return Err(Error::Syntax);
322        }
323        auth.validate(version)?;
324
325        Ok(SocksRequest {
326            version,
327            cmd,
328            addr,
329            port,
330            auth,
331        })
332    }
333
334    /// Return the negotiated version (4 or 5).
335    pub fn version(&self) -> SocksVersion {
336        self.version
337    }
338
339    /// Return the command that the client requested.
340    pub fn command(&self) -> SocksCmd {
341        self.cmd
342    }
343
344    /// Return the 'authentication' information from this request.
345    pub fn auth(&self) -> &SocksAuth {
346        &self.auth
347    }
348
349    /// Return the requested port.
350    pub fn port(&self) -> u16 {
351        self.port
352    }
353
354    /// Return the requested address.
355    pub fn addr(&self) -> &SocksAddr {
356        &self.addr
357    }
358}
359
360impl fmt::Display for SocksAddr {
361    /// Format a string (a hostname or IP address) corresponding to this
362    /// SocksAddr.
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        match self {
365            SocksAddr::Ip(a) => write!(f, "{}", a),
366            SocksAddr::Hostname(h) => write!(f, "{}", h.0),
367        }
368    }
369}
370
371/// The reply from a SOCKS proxy.
372#[derive(Debug, Clone)]
373pub 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
382impl SocksReply {
383    /// Create a new SocksReply.
384    #[cfg(feature = "client-handshake")]
385    pub(crate) fn new(status: SocksStatus, addr: SocksAddr, port: u16) -> Self {
386        Self { status, addr, port }
387    }
388
389    /// Return the status code from this socks reply.
390    pub fn status(&self) -> SocksStatus {
391        self.status
392    }
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    pub fn addr(&self) -> &SocksAddr {
402        &self.addr
403    }
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    pub fn port(&self) -> u16 {
410        self.port
411    }
412}
413
414#[cfg(test)]
415mod 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}