2024-10-02 12:37:54 +02:00
|
|
|
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 The go-mail Authors
|
2022-06-17 15:05:54 +02:00
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
2023-01-10 00:38:42 +01:00
|
|
|
package smtp
|
2022-03-12 15:10:01 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2024-10-02 12:37:54 +02:00
|
|
|
// ErrUnencrypted is an error indicating that the connection is not encrypted.
|
|
|
|
var ErrUnencrypted = errors.New("unencrypted connection")
|
|
|
|
|
2023-01-10 00:38:42 +01:00
|
|
|
// loginAuth is the type that satisfies the Auth interface for the "SMTP LOGIN" auth
|
2022-03-12 15:10:01 +01:00
|
|
|
type loginAuth struct {
|
|
|
|
username, password string
|
|
|
|
host string
|
2024-10-02 12:37:54 +02:00
|
|
|
respStep uint8
|
2022-03-12 15:10:01 +01:00
|
|
|
}
|
|
|
|
|
2024-01-10 11:05:34 +01:00
|
|
|
// LoginAuth returns an [Auth] that implements the LOGIN authentication
|
2022-03-12 15:10:01 +01:00
|
|
|
// 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:
|
2024-10-02 12:37:54 +02:00
|
|
|
// - Sending AUTH LOGIN (server might responds with "Username:")
|
|
|
|
// - Sending the username (server might responds with "Password:")
|
2022-03-12 15:10:01 +01:00
|
|
|
// - Sending the password (server authenticates)
|
2024-10-02 12:37:54 +02:00
|
|
|
// This is the common approach as specified by Microsoft in their MS-XLOGIN spec.
|
|
|
|
// See: https://msopenspecs.azureedge.net/files/MS-XLOGIN/%5bMS-XLOGIN%5d.pdf
|
|
|
|
// Yet, there is also an old IETF draft for SMTP AUTH LOGIN that states for clients:
|
|
|
|
// "The contents of both challenges SHOULD be ignored.".
|
|
|
|
// See: https://datatracker.ietf.org/doc/html/draft-murchison-sasl-login-00
|
|
|
|
// Since there is no official standard RFC and we've seen different implementations
|
|
|
|
// of this mechanism (sending "Username:", "Username", "username", "User name", etc.)
|
|
|
|
// we follow the IETF-Draft and ignore any server challange to allow compatiblity
|
|
|
|
// with most mail servers/providers.
|
2022-03-12 15:10:01 +01:00
|
|
|
//
|
|
|
|
// 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.
|
2023-01-10 00:38:42 +01:00
|
|
|
func LoginAuth(username, password, host string) Auth {
|
2024-10-02 12:37:54 +02:00
|
|
|
return &loginAuth{username, password, host, 0}
|
2022-03-12 15:10:01 +01:00
|
|
|
}
|
|
|
|
|
2024-10-02 12:37:54 +02:00
|
|
|
// Start begins the SMTP authentication process by validating server's TLS status and hostname.
|
|
|
|
// Returns "LOGIN" on success.
|
2023-01-10 00:38:42 +01:00
|
|
|
func (a *loginAuth) Start(server *ServerInfo) (string, []byte, error) {
|
2022-03-12 15:10:01 +01:00
|
|
|
// 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) {
|
2024-10-02 12:37:54 +02:00
|
|
|
return "", nil, ErrUnencrypted
|
2022-03-12 15:10:01 +01:00
|
|
|
}
|
|
|
|
if server.Name != a.host {
|
|
|
|
return "", nil, errors.New("wrong host name")
|
|
|
|
}
|
|
|
|
return "LOGIN", nil, nil
|
|
|
|
}
|
|
|
|
|
2024-10-02 12:37:54 +02:00
|
|
|
// Next processes responses from the server during the SMTP authentication exchange, sending the
|
|
|
|
// username and password.
|
2022-03-12 15:10:01 +01:00
|
|
|
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
|
|
|
if more {
|
2024-10-02 12:37:54 +02:00
|
|
|
switch a.respStep {
|
|
|
|
case 0:
|
|
|
|
a.respStep++
|
2022-03-12 15:10:01 +01:00
|
|
|
return []byte(a.username), nil
|
2024-10-02 12:37:54 +02:00
|
|
|
case 1:
|
|
|
|
a.respStep++
|
2022-03-12 15:10:01 +01:00
|
|
|
return []byte(a.password), nil
|
2023-01-07 11:31:46 +01:00
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unexpected server response: %s", string(fromServer))
|
2022-03-12 15:10:01 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-07 11:31:46 +01:00
|
|
|
return nil, nil
|
2022-03-12 15:10:01 +01:00
|
|
|
}
|