1use 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#[derive(Copy, Clone, Debug, Eq, PartialEq)]
19#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
20#[non_exhaustive]
21pub enum SocksVersion {
22 V4,
24 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#[derive(Clone, Debug)]
45#[cfg_attr(test, derive(PartialEq, Eq))]
46pub struct SocksRequest {
47 version: SocksVersion,
49 cmd: SocksCmd,
51 addr: SocksAddr,
53 port: u16,
55 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#[derive(Clone, Debug, PartialEq, Eq)]
78#[allow(clippy::exhaustive_enums)]
79pub enum SocksAddr {
80 Hostname(SocksHostname),
82 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#[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#[derive(Clone, Debug, PartialEq, Eq, Hash)]
122#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
123#[non_exhaustive]
124pub enum SocksAuth {
125 NoAuth,
127 Socks4(Vec<u8>),
129 Username(Vec<u8>, Vec<u8>),
131}
132
133caret_int! {
134 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
136 pub struct SocksCmd(u8) {
137 CONNECT = 1,
139 BIND = 2,
141 UDP_ASSOCIATE = 3,
143
144 RESOLVE = 0xF0,
146 RESOLVE_PTR = 0xF1,
148 }
149}
150
151caret_int! {
152 #[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
158 pub struct SocksStatus(u8) {
159 SUCCEEDED = 0x00,
161 GENERAL_FAILURE = 0x01,
163 NOT_ALLOWED = 0x02,
168 NETWORK_UNREACHABLE = 0x03,
170 HOST_UNREACHABLE = 0x04,
172 CONNECTION_REFUSED = 0x05,
174 TTL_EXPIRED = 0x06,
178 COMMAND_NOT_SUPPORTED = 0x07,
180 ADDRTYPE_NOT_SUPPORTED = 0x08,
182 HS_DESC_NOT_FOUND = 0xF0,
184 HS_DESC_INVALID = 0xF1,
186 HS_INTRO_FAILED = 0xF2,
188 HS_REND_FAILED = 0xF3,
190 HS_MISSING_CLIENT_AUTH = 0xF4,
192 HS_WRONG_CLIENT_AUTH = 0xF5,
194 HS_BAD_ADDRESS = 0xF6,
198 HS_INTRO_TIMEOUT = 0xF7
202 }
203}
204
205impl SocksCmd {
206 fn recognized(self) -> bool {
208 matches!(
209 self,
210 SocksCmd::CONNECT | SocksCmd::RESOLVE | SocksCmd::RESOLVE_PTR
211 )
212 }
213
214 fn requires_port(self) -> bool {
216 matches!(
217 self,
218 SocksCmd::CONNECT | SocksCmd::BIND | SocksCmd::UDP_ASSOCIATE
219 )
220 }
221}
222
223impl SocksStatus {
224 #[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 #[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 Err(bad_api_usage!("hostname too long").into())
251 } else if contains_zeros(s.as_bytes()) {
252 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 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
293fn 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 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 pub fn version(&self) -> SocksVersion {
336 self.version
337 }
338
339 pub fn command(&self) -> SocksCmd {
341 self.cmd
342 }
343
344 pub fn auth(&self) -> &SocksAuth {
346 &self.auth
347 }
348
349 pub fn port(&self) -> u16 {
351 self.port
352 }
353
354 pub fn addr(&self) -> &SocksAddr {
356 &self.addr
357 }
358}
359
360impl fmt::Display for SocksAddr {
361 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#[derive(Debug, Clone)]
373pub struct SocksReply {
374 status: SocksStatus,
376 addr: SocksAddr,
378 port: u16,
380}
381
382impl SocksReply {
383 #[cfg(feature = "client-handshake")]
385 pub(crate) fn new(status: SocksStatus, addr: SocksAddr, port: u16) -> Self {
386 Self { status, addr, port }
387 }
388
389 pub fn status(&self) -> SocksStatus {
391 self.status
392 }
393
394 pub fn addr(&self) -> &SocksAddr {
402 &self.addr
403 }
404
405 pub fn port(&self) -> u16 {
410 self.port
411 }
412}
413
414#[cfg(test)]
415mod test {
416 #![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 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}