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
#![warn(clippy::cognitive_complexity)]
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
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_time_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![deny(clippy::mod_module_files)]
38
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39
#![allow(clippy::uninlined_format_args)]
40
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43
#![allow(clippy::needless_lifetimes)] // See arti#1765
44
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45
#![allow(clippy::collapsible_if)] // See arti#2342
46
#![deny(clippy::unused_async)]
47
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48

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

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

            
62
use std::{io, sync::Arc};
63

            
64
pub use connpt::{ParsedConnectPoint, ResolveError, ResolvedConnectPoint};
65
use tor_general_addr::general;
66

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

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

            
165
            E::NotFound(_) => A::Decline,
166
            E::BadPermission(_, _, _) | E::BadOwner(_, _) => A::Decline,
167
            E::StepsExceeded | E::CurrentDirectory(_) => A::Abort,
168

            
169
            E::BadType(_) => A::Abort,
170

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

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

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

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

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

            
298
#[cfg(test)]
299
mod test {
300
    // @@ begin test lint list maintained by maint/add_warning @@
301
    #![allow(clippy::bool_assert_comparison)]
302
    #![allow(clippy::clone_on_copy)]
303
    #![allow(clippy::dbg_macro)]
304
    #![allow(clippy::mixed_attributes_style)]
305
    #![allow(clippy::print_stderr)]
306
    #![allow(clippy::print_stdout)]
307
    #![allow(clippy::single_char_pattern)]
308
    #![allow(clippy::unwrap_used)]
309
    #![allow(clippy::unchecked_time_subtraction)]
310
    #![allow(clippy::useless_vec)]
311
    #![allow(clippy::needless_pass_by_value)]
312
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
313

            
314
    use super::*;
315

            
316
    #[test]
317
    fn parse_defaults() {
318
        let _parsed: ParsedConnectPoint = USER_DEFAULT_CONNECT_POINT.parse().unwrap();
319
        if let Some(s) = SYSTEM_DEFAULT_CONNECT_POINT {
320
            let _parsed: ParsedConnectPoint = s.parse().unwrap();
321
        }
322
    }
323
}