In a few places, we call Printf-like functions, but for the format we
use either non-format messages (which is not tidy, but okay), or
variable messages (which can be problematic if they contain %-format
directives).
The patch fixes the calls by either moving to Print-like functions, or
using `Printf("%s", message)` instead.
These were found by a combination of `go vet` (which complains about
"non-constant format string in call"), and manual inspection.
181 lines
4.6 KiB
Go
181 lines
4.6 KiB
Go
// Package maillog implements a log specifically for email.
|
|
package maillog
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log/syslog"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"blitiri.com.ar/go/chasquid/internal/trace"
|
|
"blitiri.com.ar/go/log"
|
|
)
|
|
|
|
// Global event logs.
|
|
var (
|
|
authLog = trace.New("Authentication", "Incoming SMTP")
|
|
)
|
|
|
|
// Logger contains a backend used to log data to, such as a file or syslog.
|
|
// It implements various user-friendly methods for logging mail information to
|
|
// it.
|
|
type Logger struct {
|
|
inner *log.Logger
|
|
once sync.Once
|
|
}
|
|
|
|
// New creates a new Logger which will write messages to the given writer.
|
|
func New(w io.WriteCloser) *Logger {
|
|
inner := log.New(w)
|
|
|
|
// Don't include level or caller in the output, it doesn't add value for
|
|
// this type of log.
|
|
inner.LogLevel = false
|
|
inner.LogCaller = false
|
|
|
|
return &Logger{inner: inner}
|
|
}
|
|
|
|
// NewFile creates a new Logger which will write messages to the file at the
|
|
// given path.
|
|
func NewFile(path string) (*Logger, error) {
|
|
inner, err := log.NewFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Don't include level or caller in the output, it doesn't add value for
|
|
// this type of log.
|
|
inner.LogLevel = false
|
|
inner.LogCaller = false
|
|
|
|
return &Logger{inner: inner}, nil
|
|
}
|
|
|
|
// NewSyslog creates a new Logger which will write messages to syslog.
|
|
func NewSyslog() (*Logger, error) {
|
|
inner, err := log.NewSyslog(syslog.LOG_INFO|syslog.LOG_MAIL, "chasquid")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Like NewFile, we skip level and caller. In addition, we skip time, as
|
|
// syslog usually adds that on its own.
|
|
inner.LogLevel = false
|
|
inner.LogCaller = false
|
|
inner.LogTime = false
|
|
|
|
return &Logger{inner: inner}, nil
|
|
}
|
|
|
|
func (l *Logger) printf(format string, args ...interface{}) {
|
|
err := l.inner.Log(log.Info, 2, format, args...)
|
|
if err != nil {
|
|
l.once.Do(func() {
|
|
log.Errorf("failed to write to maillog: %v", err)
|
|
log.Errorf("(will not report this again)")
|
|
})
|
|
}
|
|
}
|
|
|
|
// Reopen the underlying logger.
|
|
func (l *Logger) Reopen() error {
|
|
return l.inner.Reopen()
|
|
}
|
|
|
|
// Listening logs that the daemon is listening on the given address.
|
|
func (l *Logger) Listening(a string) {
|
|
l.printf("daemon listening on %s\n", a)
|
|
}
|
|
|
|
// Auth logs an authentication request.
|
|
func (l *Logger) Auth(netAddr net.Addr, user string, successful bool) {
|
|
res := "succeeded"
|
|
if !successful {
|
|
res = "failed"
|
|
}
|
|
msg := fmt.Sprintf("%s auth %s for %s\n", netAddr, res, user)
|
|
l.printf("%s", msg)
|
|
authLog.Debugf("%s", msg)
|
|
}
|
|
|
|
// Rejected logs that we've rejected an email.
|
|
func (l *Logger) Rejected(netAddr net.Addr, from string, to []string, err string) {
|
|
if from != "" {
|
|
from = fmt.Sprintf(" from=%s", from)
|
|
}
|
|
toStr := ""
|
|
if len(to) > 0 {
|
|
toStr = fmt.Sprintf(" to=%v", to)
|
|
}
|
|
l.printf("%s rejected%s%s - %v\n", netAddr, from, toStr, err)
|
|
}
|
|
|
|
// Queued logs that we have queued an email.
|
|
func (l *Logger) Queued(netAddr net.Addr, from string, to []string, id string) {
|
|
l.printf("%s from=%s queued ip=%s to=%v\n", id, from, netAddr, to)
|
|
}
|
|
|
|
// SendAttempt logs that we have attempted to send an email.
|
|
func (l *Logger) SendAttempt(id, from, to string, err error, permanent bool) {
|
|
if err == nil {
|
|
l.printf("%s from=%s to=%s sent\n", id, from, to)
|
|
} else {
|
|
t := "(temporary)"
|
|
if permanent {
|
|
t = "(permanent)"
|
|
}
|
|
l.printf("%s from=%s to=%s failed %s: %v\n", id, from, to, t, err)
|
|
}
|
|
}
|
|
|
|
// QueueLoop logs that we have completed a queue loop.
|
|
func (l *Logger) QueueLoop(id, from string, nextDelay time.Duration) {
|
|
if nextDelay > 0 {
|
|
l.printf("%s from=%s completed loop, next in %v\n", id, from, nextDelay)
|
|
} else {
|
|
l.printf("%s from=%s all done\n", id, from)
|
|
}
|
|
}
|
|
|
|
type nopCloser struct {
|
|
io.Writer
|
|
}
|
|
|
|
func (nopCloser) Close() error { return nil }
|
|
|
|
// Default logger, used in the following top-level functions.
|
|
var Default *Logger = New(nopCloser{io.Discard})
|
|
|
|
// Listening logs that the daemon is listening on the given address.
|
|
func Listening(a string) {
|
|
Default.Listening(a)
|
|
}
|
|
|
|
// Auth logs an authentication request.
|
|
func Auth(netAddr net.Addr, user string, successful bool) {
|
|
Default.Auth(netAddr, user, successful)
|
|
}
|
|
|
|
// Rejected logs that we've rejected an email.
|
|
func Rejected(netAddr net.Addr, from string, to []string, err string) {
|
|
Default.Rejected(netAddr, from, to, err)
|
|
}
|
|
|
|
// Queued logs that we have queued an email.
|
|
func Queued(netAddr net.Addr, from string, to []string, id string) {
|
|
Default.Queued(netAddr, from, to, id)
|
|
}
|
|
|
|
// SendAttempt logs that we have attempted to send an email.
|
|
func SendAttempt(id, from, to string, err error, permanent bool) {
|
|
Default.SendAttempt(id, from, to, err, permanent)
|
|
}
|
|
|
|
// QueueLoop logs that we have completed a queue loop.
|
|
func QueueLoop(id, from string, nextDelay time.Duration) {
|
|
Default.QueueLoop(id, from, nextDelay)
|
|
}
|