Mercurial > projects > managesieve
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 } |