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