/ crates / tor-socksproto / src / handshake.rs
handshake.rs
  1  //! Implement the socks handshakes.
  2  
  3  #[cfg(any(feature = "proxy-handshake", feature = "client-handshake"))]
  4  #[macro_use]
  5  pub(crate) mod framework;
  6  
  7  #[cfg(feature = "client-handshake")]
  8  pub(crate) mod client;
  9  #[cfg(feature = "proxy-handshake")]
 10  pub(crate) mod proxy;
 11  
 12  use crate::msg::SocksAddr;
 13  use std::net::IpAddr;
 14  use tor_bytes::Result as BytesResult;
 15  use tor_bytes::{EncodeResult, Error as BytesError, Readable, Reader, Writeable, Writer};
 16  
 17  /// Constant for Username/Password-style authentication.
 18  /// (See RFC 1929)
 19  const USERNAME_PASSWORD: u8 = 0x02;
 20  /// Constant for "no authentication".
 21  const NO_AUTHENTICATION: u8 = 0x00;
 22  
 23  /// An action to take in response to a SOCKS handshake message.
 24  #[derive(Clone, Debug)]
 25  #[non_exhaustive]
 26  pub struct Action {
 27      /// If nonzero, this many bytes should be drained from the
 28      /// client's inputs.
 29      pub drain: usize,
 30      /// If nonempty, this reply should be sent to the other party.
 31      pub reply: Vec<u8>,
 32      /// If true, then this handshake is over, either successfully or not.
 33      pub finished: bool,
 34  }
 35  
 36  impl Readable for SocksAddr {
 37      fn take_from(r: &mut Reader<'_>) -> BytesResult<SocksAddr> {
 38          let atype = r.take_u8()?;
 39          match atype {
 40              1 => {
 41                  let ip4: std::net::Ipv4Addr = r.extract()?;
 42                  Ok(SocksAddr::Ip(ip4.into()))
 43              }
 44              3 => {
 45                  let hlen = r.take_u8()?;
 46                  let hostname = r.take(hlen as usize)?;
 47                  let hostname = std::str::from_utf8(hostname)
 48                      .map_err(|_| BytesError::InvalidMessage("bad utf8 on hostname".into()))?
 49                      .to_string();
 50                  let hostname = hostname
 51                      .try_into()
 52                      .map_err(|_| BytesError::InvalidMessage("hostname too long".into()))?;
 53                  Ok(SocksAddr::Hostname(hostname))
 54              }
 55              4 => {
 56                  let ip6: std::net::Ipv6Addr = r.extract()?;
 57                  Ok(SocksAddr::Ip(ip6.into()))
 58              }
 59              _ => Err(BytesError::InvalidMessage(
 60                  "unrecognized address type.".into(),
 61              )),
 62          }
 63      }
 64  }
 65  
 66  impl Writeable for SocksAddr {
 67      fn write_onto<W: Writer + ?Sized>(&self, w: &mut W) -> EncodeResult<()> {
 68          match self {
 69              SocksAddr::Ip(IpAddr::V4(ip)) => {
 70                  w.write_u8(1);
 71                  w.write(ip)?;
 72              }
 73              SocksAddr::Ip(IpAddr::V6(ip)) => {
 74                  w.write_u8(4);
 75                  w.write(ip)?;
 76              }
 77              SocksAddr::Hostname(h) => {
 78                  let h = h.as_ref();
 79                  assert!(h.len() < 256);
 80                  let hlen = h.len() as u8;
 81                  w.write_u8(3);
 82                  w.write_u8(hlen);
 83                  w.write(h.as_bytes())?;
 84              }
 85          }
 86          Ok(())
 87      }
 88  }
 89  
 90  #[cfg(all(feature = "client-handshake", feature = "proxy-handshake"))]
 91  #[cfg(test)]
 92  mod test_roundtrip {
 93      // @@ begin test lint list
 94      #![allow(clippy::bool_assert_comparison)]
 95      #![allow(clippy::clone_on_copy)]
 96      #![allow(clippy::dbg_macro)]
 97      #![allow(clippy::mixed_attributes_style)]
 98      #![allow(clippy::print_stderr)]
 99      #![allow(clippy::print_stdout)]
100      #![allow(clippy::single_char_pattern)]
101      #![allow(clippy::unwrap_used)]
102      #![allow(clippy::unchecked_duration_subtraction)]
103      #![allow(clippy::useless_vec)]
104      #![allow(clippy::needless_pass_by_value)]
105      //! <!-- @@ end test lint list
106  
107      use crate::*;
108      use std::collections::VecDeque;
109  
110      /// Given a socks request, run a complete (successful round) trip, reply with the
111      /// the given status code, and return both sides' results.
112      ///
113      /// Use the (deprecated) `Handshake::handshake` and `Action` API
114      fn run_handshake_old_api(
115          request: SocksRequest,
116          status: SocksStatus,
117      ) -> (SocksRequest, SocksReply) {
118          let mut client_hs = SocksClientHandshake::new(request);
119          let mut proxy_hs = SocksProxyHandshake::new();
120          let mut received_request = None;
121  
122          let mut last_proxy_msg = vec![];
123          // Prevent infinite loop in case of bugs.
124          for _ in 0..100 {
125              // Make sure that the client says "truncated" for all prefixes of the proxy's message.
126              for truncate in 0..last_proxy_msg.len() {
127                  let r = client_hs.handshake_for_tests(&last_proxy_msg[..truncate]);
128                  assert!(r.is_err());
129              }
130              // Get the client's actual message.
131              let client_action = client_hs
132                  .handshake_for_tests(&last_proxy_msg)
133                  .unwrap()
134                  .unwrap();
135              assert_eq!(client_action.drain, last_proxy_msg.len());
136              if client_action.finished {
137                  let received_reply = client_hs.into_reply();
138                  return (received_request.unwrap(), received_reply.unwrap());
139              }
140              let client_msg = client_action.reply;
141  
142              // Make sure that the proxy says "truncated" for all prefixes of the client's message.
143              for truncate in 0..client_msg.len() {
144                  let r = proxy_hs.handshake_for_tests(&client_msg[..truncate]);
145                  assert!(r.is_err());
146              }
147              // Get the proxy's actual reply (if any).
148              let proxy_action = proxy_hs.handshake_for_tests(&client_msg).unwrap().unwrap();
149              assert_eq!(proxy_action.drain, client_msg.len());
150              last_proxy_msg = if proxy_action.finished {
151                  // The proxy is done: have it reply with a status code.
152                  received_request = proxy_hs.clone().into_request();
153                  received_request
154                      .as_ref()
155                      .unwrap()
156                      .reply(status, None)
157                      .unwrap()
158              } else {
159                  proxy_action.reply
160              };
161          }
162          panic!("Handshake ran for too many steps")
163      }
164  
165      /// Given a socks request, run a complete (successful round) trip, reply with the
166      /// the given status code, and return both sides' results.
167      ///
168      /// Use the (new) `Handshake::step` API
169      fn run_handshake_new_api<P: ReadPrecision, const MAX_RECV: usize>(
170          request: SocksRequest,
171          status: SocksStatus,
172      ) -> (SocksRequest, SocksReply) {
173          struct State<P: ReadPrecision, H: Handshake> {
174              hs: H,
175              buf: Buffer<P>,
176              fin: Option<H::Output>,
177          }
178  
179          struct DidSomething;
180  
181          let mut client = State::<P, _>::new(SocksClientHandshake::new(request));
182          let mut server = State::<P, _>::new(SocksProxyHandshake::new());
183  
184          let mut c2s = VecDeque::new();
185          let mut s2c = VecDeque::new();
186  
187          let mut status = Some(status);
188  
189          impl<P: ReadPrecision, H: Handshake> State<P, H> {
190              fn new(hs: H) -> Self {
191                  State {
192                      hs,
193                      buf: Default::default(),
194                      fin: None,
195                  }
196              }
197  
198              fn progress_1(
199                  &mut self,
200                  max_recv: usize,
201                  rx: &mut VecDeque<u8>,
202                  tx: &mut VecDeque<u8>,
203              ) -> Option<DidSomething> {
204                  use NextStep as NS;
205  
206                  if self.fin.is_some() {
207                      return None;
208                  }
209  
210                  match self.hs.step(&mut self.buf).unwrap() {
211                      NS::Recv(mut recv) => {
212                          let n = [recv.buf().len(), rx.len(), max_recv]
213                              .into_iter()
214                              .min()
215                              .unwrap();
216                          for p in &mut recv.buf()[0..n] {
217                              *p = rx.pop_front().unwrap();
218                          }
219                          recv.note_received(n).unwrap_or_else(|e| match e {
220                              // This is actually expected; our test case produces 0-byte reads
221                              // sometimes.
222                              Error::UnexpectedEof => {}
223                              other => panic!("{:?}", other),
224                          });
225                          if n != 0 {
226                              Some(DidSomething)
227                          } else {
228                              None
229                          }
230                      }
231                      NS::Send(send) => {
232                          for c in send {
233                              tx.push_back(c);
234                          }
235                          Some(DidSomething)
236                      }
237                      NS::Finished(fin) => {
238                          self.fin = Some(fin.into_output_forbid_pipelining().unwrap());
239                          Some(DidSomething)
240                      }
241                  }
242              }
243          }
244  
245          loop {
246              let ds = [
247                  client.progress_1(MAX_RECV, &mut s2c, &mut c2s),
248                  server.progress_1(MAX_RECV, &mut c2s, &mut s2c),
249              ]
250              .into_iter()
251              .flatten()
252              .next();
253  
254              if let Some(DidSomething) = ds {
255                  continue;
256              }
257  
258              let Some(status) = status.take() else { break };
259  
260              let reply = server.fin.as_ref().unwrap().reply(status, None).unwrap();
261              for c in reply {
262                  s2c.push_back(c);
263              }
264          }
265  
266          (server.fin.unwrap(), client.fin.unwrap())
267      }
268  
269      // Invoke run_handshake and assert that the output matches the input.
270      fn test_handshake(request: &SocksRequest, status: SocksStatus) {
271          for run_handshake in [
272              run_handshake_old_api,
273              run_handshake_new_api::<(), 1>,
274              run_handshake_new_api::<(), 100>,
275              run_handshake_new_api::<PreciseReads, 1>,
276              run_handshake_new_api::<PreciseReads, 100>,
277          ] {
278              let (request_out, status_out) = run_handshake(request.clone(), status);
279              assert_eq!(&request_out, request);
280              assert_eq!(status_out.status(), status);
281          }
282      }
283  
284      #[test]
285      fn socks4() {
286          test_handshake(
287              &SocksRequest::new(
288                  SocksVersion::V4,
289                  SocksCmd::CONNECT,
290                  SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()),
291                  443,
292                  SocksAuth::NoAuth,
293              )
294              .unwrap(),
295              SocksStatus::SUCCEEDED,
296          );
297  
298          test_handshake(
299              &SocksRequest::new(
300                  SocksVersion::V4,
301                  SocksCmd::CONNECT,
302                  SocksAddr::Ip("192.0.2.33".parse().unwrap()),
303                  22,
304                  SocksAuth::Socks4(b"swordfish".to_vec()),
305              )
306              .unwrap(),
307              SocksStatus::GENERAL_FAILURE,
308          );
309      }
310  
311      #[test]
312      fn socks5() {
313          test_handshake(
314              &SocksRequest::new(
315                  SocksVersion::V5,
316                  SocksCmd::CONNECT,
317                  SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()),
318                  443,
319                  SocksAuth::NoAuth,
320              )
321              .unwrap(),
322              SocksStatus::SUCCEEDED,
323          );
324  
325          test_handshake(
326              &SocksRequest::new(
327                  SocksVersion::V5,
328                  SocksCmd::CONNECT,
329                  SocksAddr::Ip("2001:db8::32".parse().unwrap()),
330                  443,
331                  SocksAuth::Username(b"belbo".to_vec(), b"non".to_vec()),
332              )
333              .unwrap(),
334              SocksStatus::GENERAL_FAILURE,
335          );
336      }
337  }