Bump the version to 2.1.0.
[mailpopbox.git] / pop3.go
1 // mailpopbox
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
6
7 package main
8
9 import (
10 "crypto/tls"
11 "errors"
12 "fmt"
13 "io"
14 "io/ioutil"
15 "net"
16 "os"
17 "path"
18
19 "go.uber.org/zap"
20
21 "src.bluestatic.org/mailpopbox/pop3"
22 )
23
24 func runPOP3Server(config Config, log *zap.Logger) <-chan ServerControlMessage {
25 server := pop3Server{
26 config: config,
27 controlChan: make(chan ServerControlMessage),
28 log: log.With(zap.String("server", "pop3")),
29 }
30 go server.run()
31 return server.controlChan
32 }
33
34 type pop3Server struct {
35 config Config
36 controlChan chan ServerControlMessage
37 log *zap.Logger
38 }
39
40 func (server *pop3Server) run() {
41 for _, s := range server.config.Servers {
42 if err := os.Mkdir(s.MaildropPath, 0700); err != nil && !os.IsExist(err) {
43 server.log.Error("failed to open maildrop", zap.Error(err))
44 server.controlChan <- ServerControlFatalError
45 }
46 }
47
48 l, err := server.newListener()
49 if err != nil {
50 server.controlChan <- ServerControlFatalError
51 return
52 }
53
54 connChan := make(chan net.Conn)
55 go RunAcceptLoop(l, connChan, server.log)
56
57 reloadChan := CreateReloadSignal()
58
59 for {
60 select {
61 case <-reloadChan:
62 server.log.Info("restarting server")
63 l.Close()
64 server.controlChan <- ServerControlRestart
65 break
66 case conn, ok := <-connChan:
67 if ok {
68 go pop3.AcceptConnection(conn, server, server.log)
69 } else {
70 server.controlChan <- ServerControlFatalError
71 break
72 }
73 }
74 }
75 }
76
77 func (server *pop3Server) newListener() (net.Listener, error) {
78 tlsConfig, err := server.config.GetTLSConfig()
79 if err != nil {
80 server.log.Error("failed to configure TLS", zap.Error(err))
81 return nil, err
82 }
83
84 addr := fmt.Sprintf(":%d", server.config.POP3Port)
85 server.log.Info("starting server", zap.String("address", addr))
86
87 var l net.Listener
88 if tlsConfig == nil {
89 l, err = net.Listen("tcp", addr)
90 } else {
91 l, err = tls.Listen("tcp", addr, tlsConfig)
92 }
93 if err != nil {
94 server.log.Error("listen", zap.Error(err))
95 return nil, err
96 }
97
98 return l, nil
99 }
100
101 func (server *pop3Server) Name() string {
102 return server.config.Hostname
103 }
104
105 func (server *pop3Server) OpenMailbox(user, pass string) (pop3.Mailbox, error) {
106 for _, s := range server.config.Servers {
107 if user == MailboxAccount+s.Domain && pass == s.MailboxPassword {
108 return server.openMailbox(s.MaildropPath)
109 }
110 }
111 return nil, errors.New("permission denied")
112 }
113
114 func (server *pop3Server) openMailbox(maildrop string) (*mailbox, error) {
115 files, err := ioutil.ReadDir(maildrop)
116 if err != nil {
117 server.log.Error("failed read maildrop dir", zap.String("dir", maildrop), zap.Error(err))
118 return nil, errors.New("error opening maildrop")
119 }
120
121 mb := &mailbox{
122 messages: make([]message, 0, len(files)),
123 }
124
125 i := 0
126 for _, file := range files {
127 if file.IsDir() {
128 continue
129 }
130
131 msg := message{
132 filename: path.Join(maildrop, file.Name()),
133 index: i,
134 size: file.Size(),
135 }
136 mb.messages = append(mb.messages, msg)
137 i++
138 }
139
140 return mb, nil
141 }
142
143 type mailbox struct {
144 messages []message
145 }
146
147 type message struct {
148 filename string
149 index int
150 size int64
151 deleted bool
152 }
153
154 func (m message) UniqueID() string {
155 l := len(m.filename)
156 return path.Base(m.filename[:l-len(".msg")])
157 }
158
159 func (m message) ID() int {
160 return m.index + 1
161 }
162
163 func (m message) Size() int {
164 return int(m.size)
165 }
166
167 func (m message) Deleted() bool {
168 return m.deleted
169 }
170
171 func (mb *mailbox) ListMessages() ([]pop3.Message, error) {
172 msgs := make([]pop3.Message, len(mb.messages))
173 for i := 0; i < len(mb.messages); i++ {
174 msgs[i] = &mb.messages[i]
175 }
176 return msgs, nil
177 }
178
179 func (mb *mailbox) GetMessage(id int) pop3.Message {
180 if id == 0 || id > len(mb.messages) {
181 return nil
182 }
183 return &mb.messages[id-1]
184 }
185
186 func (mb *mailbox) Retrieve(msg pop3.Message) (io.ReadCloser, error) {
187 filename := msg.(*message).filename
188 return os.Open(filename)
189 }
190
191 func (mb *mailbox) Delete(msg pop3.Message) error {
192 msg.(*message).deleted = true
193 return nil
194 }
195
196 func (mb *mailbox) Close() error {
197 for _, message := range mb.messages {
198 if message.deleted {
199 os.Remove(message.filename)
200 }
201 }
202 return nil
203 }
204
205 func (mb *mailbox) Reset() {
206 for i, _ := range mb.messages {
207 mb.messages[i].deleted = false
208 }
209 }