mod hint;
use std::fmt::{self, Display};
use std::sync::Arc;
use futures::task::SpawnError;
#[cfg(feature = "onion-service-client")]
use safelog::DisplayRedacted as _;
use safelog::Sensitive;
use thiserror::Error;
use tor_circmgr::TargetPorts;
use tor_error::{ErrorKind, HasKind};
use crate::TorAddrError;
#[cfg(feature = "onion-service-client")]
use tor_hscrypto::pk::HsId;
pub use hint::HintableError;
#[derive(Error, Clone, Debug)]
pub struct Error {
#[source]
detail: Box<ErrorDetail>,
}
impl From<ErrorDetail> for Error {
fn from(detail: ErrorDetail) -> Error {
Error {
detail: detail.into(),
}
}
}
#[cfg(feature = "error_detail")]
macro_rules! pub_if_error_detail {
{ $(#[$meta:meta])* enum $e:ident $tt:tt } => {
$(#[$meta])* pub enum $e $tt
}
}
#[cfg(not(feature = "error_detail"))]
macro_rules! pub_if_error_detail {
{ $(#[$meta:meta])* enum $e:ident $tt:tt } => {
$(#[$meta])* pub(crate) enum $e $tt }
}
pub_if_error_detail! {
#[cfg_attr(docsrs, doc(cfg(feature = "error_detail")))]
#[cfg_attr(test, derive(strum::EnumDiscriminants))]
#[cfg_attr(test, strum_discriminants(vis(pub(crate))))]
#[derive(Error, Clone, Debug)]
#[non_exhaustive]
enum ErrorDetail {
#[error("Error setting up the memory quota tracker")]
MemquotaSetup(#[from] tor_memquota::StartupError),
#[error("Memory quota error during startup")]
MemquotaDuringStartup(#[from] tor_memquota::Error),
#[error("Error setting up the channel manager")]
ChanMgrSetup(#[source] tor_chanmgr::Error),
#[error("Error setting up the guard manager")]
GuardMgrSetup(#[source] tor_guardmgr::GuardMgrError),
#[cfg(all(
feature = "vanguards",
any(feature = "onion-service-client", feature = "onion-service-service")
))]
#[error("Error setting up the vanguard manager")]
VanguardMgrSetup(#[source] tor_guardmgr::VanguardMgrError),
#[error("Error setting up the circuit manager")]
CircMgrSetup(#[source] tor_circmgr::Error),
#[error("Error setting up the bridge descriptor manager")]
#[cfg(feature = "bridge-client")]
BridgeDescMgrSetup(#[from] tor_dirmgr::bridgedesc::StartupError),
#[error("Error setting up the directory manager")]
DirMgrSetup(#[source] tor_dirmgr::Error),
#[error("Error setting up the persistent state manager")]
StateMgrSetup(#[source] tor_persist::Error),
#[error("Error setting up the hidden service client connector")]
#[cfg(feature = "onion-service-client")]
HsClientConnectorSetup(#[from] tor_hsclient::StartupError),
#[cfg(feature= "onion-service-service")]
#[error("Error setting up onion service")]
OnionServiceSetup(#[source] tor_hsservice::StartupError),
#[error("Failed to obtain exit circuit for ports {exit_ports}")]
ObtainExitCircuit {
exit_ports: Sensitive<TargetPorts>,
#[source]
cause: tor_circmgr::Error,
},
#[cfg(feature = "onion-service-client")]
#[error("Failed to obtain hidden service circuit to {}", hsid.display_redacted())]
ObtainHsCircuit {
hsid: HsId,
#[source]
cause: tor_hsclient::ConnError,
},
#[error("Unable to bootstrap a working directory")]
DirMgrBootstrap(#[source] tor_dirmgr::Error),
#[error("Protocol error while launching a {kind} stream")]
StreamFailed {
kind: &'static str,
#[source]
cause: tor_circmgr::Error
},
#[error("Error while trying to access persistent state")]
StateAccess(#[source] tor_persist::Error),
#[error("Timed out while waiting for answer from exit")]
ExitTimeout,
#[error("Rejecting .onion address; feature onion-service-client not compiled in")]
OnionAddressNotSupported,
#[cfg(feature = "onion-service-client")]
#[error("Rejecting .onion address; allow_onion_addrs disabled in stream preferences")]
OnionAddressDisabled,
#[error("A .onion address cannot be resolved to an IP address")]
OnionAddressResolveRequest,
#[error("Could not parse target address")]
Address(crate::address::TorAddrError),
#[error("Rejecting hostname as invalid")]
InvalidHostname,
#[error("Cannot connect to a local-only address without enabling allow_local_addrs")]
LocalAddress,
#[error("Problem with configuration")]
Configuration(#[from] tor_config::ConfigBuildError),
#[error("Unable to change configuration")]
Reconfigure(#[from] tor_config::ReconfigureError),
#[cfg(feature="pt-client")]
#[error("Problem with a pluggable transport")]
PluggableTransport(#[from] tor_ptmgr::err::PtError),
#[error("Problem accessing filesystem")]
FsMistrust(#[from] fs_mistrust::Error),
#[error("Unable to spawn {spawning}")]
Spawn {
spawning: &'static str,
#[source]
cause: Arc<SpawnError>
},
#[error("Cannot {action} with unbootstrapped client")]
BootstrapRequired {
action: &'static str
},
#[error("Tried to {action} without a valid directory")]
NoDir {
#[source]
error: tor_netdir::Error,
action: &'static str,
},
#[error("Error while trying to access a key store")]
Keystore(#[from] tor_keymgr::Error),
#[error("Cannot {action} without enabling storage.keystore")]
KeystoreRequired {
action: &'static str
},
#[error("Bad client specifier")]
BadClientSpecifier(#[from] tor_keymgr::ArtiPathSyntaxError),
#[cfg(feature = "onion-service-client")]
#[error("Invalid onion address")]
BadOnionAddress(#[from] tor_hscrypto::pk::HsIdParseError),
#[cfg(feature= "onion-service-service")]
#[error("Unable to launch onion service")]
LaunchOnionService(#[source] tor_hsservice::StartupError),
#[error("Arti is missing a required protocol feature")]
MissingProtocol(#[source] tor_netdoc::doc::netstatus::ProtocolSupportError),
#[error("Programming problem")]
Bug(#[from] tor_error::Bug),
}
}
#[cfg(feature = "error_detail")]
impl Error {
pub fn detail(&self) -> &ErrorDetail {
&self.detail
}
}
impl Error {
pub(crate) fn into_detail(self) -> ErrorDetail {
*self.detail
}
}
impl ErrorDetail {
pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> ErrorDetail {
ErrorDetail::Spawn {
spawning,
cause: Arc::new(err),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "tor: {}: {}", self.detail.kind(), &self.detail)
}
}
impl tor_error::HasKind for Error {
fn kind(&self) -> ErrorKind {
self.detail.kind()
}
}
impl tor_error::HasKind for ErrorDetail {
fn kind(&self) -> ErrorKind {
use ErrorDetail as E;
use ErrorKind as EK;
match self {
E::ObtainExitCircuit { cause, .. } => cause.kind(),
#[cfg(feature = "onion-service-client")]
E::ObtainHsCircuit { cause, .. } => cause.kind(),
E::ExitTimeout => EK::RemoteNetworkTimeout,
E::BootstrapRequired { .. } => EK::BootstrapRequired,
E::MemquotaSetup(e) => e.kind(),
E::MemquotaDuringStartup(e) => e.kind(),
E::GuardMgrSetup(e) => e.kind(),
#[cfg(all(
feature = "vanguards",
any(feature = "onion-service-client", feature = "onion-service-service")
))]
E::VanguardMgrSetup(e) => e.kind(),
#[cfg(feature = "bridge-client")]
E::BridgeDescMgrSetup(e) => e.kind(),
E::CircMgrSetup(e) => e.kind(),
E::DirMgrSetup(e) => e.kind(),
E::StateMgrSetup(e) => e.kind(),
#[cfg(feature = "onion-service-client")]
E::HsClientConnectorSetup(e) => e.kind(),
#[cfg(feature = "onion-service-service")]
E::OnionServiceSetup(e) => e.kind(),
E::DirMgrBootstrap(e) => e.kind(),
#[cfg(feature = "pt-client")]
E::PluggableTransport(e) => e.kind(),
E::StreamFailed { cause, .. } => cause.kind(),
E::StateAccess(e) => e.kind(),
E::Configuration(e) => e.kind(),
E::Reconfigure(e) => e.kind(),
E::Spawn { cause, .. } => cause.kind(),
E::OnionAddressNotSupported => EK::FeatureDisabled,
E::OnionAddressResolveRequest => EK::NotImplemented,
#[cfg(feature = "onion-service-client")]
E::OnionAddressDisabled => EK::ForbiddenStreamTarget,
#[cfg(feature = "onion-service-client")]
E::BadOnionAddress(_) => EK::InvalidStreamTarget,
#[cfg(feature = "onion-service-service")]
E::LaunchOnionService(e) => e.kind(),
E::Address(e) => e.kind(),
E::InvalidHostname => EK::InvalidStreamTarget,
E::LocalAddress => EK::ForbiddenStreamTarget,
E::ChanMgrSetup(e) => e.kind(),
E::NoDir { error, .. } => error.kind(),
E::Keystore(e) => e.kind(),
E::KeystoreRequired { .. } => EK::InvalidConfig,
E::BadClientSpecifier(_) => EK::InvalidConfig,
E::FsMistrust(_) => EK::FsPermissions,
E::MissingProtocol(_) => EK::SoftwareDeprecated,
E::Bug(e) => e.kind(),
}
}
}
impl From<TorAddrError> for Error {
fn from(e: TorAddrError) -> Error {
ErrorDetail::from(e).into()
}
}
impl From<tor_keymgr::Error> for Error {
fn from(e: tor_keymgr::Error) -> Error {
ErrorDetail::Keystore(e).into()
}
}
impl From<TorAddrError> for ErrorDetail {
fn from(e: TorAddrError) -> ErrorDetail {
use ErrorDetail as E;
use TorAddrError as TAE;
match e {
TAE::InvalidHostname => E::InvalidHostname,
TAE::NoPort | TAE::BadPort => E::Address(e),
}
}
}
#[derive(Clone, Debug)]
pub struct ErrorHint<'a> {
inner: ErrorHintInner<'a>,
}
#[derive(Clone, Debug)]
enum ErrorHintInner<'a> {
BadPermission {
filename: &'a std::path::Path,
bits: u32,
badbits: u32,
},
MissingProtocols {
required: &'a tor_protover::Protocols,
},
}
impl<'a> Display for ErrorHint<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use fs_mistrust::anon_home::PathExt as _;
match self.inner {
ErrorHintInner::BadPermission {
filename,
bits,
badbits,
} => {
writeln!(
f,
"Permissions are set too permissively on {}: currently {}",
filename.anonymize_home(),
fs_mistrust::format_access_bits(bits, '=')
)?;
if 0 != badbits & 0o222 {
writeln!(
f,
"* Untrusted users could modify its contents and override our behavior.",
)?;
}
if 0 != badbits & 0o444 {
writeln!(f, "* Untrusted users could read its contents.")?;
}
writeln!(
f,
"You can fix this by further restricting the permissions of your filesystem, using:\n\
chmod {} {}",
fs_mistrust::format_access_bits(badbits, '-'),
filename.anonymize_home()
)?;
writeln!(
f,
"You can suppress this message by setting storage.permissions.dangerously_trust_everyone=true,\n\
or setting ARTI_FS_DISABLE_PERMISSION_CHECKS=yes in your environment."
)?;
}
ErrorHintInner::MissingProtocols { required } => {
writeln!(
f,
"The consensus directory says that we need to support certain protocols which we do not implement."
)?;
writeln!(f, "The missing protocols are: {}", required)?;
writeln!(
f,
"The best solution is to upgrade to a more recent version of Arti. If this is not possible,
you can list the missing protocols in the configuration option 'use_obsolete_software.ignore_missing_required_protocols'"
)?;
}
}
Ok(())
}
}
impl Error {
pub fn hint(&self) -> Option<ErrorHint> {
HintableError::hint(self)
}
}
#[cfg(test)]
mod test {
#![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)]
use super::*;
#[test]
fn traits_ok() {
fn assert<
T: Send + Sync + Clone + std::fmt::Debug + Display + std::error::Error + 'static,
>() {
}
fn check() {
assert::<Error>();
assert::<ErrorDetail>();
}
check(); }
}