Mercurial > projects > managesieve
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/auth.go Thu Oct 15 09:11:05 2020 +0200 @@ -0,0 +1,123 @@ +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name> +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package managesieve + +import ( + "errors" + "fmt" +) + +// this API is inspired by the SASL authentication API in net/smtp + +// ServerInfo stores information about the ManageSieve server. +type ServerInfo struct { + Name string // hostname of the server + TLS bool // whether a verified TLS connection is used + Auth []string // authentication methods advertised in capabilities +} + +// Check whether the server supports the wanted SASL authentication mechanism. +func (s *ServerInfo) HaveAuth(wanted string) bool { + for _, m := range s.Auth { + if m == wanted { + return true + } + } + return false +} + +type Auth interface { + // Initiate SASL authentication. A non-nil response will be sent in + // response to an empty challenge from the server if mandated by the + // authentication mechanism. The name of the SASL authentication + // mechanism is returned in mechanism. If an error is returned SASL + // authentication will be aborted and an AuthenticationError will be + // returned to the caller. + Start(server *ServerInfo) (mechanism string, response []byte, err error) + // Handle a challenge received from the server, if more is true the + // server expects a response, otherwise the response should be nil. If + // an error is returned SASL authentication will be aborted and an + // AuthenticationError will be returned to the caller. + Next(challenge []byte, more bool) (response []byte, err error) +} + +var ( + // ErrPlainAuthNotSupported is returned if the server does not support + // the SASL PLAIN authentication mechanism. + ErrPlainAuthNotSupported = errors.New("the server does not support PLAIN authentication") + // ErrPlainAuthTLSRequired is returned when the SASL PLAIN + // authentication mechanism is used without TLS against a server other + // than localhost. + ErrPlainAuthTLSRequired = errors.New("PLAIN authentication requires a TLS connection") +) + +// HostNameVerificationError is returned when the hostname which was passed to +// the Auth implementation could not be verified against the TLS certificate. +type HostNameVerificationError struct { + ExpectedHost, ActualHost string +} + +func (e *HostNameVerificationError) Error() string { + return fmt.Sprintf("host name mismatch: %s != %s", e.ActualHost, + e.ExpectedHost) +} + +type plainAuth struct { + identity string + username string + password string + host string +} + +func (a *plainAuth) Start(server *ServerInfo) (string, []byte, error) { + if !server.HaveAuth("PLAIN") { + return "PLAIN", nil, ErrPlainAuthNotSupported + } + + // enforce TLS for non-local servers in order to avoid leaking + // credentials via unencrypted connections or DNS spoofing + if !server.TLS && server.Name != "localhost" && + server.Name != "127.0.0.1" && server.Name != "::1" { + return "PLAIN", nil, ErrPlainAuthTLSRequired + } + + // verify server hostname before sending credentials + if server.Name != a.host { + return "PLAIN", nil, + &HostNameVerificationError{a.host, server.Name} + } + + resp := []byte(a.identity + "\x00" + a.username + "\x00" + a.password) + return "PLAIN", resp, nil +} + +func (a *plainAuth) Next(challenge []byte, more bool) ([]byte, error) { + return nil, nil +} + +// PlainAuth provides an Auth implementation of SASL PLAIN authentication as +// specified in RFC 4616 using the provided authorization identity, username +// and password. If the identity is an empty string the server will derive an +// identity from the credentials. +func PlainAuth(identity, username, password, host string) Auth { + return &plainAuth{identity, username, password, host} +}