maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1/*
  2Maddy Mail Server - Composable all-in-one email server.
  3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors
  4
  5This program is free software: you can redistribute it and/or modify
  6it under the terms of the GNU General Public License as published by
  7the Free Software Foundation, either version 3 of the License, or
  8(at your option) any later version.
  9
 10This program is distributed in the hope that it will be useful,
 11but WITHOUT ANY WARRANTY; without even the implied warranty of
 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13GNU General Public License for more details.
 14
 15You should have received a copy of the GNU General Public License
 16along with this program.  If not, see <https://www.gnu.org/licenses/>.
 17*/
 18
 19package exterrors
 20
 21import (
 22	"fmt"
 23
 24	"github.com/emersion/go-smtp"
 25)
 26
 27type EnhancedCode smtp.EnhancedCode
 28
 29func (ec EnhancedCode) FormatLog() string {
 30	return fmt.Sprintf("%d.%d.%d", ec[0], ec[1], ec[2])
 31}
 32
 33// SMTPError type is a copy of emersion/go-smtp.SMTPError type
 34// that extends it with Fields method for logging and reporting
 35// in maddy. It should be used instead of the go-smtp library type for all
 36// errors.
 37type SMTPError struct {
 38	// SMTP status code. Most of these codes are overly generic and are barely
 39	// useful. Nonetheless, take a look at the 'Associated basic status code'
 40	// in the SMTP Enhanced Status Codes registry (below), then check RFC 5321
 41	// (Section 4.3.2) and pick what you like. Stick to 451 and 554 if there are
 42	// no useful codes.
 43	Code int
 44
 45	// Enhanced SMTP status code. If you are unsure, take a look at
 46	// https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml
 47	EnhancedCode EnhancedCode
 48
 49	// Error message that should be returned to the SMTP client.
 50	// Usually, it should be a short and generic description of the error
 51	// that excludes any details. Especially, for checks, avoid
 52	// mentioning the exact policy mechanism used to avoid disclosing the
 53	// 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/O
 55	// errors. ESPECIALLY, don't include any configuration variables or object
 56	// identifiers in it.
 57	Message string
 58
 59	// If the error was generated by a message check
 60	// this field includes module name.
 61	CheckName string
 62
 63	// If the error was generated by a delivery target
 64	// this field includes module name.
 65	TargetName string
 66
 67	// If the error was generated by a message modifier
 68	// this field includes module name.
 69	ModifierName string
 70
 71	// If the error was generated as a result of another
 72	// error - this field contains the original error object.
 73	//
 74	// Err.Error() will be copied into the 'reason' field returned
 75	// by the Fields method unless a different values is specified
 76	// using the Reason field below.
 77	Err error
 78
 79	// Textual explanation of the actual error reason. Defaults to the
 80	// Err.Error() value if Err is not nil, empty string otherwise.
 81	Reason string
 82
 83	Misc map[string]interface{}
 84}
 85
 86func (se *SMTPError) Unwrap() error {
 87	return se.Err
 88}
 89
 90func (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] = v
 94	}
 95	ctx["smtp_code"] = se.Code
 96	ctx["smtp_enchcode"] = se.EnhancedCode
 97	ctx["smtp_msg"] = se.Message
 98	if se.CheckName != "" {
 99		ctx["check"] = se.CheckName
100	}
101	if se.TargetName != "" {
102		ctx["target"] = se.TargetName
103	}
104	if se.Reason != "" {
105		ctx["reason"] = se.Reason
106	} else if se.Err != nil {
107		ctx["reason"] = se.Err.Error()
108	}
109	return ctx
110}
111
112// Temporary reports whether
113func (se *SMTPError) Temporary() bool {
114	return se.Code/100 == 4
115}
116
117func (se *SMTPError) Error() string {
118	if se.Reason != "" {
119		return se.Reason
120	}
121	if se.Err != nil {
122		return se.Err.Error()
123	}
124	return se.Message
125}
126
127// SMTPCode is a convenience function that returns one of its arguments
128// depending on the result of exterrors.IsTemporary for the specified error
129// object.
130func SMTPCode(err error, temporaryCode, permanentCode int) int {
131	if IsTemporary(err) {
132		return temporaryCode
133	}
134	return permanentCode
135}
136
137// SMTPEnchCode is a convenience function changes the first number of the SMTP enhanced
138// status code based on the value exterrors.IsTemporary returns for the specified
139// error object.
140func SMTPEnchCode(err error, code EnhancedCode) EnhancedCode {
141	if IsTemporary(err) {
142		code[0] = 4
143	}
144	code[0] = 5
145	return code
146}