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 }