clippy lint fix

This commit is contained in:
Quentin 2023-05-15 18:23:23 +02:00
parent 024d8df847
commit 9d6aef34ad
Signed by: quentin
GPG key ID: E9602264D639FF68
17 changed files with 154 additions and 131 deletions

4
.albatros Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euxo pipefail
nix build --print-build-logs .#packages.x86_64-unknown-linux-musl.debug

View file

@ -80,6 +80,7 @@
echo echo
export NIX_RUST_BUILD_FLAGS="''${NIX_RUST_BUILD_FLAGS} --deny warnings" export NIX_RUST_BUILD_FLAGS="''${NIX_RUST_BUILD_FLAGS} --deny warnings"
export NIX_RUST_LINK_FLAGS="''${NIX_RUST_LINK_FLAGS} --deny warnings"
export RUSTC="''${CLIPPY_DRIVER}" export RUSTC="''${CLIPPY_DRIVER}"
''); '');
rustDebug = pkgs.rustBuilder.makePackageSet({ rustDebug = pkgs.rustBuilder.makePackageSet({

View file

@ -103,9 +103,12 @@ impl<S: BayouState> Bayou<S> {
} else { } else {
debug!("(sync) loading checkpoint: {}", key); debug!("(sync) loading checkpoint: {}", key);
let mut gor = GetObjectRequest::default(); let gor = GetObjectRequest {
gor.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
gor.key = key.to_string(); key: key.to_string(),
..Default::default()
};
let obj_res = self.s3.get_object(gor).await?; let obj_res = self.s3.get_object(gor).await?;
let obj_body = obj_res.body.ok_or(anyhow!("Missing object body"))?; let obj_body = obj_res.body.ok_or(anyhow!("Missing object body"))?;
@ -173,7 +176,7 @@ impl<S: BayouState> Bayou<S> {
} }
match &val.value[0] { match &val.value[0] {
K2vValue::Value(v) => { K2vValue::Value(v) => {
let op = open_deserialize::<S::Op>(&v, &self.key)?; let op = open_deserialize::<S::Op>(v, &self.key)?;
debug!("(sync) operation {}: {} {:?}", tsstr, base64::encode(v), op); debug!("(sync) operation {}: {} {:?}", tsstr, base64::encode(v), op);
ops.push((ts, op)); ops.push((ts, op));
} }
@ -381,10 +384,12 @@ impl<S: BayouState> Bayou<S> {
let cryptoblob = seal_serialize(&state_cp, &self.key)?; let cryptoblob = seal_serialize(&state_cp, &self.key)?;
debug!("(cp) checkpoint body length: {}", cryptoblob.len()); debug!("(cp) checkpoint body length: {}", cryptoblob.len());
let mut por = PutObjectRequest::default(); let por = PutObjectRequest{
por.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
por.key = format!("{}/checkpoint/{}", self.path, ts_cp.to_string()); key: format!("{}/checkpoint/{}", self.path, ts_cp.to_string()),
por.body = Some(cryptoblob.into()); body: Some(cryptoblob.into()),
..Default::default()
};
self.s3.put_object(por).await?; self.s3.put_object(por).await?;
// Drop old checkpoints (but keep at least CHECKPOINTS_TO_KEEP of them) // Drop old checkpoints (but keep at least CHECKPOINTS_TO_KEEP of them)
@ -395,9 +400,11 @@ impl<S: BayouState> Bayou<S> {
// Delete blobs // Delete blobs
for (_ts, key) in existing_checkpoints[..last_to_keep].iter() { for (_ts, key) in existing_checkpoints[..last_to_keep].iter() {
debug!("(cp) drop old checkpoint {}", key); debug!("(cp) drop old checkpoint {}", key);
let mut dor = DeleteObjectRequest::default(); let dor = DeleteObjectRequest {
dor.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
dor.key = key.to_string(); key: key.to_string(),
..Default::default()
};
self.s3.delete_object(dor).await?; self.s3.delete_object(dor).await?;
} }
@ -430,10 +437,12 @@ impl<S: BayouState> Bayou<S> {
async fn list_checkpoints(&self) -> Result<Vec<(Timestamp, String)>> { async fn list_checkpoints(&self) -> Result<Vec<(Timestamp, String)>> {
let prefix = format!("{}/checkpoint/", self.path); let prefix = format!("{}/checkpoint/", self.path);
let mut lor = ListObjectsV2Request::default(); let lor = ListObjectsV2Request{
lor.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
lor.max_keys = Some(1000); max_keys: Some(1000),
lor.prefix = Some(prefix.clone()); prefix: Some(prefix.clone()),
..Default::default()
};
let checkpoints_res = self.s3.list_objects_v2(lor).await?; let checkpoints_res = self.s3.list_objects_v2(lor).await?;
@ -537,6 +546,8 @@ pub struct Timestamp {
} }
impl Timestamp { impl Timestamp {
#[allow(dead_code)]
// 2023-05-15 try to make clippy happy and not sure if this fn will be used in the future.
pub fn now() -> Self { pub fn now() -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
Self { Self {
@ -563,7 +574,7 @@ impl ToString for Timestamp {
let mut bytes = [0u8; 16]; let mut bytes = [0u8; 16];
bytes[0..8].copy_from_slice(&u64::to_be_bytes(self.msec)); bytes[0..8].copy_from_slice(&u64::to_be_bytes(self.msec));
bytes[8..16].copy_from_slice(&u64::to_be_bytes(self.rand)); bytes[8..16].copy_from_slice(&u64::to_be_bytes(self.rand));
hex::encode(&bytes) hex::encode(bytes)
} }
} }

View file

@ -36,7 +36,7 @@ pub fn seal(plainblob: &[u8], key: &Key) -> Result<Vec<u8>> {
use secretbox::{gen_nonce, NONCEBYTES}; use secretbox::{gen_nonce, NONCEBYTES};
// Compress data using zstd // Compress data using zstd
let mut reader = &plainblob[..]; let mut reader = plainblob;
let zstdblob = zstd_encode(&mut reader, 0)?; let zstdblob = zstd_encode(&mut reader, 0)?;
// Encrypt // Encrypt
@ -63,5 +63,5 @@ pub fn seal_serialize<T: Serialize>(obj: T, key: &Key) -> Result<Vec<u8>> {
.with_string_variants(); .with_string_variants();
obj.serialize(&mut se)?; obj.serialize(&mut se)?;
Ok(seal(&wr, key)?) seal(&wr, key)
} }

View file

@ -15,7 +15,7 @@ pub struct AnonymousContext<'a> {
pub login_provider: Option<&'a ArcLoginProvider>, pub login_provider: Option<&'a ArcLoginProvider>,
} }
pub async fn dispatch<'a>(ctx: AnonymousContext<'a>) -> Result<(Response, flow::Transition)> { pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response, flow::Transition)> {
match &ctx.req.command.body { match &ctx.req.command.body {
CommandBody::Noop => Ok((Response::ok("Noop completed.")?, flow::Transition::None)), CommandBody::Noop => Ok((Response::ok("Noop completed.")?, flow::Transition::None)),
CommandBody::Capability => ctx.capability().await, CommandBody::Capability => ctx.capability().await,

View file

@ -25,7 +25,7 @@ pub struct AuthenticatedContext<'a> {
pub user: &'a Arc<User>, pub user: &'a Arc<User>,
} }
pub async fn dispatch<'a>(ctx: AuthenticatedContext<'a>) -> Result<(Response, flow::Transition)> { pub async fn dispatch(ctx: AuthenticatedContext<'_>) -> Result<(Response, flow::Transition)> {
match &ctx.req.command.body { match &ctx.req.command.body {
CommandBody::Create { mailbox } => ctx.create(mailbox).await, CommandBody::Create { mailbox } => ctx.create(mailbox).await,
CommandBody::Delete { mailbox } => ctx.delete(mailbox).await, CommandBody::Delete { mailbox } => ctx.delete(mailbox).await,
@ -150,9 +150,7 @@ impl<'a> AuthenticatedContext<'a> {
for (i, _) in mb.match_indices(MAILBOX_HIERARCHY_DELIMITER) { for (i, _) in mb.match_indices(MAILBOX_HIERARCHY_DELIMITER) {
if i > 0 { if i > 0 {
let smb = &mb[..i]; let smb = &mb[..i];
if !vmailboxes.contains_key(&smb) { vmailboxes.entry(smb).or_insert(false);
vmailboxes.insert(smb, false);
}
} }
} }
vmailboxes.insert(mb, true); vmailboxes.insert(mb, true);
@ -160,7 +158,7 @@ impl<'a> AuthenticatedContext<'a> {
let mut ret = vec![]; let mut ret = vec![];
for (mb, is_real) in vmailboxes.iter() { for (mb, is_real) in vmailboxes.iter() {
if matches_wildcard(&wildcard, &mb) { if matches_wildcard(&wildcard, mb) {
let mailbox = mb let mailbox = mb
.to_string() .to_string()
.try_into() .try_into()

View file

@ -23,7 +23,7 @@ pub struct ExaminedContext<'a> {
pub mailbox: &'a mut MailboxView, pub mailbox: &'a mut MailboxView,
} }
pub async fn dispatch<'a>(ctx: ExaminedContext<'a>) -> Result<(Response, flow::Transition)> { pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response, flow::Transition)> {
match &ctx.req.command.body { match &ctx.req.command.body {
// CLOSE in examined state is not the same as in selected state // CLOSE in examined state is not the same as in selected state
// (in selected state it also does an EXPUNGE, here it doesn't) // (in selected state it also does an EXPUNGE, here it doesn't)

View file

@ -21,7 +21,7 @@ pub struct SelectedContext<'a> {
pub mailbox: &'a mut MailboxView, pub mailbox: &'a mut MailboxView,
} }
pub async fn dispatch<'a>(ctx: SelectedContext<'a>) -> Result<(Response, flow::Transition)> { pub async fn dispatch(ctx: SelectedContext<'_>) -> Result<(Response, flow::Transition)> {
match &ctx.req.command.body { match &ctx.req.command.body {
// Only write commands here, read commands are handled in // Only write commands here, read commands are handled in
// `examined.rs` // `examined.rs`

View file

@ -126,7 +126,7 @@ impl MailboxView {
data.push(Body::Data(Data::Fetch { data.push(Body::Data(Data::Fetch {
seq_or_uid: NonZeroU32::try_from((i + 1) as u32).unwrap(), seq_or_uid: NonZeroU32::try_from((i + 1) as u32).unwrap(),
attributes: vec![ attributes: vec![
MessageAttribute::Uid((*uid).try_into().unwrap()), MessageAttribute::Uid(*uid),
MessageAttribute::Flags( MessageAttribute::Flags(
flags.iter().filter_map(|f| string_to_flag(f)).collect(), flags.iter().filter_map(|f| string_to_flag(f)).collect(),
), ),
@ -387,10 +387,8 @@ impl MailboxView {
} }
} }
FetchAttribute::InternalDate => { FetchAttribute::InternalDate => {
attributes.push(MessageAttribute::InternalDate(MyDateTime( let dt = Utc.fix().timestamp_opt(i64::try_from(meta.internaldate / 1000)?, 0).earliest().ok_or(anyhow!("Unable to parse internal date"))?;
Utc.fix() attributes.push(MessageAttribute::InternalDate(MyDateTime(dt)));
.timestamp(i64::try_from(meta.internaldate / 1000)?, 0),
)));
} }
} }
} }
@ -529,8 +527,7 @@ impl MailboxView {
.known_state .known_state
.idx_by_flag .idx_by_flag
.flags() .flags()
.map(|f| string_to_flag(f)) .filter_map(|f| string_to_flag(f))
.flatten()
.collect(); .collect();
for f in DEFAULT_FLAGS.iter() { for f in DEFAULT_FLAGS.iter() {
if !flags.contains(f) { if !flags.contains(f) {
@ -569,7 +566,7 @@ fn string_to_flag(f: &str) -> Option<Flag> {
"\\Deleted" => Some(Flag::Deleted), "\\Deleted" => Some(Flag::Deleted),
"\\Draft" => Some(Flag::Draft), "\\Draft" => Some(Flag::Draft),
"\\Recent" => Some(Flag::Recent), "\\Recent" => Some(Flag::Recent),
_ => match Atom::try_from(f.strip_prefix('\\').unwrap().clone()) { _ => match Atom::try_from(f.strip_prefix('\\').unwrap().to_string()) {
Err(_) => { Err(_) => {
tracing::error!(flag=%f, "Unable to encode flag as IMAP atom"); tracing::error!(flag=%f, "Unable to encode flag as IMAP atom");
None None
@ -577,7 +574,7 @@ fn string_to_flag(f: &str) -> Option<Flag> {
Ok(a) => Some(Flag::Extension(a)), Ok(a) => Some(Flag::Extension(a)),
}, },
}, },
Some(_) => match Atom::try_from(f.clone()) { Some(_) => match Atom::try_from(f.to_string()) {
Err(_) => { Err(_) => {
tracing::error!(flag=%f, "Unable to encode flag as IMAP atom"); tracing::error!(flag=%f, "Unable to encode flag as IMAP atom");
None None
@ -623,7 +620,7 @@ fn message_envelope(msg: &mail_parser::Message<'_>) -> Envelope {
), ),
from: from.clone(), from: from.clone(),
sender: convert_addresses(msg.sender()).unwrap_or(from.clone()), sender: convert_addresses(msg.sender()).unwrap_or(from.clone()),
reply_to: convert_addresses(msg.reply_to()).unwrap_or(from.clone()), reply_to: convert_addresses(msg.reply_to()).unwrap_or(from),
to: convert_addresses(msg.to()).unwrap_or(vec![]), to: convert_addresses(msg.to()).unwrap_or(vec![]),
cc: convert_addresses(msg.cc()).unwrap_or(vec![]), cc: convert_addresses(msg.cc()).unwrap_or(vec![]),
bcc: convert_addresses(msg.bcc()).unwrap_or(vec![]), bcc: convert_addresses(msg.bcc()).unwrap_or(vec![]),
@ -639,7 +636,7 @@ fn convert_addresses(a: &mail_parser::HeaderValue<'_>) -> Option<Vec<Address>> {
match a { match a {
mail_parser::HeaderValue::Address(a) => Some(vec![convert_address(a)]), mail_parser::HeaderValue::Address(a) => Some(vec![convert_address(a)]),
mail_parser::HeaderValue::AddressList(l) => { mail_parser::HeaderValue::AddressList(l) => {
Some(l.iter().map(|a| convert_address(a)).collect()) Some(l.iter().map(convert_address).collect())
} }
mail_parser::HeaderValue::Empty => None, mail_parser::HeaderValue::Empty => None,
_ => { _ => {
@ -722,7 +719,7 @@ fn build_imap_email_struct<'a>(msg: &Message<'a>, part: &MessagePart<'a>) -> Res
}) })
} }
PartType::Text(bp) | PartType::Html(bp) => { PartType::Text(bp) | PartType::Html(bp) => {
let (attrs, mut basic) = headers_to_basic_fields(&part, bp.len())?; let (attrs, mut basic) = headers_to_basic_fields(part, bp.len())?;
// If the charset is not defined, set it to "us-ascii" // If the charset is not defined, set it to "us-ascii"
if attrs.charset.is_none() { if attrs.charset.is_none() {
@ -736,10 +733,8 @@ fn build_imap_email_struct<'a>(msg: &Message<'a>, part: &MessagePart<'a>) -> Res
// difference between MIME and raw emails, hence raw emails have no subtypes. // difference between MIME and raw emails, hence raw emails have no subtypes.
let subtype = part let subtype = part
.content_type() .content_type()
.map(|h| h.c_subtype.as_ref()) .and_then(|h| h.c_subtype.as_ref())
.flatten() .and_then(|st| IString::try_from(st.to_string()).ok())
.map(|st| IString::try_from(st.to_string()).ok())
.flatten()
.unwrap_or(unchecked_istring("plain")); .unwrap_or(unchecked_istring("plain"));
let number_of_lines = msg let number_of_lines = msg
@ -761,7 +756,7 @@ fn build_imap_email_struct<'a>(msg: &Message<'a>, part: &MessagePart<'a>) -> Res
}) })
} }
PartType::Binary(bp) | PartType::InlineBinary(bp) => { PartType::Binary(bp) | PartType::InlineBinary(bp) => {
let (_, basic) = headers_to_basic_fields(&part, bp.len())?; let (_, basic) = headers_to_basic_fields(part, bp.len())?;
let ct = part let ct = part
.content_type() .content_type()
@ -790,7 +785,7 @@ fn build_imap_email_struct<'a>(msg: &Message<'a>, part: &MessagePart<'a>) -> Res
}) })
} }
PartType::Message(inner) => { PartType::Message(inner) => {
let (_, basic) = headers_to_basic_fields(&part, inner.raw_message().len())?; let (_, basic) = headers_to_basic_fields(part, inner.raw_message().len())?;
// We do not count the number of lines but the number of line // We do not count the number of lines but the number of line
// feeds to have the same behavior as Dovecot and Cyrus. // feeds to have the same behavior as Dovecot and Cyrus.
@ -803,7 +798,7 @@ fn build_imap_email_struct<'a>(msg: &Message<'a>, part: &MessagePart<'a>) -> Res
specific: SpecificFields::Message { specific: SpecificFields::Message {
envelope: message_envelope(inner), envelope: message_envelope(inner),
body_structure: Box::new(build_imap_email_struct( body_structure: Box::new(build_imap_email_struct(
&inner, inner,
inner.root_part(), inner.root_part(),
)?), )?),
@ -850,8 +845,7 @@ fn attrs_to_params<'a>(bp: &impl MimeHeaders<'a>) -> (SpecialAttrs, Vec<(IString
// Try to extract Content-Type attributes from headers // Try to extract Content-Type attributes from headers
let attrs = match bp let attrs = match bp
.content_type() .content_type()
.map(|c| c.attributes.as_ref()) .and_then(|c| c.attributes.as_ref())
.flatten()
{ {
Some(v) => v, Some(v) => v,
_ => return (SpecialAttrs::default(), vec![]), _ => return (SpecialAttrs::default(), vec![]),
@ -896,14 +890,12 @@ fn headers_to_basic_fields<'a>(
id: NString( id: NString(
bp.content_id() bp.content_id()
.map(|ci| IString::try_from(ci.to_string()).ok()) .and_then(|ci| IString::try_from(ci.to_string()).ok()),
.flatten(),
), ),
description: NString( description: NString(
bp.content_description() bp.content_description()
.map(|cd| IString::try_from(cd.to_string()).ok()) .and_then(|cd| IString::try_from(cd.to_string()).ok()),
.flatten(),
), ),
/* /*
@ -913,8 +905,7 @@ fn headers_to_basic_fields<'a>(
*/ */
content_transfer_encoding: bp content_transfer_encoding: bp
.content_transfer_encoding() .content_transfer_encoding()
.map(|h| IString::try_from(h.to_string()).ok()) .and_then(|h| IString::try_from(h.to_string()).ok())
.flatten()
.unwrap_or(unchecked_istring("7bit")), .unwrap_or(unchecked_istring("7bit")),
size: u32::try_from(size)?, size: u32::try_from(size)?,
@ -1023,7 +1014,7 @@ fn get_message_section<'a>(
} }
} }
fn map_subpart_msg<'a, F, R>(msg: &Message<'a>, path: &[NonZeroU32], f: F) -> Result<R> fn map_subpart_msg<F, R>(msg: &Message<'_>, path: &[NonZeroU32], f: F) -> Result<R>
where where
F: FnOnce(&Message<'_>) -> Result<R>, F: FnOnce(&Message<'_>) -> Result<R>,
{ {
@ -1035,14 +1026,14 @@ where
.get(path[0].get() as usize - 1) .get(path[0].get() as usize - 1)
.ok_or(anyhow!("No such subpart: {}", path[0]))?; .ok_or(anyhow!("No such subpart: {}", path[0]))?;
if let PartType::Message(msg_attach) = &part.body { if let PartType::Message(msg_attach) = &part.body {
map_subpart_msg(&msg_attach, &path[1..], f) map_subpart_msg(msg_attach, &path[1..], f)
} else { } else {
bail!("Subpart is not a message: {}", path[0]); bail!("Subpart is not a message: {}", path[0]);
} }
} }
} }
fn map_subpart<'a, F, R>(msg: &Message<'a>, path: &[NonZeroU32], f: F) -> Result<R> fn map_subpart<F, R>(msg: &Message<'_>, path: &[NonZeroU32], f: F) -> Result<R>
where where
F: FnOnce(&Message<'_>, &MessagePart<'_>) -> Result<R>, F: FnOnce(&Message<'_>, &MessagePart<'_>) -> Result<R>,
{ {
@ -1055,15 +1046,13 @@ where
.ok_or(anyhow!("No such subpart: {}", path[0]))?; .ok_or(anyhow!("No such subpart: {}", path[0]))?;
if path.len() == 1 { if path.len() == 1 {
f(msg, part) f(msg, part)
} else { } else if let PartType::Message(msg_attach) = &part.body {
if let PartType::Message(msg_attach) = &part.body { map_subpart(msg_attach, &path[1..], f)
map_subpart(&msg_attach, &path[1..], f)
} else { } else {
bail!("Subpart is not a message: {}", path[0]); bail!("Subpart is not a message: {}", path[0]);
} }
} }
} }
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View file

@ -190,8 +190,8 @@ impl CryptoKeys {
// Write values to storage // Write values to storage
k2v.insert_batch(&[ k2v.insert_batch(&[
k2v_insert_single_key("keys", "salt", salt_ct, &ident_salt), k2v_insert_single_key("keys", "salt", salt_ct, ident_salt),
k2v_insert_single_key("keys", "public", public_ct, &keys.public), k2v_insert_single_key("keys", "public", public_ct, keys.public),
k2v_insert_single_key("keys", &password_sortkey, None, &password_blob), k2v_insert_single_key("keys", &password_sortkey, None, &password_blob),
]) ])
.await .await
@ -223,8 +223,8 @@ impl CryptoKeys {
// Write values to storage // Write values to storage
k2v.insert_batch(&[ k2v.insert_batch(&[
k2v_insert_single_key("keys", "salt", salt_ct, &ident_salt), k2v_insert_single_key("keys", "salt", salt_ct, ident_salt),
k2v_insert_single_key("keys", "public", public_ct, &keys.public), k2v_insert_single_key("keys", "public", public_ct, keys.public),
]) ])
.await .await
.context("InsertBatch for salt and public")?; .context("InsertBatch for salt and public")?;
@ -265,7 +265,7 @@ impl CryptoKeys {
// Try to open blob // Try to open blob
let kdf_salt = &password_blob[..32]; let kdf_salt = &password_blob[..32];
let password_openned = let password_openned =
user_secrets.try_open_encrypted_keys(&kdf_salt, password, &password_blob[32..])?; user_secrets.try_open_encrypted_keys(kdf_salt, password, &password_blob[32..])?;
let keys = Self::deserialize(&password_openned)?; let keys = Self::deserialize(&password_openned)?;
if keys.public != expected_public { if keys.public != expected_public {
@ -332,7 +332,7 @@ impl CryptoKeys {
if entry.value.iter().any(|x| matches!(x, K2vValue::Value(_))) { if entry.value.iter().any(|x| matches!(x, K2vValue::Value(_))) {
bail!("password already exists"); bail!("password already exists");
} }
Some(entry.causality.clone()) Some(entry.causality)
} }
}; };
@ -523,7 +523,7 @@ impl CryptoKeys {
impl UserSecrets { impl UserSecrets {
fn derive_password_key_with(user_secret: &str, kdf_salt: &[u8], password: &str) -> Result<Key> { fn derive_password_key_with(user_secret: &str, kdf_salt: &[u8], password: &str) -> Result<Key> {
let tmp = format!("{}\n\n{}", user_secret, password); let tmp = format!("{}\n\n{}", user_secret, password);
Ok(Key::from_slice(&argon2_kdf(&kdf_salt, tmp.as_bytes(), 32)?).unwrap()) Ok(Key::from_slice(&argon2_kdf(kdf_salt, tmp.as_bytes(), 32)?).unwrap())
} }
fn derive_password_key(&self, kdf_salt: &[u8], password: &str) -> Result<Key> { fn derive_password_key(&self, kdf_salt: &[u8], password: &str) -> Result<Key> {
@ -579,7 +579,7 @@ pub fn k2v_read_single_key<'a>(
tombstones: bool, tombstones: bool,
) -> BatchReadOp<'a> { ) -> BatchReadOp<'a> {
BatchReadOp { BatchReadOp {
partition_key: partition_key, partition_key,
filter: Filter { filter: Filter {
start: Some(sort_key), start: Some(sort_key),
end: None, end: None,

View file

@ -151,7 +151,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<bool> {
Argon2, Argon2,
}; };
let parsed_hash = let parsed_hash =
PasswordHash::new(&hash).map_err(|e| anyhow!("Invalid hashed password: {}", e))?; PasswordHash::new(hash).map_err(|e| anyhow!("Invalid hashed password: {}", e))?;
Ok(Argon2::default() Ok(Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash) .verify_password(password.as_bytes(), &parsed_hash)
.is_ok()) .is_ok())

View file

@ -68,7 +68,7 @@ async fn incoming_mail_watch_process_internal(
let wait_new_mail = async { let wait_new_mail = async {
loop { loop {
match k2v_wait_value_changed(&k2v, &INCOMING_PK, &INCOMING_WATCH_SK, &prev_ct) match k2v_wait_value_changed(&k2v, INCOMING_PK, INCOMING_WATCH_SK, &prev_ct)
.await .await
{ {
Ok(cv) => break cv, Ok(cv) => break cv,
@ -104,7 +104,7 @@ async fn incoming_mail_watch_process_internal(
info!("User still available"); info!("User still available");
// If INBOX no longer is same mailbox, open new mailbox // If INBOX no longer is same mailbox, open new mailbox
let inbox_id = rx_inbox_id.borrow().clone(); let inbox_id = *rx_inbox_id.borrow();
if let Some((id, uidvalidity)) = inbox_id { if let Some((id, uidvalidity)) = inbox_id {
if Some(id) != inbox.as_ref().map(|b| b.id) { if Some(id) != inbox.as_ref().map(|b| b.id) {
match user.open_mailbox_by_id(id, uidvalidity).await { match user.open_mailbox_by_id(id, uidvalidity).await {
@ -145,10 +145,12 @@ async fn handle_incoming_mail(
inbox: &Arc<Mailbox>, inbox: &Arc<Mailbox>,
lock_held: &watch::Receiver<bool>, lock_held: &watch::Receiver<bool>,
) -> Result<()> { ) -> Result<()> {
let mut lor = ListObjectsV2Request::default(); let lor = ListObjectsV2Request {
lor.bucket = user.creds.storage.bucket.clone(); bucket: user.creds.storage.bucket.clone(),
lor.max_keys = Some(1000); max_keys: Some(1000),
lor.prefix = Some("incoming/".into()); prefix: Some("incoming/".into()),
..Default::default()
};
let mails_res = s3.list_objects_v2(lor).await?; let mails_res = s3.list_objects_v2(lor).await?;
for object in mails_res.contents.unwrap_or_default() { for object in mails_res.contents.unwrap_or_default() {
@ -178,9 +180,11 @@ async fn move_incoming_message(
let object_key = format!("incoming/{}", id); let object_key = format!("incoming/{}", id);
// 1. Fetch message from S3 // 1. Fetch message from S3
let mut gor = GetObjectRequest::default(); let gor = GetObjectRequest {
gor.bucket = user.creds.storage.bucket.clone(); bucket: user.creds.storage.bucket.clone(),
gor.key = object_key.clone(); key: object_key.clone(),
..Default::default()
};
let get_result = s3.get_object(gor).await?; let get_result = s3.get_object(gor).await?;
// 1.a decrypt message key from headers // 1.a decrypt message key from headers
@ -218,9 +222,11 @@ async fn move_incoming_message(
.await?; .await?;
// 3 delete from incoming // 3 delete from incoming
let mut dor = DeleteObjectRequest::default(); let dor = DeleteObjectRequest {
dor.bucket = user.creds.storage.bucket.clone(); bucket: user.creds.storage.bucket.clone(),
dor.key = object_key.clone(); key: object_key.clone(),
..Default::default()
};
s3.delete_object(dor).await?; s3.delete_object(dor).await?;
Ok(()) Ok(())
@ -441,15 +447,17 @@ impl EncryptedMessage {
sodiumoxide::crypto::sealedbox::seal(self.key.as_ref(), &creds.public_key); sodiumoxide::crypto::sealedbox::seal(self.key.as_ref(), &creds.public_key);
let key_header = base64::encode(&encrypted_key); let key_header = base64::encode(&encrypted_key);
let mut por = PutObjectRequest::default(); let por = PutObjectRequest {
por.bucket = creds.storage.bucket.clone(); bucket: creds.storage.bucket.clone(),
por.key = format!("incoming/{}", gen_ident().to_string()); key: format!("incoming/{}", gen_ident()),
por.metadata = Some( metadata: Some(
[(MESSAGE_KEY.to_string(), key_header)] [(MESSAGE_KEY.to_string(), key_header)]
.into_iter() .into_iter()
.collect::<HashMap<_, _>>(), .collect::<HashMap<_, _>>(),
); ),
por.body = Some(self.encrypted_body.clone().into()); body: Some(self.encrypted_body.clone().into()),
..Default::default()
};
s3_client.put_object(por).await?; s3_client.put_object(por).await?;
// Update watch key to signal new mail // Update watch key to signal new mail

View file

@ -156,6 +156,7 @@ impl Mailbox {
/// Move an email from an other Mailbox to this mailbox /// Move an email from an other Mailbox to this mailbox
/// (use this when possible, as it allows for a certain number of storage optimizations) /// (use this when possible, as it allows for a certain number of storage optimizations)
#[allow(dead_code)]
pub async fn move_from(&self, from: &Mailbox, uuid: UniqueIdent) -> Result<()> { pub async fn move_from(&self, from: &Mailbox, uuid: UniqueIdent) -> Result<()> {
if self.id == from.id { if self.id == from.id {
bail!("Cannot copy move same mailbox"); bail!("Cannot copy move same mailbox");
@ -178,6 +179,8 @@ impl Mailbox {
// Non standard but common flags: // Non standard but common flags:
// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml // https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml
struct MailboxInternal { struct MailboxInternal {
// 2023-05-15 will probably be used later.
#[allow(dead_code)]
id: UniqueIdent, id: UniqueIdent,
bucket: String, bucket: String,
mail_path: String, mail_path: String,
@ -256,9 +259,11 @@ impl MailboxInternal {
} }
async fn fetch_full(&self, id: UniqueIdent, message_key: &Key) -> Result<Vec<u8>> { async fn fetch_full(&self, id: UniqueIdent, message_key: &Key) -> Result<Vec<u8>> {
let mut gor = GetObjectRequest::default(); let gor = GetObjectRequest {
gor.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
gor.key = format!("{}/{}", self.mail_path, id); key: format!("{}/{}", self.mail_path, id),
..Default::default()
};
let obj_res = self.s3.get_object(gor).await?; let obj_res = self.s3.get_object(gor).await?;
@ -266,7 +271,7 @@ impl MailboxInternal {
let mut buf = Vec::with_capacity(obj_res.content_length.unwrap_or(128) as usize); let mut buf = Vec::with_capacity(obj_res.content_length.unwrap_or(128) as usize);
obj_body.into_async_read().read_to_end(&mut buf).await?; obj_body.into_async_read().read_to_end(&mut buf).await?;
Ok(cryptoblob::open(&buf, &message_key)?) cryptoblob::open(&buf, message_key)
} }
// ---- Functions for changing the mailbox ---- // ---- Functions for changing the mailbox ----
@ -292,17 +297,19 @@ impl MailboxInternal {
ident: Option<UniqueIdent>, ident: Option<UniqueIdent>,
flags: &[Flag], flags: &[Flag],
) -> Result<(ImapUidvalidity, ImapUid)> { ) -> Result<(ImapUidvalidity, ImapUid)> {
let ident = ident.unwrap_or_else(|| gen_ident()); let ident = ident.unwrap_or_else(gen_ident);
let message_key = gen_key(); let message_key = gen_key();
futures::try_join!( futures::try_join!(
async { async {
// Encrypt and save mail body // Encrypt and save mail body
let message_blob = cryptoblob::seal(mail.raw, &message_key)?; let message_blob = cryptoblob::seal(mail.raw, &message_key)?;
let mut por = PutObjectRequest::default(); let por = PutObjectRequest {
por.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
por.key = format!("{}/{}", self.mail_path, ident); key: format!("{}/{}", self.mail_path, ident),
por.body = Some(message_blob.into()); body: Some(message_blob.into()),
..Default::default()
};
self.s3.put_object(por).await?; self.s3.put_object(por).await?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}, },
@ -349,11 +356,13 @@ impl MailboxInternal {
futures::try_join!( futures::try_join!(
async { async {
// Copy mail body from previous location // Copy mail body from previous location
let mut cor = CopyObjectRequest::default(); let cor = CopyObjectRequest {
cor.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
cor.key = format!("{}/{}", self.mail_path, ident); key: format!("{}/{}", self.mail_path, ident),
cor.copy_source = format!("{}/{}", self.bucket, s3_key); copy_source: format!("{}/{}", self.bucket, s3_key),
cor.metadata_directive = Some("REPLACE".into()); metadata_directive: Some("REPLACE".into()),
..Default::default()
};
self.s3.copy_object(cor).await?; self.s3.copy_object(cor).await?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}, },
@ -393,9 +402,11 @@ impl MailboxInternal {
futures::try_join!( futures::try_join!(
async { async {
// Delete mail body from S3 // Delete mail body from S3
let mut dor = DeleteObjectRequest::default(); let dor = DeleteObjectRequest{
dor.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
dor.key = format!("{}/{}", self.mail_path, ident); key: format!("{}/{}", self.mail_path, ident),
..Default::default()
};
self.s3.delete_object(dor).await?; self.s3.delete_object(dor).await?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}, },
@ -422,6 +433,8 @@ impl MailboxInternal {
Ok(new_id) Ok(new_id)
} }
#[allow(dead_code)]
// 2023-05-15 will probably be used later
async fn move_from(&mut self, from: &mut MailboxInternal, id: UniqueIdent) -> Result<()> { async fn move_from(&mut self, from: &mut MailboxInternal, id: UniqueIdent) -> Result<()> {
self.copy_internal(from, id, id).await?; self.copy_internal(from, id, id).await?;
from.delete(id).await?; from.delete(id).await?;
@ -450,10 +463,13 @@ impl MailboxInternal {
futures::try_join!( futures::try_join!(
async { async {
// Copy mail body from S3 // Copy mail body from S3
let mut cor = CopyObjectRequest::default(); let cor = CopyObjectRequest{
cor.bucket = self.bucket.clone(); bucket: self.bucket.clone(),
cor.key = format!("{}/{}", self.mail_path, new_id); key: format!("{}/{}", self.mail_path, new_id),
cor.copy_source = format!("{}/{}/{}", from.bucket, from.mail_path, source_id); copy_source: format!("{}/{}/{}", from.bucket, from.mail_path, source_id),
..Default::default()
};
self.s3.copy_object(cor).await?; self.s3.copy_object(cor).await?;
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
}, },
@ -491,7 +507,7 @@ fn dump(uid_index: &Bayou<UidIndex>) {
s.table.get(ident).cloned().unwrap().1.join(", ") s.table.get(ident).cloned().unwrap().1.join(", ")
); );
} }
println!(""); println!();
} }
// ---- // ----

View file

@ -9,6 +9,8 @@ pub mod user;
// Internet Message Format // Internet Message Format
// aka RFC 822 - RFC 2822 - RFC 5322 // aka RFC 822 - RFC 2822 - RFC 5322
// 2023-05-15 don't want to refactor this struct now.
#[allow(clippy::upper_case_acronyms)]
pub struct IMF<'a> { pub struct IMF<'a> {
raw: &'a [u8], raw: &'a [u8],
parsed: mail_parser::Message<'a>, parsed: mail_parser::Message<'a>,

View file

@ -73,9 +73,9 @@ impl UidIndex {
// INTERNAL functions to keep state consistent // INTERNAL functions to keep state consistent
fn reg_email(&mut self, ident: UniqueIdent, uid: ImapUid, flags: &Vec<Flag>) { fn reg_email(&mut self, ident: UniqueIdent, uid: ImapUid, flags: &[Flag]) {
// Insert the email in our table // Insert the email in our table
self.table.insert(ident, (uid, flags.clone())); self.table.insert(ident, (uid, flags.to_owned()));
// Update the indexes/caches // Update the indexes/caches
self.idx_by_uid.insert(uid, ident); self.idx_by_uid.insert(uid, ident);
@ -205,7 +205,7 @@ impl FlagIndex {
fn new() -> Self { fn new() -> Self {
Self(HashMap::new()) Self(HashMap::new())
} }
fn insert(&mut self, uid: ImapUid, flags: &Vec<Flag>) { fn insert(&mut self, uid: ImapUid, flags: &[Flag]) {
flags.iter().for_each(|flag| { flags.iter().for_each(|flag| {
self.0 self.0
.entry(flag.clone()) .entry(flag.clone())
@ -213,7 +213,7 @@ impl FlagIndex {
.insert(uid); .insert(uid);
}); });
} }
fn remove(&mut self, uid: ImapUid, flags: &Vec<Flag>) -> () { fn remove(&mut self, uid: ImapUid, flags: &[Flag]) {
for flag in flags.iter() { for flag in flags.iter() {
if let Some(set) = self.0.get_mut(flag) { if let Some(set) = self.0.get_mut(flag) {
set.remove(&uid); set.remove(&uid);

View file

@ -257,7 +257,7 @@ impl User {
let saved; let saved;
let (inbox_id, inbox_uidvalidity) = match list.create_mailbox(INBOX) { let (inbox_id, inbox_uidvalidity) = match list.create_mailbox(INBOX) {
CreatedMailbox::Created(i, v) => { CreatedMailbox::Created(i, v) => {
self.save_mailbox_list(&list, ct.clone()).await?; self.save_mailbox_list(list, ct.clone()).await?;
saved = true; saved = true;
(i, v) (i, v)
} }
@ -334,23 +334,17 @@ impl MailboxList {
} }
fn has_mailbox(&self, name: &str) -> bool { fn has_mailbox(&self, name: &str) -> bool {
match self.0.get(name) { matches!(self.0.get(name), Some(MailboxListEntry {
Some(MailboxListEntry {
id_lww: (_, Some(_)), id_lww: (_, Some(_)),
.. ..
}) => true, }))
_ => false,
}
} }
fn get_mailbox(&self, name: &str) -> Option<(ImapUidvalidity, Option<UniqueIdent>)> { fn get_mailbox(&self, name: &str) -> Option<(ImapUidvalidity, Option<UniqueIdent>)> {
match self.0.get(name) { self.0.get(name).map(|MailboxListEntry {
None => None,
Some(MailboxListEntry {
id_lww: (_, mailbox_id), id_lww: (_, mailbox_id),
uidvalidity, uidvalidity,
}) => Some((*uidvalidity, *mailbox_id)), }| (*uidvalidity, *mailbox_id))
}
} }
/// Ensures mailbox `name` maps to id `id`. /// Ensures mailbox `name` maps to id `id`.

View file

@ -121,7 +121,7 @@ async fn main() -> Result<()> {
// Abort on panic (same behavior as in Go) // Abort on panic (same behavior as in Go)
std::panic::set_hook(Box::new(|panic_info| { std::panic::set_hook(Box::new(|panic_info| {
eprintln!("{}", panic_info.to_string()); eprintln!("{}", panic_info);
eprintln!("{:?}", backtrace::Backtrace::new()); eprintln!("{:?}", backtrace::Backtrace::new());
std::process::abort(); std::process::abort();
})); }));
@ -292,7 +292,7 @@ fn make_user_secrets(c: UserSecretsArgs) -> UserSecrets {
user_secret: c.user_secret, user_secret: c.user_secret,
alternate_user_secrets: c alternate_user_secrets: c
.alternate_user_secrets .alternate_user_secrets
.split(",") .split(',')
.map(|x| x.trim()) .map(|x| x.trim())
.filter(|x| !x.is_empty()) .filter(|x| !x.is_empty())
.map(|x| x.to_string()) .map(|x| x.to_string())