17 stateNew state = iota // Before EHLO.
24 type connection struct {
39 mailFrom *mail.Address
43 func AcceptConnection(netConn net.Conn, server Server) error {
46 tp: textproto.NewConn(netConn),
48 remoteAddr: netConn.RemoteAddr(),
54 conn.writeReply(220, fmt.Sprintf("%s ESMTP [%s] (mailpopbox)", server.Name(), netConn.LocalAddr()))
57 conn.line, err = conn.tp.ReadLine()
59 conn.writeReply(500, "line too long")
64 if _, err = fmt.Sscanf(conn.line, "%s", &cmd); err != nil {
65 conn.reply(ReplyBadSyntax)
69 switch strings.ToUpper(cmd) {
71 conn.writeReply(221, "Goodbye")
91 conn.writeReply(252, "I'll do my best")
93 conn.writeReply(550, "access denied")
97 conn.writeReply(250, "https://tools.ietf.org/html/rfc5321")
99 conn.writeReply(500, "unrecognized command")
106 func (conn *connection) reply(reply ReplyLine) error {
107 return conn.writeReply(reply.Code, reply.Message)
110 func (conn *connection) writeReply(code int, msg string) error {
112 return conn.tp.PrintfLine("%d %s", code, msg)
114 return conn.tp.PrintfLine("%d", code)
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
125 if strings.ToUpper(command) != strings.ToUpper(conn.line[:len(command)]) {
126 return "", ReplyLine{500, "unrecognized command"}
128 return conn.line[len(command):], ReplyOK
131 func (conn *connection) doEHLO() {
135 _, err := fmt.Sscanf(conn.line, "%s %s", &cmd, &conn.ehlo)
137 conn.reply(ReplyBadSyntax)
142 conn.writeReply(250, fmt.Sprintf("Hello %s [%s]", conn.ehlo, conn.remoteAddr))
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")
148 conn.tp.PrintfLine("250 SIZE %d", 40960000)
151 conn.state = stateInitial
154 func (conn *connection) doSTARTTLS() {
155 if conn.state != stateInitial {
156 conn.reply(ReplyBadSequence)
160 tlsConfig := conn.server.TLSConfig()
161 if !conn.esmtp || tlsConfig == nil {
162 conn.writeReply(500, "unrecognized command")
166 conn.writeReply(220, "initiate TLS connection")
168 newConn := tls.Server(conn.nc, tlsConfig)
169 if err := newConn.Handshake(); err != nil {
174 conn.tp = textproto.NewConn(conn.tlsNc)
175 conn.state = stateInitial
177 conn.writeReply(220, fmt.Sprintf("%s ESMTPS [%s] (mailpopbox)",
178 conn.server.Name(), newConn.LocalAddr()))
181 func (conn *connection) doMAIL() {
182 if conn.state != stateInitial {
183 conn.reply(ReplyBadSequence)
187 mailFrom, reply := conn.parsePath("MAIL FROM:")
188 if reply != ReplyOK {
194 conn.mailFrom, err = mail.ParseAddress(mailFrom)
196 conn.reply(ReplyBadSyntax)
200 conn.state = stateMail
204 func (conn *connection) doRCPT() {
205 if conn.state != stateMail && conn.state != stateRecipient {
206 conn.reply(ReplyBadSequence)
210 rcptTo, reply := conn.parsePath("RCPT TO:")
211 if reply != ReplyOK {
216 address, err := mail.ParseAddress(rcptTo)
218 conn.reply(ReplyBadSyntax)
221 if reply := conn.server.VerifyAddress(*address); reply != ReplyOK {
226 conn.rcptTo = append(conn.rcptTo, *address)
228 conn.state = stateRecipient
232 func (conn *connection) doDATA() {
233 if conn.state != stateRecipient {
234 conn.reply(ReplyBadSequence)
238 conn.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
240 data, err := conn.tp.ReadDotBytes()
243 conn.writeReply(552, "transaction failed")
247 received := time.Now()
249 RemoteAddr: conn.remoteAddr,
251 MailFrom: *conn.mailFrom,
254 ID: conn.envelopeID(received),
257 trace := conn.getReceivedInfo(env)
259 env.Data = append(trace, data...)
261 if reply := conn.server.OnMessageDelivered(env); reply != nil {
266 conn.state = stateInitial
270 func (conn *connection) envelopeID(t time.Time) string {
272 rand.Read(idBytes[:])
273 return fmt.Sprintf("m.%d.%x", t.UnixNano(), idBytes)
276 func (conn *connection) getReceivedInfo(envelope Envelope) []byte {
277 rhost, _, err := net.SplitHostPort(conn.remoteAddr.String())
279 rhost = conn.remoteAddr.String()
282 rhosts, err := net.LookupAddr(rhost)
284 rhost = fmt.Sprintf("%s [%s]", rhosts[0], rhost)
287 base := fmt.Sprintf("Received: from %s (%s)\r\n ", conn.ehlo, rhost)
293 if conn.tlsNc != nil {
296 base += fmt.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn.server.Name(), with, envelope.ID)
298 base += fmt.Sprintf("for <%s>\r\n ", envelope.RcptTo[0].Address)
300 transport := "PLAINTEXT"
301 if conn.tlsNc != nil {
302 // TODO: TLS version, cipher, bits
304 date := envelope.Received.Format(time.RFC1123Z) // Same as RFC 5322 ยง 3.3
305 base += fmt.Sprintf("(using %s);\r\n %s\r\n", transport, date)
310 func (conn *connection) doRSET() {
311 conn.state = stateInitial
316 func (conn *connection) resetBuffers() {
318 conn.rcptTo = make([]mail.Address, 0)