1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package exterrors2021import (22 "fmt"2324 "github.com/emersion/go-smtp"25)2627type EnhancedCode smtp.EnhancedCode2829func (ec EnhancedCode) FormatLog() string {30 return fmt.Sprintf("%d.%d.%d", ec[0], ec[1], ec[2])31}3233// SMTPError type is a copy of emersion/go-smtp.SMTPError type34// that extends it with Fields method for logging and reporting35// in maddy. It should be used instead of the go-smtp library type for all36// errors.37type SMTPError struct {38 // SMTP status code. Most of these codes are overly generic and are barely39 // useful. Nonetheless, take a look at the 'Associated basic status code'40 // in the SMTP Enhanced Status Codes registry (below), then check RFC 532141 // (Section 4.3.2) and pick what you like. Stick to 451 and 554 if there are42 // no useful codes.43 Code int4445 // Enhanced SMTP status code. If you are unsure, take a look at46 // https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml47 EnhancedCode EnhancedCode4849 // Error message that should be returned to the SMTP client.50 // Usually, it should be a short and generic description of the error51 // that excludes any details. Especially, for checks, avoid52 // mentioning the exact policy mechanism used to avoid disclosing the53 // server configuration details. Don't say "DNS error during DMARC check",54 // say "DNS error during policy check". Same goes for network and file I/O55 // errors. ESPECIALLY, don't include any configuration variables or object56 // identifiers in it.57 Message string5859 // If the error was generated by a message check60 // this field includes module name.61 CheckName string6263 // If the error was generated by a delivery target64 // this field includes module name.65 TargetName string6667 // If the error was generated by a message modifier68 // this field includes module name.69 ModifierName string7071 // If the error was generated as a result of another72 // error - this field contains the original error object.73 //74 // Err.Error() will be copied into the 'reason' field returned75 // by the Fields method unless a different values is specified76 // using the Reason field below.77 Err error7879 // Textual explanation of the actual error reason. Defaults to the80 // Err.Error() value if Err is not nil, empty string otherwise.81 Reason string8283 Misc map[string]interface{}84}8586func (se *SMTPError) Unwrap() error {87 return se.Err88}8990func (se *SMTPError) Fields() map[string]interface{} {91 ctx := make(map[string]interface{}, len(se.Misc)+3)92 for k, v := range se.Misc {93 ctx[k] = v94 }95 ctx["smtp_code"] = se.Code96 ctx["smtp_enchcode"] = se.EnhancedCode97 ctx["smtp_msg"] = se.Message98 if se.CheckName != "" {99 ctx["check"] = se.CheckName100 }101 if se.TargetName != "" {102 ctx["target"] = se.TargetName103 }104 if se.Reason != "" {105 ctx["reason"] = se.Reason106 } else if se.Err != nil {107 ctx["reason"] = se.Err.Error()108 }109 return ctx110}111112// Temporary reports whether113func (se *SMTPError) Temporary() bool {114 return se.Code/100 == 4115}116117func (se *SMTPError) Error() string {118 if se.Reason != "" {119 return se.Reason120 }121 if se.Err != nil {122 return se.Err.Error()123 }124 return se.Message125}126127// SMTPCode is a convenience function that returns one of its arguments128// depending on the result of exterrors.IsTemporary for the specified error129// object.130func SMTPCode(err error, temporaryCode, permanentCode int) int {131 if IsTemporary(err) {132 return temporaryCode133 }134 return permanentCode135}136137// SMTPEnchCode is a convenience function changes the first number of the SMTP enhanced138// status code based on the value exterrors.IsTemporary returns for the specified139// error object.140func SMTPEnchCode(err error, code EnhancedCode) EnhancedCode {141 if IsTemporary(err) {142 code[0] = 4143 }144 code[0] = 5145 return code146}