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
406
    fn take_from(r: &mut Reader<'_>) -> BytesResult<SocksAddr> {
38
406
        let atype = r.take_u8()?;
39
374
        match atype {
40
            1 => {
41
106
                let ip4: std::net::Ipv4Addr = r.extract()?;
42
54
                Ok(SocksAddr::Ip(ip4.into()))
43
            }
44
            3 => {
45
144
                let hlen = r.take_u8()?;
46
136
                let hostname = r.take(hlen as usize)?;
47
26
                let hostname = std::str::from_utf8(hostname)
48
26
                    .map_err(|_| BytesError::InvalidMessage("bad utf8 on hostname".into()))?
49
26
                    .to_string();
50
26
                let hostname = hostname
51
26
                    .try_into()
52
26
                    .map_err(|_| BytesError::InvalidMessage("hostname too long".into()))?;
53
26
                Ok(SocksAddr::Hostname(hostname))
54
            }
55
            4 => {
56
124
                let ip6: std::net::Ipv6Addr = r.extract()?;
57
26
                Ok(SocksAddr::Ip(ip6.into()))
58
            }
59
            _ => Err(BytesError::InvalidMessage(
60
                "unrecognized address type.".into(),
61
            )),
62
        }
63
406
    }
64
}
65

            
66
impl Writeable for SocksAddr {
67
50
    fn write_onto<W: Writer + ?Sized>(&self, w: &mut W) -> EncodeResult<()> {
68
34
        match self {
69
22
            SocksAddr::Ip(IpAddr::V4(ip)) => {
70
22
                w.write_u8(1);
71
22
                w.write(ip)?;
72
            }
73
12
            SocksAddr::Ip(IpAddr::V6(ip)) => {
74
12
                w.write_u8(4);
75
12
                w.write(ip)?;
76
            }
77
16
            SocksAddr::Hostname(h) => {
78
16
                let h = h.as_ref();
79
16
                assert!(h.len() < 256);
80
16
                let hlen = h.len() as u8;
81
16
                w.write_u8(3);
82
16
                w.write_u8(hlen);
83
16
                w.write(h.as_bytes())?;
84
            }
85
        }
86
50
        Ok(())
87
50
    }
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_time_subtraction)]
103
    #![allow(clippy::useless_vec)]
104
    #![allow(clippy::needless_pass_by_value)]
105
    #![allow(clippy::string_slice)] // See arti#2571
106
    //! <!-- @@ end test lint list
107

            
108
    use crate::*;
109
    use std::collections::VecDeque;
110

            
111
    /// Given a socks request, run a complete (successful round) trip, reply with the
112
    /// the given status code, and return both sides' results.
113
    ///
114
    /// Use the (deprecated) `Handshake::handshake` and `Action` API
115
    fn run_handshake_old_api(
116
        request: SocksRequest,
117
        status: SocksStatus,
118
    ) -> (SocksRequest, SocksReply) {
119
        let mut client_hs = SocksClientHandshake::new(request);
120
        let mut proxy_hs = SocksProxyHandshake::new();
121
        let mut received_request = None;
122

            
123
        let mut last_proxy_msg = vec![];
124
        // Prevent infinite loop in case of bugs.
125
        for _ in 0..100 {
126
            // Make sure that the client says "truncated" for all prefixes of the proxy's message.
127
            for truncate in 0..last_proxy_msg.len() {
128
                let r = client_hs.handshake_for_tests(&last_proxy_msg[..truncate]);
129
                assert!(r.is_err());
130
            }
131
            // Get the client's actual message.
132
            let client_action = client_hs
133
                .handshake_for_tests(&last_proxy_msg)
134
                .unwrap()
135
                .unwrap();
136
            assert_eq!(client_action.drain, last_proxy_msg.len());
137
            if client_action.finished {
138
                let received_reply = client_hs.into_reply();
139
                return (received_request.unwrap(), received_reply.unwrap());
140
            }
141
            let client_msg = client_action.reply;
142

            
143
            // Make sure that the proxy says "truncated" for all prefixes of the client's message.
144
            for truncate in 0..client_msg.len() {
145
                let r = proxy_hs.handshake_for_tests(&client_msg[..truncate]);
146
                assert!(r.is_err());
147
            }
148
            // Get the proxy's actual reply (if any).
149
            let proxy_action = proxy_hs.handshake_for_tests(&client_msg).unwrap().unwrap();
150
            assert_eq!(proxy_action.drain, client_msg.len());
151
            last_proxy_msg = if proxy_action.finished {
152
                // The proxy is done: have it reply with a status code.
153
                received_request = proxy_hs.clone().into_request();
154
                received_request
155
                    .as_ref()
156
                    .unwrap()
157
                    .reply(status, None)
158
                    .unwrap()
159
            } else {
160
                proxy_action.reply
161
            };
162
        }
163
        panic!("Handshake ran for too many steps")
164
    }
165

            
166
    /// Given a socks request, run a complete (successful round) trip, reply with the
167
    /// the given status code, and return both sides' results.
168
    ///
169
    /// Use the (new) `Handshake::step` API
