package response import ( "fmt" ) const ( // ClassSuccess specifies that the DSN is reporting a positive delivery // action. Detail sub-codes may provide notification of // transformations required for delivery. ClassSuccess = 2 // ClassTransientFailure - a persistent transient failure is one in which the message as // sent is valid, but persistence of some temporary condition has // caused abandonment or delay of attempts to send the message. // If this code accompanies a delivery failure report, sending in // the future may be successful. ClassTransientFailure = 4 // ClassPermanentFailure - a permanent failure is one which is not likely to be resolved // by resending the message in the current form. Some change to // the message or the destination must be made for successful // delivery. ClassPermanentFailure = 5 ) // space char const SP = " " // class is a type for ClassSuccess, ClassTransientFailure and ClassPermanentFailure constants type class int // String implements stringer for the class type func (c class) String() string { return fmt.Sprintf("%c00", c) } // codeMap for mapping Enhanced Status Code to Basic Code // Mapping according to https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xml // This might not be entirely useful var codeMap = struct { m map[EnhancedStatusCode]int }{m: map[EnhancedStatusCode]int{ EnhancedStatusCode{ClassSuccess, OtherAddressStatus}: 250, EnhancedStatusCode{ClassSuccess, DestinationMailboxAddressValid}: 250, EnhancedStatusCode{ClassSuccess, OtherOrUndefinedMailSystemStatus}: 250, EnhancedStatusCode{ClassSuccess, OtherOrUndefinedProtocolStatus}: 250, EnhancedStatusCode{ClassSuccess, ConversionWithLossPerformed}: 250, EnhancedStatusCode{ClassSuccess, ".6.8"}: 252, EnhancedStatusCode{ClassSuccess, ".7.0"}: 220, EnhancedStatusCode{ClassTransientFailure, BadDestinationMailboxAddress}: 451, EnhancedStatusCode{ClassTransientFailure, BadSendersSystemAddress}: 451, EnhancedStatusCode{ClassTransientFailure, MailingListExpansionProblem}: 450, EnhancedStatusCode{ClassTransientFailure, OtherOrUndefinedMailSystemStatus}: 421, EnhancedStatusCode{ClassTransientFailure, MailSystemFull}: 452, EnhancedStatusCode{ClassTransientFailure, SystemNotAcceptingNetworkMessages}: 453, EnhancedStatusCode{ClassTransientFailure, NoAnswerFromHost}: 451, EnhancedStatusCode{ClassTransientFailure, BadConnection}: 421, EnhancedStatusCode{ClassTransientFailure, RoutingServerFailure}: 451, EnhancedStatusCode{ClassTransientFailure, NetworkCongestion}: 451, EnhancedStatusCode{ClassTransientFailure, OtherOrUndefinedProtocolStatus}: 451, EnhancedStatusCode{ClassTransientFailure, InvalidCommand}: 430, EnhancedStatusCode{ClassTransientFailure, TooManyRecipients}: 452, EnhancedStatusCode{ClassTransientFailure, InvalidCommandArguments}: 451, EnhancedStatusCode{ClassTransientFailure, ".7.0"}: 450, EnhancedStatusCode{ClassTransientFailure, ".7.1"}: 451, EnhancedStatusCode{ClassTransientFailure, ".7.12"}: 422, EnhancedStatusCode{ClassTransientFailure, ".7.15"}: 450, EnhancedStatusCode{ClassTransientFailure, ".7.24"}: 451, EnhancedStatusCode{ClassPermanentFailure, BadDestinationMailboxAddress}: 550, EnhancedStatusCode{ClassPermanentFailure, BadDestinationMailboxAddressSyntax}: 501, EnhancedStatusCode{ClassPermanentFailure, BadSendersSystemAddress}: 501, EnhancedStatusCode{ClassPermanentFailure, ".1.10"}: 556, EnhancedStatusCode{ClassPermanentFailure, MailboxFull}: 552, EnhancedStatusCode{ClassPermanentFailure, MessageLengthExceedsAdministrativeLimit}: 552, EnhancedStatusCode{ClassPermanentFailure, OtherOrUndefinedMailSystemStatus}: 550, EnhancedStatusCode{ClassPermanentFailure, MessageTooBigForSystem}: 552, EnhancedStatusCode{ClassPermanentFailure, RoutingServerFailure}: 550, EnhancedStatusCode{ClassPermanentFailure, OtherOrUndefinedProtocolStatus}: 501, EnhancedStatusCode{ClassPermanentFailure, InvalidCommand}: 500, EnhancedStatusCode{ClassPermanentFailure, SyntaxError}: 500, EnhancedStatusCode{ClassPermanentFailure, InvalidCommandArguments}: 501, EnhancedStatusCode{ClassPermanentFailure, ".5.6"}: 500, EnhancedStatusCode{ClassPermanentFailure, ConversionRequiredButNotSupported}: 554, EnhancedStatusCode{ClassPermanentFailure, ".6.6"}: 554, EnhancedStatusCode{ClassPermanentFailure, ".6.7"}: 553, EnhancedStatusCode{ClassPermanentFailure, ".6.8"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".6.9"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.0"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.1"}: 551, EnhancedStatusCode{ClassPermanentFailure, ".7.2"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.4"}: 504, EnhancedStatusCode{ClassPermanentFailure, ".7.8"}: 554, EnhancedStatusCode{ClassPermanentFailure, ".7.9"}: 534, EnhancedStatusCode{ClassPermanentFailure, ".7.10"}: 523, EnhancedStatusCode{ClassPermanentFailure, ".7.11"}: 524, EnhancedStatusCode{ClassPermanentFailure, ".7.13"}: 525, EnhancedStatusCode{ClassPermanentFailure, ".7.14"}: 535, EnhancedStatusCode{ClassPermanentFailure, ".7.15"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.16"}: 552, EnhancedStatusCode{ClassPermanentFailure, ".7.17"}: 500, EnhancedStatusCode{ClassPermanentFailure, ".7.18"}: 500, EnhancedStatusCode{ClassPermanentFailure, ".7.19"}: 500, EnhancedStatusCode{ClassPermanentFailure, ".7.20"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.21"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.22"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.23"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.24"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.25"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.26"}: 550, EnhancedStatusCode{ClassPermanentFailure, ".7.27"}: 550, }} var ( // Canned is to be read-only, except in the init() function Canned Responses ) // Responses has some already pre-constructed responses type Responses struct { // The 500's FailLineTooLong *Response FailNestedMailCmd *Response FailNoSenderDataCmd *Response FailNoRecipientsDataCmd *Response FailUnrecognizedCmd *Response FailMaxUnrecognizedCmd *Response FailSyntaxError *Response FailReadLimitExceededDataCmd *Response FailMessageSizeExceeded *Response FailReadErrorDataCmd *Response FailPathTooLong *Response FailInvalidAddress *Response FailLocalPartTooLong *Response FailDomainTooLong *Response FailBackendNotRunning *Response FailBackendTransaction *Response FailBackendTimeout *Response FailRcptCmd *Response // The 400's ErrorTooManyRecipients *Response ErrorRelayDenied *Response ErrorShutdown *Response // The 200's SuccessMailCmd *Response SuccessRcptCmd *Response SuccessResetCmd *Response SuccessVerifyCmd *Response SuccessNoopCmd *Response SuccessQuitCmd *Response SuccessDataCmd *Response SuccessStartTLSCmd *Response SuccessMessageQueued *Response } // Called automatically during package load to build up the Responses struct func init() { Canned = Responses{} Canned.FailLineTooLong = &Response{ EnhancedCode: InvalidCommand, BasicCode: 554, Class: ClassPermanentFailure, Comment: "Line too long.", } Canned.FailNestedMailCmd = &Response{ EnhancedCode: InvalidCommand, BasicCode: 503, Class: ClassPermanentFailure, Comment: "Error: nested MAIL command", } Canned.SuccessMailCmd = &Response{ EnhancedCode: OtherAddressStatus, Class: ClassSuccess, } Canned.SuccessRcptCmd = &Response{ EnhancedCode: DestinationMailboxAddressValid, Class: ClassSuccess, } Canned.SuccessResetCmd = Canned.SuccessMailCmd Canned.SuccessNoopCmd = &Response{ EnhancedCode: OtherStatus, Class: ClassSuccess, } Canned.SuccessVerifyCmd = &Response{ EnhancedCode: OtherOrUndefinedProtocolStatus, BasicCode: 252, Class: ClassSuccess, Comment: "Cannot verify user", } Canned.ErrorTooManyRecipients = &Response{ EnhancedCode: TooManyRecipients, BasicCode: 452, Class: ClassTransientFailure, Comment: "Too many recipients", } Canned.ErrorRelayDenied = &Response{ EnhancedCode: BadDestinationMailboxAddress, BasicCode: 454, Class: ClassTransientFailure, Comment: "Error: Relay access denied:", } Canned.SuccessQuitCmd = &Response{ EnhancedCode: OtherStatus, BasicCode: 221, Class: ClassSuccess, Comment: "Bye", } Canned.FailNoSenderDataCmd = &Response{ EnhancedCode: InvalidCommand, BasicCode: 503, Class: ClassPermanentFailure, Comment: "Error: No sender", } Canned.FailNoRecipientsDataCmd = &Response{ EnhancedCode: InvalidCommand, BasicCode: 503, Class: ClassPermanentFailure, Comment: "Error: No recipients", } Canned.SuccessDataCmd = &Response{ BasicCode: 354, Comment: "354 Enter message, ending with '.' on a line by itself", } Canned.SuccessStartTLSCmd = &Response{ EnhancedCode: OtherStatus, BasicCode: 220, Class: ClassSuccess, Comment: "Ready to start TLS", } Canned.FailUnrecognizedCmd = &Response{ EnhancedCode: InvalidCommand, BasicCode: 554, Class: ClassPermanentFailure, Comment: "Unrecognized command", } Canned.FailMaxUnrecognizedCmd = &Response{ EnhancedCode: InvalidCommand, BasicCode: 554, Class: ClassPermanentFailure, Comment: "Too many unrecognized commands", } Canned.ErrorShutdown = &Response{ EnhancedCode: OtherOrUndefinedMailSystemStatus, BasicCode: 421, Class: ClassTransientFailure, Comment: "Server is shutting down. Please try again later. Sayonara!", } Canned.FailSyntaxError = &Response{ EnhancedCode: SyntaxError, BasicCode: 550, Class: ClassPermanentFailure, Comment: "Syntax error", } Canned.FailReadLimitExceededDataCmd = &Response{ EnhancedCode: MessageLengthExceedsAdministrativeLimit, BasicCode: 550, Class: ClassPermanentFailure, Comment: "Error:", } Canned.FailMessageSizeExceeded = &Response{ EnhancedCode: OtherOrUndefinedNetworkOrRoutingStatus, BasicCode: 552, Class: ClassPermanentFailure, Comment: "Error:", } Canned.FailReadErrorDataCmd = &Response{ EnhancedCode: OtherOrUndefinedMailSystemStatus, BasicCode: 451, Class: ClassTransientFailure, Comment: "Error:", } Canned.FailPathTooLong = &Response{ EnhancedCode: InvalidCommandArguments, BasicCode: 550, Class: ClassPermanentFailure, Comment: "Path too long", } Canned.FailInvalidAddress = &Response{ EnhancedCode: InvalidCommandArguments, BasicCode: 501, Class: ClassPermanentFailure, Comment: "Invalid address", } Canned.FailLocalPartTooLong = &Response{ EnhancedCode: InvalidCommandArguments, BasicCode: 550, Class: ClassPermanentFailure, Comment: "Local part too long, cannot exceed 64 characters", } Canned.FailDomainTooLong = &Response{ EnhancedCode: InvalidCommandArguments, BasicCode: 550, Class: ClassPermanentFailure, Comment: "Domain cannot exceed 255 characters", } Canned.FailBackendNotRunning = &Response{ EnhancedCode: OtherOrUndefinedProtocolStatus, BasicCode: 554, Class: ClassPermanentFailure, Comment: "Transaction failed - backend not running", } Canned.FailBackendTransaction = &Response{ EnhancedCode: OtherOrUndefinedProtocolStatus, BasicCode: 554, Class: ClassPermanentFailure, Comment: "Error:", } Canned.SuccessMessageQueued = &Response{ EnhancedCode: OtherStatus, BasicCode: 250, Class: ClassSuccess, Comment: "OK: queued as", } Canned.FailBackendTimeout = &Response{ EnhancedCode: OtherOrUndefinedProtocolStatus, BasicCode: 554, Class: ClassPermanentFailure, Comment: "Error: transaction timeout", } Canned.FailRcptCmd = &Response{ EnhancedCode: BadDestinationMailboxAddress, BasicCode: 550, Class: ClassPermanentFailure, Comment: "User unknown in local recipient table", } } // DefaultMap contains defined default codes (RfC 3463) const ( OtherStatus = ".0.0" OtherAddressStatus = ".1.0" BadDestinationMailboxAddress = ".1.1" BadDestinationSystemAddress = ".1.2" BadDestinationMailboxAddressSyntax = ".1.3" DestinationMailboxAddressAmbiguous = ".1.4" DestinationMailboxAddressValid = ".1.5" MailboxHasMoved = ".1.6" BadSendersMailboxAddressSyntax = ".1.7" BadSendersSystemAddress = ".1.8" OtherOrUndefinedMailboxStatus = ".2.0" MailboxDisabled = ".2.1" MailboxFull = ".2.2" MessageLengthExceedsAdministrativeLimit = ".2.3" MailingListExpansionProblem = ".2.4" OtherOrUndefinedMailSystemStatus = ".3.0" MailSystemFull = ".3.1" SystemNotAcceptingNetworkMessages = ".3.2" SystemNotCapableOfSelectedFeatures = ".3.3" MessageTooBigForSystem = ".3.4" OtherOrUndefinedNetworkOrRoutingStatus = ".4.0" NoAnswerFromHost = ".4.1" BadConnection = ".4.2" RoutingServerFailure = ".4.3" UnableToRoute = ".4.4" NetworkCongestion = ".4.5" RoutingLoopDetected = ".4.6" DeliveryTimeExpired = ".4.7" OtherOrUndefinedProtocolStatus = ".5.0" InvalidCommand = ".5.1" SyntaxError = ".5.2" TooManyRecipients = ".5.3" InvalidCommandArguments = ".5.4" WrongProtocolVersion = ".5.5" OtherOrUndefinedMediaError = ".6.0" MediaNotSupported = ".6.1" ConversionRequiredAndProhibited = ".6.2" ConversionRequiredButNotSupported = ".6.3" ConversionWithLossPerformed = ".6.4" ConversionFailed = ".6.5" ) var defaultTexts = struct { m map[EnhancedStatusCode]string }{m: map[EnhancedStatusCode]string{ EnhancedStatusCode{ClassSuccess, ".0.0"}: "OK", EnhancedStatusCode{ClassSuccess, ".1.0"}: "OK", EnhancedStatusCode{ClassSuccess, ".1.5"}: "OK", EnhancedStatusCode{ClassSuccess, ".5.0"}: "OK", EnhancedStatusCode{ClassTransientFailure, ".5.3"}: "Too many recipients", EnhancedStatusCode{ClassTransientFailure, ".5.4"}: "Relay access denied", EnhancedStatusCode{ClassPermanentFailure, ".5.1"}: "Invalid command", }} // Response type for Stringer interface type Response struct { EnhancedCode subjectDetail BasicCode int Class class // Comment is optional Comment string cached string } // it looks like this ".5.4" type subjectDetail string // EnhancedStatus are the ones that look like 2.1.0 type EnhancedStatusCode struct { Class class SubjectDetailCode subjectDetail } // String returns a string representation of EnhancedStatus func (e EnhancedStatusCode) String() string { return fmt.Sprintf("%d%s", e.Class, e.SubjectDetailCode) } // String returns a custom Response as a string func (r *Response) String() string { if r.cached != "" { return r.cached } if r.EnhancedCode == "" { r.cached = r.Comment return r.Comment } basicCode := r.BasicCode comment := r.Comment if len(comment) == 0 && r.BasicCode == 0 { var ok bool if comment, ok = defaultTexts.m[EnhancedStatusCode{r.Class, r.EnhancedCode}]; !ok { switch r.Class { case 2: comment = "OK" case 4: comment = "Temporary failure." case 5: comment = "Permanent failure." } } } e := EnhancedStatusCode{r.Class, r.EnhancedCode} if r.BasicCode == 0 { basicCode = getBasicStatusCode(e) } r.cached = fmt.Sprintf("%d %s %s", basicCode, e.String(), comment) return r.cached } // getBasicStatusCode gets the basic status code from codeMap, or fallback code if not mapped func getBasicStatusCode(e EnhancedStatusCode) int { if val, ok := codeMap.m[e]; ok { return val } // Fallback if code is not defined return int(e.Class) * 100 }