Skip to content

Commit

Permalink
Functional configuration refactoring. Fixes alienscience#5
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandru Plugaru committed Nov 4, 2014
1 parent d3b4006 commit 845b64d
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 109 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ This is an IMAP server written in Go. It is a work in progress.
In the demo subdirectory is an example IMAP server that starts up on port 1193. To run this server:

```
$ cd demo
$ go build
$ ./demo
$ go run ./demo/main.go
```

You can connect to this server using telnet or netcat. For example:
Expand All @@ -37,7 +35,11 @@ $ nc -C localhost 1193

# Developing

The server is not fully operational on its own. It defines an interface in session.go which describes the service it needs from a Mailstore. The Mailstore can be a database or filesystem or combination of both. The IMAP server does not care and it is up to the user to supply this service.
The server is not fully operational on its own. It requires a mailstore and an authentication mechanism.

It defines an interface in mailstore.go which describes the service it needs from a Mailstore. You can use multiple mailstores at the same time: database, filesystem, maildir, etc...

There are plans to add basic support for maildir and a basic database storage.

To add a new IMAP command the usual steps are:

Expand Down
46 changes: 22 additions & 24 deletions demo/main.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,53 @@

package main

import (
imap "github.com/alienscience/imapsrv"
)

// A dummy mailstore used for demonstrating the IMAP server
type Mailstore struct {
}

func main() {
// Configure an IMAP server on localhost port 1193
config := imap.DefaultConfig()
config.Interface = "127.0.0.1:1193"

// Configure a dummy mailstore
mailstore := &Mailstore{}
config.Store = mailstore

// Start the server
server := imap.Create(config)
server.Start()

// The simplest possible server - zero config
// It will find a free tcp port, create some temporary directories.. - just give me a server!
//s := imap.NewServer()
//s.Start()

// More advanced config
m := &imap.DummyMailstore{}

s := imap.NewServer(
imap.Listen("127.0.0.1:1193"),
imap.Store(m),
)
s.Start()
}

//------ Dummy Mailstore -------------------------------------------------------
// A dummy mailstore used for demonstrating the IMAP server
type DummyMailstore struct {
}

