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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
/* codes are arranged according to rfc5321 + rfc7504:

   2yz  Positive Completion reply
      The requested action has been successfully completed.  A new
      request may be initiated.

   3yz  Positive Intermediate reply
      The command has been accepted, but the requested action is being
      held in abeyance, pending receipt of further information.  The
      SMTP client should send another command specifying this
      information.  This reply is used in command sequence groups (i.e.,
      in DATA).

   4yz  Transient Negative Completion reply
      The command was not accepted, and the requested action did not
      occur.  However, the error condition is temporary, and the action
      may be requested again.  The sender should return to the beginning
      of the command sequence (if any).  It is difficult to assign a
      meaning to "transient" when two different sites (receiver- and
      sender-SMTP agents) must agree on the interpretation.  Each reply
      in this category might have a different time value, but the SMTP
      client SHOULD try again.  A rule of thumb to determine whether a
      reply fits into the 4yz or the 5yz category (see below) is that
      replies are 4yz if they can be successful if repeated without any
      change in command form or in properties of the sender or receiver
      (that is, the command is repeated identically and the receiver
      does not put up a new implementation).

   5yz  Permanent Negative Completion reply
      The command was not accepted and the requested action did not
      occur.  The SMTP client SHOULD NOT repeat the exact request (in
      the same sequence).  Even some "permanent" error conditions can be
      corrected, so the human user may want to direct the SMTP client to
      reinitiate the command sequence by direct action at some point in
      the future (e.g., after the spelling has been changed, or the user
      has altered the account status).

   x0z  Syntax: These replies refer to syntax errors, syntactically
      correct commands that do not fit any functional category, and
      unimplemented or superfluous commands.

   x1z  Information: These are replies to requests for information, such
      as status or help.

   x2z  Connections: These are replies referring to the transmission
      channel.

   x3z  Unspecified.

   x4z  Unspecified.

   x5z  Mail system: These replies indicate the status of the receiver
      mail system vis-a-vis the requested transfer or other mail system
      action.
*/
use crate::smtp::SmtpReply::*;
use std::fmt;

#[derive(Eq, PartialEq, Debug, Clone)]
pub enum SmtpReply {
    // I'm using a suffix to make names sound english:
    // 2xx => ...Info
    // 3xx => ...Challenge
    // 4xx => ...Error
    // 5xx => ...Failure
    /// no response should be given
    None,

    /// 500
    CommandSyntaxFailure,
    /// 501
    ParameterSyntaxFailure,
    /// 502
    CommandNotImplementedFailure,
    /// 503
    CommandSequenceFailure,
    /*504*/
    UnexpectedParameterFailure,

    /// 211
    StatusInfo(String),
    /// 214
    HelpInfo(String),

    /// 220 @domain service ready
    ServiceReadyInfo(String),
    /// 221 @domain service closing transmission channel
    ClosingConnectionInfo(String),
    /// 421 @domain service not available, closing transmission channel
    ServiceNotAvailableError(String),
    /// 521 RFC 7504
    MailNotAcceptedByHostFailure,

    /// 250 Ok
    OkInfo,
    /// 250 @message
    OkMessageInfo(String),
    /// 250 response to HELO/EHLO/LHLO
    OkHeloInfo {
        local: String,
        remote: String,
        extensions: Vec<String>,
    },
    /// 251 will forward to @forward-path (See Section 3.4)
    UserNotLocalInfo(String),
    /// 252 but will accept message and attempt delivery (See Section 3.5.3)
    CannotVerifyUserInfo,
    /// 354 start mail, end with CRLF.CRLF
    StartMailInputChallenge,
    /// 450 Requested mail action not taken (e.g., mailbox busy
    ///     or temporarily blocked for policy reasons)
    MailboxNotAvailableError,
    /// 451 Requested action aborted
    ProcesingError,
    /// 452 Requested action not taken
    StorageError,
    /// 455 right now the parameters given cannot be accomodated
    ParametersNotAccommodatedError,
    /// 550 Requested action not taken: mailbox unavailable (e.g.,
    ///     mailbox not found, no access, or command rejected for policy reasons)
    MailboxNotAvailableFailure,
    /// 551 please try @forward-path (See Section 3.4)
    UserNotLocalFailure(String),
    /// 552 Requested mail action aborted: exceeded storage allocation
    StorageFailure,
    /// 553 Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect)
    MailboxNameInvalidFailure,
    /// 554 (Or, in the case of a connection-opening response, "No SMTP service here")
    TransactionFailure,
    /// 555 MAIL FROM/RCPT TO parameters not recognized or not implemented
    UnknownMailParametersFailure,
    /// 556 RFC 7504
    MailNotAcceptedByDomainFailure,
}

