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) {
98 conn.err("unknown command")
103 func (conn *connection) ok(msg string) {
107 conn.tp.PrintfLine("+OK%s", msg)
110 func (conn *connection) err(msg string) {
111 conn.log.Error("error", zap.String("message", msg))
114 conn.tp.PrintfLine("-ERR%s", msg)
118 func (conn *connection) doQUIT() {
119 defer conn.tp.Close()
122 err := conn.mb.Close()
124 conn.err("failed to remove some messages")
131 func (conn *connection) doUSER() {
132 if conn.state != stateAuth {
133 conn.err(errStateAuth)
138 if len(conn.line) < cmd {
139 conn.err("invalid user")
143 conn.user = conn.line[cmd:]
147 func (conn *connection) doPASS() {
148 if conn.state != stateAuth {
149 conn.err(errStateAuth)
153 if len(conn.user) == 0 {
159 if len(conn.line) < cmd {
160 conn.err("invalid pass")
164 pass := conn.line[cmd:]
165 if mbox, err := conn.po.OpenMailbox(conn.user, pass); err == nil {
166 conn.log.Info("authenticated", zap.String("user", conn.user))
167 conn.state = stateTxn
171 conn.log.Error("failed to open mailbox", zap.Error(err))
172 conn.err(err.Error())
176 func (conn *connection) doSTAT() {
177 if conn.state != stateTxn {
178 conn.err(errStateTxn)
182 msgs, err := conn.mb.ListMessages()
184 conn.log.Error("failed to list messages", zap.Error(err))
185 conn.err(err.Error())
191 for _, msg := range msgs {
199 conn.ok(fmt.Sprintf("%d %d", num, size))
202 func (conn *connection) doLIST() {
203 if conn.state != stateTxn {
204 conn.err(errStateTxn)
208 msgs, err := conn.mb.ListMessages()
210 conn.log.Error("failed to list messages", zap.Error(err))
211 conn.err(err.Error())
215 conn.ok("scan listing")
216 for _, msg := range msgs {
217 conn.tp.PrintfLine("%d %d", msg.ID(), msg.Size())
219 conn.tp.PrintfLine(".")
222 func (conn *connection) doRETR() {
223 if conn.state != stateTxn {
224 conn.err(errStateTxn)
228 msg := conn.getRequestedMessage()
234 conn.err(errDeletedMsg)
238 rc, err := conn.mb.Retrieve(msg)
240 conn.log.Error("failed to retrieve messages", zap.Error(err))
241 conn.err(err.Error())
245 conn.log.Info("retreive message", zap.String("unique-id", msg.UniqueID()))
246 conn.ok(fmt.Sprintf("%d", msg.Size()))
248 w := conn.tp.DotWriter()
253 func (conn *connection) doDELE() {
254 if conn.state != stateTxn {
255 conn.err(errStateTxn)
259 msg := conn.getRequestedMessage()
265 conn.err(errDeletedMsg)
269 if err := conn.mb.Delete(msg); err != nil {
270 conn.log.Error("failed to delete message", zap.Error(err))
271 conn.err(err.Error())
273 conn.log.Info("delete message", zap.String("unique-id", msg.UniqueID()))
278 func (conn *connection) doRSET() {
279 if conn.state != stateTxn {
280 conn.err(errStateTxn)
284 conn.log.Info("reset")
288 func (conn *connection) doUIDL() {
289 if conn.state != stateTxn {
290 conn.err(errStateTxn)
294 msgs, err := conn.mb.ListMessages()
296 conn.log.Error("failed to list messages", zap.Error(err))
297 conn.err(err.Error())
301 conn.ok("unique-id listing")
302 for _, msg := range msgs {
306 conn.tp.PrintfLine("%d %s", msg.ID(), msg.UniqueID())
308 conn.tp.PrintfLine(".")
311 func (conn *connection) doCAPA() {
312 conn.ok("capabilitiy list")
319 for _, c := range caps {
320 conn.tp.PrintfLine(c)
324 func (conn *connection) getRequestedMessage() Message {
327 if _, err := fmt.Sscanf(conn.line, "%s %d", &cmd, &idx); err != nil {
333 conn.err("invalid message-number")
337 msg := conn.mb.GetMessage(idx)
339 conn.err("no such message")