Replaces the gotoolkit helpers with the standard library and the libraries they wrapped: - gotoolkit/encoding TOML wrapper -> pelletier/go-toml/v2 directly, behind a new smtp2shoutrrr.LoadConfig shared by both commands. - gotoolkit/service + gotoolkit/model.Server -> signal.NotifyContext and a concrete *Server whose Start shuts the listener down gracefully when its context is cancelled. Also drops the unused ANONYMOUS SASL client from cmd/sendmail, bumps alpine, golangci-lint and the CI actions, and adds coverage for config loading and the server lifecycle. Closes FMG-2 Co-authored-by: multica-agent <github@multica.ai>
76 lines
2 KiB
Go
76 lines
2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net/smtp"
|
|
"os"
|
|
|
|
"git.nakama.town/fmartingr/smtp2shoutrrr"
|
|
)
|
|
|
|
// The PLAIN mechanism name.
|
|
const Plain = "PLAIN"
|
|
|
|
type plainClient struct {
|
|
Identity string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
func (a *plainClient) Start(si *smtp.ServerInfo) (mech string, ir []byte, err error) {
|
|
mech = Plain
|
|
ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password)
|
|
return
|
|
}
|
|
|
|
func (a *plainClient) Next(challenge []byte, b bool) (response []byte, err error) {
|
|
slog.Debug("SASL challenge received", slog.String("challenge", string(challenge)))
|
|
return nil, nil
|
|
}
|
|
|
|
// NewPlainClient is a client implementation of the PLAIN authentication
|
|
// mechanism, as described in RFC 4616. Unlike smtp.PlainAuth it does not
|
|
// require a TLS connection, which the development server does not offer.
|
|
// Authorization identity may be left blank to indicate that it is the same as
|
|
// the username.
|
|
func NewPlainClient(identity, username, password string) smtp.Auth {
|
|
return &plainClient{identity, username, password}
|
|
}
|
|
|
|
const configPath = "config.toml"
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
slog.Error("sendmail failed", slog.String("err", err.Error()))
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func run() error {
|
|
config, err := smtp2shoutrrr.LoadConfig(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("loading configuration: %w", err)
|
|
}
|
|
|
|
// The development server listens on localhost without TLS.
|
|
hostname := "localhost"
|
|
auth := NewPlainClient("", config.Username, config.Password)
|
|
|
|
slog.Info("Using first recipient configuration to send a test email")
|
|
|
|
if len(config.Recipients) == 0 {
|
|
return errors.New("no recipients found in configuration")
|
|
}
|
|
|
|
if len(config.Recipients[0].Addresses) == 0 {
|
|
return errors.New("no email addresses found in first recipient configuration")
|
|
}
|
|
|
|
recipients := []string{config.Recipients[0].Addresses[0]}
|
|
msg := []byte("Subject: Test notification\r\n\r\nThis is a test notification")
|
|
from := "hello@localhost"
|
|
|
|
return smtp.SendMail(fmt.Sprintf("%s:%d", hostname, config.Port), auth, from, recipients, msg)
|
|
}
|