450 lines
14 KiB
Rust
450 lines
14 KiB
Rust
mod error;
|
|
pub use error::Error;
|
|
|
|
#[cfg(feature = "serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
#[repr(u8)]
|
|
/// Status codes sent back to the sender representing how a receiving server has
|
|
/// processed a message.
|
|
pub enum Status {
|
|
/// These codes are reserved, and must not be sent by a Misfin server.
|
|
Input = 10,
|
|
/// Status codes beginning with 2 are SUCCESS status codes, which mean
|
|
/// that the client's message has been delivered.
|
|
Success = 20,
|
|
/// The mailbox has moved to a different address, and this message should
|
|
/// be resent to that address.
|
|
Redirect(Redirect) = 30,
|
|
/// Status codes beginning with 5 are PERMANENT FAILURE status codes, which
|
|
/// mean the request did not succeed, and should not be retried.
|
|
TemporaryFailure(TemporaryFailure) = 40,
|
|
/// Something is wrong with the mailserver, and you should not try to
|
|
/// resend your message.
|
|
PermanentFailure(PermanentFailure) = 50,
|
|
/// Status codes beginning with 6 are AUTHENTICATION FAILURE status codes,
|
|
/// which mean that there was an issue with the client's certificate.
|
|
AuthenticationFailure(AuthenticationFailure) = 60,
|
|
}
|
|
|
|
impl From<Status> for u8 {
|
|
fn from(value: Status) -> Self {
|
|
match value {
|
|
Status::Input => 10,
|
|
Status::Success => 20,
|
|
Status::Redirect(n) => 30 + n as u8,
|
|
Status::TemporaryFailure(n) => 40 + n as u8,
|
|
Status::PermanentFailure(n) => 50 + n as u8,
|
|
Status::AuthenticationFailure(n) => 60 + n as u8,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u8> for Status {
|
|
type Error = Error;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
match value / 10 {
|
|
1 => Ok(Self::Input),
|
|
2 => Ok(Self::Success),
|
|
3 => Ok(Self::Redirect((value % 30).try_into()?)),
|
|
4 => Ok(Self::TemporaryFailure((value % 40).try_into()?)),
|
|
5 => Ok(Self::PermanentFailure((value % 50).try_into()?)),
|
|
6 => Ok(Self::AuthenticationFailure((value % 60).try_into()?)),
|
|
_ => Err(Error),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
/// Status codes representing that a redirect is required
|
|
pub enum Redirect {
|
|
/// The mailbox has moved to a different address, and this message
|
|
/// should be resent to that address.
|
|
Temporary = 0,
|
|
/// The mailbox has moved to a different address, and all future
|
|
/// messages should be sent to that address.
|
|
Permanent = 1,
|
|
Other = 2,
|
|
}
|
|
|
|
impl TryFrom<u8> for Redirect {
|
|
type Error = Error;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
match value {
|
|
0 => Ok(Self::Temporary),
|
|
1 => Ok(Self::Permanent),
|
|
n if n < 10 => Ok(Self::Other),
|
|
_ => Err(Error),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
/// Status codes representing that a temporary failure has occurred. The sending server should
|
|
/// retry sending the message.
|
|
pub enum TemporaryFailure {
|
|
/// The mailserver experienced a transient issue, and the message
|
|
/// should be resent.
|
|
TemporaryError = 0,
|
|
/// The mailserver can't accept mail right now.
|
|
ServerUnavailable = 1,
|
|
/// A mailserver script ran for your message, but experienced an error.
|
|
CgiError = 2,
|
|
/// There was a problem accepting mail for that domain, but it might
|
|
/// resolve itself.
|
|
ProxyError = 3,
|
|
/// You are being rate limited - wait before trying to send more mail.
|
|
RateLimit = 4,
|
|
/// The mailbox isn't accepting mail right now, but it might in the future.
|
|
MailboxFull = 5,
|
|
Other = 6,
|
|
}
|
|
|
|
impl TryFrom<u8> for TemporaryFailure {
|
|
type Error = Error;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
match value {
|
|
0 => Ok(Self::TemporaryError),
|
|
1 => Ok(Self::ServerUnavailable),
|
|
2 => Ok(Self::CgiError),
|
|
3 => Ok(Self::ProxyError),
|
|
4 => Ok(Self::RateLimit),
|
|
5 => Ok(Self::MailboxFull),
|
|
n if n < 10 => Ok(Self::Other),
|
|
_ => Err(Error),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
/// Status codes representing that a permanent failure has occurred and the sending
|
|
/// server should not resend the message.
|
|
pub enum PermanentFailure {
|
|
/// Something is wrong with the mailserver, and you should not try to resend
|
|
/// your message.
|
|
PermanentError = 0,
|
|
/// The mailbox you are trying to send to doesn't exist, and the mailserver
|
|
/// won't accept your message.
|
|
MailboxNonexistent = 1,
|
|
/// The mailbox you are trying to send to existed once, but doesn't anymore.
|
|
MailboxGone = 2,
|
|
/// This mailserver doesn't serve mail for the hostname you provided.
|
|
DomainNotServiced = 3,
|
|
/// Your request is malformed, and won't be accepted by the mailserver.
|
|
BadRequest = 9,
|
|
Other = 4,
|
|
}
|
|
|
|
impl TryFrom<u8> for PermanentFailure {
|
|
type Error = Error;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
match value {
|
|
0 => Ok(Self::PermanentError),
|
|
1 => Ok(Self::MailboxNonexistent),
|
|
2 => Ok(Self::MailboxGone),
|
|
3 => Ok(Self::DomainNotServiced),
|
|
9 => Ok(Self::BadRequest),
|
|
n if n < 10 => Ok(Self::Other),
|
|
_ => Err(Error),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
/// Status codes representing an authentication failure
|
|
pub enum AuthenticationFailure {
|
|
/// This mailserver doesn't accept anonymous mail, and you need to repeat your
|
|
/// request with a certificate.
|
|
CertificateRequired = 0,
|
|
/// Your certificate was validated, but you are not allowed to send mail to
|
|
/// that mailbox.
|
|
UnauthorizedSender = 1,
|
|
/// Your certificate might be legitimate, but it has a problem - it is expired,
|
|
/// or it doesn't point to a valid Misfin identity, etc.
|
|
CertificateInvalid = 2,
|
|
/// Your certificate matches an identity that the mailserver recognizes, but
|
|
/// the fingerprint has changed, so it is rejecting your message.
|
|
IdentityMismatch = 3,
|
|
/// The mailserver needs you to complete a task to confirm that you are a
|
|
/// legitimate sender. (This is reserved for a Hashcash style anti-spam measure).
|
|
ProofRequired = 4,
|
|
Other = 5,
|
|
}
|
|
|
|
impl TryFrom<u8> for AuthenticationFailure {
|
|
type Error = Error;
|
|
|
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
match value {
|
|
0 => Ok(Self::CertificateRequired),
|
|
1 => Ok(Self::UnauthorizedSender),
|
|
2 => Ok(Self::CertificateInvalid),
|
|
3 => Ok(Self::IdentityMismatch),
|
|
4 => Ok(Self::ProofRequired),
|
|
n if n < 10 => Ok(Self::Other),
|
|
_ => Err(Error),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn to_number_success() {
|
|
let num: u8 = Status::Success.into();
|
|
assert_eq!(num, 20);
|
|
}
|
|
|
|
#[test]
|
|
fn to_number_redirect() {
|
|
assert_eq!(u8::from(Status::Redirect(Redirect::Temporary)), 30);
|
|
assert_eq!(u8::from(Status::Redirect(Redirect::Permanent)), 31);
|
|
assert_eq!(u8::from(Status::Redirect(Redirect::Other)), 32);
|
|
}
|
|
|
|
#[test]
|
|
fn to_number_temporary() {
|
|
assert_eq!(
|
|
u8::from(Status::TemporaryFailure(TemporaryFailure::TemporaryError)),
|
|
40
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::TemporaryFailure(
|
|
TemporaryFailure::ServerUnavailable
|
|
)),
|
|
41
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::TemporaryFailure(TemporaryFailure::CgiError)),
|
|
42
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::TemporaryFailure(TemporaryFailure::ProxyError)),
|
|
43
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::TemporaryFailure(TemporaryFailure::RateLimit)),
|
|
44
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::TemporaryFailure(TemporaryFailure::MailboxFull)),
|
|
45
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::TemporaryFailure(TemporaryFailure::Other)),
|
|
46
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn to_number_permanent() {
|
|
assert_eq!(
|
|
u8::from(Status::PermanentFailure(PermanentFailure::PermanentError)),
|
|
50
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::PermanentFailure(
|
|
PermanentFailure::MailboxNonexistent
|
|
)),
|
|
51
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::PermanentFailure(PermanentFailure::MailboxGone)),
|
|
52
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::PermanentFailure(
|
|
PermanentFailure::DomainNotServiced
|
|
)),
|
|
53
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::PermanentFailure(PermanentFailure::BadRequest)),
|
|
59
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::PermanentFailure(PermanentFailure::Other)),
|
|
54
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn to_number_auth() {
|
|
assert_eq!(
|
|
u8::from(Status::AuthenticationFailure(
|
|
AuthenticationFailure::CertificateRequired
|
|
)),
|
|
60
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::AuthenticationFailure(
|
|
AuthenticationFailure::UnauthorizedSender
|
|
)),
|
|
61
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::AuthenticationFailure(
|
|
AuthenticationFailure::CertificateInvalid
|
|
)),
|
|
62
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::AuthenticationFailure(
|
|
AuthenticationFailure::IdentityMismatch
|
|
)),
|
|
63
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::AuthenticationFailure(
|
|
AuthenticationFailure::ProofRequired
|
|
)),
|
|
64
|
|
);
|
|
assert_eq!(
|
|
u8::from(Status::AuthenticationFailure(AuthenticationFailure::Other)),
|
|
65
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_status_success() {
|
|
let status = Status::try_from(21).unwrap();
|
|
assert_eq!(status, Status::Success);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_redirect_err() {
|
|
assert!(Redirect::try_from(42).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_status_redirect() {
|
|
assert_eq!(
|
|
Status::try_from(30).unwrap(),
|
|
Status::Redirect(Redirect::Temporary)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(31).unwrap(),
|
|
Status::Redirect(Redirect::Permanent)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(32).unwrap(),
|
|
Status::Redirect(Redirect::Other)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_temporary_err() {
|
|
assert!(TemporaryFailure::try_from(42).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_status_temporary() {
|
|
assert_eq!(
|
|
Status::try_from(40).unwrap(),
|
|
Status::TemporaryFailure(TemporaryFailure::TemporaryError)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(41).unwrap(),
|
|
Status::TemporaryFailure(TemporaryFailure::ServerUnavailable)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(42).unwrap(),
|
|
Status::TemporaryFailure(TemporaryFailure::CgiError)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(43).unwrap(),
|
|
Status::TemporaryFailure(TemporaryFailure::ProxyError)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(44).unwrap(),
|
|
Status::TemporaryFailure(TemporaryFailure::RateLimit)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(45).unwrap(),
|
|
Status::TemporaryFailure(TemporaryFailure::MailboxFull)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(46).unwrap(),
|
|
Status::TemporaryFailure(TemporaryFailure::Other)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_permanent_err() {
|
|
assert!(PermanentFailure::try_from(42).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_status_permanent() {
|
|
assert_eq!(
|
|
Status::try_from(50).unwrap(),
|
|
Status::PermanentFailure(PermanentFailure::PermanentError)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(51).unwrap(),
|
|
Status::PermanentFailure(PermanentFailure::MailboxNonexistent)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(52).unwrap(),
|
|
Status::PermanentFailure(PermanentFailure::MailboxGone)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(53).unwrap(),
|
|
Status::PermanentFailure(PermanentFailure::DomainNotServiced)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(59).unwrap(),
|
|
Status::PermanentFailure(PermanentFailure::BadRequest)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(57).unwrap(),
|
|
Status::PermanentFailure(PermanentFailure::Other)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_auth_err() {
|
|
assert!(AuthenticationFailure::try_from(42).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_status_auth() {
|
|
assert_eq!(
|
|
Status::try_from(60).unwrap(),
|
|
Status::AuthenticationFailure(AuthenticationFailure::CertificateRequired)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(61).unwrap(),
|
|
Status::AuthenticationFailure(AuthenticationFailure::UnauthorizedSender)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(62).unwrap(),
|
|
Status::AuthenticationFailure(AuthenticationFailure::CertificateInvalid)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(63).unwrap(),
|
|
Status::AuthenticationFailure(AuthenticationFailure::IdentityMismatch)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(64).unwrap(),
|
|
Status::AuthenticationFailure(AuthenticationFailure::ProofRequired)
|
|
);
|
|
assert_eq!(
|
|
Status::try_from(65).unwrap(),
|
|
Status::AuthenticationFailure(AuthenticationFailure::Other)
|
|
);
|
|
}
|
|
}
|