1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00
Files
go-chasquid-smtp/internal/smtpsrv/server.go
Alberto Bertogli fe146f00e5 queue: Always use the main domain for sending DSNs
Today, we pick the domain used to send the DSN from based on what we
presented to the client at EHLO time, which itself may be based on the
TLS negotiation (which is not necessarily trusted).

This is complex, not necessarily correct, and involves passing the
domain around through the queue and persisting it in the items.

So this patch simplifies that handling by always using the main domain
as specified by the configuration.
2016-10-21 22:18:53 +01:00

211 lines
4.9 KiB
Go

// Package smtpsrv implements chasquid's SMTP server and connection handler.
package smtpsrv
import (
"crypto/tls"
"net"
"net/http"
"net/textproto"
"time"
"blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/courier"
"blitiri.com.ar/go/chasquid/internal/domaininfo"
"blitiri.com.ar/go/chasquid/internal/queue"
"blitiri.com.ar/go/chasquid/internal/set"
"blitiri.com.ar/go/chasquid/internal/userdb"
"github.com/golang/glog"
)
type Server struct {
// Main hostname, used for display only.
Hostname string
// Maximum data size.
MaxDataSize int64
// Addresses.
addrs map[SocketMode][]string
// Listeners (that came via systemd).
listeners map[SocketMode][]net.Listener
// TLS config (including loaded certificates).
tlsConfig *tls.Config
// Local domains.
localDomains *set.String
// User databases (per domain).
userDBs map[string]*userdb.DB
// Aliases resolver.
aliasesR *aliases.Resolver
// Domain info database.
dinfo *domaininfo.DB
// Time before we give up on a connection, even if it's sending data.
connTimeout time.Duration
// Time we wait for command round-trips (excluding DATA).
commandTimeout time.Duration
// Queue where we put incoming mail.
queue *queue.Queue
}
func NewServer() *Server {
return &Server{
addrs: map[SocketMode][]string{},
listeners: map[SocketMode][]net.Listener{},
tlsConfig: &tls.Config{},
connTimeout: 20 * time.Minute,
commandTimeout: 1 * time.Minute,
localDomains: &set.String{},
userDBs: map[string]*userdb.DB{},
aliasesR: aliases.NewResolver(),
}
}
func (s *Server) AddCerts(certPath, keyPath string) error {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return err
}
s.tlsConfig.Certificates = append(s.tlsConfig.Certificates, cert)
return nil
}
func (s *Server) AddAddr(a string, m SocketMode) {
s.addrs[m] = append(s.addrs[m], a)
}
func (s *Server) AddListeners(ls []net.Listener, m SocketMode) {
s.listeners[m] = append(s.listeners[m], ls...)
}
func (s *Server) AddDomain(d string) {
s.localDomains.Add(d)
s.aliasesR.AddDomain(d)
}
func (s *Server) AddUserDB(domain string, db *userdb.DB) {
s.userDBs[domain] = db
}
func (s *Server) AddAliasesFile(domain, f string) error {
return s.aliasesR.AddAliasesFile(domain, f)
}
func (s *Server) SetAliasesConfig(suffixSep, dropChars string) {
s.aliasesR.SuffixSep = suffixSep
s.aliasesR.DropChars = dropChars
}
func (s *Server) InitDomainInfo(dir string) *domaininfo.DB {
var err error
s.dinfo, err = domaininfo.New(dir)
if err != nil {
glog.Fatalf("Error opening domain info database: %v", err)
}
err = s.dinfo.Load()
if err != nil {
glog.Fatalf("Error loading domain info database: %v", err)
}
return s.dinfo
}
func (s *Server) InitQueue(path string, localC, remoteC courier.Courier) {
q := queue.New(path, s.localDomains, s.aliasesR, localC, remoteC, s.Hostname)
err := q.Load()
if err != nil {
glog.Fatalf("Error loading queue: %v", err)
}
s.queue = q
http.HandleFunc("/debug/queue",
func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(q.DumpString()))
})
}
// PeriodicallyReload some of the server's information, such as aliases and
// the user databases.
func (s *Server) periodicallyReload() {
for range time.Tick(30 * time.Second) {
err := s.aliasesR.Reload()
if err != nil {
glog.Errorf("Error reloading aliases: %v", err)
}
for domain, udb := range s.userDBs {
err = udb.Reload()
if err != nil {
glog.Errorf("Error reloading %q user db: %v", domain, err)
}
}
}
}
func (s *Server) ListenAndServe() {
// At this point the TLS config should be done, build the
// name->certificate map (used by the TLS library for SNI).
s.tlsConfig.BuildNameToCertificate()
go s.periodicallyReload()
for m, addrs := range s.addrs {
for _, addr := range addrs {
l, err := net.Listen("tcp", addr)
if err != nil {
glog.Fatalf("Error listening: %v", err)
}
glog.Infof("Server listening on %s (%v)", addr, m)
go s.serve(l, m)
}
}
for m, ls := range s.listeners {
for _, l := range ls {
glog.Infof("Server listening on %s (%v, via systemd)", l.Addr(), m)
go s.serve(l, m)
}
}
// Never return. If the serve goroutines have problems, they will abort
// execution.
for {
time.Sleep(24 * time.Hour)
}
}
func (s *Server) serve(l net.Listener, mode SocketMode) {
for {
conn, err := l.Accept()
if err != nil {
glog.Fatalf("Error accepting: %v", err)
}
sc := &Conn{
hostname: s.Hostname,
maxDataSize: s.MaxDataSize,
conn: conn,
tc: textproto.NewConn(conn),
mode: mode,
tlsConfig: s.tlsConfig,
userDBs: s.userDBs,
aliasesR: s.aliasesR,
localDomains: s.localDomains,
dinfo: s.dinfo,
deadline: time.Now().Add(s.connTimeout),
commandTimeout: s.commandTimeout,
queue: s.queue,
}
go sc.Handle()
}
}