1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![allow(clippy::cognitive_complexity)] // See arti#2556
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

            
52
// TODO #1645 (either remove this, or decide to have it everywhere)
53
#![cfg_attr(not(all(feature = "full")), allow(unused))]
54

            
55
pub mod auth;
56
#[cfg(feature = "rpc-client")]
57
pub mod client;
58
mod connpt;
59
pub mod load;
60
#[cfg(feature = "rpc-server")]
61
pub mod server;
62
#[cfg(test)]
63
mod testing;
64

            
65
use std::{io, sync::Arc};
66

            
67
pub use connpt::{ParsedConnectPoint, ResolveError, ResolvedConnectPoint};
68
use tor_general_addr::general;
69

            
70
/// An action that an RPC client should take when a connect point fails.
71
///
72
/// (This terminology is taken from the spec.)
73
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74
#[allow(clippy::exhaustive_enums)]
75
pub enum ClientErrorAction {
76
    /// The client must stop, and must not make any more connect attempts.
77
    Abort,
78
    /// The connect point has failed; the client can continue to the next connect point.
79
    Decline,
80
}
81
/// An error that has a [`ClientErrorAction`].
82
pub trait HasClientErrorAction {
83
    /// Return the action that an RPC client should take based on this error.
84
    fn client_action(&self) -> ClientErrorAction;
85
}
86
impl HasClientErrorAction for tor_config_path::CfgPathError {
87
    fn client_action(&self) -> ClientErrorAction {
88
        // Every variant of this means a configuration error
89
        // or an ill-formed TOML file.
90
        ClientErrorAction::Abort
91
    }
92
}
93
impl HasClientErrorAction for tor_config_path::addr::CfgAddrError {
94
    fn client_action(&self) -> ClientErrorAction {
95
        use ClientErrorAction as A;
96
        use tor_config_path::addr::CfgAddrError as CAE;
97
        match self {
98
            CAE::NoAfUnixSocketSupport(_) => A::Decline,
99
            CAE::Path(cfg_path_error) => cfg_path_error.client_action(),
100
            CAE::ConstructAfUnixAddress(_) => A::Abort,
101
            // No variants are currently captured in this pattern, but they _could_ be in the future.
102
            _ => A::Abort,
103
        }
104
    }
105
}
106
impl HasClientErrorAction for tor_general_addr::general::AddrParseError {
107
    fn client_action(&self) -> ClientErrorAction {
108
        use ClientErrorAction as A;
109
        use tor_general_addr::general::AddrParseError as E;
110
        match self {
111
            E::UnrecognizedSchema(_) => A::Decline,
112
            E::NoSchema => A::Decline,
113
            E::InvalidAfUnixAddress(_) => A::Abort,
114
            // We might want to turn this into an Abort in the future, but I think that we might
115
            // want to allow "auto" as a port format in CfgAddr.
116
            E::InvalidInetAddress(_) => A::Decline,
117
            // No variants are currently captured in this pattern, but they _could_ be in the future.
118
            _ => A::Abort,
119
        }
120
    }
121
}
122

            
123
/// Return the ClientErrorAction for an IO error encountered
124
/// while accessing the filesystem.
125
///
126
/// Note that this is not an implementation of `HasClientErrorAction`:
127
/// We want to decline on a different set of errors for network operation.
128
fn fs_error_action(err: &std::io::Error) -> ClientErrorAction {
129
    use ClientErrorAction as A;
130
    use std::io::ErrorKind as EK;
131
    match err.kind() {
132
        EK::NotFound => A::Decline,
133
        EK::PermissionDenied => A::Decline,
134
        EK::ReadOnlyFilesystem => A::Decline,
135
        _ => A::Abort,
136
    }
137
}
138
/// Return the ClientErrorAction for an IO error encountered
139
/// while opening a socket.
140
///
141
/// Note that this is not an implementation of `HasClientErrorAction`:
142
/// We want to decline on a different set of errors for fs operation.
143
fn net_error_action(err: &std::io::Error) -> ClientErrorAction {
144
    use ClientErrorAction as A;
145
    use std::io::ErrorKind as EK;
146
    match err.kind() {
147
        EK::ConnectionRefused => A::Decline,
148
        EK::ConnectionReset => A::Decline,
149
        EK::HostUnreachable => A::Decline,
150
        EK::NetworkDown => A::Decline,
151
        EK::NetworkUnreachable => A::Decline,
152
        _ => A::Abort,
153
    }
154
}
155
impl HasClientErrorAction for fs_mistrust::Error {
156
    fn client_action(&self) -> ClientErrorAction {
157
        use ClientErrorAction as A;
158
        use fs_mistrust::Error as E;
159
        match self {
160
            E::Multiple(errs) => {
161
                if errs.iter().any(|e| e.client_action() == A::Abort) {
162
                    A::Abort
163
                } else {
164
                    A::Decline
165
                }
166
            }
167
            E::Io { err, .. } => fs_error_action(err),
168
            E::CouldNotInspect(_, err) => fs_error_action(err),
169

            
170
            E::NotFound(_) => A::Decline,
171
            E::BadPermission(_, _, _) | E::BadOwner(_, _) => A::Decline,
172
            E::StepsExceeded | E::CurrentDirectory(_) => A::Abort,
173

            
174
            E::BadType(_) => A::Abort,
175

            
176
            // These should be impossible for clients given how we use fs_mistrust in this crate.
177
            E::CreatingDir(_)
178
            | E::Content(_)
179
            | E::NoSuchGroup(_)
180
            | E::NoSuchUser(_)
181
            | E::MissingField(_)
182
            | E::InvalidSubdirectory => A::Abort,
183
            E::PasswdGroupIoError(_) => A::Abort,
184
            _ => A::Abort,
185
        }
186
    }
187
}
188

            
189
/// A failure to connect or bind to a [`ResolvedConnectPoint`].
190
#[derive(Clone, Debug, thiserror::Error)]
191
#[non_exhaustive]
192
pub enum ConnectError {
193
    /// We encountered an IO error while actually opening our socket.
194
    #[error("IO error while connecting")]
195
    Io(#[source] Arc<io::Error>),
196
    /// The connect point told us to abort explicitly.
197
    #[error("Encountered an explicit \"abort\"")]
198
    ExplicitAbort,
199
    /// We couldn't load the cookie file for cookie authentication.
200
    #[error("Unable to load cookie file")]
201
    LoadCookie(#[from] auth::cookie::CookieAccessError),
202
    /// We were told to connect to a socket type that we don't support.
203
    #[error("Unsupported socket type")]
204
    UnsupportedSocketType,
205
    /// We were told to connect using an auth type that we don't support.
206
    #[error("Unsupported authentication type")]
207
    UnsupportedAuthType,
208
    /// Unable to access the location of an AF\_UNIX socket.
209
    #[error("Unix domain socket path access")]
210
    AfUnixSocketPathAccess(#[from] fs_mistrust::Error),
211
    /// Unable to access the location of `socket_address_file`.
212
    #[error("Problem accessing socket address file")]
213
    SocketAddressFileAccess(#[source] fs_mistrust::Error),
214
    /// We couldn't parse the JSON contents of a socket address file.
215
    #[error("Invalid JSON contents in socket address file")]
216
    SocketAddressFileJson(#[source] Arc<serde_json::Error>),
217
    /// We couldn't parse the address in a socket address file.
218
    #[error("Invalid address in socket address file")]
219
    SocketAddressFileContent(#[source] general::AddrParseError),
220
    /// We found an address in the socket address file that didn't match the connect point.
221
    #[error("Socket address file contents didn't match connect point")]
222
    SocketAddressFileMismatch,
223
    /// Another process was holding a lock for this connect point,
224
    /// so we couldn't bind to it.
225
    #[error("Could not acquire lock: Another process is listening on this connect point")]
226
    AlreadyLocked,
227
    /// We encountered an internal logic error.
228
    //
229
    // (We're not using tor_error::Bug here because we want this code to work properly in rpc-client-core.)
230
    #[error("Internal error: {0}")]
231
    Internal(String),
232
}
233

            
234
impl From<io::Error> for ConnectError {
235
    fn from(err: io::Error) -> Self {
236
        ConnectError::Io(Arc::new(err))
237
    }
238
}
239
impl crate::HasClientErrorAction for ConnectError {
240
    fn client_action(&self) -> crate::ClientErrorAction {
241
        use crate::ClientErrorAction as A;
242
        use ConnectError as E;
243
        match self {
244
            E::Io(err) => crate::net_error_action(err),
245
            E::ExplicitAbort => A::Abort,
246
            E::LoadCookie(err) => err.client_action(),
247
            E::UnsupportedSocketType => A::Decline,
248
            E::UnsupportedAuthType => A::Decline,
249
            E::AfUnixSocketPathAccess(err) => err.client_action(),
250
            E::SocketAddressFileAccess(err) => err.client_action(),
251
            E::SocketAddressFileJson(_) => A::Decline,
252
            E::SocketAddressFileContent(_) => A::Decline,
253
            E::SocketAddressFileMismatch => A::Decline,
254
            E::AlreadyLocked => A::Abort, // (This one can't actually occur for clients.)
255
            E::Internal(_) => A::Abort,
256
        }
257
    }
258
}
259
#[cfg(any(feature = "rpc-client", feature = "rpc-server"))]
260
/// Given a `general::SocketAddr`, try to return the path of its parent directory (if any).
261
fn socket_parent_path(addr: &tor_general_addr::general::SocketAddr) -> Option<&std::path::Path> {
262
    addr.as_pathname().and_then(|p| p.parent())
263
}
264

            
265
/// Default connect point for a user-owned Arti instance.
266
pub const USER_DEFAULT_CONNECT_POINT: &str = {
267
    cfg_if::cfg_if! {
268
        if #[cfg(unix)] {
269
r#"
270
[connect]
271
socket = "unix:${ARTI_LOCAL_DATA}/rpc/arti_rpc_socket"
272
auth = "none"
273
"#
274
        } else {
275
r#"
276
[connect]
277
socket = "inet:127.0.0.1:9180"
278
auth = { cookie = { path = "${ARTI_LOCAL_DATA}/rpc/arti_rpc_cookie" } }
279
"#
280
        }
281
    }
282
};
283

            
284
/// Default connect point for a system-wide Arti instance.
285
///
286
/// This is `None` if, on this platform, there is no such default connect point.
287
pub const SYSTEM_DEFAULT_CONNECT_POINT: Option<&str> = {
288
    cfg_if::cfg_if! {
289
        if #[cfg(unix)] {
290
            Some(
291
r#"
292
[connect]
293
socket = "unix:/var/run/arti-rpc/arti_rpc_socket"
294
auth = "none"
295
"#
296
            )
297
        } else {
298
            None
299
        }
300
    }
301
};
302

            
303
/// An enum to reflect whether an authenticated connection to a connect point is allowed to acquire
304
/// superuser (admin) capabilities.
305
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
306
#[non_exhaustive]
307
pub enum SuperuserPermission {
308
    /// The connection may acquire superuser capabilities.
309
    Allowed,
310
    /// The connection may not acquire superuser capabilities.
311
    NotAllowed,
312
}
313

            
314
#[cfg(test)]
315
mod test {
316
    // @@ begin test lint list maintained by maint/add_warning @@
317
    #![allow(clippy::bool_assert_comparison)]
318
    #![allow(clippy::clone_on_copy)]
319
    #![allow(clippy::dbg_macro)]
320
    #![allow(clippy::mixed_attributes_style)]
321
    #![allow(clippy::print_stderr)]
322
    #![allow(clippy::print_stdout)]
323
    #![allow(clippy::single_char_pattern)]
324
    #![allow(clippy::unwrap_used)]
325
    #![allow(clippy::unchecked_time_subtraction)]
326
    #![allow(clippy::useless_vec)]
327
    #![allow(clippy::needless_pass_by_value)]
328
    #![allow(clippy::string_slice)] // See arti#2571
329
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
330

            
331
    use super::*;
332

            
333
    #[test]
334
    fn parse_defaults() {
335
        let _parsed: ParsedConnectPoint = USER_DEFAULT_CONNECT_POINT.parse().unwrap();
336
        if let Some(s) = SYSTEM_DEFAULT_CONNECT_POINT {
337
            let _parsed: ParsedConnectPoint = s.parse().unwrap();
338
        }
339
    }
340
}