7 "github.com/mjl-/mox/dns"
8 "github.com/mjl-/mox/message"
13// Received represents a Received-SPF header with the SPF verify results, to be
14// prepended to a message.
18// Received-SPF: pass (mybox.example.org: domain of
19// myname@example.com designates 192.0.2.1 as permitted sender)
20// receiver=mybox.example.org; client-ip=192.0.2.1;
21// envelope-from="myname@example.com"; helo=foo.example.com;
24 Comment string // Additional free-form information about the verification result. Optional. Included in message header comment inside "()".
25 ClientIP net.IP // IP address of remote SMTP client, "client-ip=".
26 EnvelopeFrom string // Sender mailbox, typically SMTP MAIL FROM, but will be set to "postmaster" at SMTP EHLO if MAIL FROM is empty, "envelop-from=".
27 Helo dns.IPDomain // IP or host name from EHLO or HELO command, "helo=".
28 Problem string // Optional. "problem="
29 Receiver string // Hostname of receiving mail server, "receiver=".
30 Identity Identity // The identity that was checked, "mailfrom" or "helo", for "identity=".
31 Mechanism string // Mechanism that caused the result, can be "default". Optional.
34// Identity that was verified.
38 ReceivedMailFrom Identity = "mailfrom"
39 ReceivedHELO Identity = "helo"
42func receivedValueEncode(s string) string {
44 return quotedString("")
47 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c > 0x7f {
51 const atext = "!#$%&'*+-/=?^_`{|}~"
52 if strings.IndexByte(atext, byte(c)) >= 0 {
55 if c != '.' || (i == 0 || i+1 == len(s)) {
56 return quotedString(s)
63func quotedString(s string) string {
64 w := &strings.Builder{}
67 if c > ' ' && c < 0x7f && c != '"' && c != '\\' || c > 0x7f || c == ' ' || c == '\t' {
68 // We allow utf-8. This should only be needed when the destination address has an
69 // utf8 localpart, in which case we are already doing smtputf8.
70 // We also allow unescaped space and tab. This is FWS, and the name of ABNF
71 // production "qcontent" implies the FWS is not part of the string, but escaping
77 case ' ', '\t', '"', '\\':
86// Header returns a Received-SPF header including trailing crlf that can be
87// prepended to an incoming message.
88func (r Received) Header() string {
90 w := &message.HeaderWriter{}
91 w.Add("", "Received-SPF: "+string(r.Result))
93 w.Add(" ", "("+r.Comment+")")
95 w.Addf(" ", "client-ip=%s;", receivedValueEncode(r.ClientIP.String()))
96 w.Addf(" ", "envelope-from=%s;", receivedValueEncode(r.EnvelopeFrom))
98 if len(r.Helo.IP) > 0 {
99 helo = r.Helo.IP.String()
101 helo = r.Helo.Domain.ASCII
103 w.Addf(" ", "helo=%s;", receivedValueEncode(helo))
106 max := 77 - len("problem=; ")
110 w.Addf(" ", "problem=%s;", receivedValueEncode(s))
112 if r.Mechanism != "" {
113 w.Addf(" ", "mechanism=%s;", receivedValueEncode(r.Mechanism))
115 w.Addf(" ", "receiver=%s;", receivedValueEncode(r.Receiver))
116 w.Addf(" ", "identity=%s", receivedValueEncode(string(r.Identity)))