1 //! Structures that represent SOCKS messages 2 3 use crate::{Error, Result}; 4 5 use caret::caret_int; 6 use std::convert::TryFrom; 7 use std::fmt; 8 use std::net::IpAddr; 9 10 /// A supported SOCKS version. 11 #[derive(Copy, Clone, Debug, Eq, PartialEq)] 12 #[non_exhaustive] 13 pub enum SocksVersion { 14 /// Socks v4. 15 V4, 16 /// Socks v5. 17 V5, 18 } 19 20 impl From<SocksVersion> for u8 { from(v: SocksVersion) -> u821 fn from(v: SocksVersion) -> u8 { 22 match v { 23 SocksVersion::V4 => 4, 24 SocksVersion::V5 => 5, 25 } 26 } 27 } 28 29 impl TryFrom<u8> for SocksVersion { 30 type Error = Error; try_from(v: u8) -> Result<SocksVersion>31 fn try_from(v: u8) -> Result<SocksVersion> { 32 match v { 33 4 => Ok(SocksVersion::V4), 34 5 => Ok(SocksVersion::V5), 35 _ => Err(Error::BadProtocol(v)), 36 } 37 } 38 } 39 40 /// A completed SOCKS request, as negotiated on a SOCKS connection. 41 /// 42 /// Once this request is done, we know where to connect. Don't 43 /// discard this object immediately: Use it to report success or 44 /// failure. 45 #[derive(Clone, Debug)] 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 /// An address sent or received as part of a SOCKS handshake 63 #[derive(Clone, Debug, PartialEq, Eq)] 64 #[allow(clippy::exhaustive_enums)] 65 pub enum SocksAddr { 66 /// A regular DNS hostname. 67 Hostname(SocksHostname), 68 /// An IP address. (Tor doesn't like to receive these during SOCKS 69 /// handshakes, since they usually indicate that the hostname lookup 70 /// happened somewhere else.) 71 Ip(IpAddr), 72 } 73 74 /// A hostname for use with SOCKS. It is limited in length. 75 #[derive(Clone, Debug, PartialEq, Eq)] 76 pub struct SocksHostname(String); 77 78 /// Provided authentication from a SOCKS handshake 79 #[derive(Clone, Debug, PartialEq, Eq, Hash)] 80 #[non_exhaustive] 81 pub enum SocksAuth { 82 /// No authentication was provided 83 NoAuth, 84 /// Socks4 authentication (a string) was provided. 85 Socks4(Vec<u8>), 86 /// Socks5 username/password authentication was provided. 87 Username(Vec<u8>, Vec<u8>), 88 } 89 90 caret_int! { 91 /// Command from the socks client telling us what to do. 92 pub struct SocksCmd(u8) { 93 /// Connect to a remote TCP address:port. 94 CONNECT = 1, 95 /// Not supported in Tor. 96 BIND = 2, 97 /// Not supported in Tor. 98 UDP_ASSOCIATE = 3, 99 100 /// Lookup a hostname, return an IP address. (Tor only.) 101 RESOLVE = 0xF0, 102 /// Lookup an IP address, return a hostname. (Tor only.) 103 RESOLVE_PTR = 0xF1, 104 } 105 } 106 107 caret_int! { 108 /// Possible reply status values from a SOCKS5 handshake. 109 /// 110 /// Note that the documentation for these values is kind of scant, 111 /// and is limited to what the RFC says. Note also that SOCKS4 112 /// only represents success and failure. 113 pub struct SocksStatus(u8) { 114 /// RFC 1928: "succeeded" 115 SUCCEEDED = 0x00, 116 /// RFC 1928: "general SOCKS server failure" 117 GENERAL_FAILURE = 0x01, 118 /// RFC 1928: "connection not allowable by ruleset" 119 /// 120 /// (This is the only occurrence of 'ruleset' or even 'rule' 121 /// in RFC 1928.) 122 NOT_ALLOWED = 0x02, 123 /// RFC 1928: "Network unreachable" 124 NETWORK_UNREACHABLE = 0x03, 125 /// RFC 1928: "Host unreachable" 126 HOST_UNREACHABLE = 0x04, 127 /// RFC 1928: "Connection refused" 128 CONNECTION_REFUSED = 0x05, 129 /// RFC 1928: "TTL expired" 130 /// 131 /// (This is the only occurrence of 'TTL' in RFC 1928.) 132 TTL_EXPIRED = 0x06, 133 /// RFC 1929: "Command not supported" 134 COMMAND_NOT_SUPPORTED = 0x07, 135 /// RFC 1929: "Address type not supported" 136 ADDRTYPE_NOT_SUPPORTED = 0x08, 137 } 138 } 139 140 impl SocksCmd { 141 /// Return true if this is a supported command. recognized(self) -> bool142 fn recognized(self) -> bool { 143 matches!( 144 self, 145 SocksCmd::CONNECT | SocksCmd::RESOLVE | SocksCmd::RESOLVE_PTR 146 ) 147 } 148 149 /// Return true if this is a command for which we require a port. requires_port(self) -> bool150 fn requires_port(self) -> bool { 151 matches!( 152 self, 153 SocksCmd::CONNECT | SocksCmd::BIND | SocksCmd::UDP_ASSOCIATE 154 ) 155 } 156 } 157 158 impl SocksStatus { 159 /// Convert this status into a value for use with SOCKS4 or SOCKS4a. into_socks4_status(self) -> u8160 pub(crate) fn into_socks4_status(self) -> u8 { 161 match self { 162 SocksStatus::SUCCEEDED => 0x5A, 163 _ => 0x5B, 164 } 165 } 166 } 167 168 impl TryFrom<String> for SocksHostname { 169 type Error = Error; try_from(s: String) -> Result<SocksHostname>170 fn try_from(s: String) -> Result<SocksHostname> { 171 if s.len() > 255 { 172 Err(Error::Syntax) 173 } else { 174 Ok(SocksHostname(s)) 175 } 176 } 177 } 178 179 impl AsRef<str> for SocksHostname { as_ref(&self) -> &str180 fn as_ref(&self) -> &str { 181 self.0.as_ref() 182 } 183 } 184 185 impl From<SocksHostname> for String { from(s: SocksHostname) -> String186 fn from(s: SocksHostname) -> String { 187 s.0 188 } 189 } 190 191 impl SocksRequest { 192 /// Create a SocksRequest with a given set of fields. 193 /// 194 /// Return an error if the inputs aren't supported or valid. new( version: SocksVersion, cmd: SocksCmd, addr: SocksAddr, port: u16, auth: SocksAuth, ) -> Result<Self>195 pub(crate) fn new( 196 version: SocksVersion, 197 cmd: SocksCmd, 198 addr: SocksAddr, 199 port: u16, 200 auth: SocksAuth, 201 ) -> Result<Self> { 202 if !cmd.recognized() { 203 return Err(Error::NoSupport); 204 } 205 if port == 0 && cmd.requires_port() { 206 return Err(Error::Syntax); 207 } 208 209 Ok(SocksRequest { 210 version, 211 cmd, 212 addr, 213 port, 214 auth, 215 }) 216 } 217 218 /// Return the negotiated version (4 or 5). version(&self) -> SocksVersion219 pub fn version(&self) -> SocksVersion { 220 self.version 221 } 222 223 /// Return the command that the client requested. command(&self) -> SocksCmd224 pub fn command(&self) -> SocksCmd { 225 self.cmd 226 } 227 228 /// Return the 'authentication' information from this request. auth(&self) -> &SocksAuth229 pub fn auth(&self) -> &SocksAuth { 230 &self.auth 231 } 232 233 /// Return the requested port. port(&self) -> u16234 pub fn port(&self) -> u16 { 235 self.port 236 } 237 238 /// Return the requested address. addr(&self) -> &SocksAddr239 pub fn addr(&self) -> &SocksAddr { 240 &self.addr 241 } 242 } 243 244 impl fmt::Display for SocksAddr { 245 /// Format a string (a hostname or IP address) corresponding to this 246 /// SocksAddr. fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 248 match self { 249 SocksAddr::Ip(a) => write!(f, "{}", a), 250 SocksAddr::Hostname(h) => write!(f, "{}", h.0), 251 } 252 } 253 } 254 255 #[cfg(test)] 256 mod test { 257 #![allow(clippy::unwrap_used)] 258 use super::*; 259 use std::convert::TryInto; 260 261 #[test] display_sa()262 fn display_sa() { 263 let a = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap())); 264 assert_eq!(a.to_string(), "127.0.0.1"); 265 266 let a = SocksAddr::Ip(IpAddr::V6("f00::9999".parse().unwrap())); 267 assert_eq!(a.to_string(), "f00::9999"); 268 269 let a = SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()); 270 assert_eq!(a.to_string(), "www.torproject.org"); 271 } 272 273 #[test] ok_request()274 fn ok_request() { 275 let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap())); 276 let r = SocksRequest::new( 277 SocksVersion::V4, 278 SocksCmd::CONNECT, 279 localhost_v4.clone(), 280 1024, 281 SocksAuth::NoAuth, 282 ) 283 .unwrap(); 284 assert_eq!(r.version(), SocksVersion::V4); 285 assert_eq!(r.command(), SocksCmd::CONNECT); 286 assert_eq!(r.addr(), &localhost_v4); 287 assert_eq!(r.auth(), &SocksAuth::NoAuth); 288 } 289 290 #[test] bad_request()291 fn bad_request() { 292 let localhost_v4 = SocksAddr::Ip(IpAddr::V4("127.0.0.1".parse().unwrap())); 293 294 let e = SocksRequest::new( 295 SocksVersion::V4, 296 SocksCmd::BIND, 297 localhost_v4.clone(), 298 1024, 299 SocksAuth::NoAuth, 300 ); 301 assert!(matches!(e, Err(Error::NoSupport))); 302 303 let e = SocksRequest::new( 304 SocksVersion::V4, 305 SocksCmd::CONNECT, 306 localhost_v4, 307 0, 308 SocksAuth::NoAuth, 309 ); 310 assert!(matches!(e, Err(Error::Syntax))); 311 } 312 } 313