10 "github.com/uber-go/zap"
16 stateAuth state = iota
22 errStateAuth = "not in AUTHORIZATION"
23 errStateTxn = "not in TRANSACTION"
24 errSyntax = "syntax error"
25 errDeletedMsg = "no such message - deleted"
28 type connection struct {
43 func AcceptConnection(netConn net.Conn, po PostOffice, log zap.Logger) {
44 log = log.With(zap.Stringer("client", netConn.RemoteAddr()))
47 tp: textproto.NewConn(netConn),
52 conn.log.Info("accepted connection")
53 conn.ok(fmt.Sprintf("POP3 (mailpopbox) server %s", po.Name()))
58 conn.line, err = conn.tp.ReadLine()
60 conn.log.Error("ReadLine()", zap.Error(err))
66 if _, err := fmt.Sscanf(conn.line, "%s", &cmd); err != nil {
67 conn.err("invalid command")
71 conn.log = log.With(zap.String("command", cmd))
73 switch strings.ToUpper(cmd) {
96 conn.log.Error("unknown command")
97 conn.err("unknown command")
102 func (conn *connection) ok(msg string) {
106 conn.tp.PrintfLine("+OK%s", msg)
109 func (conn *connection) err(msg string) {
110 conn.log.Error("error", zap.String("message", msg))
113 conn.tp.PrintfLine("-ERR%s", msg)
117 func (conn *connection) doQUIT() {
118 defer conn.tp.Close()
121 err := conn.mb.Close()
123 conn.err("failed to remove some messages")
130 func (conn *connection) doUSER() {
131 if conn.state != stateAuth {
132 conn.err(errStateAuth)
137 if len(conn.line) < cmd {
138 conn.err("invalid user")
142 conn.user = conn.line[cmd:]
146 func (conn *connection) doPASS() {
147 if conn.state != stateAuth {
148 conn.err(errStateAuth)
152 if len(conn.user) == 0 {
158 if len(conn.line) < cmd {
159 conn.err("invalid pass")
163 pass := conn.line[cmd:]
164 if mbox, err := conn.po.OpenMailbox(conn.user, pass); err == nil {
165 conn.log.Info("authenticated", zap.String("user", conn.user))
166 conn.state = stateTxn
170 conn.log.Error("failed to open mailbox", zap.Error(err))
171 conn.err(err.Error())
175 func (conn *connection) doSTAT() {
176 if conn.state != stateTxn {
177 conn.err(errStateTxn)
181 msgs, err := conn.mb.ListMessages()
183 conn.log.Error("failed to list messages", zap.Error(err))
184 conn.err(err.Error())
190 for _, msg := range msgs {
198 conn.ok(fmt.Sprintf("%d %d", num, size))
201 func (conn *connection) doLIST() {
202 if conn.state != stateTxn {
203 conn.err(errStateTxn)
207 msgs, err := conn.mb.ListMessages()
209 conn.log.Error("failed to list messages", zap.Error(err))
210 conn.err(err.Error())
214 conn.ok("scan listing")
215 for _, msg := range msgs {
216 conn.tp.PrintfLine("%d %d", msg.ID(), msg.Size())
218 conn.tp.PrintfLine(".")
221 func (conn *connection) doRETR() {
222 if conn.state != stateTxn {
223 conn.err(errStateTxn)
227 msg := conn.getRequestedMessage()
233 conn.err(errDeletedMsg)
237 rc, err := conn.mb.Retrieve(msg)
239 conn.log.Error("failed to retrieve messages", zap.Error(err))
240 conn.err(err.Error())
244 conn.ok(fmt.Sprintf("%d", msg.Size()))
246 w := conn.tp.DotWriter()
251 func (conn *connection) doDELE() {
252 if conn.state != stateTxn {
253 conn.err(errStateTxn)
257 msg := conn.getRequestedMessage()
263 conn.err(errDeletedMsg)
267 if err := conn.mb.Delete(msg); err != nil {
268 conn.log.Error("failed to delete message", zap.Error(err))
269 conn.err(err.Error())
275 func (conn *connection) doRSET() {
276 if conn.state != stateTxn {
277 conn.err(errStateTxn)
284 func (conn *connection) doUIDL() {
285 if conn.state != stateTxn {
286 conn.err(errStateTxn)
290 msgs, err := conn.mb.ListMessages()
292 conn.log.Error("failed to list messages", zap.Error(err))
293 conn.err(err.Error())
297 conn.ok("unique-id listing")
298 for _, msg := range msgs {
302 conn.tp.PrintfLine("%d %s", msg.ID(), msg.UniqueID())
304 conn.tp.PrintfLine(".")
307 func (conn *connection) getRequestedMessage() Message {
310 if _, err := fmt.Sscanf(conn.line, "%s %d", &cmd, &idx); err != nil {
316 conn.err("invalid message-number")
320 msg := conn.mb.GetMessage(idx)
322 conn.err("no such message")