mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-14 09:52:54 +01:00
Winni Neessen
862749f863
This fixes #94 and basically reverts d0f0435
. As James points out correctly in #94, we should not assume specific responses from the server. As long as the spec is followed and the server returns the correct SMTP code, we should not do our own magic.
I've also extended the `getTestConnection` method in client_test.go, so that we can specify more test environment options like `TEST_PORT` and `TEST_TLS_SKIP_VERIFY`. This was needed for testing with the ProtonMail Bridge which listens on a different port and has non-trusted certificates.
73 lines
2.2 KiB
Go
73 lines
2.2 KiB
Go
// SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Package auth implements the LOGIN and MD5-DIGEST smtp authentication mechanisms
|
|
package auth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/smtp"
|
|
)
|
|
|
|
type loginAuth struct {
|
|
username, password string
|
|
host string
|
|
}
|
|
|
|
const (
|
|
// ServerRespUsername represents the "Username:" response by the SMTP server
|
|
ServerRespUsername = "Username:"
|
|
|
|
// ServerRespPassword represents the "Password:" response by the SMTP server
|
|
ServerRespPassword = "Password:"
|
|
)
|
|
|
|
// 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) smtp.Auth {
|
|
return &loginAuth{username, password, host}
|
|
}
|
|
|
|
func isLocalhost(name string) bool {
|
|
return name == "localhost" || name == "127.0.0.1" || name == "::1"
|
|
}
|
|
|
|
func (a *loginAuth) Start(server *smtp.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 ServerRespUsername:
|
|
return []byte(a.username), nil
|
|
case ServerRespPassword:
|
|
return []byte(a.password), nil
|
|
default:
|
|
return nil, fmt.Errorf("unexpected server response: %s", string(fromServer))
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|