mirror of
https://gitlab.com/hagrid-keyserver/hagrid.git
synced 2023-02-13 20:55:02 -05:00
parent
cd750c3d78
commit
896494974b
2 changed files with 235 additions and 0 deletions
|
@ -1,4 +1,5 @@
|
||||||
use parking_lot::{Mutex, MutexGuard};
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
use std::convert::{TryInto, TryFrom};
|
||||||
use std::fs::{create_dir_all, read_link, remove_file, rename, File};
|
use std::fs::{create_dir_all, read_link, remove_file, rename, File};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -113,6 +114,15 @@ impl Filesystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the KeyID the given path is pointing to.
|
||||||
|
fn path_to_keyid(&self, path: &Path) -> Option<KeyID> {
|
||||||
|
use std::str::FromStr;
|
||||||
|
let rest = path.file_name()?;
|
||||||
|
let prefix = path.parent()?.file_name()?;
|
||||||
|
KeyID::from_str(&format!("{}{}", prefix.to_str()?, rest.to_str()?))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the Fingerprint the given path is pointing to.
|
/// Returns the Fingerprint the given path is pointing to.
|
||||||
fn path_to_fingerprint(&self, path: &Path) -> Option<Fingerprint> {
|
fn path_to_fingerprint(&self, path: &Path) -> Option<Fingerprint> {
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -122,6 +132,17 @@ impl Filesystem {
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Email the given path is pointing to.
|
||||||
|
fn path_to_email(&self, path: &Path) -> Option<Email> {
|
||||||
|
use std::str::FromStr;
|
||||||
|
let rest = path.file_name()?;
|
||||||
|
let prefix = path.parent()?.file_name()?;
|
||||||
|
let joined = format!("{}{}", prefix.to_str()?, rest.to_str()?);
|
||||||
|
let decoded = url::form_urlencoded::parse(joined.as_bytes())
|
||||||
|
.next()?.0;
|
||||||
|
Email::from_str(&decoded).ok()
|
||||||
|
}
|
||||||
|
|
||||||
fn new_token<'a>(&self, base: &'a str) -> Result<(File, String)> {
|
fn new_token<'a>(&self, base: &'a str) -> Result<(File, String)> {
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
@ -151,6 +172,194 @@ impl Filesystem {
|
||||||
remove_file(path)?;
|
remove_file(path)?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks the database for consistency.
|
||||||
|
///
|
||||||
|
/// Note that this operation may take a long time, and is
|
||||||
|
/// generally only useful for testing.
|
||||||
|
pub fn check_consistency(&self) -> Result<()> {
|
||||||
|
use std::fs;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use failure::format_err;
|
||||||
|
|
||||||
|
// A cache of all TPKs, for quick lookups.
|
||||||
|
let mut tpks = HashMap::new();
|
||||||
|
|
||||||
|
// Check Fingerprints.
|
||||||
|
for entry in fs::read_dir(&self.base_by_fingerprint)? {
|
||||||
|
let prefix = entry?;
|
||||||
|
let prefix_path = prefix.path();
|
||||||
|
if ! prefix_path.is_dir() {
|
||||||
|
return Err(format_err!("{:?} is not a directory", prefix_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in fs::read_dir(prefix_path)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
let typ = fs::symlink_metadata(&path)?.file_type();
|
||||||
|
|
||||||
|
// The Fingerprint corresponding with this path.
|
||||||
|
let fp = self.path_to_fingerprint(&path)
|
||||||
|
.ok_or_else(|| format_err!("Malformed path: {:?}", path))?;
|
||||||
|
|
||||||
|
// Compute the corresponding primary fingerprint just
|
||||||
|
// by looking at the paths.
|
||||||
|
let primary_fp = match () {
|
||||||
|
_ if typ.is_file() =>
|
||||||
|
fp.clone(),
|
||||||
|
_ if typ.is_symlink() =>
|
||||||
|
self.path_to_fingerprint(&path.read_link()?)
|
||||||
|
.ok_or_else(
|
||||||
|
|| format_err!("Malformed path: {:?}",
|
||||||
|
path.read_link().unwrap()))?,
|
||||||
|
_ => return
|
||||||
|
Err(format_err!("{:?} is neither a file nor a symlink \
|
||||||
|
but a {:?}", path, typ)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load into cache.
|
||||||
|
if ! tpks.contains_key(&primary_fp) {
|
||||||
|
tpks.insert(
|
||||||
|
primary_fp.clone(),
|
||||||
|
self.lookup(&Query::ByFingerprint(primary_fp.clone()))
|
||||||
|
?.ok_or_else(
|
||||||
|
|| format_err!("No TPK with fingerprint {:?}",
|
||||||
|
primary_fp))?);
|
||||||
|
}
|
||||||
|
let tpk = tpks.get(&primary_fp).unwrap();
|
||||||
|
|
||||||
|
if typ.is_file() {
|
||||||
|
let tpk_primary_fp =
|
||||||
|
tpk.primary().fingerprint().try_into().unwrap();
|
||||||
|
if fp != tpk_primary_fp {
|
||||||
|
return Err(format_err!(
|
||||||
|
"{:?} points to the wrong TPK, expected {} \
|
||||||
|
but found {}",
|
||||||
|
path, fp, tpk_primary_fp));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut found = false;
|
||||||
|
for skb in tpk.subkeys() {
|
||||||
|
if Fingerprint::try_from(skb.subkey().fingerprint())
|
||||||
|
.unwrap() == fp
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ! found {
|
||||||
|
return Err(format_err!(
|
||||||
|
"{:?} points to the wrong TPK, the TPK does not \
|
||||||
|
contain the subkey {}", path, fp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check KeyIDs.
|
||||||
|
for entry in fs::read_dir(&self.base_by_keyid)? {
|
||||||
|
let prefix = entry?;
|
||||||
|
let prefix_path = prefix.path();
|
||||||
|
if ! prefix_path.is_dir() {
|
||||||
|
return Err(format_err!("{:?} is not a directory", prefix_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in fs::read_dir(prefix_path)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
let typ = fs::symlink_metadata(&path)?.file_type();
|
||||||
|
|
||||||
|
// The KeyID corresponding with this path.
|
||||||
|
let id = self.path_to_keyid(&path)
|
||||||
|
.ok_or_else(|| format_err!("Malformed path: {:?}", path))?;
|
||||||
|
|
||||||
|
// Compute the corresponding primary fingerprint just
|
||||||
|
// by looking at the paths.
|
||||||
|
let primary_fp = match () {
|
||||||
|
_ if typ.is_symlink() =>
|
||||||
|
self.path_to_fingerprint(&path.read_link()?)
|
||||||
|
.ok_or_else(
|
||||||
|
|| format_err!("Malformed path: {:?}",
|
||||||
|
path.read_link().unwrap()))?,
|
||||||
|
_ => return
|
||||||
|
Err(format_err!("{:?} is not a symlink but a {:?}",
|
||||||
|
path, typ)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tpk = tpks.get(&primary_fp)
|
||||||
|
.ok_or_else(
|
||||||
|
|| format_err!("Broken symlink {:?}: No such Key {}",
|
||||||
|
path, primary_fp))?;
|
||||||
|
|
||||||
|
let mut found = false;
|
||||||
|
for (_, _, key) in tpk.keys() {
|
||||||
|
if KeyID::try_from(key.fingerprint()).unwrap() == id
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ! found {
|
||||||
|
return Err(format_err!(
|
||||||
|
"{:?} points to the wrong TPK, the TPK does not \
|
||||||
|
contain the (sub)key {}", path, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Emails.
|
||||||
|
for entry in fs::read_dir(&self.base_by_email)? {
|
||||||
|
let prefix = entry?;
|
||||||
|
let prefix_path = prefix.path();
|
||||||
|
if ! prefix_path.is_dir() {
|
||||||
|
return Err(format_err!("{:?} is not a directory", prefix_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in fs::read_dir(prefix_path)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
let typ = fs::symlink_metadata(&path)?.file_type();
|
||||||
|
|
||||||
|
// The Email corresponding with this path.
|
||||||
|
let email = self.path_to_email(&path)
|
||||||
|
.ok_or_else(|| format_err!("Malformed path: {:?}", path))?;
|
||||||
|
|
||||||
|
// Compute the corresponding primary fingerprint just
|
||||||
|
// by looking at the paths.
|
||||||
|
let primary_fp = match () {
|
||||||
|
_ if typ.is_symlink() =>
|
||||||
|
self.path_to_fingerprint(&path.read_link()?)
|
||||||
|
.ok_or_else(
|
||||||
|
|| format_err!("Malformed path: {:?}",
|
||||||
|
path.read_link().unwrap()))?,
|
||||||
|
_ => return
|
||||||
|
Err(format_err!("{:?} is not a symlink but a {:?}",
|
||||||
|
path, typ)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tpk = tpks.get(&primary_fp)
|
||||||
|
.ok_or_else(
|
||||||
|
|| format_err!("Broken symlink {:?}: No such Key {}",
|
||||||
|
path, primary_fp))?;
|
||||||
|
|
||||||
|
let mut found = false;
|
||||||
|
for uidb in tpk.userids() {
|
||||||
|
if Email::try_from(uidb.userid()).unwrap() == email
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ! found {
|
||||||
|
return Err(format_err!(
|
||||||
|
"{:?} points to the wrong TPK, the TPK does not \
|
||||||
|
contain the email {}", path, email));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like `symlink`, but instead of failing if `symlink_name` already
|
// Like `symlink`, but instead of failing if `symlink_name` already
|
||||||
|
@ -457,6 +666,13 @@ impl Database for Filesystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Drop for Filesystem {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.check_consistency().expect("inconsistent database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -807,6 +807,15 @@ mod tests {
|
||||||
Ok((root, config))
|
Ok((root, config))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_consistency(rocket: &rocket::Rocket) {
|
||||||
|
let db = rocket.state::<Polymorphic>().unwrap();
|
||||||
|
if let Polymorphic::Filesystem(fs) = db {
|
||||||
|
fs.check_consistency().unwrap();
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basics() {
|
fn basics() {
|
||||||
let (_tmpdir, config) = configuration().unwrap();
|
let (_tmpdir, config) = configuration().unwrap();
|
||||||
|
@ -833,6 +842,8 @@ mod tests {
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
assert_eq!(response.content_type(), Some(ContentType::HTML));
|
||||||
assert!(response.body_string().unwrap().contains("/vks/v1/by-keyid"));
|
assert!(response.body_string().unwrap().contains("/vks/v1/by-keyid"));
|
||||||
|
|
||||||
|
assert_consistency(client.rocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -891,6 +902,8 @@ mod tests {
|
||||||
&client,
|
&client,
|
||||||
"/pks/lookup?op=get&search=foo@invalid.example.com",
|
"/pks/lookup?op=get&search=foo@invalid.example.com",
|
||||||
&tpk);
|
&tpk);
|
||||||
|
|
||||||
|
assert_consistency(client.rocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -946,6 +959,8 @@ mod tests {
|
||||||
&client,
|
&client,
|
||||||
"/vks/v1/by-email/bar@invalid.example.com",
|
"/vks/v1/by-email/bar@invalid.example.com",
|
||||||
&tpk_1, 1);
|
&tpk_1, 1);
|
||||||
|
|
||||||
|
assert_consistency(client.rocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts that the given URI 404s.
|
/// Asserts that the given URI 404s.
|
||||||
|
@ -1116,6 +1131,8 @@ mod tests {
|
||||||
|
|
||||||
// And check that we can see the human-readable result page.
|
// And check that we can see the human-readable result page.
|
||||||
check_hr_responses_by_fingerprint(&client, &tpk);
|
check_hr_responses_by_fingerprint(&client, &tpk);
|
||||||
|
|
||||||
|
assert_consistency(client.rocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -1162,6 +1179,8 @@ mod tests {
|
||||||
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
|
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
|
||||||
check_hr_responses_by_fingerprint(&client, &tpk_0);
|
check_hr_responses_by_fingerprint(&client, &tpk_0);
|
||||||
check_hr_responses_by_fingerprint(&client, &tpk_1);
|
check_hr_responses_by_fingerprint(&client, &tpk_1);
|
||||||
|
|
||||||
|
assert_consistency(client.rocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns and removes the first mail it finds from the given
|
/// Returns and removes the first mail it finds from the given
|
||||||
|
|
Loading…
Reference in a new issue