1
#![cfg_attr(docsrs, feature(doc_auto_cfg, 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_duration_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
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46

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

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

            
60
use std::{io, sync::Arc};
61

            
62
pub use connpt::{ParsedConnectPoint, ResolveError, ResolvedConnectPoint};
63

            
64
/// An action that an RPC client should take when a connect point fails.
65
///
66
/// (This terminology is taken from the spec.)
67
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68
#[allow(clippy::exhaustive_enums)]
69
pub enum ClientErrorAction {
70
    /// The client must stop, and must not make any more connect attempts.
71
    Abort,
72
    /// The connect point has failed; the client can continue to the next connect point.
73
    Decline,
74
}
75
/// An error that has a [`ClientErrorAction`].
76
pub trait HasClientErrorAction {
77
    /// Return the action that an RPC client should take based on this error.
78
    fn client_action(&self) -> ClientErrorAction;
79
}
80
impl HasClientErrorAction for tor_config_path::CfgPathError {
81
    fn client_action(&self) -> ClientErrorAction {
82
        // Every variant of this means a configuration error
83
        // or an ill-formed TOML file.
84
        ClientErrorAction::Abort
85
    }
86
}
87
impl HasClientErrorAction for tor_config_path::addr::CfgAddrError {
88
    fn client_action(&self) -> ClientErrorAction {
89
        use tor_config_path::addr::CfgAddrError as CAE;
90
        use ClientErrorAction as A;
91
        match self {
92
            CAE::NoAfUnixSocketSupport(_) => A::Decline,
93
            CAE::Path(cfg_path_error) => cfg_path_error.client_action(),
94
            CAE::ConstructAfUnixAddress(_) => A::Abort,
95
            // No variants are currently captured in this pattern, but they _could_ be in the future.
96
            _ => A::Abort,
97
        }
98
    }
99
}
100
/// Return the ClientErrorAction for an IO error encountered
101
/// while accessing the filesystem.
102
///
103
/// Note that this is not an implementation of `HasClientErrorAction`:
104
/// We want to decline on a different set of errors for network operation.
105
fn fs_error_action(err: &std::io::Error) -> ClientErrorAction {
106
    use std::io::ErrorKind as EK;
107
    use ClientErrorAction as A;
108
    match err.kind() {
109
        EK::NotFound => A::Decline,
110
        EK::PermissionDenied => A::Decline,
111
        _ => A::Abort,
112
    }
113
}
114
/// Return the ClientErrorAction for an IO error encountered
115
/// while opening a socket.
116
///
117
/// Note that this is not an implementation of `HasClientErrorAction`:
118
/// We want to decline on a different set of errors for fs operation.
119
fn net_error_action(err: &std::io::Error) -> ClientErrorAction {
120
    use std::io::ErrorKind as EK;
121
    use ClientErrorAction as A;
122
    match err.kind() {
123
        EK::ConnectionRefused => A::Decline,
124
        EK::ConnectionReset => A::Decline,
125
        // TODO MSRV 1.83; revisit once some of `io_error_more` is stabilized.
126
        // see https://github.com/rust-lang/rust/pull/128316
127
        _ => A::Abort,
128
    }
129
}
130
impl HasClientErrorAction for fs_mistrust::Error {
131
    fn client_action(&self) -> ClientErrorAction {
132
        use fs_mistrust::Error as E;
133
        use ClientErrorAction as A;
134
        match self {
135
            E::Multiple(errs) => {
136
                if errs.iter().any(|e| e.client_action() == A::Abort) {
137
                    A::Abort
138
                } else {
139
                    A::Decline
140
                }
141
            }
142
            E::Io { err, .. } => fs_error_action(err),
143
            E::CouldNotInspect(_, err) => fs_error_action(err),
144

            
145
            E::NotFound(_) => A::Decline,
146
            E::BadPermission(_, _, _) | E::BadOwner(_, _) => A::Decline,
147
            E::StepsExceeded | E::CurrentDirectory(_) => A::Abort,
148

            
149
            E::BadType(_) => A::Abort,
150

            
151
            // These should be impossible for clients given how we use fs_mistrust in this crate.
152
            E::CreatingDir(_)
153
            | E::Content(_)
154
            | E::NoSuchGroup(_)
155
            | E::NoSuchUser(_)
156
            | E::MissingField(_)
157
            | E::InvalidSubdirectory => A::Abort,
158
            E::PasswdGroupIoError(_) => A::Abort,
159
            _ => A::Abort,
160
        }
161
    }
162
}
163

            
164
/// A failure to connect or bind to a [`ResolvedConnectPoint`].
165
#[derive(Clone, Debug, thiserror::Error)]
166
#[non_exhaustive]
167
pub enum ConnectError {
168
    /// We encountered an IO error while actually opening our socket.
169
    #[error("IO error while connecting")]
170
    Io(#[source] Arc<io::Error>),
171
    /// The connect point told us to abort explicitly.
172
    #[error("Encountered an explicit \"abort\"")]
173
    ExplicitAbort,
174
    /// We couldn't load the cookie file for cookie authentication.
175
    #[error("Unable to load cookie file")]
176
    LoadCookie(#[from] auth::cookie::CookieAccessError),
177
    /// We were told to connect to a socket type that we don't support.
178
    #[error("Unsupported socket type")]
179
    UnsupportedSocketType,
180
    /// We were told to connect using an auth type that we don't support.
181
    #[error("Unsupported authentication type")]
182
    UnsupportedAuthType,
183
    /// Unable to access the location of an AF\_UNIX socket.
184
    #[error("Unix domain socket path access")]
185
    AfUnixSocketPathAccess(#[from] fs_mistrust::Error),
186
    /// Another process was holding a lock for this connect point,
187
    /// so we couldn't bind to it.
188
    #[error("Could not acquire lock: Another process is listening on this connect point")]
189
    AlreadyLocked,
190
}
191

            
192
impl From<io::Error> for ConnectError {
193
    fn from(err: io::Error) -> Self {
194
        ConnectError::Io(Arc::new(err))
195
    }
196
}
197
impl crate::HasClientErrorAction for ConnectError {
198
    fn client_action(&self) -> crate::ClientErrorAction {
199
        use crate::ClientErrorAction as A;
200
        use ConnectError as E;
201
        match self {
202
            E::Io(err) => crate::net_error_action(err),
203
            E::ExplicitAbort => A::Abort,
204
            E::LoadCookie(err) => err.client_action(),
205
            E::UnsupportedSocketType => A::Decline,
206
            E::UnsupportedAuthType => A::Decline,
207
            E::AfUnixSocketPathAccess(err) => err.client_action(),
208
            E::AlreadyLocked => A::Abort, // (This one can't actually occur for clients.)
209
        }
210
    }
211
}
212
#[cfg(any(feature = "rpc-client", feature = "rpc-server"))]
213
/// Given a `general::SocketAddr`, try to return the path of its parent directory (if any).
214
fn socket_parent_path(addr: &tor_general_addr::general::SocketAddr) -> Option<&std::path::Path> {
215
    addr.as_pathname().and_then(|p| p.parent())
216
}
217

            
218
/// Default connect point for a user-owned Arti instance.
219
pub const USER_DEFAULT_CONNECT_POINT: &str = {
220
    cfg_if::cfg_if! {
221
        if #[cfg(unix)] {
222
r#"
223
[connect]
224
socket = "unix:${ARTI_LOCAL_DATA}/rpc/arti_rpc_socket"
225
auth = "none"
226
"#
227
        } else {
228
r#"
229
[connect]
230
socket = "inet:127.0.0.1:9180"
231
auth = { cookie = { path = "${ARTI_LOCAL_DATA}/rpc/arti_rpc_cookie" } }
232
"#
233
        }
234
    }
235
};
236

            
237
/// Default connect point for a system-wide Arti instance.
238
///
239
/// This is `None` if, on this platform, there is no such default connect point.
240
pub const SYSTEM_DEFAULT_CONNECT_POINT: Option<&str> = {
241
    cfg_if::cfg_if! {
242
        if #[cfg(unix)] {
243
            Some(
244
r#"
245
[connect]
246
socket = "unix:/var/run/arti-rpc/arti_rpc_socket"
247
auth = "none"
248
"#
249
            )
250
        } else {
251
            None
252
        }
253
    }
254
};
255

            
256
#[cfg(test)]
257
mod test {
258
    // @@ begin test lint list maintained by maint/add_warning @@
259
    #![allow(clippy::bool_assert_comparison)]
260
    #![allow(clippy::clone_on_copy)]
261
    #![allow(clippy::dbg_macro)]
262
    #![allow(clippy::mixed_attributes_style)]
263
    #![allow(clippy::print_stderr)]
264
    #![allow(clippy::print_stdout)]
265
    #![allow(clippy::single_char_pattern)]
266
    #![allow(clippy::unwrap_used)]
267
    #![allow(clippy::unchecked_duration_subtraction)]
268
    #![allow(clippy::useless_vec)]
269
    #![allow(clippy::needless_pass_by_value)]
270
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
271

            
272
    use super::*;
273

            
274
    #[test]
275
    fn parse_defaults() {
276
        let _parsed: ParsedConnectPoint = USER_DEFAULT_CONNECT_POINT.parse().unwrap();
277
        if let Some(s) = SYSTEM_DEFAULT_CONNECT_POINT {
278
            let _parsed: ParsedConnectPoint = s.parse().unwrap();
279
        }
280
    }
281
}