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

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

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

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

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

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

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