]>
src.bluestatic.org Git - mailpopbox.git/blob - pop3/conn.go
2 // Copyright 2020 Blue Static <https://www.bluestatic.org>
3 // This program is free software licensed under the GNU General Public License,
4 // version 3.0. The full text of the license can be found in LICENSE.txt.
5 // SPDX-License-Identifier: GPL-3.0-only
22 stateAuth state
= iota
28 errStateAuth
= "not in AUTHORIZATION"
29 errStateTxn
= "not in TRANSACTION"
30 errSyntax
= "syntax error"
31 errDeletedMsg
= "no such message - deleted"
34 type connection
struct {
49 func AcceptConnection(netConn net
.Conn
, po PostOffice
, log
*zap
.Logger
) {
50 log
= log
.With(zap
.Stringer("client", netConn
.RemoteAddr()))
53 tp
: textproto
.NewConn(netConn
),
58 conn
.log
.Info("accepted connection")
59 conn
.ok(fmt
.Sprintf("POP3 (mailpopbox) server %s", po
.Name()))
64 conn
.line
, err
= conn
.tp
.ReadLine()
66 conn
.log
.Error("ReadLine()", zap
.Error(err
))
72 if _
, err
:= fmt
.Sscanf(conn
.line
, "%s", &cmd
); err
!= nil {
73 conn
.err("invalid command")
77 conn
.log
= log
.With(zap
.String("command", cmd
))
79 switch strings
.ToUpper(cmd
) {
104 conn
.err("unknown command")
109 func (conn
*connection
) ok(msg
string) {
110 conn
.log
.Info("ok", zap
.String("reply", msg
))
114 conn
.tp
.PrintfLine("+OK%s", msg
)
117 func (conn
*connection
) err(msg
string) {
118 conn
.log
.Error("error", zap
.String("message", msg
))
121 conn
.tp
.PrintfLine("-ERR%s", msg
)
125 func (conn
*connection
) doQUIT() {
126 defer conn
.tp
.Close()
129 err
:= conn
.mb
.Close()
131 conn
.err("failed to remove some messages")
138 func (conn
*connection
) doUSER() {
139 if conn
.state
!= stateAuth
{
140 conn
.err(errStateAuth
)
145 if len(conn
.line
) < cmd
{
146 conn
.err("invalid user")
150 conn
.user
= conn
.line
[cmd
:]
154 func (conn
*connection
) doPASS() {
155 if conn
.state
!= stateAuth
{
156 conn
.err(errStateAuth
)
160 if len(conn
.user
) == 0 {
166 if len(conn
.line
) < cmd
{
167 conn
.err("invalid pass")
171 pass
:= conn
.line
[cmd
:]
172 if mbox
, err
:= conn
.po
.OpenMailbox(conn
.user
, pass
); err
== nil {
173 conn
.log
.Info("authenticated", zap
.String("user", conn
.user
))
174 conn
.state
= stateTxn
178 conn
.log
.Error("failed to open mailbox", zap
.Error(err
))
179 conn
.err(err
.Error())
183 func (conn
*connection
) doSTAT() {
184 if conn
.state
!= stateTxn
{
185 conn
.err(errStateTxn
)
189 msgs
, err
:= conn
.mb
.ListMessages()
191 conn
.log
.Error("failed to list messages", zap
.Error(err
))
192 conn
.err(err
.Error())
198 for _
, msg
:= range msgs
{
206 conn
.ok(fmt
.Sprintf("%d %d", num
, size
))
209 func (conn
*connection
) doLIST() {
210 if conn
.state
!= stateTxn
{
211 conn
.err(errStateTxn
)
215 msgs
, err
:= conn
.mb
.ListMessages()
217 conn
.log
.Error("failed to list messages", zap
.Error(err
))
218 conn
.err(err
.Error())
222 conn
.ok("scan listing")
223 for _
, msg
:= range msgs
{
224 conn
.tp
.PrintfLine("%d %d", msg
.ID(), msg
.Size())
226 conn
.tp
.PrintfLine(".")
229 func (conn
*connection
) doRETR() {
230 if conn
.state
!= stateTxn
{
231 conn
.err(errStateTxn
)
235 msg
:= conn
.getRequestedMessage()
241 conn
.err(errDeletedMsg
)
245 rc
, err
:= conn
.mb
.Retrieve(msg
)
247 conn
.log
.Error("failed to retrieve messages", zap
.Error(err
))
248 conn
.err(err
.Error())
252 conn
.log
.Info("retrieve message", zap
.String("unique-id", msg
.UniqueID()))
253 conn
.ok(fmt
.Sprintf("%d", msg
.Size()))
255 w
:= conn
.tp
.DotWriter()
260 func (conn
*connection
) doDELE() {
261 if conn
.state
!= stateTxn
{
262 conn
.err(errStateTxn
)
266 msg
:= conn
.getRequestedMessage()
272 conn
.err(errDeletedMsg
)
276 if err
:= conn
.mb
.Delete(msg
); err
!= nil {
277 conn
.log
.Error("failed to delete message", zap
.Error(err
))
278 conn
.err(err
.Error())
280 conn
.log
.Info("delete message", zap
.String("unique-id", msg
.UniqueID()))
285 func (conn
*connection
) doRSET() {
286 if conn
.state
!= stateTxn
{
287 conn
.err(errStateTxn
)
291 conn
.log
.Info("reset")
295 func (conn
*connection
) doUIDL() {
296 if conn
.state
!= stateTxn
{
297 conn
.err(errStateTxn
)
301 msgs
, err
:= conn
.mb
.ListMessages()
303 conn
.log
.Error("failed to list messages", zap
.Error(err
))
304 conn
.err(err
.Error())
308 conn
.ok("unique-id listing")
309 for _
, msg
:= range msgs
{
313 conn
.tp
.PrintfLine("%d %s", msg
.ID(), msg
.UniqueID())
315 conn
.tp
.PrintfLine(".")
318 func (conn
*connection
) doCAPA() {
319 conn
.ok("capability list")
326 for _
, c
:= range caps
{
327 conn
.tp
.PrintfLine(c
)
331 func (conn
*connection
) getRequestedMessage() Message
{
334 if _
, err
:= fmt
.Sscanf(conn
.line
, "%s %d", &cmd
, &idx
); err
!= nil {
340 conn
.err("invalid message-number")
344 msg
:= conn
.mb
.GetMessage(idx
)
346 conn
.err("no such message")