impl SmtpReply {
    pub fn code(&self) -> u16 {
        match *self {
            /* &Custom(ref class, ref category, ref digit, _, _) => {
                *class as u16 + *category as u16 + *digit as u16
            }*/
            None => 0,
            CommandSyntaxFailure => 500,
            ParameterSyntaxFailure => 501,
            CommandNotImplementedFailure => 502,
            CommandSequenceFailure => 503,
            UnexpectedParameterFailure => 504,

            StatusInfo(_) => 211,
            HelpInfo(_) => 214,

            // @domain service ready
            ServiceReadyInfo(_) => 220,
            // @domain service closing transmission channel
            ClosingConnectionInfo(_) => 221,
            // @domain service not available, closing transmission channel
            ServiceNotAvailableError(_) => 421,
            // RFC 7504
            MailNotAcceptedByHostFailure => 521,

            // first line is either Ok or specific message
            OkInfo => 250,
            OkMessageInfo(_) => 250,
            OkHeloInfo { .. } => 250,
            // will forward to @forward-path (See Section 3.4)
            UserNotLocalInfo(_) => 251,
            //, but will accept message and attempt delivery (See Section 3.5.3)
            CannotVerifyUserInfo => 252,
            // end with CRLF.CRLF
            StartMailInputChallenge => 354,
            // Requested mail action not taken (e.g., mailbox busy
            // or temporarily blocked for policy reasons)
            MailboxNotAvailableError => 450,
            // Requested action aborted
            ProcesingError => 451,
            // Requested action not taken
            StorageError => 452,
            // right now the parameters given cannot be accomodated
            ParametersNotAccommodatedError => 455,
            // Requested action not taken: mailbox unavailable (e.g.,
            // mailbox not found, no access, or command rejected for policy reasons)
            MailboxNotAvailableFailure => 550,
            // please try @forward-path (See Section 3.4)
            UserNotLocalFailure(_) => 551,
            // Requested mail action aborted: exceeded storage allocation
            StorageFailure => 552,
            // Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect)
            MailboxNameInvalidFailure => 553,
            // (Or, in the case of a connection-opening response, "No SMTP service here")
            TransactionFailure => 554,
            // MAIL FROM/RCPT TO parameters not recognized or not implemented
            UnknownMailParametersFailure => 555,
            // RFC 7504
            MailNotAcceptedByDomainFailure => 556,
        }
    }

