mirror of
https://github.com/neilalexander/yggmail.git
synced 2026-05-14 15:46:28 +03:00
Very early NOTIFY support, hopefully fix -password on Windows
This commit is contained in:
@@ -10,8 +10,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/emersion/go-imap/server"
|
|
||||||
"github.com/emersion/go-sasl"
|
"github.com/emersion/go-sasl"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
@@ -86,13 +86,13 @@ func main() {
|
|||||||
switch {
|
switch {
|
||||||
case password != nil && *password:
|
case password != nil && *password:
|
||||||
log.Println("Please enter your new password:")
|
log.Println("Please enter your new password:")
|
||||||
password1, err := term.ReadPassword(0)
|
password1, err := term.ReadPassword(syscall.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
log.Println("Please enter your new password again:")
|
log.Println("Please enter your new password again:")
|
||||||
password2, err := term.ReadPassword(0)
|
password2, err := term.ReadPassword(syscall.Stdin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -127,31 +127,19 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
queues := smtpsender.NewQueues(cfg, log, transport, storage)
|
queues := smtpsender.NewQueues(cfg, log, transport, storage)
|
||||||
|
var notify *imapserver.IMAPNotify
|
||||||
|
|
||||||
go func() {
|
imapBackend := &imapserver.Backend{
|
||||||
defer wg.Done()
|
Log: log,
|
||||||
|
Config: cfg,
|
||||||
|
Storage: storage,
|
||||||
|
}
|
||||||
|
|
||||||
imapBackend := &imapserver.Backend{
|
_, notify, err = imapserver.NewIMAPServer(imapBackend, *imapaddr, true)
|
||||||
Log: log,
|
if err != nil {
|
||||||
Config: cfg,
|
log.Fatal(err)
|
||||||
Storage: storage,
|
}
|
||||||
}
|
log.Println("Listening for IMAP on:", *imapaddr)
|
||||||
|
|
||||||
imapServer := server.New(imapBackend)
|
|
||||||
imapServer.Addr = *imapaddr
|
|
||||||
imapServer.AllowInsecureAuth = true
|
|
||||||
imapServer.EnableAuth(sasl.Login, func(conn server.Conn) sasl.Server {
|
|
||||||
return sasl.NewLoginServer(func(username, password string) error {
|
|
||||||
_, err := imapBackend.Login(nil, username, password)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
log.Println("Listening for IMAP on:", imapServer.Addr)
|
|
||||||
if err := imapServer.ListenAndServe(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -162,6 +150,7 @@ func main() {
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Queues: queues,
|
Queues: queues,
|
||||||
|
Notify: notify,
|
||||||
}
|
}
|
||||||
|
|
||||||
localServer := smtp.NewServer(localBackend)
|
localServer := smtp.NewServer(localBackend)
|
||||||
@@ -192,6 +181,7 @@ func main() {
|
|||||||
Config: cfg,
|
Config: cfg,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Queues: queues,
|
Queues: queues,
|
||||||
|
Notify: notify,
|
||||||
}
|
}
|
||||||
|
|
||||||
overlayServer := smtp.NewServer(overlayBackend)
|
overlayServer := smtp.NewServer(overlayBackend)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type Backend struct {
|
|||||||
Config *config.Config
|
Config *config.Config
|
||||||
Log *log.Logger
|
Log *log.Logger
|
||||||
Storage storage.Storage
|
Storage storage.Storage
|
||||||
|
Server *IMAPServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) Login(conn *imap.ConnInfo, username, password string) (backend.User, error) {
|
func (b *Backend) Login(conn *imap.ConnInfo, username, password string) (backend.User, error) {
|
||||||
@@ -38,6 +39,26 @@ func (b *Backend) Login(conn *imap.ConnInfo, username, password string) (backend
|
|||||||
user := &User{
|
user := &User{
|
||||||
backend: b,
|
backend: b,
|
||||||
username: username,
|
username: username,
|
||||||
|
conn: conn,
|
||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (b *Backend) NotifyNew(id int) error {
|
||||||
|
b.Server.server.ForEachConn(func(conn server.Conn) {
|
||||||
|
notify := false
|
||||||
|
for _, cap := range conn.Capabilities() {
|
||||||
|
if cap == "NOTIFY" {
|
||||||
|
notify = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !notify {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.WaitReady()
|
||||||
|
conn.WriteResp()
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package imapserver
|
package imapserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
idle "github.com/emersion/go-imap-idle"
|
idle "github.com/emersion/go-imap-idle"
|
||||||
"github.com/emersion/go-imap/server"
|
"github.com/emersion/go-imap/server"
|
||||||
|
"github.com/emersion/go-sasl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IMAPServer struct {
|
type IMAPServer struct {
|
||||||
@@ -10,11 +14,27 @@ type IMAPServer struct {
|
|||||||
backend *Backend
|
backend *Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIMAPServer(backend *Backend) (*IMAPServer, error) {
|
func NewIMAPServer(backend *Backend, addr string, insecure bool) (*IMAPServer, *IMAPNotify, error) {
|
||||||
s := &IMAPServer{
|
s := &IMAPServer{
|
||||||
server: server.New(backend),
|
server: server.New(backend),
|
||||||
backend: backend,
|
backend: backend,
|
||||||
}
|
}
|
||||||
|
notify := NewIMAPNotify(s.server, backend.Log)
|
||||||
|
s.server.Addr = addr
|
||||||
|
s.server.AllowInsecureAuth = insecure
|
||||||
|
s.server.Debug = os.Stdout
|
||||||
s.server.Enable(idle.NewExtension())
|
s.server.Enable(idle.NewExtension())
|
||||||
return s, nil
|
s.server.Enable(notify)
|
||||||
|
s.server.EnableAuth(sasl.Login, func(conn server.Conn) sasl.Server {
|
||||||
|
return sasl.NewLoginServer(func(username, password string) error {
|
||||||
|
_, err := s.backend.Login(nil, username, password)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
if err := s.server.ListenAndServe(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return s, notify, nil
|
||||||
}
|
}
|
||||||
|
|||||||
65
internal/imapserver/notify.go
Normal file
65
internal/imapserver/notify.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package imapserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-imap/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IMAPNotifyHandler struct {
|
||||||
|
imap.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IMAPNotifyHandler) Handle(conn server.Conn) error {
|
||||||
|
// TODO: Support setting NOTIFY subscriptions or not
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type IMAPNotify struct {
|
||||||
|
server *server.Server
|
||||||
|
log *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *IMAPNotify) Capabilities(c server.Conn) []string {
|
||||||
|
if c.Context().State&imap.AuthenticatedState != 0 {
|
||||||
|
return []string{"NOTIFY"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *IMAPNotify) Command(name string) server.HandlerFactory {
|
||||||
|
if name != "NOTIFY" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return func() server.Handler {
|
||||||
|
return &IMAPNotifyHandler{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *IMAPNotify) NotifyNew(id, count int) error {
|
||||||
|
ext.server.ForEachConn(func(c server.Conn) {
|
||||||
|
var resptype imap.StatusRespType
|
||||||
|
if mailbox := c.Context().Mailbox; mailbox != nil && mailbox.Name() == "INBOX" {
|
||||||
|
resptype = imap.StatusRespType(
|
||||||
|
fmt.Sprintf("EXISTS %d", id),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resptype = imap.StatusRespType(
|
||||||
|
fmt.Sprintf("STATUS INBOX (UIDNEXT %d MESSAGES %d)", id+1, count),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ = c.WriteResp(&imap.StatusResp{
|
||||||
|
Type: resptype,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIMAPNotify(s *server.Server, log *log.Logger) *IMAPNotify {
|
||||||
|
return &IMAPNotify{
|
||||||
|
server: s,
|
||||||
|
log: log,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
"github.com/emersion/go-imap/backend"
|
"github.com/emersion/go-imap/backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
backend *Backend
|
backend *Backend
|
||||||
username string
|
username string
|
||||||
|
conn *imap.ConnInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Username() string {
|
func (u *User) Username() string {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/neilalexander/yggmail/internal/config"
|
"github.com/neilalexander/yggmail/internal/config"
|
||||||
|
"github.com/neilalexander/yggmail/internal/imapserver"
|
||||||
"github.com/neilalexander/yggmail/internal/smtpsender"
|
"github.com/neilalexander/yggmail/internal/smtpsender"
|
||||||
"github.com/neilalexander/yggmail/internal/storage"
|
"github.com/neilalexander/yggmail/internal/storage"
|
||||||
"github.com/neilalexander/yggmail/internal/utils"
|
"github.com/neilalexander/yggmail/internal/utils"
|
||||||
@@ -25,6 +26,7 @@ type Backend struct {
|
|||||||
Config *config.Config
|
Config *config.Config
|
||||||
Queues *smtpsender.Queues
|
Queues *smtpsender.Queues
|
||||||
Storage storage.Storage
|
Storage storage.Storage
|
||||||
|
Notify *imapserver.IMAPNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
|
func (b *Backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
|
||||||
|
|||||||
@@ -65,10 +65,17 @@ func (s *SessionRemote) Data(r io.Reader) error {
|
|||||||
return fmt.Errorf("m.WriteTo: %w", err)
|
return fmt.Errorf("m.WriteTo: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := s.backend.Storage.MailCreate("INBOX", b.Bytes()); err != nil {
|
if id, err := s.backend.Storage.MailCreate("INBOX", b.Bytes()); err != nil {
|
||||||
return fmt.Errorf("s.backend.Storage.StoreMessageFor: %w", err)
|
return fmt.Errorf("s.backend.Storage.StoreMessageFor: %w", err)
|
||||||
|
} else {
|
||||||
|
s.backend.Log.Printf("Stored new mail from %s", s.from)
|
||||||
|
|
||||||
|
if count, err := s.backend.Storage.MailCount("INBOX"); err == nil {
|
||||||
|
if err := s.backend.Notify.NotifyNew(id, count); err != nil {
|
||||||
|
s.backend.Log.Println("Failed to notify:", s.from)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.backend.Log.Printf("Stored new mail from %s", s.from)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,20 @@ package smtpserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
|
"github.com/neilalexander/yggmail/internal/imapserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SMTPServer struct {
|
type SMTPServer struct {
|
||||||
server *smtp.Server
|
server *smtp.Server
|
||||||
backend smtp.Backend
|
backend smtp.Backend
|
||||||
|
notify *imapserver.IMAPNotify
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSMTPServer(backend smtp.Backend) *SMTPServer {
|
func NewSMTPServer(backend smtp.Backend, notify *imapserver.IMAPNotify) *SMTPServer {
|
||||||
s := &SMTPServer{
|
s := &SMTPServer{
|
||||||
server: smtp.NewServer(backend),
|
server: smtp.NewServer(backend),
|
||||||
backend: backend,
|
backend: backend,
|
||||||
|
notify: notify,
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user