comparison auth.go @ 0:6369453d47a3

Initial revision
author Guido Berhoerster <guido+managesieve@berhoerster.name>
date Thu, 15 Oct 2020 09:11:05 +0200
parents
children 66b46b3d73be
comparison
equal deleted inserted replaced
-1:000000000000 0:6369453d47a3
1 // Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 package managesieve
23
24 import (
25 "errors"
26 "fmt"
27 )
28
29 // this API is inspired by the SASL authentication API in net/smtp
30
31 // ServerInfo stores information about the ManageSieve server.
32 type ServerInfo struct {
33 Name string // hostname of the server
34 TLS bool // whether a verified TLS connection is used
35 Auth []string // authentication methods advertised in capabilities
36 }
37
38 // Check whether the server supports the wanted SASL authentication mechanism.
39 func (s *ServerInfo) HaveAuth(wanted string) bool {
40 for _, m := range s.Auth {
41 if m == wanted {
42 return true
43 }
44 }
45 return false
46 }
47
48 type Auth interface {
49 // Initiate SASL authentication. A non-nil response will be sent in
50 // response to an empty challenge from the server if mandated by the
51 // authentication mechanism. The name of the SASL authentication
52 // mechanism is returned in mechanism. If an error is returned SASL
53 // authentication will be aborted and an AuthenticationError will be
54 // returned to the caller.
55 Start(server *ServerInfo) (mechanism string, response []byte, err error)
56 // Handle a challenge received from the server, if more is true the
57 // server expects a response, otherwise the response should be nil. If
58 // an error is returned SASL authentication will be aborted and an
59 // AuthenticationError will be returned to the caller.
60 Next(challenge []byte, more bool) (response []byte, err error)
61 }
62
63 var (
64 // ErrPlainAuthNotSupported is returned if the server does not support
65 // the SASL PLAIN authentication mechanism.
66 ErrPlainAuthNotSupported = errors.New("the server does not support PLAIN authentication")
67 // ErrPlainAuthTLSRequired is returned when the SASL PLAIN
68 // authentication mechanism is used without TLS against a server other
69 // than localhost.
70 ErrPlainAuthTLSRequired = errors.New("PLAIN authentication requires a TLS connection")
71 )
72
73 // HostNameVerificationError is returned when the hostname which was passed to
74 // the Auth implementation could not be verified against the TLS certificate.
75 type HostNameVerificationError struct {
76 ExpectedHost, ActualHost string
77 }
78
79 func (e *HostNameVerificationError) Error() string {
80 return fmt.Sprintf("host name mismatch: %s != %s", e.ActualHost,
81 e.ExpectedHost)
82 }
83
84 type plainAuth struct {
85 identity string
86 username string
87 password string
88 host string
89 }
90
91 func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) {
92 if !server.HaveAuth("PLAIN") {
93 return "PLAIN", nil, ErrPlainAuthNotSupported
94 }
95
96 // enforce TLS for non-local servers in order to avoid leaking
97 // credentials via unencrypted connections or DNS spoofing
98 if !server.TLS && server.Name != "localhost" &&
99 server.Name != "127.0.0.1" && server.Name != "::1" {
100 return "PLAIN", nil, ErrPlainAuthTLSRequired
101 }
102
103 // verify server hostname before sending credentials
104 if server.Name != a.host {
105 return "PLAIN", nil,
106 &HostNameVerificationError{a.host, server.Name}
107 }
108
109 resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password)
110 return "PLAIN", resp, nil
111 }
112
113 func (a *plainAuth) Next(challenge []byte, more bool) ([]byte, error) {
114 return nil, nil
115 }
116
117 // PlainAuth provides an Auth implementation of SASL PLAIN authentication as
118 // specified in RFC 4616 using the provided authorization identity, username
119 // and password. If the identity is an empty string the server will derive an
120 // identity from the credentials.
121 func PlainAuth(identity, username, password, host string) Auth {
122 return &plainAuth{identity, username, password, host}
123 }