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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use database::{Database, Delete, Filesystem, Memory, Verify};
use errors::Result; use errors::Result;
use database::{Verify, Delete, Database, Filesystem, Memory}; use types::{Email, Fingerprint, KeyID};
use types::{Fingerprint, Email, KeyID};
pub enum Polymorphic { pub enum Polymorphic {
Memory(Memory), 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 { match self {
&Polymorphic::Memory(ref db) => db.compare_and_swap(fpr, present, new), &Polymorphic::Memory(ref db) => {
&Polymorphic::Filesystem(ref db) => db.compare_and_swap(fpr, present, new), 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 // pub & verify
// req del one // req del one
// fetch by uid & fpr // fetch by uid & fpr
// confirm // confirm
// fetch by uid & fpr // fetch by uid & fpr
// confirm again // confirm again
// fetch by uid & fpr // fetch by uid & fpr
@ -19,8 +19,11 @@ use std::str::FromStr;
use database::Database; use database::Database;
use sequoia_openpgp::tpk::{TPKBuilder, UserIDBinding}; use sequoia_openpgp::tpk::{TPKBuilder, UserIDBinding};
use sequoia_openpgp::{Packet, packet::UserID, TPK, PacketPile, parse::Parse, RevocationStatus, constants::ReasonForRevocation, constants::SignatureType}; use sequoia_openpgp::{
use types::{KeyID, Email, Fingerprint}; 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) { pub fn test_uid_verification<D: Database>(db: &mut D) {
let str_uid1 = "Test A <test_a@example.com>"; 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() let tpk = TPKBuilder::default()
.add_userid(str_uid1) .add_userid(str_uid1)
.add_userid(str_uid2) .add_userid(str_uid2)
.generate().unwrap().0; .generate()
.unwrap()
.0;
let mut uid1 = UserID::new(); let mut uid1 = UserID::new();
let mut uid2 = 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(); let uid = key.userids().next().unwrap().userid().clone();
assert!((uid == uid1) ^ (uid == uid2)); 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); assert_eq!(db.by_email(&email).unwrap(), raw);
if email1 == email { 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(); let uid = key.userids().next().unwrap().userid().clone();
assert!((uid == uid1) ^ (uid == uid2)); 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); assert_eq!(db.by_email(&email).unwrap(), raw);
if email1 == email { 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(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).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 // 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 // publish w/ one uid less
{ {
let packets = tpk.clone() let packets =
.to_packet_pile() tpk.clone().to_packet_pile().into_children().filter(|pkt| {
.into_children()
.filter(|pkt| {
match pkt { match pkt {
Packet::UserID(ref uid) => *uid != uid1, Packet::UserID(ref uid) => *uid != uid1,
_ => true _ => true,
} }
}); });
let pile = PacketPile::from_packets(packets.collect()); let pile = PacketPile::from_packets(packets.collect());
let short_tpk = TPK::from_packet_pile(pile).unwrap(); 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 // fetch by fpr
let raw = db.by_fpr(&fpr).unwrap(); 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(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).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 // publish w/one uid more
{ {
let mut packets = tpk.clone() let mut packets = tpk
.clone()
.to_packet_pile() .to_packet_pile()
.into_children() .into_children()
.filter(|pkt| { .filter(|pkt| {
match pkt { match pkt {
Packet::UserID(ref uid) => *uid != uid1, Packet::UserID(ref uid) => *uid != uid1,
_ => true _ => true,
} }
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
let str_uid3 = "Test C <test_c@example.com>"; let str_uid3 = "Test C <test_c@example.com>";
let mut uid3 = UserID::new(); let mut uid3 = UserID::new();
uid3.set_userid_from_bytes(str_uid3.as_bytes()); 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(); let bind = UserIDBinding::new(key, uid3.clone(), key).unwrap();
packets.push(Packet::UserID(uid3.clone())); 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 pile = PacketPile::from_packets(packets);
let ext_tpk = TPK::from_packet_pile(pile).unwrap(); 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(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).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()); 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() let tpk = TPKBuilder::default()
.add_userid(str_uid1) .add_userid(str_uid1)
.add_userid(str_uid2) .add_userid(str_uid2)
.generate().unwrap().0; .generate()
.unwrap()
.0;
let mut uid1 = UserID::new(); let mut uid1 = UserID::new();
let mut uid2 = UserID::new(); let mut uid2 = UserID::new();
@ -241,7 +268,7 @@ pub fn test_uid_deletion<D: Database>(db: &mut D) {
// req. deletion // req. deletion
let del = db.request_deletion(fpr.clone()).unwrap().0; let del = db.request_deletion(fpr.clone()).unwrap().0;
// check it's still there // check it's still there
{ {
// fetch by fpr // fetch by fpr
let raw = db.by_fpr(&fpr).unwrap(); 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(&email1).unwrap(), raw);
assert_eq!(db.by_email(&email2).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 // confirm deletion
@ -281,13 +311,21 @@ pub fn test_subkey_lookup<D: Database>(db: &mut D) {
.add_userid("Testy <test@example.com>") .add_userid("Testy <test@example.com>")
.add_signing_subkey() .add_signing_subkey()
.add_encryption_subkey() .add_encryption_subkey()
.generate().unwrap().0; .generate()
.unwrap()
.0;
// upload key // upload key
let _ = db.merge_or_publish(tpk.clone()).unwrap(); let _ = db.merge_or_publish(tpk.clone()).unwrap();
let primary_fpr = Fingerprint::try_from(tpk.fingerprint()).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 sub1_fpr = Fingerprint::try_from(
let sub2_fpr = Fingerprint::try_from(tpk.subkeys().skip(1).next().map(|x| x.subkey().fingerprint()).unwrap()).unwrap(); 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 raw1 = db.by_fpr(&primary_fpr).unwrap();
let raw2 = db.by_fpr(&sub1_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_userid("Testy <test@example.com>")
.add_signing_subkey() .add_signing_subkey()
.add_encryption_subkey() .add_encryption_subkey()
.generate().unwrap().0; .generate()
.unwrap()
.0;
// upload key // upload key
let _ = db.merge_or_publish(tpk.clone()).unwrap(); let _ = db.merge_or_publish(tpk.clone()).unwrap();
let primary_kid = KeyID::try_from(tpk.fingerprint()).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 sub1_kid = KeyID::try_from(
let sub2_kid = KeyID::try_from(tpk.subkeys().skip(1).next().map(|x| x.subkey().fingerprint()).unwrap()).unwrap(); 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 raw1 = db.by_kid(&primary_kid).unwrap();
let raw2 = db.by_kid(&sub1_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() let tpk = TPKBuilder::default()
.add_userid(str_uid1) .add_userid(str_uid1)
.add_userid(str_uid2) .add_userid(str_uid2)
.generate().unwrap().0; .generate()
.unwrap()
.0;
let mut uid1 = UserID::new(); let mut uid1 = UserID::new();
let mut uid2 = 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)); assert_eq!(RevocationStatus::NotAsFarAsWeKnow, uid.revoked(None));
let mut keypair = tpk.primary().clone().into_keypair().unwrap(); let mut keypair = tpk.primary().clone().into_keypair().unwrap();
uid.revoke(&mut keypair, uid.revoke(
ReasonForRevocation::UIDRetired, &mut keypair,
b"It was the maid :/").unwrap() ReasonForRevocation::UIDRetired,
b"It was the maid :/",
)
.unwrap()
}; };
assert_eq!(sig.sigtype(), SignatureType::CertificateRevocation); assert_eq!(sig.sigtype(), SignatureType::CertificateRevocation);
let tpk = tpk.merge_packets(vec![sig.to_packet()]).unwrap(); let tpk = tpk.merge_packets(vec![sig.to_packet()]).unwrap();

View File

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

View File

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

View File

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

View File

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