1
//! SOCKS-specific proxy support.
2

            
3
use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, BufReader};
4
use safelog::sensitive;
5
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6
use tracing::{debug, instrument, warn};
7

            
8
#[allow(unused)]
9
use arti_client::HasKind;
10
use arti_client::{ErrorKind, IntoTorAddr as _, StreamPrefs};
11
#[cfg(feature = "rpc")]
12
use tor_rpcbase::{self as rpc};
13
use tor_rtcompat::Runtime;
14
use tor_socksproto::{Handshake as _, SocksAddr, SocksAuth, SocksCmd, SocksRequest};
15

            
16
use anyhow::{Context, Result, anyhow};
17

            
18
use super::{
19
    ListenerIsolation, ProvidedIsolation, ProxyContext, StreamIsolationKey, write_all_and_close,
20
    write_all_and_flush,
21
};
22
cfg_if::cfg_if! {
23
    if #[cfg(feature="rpc")] {
24
        use crate::rpc::conntarget::ConnTarget;
25
    } else {
26
        use arti_client::TorClient;
27

            
28
        /// A type returned by get_prefs_and_session,
29
        /// and used to launch data streams or resolve attempts.
30
        ///
31
        /// TODO RPC: This is quite ugly; we should do something better.
32
        /// At least, we should never expose this outside the socks module.
33
        type ConnTarget<R> = TorClient<R>;
34
    }
