package imap import ( "crypto/tls" "fmt" "net/url" "strings" "github.com/emersion/go-imap" idle "github.com/emersion/go-imap-idle" "github.com/emersion/go-imap/client" "golang.org/x/oauth2" "git.sr.ht/~sircmpwn/aerc/lib" "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/worker/handlers" "git.sr.ht/~sircmpwn/aerc/worker/types" ) func init() { handlers.RegisterWorkerFactory("imap", NewIMAPWorker) handlers.RegisterWorkerFactory("imaps", NewIMAPWorker) } var errUnsupported = fmt.Errorf("unsupported command") type imapClient struct { *client.Client idle *idle.IdleClient } type IMAPWorker struct { config struct { scheme string insecure bool addr string user *url.Userinfo folders []string oauthBearer lib.OAuthBearer } client *imapClient idleStop chan struct{} idleDone chan error selected imap.MailboxStatus updates chan client.Update worker *types.Worker // Map of sequence numbers to UIDs, index 0 is seq number 1 seqMap []uint32 } func NewIMAPWorker(worker *types.Worker) (types.Backend, error) { return &IMAPWorker{ idleDone: make(chan error), updates: make(chan client.Update, 50), worker: worker, }, nil } func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error { if w.idleStop != nil { close(w.idleStop) if err := <-w.idleDone; err != nil { w.worker.PostMessage(&types.Error{Error: err}, nil) } } switch msg := msg.(type) { case *types.Unsupported: // No-op case *types.Configure: u, err := url.Parse(msg.Config.Source) if err != nil { return err } w.config.scheme = u.Scheme if strings.HasSuffix(w.config.scheme, "+insecure") { w.config.scheme = strings.TrimSuffix(w.config.scheme, "+insecure") w.config.insecure = true } if strings.HasSuffix(w.config.scheme, "+oauthbearer") { w.config.scheme = strings.TrimSuffix(w.config.scheme, "+oauthbearer") w.config.oauthBearer.Enabled = true q := u.Query() if q.Get("token_endpoint") != "" { w.config.oauthBearer.OAuth2 = &oauth2.Config{ ClientID: q.Get("client_id"), ClientSecret: q.Get("client_secret"), Scopes: []string{q.Get("scope")}, } w.config.oauthBearer.OAuth2.Endpoint.TokenURL = q.Get("token_endpoint") } } w.config.addr = u.Host if !strings.ContainsRune(w.config.addr, ':') { w.config.addr += ":" + w.config.scheme } w.config.user = u.User w.config.folders = msg.Config.Folders case *types.Connect: var ( c *client.Client err error ) switch w.config.scheme { case "imap": c, err = client.Dial(w.config.addr) if err != nil { return err } if !w.config.insecure { if err := c.StartTLS(&tls.Config{}); err != nil { return err } } case "imaps": c, err = client.DialTLS(w.config.addr, &tls.Config{}) if err != nil { return err } default: return fmt.Errorf("Unknown IMAP scheme %s", w.config.scheme) } if w.config.user != nil { username := w.config.user.Username() password, hasPassword := w.config.user.Password() if !hasPassword { // TODO: ask password } if w.config.oauthBearer.Enabled { if err := w.config.oauthBearer.Authenticate(username, password, c); err != nil { return err } } else if err := c.Login(username, password); err != nil { return err } } c.SetDebug(w.worker.Logger.Writer()) if _, err := c.Select(imap.InboxName, false); err != nil { return err } c.Updates = w.updates w.client = &imapClient{c, idle.NewClient(c)} w.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil) case *types.ListDirectories: w.ha