Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion services/mailer/sender/sendmail.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ func (s *SendmailSender) Send(from string, to []string, msg io.WriterTo) error {

args := []string{"-f", envelopeFrom, "-i"}
args = append(args, setting.MailService.SendmailArgs...)
args = append(args, to...)
for _, recipient := range to {
smtpTo, err := sanitizeEmailAddress(recipient)
if err != nil {
return fmt.Errorf("invalid recipient address %q: %w", recipient, err)
}
args = append(args, smtpTo)
}
log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)

desc := fmt.Sprintf("SendMail: %s %v", setting.MailService.SendmailPath, args)
Expand Down
38 changes: 26 additions & 12 deletions services/mailer/sender/smtp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (
"fmt"
"io"
"net"
"net/mail"
"net/smtp"
"os"
"strings"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"

"github.com/wneessen/go-mail/smtp"
)

// SMTPSender Sender SMTP mail sender
Expand Down Expand Up @@ -108,7 +108,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {
if strings.Contains(options, "CRAM-MD5") {
auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
} else if strings.Contains(options, "PLAIN") {
auth = smtp.PlainAuth("", opts.User, opts.Passwd, host, false)
auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
} else if strings.Contains(options, "LOGIN") {
// Patch for AUTH LOGIN
auth = LoginAuth(opts.User, opts.Passwd)
Expand All @@ -123,18 +123,24 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {
}
}

if opts.OverrideEnvelopeFrom {
if err = client.Mail(opts.EnvelopeFrom); err != nil {
return fmt.Errorf("failed to issue MAIL command: %w", err)
}
} else {
if err = client.Mail(fmt.Sprintf("<%s>", from)); err != nil {
return fmt.Errorf("failed to issue MAIL command: %w", err)
}
fromAddr := from
if opts.OverrideEnvelopeFrom && opts.EnvelopeFrom != "" {
fromAddr = opts.EnvelopeFrom
}
smtpFrom, err := sanitizeEmailAddress(fromAddr)
if err != nil {
return fmt.Errorf("invalid envelope from address: %w", err)
}
if err = client.Mail(smtpFrom); err != nil {
return fmt.Errorf("failed to issue MAIL command: %w", err)
}

for _, rec := range to {
if err = client.Rcpt(rec); err != nil {
smtpTo, err := sanitizeEmailAddress(rec)
if err != nil {
return fmt.Errorf("invalid recipient address %q: %w", rec, err)
}
if err = client.Rcpt(smtpTo); err != nil {
return fmt.Errorf("failed to issue RCPT command: %w", err)
}
}
Expand All @@ -155,3 +161,11 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error {

return nil
}

func sanitizeEmailAddress(raw string) (string, error) {
addr, err := mail.ParseAddress(strings.TrimSpace(strings.Trim(raw, "<>")))
if err != nil {
return "", err
}
return addr.Address, nil
}
2 changes: 1 addition & 1 deletion services/mailer/sender/smtp_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ package sender
import (
"errors"
"fmt"
"net/smtp"

"github.com/Azure/go-ntlmssp"
"github.com/wneessen/go-mail/smtp"
)

type loginAuth struct {
Expand Down
30 changes: 30 additions & 0 deletions services/mailer/sender/smtp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package sender

import "testing"

func TestSanitizeEmailAddress(t *testing.T) {
tests := []struct {
input string
expected string
hasError bool
}{
{"abc@gitea.com", "abc@gitea.com", false},
{"<abc@gitea.com>", "abc@gitea.com", false},
{"ssss.com", "", true},
{"<invalid-email>", "", true},
}

for _, tt := range tests {
result, err := sanitizeEmailAddress(tt.input)
if (err != nil) != tt.hasError {
t.Errorf("sanitizeEmailAddress(%q) unexpected error status: got %v, want error: %v", tt.input, err != nil, tt.hasError)
continue
}
if result != tt.expected {
t.Errorf("sanitizeEmailAddress(%q) = %q; want %q", tt.input, result, tt.expected)
}
}
}