use rustfmt to format source

This commit is contained in:
Kai Michaelis 2019-02-07 20:58:31 +01:00
parent 4612a96b23
commit 53719c2166
11 changed files with 708 additions and 431 deletions

6
rustfmt.toml Normal file
View File

@ -0,0 +1,6 @@
overflow_delimited_expr = true
unstable_features = true
use_small_heuristics = "Max"
fn_args_density = "Compressed"
max_width = 80
force_multiline_blocks = true

View File

@ -1,16 +1,16 @@
use std::str;
use std::path::PathBuf;
use std::fs::{File, remove_file, create_dir_all, read_link};
use std::io::{Write, Read};
use std::fs::{create_dir_all, read_link, remove_file, File};
use std::io::{Read, Write};
use std::os::unix::fs::symlink;
use std::path::PathBuf;
use std::str;
use tempfile;
use serde_json;
use tempfile;
use url;
use database::{Verify, Delete, Database};
use Result;
use database::{Database, Delete, Verify};
use types::{Email, Fingerprint, KeyID};
use Result;
pub struct Filesystem {
base: PathBuf,
@ -28,17 +28,29 @@ impl Filesystem {
match meta {
Ok(meta) => {
if !meta.file_type().is_dir() {
return Err(format!("'{}' exists already and is not a directory",
base.display()).into());
return Err(format!(
"'{}' exists already and is not a directory",
base.display()
)
.into());
}
if meta.permissions().readonly() {
return Err(format!("Cannot write '{}'", base.display()).into());
return Err(format!(
"Cannot write '{}'",
base.display()
)
.into());
}
}
Err(e) => {
return Err(format!("Cannot read '{}': {}", base.display(),e).into());
return Err(format!(
"Cannot read '{}': {}",
base.display(),
e
)
.into());
}
}
}
@ -52,14 +64,12 @@ impl Filesystem {
create_dir_all(base.join("public").join("by-kid"))?;
info!("Opened base dir '{}'", base.display());
Ok(Filesystem{
base: base,
})
Ok(Filesystem { base: base })
}
fn new_token<'a>(&self, base: &'a str) -> Result<(File, String)> {
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
// samples from [a-zA-Z0-9]
@ -71,7 +81,9 @@ impl Filesystem {
Ok((fd, name))
}
fn pop_token<'a>(&self, base: &'a str, token: &'a str) -> Result<Box<[u8]>> {
fn pop_token<'a>(
&self, base: &'a str, token: &'a str,
) -> Result<Box<[u8]>> {
let path = self.base.join(base).join(token);
let buf = {
let mut fd = File::open(path.clone())?;
@ -101,8 +113,11 @@ impl Database for Filesystem {
Ok(name)
}
fn compare_and_swap(&self, fpr: &Fingerprint, old: Option<&[u8]>, new: Option<&[u8]>) -> Result<bool> {
let target = self.base.join("public").join("by-fpr").join(fpr.to_string());
fn compare_and_swap(
&self, fpr: &Fingerprint, old: Option<&[u8]>, new: Option<&[u8]>,
) -> Result<bool> {
let target =
self.base.join("public").join("by-fpr").join(fpr.to_string());
let dir = self.base.join("scratch_pad");
match new {
@ -114,15 +129,20 @@ impl Database for Filesystem {
tmp.write_all(new)?;
if target.is_file() {
if old.is_some() { remove_file(target.clone())?; }
else { return Err(format!("stray file {}", target.display()).into()); }
if old.is_some() {
remove_file(target.clone())?;
} else {
return Err(
format!("stray file {}", target.display()).into()
);
}
}
let _ = tmp.persist(&target)?;
// fix permissions to 640
if cfg!(unix) {
use std::os::unix::fs::PermissionsExt;
use std::fs::{set_permissions, Permissions};
use std::os::unix::fs::PermissionsExt;
let perm = Permissions::from_mode(0o640);
set_permissions(target, perm)?;
@ -138,8 +158,11 @@ impl Database for Filesystem {
}
fn link_email(&self, email: &Email, fpr: &Fingerprint) {
let email = url::form_urlencoded::byte_serialize(email.to_string().as_bytes()).collect::<String>();
let target = self.base.join("public").join("by-fpr").join(fpr.to_string());
let email =
url::form_urlencoded::byte_serialize(email.to_string().as_bytes())
.collect::<String>();
let target =
self.base.join("public").join("by-fpr").join(fpr.to_string());
let link = self.base.join("public").join("by-email").join(email);
if link.exists() {
@ -150,12 +173,18 @@ impl Database for Filesystem {
}
fn unlink_email(&self, email: &Email, fpr: &Fingerprint) {
let email = url::form_urlencoded::byte_serialize(email.to_string().as_bytes()).collect::<String>();
let email =
url::form_urlencoded::byte_serialize(email.to_string().as_bytes())
.collect::<String>();
let link = self.base.join("public").join("by-email").join(email);
match read_link(link.clone()) {
Ok(target) => {
let expected = self.base.join("public").join("by-fpr").join(fpr.to_string());
let expected = self
.base
.join("public")
.join("by-fpr")
.join(fpr.to_string());
if target == expected {
let _ = remove_file(link);
@ -166,8 +195,10 @@ impl Database for Filesystem {
}
fn link_kid(&self, kid: &KeyID, fpr: &Fingerprint) {
let target = self.base.join("public").join("by-fpr").join(fpr.to_string());
let link = self.base.join("public").join("by-kid").join(kid.to_string());
let target =
self.base.join("public").join("by-fpr").join(fpr.to_string());
let link =
self.base.join("public").join("by-kid").join(kid.to_string());
if link.exists() {
let _ = remove_file(link.clone());
@ -177,11 +208,16 @@ impl Database for Filesystem {
}
fn unlink_kid(&self, kid: &KeyID, fpr: &Fingerprint) {
let link = self.base.join("public").join("by-kid").join(kid.to_string());
let link =
self.base.join("public").join("by-kid").join(kid.to_string());
match read_link(link.clone()) {
Ok(target) => {
let expected = self.base.join("public").join("by-fpr").join(fpr.to_string());
let expected = self
.base
.join("public")
.join("by-fpr")
.join(fpr.to_string());
if target == expected {
let _ = remove_file(link);
@ -192,10 +228,14 @@ impl Database for Filesystem {
}
fn link_fpr(&self, from: &Fingerprint, fpr: &Fingerprint) {
let target = self.base.join("public").join("by-fpr").join(fpr.to_string());
let link = self.base.join("public").join("by-fpr").join(from.to_string());
let target =
self.base.join("public").join("by-fpr").join(fpr.to_string());
let link =
self.base.join("public").join("by-fpr").join(from.to_string());
if link == target { return; }
if link == target {
return;
}
if link.exists() {
match link.metadata() {
Ok(ref meta) if meta.file_type().is_symlink() => {
@ -209,11 +249,16 @@ impl Database for Filesystem {
}
fn unlink_fpr(&self, from: &Fingerprint, fpr: &Fingerprint) {
let link = self.base.join("public").join("by-fpr").join(from.to_string());
let link =
self.base.join("public").join("by-fpr").join(from.to_string());
match read_link(link.clone()) {
Ok(target) => {
let expected = self.base.join("public").join("by-fpr").join(fpr.to_string());
let expected = self
.base
.join("public")
.join("by-fpr")
.join(fpr.to_string());
if target == expected {
let _ = remove_file(link);
@ -224,25 +269,26 @@ impl Database for Filesystem {
}
fn pop_verify_token(&self, token: &str) -> Option<Verify> {
self.pop_token("verification_tokens", token).ok().and_then(|raw| {
str::from_utf8(&raw).ok().map(|s| s.to_string())
}).and_then(|s| {
let s = serde_json::from_str(&s);
s.ok()
})
self.pop_token("verification_tokens", token)
.ok()
.and_then(|raw| str::from_utf8(&raw).ok().map(|s| s.to_string()))
.and_then(|s| {
let s = serde_json::from_str(&s);
s.ok()
})
}
fn pop_delete_token(&self, token: &str) -> Option<Delete> {
self.pop_token("deletion_tokens", token).ok().and_then(|raw| {
str::from_utf8(&raw).ok().map(|s| s.to_string())
}).and_then(|s| {
serde_json::from_str(&s).ok()
})
self.pop_token("deletion_tokens", token)
.ok()
.and_then(|raw| str::from_utf8(&raw).ok().map(|s| s.to_string()))
.and_then(|s| serde_json::from_str(&s).ok())
}
// XXX: slow
fn by_fpr(&self, fpr: &Fingerprint) -> Option<Box<[u8]>> {
let target = self.base.join("public").join("by-fpr").join(fpr.to_string());
let target =
self.base.join("public").join("by-fpr").join(fpr.to_string());
File::open(target).ok().and_then(|mut fd| {
let mut buf = Vec::default();
@ -258,19 +304,24 @@ impl Database for Filesystem {
fn by_email(&self, email: &Email) -> Option<Box<[u8]>> {
use std::fs;
let email = url::form_urlencoded::byte_serialize(email.to_string().as_bytes()).collect::<String>();
let email =
url::form_urlencoded::byte_serialize(email.to_string().as_bytes())
.collect::<String>();
let path = self.base.join("public").join("by-email").join(email);
fs::canonicalize(path).ok()
.and_then(|p| {
if p.starts_with(&self.base) {
Some(p)
} else {
None
}
}).and_then(|p| {
File::open(p).ok()
}).and_then(|mut fd| {
fs::canonicalize(path)
.ok()
.and_then(
|p| {
if p.starts_with(&self.base) {
Some(p)
} else {
None
}
},
)
.and_then(|p| File::open(p).ok())
.and_then(|mut fd| {
let mut buf = Vec::default();
if fd.read_to_end(&mut buf).is_ok() {
Some(buf.into_boxed_slice())
@ -284,18 +335,22 @@ impl Database for Filesystem {
fn by_kid(&self, kid: &KeyID) -> Option<Box<[u8]>> {
use std::fs;
let path = self.base.join("public").join("by-kid").join(kid.to_string());
let path =
self.base.join("public").join("by-kid").join(kid.to_string());
fs::canonicalize(path).ok()
.and_then(|p| {
if p.starts_with(&self.base) {
Some(p)
} else {
None
}
}).and_then(|p| {
File::open(p).ok()
}).and_then(|mut fd| {
fs::canonicalize(path)
.ok()
.and_then(
|p| {
if p.starts_with(&self.base) {
Some(p)
} else {
None
}
},
)
.and_then(|p| File::open(p).ok())
.and_then(|mut fd| {
let mut buf = Vec::default();
if fd.read_to_end(&mut buf).is_ok() {
Some(buf.into_boxed_slice())
@ -309,9 +364,9 @@ impl Database for Filesystem {
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
use sequoia_openpgp::tpk::TPKBuilder;
use database::test;
use sequoia_openpgp::tpk::TPKBuilder;
use tempfile::TempDir;
#[test]
fn init() {

View File

@ -1,7 +1,7 @@
use std::collections::HashMap;
use parking_lot::Mutex;
use std::collections::HashMap;
use database::{Verify, Delete, Database};
use database::{Database, Delete, Verify};
use types::{Email, Fingerprint, KeyID};
use Result;
@ -17,7 +17,7 @@ pub struct Memory {
impl Default for Memory {
fn default() -> Self {
Memory{
Memory {
fpr: Mutex::new(HashMap::default()),
fpr_links: Mutex::new(HashMap::default()),
kid: Mutex::new(HashMap::default()),
@ -43,7 +43,9 @@ impl Database for Memory {
Ok(token)
}
fn compare_and_swap(&self, fpr: &Fingerprint, present: Option<&[u8]>, new: Option<&[u8]>) -> Result<bool> {
fn compare_and_swap(
&self, fpr: &Fingerprint, present: Option<&[u8]>, new: Option<&[u8]>,
) -> Result<bool> {
let mut fprs = self.fpr.lock();
if fprs.get(fpr).map(|x| &x[..]) == present {
@ -119,8 +121,8 @@ impl Database for Memory {
impl Memory {
pub fn new_token() -> String {
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
// samples from [a-zA-Z0-9]
@ -132,8 +134,8 @@ impl Memory {
#[cfg(test)]
mod tests {
use super::*;
use sequoia_openpgp::tpk::TPKBuilder;
use database::test;
use sequoia_openpgp::tpk::TPKBuilder;
#[test]
fn new() {

View File

@ -1,12 +1,15 @@
use std::io::Cursor;
use std::convert::TryFrom;
use std::io::Cursor;
use std::result;
use sequoia_openpgp::{
constants::SignatureType, packet::Signature, packet::UserID, parse::Parse,
Packet, PacketPile, TPK,
};
use serde::{Deserialize, Deserializer, Serializer};
use time;
use sequoia_openpgp::{packet::Signature, TPK, packet::UserID, Packet, PacketPile, constants::SignatureType, parse::Parse};
use types::{Email, Fingerprint, KeyID};
use Result;
use types::{Fingerprint, Email, KeyID};
use serde::{Serializer, Deserializer, Deserialize};
mod fs;
pub use self::fs::Filesystem;
@ -18,7 +21,7 @@ pub use self::poly::Polymorphic;
#[cfg(test)]
mod test;
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Verify {
created: i64,
#[serde(serialize_with = "as_base64", deserialize_with = "from_base64")]
@ -28,36 +31,45 @@ pub struct Verify {
}
fn as_base64<S>(d: &Box<[u8]>, serializer: S) -> result::Result<S::Ok, S::Error>
where S: Serializer
where
S: Serializer,
{
serializer.serialize_str(&base64::encode(&d))
}
fn from_base64<'de, D>(deserializer: D) -> result::Result<Box<[u8]>, D::Error>
where D: Deserializer<'de>
where
D: Deserializer<'de>,
{
use serde::de::Error;
String::deserialize(deserializer)
.and_then(|string| base64::decode(&string).map_err(|err| Error::custom(err.to_string())))
.and_then(|string| {
base64::decode(&string)
.map_err(|err| Error::custom(err.to_string()))
})
.map(|bytes| bytes.into_boxed_slice())
}
impl Verify {
pub fn new(uid: &UserID, sig: &[&Signature], fpr: Fingerprint) -> Result<Self> {
pub fn new(
uid: &UserID, sig: &[&Signature], fpr: Fingerprint,
) -> Result<Self> {
use sequoia_openpgp::serialize::Serialize;
let mut cur = Cursor::new(Vec::default());
let res: Result<()> = uid.serialize(&mut cur)
let res: Result<()> = uid
.serialize(&mut cur)
.map_err(|e| format!("sequoia_openpgp: {}", e).into());
res?;
for s in sig {
let res: Result<()> = s.serialize(&mut cur)
let res: Result<()> = s
.serialize(&mut cur)
.map_err(|e| format!("sequoia_openpgp: {}", e).into());
res?;
}
Ok(Verify{
Ok(Verify {
created: time::now().to_timespec().sec,
packets: cur.into_inner().into(),
fpr: fpr,
@ -66,18 +78,15 @@ impl Verify {
}
}
#[derive(Serialize,Deserialize,Clone,Debug)]
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Delete {
created: i64,
fpr: Fingerprint
fpr: Fingerprint,
}
impl Delete {
pub fn new(fpr: Fingerprint) -> Self {
Delete{
created: time::now().to_timespec().sec,
fpr: fpr
}
Delete { created: time::now().to_timespec().sec, fpr: fpr }
}
}
@ -85,7 +94,9 @@ pub trait Database: Sync + Send {
fn new_verify_token(&self, payload: Verify) -> Result<String>;
fn new_delete_token(&self, payload: Delete) -> Result<String>;
fn compare_and_swap(&self, fpr: &Fingerprint, present: Option<&[u8]>, new: Option<&[u8]>) -> Result<bool>;
fn compare_and_swap(
&self, fpr: &Fingerprint, present: Option<&[u8]>, new: Option<&[u8]>,
) -> Result<bool>;
fn link_email(&self, email: &Email, fpr: &Fingerprint);
fn unlink_email(&self, email: &Email, fpr: &Fingerprint);
@ -106,30 +117,38 @@ pub trait Database: Sync + Send {
fn by_email(&self, email: &Email) -> Option<Box<[u8]>>;
fn strip_userids(tpk: TPK) -> Result<TPK> {
let pile = tpk.to_packet_pile().into_children().filter(|pkt| {
match pkt {
&Packet::PublicKey(_) | &Packet::PublicSubkey(_) => true,
&Packet::Signature(ref sig) => {
sig.sigtype() == SignatureType::DirectKey
|| sig.sigtype() == SignatureType::SubkeyBinding
let pile = tpk
.to_packet_pile()
.into_children()
.filter(|pkt| {
match pkt {
&Packet::PublicKey(_) | &Packet::PublicSubkey(_) => true,
&Packet::Signature(ref sig) => {
sig.sigtype() == SignatureType::DirectKey
|| sig.sigtype() == SignatureType::SubkeyBinding
}
_ => false,
}
_ => false,
}
}).collect::<Vec<_>>();
})
.collect::<Vec<_>>();
TPK::from_packet_pile(PacketPile::from_packets(pile))
.map_err(|e| format!("openpgp: {}", e).into())
}
fn tpk_into_bytes(tpk: &TPK) -> Result<Vec<u8>> {
use std::io::Cursor;
use sequoia_openpgp::serialize::Serialize;
use std::io::Cursor;
let mut cur = Cursor::new(Vec::default());
tpk.serialize(&mut cur).map(|_| cur.into_inner()).map_err(|e| format!("{}", e).into())
tpk.serialize(&mut cur)
.map(|_| cur.into_inner())
.map_err(|e| format!("{}", e).into())
}
fn link_subkeys(&self, fpr: &Fingerprint, subkeys: Vec<sequoia_openpgp::Fingerprint>) -> Result<()> {
fn link_subkeys(
&self, fpr: &Fingerprint, subkeys: Vec<sequoia_openpgp::Fingerprint>,
) -> Result<()> {
// link (subkey) kid & and subkey fpr
self.link_kid(&fpr.clone().into(), &fpr);
@ -164,22 +183,28 @@ pub trait Database: Sync + Send {
RevocationStatus::CouldBe(_) | RevocationStatus::Revoked(_) => {
revoked_uids.push(email);
}
RevocationStatus::NotAsFarAsWeKnow if self.by_email(&email).is_none() => {
RevocationStatus::NotAsFarAsWeKnow
if self.by_email(&email).is_none() =>
{
let payload = Verify::new(
uid.userid(),
&uid.selfsigs().collect::<Vec<_>>(),
fpr.clone())?;
fpr.clone(),
)?;
active_uids.push((email, self.new_verify_token(payload)?));
}
_ => {}
}
}
let subkeys = tpk.subkeys().map(|s| s.subkey().fingerprint()).collect::<Vec<_>>();
let subkeys =
tpk.subkeys().map(|s| s.subkey().fingerprint()).collect::<Vec<_>>();
tpk = Self::strip_userids(tpk)?;
for _ in 0..100 /* while cas failed */ {
for _ in 0..100
/* while cas failed */
{
// merge or update key db
match self.by_fpr(&fpr).map(|x| x.to_vec()) {
Some(old) => {
@ -206,7 +231,11 @@ pub trait Database: Sync + Send {
}
}
error!("Compare-and-swap of {} failed {} times in a row. Aborting.", fpr.to_string(), 100);
error!(
"Compare-and-swap of {} failed {} times in a row. Aborting.",
fpr.to_string(),
100
);
Err("Database update failed".into())
}
@ -217,19 +246,29 @@ pub trait Database: Sync + Send {
// cas(tpk, merged)
// }
// }
fn verify_token(&self, token: &str) -> Result<Option<(Email, Fingerprint)>> {
fn verify_token(
&self, token: &str,
) -> Result<Option<(Email, Fingerprint)>> {
match self.pop_verify_token(token) {
Some(Verify{ created, packets, fpr, email }) => {
Some(Verify { created, packets, fpr, email }) => {
let now = time::now().to_timespec().sec;
if created > now || now - created > 3 * 3600 { return Ok(None); }
if created > now || now - created > 3 * 3600 {
return Ok(None);
}
loop /* while cas falied */ {
loop
/* while cas falied */
{
match self.by_fpr(&fpr).map(|x| x.to_vec()) {
Some(old) => {
let mut new = old.clone();
new.extend(packets.into_iter());
if self.compare_and_swap(&fpr, Some(&old), Some(&new))? {
if self.compare_and_swap(
&fpr,
Some(&old),
Some(&new),
)? {
self.link_email(&email, &fpr);
return Ok(Some((email.clone(), fpr.clone())));
}
@ -244,7 +283,9 @@ pub trait Database: Sync + Send {
}
}
fn request_deletion(&self, fpr: Fingerprint) -> Result<(String, Vec<Email>)> {
fn request_deletion(
&self, fpr: Fingerprint,
) -> Result<(String, Vec<Email>)> {
match self.by_fpr(&fpr) {
Some(tpk) => {
let payload = Delete::new(fpr);
@ -252,12 +293,17 @@ pub trait Database: Sync + Send {
let tpk = match TPK::from_bytes(&tpk) {
Ok(tpk) => tpk,
Err(e) => {
return Err(format!("Failed to parse TPK: {:?}", e).into());
return Err(
format!("Failed to parse TPK: {:?}", e).into()
);
}
};
let emails = tpk.userids().filter_map(|uid| {
Email::try_from(uid.userid().clone()).ok()
}).collect::<Vec<_>>();
let emails = tpk
.userids()
.filter_map(|uid| {
Email::try_from(uid.userid().clone()).ok()
})
.collect::<Vec<_>>();
Ok((tok, emails))
}
@ -275,9 +321,11 @@ pub trait Database: Sync + Send {
// }
fn confirm_deletion(&self, token: &str) -> Result<bool> {
match self.pop_delete_token(token) {
Some(Delete{ created, fpr }) => {
Some(Delete { created, fpr }) => {
let now = time::now().to_timespec().sec;
if created > now || now - created > 3 * 3600 { return Ok(false); }
if created > now || now - created > 3 * 3600 {
return Ok(false);
}
loop {
match self.by_fpr(&fpr).map(|x| x.to_vec()) {
@ -285,15 +333,26 @@ pub trait Database: Sync + Send {
let tpk = match TPK::from_bytes(&old) {
Ok(tpk) => tpk,
Err(e) => {
return Err(format!("Failed to parse old TPK: {:?}", e).into());
return Err(format!(
"Failed to parse old TPK: {:?}",
e
)
.into());
}
};
for uid in tpk.userids() {
self.unlink_email(&Email::try_from(uid.userid().clone())?, &fpr);
self.unlink_email(
&Email::try_from(uid.userid().clone())?,
&fpr,
);
}
while !self.compare_and_swap(&fpr, Some(&old), None)? {}
while !self.compare_and_swap(
&fpr,
Some(&old),
None,
)? {}
return Ok(true);
}
None => {

View File

@ -1,6 +1,6 @@
use database::{Database, Delete, Filesystem, Memory, Verify};
use errors::Result;
use database::{Verify, Delete, Database, Filesystem, Memory};
use types::{Fingerprint, Email, KeyID};
use types::{Email, Fingerprint, KeyID};
pub enum Polymorphic {
Memory(Memory),
@ -22,10 +22,16 @@ impl Database for Polymorphic {
}
}
fn compare_and_swap(&self, fpr: &Fingerprint, present: Option<&[u8]>, new: Option<&[u8]>) -> Result<bool> {
fn compare_and_swap(
&self, fpr: &Fingerprint, present: Option<&[u8]>, new: Option<&[u8]>,
) -> Result<bool> {
match self {
&Polymorphic::Memory(ref db) => db.compare_and_swap(fpr, present, new),
&Polymorphic::Filesystem(ref db) => db.compare_and_swap(fpr, present, new),
&Polymorphic::Memory(ref db) => {
db.compare_and_swap(fpr, present, new)
}
&Polymorphic::Filesystem(ref db) => {
db.compare_and_swap(fpr, present, new)
}
}
}

View File

@ -9,7 +9,7 @@
// pub & verify
// req del one
// fetch by uid & fpr
// confirm
// confirm
// fetch by uid & fpr
// confirm again
// fetch by uid & fpr
@ -19,8 +19,11 @@ use std::str::FromStr;
use database::Database;
use sequoia_openpgp::tpk::{TPKBuilder, UserIDBinding};
use sequoia_openpgp::{Packet, packet::UserID, TPK, PacketPile, parse::Parse, RevocationStatus, constants::ReasonForRevocation, constants::SignatureType};
use types::{KeyID, Email, Fingerprint};
use sequoia_openpgp::{
constants::ReasonForRevocation, constants::SignatureType, packet::UserID,
parse::Parse, Packet, PacketPile, RevocationStatus, TPK,
};
use types::{Email, Fingerprint, KeyID};
pub fn test_uid_verification<D: Database>(db: &mut D) {
let str_uid1 = "Test A <test_a@example.com>";
@ -28,7 +31,9 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
let tpk = TPKBuilder::default()
.add_userid(str_uid1)
.add_userid(str_uid2)
.generate().unwrap().0;
.generate()
.unwrap()
.0;
let mut uid1 = UserID::new();
let mut uid2 = UserID::new();
@ -73,7 +78,9 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
let uid = key.userids().next().unwrap().userid().clone();
assert!((uid == uid1) ^ (uid == uid2));
let email = Email::from_str(&String::from_utf8(uid.userid().to_vec()).unwrap()).unwrap();
let email =
Email::from_str(&String::from_utf8(uid.userid().to_vec()).unwrap())
.unwrap();
assert_eq!(db.by_email(&email).unwrap(), raw);
if email1 == email {
@ -100,7 +107,9 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
let uid = key.userids().next().unwrap().userid().clone();
assert!((uid == uid1) ^ (uid == uid2));
let email = Email::from_str(&String::from_utf8(uid.userid().to_vec()).unwrap()).unwrap();
let email =
Email::from_str(&String::from_utf8(uid.userid().to_vec()).unwrap())
.unwrap();
assert_eq!(db.by_email(&email).unwrap(), raw);
if email1 == email {
@ -129,27 +138,34 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
assert_eq!(db.by_email(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).unwrap(), raw);
assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1)));
assert!(
((myuid1 == uid1) & (myuid2 == uid2))
^ ((myuid1 == uid2) & (myuid2 == uid1))
);
}
// upload again
assert_eq!(db.merge_or_publish(tpk.clone()).unwrap(), Vec::<(Email,String)>::default());
assert_eq!(
db.merge_or_publish(tpk.clone()).unwrap(),
Vec::<(Email, String)>::default()
);
// publish w/ one uid less
{
let packets = tpk.clone()
.to_packet_pile()
.into_children()
.filter(|pkt| {
let packets =
tpk.clone().to_packet_pile().into_children().filter(|pkt| {
match pkt {
Packet::UserID(ref uid) => *uid != uid1,
_ => true
_ => true,
}
});
let pile = PacketPile::from_packets(packets.collect());
let short_tpk = TPK::from_packet_pile(pile).unwrap();
assert_eq!(db.merge_or_publish(short_tpk.clone()).unwrap(), Vec::<(Email,String)>::default());
assert_eq!(
db.merge_or_publish(short_tpk.clone()).unwrap(),
Vec::<(Email, String)>::default()
);
// fetch by fpr
let raw = db.by_fpr(&fpr).unwrap();
@ -164,20 +180,25 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
assert_eq!(db.by_email(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).unwrap(), raw);
assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1)));
assert!(
((myuid1 == uid1) & (myuid2 == uid2))
^ ((myuid1 == uid2) & (myuid2 == uid1))
);
}
// publish w/one uid more
{
let mut packets = tpk.clone()
let mut packets = tpk
.clone()
.to_packet_pile()
.into_children()
.filter(|pkt| {
match pkt {
Packet::UserID(ref uid) => *uid != uid1,
_ => true
_ => true,
}
}).collect::<Vec<_>>();
})
.collect::<Vec<_>>();
let str_uid3 = "Test C <test_c@example.com>";
let mut uid3 = UserID::new();
uid3.set_userid_from_bytes(str_uid3.as_bytes());
@ -187,7 +208,8 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
let bind = UserIDBinding::new(key, uid3.clone(), key).unwrap();
packets.push(Packet::UserID(uid3.clone()));
packets.push(Packet::Signature(bind.selfsigs().next().unwrap().clone()));
packets
.push(Packet::Signature(bind.selfsigs().next().unwrap().clone()));
let pile = PacketPile::from_packets(packets);
let ext_tpk = TPK::from_packet_pile(pile).unwrap();
@ -208,7 +230,10 @@ pub fn test_uid_verification<D: Database>(db: &mut D) {
assert_eq!(db.by_email(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).unwrap(), raw);
assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1)));
assert!(
((myuid1 == uid1) & (myuid2 == uid2))
^ ((myuid1 == uid2) & (myuid2 == uid1))
);
assert!(db.by_email(&email3).is_none());
}
}
@ -219,7 +244,9 @@ pub fn test_uid_deletion<D: Database>(db: &mut D) {
let tpk = TPKBuilder::default()
.add_userid(str_uid1)
.add_userid(str_uid2)
.generate().unwrap().0;
.generate()
.unwrap()
.0;
let mut uid1 = UserID::new();
let mut uid2 = UserID::new();
@ -241,7 +268,7 @@ pub fn test_uid_deletion<D: Database>(db: &mut D) {
// req. deletion
let del = db.request_deletion(fpr.clone()).unwrap().0;
// check it's still there
// check it's still there
{
// fetch by fpr
let raw = db.by_fpr(&fpr).unwrap();
@ -256,7 +283,10 @@ pub fn test_uid_deletion<D: Database>(db: &mut D) {
assert_eq!(db.by_email(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).unwrap(), raw);
assert!(((myuid1 == uid1) & (myuid2 == uid2)) ^ ((myuid1 == uid2) & (myuid2 == uid1)));
assert!(
((myuid1 == uid1) & (myuid2 == uid2))
^ ((myuid1 == uid2) & (myuid2 == uid1))
);
}
// confirm deletion
@ -281,13 +311,21 @@ pub fn test_subkey_lookup<D: Database>(db: &mut D) {
.add_userid("Testy <test@example.com>")
.add_signing_subkey()
.add_encryption_subkey()
.generate().unwrap().0;
.generate()
.unwrap()
.0;
// upload key
let _ = db.merge_or_publish(tpk.clone()).unwrap();
let primary_fpr = Fingerprint::try_from(tpk.fingerprint()).unwrap();
let sub1_fpr = Fingerprint::try_from(tpk.subkeys().next().map(|x| x.subkey().fingerprint()).unwrap()).unwrap();
let sub2_fpr = Fingerprint::try_from(tpk.subkeys().skip(1).next().map(|x| x.subkey().fingerprint()).unwrap()).unwrap();
let sub1_fpr = Fingerprint::try_from(
tpk.subkeys().next().map(|x| x.subkey().fingerprint()).unwrap(),
)
.unwrap();
let sub2_fpr = Fingerprint::try_from(
tpk.subkeys().skip(1).next().map(|x| x.subkey().fingerprint()).unwrap(),
)
.unwrap();
let raw1 = db.by_fpr(&primary_fpr).unwrap();
let raw2 = db.by_fpr(&sub1_fpr).unwrap();
@ -302,13 +340,21 @@ pub fn test_kid_lookup<D: Database>(db: &mut D) {
.add_userid("Testy <test@example.com>")
.add_signing_subkey()
.add_encryption_subkey()
.generate().unwrap().0;
.generate()
.unwrap()
.0;
// upload key
let _ = db.merge_or_publish(tpk.clone()).unwrap();
let primary_kid = KeyID::try_from(tpk.fingerprint()).unwrap();
let sub1_kid = KeyID::try_from(tpk.subkeys().next().map(|x| x.subkey().fingerprint()).unwrap()).unwrap();
let sub2_kid = KeyID::try_from(tpk.subkeys().skip(1).next().map(|x| x.subkey().fingerprint()).unwrap()).unwrap();
let sub1_kid = KeyID::try_from(
tpk.subkeys().next().map(|x| x.subkey().fingerprint()).unwrap(),
)
.unwrap();
let sub2_kid = KeyID::try_from(
tpk.subkeys().skip(1).next().map(|x| x.subkey().fingerprint()).unwrap(),
)
.unwrap();
let raw1 = db.by_kid(&primary_kid).unwrap();
let raw2 = db.by_kid(&sub1_kid).unwrap();
@ -326,7 +372,9 @@ pub fn test_uid_revocation<D: Database>(db: &mut D) {
let tpk = TPKBuilder::default()
.add_userid(str_uid1)
.add_userid(str_uid2)
.generate().unwrap().0;
.generate()
.unwrap()
.0;
let mut uid1 = UserID::new();
let mut uid2 = UserID::new();
@ -356,9 +404,12 @@ pub fn test_uid_revocation<D: Database>(db: &mut D) {
assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revoked(None));
let mut keypair = tpk.primary().clone().into_keypair().unwrap();
uid.revoke(&mut keypair,
ReasonForRevocation::UIDRetired,
b"It was the maid :/").unwrap()
uid.revoke(
&mut keypair,
ReasonForRevocation::UIDRetired,
b"It was the maid :/",
)
.unwrap()
};
assert_eq!(sig.sigtype(), SignatureType::CertificateRevocation);
let tpk = tpk.merge_packets(vec![sig.to_packet()]).unwrap();

View File

@ -1,32 +1,37 @@
use handlebars::Handlebars;
use lettre::{SendmailTransport, EmailTransport};
use lettre::{EmailTransport, SendmailTransport};
use lettre_email::EmailBuilder;
use serde::Serialize;
use Result;
use types::Email;
use Result;
#[derive(Serialize, Clone)]
pub struct Context{
pub struct Context {
pub token: String,
pub userid: String,
pub domain: String,
}
fn send_mail<T>(to: &Email, subject: &str, mail_templates: &Handlebars,
template: &str, from: &str, ctx: T)
-> Result<()> where T: Serialize + Clone
fn send_mail<T>(
to: &Email, subject: &str, mail_templates: &Handlebars, template: &str,
from: &str, ctx: T,
) -> Result<()>
where
T: Serialize + Clone,
{
let tmpl_html = format!("{}-html", template);
let tmpl_txt = format!("{}-txt", template);
let (html, txt) = {
if let (Ok(inner_html), Ok(inner_txt)) =
(mail_templates.render(&tmpl_html, &ctx), mail_templates.render(&tmpl_txt, &ctx)) {
(Some(inner_html), Some(inner_txt))
} else {
(None, None)
}
if let (Ok(inner_html), Ok(inner_txt)) = (
mail_templates.render(&tmpl_html, &ctx),
mail_templates.render(&tmpl_txt, &ctx),
) {
(Some(inner_html), Some(inner_txt))
} else {
(None, None)
}
};
let email = EmailBuilder::new()
@ -35,38 +40,52 @@ fn send_mail<T>(to: &Email, subject: &str, mail_templates: &Handlebars,
.subject(subject)
.alternative(
html.ok_or("Email template failed to render")?,
txt.ok_or("Email template failed to render")?)
.build().unwrap();
txt.ok_or("Email template failed to render")?,
)
.build()
.unwrap();
let mut sender = SendmailTransport::new();
sender.send(&email)?;
Ok(())
}
pub fn send_verification_mail(userid: &Email, token: &str, mail_templates: &Handlebars,
domain: &str, from: &str)
-> Result<()>
{
let ctx = Context{
pub fn send_verification_mail(
userid: &Email, token: &str, mail_templates: &Handlebars, domain: &str,
from: &str,
) -> Result<()> {
let ctx = Context {
token: token.to_string(),
userid: userid.to_string(),
domain: domain.to_string(),
};
send_mail(userid, "Please verify your email address", mail_templates,
"verify", from, ctx)
send_mail(
userid,
"Please verify your email address",
mail_templates,
"verify",
from,
ctx,
)
}
pub fn send_confirmation_mail(userid: &Email, token: &str, mail_templates: &Handlebars,
domain: &str, from: &str)
-> Result<()>
{
let ctx = Context{
pub fn send_confirmation_mail(
userid: &Email, token: &str, mail_templates: &Handlebars, domain: &str,
from: &str,
) -> Result<()> {
let ctx = Context {
token: token.to_string(),
userid: userid.to_string(),
domain: domain.to_string(),
};
send_mail(userid, "Please confirm deletion of your key", mail_templates,
"confirm", from, ctx)
send_mail(
userid,
"Please confirm deletion of your key",
mail_templates,
"confirm",
from,
ctx,
)
}

View File

@ -3,36 +3,40 @@
#![feature(try_from)]
extern crate serde;
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate hex;
extern crate time;
extern crate url;
extern crate hex;
#[macro_use] extern crate rocket;
extern crate rocket_contrib;
#[macro_use]
extern crate rocket;
extern crate multipart;
extern crate rocket_contrib;
extern crate sequoia_openpgp;
#[macro_use] extern crate error_chain;
#[macro_use] extern crate log;
extern crate rand;
extern crate tempfile;
extern crate parking_lot;
extern crate structopt;
#[macro_use]
extern crate error_chain;
#[macro_use]
extern crate log;
extern crate base64;
extern crate handlebars;
extern crate lettre;
extern crate lettre_email;
extern crate handlebars;
extern crate base64;
extern crate parking_lot;
extern crate rand;
extern crate structopt;
extern crate tempfile;
mod web;
mod database;
mod types;
mod mail;
mod types;
mod web;
mod errors {
error_chain!{
error_chain! {
foreign_links {
Fmt(::std::fmt::Error);
Io(::std::io::Error);
@ -52,7 +56,10 @@ use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(name = "garbage", about = "Garbage Pile - The verifying OpenPGP key server.")]
#[structopt(
name = "garbage",
about = "Garbage Pile - The verifying OpenPGP key server."
)]
pub struct Opt {
/// More verbose output. Disabled when running as daemon.
#[structopt(short = "v", long = "verbose")]
@ -69,9 +76,13 @@ pub struct Opt {
/// FQDN of the server. Used in templates.
#[structopt(short = "D", long = "domain", default_value = "localhost")]
domain: String,
#[structopt(short = "F", long = "from", default_value = "noreply@localhost")]
#[structopt(
short = "F",
long = "from",
default_value = "noreply@localhost"
)]
from: String,
}
}
fn main() {
use database::{Filesystem, Polymorphic};

View File

@ -1,12 +1,12 @@
use std::str::FromStr;
use std::convert::TryFrom;
use std::result;
use std::str::FromStr;
use sequoia_openpgp::{self, packet::UserID};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use {Error, Result};
use serde::{Serialize, Serializer, Deserializer, Deserialize};
#[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Email(String);
impl TryFrom<UserID> for Email {
@ -20,7 +20,9 @@ impl TryFrom<UserID> for Email {
}
impl ToString for Email {
fn to_string(&self) -> String { self.0.clone() }
fn to_string(&self) -> String {
self.0.clone()
}
}
impl FromStr for Email {
@ -37,7 +39,7 @@ impl FromStr for Email {
}
}
#[derive(Clone,Debug,Hash,PartialEq,Eq)]
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Fingerprint([u8; 20]);
impl TryFrom<sequoia_openpgp::Fingerprint> for Fingerprint {
@ -46,7 +48,9 @@ impl TryFrom<sequoia_openpgp::Fingerprint> for Fingerprint {
fn try_from(fpr: sequoia_openpgp::Fingerprint) -> Result<Self> {
match fpr {
sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a)),
sequoia_openpgp::Fingerprint::Invalid(_) => Err("invalid fingerprint".into()),
sequoia_openpgp::Fingerprint::Invalid(_) => {
Err("invalid fingerprint".into())
}
}
}
}
@ -59,7 +63,8 @@ impl ToString for Fingerprint {
impl Serialize for Fingerprint {
fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
where S: Serializer
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
@ -67,11 +72,14 @@ impl Serialize for Fingerprint {
impl<'de> Deserialize<'de> for Fingerprint {
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
where D: Deserializer<'de>
where
D: Deserializer<'de>,
{
use serde::de::Error;
String::deserialize(deserializer)
.and_then(|string| Self::from_str(&string).map_err(|err| Error::custom(err.to_string())))
String::deserialize(deserializer).and_then(|string| {
Self::from_str(&string)
.map_err(|err| Error::custom(err.to_string()))
})
}
}
@ -95,7 +103,7 @@ impl FromStr for Fingerprint {
}
}
#[derive(Serialize,Deserialize,Clone,Debug,Hash,PartialEq,Eq)]
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
pub struct KeyID([u8; 8]);
impl TryFrom<sequoia_openpgp::Fingerprint> for KeyID {
@ -104,7 +112,9 @@ impl TryFrom<sequoia_openpgp::Fingerprint> for KeyID {
fn try_from(fpr: sequoia_openpgp::Fingerprint) -> Result<Self> {
match fpr {
sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a).into()),
sequoia_openpgp::Fingerprint::Invalid(_) => Err("invalid fingerprint".into()),
sequoia_openpgp::Fingerprint::Invalid(_) => {
Err("invalid fingerprint".into())
}
}
}
}

View File

@ -1,10 +1,10 @@
use rocket;
use rocket::{State, Outcome};
use rocket::http::Status;
use rocket::request::{self, Request, FromRequest};
use rocket::response::status::Custom;
use rocket::response::{Response, NamedFile};
use rocket::fairing::AdHoc;
use rocket::http::Status;
use rocket::request::{self, FromRequest, Request};
use rocket::response::status::Custom;
use rocket::response::{NamedFile, Response};
use rocket::{Outcome, State};
use rocket_contrib::templates::Template;
use handlebars::Handlebars;
@ -12,21 +12,21 @@ use std::path::{Path, PathBuf};
mod upload;
use database::{Polymorphic, Database};
use types::{Fingerprint, Email, KeyID};
use database::{Database, Polymorphic};
use errors::Result;
use types::{Email, Fingerprint, KeyID};
use Opt;
use std::str::FromStr;
use std::result;
use std::str::FromStr;
mod queries {
use types::{Fingerprint, Email};
use types::{Email, Fingerprint};
#[derive(Debug)]
pub enum Hkp {
Fingerprint{ fpr: Fingerprint, index: bool },
Email{ email: Email, index: bool },
Fingerprint { fpr: Fingerprint, index: bool },
Email { email: Email, index: bool },
}
}
@ -58,35 +58,46 @@ pub struct MailTemplates(Handlebars);
impl<'a, 'r> FromRequest<'a, 'r> for queries::Hkp {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<queries::Hkp, ()> {
fn from_request(
request: &'a Request<'r>,
) -> request::Outcome<queries::Hkp, ()> {
use rocket::request::FormItems;
use std::collections::HashMap;
let query = request.uri().query().unwrap_or("");
let fields = FormItems::from(query).map(|item| {
let fields = FormItems::from(query)
.map(|item| {
let (k, v) = item.key_value();
let (k, v) = item.key_value();
let key = k.url_decode().unwrap_or_default();
let value = v.url_decode().unwrap_or_default();
(key, value)
})
.collect::<HashMap<_, _>>();
let key = k.url_decode().unwrap_or_default();
let value = v.url_decode().unwrap_or_default();
(key, value)
}).collect::<HashMap<_,_>>();
if fields.len() >= 2 && fields.get("op").map(|x| x == "get" || x == "index").unwrap_or(false) {
let index = fields.get("op").map(|x| x == "index")
.unwrap_or(false);
if fields.len() >= 2
&& fields
.get("op")
.map(|x| x == "get" || x == "index")
.unwrap_or(false)
{
let index = fields.get("op").map(|x| x == "index").unwrap_or(false);
let search = fields.get("search").cloned().unwrap_or_default();
let maybe_fpr = Fingerprint::from_str(&search);
if let Ok(fpr) = maybe_fpr {
Outcome::Success(queries::Hkp::Fingerprint{
fpr: fpr, index: index
Outcome::Success(queries::Hkp::Fingerprint {
fpr: fpr,
index: index,
})
} else {
match Email::from_str(&search) {
Ok(email) => Outcome::Success(queries::Hkp::Email{
email: email, index: index
}),
Ok(email) => {
Outcome::Success(queries::Hkp::Email {
email: email,
index: index,
})
}
Err(_) => Outcome::Failure((Status::BadRequest, ())),
}
}
@ -96,11 +107,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for queries::Hkp {
}
}
fn key_to_response<'a,'b>(bytes: &'a[u8]) -> Response<'b> {
use std::io::Write;
use sequoia_openpgp::armor::{Writer, Kind};
fn key_to_response<'a, 'b>(bytes: &'a [u8]) -> Response<'b> {
use rocket::http::{ContentType, Status};
use sequoia_openpgp::armor::{Kind, Writer};
use std::io::Cursor;
use rocket::http::{Status, ContentType};
use std::io::Write;
let key = || -> Result<String> {
let mut buffer = Vec::default();
@ -113,27 +124,27 @@ fn key_to_response<'a,'b>(bytes: &'a[u8]) -> Response<'b> {
}();
match key {
Ok(s) =>
Ok(s) => {
Response::build()
.status(Status::Ok)
.header(ContentType::new("application", "pgp-keys"))
.sized_body(Cursor::new(s))
.finalize(),
Err(_) =>
.status(Status::Ok)
.header(ContentType::new("application", "pgp-keys"))
.sized_body(Cursor::new(s))
.finalize()
}
Err(_) => {
Response::build()
.status(Status::InternalServerError)
.header(ContentType::Plain)
.sized_body(Cursor::new("Failed to ASCII armor key"))
.finalize(),
.status(Status::InternalServerError)
.header(ContentType::Plain)
.sized_body(Cursor::new("Failed to ASCII armor key"))
.finalize()
}
}
}
#[get("/by-fpr/<fpr>")]
fn by_fpr(db: rocket::State<Polymorphic>, fpr: String)
-> Response
{
fn by_fpr(db: rocket::State<Polymorphic>, fpr: String) -> Response {
use rocket::http::{ContentType, Status};
use std::io::Cursor;
use rocket::http::{Status, ContentType};
let maybe_key = match Fingerprint::from_str(&fpr) {
Ok(ref fpr) => db.by_fpr(fpr),
@ -142,21 +153,20 @@ fn by_fpr(db: rocket::State<Polymorphic>, fpr: String)
match maybe_key {
Some(ref bytes) => key_to_response(bytes),
None =>
None => {
Response::build()
.status(Status::NotFound)
.header(ContentType::Plain)
.sized_body(Cursor::new("No such key :-("))
.finalize(),
.status(Status::NotFound)
.header(ContentType::Plain)
.sized_body(Cursor::new("No such key :-("))
.finalize()
}
}
}
#[get("/by-email/<email>")]
fn by_email(db: rocket::State<Polymorphic>, email: String)
-> Response
{
fn by_email(db: rocket::State<Polymorphic>, email: String) -> Response {
use rocket::http::{ContentType, Status};
use std::io::Cursor;
use rocket::http::{Status, ContentType};
let maybe_key = match Email::from_str(&email) {
Ok(ref email) => db.by_email(email),
@ -165,21 +175,20 @@ fn by_email(db: rocket::State<Polymorphic>, email: String)
match maybe_key {
Some(ref bytes) => key_to_response(bytes),
None =>
None => {
Response::build()
.status(Status::NotFound)
.header(ContentType::Plain)
.sized_body(Cursor::new("No such key :-("))
.finalize(),
.status(Status::NotFound)
.header(ContentType::Plain)
.sized_body(Cursor::new("No such key :-("))
.finalize()
}
}
}
#[get("/by-kid/<kid>")]
fn by_kid(db: rocket::State<Polymorphic>, kid: String)
-> Response
{
fn by_kid(db: rocket::State<Polymorphic>, kid: String) -> Response {
use rocket::http::{ContentType, Status};
use std::io::Cursor;
use rocket::http::{Status, ContentType};
let maybe_key = match KeyID::from_str(&kid) {
Ok(ref key) => db.by_kid(key),
@ -187,26 +196,24 @@ fn by_kid(db: rocket::State<Polymorphic>, kid: String)
};
match maybe_key {
Some(ref bytes) => {
key_to_response(bytes)
}
None =>
Some(ref bytes) => key_to_response(bytes),
None => {
Response::build()
.status(Status::NotFound)
.header(ContentType::Plain)
.sized_body(Cursor::new("No such key :-("))
.finalize(),
.status(Status::NotFound)
.header(ContentType::Plain)
.sized_body(Cursor::new("No such key :-("))
.finalize()
}
}
}
#[get("/vks/verify/<token>")]
fn verify(db: rocket::State<Polymorphic>, token: String)
-> result::Result<Template, Custom<String>>
{
fn verify(
db: rocket::State<Polymorphic>, token: String,
) -> result::Result<Template, Custom<String>> {
match db.verify_token(&token) {
Ok(Some((userid, fpr))) => {
let context = templates::Verify{
let context = templates::Verify {
verified: true,
userid: userid.to_string(),
fpr: fpr.to_string(),
@ -215,7 +222,7 @@ fn verify(db: rocket::State<Polymorphic>, token: String)
Ok(Template::render("verify", context))
}
Ok(None) | Err(_) => {
let context = templates::Verify{
let context = templates::Verify {
verified: false,
userid: "".into(),
fpr: "".into(),
@ -227,58 +234,56 @@ fn verify(db: rocket::State<Polymorphic>, token: String)
}
#[get("/vks/delete/<fpr>")]
fn delete(db: rocket::State<Polymorphic>, fpr: String,
tmpl: State<MailTemplates>, domain: State<Domain>, from: State<From>)
-> result::Result<Template, Custom<String>>
{
fn delete(
db: rocket::State<Polymorphic>, fpr: String, tmpl: State<MailTemplates>,
domain: State<Domain>, from: State<From>,
) -> result::Result<Template, Custom<String>> {
use mail::send_confirmation_mail;
let fpr = match Fingerprint::from_str(&fpr) {
Ok(fpr) => fpr,
Err(_) => {
return Err(Custom(Status::BadRequest,
"Invalid fingerprint".to_string()));
return Err(Custom(
Status::BadRequest,
"Invalid fingerprint".to_string(),
));
}
};
match db.request_deletion(fpr.clone()) {
Ok((token,uids)) => {
let context = templates::Delete{
Ok((token, uids)) => {
let context = templates::Delete {
fpr: fpr.to_string(),
token: token.clone(),
};
for uid in uids {
send_confirmation_mail(&uid, &token, &tmpl.0, &domain.0, &from.0)
.map_err(|err| {
Custom(Status::InternalServerError,
format!("{:?}", err))
})?;
send_confirmation_mail(
&uid, &token, &tmpl.0, &domain.0, &from.0,
)
.map_err(|err| {
Custom(Status::InternalServerError, format!("{:?}", err))
})?;
}
Ok(Template::render("delete", context))
}
Err(e) => Err(Custom(Status::InternalServerError,
format!("{}", e))),
Err(e) => Err(Custom(Status::InternalServerError, format!("{}", e))),
}
}
#[get("/vks/confirm/<token>")]
fn confirm(db: rocket::State<Polymorphic>, token: String)
-> result::Result<Template, Custom<String>>
{
fn confirm(
db: rocket::State<Polymorphic>, token: String,
) -> result::Result<Template, Custom<String>> {
match db.confirm_deletion(&token) {
Ok(true) => {
let context = templates::Confirm{
deleted: true,
};
let context = templates::Confirm { deleted: true };
Ok(Template::render("confirm", context))
}
Ok(false) | Err(_) => {
let context = templates::Confirm{
deleted: false,
};
let context = templates::Confirm { deleted: false };
Ok(Template::render("confirm", context))
}
@ -291,22 +296,24 @@ fn files(file: PathBuf, static_dir: State<StaticDir>) -> Option<NamedFile> {
}
#[get("/pks/lookup")]
fn lookup(db: rocket::State<Polymorphic>, key: Option<queries::Hkp>)
-> result::Result<String, Custom<String>>
{
use std::io::Write;
fn lookup(
db: rocket::State<Polymorphic>, key: Option<queries::Hkp>,
) -> result::Result<String, Custom<String>> {
use sequoia_openpgp::armor::{Kind, Writer};
use sequoia_openpgp::RevocationStatus;
use sequoia_openpgp::{TPK, parse::Parse};
use sequoia_openpgp::armor::{Writer, Kind};
use sequoia_openpgp::{parse::Parse, TPK};
use std::io::Write;
let (maybe_key,index) = match key {
Some(queries::Hkp::Fingerprint{ ref fpr, index }) => {
(db.by_fpr(fpr),index)
let (maybe_key, index) = match key {
Some(queries::Hkp::Fingerprint { ref fpr, index }) => {
(db.by_fpr(fpr), index)
}
Some(queries::Hkp::Email{ ref email, index }) => {
(db.by_email(email),index)
Some(queries::Hkp::Email { ref email, index }) => {
(db.by_email(email), index)
}
None => {
return Ok("nothing to do".to_string());
}
None => { return Ok("nothing to do".to_string()); }
};
match maybe_key {
@ -314,8 +321,8 @@ fn lookup(db: rocket::State<Polymorphic>, key: Option<queries::Hkp>)
let key = || -> Result<String> {
let mut buffer = Vec::default();
{
let mut writer = Writer::new(&mut buffer, Kind::PublicKey,
&[])?;
let mut writer =
Writer::new(&mut buffer, Kind::PublicKey, &[])?;
writer.write_all(&bytes)?;
}
@ -324,17 +331,20 @@ fn lookup(db: rocket::State<Polymorphic>, key: Option<queries::Hkp>)
match key {
Ok(s) => Ok(s),
Err(_) =>
Err(Custom(Status::InternalServerError,
"Failed to ASCII armor key".to_string())),
Err(_) => {
Err(Custom(
Status::InternalServerError,
"Failed to ASCII armor key".to_string(),
))
}
}
}
None if !index => Ok("No such key :-(".to_string()),
Some(ref bytes) if index => {
let tpk = TPK::from_bytes(bytes)
.map_err(|e| Custom(Status::InternalServerError,
format!("{}", e)))?;
let tpk = TPK::from_bytes(bytes).map_err(|e| {
Custom(Status::InternalServerError, format!("{}", e))
})?;
let mut out = String::default();
let p = tpk.primary();
@ -363,16 +373,21 @@ fn lookup(db: rocket::State<Polymorphic>, key: Option<queries::Hkp>)
let algo: u8 = p.pk_algo().into();
out.push_str("info:1:1\r\n");
out.push_str(&format!("pub:{}:{}:{}:{}:{}:{}{}\r\n",
p.fingerprint().to_string().replace(" ",""),
algo,
p.mpis().bits(),
ctime,extime,is_exp,is_rev));
out.push_str(&format!(
"pub:{}:{}:{}:{}:{}:{}{}\r\n",
p.fingerprint().to_string().replace(" ", ""),
algo,
p.mpis().bits(),
ctime,
extime,
is_exp,
is_rev
));
for uid in tpk.userids() {
let u =
url::form_urlencoded::byte_serialize(uid.userid().userid())
.fold(String::default(),|acc,x| acc + x);
.fold(String::default(), |acc, x| acc + x);
let ctime = uid
.binding_signature()
.and_then(|x| x.signature_creation_time())
@ -389,21 +404,22 @@ fn lookup(db: rocket::State<Polymorphic>, key: Option<queries::Hkp>)
if x.signature_expired() { "e" } else { "" }.into()
})
.unwrap_or_default();
let is_rev =
if uid.revoked(None) != RevocationStatus::NotAsFarAsWeKnow {
"r"
} else {
""
};
let is_rev = if uid.revoked(None)
!= RevocationStatus::NotAsFarAsWeKnow
{
"r"
} else {
""
};
out.push_str(&format!("uid:{}:{}:{}:{}{}\r\n",
u,ctime,extime,is_exp,is_rev));
out.push_str(&format!(
"uid:{}:{}:{}:{}{}\r\n",
u, ctime, extime, is_exp, is_rev
));
}
Ok(out)
}
None if index => {
Ok("info:1:0\r\n".into())
}
None if index => Ok("info:1:0\r\n".into()),
_ => unreachable!(),
}
@ -424,14 +440,14 @@ pub fn serve(opt: &Opt, db: Polymorphic) -> Result<()> {
Some(p) => {
let addr = opt.listen[0..p].to_string();
let port = if p < opt.listen.len() - 1 {
u16::from_str(&opt.listen[p+1..]).ok().unwrap_or(8080)
u16::from_str(&opt.listen[p + 1..]).ok().unwrap_or(8080)
} else {
8080
};
(addr, port)
}
None => (opt.listen.to_string(), 8080)
None => (opt.listen.to_string(), 8080),
};
let config = Config::build(Environment::Staging)
@ -439,10 +455,17 @@ pub fn serve(opt: &Opt, db: Polymorphic) -> Result<()> {
.port(port)
.workers(2)
.root(opt.base.clone())
.extra("template_dir", opt.base.join("templates").to_str()
.ok_or("Template path invalid")?)
.extra("static_dir", opt.base.join("public").to_str()
.ok_or("Static path invalid")?)
.extra(
"template_dir",
opt.base
.join("templates")
.to_str()
.ok_or("Template path invalid")?,
)
.extra(
"static_dir",
opt.base.join("public").to_str().ok_or("Static path invalid")?,
)
.extra("domain", opt.domain.clone())
.extra("from", opt.from.clone())
.finalize()?;
@ -466,31 +489,24 @@ pub fn serve(opt: &Opt, db: Polymorphic) -> Result<()> {
rocket::custom(config)
.attach(Template::fairing())
.attach(AdHoc::on_attach("static_dir", |rocket| {
let static_dir = rocket.config()
.get_str("static_dir")
.unwrap()
.to_string();
let static_dir =
rocket.config().get_str("static_dir").unwrap().to_string();
Ok(rocket.manage(StaticDir(static_dir)))
}))
.attach(AdHoc::on_attach("domain", |rocket| {
let domain = rocket.config()
.get_str("domain")
.unwrap()
.to_string();
let domain = rocket.config().get_str("domain").unwrap().to_string();
Ok(rocket.manage(Domain(domain)))
}))
.attach(AdHoc::on_attach("from", |rocket| {
let from = rocket.config()
.get_str("from")
.unwrap()
.to_string();
let from = rocket.config().get_str("from").unwrap().to_string();
Ok(rocket.manage(From(from)))
}))
.attach(AdHoc::on_attach("mail_templates", |rocket| {
let dir: PathBuf = rocket.config()
let dir: PathBuf = rocket
.config()
.get_str("template_dir")
.unwrap()
.to_string()
@ -499,16 +515,23 @@ pub fn serve(opt: &Opt, db: Polymorphic) -> Result<()> {
let confirm_txt = dir.join("confirm-email-txt.hbs");
let verify_html = dir.join("verify-email-html.hbs");
let verify_txt = dir.join("verify-email-txt.hbs");
let mut handlebars = Handlebars::new();
let mut handlebars = Handlebars::new();
handlebars.register_template_file("confirm-html", confirm_html).unwrap();
handlebars.register_template_file("confirm-txt", confirm_txt).unwrap();
handlebars.register_template_file("verify-html", verify_html).unwrap();
handlebars.register_template_file("verify-txt", verify_txt).unwrap();
handlebars
.register_template_file("confirm-html", confirm_html)
.unwrap();
handlebars
.register_template_file("confirm-txt", confirm_txt)
.unwrap();
handlebars
.register_template_file("verify-html", verify_html)
.unwrap();
handlebars
.register_template_file("verify-txt", verify_txt)
.unwrap();
Ok(rocket.manage(MailTemplates(handlebars)))
}))
.mount("/", routes)
.manage(db)
.launch();

View File

@ -1,18 +1,18 @@
use multipart::server::Multipart;
use multipart::server::save::Entries;
use multipart::server::save::SaveResult::*;
use multipart::server::Multipart;
use rocket::{State, Data};
use rocket::http::{ContentType, Status};
use rocket::response::status::Custom;
use rocket::{Data, State};
use rocket_contrib::templates::Template;
use handlebars::Handlebars;
use types::Email;
use mail::send_verification_mail;
use web::{From, Domain, MailTemplates};
use database::{Database, Polymorphic};
use mail::send_verification_mail;
use types::Email;
use web::{Domain, From, MailTemplates};
use std::io::Read;
use std::str::FromStr;
@ -32,11 +32,10 @@ mod template {
#[post("/pks/add", data = "<data>")]
// signature requires the request to have a `Content-Type`
pub fn multipart_upload(db: State<Polymorphic>, cont_type: &ContentType,
data: Data, tmpl: State<MailTemplates>,
domain: State<Domain>, from: State<From>)
-> Result<Template, Custom<String>>
{
pub fn multipart_upload(
db: State<Polymorphic>, cont_type: &ContentType, data: Data,
tmpl: State<MailTemplates>, domain: State<Domain>, from: State<From>,
) -> Result<Template, Custom<String>> {
if cont_type.is_form_data() {
// multipart/form-data
let (_, boundary) = cont_type.params().find(|&(k, _)| k == "boundary").ok_or_else(
@ -55,8 +54,11 @@ pub fn multipart_upload(db: State<Polymorphic>, cont_type: &ContentType,
let mut buf = Vec::default();
data.stream_to(&mut buf).or_else(|_| {
Err(Custom(Status::BadRequest,
"`Content-Type: application/x-www-form-urlencoded` not valid".into()))
Err(Custom(
Status::BadRequest,
"`Content-Type: application/x-www-form-urlencoded` not valid"
.into(),
))
})?;
for item in FormItems::from(&*String::from_utf8_lossy(&buf)) {
@ -68,8 +70,13 @@ pub fn multipart_upload(db: State<Polymorphic>, cont_type: &ContentType,
match key.as_str() {
"keytext" => {
return process_key(Cursor::new(decoded_value.as_bytes()),
&db, &tmpl.0, &domain.0, &from.0);
return process_key(
Cursor::new(decoded_value.as_bytes()),
&db,
&tmpl.0,
&domain.0,
&from.0,
);
}
_ => { /* skip */ }
}
@ -81,24 +88,28 @@ pub fn multipart_upload(db: State<Polymorphic>, cont_type: &ContentType,
}
}
fn process_upload(boundary: &str, data: Data, db: &Polymorphic, mail_templates: &Handlebars,
domain: &str, from: &str)
-> Result<Template, Custom<String>>
{
fn process_upload(
boundary: &str, data: Data, db: &Polymorphic, mail_templates: &Handlebars,
domain: &str, from: &str,
) -> Result<Template, Custom<String>> {
// saves all fields, any field longer than 10kB goes to a temporary directory
// Entries could implement FromData though that would give zero control over
// how the files are saved; Multipart would be a good impl candidate though
match Multipart::with_body(data.open(), boundary).save().temp() {
Full(entries) => process_multipart(entries, db, mail_templates, domain, from),
Partial(partial, _) => process_multipart(partial.entries, db, mail_templates, domain, from),
Full(entries) => {
process_multipart(entries, db, mail_templates, domain, from)
}
Partial(partial, _) => {
process_multipart(partial.entries, db, mail_templates, domain, from)
}
Error(err) => Err(Custom(Status::InternalServerError, err.to_string())),
}
}
fn process_multipart(entries: Entries, db: &Polymorphic, mail_templates: &Handlebars,
domain: &str, from: &str)
-> Result<Template, Custom<String>>
{
fn process_multipart(
entries: Entries, db: &Polymorphic, mail_templates: &Handlebars,
domain: &str, from: &str,
) -> Result<Template, Custom<String>> {
match entries.fields.get("keytext") {
Some(ent) if ent.len() == 1 => {
let reader = ent[0].data.readable().map_err(|err| {
@ -107,48 +118,72 @@ fn process_multipart(entries: Entries, db: &Polymorphic, mail_templates: &Handle
process_key(reader, db, mail_templates, domain, from)
}
Some(_) | None =>
Err(Custom(Status::BadRequest, "Not a PGP public key".into())),
Some(_) | None => {
Err(Custom(Status::BadRequest, "Not a PGP public key".into()))
}
}
}
fn process_key<R>(reader: R, db: &Polymorphic, mail_templates: &Handlebars, domain: &str, from: &str)
-> Result<Template, Custom<String>> where R: Read
fn process_key<R>(
reader: R, db: &Polymorphic, mail_templates: &Handlebars, domain: &str,
from: &str,
) -> Result<Template, Custom<String>>
where
R: Read,
{
use sequoia_openpgp::TPK;
use sequoia_openpgp::parse::Parse;
use sequoia_openpgp::TPK;
match TPK::from_reader(reader) {
Ok(tpk) => {
match db.merge_or_publish(tpk) {
Ok(tokens) => {
let tokens = tokens
.into_iter().map(|(uid,tok)| {
template::Token{ userid: uid.to_string(), token: tok }
}).collect::<Vec<_>>();
.into_iter()
.map(|(uid, tok)| {
template::Token {
userid: uid.to_string(),
token: tok,
}
})
.collect::<Vec<_>>();
// send out emails
for tok in tokens.iter() {
let &template::Token{ ref userid, ref token } = tok;
let &template::Token { ref userid, ref token } = tok;
Email::from_str(userid).and_then(|email| {
send_verification_mail(&email, token, mail_templates, domain, from)
}).map_err(|err| {
Custom(Status::InternalServerError, format!("{:?}", err))
})?;
Email::from_str(userid)
.and_then(|email| {
send_verification_mail(
&email,
token,
mail_templates,
domain,
from,
)
})
.map_err(|err| {
Custom(
Status::InternalServerError,
format!("{:?}", err),
)
})?;
}
let context = template::Verify{
tokens: tokens
};
let context = template::Verify { tokens: tokens };
Ok(Template::render("upload", context))
}
Err(err) =>
Err(Custom(Status::InternalServerError,
format!("{:?}", err))),
Err(err) => {
Err(Custom(
Status::InternalServerError,
format!("{:?}", err),
))
}
}
}
Err(_) => Err(Custom(Status::BadRequest, "Not a PGP public key".into())),
Err(_) => {
Err(Custom(Status::BadRequest, "Not a PGP public key".into()))
}
}
}