//! Server operations for working with connect points. use std::{io, path::PathBuf, sync::Arc}; use crate::{ ConnectError, ResolvedConnectPoint, auth::{RpcAuth, RpcCookieSource, cookie::Cookie}, }; use fs_mistrust::Mistrust; use tor_general_addr::general; use tor_rtcompat::NetStreamProvider; /// A listener and associated authentication at which an RPC server can watch for connections. #[non_exhaustive] pub struct Listener { /// The listener on which connections will arrive. pub listener: tor_rtcompat::general::Listener, /// The authentication to require from incoming connections. pub auth: RpcAuth, /// An object we must hold for as long as we're listening on this socket. pub guard: ListenerGuard, } /// An object to control shutdown for a listener. We should drop it only when we're no longer /// listening on the socket. // // TODO It might be neat to combine this with the stream of requests from listener, // so that we can't accidentally drop one prematurely. pub struct ListenerGuard { /// A handle to a file that should be deleted when this is dropped. #[allow(unused)] rm_guard: Option, /// A handle to a lockfile on disk. // // (Note that this field is ordered after rm_guard: // rust guarantees that fields are dropped in order.) #[allow(unused)] lock_guard: Option, } /// Object that unlinks a file when it is dropped. struct UnlinkOnDrop(PathBuf); impl Drop for UnlinkOnDrop { fn drop(&mut self) { let _ignore = std::fs::remove_file(&self.0); } } impl ResolvedConnectPoint { /// Try to bind to a location as specified by this connect point. pub async fn bind(&self, runtime: &R, mistrust: &Mistrust) -> Result where R: NetStreamProvider, { use crate::connpt::ConnectPointEnum as CptE; match &self.0 { CptE::Connect(connect) => connect.bind(runtime, mistrust).await, CptE::Builtin(builtin) => builtin.bind(), } } } impl crate::connpt::Builtin { /// Try to bind to a "Builtin" connect point. fn bind(&self) -> Result { use crate::connpt::BuiltinVariant as BV; match self.builtin { BV::Abort => Err(ConnectError::ExplicitAbort), } } } impl crate::connpt::Connect { /// Try to bind to a "Connect" connect point. async fn bind(&self, runtime: &R, mistrust: &Mistrust) -> Result where R: NetStreamProvider, { // Create parent directory for socket if needed. if let Some(sock_parent_dir) = crate::socket_parent_path(self.socket.as_ref()) { mistrust.make_directory(sock_parent_dir)?; } let guard = if let Some(socket_path) = self.socket.as_ref().as_pathname() { // This socket has a representation in the filesystem. // We need an associated lock to make sure that we don't delete the socket // while it is in use. // // (We can't just rely on getting an EADDRINUSE when we bind the socket, // since AF_UNIX sockets give that error unconditionally if the file exists, // whether anybody has bound to it or not. // We can't just check whether the socket file exists, // since it might be a stale socket left over from a process that has crashed. // We can't lock the socket file itself, // since we need to delete it before we can bind to it.) let lock_path = { let mut p = socket_path.to_owned(); p.as_mut_os_string().push(".lock"); p }; let lock_guard = Some( fslock_guard::LockFileGuard::try_lock(lock_path)? .ok_or(ConnectError::AlreadyLocked)?, ); // Now that we have the lock, we know that nobody else is listening on this socket. // Now we just remove any stale socket file before we bind.) match std::fs::remove_file(socket_path) { Ok(()) => {} Err(e) if e.kind() == io::ErrorKind::NotFound => {} Err(other) => return Err(other.into()), } let rm_guard = Some(UnlinkOnDrop(socket_path.to_owned())); ListenerGuard { rm_guard, lock_guard, } } else { ListenerGuard { rm_guard: None, lock_guard: None, } }; let listener = runtime.listen(self.socket.as_ref()).await?; // We try to bind to the listener before we (maybe) create the cookie file, // so that if we encounter an `EADDRINUSE` we won't overwrite the old cookie file. let auth = match &self.auth { crate::connpt::Auth::None => RpcAuth::Inherent, crate::connpt::Auth::Cookie { path } => RpcAuth::Cookie { secret: RpcCookieSource::Loaded(Arc::new(Cookie::create( path.as_path(), &mut rand::rng(), mistrust, )?)), server_address: self.socket.as_str().to_owned(), }, crate::connpt::Auth::Unrecognized(_) => return Err(ConnectError::UnsupportedAuthType), }; Ok(Listener { listener, auth, guard, }) } }