3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-01 16:39:26 +01:00
ergo/irc/email/email.go

154 lines
4.1 KiB
Go
Raw Normal View History

2020-04-05 09:48:59 +02:00
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
package email
import (
"bytes"
2020-04-05 09:48:59 +02:00
"errors"
"fmt"
"net"
"regexp"
"strings"
2021-07-07 13:12:15 +02:00
"time"
2020-04-05 09:48:59 +02:00
"github.com/ergochat/ergo/irc/custime"
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/smtp"
"github.com/ergochat/ergo/irc/utils"
2020-04-05 09:48:59 +02:00
)
var (
ErrBlacklistedAddress = errors.New("Email address is blacklisted")
ErrInvalidAddress = errors.New("Email address is invalid")
2020-04-05 09:48:59 +02:00
ErrNoMXRecord = errors.New("Couldn't resolve MX record")
)
type MTAConfig struct {
Server string
Port int
Username string
Password string
}
type MailtoConfig struct {
// legacy config format assumed the use of an MTA/smarthost,
// so server, port, etc. appear directly at top level
// XXX: see https://github.com/go-yaml/yaml/issues/63
MTAConfig `yaml:",inline"`
2020-10-07 00:04:29 +02:00
Enabled bool
2020-04-05 09:48:59 +02:00
Sender string
HeloDomain string `yaml:"helo-domain"`
RequireTLS bool `yaml:"require-tls"`
VerifyMessageSubject string `yaml:"verify-message-subject"`
DKIM DKIMConfig
MTAReal MTAConfig `yaml:"mta"`
BlacklistRegexes []string `yaml:"blacklist-regexes"`
blacklistRegexes []*regexp.Regexp
2021-07-07 13:12:15 +02:00
Timeout time.Duration
PasswordReset struct {
Enabled bool
Cooldown custime.Duration
Timeout custime.Duration
} `yaml:"password-reset"`
2020-04-05 09:48:59 +02:00
}
func (config *MailtoConfig) Postprocess(heloDomain string) (err error) {
if config.Sender == "" {
return errors.New("Invalid mailto sender address")
}
// check for MTA config fields at top level,
// copy to MTAReal if present
if config.Server != "" && config.MTAReal.Server == "" {
config.MTAReal = config.MTAConfig
}
if config.HeloDomain == "" {
config.HeloDomain = heloDomain
}
for _, reg := range config.BlacklistRegexes {
compiled, err := regexp.Compile(fmt.Sprintf("^%s$", reg))
if err != nil {
return err
}
config.blacklistRegexes = append(config.blacklistRegexes, compiled)
}
if config.MTAConfig.Server != "" {
// smarthost, nothing more to validate
return nil
}
return config.DKIM.Postprocess()
}
// are we sending email directly, as opposed to deferring to an MTA?
func (config *MailtoConfig) DirectSendingEnabled() bool {
return config.MTAReal.Server == ""
}
2020-04-05 09:48:59 +02:00
// get the preferred MX record hostname, "" on error
func lookupMX(domain string) (server string) {
var minPref uint16
results, err := net.LookupMX(domain)
if err != nil {
return
}
for _, result := range results {
if minPref == 0 || result.Pref < minPref {
server, minPref = result.Host, result.Pref
}
}
return
}
func ComposeMail(config MailtoConfig, recipient, subject string) (message bytes.Buffer) {
fmt.Fprintf(&message, "From: %s\r\n", config.Sender)
fmt.Fprintf(&message, "To: %s\r\n", recipient)
dkimDomain := config.DKIM.Domain
if dkimDomain != "" {
fmt.Fprintf(&message, "Message-ID: <%s@%s>\r\n", utils.GenerateSecretKey(), dkimDomain)
}
fmt.Fprintf(&message, "Date: %s\r\n", time.Now().UTC().Format(time.RFC1123Z))
fmt.Fprintf(&message, "Subject: %s\r\n", subject)
message.WriteString("\r\n") // blank line: end headers, begin message body
return message
}
2020-04-05 09:48:59 +02:00
func SendMail(config MailtoConfig, recipient string, msg []byte) (err error) {
for _, reg := range config.blacklistRegexes {
if reg.MatchString(recipient) {
return ErrBlacklistedAddress
}
}
if config.DKIM.Domain != "" {
msg, err = DKIMSign(msg, config.DKIM)
if err != nil {
return
}
}
var addr string
var auth smtp.Auth
if !config.DirectSendingEnabled() {
2020-04-05 09:48:59 +02:00
addr = fmt.Sprintf("%s:%d", config.MTAReal.Server, config.MTAReal.Port)
if config.MTAReal.Username != "" && config.MTAReal.Password != "" {
auth = smtp.PlainAuth("", config.MTAReal.Username, config.MTAReal.Password, config.MTAReal.Server)
}
} else {
idx := strings.IndexByte(recipient, '@')
if idx == -1 {
return ErrInvalidAddress
}
mx := lookupMX(recipient[idx+1:])
if mx == "" {
return ErrNoMXRecord
}
addr = fmt.Sprintf("%s:smtp", mx)
}
2021-07-07 13:12:15 +02:00
return smtp.SendMail(addr, auth, config.HeloDomain, config.Sender, []string{recipient}, msg, config.RequireTLS, config.Timeout)
2020-04-05 09:48:59 +02:00
}