Rework logging

Implement debug log (can be enabled using `debug` config directive)
Remove errors directive for IMAP endpoint module.
This commit is contained in:
fox.cpp 2019-03-26 01:36:56 +03:00 committed by emersion
parent 84d150a00f
commit ee553a4cc4
10 changed files with 173 additions and 92 deletions

View file

@ -39,21 +39,14 @@ Valid configuration directives and their forms:
* `io_debug` * `io_debug`
Write all protocol commands from clients and responses to stderr. Write all protocol commands from clients and responses to stderr.
* `errors stderr` * `debug`
Write protocol errors log to stderr (default). Verbose log only for this module.
* `errors stdout`
Write protocol errors log to stdout.
* `errors <file>`
Write the protocol error log to the specified file (deprecated).
``` ```
imap imap://0.0.0.0 imaps://0.0.0.0:993 { imap imap://0.0.0.0 imaps://0.0.0.0:993 {
tls /etc/ssl/private/cert.pem /etc/ssl/private/pkey.key tls /etc/ssl/private/cert.pem /etc/ssl/private/pkey.key
auth pam auth pam
insecure_auth insecure_auth
errors /var/lob/imap-errs.log
storage spool storage spool
} }
``` ```
@ -99,6 +92,9 @@ Valid configuration directives and their forms:
* `io_debug` * `io_debug`
Write all protocol commands from clients and responses to stderr. Write all protocol commands from clients and responses to stderr.
* `debug`
Verbose log only for this module.
* `hostname <domain>` * `hostname <domain>`
Set server domain name to advertise in EHLO/HELO response and for matching Set server domain name to advertise in EHLO/HELO response and for matching
during delivery. Required. during delivery. Required.
@ -239,6 +235,9 @@ Valid configuration directives:
* `appendlimit <value>` * `appendlimit <value>`
Refuse to accept messages larger than `value` bytes. Default is 32 MiB. Refuse to accept messages larger than `value` bytes. Default is 32 MiB.
* `debug`
Verbose log only for this module.
### 'dummy' module ### 'dummy' module
No-op module. It doesn't need to be configured explicitly and can be referenced No-op module. It doesn't need to be configured explicitly and can be referenced

View file

@ -2,12 +2,12 @@ package main
import ( import (
"flag" "flag"
"log"
"os" "os"
"path/filepath" "path/filepath"
"github.com/emersion/maddy" "github.com/emersion/maddy"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/log"
) )
func main() { func main() {
@ -17,21 +17,25 @@ func main() {
absCfg, err := filepath.Abs(configpath) absCfg, err := filepath.Abs(configpath)
if err != nil { if err != nil {
log.Fatalf("Failed to resolve path to config: %v", err) log.Println("Failed to resolve path to config: %v", err)
os.Exit(1)
} }
f, err := os.Open(absCfg) f, err := os.Open(absCfg)
if err != nil { if err != nil {
log.Fatalf("Cannot open %q: %v", configpath, err) log.Println("Cannot open %q: %v", configpath, err)
os.Exit(1)
} }
defer f.Close() defer f.Close()
config, err := config.Read(f, absCfg) config, err := config.Read(f, absCfg)
if err != nil { if err != nil {
log.Fatalf("Cannot parse %q: %v", configpath, err) log.Println("Cannot parse %q: %v", configpath, err)
os.Exit(1)
} }
if err := maddy.Start(config); err != nil { if err := maddy.Start(config); err != nil {
log.Fatal(err) log.Println(err)
os.Exit(1)
} }
} }

View file

@ -3,10 +3,6 @@ package maddy
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"log"
"os"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/module" "github.com/emersion/maddy/module"
@ -123,31 +119,3 @@ func defaultStorage() (interface{}, error) {
} }
return res, nil return res, nil
} }
func errorsDirective(m *config.Map, node *config.Node) (interface{}, error) {
if len(node.Args) != 1 {
return nil, m.MatchErr("expected 1 argument")
}
if len(node.Children) != 0 {
return nil, m.MatchErr("can't declare block here")
}
output := node.Args[0]
var w io.Writer
switch output {
case "off":
w = ioutil.Discard
case "stdout":
w = os.Stdout
case "stderr":
w = os.Stderr
default:
f, err := os.OpenFile(output, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return nil, err
}
w = f
}
return log.New(w, "imap ", log.LstdFlags), nil
}

