-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathserver.go
125 lines (109 loc) · 3.32 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package popart
import (
"errors"
"fmt"
"net"
"os"
"time"
)
// Server listens for incoming POP3 connections and handles them with the help
// of Handler objects passed via dependency injection.
type Server struct {
// Hostname defines how the server should introduce itself. It is only
// really important if the server is supposed to support APOP
// authentication method.
Hostname string
// OnNewConnection is a callback capable of producing Handler objects
// to handle incoming connections.
OnNewConnection func(peer net.Addr) Handler
// Timeout allows setting an inactivity autologout timer. According to
// rfc1939 such a timer MUST be of at least 10 minutes' duration.
Timeout time.Duration
// Implementation allows the server to provide custom implementation
// name to the POP3 client. The default one is "popart".
Implementation string
// Expire allows the server to provide message expiration advice to the
// client. The default one is "NEVER".
Expire string
// APOP determines whether the server should implement the APOP
// authentication method.
APOP bool
// capabilites is a pre-calculated set of things server can announce to
// the client upon receiving the CAPA command.
capabilities []string
}
// Serve takes a net.Listener and starts processing incoming requests. Please
// note that Server does not implement STARTTLS so unless your Listener
// implements TLS (see package crypto/tls in the standard library) all
// communications happen in plaintext. You have been warned.
func (s *Server) Serve(listener net.Listener) error {
if err := s.verifySettings(); err != nil {
return err
}
s.calculateCapabilities()
for {
conn, err := listener.Accept()
if err != nil && s.handleAcceptError(err) != nil {
return err
}
s.serveOne(conn)
}
}
func (s *Server) handleAcceptError(err error) error {
if ne, ok := err.(net.Error); ok && ne.Temporary() {
time.Sleep(time.Second)
return nil
}
return err
}
func (s *Server) verifySettings() error {
if s.OnNewConnection == nil {
return errors.New("new connection callback not be nil")
}
if s.Timeout < 10*time.Minute {
return errors.New("at least 10 minutes timeout required")
}
return nil
}
func (s *Server) serveOne(conn net.Conn) {
handler := s.OnNewConnection(conn.RemoteAddr())
if handler == nil {
// This must have been a conscious decision on the
// part of the HandlerFactory so not treating that as
// an error. In fact, not even logging it since the
// OnNewConnection callback is perfectly capable of
// doing that.
return
}
go newSession(s, handler, conn).serve()
}
func (s *Server) calculateCapabilities() {
s.Expire = withDefault(s.Expire, "NEVER")
s.Implementation = withDefault(s.Implementation, "popart")
s.capabilities = capabilities(s.Expire, s.Implementation)
}
// getBanner is only relevant within the context of an APOP exchange.
func (s *Server) getBanner() string {
return fmt.Sprintf(
"<%d.%d@%s>",
os.Getpid(),
time.Now().Unix(),
s.Hostname,
)
}
func capabilities(expire, implementation string) []string {
return []string{
"TOP",
"USER", // TODO: this should be factored out.
"PIPELINING",
fmt.Sprintf("%s %s", "EXPIRE", expire),
"UIDL",
fmt.Sprintf("%s %s", "IMPLEMENTATION", implementation),
}
}
func withDefault(value, fallback string) string {
if value == "" {
return fallback
}
return value
}