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::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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
75
src/mail.rs
75
src/mail.rs
|
@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
47
src/main.rs
47
src/main.rs
|
@ -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};
|
||||||
|
|
34
src/types.rs
34
src/types.rs
|
@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
349
src/web/mod.rs
349
src/web/mod.rs
|
@ -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();
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue