285 lines
6.1 KiB
Go
285 lines
6.1 KiB
Go
package imap
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
)
|
|
|
|
// A connection state.
|
|
// See RFC 3501 section 3.
|
|
type ConnState int
|
|
|
|
const (
|
|
// In the connecting state, the server has not yet sent a greeting and no
|
|
// command can be issued.
|
|
ConnectingState = 0
|
|
|
|
// In the not authenticated state, the client MUST supply
|
|
// authentication credentials before most commands will be
|
|
// permitted. This state is entered when a connection starts
|
|
// unless the connection has been pre-authenticated.
|
|
NotAuthenticatedState ConnState = 1 << 0
|
|
|
|
// In the authenticated state, the client is authenticated and MUST
|
|
// select a mailbox to access before commands that affect messages
|
|
// will be permitted. This state is entered when a
|
|
// pre-authenticated connection starts, when acceptable
|
|
// authentication credentials have been provided, after an error in
|
|
// selecting a mailbox, or after a successful CLOSE command.
|
|
AuthenticatedState = 1 << 1
|
|
|
|
// In a selected state, a mailbox has been selected to access.
|
|
// This state is entered when a mailbox has been successfully
|
|
// selected.
|
|
SelectedState = AuthenticatedState + 1<<2
|
|
|
|
// In the logout state, the connection is being terminated. This
|
|
// state can be entered as a result of a client request (via the
|
|
// LOGOUT command) or by unilateral action on the part of either
|
|
// the client or server.
|
|
LogoutState = 1 << 3
|
|
|
|
// ConnectedState is either NotAuthenticatedState, AuthenticatedState or
|
|
// SelectedState.
|
|
ConnectedState = NotAuthenticatedState | AuthenticatedState | SelectedState
|
|
)
|
|
|
|
// A function that upgrades a connection.
|
|
//
|
|
// This should only be used by libraries implementing an IMAP extension (e.g.
|
|
// COMPRESS).
|
|
type ConnUpgrader func(conn net.Conn) (net.Conn, error)
|
|
|
|
type Waiter struct {
|
|
start sync.WaitGroup
|
|
end sync.WaitGroup
|
|
finished bool
|
|
}
|
|
|
|
func NewWaiter() *Waiter {
|
|
w := &Waiter{finished: false}
|
|
w.start.Add(1)
|
|
w.end.Add(1)
|
|
return w
|
|
}
|
|
|
|
func (w *Waiter) Wait() {
|
|
if !w.finished {
|
|
// Signal that we are ready for upgrade to continue.
|
|
w.start.Done()
|
|
// Wait for upgrade to finish.
|
|
w.end.Wait()
|
|
w.finished = true
|
|
}
|
|
}
|
|
|
|
func (w *Waiter) WaitReady() {
|
|
if !w.finished {
|
|
// Wait for reader/writer goroutine to be ready for upgrade.
|
|
w.start.Wait()
|
|
}
|
|
}
|
|
|
|
func (w *Waiter) Close() {
|
|
if !w.finished {
|
|
// Upgrade is finished, close chanel to release reader/writer
|
|
w.end.Done()
|
|
}
|
|
}
|
|
|
|
type LockedWriter struct {
|
|
lock sync.Mutex
|
|
writer io.Writer
|
|
}
|
|
|
|
// NewLockedWriter - goroutine safe writer.
|
|
func NewLockedWriter(w io.Writer) io.Writer {
|
|
return &LockedWriter{writer: w}
|
|
}
|
|
|
|
func (w *LockedWriter) Write(b []byte) (int, error) {
|
|
w.lock.Lock()
|
|
defer w.lock.Unlock()
|
|
return w.writer.Write(b)
|
|
}
|
|
|
|
type debugWriter struct {
|
|
io.Writer
|
|
|
|
local io.Writer
|
|
remote io.Writer
|
|
}
|
|
|
|
// NewDebugWriter creates a new io.Writer that will write local network activity
|
|
// to local and remote network activity to remote.
|
|
func NewDebugWriter(local, remote io.Writer) io.Writer {
|
|
return &debugWriter{Writer: local, local: local, remote: remote}
|
|
}
|
|
|
|
type multiFlusher struct {
|
|
flushers []flusher
|
|
}
|
|
|
|
func (mf *multiFlusher) Flush() error {
|
|
for _, f := range mf.flushers {
|
|
if err := f.Flush(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newMultiFlusher(flushers ...flusher) flusher {
|
|
return &multiFlusher{flushers}
|
|
}
|
|
|
|
// Underlying connection state information.
|
|
type ConnInfo struct {
|
|
RemoteAddr net.Addr
|
|
LocalAddr net.Addr
|
|
|
|
// nil if connection is not using TLS.
|
|
TLS *tls.ConnectionState
|
|
}
|
|
|
|
// An IMAP connection.
|
|
type Conn struct {
|
|
net.Conn
|
|
*Reader
|
|
*Writer
|
|
|
|
br *bufio.Reader
|
|
bw *bufio.Writer
|
|
|
|
waiter *Waiter
|
|
|
|
// Print all commands and responses to this io.Writer.
|
|
debug io.Writer
|
|
}
|
|
|
|
// NewConn creates a new IMAP connection.
|
|
func NewConn(conn net.Conn, r *Reader, w *Writer) *Conn {
|
|
c := &Conn{Conn: conn, Reader: r, Writer: w}
|
|
|
|
c.init()
|
|
return c
|
|
}
|
|
|
|
func (c *Conn) createWaiter() *Waiter {
|
|
// create new waiter each time.
|
|
w := NewWaiter()
|
|
c.waiter = w
|
|
return w
|
|
}
|
|
|
|
func (c *Conn) init() {
|
|
r := io.Reader(c.Conn)
|
|
w := io.Writer(c.Conn)
|
|
|
|
if c.debug != nil {
|
|
localDebug, remoteDebug := c.debug, c.debug
|
|
if debug, ok := c.debug.(*debugWriter); ok {
|
|
localDebug, remoteDebug = debug.local, debug.remote
|
|
}
|
|
// If local and remote are the same, then we need a LockedWriter.
|
|
if localDebug == remoteDebug {
|
|
localDebug = NewLockedWriter(localDebug)
|
|
remoteDebug = localDebug
|
|
}
|
|
|
|
if localDebug != nil {
|
|
w = io.MultiWriter(c.Conn, localDebug)
|
|
}
|
|
if remoteDebug != nil {
|
|
r = io.TeeReader(c.Conn, remoteDebug)
|
|
}
|
|
}
|
|
|
|
if c.br == nil {
|
|
c.br = bufio.NewReader(r)
|
|
c.Reader.reader = c.br
|
|
} else {
|
|
c.br.Reset(r)
|
|
}
|
|
|
|
if c.bw == nil {
|
|
c.bw = bufio.NewWriter(w)
|
|
c.Writer.Writer = c.bw
|
|
} else {
|
|
c.bw.Reset(w)
|
|
}
|
|
|
|
if f, ok := c.Conn.(flusher); ok {
|
|
c.Writer.Writer = struct {
|
|
io.Writer
|
|
flusher
|
|
}{
|
|
c.bw,
|
|
newMultiFlusher(c.bw, f),
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Conn) Info() *ConnInfo {
|
|
info := &ConnInfo{
|
|
RemoteAddr: c.RemoteAddr(),
|
|
LocalAddr: c.LocalAddr(),
|
|
}
|
|
|
|
tlsConn, ok := c.Conn.(*tls.Conn)
|
|
if ok {
|
|
state := tlsConn.ConnectionState()
|
|
info.TLS = &state
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// Write implements io.Writer.
|
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
|
return c.Writer.Write(b)
|
|
}
|
|
|
|
// Flush writes any buffered data to the underlying connection.
|
|
func (c *Conn) Flush() error {
|
|
return c.Writer.Flush()
|
|
}
|
|
|
|
// Upgrade a connection, e.g. wrap an unencrypted connection with an encrypted
|
|
// tunnel.
|
|
func (c *Conn) Upgrade(upgrader ConnUpgrader) error {
|
|
// Block reads and writes during the upgrading process
|
|
w := c.createWaiter()
|
|
defer w.Close()
|
|
|
|
upgraded, err := upgrader(c.Conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Conn = upgraded
|
|
c.init()
|
|
return nil
|
|
}
|
|
|
|
// Called by reader/writer goroutines to wait for Upgrade to finish
|
|
func (c *Conn) Wait() {
|
|
c.waiter.Wait()
|
|
}
|
|
|
|
// Called by Upgrader to wait for reader/writer goroutines to be ready for
|
|
// upgrade.
|
|
func (c *Conn) WaitReady() {
|
|
c.waiter.WaitReady()
|
|
}
|
|
|
|
// SetDebug defines an io.Writer to which all network activity will be logged.
|
|
// If nil is provided, network activity will not be logged.
|
|
func (c *Conn) SetDebug(w io.Writer) {
|
|
c.debug = w
|
|
c.init()
|
|
}
|