35
}
36

            
37
/// Payload to return when an HTTP connection arrive on a Socks port
38
/// without HTTP support.
39
pub(super) const WRONG_PROTOCOL_PAYLOAD: &[u8] = br#"HTTP/1.0 501 Not running as an HTTP Proxy
40
Content-Type: text/html; charset=utf-8
41

            
42
<!DOCTYPE html>
43
<html>
44
<head>
45
<title>This is a SOCKS Proxy, Not An HTTP Proxy</title>
46
</head>
47
<body>
48
<h1>This is a SOCKS proxy, not an HTTP proxy.</h1>
49
<p>
50
It appears you have configured your web browser to use this Tor port as
51
an HTTP proxy.
52
</p>
53
<p>
54
This is not correct: This port is configured as a SOCKS proxy, not
55
an HTTP proxy. If you need an HTTP proxy tunnel,
56
build Arti with the <code>http-connect</code> feature enabled.
57
</p>
58
<p>
59
See <a href="https://gitlab.torproject.org/tpo/core/arti/#todo-need-to-change-when-arti-get-a-user-documentation">https://gitlab.torproject.org/tpo/core/arti</a> for more information.
60
</p>
61
</body>
62
</html>"#;
63

            
64
/// Find out which kind of address family we can/should use for a
65
/// given `SocksRequest`.
66
#[cfg_attr(feature = "experimental-api", visibility::make(pub))]
67
fn stream_preference(req: &SocksRequest, addr: &str) -> StreamPrefs {
68
    let mut prefs = StreamPrefs::new();
69
    if addr.parse::<Ipv4Addr>().is_ok() {
70
        // If they asked for an IPv4 address correctly, nothing else will do.
71
        prefs.ipv4_only();
72
    } else if addr.parse::<Ipv6Addr>().is_ok() {
73
        // If they asked for an IPv6 address correctly, nothing else will do.
74
        prefs.ipv6_only();
75
    } else if req.version() == tor_socksproto::SocksVersion::V4 {
76
        // SOCKS4 and SOCKS4a only support IPv4
77
        prefs.ipv4_only();
78
    } else {
79
        // Otherwise, default to saying IPv4 is preferred.
80
        prefs.ipv4_preferred();
81
    }
82
    prefs
83
}
84

            
85
/// The meaning of a SOCKS authentication field, according to our conventions.
86
struct AuthInterpretation {
87
    /// Associate this stream with a DataStream created by using a particular RPC object
88
    /// as a Tor client.
89
    #[cfg(feature = "rpc")]
90
    rpc_object: Option<rpc::ObjectId>,
91

            
92
    /// Isolate this stream from other streams that do not have the same
93
    /// value.
94
    isolation: ProvidedIsolation,
95
}
96

            
97
/// Given the authentication object from a socks connection, determine what it's telling
98
/// us to do.
99
///
100
/// (In no case is it actually SOCKS authentication: it can either be a message
101
/// to the stream isolation system or the RPC system.)
102
fn interpret_socks_auth(auth: &SocksAuth) -> Result<AuthInterpretation> {
103
    /// Interpretation of a SOCKS5 username according to
104
    /// the [SOCKS extended authentication](https://spec.torproject.org/socks-extensions.html#extended-auth)
105
    /// specification.
106
    enum Uname<'a> {
107
        /// This is a legacy username; it's just part of the
108
        /// isolation information.
109
        //
110
        // Note: We're not actually throwing away the username here;
111
        // instead we're going to use the whole SocksAuth
112
        // in a `ProvidedAuthentication::Legacy``.
113
        // TODO RPC: Find a more idiomatic way to express this data flow.
114
        Legacy,
115
        /// This is using the socks extension: contains the extension
116
        /// format code and the remaining information from the username.
117
        Extended(u8, &'a [u8]),
118
    }
119
    /// Helper: Try to interpret a SOCKS5 username field as indicating the start of a set of
120
    /// extended socks authentication information.
121
    ///
122
    /// Implements [SOCKS extended authentication](https://spec.torproject.org/socks-extensions.html#extended-auth).
123
    ///
124
    /// If it does indicate that extensions are in use,
125
    /// return a `Uname::Extended` containing
126
    /// the extension format type and the remaining information from the username.
127
    ///
128
    /// If it indicates that no extensions are in use,
129
    /// return `Uname::Legacy`.
130
    ///
131
    /// If it is badly formatted, return an error.
132
    fn interpret_socks5_username(username: &[u8]) -> Result<Uname<'_>> {
133
        /// 8-byte "magic" sequence from
134
        /// [SOCKS extended authentication](https://spec.torproject.org/socks-extensions.html#extended-auth).
135
        /// When it appears at the start of a username,
136
        /// indicates that the username/password are to be interpreted as
137
        /// as encoding SOCKS5 extended parameters,
138
        /// but the format might not be one we recognize.
139
        const SOCKS_EXT_CONST_ANY: &[u8] = b"<torS0X>";
140
        let Some(remainder) = username.strip_prefix(SOCKS_EXT_CONST_ANY) else {
141
            return Ok(Uname::Legacy);
142
        };
143
        let (format_code, remainder) = remainder
144
            .split_at_checked(1)
145
            .ok_or_else(|| anyhow!("Extended SOCKS information without format code."))?;
146
        Ok(Uname::Extended(format_code[0], remainder))
147
    }
148

            
149
    let isolation = match auth {
150
        SocksAuth::Username(user, pass) => match interpret_socks5_username(user)? {
151
            Uname::Legacy => ProvidedIsolation::LegacySocks(auth.clone()),
152
            Uname::Extended(b'1', b"") => {
153
                return Err(anyhow!("Received empty RPC object ID"));
154
            }
155
            Uname::Extended(format_code @ b'1', remainder) => {
156
                #[cfg(not(feature = "rpc"))]
157
                return Err(anyhow!(
158
                    "Received RPC object ID, but not built with support for RPC"
159
                ));
160
                #[cfg(feature = "rpc")]
161
                return Ok(AuthInterpretation {
162
                    rpc_object: Some(rpc::ObjectId::from(
163
                        std::str::from_utf8(remainder).context("Rpc object ID was not utf-8")?,
164
                    )),
165
                    isolation: ProvidedIsolation::ExtendedSocks {
166
                        format_code,
167
                        isolation: pass.clone().into(),
168
                    },
169
                });
170
            }
171
            Uname::Extended(format_code @ b'0', b"") => ProvidedIsolation::ExtendedSocks {
172
                format_code,
173
                isolation: pass.clone().into(),
174
            },
175
            Uname::Extended(b'0', _) => {
176
                return Err(anyhow!("Extraneous information in SOCKS username field."));
177
            }
178
            _ => return Err(anyhow!("Unrecognized SOCKS format code")),
179
        },
180
        _ => ProvidedIsolation::LegacySocks(auth.clone()),
181
    };
