aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/emersion/go-imap/commands/authenticate.go
blob: b66f21f3e2d19a5101ec82f6be8f53d9db8e8a5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package commands

import (
	"bufio"
	"encoding/base64"
	"errors"
	"io"
	"strings"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-sasl"
)

// AuthenticateConn is a connection that supports IMAP authentication.
type AuthenticateConn interface {
	io.Reader

	// WriteResp writes an IMAP response to this connection.
	WriteResp(res imap.WriterTo) error
}

// Authenticate is an AUTHENTICATE command, as defined in RFC 3501 section
// 6.2.2.
type Authenticate struct {
	Mechanism       string
	InitialResponse []byte
}

func (cmd *Authenticate) Command() *imap.Command {
	args := []interface{}{imap.RawString(cmd.Mechanism)}
	if cmd.InitialResponse != nil {
		var encodedResponse string
		if len(cmd.InitialResponse) == 0 {
			// Empty initial response should be encoded as "=", not empty
			// string.
			encodedResponse = "="
		} else {
			encodedResponse = base64.StdEncoding.EncodeToString(cmd.InitialResponse)
		}

		args = append(args, imap.RawString(encodedResponse))
	}
	return &imap.Command{
		Name:      "AUTHENTICATE",
		Arguments: args,
	}
}

func (cmd *Authenticate) Parse(fields []interface{}) error {
	if len(fields) < 1 {
		return errors.New("Not enough arguments")
	}

	var ok bool
	if cmd.Mechanism, ok = fields[0].(string); !ok {
		return errors.New("Mechanism must be a string")
	}
	cmd.Mechanism = strings.ToUpper(cmd.Mechanism)

	if len(fields) != 2 {
		return nil
	}

	encodedResponse, ok := fields[1].(string)
	if !ok {
		return errors.New("Initial response must be a string")
	}
	if encodedResponse == "=" {
		cmd.InitialResponse = []byte{}
		return nil
	}

	var err error
	cmd.InitialResponse, err = base64.StdEncoding.DecodeString(encodedResponse)
	if err != nil {
		return err
	}

	return nil
}

func (cmd *Authenticate) Handle(mechanisms map[string]sasl.Server, conn AuthenticateConn) error {
	sasl, ok := mechanisms[cmd.Mechanism]
	if !ok {
		return errors.New("Unsupported mechanism")
	}

	scanner := bufio.NewScanner(conn)

	response := cmd.InitialResponse
	for {
		challenge, done, err := sasl.Next(response)
		if err != nil || done {
			return err
		}

		encoded := base64.StdEncoding.EncodeToString(challenge)
		cont := &imap.ContinuationReq{Info: encoded}
		if err := conn.WriteResp(cont); err != nil {
			return err
		}

		if !scanner.Scan() {
			if err := scanner.Err(); err != nil {
				return err
			}
			return errors.New("unexpected EOF")
		}

		encoded = scanner.Text()
		if encoded != "" {
			if encoded == "*" {
				return &imap.ErrStatusResp{Resp: &imap.StatusResp{
					Type: imap.StatusRespBad,
					Info: "negotiation cancelled",
				}}
			}
			response, err = base64.StdEncoding.DecodeString(encoded)
			if err != nil {
				return err
			}
		}
	}
}