mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
move sendmail to separate file
This commit is contained in:
parent
317dc78397
commit
5535515fcb
2 changed files with 242 additions and 222 deletions
222
main.go
222
main.go
|
@ -1,12 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -16,7 +14,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/mail"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -42,7 +39,6 @@ import (
|
||||||
"github.com/mjl-/mox/moxvar"
|
"github.com/mjl-/mox/moxvar"
|
||||||
"github.com/mjl-/mox/mtasts"
|
"github.com/mjl-/mox/mtasts"
|
||||||
"github.com/mjl-/mox/smtp"
|
"github.com/mjl-/mox/smtp"
|
||||||
"github.com/mjl-/mox/smtpclient"
|
|
||||||
"github.com/mjl-/mox/spf"
|
"github.com/mjl-/mox/spf"
|
||||||
"github.com/mjl-/mox/store"
|
"github.com/mjl-/mox/store"
|
||||||
"github.com/mjl-/mox/tlsrpt"
|
"github.com/mjl-/mox/tlsrpt"
|
||||||
|
@ -1990,221 +1986,3 @@ func cmdConfigDescribeSendmail(c *cmd) {
|
||||||
err := sconf.Describe(os.Stdout, submitconf)
|
err := sconf.Describe(os.Stdout, submitconf)
|
||||||
xcheckf(err, "describe config")
|
xcheckf(err, "describe config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdSendmail(c *cmd) {
|
|
||||||
c.params = "[-Fname] [ignoredflags] [-t] [<message]"
|
|
||||||
c.help = `Sendmail is a drop-in replacement for /usr/sbin/sendmail to deliver emails sent by unix processes like cron.
|
|
||||||
|
|
||||||
If invoked as "sendmail", it will act as sendmail for sending messages. Its
|
|
||||||
intention is to let processes like cron send emails. Messages are submitted to
|
|
||||||
an actual mail server over SMTP. The destination mail server and credentials are
|
|
||||||
configured in /etc/moxsubmit.conf, see mox config describe-sendmail. The From
|
|
||||||
message header is rewritten to the configured address. When the addressee
|
|
||||||
appears to be a local user, because without @, the message is sent to the
|
|
||||||
configured default address.
|
|
||||||
|
|
||||||
If submitting an email fails, it is added to a directory moxsubmit.failures in
|
|
||||||
the user's home directory.
|
|
||||||
|
|
||||||
Most flags are ignored to fake compatibility with other sendmail
|
|
||||||
implementations. A single recipient is required, or the tflag.
|
|
||||||
|
|
||||||
/etc/moxsubmit.conf should be group-readable and not readable by others and this
|
|
||||||
binary should be setgid that group:
|
|
||||||
|
|
||||||
groupadd moxsubmit
|
|
||||||
install -m 2755 -o root -g moxsubmit mox /usr/sbin/sendmail
|
|
||||||
touch /etc/moxsubmit.conf
|
|
||||||
chown root:moxsubmit /etc/moxsubmit.conf
|
|
||||||
chmod 640 /etc/moxsubmit.conf
|
|
||||||
# edit /etc/moxsubmit.conf
|
|
||||||
`
|
|
||||||
|
|
||||||
// We are faking that we parse flags, this is non-standard, we want to be lax and ignore most flags.
|
|
||||||
args := c.flagArgs
|
|
||||||
c.flagArgs = []string{}
|
|
||||||
c.Parse() // We still have to call Parse for the usage gathering.
|
|
||||||
|
|
||||||
// Typical cron usage of sendmail:
|
|
||||||
// anacron: https://salsa.debian.org/debian/anacron/-/blob/c939c8c80fc9419c11a5e6be5cbe84f03ad332fd/runjob.c#L183
|
|
||||||
// cron: https://github.com/vixie/cron/blob/fea7a6c5421f88f034be8eef66a84d8b65b5fbe0/config.h#L41
|
|
||||||
|
|
||||||
var from string
|
|
||||||
var tflag bool // If set, we need to take the recipient(s) from the message headers. We only do one recipient, in To.
|
|
||||||
o := 0
|
|
||||||
for i, s := range args {
|
|
||||||
if s == "--" {
|
|
||||||
o = i + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(s, "-") {
|
|
||||||
o = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
s = s[1:]
|
|
||||||
if strings.HasPrefix(s, "F") {
|
|
||||||
from = s[1:]
|
|
||||||
log.Printf("ignoring -F %q", from) // todo
|
|
||||||
} else if s == "t" {
|
|
||||||
tflag = true
|
|
||||||
}
|
|
||||||
o = i + 1
|
|
||||||
// Ignore options otherwise.
|
|
||||||
// todo: we may want to parse more flags. some invocations may not be about sending a message. for now, we'll assume sendmail is only invoked to send a message.
|
|
||||||
}
|
|
||||||
args = args[o:]
|
|
||||||
|
|
||||||
// todo: perhaps allow configuration of config file through environment variable? have to keep in mind that mox with setgid moxsubmit would be reading the file.
|
|
||||||
const confPath = "/etc/moxsubmit.conf"
|
|
||||||
err := sconf.ParseFile(confPath, &submitconf)
|
|
||||||
xcheckf(err, "parsing config")
|
|
||||||
|
|
||||||
var recipient string
|
|
||||||
if len(args) == 1 && !tflag {
|
|
||||||
recipient = args[0]
|
|
||||||
if !strings.Contains(recipient, "@") {
|
|
||||||
if submitconf.DefaultDestination == "" {
|
|
||||||
log.Fatalf("recipient %q has no @ and no default destination configured", recipient)
|
|
||||||
}
|
|
||||||
recipient = submitconf.DefaultDestination
|
|
||||||
} else {
|
|
||||||
_, err := smtp.ParseAddress(args[0])
|
|
||||||
xcheckf(err, "parsing recipient address")
|
|
||||||
}
|
|
||||||
} else if !tflag || len(args) != 0 {
|
|
||||||
log.Fatalln("need either exactly 1 recipient, or -t")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read message and build message we are going to send. We replace \n
|
|
||||||
// with \r\n, and we replace the From header.
|
|
||||||
// todo: should we also wrap lines that are too long? perhaps only if this is just text, no multipart?
|
|
||||||
var sb strings.Builder
|
|
||||||
r := bufio.NewReader(os.Stdin)
|
|
||||||
header := true // Whether we are in the header.
|
|
||||||
fmt.Fprintf(&sb, "From: <%s>\r\n", submitconf.From)
|
|
||||||
var haveTo bool
|
|
||||||
for {
|
|
||||||
line, err := r.ReadString('\n')
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
xcheckf(err, "reading message")
|
|
||||||
}
|
|
||||||
if line != "" {
|
|
||||||
if !strings.HasSuffix(line, "\n") {
|
|
||||||
line += "\n"
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(line, "\r\n") {
|
|
||||||
line = line[:len(line)-1] + "\r\n"
|
|
||||||
}
|
|
||||||
if header && line == "\r\n" {
|
|
||||||
// Bare \r\n marks end of header.
|
|
||||||
if !haveTo {
|
|
||||||
line = fmt.Sprintf("To: <%s>\r\n", recipient) + line
|
|
||||||
}
|
|
||||||
header = false
|
|
||||||
} else if header {
|
|
||||||
t := strings.SplitN(line, ":", 2)
|
|
||||||
if len(t) != 2 {
|
|
||||||
log.Fatalf("invalid message, missing colon in header")
|
|
||||||
}
|
|
||||||
k := strings.ToLower(t[0])
|
|
||||||
if k == "from" {
|
|
||||||
// We already added a From header.
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if tflag && k == "to" {
|
|
||||||
if recipient != "" {
|
|
||||||
log.Fatalf("only single To header allowed")
|
|
||||||
}
|
|
||||||
s := strings.TrimSpace(t[1])
|
|
||||||
if !strings.Contains(s, "@") {
|
|
||||||
if submitconf.DefaultDestination == "" {
|
|
||||||
log.Fatalf("recipient %q has no @ and no default destination is configured", s)
|
|
||||||
}
|
|
||||||
recipient = submitconf.DefaultDestination
|
|
||||||
} else {
|
|
||||||
addrs, err := mail.ParseAddressList(s)
|
|
||||||
xcheckf(err, "parsing To address list")
|
|
||||||
if len(addrs) != 1 {
|
|
||||||
log.Fatalf("only single address allowed in To header")
|
|
||||||
}
|
|
||||||
recipient = addrs[0].Address
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if k == "to" {
|
|
||||||
haveTo = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.WriteString(line)
|
|
||||||
}
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg := sb.String()
|
|
||||||
|
|
||||||
if recipient == "" {
|
|
||||||
log.Fatalf("no recipient")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message seems acceptable. We'll try to deliver it from here. If that fails, we
|
|
||||||
// store the message in the users home directory.
|
|
||||||
|
|
||||||
xcheckf := func(err error, format string, args ...any) {
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("submit failed: %s: %s", fmt.Sprintf(format, args...), err)
|
|
||||||
homedir, err := os.UserHomeDir()
|
|
||||||
xcheckf(err, "finding homedir for storing message after failed delivery")
|
|
||||||
maildir := filepath.Join(homedir, "moxsubmit.failures")
|
|
||||||
os.Mkdir(maildir, 0700)
|
|
||||||
f, err := os.CreateTemp(maildir, "newmsg.")
|
|
||||||
xcheckf(err, "creating temp file for storing message after failed delivery")
|
|
||||||
defer func() {
|
|
||||||
if f != nil {
|
|
||||||
if err := os.Remove(f.Name()); err != nil {
|
|
||||||
log.Printf("removing temp file after failure storing failed delivery: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_, err = f.Write([]byte(msg))
|
|
||||||
xcheckf(err, "writing message to temp file after failed delivery")
|
|
||||||
name := f.Name()
|
|
||||||
err = f.Close()
|
|
||||||
xcheckf(err, "closing message in temp file after failed delivery")
|
|
||||||
f = nil
|
|
||||||
log.Printf("saved message in %s", name)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var conn net.Conn
|
|
||||||
addr := net.JoinHostPort(submitconf.Host, fmt.Sprintf("%d", submitconf.Port))
|
|
||||||
d := net.Dialer{Timeout: 30 * time.Second}
|
|
||||||
if submitconf.TLS {
|
|
||||||
conn, err = tls.DialWithDialer(&d, "tcp", addr, nil)
|
|
||||||
} else {
|
|
||||||
conn, err = d.Dial("tcp", addr)
|
|
||||||
}
|
|
||||||
xcheckf(err, "dial submit server")
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
|
||||||
defer cancel()
|
|
||||||
tlsMode := smtpclient.TLSStrict
|
|
||||||
if !submitconf.STARTTLS {
|
|
||||||
tlsMode = smtpclient.TLSSkip
|
|
||||||
}
|
|
||||||
// todo: should have more auth options, scram-sha-256 at least, perhaps cram-md5 for compatibility as well.
|
|
||||||
authLine := fmt.Sprintf("AUTH PLAIN %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("\u0000%s\u0000%s", submitconf.Username, submitconf.Password))))
|
|
||||||
mox.Conf.Static.HostnameDomain.ASCII = submitconf.LocalHostname
|
|
||||||
client, err := smtpclient.New(ctx, mlog.New("sendmail"), conn, tlsMode, submitconf.Host, authLine)
|
|
||||||
xcheckf(err, "open smtp session")
|
|
||||||
|
|
||||||
err = client.Deliver(ctx, submitconf.From, recipient, int64(len(msg)), strings.NewReader(msg), true, false)
|
|
||||||
xcheckf(err, "submit message")
|
|
||||||
|
|
||||||
if err := client.Close(); err != nil {
|
|
||||||
log.Printf("closing smtp session after message was sent: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
242
sendmail.go
Normal file
242
sendmail.go
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/mail"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mjl-/sconf"
|
||||||
|
|
||||||
|
"github.com/mjl-/mox/mlog"
|
||||||
|
"github.com/mjl-/mox/mox-"
|
||||||
|
"github.com/mjl-/mox/smtp"
|
||||||
|
"github.com/mjl-/mox/smtpclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdSendmail(c *cmd) {
|
||||||
|
c.params = "[-Fname] [ignoredflags] [-t] [<message]"
|
||||||
|
c.help = `Sendmail is a drop-in replacement for /usr/sbin/sendmail to deliver emails sent by unix processes like cron.
|
||||||
|
|
||||||
|
If invoked as "sendmail", it will act as sendmail for sending messages. Its
|
||||||
|
intention is to let processes like cron send emails. Messages are submitted to
|
||||||
|
an actual mail server over SMTP. The destination mail server and credentials are
|
||||||
|
configured in /etc/moxsubmit.conf, see mox config describe-sendmail. The From
|
||||||
|
message header is rewritten to the configured address. When the addressee
|
||||||
|
appears to be a local user, because without @, the message is sent to the
|
||||||
|
configured default address.
|
||||||
|
|
||||||
|
If submitting an email fails, it is added to a directory moxsubmit.failures in
|
||||||
|
the user's home directory.
|
||||||
|
|
||||||
|
Most flags are ignored to fake compatibility with other sendmail
|
||||||
|
implementations. A single recipient is required, or the tflag.
|
||||||
|
|
||||||
|
/etc/moxsubmit.conf should be group-readable and not readable by others and this
|
||||||
|
binary should be setgid that group:
|
||||||
|
|
||||||
|
groupadd moxsubmit
|
||||||
|
install -m 2755 -o root -g moxsubmit mox /usr/sbin/sendmail
|
||||||
|
touch /etc/moxsubmit.conf
|
||||||
|
chown root:moxsubmit /etc/moxsubmit.conf
|
||||||
|
chmod 640 /etc/moxsubmit.conf
|
||||||
|
# edit /etc/moxsubmit.conf
|
||||||
|
`
|
||||||
|
|
||||||
|
// We are faking that we parse flags, this is non-standard, we want to be lax and ignore most flags.
|
||||||
|
args := c.flagArgs
|
||||||
|
c.flagArgs = []string{}
|
||||||
|
c.Parse() // We still have to call Parse for the usage gathering.
|
||||||
|
|
||||||
|
// Typical cron usage of sendmail:
|
||||||
|
// anacron: https://salsa.debian.org/debian/anacron/-/blob/c939c8c80fc9419c11a5e6be5cbe84f03ad332fd/runjob.c#L183
|
||||||
|
// cron: https://github.com/vixie/cron/blob/fea7a6c5421f88f034be8eef66a84d8b65b5fbe0/config.h#L41
|
||||||
|
|
||||||
|
var from string
|
||||||
|
var tflag bool // If set, we need to take the recipient(s) from the message headers. We only do one recipient, in To.
|
||||||
|
o := 0
|
||||||
|
for i, s := range args {
|
||||||
|
if s == "--" {
|
||||||
|
o = i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(s, "-") {
|
||||||
|
o = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
if strings.HasPrefix(s, "F") {
|
||||||
|
from = s[1:]
|
||||||
|
log.Printf("ignoring -F %q", from) // todo
|
||||||
|
} else if s == "t" {
|
||||||
|
tflag = true
|
||||||
|
}
|
||||||
|
o = i + 1
|
||||||
|
// Ignore options otherwise.
|
||||||
|
// todo: we may want to parse more flags. some invocations may not be about sending a message. for now, we'll assume sendmail is only invoked to send a message.
|
||||||
|
}
|
||||||
|
args = args[o:]
|
||||||
|
|
||||||
|
// todo: perhaps allow configuration of config file through environment variable? have to keep in mind that mox with setgid moxsubmit would be reading the file.
|
||||||
|
const confPath = "/etc/moxsubmit.conf"
|
||||||
|
err := sconf.ParseFile(confPath, &submitconf)
|
||||||
|
xcheckf(err, "parsing config")
|
||||||
|
|
||||||
|
var recipient string
|
||||||
|
if len(args) == 1 && !tflag {
|
||||||
|
recipient = args[0]
|
||||||
|
if !strings.Contains(recipient, "@") {
|
||||||
|
if submitconf.DefaultDestination == "" {
|
||||||
|
log.Fatalf("recipient %q has no @ and no default destination configured", recipient)
|
||||||
|
}
|
||||||
|
recipient = submitconf.DefaultDestination
|
||||||
|
} else {
|
||||||
|
_, err := smtp.ParseAddress(args[0])
|
||||||
|
xcheckf(err, "parsing recipient address")
|
||||||
|
}
|
||||||
|
} else if !tflag || len(args) != 0 {
|
||||||
|
log.Fatalln("need either exactly 1 recipient, or -t")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read message and build message we are going to send. We replace \n
|
||||||
|
// with \r\n, and we replace the From header.
|
||||||
|
// todo: should we also wrap lines that are too long? perhaps only if this is just text, no multipart?
|
||||||
|
var sb strings.Builder
|
||||||
|
r := bufio.NewReader(os.Stdin)
|
||||||
|
header := true // Whether we are in the header.
|
||||||
|
fmt.Fprintf(&sb, "From: <%s>\r\n", submitconf.From)
|
||||||
|
var haveTo bool
|
||||||
|
for {
|
||||||
|
line, err := r.ReadString('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
xcheckf(err, "reading message")
|
||||||
|
}
|
||||||
|
if line != "" {
|
||||||
|
if !strings.HasSuffix(line, "\n") {
|
||||||
|
line += "\n"
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(line, "\r\n") {
|
||||||
|
line = line[:len(line)-1] + "\r\n"
|
||||||
|
}
|
||||||
|
if header && line == "\r\n" {
|
||||||
|
// Bare \r\n marks end of header.
|
||||||
|
if !haveTo {
|
||||||
|
line = fmt.Sprintf("To: <%s>\r\n", recipient) + line
|
||||||
|
}
|
||||||
|
header = false
|
||||||
|
} else if header {
|
||||||
|
t := strings.SplitN(line, ":", 2)
|
||||||
|
if len(t) != 2 {
|
||||||
|
log.Fatalf("invalid message, missing colon in header")
|
||||||
|
}
|
||||||
|
k := strings.ToLower(t[0])
|
||||||
|
if k == "from" {
|
||||||
|
// We already added a From header.
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if tflag && k == "to" {
|
||||||
|
if recipient != "" {
|
||||||
|
log.Fatalf("only single To header allowed")
|
||||||
|
}
|
||||||
|
s := strings.TrimSpace(t[1])
|
||||||
|
if !strings.Contains(s, "@") {
|
||||||
|
if submitconf.DefaultDestination == "" {
|
||||||
|
log.Fatalf("recipient %q has no @ and no default destination is configured", s)
|
||||||
|
}
|
||||||
|
recipient = submitconf.DefaultDestination
|
||||||
|
} else {
|
||||||
|
addrs, err := mail.ParseAddressList(s)
|
||||||
|
xcheckf(err, "parsing To address list")
|
||||||
|
if len(addrs) != 1 {
|
||||||
|
log.Fatalf("only single address allowed in To header")
|
||||||
|
}
|
||||||
|
recipient = addrs[0].Address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if k == "to" {
|
||||||
|
haveTo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.WriteString(line)
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg := sb.String()
|
||||||
|
|
||||||
|
if recipient == "" {
|
||||||
|
log.Fatalf("no recipient")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message seems acceptable. We'll try to deliver it from here. If that fails, we
|
||||||
|
// store the message in the users home directory.
|
||||||
|
|
||||||
|
xcheckf := func(err error, format string, args ...any) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("submit failed: %s: %s", fmt.Sprintf(format, args...), err)
|
||||||
|
homedir, err := os.UserHomeDir()
|
||||||
|
xcheckf(err, "finding homedir for storing message after failed delivery")
|
||||||
|
maildir := filepath.Join(homedir, "moxsubmit.failures")
|
||||||
|
os.Mkdir(maildir, 0700)
|
||||||
|
f, err := os.CreateTemp(maildir, "newmsg.")
|
||||||
|
xcheckf(err, "creating temp file for storing message after failed delivery")
|
||||||
|
defer func() {
|
||||||
|
if f != nil {
|
||||||
|
if err := os.Remove(f.Name()); err != nil {
|
||||||
|
log.Printf("removing temp file after failure storing failed delivery: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_, err = f.Write([]byte(msg))
|
||||||
|
xcheckf(err, "writing message to temp file after failed delivery")
|
||||||
|
name := f.Name()
|
||||||
|
err = f.Close()
|
||||||
|
xcheckf(err, "closing message in temp file after failed delivery")
|
||||||
|
f = nil
|
||||||
|
log.Printf("saved message in %s", name)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var conn net.Conn
|
||||||
|
addr := net.JoinHostPort(submitconf.Host, fmt.Sprintf("%d", submitconf.Port))
|
||||||
|
d := net.Dialer{Timeout: 30 * time.Second}
|
||||||
|
if submitconf.TLS {
|
||||||
|
conn, err = tls.DialWithDialer(&d, "tcp", addr, nil)
|
||||||
|
} else {
|
||||||
|
conn, err = d.Dial("tcp", addr)
|
||||||
|
}
|
||||||
|
xcheckf(err, "dial submit server")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
tlsMode := smtpclient.TLSStrict
|
||||||
|
if !submitconf.STARTTLS {
|
||||||
|
tlsMode = smtpclient.TLSSkip
|
||||||
|
}
|
||||||
|
// todo: should have more auth options, scram-sha-256 at least, perhaps cram-md5 for compatibility as well.
|
||||||
|
authLine := fmt.Sprintf("AUTH PLAIN %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("\u0000%s\u0000%s", submitconf.Username, submitconf.Password))))
|
||||||
|
mox.Conf.Static.HostnameDomain.ASCII = submitconf.LocalHostname
|
||||||
|
client, err := smtpclient.New(ctx, mlog.New("sendmail"), conn, tlsMode, submitconf.Host, authLine)
|
||||||
|
xcheckf(err, "open smtp session")
|
||||||
|
|
||||||
|
err = client.Deliver(ctx, submitconf.From, recipient, int64(len(msg)), strings.NewReader(msg), true, false)
|
||||||
|
xcheckf(err, "submit message")
|
||||||
|
|
||||||
|
if err := client.Close(); err != nil {
|
||||||
|
log.Printf("closing smtp session after message was sent: %v", err)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue