1package message
2
3import (
4 "fmt"
5 "io"
6 "net/textproto"
7
8 "golang.org/x/exp/slog"
9
10 "github.com/mjl-/mox/dns"
11 "github.com/mjl-/mox/mlog"
12 "github.com/mjl-/mox/smtp"
13)
14
15// From extracts the address in the From-header.
16//
17// An RFC5322 message must have a From header.
18// In theory, multiple addresses may be present. In practice zero or multiple
19// From headers may be present. From returns an error if there is not exactly
20// one address. This address can be used for evaluating a DMARC policy against
21// SPF and DKIM results.
22func From(elog *slog.Logger, strict bool, r io.ReaderAt) (raddr smtp.Address, envelope *Envelope, header textproto.MIMEHeader, rerr error) {
23 log := mlog.New("message", elog)
24
25 // ../rfc/7489:1243
26
27 // todo: only allow utf8 if enabled in session/message?
28
29 p, err := Parse(log.Logger, strict, r)
30 if err != nil {
31 // todo: should we continue with p, perhaps headers can be parsed?
32 return raddr, nil, nil, fmt.Errorf("parsing message: %v", err)
33 }
34 header, err = p.Header()
35 if err != nil {
36 return raddr, nil, nil, fmt.Errorf("parsing message header: %v", err)
37 }
38 from := p.Envelope.From
39 if len(from) != 1 {
40 return raddr, nil, nil, fmt.Errorf("from header has %d addresses, need exactly 1 address", len(from))
41 }
42 d, err := dns.ParseDomain(from[0].Host)
43 if err != nil {
44 return raddr, nil, nil, fmt.Errorf("bad domain in from address: %v", err)
45 }
46 addr := smtp.Address{Localpart: smtp.Localpart(from[0].User), Domain: d}
47 return addr, p.Envelope, textproto.MIMEHeader(header), nil
48}
49