182

            
183
    Ok(AuthInterpretation {
184
        #[cfg(feature = "rpc")]
185
        rpc_object: None,
186
        isolation,
187
    })
188
}
189

            
190
impl<R: Runtime> super::ProxyContext<R> {
191
    /// Interpret a SOCKS request and our input information to determine which
192
    /// TorClient / ClientConnectionTarget object and StreamPrefs we should use.
193
    ///
194
    /// TODO RPC: The return type here is a bit ugly.
195
    fn get_prefs_and_session(
196
        &self,
197
        request: &SocksRequest,
198
        target_addr: &str,
199
        conn_isolation: ListenerIsolation,
200
    ) -> Result<(StreamPrefs, ConnTarget<R>)> {
201
        // Determine whether we want to ask for IPv4/IPv6 addresses.
202
        let mut prefs = stream_preference(request, target_addr);
203

            
204
        // Interpret socks authentication to see whether we want to connect to an RPC connector.
205
        let interp = interpret_socks_auth(request.auth())?;
206
        prefs.set_isolation(StreamIsolationKey(conn_isolation, interp.isolation));
207

            
208
        #[cfg(feature = "rpc")]
209
        if let Some(session) = interp.rpc_object {
210
            if let Some(mgr) = &self.rpc_mgr {
211
                let (context, object) = mgr
212
                    .lookup_object(&session)
213
                    .context("no such session found")?;
214
                let target = ConnTarget::Rpc { context, object };
215
                return Ok((prefs, target));
216
            } else {
217
                return Err(anyhow!("no rpc manager found!?"));
218
            }
219
        }
220

            
221
        let client = self.tor_client.clone();
222
        #[cfg(feature = "rpc")]
223
        let client = ConnTarget::Client(Box::new(client));
224

            
225
        Ok((prefs, client))
226
    }
227
}
228

            
229
/// Given a just-received TCP connection `S` on a SOCKS port, handle the
230
/// SOCKS handshake and relay the connection over the Tor network.
231
///
232
/// Uses `isolation_info` to decide which circuits this connection
233
/// may use.  Requires that `isolation_info` is a pair listing the listener
234
/// id and the source address for the socks request.
235
#[instrument(skip_all, level = "trace")]
236
pub(super) async fn handle_socks_conn<R, S>(
237
    context: ProxyContext<R>,
238
    mut socks_stream: BufReader<S>,
239
    isolation_info: ListenerIsolation,
240
) -> Result<()>
241
where
242
    R: Runtime,
243
    S: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
