1
//! Server operations for working with connect points.
2

            
3
use std::{io, path::PathBuf, sync::Arc};
4

            
5
use crate::{
6
    auth::{cookie::Cookie, RpcAuth, RpcCookieSource},
7
    ConnectError, ResolvedConnectPoint,
8
};
9
use fs_mistrust::Mistrust;
10
use tor_general_addr::general;
11
use tor_rtcompat::NetStreamProvider;
12

            
13
/// A listener and associated authentication at which an RPC server can watch for connections.
14
#[non_exhaustive]
15
pub 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.
29
pub 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.
42
struct UnlinkOnDrop(PathBuf);
43
impl Drop for UnlinkOnDrop {
44
    fn drop(&mut self) {
45
        let _ignore = std::fs::remove_file(&self.0);
46
    }
47
}
48

            
49
impl 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

            
63
impl 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

            
73
impl 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
}