go-mail/smtp/auth_login.go

88 lines
3.3 KiB
Go

// SPDX-FileCopyrightText: Copyright (c) 2022-2023 The go-mail Authors
//
// SPDX-License-Identifier: MIT
package smtp
import (
"errors"
"fmt"
)
// loginAuth is the type that satisfies the Auth interface for the "SMTP LOGIN" auth
type loginAuth struct {
username, password string
host string
}
const (
// LoginXUsernameChallenge represents the Username Challenge response sent by the SMTP server per the AUTH LOGIN
// extension.
//
// See: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-xlogin/.
LoginXUsernameChallenge = "Username:"
// LoginXPasswordChallenge represents the Password Challenge response sent by the SMTP server per the AUTH LOGIN
// extension.
//
// See: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-xlogin/.
LoginXPasswordChallenge = "Password:"
// LoginXDraftUsernameChallenge represents the Username Challenge response sent by the SMTP server per the IETF
// draft AUTH LOGIN extension. It should be noted this extension is an expired draft which was never formally
// published and was deprecated in favor of the AUTH PLAIN extension.
//
// See: https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00.
LoginXDraftUsernameChallenge = "User Name\x00"
// LoginXDraftPasswordChallenge represents the Password Challenge response sent by the SMTP server per the IETF
// draft AUTH LOGIN extension. It should be noted this extension is an expired draft which was never formally
// published and was deprecated in favor of the AUTH PLAIN extension.
//
// See: https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00.
LoginXDraftPasswordChallenge = "Password\x00"
)
// LoginAuth returns an [Auth] that implements the LOGIN authentication
// mechanism as it is used by MS Outlook. The Auth works similar to PLAIN
// but instead of sending all in one response, the login is handled within
// 3 steps:
// - Sending AUTH LOGIN (server responds with "Username:")
// - Sending the username (server responds with "Password:")
// - Sending the password (server authenticates)
//
// LoginAuth will only send the credentials if the connection is using TLS
// or is connected to localhost. Otherwise authentication will fail with an
// error, without sending the credentials.
func LoginAuth(username, password, host string) Auth {
return &loginAuth{username, password, host}
}
func (a *loginAuth) Start(server *ServerInfo) (string, []byte, error) {
// Must have TLS, or else localhost server.
// Note: If TLS is not true, then we can't trust ANYTHING in ServerInfo.
// In particular, it doesn't matter if the server advertises LOGIN auth.
// That might just be the attacker saying
// "it's ok, you can trust me with your password."
if !server.TLS && !isLocalhost(server.Name) {
return "", nil, errors.New("unencrypted connection")
}
if server.Name != a.host {
return "", nil, errors.New("wrong host name")
}
return "LOGIN", nil, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case LoginXUsernameChallenge, LoginXDraftUsernameChallenge:
return []byte(a.username), nil
case LoginXPasswordChallenge, LoginXDraftPasswordChallenge:
return []byte(a.password), nil
default:
return nil, fmt.Errorf("unexpected server response: %s", string(fromServer))
}
}
return nil, nil
}