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