github.com/jordwest/imap-server@v0.0.0-20200627020849-1cf758ba359f/server.go (about) 1 package imap 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "net" 8 "net/textproto" 9 10 "github.com/jordwest/imap-server/conn" 11 "github.com/jordwest/imap-server/mailstore" 12 ) 13 14 const ( 15 // defaultAddress is the default address that the IMAP server should listen 16 // on. 17 defaultAddress = ":143" 18 ) 19 20 // Server represents an IMAP server instance. 21 type Server struct { 22 Addr string 23 listeners []net.Listener 24 Transcript io.Writer 25 mailstore mailstore.Mailstore 26 } 27 28 // NewServer initialises a new Server. Note that this does not start the server. 29 // You must called either Listen() followed by Serve() or call ListenAndServe() 30 func NewServer(store mailstore.Mailstore) *Server { 31 s := &Server{ 32 Addr: defaultAddress, 33 mailstore: store, 34 Transcript: ioutil.Discard, 35 } 36 return s 37 } 38 39 // ListenAndServe is shorthand for calling Serve() with a listener listening 40 // on the default port. 41 func (s *Server) ListenAndServe() (err error) { 42 fmt.Fprintf(s.Transcript, "Listening on %s\n", s.Addr) 43 ln, err := net.Listen("tcp", s.Addr) 44 if err != nil { 45 return fmt.Errorf("Error listening: %s\n", err) 46 } 47 48 return s.Serve(ln) 49 } 50 51 // Serve starts the server and spawns new goroutines to handle each client 52 // connection as they come in. This function blocks. 53 func (s *Server) Serve(l net.Listener) error { 54 fmt.Fprintf(s.Transcript, "Serving on %s\n", l.Addr().String()) 55 defer l.Close() 56 for { 57 conn, err := l.Accept() 58 if err != nil { 59 return fmt.Errorf("Error accepting connection: %s\n", err) 60 } 61 62 fmt.Fprintf(s.Transcript, "Connection accepted\n") 63 c, err := s.newConn(conn) 64 if err != nil { 65 return err 66 } 67 68 go c.Start() 69 } 70 } 71 72 func (s *Server) newConn(netConn net.Conn) (c *conn.Conn, err error) { 73 c = conn.NewConn(s.mailstore, netConn, s.Transcript) 74 c.SetState(conn.StateNew) 75 return c, nil 76 } 77 78 // NewTestConnection is for test facilitation. 79 // Creates a server and then dials the server, returning the connection, 80 // allowing test to inject state and wait for an expected response 81 // The connection must be started manually with `go conn.Start()` 82 // once desired state has been injected 83 func NewTestConnection(transcript io.Writer) (s *Server, clientConn *textproto.Conn, serverConn *conn.Conn, server *Server, err error) { 84 mStore := mailstore.NewDummyMailstore() 85 s = NewServer(mStore) 86 s.Addr = ":10143" 87 s.Transcript = transcript 88 89 l, err := net.Listen("tcp", s.Addr) 90 if err != nil { 91 return nil, nil, nil, nil, err 92 } 93 94 c, err := net.Dial("tcp4", "localhost:10143") 95 if err != nil { 96 return nil, nil, nil, nil, err 97 } 98 99 textc := textproto.NewConn(c) 100 clientConn = textc 101 102 conn, err := l.Accept() 103 if err != nil { 104 return nil, nil, nil, nil, err 105 } 106 fmt.Fprintf(s.Transcript, "Client connected\n") 107 serverConn, err = s.newConn(conn) 108 109 return s, clientConn, serverConn, s, nil 110 }