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 @@ -->
4647// TODO #1645 (either remove this, or decide to have it everywhere)
48#![cfg_attr(not(all(feature = "full")), allow(unused))]
4950pub mod auth;
51#[cfg(feature = "rpc-client")]
52pub mod client;
53mod connpt;
54pub mod load;
55#[cfg(feature = "rpc-server")]
56pub mod server;
57#[cfg(test)]
58mod testing;
5960use std::{io, sync::Arc};
6162pub use connpt::{ParsedConnectPoint, ResolveError, ResolvedConnectPoint};
6364/// 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)]
69pub enum ClientErrorAction {
70/// The client must stop, and must not make any more connect attempts.
71Abort,
72/// The connect point has failed; the client can continue to the next connect point.
73Decline,
74}
75/// An error that has a [`ClientErrorAction`].
76pub trait HasClientErrorAction {
77/// Return the action that an RPC client should take based on this error.
78fn client_action(&self) -> ClientErrorAction;
79}
80impl HasClientErrorAction for tor_config_path::CfgPathError {
81fn client_action(&self) -> ClientErrorAction {
82// Every variant of this means a configuration error
83 // or an ill-formed TOML file.
84ClientErrorAction::Abort
85 }
86}
87impl HasClientErrorAction for tor_config_path::addr::CfgAddrError {
88fn client_action(&self) -> ClientErrorAction {
89use tor_config_path::addr::CfgAddrError as CAE;
90use ClientErrorAction as A;
91match 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.
105fn fs_error_action(err: &std::io::Error) -> ClientErrorAction {
106use std::io::ErrorKind as EK;
107use ClientErrorAction as A;
108match 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.
119fn net_error_action(err: &std::io::Error) -> ClientErrorAction {
120use std::io::ErrorKind as EK;
121use ClientErrorAction as A;
122match 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}
130impl HasClientErrorAction for fs_mistrust::Error {
131fn client_action(&self) -> ClientErrorAction {
132use fs_mistrust::Error as E;
133use ClientErrorAction as A;
134match self {
135 E::Multiple(errs) => {
136if 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),
144145 E::NotFound(_) => A::Decline,
146 E::BadPermission(_, _, _) | E::BadOwner(_, _) => A::Decline,
147 E::StepsExceeded | E::CurrentDirectory(_) => A::Abort,
148149 E::BadType(_) => A::Abort,
150151// These should be impossible for clients given how we use fs_mistrust in this crate.
152E::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}
163164/// A failure to connect or bind to a [`ResolvedConnectPoint`].
165#[derive(Clone, Debug, thiserror::Error)]
166#[non_exhaustive]
167pub enum ConnectError {
168/// We encountered an IO error while actually opening our socket.
169#[error("IO error while connecting")]
170Io(#[source] Arc<io::Error>),
171/// The connect point told us to abort explicitly.
172#[error("Encountered an explicit \"abort\"")]
173ExplicitAbort,
174/// We couldn't load the cookie file for cookie authentication.
175#[error("Unable to load cookie file")]
176LoadCookie(#[from] auth::cookie::CookieAccessError),
177/// We were told to connect to a socket type that we don't support.
178#[error("Unsupported socket type")]
179UnsupportedSocketType,
180/// We were told to connect using an auth type that we don't support.
181#[error("Unsupported authentication type")]
182UnsupportedAuthType,
183/// Unable to access the location of an AF\_UNIX socket.
184#[error("Unix domain socket path access")]
185AfUnixSocketPathAccess(#[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")]
189AlreadyLocked,
190}
191192impl From<io::Error> for ConnectError {
193fn from(err: io::Error) -> Self {
194 ConnectError::Io(Arc::new(err))
195 }
196}
197impl crate::HasClientErrorAction for ConnectError {
198fn client_action(&self) -> crate::ClientErrorAction {
199use crate::ClientErrorAction as A;
200use ConnectError as E;
201match 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).
214fn socket_parent_path(addr: &tor_general_addr::general::SocketAddr) -> Option<&std::path::Path> {
215 addr.as_pathname().and_then(|p| p.parent())
216}
217218/// Default connect point for a user-owned Arti instance.
219pub const USER_DEFAULT_CONNECT_POINT: &str = {
220cfg_if::cfg_if! {
221if #[cfg(unix)] {
222r#"
223[connect]
224socket = "unix:${ARTI_LOCAL_DATA}/rpc/arti_rpc_socket"
225auth = "none"
226"#
227} else {
228r#"
229[connect]
230socket = "inet:127.0.0.1:9180"
231auth = { cookie = { path = "${ARTI_LOCAL_DATA}/rpc/arti_rpc_cookie" } }
232"#
233}
234 }
235};
236237/// 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.
240pub const SYSTEM_DEFAULT_CONNECT_POINT: Option<&str> = {
241cfg_if::cfg_if! {
242if #[cfg(unix)] {
243Some(
244r#"
245[connect]
246socket = "unix:/var/run/arti-rpc/arti_rpc_socket"
247auth = "none"
248"#
249)
250 } else {
251None
252}
253 }
254};
255256#[cfg(test)]
257mod 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 @@ -->
271272use super::*;
273274#[test]
275fn parse_defaults() {
276let _parsed: ParsedConnectPoint = USER_DEFAULT_CONNECT_POINT.parse().unwrap();
277if let Some(s) = SYSTEM_DEFAULT_CONNECT_POINT {
278let _parsed: ParsedConnectPoint = s.parse().unwrap();
279 }
280 }
281}