/ crates / tor-socksproto / src / msg.rs
msg.rs
  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      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))]
 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      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  
223  impl 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  
244  impl 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  
261  impl AsRef<str> for SocksHostname {
262      fn as_ref(&self) -> &str {
263          self.0.as_ref()
264      }
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      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.
296  fn 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  
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      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  
360  impl 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)]
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      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)]
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  }