Initial commit
This commit is contained in:
52
vendor/github.com/emersion/go-imap/server/cmd_any.go
generated
vendored
Normal file
52
vendor/github.com/emersion/go-imap/server/cmd_any.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
type Capability struct {
|
||||
commands.Capability
|
||||
}
|
||||
|
||||
func (cmd *Capability) Handle(conn Conn) error {
|
||||
res := &responses.Capability{Caps: conn.Capabilities()}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
type Noop struct {
|
||||
commands.Noop
|
||||
}
|
||||
|
||||
func (cmd *Noop) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox != nil {
|
||||
// If a mailbox is selected, NOOP can be used to poll for server updates
|
||||
if mbox, ok := ctx.Mailbox.(backend.UpdaterMailbox); ok {
|
||||
return mbox.Poll()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Logout struct {
|
||||
commands.Logout
|
||||
}
|
||||
|
||||
func (cmd *Logout) Handle(conn Conn) error {
|
||||
res := &imap.StatusResp{
|
||||
Type: imap.StatusBye,
|
||||
Info: "Closing connection",
|
||||
}
|
||||
|
||||
if err := conn.WriteResp(res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Request to close the connection
|
||||
conn.Context().State = imap.LogoutState
|
||||
return nil
|
||||
}
|
261
vendor/github.com/emersion/go-imap/server/cmd_auth.go
generated
vendored
Normal file
261
vendor/github.com/emersion/go-imap/server/cmd_auth.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// imap errors in Authenticated state.
|
||||
var (
|
||||
ErrNotAuthenticated = errors.New("Not authenticated")
|
||||
)
|
||||
|
||||
type Select struct {
|
||||
commands.Select
|
||||
}
|
||||
|
||||
func (cmd *Select) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
items := []string{
|
||||
imap.MailboxFlags, imap.MailboxPermanentFlags,
|
||||
imap.MailboxMessages, imap.MailboxRecent, imap.MailboxUnseen,
|
||||
imap.MailboxUidNext, imap.MailboxUidValidity,
|
||||
}
|
||||
|
||||
status, err := mbox.Status(items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Mailbox = mbox
|
||||
ctx.MailboxReadOnly = cmd.ReadOnly || status.ReadOnly
|
||||
|
||||
res := &responses.Select{Mailbox: status}
|
||||
if err := conn.WriteResp(res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code := imap.CodeReadWrite
|
||||
if ctx.MailboxReadOnly {
|
||||
code = imap.CodeReadOnly
|
||||
}
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: code,
|
||||
})
|
||||
}
|
||||
|
||||
type Create struct {
|
||||
commands.Create
|
||||
}
|
||||
|
||||
func (cmd *Create) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
return ctx.User.CreateMailbox(cmd.Mailbox)
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
commands.Delete
|
||||
}
|
||||
|
||||
func (cmd *Delete) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
return ctx.User.DeleteMailbox(cmd.Mailbox)
|
||||
}
|
||||
|
||||
type Rename struct {
|
||||
commands.Rename
|
||||
}
|
||||
|
||||
func (cmd *Rename) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
return ctx.User.RenameMailbox(cmd.Existing, cmd.New)
|
||||
}
|
||||
|
||||
type Subscribe struct {
|
||||
commands.Subscribe
|
||||
}
|
||||
|
||||
func (cmd *Subscribe) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mbox.SetSubscribed(true)
|
||||
}
|
||||
|
||||
type Unsubscribe struct {
|
||||
commands.Unsubscribe
|
||||
}
|
||||
|
||||
func (cmd *Unsubscribe) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mbox.SetSubscribed(false)
|
||||
}
|
||||
|
||||
type List struct {
|
||||
commands.List
|
||||
}
|
||||
|
||||
func (cmd *List) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
ch := make(chan *imap.MailboxInfo)
|
||||
res := &responses.List{Mailboxes: ch, Subscribed: cmd.Subscribed}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go (func() {
|
||||
done <- conn.WriteResp(res)
|
||||
close(done)
|
||||
})()
|
||||
|
||||
mailboxes, err := ctx.User.ListMailboxes(cmd.Subscribed)
|
||||
if err != nil {
|
||||
close(ch)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, mbox := range mailboxes {
|
||||
info, err := mbox.Info()
|
||||
if err != nil {
|
||||
close(ch)
|
||||
return err
|
||||
}
|
||||
|
||||
// An empty ("" string) mailbox name argument is a special request to return
|
||||
// the hierarchy delimiter and the root name of the name given in the
|
||||
// reference.
|
||||
if cmd.Mailbox == "" {
|
||||
ch <- &imap.MailboxInfo{
|
||||
Attributes: []string{imap.NoSelectAttr},
|
||||
Delimiter: info.Delimiter,
|
||||
Name: info.Delimiter,
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if info.Match(cmd.Reference, cmd.Mailbox) {
|
||||
ch <- info
|
||||
}
|
||||
}
|
||||
|
||||
close(ch)
|
||||
|
||||
return <-done
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
commands.Status
|
||||
}
|
||||
|
||||
func (cmd *Status) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status, err := mbox.Status(cmd.Items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only keep items thqat have been requested
|
||||
items := make(map[string]interface{})
|
||||
for _, k := range cmd.Items {
|
||||
items[k] = status.Items[k]
|
||||
}
|
||||
status.Items = items
|
||||
|
||||
res := &responses.Status{Mailbox: status}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
type Append struct {
|
||||
commands.Append
|
||||
}
|
||||
|
||||
func (cmd *Append) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.User == nil {
|
||||
return ErrNotAuthenticated
|
||||
}
|
||||
|
||||
mbox, err := ctx.User.GetMailbox(cmd.Mailbox)
|
||||
if err == backend.ErrNoSuchMailbox {
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusNo,
|
||||
Code: imap.CodeTryCreate,
|
||||
Info: err.Error(),
|
||||
})
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mbox.CreateMessage(cmd.Flags, cmd.Date, cmd.Message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If APPEND targets the currently selected mailbox, send an untagged EXISTS
|
||||
// Do this only if the backend doesn't send updates itself
|
||||
if conn.Server().Updates == nil && ctx.Mailbox != nil && ctx.Mailbox.Name() == mbox.Name() {
|
||||
status, err := mbox.Status([]string{imap.MailboxMessages})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := &responses.Select{Mailbox: status}
|
||||
if err := conn.WriteResp(res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
127
vendor/github.com/emersion/go-imap/server/cmd_noauth.go
generated
vendored
Normal file
127
vendor/github.com/emersion/go-imap/server/cmd_noauth.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// IMAP errors in Not Authenticated state.
|
||||
var (
|
||||
ErrAlreadyAuthenticated = errors.New("Already authenticated")
|
||||
ErrAuthDisabled = errors.New("Authentication disabled")
|
||||
)
|
||||
|
||||
type StartTLS struct {
|
||||
commands.StartTLS
|
||||
}
|
||||
|
||||
func (cmd *StartTLS) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.State != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyAuthenticated
|
||||
}
|
||||
if conn.IsTLS() {
|
||||
return errors.New("TLS is already enabled")
|
||||
}
|
||||
if conn.Server().TLSConfig == nil {
|
||||
return errors.New("TLS support not enabled")
|
||||
}
|
||||
|
||||
// Send an OK status response to let the client know that the TLS handshake
|
||||
// can begin
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Info: "Begin TLS negotiation now",
|
||||
})
|
||||
}
|
||||
|
||||
func (cmd *StartTLS) Upgrade(conn Conn) error {
|
||||
tlsConfig := conn.Server().TLSConfig
|
||||
|
||||
var tlsConn *tls.Conn
|
||||
err := conn.Upgrade(func(conn net.Conn) (net.Conn, error) {
|
||||
tlsConn = tls.Server(conn, tlsConfig)
|
||||
err := tlsConn.Handshake()
|
||||
return tlsConn, err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn.setTLSConn(tlsConn)
|
||||
|
||||
res := &responses.Capability{Caps: conn.Capabilities()}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
func afterAuthStatus(conn Conn) error {
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodeCapability,
|
||||
Arguments: imap.FormatStringList(conn.Capabilities()),
|
||||
})
|
||||
}
|
||||
|
||||
func canAuth(conn Conn) bool {
|
||||
for _, cap := range conn.Capabilities() {
|
||||
if cap == "AUTH=PLAIN" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
commands.Login
|
||||
}
|
||||
|
||||
func (cmd *Login) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.State != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyAuthenticated
|
||||
}
|
||||
if !canAuth(conn) {
|
||||
return ErrAuthDisabled
|
||||
}
|
||||
|
||||
user, err := conn.Server().Backend.Login(cmd.Username, cmd.Password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.State = imap.AuthenticatedState
|
||||
ctx.User = user
|
||||
return afterAuthStatus(conn)
|
||||
}
|
||||
|
||||
type Authenticate struct {
|
||||
commands.Authenticate
|
||||
}
|
||||
|
||||
func (cmd *Authenticate) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.State != imap.NotAuthenticatedState {
|
||||
return ErrAlreadyAuthenticated
|
||||
}
|
||||
if !canAuth(conn) {
|
||||
return ErrAuthDisabled
|
||||
}
|
||||
|
||||
mechanisms := map[string]sasl.Server{}
|
||||
for name, newSasl := range conn.Server().auths {
|
||||
mechanisms[name] = newSasl(conn)
|
||||
}
|
||||
|
||||
err := cmd.Authenticate.Handle(mechanisms, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return afterAuthStatus(conn)
|
||||
}
|
313
vendor/github.com/emersion/go-imap/server/cmd_selected.go
generated
vendored
Normal file
313
vendor/github.com/emersion/go-imap/server/cmd_selected.go
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/commands"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
)
|
||||
|
||||
// imap errors in Selected state.
|
||||
var (
|
||||
ErrNoMailboxSelected = errors.New("No mailbox selected")
|
||||
ErrMailboxReadOnly = errors.New("Mailbox opened in read-only mode")
|
||||
)
|
||||
|
||||
// A command handler that supports UIDs.
|
||||
type UidHandler interface {
|
||||
Handler
|
||||
|
||||
// Handle this command using UIDs for a given connection.
|
||||
UidHandle(conn Conn) error
|
||||
}
|
||||
|
||||
type Check struct {
|
||||
commands.Check
|
||||
}
|
||||
|
||||
func (cmd *Check) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
if ctx.MailboxReadOnly {
|
||||
return ErrMailboxReadOnly
|
||||
}
|
||||
|
||||
return ctx.Mailbox.Check()
|
||||
}
|
||||
|
||||
type Close struct {
|
||||
commands.Close
|
||||
}
|
||||
|
||||
func (cmd *Close) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
mailbox := ctx.Mailbox
|
||||
ctx.Mailbox = nil
|
||||
ctx.MailboxReadOnly = false
|
||||
|
||||
if err := mailbox.Expunge(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// No need to send expunge updates here, since the mailbox is already unselected
|
||||
return nil
|
||||
}
|
||||
|
||||
type Expunge struct {
|
||||
commands.Expunge
|
||||
}
|
||||
|
||||
func (cmd *Expunge) Handle(conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
if ctx.MailboxReadOnly {
|
||||
return ErrMailboxReadOnly
|
||||
}
|
||||
|
||||
// Get a list of messages that will be deleted
|
||||
// That will allow us to send expunge updates if the backend doesn't support it
|
||||
var seqnums []uint32
|
||||
if conn.Server().Updates == nil {
|
||||
criteria := &imap.SearchCriteria{
|
||||
WithFlags: []string{imap.DeletedFlag},
|
||||
}
|
||||
|
||||
var err error
|
||||
seqnums, err = ctx.Mailbox.SearchMessages(false, criteria)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.Mailbox.Expunge(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the backend doesn't support expunge updates, let's do it ourselves
|
||||
if conn.Server().Updates == nil {
|
||||
done := make(chan error)
|
||||
defer close(done)
|
||||
|
||||
ch := make(chan uint32)
|
||||
res := &responses.Expunge{SeqNums: ch}
|
||||
|
||||
go (func() {
|
||||
done <- conn.WriteResp(res)
|
||||
})()
|
||||
|
||||
// Iterate sequence numbers from the last one to the first one, as deleting
|
||||
// messages changes their respective numbers
|
||||
for i := len(seqnums) - 1; i >= 0; i-- {
|
||||
ch <- seqnums[i]
|
||||
}
|
||||
close(ch)
|
||||
|
||||
if err := <-done; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Search struct {
|
||||
commands.Search
|
||||
}
|
||||
|
||||
func (cmd *Search) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
ids, err := ctx.Mailbox.SearchMessages(uid, cmd.Criteria)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res := &responses.Search{Ids: ids}
|
||||
return conn.WriteResp(res)
|
||||
}
|
||||
|
||||
func (cmd *Search) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Search) UidHandle(conn Conn) error {
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Fetch struct {
|
||||
commands.Fetch
|
||||
}
|
||||
|
||||
func (cmd *Fetch) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
ch := make(chan *imap.Message)
|
||||
res := &responses.Fetch{Messages: ch}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go (func() {
|
||||
done <- conn.WriteResp(res)
|
||||
})()
|
||||
|
||||
err := ctx.Mailbox.ListMessages(uid, cmd.SeqSet, cmd.Items, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return <-done
|
||||
}
|
||||
|
||||
func (cmd *Fetch) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Fetch) UidHandle(conn Conn) error {
|
||||
// Append UID to the list of requested items if it isn't already present
|
||||
hasUid := false
|
||||
for _, item := range cmd.Items {
|
||||
if item == "UID" {
|
||||
hasUid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasUid {
|
||||
cmd.Items = append(cmd.Items, "UID")
|
||||
}
|
||||
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Store struct {
|
||||
commands.Store
|
||||
}
|
||||
|
||||
func (cmd *Store) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
if ctx.MailboxReadOnly {
|
||||
return ErrMailboxReadOnly
|
||||
}
|
||||
|
||||
itemStr := cmd.Item
|
||||
silent := strings.HasSuffix(itemStr, imap.SilentOp)
|
||||
if silent {
|
||||
itemStr = strings.TrimSuffix(itemStr, imap.SilentOp)
|
||||
}
|
||||
item := imap.FlagsOp(itemStr)
|
||||
|
||||
if item != imap.SetFlags && item != imap.AddFlags && item != imap.RemoveFlags {
|
||||
return errors.New("Unsupported STORE operation")
|
||||
}
|
||||
|
||||
flagsList, ok := cmd.Value.([]interface{})
|
||||
if !ok {
|
||||
return errors.New("Flags must be a list")
|
||||
}
|
||||
flags, err := imap.ParseStringList(flagsList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, flag := range flags {
|
||||
flags[i] = imap.CanonicalFlag(flag)
|
||||
}
|
||||
|
||||
// If the backend supports message updates, this will prevent this connection
|
||||
// from receiving them
|
||||
// TODO: find a better way to do this, without conn.silent
|
||||
*conn.silent() = silent
|
||||
err = ctx.Mailbox.UpdateMessagesFlags(uid, cmd.SeqSet, item, flags)
|
||||
*conn.silent() = false
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Not silent: send FETCH updates if the backend doesn't support message
|
||||
// updates
|
||||
if conn.Server().Updates == nil && !silent {
|
||||
inner := &Fetch{}
|
||||
inner.SeqSet = cmd.SeqSet
|
||||
inner.Items = []string{"FLAGS"}
|
||||
if uid {
|
||||
inner.Items = append(inner.Items, "UID")
|
||||
}
|
||||
|
||||
if err := inner.handle(uid, conn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Store) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Store) UidHandle(conn Conn) error {
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Copy struct {
|
||||
commands.Copy
|
||||
}
|
||||
|
||||
func (cmd *Copy) handle(uid bool, conn Conn) error {
|
||||
ctx := conn.Context()
|
||||
if ctx.Mailbox == nil {
|
||||
return ErrNoMailboxSelected
|
||||
}
|
||||
|
||||
return ctx.Mailbox.CopyMessages(uid, cmd.SeqSet, cmd.Mailbox)
|
||||
}
|
||||
|
||||
func (cmd *Copy) Handle(conn Conn) error {
|
||||
return cmd.handle(false, conn)
|
||||
}
|
||||
|
||||
func (cmd *Copy) UidHandle(conn Conn) error {
|
||||
return cmd.handle(true, conn)
|
||||
}
|
||||
|
||||
type Uid struct {
|
||||
commands.Uid
|
||||
}
|
||||
|
||||
func (cmd *Uid) Handle(conn Conn) error {
|
||||
inner := cmd.Cmd.Command()
|
||||
hdlr, err := conn.commandHandler(inner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uidHdlr, ok := hdlr.(UidHandler)
|
||||
if !ok {
|
||||
return errors.New("Command unsupported with UID")
|
||||
}
|
||||
|
||||
if err := uidHdlr.UidHandle(conn); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrStatusResp(&imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Info: imap.Uid + " " + inner.Name + " completed",
|
||||
})
|
||||
}
|
383
vendor/github.com/emersion/go-imap/server/conn.go
generated
vendored
Normal file
383
vendor/github.com/emersion/go-imap/server/conn.go
generated
vendored
Normal file
@@ -0,0 +1,383 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
)
|
||||
|
||||
// Conn is a connection to a client.
|
||||
type Conn interface {
|
||||
io.Reader
|
||||
|
||||
// Server returns this connection's server.
|
||||
Server() *Server
|
||||
// Context returns this connection's context.
|
||||
Context() *Context
|
||||
// Capabilities returns a list of capabilities enabled for this connection.
|
||||
Capabilities() []string
|
||||
// WriteResp writes a response to this connection.
|
||||
WriteResp(res imap.WriterTo) error
|
||||
// IsTLS returns true if TLS is enabled.
|
||||
IsTLS() bool
|
||||
// TLSState returns the TLS connection state if TLS is enabled, nil otherwise.
|
||||
TLSState() *tls.ConnectionState
|
||||
// Upgrade upgrades a connection, e.g. wrap an unencrypted connection with an
|
||||
// encrypted tunnel.
|
||||
Upgrade(upgrader imap.ConnUpgrader) error
|
||||
// Close closes this connection.
|
||||
Close() error
|
||||
|
||||
setTLSConn(*tls.Conn)
|
||||
silent() *bool // TODO: remove this
|
||||
serve() error
|
||||
commandHandler(cmd *imap.Command) (hdlr Handler, err error)
|
||||
}
|
||||
|
||||
// Context stores a connection's metadata.
|
||||
type Context struct {
|
||||
// This connection's current state.
|
||||
State imap.ConnState
|
||||
// If the client is logged in, the user.
|
||||
User backend.User
|
||||
// If the client has selected a mailbox, the mailbox.
|
||||
Mailbox backend.Mailbox
|
||||
// True if the currently selected mailbox has been opened in read-only mode.
|
||||
MailboxReadOnly bool
|
||||
// Responses to send to the client.
|
||||
Responses chan<- imap.WriterTo
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
*imap.Conn
|
||||
|
||||
s *Server
|
||||
ctx *Context
|
||||
l sync.Locker
|
||||
tlsConn *tls.Conn
|
||||
continues chan bool
|
||||
responses chan imap.WriterTo
|
||||
silentVal bool
|
||||
}
|
||||
|
||||
func newConn(s *Server, c net.Conn) *conn {
|
||||
// Create an imap.Reader and an imap.Writer
|
||||
continues := make(chan bool)
|
||||
r := imap.NewServerReader(nil, continues)
|
||||
w := imap.NewWriter(nil)
|
||||
|
||||
responses := make(chan imap.WriterTo)
|
||||
|
||||
tlsConn, _ := c.(*tls.Conn)
|
||||
|
||||
conn := &conn{
|
||||
Conn: imap.NewConn(c, r, w),
|
||||
|
||||
s: s,
|
||||
l: &sync.Mutex{},
|
||||
ctx: &Context{
|
||||
State: imap.ConnectingState,
|
||||
Responses: responses,
|
||||
},
|
||||
tlsConn: tlsConn,
|
||||
continues: continues,
|
||||
responses: responses,
|
||||
}
|
||||
|
||||
if s.Debug != nil {
|
||||
conn.Conn.SetDebug(s.Debug)
|
||||
}
|
||||
if s.MaxLiteralSize > 0 {
|
||||
conn.Conn.MaxLiteralSize = s.MaxLiteralSize
|
||||
}
|
||||
|
||||
conn.l.Lock()
|
||||
go conn.send()
|
||||
|
||||
return conn
|
||||
}
|
||||
|
||||
func (c *conn) Server() *Server {
|
||||
return c.s
|
||||
}
|
||||
|
||||
func (c *conn) Context() *Context {
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
type response struct {
|
||||
response imap.WriterTo
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (r *response) WriteTo(w *imap.Writer) error {
|
||||
err := r.response.WriteTo(w)
|
||||
close(r.done)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) setDeadline() {
|
||||
if c.s.AutoLogout == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
dur := c.s.AutoLogout
|
||||
if dur < MinAutoLogout {
|
||||
dur = MinAutoLogout
|
||||
}
|
||||
t := time.Now().Add(dur)
|
||||
|
||||
c.Conn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *conn) WriteResp(r imap.WriterTo) error {
|
||||
done := make(chan struct{})
|
||||
c.responses <- &response{r, done}
|
||||
<-done
|
||||
c.setDeadline()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
if c.ctx.User != nil {
|
||||
c.ctx.User.Logout()
|
||||
}
|
||||
|
||||
if err := c.Conn.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
close(c.continues)
|
||||
|
||||
c.ctx.State = imap.LogoutState
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Capabilities() []string {
|
||||
caps := []string{"IMAP4rev1"}
|
||||
|
||||
if c.ctx.State == imap.NotAuthenticatedState {
|
||||
if !c.IsTLS() && c.s.TLSConfig != nil {
|
||||
caps = append(caps, "STARTTLS")
|
||||
}
|
||||
|
||||
if !c.canAuth() {
|
||||
caps = append(caps, "LOGINDISABLED")
|
||||
} else {
|
||||
for name := range c.s.auths {
|
||||
caps = append(caps, "AUTH="+name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ext := range c.s.extensions {
|
||||
caps = append(caps, ext.Capabilities(c)...)
|
||||
}
|
||||
|
||||
return caps
|
||||
}
|
||||
|
||||
func (c *conn) send() {
|
||||
// Send continuation requests
|
||||
go func() {
|
||||
for range c.continues {
|
||||
res := &imap.ContinuationResp{Info: "send literal"}
|
||||
if err := res.WriteTo(c.Writer); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot send continuation request: ", err)
|
||||
} else if err := c.Writer.Flush(); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot flush connection: ", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Send responses
|
||||
for {
|
||||
// Get a response that needs to be sent
|
||||
res := <-c.responses
|
||||
|
||||
// Request to send the response
|
||||
c.l.Lock()
|
||||
|
||||
// Send the response
|
||||
if err := res.WriteTo(c.Writer); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot send response: ", err)
|
||||
} else if err := c.Writer.Flush(); err != nil {
|
||||
c.Server().ErrorLog.Println("cannot flush connection: ", err)
|
||||
}
|
||||
|
||||
c.l.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) greet() error {
|
||||
c.ctx.State = imap.NotAuthenticatedState
|
||||
|
||||
caps := c.Capabilities()
|
||||
args := make([]interface{}, len(caps))
|
||||
for i, cap := range caps {
|
||||
args[i] = cap
|
||||
}
|
||||
|
||||
greeting := &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
Code: imap.CodeCapability,
|
||||
Arguments: args,
|
||||
Info: "IMAP4rev1 Service Ready",
|
||||
}
|
||||
|
||||
c.l.Unlock()
|
||||
defer c.l.Lock()
|
||||
|
||||
return c.WriteResp(greeting)
|
||||
}
|
||||
|
||||
func (c *conn) setTLSConn(tlsConn *tls.Conn) {
|
||||
c.tlsConn = tlsConn
|
||||
}
|
||||
|
||||
func (c *conn) IsTLS() bool {
|
||||
return c.tlsConn != nil
|
||||
}
|
||||
|
||||
func (c *conn) TLSState() *tls.ConnectionState {
|
||||
if c.tlsConn != nil {
|
||||
state := c.tlsConn.ConnectionState()
|
||||
return &state
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// canAuth checks if the client can use plain text authentication.
|
||||
func (c *conn) canAuth() bool {
|
||||
return c.IsTLS() || c.s.AllowInsecureAuth
|
||||
}
|
||||
|
||||
func (c *conn) silent() *bool {
|
||||
return &c.silentVal
|
||||
}
|
||||
|
||||
func (c *conn) serve() error {
|
||||
// Send greeting
|
||||
if err := c.greet(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
if c.ctx.State == imap.LogoutState {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res *imap.StatusResp
|
||||
var up Upgrader
|
||||
|
||||
c.Wait()
|
||||
fields, err := c.ReadLine()
|
||||
if err == io.EOF || c.ctx.State == imap.LogoutState {
|
||||
return nil
|
||||
}
|
||||
c.setDeadline()
|
||||
|
||||
if err != nil {
|
||||
if imap.IsParseError(err) {
|
||||
res = &imap.StatusResp{
|
||||
Type: imap.StatusBad,
|
||||
Info: err.Error(),
|
||||
}
|
||||
} else {
|
||||
c.s.ErrorLog.Println("cannot read command:", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
cmd := &imap.Command{}
|
||||
if err := cmd.Parse(fields); err != nil {
|
||||
res = &imap.StatusResp{
|
||||
Tag: cmd.Tag,
|
||||
Type: imap.StatusBad,
|
||||
Info: err.Error(),
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
res, up, err = c.handleCommand(cmd)
|
||||
if err != nil {
|
||||
res = &imap.StatusResp{
|
||||
Tag: cmd.Tag,
|
||||
Type: imap.StatusBad,
|
||||
Info: err.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if res != nil {
|
||||
c.l.Unlock()
|
||||
|
||||
if err := c.WriteResp(res); err != nil {
|
||||
c.s.ErrorLog.Println("cannot write response:", err)
|
||||
c.l.Lock()
|
||||
continue
|
||||
}
|
||||
|
||||
if up != nil && res.Type == imap.StatusOk {
|
||||
if err := up.Upgrade(c); err != nil {
|
||||
c.s.ErrorLog.Println("cannot upgrade connection:", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.l.Lock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *conn) commandHandler(cmd *imap.Command) (hdlr Handler, err error) {
|
||||
newHandler := c.s.Command(cmd.Name)
|
||||
if newHandler == nil {
|
||||
err = errors.New("Unknown command")
|
||||
return
|
||||
}
|
||||
|
||||
hdlr = newHandler()
|
||||
err = hdlr.Parse(cmd.Arguments)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *conn) handleCommand(cmd *imap.Command) (res *imap.StatusResp, up Upgrader, err error) {
|
||||
hdlr, err := c.commandHandler(cmd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.l.Unlock()
|
||||
defer c.l.Lock()
|
||||
|
||||
hdlrErr := hdlr.Handle(c)
|
||||
if statusErr, ok := hdlrErr.(*errStatusResp); ok {
|
||||
res = statusErr.resp
|
||||
} else if hdlrErr != nil {
|
||||
res = &imap.StatusResp{
|
||||
Type: imap.StatusNo,
|
||||
Info: hdlrErr.Error(),
|
||||
}
|
||||
} else {
|
||||
res = &imap.StatusResp{
|
||||
Type: imap.StatusOk,
|
||||
}
|
||||
}
|
||||
|
||||
if res != nil {
|
||||
res.Tag = cmd.Tag
|
||||
|
||||
if res.Type == imap.StatusOk && res.Info == "" {
|
||||
res.Info = cmd.Name + " completed"
|
||||
}
|
||||
}
|
||||
|
||||
up, _ = hdlr.(Upgrader)
|
||||
return
|
||||
}
|
426
vendor/github.com/emersion/go-imap/server/server.go
generated
vendored
Normal file
426
vendor/github.com/emersion/go-imap/server/server.go
generated
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
// Package server provides an IMAP server.
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-imap/responses"
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// The minimum autologout duration defined in RFC 3501 section 5.4.
|
||||
const MinAutoLogout = 30 * time.Minute
|
||||
|
||||
// A command handler.
|
||||
type Handler interface {
|
||||
imap.Parser
|
||||
|
||||
// Handle this command for a given connection.
|
||||
//
|
||||
// By default, after this function has returned a status response is sent. To
|
||||
// prevent this behavior handlers can use ErrStatusResp or ErrNoStatusResp.
|
||||
Handle(conn Conn) error
|
||||
}
|
||||
|
||||
// A connection upgrader. If a Handler is also an Upgrader, the connection will
|
||||
// be upgraded after the Handler succeeds.
|
||||
//
|
||||
// This should only be used by libraries implementing an IMAP extension (e.g.
|
||||
// COMPRESS).
|
||||
type Upgrader interface {
|
||||
// Upgrade the connection. This method should call conn.Upgrade().
|
||||
Upgrade(conn Conn) error
|
||||
}
|
||||
|
||||
// A function that creates handlers.
|
||||
type HandlerFactory func() Handler
|
||||
|
||||
// A function that creates SASL servers.
|
||||
type SaslServerFactory func(conn Conn) sasl.Server
|
||||
|
||||
// An IMAP extension.
|
||||
type Extension interface {
|
||||
// Get capabilities provided by this extension for a given connection.
|
||||
Capabilities(c Conn) []string
|
||||
// Get the command handler factory for the provided command name.
|
||||
Command(name string) HandlerFactory
|
||||
}
|
||||
|
||||
// An extension that provides additional features to each connection.
|
||||
type ConnExtension interface {
|
||||
Extension
|
||||
|
||||
// This function will be called when a client connects to the server. It can
|
||||
// be used to add new features to the default Conn interface by implementing
|
||||
// new methods.
|
||||
NewConn(c Conn) Conn
|
||||
}
|
||||
|
||||
type errStatusResp struct {
|
||||
resp *imap.StatusResp
|
||||
}
|
||||
|
||||
func (err *errStatusResp) Error() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ErrStatusResp can be returned by a Handler to replace the default status
|
||||
// response. The response tag must be empty.
|
||||
//
|
||||
// To disable the default status response, use ErrNoStatusResp instead.
|
||||
func ErrStatusResp(res *imap.StatusResp) error {
|
||||
return &errStatusResp{res}
|
||||
}
|
||||
|
||||
// ErrNoStatusResp can be returned by a Handler to prevent the default status
|
||||
// response from being sent.
|
||||
func ErrNoStatusResp() error {
|
||||
return &errStatusResp{nil}
|
||||
}
|
||||
|
||||
// An IMAP server.
|
||||
type Server struct {
|
||||
locker sync.Mutex
|
||||
listeners map[net.Listener]struct{}
|
||||
conns map[Conn]struct{}
|
||||
|
||||
commands map[string]HandlerFactory
|
||||
auths map[string]SaslServerFactory
|
||||
extensions []Extension
|
||||
|
||||
// TCP address to listen on.
|
||||
Addr string
|
||||
// This server's TLS configuration.
|
||||
TLSConfig *tls.Config
|
||||
// This server's backend.
|
||||
Backend backend.Backend
|
||||
// Backend updates that will be sent to connected clients.
|
||||
Updates <-chan interface{}
|
||||
// Automatically logout clients after a duration. To do not logout users
|
||||
// automatically, set this to zero. The duration MUST be at least
|
||||
// MinAutoLogout (as stated in RFC 3501 section 5.4).
|
||||
AutoLogout time.Duration
|
||||
// Allow authentication over unencrypted connections.
|
||||
AllowInsecureAuth bool
|
||||
// An io.Writer to which all network activity will be mirrored.
|
||||
Debug io.Writer
|
||||
// ErrorLog specifies an optional logger for errors accepting
|
||||
// connections and unexpected behavior from handlers.
|
||||
// If nil, logging goes to os.Stderr via the log package's
|
||||
// standard logger.
|
||||
ErrorLog imap.Logger
|
||||
// The maximum literal size, in bytes. Literals exceeding this size will be
|
||||
// rejected. A value of zero disables the limit (this is the default).
|
||||
MaxLiteralSize uint32
|
||||
}
|
||||
|
||||
// Create a new IMAP server from an existing listener.
|
||||
func New(bkd backend.Backend) *Server {
|
||||
s := &Server{
|
||||
listeners: make(map[net.Listener]struct{}),
|
||||
conns: make(map[Conn]struct{}),
|
||||
Backend: bkd,
|
||||
ErrorLog: log.New(os.Stderr, "imap/server: ", log.LstdFlags),
|
||||
}
|
||||
|
||||
s.auths = map[string]SaslServerFactory{
|
||||
sasl.Plain: func(conn Conn) sasl.Server {
|
||||
return sasl.NewPlainServer(func(identity, username, password string) error {
|
||||
if identity != "" && identity != username {
|
||||
return errors.New("Identities not supported")
|
||||
}
|
||||
|
||||
user, err := bkd.Login(username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := conn.Context()
|
||||
ctx.State = imap.AuthenticatedState
|
||||
ctx.User = user
|
||||
return nil
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
s.commands = map[string]HandlerFactory{
|
||||
imap.Noop: func() Handler { return &Noop{} },
|
||||
imap.Capability: func() Handler { return &Capability{} },
|
||||
imap.Logout: func() Handler { return &Logout{} },
|
||||
|
||||
imap.StartTLS: func() Handler { return &StartTLS{} },
|
||||
imap.Login: func() Handler { return &Login{} },
|
||||
imap.Authenticate: func() Handler { return &Authenticate{} },
|
||||
|
||||
imap.Select: func() Handler { return &Select{} },
|
||||
imap.Examine: func() Handler {
|
||||
hdlr := &Select{}
|
||||
hdlr.ReadOnly = true
|
||||
return hdlr
|
||||
},
|
||||
imap.Create: func() Handler { return &Create{} },
|
||||
imap.Delete: func() Handler { return &Delete{} },
|
||||
imap.Rename: func() Handler { return &Rename{} },
|
||||
imap.Subscribe: func() Handler { return &Subscribe{} },
|
||||
imap.Unsubscribe: func() Handler { return &Unsubscribe{} },
|
||||
imap.List: func() Handler { return &List{} },
|
||||
imap.Lsub: func() Handler {
|
||||
hdlr := &List{}
|
||||
hdlr.Subscribed = true
|
||||
return hdlr
|
||||
},
|
||||
imap.Status: func() Handler { return &Status{} },
|
||||
imap.Append: func() Handler { return &Append{} },
|
||||
|
||||
imap.Check: func() Handler { return &Check{} },
|
||||
imap.Close: func() Handler { return &Close{} },
|
||||
imap.Expunge: func() Handler { return &Expunge{} },
|
||||
imap.Search: func() Handler { return &Search{} },
|
||||
imap.Fetch: func() Handler { return &Fetch{} },
|
||||
imap.Store: func() Handler { return &Store{} },
|
||||
imap.Copy: func() Handler { return &Copy{} },
|
||||
imap.Uid: func() Handler { return &Uid{} },
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the Listener l.
|
||||
func (s *Server) Serve(l net.Listener) error {
|
||||
s.locker.Lock()
|
||||
s.listeners[l] = struct{}{}
|
||||
s.locker.Unlock()
|
||||
|
||||
defer func() {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
l.Close()
|
||||
delete(s.listeners, l)
|
||||
}()
|
||||
|
||||
go s.listenUpdates()
|
||||
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var conn Conn = newConn(s, c)
|
||||
for _, ext := range s.extensions {
|
||||
if ext, ok := ext.(ConnExtension); ok {
|
||||
conn = ext.NewConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
go s.serveConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address s.Addr and then calls Serve
|
||||
// to handle requests on incoming connections.
|
||||
//
|
||||
// If s.Addr is blank, ":imap" is used.
|
||||
func (s *Server) ListenAndServe() error {
|
||||
addr := s.Addr
|
||||
if addr == "" {
|
||||
addr = ":imap"
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Serve(l)
|
||||
}
|
||||
|
||||
// ListenAndServeTLS listens on the TCP network address s.Addr and then calls
|
||||
// Serve to handle requests on incoming TLS connections.
|
||||
//
|
||||
// If s.Addr is blank, ":imaps" is used.
|
||||
func (s *Server) ListenAndServeTLS() error {
|
||||
addr := s.Addr
|
||||
if addr == "" {
|
||||
addr = ":imaps"
|
||||
}
|
||||
|
||||
l, err := tls.Listen("tcp", addr, s.TLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Serve(l)
|
||||
}
|
||||
|
||||
func (s *Server) serveConn(conn Conn) error {
|
||||
s.locker.Lock()
|
||||
s.conns[conn] = struct{}{}
|
||||
s.locker.Unlock()
|
||||
|
||||
defer func() {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
conn.Close()
|
||||
delete(s.conns, conn)
|
||||
}()
|
||||
|
||||
return conn.serve()
|
||||
}
|
||||
|
||||
// Get a command handler factory for the provided command name.
|
||||
func (s *Server) Command(name string) HandlerFactory {
|
||||
// Extensions can override builtin commands
|
||||
for _, ext := range s.extensions {
|
||||
if h := ext.Command(name); h != nil {
|
||||
return h
|
||||
}
|
||||
}
|
||||
|
||||
return s.commands[name]
|
||||
}
|
||||
|
||||
func (s *Server) listenUpdates() (err error) {
|
||||
updater, ok := s.Backend.(backend.Updater)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
s.Updates = updater.Updates()
|
||||
|
||||
for {
|
||||
item := <-s.Updates
|
||||
|
||||
var (
|
||||
update *backend.Update
|
||||
res imap.WriterTo
|
||||
)
|
||||
|
||||
switch item := item.(type) {
|
||||
case *backend.StatusUpdate:
|
||||
update = &item.Update
|
||||
res = item.StatusResp
|
||||
case *backend.MailboxUpdate:
|
||||
update = &item.Update
|
||||
res = &responses.Select{Mailbox: item.MailboxStatus}
|
||||
case *backend.MessageUpdate:
|
||||
update = &item.Update
|
||||
|
||||
ch := make(chan *imap.Message, 1)
|
||||
ch <- item.Message
|
||||
close(ch)
|
||||
|
||||
res = &responses.Fetch{Messages: ch}
|
||||
case *backend.ExpungeUpdate:
|
||||
update = &item.Update
|
||||
|
||||
ch := make(chan uint32, 1)
|
||||
ch <- item.SeqNum
|
||||
close(ch)
|
||||
|
||||
res = &responses.Expunge{SeqNums: ch}
|
||||
default:
|
||||
s.ErrorLog.Printf("unhandled update: %T\n", item)
|
||||
}
|
||||
|
||||
if update == nil || res == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sends := make(chan struct{})
|
||||
wait := 0
|
||||
s.locker.Lock()
|
||||
for conn := range s.conns {
|
||||
ctx := conn.Context()
|
||||
|
||||
if update.Username != "" && (ctx.User == nil || ctx.User.Username() != update.Username) {
|
||||
continue
|
||||
}
|
||||
if update.Mailbox != "" && (ctx.Mailbox == nil || ctx.Mailbox.Name() != update.Mailbox) {
|
||||
continue
|
||||
}
|
||||
if *conn.silent() {
|
||||
// If silent is set, do not send message updates
|
||||
if _, ok := res.(*responses.Fetch); ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
conn := conn // Copy conn to a local variable
|
||||
go func() {
|
||||
done := make(chan struct{})
|
||||
conn.Context().Responses <- &response{
|
||||
response: res,
|
||||
done: done,
|
||||
}
|
||||
<-done
|
||||
sends <- struct{}{}
|
||||
}()
|
||||
|
||||
wait++
|
||||
}
|
||||
s.locker.Unlock()
|
||||
|
||||
if wait > 0 {
|
||||
go func() {
|
||||
for done := 0; done < wait; done++ {
|
||||
<-sends
|
||||
}
|
||||
close(sends)
|
||||
|
||||
backend.DoneUpdate(update)
|
||||
}()
|
||||
} else {
|
||||
backend.DoneUpdate(update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachConn iterates through all opened connections.
|
||||
func (s *Server) ForEachConn(f func(Conn)) {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
for conn := range s.conns {
|
||||
f(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// Stops listening and closes all current connections.
|
||||
func (s *Server) Close() error {
|
||||
s.locker.Lock()
|
||||
defer s.locker.Unlock()
|
||||
|
||||
for l := range s.listeners {
|
||||
l.Close()
|
||||
}
|
||||
|
||||
for conn := range s.conns {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Enable some IMAP extensions on this server.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (s *Server) Enable(extensions ...Extension) {
|
||||
s.extensions = append(s.extensions, extensions...)
|
||||
}
|
||||
|
||||
// Enable an authentication mechanism on this server.
|
||||
//
|
||||
// This function should not be called directly, it must only be used by
|
||||
// libraries implementing extensions of the IMAP protocol.
|
||||
func (s *Server) EnableAuth(name string, f SaslServerFactory) {
|
||||
s.auths[name] = f
|
||||
}
|
Reference in New Issue
Block a user