Skip to main content

arti/proxy/
socks.rs

1//! SOCKS-specific proxy support.
2
3use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, BufReader};
4use safelog::sensitive;
5use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6use tracing::{debug, instrument, warn};
7
8#[allow(unused)]
9use arti_client::HasKind;
10use arti_client::{ErrorKind, IntoTorAddr as _, StreamPrefs};
11#[cfg(feature = "rpc")]
12use tor_rpcbase::{self as rpc};
13use tor_rtcompat::Runtime;
14use tor_socksproto::{Handshake as _, SocksAddr, SocksAuth, SocksCmd, SocksRequest};
15
16use anyhow::{Context, Result, anyhow};
17
18use super::{
19    ListenerIsolation, ProvidedIsolation, ProxyContext, StreamIsolationKey, write_all_and_close,
20    write_all_and_flush,
21};
22cfg_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.
39pub(super) const WRONG_PROTOCOL_PAYLOAD: &[u8] = br#"HTTP/1.0 501 Not running as an HTTP Proxy
40Content-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>
50It appears you have configured your web browser to use this Tor port as
51an HTTP proxy.
52</p>
53<p>
54This is not correct: This port is configured as a SOCKS proxy, not
55an HTTP proxy. If you need an HTTP proxy tunnel,
56build Arti with the <code>http-connect</code> feature enabled.
57</p>
58<p>
59See <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))]
67fn 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.
86struct 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.)
102fn 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    tracing::debug!("socks auth {:?} -> isolation {:?}", sensitive(&auth), sensitive(&isolation));
183
184    Ok(AuthInterpretation {
185        #[cfg(feature = "rpc")]
186        rpc_object: None,
187        isolation,
188    })
189}
190
191impl<R: Runtime> super::ProxyContext<R> {
192    /// Interpret a SOCKS request and our input information to determine which
193    /// TorClient / ClientConnectionTarget object and StreamPrefs we should use.
194    ///
195    /// TODO RPC: The return type here is a bit ugly.
196    fn get_prefs_and_session(
197        &self,
198        request: &SocksRequest,
199        target_addr: &str,
200        conn_isolation: ListenerIsolation,
201    ) -> Result<(StreamPrefs, ConnTarget<R>)> {
202        // Determine whether we want to ask for IPv4/IPv6 addresses.
203        let mut prefs = stream_preference(request, target_addr);
204
205        // Interpret socks authentication to see whether we want to connect to an RPC connector.
206        let interp = interpret_socks_auth(request.auth())?;
207        prefs.set_isolation(StreamIsolationKey(conn_isolation, interp.isolation));
208
209        #[cfg(feature = "rpc")]
210        if let Some(session) = interp.rpc_object {
211            if let Some(mgr) = &self.rpc_mgr {
212                let (context, object) = mgr
213                    .lookup_object(&session)
214                    .context("no such session found")?;
215                let target = ConnTarget::Rpc { context, object };
216                return Ok((prefs, target));
217            } else {
218                return Err(anyhow!("no rpc manager found!?"));
219            }
220        }
221
222        let client = self.tor_client.clone();
223        #[cfg(feature = "rpc")]
224        let client = ConnTarget::Client(Box::new(client));
225
226        Ok((prefs, client))
227    }
228}
229
230/// Given a just-received TCP connection `S` on a SOCKS port, handle the
231/// SOCKS handshake and relay the connection over the Tor network.
232///
233/// Uses `isolation_info` to decide which circuits this connection
234/// may use.  Requires that `isolation_info` is a pair listing the listener
235/// id and the source address for the socks request.
236#[instrument(skip_all, level = "trace")]
237pub(super) async fn handle_socks_conn<R, S>(
238    context: ProxyContext<R>,
239    mut socks_stream: BufReader<S>,
240    isolation_info: ListenerIsolation,
241) -> Result<()>
242where
243    R: Runtime,
244    S: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,
245{
246    // Part 1: Perform the SOCKS handshake, to learn where we are
247    // being asked to connect, and what we're being asked to do once
248    // we connect there.
249    //
250    // The SOCKS handshake can require multiple round trips (SOCKS5
251    // always does) so we need to run this part of the process in a
252    // loop.
253    let mut handshake = tor_socksproto::SocksProxyHandshake::new();
254
255    let mut inbuf = tor_socksproto::Buffer::new();
256    let request = loop {
257        use tor_socksproto::NextStep as NS;
258
259        // Try to perform the next step in the handshake.
260        // (If there is an handshake error, don't reply with a Socks error, remote does not
261        // seems to speak Socks.)
262        let step = handshake.step(&mut inbuf)?;
263
264        match step {
265            NS::Recv(mut recv) => {
266                let n = socks_stream
267                    .read(recv.buf())
268                    .await
269                    .context("Error while reading SOCKS handshake")?;
270                recv.note_received(n)?;
271            }
272            NS::Send(data) => write_all_and_flush(&mut socks_stream, &data).await?,
273            NS::Finished(fin) => break fin.into_output_forbid_pipelining()?,
274        }
275    };
276
277    // Make sure there is no buffered data!
278    if !socks_stream.buffer().is_empty() {
279        let error = tor_socksproto::Error::ForbiddenPipelining;
280        return reply_error(&mut socks_stream, &request, error.kind()).await;
281    }
282
283    // Unpack the socks request and find out where we're connecting to.
284    let addr = request.addr().to_string();
285    let port = request.port();
286    debug!(
287        "Got a socks request: {} {}:{}",
288        request.command(),
289        sensitive(&addr),
290        port
291    );
292
293    let (prefs, tor_client) = context.get_prefs_and_session(&request, &addr, isolation_info)?;
294
295    match request.command() {
296        SocksCmd::CONNECT => {
297            // The SOCKS request wants us to connect to a given address.
298            // So, launch a connection over Tor.
299            let tor_addr = (addr.clone(), port).into_tor_addr()?;
300            let tor_stream = tor_client.connect_with_prefs(&tor_addr, &prefs).await;
301            let tor_stream = match tor_stream {
302                Ok(s) => s,
303                Err(e) => return reply_error(&mut socks_stream, &request, e.kind()).await,
304            };
305            // Okay, great! We have a connection over the Tor network.
306            debug!("Got a stream for {}:{}", sensitive(&addr), port);
307
308            // Send back a SOCKS response, telling the client that it
309            // successfully connected.
310            let reply = request
311                .reply(tor_socksproto::SocksStatus::SUCCEEDED, None)
312                .context("Encoding socks reply")?;
313            write_all_and_flush(&mut socks_stream, &reply[..]).await?;
314
315            let tor_stream = BufReader::with_capacity(super::APP_STREAM_BUF_LEN, tor_stream);
316
317            // Finally, relay traffic between
318            // the socks stream and the tor stream.
319            futures_copy::copy_buf_bidirectional(
320                socks_stream,
321                tor_stream,
322                futures_copy::eof::Close,
323                futures_copy::eof::Close,
324            )
325            .await?;
326        }
327        SocksCmd::RESOLVE => {
328            // We've been asked to perform a regular hostname lookup.
329            // (This is a tor-specific SOCKS extension.)
330
331            let addr = if let Ok(addr) = addr.parse() {
332                // if this is a valid ip address, just parse it and reply.
333                Ok(addr)
334            } else {
335                tor_client
336                    .resolve_with_prefs(&addr, &prefs)
337                    .await
338                    .map_err(|e| e.kind())
339                    .and_then(|addrs| addrs.first().copied().ok_or(ErrorKind::Other))
340            };
341            match addr {
342                Ok(addr) => {
343                    let reply = request
344                        .reply(
345                            tor_socksproto::SocksStatus::SUCCEEDED,
346                            Some(&SocksAddr::Ip(addr)),
347                        )
348                        .context("Encoding socks reply")?;
349                    write_all_and_close(&mut socks_stream, &reply[..]).await?;
350                }
351                Err(e) => return reply_error(&mut socks_stream, &request, e).await,
352            }
353        }
354        SocksCmd::RESOLVE_PTR => {
355            // We've been asked to perform a reverse hostname lookup.
356            // (This is a tor-specific SOCKS extension.)
357            let addr: IpAddr = match addr.parse() {
358                Ok(ip) => ip,
359                Err(e) => {
360                    let reply = request
361                        .reply(tor_socksproto::SocksStatus::ADDRTYPE_NOT_SUPPORTED, None)
362                        .context("Encoding socks reply")?;
363                    write_all_and_close(&mut socks_stream, &reply[..]).await?;
364                    return Err(anyhow!(e));
365                }
366            };
367            let hosts = match tor_client.resolve_ptr_with_prefs(addr, &prefs).await {
368                Ok(hosts) => hosts,
369                Err(e) => return reply_error(&mut socks_stream, &request, e.kind()).await,
370            };
371            if let Some(host) = hosts.into_iter().next() {
372                // this conversion should never fail, legal DNS names len must be <= 253 but Socks
373                // names can be up to 255 chars.
374                let hostname = SocksAddr::Hostname(host.try_into()?);
375                let reply = request
376                    .reply(tor_socksproto::SocksStatus::SUCCEEDED, Some(&hostname))
377                    .context("Encoding socks reply")?;
378                write_all_and_close(&mut socks_stream, &reply[..]).await?;
379            }
380        }
381        _ => {
382            // We don't support this SOCKS command.
383            warn!("Dropping request; {:?} is unsupported", request.command());
384            let reply = request
385                .reply(tor_socksproto::SocksStatus::COMMAND_NOT_SUPPORTED, None)
386                .context("Encoding socks reply")?;
387            write_all_and_close(&mut socks_stream, &reply[..]).await?;
388        }
389    };
390
391    // TODO: we should close the TCP stream if either task fails. Do we?
392    // See #211 and #190.
393
394    Ok(())
395}
396
397/// Reply a Socks error based on an arti-client Error and close the stream.
398/// Returns the error provided in parameter
399async fn reply_error<W>(
400    writer: &mut W,
401    request: &SocksRequest,
402    error: arti_client::ErrorKind,
403) -> Result<()>
404where
405    W: AsyncWrite + Unpin,
406{
407    use {ErrorKind as EK, tor_socksproto::SocksStatus as S};
408
409    // TODO: Currently we _always_ try to return extended SOCKS return values
410    // for onion service failures from proposal 304 when they are appropriate.
411    // But according to prop 304, this is something we should only do when it's
412    // requested, for compatibility with SOCKS implementations that can't handle
413    // unexpected REP codes.
414    //
415    // I suggest we make these extended error codes "always-on" for now, and
416    // later add a feature to disable them if it's needed. -nickm
417
418    // TODO: Perhaps we should map the extended SOCKS return values for onion
419    // service failures unconditionally, even if we haven't compiled in onion
420    // service client support.  We can make that change after the relevant
421    // ErrorKinds are no longer `experimental-api` in `tor-error`.
422
423    // We need to send an error. See what kind it is.
424    //
425    // TODO: Perhaps move this to tor-error, so it can be an exhaustive match.
426    let status = match error {
427        EK::RemoteNetworkFailed => S::TTL_EXPIRED,
428
429        #[cfg(feature = "onion-service-client")]
430        EK::OnionServiceNotFound => S::HS_DESC_NOT_FOUND,
431        #[cfg(feature = "onion-service-client")]
432        EK::OnionServiceAddressInvalid => S::HS_BAD_ADDRESS,
433        #[cfg(feature = "onion-service-client")]
434        EK::OnionServiceMissingClientAuth => S::HS_MISSING_CLIENT_AUTH,
435        #[cfg(feature = "onion-service-client")]
436        EK::OnionServiceWrongClientAuth => S::HS_WRONG_CLIENT_AUTH,
437
438        // NOTE: This is not a perfect correspondence from these ErrorKinds to
439        // the errors we're returning here. In the longer run, we'll want to
440        // encourage other ways to indicate failure to clients.  Those ways might
441        // include encouraging HTTP CONNECT, or the RPC system, both of which
442        // would give us more robust ways to report different kinds of failure.
443        #[cfg(feature = "onion-service-client")]
444        EK::OnionServiceNotRunning
445        | EK::OnionServiceConnectionFailed
446        | EK::OnionServiceProtocolViolation => S::HS_INTRO_FAILED,
447
448        _ => S::GENERAL_FAILURE,
449    };
450    let reply = request
451        .reply(status, None)
452        .context("Encoding socks reply")?;
453    // if writing back the error fail, still return the original error
454    let _ = write_all_and_close(writer, &reply[..]).await;
455
456    Err(anyhow!(error))
457}