1#![allow(dead_code)]
14
15use std::{
16 net::{IpAddr, SocketAddr},
17 sync::Arc,
18};
19
20use base64ct::{Base64, Encoding};
21use futures::io::{AsyncBufReadExt, BufReader};
22use futures::{AsyncReadExt, AsyncWriteExt};
23use httparse;
24use safelog::Sensitive;
25use tor_linkspec::PtTargetAddr;
26use tor_rtcompat::NetStreamProvider;
27use tor_socksproto::{
28 Handshake as _, SocksAddr, SocksAuth, SocksClientHandshake, SocksCmd, SocksRequest,
29 SocksStatus, SocksVersion,
30};
31use tracing::trace;
32
33#[cfg(feature = "pt-client")]
34use super::TransportImplHelper;
35#[cfg(feature = "pt-client")]
36use async_trait::async_trait;
37#[cfg(feature = "pt-client")]
38use tor_error::bad_api_usage;
39#[cfg(feature = "pt-client")]
40use tor_linkspec::{ChannelMethod, HasChanMethod, OwnedChanTarget};
41#[cfg(feature = "pt-client")]
42use tor_proto::peer::PeerAddr;
43
44#[derive(Clone, Debug, Eq, PartialEq)]
46#[non_exhaustive]
47pub enum Protocol {
48 Socks(SocksVersion, SocksAuth),
50 HttpConnect {
52 auth: Option<(Sensitive<String>, Sensitive<String>)>,
54 },
55}
56
57const NO_ADDR: IpAddr = IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 1));
59const MAX_HTTP_HEADER_BYTES: usize = 16 * 1024;
61
62pub(crate) async fn connect_via_proxy<R: NetStreamProvider + Send + Sync>(
79 runtime: &R,
80 proxy: &SocketAddr,
81 protocol: &Protocol,
82 target: &PtTargetAddr,
83) -> Result<R::Stream, ProxyError> {
84 trace!(
85 "Launching a proxied connection to {} via proxy at {} using {:?}",
86 target, proxy, protocol
87 );
88 let stream = runtime
89 .connect(proxy)
90 .await
91 .map_err(|e| ProxyError::ProxyConnect(Arc::new(e)))?;
92
93 match protocol {
94 Protocol::Socks(version, auth) => {
95 do_socks_handshake::<R>(stream, version, auth, target).await
96 }
97 Protocol::HttpConnect { auth } => {
98 do_http_connect_handshake::<R>(stream, auth, target).await
99 }
100 }
101}
102
103async fn do_socks_handshake<R: NetStreamProvider + Send + Sync>(
105 mut stream: R::Stream,
106 version: &SocksVersion,
107 auth: &SocksAuth,
108 target: &PtTargetAddr,
109) -> Result<R::Stream, ProxyError> {
110 let (target_addr, target_port): (SocksAddr, u16) = match target {
111 PtTargetAddr::IpPort(a) => (SocksAddr::Ip(a.ip()), a.port()),
112 #[cfg(feature = "pt-client")]
113 PtTargetAddr::HostPort(host, port) => (
114 SocksAddr::Hostname(
115 host.clone()
116 .try_into()
117 .map_err(ProxyError::InvalidSocksAddr)?,
118 ),
119 *port,
120 ),
121 #[cfg(feature = "pt-client")]
122 PtTargetAddr::None => (SocksAddr::Ip(NO_ADDR), 1),
123 _ => return Err(ProxyError::UnrecognizedAddr),
124 };
125
126 let request = SocksRequest::new(
127 *version,
128 SocksCmd::CONNECT,
129 target_addr,
130 target_port,
131 auth.clone(),
132 )
133 .map_err(ProxyError::InvalidSocksRequest)?;
134 let mut handshake = SocksClientHandshake::new(request);
135
136 let mut buf = tor_socksproto::Buffer::new();
137 let reply = loop {
138 use tor_socksproto::NextStep as NS;
139 match handshake.step(&mut buf).map_err(ProxyError::SocksProto)? {
140 NS::Send(send) => {
141 stream.write_all(&send).await?;
142 stream.flush().await?;
143 }
144 NS::Finished(fin) => {
145 break fin
146 .into_output_forbid_pipelining()
147 .map_err(ProxyError::SocksProto)?;
148 }
149 NS::Recv(mut recv) => {
150 let n = stream.read(recv.buf()).await?;
151 recv.note_received(n).map_err(ProxyError::SocksProto)?;
152 }
153 }
154 };
155
156 let status = reply.status();
157 trace!("SOCKS handshake succeeded, status {:?}", status);
158
159 if status != SocksStatus::SUCCEEDED {
160 return Err(ProxyError::SocksError(status));
161 }
162
163 Ok(stream)
164}
165
166fn format_connect_target(target: &PtTargetAddr) -> Result<String, ProxyError> {
168 match target {
169 PtTargetAddr::IpPort(a) => {
170 let host = match a.ip() {
171 IpAddr::V4(ip) => ip.to_string(),
172 IpAddr::V6(ip) => format!("[{}]", ip),
173 };
174 Ok(format!("{}:{}", host, a.port()))
175 }
176 #[cfg(feature = "pt-client")]
177 PtTargetAddr::HostPort(host, port) => Ok(format!("{}:{}", host, port)),
178 #[cfg(feature = "pt-client")]
179 PtTargetAddr::None => Err(ProxyError::UnrecognizedAddr),
180 _ => Err(ProxyError::UnrecognizedAddr),
181 }
182}
183
184fn build_http_connect_request(
186 target_str: &str,
187 auth: &Option<(Sensitive<String>, Sensitive<String>)>,
188) -> String {
189 let mut request = format!(
191 "CONNECT {} HTTP/1.1\r\nHost: {}\r\n",
192 target_str, target_str
193 );
194
195 if let Some((user, pass)) = auth {
196 let credentials = format!("{}:{}", user.as_ref(), pass.as_ref());
198 let encoded = Base64::encode_string(credentials.as_bytes());
199 request.push_str(&format!("Proxy-Authorization: Basic {}\r\n", encoded));
200 }
201
202 request.push_str("\r\n");
203 request
204}
205
206fn parse_http_connect_response(response_bytes: &[u8]) -> Result<u16, ProxyError> {
212 if response_bytes.len() > MAX_HTTP_HEADER_BYTES {
213 return Err(ProxyError::HttpConnectMalformed);
214 }
215
216 let mut headers = [httparse::EMPTY_HEADER; 64];
217 let mut resp = httparse::Response::new(&mut headers);
218
219 match resp.parse(response_bytes) {
220 Ok(httparse::Status::Complete(header_end)) => {
221 let status = resp.code.ok_or(ProxyError::HttpConnectMalformed)?;
222
223 if !(200..300).contains(&status) {
224 return Err(ProxyError::HttpConnectError(status));
225 }
226
227 if header_end < response_bytes.len() {
229 return Err(ProxyError::UnexpectedData);
230 }
231
232 trace!("HTTP CONNECT successful, status {}", status);
233 Ok(status)
234 }
235 Ok(httparse::Status::Partial) => Err(ProxyError::HttpConnectMalformed),
236 Err(_) => Err(ProxyError::HttpConnectMalformed),
237 }
238}
239
240async fn send_http_connect_request<R: NetStreamProvider + Send + Sync>(
242 stream: &mut R::Stream,
243 auth: &Option<(Sensitive<String>, Sensitive<String>)>,
244 target_str: &str,
245) -> Result<(), ProxyError> {
246 let request = build_http_connect_request(target_str, auth);
247 trace!("Sending HTTP CONNECT request for {}", target_str);
248 stream.write_all(request.as_bytes()).await?;
249 stream.flush().await?;
250 Ok(())
251}
252
253async fn do_http_connect_handshake<R: NetStreamProvider + Send + Sync>(
255 mut stream: R::Stream,
256 auth: &Option<(Sensitive<String>, Sensitive<String>)>,
257 target: &PtTargetAddr,
258) -> Result<R::Stream, ProxyError> {
259 let target_str = format_connect_target(target)?;
260 send_http_connect_request::<R>(&mut stream, auth, &target_str).await?;
261
262 let mut response_buffer = Vec::new();
264 let mut reader = BufReader::new(stream);
265 let mut line = String::new();
266
267 loop {
268 line.clear();
269 let n = reader.read_line(&mut line).await?;
270 if n == 0 {
271 return Err(ProxyError::HttpConnectMalformed);
272 }
273
274 response_buffer.extend_from_slice(line.as_bytes());
275 if response_buffer.len() > MAX_HTTP_HEADER_BYTES {
276 return Err(ProxyError::HttpConnectMalformed);
277 }
278
279 if line == "\r\n" || line == "\n" {
281 break;
282 }
283 }
284
285 let _status_code = parse_http_connect_response(&response_buffer)?;
287
288 Ok(reader.into_inner())
290}
291
292#[derive(Clone, Debug, thiserror::Error)]
294#[non_exhaustive]
295pub enum ProxyError {
296 #[error("Problem while connecting to proxy")]
298 ProxyConnect(#[source] Arc<std::io::Error>),
299
300 #[error("Problem while communicating with proxy")]
302 ProxyIo(#[source] Arc<std::io::Error>),
303
304 #[error("SOCKS proxy does not support target address")]
306 InvalidSocksAddr(#[source] tor_socksproto::Error),
307
308 #[error("Got an address type we don't recognize")]
310 UnrecognizedAddr,
311
312 #[error("Tried to make an invalid SOCKS request")]
314 InvalidSocksRequest(#[source] tor_socksproto::Error),
315
316 #[error("Protocol error while communicating with SOCKS proxy")]
318 SocksProto(#[source] tor_socksproto::Error),
319
320 #[error("Internal error")]
322 Bug(#[from] tor_error::Bug),
323
324 #[error("Received unexpected early data from peer")]
335 UnexpectedData,
336
337 #[error("SOCKS proxy reported an error: {0}")]
339 SocksError(SocksStatus),
340
341 #[error("HTTP CONNECT proxy returned status: {0}")]
343 HttpConnectError(u16),
344
345 #[error("HTTP CONNECT proxy returned invalid response")]
347 HttpConnectMalformed,
348}
349
350impl From<std::io::Error> for ProxyError {
351 fn from(e: std::io::Error) -> Self {
352 ProxyError::ProxyIo(Arc::new(e))
353 }
354}
355
356impl From<ProxyError> for std::io::Error {
357 fn from(e: ProxyError) -> Self {
358 std::io::Error::other(e)
359 }
360}
361
362impl tor_error::HasKind for ProxyError {
363 fn kind(&self) -> tor_error::ErrorKind {
364 use ProxyError as E;
365 use tor_error::ErrorKind as EK;
366 match self {
367 E::ProxyConnect(_) | E::ProxyIo(_) => EK::LocalNetworkError,
368 E::InvalidSocksAddr(_) | E::InvalidSocksRequest(_) => EK::BadApiUsage,
369 E::UnrecognizedAddr => EK::NotImplemented,
370 E::SocksProto(_) => EK::LocalProtocolViolation,
371 E::Bug(e) => e.kind(),
372 E::UnexpectedData => EK::NotImplemented,
373 E::SocksError(_) => EK::LocalProtocolViolation,
374 E::HttpConnectError(_) | E::HttpConnectMalformed => EK::LocalProtocolViolation,
375 }
376 }
377}
378
379impl tor_error::HasRetryTime for ProxyError {
380 fn retry_time(&self) -> tor_error::RetryTime {
381 use ProxyError as E;
382 use SocksStatus as S;
383 use tor_error::RetryTime as RT;
384 match self {
385 E::ProxyConnect(_) | E::ProxyIo(_) => RT::AfterWaiting,
386 E::InvalidSocksAddr(_) => RT::Never,
387 E::UnrecognizedAddr => RT::Never,
388 E::InvalidSocksRequest(_) => RT::Never,
389 E::SocksProto(_) => RT::AfterWaiting,
390 E::Bug(_) => RT::Never,
391 E::UnexpectedData => RT::Never,
392 E::SocksError(e) => match *e {
393 S::CONNECTION_REFUSED
394 | S::GENERAL_FAILURE
395 | S::HOST_UNREACHABLE
396 | S::NETWORK_UNREACHABLE
397 | S::TTL_EXPIRED => RT::AfterWaiting,
398 _ => RT::Never,
399 },
400 E::HttpConnectError(code) => {
401 if *code == 502 || *code == 503 || *code == 504 {
403 RT::AfterWaiting
404 } else {
405 RT::Never
406 }
407 }
408 E::HttpConnectMalformed => RT::Never,
409 }
410 }
411}
412
413#[cfg(feature = "pt-client")]
414#[derive(Clone, Debug)]
417pub struct ExternalProxyPlugin<R> {
418 runtime: R,
420 proxy_addr: SocketAddr,
422 proxy_version: SocksVersion,
424}
425
426#[cfg(feature = "pt-client")]
427impl<R: NetStreamProvider + Send + Sync> ExternalProxyPlugin<R> {
428 pub fn new(rt: R, proxy_addr: SocketAddr, proxy_version: SocksVersion) -> Self {
430 Self {
431 runtime: rt,
432 proxy_addr,
433 proxy_version,
434 }
435 }
436}
437
438#[cfg(feature = "pt-client")]
439#[async_trait]
440impl<R: NetStreamProvider + Send + Sync> TransportImplHelper for ExternalProxyPlugin<R> {
441 type Stream = R::Stream;
442
443 async fn connect(&self, target: &OwnedChanTarget) -> crate::Result<(PeerAddr, R::Stream)> {
444 let pt_target = match target.chan_method() {
445 ChannelMethod::Direct(_) => {
446 return Err(crate::Error::UnusableTarget(bad_api_usage!(
447 "Used pluggable transport for a TCP connection."
448 )));
449 }
450 ChannelMethod::Pluggable(target) => target,
451 other => {
452 return Err(crate::Error::UnusableTarget(bad_api_usage!(
453 "Used unknown, unsupported, transport {:?} for a TCP connection.",
454 other,
455 )));
456 }
457 };
458
459 let protocol =
460 settings_to_protocol(self.proxy_version, encode_settings(pt_target.settings()))?;
461 let stream =
462 connect_via_proxy(&self.runtime, &self.proxy_addr, &protocol, pt_target.addr()).await?;
463
464 Ok((pt_target.into(), stream))
465 }
466}
467
468#[cfg(feature = "pt-client")]
470fn encode_settings<'a, IT>(settings: IT) -> String
471where
472 IT: Iterator<Item = (&'a str, &'a str)>,
473{
474 enum EscChar {
478 Backslash(char),
480 Literal(char),
482 Done,
484 }
485 impl EscChar {
486 fn new(ch: char, in_key: bool) -> Self {
488 match ch {
489 '\\' | ';' => EscChar::Backslash(ch),
490 '=' if in_key => EscChar::Backslash(ch),
491 _ => EscChar::Literal(ch),
492 }
493 }
494 }
495 impl Iterator for EscChar {
496 type Item = char;
497
498 fn next(&mut self) -> Option<Self::Item> {
499 match *self {
500 EscChar::Backslash(ch) => {
501 *self = EscChar::Literal(ch);
502 Some('\\')
503 }
504 EscChar::Literal(ch) => {
505 *self = EscChar::Done;
506 Some(ch)
507 }
508 EscChar::Done => None,
509 }
510 }
511 }
512
513 fn esc(s: &str, in_key: bool) -> impl Iterator<Item = char> + '_ {
515 s.chars().flat_map(move |c| EscChar::new(c, in_key))
516 }
517
518 let mut result = String::new();
519 for (k, v) in settings {
520 result.extend(esc(k, true));
521 result.push('=');
522 result.extend(esc(v, false));
523 result.push(';');
524 }
525 result.pop(); result
528}
529
530#[cfg(feature = "pt-client")]
534pub fn settings_to_protocol(vers: SocksVersion, s: String) -> Result<Protocol, ProxyError> {
535 let mut bytes: Vec<_> = s.into();
536 Ok(if bytes.is_empty() {
537 Protocol::Socks(vers, SocksAuth::NoAuth)
538 } else if vers == SocksVersion::V4 {
539 if bytes.contains(&0) {
540 return Err(ProxyError::InvalidSocksRequest(
541 tor_socksproto::Error::NotImplemented(
542 "SOCKS 4 doesn't support internal NUL bytes (for PT settings list)".into(),
543 ),
544 ));
545 } else {
546 Protocol::Socks(SocksVersion::V4, SocksAuth::Socks4(bytes))
547 }
548 } else if bytes.len() <= 255 {
549 Protocol::Socks(SocksVersion::V5, SocksAuth::Username(bytes, vec![0]))
551 } else if bytes.len() <= (255 * 2) {
552 let password = bytes.split_off(255);
553 Protocol::Socks(SocksVersion::V5, SocksAuth::Username(bytes, password))
554 } else {
555 return Err(ProxyError::InvalidSocksRequest(
556 tor_socksproto::Error::NotImplemented("PT settings list too long for SOCKS 5".into()),
557 ));
558 })
559}
560
561#[cfg(test)]
562mod test {
563 #![allow(clippy::bool_assert_comparison)]
565 #![allow(clippy::clone_on_copy)]
566 #![allow(clippy::dbg_macro)]
567 #![allow(clippy::mixed_attributes_style)]
568 #![allow(clippy::print_stderr)]
569 #![allow(clippy::print_stdout)]
570 #![allow(clippy::single_char_pattern)]
571 #![allow(clippy::unwrap_used)]
572 #![allow(clippy::unchecked_time_subtraction)]
573 #![allow(clippy::useless_vec)]
574 #![allow(clippy::needless_pass_by_value)]
575 #[allow(unused_imports)]
577 use super::*;
578
579 #[test]
580 fn protocol_debug_redacts_http_connect_auth() {
581 let proto = Protocol::HttpConnect {
582 auth: Some((
583 Sensitive::new("user_name".to_owned()),
584 Sensitive::new("pass_word".to_owned()),
585 )),
586 };
587
588 let formatted = format!("{proto:?}");
589 assert!(formatted.contains("HttpConnect"));
590 assert!(!formatted.contains("user_name"));
591 assert!(!formatted.contains("pass_word"));
592 }
593
594 #[cfg(feature = "pt-client")]
595 #[test]
596 fn setting_encoding() {
597 fn check(settings: Vec<(&str, &str)>, expected: &str) {
598 assert_eq!(encode_settings(settings.into_iter()), expected);
599 }
600
601 check(vec![], "");
603 check(vec![("hello", "world")], "hello=world");
604 check(
605 vec![("hey", "verden"), ("hello", "world")],
606 "hey=verden;hello=world",
607 );
608 check(
609 vec![("hey", "verden"), ("hello", "world"), ("selv", "tak")],
610 "hey=verden;hello=world;selv=tak",
611 );
612
613 check(
614 vec![("semi;colon", "equals=sign")],
615 r"semi\;colon=equals=sign",
616 );
617 check(
618 vec![("equals=sign", "semi;colon")],
619 r"equals\=sign=semi\;colon",
620 );
621 check(
622 vec![("semi;colon", "equals=sign"), ("also", "back\\slash")],
623 r"semi\;colon=equals=sign;also=back\\slash",
624 );
625 }
626
627 #[cfg(feature = "pt-client")]
628 #[test]
629 fn split_settings() {
630 use SocksVersion::*;
631 let long_string = "examplestrg".to_owned().repeat(50);
632 assert_eq!(long_string.len(), 550);
633 let sv = |v, a, b| settings_to_protocol(v, long_string[a..b].to_owned()).unwrap();
634 let s = |a, b| sv(V5, a, b);
635 let v = |a, b| long_string.as_bytes()[a..b].to_vec();
636
637 assert_eq!(s(0, 0), Protocol::Socks(V5, SocksAuth::NoAuth));
638 assert_eq!(
639 s(0, 50),
640 Protocol::Socks(V5, SocksAuth::Username(v(0, 50), vec![0]))
641 );
642 assert_eq!(
643 s(0, 255),
644 Protocol::Socks(V5, SocksAuth::Username(v(0, 255), vec![0]))
645 );
646 assert_eq!(
647 s(0, 256),
648 Protocol::Socks(V5, SocksAuth::Username(v(0, 255), v(255, 256)))
649 );
650 assert_eq!(
651 s(0, 300),
652 Protocol::Socks(V5, SocksAuth::Username(v(0, 255), v(255, 300)))
653 );
654 assert_eq!(
655 s(0, 510),
656 Protocol::Socks(V5, SocksAuth::Username(v(0, 255), v(255, 510)))
657 );
658
659 assert_eq!(
661 sv(V4, 0, 511),
662 Protocol::Socks(V4, SocksAuth::Socks4(v(0, 511)))
663 );
664
665 assert_eq!(
667 settings_to_protocol(V5, "\0".to_owned()).unwrap(),
668 Protocol::Socks(V5, SocksAuth::Username(vec![0], vec![0]))
669 );
670 assert_eq!(
671 settings_to_protocol(V5, "\0".to_owned().repeat(510)).unwrap(),
672 Protocol::Socks(V5, SocksAuth::Username(vec![0; 255], vec![0; 255]))
673 );
674
675 assert!(settings_to_protocol(V5, "\0".to_owned().repeat(511)).is_err());
677
678 assert!(settings_to_protocol(V5, long_string[0..512].to_owned()).is_err());
680
681 assert!(settings_to_protocol(V4, "\0".to_owned()).is_err());
683 }
684
685 #[test]
686 fn parse_http_connect_200_ok() {
687 let response = b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
688 assert_eq!(parse_http_connect_response(response).unwrap(), 200);
689 }
690
691 #[test]
692 fn parse_http_connect_407_auth_required() {
693 let response = b"HTTP/1.1 407 Proxy Authentication Required\r\n\r\n";
694 match parse_http_connect_response(response) {
695 Err(ProxyError::HttpConnectError(407)) => (), other => panic!("Expected 407 error, got {:?}", other),
697 }
698 }
699
700 #[test]
701 fn parse_http_connect_malformed_no_status() {
702 let response = b"INVALID HTTP";
703 assert!(matches!(
704 parse_http_connect_response(response),
705 Err(ProxyError::HttpConnectMalformed)
706 ));
707 }
708
709 #[test]
710 fn parse_http_connect_with_headers() {
711 let response = b"HTTP/1.1 200 Connection Established\r\nConnection: close\r\nProxy-Agent: Proxy/1.0\r\n\r\n";
712 assert_eq!(parse_http_connect_response(response).unwrap(), 200);
713 }
714
715 #[test]
716 fn parse_http_connect_rejects_pipelined_data() {
717 let response = b"HTTP/1.1 200 OK\r\n\r\nEXTRA_DATA";
718 assert!(matches!(
719 parse_http_connect_response(response),
720 Err(ProxyError::UnexpectedData)
721 ));
722 }
723
724 #[test]
725 fn parse_http_connect_oversized_headers() {
726 let huge_header = vec![b'X'; MAX_HTTP_HEADER_BYTES + 1];
727 assert!(matches!(
728 parse_http_connect_response(&huge_header),
729 Err(ProxyError::HttpConnectMalformed)
730 ));
731 }
732}