From b426dd8f93ea2b5da890082b6c51a83185e99429 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 7 Apr 2024 15:09:51 -0400 Subject: [PATCH] fix #2142 Allow specifying TCP4 or TCP6 for outgoing email sending, or choosing a specific local address to send from. --- default.yaml | 4 ++++ irc/email/email.go | 24 +++++++++++++++++++++++- irc/smtp/smtp.go | 13 +++++++------ traditional.yaml | 4 ++++ 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/default.yaml b/default.yaml index 36f3ee1e..e7876bda 100644 --- a/default.yaml +++ b/default.yaml @@ -405,6 +405,10 @@ accounts: sender: "admin@my.network" require-tls: true helo-domain: "my.network" # defaults to server name if unset + # set to `tcp4` to force sending over IPv4, `tcp6` to force IPv6: + # protocol: "tcp4" + # set to force a specific source/local IPv4 or IPv6 address: + # local-address: "1.2.3.4" # options to enable DKIM signing of outgoing emails (recommended, but # requires creating a DNS entry for the public key): # dkim: diff --git a/irc/email/email.go b/irc/email/email.go index c87840fb..1d6bd1ce 100644 --- a/irc/email/email.go +++ b/irc/email/email.go @@ -75,6 +75,9 @@ type MailtoConfig struct { Sender string HeloDomain string `yaml:"helo-domain"` RequireTLS bool `yaml:"require-tls"` + Protocol string `yaml:"protocol"` + LocalAddress string `yaml:"local-address"` + localAddress net.Addr VerifyMessageSubject string `yaml:"verify-message-subject"` DKIM DKIMConfig MTAReal MTAConfig `yaml:"mta"` @@ -159,6 +162,25 @@ func (config *MailtoConfig) Postprocess(heloDomain string) (err error) { } } + config.Protocol = strings.ToLower(config.Protocol) + if config.Protocol == "" { + config.Protocol = "tcp" + } + if !(config.Protocol == "tcp" || config.Protocol == "tcp4" || config.Protocol == "tcp6") { + return fmt.Errorf("Invalid protocol for email sending: `%s`", config.Protocol) + } + + if config.LocalAddress != "" { + ipAddr := net.ParseIP(config.LocalAddress) + if ipAddr == nil { + return fmt.Errorf("Could not parse local-address for email sending: `%s`", config.LocalAddress) + } + config.localAddress = &net.TCPAddr{ + IP: ipAddr, + Port: 0, + } + } + if config.MTAConfig.Server != "" { // smarthost, nothing more to validate return nil @@ -241,6 +263,6 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) { return smtp.SendMail( addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, - config.RequireTLS, implicitTLS, config.Timeout, + config.RequireTLS, implicitTLS, config.Protocol, config.localAddress, config.Timeout, ) } diff --git a/irc/smtp/smtp.go b/irc/smtp/smtp.go index f43d23d5..64949b2a 100644 --- a/irc/smtp/smtp.go +++ b/irc/smtp/smtp.go @@ -55,17 +55,18 @@ type Client struct { // Dial returns a new Client connected to an SMTP server at addr. // The addr must include a port, as in "mail.example.com:smtp". -func Dial(addr string, timeout time.Duration, implicitTLS bool) (*Client, error) { +func Dial(protocol, addr string, localAddress net.Addr, timeout time.Duration, implicitTLS bool) (*Client, error) { var conn net.Conn var err error dialer := net.Dialer{ - Timeout: timeout, + Timeout: timeout, + LocalAddr: localAddress, } start := time.Now() if !implicitTLS { - conn, err = dialer.Dial("tcp", addr) + conn, err = dialer.Dial(protocol, addr) } else { - conn, err = tls.DialWithDialer(&dialer, "tcp", addr, nil) + conn, err = tls.DialWithDialer(&dialer, protocol, addr, nil) } if err != nil { return nil, err @@ -341,7 +342,7 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests // functionality. Higher-level packages exist outside of the standard // library. // XXX: modified in Ergo to add `requireTLS`, `heloDomain`, and `timeout` arguments -func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS, implicitTLS bool, timeout time.Duration) error { +func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS, implicitTLS bool, protocol string, localAddress net.Addr, timeout time.Duration) error { if err := validateLine(from); err != nil { return err } @@ -350,7 +351,7 @@ func SendMail(addr string, a Auth, heloDomain string, from string, to []string, return err } } - c, err := Dial(addr, timeout, implicitTLS) + c, err := Dial(protocol, addr, localAddress, timeout, implicitTLS) if err != nil { return err } diff --git a/traditional.yaml b/traditional.yaml index 862fe47d..281c2801 100644 --- a/traditional.yaml +++ b/traditional.yaml @@ -378,6 +378,10 @@ accounts: sender: "admin@my.network" require-tls: true helo-domain: "my.network" # defaults to server name if unset + # set to `tcp4` to force sending over IPv4, `tcp6` to force IPv6: + # protocol: "tcp4" + # set to force a specific source/local IPv4 or IPv6 address: + # local-address: "1.2.3.4" # options to enable DKIM signing of outgoing emails (recommended, but # requires creating a DNS entry for the public key): # dkim: