Skip to content

Commit

Permalink
Mail is now stored using a bolt database. (#29)
Browse files Browse the repository at this point in the history
Fetching does not yet work because of the parsing of the body types. WIP (#2)
  • Loading branch information
EtienneBruines committed Jul 6, 2015
1 parent 1ba3111 commit a173575
Show file tree
Hide file tree
Showing 18 changed files with 363 additions and 105 deletions.
1 change: 1 addition & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package auth

import (
"fmt"

"golang.org/x/crypto/bcrypt"
)

Expand Down
3 changes: 2 additions & 1 deletion auth/boltstore/bolt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ package boltstore

import (
"fmt"
"os"

"github.com/alienscience/imapsrv/auth"
"github.com/boltdb/bolt"
"os"
)

type BoltAuthStore struct {
Expand Down
6 changes: 6 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func (c *login) execute(sess *session, out chan response) {
auth, err := sess.server.config.AuthBackend.Authenticate(c.userId, c.password)
if auth {
sess.st = authenticated
sess.user = c.userId
out <- ok(c.tag, "LOGIN completed")
return
}
Expand All @@ -144,6 +145,7 @@ func (c *logout) execute(sess *session, out chan response) {
defer close(out)

sess.st = notAuthenticated
sess.user = ""
out <- ok(c.tag, "LOGOUT completed").
shouldClose().
putLine("BYE IMAP4rev1 Server logging out")
Expand Down Expand Up @@ -298,6 +300,10 @@ func (c *fetch) execute(sess *session, out chan response) {
return
}

if sess.mailbox == nil {
out <- bad(c.tag, "Must SELECT first") // TODO: is this the correct message?
}

// If there is a fetch macro - convert it into fetch attachments
c.expandMacro()

Expand Down
5 changes: 3 additions & 2 deletions demo/auth/main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package main

import (
imap "github.com/alienscience/imapsrv"
"github.com/alienscience/imapsrv/auth/boltstore"
"io/ioutil"
"log"

imap "github.com/alienscience/imapsrv"
"github.com/alienscience/imapsrv/auth/boltstore"
)

func main() {
Expand Down
5 changes: 1 addition & 4 deletions demo/basic/main.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package main

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

func main() {
// The simplest possible server - zero config
Expand Down
12 changes: 8 additions & 4 deletions demo/complete/main.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package main

import (
"io/ioutil"
"log"

imap "github.com/alienscience/imapsrv"
"github.com/alienscience/imapsrv/auth/boltstore"
"github.com/alienscience/imapsrv/mailstore/boltmail"
"io/ioutil"
"log"
)

func main() {
Expand All @@ -21,14 +22,17 @@ func main() {
if err != nil {
log.Fatalln("Could not create BoltAuthStore:", err)
}
// Add a user
authStore.CreateUser("test@example.com", "password")

// Initialize mailstorage backend
mailStore, err := boltmail.NewBoltMailstore(tmpFile.Name() + "_mailstore")
if err != nil {
log.Fatalln("Could not create BoltMailstore:", err)
}

// Add a user
authStore.CreateUser("test@example.local", "password")
mailStore.NewUser("test@example.local")

// Put everything together
s := imap.NewServer(
// AUTH
Expand Down
3 changes: 2 additions & 1 deletion demo/customaddress/main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package main

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

imap "github.com/alienscience/imapsrv"
)

func main() {
Expand Down
3 changes: 2 additions & 1 deletion demo/starttls/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package main

import (
"fmt"
imap "github.com/alienscience/imapsrv"
"log"

imap "github.com/alienscience/imapsrv"
)

func main() {
Expand Down
3 changes: 2 additions & 1 deletion fetchAttachments.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package imapsrv
import (
"errors"
"fmt"
"github.com/jhillyerd/go.enmime"
"log"
"mime"
"strings"

"github.com/jhillyerd/go.enmime"
)

// A fetch attachment extracts part of a message and adds it to the response.
Expand Down
6 changes: 5 additions & 1 deletion imap.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ type Config struct {

AuthBackend auth.AuthStore

LmtpEndpoints []lmtpEntryPoint
LmtpEndpoints []endPoint

// Hostname is the hostname of this entire server
Hostname string

// Production indicates whether or not this is used in production
// - disabling this allows for the program to panic
Production bool

// AliasMapEndpoints indicate the endpoints on which to advertise the available addresses
// TODO: use / advertise these
AliasMapEndpoints []endPoint
}

type option func(*Server) error
Expand Down
44 changes: 36 additions & 8 deletions lmtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package imapsrv

import (
"bufio"
"bytes"
"fmt"
"log"
"net"
Expand Down Expand Up @@ -34,26 +35,26 @@ type lmtpClient struct {
session *lmtpSession
}

type lmtpEntryPoint struct {
type endPoint struct {
unix bool
addr string
}

func LMTPOptionSocket(loc string) func(*Server) error {
return func(s *Server) error {
s.config.LmtpEndpoints = append(s.config.LmtpEndpoints, lmtpEntryPoint{true, loc})
s.config.LmtpEndpoints = append(s.config.LmtpEndpoints, endPoint{true, loc})
return nil
}
}

func LMTPOptionTCP(addr string) func(*Server) error {
return func(s *Server) error {
s.config.LmtpEndpoints = append(s.config.LmtpEndpoints, lmtpEntryPoint{false, addr})
s.config.LmtpEndpoints = append(s.config.LmtpEndpoints, endPoint{false, addr})
return nil
}
}

func (s *Server) runLMTPListener(entrypoint lmtpEntryPoint, number int) {
func (s *Server) runLMTPListener(entrypoint endPoint, number int) {
// Add hostname to responses
lmtpStatusReady = fmt.Sprintf(lmtpStatusReady, s.config.Hostname)
lmtpStatusClosing = fmt.Sprintf(lmtpStatusClosing, s.config.Hostname)
Expand All @@ -66,13 +67,29 @@ func (s *Server) runLMTPListener(entrypoint lmtpEntryPoint, number int) {
addr, err := net.ResolveUnixAddr(unixNetwork, entrypoint.addr)
if err != nil {
log.Println("Warning, LMTP endpoint", number, "not started:", err)
return
}

// Create unix socket
listener, err = net.ListenUnix(unixNetwork, addr)
ulistener, err := net.ListenUnix(unixNetwork, addr)
if err != nil {
log.Println("Warning, LMTP endpoint", number, "not started:", err)
return
}
f, err := ulistener.File()
if err != nil {
log.Println("Warning, LMTP endpoint", number, "not started:", err)
// TODO: cleanup
return
}
err = f.Chmod(0777) // TODO: which permissions should be used?
// TODO: locally this changes it correctly to 0777, but when using `ls -l`, it changed to 0755
if err != nil {
log.Println("Warning, LMTP endpoint", number, "not started:", err)
// TODO: cleanup
return
}
listener = ulistener
} else {
// Parse tcp address
addr, err := net.ResolveTCPAddr(tcpNetwork, entrypoint.addr)
Expand Down Expand Up @@ -145,12 +162,23 @@ func (c *lmtpClient) handle(s *Server) {
}

c.session.receivingData = false
writeSimpleLine("250 OK", c.session.rw.W)

// TODO: we should now process the session,
// before continuing (or else it gets lost forever)
log.Println(string(data))

buf := bytes.NewBuffer(data)
for _, rcpt := range c.session.recipients {
msg, err := s.config.Mailstore.NewMessage(rcpt, buf)
if err != nil {
log.Println("Error occured:", err)
// TODO: this does not have to be the correct error message... it could be
// another reason for failing
writeSimpleLine("550 No such user here", c.session.rw.W)
continue
}
writeSimpleLine("250 OK", c.session.rw.W)
d, _ := msg.InternalDate()
log.Println(rcpt, "received:", d)
}
} else {
// Read line, tags, process and respond
line, err := c.session.rw.ReadLine()
Expand Down
14 changes: 9 additions & 5 deletions mailstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import (
type Mailstore interface {
// Get IMAP mailbox information
// Returns nil if the mailbox does not exist
Mailbox(path []string) (Mailbox, error)
Mailbox(owner string, path []string) (Mailbox, error)
// Get a list of mailboxes at the given path
Mailboxes(path []string) ([]Mailbox, error)
Mailboxes(owner string, path []string) ([]Mailbox, error)
// NewMessage adds the raw message information to the server, in the correct location
NewMessage(message io.Reader) (Message, error)
NewMessage(rcpt string, message io.Reader) (Message, error)
// NewUser adds the user to the server
NewUser(email string) error
// Addresses returns a list of all available recipients
Addresses() ([]string, error)
}

// An IMAP mailbox
Expand Down Expand Up @@ -69,8 +73,8 @@ var mailboxFlags = map[uint8]string{
// A message is read through this interface
type MessageReader interface {
io.Reader
io.Seeker
io.Closer
//io.Seeker
//io.Closer
}

// An IMAP message
Expand Down
12 changes: 12 additions & 0 deletions mailstore/boltmail/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package boltmail
import (
"bytes"
"encoding/gob"
"fmt"
"strconv"

"github.com/boltdb/bolt"
)

func toBytes(i interface{}) ([]byte, error) {
Expand All @@ -20,3 +24,11 @@ func fromBytes(b []byte, i interface{}) error {
dec := gob.NewDecoder(buf)
return dec.Decode(i)
}

func getInt(key []byte, buck *bolt.Bucket) (int, error) {
b := buck.Get(key)
if len(b) == 0 {
return 0, fmt.Errorf("key did not exist")
}
return strconv.Atoi(string(b))
}
Loading

0 comments on commit a173575

Please sign in to comment.