Skip to main content

tor_rpc_connect/
server.rs

1//! Server operations for working with connect points.
2
3use std::{io, path::PathBuf, sync::Arc};
4
5use crate::{
6    ConnectError, ResolvedConnectPoint,
7    auth::{RpcAuth, RpcCookieSource, cookie::Cookie},
8    connpt::{AddressFile, ConnectAddress},
9};
10use fs_mistrust::Mistrust;
11use tor_general_addr::general;
12use tor_rtcompat::NetStreamProvider;
13
14/// A listener and associated authentication at which an RPC server can watch for connections.
15#[non_exhaustive]
16pub struct Listener {
17    /// The listener on which connections will arrive.
18    pub listener: tor_rtcompat::general::Listener,
19    /// The authentication to require from incoming connections.
20    pub auth: RpcAuth,
21    /// An object we must hold for as long as we're listening on this socket.
22    pub guard: ListenerGuard,
23}
24
25/// An object to control shutdown for a listener.  We should drop it only when we're no longer
26/// listening on the socket.
27//
28// TODO It might be neat to combine this with the stream of requests from listener,
29// so that we can't accidentally drop one prematurely.
30pub struct ListenerGuard {
31    /// A handle to a file that should be deleted when this is dropped.
32    #[allow(unused)]
33    rm_guard: Option<UnlinkOnDrop>,
34    /// A handle to a lockfile on disk.
35    //
36    // (Note that this field is ordered after rm_guard:
37    // rust guarantees that fields are dropped in order.)
38    #[allow(unused)]
39    lock_guard: Option<fslock_guard::LockFileGuard>,
40}
41
42/// Object that unlinks a file when it is dropped.
43struct UnlinkOnDrop(PathBuf);
44impl Drop for UnlinkOnDrop {
45    fn drop(&mut self) {
46        let _ignore = std::fs::remove_file(&self.0);
47    }
48}
49
50impl ResolvedConnectPoint {
51    /// Try to bind to a location as specified by this connect point.
52    pub async fn bind<R>(&self, runtime: &R, mistrust: &Mistrust) -> Result<Listener, ConnectError>
53    where
54        R: NetStreamProvider<general::SocketAddr, Listener = tor_rtcompat::general::Listener>,
55    {
56        use crate::connpt::ConnectPointEnum as CptE;
57        match &self.0 {
58            CptE::Connect(connect) => connect.bind(runtime, mistrust).await,
59            CptE::Builtin(builtin) => builtin.bind(),
60        }
61    }
62}
63
64impl crate::connpt::Builtin {
65    /// Try to bind to a "Builtin" connect point.
66    fn bind(&self) -> Result<Listener, ConnectError> {
67        use crate::connpt::BuiltinVariant as BV;
68        match self.builtin {
69            BV::Abort => Err(ConnectError::ExplicitAbort),
70        }
71    }
72}
73
74impl crate::connpt::Connect<crate::connpt::Resolved> {
75    /// Try to bind to a "Connect" connect point.
76    async fn bind<R>(&self, runtime: &R, mistrust: &Mistrust) -> Result<Listener, ConnectError>
77    where
78        R: NetStreamProvider<general::SocketAddr, Listener = tor_rtcompat::general::Listener>,
79    {
80        // Create parent directory for socket if needed.
81        if let ConnectAddress::Socket(bind_to_socket) = &self.socket {
82            if let Some(sock_parent_dir) = crate::socket_parent_path(bind_to_socket.as_ref()) {
83                mistrust.make_directory(sock_parent_dir)?;
84            }
85        }
86
87        let af_unix_pathname = if let ConnectAddress::Socket(bind_to_socket) = &self.socket {
88            bind_to_socket.as_ref().as_pathname()
89        } else {
90            None
91        };
92
93        let guard = if let Some(socket_path) = af_unix_pathname {
94            // This socket has a representation in the filesystem.
95            // We need an associated lock to make sure that we don't delete the socket
96            // while it is in use.
97            //
98            // (We can't just rely on getting an EADDRINUSE when we bind the socket,
99            // since AF_UNIX sockets give that error unconditionally if the file exists,
100            // whether anybody has bound to it or not.
101            // We can't just check whether the socket file exists,
102            // since it might be a stale socket left over from a process that has crashed.
103            // We can't lock the socket file itself,
104            // since we need to delete it before we can bind to it.)
105            let lock_path = {
106                let mut p = socket_path.to_owned();
107                p.as_mut_os_string().push(".lock");
108                p
109            };
110            let lock_guard = Some(
111                fslock_guard::LockFileGuard::try_lock(lock_path)?
112                    .ok_or(ConnectError::AlreadyLocked)?,
113            );
114            // Now that we have the lock, we know that nobody else is listening on this socket.
115            // Now we just remove any stale socket file before we bind.)
116            match std::fs::remove_file(socket_path) {
117                Ok(()) => {}
118                Err(e) if e.kind() == io::ErrorKind::NotFound => {}
119                Err(other) => return Err(other.into()),
120            }
121
122            let rm_guard = Some(UnlinkOnDrop(socket_path.to_owned()));
123            ListenerGuard {
124                rm_guard,
125                lock_guard,
126            }
127        } else {
128            ListenerGuard {
129                rm_guard: None,
130                lock_guard: None,
131            }
132        };
133
134        let (listener, chosen_address) = self.socket.bind(runtime).await?;
135
136        if let Some(addr_file) = &self.socket_address_file {
137            let file_contents = serde_json::to_string(&AddressFile {
138                address: chosen_address.clone(),
139            })
140            .expect("Unable to serialize address!");
141
142            mistrust
143                .verifier()
144                .permit_readable()
145                .file_access()
146                .write_and_replace(addr_file, file_contents)
147                .map_err(ConnectError::SocketAddressFileAccess)?;
148        };
149
150        // We try to bind to the listener before we (maybe) create the cookie file,
151        // so that if we encounter an `EADDRINUSE` we won't overwrite the old cookie file.
152        let auth = match &self.auth {
153            crate::connpt::Auth::None => RpcAuth::Inherent,
154            crate::connpt::Auth::Cookie { path } => RpcAuth::Cookie {
155                secret: RpcCookieSource::Loaded(Arc::new(Cookie::create(
156                    path.as_path(),
157                    &mut rand::rng(),
158                    mistrust,
159                )?)),
160                server_address: chosen_address,
161            },
162            crate::connpt::Auth::Unrecognized(_) => return Err(ConnectError::UnsupportedAuthType),
163        };
164
165        Ok(Listener {
166            listener,
167            auth,
168            guard,
169        })
170    }
171}