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