244
{
245
    // Part 1: Perform the SOCKS handshake, to learn where we are
246
    // being asked to connect, and what we're being asked to do once
247
    // we connect there.
248
    //
249
    // The SOCKS handshake can require multiple round trips (SOCKS5
250
    // always does) so we need to run this part of the process in a
251
    // loop.
252
    let mut handshake = tor_socksproto::SocksProxyHandshake::new();
253

            
254
    let mut inbuf = tor_socksproto::Buffer::new();
255
    let request = loop {
256
        use tor_socksproto::NextStep as NS;
257

            
258
        // Try to perform the next step in the handshake.
259
        // (If there is an handshake error, don't reply with a Socks error, remote does not
260
        // seems to speak Socks.)
261
        let step = handshake.step(&mut inbuf)?;
262

            
263
        match step {
264
            NS::Recv(mut recv) => {
265
                let n = socks_stream
266
                    .read(recv.buf())
267
                    .await
268
                    .context("Error while reading SOCKS handshake")?;
269
                recv.note_received(n)?;
270
            }
271
            NS::Send(data) => write_all_and_flush(&mut socks_stream, &data).await?,
272
            NS::Finished(fin) => break fin.into_output_forbid_pipelining()?,
273
        }
274
    };
275

            
276
    // Make sure there is no buffered data!
277
    if !socks_stream.buffer().is_empty() {
278
        let error = tor_socksproto::Error::ForbiddenPipelining;
279
        return reply_error(&mut socks_stream, &request, error.kind()).await;
280
    }
281

            
282
    // Unpack the socks request and find out where we're connecting to.
283
    let addr = request.addr().to_string();
284
    let port = request.port();
285
    debug!(
286
        "Got a socks request: {} {}:{}",
287
        request.command(),
288
        sensitive(&addr),
289
        port
290
    );
291

            
292
    let (prefs, tor_client) = context.get_prefs_and_session(&request, &addr, isolation_info)?;
293

            
294
    match request.command() {
295
        SocksCmd::CONNECT => {
296
            // The SOCKS request wants us to connect to a given address.
297
            // So, launch a connection over Tor.
298
            let tor_addr = (addr.clone(), port).into_tor_addr()?;
299
            let tor_stream = tor_client.connect_with_prefs(&tor_addr, &prefs).await;
300
            let tor_stream = match tor_stream {
301
                Ok(s) => s,
302
                Err(e) => return reply_error(&mut socks_stream, &request, e.kind()).await,
303
            };
304
            // Okay, great! We have a connection over the Tor network.
305
            debug!("Got a stream for {}:{}", sensitive(&addr), port);
306

            
307
            // Send back a SOCKS response, telling the client that it
308
            // successfully connected.
309
            let reply = request
310
                .reply(tor_socksproto::SocksStatus::SUCCEEDED, None)
311
                .context("Encoding socks reply")?;
312
            write_all_and_flush(&mut socks_stream, &reply[..]).await?;
313

            
314
            let tor_stream = BufReader::with_capacity(super::APP_STREAM_BUF_LEN, tor_stream);
315

            
316
            // Finally, relay traffic between
317
            // the socks stream and the tor stream.
318
            futures_copy::copy_buf_bidirectional(
319
                socks_stream,
320
                tor_stream,
321
                futures_copy::eof::Close,
322
                futures_copy::eof::Close,
323
            )
324
            .await?;
325
        }
326
        SocksCmd::RESOLVE => {
327
            // We've been asked to perform a regular hostname lookup.
328
            // (This is a tor-specific SOCKS extension.)
329

            
330
            let addr = if let Ok(addr) = addr.parse() {
331
                // if this is a valid ip address, just parse it and reply.
332
                Ok(addr)
333
            } else {
334
                tor_client
335
                    .resolve_with_prefs(&addr, &prefs)
336
                    .await
337
                    .map_err(|e| e.kind())
338
                    .and_then(|addrs| addrs.first().copied().ok_or(ErrorKind::Other))
339
            };
340
            match addr {
341
                Ok(addr) => {
342
                    let reply = request
343
                        .reply(
344
                            tor_socksproto::SocksStatus::SUCCEEDED,
345
                            Some(&SocksAddr::Ip(addr)),
346
                        )
347
                        .context("Encoding socks reply")?;
348
                    write_all_and_close(&mut socks_stream, &reply[..]).await?;
349
                }
350
                Err(e) => return reply_error(&mut socks_stream, &request, e).await,
351
            }
352
        }
353
        SocksCmd::RESOLVE_PTR => {
354
            // We've been asked to perform a reverse hostname lookup.
355
            // (This is a tor-specific SOCKS extension.)
356
            let addr: IpAddr = match addr.parse() {
357
                Ok(ip) => ip,
358
                Err(e) => {
359
                    let reply = request
360
                        .reply(tor_socksproto::SocksStatus::ADDRTYPE_NOT_SUPPORTED, None)
361
                        .context("Encoding socks reply")?;
362
                    write_all_and_close(&mut socks_stream, &reply[..]).await?;
363
                    return Err(anyhow!(e));
364
                }
365
            };
366
            let hosts = match tor_client.resolve_ptr_with_prefs(addr, &prefs).await {
367
                Ok(hosts) => hosts,
368
                Err(e) => return reply_error(&mut socks_stream, &request, e.kind()).await,
369
            };
370
            if let Some(host) = hosts.into_iter().next() {
371
                // this conversion should never fail, legal DNS names len must be <= 253 but Socks
372
                // names can be up to 255 chars.
373
                let hostname = SocksAddr::Hostname(host.try_into()?);
374
                let reply = request
375
                    .reply(tor_socksproto::SocksStatus::SUCCEEDED, Some(&hostname))
376
                    .context("Encoding socks reply")?;
377
                write_all_and_close(&mut socks_stream, &reply[..]).await?;
378
            }
379
        }
380
        _ => {
381
            // We don't support this SOCKS command.
382
            warn!("Dropping request; {:?} is unsupported", request.command());
383
            let reply = request
384
                .reply(tor_socksproto::SocksStatus::COMMAND_NOT_SUPPORTED, None)
385
                .context("Encoding socks reply")?;
386
            write_all_and_close(&mut socks_stream, &reply[..]).await?;
387
        }
388
    };
389

            
390
    // TODO: we should close the TCP stream if either task fails. Do we?
391
    // See #211 and #190.
392

            
393
    Ok(())
394
}
395

            
396
/// Reply a Socks error based on an arti-client Error and close the stream.
397
/// Returns the error provided in parameter
398
async fn reply_error<W>(
399
    writer: &mut W,
400
    request: &SocksRequest,
401
    error: arti_client::ErrorKind,
402
) -> Result<()>
403
where
404
    W: AsyncWrite + Unpin,
