Format the TLS string in the receive-trace.
[mailpopbox.git] / smtp / conn.go
1 package smtp
2
3 import (
4 "crypto/rand"
5 "crypto/tls"
6 "fmt"
7 "net"
8 "net/mail"
9 "net/textproto"
10 "strings"
11 "time"
12 )
13
14 type state int
15
16 const (
17 stateNew state = iota // Before EHLO.
18 stateInitial
19 stateMail
20 stateRecipient
21 stateData
22 )
23
24 type connection struct {
25 server Server
26
27 tp *textproto.Conn
28
29 nc net.Conn
30 tlsNc *tls.Conn
31 remoteAddr net.Addr
32
33 esmtp bool
34
35 state
36 line string
37
38 ehlo string
39 mailFrom *mail.Address
40 rcptTo []mail.Address
41 }
42
43 func AcceptConnection(netConn net.Conn, server Server) error {
44 conn := connection{
45 server: server,
46 tp: textproto.NewConn(netConn),
47 nc: netConn,
48 remoteAddr: netConn.RemoteAddr(),
49 state: stateNew,
50 }
51
52 var err error
53
54 conn.writeReply(220, fmt.Sprintf("%s ESMTP [%s] (mailpopbox)", server.Name(), netConn.LocalAddr()))
55
56 for {
57 conn.line, err = conn.tp.ReadLine()
58 if err != nil {
59 conn.writeReply(500, "line too long")
60 continue
61 }
62
63 var cmd string
64 if _, err = fmt.Sscanf(conn.line, "%s", &cmd); err != nil {
65 conn.reply(ReplyBadSyntax)
66 continue
67 }
68
69 switch strings.ToUpper(cmd) {
70 case "QUIT":
71 conn.writeReply(221, "Goodbye")
72 conn.tp.Close()
73 break
74 case "HELO":
75 conn.esmtp = false
76 fallthrough
77 case "EHLO":
78 conn.esmtp = true
79 conn.doEHLO()
80 case "STARTTLS":
81 conn.doSTARTTLS()
82 case "MAIL":
83 conn.doMAIL()
84 case "RCPT":
85 conn.doRCPT()
86 case "DATA":
87 conn.doDATA()
88 case "RSET":
89 conn.doRSET()
90 case "VRFY":
91 conn.writeReply(252, "I'll do my best")
92 case "EXPN":
93 conn.writeReply(550, "access denied")
94 case "NOOP":
95 conn.reply(ReplyOK)
96 case "HELP":
97 conn.writeReply(250, "https://tools.ietf.org/html/rfc5321")
98 default:
99 conn.writeReply(500, "unrecognized command")
100 }
101 }
102
103 return err
104 }
105
106 func (conn *connection) reply(reply ReplyLine) error {
107 return conn.writeReply(reply.Code, reply.Message)
108 }
109
110 func (conn *connection) writeReply(code int, msg string) error {
111 if len(msg) > 0 {
112 return conn.tp.PrintfLine("%d %s", code, msg)
113 } else {
114 return conn.tp.PrintfLine("%d", code)
115 }
116 }
117
118 // parsePath parses out either a forward-, reverse-, or return-path from the
119 // current connection line. Returns a (valid-path, ReplyOK) if it was
120 // successfully parsed.
121 func (conn *connection) parsePath(command string) (string, ReplyLine) {
122 if len(conn.line) < len(command) {
123 return "", ReplyBadSyntax
124 }
125 if strings.ToUpper(command) != strings.ToUpper(conn.line[:len(command)]) {
126 return "", ReplyLine{500, "unrecognized command"}
127 }
128 return conn.line[len(command):], ReplyOK
129 }
130
131 func (conn *connection) doEHLO() {
132 conn.resetBuffers()
133
134 var cmd string
135 _, err := fmt.Sscanf(conn.line, "%s %s", &cmd, &conn.ehlo)
136 if err != nil {
137 conn.reply(ReplyBadSyntax)
138 return
139 }
140
141 if cmd == "HELO" {
142 conn.writeReply(250, fmt.Sprintf("Hello %s [%s]", conn.ehlo, conn.remoteAddr))
143 } else {
144 conn.tp.PrintfLine("250-Hello %s [%s]", conn.ehlo, conn.remoteAddr)
145 if conn.server.TLSConfig() != nil && conn.tlsNc == nil {
146 conn.tp.PrintfLine("250-STARTTLS")
147 }
148 conn.tp.PrintfLine("250 SIZE %d", 40960000)
149 }
150
151 conn.state = stateInitial
152 }
153
154 func (conn *connection) doSTARTTLS() {
155 if conn.state != stateInitial {
156 conn.reply(ReplyBadSequence)
157 return
158 }
159
160 tlsConfig := conn.server.TLSConfig()
161 if !conn.esmtp || tlsConfig == nil {
162 conn.writeReply(500, "unrecognized command")
163 return
164 }
165
166 conn.writeReply(220, "initiate TLS connection")
167
168 newConn := tls.Server(conn.nc, tlsConfig)
169 if err := newConn.Handshake(); err != nil {
170 return
171 }
172
173 conn.tlsNc = newConn
174 conn.tp = textproto.NewConn(conn.tlsNc)
175 conn.state = stateInitial
176
177 conn.writeReply(220, fmt.Sprintf("%s ESMTPS [%s] (mailpopbox)",
178 conn.server.Name(), newConn.LocalAddr()))
179 }
180
181 func (conn *connection) doMAIL() {
182 if conn.state != stateInitial {
183 conn.reply(ReplyBadSequence)
184 return
185 }
186
187 mailFrom, reply := conn.parsePath("MAIL FROM:")
188 if reply != ReplyOK {
189 conn.reply(reply)
190 return
191 }
192
193 var err error
194 conn.mailFrom, err = mail.ParseAddress(mailFrom)
195 if err != nil {
196 conn.reply(ReplyBadSyntax)
197 return
198 }
199
200 conn.state = stateMail
201 conn.reply(ReplyOK)
202 }
203
204 func (conn *connection) doRCPT() {
205 if conn.state != stateMail && conn.state != stateRecipient {
206 conn.reply(ReplyBadSequence)
207 return
208 }
209
210 rcptTo, reply := conn.parsePath("RCPT TO:")
211 if reply != ReplyOK {
212 conn.reply(reply)
213 return
214 }
215
216 address, err := mail.ParseAddress(rcptTo)
217 if err != nil {
218 conn.reply(ReplyBadSyntax)
219 }
220
221 if reply := conn.server.VerifyAddress(*address); reply != ReplyOK {
222 conn.reply(reply)
223 return
224 }
225
226 conn.rcptTo = append(conn.rcptTo, *address)
227
228 conn.state = stateRecipient
229 conn.reply(ReplyOK)
230 }
231
232 func (conn *connection) doDATA() {
233 if conn.state != stateRecipient {
234 conn.reply(ReplyBadSequence)
235 return
236 }
237
238 conn.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
239
240 data, err := conn.tp.ReadDotBytes()
241 if err != nil {
242 // TODO: log error
243 conn.writeReply(552, "transaction failed")
244 return
245 }
246
247 received := time.Now()
248 env := Envelope{
249 RemoteAddr: conn.remoteAddr,
250 EHLO: conn.ehlo,
251 MailFrom: *conn.mailFrom,
252 RcptTo: conn.rcptTo,
253 Received: received,
254 ID: conn.envelopeID(received),
255 }
256
257 trace := conn.getReceivedInfo(env)
258
259 env.Data = append(trace, data...)
260
261 if reply := conn.server.OnMessageDelivered(env); reply != nil {
262 conn.reply(*reply)
263 return
264 }
265
266 conn.state = stateInitial
267 conn.reply(ReplyOK)
268 }
269
270 func (conn *connection) envelopeID(t time.Time) string {
271 var idBytes [4]byte
272 rand.Read(idBytes[:])
273 return fmt.Sprintf("m.%d.%x", t.UnixNano(), idBytes)
274 }
275
276 func (conn *connection) getReceivedInfo(envelope Envelope) []byte {
277 rhost, _, err := net.SplitHostPort(conn.remoteAddr.String())
278 if err != nil {
279 rhost = conn.remoteAddr.String()
280 }
281
282 rhosts, err := net.LookupAddr(rhost)
283 if err == nil {
284 rhost = fmt.Sprintf("%s [%s]", rhosts[0], rhost)
285 }
286
287 base := fmt.Sprintf("Received: from %s (%s)\r\n ", conn.ehlo, rhost)
288
289 with := "SMTP"
290 if conn.esmtp {
291 with = "E" + with
292 }
293 if conn.tlsNc != nil {
294 with += "S"
295 }
296 base += fmt.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn.server.Name(), with, envelope.ID)
297
298 base += fmt.Sprintf("for <%s>\r\n ", envelope.RcptTo[0].Address)
299
300 transport := conn.getTransportString()
301 date := envelope.Received.Format(time.RFC1123Z) // Same as RFC 5322 ยง 3.3
302 base += fmt.Sprintf("(using %s);\r\n %s\r\n", transport, date)
303
304 return []byte(base)
305 }
306
307 func (conn *connection) getTransportString() string {
308 if conn.tlsNc == nil {
309 return "PLAINTEXT"
310 }
311
312 ciphers := map[uint16]string{
313 tls.TLS_RSA_WITH_RC4_128_SHA: "TLS_RSA_WITH_RC4_128_SHA",
314 tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
315 tls.TLS_RSA_WITH_AES_128_CBC_SHA: "TLS_RSA_WITH_AES_128_CBC_SHA",
316 tls.TLS_RSA_WITH_AES_256_CBC_SHA: "TLS_RSA_WITH_AES_256_CBC_SHA",
317 tls.TLS_RSA_WITH_AES_128_GCM_SHA256: "TLS_RSA_WITH_AES_128_GCM_SHA256",
318 tls.TLS_RSA_WITH_AES_256_GCM_SHA384: "TLS_RSA_WITH_AES_256_GCM_SHA384",
319 tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
320 tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
321 tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
322 tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
323 tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
324 tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
325 tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
326 tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
327 tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
328 tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
329 tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
330 }
331 versions := map[uint16]string{
332 tls.VersionSSL30: "SSLv3.0",
333 tls.VersionTLS10: "TLSv1.0",
334 tls.VersionTLS11: "TLSv1.1",
335 tls.VersionTLS12: "TLSv1.2",
336 }
337
338 state := conn.tlsNc.ConnectionState()
339
340 version := versions[state.Version]
341 cipher := ciphers[state.CipherSuite]
342
343 if version == "" {
344 version = fmt.Sprintf("%x", state.Version)
345 }
346 if cipher == "" {
347 cipher = fmt.Sprintf("%x", state.CipherSuite)
348 }
349
350 name := ""
351 if state.ServerName != "" {
352 name = fmt.Sprintf(" name=%s", state.ServerName)
353 }
354
355 return fmt.Sprintf("%s cipher=%s%s", version, cipher, name)
356 }
357
358 func (conn *connection) doRSET() {
359 conn.state = stateInitial
360 conn.resetBuffers()
361 conn.reply(ReplyOK)
362 }
363
364 func (conn *connection) resetBuffers() {
365 conn.mailFrom = nil
366 conn.rcptTo = make([]mail.Address, 0)
367 }