Add zap logging through the servers.
[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 error {
19 server := pop3Server{
20 config: config,
21 rc: make(chan error),
22 log: log.With(zap.String("server", "pop3")),
23 }
24 go server.run()
25 return server.rc
26 }
27
28 type pop3Server struct {
29 config Config
30 rc chan error
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.rc <- err
39 }
40 }
41
42 tlsConfig, err := server.config.GetTLSConfig()
43 if err != nil {
44 server.log.Error("failed to configure TLS", zap.Error(err))
45 server.rc <- err
46 return
47 }
48
49 addr := fmt.Sprintf(":%d", server.config.POP3Port)
50 server.log.Info("starting server", zap.String("address", addr))
51
52 var l net.Listener
53 if tlsConfig == nil {
54 l, err = net.Listen("tcp", addr)
55 } else {
56 l, err = tls.Listen("tcp", addr, tlsConfig)
57 }
58 if err != nil {
59 server.log.Error("listen", zap.Error(err))
60 server.rc <- err
61 return
62 }
63
64 for {
65 conn, err := l.Accept()
66 if err != nil {
67 server.log.Error("accept", zap.Error(err))
68 server.rc <- err
69 break
70 }
71
72 go pop3.AcceptConnection(conn, server, server.log)
73 }
74 }
75
76 func (server *pop3Server) Name() string {
77 return server.config.Hostname
78 }
79
80 func (server *pop3Server) OpenMailbox(user, pass string) (pop3.Mailbox, error) {
81 for _, s := range server.config.Servers {
82 if user == "mailbox@"+s.Domain && pass == s.MailboxPassword {
83 return server.openMailbox(s.MaildropPath)
84 }
85 }
86 return nil, errors.New("permission denied")
87 }
88
89 func (server *pop3Server) openMailbox(maildrop string) (*mailbox, error) {
90 files, err := ioutil.ReadDir(maildrop)
91 if err != nil {
92 // TODO: hide error, log instead
93 return nil, err
94 }
95
96 mb := &mailbox{
97 messages: make([]message, 0, len(files)),
98 }
99
100 i := 0
101 for _, file := range files {
102 if file.IsDir() {
103 continue
104 }
105
106 msg := message{
107 filename: path.Join(maildrop, file.Name()),
108 index: i,
109 size: file.Size(),
110 }
111 mb.messages = append(mb.messages, msg)
112 i++
113 }
114
115 return mb, nil
116 }
117
118 type mailbox struct {
119 messages []message
120 }
121
122 type message struct {
123 filename string
124 index int
125 size int64
126 deleted bool
127 }
128
129 func (m message) ID() int {
130 return m.index + 1
131 }
132
133 func (m message) Size() int {
134 return int(m.size)
135 }
136
137 func (m message) Deleted() bool {
138 return m.deleted
139 }
140
141 func (mb *mailbox) ListMessages() ([]pop3.Message, error) {
142 msgs := make([]pop3.Message, len(mb.messages))
143 for i := 0; i < len(mb.messages); i++ {
144 msgs[i] = &mb.messages[i]
145 }
146 return msgs, nil
147 }
148
149 func (mb *mailbox) GetMessage(id int) pop3.Message {
150 if id > len(mb.messages) {
151 return nil
152 }
153 return &mb.messages[id-1]
154 }
155
156 func (mb *mailbox) Retrieve(msg pop3.Message) (io.ReadCloser, error) {
157 filename := msg.(*message).filename
158 return os.Open(filename)
159 }
160
161 func (mb *mailbox) Delete(msg pop3.Message) error {
162 msg.(*message).deleted = true
163 return nil
164 }
165
166 func (mb *mailbox) Close() error {
167 for _, message := range mb.messages {
168 if message.deleted {
169 os.Remove(message.filename)
170 }
171 }
172 return nil
173 }
174
175 func (mb *mailbox) Reset() {
176 for _, message := range mb.messages {
177 message.deleted = false
178 }
179 }