Move send-as handling out of the smtp package and into the core server.
[mailpopbox.git] / smtp / server.go
1 // mailpopbox
2 // Copyright 2020 Blue Static <https://www.bluestatic.org>
3 // This program is free software licensed under the GNU General Public License,
4 // version 3.0. The full text of the license can be found in LICENSE.txt.
5 // SPDX-License-Identifier: GPL-3.0-only
6
7 package smtp
8
9 import (
10 "crypto/rand"
11 "crypto/tls"
12 "fmt"
13 "io"
14 "net"
15 "net/mail"
16 "strings"
17 "time"
18
19 "go.uber.org/zap"
20 )
21
22 type ReplyLine struct {
23 Code int
24 Message string
25 }
26
27 func (l ReplyLine) String() string {
28 return fmt.Sprintf("%d %s", l.Code, l.Message)
29 }
30
31 var (
32 ReplyOK = ReplyLine{250, "OK"}
33 ReplyAuthOK = ReplyLine{235, "auth success"}
34 ReplyBadSyntax = ReplyLine{501, "syntax error"}
35 ReplyBadSequence = ReplyLine{503, "bad sequence of commands"}
36 ReplyBadMailbox = ReplyLine{550, "mailbox unavailable"}
37 ReplyMailboxUnallowed = ReplyLine{553, "mailbox name not allowed"}
38 )
39
40 func DomainForAddress(addr mail.Address) string {
41 return DomainForAddressString(addr.Address)
42 }
43
44 func DomainForAddressString(address string) string {
45 domainIdx := strings.LastIndex(address, "@")
46 if domainIdx == -1 {
47 return ""
48 }
49 return address[domainIdx+1:]
50 }
51
52 type Envelope struct {
53 RemoteAddr net.Addr
54 EHLO string
55 MailFrom mail.Address
56 RcptTo []mail.Address
57 Data []byte
58 Received time.Time
59 ID string
60 }
61
62 func WriteEnvelopeForDelivery(w io.Writer, e Envelope) {
63 fmt.Fprintf(w, "Delivered-To: <%s>\r\n", e.RcptTo[0].Address)
64 fmt.Fprintf(w, "Return-Path: <%s>\r\n", e.MailFrom.Address)
65 w.Write(e.Data)
66 }
67
68 func generateEnvelopeId(prefix string, t time.Time) string {
69 var idBytes [4]byte
70 rand.Read(idBytes[:])
71 return fmt.Sprintf("%s.%d.%x", prefix, t.UnixNano(), idBytes)
72 }
73
74 // lookupRemoteHost attempts to reverse look-up the provided IP address. On
75 // success, it returns the hostname and the IP as formatted for a receive
76 // trace. If the lookup fails, it just returns the original IP.
77 func lookupRemoteHost(addr net.Addr) string {
78 rhost, _, err := net.SplitHostPort(addr.String())
79 if err != nil {
80 rhost = addr.String()
81 }
82
83 rhosts, err := net.LookupAddr(rhost)
84 if err == nil {
85 rhost = fmt.Sprintf("%s [%s]", rhosts[0], rhost)
86 }
87
88 return rhost
89 }
90
91 // Server provides an interface for handling incoming SMTP requests via
92 // AcceptConnection.
93 type Server interface {
94 // Returns the name of the server, to use in HELO advertisements.
95 Name() string
96
97 // If non-nil, enables STARTTLS support on the SMTP server with the given
98 // configuration.
99 TLSConfig() *tls.Config
100
101 // Returns an status line indicating whether this server can receive
102 // mail for the specified email address.
103 VerifyAddress(mail.Address) ReplyLine
104
105 // Verify that the authc+passwd identity can send mail as authz on this
106 // server.
107 Authenticate(authz, authc, passwd string) bool
108
109 // Delivers a valid incoming message to a recipient on this server. The
110 // addressee has been validated via VerifyAddress.
111 DeliverMessage(Envelope) *ReplyLine
112
113 // RelayMessage instructs the server to send the Envelope to another
114 // MTA for outbound delivery. `authc` reports the authenticated username.
115 RelayMessage(en Envelope, authc string)
116 }
117
118 // MTA (Mail Transport Agent) allows a Server to interface with other SMTP
119 // MTAs.
120 type MTA interface {
121 // RelayMessage will attempt to send the specified Envelope. It will ask the
122 // Server to dial the MX servers for the addresses in Envelope.RcptTo for
123 // delivery. If relaying fails, a failure notice will be sent to the sender
124 // via Server.DeliverMessage.
125 RelayMessage(Envelope)
126 }
127
128 func NewDefaultMTA(server Server, log *zap.Logger) MTA {
129 return &mta{
130 server: server,
131 log: log,
132 }
133 }
134
135 type mta struct {
136 server Server
137 log *zap.Logger
138 }
139
140 type EmptyServerCallbacks struct{}
141
142 func (*EmptyServerCallbacks) TLSConfig() *tls.Config {
143 return nil
144 }
145
146 func (*EmptyServerCallbacks) VerifyAddress(mail.Address) ReplyLine {
147 return ReplyOK
148 }
149
150 func (*EmptyServerCallbacks) Authenticate(authz, authc, passwd string) bool {
151 return false
152 }
153
154 func (*EmptyServerCallbacks) DeliverMessage(Envelope) *ReplyLine {
155 return nil
156 }
157
158 func (*EmptyServerCallbacks) RelayMessage(Envelope) {
159 }