405
{
406
    use {ErrorKind as EK, tor_socksproto::SocksStatus as S};
407

            
408
    // TODO: Currently we _always_ try to return extended SOCKS return values
409
    // for onion service failures from proposal 304 when they are appropriate.
410
    // But according to prop 304, this is something we should only do when it's
411
    // requested, for compatibility with SOCKS implementations that can't handle
412
    // unexpected REP codes.
413
    //
414
    // I suggest we make these extended error codes "always-on" for now, and
415
    // later add a feature to disable them if it's needed. -nickm
416

            
417
    // TODO: Perhaps we should map the extended SOCKS return values for onion
418
    // service failures unconditionally, even if we haven't compiled in onion
419
    // service client support.  We can make that change after the relevant
420
    // ErrorKinds are no longer `experimental-api` in `tor-error`.
421

            
422
    // We need to send an error. See what kind it is.
423
    //
424
    // TODO: Perhaps move this to tor-error, so it can be an exhaustive match.
425
    let status = match error {
426
        EK::RemoteNetworkFailed => S::TTL_EXPIRED,
427

            
428
        #[cfg(feature = "onion-service-client")]
429
        EK::OnionServiceNotFound => S::HS_DESC_NOT_FOUND,
430
        #[cfg(feature = "onion-service-client")]
431
        EK::OnionServiceAddressInvalid => S::HS_BAD_ADDRESS,
432
        #[cfg(feature = "onion-service-client")]
433
        EK::OnionServiceMissingClientAuth => S::HS_MISSING_CLIENT_AUTH,
434
        #[cfg(feature = "onion-service-client")]
435
        EK::OnionServiceWrongClientAuth => S::HS_WRONG_CLIENT_AUTH,
436

            
437
        // NOTE: This is not a perfect correspondence from these ErrorKinds to
438
        // the errors we're returning here. In the longer run, we'll want to
439
        // encourage other ways to indicate failure to clients.  Those ways might
440
        // include encouraging HTTP CONNECT, or the RPC system, both of which
441
        // would give us more robust ways to report different kinds of failure.
442
        #[cfg(feature = "onion-service-client")]
443
        EK::OnionServiceNotRunning
444
        | EK::OnionServiceConnectionFailed
445
        | EK::OnionServiceProtocolViolation => S::HS_INTRO_FAILED,
446

            
447
        _ => S::GENERAL_FAILURE,
448
    };
449
    let reply = request
450
        .reply(status, None)
451
        .context("Encoding socks reply")?;
452
    // if writing back the error fail, still return the original error
453
    let _ = write_all_and_close(writer, &reply[..]).await;
454

            
455
    Err(anyhow!(error))
456
}