14 stateNew state = iota // Before EHLO.
21 type connection struct {
31 mailFrom *mail.Address
35 func AcceptConnection(netConn net.Conn, server Server) error {
38 tp: textproto.NewConn(netConn),
39 remoteAddr: netConn.RemoteAddr(),
45 conn.writeReply(220, fmt.Sprintf("%s ESMTP [%s] (mailpopbox)", server.Name(), netConn.LocalAddr()))
48 conn.line, err = conn.tp.ReadLine()
50 conn.writeReply(500, "line too long")
55 if _, err = fmt.Sscanf(conn.line, "%s", &cmd); err != nil {
56 conn.reply(ReplyBadSyntax)
60 switch strings.ToUpper(cmd) {
62 conn.writeReply(221, "Goodbye")
78 conn.writeReply(252, "I'll do my best")
80 conn.writeReply(550, "access denied")
84 conn.writeReply(250, "https://tools.ietf.org/html/rfc5321")
86 conn.writeReply(500, "unrecognized command")
93 func (conn *connection) reply(reply ReplyLine) {
94 conn.writeReply(reply.Code, reply.Message)
97 func (conn *connection) writeReply(code int, msg string) {
99 conn.tp.PrintfLine("%d %s", code, msg)
101 conn.tp.PrintfLine("%d", code)
105 // parsePath parses out either a forward-, reverse-, or return-path from the
106 // current connection line. Returns a (valid-path, ReplyOK) if it was
107 // successfully parsed.
108 func (conn *connection) parsePath(command string) (string, ReplyLine) {
109 if len(conn.line) < len(command) {
110 return "", ReplyBadSyntax
112 if strings.ToUpper(command) != strings.ToUpper(conn.line[:len(command)]) {
113 return "", ReplyLine{500, "unrecognized command"}
115 return conn.line[len(command):], ReplyOK
118 func (conn *connection) doEHLO() {
122 _, err := fmt.Sscanf(conn.line, "%s %s", &cmd, &conn.ehlo)
124 conn.reply(ReplyBadSyntax)
129 conn.writeReply(250, fmt.Sprintf("Hello %s [%s]", conn.ehlo, conn.remoteAddr))
131 conn.tp.PrintfLine("250-Hello %s [%s]", conn.ehlo, conn.remoteAddr)
132 if conn.server.TLSConfig() != nil {
133 conn.tp.PrintfLine("250-STARTTLS")
135 conn.tp.PrintfLine("250 SIZE %d", 40960000)
138 conn.state = stateInitial
141 func (conn *connection) doMAIL() {
142 if conn.state != stateInitial {
143 conn.reply(ReplyBadSequence)
147 mailFrom, reply := conn.parsePath("MAIL FROM:")
148 if reply != ReplyOK {
154 conn.mailFrom, err = mail.ParseAddress(mailFrom)
156 conn.reply(ReplyBadSyntax)
160 conn.state = stateMail
164 func (conn *connection) doRCPT() {
165 if conn.state != stateMail && conn.state != stateRecipient {
166 conn.reply(ReplyBadSequence)
170 rcptTo, reply := conn.parsePath("RCPT TO:")
171 if reply != ReplyOK {
176 address, err := mail.ParseAddress(rcptTo)
178 conn.reply(ReplyBadSyntax)
181 if reply := conn.server.VerifyAddress(*address); reply != ReplyOK {
186 conn.rcptTo = append(conn.rcptTo, *address)
188 conn.state = stateRecipient
192 func (conn *connection) doDATA() {
193 if conn.state != stateRecipient {
194 conn.reply(ReplyBadSequence)
198 conn.writeReply(354, "Start mail input; end with <CRLF>.<CRLF>")
200 data, err := conn.tp.ReadDotBytes()
203 conn.writeReply(552, "transaction failed")
208 RemoteAddr: conn.remoteAddr,
210 MailFrom: *conn.mailFrom,
215 if reply := conn.server.OnMessageDelivered(env); reply != nil {
220 conn.state = stateInitial
224 func (conn *connection) doRSET() {
225 conn.state = stateInitial
230 func (conn *connection) resetBuffers() {
232 conn.rcptTo = make([]mail.Address, 0)