1use crate::PtClientMethod;
8use crate::err;
9use crate::err::PtError;
10use futures::StreamExt;
11use futures::channel::mpsc::Receiver;
12use itertools::Itertools;
13use std::borrow::Cow;
14use std::collections::HashMap;
15use std::ffi::OsString;
16use std::io::{BufRead, BufReader};
17use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
18use std::path::PathBuf;
19use std::process::{Child, Command, Stdio};
20use std::str::FromStr;
21use std::sync::Arc;
22use std::{io, thread};
23use tor_basic_utils::PathExt as _;
24use tor_error::{internal, warn_report};
25use tor_linkspec::PtTransportName;
26use tor_rtcompat::{Runtime, SleepProviderExt};
27use tor_socksproto::SocksVersion;
28use tracing::{debug, error, info, trace, warn};
29use web_time_compat::{Duration, Instant, InstantExt};
30
31const GRACEFUL_EXIT_TIME: Duration = Duration::from_secs(5);
33const PT_START_TIMEOUT: Duration = Duration::from_secs(30);
35const PT_STDIO_BUFFER: usize = 64;
37
38#[derive(PartialEq, Eq, Debug, Clone)]
40pub struct PtStatus {
41 data: HashMap<String, String>,
45}
46
47#[derive(PartialEq, Eq, Debug, Clone)]
51#[non_exhaustive]
52#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
53pub enum PtMessage {
54 VersionError(String),
56 Version(String),
58 EnvError(String),
60 ProxyDone,
62 ProxyError(String),
64 ClientTransportLaunched {
66 transport: PtTransportName,
68 protocol: String,
70 endpoint: SocketAddr,
73 },
74 ClientTransportFailed {
76 transport: PtTransportName,
78 message: String,
80 },
81 ClientTransportsDone,
83 ServerTransportLaunched {
85 transport: PtTransportName,
87 endpoint: SocketAddr,
89 options: HashMap<String, String>,
92 },
93 ServerTransportFailed {
95 transport: PtTransportName,
97 message: String,
99 },
100 ServerTransportsDone,
102 Log {
104 severity: String,
106 message: String,
108 },
109 Status(PtStatus),
111 Unknown(String),
113}
114
115fn parse_one_value(from: &str) -> Result<(String, &str), &'static str> {
118 let first_char = from.chars().next();
119 Ok(if first_char.is_none() {
120 (String::new(), "")
121 } else if let Some('"') = first_char {
122 let mut ret = String::new();
126 let mut chars = from.chars();
127 assert_eq!(chars.next(), Some('"')); loop {
129 let ch = chars.next().ok_or("ran out of input parsing CString")?;
130 match ch {
131 '\\' => match chars
132 .next()
133 .ok_or("encountered trailing backslash in CString")?
134 {
135 'n' => ret.push('\n'),
136 'r' => ret.push('\r'),
137 't' => ret.push('\t'),
138 '0'..='8' => return Err("attempted unsupported octal escape code"),
139 ch2 => ret.push(ch2),
140 },
141 '"' => break,
142 _ => ret.push(ch),
143 }
144 }
145 (ret, chars.as_str())
146 } else {
147 if let Some((start, rest)) = from.split_once(' ') {
149 (start.to_string(), rest)
150 } else {
151 (from.to_string(), "")
152 }
153 })
154}
155
156fn parse_one_smethod_arg(args: &str) -> Result<(String, String, &str), &'static str> {
160 let mut key = String::new();
164 let mut val = String::new();
165 let mut reading_val = false;
167 let mut chars = args.chars();
168 while let Some(c) = chars.next() {
169 let target = if reading_val { &mut val } else { &mut key };
170 match c {
171 '\\' => {
172 let c = chars
173 .next()
174 .ok_or("smethod arg terminates with backslash")?;
175 target.push(c);
176 }
177 '=' => {
178 if reading_val {
179 return Err("encountered = while parsing value");
180 }
181 reading_val = true;
182 }
183 ',' => break,
184 c => target.push(c),
185 }
186 }
187 if !reading_val {
188 return Err("ran out of chars parsing smethod arg");
189 }
190 Ok((key, val, chars.as_str()))
191}
192
193impl FromStr for PtMessage {
194 type Err = Cow<'static, str>;
195
196 #[allow(clippy::cognitive_complexity)]
199 fn from_str(s: &str) -> Result<Self, Self::Err> {
200 let mut words = s.split(' ');
203 let first_word = words.next().ok_or_else(|| Cow::from("empty line"))?;
204 Ok(match first_word {
205 "VERSION-ERROR" => {
206 let rest = words.join(" ");
207 Self::VersionError(rest)
208 }
209 "VERSION" => {
210 let vers = words.next().ok_or_else(|| Cow::from("no version"))?;
211 Self::Version(vers.into())
212 }
213 "ENV-ERROR" => {
214 let rest = words.join(" ");
215 Self::EnvError(rest)
216 }
217 "PROXY" => match words.next() {
218 Some("DONE") => Self::ProxyDone,
219 _ => Self::Unknown(s.into()),
220 },
221 "PROXY-ERROR" => {
222 let rest = words.join(" ");
223 Self::ProxyError(rest)
224 }
225 "CMETHOD" => {
226 let transport = words.next().ok_or_else(|| Cow::from("no transport"))?;
227 let protocol = words.next().ok_or_else(|| Cow::from("no protocol"))?;
228 let endpoint = words
229 .next()
230 .ok_or_else(|| Cow::from("no endpoint"))?
231 .parse::<SocketAddr>()
232 .map_err(|e| Cow::from(format!("failed to parse endpoint: {}", e)))?;
233 if !endpoint.ip().is_loopback() {
234 return Err(Cow::from(format!(
235 "CMETHOD endpoint {endpoint} was not localhost"
236 )));
237 }
238 Self::ClientTransportLaunched {
239 transport: transport
240 .parse()
241 .map_err(|_| Cow::from("bad transport ID"))?,
242 protocol: protocol.to_string(),
243 endpoint,
244 }
245 }
246 "CMETHOD-ERROR" => {
247 let transport = words.next().ok_or_else(|| Cow::from("no transport"))?;
248 let rest = words.join(" ");
249 Self::ClientTransportFailed {
250 transport: transport
251 .parse()
252 .map_err(|_| Cow::from("bad transport ID"))?,
253 message: rest,
254 }
255 }
256 "CMETHODS" => match words.next() {
257 Some("DONE") => Self::ClientTransportsDone,
258 _ => Self::Unknown(s.into()),
259 },
260 "SMETHOD" => {
261 let transport = words.next().ok_or_else(|| Cow::from("no transport"))?;
262 let endpoint = words
263 .next()
264 .ok_or_else(|| Cow::from("no endpoint"))?
265 .parse::<SocketAddr>()
266 .map_err(|e| Cow::from(format!("failed to parse endpoint: {}", e)))?;
267 let mut parsed_args = HashMap::new();
269
270 for option in words {
276 if let Some(mut args) = option.strip_prefix("ARGS:") {
277 while !args.is_empty() {
278 let (k, v, rest) = parse_one_smethod_arg(args).map_err(|e| {
279 Cow::from(format!("failed to parse SMETHOD ARGS: {}", e))
280 })?;
281 if parsed_args.contains_key(&k) {
282 warn!("PT SMETHOD arguments contain repeated key {}!", k);
285 }
286 parsed_args.insert(k, v);
287 args = rest;
288 }
289 }
290 }
291 Self::ServerTransportLaunched {
292 transport: transport
293 .parse()
294 .map_err(|_| Cow::from("bad transport ID"))?,
295 endpoint,
296 options: parsed_args,
297 }
298 }
299 "SMETHOD-ERROR" => {
300 let transport = words.next().ok_or_else(|| Cow::from("no transport"))?;
301 let rest = words.join(" ");
302 Self::ServerTransportFailed {
303 transport: transport
304 .parse()
305 .map_err(|_| Cow::from("bad transport ID"))?,
306 message: rest,
307 }
308 }
309 "SMETHODS" => match words.next() {
310 Some("DONE") => Self::ServerTransportsDone,
311 _ => Self::Unknown(s.into()),
312 },
313 "LOG" => {
314 let severity = words
315 .next()
316 .ok_or_else(|| Cow::from("no severity"))?
317 .strip_prefix("SEVERITY=")
318 .ok_or_else(|| Cow::from("badly formatted severity"))?;
319 let message = words.join(" ");
320 let message = parse_one_value(
321 message
322 .strip_prefix("MESSAGE=")
323 .ok_or_else(|| Cow::from("no or badly formatted message"))?,
324 )
325 .map_err(Cow::from)?
326 .0;
327 Self::Log {
328 severity: severity.into(),
329 message,
330 }
331 }
332 "STATUS" => {
333 let mut ret = HashMap::new();
334 let message = words.join(" ");
335 let mut message = &message as &str;
336 while !message.is_empty() {
337 let (k, rest) = message
338 .split_once('=')
339 .ok_or_else(|| Cow::from(format!("failed to find = in '{}'", message)))?;
340 if rest.is_empty() {
341 return Err(Cow::from("key with no value"));
342 }
343 let (v, rest) = parse_one_value(rest).map_err(Cow::from)?;
344 if ret.contains_key(k) {
345 warn!("STATUS contains repeated key {}!", k);
348 }
349 ret.insert(k.to_owned(), v);
350 message = rest;
351 if let Some(remainder) = message.strip_prefix(" ") {
352 message = remainder;
353 }
354 }
355 Self::Status(PtStatus { data: ret })
356 }
357 _ => Self::Unknown(s.into()),
358 })
359 }
360}
361
362use sealed::*;
363pub(crate) mod sealed {
365 use super::*;
366
367 #[derive(Debug)]
372 pub struct AsyncPtChild {
373 stdout: Receiver<io::Result<String>>,
375 pub identifier: String,
377 }
378
379 impl AsyncPtChild {
380 pub fn new(mut child: Child, identifier: String) -> Result<Self, PtError> {
383 let (stdin, stdout) = (
384 child.stdin.take().ok_or_else(|| {
385 PtError::Internal(internal!("Created child process without stdin pipe"))
386 })?,
387 child.stdout.take().ok_or_else(|| {
388 PtError::Internal(internal!("Created child process without stdout pipe"))
389 })?,
390 );
391 let (mut tx, rx) = tor_async_utils::mpsc_channel_no_memquota(PT_STDIO_BUFFER);
393 let ident = identifier.clone();
394 #[allow(clippy::cognitive_complexity)]
395 thread::spawn(move || {
396 let reader = BufReader::new(stdout);
397 let _stdin = stdin;
398 let mut noted_full = false;
399 for line in reader.lines() {
401 let err = line.is_err();
402 match &line {
403 Ok(l) => trace!("<-- PT {}: {:?}", ident, l),
404 Err(e) => trace!("<-- PT {}: Error: {:?}", ident, e),
405 }
406 if let Err(e) = tx.try_send(line) {
407 if e.is_disconnected() {
408 debug!("PT {} is disconnected; shutting it down.", ident);
409 break;
411 }
412 if !noted_full {
415 noted_full = true; warn!(
417 "Bug: Message queue for PT {} became full; dropping message",
418 ident
419 );
420 }
421 }
422 if err {
423 break;
427 }
428 }
429 if let Ok(Some(_)) = child.try_wait() {
431 debug!("PT {} has exited.", ident);
434 return;
435 }
436 trace!("Asking PT {} to exit, nicely.", ident);
440 drop(_stdin);
441 thread::sleep(GRACEFUL_EXIT_TIME);
443 match child.try_wait() {
444 Ok(None) => {
445 debug!("Sending kill signal to PT {}", ident);
447 if let Err(e) = child.kill() {
448 warn_report!(e, "Failed to kill() spawned PT {}", ident);
449 }
450 }
451 Ok(Some(_)) => {
452 debug!("PT {} shut down successfully.", ident);
453 } Err(e) => {
455 warn_report!(e, "Failed to call try_wait() on spawned PT {}", ident);
456 }
457 }
458 });
459 Ok(AsyncPtChild {
460 stdout: rx,
461 identifier,
462 })
463 }
464
465 #[allow(clippy::cognitive_complexity)] pub async fn recv(&mut self) -> err::Result<PtMessage> {
470 loop {
471 match self.stdout.next().await {
472 None => return Err(PtError::ChildGone),
473 Some(Ok(line)) => {
474 let line =
475 line.parse::<PtMessage>()
476 .map_err(|e| PtError::IpcParseFailed {
477 line,
478 error: e.into(),
479 })?;
480 if let PtMessage::Log { severity, message } = line {
481 match &severity as &str {
484 "error" => error!("[pt {}] {}", self.identifier, message),
485 "warning" => warn!("[pt {}] {}", self.identifier, message),
486 "notice" => info!("[pt {}] {}", self.identifier, message),
487 "info" => debug!("[pt {}] {}", self.identifier, message),
488 "debug" => trace!("[pt {}] {}", self.identifier, message),
489 x => warn!("[pt] {} {} {}", self.identifier, x, message),
490 }
491 } else {
492 return Ok(line);
493 }
494 }
495 Some(Err(e)) => {
496 return Err(PtError::ChildReadFailed(Arc::new(e)));
497 }
498 }
499 }
500 }
501 }
502
503 #[async_trait::async_trait]
505 pub trait PluggableTransportPrivate {
506 fn inner(&mut self) -> Result<&mut AsyncPtChild, PtError>;
508
509 fn set_inner(&mut self, newval: Option<AsyncPtChild>);
511
512 fn identifier(&self) -> &str;
514
515 fn specific_params_contains(&self, transport: &PtTransportName) -> bool;
517
518 fn common_transport_launched_handler(
520 &self,
521 protocol: Option<String>,
522 transport: PtTransportName,
523 endpoint: SocketAddr,
524 methods: &mut HashMap<PtTransportName, PtClientMethod>,
525 ) -> Result<(), PtError> {
526 if !self.specific_params_contains(&transport) {
527 return Err(PtError::ProtocolViolation(format!(
528 "binary launched unwanted transport '{}'",
529 transport
530 )));
531 }
532 let protocol = match protocol {
533 Some(protocol_str) => match &protocol_str as &str {
534 "socks4" => SocksVersion::V4,
535 "socks5" => SocksVersion::V5,
536 x => {
537 return Err(PtError::ProtocolViolation(format!(
538 "unknown CMETHOD protocol '{}'",
539 x
540 )));
541 }
542 },
543 None => SocksVersion::V5,
544 };
545 let method = PtClientMethod {
546 kind: protocol,
547 endpoint,
548 };
549 info!("Transport '{}' uses method {:?}", transport, method);
550 methods.insert(transport, method);
551 Ok(())
552 }
553
554 fn get_child_from_pt_launch(
556 inner: &Option<AsyncPtChild>,
557 transports: &Vec<PtTransportName>,
558 binary_path: &PathBuf,
559 arguments: &[String],
560 all_env_vars: HashMap<OsString, OsString>,
561 ) -> Result<AsyncPtChild, PtError> {
562 if inner.is_some() {
563 let warning_msg =
564 format!("Attempted to launch PT binary for {:?} twice.", transports);
565 warn!("{warning_msg}");
566 return Err(PtError::ChildProtocolViolation(warning_msg));
568 }
569 info!(
570 "Launching pluggable transport at {} for {:?}",
571 binary_path.display_lossy(),
572 transports
573 );
574 let child = Command::new(binary_path)
575 .args(arguments.iter())
576 .envs(all_env_vars)
577 .stdout(Stdio::piped())
578 .stdin(Stdio::piped())
579 .spawn()
580 .map_err(|e| PtError::ChildSpawnFailed {
581 path: binary_path.clone(),
582 error: Arc::new(e),
583 })?;
584
585 let identifier = crate::managed::pt_identifier(binary_path)?;
586 AsyncPtChild::new(child, identifier)
587 }
588
589 async fn try_match_common_messages<R: Runtime>(
598 &self,
599 rt: &R,
600 deadline: Instant,
601 async_child: &mut AsyncPtChild,
602 ) -> Result<Option<PtMessage>, PtError> {
603 match rt
604 .timeout(
605 deadline.saturating_duration_since(Instant::get()),
607 async_child.recv(),
608 )
609 .await
610 .map_err(|_| PtError::Timeout)??
611 {
612 PtMessage::ClientTransportFailed { transport, message }
613 | PtMessage::ServerTransportFailed { transport, message } => {
614 warn!(
615 "PT {} unable to launch {}. It said: {:?}",
616 async_child.identifier, transport, message
617 );
618 return Err(PtError::TransportGaveError {
619 transport: transport.to_string(),
620 message,
621 });
622 }
623 PtMessage::VersionError(e) => {
624 if e != "no-version" {
625 warn!("weird VERSION-ERROR: {}", e);
626 }
627 return Err(PtError::UnsupportedVersion);
628 }
629 PtMessage::Version(vers) => {
630 if vers != "1" {
631 return Err(PtError::ProtocolViolation(format!(
632 "stated version is {}, asked for 1",
633 vers
634 )));
635 }
636 Ok(None)
637 }
638 PtMessage::EnvError(e) => return Err(PtError::ChildProtocolViolation(e)),
639 PtMessage::ProxyError(e) => return Err(PtError::ProxyError(e)),
640 PtMessage::Status(_) => Ok(None),
642 PtMessage::Unknown(x) => {
643 warn!("unknown PT line: {}", x);
644 Ok(None)
645 }
646 x => {
649 return Ok(Some(x));
650 }
651 }
652 }
653 }
654}
655
656#[derive(PartialEq, Eq, Clone, Debug, derive_builder::Builder)]
658pub struct PtCommonParameters {
659 state_location: PathBuf,
661 #[builder(default)]
665 outbound_bind_v4: Option<Ipv4Addr>,
666 #[builder(default)]
670 outbound_bind_v6: Option<Ipv6Addr>,
671 #[builder(default)]
674 timeout: Option<Duration>,
675}
676
677impl PtCommonParameters {
678 pub fn builder() -> PtCommonParametersBuilder {
680 PtCommonParametersBuilder::default()
681 }
682
683 fn common_environment_variables(&self) -> HashMap<OsString, OsString> {
686 let mut ret = HashMap::new();
687 ret.insert("TOR_PT_MANAGED_TRANSPORT_VER".into(), "1".into());
688 ret.insert(
689 "TOR_PT_STATE_LOCATION".into(),
690 self.state_location.clone().into_os_string(),
691 );
692 ret.insert("TOR_PT_EXIT_ON_STDIN_CLOSE".into(), "1".into());
693 if let Some(v4) = self.outbound_bind_v4 {
694 ret.insert(
695 "TOR_PT_OUTBOUND_BIND_ADDRESS_V4".into(),
696 v4.to_string().into(),
697 );
698 }
699 if let Some(v6) = self.outbound_bind_v6 {
700 ret.insert(
702 "TOR_PT_OUTBOUND_BIND_ADDRESS_V6".into(),
703 format!("[{}]", v6).into(),
704 );
705 }
706 ret
707 }
708}
709
710#[derive(PartialEq, Eq, Clone, Debug, derive_builder::Builder)]
712pub struct PtClientParameters {
713 #[builder(default)]
715 proxy_uri: Option<String>,
716 transports: Vec<PtTransportName>,
720}
721
722impl PtClientParameters {
723 pub fn builder() -> PtClientParametersBuilder {
725 PtClientParametersBuilder::default()
726 }
727
728 fn environment_variables(
731 &self,
732 common_params: &PtCommonParameters,
733 ) -> HashMap<OsString, OsString> {
734 let mut ret = common_params.common_environment_variables();
735 if let Some(ref proxy_uri) = self.proxy_uri {
736 ret.insert("TOR_PT_PROXY".into(), proxy_uri.clone().into());
737 }
738 ret.insert(
739 "TOR_PT_CLIENT_TRANSPORTS".into(),
740 self.transports.iter().join(",").into(),
741 );
742 ret
743 }
744}
745
746#[derive(PartialEq, Eq, Clone, Debug, derive_builder::Builder)]
748pub struct PtServerParameters {
749 transports: Vec<PtTransportName>,
753 #[builder(default)]
755 server_transport_options: String,
756 #[builder(default)]
758 server_bindaddr: String,
759 #[builder(default)]
761 server_orport: Option<String>,
762 #[builder(default)]
764 server_extended_orport: Option<String>,
765}
766
767impl PtServerParameters {
768 pub fn builder() -> PtServerParametersBuilder {
770 PtServerParametersBuilder::default()
771 }
772
773 fn environment_variables(
776 &self,
777 common_params: &PtCommonParameters,
778 ) -> HashMap<OsString, OsString> {
779 let mut ret = common_params.common_environment_variables();
780 ret.insert(
781 "TOR_PT_SERVER_TRANSPORTS".into(),
782 self.transports.iter().join(",").into(),
783 );
784 ret.insert(
785 "TOR_PT_SERVER_TRANSPORT_OPTIONS".into(),
786 self.server_transport_options.clone().into(),
787 );
788 ret.insert(
789 "TOR_PT_SERVER_BINDADDR".into(),
790 self.server_bindaddr.clone().into(),
791 );
792 if let Some(ref server_orport) = self.server_orport {
793 ret.insert("TOR_PT_ORPORT".into(), server_orport.into());
794 }
795 if let Some(ref server_extended_orport) = self.server_extended_orport {
796 ret.insert(
797 "TOR_PT_EXTENDED_SERVER_PORT".into(),
798 server_extended_orport.into(),
799 );
800 }
801 ret
802 }
803}
804
805#[async_trait::async_trait]
807#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
808pub trait PluggableTransport: PluggableTransportPrivate {
809 fn transport_methods(&self) -> &HashMap<PtTransportName, PtClientMethod>;
814
815 async fn next_message(&mut self) -> err::Result<PtMessage> {
823 let inner = self.inner()?;
824 let ret = inner.recv().await;
825 if let Err(PtError::ChildGone) | Err(PtError::ChildReadFailed { .. }) = &ret {
826 debug!(
829 "PT {}: Received {:?}; shutting down.",
830 self.identifier(),
831 ret
832 );
833 self.set_inner(None);
834 }
835 ret
836 }
837}
838#[derive(Debug)]
843pub struct PluggableClientTransport {
844 inner: Option<AsyncPtChild>,
846 pub(crate) binary_path: PathBuf,
848 arguments: Vec<String>,
850 common_params: PtCommonParameters,
852 client_params: PtClientParameters,
854 cmethods: HashMap<PtTransportName, PtClientMethod>,
856}
857
858impl PluggableTransport for PluggableClientTransport {
859 fn transport_methods(&self) -> &HashMap<PtTransportName, PtClientMethod> {
860 &self.cmethods
861 }
862}
863
864impl PluggableTransportPrivate for PluggableClientTransport {
865 fn inner(&mut self) -> Result<&mut AsyncPtChild, PtError> {
866 self.inner.as_mut().ok_or(PtError::ChildGone)
867 }
868 fn set_inner(&mut self, newval: Option<AsyncPtChild>) {
869 self.inner = newval;
870 }
871 fn identifier(&self) -> &str {
872 match &self.inner {
873 Some(child) => &child.identifier,
874 None => "<not yet launched>",
875 }
876 }
877 fn specific_params_contains(&self, transport: &PtTransportName) -> bool {
878 self.client_params.transports.contains(transport)
879 }
880}
881
882impl PluggableClientTransport {
883 pub fn new(
888 binary_path: PathBuf,
889 arguments: Vec<String>,
890 common_params: PtCommonParameters,
891 client_params: PtClientParameters,
892 ) -> Self {
893 Self {
894 common_params,
895 client_params,
896 arguments,
897 binary_path,
898 inner: None,
899 cmethods: Default::default(),
900 }
901 }
902
903 pub async fn launch<R: Runtime>(&mut self, rt: R) -> err::Result<()> {
908 let all_env_vars = self
909 .client_params
910 .environment_variables(&self.common_params);
911
912 let mut async_child =
913 <PluggableClientTransport as PluggableTransportPrivate>::get_child_from_pt_launch(
914 &self.inner,
915 &self.client_params.transports,
916 &self.binary_path,
917 &self.arguments,
918 all_env_vars,
919 )?;
920
921 let deadline = Instant::get() + self.common_params.timeout.unwrap_or(PT_START_TIMEOUT);
922 let mut cmethods = HashMap::new();
923 let mut proxy_done = self.client_params.proxy_uri.is_none();
924
925 loop {
926 match self
927 .try_match_common_messages(&rt, deadline, &mut async_child)
928 .await
929 {
930 Ok(maybe_message) => {
931 if let Some(message) = maybe_message {
932 match message {
933 PtMessage::ClientTransportLaunched {
934 transport,
935 protocol,
936 endpoint,
937 } => {
938 self.common_transport_launched_handler(
939 Some(protocol),
940 transport,
941 endpoint,
942 &mut cmethods,
943 )?;
944 }
945 PtMessage::ProxyDone => {
946 if proxy_done {
947 return Err(PtError::ProtocolViolation(
948 "binary initiated proxy when not asked (or twice)".into(),
949 ));
950 }
951 info!("PT binary now proxying connections via supplied URI");
952 proxy_done = true;
953 }
954 PtMessage::ClientTransportsDone => {
956 let unsupported = self
957 .client_params
958 .transports
959 .iter()
960 .filter(|&x| !cmethods.contains_key(x))
961 .map(|x| x.to_string())
962 .collect::<Vec<_>>();
963 if !unsupported.is_empty() {
964 warn!(
965 "PT binary failed to initialise transports: {:?}",
966 unsupported
967 );
968 return Err(PtError::ClientTransportsUnsupported(unsupported));
969 }
970 info!("PT binary initialisation done");
971 break;
972 }
973 x => {
974 return Err(PtError::ProtocolViolation(format!(
975 "received unexpected {:?}",
976 x
977 )));
978 }
979 }
980 }
981 }
982 Err(e) => return Err(e),
983 }
984 }
985 self.cmethods = cmethods;
986 self.inner = Some(async_child);
987 Ok(())
989 }
990}
991
992#[derive(Debug)]
997pub struct PluggableServerTransport {
998 inner: Option<AsyncPtChild>,
1000 pub(crate) binary_path: PathBuf,
1002 arguments: Vec<String>,
1004 common_params: PtCommonParameters,
1006 server_params: PtServerParameters,
1008 smethods: HashMap<PtTransportName, PtClientMethod>,
1010}
1011
1012impl PluggableTransportPrivate for PluggableServerTransport {
1013 fn inner(&mut self) -> Result<&mut AsyncPtChild, PtError> {
1014 self.inner.as_mut().ok_or(PtError::ChildGone)
1015 }
1016 fn set_inner(&mut self, newval: Option<AsyncPtChild>) {
1017 self.inner = newval;
1018 }
1019 fn identifier(&self) -> &str {
1020 match &self.inner {
1021 Some(child) => &child.identifier,
1022 None => "<not yet launched>",
1023 }
1024 }
1025 fn specific_params_contains(&self, transport: &PtTransportName) -> bool {
1026 self.server_params.transports.contains(transport)
1027 }
1028}
1029
1030impl PluggableTransport for PluggableServerTransport {
1031 fn transport_methods(&self) -> &HashMap<PtTransportName, PtClientMethod> {
1032 &self.smethods
1033 }
1034}
1035
1036impl PluggableServerTransport {
1037 pub fn new(
1042 binary_path: PathBuf,
1043 arguments: Vec<String>,
1044 common_params: PtCommonParameters,
1045 server_params: PtServerParameters,
1046 ) -> Self {
1047 Self {
1048 common_params,
1049 server_params,
1050 arguments,
1051 binary_path,
1052 inner: None,
1053 smethods: Default::default(),
1054 }
1055 }
1056
1057 pub async fn launch<R: Runtime>(&mut self, rt: R) -> err::Result<()> {
1062 let all_env_vars = self
1063 .server_params
1064 .environment_variables(&self.common_params);
1065
1066 let mut async_child =
1067 <PluggableServerTransport as PluggableTransportPrivate>::get_child_from_pt_launch(
1068 &self.inner,
1069 &self.server_params.transports,
1070 &self.binary_path,
1071 &self.arguments,
1072 all_env_vars,
1073 )?;
1074
1075 let deadline = Instant::get() + self.common_params.timeout.unwrap_or(PT_START_TIMEOUT);
1076 let mut smethods = HashMap::new();
1077
1078 loop {
1079 match self
1080 .try_match_common_messages(&rt, deadline, &mut async_child)
1081 .await
1082 {
1083 Ok(maybe_message) => {
1084 if let Some(message) = maybe_message {
1085 match message {
1086 PtMessage::ServerTransportLaunched {
1087 transport,
1088 endpoint,
1089 options: _,
1090 } => {
1091 self.common_transport_launched_handler(
1092 None,
1093 transport,
1094 endpoint,
1095 &mut smethods,
1096 )?;
1097 }
1098 PtMessage::ServerTransportsDone => {
1099 let unsupported = self
1100 .server_params
1101 .transports
1102 .iter()
1103 .filter(|&x| !smethods.contains_key(x))
1104 .map(|x| x.to_string())
1105 .collect::<Vec<_>>();
1106 if !unsupported.is_empty() {
1107 warn!(
1108 "PT binary failed to initialise transports: {:?}",
1109 unsupported
1110 );
1111 return Err(PtError::ClientTransportsUnsupported(unsupported));
1112 }
1113 info!("PT binary initialisation done");
1114 break;
1115 }
1116 x => {
1117 return Err(PtError::ProtocolViolation(format!(
1118 "received unexpected {:?}",
1119 x
1120 )));
1121 }
1122 }
1123 }
1124 }
1125 Err(e) => return Err(e),
1126 }
1127 }
1128 self.smethods = smethods;
1129 self.inner = Some(async_child);
1130 Ok(())
1132 }
1133}
1134
1135#[cfg(test)]
1136mod test {
1137 #![allow(clippy::bool_assert_comparison)]
1139 #![allow(clippy::clone_on_copy)]
1140 #![allow(clippy::dbg_macro)]
1141 #![allow(clippy::mixed_attributes_style)]
1142 #![allow(clippy::print_stderr)]
1143 #![allow(clippy::print_stdout)]
1144 #![allow(clippy::single_char_pattern)]
1145 #![allow(clippy::unwrap_used)]
1146 #![allow(clippy::unchecked_time_subtraction)]
1147 #![allow(clippy::useless_vec)]
1148 #![allow(clippy::needless_pass_by_value)]
1149 #![allow(clippy::string_slice)] use crate::ipc::{PtMessage, PtStatus};
1153 use std::borrow::Cow;
1154 use std::collections::HashMap;
1155 use std::net::{IpAddr, Ipv4Addr, SocketAddr};
1156
1157 #[test]
1158 fn it_parses_spec_examples() {
1159 assert_eq!(
1160 "VERSION-ERROR no-version".parse(),
1161 Ok(PtMessage::VersionError("no-version".into()))
1162 );
1163 assert_eq!("VERSION 1".parse(), Ok(PtMessage::Version("1".into())));
1164 assert_eq!(
1165 "ENV-ERROR No TOR_PT_AUTH_COOKIE_FILE when TOR_PT_EXTENDED_SERVER_PORT set".parse(),
1166 Ok(PtMessage::EnvError(
1167 "No TOR_PT_AUTH_COOKIE_FILE when TOR_PT_EXTENDED_SERVER_PORT set".into()
1168 ))
1169 );
1170 assert_eq!("PROXY DONE".parse(), Ok(PtMessage::ProxyDone));
1171 assert_eq!(
1172 "PROXY-ERROR SOCKS 4 upstream proxies unsupported".parse(),
1173 Ok(PtMessage::ProxyError(
1174 "SOCKS 4 upstream proxies unsupported".into()
1175 ))
1176 );
1177 assert_eq!(
1178 "CMETHOD trebuchet socks5 127.0.0.1:19999".parse(),
1179 Ok(PtMessage::ClientTransportLaunched {
1180 transport: "trebuchet".parse().unwrap(),
1181 protocol: "socks5".to_string(),
1182 endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 19999)
1183 })
1184 );
1185 assert_eq!(
1186 "CMETHOD-ERROR trebuchet no rocks available".parse(),
1187 Ok(PtMessage::ClientTransportFailed {
1188 transport: "trebuchet".parse().unwrap(),
1189 message: "no rocks available".to_string()
1190 })
1191 );
1192 assert_eq!("CMETHODS DONE".parse(), Ok(PtMessage::ClientTransportsDone));
1193 assert_eq!(
1194 "SMETHOD trebuchet 198.51.100.1:19999".parse(),
1195 Ok(PtMessage::ServerTransportLaunched {
1196 transport: "trebuchet".parse().unwrap(),
1197 endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)), 19999),
1198 options: Default::default()
1199 })
1200 );
1201 let mut map = HashMap::new();
1202 map.insert("N".to_string(), "13".to_string());
1203 assert_eq!(
1204 "SMETHOD rot_by_N 198.51.100.1:2323 ARGS:N=13".parse(),
1205 Ok(PtMessage::ServerTransportLaunched {
1206 transport: "rot_by_N".parse().unwrap(),
1207 endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)), 2323),
1208 options: map
1209 })
1210 );
1211 let mut map = HashMap::new();
1212 map.insert(
1213 "cert".to_string(),
1214 "HszPy3vWfjsESCEOo9ZBkRv6zQ/1mGHzc8arF0y2SpwFr3WhsMu8rK0zyaoyERfbz3ddFw".to_string(),
1215 );
1216 map.insert("iat-mode".to_string(), "0".to_string());
1217 assert_eq!(
1218 "SMETHOD obfs4 198.51.100.1:43734 ARGS:cert=HszPy3vWfjsESCEOo9ZBkRv6zQ/1mGHzc8arF0y2SpwFr3WhsMu8rK0zyaoyERfbz3ddFw,iat-mode=0".parse(),
1219 Ok(PtMessage::ServerTransportLaunched {
1220 transport: "obfs4".parse().unwrap(),
1221 endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1)), 43734),
1222 options: map
1223 })
1224 );
1225 assert_eq!(
1226 "SMETHOD-ERROR trebuchet no cows available".parse(),
1227 Ok(PtMessage::ServerTransportFailed {
1228 transport: "trebuchet".parse().unwrap(),
1229 message: "no cows available".to_string()
1230 })
1231 );
1232 assert_eq!(
1233 "LOG SEVERITY=debug MESSAGE=\"Connected to bridge A\"".parse(),
1234 Ok(PtMessage::Log {
1235 severity: "debug".to_string(),
1236 message: "Connected to bridge A".to_string()
1237 })
1238 );
1239 assert_eq!(
1240 "LOG SEVERITY=debug MESSAGE=\"\\r\\n\\t\"".parse(),
1241 Ok(PtMessage::Log {
1242 severity: "debug".to_string(),
1243 message: "\r\n\t".to_string()
1244 })
1245 );
1246 assert_eq!(
1247 "LOG SEVERITY=debug MESSAGE=".parse(),
1248 Ok(PtMessage::Log {
1249 severity: "debug".to_string(),
1250 message: "".to_string()
1251 })
1252 );
1253 assert_eq!(
1254 "LOG SEVERITY=debug MESSAGE=\"\\a\"".parse::<PtMessage>(),
1255 Ok(PtMessage::Log {
1256 severity: "debug".to_string(),
1257 message: "a".to_string()
1258 })
1259 );
1260
1261 for i in 0..9 {
1262 let msg = format!("LOG SEVERITY=debug MESSAGE=\"\\{i}\"");
1263 assert_eq!(
1264 msg.parse::<PtMessage>(),
1265 Err(Cow::from("attempted unsupported octal escape code"))
1266 );
1267 }
1268 assert_eq!(
1269 "SMETHOD obfs4 198.51.100.1:43734 ARGS:iat-mode=0\\".parse::<PtMessage>(),
1270 Err(Cow::from(
1271 "failed to parse SMETHOD ARGS: smethod arg terminates with backslash"
1272 ))
1273 );
1274 assert_eq!(
1275 "SMETHOD obfs4 198.51.100.1:43734 ARGS:iat-mode=fo=o".parse::<PtMessage>(),
1276 Err(Cow::from(
1277 "failed to parse SMETHOD ARGS: encountered = while parsing value"
1278 ))
1279 );
1280 assert_eq!(
1281 "SMETHOD obfs4 198.51.100.1:43734 ARGS:iat-mode".parse::<PtMessage>(),
1282 Err(Cow::from(
1283 "failed to parse SMETHOD ARGS: ran out of chars parsing smethod arg"
1284 ))
1285 );
1286
1287 let mut map = HashMap::new();
1288 map.insert("ADDRESS".to_string(), "198.51.100.123:1234".to_string());
1289 map.insert("CONNECT".to_string(), "Success".to_string());
1290 assert_eq!(
1291 "STATUS ADDRESS=198.51.100.123:1234 CONNECT=Success".parse(),
1292 Ok(PtMessage::Status(PtStatus { data: map }))
1293 );
1294
1295 let mut map = HashMap::new();
1296 map.insert("ADDRESS".to_string(), "198.51.100.123:1234".to_string());
1297 map.insert("CONNECT".to_string(), "Success".to_string());
1298 map.insert("TRANSPORT".to_string(), "obfs4".to_string());
1299 assert_eq!(
1300 "STATUS TRANSPORT=obfs4 ADDRESS=198.51.100.123:1234 CONNECT=Success".parse(),
1301 Ok(PtMessage::Status(PtStatus { data: map }))
1302 );
1303
1304 let mut map = HashMap::new();
1305 map.insert("ADDRESS".to_string(), "198.51.100.222:2222".to_string());
1306 map.insert("CONNECT".to_string(), "Failed".to_string());
1307 map.insert("FINGERPRINT".to_string(), "<Fingerprint>".to_string());
1308 map.insert("ERRSTR".to_string(), "Connection refused".to_string());
1309 assert_eq!(
1310 "STATUS ADDRESS=198.51.100.222:2222 CONNECT=Failed FINGERPRINT=<Fingerprint> ERRSTR=\"Connection refused\"".parse(),
1311 Ok(PtMessage::Status(PtStatus {
1312 data: map
1313 }))
1314 );
1315 }
1316}