// Get mailbox information
func (m *Mailstore) GetMailbox(name string) (*imap.Mailbox, error) {
func (m *DummyMailstore) GetMailbox(name string) (*imap.Mailbox, error) {
return &imap.Mailbox{
Name: "inbox",
Id: 1,
Id: 1,
}, nil
}

// Get the sequence number of the first unseen message
func (m *Mailstore) FirstUnseen(mbox int64) (int64, error) {
func (m *DummyMailstore) FirstUnseen(mbox int64) (int64, error) {
return 4, nil
}

// Get the total number of messages in an IMAP mailbox
func (m *Mailstore) TotalMessages(mbox int64) (int64, error) {
func (m *DummyMailstore) TotalMessages(mbox int64) (int64, error) {
return 8, nil
}

// Get the total number of unread messages in an IMAP mailbox
func (m *Mailstore) RecentMessages(mbox int64) (int64, error) {
func (m *DummyMailstore) RecentMessages(mbox int64) (int64, error) {
return 4, nil
}

// Get the next available uid in an IMAP mailbox
func (m *Mailstore) NextUid(mbox int64) (int64, error) {
func (m *DummyMailstore) NextUid(mbox int64) (int64, error) {
return 9, nil
}
146 changes: 117 additions & 29 deletions imap.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

// An IMAP server
package imapsrv

Expand All @@ -10,17 +9,22 @@ import (

// IMAP server configuration
type Config struct {
Interface string
MaxClients int
Store Mailstore
MaxClients uint
Listeners []Listener
Mailstores []Mailstore
}

// An IMAP Server
type Server struct {
// Server configuration
config *Config
// Number of active clients
activeClients int
activeClients uint
}

// A listener is listening on a given address. Ex: 0.0.0.0:193
type Listener struct {
Addr string
}

// An IMAP Client as seen by an IMAP server
Expand All @@ -42,47 +46,131 @@ func Create(config *Config) *Server {

// Return the default server configuration
func DefaultConfig() *Config {
listeners := []Listener{
Listener{
Addr: "0.0.0.0:143",
},
}

return &Config{
Interface: "0.0.0.0:193",
Listeners: listeners,
MaxClients: 8,
}
}

// Start an IMAP server
func (s *Server) Start() {
// Add a mailstore to the config
func Store(m Mailstore) func(*Server) error {
return func(s *Server) error {
s.config.Mailstores = append(s.config.Mailstores, m)
return nil
}
}

// Start listening for IMAP connections
iface := s.config.Interface
listener, err := net.Listen("tcp", iface)
if err != nil {
log.Fatalf("IMAP cannot listen on %s, %v", iface, err)
// test if 2 listeners are equal
func equalListeners(l1, l2 []Listener) bool {
for i, l := range l1 {
if l != l2[i] {
return false
}
}
return true
}

log.Print("IMAP server listening on ", iface)
// Add an interface to listen to
func Listen(Addr string) func(*Server) error {
return func(s *Server) error {
// if we only have the default config we should override it
dc := DefaultConfig()
l := Listener{
Addr: Addr,
}
if equalListeners(dc.Listeners, s.config.Listeners) {
s.config.Listeners = []Listener{l}
} else {
s.config.Listeners = append(s.config.Listeners, l)
}

clientNumber := 1
return nil
}
}

for {
// Accept a connection from a new client
conn, err := listener.Accept()
// Set MaxClients config
func MaxClients(max uint) func(*Server) error {
return func(s *Server) error {
s.config.MaxClients = max
return nil
}
}

func NewServer(options ...func(*Server) error) *Server {
// set the default config
s := &Server{}
dc := DefaultConfig()
s.config = dc

// override the config with the functional options
for _, option := range options {
err := option(s)
if err != nil {
log.Print("IMAP accept error, ", err)
continue
panic(err)
}
}

//Check if we can listen on default ports, if not try to find a free port
if equalListeners(dc.Listeners, s.config.Listeners) {
listener := s.config.Listeners[0]
l, err := net.Listen("tcp", listener.Addr)
if err != nil {
l, err = net.Listen("tcp4", ":0") // this will ask the OS to give us a free port
if err != nil {
panic("Can't listen on any port")
}
l.Close()
s.config.Listeners[0].Addr = l.Addr().String()
} else {
l.Close()
}
}

return s
}

// Handle the client
client := &client{
conn: conn,
bufin: bufio.NewReader(conn),
bufout: bufio.NewWriter(conn),
id: clientNumber,
config: s.config,
// Start an IMAP server
func (s *Server) Start() error {
// Start listening for IMAP connections
for _, iface := range s.config.Listeners {
listener, err := net.Listen("tcp", iface.Addr)
if err != nil {
log.Fatalf("IMAP cannot listen on %s, %v", iface.Addr, err)
}

go client.handle()
log.Print("IMAP server listening on ", iface.Addr)

clientNumber := 1

clientNumber += 1
for {
// Accept a connection from a new client
conn, err := listener.Accept()
if err != nil {
log.Print("IMAP accept error, ", err)
continue
}

// Handle the client
client := &client{
conn: conn,
bufin: bufio.NewReader(conn),
bufout: bufio.NewWriter(conn),
id: clientNumber,
config: s.config,
}

go client.handle()

clientNumber += 1
}
}
return nil
}

// Handle requests from an IMAP client
Expand Down
48 changes: 48 additions & 0 deletions mailstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package imapsrv

// A service that is needed to read mail messages
type Mailstore interface {
// Get IMAP mailbox information
// Returns nil if the mailbox does not exist
GetMailbox(name string) (*Mailbox, error)
// Get the sequence number of the first unseen message
FirstUnseen(mbox int64) (int64, error)
// Get the total number of messages in an IMAP mailbox
TotalMessages(mbox int64) (int64, error)
// Get the total number of unread messages in an IMAP mailbox
RecentMessages(mbox int64) (int64, error)
// Get the next available uid in an IMAP mailbox
NextUid(mbox int64) (int64, error)
}

// A dummy mailstore used for demonstrating the IMAP server
type DummyMailstore struct {
}

// Get mailbox information
func (m *DummyMailstore) GetMailbox(name string) (*Mailbox, error) {
return &Mailbox{
Name: "inbox",
Id: 1,
}, nil
}

// Get the sequence number of the first unseen message
func (m *DummyMailstore) FirstUnseen(mbox int64) (int64, error) {
return 4, nil
}

// Get the total number of messages in an IMAP mailbox
func (m *DummyMailstore) TotalMessages(mbox int64) (int64, error) {
return 8, nil
}

// Get the total number of unread messages in an IMAP mailbox
func (m *DummyMailstore) RecentMessages(mbox int64) (int64, error) {
return 4, nil
}

// Get the next available uid in an IMAP mailbox
func (m *DummyMailstore) NextUid(mbox int64) (int64, error) {
return 9, nil
}
1 change: 0 additions & 1 deletion parser.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package imapsrv

import (
Expand Down
Loading

0 comments on commit 845b64d

Please sign in to comment.