mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
116 lines
2.8 KiB
Go
116 lines
2.8 KiB
Go
package smtpserver
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mjl-/mox/message"
|
|
)
|
|
|
|
// ../rfc/8601:577
|
|
|
|
// Authentication-Results header, see RFC 8601.
|
|
type AuthResults struct {
|
|
Hostname string
|
|
Comment string // If not empty, header comment without "()", added after Hostname.
|
|
Methods []AuthMethod
|
|
}
|
|
|
|
// ../rfc/8601:598
|
|
|
|
// AuthMethod is a result for one authentication method.
|
|
//
|
|
// Example encoding in the header: "spf=pass smtp.mailfrom=example.net".
|
|
type AuthMethod struct {
|
|
// E.g. "dkim", "spf", "iprev", "auth".
|
|
Method string
|
|
Result string // Each method has a set of known values, e.g. "pass", "temperror", etc.
|
|
Comment string // Optional, message header comment.
|
|
Reason string // Optional.
|
|
Props []AuthProp
|
|
}
|
|
|
|
// ../rfc/8601:606
|
|
|
|
// AuthProp describes properties for an authentication method.
|
|
// Each method has a set of known properties.
|
|
// Encoded in the header as "type.property=value", e.g. "smtp.mailfrom=example.net"
|
|
// for spf.
|
|
type AuthProp struct {
|
|
// Valid values maintained at https://www.iana.org/assignments/email-auth/email-auth.xhtml
|
|
Type string
|
|
Property string
|
|
Value string
|
|
// Whether value is address-like (localpart@domain, or domain). Or another value,
|
|
// which is subject to escaping.
|
|
IsAddrLike bool
|
|
Comment string // If not empty, header comment withtout "()", added after Value.
|
|
}
|
|
|
|
// todo future: we could store fields as dns.Domain, and when we encode as non-ascii also add the ascii version as a comment.
|
|
|
|
// Header returns an Authentication-Results header, possibly spanning multiple
|
|
// lines, always ending in crlf.
|
|
func (h AuthResults) Header() string {
|
|
// Escaping of values: ../rfc/8601:684 ../rfc/2045:661
|
|
|
|
optComment := func(s string) string {
|
|
if s != "" {
|
|
return " (" + s + ")"
|
|
}
|
|
return s
|
|
}
|
|
|
|
w := &message.HeaderWriter{}
|
|
w.Add("", "Authentication-Results:"+optComment(h.Comment)+" "+value(h.Hostname)+";")
|
|
for i, m := range h.Methods {
|
|
tokens := []string{}
|
|
addf := func(format string, args ...any) {
|
|
s := fmt.Sprintf(format, args...)
|
|
tokens = append(tokens, s)
|
|
}
|
|
addf("%s=%s", m.Method, m.Result)
|
|
if m.Comment != "" && (m.Reason != "" || len(m.Props) > 0) {
|
|
addf("(%s)", m.Comment)
|
|
}
|
|
if m.Reason != "" {
|
|
addf("reason=%s", value(m.Reason))
|
|
}
|
|
for _, p := range m.Props {
|
|
v := p.Value
|
|
if !p.IsAddrLike {
|
|
v = value(v)
|
|
}
|
|
addf("%s.%s=%s%s", p.Type, p.Property, v, optComment(p.Comment))
|
|
}
|
|
for j, t := range tokens {
|
|
if j == len(tokens)-1 && i < len(h.Methods)-1 {
|
|
t += ";"
|
|
}
|
|
w.Add(" ", t)
|
|
}
|
|
}
|
|
return w.String()
|
|
}
|
|
|
|
func value(s string) string {
|
|
quote := s == ""
|
|
for _, c := range s {
|
|
// utf-8 does not have to be quoted. ../rfc/6532:242
|
|
if c == '"' || c == '\\' || c <= ' ' || c == 0x7f {
|
|
quote = true
|
|
break
|
|
}
|
|
}
|
|
if !quote {
|
|
return s
|
|
}
|
|
r := `"`
|
|
for _, c := range s {
|
|
if c == '"' || c == '\\' {
|
|
r += "\\"
|
|
}
|
|
r += string(c)
|
|
}
|
|
r += `"`
|
|
return r
|
|
}
|