aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--worker/imap/connect.go178
-rw-r--r--worker/imap/worker.go130
2 files changed, 188 insertions, 120 deletions
diff --git a/worker/imap/connect.go b/worker/imap/connect.go
new file mode 100644
index 00000000..9cc394b4
--- /dev/null
+++ b/worker/imap/connect.go
@@ -0,0 +1,178 @@
+package imap
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net"
+ "time"
+
+ "git.sr.ht/~rjarry/aerc/lib"
+ "github.com/emersion/go-imap"
+ "github.com/emersion/go-imap/client"
+)
+
+// connect establishes a new tcp connection to the imap server, logs in and
+// selects the default inbox. If no error is returned, the imap client will be
+// in the imap.SelectedState.
+func (w *IMAPWorker) connect() (*client.Client, error) {
+
+ var (
+ conn *net.TCPConn
+ err error
+ c *client.Client
+ )
+
+ conn, err = newTCPConn(w.config.addr, w.config.connection_timeout)
+ if conn == nil || err != nil {
+ return nil, err
+ }
+
+ if w.config.connection_timeout > 0 {
+ end := time.Now().Add(w.config.connection_timeout)
+ err = conn.SetDeadline(end)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if w.config.keepalive_period > 0 {
+ err = w.setKeepaliveParameters(conn)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ serverName, _, _ := net.SplitHostPort(w.config.addr)
+ tlsConfig := &tls.Config{ServerName: serverName}
+
+ switch w.config.scheme {
+ case "imap":
+ c, err = client.New(conn)
+ if err != nil {
+ return nil, err
+ }
+ if !w.config.insecure {
+ if err = c.StartTLS(tlsConfig); err != nil {
+ return nil, err
+ }
+ }
+ case "imaps":
+ tlsConn := tls.Client(conn, tlsConfig)
+ c, err = client.New(tlsConn)
+ if err != nil {
+ return nil, err
+ }
+ default:
+ return nil, fmt.Errorf("Unknown IMAP scheme %s", w.config.scheme)
+ }
+
+ c.ErrorLog = w.worker.Logger
+
+ 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 nil, err
+ }
+ } else if err := c.Login(username, password); err != nil {
+ return nil, err
+ }
+ }
+
+ c.SetDebug(w.worker.Logger.Writer())
+
+ if _, err := c.Select(imap.InboxName, false); err != nil {
+ return nil, err
+ }
+
+ return c, nil
+}
+
+// newTCPConn establishes a new tcp connection. Timeout will ensure that the
+// function does not hang when there is no connection. If there is a timeout,
+// but a valid connection is eventually returned, ensure that it is properly
+// closed.
+func newTCPConn(addr string, timeout time.Duration) (*net.TCPConn, error) {
+
+ var errTCPTimeout = fmt.Errorf("tcp connection timeout")
+
+ type tcpConn struct {
+ conn *net.TCPConn
+ err error
+ }
+
+ done := make(chan tcpConn)
+ go func() {
+ addr, err := net.ResolveTCPAddr("tcp", addr)
+ if err != nil {
+ done <- tcpConn{nil, err}
+ return
+ }
+
+ newConn, err := net.DialTCP("tcp", nil, addr)
+ if err != nil {
+ done <- tcpConn{nil, err}
+ return
+ }
+
+ done <- tcpConn{newConn, nil}
+ return
+ }()
+
+ select {
+ case <-time.After(timeout):
+ go func() {
+ if tcpResult := <-done; tcpResult.conn != nil {
+ tcpResult.conn.Close()
+ }
+ }()
+ return nil, errTCPTimeout
+ case tcpResult := <-done:
+ if tcpResult.conn == nil || tcpResult.err != nil {
+ return nil, tcpResult.err
+ }
+ return tcpResult.conn, nil
+ }
+}
+
+// Set additional keepalive parameters.
+// Uses new interfaces introduced in Go1.11, which let us get connection's file
+// descriptor, without blocking, and therefore without uncontrolled spawning of
+// threads (not goroutines, actual threads).
+func (w *IMAPWorker) setKeepaliveParameters(conn *net.TCPConn) error {
+ err := conn.SetKeepAlive(true)
+ if err != nil {
+ return err
+ }
+ // Idle time before sending a keepalive probe
+ err = conn.SetKeepAlivePeriod(w.config.keepalive_period)
+ if err != nil {
+ return err
+ }
+ rawConn, e := conn.SyscallConn()
+ if e != nil {
+ return e
+ }
+ err = rawConn.Control(func(fdPtr uintptr) {
+ fd := int(fdPtr)
+ // Max number of probes before failure
+ err := lib.SetTcpKeepaliveProbes(fd, w.config.keepalive_probes)
+ if err != nil {
+ w.worker.Logger.Printf(
+ "cannot set tcp keepalive probes: %v\n", err)
+ }
+ // Wait time after an unsuccessful probe
+ err = lib.SetTcpKeepaliveInterval(fd, w.config.keepalive_interval)
+ if err != nil {
+ w.worker.Logger.Printf(
+ "cannot set tcp keepalive interval: %v\n", err)
+ }
+ })
+ return err
+}
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index 1ff6341f..eabaae02 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -1,9 +1,7 @@
package imap
import (
- "crypto/tls"
"fmt"
- "net"
"net/url"
"time"
@@ -25,6 +23,7 @@ func init() {
var (
errUnsupported = fmt.Errorf("unsupported command")
+ errClientNotReady = fmt.Errorf("client not ready")
errNotConnected = fmt.Errorf("not connected")
errAlreadyConnected = fmt.Errorf("already connected")
)
@@ -93,6 +92,15 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
var reterr error // will be returned at the end, needed to support idle
+ // when client is nil allow only certain messages to be handled
+ if w.client == nil {
+ switch msg.(type) {
+ case *types.Connect, *types.Reconnect, *types.Disconnect, *types.Configure:
+ default:
+ return errClientNotReady
+ }
+ }
+
// set connection timeout for calls to imap server
if w.client != nil {
w.client.Timeout = w.config.connection_timeout
@@ -251,124 +259,6 @@ func (w *IMAPWorker) handleImapUpdate(update client.Update) {
}
}
-func (w *IMAPWorker) connect() (*client.Client, error) {
- var (
- conn *net.TCPConn
- c *client.Client
- )
-
- addr, err := net.ResolveTCPAddr("tcp", w.config.addr)
- if err != nil {
- return nil, err
- }
-
- conn, err = net.DialTCP("tcp", nil, addr)
- if err != nil {
- return nil, err
- }
-
- if w.config.connection_timeout > 0 {
- end := time.Now().Add(w.config.connection_timeout)
- err = conn.SetDeadline(end)
- if err != nil {
- return nil, err
- }
- }
- if w.config.keepalive_period > 0 {
- err = w.setKeepaliveParameters(conn)
- if err != nil {
- return nil, err
- }
- }
-
- serverName, _, _ := net.SplitHostPort(w.config.addr)
- tlsConfig := &tls.Config{ServerName: serverName}
-
- switch w.config.scheme {
- case "imap":
- c, err = client.New(conn)
- if err != nil {
- return nil, err
- }
- if !w.config.insecure {
- if err = c.StartTLS(tlsConfig); err != nil {
- return nil, err
- }
- }
- case "imaps":
- tlsConn := tls.Client(conn, tlsConfig)
- c, err = client.New(tlsConn)
- if err != nil {
- return nil, err
- }
- default:
- return nil, fmt.Errorf("Unknown IMAP scheme %s", w.config.scheme)
- }
-
- c.ErrorLog = w.worker.Logger
-
- 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 nil, err
- }
- } else if err := c.Login(username, password); err != nil {
- return nil, err
- }
- }
-
- c.SetDebug(w.worker.Logger.Writer())
-
- if _, err := c.Select(imap.InboxName, false); err != nil {
- return nil, err
- }
-
- return c, nil
-}
-
-// Set additional keepalive parameters.
-// Uses new interfaces introduced in Go1.11, which let us get connection's file
-// descriptor, without blocking, and therefore without uncontrolled spawning of
-// threads (not goroutines, actual threads).
-func (w *IMAPWorker) setKeepaliveParameters(conn *net.TCPConn) error {
- err := conn.SetKeepAlive(true)
- if err != nil {
- return err
- }
- // Idle time before sending a keepalive probe
- err = conn.SetKeepAlivePeriod(w.config.keepalive_period)
- if err != nil {
- return err
- }
- rawConn, e := conn.SyscallConn()
- if e != nil {
- return e
- }
- err = rawConn.Control(func(fdPtr uintptr) {
- fd := int(fdPtr)
- // Max number of probes before failure
- err := lib.SetTcpKeepaliveProbes(fd, w.config.keepalive_probes)
- if err != nil {
- w.worker.Logger.Printf(
- "cannot set tcp keepalive probes: %v\n", err)
- }
- // Wait time after an unsuccessful probe
- err = lib.SetTcpKeepaliveInterval(fd, w.config.keepalive_interval)
- if err != nil {
- w.worker.Logger.Printf(
- "cannot set tcp keepalive interval: %v\n", err)
- }
- })
- return err
-}
-
func (w *IMAPWorker) Run() {
for {
select {