1
//! Declare an error type for the tor-dirmgr crate.
2

            
3
use std::error::Error as StdError;
4
use std::sync::Arc;
5

            
6
use crate::DocSource;
7
use fs_mistrust::anon_home::PathExt as _;
8
use futures::task::SpawnError;
9
use thiserror::Error;
10
use tor_error::{ErrorKind, HasKind};
11
use tor_persist::FsMistrustErrorExt as _;
12

            
13
/// An error originated by the directory manager code
14
#[derive(Error, Debug, Clone)]
15
#[non_exhaustive]
16
pub enum Error {
17
    /// We received a document we didn't want at all.
18
    #[error("Received an object we didn't ask for: {0}")]
19
    Unwanted(&'static str),
20
    /// The NetDir we downloaded is older than the one we already have.
21
    #[error("Downloaded netdir is older than the one we have")]
22
    NetDirOlder,
23
    /// This DirMgr doesn't support downloads.
24
    #[error("Tried to download information on a DirMgr with no download support")]
25
    NoDownloadSupport,
26
    /// We couldn't read something from disk that we should have been
27
    /// able to read.
28
    #[error("Corrupt cache: {0}")]
29
    CacheCorruption(&'static str),
30
    /// rusqlite gave us an error.
31
    #[error("Error from sqlite database")]
32
    SqliteError(#[source] Arc<rusqlite::Error>),
33
    /// Error while creating a read-only store.
34
    #[error("Failed to create read-only store")]
35
    ReadOnlyStorage(#[from] ReadOnlyStorageError),
36
    /// A schema version that says we can't read it.
37
    #[error("Unrecognized data storage schema v{schema}. (We support v{supported})")]
38
    UnrecognizedSchema {
39
        /// The schema version in the database
40
        schema: u32,
41
        /// The schema that we actually support.
42
        supported: u32,
43
    },
44
    /// User requested an operation that required a usable
45
    /// bootstrapped directory, but we didn't have one.
46
    #[error("Directory not present or not up-to-date")]
47
    DirectoryNotPresent,
48
    /// A consensus document is signed by an unrecognized authority set.
49
    #[error("Authorities on consensus are not the ones we expect")]
50
    UnrecognizedAuthorities,
51
    /// A directory manager has been dropped; background tasks can exit too.
52
    #[error("Dirmgr has been dropped; background tasks exiting")]
53
    ManagerDropped,
54
    /// We made a bunch of attempts, but weren't unable to advance the
55
    /// state of a download.
56
    #[error("Unable to finish bootstrapping a directory")]
57
    CantAdvanceState,
58
    /// Error while accessing a lockfile.
59
    #[error("Unable to access lock file")]
60
    LockFile(Arc<std::io::Error>),
61
    /// Error while accessing a file in the store.
62
    #[error("Error while {action} cache file {}", fname.anonymize_home())]
63
    CacheFile {
64
        /// What we were doing when we encountered the error.
65
        action: &'static str,
66
        /// The file that we were trying to access.
67
        fname: std::path::PathBuf,
68
        /// The underlying IO error.
69
        #[source]
70
        error: Arc<std::io::Error>,
71
    },
72
    /// An error given by the consensus diff crate.
73
    #[error("Problem applying consensus diff")]
74
    ConsensusDiffError(#[from] tor_consdiff::Error),
75
    /// Invalid UTF8 in directory response.
76
    #[error("Invalid utf-8 from directory server")]
77
    BadUtf8FromDirectory(#[source] std::string::FromUtf8Error),
78
    /// Invalid UTF8 from our cache.
79
    #[error("Invalid utf-8 in directory cache")]
80
    BadUtf8InCache(#[source] std::str::Utf8Error),
81
    /// Invalid hexadecimal value in the cache.
82
    #[error("Invalid hexadecimal id in directory cache")]
83
    BadHexInCache(#[source] hex::FromHexError),
84
    /// Invalid json value in the cache
85
    #[error("Invalid JSON in directory cache")]
86
    BadJsonInCache(#[source] Arc<serde_json::Error>),
87
    /// An error given by the network document crate.
88
    #[error("Invalid document from {source}")]
89
    NetDocError {
90
        /// Where the document came from.
91
        source: DocSource,
92
        /// What error we got.
93
        #[source]
94
        cause: tor_netdoc::Error,
95
    },
96
    /// An error indicating that the consensus could not be validated.
97
    ///
98
    /// This kind of error is only returned during the certificate fetching
99
    /// state; it indicates that a consensus which previously seemed to be
100
    /// plausible has turned out to be wrong after we got the certificates.
101
    #[error("Could not validate consensus from {source}")]
102
    ConsensusInvalid {
103
        /// Where the document came from.
104
        source: DocSource,
105
        /// What error we got.
106
        #[source]
107
        cause: tor_netdoc::Error,
108
    },
109
    /// An error caused by an expired or not-yet-valid object.
110
    #[error("Directory object expired or not yet valid")]
111
    UntimelyObject(#[from] tor_checkable::TimeValidityError),
112
    /// An error given by dirclient
113
    #[error("Problem downloading directory object")]
114
    DirClientError(#[from] tor_dirclient::Error),
115
    /// An error given by the checkable crate.
116
    #[error("Invalid signatures")]
117
    SignatureError(#[source] Arc<signature::Error>),
118
    /// An attempt was made to bootstrap a `DirMgr` created in offline mode.
119
    #[error("Tried to bootstrap a DirMgr that was configured as offline-only")]
120
    OfflineMode,
121
    /// A problem accessing our cache directory (for example, no such directory)
122
    #[error("Problem accessing cache directory")]
123
    CacheAccess(#[from] fs_mistrust::Error),
124
    /// A problem accessing our cache directory (for example, no such directory)
125
    ///
126
    /// This variant name is misleading - see the docs for [`fs_mistrust::Error`].
127
    /// Please use [`Error::CacheAccess`] instead.
128
    #[error("Problem accessing cache directory")]
129
    #[deprecated = "use Error::CacheAccess instead"]
130
    CachePermissions(#[source] fs_mistrust::Error),
131
    /// Unable to spawn task
132
    #[error("Unable to spawn {spawning}")]
133
    Spawn {
134
        /// What we were trying to spawn
135
        spawning: &'static str,
136
        /// What happened when we tried to spawn it
137
        #[source]
138
        cause: Arc<SpawnError>,
139
    },
140

            
141
    /// Other error from an external directory provider
142
    #[error("Error from external directory provider")]
143
    ExternalDirProvider {
144
        /// What happened
145
        #[source]
146
        cause: Arc<dyn std::error::Error + Send + Sync + 'static>,
147

            
148
        /// The kind
149
        kind: ErrorKind,
150
    },
151

            
152
    /// A programming problem, either in our code or the code calling it.
153
    #[error("Internal programming issue")]
154
    Bug(#[from] tor_error::Bug),
155
}
156

            
157
impl From<signature::Error> for Error {
158
    fn from(err: signature::Error) -> Self {
159
        Self::SignatureError(Arc::new(err))
160
    }
161
}
162

            
163
impl From<tor_rtcompat::scheduler::SleepError> for Error {
164
    fn from(err: tor_rtcompat::scheduler::SleepError) -> Self {
165
        use tor_rtcompat::scheduler::SleepError::*;
166
        match err {
167
            ScheduleDropped => Error::ManagerDropped,
168
            e => tor_error::into_internal!("Unexpected sleep error")(e).into(),
169
        }
170
    }
171
}
172

            
173
impl AsRef<dyn StdError + 'static> for Error {
174
    fn as_ref(&self) -> &(dyn StdError + 'static) {
175
        self
176
    }
177
}
178

            
179
/// The effect that a given error has on our bootstrapping process
180
#[derive(Copy, Clone, Debug)]
181
pub(crate) enum BootstrapAction {
182
    /// The error isn't fatal.  We should blame it on its source (if any), and
183
    /// continue bootstrapping.
184
    Nonfatal,
185
    /// The error requires that we restart bootstrapping from scratch.  
186
    ///
187
    /// This kind of error typically means that we've downloaded a consensus
188
    /// that turned out to be useless at a later stage, and so we need to
189
    /// restart the downloading process from the beginning, by downloading a
190
    /// fresh one.
191
    Reset,
192
    /// The error indicates that we cannot bootstrap, and should stop trying.
193
    ///
194
    /// These are typically internal programming errors, filesystem access
195
    /// problems, directory manager shutdown, and the like.
196
    Fatal,
197
}
198

            
199
impl Error {
200
    /// Construct a new `Error` from a `SpawnError`.
201
    pub(crate) fn from_spawn(spawning: &'static str, err: SpawnError) -> Error {
202
        Error::Spawn {
203
            spawning,
204
            cause: Arc::new(err),
205
        }
206
    }
207

            
208
    /// Construct a new `Error` from `tor_netdoc::Error`.
209
    ///
210
    /// Also takes a source so that we can keep track of where the document came from.
211
2
    pub(crate) fn from_netdoc(source: DocSource, cause: tor_netdoc::Error) -> Error {
212
2
        Error::NetDocError { source, cause }
213
2
    }
214

            
215
    /// Construct a new `Error` from `std::io::Error` for an error that occurred
216
    /// while locking a file.
217
    pub(crate) fn from_lockfile(err: std::io::Error) -> Error {
218
        Error::LockFile(Arc::new(err))
219
    }
220

            
221
    /// Return true if this error is serious enough that we should mark this
222
    /// cache as having failed.
223
    pub(crate) fn indicates_cache_failure(&self) -> bool {
224
        #[allow(deprecated)]
225
        match self {
226
            // These indicate a problem from the cache.
227
            Error::Unwanted(_)
228
            | Error::UnrecognizedAuthorities
229
            | Error::BadUtf8FromDirectory(_)
230
            | Error::ConsensusDiffError(_)
231
            | Error::SignatureError(_)
232
            | Error::ConsensusInvalid { .. }
233
            | Error::UntimelyObject(_) => true,
234

            
235
            // These errors cannot come from a directory cache.
236
            Error::NoDownloadSupport
237
            | Error::CacheCorruption(_)
238
            | Error::CachePermissions(_)
239
            | Error::CacheAccess(_)
240
            | Error::SqliteError(_)
241
            | Error::ReadOnlyStorage(_)
242
            | Error::UnrecognizedSchema { .. }
243
            | Error::DirectoryNotPresent
244
            | Error::ManagerDropped
245
            | Error::CantAdvanceState
246
            | Error::LockFile { .. }
247
            | Error::CacheFile { .. }
248
            | Error::BadUtf8InCache(_)
249
            | Error::BadHexInCache(_)
250
            | Error::OfflineMode
251
            | Error::Spawn { .. }
252
            | Error::NetDirOlder
253
            | Error::BadJsonInCache(_)
254
            | Error::Bug(_) => false,
255

            
256
            // For this one, we delegate.
257
            Error::DirClientError(e) => e.should_retire_circ(),
258

            
259
            // TODO: This one is special.  It could mean that the directory
260
            // cache is serving us bad unparsable stuff, or it could mean that
261
            // for some reason we're unable to parse a real legit document.
262
            //
263
            // If the cache is serving us something unparsable, it might be
264
            // because the cache doesn't know all the same parsing rules for the
265
            // object that we know.  That case might need special handling to
266
            // avoid erroneously avoiding a good cache... especially if the document
267
            // is one that the cache could be tricked into serving us.
268
            Error::NetDocError { .. } => true,
269

            
270
            // We can never see this kind of error from within the crate.
271
            Error::ExternalDirProvider { .. } => false,
272
        }
273
    }
274

            
275
    /// Return information about which directory cache caused this error, if
276
    /// this error contains one.
277
    pub(crate) fn responsible_cache(&self) -> Option<&tor_dirclient::SourceInfo> {
278
        match self {
279
            Error::NetDocError {
280
                source: DocSource::DirServer { source },
281
                ..
282
            } => source.as_ref(),
283
            Error::ConsensusInvalid {
284
                source: DocSource::DirServer { source },
285
                ..
286
            } => source.as_ref(),
287
            _ => None,
288
        }
289
    }
290

            
291
    /// Return information about what to do if this error occurs during the
292
    /// bootstrapping process.
293
    #[allow(dead_code)]
294
    pub(crate) fn bootstrap_action(&self) -> BootstrapAction {
295
        #[allow(deprecated)]
296
        match self {
297
            Error::Unwanted(_)
298
            | Error::NetDirOlder
299
            | Error::UnrecognizedAuthorities
300
            | Error::ConsensusDiffError(_)
301
            | Error::BadUtf8FromDirectory(_)
302
            | Error::UntimelyObject(_)
303
            | Error::DirClientError(_)
304
            | Error::SignatureError(_)
305
            | Error::NetDocError { .. } => BootstrapAction::Nonfatal,
306

            
307
            Error::ConsensusInvalid { .. } | Error::CantAdvanceState => BootstrapAction::Reset,
308

            
309
            Error::NoDownloadSupport
310
            | Error::OfflineMode
311
            | Error::CacheCorruption(_)
312
            | Error::SqliteError(_)
313
            | Error::ReadOnlyStorage(_)
314
            | Error::UnrecognizedSchema { .. }
315
            | Error::ManagerDropped
316
            | Error::LockFile { .. }
317
            | Error::CacheFile { .. }
318
            | Error::BadUtf8InCache(_)
319
            | Error::BadHexInCache(_)
320
            | Error::CachePermissions(_)
321
            | Error::CacheAccess(_)
322
            | Error::Spawn { .. }
323
            | Error::BadJsonInCache(_)
324
            | Error::ExternalDirProvider { .. } => BootstrapAction::Fatal,
325

            
326
            // These should actually be impossible during the bootstrap process.
327
            Error::DirectoryNotPresent | Error::Bug(_) => BootstrapAction::Fatal,
328
        }
329
    }
330
}
331

            
332
impl From<rusqlite::Error> for Error {
333
    fn from(err: rusqlite::Error) -> Self {
334
        use ErrorKind as EK;
335
        let kind = sqlite_error_kind(&err);
336
        match kind {
337
            EK::Internal | EK::BadApiUsage => {
338
                // TODO: should this be a .is_bug() on EK ?
339
                tor_error::Bug::from_error(kind, err, "sqlite detected bug").into()
340
            }
341
            _ => Self::SqliteError(Arc::new(err)),
342
        }
343
    }
344
}
345

            
346
impl HasKind for Error {
347
    fn kind(&self) -> ErrorKind {
348
        use Error as E;
349
        use ErrorKind as EK;
350
        #[allow(deprecated)]
351
        match self {
352
            E::Unwanted(_) => EK::TorProtocolViolation,
353
            E::NoDownloadSupport => EK::NotImplemented,
354
            E::CacheCorruption(_) => EK::CacheCorrupted,
355
            E::CachePermissions(e) => e.cache_error_kind(),
356
            E::CacheAccess(e) => e.cache_error_kind(),
357
            E::SqliteError(e) => sqlite_error_kind(e),
358
            E::ReadOnlyStorage(_) => EK::LocalResourceAlreadyInUse,
359
            E::UnrecognizedSchema { .. } => EK::CacheCorrupted,
360
            E::BadJsonInCache(_) => EK::CacheCorrupted,
361
            E::DirectoryNotPresent => EK::DirectoryExpired,
362
            E::NetDirOlder => EK::TorDirectoryError,
363
            E::BadUtf8FromDirectory(_) => EK::TorProtocolViolation,
364
            E::BadUtf8InCache(_) => EK::CacheCorrupted,
365
            E::BadHexInCache(_) => EK::CacheCorrupted,
366
            E::UnrecognizedAuthorities => EK::TorProtocolViolation,
367
            E::ManagerDropped => EK::ArtiShuttingDown,
368
            E::CantAdvanceState => EK::TorAccessFailed,
369
            E::LockFile { .. } => EK::CacheAccessFailed,
370
            E::CacheFile { .. } => EK::CacheAccessFailed,
371
            E::ConsensusDiffError(_) => EK::TorProtocolViolation,
372
            E::NetDocError { source, .. } => match source {
373
                DocSource::LocalCache => EK::CacheCorrupted,
374
                DocSource::DirServer { .. } => EK::TorProtocolViolation,
375
            },
376
            E::ConsensusInvalid { source, .. } => match source {
377
                DocSource::LocalCache => EK::CacheCorrupted,
378
                DocSource::DirServer { .. } => EK::TorProtocolViolation,
379
            },
380
            E::UntimelyObject(_) => EK::TorProtocolViolation,
381
            E::DirClientError(e) => e.kind(),
382
            E::SignatureError(_) => EK::TorProtocolViolation,
383
            E::OfflineMode => EK::BadApiUsage,
384
            E::Spawn { cause, .. } => cause.kind(),
385
            E::ExternalDirProvider { kind, .. } => *kind,
386
            E::Bug(e) => e.kind(),
387
        }
388
    }
389
}
390

            
391
/// Convert a sqlite error code into a real ErrorKind.
392
fn sqlite_error_kind(e: &rusqlite::Error) -> ErrorKind {
393
    use rusqlite::ErrorCode as RE;
394
    use ErrorKind as EK;
395

            
396
    match e {
397
        rusqlite::Error::SqliteFailure(code, _) => match code.code {
398
            RE::DatabaseCorrupt => EK::CacheCorrupted,
399
            RE::SchemaChanged
400
            | RE::TooBig
401
            | RE::ConstraintViolation
402
            | RE::TypeMismatch
403
            | RE::ApiMisuse
404
            | RE::NoLargeFileSupport
405
            | RE::ParameterOutOfRange
406
            | RE::OperationInterrupted
407
            | RE::ReadOnly
408
            | RE::OperationAborted
409
            | RE::DatabaseBusy
410
            | RE::DatabaseLocked
411
            | RE::OutOfMemory
412
            | RE::InternalMalfunction => EK::Internal,
413

            
414
            RE::FileLockingProtocolFailed
415
            | RE::AuthorizationForStatementDenied
416
            | RE::NotFound
417
            | RE::DiskFull
418
            | RE::CannotOpen
419
            | RE::SystemIoFailure
420
            | RE::PermissionDenied => EK::CacheAccessFailed,
421
            RE::NotADatabase => EK::InvalidConfig,
422
            _ => EK::Internal,
423
        },
424

            
425
        // TODO: Some of the other sqlite error types can sometimes represent
426
        // possible database corruption (like UTF8Error.)  But I haven't
427
        // found a way to distinguish when.
428
        _ => EK::Internal,
429
    }
430
}
431

            
432
/// An error coming from a read-only store.
433
#[derive(Error, Debug, Clone)]
434
#[non_exhaustive]
435
pub enum ReadOnlyStorageError {
436
    /// We couldn't find the database.
437
    #[error("The database could not be found.")]
438
    NoDatabase,
439

            
440
    /// A schema version that is not current.
441
    #[error("Incompatible data storage schema v{schema}. (We expected v{supported})")]
442
    IncompatibleSchema {
443
        /// The schema version in the database
444
        schema: u32,
445
        /// The schema that we actually support.
446
        supported: u32,
447
    },
448
}