aerc/worker/imap/configure.go
Julian Pidancet 9217dbeea4 imap,smtp: add XOAUTH2 support
Add XOAUTH2 authentication support for IMAP and SMTP. Although XOAUTH2
is now deprecated in favor of OAuthBearer, it is the only way to connect
to Office365 since Basic Auth is now completely removed.

Since XOAUTH2 is very similar to OAuthBearer and uses the same
configuration parameters, this is basically a copy-paste of the existing
OAuthBearer code.

However, XOAUTH2 support was removed from go-sasl library, so this
change reimports the code that was removed from go-sasl and offers it
a new home in lib/xoauth2.go. Hopefully it shouldn't be too hard to
maintain, being less than 50 SLOC.

Link: https://github.com/emersion/go-sasl/commit/7bfe0ed36a21
Implements: https://todo.sr.ht/~rjarry/aerc/78
Signed-off-by: Julian Pidancet <julian.pidancet@oracle.com>
Tested-by: Inwit <inwit@sindominio.net>
Acked-by: Tim Culverhouse <tim@timculverhouse.com>
2022-10-01 15:47:33 +02:00

160 lines
4.2 KiB
Go

package imap
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
"git.sr.ht/~rjarry/aerc/worker/types"
"golang.org/x/oauth2"
)
func (w *IMAPWorker) handleConfigure(msg *types.Configure) error {
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()
oauth2 := &oauth2.Config{}
if q.Get("token_endpoint") != "" {
oauth2.ClientID = q.Get("client_id")
oauth2.ClientSecret = q.Get("client_secret")
oauth2.Scopes = []string{q.Get("scope")}
oauth2.Endpoint.TokenURL = q.Get("token_endpoint")
}
w.config.oauthBearer.OAuth2 = oauth2
}
if strings.HasSuffix(w.config.scheme, "+xoauth2") {
w.config.scheme = strings.TrimSuffix(w.config.scheme, "+xoauth2")
w.config.xoauth2.Enabled = true
q := u.Query()
oauth2 := &oauth2.Config{}
if q.Get("token_endpoint") != "" {
oauth2.ClientID = q.Get("client_id")
oauth2.ClientSecret = q.Get("client_secret")
oauth2.Scopes = []string{q.Get("scope")}
oauth2.Endpoint.TokenURL = q.Get("token_endpoint")
}
w.config.xoauth2.OAuth2 = oauth2
}
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
w.config.idle_timeout = 10 * time.Second
w.config.idle_debounce = 10 * time.Millisecond
w.config.connection_timeout = 30 * time.Second
w.config.keepalive_period = 0 * time.Second
w.config.keepalive_probes = 3
w.config.keepalive_interval = 3
w.config.reconnect_maxwait = 30 * time.Second
w.config.cacheEnabled = false
w.config.cacheMaxAge = 30 * 24 * time.Hour // 30 days
for key, value := range msg.Config.Params {
switch key {
case "idle-timeout":
val, err := time.ParseDuration(value)
if err != nil || val < 0 {
return fmt.Errorf(
"invalid idle-timeout value %v: %w",
value, err)
}
w.config.idle_timeout = val
case "idle-debounce":
val, err := time.ParseDuration(value)
if err != nil || val < 0 {
return fmt.Errorf(
"invalid idle-debounce value %v: %w",
value, err)
}
w.config.idle_debounce = val
case "reconnect-maxwait":
val, err := time.ParseDuration(value)
if err != nil || val < 0 {
return fmt.Errorf(
"invalid reconnect-maxwait value %v: %w",
value, err)
}
w.config.reconnect_maxwait = val
case "connection-timeout":
val, err := time.ParseDuration(value)
if err != nil || val < 0 {
return fmt.Errorf(
"invalid connection-timeout value %v: %w",
value, err)
}
w.config.connection_timeout = val
case "keepalive-period":
val, err := time.ParseDuration(value)
if err != nil || val < 0 {
return fmt.Errorf(
"invalid keepalive-period value %v: %w",
value, err)
}
w.config.keepalive_period = val
case "keepalive-probes":
val, err := strconv.Atoi(value)
if err != nil || val < 0 {
return fmt.Errorf(
"invalid keepalive-probes value %v: %w",
value, err)
}
w.config.keepalive_probes = val
case "keepalive-interval":
val, err := time.ParseDuration(value)
if err != nil || val < 0 {
return fmt.Errorf(
"invalid keepalive-interval value %v: %w",
value, err)
}
w.config.keepalive_interval = int(val.Seconds())
case "cache-headers":
cache, err := strconv.ParseBool(value)
if err != nil {
// Return an error here because the user tried to set header
// caching, and we want them to know they didn't set it right -
// one way or the other
return fmt.Errorf("invalid cache-headers value %v: %w", value, err)
}
w.config.cacheEnabled = cache
case "cache-max-age":
val, err := time.ParseDuration(value)
if err != nil || val < 0 {
return fmt.Errorf("invalid cache-max-age value %v: %w", value, err)
}
w.config.cacheMaxAge = val
}
}
if w.config.cacheEnabled {
w.initCacheDb(msg.Config.Name)
}
w.idler = newIdler(w.config, w.worker)
w.observer = newObserver(w.config, w.worker)
return nil
}