3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-21 19:39:43 +01:00

support implicit TLS for mail submission agents

Fixes #2048
This commit is contained in:
Shivaram Lingamneni 2023-02-17 00:07:21 -05:00
parent ca03a42dff
commit 71871ca1ef
4 changed files with 40 additions and 27 deletions

View File

@ -414,6 +414,7 @@ accounts:
# port: 25 # port: 25
# username: "admin" # username: "admin"
# password: "hunter2" # password: "hunter2"
# implicit-tls: false # TLS from the first byte, typically on port 465
blacklist-regexes: blacklist-regexes:
# - ".*@mailinator.com" # - ".*@mailinator.com"
timeout: 60s timeout: 60s

View File

@ -24,10 +24,11 @@ var (
) )
type MTAConfig struct { type MTAConfig struct {
Server string Server string
Port int Port int
Username string Username string
Password string Password string
ImplicitTLS bool `yaml:"implicit-tls"`
} }
type MailtoConfig struct { type MailtoConfig struct {
@ -132,11 +133,13 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
var addr string var addr string
var auth smtp.Auth var auth smtp.Auth
var implicitTLS bool
if !config.DirectSendingEnabled() { if !config.DirectSendingEnabled() {
addr = fmt.Sprintf("%s:%d", config.MTAReal.Server, config.MTAReal.Port) addr = fmt.Sprintf("%s:%d", config.MTAReal.Server, config.MTAReal.Port)
if config.MTAReal.Username != "" && config.MTAReal.Password != "" { if config.MTAReal.Username != "" && config.MTAReal.Password != "" {
auth = smtp.PlainAuth("", config.MTAReal.Username, config.MTAReal.Password, config.MTAReal.Server) auth = smtp.PlainAuth("", config.MTAReal.Username, config.MTAReal.Password, config.MTAReal.Server)
} }
implicitTLS = config.MTAReal.ImplicitTLS
} else { } else {
idx := strings.IndexByte(recipient, '@') idx := strings.IndexByte(recipient, '@')
if idx == -1 { if idx == -1 {
@ -149,5 +152,8 @@ func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
addr = fmt.Sprintf("%s:smtp", mx) addr = fmt.Sprintf("%s:smtp", mx)
} }
return smtp.SendMail(addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, config.RequireTLS, config.Timeout) return smtp.SendMail(
addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg,
config.RequireTLS, implicitTLS, config.Timeout,
)
} }

View File

@ -55,14 +55,17 @@ type Client struct {
// Dial returns a new Client connected to an SMTP server at addr. // Dial returns a new Client connected to an SMTP server at addr.
// The addr must include a port, as in "mail.example.com:smtp". // The addr must include a port, as in "mail.example.com:smtp".
func Dial(addr string, timeout time.Duration) (*Client, error) { func Dial(addr string, timeout time.Duration, implicitTLS bool) (*Client, error) {
var conn net.Conn var conn net.Conn
var err error var err error
dialer := net.Dialer{
Timeout: timeout,
}
start := time.Now() start := time.Now()
if timeout == 0 { if !implicitTLS {
conn, err = net.Dial("tcp", addr) conn, err = dialer.Dial("tcp", addr)
} else { } else {
conn, err = net.DialTimeout("tcp", addr, timeout) conn, err = tls.DialWithDialer(&dialer, "tcp", addr, nil)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -338,7 +341,7 @@ var testHookStartTLS func(*tls.Config) // nil, except for tests
// functionality. Higher-level packages exist outside of the standard // functionality. Higher-level packages exist outside of the standard
// library. // library.
// XXX: modified in Ergo to add `requireTLS`, `heloDomain`, and `timeout` arguments // 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 bool, timeout time.Duration) error { func SendMail(addr string, a Auth, heloDomain string, from string, to []string, msg []byte, requireTLS, implicitTLS bool, timeout time.Duration) error {
if err := validateLine(from); err != nil { if err := validateLine(from); err != nil {
return err return err
} }
@ -347,7 +350,7 @@ func SendMail(addr string, a Auth, heloDomain string, from string, to []string,
return err return err
} }
} }
c, err := Dial(addr, timeout) c, err := Dial(addr, timeout, implicitTLS)
if err != nil { if err != nil {
return err return err
} }
@ -355,23 +358,25 @@ func SendMail(addr string, a Auth, heloDomain string, from string, to []string,
if err = c.Hello(heloDomain); err != nil { if err = c.Hello(heloDomain); err != nil {
return err return err
} }
if ok, _ := c.Extension("STARTTLS"); ok { if !implicitTLS {
var config *tls.Config if ok, _ := c.Extension("STARTTLS"); ok {
if requireTLS { var config *tls.Config
config = &tls.Config{ServerName: c.serverName} if requireTLS {
} else { config = &tls.Config{ServerName: c.serverName}
// if TLS isn't a hard requirement, don't verify the certificate either, } else {
// since a MITM attacker could just remove the STARTTLS advertisement // if TLS isn't a hard requirement, don't verify the certificate either,
config = &tls.Config{InsecureSkipVerify: true} // since a MITM attacker could just remove the STARTTLS advertisement
config = &tls.Config{InsecureSkipVerify: true}
}
if testHookStartTLS != nil {
testHookStartTLS(config)
}
if err = c.StartTLS(config); err != nil {
return err
}
} else if requireTLS {
return errors.New("TLS required, but not negotiated")
} }
if testHookStartTLS != nil {
testHookStartTLS(config)
}
if err = c.StartTLS(config); err != nil {
return err
}
} else if requireTLS {
return errors.New("TLS required, but not negotiated")
} }
if a != nil && c.ext != nil { if a != nil && c.ext != nil {
if _, ok := c.ext["AUTH"]; !ok { if _, ok := c.ext["AUTH"]; !ok {

View File

@ -387,6 +387,7 @@ accounts:
# port: 25 # port: 25
# username: "admin" # username: "admin"
# password: "hunter2" # password: "hunter2"
# implicit-tls: false # TLS from the first byte, typically on port 465
blacklist-regexes: blacklist-regexes:
# - ".*@mailinator.com" # - ".*@mailinator.com"
timeout: 60s timeout: 60s