View file

@ -1,9 +1,8 @@
package maddy package maddy
import ( import (
"log"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/log"
"github.com/emersion/maddy/module" "github.com/emersion/maddy/module"
) )

33
imap.go
View file

@ -4,18 +4,19 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"log"
"net" "net"
"os" "os"
"strings" "strings"
"sync" "sync"
appendlimit "github.com/emersion/go-imap-appendlimit"
move "github.com/emersion/go-imap-move"
imapbackend "github.com/emersion/go-imap/backend" imapbackend "github.com/emersion/go-imap/backend"
imapserver "github.com/emersion/go-imap/server" imapserver "github.com/emersion/go-imap/server"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/log"
"github.com/emersion/maddy/module" "github.com/emersion/maddy/module"
appendlimit "github.com/emersion/go-imap-appendlimit"
move "github.com/emersion/go-imap-move"
"github.com/foxcpp/go-sqlmail/imap/children" "github.com/foxcpp/go-sqlmail/imap/children"
) )
@ -28,13 +29,17 @@ type IMAPEndpoint struct {
tlsConfig *tls.Config tlsConfig *tls.Config
listenersWg sync.WaitGroup listenersWg sync.WaitGroup
Log log.Logger
} }
func NewIMAPEndpoint(instName string, globalCfg map[string]config.Node, rawCfg config.Node) (module.Module, error) { func NewIMAPEndpoint(instName string, globalCfg map[string]config.Node, rawCfg config.Node) (module.Module, error) {
endp := new(IMAPEndpoint) endp := &IMAPEndpoint{
name: instName,
Log: log.Logger{Out: log.StderrLog, Name: "imap"},
}
endp.name = instName endp.name = instName
var ( var (
errorLog *log.Logger
insecureAuth bool insecureAuth bool
ioDebug bool ioDebug bool
) )
@ -45,10 +50,7 @@ func NewIMAPEndpoint(instName string, globalCfg map[string]config.Node, rawCfg c
cfg.Custom("tls", true, true, nil, tlsDirective, &endp.tlsConfig) cfg.Custom("tls", true, true, nil, tlsDirective, &endp.tlsConfig)
cfg.Bool("insecure_auth", false, &insecureAuth) cfg.Bool("insecure_auth", false, &insecureAuth)
cfg.Bool("io_debug", false, &insecureAuth) cfg.Bool("io_debug", false, &insecureAuth)
cfg.Custom("errors", false, false, func() (interface{}, error) { cfg.Bool("debug", true, &endp.Log.Debug)
return log.New(os.Stderr, "imap ", log.LstdFlags), nil
}, errorsDirective, &errorLog)
if _, err := cfg.Process(globalCfg, &rawCfg); err != nil { if _, err := cfg.Process(globalCfg, &rawCfg); err != nil {
return nil, err return nil, err
} }
@ -57,10 +59,10 @@ func NewIMAPEndpoint(instName string, globalCfg map[string]config.Node, rawCfg c
for _, addr := range rawCfg.Args { for _, addr := range rawCfg.Args {
saddr, err := standardizeAddress(addr) saddr, err := standardizeAddress(addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid address: %s", instName) return nil, fmt.Errorf("imap: invalid address: %s", instName)
} }
if saddr.Scheme != "imap" && saddr.Scheme != "imaps" { if saddr.Scheme != "imap" && saddr.Scheme != "imaps" {
return nil, fmt.Errorf("imap %s: imap or imaps scheme must be used, got %s", instName, saddr.Scheme) return nil, fmt.Errorf("imap: imap or imaps scheme must be used, got %s", saddr.Scheme)
} }
addresses = append(addresses, saddr) addresses = append(addresses, saddr)
} }
@ -68,6 +70,7 @@ func NewIMAPEndpoint(instName string, globalCfg map[string]config.Node, rawCfg c
endp.serv = imapserver.New(endp) endp.serv = imapserver.New(endp)
endp.serv.AllowInsecureAuth = insecureAuth endp.serv.AllowInsecureAuth = insecureAuth
endp.serv.TLSConfig = endp.tlsConfig endp.serv.TLSConfig = endp.tlsConfig
endp.serv.ErrorLog = endp.Log.DebugWriter()
if ioDebug { if ioDebug {
endp.serv.Debug = os.Stderr endp.serv.Debug = os.Stderr
} }
@ -83,7 +86,7 @@ func NewIMAPEndpoint(instName string, globalCfg map[string]config.Node, rawCfg c
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to bind on %v: %v", addr, err) return nil, fmt.Errorf("failed to bind on %v: %v", addr, err)
} }
log.Printf("imap: listening on %v\n", addr) endp.Log.Printf("imap: listening on %v", addr)
if addr.IsTLS() { if addr.IsTLS() {
if endp.tlsConfig == nil { if endp.tlsConfig == nil {
@ -98,17 +101,17 @@ func NewIMAPEndpoint(instName string, globalCfg map[string]config.Node, rawCfg c
addr := addr addr := addr
go func() { go func() {
if err := endp.serv.Serve(l); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { if err := endp.serv.Serve(l); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") {
log.Printf("imap: failed to serve %s: %s\n", addr, err) endp.Log.Printf("imap: failed to serve %s: %s", addr, err)
} }
endp.listenersWg.Done() endp.listenersWg.Done()
}() }()
} }
if endp.serv.AllowInsecureAuth { if endp.serv.AllowInsecureAuth {
log.Printf("imap %s: authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!", endp.name) endp.Log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!")
} }
if endp.serv.TLSConfig == nil { if endp.serv.TLSConfig == nil {
log.Printf("imap %s: TLS is disabled, this is insecure configuration and should be used only for testing!", endp.name) endp.Log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!")
endp.serv.AllowInsecureAuth = true endp.serv.AllowInsecureAuth = true
} }

74
log/log.go Normal file
View file

@ -0,0 +1,74 @@
package log
import (
"fmt"
"io"
"os"
"time"
)
/*
Brutal logging library.
*/
type FuncLog func(t time.Time, debug bool, str string)
type Logger struct {
Out FuncLog
Name string
Debug bool
}
func (l *Logger) Debugf(format string, val ...interface{}) {
if !l.Debug {
return
}
l.log(true, fmt.Sprintf(format+"\n", val...))
}
func (l *Logger) Debugln(val ...interface{}) {
if !l.Debug {
return
}
l.log(true, fmt.Sprintln(val...))
}
func (l *Logger) Printf(format string, val ...interface{}) {
l.log(false, fmt.Sprintf(format+"\n", val...))
}
func (l *Logger) Println(val ...interface{}) {
l.log(false, fmt.Sprintln(val...))
}
func (l *Logger) Write(s []byte) (int, error) {
l.log(false, string(s))
return len(s), nil
}
func (l Logger) DebugWriter() *Logger {
l.Debug = true
return &l
}
func (l *Logger) log(debug bool, s string) {
if l.Name != "" {
s = l.Name + ": " + s
}
l.Out(time.Now(), debug, s)
}
var DefaultLogger = Logger{Out: StderrLog}
func Debugf(format string, val ...interface{}) { DefaultLogger.Debugf(format, val...) }
func Debugln(val ...interface{}) { DefaultLogger.Debugln(val...) }
func Printf(format string, val ...interface{}) { DefaultLogger.Printf(format, val...) }
func Println(val ...interface{}) { DefaultLogger.Println(val...) }
func StderrLog(t time.Time, debug bool, str string) {
if debug {
str = "[debug] " + str
}
str = t.Format("02.01.06 15:04:05") + " " + str
io.WriteString(os.Stderr, str)
}

View file

@ -3,12 +3,12 @@ package maddy
import ( import (
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/log"
"github.com/emersion/maddy/module" "github.com/emersion/maddy/module"
) )
@ -19,7 +19,7 @@ func Start(cfg []config.Node) error {
defaultPresent := false defaultPresent := false
for _, block := range cfg { for _, block := range cfg {
switch block.Name { switch block.Name {
case "tls", "hostname": case "tls", "hostname", "debug":
globalCfg[block.Name] = block globalCfg[block.Name] = block
continue continue
default: default:
@ -33,9 +33,13 @@ func Start(cfg []config.Node) error {
initDefaultStorage(globalCfg) initDefaultStorage(globalCfg)
} }
if _, ok := globalCfg["debug"]; ok {
log.DefaultLogger.Debug = true
}
for _, block := range cfg { for _, block := range cfg {
switch block.Name { switch block.Name {
case "hostname", "tls": case "hostname", "tls", "debug":
continue continue
} }
@ -57,9 +61,10 @@ func Start(cfg []config.Node) error {
return fmt.Errorf("%s:%d: module instance named %s already exists", block.File, block.Line, instName) return fmt.Errorf("%s:%d: module instance named %s already exists", block.File, block.Line, instName)
} }
log.Debugln("init for module", modName, instName)
inst, err := factory(instName, globalCfg, block) inst, err := factory(instName, globalCfg, block)
if err != nil { if err != nil {
return fmt.Errorf("module instance %s initialization failed: %v", instName, err) return err
} }
module.RegisterInstance(inst) module.RegisterInstance(inst)
@ -70,15 +75,16 @@ func Start(cfg []config.Node) error {
signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGINT) signal.Notify(sig, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGINT)
s := <-sig s := <-sig
log.Printf("signal received (%v), next signal will force immediate shutdown.\n", s) log.Printf("signal received (%v), next signal will force immediate shutdown.", s)
go func() { go func() {
s := <-sig s := <-sig
log.Printf("forced shutdown due to signal (%v)!\n", s) log.Printf("forced shutdown due to signal (%v)!", s)
os.Exit(1) os.Exit(1)
}() }()
for _, inst := range instances { for _, inst := range instances {
if closer, ok := inst.(io.Closer); ok { if closer, ok := inst.(io.Closer); ok {
log.Debugln("clean-up for module", inst.Name(), inst.InstanceName())
closer.Close() closer.Close()
} }
} }

50
smtp.go
View file

@ -6,7 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"net" "net"
"net/mail" "net/mail"
"os" "os"
@ -15,6 +14,7 @@ import (
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/log"
"github.com/emersion/maddy/module" "github.com/emersion/maddy/module"
) )
@ -62,7 +62,7 @@ func (u SMTPUser) Send(from string, to []string, r io.Reader) error {
return nil return nil
} }
if err != nil { if err != nil {
log.Printf("smtp %s: %T failed: %v", u.endp.name, step, err) u.endp.Log.Printf("%T failed: %v", step, err)
return err return err
} }
} }
@ -70,13 +70,16 @@ func (u SMTPUser) Send(from string, to []string, r io.Reader) error {
if r != nil && r != io.Reader(currentMsg) { if r != nil && r != io.Reader(currentMsg) {
buf.Reset() buf.Reset()
if _, err := io.Copy(&buf, r); err != nil { if _, err := io.Copy(&buf, r); err != nil {
log.Printf("smtp %s: failed to buffer message: %v", u.endp.name, err) u.endp.Log.Printf("failed to buffer message: %v", err)
return err return err
} }
currentMsg.Reset(buf.Bytes()) currentMsg.Reset(buf.Bytes())
} }
currentMsg.Seek(0, io.SeekStart) currentMsg.Seek(0, io.SeekStart)
} }
u.endp.Log.Printf("accepted incoming message from %s (%s)", ctx.SrcHostname, ctx.SrcAddr)
return nil return nil
} }
@ -94,6 +97,9 @@ type SMTPEndpoint struct {
submission bool submission bool
listenersWg sync.WaitGroup listenersWg sync.WaitGroup
debug bool
Log log.Logger
} }
func (endp *SMTPEndpoint) Name() string { func (endp *SMTPEndpoint) Name() string {
@ -109,18 +115,25 @@ func (endp *SMTPEndpoint) Version() string {
} }
func NewSMTPEndpoint(instName string, globalCfg map[string]config.Node, cfg config.Node) (module.Module, error) { func NewSMTPEndpoint(instName string, globalCfg map[string]config.Node, cfg config.Node) (module.Module, error) {
endp := new(SMTPEndpoint) endp := &SMTPEndpoint{
name: instName,
Log: log.Logger{Out: log.StderrLog, Name: "smtp"},
}
endp.serv = smtp.NewServer(endp)
endp.name = instName endp.name = instName
endp.serv = smtp.NewServer(endp) endp.serv = smtp.NewServer(endp)
if err := endp.setConfig(globalCfg, cfg); err != nil { if err := endp.setConfig(globalCfg, cfg); err != nil {
return nil, err return nil, err
} }
endp.Log.Debugf("authentication provider: %s %s", endp.Auth.(module.Module).Name(), endp.Auth.(module.Module).InstanceName())
endp.Log.Debugf("pipeline: %#v", endp.pipeline)
addresses := make([]Address, 0, len(cfg.Args)) addresses := make([]Address, 0, len(cfg.Args))
for _, addr := range cfg.Args { for _, addr := range cfg.Args {
saddr, err := standardizeAddress(addr) saddr, err := standardizeAddress(addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid address: %s", instName) return nil, fmt.Errorf("smtp: invalid address: %s", addr)
} }
addresses = append(addresses, saddr) addresses = append(addresses, saddr)
@ -134,10 +147,10 @@ func NewSMTPEndpoint(instName string, globalCfg map[string]config.Node, cfg conf
} }
if endp.serv.AllowInsecureAuth { if endp.serv.AllowInsecureAuth {
log.Printf("smtp %s: authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!", endp.name) endp.Log.Println("authentication over unencrypted connections is allowed, this is insecure configuration and should be used only for testing!")
} }
if endp.serv.TLSConfig == nil && !endp.serv.LMTP { if endp.serv.TLSConfig == nil && !endp.serv.LMTP {
log.Printf("smtp %s: TLS is disabled, this is insecure configuration and should be used only for testing!", endp.name) endp.Log.Println("TLS is disabled, this is insecure configuration and should be used only for testing!")
endp.serv.AllowInsecureAuth = true endp.serv.AllowInsecureAuth = true
} }
@ -164,6 +177,7 @@ func (endp *SMTPEndpoint) setConfig(globalCfg map[string]config.Node, rawCfg con
cfg.Custom("tls", true, true, nil, tlsDirective, &endp.serv.TLSConfig) cfg.Custom("tls", true, true, nil, tlsDirective, &endp.serv.TLSConfig)
cfg.Bool("insecure_auth", false, &endp.serv.AllowInsecureAuth) cfg.Bool("insecure_auth", false, &endp.serv.AllowInsecureAuth)
cfg.Bool("io_debug", false, &ioDebug) cfg.Bool("io_debug", false, &ioDebug)
cfg.Bool("debug", true, &endp.Log.Debug)
cfg.Bool("submission", false, &endp.submission) cfg.Bool("submission", false, &endp.submission)
cfg.AllowUnknown() cfg.AllowUnknown()
@ -176,27 +190,27 @@ func (endp *SMTPEndpoint) setConfig(globalCfg map[string]config.Node, rawCfg con
switch entry.Name { switch entry.Name {
case "local_delivery": case "local_delivery":
if len(entry.Args) == 0 { if len(entry.Args) == 0 {
return errors.New("local_delivery: expected at least 1 argument") return errors.New("smtp: local_delivery: expected at least 1 argument")
} }
if len(endp.pipeline) != 0 { if len(endp.pipeline) != 0 {
return errors.New("can't use custom pipeline with local_delivery or remote_delivery") return errors.New("smtp: can't use custom pipeline with local_delivery or remote_delivery")
} }
localDeliveryDefault = entry.Args[0] localDeliveryDefault = entry.Args[0]
localDeliveryOpts = readOpts(entry.Args[1:]) localDeliveryOpts = readOpts(entry.Args[1:])
case "remote_delivery": case "remote_delivery":
if len(entry.Args) == 0 { if len(entry.Args) == 0 {
return errors.New("remote_delivery: expected at least 1 argument") return errors.New("smtp: remote_delivery: expected at least 1 argument")
} }
if len(endp.pipeline) != 0 { if len(endp.pipeline) != 0 {
return errors.New("can't use custom pipeline with local_delivery or remote_delivery") return errors.New("smtp: can't use custom pipeline with local_delivery or remote_delivery")
} }
remoteDeliveryDefault = entry.Args[0] remoteDeliveryDefault = entry.Args[0]
remoteDeliveryOpts = readOpts(entry.Args[1:]) remoteDeliveryOpts = readOpts(entry.Args[1:])
case "filter", "delivery", "match", "stop", "require_auth": case "filter", "delivery", "match", "stop", "require_auth":
if localDeliveryDefault != "" || remoteDeliveryDefault != "" { if localDeliveryDefault != "" || remoteDeliveryDefault != "" {
return errors.New("can't use custom pipeline with local_delivery or remote_delivery") return errors.New("smtp: can't use custom pipeline with local_delivery or remote_delivery")
} }
step, err := StepFromCfg(entry) step, err := StepFromCfg(entry)
@ -205,7 +219,7 @@ func (endp *SMTPEndpoint) setConfig(globalCfg map[string]config.Node, rawCfg con
} }
endp.pipeline = append(endp.pipeline, step) endp.pipeline = append(endp.pipeline, step)
default: default:
return fmt.Errorf("unknown config directive: %v", entry.Name) return fmt.Errorf("smtp: unknown config directive: %v", entry.Name)
} }
} }
@ -231,13 +245,13 @@ func (endp *SMTPEndpoint) setupListeners(addresses []Address) error {
for _, addr := range addresses { for _, addr := range addresses {
if addr.Scheme == "smtp" || addr.Scheme == "smtps" { if addr.Scheme == "smtp" || addr.Scheme == "smtps" {
if lmtpUsed { if lmtpUsed {
return errors.New("can't mix LMTP with SMTP in one endpoint block") return errors.New("smtp: can't mix LMTP with SMTP in one endpoint block")
} }
smtpUsed = true smtpUsed = true
} }
if addr.Scheme == "lmtp+unix" || addr.Scheme == "lmtp" { if addr.Scheme == "lmtp+unix" || addr.Scheme == "lmtp" {
if smtpUsed { if smtpUsed {
return errors.New("can't mix LMTP with SMTP in one endpoint block") return errors.New("smtp: can't mix LMTP with SMTP in one endpoint block")
} }
lmtpUsed = true lmtpUsed = true
} }
@ -248,11 +262,11 @@ func (endp *SMTPEndpoint) setupListeners(addresses []Address) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to bind on %v: %v", addr, err) return fmt.Errorf("failed to bind on %v: %v", addr, err)
} }
log.Printf("smtp: listening on %v\n", addr) endp.Log.Printf("listening on %v", addr)
if addr.IsTLS() { if addr.IsTLS() {
if endp.serv.TLSConfig == nil { if endp.serv.TLSConfig == nil {
return errors.New("can't bind on SMTPS endpoint without TLS configuration") return errors.New("smtp: can't bind on SMTPS endpoint without TLS configuration")
} }
l = tls.NewListener(l, endp.serv.TLSConfig) l = tls.NewListener(l, endp.serv.TLSConfig)
} }
@ -263,7 +277,7 @@ func (endp *SMTPEndpoint) setupListeners(addresses []Address) error {
addr := addr addr := addr
go func() { go func() {
if err := endp.serv.Serve(l); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { if err := endp.serv.Serve(l); err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") {
log.Printf("smtp: failed to serve %s: %s\n", addr, err) endp.Log.Printf("failed to serve %s: %s", addr, err)
} }
endp.listenersWg.Done() endp.listenersWg.Done()
}() }()

View file

@ -9,6 +9,7 @@ import (
"github.com/emersion/go-imap/backend" "github.com/emersion/go-imap/backend"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/log"
"github.com/emersion/maddy/module" "github.com/emersion/maddy/module"
"github.com/foxcpp/go-sqlmail" "github.com/foxcpp/go-sqlmail"
imapsqlmail "github.com/foxcpp/go-sqlmail/imap" imapsqlmail "github.com/foxcpp/go-sqlmail/imap"
@ -17,6 +18,7 @@ import (
type SQLMail struct { type SQLMail struct {
*imapsqlmail.Backend *imapsqlmail.Backend
instName string instName string
Log log.Logger
} }
func (sqlm *SQLMail) Name() string { func (sqlm *SQLMail) Name() string {
@ -37,11 +39,16 @@ func NewSQLMail(instName string, globalCfg map[string]config.Node, rawCfg config
appendlimitVal := int64(-1) appendlimitVal := int64(-1)
opts := imapsqlmail.Opts{} opts := imapsqlmail.Opts{}
mod := SQLMail{
instName: instName,
Log: log.Logger{Out: log.StderrLog, Name: "sqlmail"},
}
cfg := config.Map{} cfg := config.Map{}
cfg.String("driver", false, true, "", &driver) cfg.String("driver", false, true, "", &driver)
cfg.String("dsn", false, true, "", &dsn) cfg.String("dsn", false, true, "", &dsn)
cfg.Int64("appendlimit", false, false, 32*1024*1024, &appendlimitVal) cfg.Int64("appendlimit", false, false, 32*1024*1024, &appendlimitVal)
cfg.Bool("debug", true, &mod.Log.Debug)
if _, err := cfg.Process(globalCfg, &rawCfg); err != nil { if _, err := cfg.Process(globalCfg, &rawCfg); err != nil {
return nil, err return nil, err
@ -54,11 +61,7 @@ func NewSQLMail(instName string, globalCfg map[string]config.Node, rawCfg config
*opts.MaxMsgBytes = uint32(appendlimitVal) *opts.MaxMsgBytes = uint32(appendlimitVal)
} }
sqlm, err := imapsqlmail.NewBackend(driver, dsn, opts) sqlm, err := imapsqlmail.NewBackend(driver, dsn, opts)
mod.Backend = sqlm
mod := SQLMail{
Backend: sqlm,
instName: instName,
}
if err != nil { if err != nil {
return nil, err return nil, err
@ -79,6 +82,7 @@ func (sqlm *SQLMail) Deliver(ctx module.DeliveryContext, msg io.Reader) error {
for _, rcpt := range ctx.To { for _, rcpt := range ctx.To {
parts := strings.Split(rcpt, "@") parts := strings.Split(rcpt, "@")
if len(parts) != 2 { if len(parts) != 2 {
sqlm.Log.Println("malformed address:", rcpt)
return errors.New("Deliver: missing domain part") return errors.New("Deliver: missing domain part")
} }
@ -89,6 +93,7 @@ func (sqlm *SQLMail) Deliver(ctx module.DeliveryContext, msg io.Reader) error {
} }
if parts[1] != hostname { if parts[1] != hostname {
sqlm.Log.Debugf("local_only, skipping %s", rcpt)
continue continue
} }
} }
@ -99,12 +104,14 @@ func (sqlm *SQLMail) Deliver(ctx module.DeliveryContext, msg io.Reader) error {
} }
if parts[1] == hostname { if parts[1] == hostname {
sqlm.Log.Debugf("remote_only, skipping %s", rcpt)
continue continue
} }
} }
u, err := sqlm.GetExistingUser(parts[0]) u, err := sqlm.GetExistingUser(parts[0])
if err != nil { if err != nil {
sqlm.Log.Debugf("failed to get user for %s: %v", rcpt, err)
return err return err
} }
@ -115,7 +122,9 @@ func (sqlm *SQLMail) Deliver(ctx module.DeliveryContext, msg io.Reader) error {
if err != nil { if err != nil {
if err == backend.ErrNoSuchMailbox { if err == backend.ErrNoSuchMailbox {
// Create INBOX if it doesn't exists. // Create INBOX if it doesn't exists.
sqlm.Log.Debugln("creating inbox for", rcpt)
if err := u.CreateMailbox(tgtMbox); err != nil { if err := u.CreateMailbox(tgtMbox); err != nil {
sqlm.Log.Debugln("inbox creation failed for", rcpt)
return err return err
} }
mbox, err = u.GetMailbox(tgtMbox) mbox, err = u.GetMailbox(tgtMbox)
@ -123,11 +132,13 @@ func (sqlm *SQLMail) Deliver(ctx module.DeliveryContext, msg io.Reader) error {
return err return err
} }
} else { } else {
sqlm.Log.Debugf("failed to get inbox for %s: %v", rcpt, err)
return err return err
} }
} }
if err := mbox.CreateMessage([]string{}, time.Now(), &buf); err != nil { if err := mbox.CreateMessage([]string{}, time.Now(), &buf); err != nil {
sqlm.Log.Debugf("failed to save msg for %s: %v", rcpt, err)
return err return err
} }
} }

View file

@ -11,6 +11,7 @@ import (
"github.com/emersion/go-message" "github.com/emersion/go-message"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
"github.com/emersion/maddy/config" "github.com/emersion/maddy/config"
"github.com/emersion/maddy/log"
"github.com/emersion/maddy/module" "github.com/emersion/maddy/module"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -33,6 +34,7 @@ func (step submissionPrepareStep) Pass(ctx *module.DeliveryContext, msg io.Reade
if err != nil { if err != nil {
return nil, false, errors.New("Message-ID generation failed") return nil, false, errors.New("Message-ID generation failed")
} }
log.Debugf("adding missing Message-ID header to message from %s (%s)", ctx.SrcHostname, ctx.SrcAddr)
parsed.Header.Set("Message-ID", "<"+msgId.String()+"@"+ctx.OurHostname+">") parsed.Header.Set("Message-ID", "<"+msgId.String()+"@"+ctx.OurHostname+">")
} }
@ -90,6 +92,7 @@ func (step submissionPrepareStep) Pass(ctx *module.DeliveryContext, msg io.Reade
} }
} }
} else { } else {
log.Debugf("adding missing Date header to message from %s (%s)", ctx.SrcHostname, ctx.SrcAddr)
parsed.Header.Set("Date", time.Now().Format("Mon, 2 Jan 2006 15:04:05 -0700")) parsed.Header.Set("Date", time.Now().Format("Mon, 2 Jan 2006 15:04:05 -0700"))
} }