From a17357537a8f85d3baa5812cc030bfce30be457e Mon Sep 17 00:00:00 2001 From: Etienne Bruines Date: Mon, 6 Jul 2015 02:35:18 +0200 Subject: [PATCH] Mail is now stored using a bolt database. (#29) Fetching does not yet work because of the parsing of the body types. WIP (#2) --- auth/auth.go | 1 + auth/boltstore/bolt.go | 3 +- command.go | 6 + demo/auth/main.go | 5 +- demo/basic/main.go | 5 +- demo/complete/main.go | 12 +- demo/customaddress/main.go | 3 +- demo/starttls/main.go | 3 +- fetchAttachments.go | 3 +- imap.go | 6 +- lmtp.go | 44 +++++-- mailstore.go | 14 ++- mailstore/boltmail/helpers.go | 12 ++ mailstore/boltmail/mailbox.go | 207 +++++++++++++++++++++++++++++--- mailstore/boltmail/mailstore.go | 72 +++++++++-- mailstore/boltmail/message.go | 47 ++------ session.go | 12 +- wrappers.go | 13 +- 18 files changed, 363 insertions(+), 105 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 2f8e3f1..4b38792 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -2,6 +2,7 @@ package auth import ( "fmt" + "golang.org/x/crypto/bcrypt" ) diff --git a/auth/boltstore/bolt.go b/auth/boltstore/bolt.go index bfb1942..521a497 100644 --- a/auth/boltstore/bolt.go +++ b/auth/boltstore/bolt.go @@ -4,9 +4,10 @@ package boltstore import ( "fmt" + "os" + "github.com/alienscience/imapsrv/auth" "github.com/boltdb/bolt" - "os" ) type BoltAuthStore struct { diff --git a/command.go b/command.go index 33c4e99..2b02173 100644 --- a/command.go +++ b/command.go @@ -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 } @@ -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") @@ -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() diff --git a/demo/auth/main.go b/demo/auth/main.go index 208678b..610ebbc 100644 --- a/demo/auth/main.go +++ b/demo/auth/main.go @@ -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() { diff --git a/demo/basic/main.go b/demo/basic/main.go index 8f319dc..6bd0b41 100644 --- a/demo/basic/main.go +++ b/demo/basic/main.go @@ -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 diff --git a/demo/complete/main.go b/demo/complete/main.go index 748bb37..8545eea 100644 --- a/demo/complete/main.go +++ b/demo/complete/main.go @@ -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() { @@ -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 diff --git a/demo/customaddress/main.go b/demo/customaddress/main.go index 4e8d5ab..cb2eea6 100644 --- a/demo/customaddress/main.go +++ b/demo/customaddress/main.go @@ -1,8 +1,9 @@ package main import ( - imap "github.com/alienscience/imapsrv" "log" + + imap "github.com/alienscience/imapsrv" ) func main() { diff --git a/demo/starttls/main.go b/demo/starttls/main.go index ecfd49b..ae224a5 100644 --- a/demo/starttls/main.go +++ b/demo/starttls/main.go @@ -2,8 +2,9 @@ package main import ( "fmt" - imap "github.com/alienscience/imapsrv" "log" + + imap "github.com/alienscience/imapsrv" ) func main() { diff --git a/fetchAttachments.go b/fetchAttachments.go index 9a83d6a..d4391ed 100644 --- a/fetchAttachments.go +++ b/fetchAttachments.go @@ -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. diff --git a/imap.go b/imap.go index 46a7f69..4db302a 100644 --- a/imap.go +++ b/imap.go @@ -26,7 +26,7 @@ type Config struct { AuthBackend auth.AuthStore - LmtpEndpoints []lmtpEntryPoint + LmtpEndpoints []endPoint // Hostname is the hostname of this entire server Hostname string @@ -34,6 +34,10 @@ type Config struct { // 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 diff --git a/lmtp.go b/lmtp.go index 36cde09..b590db4 100644 --- a/lmtp.go +++ b/lmtp.go @@ -2,6 +2,7 @@ package imapsrv import ( "bufio" + "bytes" "fmt" "log" "net" @@ -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) @@ -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) @@ -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() diff --git a/mailstore.go b/mailstore.go index 08232a6..beaccd0 100644 --- a/mailstore.go +++ b/mailstore.go @@ -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 @@ -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 diff --git a/mailstore/boltmail/helpers.go b/mailstore/boltmail/helpers.go index 0cc7ed3..faaa5ac 100644 --- a/mailstore/boltmail/helpers.go +++ b/mailstore/boltmail/helpers.go @@ -3,6 +3,10 @@ package boltmail import ( "bytes" "encoding/gob" + "fmt" + "strconv" + + "github.com/boltdb/bolt" ) func toBytes(i interface{}) ([]byte, error) { @@ -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)) +} diff --git a/mailstore/boltmail/mailbox.go b/mailstore/boltmail/mailbox.go index 3c9e244..82954a2 100644 --- a/mailstore/boltmail/mailbox.go +++ b/mailstore/boltmail/mailbox.go @@ -2,21 +2,24 @@ package boltmail import ( "fmt" - "github.com/alienscience/imapsrv" - "github.com/boltdb/bolt" "strconv" "strings" + + "bytes" + + "github.com/alienscience/imapsrv" + "github.com/boltdb/bolt" ) type boltMailbox struct { - store *BoltMailstore - owner string - - path []string - flags uint8 - currentUID int32 + uid int32 + uidSet bool + store *BoltMailstore + owner string - children []*boltMailbox + path []string + flags uint8 + flagsSet bool /* // Get the path of the mailbox @@ -45,6 +48,10 @@ var ( firstUnseen_bucket = []byte("firstUnseen") total_bucket = []byte("total") recent_bucket = []byte("recent") + uid_bucket = []byte("uid") + + uid_key = []byte("uid") + counter_key = []byte("increment-counter") ) func (b *boltMailbox) Path() []string { @@ -52,17 +59,90 @@ func (b *boltMailbox) Path() []string { } func (b *boltMailbox) Flags() (uint8, error) { + if !b.flagsSet { + // TODO: fetch flags + } return b.flags, nil } func (b *boltMailbox) UidValidity() (int32, error) { - return -1, fmt.Errorf("UidValidity() not implemented") + if !b.uidSet { + // TODO: fetch uid + err := b.store.connection.View(func(tx *bolt.Tx) error { + owner := tx.Bucket(usersBucket).Bucket([]byte(b.owner)) + if owner == nil { + return fmt.Errorf("user not found: %s", owner) + } + + // Get the UID bucket + var err error + uidBuck := owner.Bucket(uid_bucket) + if uidBuck == nil { + if tx.Writable() { + uidBuck, err = owner.CreateBucket(uid_bucket) + if err != nil { + return err + } + uidBuck.Put([]byte(strings.Join(b.path, "/")), []byte(strconv.Itoa(0))) + uidBuck.Put(counter_key, []byte(strconv.Itoa(1))) + } + b.uid = 0 + return nil + } + // It existed, so get the value + val := uidBuck.Get([]byte(strings.Join(b.path, "/"))) + if len(val) == 0 { + // Did not have an entry? Set it if we can + if tx.Writable() { + prevValue, err := getInt(counter_key, uidBuck) + if err != nil { + return err + } + uidBuck.Put([]byte(strings.Join(b.path, "/")), []byte(strconv.Itoa(prevValue))) + uidBuck.Put(counter_key, []byte(strconv.Itoa(prevValue+1))) + } + b.uid = 0 + return nil + } + // Parse it + uid_int, err := strconv.Atoi(string(val)) + if err != nil { + return err + } + b.uid = int32(uid_int) + + return nil + }) + if err != nil { + return -1, err + } + } + return b.uid, nil +} + +func (b *boltMailbox) NextUid() (uid int32, err error) { + err = b.store.connection.Update(func(tx *bolt.Tx) error { + mailbox, e := b.getMailboxBucket(tx) + if e != nil { + return e + } + uid, e = b.nextUidTransaction(mailbox) + return e + }) + return } -func (b *boltMailbox) NextUid() (int32, error) { - // TODO: make it safe for concurrent access - return b.currentUID + 1, nil - // TODO: should we update the value here, or at the Mailstore.NewMessage? +// nextUidTransaction increments the uid by one, in a transaction in which `mailbox` is valid +func (b *boltMailbox) nextUidTransaction(mailbox *bolt.Bucket) (uid int32, err error) { + val := mailbox.Get(uid_key) + uid_int, e := strconv.Atoi(string(val)) + if e != nil { + uid_int = 0 + } + uid_int++ + uid = int32(uid_int) + err = mailbox.Put(uid_key, []byte(strconv.Itoa(uid_int))) + return } func (b *boltMailbox) AllUids() (uids []int32, err error) { @@ -100,6 +180,11 @@ func (b *boltMailbox) FirstUnseen() (uid int32, err error) { } uid_b := mailboxBucket.Get(firstUnseen_bucket) + if len(uid_b) == 0 { + uid = 0 + return nil + } + // TODO: what to return if no messages are present? (empty) uid_64, err := strconv.ParseInt(string(uid_b), 10, 32) if err != nil { return err @@ -119,6 +204,11 @@ func (b *boltMailbox) TotalMessages() (total int32, err error) { } total_b := mailboxBucket.Get(total_bucket) + if len(total_b) == 0 { + total = 0 + return nil + } + totalRecent, err := strconv.ParseInt(string(total_b), 10, 32) if err != nil { return err @@ -138,6 +228,11 @@ func (b *boltMailbox) RecentMessages() (total int32, err error) { } total_b := mailboxBucket.Get(recent_bucket) + if len(total_b) == 0 { + total = 0 + return nil + } + totalRecent, err := strconv.ParseInt(string(total_b), 10, 32) if err != nil { return err @@ -169,7 +264,7 @@ func (b *boltMailbox) Fetch(uid int32) (imapsrv.Message, error) { return nil, fmt.Errorf("mail %d not found", uid) } - err = fromBytes(binary, msg) + err = msg.GobDecode(binary) if err != nil { return nil, err } @@ -177,16 +272,52 @@ func (b *boltMailbox) Fetch(uid int32) (imapsrv.Message, error) { return msg, nil } +func (b *boltMailbox) storeTransaction(msg *basicMessage, tx *bolt.Tx) error { + mailbox, err := b.getMailboxBucket(tx) + if err != nil { + return err + } + + uid, err := b.nextUidTransaction(mailbox) + if err != nil { + return err + } + + mail, err := mailbox.CreateBucketIfNotExists(mail_bucket) + if err != nil { + return fmt.Errorf("Could not create /INBOX mail bucket: %s", err) + } + + val, err := msg.GobEncode() + if err != nil { + return err + } + + return mail.Put([]byte(strconv.Itoa(int(uid))), val) + +} + func (b *boltMailbox) getMailboxBucket(tx *bolt.Tx) (*bolt.Bucket, error) { - bucket := tx.Bucket([]byte(b.owner)) + users := tx.Bucket(usersBucket) + bucket := users.Bucket([]byte(b.owner)) if bucket == nil { return nil, fmt.Errorf("user bucket not found: %s", b.owner) } + var mailboxBucket *bolt.Bucket + var err error + pathString := strings.Join(b.path, "/") - mailboxBucket := bucket.Bucket([]byte(pathString)) - if mailboxBucket == nil { - return nil, fmt.Errorf("mailbox bucket not found: %s", pathString) + if tx.Writable() { + mailboxBucket, err = bucket.CreateBucketIfNotExists([]byte(pathString)) + if err != nil { + return nil, fmt.Errorf("mailbox bucket not found and could not be created: %s", err) + } + } else { + mailboxBucket = bucket.Bucket([]byte(pathString)) + if mailboxBucket == nil { + return nil, fmt.Errorf("mailbox not found: %s", pathString) + } } return mailboxBucket, nil @@ -205,3 +336,41 @@ func (b *boltMailbox) getMailsBucket(tx *bolt.Tx) (*bolt.Bucket, error) { return buck, nil } + +func (b *boltMailbox) getChildren() (boxes []imapsrv.Mailbox, err error) { + err = b.store.connection.View(func(tx *bolt.Tx) error { + users := tx.Bucket(usersBucket) // TODO: specific owner! + owner := users.Bucket([]byte(b.owner)) + if owner == nil { + return fmt.Errorf("user does not exist: %s", b.owner) + } + c := owner.Cursor() + if c == nil { + return fmt.Errorf("could not create cursor") + } + + prefix := []byte(strings.Join(b.path, "/")) + for k, _ := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = c.Next() { + boxes = append(boxes, &boltMailbox{ + owner: b.owner, + path: strings.Split(string(k), "/"), + }) + } + + return nil + }) + return +} + +func (b *boltMailbox) Exists() (exists bool, err error) { + err = b.store.connection.View(func(tx *bolt.Tx) error { + users := tx.Bucket(usersBucket) + owner := users.Bucket([]byte(b.owner)) + if owner == nil { + return fmt.Errorf("user not found: %s", owner) // TODO: potential injection threath? + } + exists = owner.Bucket([]byte(strings.Join(b.path, "/"))) != nil + return nil + }) + return +} diff --git a/mailstore/boltmail/mailstore.go b/mailstore/boltmail/mailstore.go index 176a4ff..f32e456 100644 --- a/mailstore/boltmail/mailstore.go +++ b/mailstore/boltmail/mailstore.go @@ -1,12 +1,15 @@ package boltmail import ( - "github.com/alienscience/imapsrv" - "github.com/boltdb/bolt" "io" "io/ioutil" "os" "time" + + "fmt" + + "github.com/alienscience/imapsrv" + "github.com/boltdb/bolt" ) type BoltMailstore struct { @@ -50,15 +53,32 @@ func NewBoltMailstore(filename string) (*BoltMailstore, error) { return store, nil } -func (b *BoltMailstore) Mailbox(path []string) (imapsrv.Mailbox, error) { - return nil, nil // TODO: Implement +func (b *BoltMailstore) Mailbox(owner string, path []string) (box imapsrv.Mailbox, err error) { + err = b.connection.View(func(tx *bolt.Tx) error { + boltBox := &boltMailbox{ + owner: owner, + path: path, + store: b, + } + if e, _ := boltBox.Exists(); !e { + return fmt.Errorf("mailbox not found: %v", path) // TODO: injection danger? / security + } + box = boltBox + return nil + }) + return } -func (b *BoltMailstore) Mailboxes(path []string) ([]imapsrv.Mailbox, error) { - return nil, nil // TODO: Implement +func (b *BoltMailstore) Mailboxes(owner string, path []string) (boxes []imapsrv.Mailbox, err error) { + box := &boltMailbox{ + owner: owner, + path: path, + store: b, + } + return box.getChildren() } -func (b *BoltMailstore) NewMessage(input io.Reader) (imapsrv.Message, error) { +func (b *BoltMailstore) NewMessage(rcpt string, input io.Reader) (imapsrv.Message, error) { msg := &basicMessage{} var err error @@ -70,5 +90,43 @@ func (b *BoltMailstore) NewMessage(input io.Reader) (imapsrv.Message, error) { msg.internalDate = time.Now() msg.size = uint32(len(msg.body)) + err = b.connection.Update(func(tx *bolt.Tx) error { + box := &boltMailbox{ + owner: rcpt, + path: []string{"INBOX"}, + store: b, + } + return box.storeTransaction(msg, tx) + }) + if err != nil { + return nil, err + } + return msg, nil } + +func (b *BoltMailstore) NewUser(email string) error { + err := b.connection.Update(func(tx *bolt.Tx) error { + buck := tx.Bucket(usersBucket) + _, err := buck.CreateBucketIfNotExists([]byte(email)) + return err + }) + return err +} + +func (b *BoltMailstore) Addresses() ([]string, error) { + var messages []string + + err := b.connection.View(func(tx *bolt.Tx) error { + buck := tx.Bucket(usersBucket) + return buck.ForEach(func(k, _ []byte) error { + messages = append(messages, string(k)) + return nil + }) + }) + if err != nil { + return nil, err + } + + return messages, nil +} diff --git a/mailstore/boltmail/message.go b/mailstore/boltmail/message.go index 0d5acd5..8c1438e 100644 --- a/mailstore/boltmail/message.go +++ b/mailstore/boltmail/message.go @@ -4,8 +4,10 @@ import ( "bytes" "encoding/gob" "fmt" - "github.com/alienscience/imapsrv" "time" + + "github.com/alienscience/imapsrv" + "github.com/tdewolff/buffer" ) type basicMessage struct { @@ -43,19 +45,19 @@ func (b *basicMessage) GobDecode(byt []byte) error { r := bytes.NewBuffer(byt) dec := gob.NewDecoder(r) - err := dec.Decode(b.flags) + err := dec.Decode(&b.flags) if err != nil { return err } - err = dec.Decode(b.internalDate) + err = dec.Decode(&b.internalDate) if err != nil { return err } - err = dec.Decode(b.size) + err = dec.Decode(&b.size) if err != nil { return err } - err = dec.Decode(b.body) + err = dec.Decode(&b.body) if err != nil { return err } @@ -84,39 +86,6 @@ func (b *basicMessage) Size() (uint32, error) { } func (b *basicMessage) Reader() (imapsrv.MessageReader, error) { - reader := newBinaryMessageReader(b.body) + reader := buffer.NewReader(b.body) return reader, nil } - -type binaryMessageReader struct { - data []byte - offset int64 -} - -func newBinaryMessageReader(data []byte) *binaryMessageReader { - return &binaryMessageReader{data, 0} -} - -func (b *binaryMessageReader) Close() error { - return nil -} -func (b *binaryMessageReader) Seek(offset int64, whence int) (int64, error) { - // 0 means relative to the origin of - // the file, 1 means relative to the current offset, and 2 means - // relative to the end. - switch whence { - case 0: - b.offset = offset - case 1: - b.offset += offset - case 2: - b.offset = int64(len(b.data)) - offset - default: - return 0, fmt.Errorf("bad whence value: %d - expected 0, 1 or 2", whence) - } - return b.offset, nil -} -func (b *binaryMessageReader) Read(p []byte) (n int, err error) { - p = b.data[b.offset:] - return len(p), nil -} diff --git a/session.go b/session.go index f108559..b812f1d 100644 --- a/session.go +++ b/session.go @@ -44,6 +44,8 @@ type session struct { encryption encryptionLevel // mailbox is the currently selected mailbox (if st == selected) mailbox *mailboxWrap + // user is the currently authenticated user + user string } // Create a new IMAP session @@ -70,7 +72,7 @@ func (s *session) log(info ...interface{}) { func (s *session) selectMailbox(path []string) (bool, error) { // Lookup the mailbox mailstore := s.config.Mailstore - mbox, err := getMailbox(mailstore, path) + mbox, err := getMailbox(mailstore, s.user, path) if err != nil { return false, err @@ -103,7 +105,7 @@ func (s *session) list(reference []string, pattern []string) ([]*mailboxWrap, er // Just return a single mailbox if there are no wildcards if wildcard == -1 { - mbox, err := getMailbox(s.config.Mailstore, path) + mbox, err := getMailbox(s.config.Mailstore, s.user, path) if err != nil { return ret, err } @@ -203,7 +205,7 @@ func (s *session) depthFirstMailboxes( switch pat { case "%": // Get all the mailboxes at the current path - all, err := getMailboxes(mailstore, path) + all, err := getMailboxes(mailstore, s.user, path) if err == nil { for _, mbox := range all { // Consider the next pattern @@ -218,7 +220,7 @@ func (s *session) depthFirstMailboxes( case "*": // Get all the mailboxes at the current path - all, err := getMailboxes(mailstore, path) + all, err := getMailboxes(mailstore, s.user, path) if err == nil { for _, mbox := range all { // Keep using this pattern @@ -233,7 +235,7 @@ func (s *session) depthFirstMailboxes( default: // Not a wildcard pattern - mbox, err := getMailbox(mailstore, path) + mbox, err := getMailbox(mailstore, s.user, path) if err == nil { ret = append(results, mbox) ret, err = s.depthFirstMailboxes( diff --git a/wrappers.go b/wrappers.go index 4d75195..40293bb 100644 --- a/wrappers.go +++ b/wrappers.go @@ -3,8 +3,9 @@ package imapsrv import ( "bufio" "bytes" - "github.com/jhillyerd/go.enmime" "net/mail" + + "github.com/jhillyerd/go.enmime" ) // A wrapper around a Mailbox that provides helper functions @@ -29,8 +30,8 @@ type messageWrap struct { const dateFormat = "02-Jan-2006 15:04:05 -0700" // Get a mailbox from a mailstore -func getMailbox(store Mailstore, path []string) (*mailboxWrap, error) { - mbox, err := store.Mailbox(path) +func getMailbox(store Mailstore, owner string, path []string) (*mailboxWrap, error) { + mbox, err := store.Mailbox(owner, path) if err != nil { return nil, err } @@ -39,8 +40,8 @@ func getMailbox(store Mailstore, path []string) (*mailboxWrap, error) { } // Get mailboxes from a mailstore -func getMailboxes(store Mailstore, path []string) ([]*mailboxWrap, error) { - mboxes, err := store.Mailboxes(path) +func getMailboxes(store Mailstore, owner string, path []string) ([]*mailboxWrap, error) { + mboxes, err := store.Mailboxes(owner, path) if err != nil { return nil, err } @@ -109,7 +110,6 @@ func (m *messageWrap) getMessage() (*mail.Message, error) { if err != nil { return nil, err } - defer reader.Close() m.message, err = mail.ReadMessage(reader) if err != nil { @@ -146,7 +146,6 @@ func (m *messageWrap) rfc822Header() (string, error) { if err != nil { return "", err } - defer reader.Close() // Read the message line by line buf := new(bytes.Buffer)