16 stateNew state = iota // Before EHLO.
23 type connection struct {
36 mailFrom *mail.Address
40 func AcceptConnection(netConn net.Conn, server Server) error {
43 tp: textproto.NewConn(netConn),
44 remoteAddr: netConn.RemoteAddr(),
50 conn.writeReply(220, fmt.Sprintf("%s ESMTP [%s] (mailpopbox)", server.Name(), netConn.LocalAddr()))
53 conn.line, err = conn.tp.ReadLine()
55 conn.writeReply(500, "line too long")
60 if _, err = fmt.Sscanf(conn.line, "%s", &cmd); err != nil {
61 conn.reply(ReplyBadSyntax)
65 switch strings.ToUpper(cmd) {
67 conn.writeReply(221, "Goodbye")
85 conn.writeReply(252, "I'll do my best")
87 conn.writeReply(550, "access denied")
91 conn.writeReply(250, "https://tools.ietf.org/html/rfc5321")
93 conn.writeReply(500, "unrecognized command")
100 func (conn *connection) reply(reply ReplyLine) {
101 conn.writeReply(reply.Code, reply.Message)
104 func (conn *connection) writeReply(code int, msg string) {
106 conn.tp.PrintfLine("%d %s", code, msg)
108 conn.tp.PrintfLine("%d", code)
112 // parsePath parses out either a forward-, reverse-, or return-path from the
113 // current connection line. Returns a (valid-path, ReplyOK) if it was
114 // successfully parsed.
115 func (conn *connection) parsePath(command string) (string, ReplyLine) {
116 if len(conn.line) < len(command) {
117 return "", ReplyBadSyntax
119 if strings.ToUpper(command) != strings.ToUpper(conn.line[:len(command)]) {
120 return "", ReplyLine{500, "unrecognized command"}
122 return conn.line[len(command):], ReplyOK
125 func (conn *connection) doEHLO() {
129 _, err := fmt.Sscanf(conn.line, "%s %s", &cmd, &conn.ehlo)
131 conn.reply(ReplyBadSyntax)
136 conn.writeReply(250, fmt.Sprintf("Hello %s [%s]", conn.ehlo, conn.remoteAddr))
138 conn.tp.PrintfLine("250-Hello %s [%s]", conn.ehlo, conn.remoteAddr)
139 if conn.server.TLSConfig() != nil {
140 conn.tp.PrintfLine("250-STARTTLS")
142 conn.tp.PrintfLine("250 SIZE %d", 40960000)
145 conn.state = stateInitial
148 func (conn *connection) doMAIL() {
149 if conn.state != stateInitial {
150 conn.reply(ReplyBadSequence)
154 mailFrom, reply := conn.parsePath("MAIL FROM:")
155 if reply != ReplyOK {
161 conn.mailFrom, err = mail.ParseAddress(mailFrom)
163 conn.reply(ReplyBadSyntax)
167 conn.state = stateMail
171 func (conn *connection) doRCPT() {
172 if conn.state != stateMail && conn.state != stateRecipient {
173 conn.reply(ReplyBadSequence)
177 rcptTo, reply := conn.parsePath("RCPT TO:")
178 if reply != ReplyOK {
183 address, err := mail.ParseAddress(rcptTo)
185 conn.reply(ReplyBadSyntax)
188 if reply := conn.server.VerifyAddress(*address); reply != ReplyOK {
193 conn.rcptTo = append(conn.rcptTo, *address)
195 conn.state = stateRecipient
199 func (conn *connection) doDATA() {
200 if conn.state != stateRecipient {
201 conn.reply(ReplyBadSequence)
205 conn.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
207 data, err := conn.tp.ReadDotBytes()
210 conn.writeReply(552, "transaction failed")
214 received := time.Now()
216 RemoteAddr: conn.remoteAddr,
218 MailFrom: *conn.mailFrom,
221 ID: conn.envelopeID(received),
224 trace := conn.getReceivedInfo(env)
226 env.Data = append(trace, data...)
228 if reply := conn.server.OnMessageDelivered(env); reply != nil {
233 conn.state = stateInitial
237 func (conn *connection) envelopeID(t time.Time) string {
239 rand.Read(idBytes[:])
240 return fmt.Sprintf("m.%d.%x", t.UnixNano(), idBytes)
243 func (conn *connection) getReceivedInfo(envelope Envelope) []byte {
244 rhost, _, err := net.SplitHostPort(conn.remoteAddr.String())
246 rhost = conn.remoteAddr.String()
249 rhosts, err := net.LookupAddr(rhost)
251 rhost = fmt.Sprintf("%s [%s]", rhosts[0], rhost)
254 base := fmt.Sprintf("Received: from %s (%s)\r\n ", conn.ehlo, rhost)
263 base += fmt.Sprintf("by %s (mailpopbox) with %s id %s\r\n ", conn.server.Name(), with, envelope.ID)
265 base += fmt.Sprintf("for <%s>\r\n ", envelope.RcptTo[0].Address)
267 transport := "PLAINTEXT"
269 // TODO: TLS version, cipher, bits
271 date := envelope.Received.Format(time.RFC1123Z) // Same as RFC 5322 ยง 3.3
272 base += fmt.Sprintf("(using %s);\r\n %s\r\n", transport, date)
277 func (conn *connection) doRSET() {
278 conn.state = stateInitial
283 func (conn *connection) resetBuffers() {
285 conn.rcptTo = make([]mail.Address, 0)