use rustfmt to format source
This commit is contained in:
parent
4612a96b23
commit
53719c2166
|
@ -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
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
75
src/mail.rs
75
src/mail.rs
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
47
src/main.rs
47
src/main.rs
|
@ -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};
|
||||
|
|
34
src/types.rs
34
src/types.rs
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
349
src/web/mod.rs
349
src/web/mod.rs
|
@ -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();
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue