forked from jordwest/imap-server
/
server.go
129 lines (113 loc) · 3.1 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
126
127
128
129
package imap
import (
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/textproto"
"github.com/jordwest/imap-server/conn"
"github.com/jordwest/imap-server/mailstore"
)
// Server represents an IMAP server instance
type Server struct {
Addr string
listener net.Listener
Transcript io.Writer
mailstore mailstore.Mailstore
}
// NewServer initialises a new Server. Note that this does not start the server.
// You must called either Listen() followed by Serve() or call ListenAndServe()
func NewServer(store mailstore.Mailstore) *Server {
s := &Server{
Addr: ":143",
mailstore: store,
Transcript: ioutil.Discard,
}
return s
}
// ListenAndServe is shorthand for calling Listen() followed by Serve().
func (s *Server) ListenAndServe() (err error) {
err = s.Listen()
if err != nil {
return err
}
return s.Serve()
}
// Listen has the server begin listening for new connections.
// This function is non-blocking.
func (s *Server) Listen() error {
if s.listener != nil {
return errors.New("Listener already exists")
}
fmt.Fprintf(s.Transcript, "Listening on %s\n", s.Addr)
ln, err := net.Listen("tcp", s.Addr)
if err != nil {
fmt.Printf("Error listening: %s\n", err)
return err
}
s.listener = ln
return nil
}
// Serve starts the server and spawns new goroutines to handle each client connection
// as they come in. This function blocks.
func (s *Server) Serve() error {
defer s.listener.Close()
for {
conn, err := s.listener.Accept()
if err != nil {
fmt.Errorf("Error accepting connection: %s\n", err)
return err
}
fmt.Fprintf(s.Transcript, "Connection accepted\n")
c, err := s.newConn(conn)
if err != nil {
return err
}
go c.Start()
}
}
// Close stops the server listening for all new connections
func (s *Server) Close() (err error) {
fmt.Fprintf(s.Transcript, "Closing server listener\n")
if s.listener == nil {
return errors.New("Server not started")
}
err = s.listener.Close()
if err == nil {
s.listener = nil
}
return err
}
func (s *Server) newConn(netConn net.Conn) (c *conn.Conn, err error) {
c = conn.NewConn(s.mailstore, netConn, s.Transcript)
c.SetState(conn.StateNew)
return c, nil
}
// NewTestConnection is for test facilitation.
// Creates a server and then dials the server, returning the connection,
// allowing test to inject state and wait for an expected response
// The connection must be started manually with `go conn.Start()`
// once desired state has been injected
func NewTestConnection(transcript io.Writer) (s *Server, clientConn *textproto.Conn, serverConn *conn.Conn, server *Server, err error) {
mStore := mailstore.NewDummyMailstore()
s = NewServer(mStore)
s.Addr = ":10143"
s.Transcript = transcript
if err = s.Listen(); err != nil {
return nil, nil, nil, nil, err
}
c, err := net.Dial("tcp4", "localhost:10143")
if err != nil {
return nil, nil, nil, nil, err
}
textc := textproto.NewConn(c)
clientConn = textc
conn, err := s.listener.Accept()
if err != nil {
return nil, nil, nil, nil, err
}
fmt.Fprintf(s.Transcript, "Client connected\n")
serverConn, err = s.newConn(conn)
return s, clientConn, serverConn, s, nil
}