1
//! Connect to relays via a proxy.
2
//!
3
//! This code is here for two reasons:
4
//!   1. To connect via external pluggable transports (for which we use SOCKS to
5
//!      build our connections).
6
//!   2. To support users who are behind a firewall that requires them to use a
7
//!      SOCKS proxy to connect.
8
//!
9
//! Currently only SOCKS proxies are supported.
10
//
11
// TODO: Add support for `HTTP(S) CONNECT` someday?
12
//
13
// TODO: Maybe refactor this so that tor-ptmgr can exist in a more freestanding
14
// way, with fewer arti dependencies.
15
#![allow(dead_code)]
16

            
17
use std::{
18
    net::{IpAddr, SocketAddr},
19
    sync::Arc,
20
};
21

            
22
use futures::{AsyncReadExt, AsyncWriteExt};
23
use tor_linkspec::PtTargetAddr;
24
use tor_rtcompat::NetStreamProvider;
25
use tor_socksproto::{
26
    Handshake as _, SocksAddr, SocksAuth, SocksClientHandshake, SocksCmd, SocksRequest,
27
    SocksStatus, SocksVersion,
28
};
29
use tracing::trace;
30

            
31
#[cfg(feature = "pt-client")]
32
use super::TransportImplHelper;
33
#[cfg(feature = "pt-client")]
34
use async_trait::async_trait;
35
#[cfg(feature = "pt-client")]
36
use tor_error::bad_api_usage;
37
#[cfg(feature = "pt-client")]
38
use tor_linkspec::{ChannelMethod, HasChanMethod, OwnedChanTarget};
39

            
40
/// Information about what proxy protocol to use, and how to use it.
41
#[derive(Clone, Debug, Eq, PartialEq)]
42
#[non_exhaustive]
43
pub enum Protocol {
44
    /// Connect via SOCKS 4, SOCKS 4a, or SOCKS 5.
45
    Socks(SocksVersion, SocksAuth),
46
}
47

            
48
/// An address to use when told to connect to "no address."
49
const NO_ADDR: IpAddr = IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 1));
50

            
51
/// Open a connection to `target` via the proxy at `proxy`, using the protocol
52
/// at `protocol`.
53
///
54
/// # Limitations
55
///
56
/// We will give an error if the proxy sends us any data on the connection along
57
/// with its final handshake: due to our implementation, any such data will be
58
/// discarded, and so we give an error rather than fail silently.
59
///
60
/// This limitation doesn't matter when the underlying protocol is Tor, or
61
/// anything else where the initiator is expected to speak before the responder
62
/// says anything.  To lift it, we would have to make this function's return
63
/// type become something buffered.
64
//
65
// TODO: Perhaps we should refactor this someday so it can be a general-purpose
66
// proxy function, not only for Arti.
67
pub(crate) async fn connect_via_proxy<R: NetStreamProvider + Send + Sync>(
68
    runtime: &R,
69
    proxy: &SocketAddr,
70
    protocol: &Protocol,
71
    target: &PtTargetAddr,
72
) -> Result<R::Stream, ProxyError> {
73
    trace!(
74
        "Launching a proxied connection to {} via proxy at {} using {:?}",
75
        target,
76
        proxy,
77
        protocol
78
    );
79
    let mut stream = runtime
80
        .connect(proxy)
81
        .await
82
        .map_err(|e| ProxyError::ProxyConnect(Arc::new(e)))?;
83

            
84
    let Protocol::Socks(version, auth) = protocol;
85

            
86
    let (target_addr, target_port): (tor_socksproto::SocksAddr, u16) = match target {
87
        PtTargetAddr::IpPort(a) => (SocksAddr::Ip(a.ip()), a.port()),
88
        #[cfg(feature = "pt-client")]
89
        PtTargetAddr::HostPort(host, port) => (
90
            SocksAddr::Hostname(
91
                host.clone()
92
                    .try_into()
93
                    .map_err(ProxyError::InvalidSocksAddr)?,
94
            ),
95
            *port,
96
        ),
97
        #[cfg(feature = "pt-client")]
98
        PtTargetAddr::None => (SocksAddr::Ip(NO_ADDR), 1),
99
        _ => return Err(ProxyError::UnrecognizedAddr),
100
    };
101

            
102
    let request = SocksRequest::new(
103
        *version,
104
        SocksCmd::CONNECT,
105
        target_addr,
106
        target_port,
107
        auth.clone(),
108
    )
109
    .map_err(ProxyError::InvalidSocksRequest)?;
110
    let mut handshake = SocksClientHandshake::new(request);
111

            
112
    // TODO: This code is largely copied from the socks server wrapper code in
113
    // arti::proxy. Perhaps we should condense them into a single thing, if we
114
    // don't just revise the SOCKS code completely.
115
    let mut buf = tor_socksproto::Buffer::new();
116
    let reply = loop {
117
        use tor_socksproto::NextStep as NS;
118
        match handshake.step(&mut buf).map_err(ProxyError::SocksProto)? {
119
            NS::Send(send) => {
120
                stream.write_all(&send).await?;
121
                stream.flush().await?;
122
            }
123
            NS::Finished(fin) => {
124
                break fin
125
                    .into_output_forbid_pipelining()
126
                    .map_err(ProxyError::SocksProto)?
127
            }
128
            NS::Recv(mut recv) => {
129
                let n = stream.read(recv.buf()).await?;
130
                recv.note_received(n).map_err(ProxyError::SocksProto)?;
131
            }
132
        }
133
    };
134

            
135
    let status = reply.status();
136
    trace!(
137
        "SOCKS handshake with {} succeeded, with status {:?}",
138
        proxy,
139
        status
140
    );
141

            
142
    if status != SocksStatus::SUCCEEDED {
143
        return Err(ProxyError::SocksError(status));
144
    }
145

            
146
    Ok(stream)
147
}
148

            
149
/// An error that occurs while negotiating a connection with a proxy.
150
#[derive(Clone, Debug, thiserror::Error)]
151
#[non_exhaustive]
152
pub enum ProxyError {
153
    /// We had an IO error while trying to open a connection to the proxy.
154
    #[error("Problem while connecting to proxy")]
155
    ProxyConnect(#[source] Arc<std::io::Error>),
156

            
157
    /// We had an IO error while talking to the proxy.
158
    #[error("Problem while communicating with proxy")]
159
    ProxyIo(#[source] Arc<std::io::Error>),
160

            
161
    /// We tried to use an address which socks doesn't support.
162
    #[error("SOCKS proxy does not support target address")]
163
    InvalidSocksAddr(#[source] tor_socksproto::Error),
164

            
165
    /// We tried to use an address type which _we_ don't recognize.
166
    #[error("Got an address type we don't recognize")]
167
    UnrecognizedAddr,
168

            
169
    /// Our SOCKS implementation told us that this request cannot be encoded.
170
    #[error("Tried to make an invalid SOCKS request")]
171
    InvalidSocksRequest(#[source] tor_socksproto::Error),
172

            
173
    /// The peer refused our request, or spoke SOCKS incorrectly.
174
    #[error("Protocol error while communicating with SOCKS proxy")]
175
    SocksProto(#[source] tor_socksproto::Error),
176

            
177
    /// We encountered an internal programming error.
178
    #[error("Internal error")]
179
    Bug(#[from] tor_error::Bug),
180

            
181
    /// We got extra data immediately after our handshake, before we actually
182
    /// sent anything.
183
    ///
184
    /// This is not a bug in the calling code or in the peer protocol: it just
185
    /// means that the remote peer sent us data before we actually sent it any
186
    /// data. Unfortunately, there's a limitation in our code that makes it
187
    /// discard any such data, and therefore we have to give this error to
188
    /// prevent bugs.
189
    ///
190
    /// We could someday remove this limitation.
191
    #[error("Received unexpected early data from peer")]
192
    UnexpectedData,
193

            
194
    /// The proxy told us that our attempt failed.
195
    #[error("SOCKS proxy reported an error: {0}")]
196
    SocksError(SocksStatus),
197
}
198

            
199
impl From<std::io::Error> for ProxyError {
200
    fn from(e: std::io::Error) -> Self {
201
        ProxyError::ProxyIo(Arc::new(e))
202
    }
203
}
204

            
205
impl tor_error::HasKind for ProxyError {
206
    fn kind(&self) -> tor_error::ErrorKind {
207
        use tor_error::ErrorKind as EK;
208
        use ProxyError as E;
209
        match self {
210
            E::ProxyConnect(_) | E::ProxyIo(_) => EK::LocalNetworkError,
211
            E::InvalidSocksAddr(_) | E::InvalidSocksRequest(_) => EK::BadApiUsage,
212
            E::UnrecognizedAddr => EK::NotImplemented,
213
            E::SocksProto(_) => EK::LocalProtocolViolation,
214
            E::Bug(e) => e.kind(),
215
            E::UnexpectedData => EK::NotImplemented,
216
            E::SocksError(_) => EK::LocalProtocolViolation,
217
        }
218
    }
219
}
220

            
221
impl tor_error::HasRetryTime for ProxyError {
222
    fn retry_time(&self) -> tor_error::RetryTime {
223
        use tor_error::RetryTime as RT;
224
        use ProxyError as E;
225
        use SocksStatus as S;
226
        match self {
227
            E::ProxyConnect(_) | E::ProxyIo(_) => RT::AfterWaiting,
228
            E::InvalidSocksAddr(_) => RT::Never,
229
            E::UnrecognizedAddr => RT::Never,
230
            E::InvalidSocksRequest(_) => RT::Never,
231
            E::SocksProto(_) => RT::AfterWaiting,
232
            E::Bug(_) => RT::Never,
233
            E::UnexpectedData => RT::Never,
234
            E::SocksError(e) => match *e {
235
                S::CONNECTION_REFUSED
236
                | S::GENERAL_FAILURE
237
                | S::HOST_UNREACHABLE
238
                | S::NETWORK_UNREACHABLE
239
                | S::TTL_EXPIRED => RT::AfterWaiting,
240
                _ => RT::Never,
241
            },
242
        }
243
    }
244
}
245

            
246
#[cfg(feature = "pt-client")]
247
#[cfg_attr(docsrs, doc(cfg(feature = "pt-client")))]
248
/// An object that connects to a Tor bridge via an external pluggable transport
249
/// that provides a proxy.
250
#[derive(Clone, Debug)]
251
pub struct ExternalProxyPlugin<R> {
252
    /// The runtime to use for connections.
253
    runtime: R,
254
    /// The location of the proxy.
255
    proxy_addr: SocketAddr,
256
    /// The SOCKS protocol version to use.
257
    proxy_version: SocksVersion,
258
}
259

            
260
#[cfg(feature = "pt-client")]
261
#[cfg_attr(docsrs, doc(cfg(feature = "pt-client")))]
262
impl<R: NetStreamProvider + Send + Sync> ExternalProxyPlugin<R> {
263
    /// Make a new `ExternalProxyPlugin`.
264
    pub fn new(rt: R, proxy_addr: SocketAddr, proxy_version: SocksVersion) -> Self {
265
        Self {
266
            runtime: rt,
267
            proxy_addr,
268
            proxy_version,
269
        }
270
    }
271
}
272

            
273
#[cfg(feature = "pt-client")]
274
#[async_trait]
275
impl<R: NetStreamProvider + Send + Sync> TransportImplHelper for ExternalProxyPlugin<R> {
276
    type Stream = R::Stream;
277

            
278
    async fn connect(
279
        &self,
280
        target: &OwnedChanTarget,
281
    ) -> crate::Result<(OwnedChanTarget, R::Stream)> {
282
        let pt_target = match target.chan_method() {
283
            ChannelMethod::Direct(_) => {
284
                return Err(crate::Error::UnusableTarget(bad_api_usage!(
285
                    "Used pluggable transport for a TCP connection."
286
                )))
287
            }
288
            ChannelMethod::Pluggable(target) => target,
289
            other => {
290
                return Err(crate::Error::UnusableTarget(bad_api_usage!(
291
                    "Used unknown, unsupported, transport {:?} for a TCP connection.",
292
                    other,
293
                )))
294
            }
295
        };
296

            
297
        let protocol =
298
            settings_to_protocol(self.proxy_version, encode_settings(pt_target.settings()))?;
299

            
300
        Ok((
301
            target.clone(),
302
            connect_via_proxy(&self.runtime, &self.proxy_addr, &protocol, pt_target.addr()).await?,
303
        ))
304
    }
305
}
306

            
307
/// Encode the PT settings from `IT` in a format that a pluggable transport can use.
308
#[cfg(feature = "pt-client")]
309
14
fn encode_settings<'a, IT>(settings: IT) -> String
310
14
where
311
14
    IT: Iterator<Item = (&'a str, &'a str)>,
312
14
{
313
    /// Escape a character in the way expected by pluggable transports.
314
    ///
315
    /// This escape machinery is a mirror of that in the standard library.
316
    enum EscChar {
317
        /// Return a backslash then a character.
318
        Backslash(char),
319
        /// Return a character.
320
        Literal(char),
321
        /// Return nothing.
322
        Done,
323
    }
324
    impl EscChar {
325
        /// Create an iterator to escape one character.
326
264
        fn new(ch: char, in_key: bool) -> Self {
327
6
            match ch {
328
8
                '\\' | ';' => EscChar::Backslash(ch),
329
2
                '=' if in_key => EscChar::Backslash(ch),
330
254
                _ => EscChar::Literal(ch),
331
            }
332
264
        }
333
    }
334
    impl Iterator for EscChar {
335
        type Item = char;
336

            
337
538
        fn next(&mut self) -> Option<Self::Item> {
338
538
            match *self {
339
10
                EscChar::Backslash(ch) => {
340
10
                    *self = EscChar::Literal(ch);
341
10
                    Some('\\')
342
                }
343
264
                EscChar::Literal(ch) => {
344
264
                    *self = EscChar::Done;
345
264
                    Some(ch)
346
                }
347
264
                EscChar::Done => None,
348
            }
349
538
        }
350
    }
351

            
352
    /// escape a key or value string.
353
40
    fn esc(s: &str, in_key: bool) -> impl Iterator<Item = char> + '_ {
354
284
        s.chars().flat_map(move |c| EscChar::new(c, in_key))
355
40
    }
356

            
357
14
    let mut result = String::new();
358
34
    for (k, v) in settings {
359
20
        result.extend(esc(k, true));
360
20
        result.push('=');
361
20
        result.extend(esc(v, false));
362
20
        result.push(';');
363
20
    }
364
14
    result.pop(); // remove the final ';' if any. Yes this is ugly.
365
14

            
366
14
    result
367
14
}
368

            
369
/// Transform a string into a representation that can be sent as SOCKS
370
/// authentication.
371
// NOTE(eta): I am very unsure of the logic in here.
372
#[cfg(feature = "pt-client")]
373
24
pub fn settings_to_protocol(vers: SocksVersion, s: String) -> Result<Protocol, ProxyError> {
374
24
    let mut bytes: Vec<_> = s.into();
375
24
    Ok(if bytes.is_empty() {
376
2
        Protocol::Socks(vers, SocksAuth::NoAuth)
377
22
    } else if vers == SocksVersion::V4 {
378
4
        if bytes.contains(&0) {
379
2
            return Err(ProxyError::InvalidSocksRequest(
380
2
                tor_socksproto::Error::NotImplemented(
381
2
                    "SOCKS 4 doesn't support internal NUL bytes (for PT settings list)".into(),
382
2
                ),
383
2
            ));
384
        } else {
385
2
            Protocol::Socks(SocksVersion::V4, SocksAuth::Socks4(bytes))
386
        }
387
18
    } else if bytes.len() <= 255 {
388
        // The [0] here is mandatory according to the pt-spec.
389
6
        Protocol::Socks(SocksVersion::V5, SocksAuth::Username(bytes, vec![0]))
390
12
    } else if bytes.len() <= (255 * 2) {
391
8
        let password = bytes.split_off(255);
392
8
        Protocol::Socks(SocksVersion::V5, SocksAuth::Username(bytes, password))
393
    } else {
394
4
        return Err(ProxyError::InvalidSocksRequest(
395
4
            tor_socksproto::Error::NotImplemented("PT settings list too long for SOCKS 5".into()),
396
4
        ));
397
    })
398
24
}
399

            
400
#[cfg(test)]
401
mod test {
402
    // @@ begin test lint list maintained by maint/add_warning @@
403
    #![allow(clippy::bool_assert_comparison)]
404
    #![allow(clippy::clone_on_copy)]
405
    #![allow(clippy::dbg_macro)]
406
    #![allow(clippy::mixed_attributes_style)]
407
    #![allow(clippy::print_stderr)]
408
    #![allow(clippy::print_stdout)]
409
    #![allow(clippy::single_char_pattern)]
410
    #![allow(clippy::unwrap_used)]
411
    #![allow(clippy::unchecked_duration_subtraction)]
412
    #![allow(clippy::useless_vec)]
413
    #![allow(clippy::needless_pass_by_value)]
414
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
415
    #[allow(unused_imports)]
416
    use super::*;
417

            
418
    #[cfg(feature = "pt-client")]
419
    #[test]
420
    fn setting_encoding() {
421
        fn check(settings: Vec<(&str, &str)>, expected: &str) {
422
            assert_eq!(encode_settings(settings.into_iter()), expected);
423
        }
424

            
425
        // Easy cases, no escapes.
426
        check(vec![], "");
427
        check(vec![("hello", "world")], "hello=world");
428
        check(
429
            vec![("hey", "verden"), ("hello", "world")],
430
            "hey=verden;hello=world",
431
        );
432
        check(
433
            vec![("hey", "verden"), ("hello", "world"), ("selv", "tak")],
434
            "hey=verden;hello=world;selv=tak",
435
        );
436

            
437
        check(
438
            vec![("semi;colon", "equals=sign")],
439
            r"semi\;colon=equals=sign",
440
        );
441
        check(
442
            vec![("equals=sign", "semi;colon")],
443
            r"equals\=sign=semi\;colon",
444
        );
445
        check(
446
            vec![("semi;colon", "equals=sign"), ("also", "back\\slash")],
447
            r"semi\;colon=equals=sign;also=back\\slash",
448
        );
449
    }
450

            
451
    #[cfg(feature = "pt-client")]
452
    #[test]
453
    fn split_settings() {
454
        use SocksVersion::*;
455
        let long_string = "examplestrg".to_owned().repeat(50);
456
        assert_eq!(long_string.len(), 550);
457
        let sv = |v, a, b| settings_to_protocol(v, long_string[a..b].to_owned()).unwrap();
458
        let s = |a, b| sv(V5, a, b);
459
        let v = |a, b| long_string.as_bytes()[a..b].to_vec();
460

            
461
        assert_eq!(s(0, 0), Protocol::Socks(V5, SocksAuth::NoAuth));
462
        assert_eq!(
463
            s(0, 50),
464
            Protocol::Socks(V5, SocksAuth::Username(v(0, 50), vec![0]))
465
        );
466
        assert_eq!(
467
            s(0, 255),
468
            Protocol::Socks(V5, SocksAuth::Username(v(0, 255), vec![0]))
469
        );
470
        assert_eq!(
471
            s(0, 256),
472
            Protocol::Socks(V5, SocksAuth::Username(v(0, 255), v(255, 256)))
473
        );
474
        assert_eq!(
475
            s(0, 300),
476
            Protocol::Socks(V5, SocksAuth::Username(v(0, 255), v(255, 300)))
477
        );
478
        assert_eq!(
479
            s(0, 510),
480
            Protocol::Socks(V5, SocksAuth::Username(v(0, 255), v(255, 510)))
481
        );
482

            
483
        // This one needs to use socks4, or it won't fit. :P
484
        assert_eq!(
485
            sv(V4, 0, 511),
486
            Protocol::Socks(V4, SocksAuth::Socks4(v(0, 511)))
487
        );
488

            
489
        // Small requests with "0" bytes work fine...
490
        assert_eq!(
491
            settings_to_protocol(V5, "\0".to_owned()).unwrap(),
492
            Protocol::Socks(V5, SocksAuth::Username(vec![0], vec![0]))
493
        );
494
        assert_eq!(
495
            settings_to_protocol(V5, "\0".to_owned().repeat(510)).unwrap(),
496
            Protocol::Socks(V5, SocksAuth::Username(vec![0; 255], vec![0; 255]))
497
        );
498

            
499
        // Huge requests with "0" simply can't be encoded.
500
        assert!(settings_to_protocol(V5, "\0".to_owned().repeat(511)).is_err());
501

            
502
        // Huge requests without "0" can't be encoded as V5
503
        assert!(settings_to_protocol(V5, long_string[0..512].to_owned()).is_err());
504

            
505
        // Requests with "0" can't be encoded as V4.
506
        assert!(settings_to_protocol(V4, "\0".to_owned()).is_err());
507
    }
508
}