Skip to content

Commit 6684bd6

Browse files
authored
fix: SMTP HELO hostname (#1284)
## What? Fixes #1084 by using the local OS hostname for the SMTP `HELO`/`EHLO` greeting instead of always sending `localhost`. Both normal message sending and calendar replies now call a shared helper that returns `os.Hostname()` when available, with `localhost` kept as the fallback for hostname lookup failures or empty hostnames. ## Why? Some SMTP anti-spam checks reject or penalize public clients that identify as `localhost`. Sending the real client hostname matches the issue's requested behavior while preserving a safe fallback if hostname lookup is unavailable. --------- Signed-off-by: Nanook <nanookclaw@users.noreply.github.com>
1 parent 1b72ab2 commit 6684bd6

2 files changed

Lines changed: 36 additions & 3 deletions

File tree

sender/sender.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
7979
// randReader is the source of randomness for boundary generation. It is a
8080
// variable so tests can swap it with a deterministic or failing reader. By
8181
// default it is crypto/rand.Reader.
82-
var randReader io.Reader = rand.Reader
82+
var (
83+
randReader io.Reader = rand.Reader
84+
osHostname = os.Hostname
85+
)
8386

8487
// smimeOuterBoundary returns a fresh, high-entropy MIME boundary for an S/MIME
8588
// multipart/signed wrapper. If crypto/rand cannot supply randomness it returns
@@ -92,6 +95,16 @@ func smimeOuterBoundary() (string, error) {
9295
return "signed-" + fmt.Sprintf("%x", rb[:]), nil
9396
}
9497

98+
// smtpHelloHostname returns the hostname used in the SMTP HELO/EHLO greeting.
99+
// It falls back to localhost when the OS hostname cannot be read.
100+
func smtpHelloHostname() string {
101+
hostname, err := osHostname()
102+
if err != nil || strings.TrimSpace(hostname) == "" {
103+
return "localhost"
104+
}
105+
return hostname
106+
}
107+
95108
// generateMessageID creates a unique Message-ID header.
96109
func generateMessageID(from string) string {
97110
buf := make([]byte, 16)
@@ -672,7 +685,7 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody
672685
}
673686
defer c.Close()
674687

675-
if err = c.Hello("localhost"); err != nil {
688+
if err = c.Hello(smtpHelloHostname()); err != nil {
676689
return nil, err
677690
}
678691

@@ -884,7 +897,7 @@ func SendCalendarReply(account *config.Account, to []string, subject, plainBody
884897
}
885898
defer c.Close()
886899

887-
if err = c.Hello("localhost"); err != nil {
900+
if err = c.Hello(smtpHelloHostname()); err != nil {
888901
return nil, err
889902
}
890903

sender/sender_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ func TestSMIMEOuterBoundary_Success(t *testing.T) {
5656
// Ensure io is referenced even if a future refactor removes it indirectly.
5757
var _ io.Reader = failingReader{}
5858

59+
func TestSMTPHelloHostname(t *testing.T) {
60+
orig := osHostname
61+
t.Cleanup(func() { osHostname = orig })
62+
63+
osHostname = func() (string, error) { return "mail.example.com", nil }
64+
if got := smtpHelloHostname(); got != "mail.example.com" {
65+
t.Fatalf("expected hostname, got %q", got)
66+
}
67+
68+
osHostname = func() (string, error) { return "", nil }
69+
if got := smtpHelloHostname(); got != "localhost" {
70+
t.Fatalf("expected localhost fallback for empty hostname, got %q", got)
71+
}
72+
73+
osHostname = func() (string, error) { return "ignored", errors.New("hostname unavailable") }
74+
if got := smtpHelloHostname(); got != "localhost" {
75+
t.Fatalf("expected localhost fallback on error, got %q", got)
76+
}
77+
}
78+
5979
// TestGenerateMessageID ensures the Message-ID has the correct format.
6080
func TestGenerateMessageID(t *testing.T) {
6181
from := "test@example.com"

0 commit comments

Comments
 (0)