tor_socksproto/handshake/
client.rs

1//! Implementation for a SOCKS client handshake.
2
3use super::framework::{HandshakeImpl, ImplNextStep};
4use super::{NO_AUTHENTICATION, USERNAME_PASSWORD};
5use crate::msg::{SocksAddr, SocksAuth, SocksReply, SocksRequest, SocksStatus, SocksVersion};
6use crate::{Error, Result};
7
8use tor_bytes::{Reader, Writer};
9use tor_error::{internal, into_internal};
10
11use derive_deftly::Deftly;
12
13use std::net::{IpAddr, Ipv4Addr};
14
15/// The client (initiator) side of a SOCKS handshake.
16///
17/// Create you have one of these with [`SocksClientHandshake::new()`],
18/// and then use [`Handshake::step`](crate::Handshake::step) to drive it.
19#[derive(Clone, Debug, Deftly)]
20#[derive_deftly(Handshake)]
21pub struct SocksClientHandshake {
22    /// The request that we are currently trying to negotiate with the proxy.
23    request: SocksRequest,
24    /// Our current state in negotiating that request.
25    state: State,
26    /// If present, the return message that we received from the proxy.
27    #[deftly(handshake(output))]
28    reply: Option<SocksReply>,
29}
30
31/// An internal state for a `SocksClientHandshake`.
32#[derive(Clone, Debug)]
33enum State {
34    /// We have sent nothing yet.
35    Initial,
36    /// We have sent a SOCKS4 request, and are waiting for a response.
37    Socks4Wait,
38    /// We have sent a SOCKS5 init message, and are waiting to hear what kind
39    /// of authentication to use.
40    Socks5AuthWait,
41    /// We have sent a SOCKS5 username/password, and are waiting to hear whether
42    /// it's accepted.
43    Socks5UsernameWait,
44    /// We have sent a SOCKS5 request, and are waiting for a response.
45    Socks5Wait,
46    /// We have received the final reply from the proxy.  This reply may be
47    /// successful or unsuccessful, depending on the value of
48    /// `SocksClientHandshake::status`.
49    Done,
50    /// The handshake has failed and no further progress can be made.
51    Failed,
52}
53
54impl HandshakeImpl for SocksClientHandshake {
55    fn handshake_impl(&mut self, input: &mut Reader<'_>) -> Result<ImplNextStep> {
56        use State::*;
57        match self.state {
58            Initial => match self.request.version() {
59                SocksVersion::V4 => self.send_v4(),
60                SocksVersion::V5 => self.send_v5_initial(),
61            },
62            Socks4Wait => self.handle_v4(input),
63            Socks5AuthWait => self.handle_v5_auth(input),
64            Socks5UsernameWait => self.handle_v5_username_ack(input),
65            Socks5Wait => self.handle_v5_final(input),
66            Done => Err(Error::AlreadyFinished(internal!(
67                "called handshake() after handshaking succeeded"
68            ))),
69            Failed => Err(Error::AlreadyFinished(internal!(
70                "called handshake() after handshaking failed"
71            ))),
72        }
73    }
74}
75
76impl SocksClientHandshake {
77    /// Construct a new [`SocksClientHandshake`] that will attempt to negotiate
78    /// with a peer using `request`.
79    pub fn new(request: SocksRequest) -> Self {
80        SocksClientHandshake {
81            request,
82            state: State::Initial,
83            reply: None,
84        }
85    }
86
87    /// Consume this handshake's state; if it finished successfully,
88    /// return the [`SocksReply`] that we got from the proxy..
89    pub fn into_reply(self) -> Option<SocksReply> {
90        self.reply
91    }
92
93    /// Send the client side of the socks 4 handshake.
94    fn send_v4(&mut self) -> Result<ImplNextStep> {
95        let mut msg = Vec::new();
96
97        msg.write_u8(4);
98        msg.write_u8(self.request.command().into());
99        msg.write_u16(self.request.port());
100
101        let use_v4a = match self.request.addr() {
102            SocksAddr::Ip(IpAddr::V4(ipv4)) => {
103                msg.write_u32((*ipv4).into());
104                false
105            }
106            _ => {
107                msg.write_u32(1);
108                true
109            }
110        };
111
112        match self.request.auth() {
113            SocksAuth::NoAuth => msg.write_u8(0),
114            SocksAuth::Socks4(s) => {
115                msg.write_all(s);
116                msg.write_u8(0);
117            }
118            SocksAuth::Username(_, _) => {
119                return Err(internal!("tried to send socks5 auth over socks4.").into())
120            }
121        }
122
123        if use_v4a {
124            // We are using socks4a, so we need to send the address now.
125            msg.write_all(self.request.addr().to_string().as_bytes());
126            msg.write_u8(0);
127        }
128
129        self.state = State::Socks4Wait;
130        Ok(ImplNextStep::Reply { reply: msg })
131    }
132
133    /// Handle a SOCKSv4 response.
134    fn handle_v4(&mut self, r: &mut Reader<'_>) -> Result<ImplNextStep> {
135        let ver = r.take_u8()?;
136        if ver != 0 {
137            return Err(Error::Syntax);
138        }
139        let status = r.take_u8()?;
140        let port = r.take_u16()?;
141        let ip: Ipv4Addr = r.extract()?;
142
143        self.state = State::Done;
144        self.reply = Some(SocksReply::new(
145            SocksStatus::from_socks4_status(status),
146            SocksAddr::Ip(ip.into()),
147            port,
148        ));
149
150        Ok(ImplNextStep::Finished)
151    }
152
153    /// Send our initial socks5 message (which negotiates our authentication methods).
154    fn send_v5_initial(&mut self) -> Result<ImplNextStep> {
155        let mut msg = Vec::new();
156        msg.write_u8(5);
157        match self.request.auth() {
158            SocksAuth::NoAuth => {
159                msg.write_u8(1); // 1 method.
160                msg.write_u8(NO_AUTHENTICATION);
161            }
162            SocksAuth::Socks4(_) => return Err(internal!("Mismatched authentication type").into()),
163            SocksAuth::Username(_, _) => {
164                msg.write_u8(2); // 2 methods.
165                msg.write_u8(USERNAME_PASSWORD);
166                msg.write_u8(NO_AUTHENTICATION);
167            }
168        }
169
170        self.state = State::Socks5AuthWait;
171        Ok(ImplNextStep::Reply { reply: msg })
172    }
173
174    /// Try to handle a socks5 reply telling us what authentication method to
175    /// use, and reply as appropriate.
176    fn handle_v5_auth(&mut self, r: &mut Reader<'_>) -> Result<ImplNextStep> {
177        let ver = r.take_u8()?;
178        if ver != 5 {
179            return Err(Error::Syntax);
180        }
181        let auth = r.take_u8()?;
182        let (msg, next_state) = match auth {
183            USERNAME_PASSWORD => (self.generate_v5_username_auth()?, State::Socks5UsernameWait),
184            NO_AUTHENTICATION => (self.generate_v5_command()?, State::Socks5Wait),
185            other => {
186                return Err(Error::NotImplemented(
187                    format!("authentication type {}", other).into(),
188                ))
189            }
190        };
191
192        self.state = next_state;
193        Ok(ImplNextStep::Reply { reply: msg })
194    }
195
196    /// Return a message to perform username/password authentication.
197    fn generate_v5_username_auth(&self) -> Result<Vec<u8>> {
198        if let SocksAuth::Username(username, pass) = self.request.auth() {
199            let mut msg = Vec::new();
200
201            msg.write_u8(1); // version
202            let mut n = msg.write_nested_u8len();
203            n.write_all(username);
204            n.finish().map_err(into_internal!("id too long"))?;
205
206            let mut n = msg.write_nested_u8len();
207            n.write_all(pass);
208            n.finish().map_err(into_internal!("password too long"))?;
209
210            Ok(msg)
211        } else {
212            // Can't perform this authentication when it wasn't what we asked for.
213            Err(Error::Syntax)
214        }
215    }
216
217    /// Try to handle a reply from the socks5 proxy to acknowledge our
218    /// username/password authentication, and reply as appropriate.
219    fn handle_v5_username_ack(&mut self, r: &mut Reader<'_>) -> Result<ImplNextStep> {
220        let ver = r.take_u8()?;
221        if ver != 1 {
222            return Err(Error::Syntax);
223        }
224        let result = r.take_u8()?;
225        if result != 0 {
226            return Err(Error::AuthRejected);
227        }
228
229        self.state = State::Socks5Wait;
230        Ok(ImplNextStep::Reply {
231            reply: self.generate_v5_command()?,
232        })
233    }
234
235    /// Return a message to encode our final socks5 request.
236    ///
237    /// (This can be done either in response getting an ACK for our
238    /// authentication, or in response to being told that we don't need to
239    /// authenticate.)
240    fn generate_v5_command(&self) -> Result<Vec<u8>> {
241        let mut msg = Vec::new();
242        msg.write_u8(5); // version
243        msg.write_u8(self.request.command().into());
244        msg.write_u8(0); // reserved.
245        msg.write(self.request.addr())
246            .map_err(into_internal!("Can't encode address"))?;
247        msg.write_u16(self.request.port());
248
249        Ok(msg)
250    }
251
252    /// Handle a final socks5 reply.
253    fn handle_v5_final(&mut self, r: &mut Reader<'_>) -> Result<ImplNextStep> {
254        let ver = r.take_u8()?;
255        if ver != 5 {
256            return Err(Error::Syntax);
257        }
258        let status: SocksStatus = r.take_u8()?.into();
259        let _reserved = r.take_u8()?;
260        let addr: SocksAddr = r.extract()?;
261        let port = r.take_u16()?;
262
263        self.state = State::Done;
264        self.reply = Some(SocksReply::new(status, addr, port));
265        Ok(ImplNextStep::Finished)
266    }
267}
268
269#[cfg(test)]
270mod test {
271    // @@ begin test lint list maintained by maint/add_warning @@
272    #![allow(clippy::bool_assert_comparison)]
273    #![allow(clippy::clone_on_copy)]
274    #![allow(clippy::dbg_macro)]
275    #![allow(clippy::mixed_attributes_style)]
276    #![allow(clippy::print_stderr)]
277    #![allow(clippy::print_stdout)]
278    #![allow(clippy::single_char_pattern)]
279    #![allow(clippy::unwrap_used)]
280    #![allow(clippy::unchecked_duration_subtraction)]
281    #![allow(clippy::useless_vec)]
282    #![allow(clippy::needless_pass_by_value)]
283    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
284
285    use super::*;
286    use crate::{msg::SocksCmd, Handshake as _};
287    use hex_literal::hex;
288
289    #[test]
290    fn socks4_ok() {
291        let r = SocksRequest::new(
292            SocksVersion::V4,
293            SocksCmd::CONNECT,
294            SocksAddr::Ip("192.0.2.15".parse().unwrap()),
295            443,
296            SocksAuth::NoAuth,
297        )
298        .unwrap();
299        let mut hs = SocksClientHandshake::new(r);
300        let action = hs.handshake_for_tests(&[]).unwrap().unwrap();
301        assert_eq!(action.drain, 0);
302        assert_eq!(action.reply, hex!("04 01 01BB C000020F 00"));
303        assert_eq!(action.finished, false);
304
305        let action = hs
306            .handshake_for_tests(&hex!("00 5A 01BB C000020F"))
307            .unwrap()
308            .unwrap();
309        assert_eq!(action.drain, 8);
310        assert!(action.reply.is_empty());
311        assert_eq!(action.finished, true);
312
313        let reply = hs.into_reply().unwrap();
314        assert_eq!(reply.status(), SocksStatus::SUCCEEDED);
315        assert_eq!(reply.port(), 443);
316        assert_eq!(reply.addr().to_string(), "192.0.2.15");
317    }
318
319    #[test]
320    fn socks4a_ok() {
321        let r = SocksRequest::new(
322            SocksVersion::V4,
323            SocksCmd::CONNECT,
324            SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()),
325            443,
326            SocksAuth::Socks4(b"hello".to_vec()),
327        )
328        .unwrap();
329        let mut hs = SocksClientHandshake::new(r);
330        let action = hs.handshake_for_tests(&[]).unwrap().unwrap();
331        assert_eq!(action.drain, 0);
332        assert_eq!(
333            action.reply,
334            hex!("04 01 01BB 00000001 68656c6c6f00 7777772e746f7270726f6a6563742e6f726700")
335        );
336        assert_eq!(action.finished, false);
337
338        let action = hs
339            .handshake_for_tests(&hex!("00 5A 01BB C0000215"))
340            .unwrap()
341            .unwrap();
342        assert_eq!(action.drain, 8);
343        assert!(action.reply.is_empty());
344        assert_eq!(action.finished, true);
345
346        let reply = hs.into_reply().unwrap();
347        assert_eq!(reply.status(), SocksStatus::SUCCEEDED);
348        assert_eq!(reply.port(), 443);
349        assert_eq!(reply.addr().to_string(), "192.0.2.21");
350    }
351
352    #[test]
353    fn socks5_with_no_auth() {
354        let r = SocksRequest::new(
355            SocksVersion::V5,
356            SocksCmd::CONNECT,
357            SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()),
358            443,
359            SocksAuth::NoAuth,
360        )
361        .unwrap();
362
363        // client begins by proposing authentication types.
364        let mut hs = SocksClientHandshake::new(r);
365        let action = hs.handshake_for_tests(&[]).unwrap().unwrap();
366        assert_eq!(action.drain, 0);
367        assert_eq!(action.reply, hex!("05 01 00"));
368        assert_eq!(action.finished, false);
369
370        // proxy chooses noauth; client replies with its handshake.
371        let action = hs.handshake_for_tests(&hex!("0500")).unwrap().unwrap();
372        assert_eq!(action.drain, 2);
373        assert_eq!(
374            action.reply,
375            hex!("05 01 00 03 12 7777772e746f7270726f6a6563742e6f7267 01BB")
376        );
377        assert_eq!(action.finished, false);
378
379        // Proxy says "okay, you're connected."
380        // Client is done.
381        let action = hs
382            .handshake_for_tests(&hex!("05 00 00 01 C0000215 01BB"))
383            .unwrap()
384            .unwrap();
385        assert_eq!(action.drain, 10);
386        assert!(action.reply.is_empty());
387        assert_eq!(action.finished, true);
388
389        let reply = hs.into_reply().unwrap();
390        assert_eq!(reply.status(), SocksStatus::SUCCEEDED);
391        assert_eq!(reply.port(), 443);
392        assert_eq!(reply.addr().to_string(), "192.0.2.21");
393    }
394
395    #[test]
396    fn socks5_with_auth_ok() {
397        let r = SocksRequest::new(
398            SocksVersion::V5,
399            SocksCmd::CONNECT,
400            SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()),
401            443,
402            SocksAuth::Username(b"hello".to_vec(), b"world".to_vec()),
403        )
404        .unwrap();
405
406        // client begins by proposing authentication types.
407        let mut hs = SocksClientHandshake::new(r);
408        let action = hs.handshake_for_tests(&[]).unwrap().unwrap();
409        assert_eq!(action.drain, 0);
410        assert_eq!(action.reply, hex!("05 02 0200"));
411        assert_eq!(action.finished, false);
412
413        // proxy chooses username/password; client replies with "hello"/"world"
414        let action = hs.handshake_for_tests(&hex!("0502")).unwrap().unwrap();
415        assert_eq!(action.drain, 2);
416        assert_eq!(action.reply, hex!("01 05 68656c6c6f 05 776f726c64"));
417        assert_eq!(action.finished, false);
418
419        // Proxy says "yeah, that's good authentication, go ahead."
420        // Client says what it actually wants.
421        let action = hs.handshake_for_tests(&hex!("0100")).unwrap().unwrap();
422        assert_eq!(action.drain, 2);
423        assert_eq!(
424            action.reply,
425            hex!("05 01 00 03 12 7777772e746f7270726f6a6563742e6f7267 01BB")
426        );
427        assert_eq!(action.finished, false);
428
429        // Proxy says "okay, you're connected."
430        // Client is done.
431        let action = hs
432            .handshake_for_tests(&hex!("05 00 00 01 C0000215 01BB"))
433            .unwrap()
434            .unwrap();
435        assert_eq!(action.drain, 10);
436        assert!(action.reply.is_empty());
437        assert_eq!(action.finished, true);
438
439        let reply = hs.into_reply().unwrap();
440        assert_eq!(reply.status(), SocksStatus::SUCCEEDED);
441        assert_eq!(reply.port(), 443);
442        assert_eq!(reply.addr().to_string(), "192.0.2.21");
443    }
444}