1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
use log::trace;
use std::io;

// Empty response that sends nothing back to the client
pub(crate) const EMPTY_RESPONSE: Response = Response::empty();
// Start TLS handshake
pub(crate) const START_TLS: Response =
    Response::fixed_action(220, "Ready to start TLS", Action::UpgradeTls);
/// Response to indicate that the SMTP session finished
pub const GOODBYE: Response = Response::fixed(221, "Goodbye");
/// Authentication succeeded
pub const AUTH_OK: Response = Response::fixed(235, "Authentication succeeded");
/// OK response
pub const OK: Response = Response::fixed(250, "OK");
// Non-commital response to VERIFY command
pub(crate) const VERIFY_RESPONSE: Response = Response::fixed(252, "Maybe");
// Empty response sent as an auth challenge.
pub(crate) const EMPTY_AUTH_CHALLENGE: Response = Response::fixed(334, "");
/// Response sent to the client before accepting data
pub const START_DATA: Response = Response::fixed(354, "Start mail input; end with <CRLF>.<CRLF>");
// State machine is not accepting commands
pub(crate) const INVALID_STATE: Response =
    Response::fixed(421, "Internal service error, closing connection");
/// Service not available
pub const NO_SERVICE: Response = Response::fixed(421, "Service not available, closing connection");
/// Internal server error
pub const INTERNAL_ERROR: Response = Response::fixed(451, "Aborted: local error in processing");
/// Insufficient system storage
pub const OUT_OF_SPACE: Response = Response::fixed(452, "Insufficient system storage");
/// Authentication system is not working
pub const TEMP_AUTH_FAILURE: Response = Response::fixed(454, "Temporary authentication failure");
// Parser error
pub(crate) const SYNTAX_ERROR: Response = Response::fixed(500, "Syntax error");
// Parser found missing parameter
pub(crate) const MISSING_PARAMETER: Response = Response::fixed(502, "Missing parameter");
// Command is unexpected for the current state
pub(crate) const BAD_SEQUENCE_COMMANDS: Response = Response::fixed(503, "Bad sequence of commands");
/// User storage quota exceeded
pub const NO_STORAGE: Response = Response::fixed(552, "Exceeded storage allocation");
/// Authentication required
pub const AUTHENTICATION_REQUIRED: Response = Response::fixed(530, "Authentication required");
/// Bad authentication attempt
pub const INVALID_CREDENTIALS: Response = Response::fixed(535, "Invalid credentials");
/// Unknown user
pub const NO_MAILBOX: Response = Response::fixed(550, "Mailbox unavailable");
/// Error with HELO
pub const BAD_HELLO: Response = Response::fixed(550, "Bad HELO");
/// IP address on blocklists
pub const BLOCKED_IP: Response = Response::fixed(550, "IP address on blocklists");
/// Invalid mailbox name
pub const BAD_MAILBOX: Response = Response::fixed(553, "Mailbox name not allowed");
/// Error handling incoming message
pub const TRANSACTION_FAILED: Response = Response::fixed(554, "Transaction failed");

/// Response contains a code and message to be sent back to the client
#[derive(Clone, Debug, PartialEq)]
pub struct Response {
    /// The three digit response code
    pub code: u16,
    /// The text message
    message: Message,
    /// Is the response an error response?
    pub is_error: bool,
    /// The action to take after sending the response to the client
    pub action: Action,
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Message {
    Fixed(&'static str),
    Custom(String),
    Dynamic(String, Vec<&'static str>),
    Empty,
}

/// Action indicates the recommended action to take on a response
#[derive(PartialEq, Clone, Debug)]
pub enum Action {
    /// Send the response and close the connection
    Close,
    /// Upgrade the connection to use TLS
    UpgradeTls,
    /// Do not reply, wait for the client to send more data
    NoReply,
    /// Send a reply and keep the connection open
    Reply,
}

impl Response {
    // A response that uses a fixed static string
    pub(crate) const fn fixed(code: u16, message: &'static str) -> Self {
        Self::fixed_action(code, message, Response::action_from_code(code))
    }

    const fn action_from_code(code: u16) -> Action {
        match code {
            221 | 421 => Action::Close,
            _ => Action::Reply,
        }
    }

    // A response that uses a fixed static string and a given action
    pub(crate) const fn fixed_action(code: u16, message: &'static str, action: Action) -> Self {
        Self {
            code,
            message: Message::Fixed(message),
            is_error: (code < 200 || code >= 400),
            action,
        }
    }

    /// Create an application defined response.
    pub const fn custom(code: u16, message: String) -> Self {
        Self {
            code,
            message: Message::Custom(message),
            is_error: (code < 200 || code >= 400),
            action: Response::action_from_code(code),
        }
    }

    // A response that is built dynamically and can be a multiline response
    pub(crate) fn dynamic(code: u16, head: String, tail: Vec<&'static str>) -> Self {
        Self {
            code,
            message: Message::Dynamic(head, tail),
            is_error: false,
            action: Action::Reply,
        }
    }

    // An empty response
    pub(crate) const fn empty() -> Self {
        Self {
            code: 0,
            message: Message::Empty,
            is_error: false,
            action: Action::NoReply,
        }
    }

    /// Write the response to the given writer
    pub fn write_to(&self, out: &mut dyn io::Write) -> io::Result<()> {
        match &self.message {
            Message::Dynamic(ref head, ref tail) => {
                if tail.is_empty() {
                    write!(out, "{} {}\r\n", self.code, head)?;
                } else {
                    write!(out, "{}-{}\r\n", self.code, head)?;
                    for i in 0..tail.len() {
                        if tail.len() > 1 && i < tail.len() - 1 {
                            write!(out, "{}-{}\r\n", self.code, tail[i])?;
                        } else {
                            write!(out, "{} {}\r\n", self.code, tail[i])?;
                        }
                    }
                }
            }
            Message::Fixed(s) => write!(out, "{} {}\r\n", self.code, s)?,
            Message::Custom(s) => write!(out, "{} {}\r\n", self.code, s)?,
            Message::Empty => (),
        };
        Ok(())
    }

    // Log the response
    pub(crate) fn log(&self) {
        match self.message {
            Message::Empty => (),
            _ => {
                let mut buf = Vec::new();
                let _ = self.write_to(&mut buf);
                trace!("< {}", String::from_utf8_lossy(&buf));
            }
        }
    }
}