package main
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"flag"
"io"
"net"
"net/smtp"
"os"
"strings"
)
var (
addr = flag.String("addr", "", "Address of the SMTP server")
user = flag.String("user", "", "Username to use in SMTP AUTH")
password = flag.String("password", "", "Password to use in SMTP AUTH")
from = flag.String("from", "", "From address to use in the message")
serverCert = flag.String("server_cert", "",
"Path to the server certificate to expect")
confPath = flag.String("c", "smtpc.conf",
"Path to the configuration file")
)
func main() {
flag.Parse()
loadConfig()
// Read message from stdin.
rawMsg, err := io.ReadAll(os.Stdin)
notnil(err)
// RCPT TO from the command line.
tos := make([]string, len(flag.Args()))
for i, to := range flag.Args() {
tos[i] = to
}
// Connect to the server.
var conn net.Conn
if *serverCert != "" {
cert := loadCert(*serverCert)
rootCAs := x509.NewCertPool()
rootCAs.AddCert(cert)
tlsConfig := &tls.Config{
ServerName: cert.DNSNames[0],
RootCAs: rootCAs,
}
conn, err = tls.Dial("tcp", *addr, tlsConfig)
defer conn.Close()
} else {
conn, err = net.Dial("tcp", *addr)
}
notnil(err)
// Send the message.
client, err := smtp.NewClient(conn, *addr)
notnil(err)
if *user != "" {
auth := smtp.PlainAuth("", *user, *password, *addr)
err = client.Auth(auth)
notnil(err)
}
if *from == "" {
*from = *user
}
err = client.Mail(*from)
notnil(err)
for _, to := range tos {
err = client.Rcpt(to)
notnil(err)
}
w, err := client.Data()
notnil(err)
_, err = io.Copy(w, bytes.NewReader(rawMsg))
notnil(err)
err = w.Close()
notnil(err)
err = client.Quit()
notnil(err)
}
func loadConfig() {
data, err := os.ReadFile(*confPath)
if errors.Is(err, os.ErrNotExist) {
return
}
notnil(err)
for _, line := range strings.Split(string(data), "\n") {
k, v, ok := strings.Cut(line, " ")
if !ok {
continue
}
k = strings.TrimSpace(k)
// Set the flag but only if it wasn't already set.
// Command-line flags take precedence.
isSet := false
flag.Visit(func(f *flag.Flag) {
if f.Name == k {
isSet = true
}
})
if !isSet {
flag.Lookup(k).Value.Set(strings.TrimSpace(v))
}
}
}
func loadCert(path string) *x509.Certificate {
data, err := os.ReadFile(path)
notnil(err)
block, _ := pem.Decode(data)
cert, err := x509.ParseCertificate(block.Bytes)
notnil(err)
return cert
}
func notnil(err error) {
if err != nil {
panic(err)
}
}