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) {
46 tp: textproto.NewConn(netConn),
48 log: log.With(zap.Stringer("client", netConn.RemoteAddr())),
52 conn.ok(fmt.Sprintf("POP3 (mailpopbox) server %s", po.Name()))
55 conn.line, err = conn.tp.ReadLine()
60 conn.log.Error("ReadLine()", zap.Error(err))
61 conn.err("did't catch that")
66 if _, err := fmt.Sscanf(conn.line, "%s", &cmd); err != nil {
67 conn.err("invalid command")
71 switch strings.ToUpper(cmd) {
92 conn.err("unknown command")
97 func (conn *connection) ok(msg string) {
101 conn.tp.PrintfLine("+OK%s", msg)
104 func (conn *connection) err(msg string) {
107 conn.tp.PrintfLine("-ERR%s", msg)
111 func (conn *connection) doQUIT() {
112 defer conn.tp.Close()
115 err := conn.mb.Close()
117 conn.err("failed to remove some messages")
124 func (conn *connection) doUSER() {
125 if conn.state != stateAuth {
126 conn.err(errStateAuth)
130 conn.user = conn.line[len("USER "):]
134 func (conn *connection) doPASS() {
135 if conn.state != stateAuth {
136 conn.err(errStateAuth)
140 if len(conn.user) == 0 {
145 pass := conn.line[len("PASS "):]
146 if mbox, err := conn.po.OpenMailbox(conn.user, pass); err == nil {
147 conn.log.Info("authenticated", zap.String("user", conn.user))
148 conn.state = stateTxn
152 conn.log.Error("PASS", zap.Error(err))
153 conn.err(err.Error())
157 func (conn *connection) doSTAT() {
158 if conn.state != stateTxn {
159 conn.err(errStateTxn)
163 msgs, err := conn.mb.ListMessages()
165 conn.log.Error("STAT", zap.Error(err))
166 conn.err(err.Error())
172 for _, msg := range msgs {
180 conn.ok(fmt.Sprintf("%d %d", num, size))
183 func (conn *connection) doLIST() {
184 if conn.state != stateTxn {
185 conn.err(errStateTxn)
189 msgs, err := conn.mb.ListMessages()
191 conn.log.Error("LIST", zap.Error(err))
192 conn.err(err.Error())
196 conn.ok("scan listing")
197 for _, msg := range msgs {
198 conn.tp.PrintfLine("%d %d", msg.ID(), msg.Size())
200 conn.tp.PrintfLine(".")
203 func (conn *connection) doRETR() {
204 if conn.state != stateTxn {
205 conn.err(errStateTxn)
209 msg := conn.getRequestedMessage()
215 conn.err(errDeletedMsg)
219 rc, err := conn.mb.Retrieve(msg)
221 conn.log.Error("RETR", zap.Error(err))
222 conn.err(err.Error())
226 conn.ok(fmt.Sprintf("%d", msg.Size()))
228 w := conn.tp.DotWriter()
233 func (conn *connection) doDELE() {
234 if conn.state != stateTxn {
235 conn.err(errStateTxn)
239 msg := conn.getRequestedMessage()
245 conn.err(errDeletedMsg)
249 if err := conn.mb.Delete(msg); err != nil {
250 conn.log.Error("DELE", zap.Error(err))
251 conn.err(err.Error())
257 func (conn *connection) doRSET() {
258 if conn.state != stateTxn {
259 conn.err(errStateTxn)
266 func (conn *connection) getRequestedMessage() Message {
269 if _, err := fmt.Sscanf(conn.line, "%s %d", &cmd, &idx); err != nil {
275 conn.err("invalid message-number")
279 msg := conn.mb.GetMessage(idx)
281 conn.err("no such message")