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