170
    fn run_handshake_new_api<P: ReadPrecision, const MAX_RECV: usize>(
171
        request: SocksRequest,
172
        status: SocksStatus,
173
    ) -> (SocksRequest, SocksReply) {
174
        struct State<P: ReadPrecision, H: Handshake> {
175
            hs: H,
176
            buf: Buffer<P>,
177
            fin: Option<H::Output>,
178
        }
179

            
180
        struct DidSomething;
181

            
182
        let mut client = State::<P, _>::new(SocksClientHandshake::new(request));
183
        let mut server = State::<P, _>::new(SocksProxyHandshake::new());
184

            
185
        let mut c2s = VecDeque::new();
186
        let mut s2c = VecDeque::new();
187

            
188
        let mut status = Some(status);
189

            
190
        impl<P: ReadPrecision, H: Handshake> State<P, H> {
191
            fn new(hs: H) -> Self {
192
                State {
193
                    hs,
194
                    buf: Default::default(),
195
                    fin: None,
196
                }
197
            }
198

            
199
            fn progress_1(
200
                &mut self,
201
                max_recv: usize,
202
                rx: &mut VecDeque<u8>,
203
                tx: &mut VecDeque<u8>,
204
            ) -> Option<DidSomething> {
205
                use NextStep as NS;
206

            
207
                if self.fin.is_some() {
208
                    return None;
209
                }
210

            
211
                match self.hs.step(&mut self.buf).unwrap() {
212
                    NS::Recv(mut recv) => {
213
                        let n = [recv.buf().len(), rx.len(), max_recv]
214
                            .into_iter()
215
                            .min()
216
                            .unwrap();
217
                        for p in &mut recv.buf()[0..n] {
218
                            *p = rx.pop_front().unwrap();
219
                        }
220
                        recv.note_received(n).unwrap_or_else(|e| match e {
221
                            // This is actually expected; our test case produces 0-byte reads
222
                            // sometimes.
223
                            Error::UnexpectedEof => {}
224
                            other => panic!("{:?}", other),
225
                        });
226
                        if n != 0 { Some(DidSomething) } else { None }
227
                    }
228
                    NS::Send(send) => {
229
                        for c in send {
230
                            tx.push_back(c);
231
                        }
232
                        Some(DidSomething)
233
                    }
234
                    NS::Finished(fin) => {
235
                        self.fin = Some(fin.into_output_forbid_pipelining().unwrap());
236
                        Some(DidSomething)
237
                    }
238
                }
239
            }
240
        }
241

            
242
        loop {
243
            let ds = [
244
                client.progress_1(MAX_RECV, &mut s2c, &mut c2s),
245
                server.progress_1(MAX_RECV, &mut c2s, &mut s2c),
246
            ]
247
            .into_iter()
248
            .flatten()
249
            .next();
250

            
251
            if let Some(DidSomething) = ds {
252
                continue;
253
            }
254

            
255
            let Some(status) = status.take() else { break };
256

            
257
            let reply = server.fin.as_ref().unwrap().reply(status, None).unwrap();
258
            for c in reply {
259
                s2c.push_back(c);
260
            }
261
        }
262

            
263
        (server.fin.unwrap(), client.fin.unwrap())
264
    }
265

            
266
    // Invoke run_handshake and assert that the output matches the input.
267
    fn test_handshake(request: &SocksRequest, status: SocksStatus) {
268
        for run_handshake in [
269
            run_handshake_old_api,
270
            run_handshake_new_api::<(), 1>,
271
            run_handshake_new_api::<(), 100>,
272
            run_handshake_new_api::<PreciseReads, 1>,
273
            run_handshake_new_api::<PreciseReads, 100>,
274
        ] {
275
            let (request_out, status_out) = run_handshake(request.clone(), status);
276
            assert_eq!(&request_out, request);
277
            assert_eq!(status_out.status(), status);
278
        }
279
    }
280

            
281
    #[test]
282
    fn socks4() {
283
        test_handshake(
284
            &SocksRequest::new(
285
                SocksVersion::V4,
286
                SocksCmd::CONNECT,
287
                SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()),
288
                443,
289
                SocksAuth::NoAuth,
290
            )
291
            .unwrap(),
292
            SocksStatus::SUCCEEDED,
293
        );
294

            
295
        test_handshake(
296
            &SocksRequest::new(
297
                SocksVersion::V4,
298
                SocksCmd::CONNECT,
299
                SocksAddr::Ip("192.0.2.33".parse().unwrap()),
300
                22,
301
                SocksAuth::Socks4(b"swordfish".to_vec()),
302
            )
303
            .unwrap(),
304
            SocksStatus::GENERAL_FAILURE,
305
        );
306
    }
307

            
308
    #[test]
309
    fn socks5() {
310
        test_handshake(
311
            &SocksRequest::new(
312
                SocksVersion::V5,
313
                SocksCmd::CONNECT,
314
                SocksAddr::Hostname("www.torproject.org".to_string().try_into().unwrap()),
315
                443,
316
                SocksAuth::NoAuth,
317
            )
318
            .unwrap(),
319
            SocksStatus::SUCCEEDED,
320
        );
321

            
322
        test_handshake(
323
            &SocksRequest::new(
324
                SocksVersion::V5,
325
                SocksCmd::CONNECT,
326
                SocksAddr::Ip("2001:db8::32".parse().unwrap()),
327
                443,
328
                SocksAuth::Username(b"belbo".to_vec(), b"non".to_vec()),
329
            )
330
            .unwrap(),
331
            SocksStatus::GENERAL_FAILURE,
332
        );
333
    }
334
}