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  }