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