1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
|
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
// @@ begin lint list maintained by maint/add_warning @@
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
#![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unchecked_time_subtraction)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::mod_module_files)]
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
#![allow(clippy::needless_lifetimes)] // See arti#1765
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
#![allow(clippy::collapsible_if)] // See arti#2342
#![deny(clippy::unused_async)]
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
// TODO #1645 (either remove this, or decide to have it everywhere)
#![cfg_attr(not(all(feature = "full")), allow(unused))]
pub mod auth;
#[cfg(feature = "rpc-client")]
pub mod client;
mod connpt;
pub mod load;
#[cfg(feature = "rpc-server")]
pub mod server;
#[cfg(test)]
mod testing;
use std::{io, sync::Arc};
pub use connpt::{ParsedConnectPoint, ResolveError, ResolvedConnectPoint};
use tor_general_addr::general;
/// An action that an RPC client should take when a connect point fails.
///
/// (This terminology is taken from the spec.)
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(clippy::exhaustive_enums)]
pub enum ClientErrorAction {
/// The client must stop, and must not make any more connect attempts.
Abort,
/// The connect point has failed; the client can continue to the next connect point.
Decline,
}
/// An error that has a [`ClientErrorAction`].
pub trait HasClientErrorAction {
/// Return the action that an RPC client should take based on this error.
fn client_action(&self) -> ClientErrorAction;
}
impl HasClientErrorAction for tor_config_path::CfgPathError {
fn client_action(&self) -> ClientErrorAction {
// Every variant of this means a configuration error
// or an ill-formed TOML file.
ClientErrorAction::Abort
}
}
impl HasClientErrorAction for tor_config_path::addr::CfgAddrError {
fn client_action(&self) -> ClientErrorAction {
use ClientErrorAction as A;
use tor_config_path::addr::CfgAddrError as CAE;
match self {
CAE::NoAfUnixSocketSupport(_) => A::Decline,
CAE::Path(cfg_path_error) => cfg_path_error.client_action(),
CAE::ConstructAfUnixAddress(_) => A::Abort,
// No variants are currently captured in this pattern, but they _could_ be in the future.
_ => A::Abort,
}
}
}
impl HasClientErrorAction for tor_general_addr::general::AddrParseError {
fn client_action(&self) -> ClientErrorAction {
use ClientErrorAction as A;
use tor_general_addr::general::AddrParseError as E;
match self {
E::UnrecognizedSchema(_) => A::Decline,
E::NoSchema => A::Decline,
E::InvalidAfUnixAddress(_) => A::Abort,
// We might want to turn this into an Abort in the future, but I think that we might
// want to allow "auto" as a port format in CfgAddr.
E::InvalidInetAddress(_) => A::Decline,
// No variants are currently captured in this pattern, but they _could_ be in the future.
_ => A::Abort,
}
}
}
/// Return the ClientErrorAction for an IO error encountered
/// while accessing the filesystem.
///
/// Note that this is not an implementation of `HasClientErrorAction`:
/// We want to decline on a different set of errors for network operation.
fn fs_error_action(err: &std::io::Error) -> ClientErrorAction {
use ClientErrorAction as A;
use std::io::ErrorKind as EK;
match err.kind() {
EK::NotFound => A::Decline,
EK::PermissionDenied => A::Decline,
_ => A::Abort,
}
}
/// Return the ClientErrorAction for an IO error encountered
/// while opening a socket.
///
/// Note that this is not an implementation of `HasClientErrorAction`:
/// We want to decline on a different set of errors for fs operation.
fn net_error_action(err: &std::io::Error) -> ClientErrorAction {
use ClientErrorAction as A;
use std::io::ErrorKind as EK;
match err.kind() {
EK::ConnectionRefused => A::Decline,
EK::ConnectionReset => A::Decline,
// TODO MSRV 1.83; revisit once some of `io_error_more` is stabilized.
// see https://github.com/rust-lang/rust/pull/128316
_ => A::Abort,
}
}
impl HasClientErrorAction for fs_mistrust::Error {
fn client_action(&self) -> ClientErrorAction {
use ClientErrorAction as A;
use fs_mistrust::Error as E;
match self {
E::Multiple(errs) => {
if errs.iter().any(|e| e.client_action() == A::Abort) {
A::Abort
} else {
A::Decline
}
}
E::Io { err, .. } => fs_error_action(err),
E::CouldNotInspect(_, err) => fs_error_action(err),
E::NotFound(_) => A::Decline,
E::BadPermission(_, _, _) | E::BadOwner(_, _) => A::Decline,
E::StepsExceeded | E::CurrentDirectory(_) => A::Abort,
E::BadType(_) => A::Abort,
// These should be impossible for clients given how we use fs_mistrust in this crate.
E::CreatingDir(_)
| E::Content(_)
| E::NoSuchGroup(_)
| E::NoSuchUser(_)
| E::MissingField(_)
| E::InvalidSubdirectory => A::Abort,
E::PasswdGroupIoError(_) => A::Abort,
_ => A::Abort,
}
}
}
/// A failure to connect or bind to a [`ResolvedConnectPoint`].
#[derive(Clone, Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ConnectError {
/// We encountered an IO error while actually opening our socket.
#[error("IO error while connecting")]
Io(#[source] Arc<io::Error>),
/// The connect point told us to abort explicitly.
#[error("Encountered an explicit \"abort\"")]
ExplicitAbort,
/// We couldn't load the cookie file for cookie authentication.
#[error("Unable to load cookie file")]
LoadCookie(#[from] auth::cookie::CookieAccessError),
/// We were told to connect to a socket type that we don't support.
#[error("Unsupported socket type")]
UnsupportedSocketType,
/// We were told to connect using an auth type that we don't support.
#[error("Unsupported authentication type")]
UnsupportedAuthType,
/// Unable to access the location of an AF\_UNIX socket.
#[error("Unix domain socket path access")]
AfUnixSocketPathAccess(#[from] fs_mistrust::Error),
/// Unable to access the location of `socket_address_file`.
#[error("Problem accessing socket address file")]
SocketAddressFileAccess(#[source] fs_mistrust::Error),
/// We couldn't parse the JSON contents of a socket address file.
#[error("Invalid JSON contents in socket address file")]
SocketAddressFileJson(#[source] Arc<serde_json::Error>),
/// We couldn't parse the address in a socket address file.
#[error("Invalid address in socket address file")]
SocketAddressFileContent(#[source] general::AddrParseError),
/// We found an address in the socket address file that didn't match the connect point.
#[error("Socket address file contents didn't match connect point")]
SocketAddressFileMismatch,
/// Another process was holding a lock for this connect point,
/// so we couldn't bind to it.
#[error("Could not acquire lock: Another process is listening on this connect point")]
AlreadyLocked,
/// We encountered an internal logic error.
//
// (We're not using tor_error::Bug here because we want this code to work properly in rpc-client-core.)
#[error("Internal error: {0}")]
Internal(String),
}
impl From<io::Error> for ConnectError {
fn from(err: io::Error) -> Self {
ConnectError::Io(Arc::new(err))
}
}
impl crate::HasClientErrorAction for ConnectError {
fn client_action(&self) -> crate::ClientErrorAction {
use crate::ClientErrorAction as A;
use ConnectError as E;
match self {
E::Io(err) => crate::net_error_action(err),
E::ExplicitAbort => A::Abort,
E::LoadCookie(err) => err.client_action(),
E::UnsupportedSocketType => A::Decline,
E::UnsupportedAuthType => A::Decline,
E::AfUnixSocketPathAccess(err) => err.client_action(),
E::SocketAddressFileAccess(err) => err.client_action(),
E::SocketAddressFileJson(_) => A::Decline,
E::SocketAddressFileContent(_) => A::Decline,
E::SocketAddressFileMismatch => A::Decline,
E::AlreadyLocked => A::Abort, // (This one can't actually occur for clients.)
E::Internal(_) => A::Abort,
}
}
}
#[cfg(any(feature = "rpc-client", feature = "rpc-server"))]
/// Given a `general::SocketAddr`, try to return the path of its parent directory (if any).
fn socket_parent_path(addr: &tor_general_addr::general::SocketAddr) -> Option<&std::path::Path> {
addr.as_pathname().and_then(|p| p.parent())
}
/// Default connect point for a user-owned Arti instance.
pub const USER_DEFAULT_CONNECT_POINT: &str = {
cfg_if::cfg_if! {
if #[cfg(unix)] {
r#"
[connect]
socket = "unix:${ARTI_LOCAL_DATA}/rpc/arti_rpc_socket"
auth = "none"
"#
} else {
r#"
[connect]
socket = "inet:127.0.0.1:9180"
auth = { cookie = { path = "${ARTI_LOCAL_DATA}/rpc/arti_rpc_cookie" } }
"#
}
}
};
/// Default connect point for a system-wide Arti instance.
///
/// This is `None` if, on this platform, there is no such default connect point.
pub const SYSTEM_DEFAULT_CONNECT_POINT: Option<&str> = {
cfg_if::cfg_if! {
if #[cfg(unix)] {
Some(
r#"
[connect]
socket = "unix:/var/run/arti-rpc/arti_rpc_socket"
auth = "none"
"#
)
} else {
None
}
}
};
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
use super::*;
#[test]
fn parse_defaults() {
let _parsed: ParsedConnectPoint = USER_DEFAULT_CONNECT_POINT.parse().unwrap();
if let Some(s) = SYSTEM_DEFAULT_CONNECT_POINT {
let _parsed: ParsedConnectPoint = s.parse().unwrap();
}
}
}
|