    pub fn text(&self) -> String {
        match *self {
            None => "".to_owned(),
            CommandSyntaxFailure => "Syntax error, command unrecognized".to_owned(),
            ParameterSyntaxFailure => "Syntax error in parameters or arguments".to_owned(),
            CommandNotImplementedFailure => "Command not implemented".to_owned(),
            CommandSequenceFailure => "Bad sequence of commands".to_owned(),
            UnexpectedParameterFailure => "Command parameter not implemented".to_owned(),

            StatusInfo(ref text) => text.to_string(),
            HelpInfo(ref text) => text.to_string(),

            ServiceReadyInfo(ref domain) => format!("{} service ready", domain),
            ClosingConnectionInfo(ref domain) => {
                format!("{} service closing transmission channel", domain)
            }
            ServiceNotAvailableError(ref domain) => format!(
                "{} service not available, closing transmission channel",
                domain
            ),
            MailNotAcceptedByHostFailure => "Host does not accept mail".to_owned(),

            OkInfo => "Ok".to_owned(),
            OkMessageInfo(ref text) => text.to_string(),
            OkHeloInfo {
                ref local,
                ref remote,
                ..
            } => format!("{} greets {}", local, remote),

            UserNotLocalInfo(ref forward_path) => {
                format!("User not local, will forward to {}", forward_path)
            }
            CannotVerifyUserInfo => {
                "Cannot VFRY user, but will accept message and attempt delivery".to_owned()
            }
            StartMailInputChallenge => "Start mail input, end with <CRLF>.<CRLF>".to_owned(),
            MailboxNotAvailableError => {
                "Requested mail action not taken: mailbox unavailable".to_owned()
            }
            ProcesingError => "Requested action aborted: error in processing.".to_owned(),

            StorageError => "Requested action not taken: insufficient system storage".to_owned(),
            ParametersNotAccommodatedError => "Server unable to accommodate parameters".to_owned(),
            MailboxNotAvailableFailure => {
                "Requested action not taken: mailbox unavailable".to_owned()
            }
            UserNotLocalFailure(ref forward_path) => {
                format!("User not local; please try {}", forward_path)
            }
            StorageFailure => {
                "Requested mail action aborted: exceeded storage allocation".to_owned()
            }
            MailboxNameInvalidFailure => {
                "Requested action not taken: mailbox name not allowed".to_owned()
            }
            TransactionFailure => "Transaction failed".to_owned(),
            UnknownMailParametersFailure => {
                "MAIL FROM/RCPT TO parameters not recognized or not implemented".to_owned()
            }
            MailNotAcceptedByDomainFailure => "Domain does not accept mail".to_owned(),
        }
    }
    pub fn items(&self) -> Vec<String> {
        match *self {
            OkHeloInfo { ref extensions, .. } => extensions.iter().map(|e| e.to_string()).collect(),
            _ => vec![],
        }
    }
    pub fn class(&self) -> SmtpReplyClass {
        match self.code() {
            0..=299 => SmtpReplyClass::Info,
            300..=399 => SmtpReplyClass::Challenge,
            400..=499 => SmtpReplyClass::Error,
            _ => SmtpReplyClass::Failure,
        }
    }
    pub fn category(&self) -> SmtpReplyCategory {
        match self.code() % 100 {
            0..=9 => SmtpReplyCategory::Syntax,
            10..=19 => SmtpReplyCategory::Information,
            20..=29 => SmtpReplyCategory::Connections,
            30..=39 => SmtpReplyCategory::Reserved3,
            40..=49 => SmtpReplyCategory::Reserved4,
            _ => SmtpReplyCategory::System,
        }
    }
    pub fn digit(&self) -> SmtpReplyDigit {
        match self.code() % 10 {
            0 => SmtpReplyDigit::D0,
            1 => SmtpReplyDigit::D1,
            2 => SmtpReplyDigit::D2,
            3 => SmtpReplyDigit::D3,
            4 => SmtpReplyDigit::D4,
            5 => SmtpReplyDigit::D5,
            6 => SmtpReplyDigit::D6,
            7 => SmtpReplyDigit::D7,
            8 => SmtpReplyDigit::D8,
            _ => SmtpReplyDigit::D9,
        }
    }
}

impl fmt::Display for SmtpReply {
    fn fmt<'a>(&self, mut buf: &'a mut fmt::Formatter) -> Result<(), fmt::Error> {
        let code = self.code();
        let text = self.text();
        let items = self.items();

        debug_assert!(!text.contains('\n'), "text line must not contain new lines");

        if items.is_empty() {
            write_reply_end(&mut buf, code, &text)?;
        } else {
            write_reply_continued(&mut buf, code, &text)?;
            for i in 0..items.len() {
                if i == items.len() - 1 {
                    write_reply_end(&mut buf, code, &items[i])?;
                } else {
                    write_reply_continued(&mut buf, code, &items[i])?;
                }
            }
        }
        Ok(())
    }
}
fn write_reply_end(buf: &mut dyn fmt::Write, code: u16, text: &str) -> Result<(), fmt::Error> {
    write!(buf, "{} {}\r\n", code, text)
}
fn write_reply_continued(
    buf: &mut dyn fmt::Write,
    code: u16,
    text: &str,
) -> Result<(), fmt::Error> {
    write!(buf, "{}-{}\r\n", code, text)
}

#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum SmtpReplyClass {
    Info = 200,
    Challenge = 300,
    Error = 400,
    Failure = 500,
}

#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum SmtpReplyCategory {
    Syntax = 0,
    Information = 10,
    Connections = 20,
    Reserved3 = 30,
    Reserved4 = 40,
    System = 50,
}

#[derive(Eq, PartialEq, Debug, Copy, Clone)]
pub enum SmtpReplyDigit {
    D0 = 0,
    D1 = 1,
    D2 = 2,
    D3 = 3,
    D4 = 4,
    D5 = 5,
    D6 = 6,
    D7 = 7,
    D8 = 8,
    D9 = 9,
}