1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-17 14:37:02 +00:00

maillog: Introduce a special-purpose mail logging package

The default INFO logs are more oriented towards debugging and can be
a bit too verbose when looking for high-level information.

This patch introduces a new "maillog" package, used to log messages of
particular relevance to mail transmission at a higher level.
This commit is contained in:
Alberto Bertogli
2016-10-16 17:52:10 +01:00
parent cda11e03a5
commit febe96697a
20 changed files with 350 additions and 32 deletions

View File

@@ -14,6 +14,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/config" "blitiri.com.ar/go/chasquid/internal/config"
"blitiri.com.ar/go/chasquid/internal/courier" "blitiri.com.ar/go/chasquid/internal/courier"
"blitiri.com.ar/go/chasquid/internal/maillog"
"blitiri.com.ar/go/chasquid/internal/normalize" "blitiri.com.ar/go/chasquid/internal/normalize"
"blitiri.com.ar/go/chasquid/internal/smtpsrv" "blitiri.com.ar/go/chasquid/internal/smtpsrv"
"blitiri.com.ar/go/chasquid/internal/systemd" "blitiri.com.ar/go/chasquid/internal/systemd"
@@ -44,7 +45,7 @@ func main() {
conf, err := config.Load(*configDir + "/chasquid.conf") conf, err := config.Load(*configDir + "/chasquid.conf")
if err != nil { if err != nil {
glog.Fatalf("Error reading config") glog.Fatalf("Error reading config: %v", err)
} }
config.LogConfig(conf) config.LogConfig(conf)
@@ -55,6 +56,8 @@ func main() {
// fixes the point of reference. // fixes the point of reference.
os.Chdir(*configDir) os.Chdir(*configDir)
initMailLog(conf.MailLogPath)
if conf.MonitoringAddress != "" { if conf.MonitoringAddress != "" {
launchMonitoringServer(conf.MonitoringAddress) launchMonitoringServer(conf.MonitoringAddress)
} }
@@ -149,6 +152,23 @@ func loadAddresses(srv *smtpsrv.Server, addrs []string, ls []net.Listener, mode
} }
} }
func initMailLog(path string) {
var err error
if path == "<syslog>" {
maillog.Default, err = maillog.NewSyslog()
} else {
os.MkdirAll(filepath.Dir(path), 0775)
var f *os.File
f, err = os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
maillog.Default = maillog.New(f)
}
if err != nil {
glog.Fatalf("Error opening mail log: %v", err)
}
}
// Helper to load a single domain configuration into the server. // Helper to load a single domain configuration into the server.
func loadDomain(name, dir string, s *smtpsrv.Server) { func loadDomain(name, dir string, s *smtpsrv.Server) {
glog.Infof(" %s", name) glog.Infof(" %s", name)

View File

@@ -70,6 +70,10 @@ func Load(path string) (*Config, error) {
c.DropCharacters = "." c.DropCharacters = "."
} }
if c.MailLogPath == "" {
c.MailLogPath = "<syslog>"
}
return c, nil return c, nil
} }
@@ -84,4 +88,5 @@ func LogConfig(c *Config) {
glog.Infof(" Data directory: %s", c.DataDir) glog.Infof(" Data directory: %s", c.DataDir)
glog.Infof(" Suffix separators: %s", c.SuffixSeparators) glog.Infof(" Suffix separators: %s", c.SuffixSeparators)
glog.Infof(" Drop characters: %s", c.DropCharacters) glog.Infof(" Drop characters: %s", c.DropCharacters)
glog.Infof(" Mail log: %s", c.MailLogPath)
} }

View File

@@ -68,13 +68,19 @@ type Config struct {
// Suffix separator, to perform suffix removal of local users. // Suffix separator, to perform suffix removal of local users.
// For example, if you set this to "-+", email to local user // For example, if you set this to "-+", email to local user
// "user-blah" and "user+blah" will be delivered to "user". // "user-blah" and "user+blah" will be delivered to "user".
// Default: none. // Including "+" is strongly encouraged, as it is assumed for email
// forwarding.
// Default: "+".
SuffixSeparators string `protobuf:"bytes,9,opt,name=suffix_separators,json=suffixSeparators" json:"suffix_separators,omitempty"` SuffixSeparators string `protobuf:"bytes,9,opt,name=suffix_separators,json=suffixSeparators" json:"suffix_separators,omitempty"`
// Characters to drop from the user part on local emails. // Characters to drop from the user part on local emails.
// For example, if you set this to "._", email to local user // For example, if you set this to "._", email to local user
// "u.se_r" will be delivered to "user". // "u.se_r" will be delivered to "user".
// Default: none. // Default: ".".
DropCharacters string `protobuf:"bytes,10,opt,name=drop_characters,json=dropCharacters" json:"drop_characters,omitempty"` DropCharacters string `protobuf:"bytes,10,opt,name=drop_characters,json=dropCharacters" json:"drop_characters,omitempty"`
// Path where to write the mail log to.
// If "<syslog>", log using the syslog (at MAIL|INFO priority).
// Default: <syslog>
MailLogPath string `protobuf:"bytes,11,opt,name=mail_log_path,json=mailLogPath" json:"mail_log_path,omitempty"`
} }
func (m *Config) Reset() { *m = Config{} } func (m *Config) Reset() { *m = Config{} }
@@ -89,24 +95,25 @@ func init() {
func init() { proto.RegisterFile("config.proto", fileDescriptor0) } func init() { proto.RegisterFile("config.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 296 bytes of a gzipped FileDescriptorProto // 317 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x91, 0x41, 0x4e, 0xeb, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x91, 0xdf, 0x4e, 0xc2, 0x30,
0x10, 0x86, 0xd5, 0x97, 0x47, 0x9a, 0x9a, 0x02, 0xad, 0x05, 0xc2, 0xb0, 0x2a, 0x6c, 0x40, 0x42, 0x14, 0xc6, 0x83, 0x53, 0xfe, 0x14, 0x50, 0x68, 0x34, 0x56, 0xaf, 0x90, 0x1b, 0x4d, 0x8c, 0xde,
0xb0, 0x41, 0x88, 0x75, 0x68, 0xb6, 0x6c, 0xda, 0x03, 0x58, 0x4e, 0xec, 0xa4, 0x23, 0xd5, 0x76, 0x18, 0xe3, 0x35, 0xc2, 0xa5, 0x26, 0x06, 0x1e, 0xa0, 0xe9, 0x58, 0x19, 0x27, 0x59, 0xdb, 0xa5,
0x64, 0x1b, 0x54, 0xb8, 0x14, 0x57, 0x64, 0xe2, 0x42, 0x8a, 0x04, 0xcb, 0xf9, 0xfe, 0x6f, 0xec, 0xad, 0x06, 0x7d, 0x4d, 0x5f, 0xc8, 0xb3, 0x83, 0x0e, 0x13, 0xbd, 0x3c, 0xbf, 0xef, 0x77, 0xb6,
0xf1, 0x98, 0x8c, 0x2b, 0x6b, 0x6a, 0x68, 0xee, 0x5a, 0x67, 0x83, 0xbd, 0xfc, 0x48, 0x48, 0x3a, 0x6f, 0x67, 0xac, 0xb7, 0x74, 0x76, 0x05, 0xf9, 0x6d, 0xe9, 0x5d, 0x74, 0xe3, 0xcf, 0x84, 0x35,
0x8f, 0x80, 0x9e, 0x93, 0x6c, 0x65, 0x7d, 0x30, 0x42, 0x2b, 0x36, 0x98, 0x0d, 0xae, 0x47, 0x8b, 0xa7, 0x04, 0xf8, 0x39, 0x6b, 0xaf, 0x5d, 0x88, 0x56, 0x19, 0x2d, 0x1a, 0xa3, 0xc6, 0x55, 0x67,
0xbe, 0xa6, 0x57, 0x64, 0xa2, 0xc5, 0x86, 0x4b, 0x11, 0x04, 0xf7, 0xf0, 0xae, 0xb8, 0x2e, 0xd9, 0x5e, 0xcf, 0xfc, 0x92, 0x0d, 0x8c, 0xda, 0xc8, 0x4c, 0x45, 0x25, 0x03, 0x7c, 0x68, 0x69, 0x52,
0x3f, 0x74, 0x92, 0xc5, 0x01, 0xf2, 0x02, 0xf1, 0x12, 0xe9, 0x73, 0x49, 0x2f, 0xc8, 0xd8, 0xeb, 0xb1, 0x87, 0x4e, 0x32, 0xef, 0x23, 0x9f, 0x21, 0x5e, 0x20, 0x7d, 0x4e, 0xf9, 0x05, 0xeb, 0x05,
0xd0, 0x72, 0x21, 0xa5, 0x53, 0xde, 0xb3, 0x64, 0x96, 0xe0, 0x41, 0xfb, 0x1d, 0xcb, 0xb7, 0x88, 0x13, 0x4b, 0xa9, 0xb2, 0xcc, 0xeb, 0x10, 0x44, 0x32, 0x4a, 0xf0, 0x41, 0xdd, 0x8a, 0x4d, 0xb6,
0xde, 0x12, 0xea, 0x5f, 0x4a, 0x0d, 0xde, 0x83, 0x35, 0xbd, 0xf8, 0x3f, 0x8a, 0xd3, 0x5d, 0xf2, 0x88, 0xdf, 0x30, 0x1e, 0x5e, 0x53, 0x03, 0x21, 0x80, 0xb3, 0xb5, 0xb8, 0x4f, 0xe2, 0x70, 0x97,
0x43, 0xd7, 0xd6, 0x40, 0xb0, 0x0e, 0x4c, 0xd3, 0xeb, 0x7b, 0x71, 0xc0, 0xe9, 0x2e, 0xf9, 0xd6, 0xfc, 0xd2, 0x8d, 0xb3, 0x10, 0x9d, 0x07, 0x9b, 0xd7, 0xfa, 0x01, 0x15, 0x1c, 0xee, 0x92, 0x1f,
0x1f, 0xc8, 0xa9, 0x16, 0xb0, 0xe6, 0x52, 0xad, 0xe1, 0x55, 0xb9, 0x37, 0x2e, 0x1a, 0x65, 0x02, 0xfd, 0x9e, 0x9d, 0x1a, 0x05, 0x85, 0xcc, 0x74, 0x01, 0x6f, 0xda, 0xbf, 0x4b, 0x95, 0x6b, 0x1b,
0x2f, 0xc1, 0xb0, 0x34, 0xf6, 0x1c, 0x77, 0x71, 0xf1, 0x95, 0xe6, 0x5d, 0xf8, 0x04, 0x86, 0x3e, 0x65, 0x0a, 0x56, 0x34, 0x69, 0xe7, 0xb8, 0x8a, 0x67, 0xdf, 0xe9, 0xa4, 0x0a, 0x1f, 0xc1, 0xf2,
0x12, 0xf6, 0x57, 0x9b, 0x70, 0x8d, 0x67, 0xc3, 0x38, 0xda, 0xc9, 0xaf, 0xbe, 0x1c, 0x43, 0x7a, 0x07, 0x26, 0xfe, 0x5b, 0x53, 0x3e, 0x0f, 0xa2, 0x45, 0xd5, 0x4e, 0xfe, 0xec, 0x4d, 0x30, 0xe4,
0x46, 0xb2, 0xb8, 0x15, 0x09, 0x8e, 0x65, 0xf1, 0x82, 0x61, 0x57, 0x17, 0xe0, 0xe8, 0x0d, 0xc1, 0x67, 0xac, 0x4d, 0x57, 0xc9, 0xc0, 0x8b, 0x36, 0xbd, 0xa0, 0x55, 0xcd, 0x33, 0xf0, 0xfc, 0x9a,
0xe7, 0xd4, 0x35, 0x6c, 0xb8, 0x57, 0xad, 0x70, 0x02, 0x07, 0xf5, 0x6c, 0x14, 0x9d, 0xc9, 0x36, 0xe1, 0xe7, 0xac, 0x56, 0xb0, 0x91, 0x41, 0x97, 0xca, 0x2b, 0x2c, 0x1a, 0x44, 0x87, 0x9c, 0xc1,
0x58, 0xf6, 0x1c, 0x37, 0x7c, 0x24, 0x9d, 0x6d, 0x79, 0xb5, 0x42, 0x52, 0x05, 0x85, 0x2a, 0x89, 0x36, 0x58, 0xd4, 0x1c, 0x2f, 0x7c, 0x94, 0x79, 0x57, 0xca, 0xe5, 0x1a, 0xc9, 0x32, 0x6a, 0x54,
0xea, 0x61, 0x87, 0xe7, 0x3d, 0x2d, 0xd3, 0xf8, 0x71, 0xf7, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x19, 0xa9, 0x87, 0x15, 0x9e, 0xd6, 0x94, 0x8f, 0x59, 0x9f, 0x9a, 0x16, 0x2e, 0x97, 0xa5, 0x8a,
0xd1, 0x68, 0xa2, 0x22, 0xc8, 0x01, 0x00, 0x00, 0x6b, 0xd1, 0x25, 0xad, 0x5b, 0xc1, 0x27, 0x97, 0xbf, 0x20, 0x4a, 0x9b, 0xf4, 0x73, 0xef, 0xbe,
0x02, 0x00, 0x00, 0xff, 0xff, 0x04, 0x0f, 0xa4, 0xff, 0xec, 0x01, 0x00, 0x00,
} }

View File

@@ -59,5 +59,10 @@ message Config {
// "u.se_r" will be delivered to "user". // "u.se_r" will be delivered to "user".
// Default: ".". // Default: ".".
string drop_characters = 10; string drop_characters = 10;
// Path where to write the mail log to.
// If "<syslog>", log using the syslog (at MAIL|INFO priority).
// Default: <syslog>
string mail_log_path = 11;
} }

136
internal/maillog/maillog.go Normal file
View File

@@ -0,0 +1,136 @@
// Package maillog implements a log specifically for email.
package maillog
import (
"fmt"
"io"
"io/ioutil"
"log/syslog"
"net"
"sync"
"time"
"github.com/golang/glog"
"blitiri.com.ar/go/chasquid/internal/trace"
)
// Global event logs.
var (
authLog = trace.NewEventLog("Authentication", "Incoming SMTP")
)
// A writer that prepends timing information.
type timedWriter struct {
w io.Writer
}
func (t timedWriter) Write(b []byte) (int, error) {
fmt.Fprintf(t.w, "%s ", time.Now().Format("2006-01-02 15:04:05.000000"))
return t.w.Write(b)
}
type Logger struct {
w io.Writer
once sync.Once
}
func New(w io.Writer) *Logger {
return &Logger{w: timedWriter{w}}
}
func NewSyslog() (*Logger, error) {
w, err := syslog.New(syslog.LOG_INFO|syslog.LOG_MAIL, "chasquid")
if err != nil {
return nil, err
}
l := &Logger{w: w}
return l, nil
}
func (l *Logger) printf(format string, args ...interface{}) {
_, err := fmt.Fprintf(l.w, format, args...)
if err != nil {
l.once.Do(func() {
glog.Errorf("failed to write to maillog: %v", err)
glog.Error("(will not report this again)")
})
}
}
func (l *Logger) Listening(a string) {
l.printf("daemon listening on %s\n", a)
}
func (l *Logger) Auth(netAddr net.Addr, user string, successful bool) {
res := "successful"
if !successful {
res = "failed"
}
msg := fmt.Sprintf("%s authentication %s for %s\n", netAddr, res, user)
l.printf(msg)
authLog.Debugf(msg)
}
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)
}
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)
}
func (l *Logger) SendAttempt(id, from, to string, err error, permanent bool) {
if err == nil {
l.printf("%s from=%s to=%s sent successfully\n", id, from, to)
} else {
t := "(temporary)"
if permanent {
t = "(permanent)"
}
l.printf("%s from=%s to=%s sent failed %s: %v\n", id, from, to, t, err)
}
}
func (l *Logger) QueueLoop(id string, nextDelay time.Duration) {
if nextDelay > 0 {
l.printf("%s completed loop, next in %v\n", id, nextDelay)
} else {
l.printf("%s all done\n", id)
}
}
// The default logger used in the following top-level functions.
var Default *Logger = New(ioutil.Discard)
func Listening(a string) {
Default.Listening(a)
}
func Auth(netAddr net.Addr, user string, successful bool) {
Default.Auth(netAddr, user, successful)
}
func Rejected(netAddr net.Addr, from string, to []string, err string) {
Default.Rejected(netAddr, from, to, err)
}
func Queued(netAddr net.Addr, from string, to []string, id string) {
Default.Queued(netAddr, from, to, id)
}
func SendAttempt(id, from, to string, err error, permanent bool) {
Default.SendAttempt(id, from, to, err, permanent)
}
func QueueLoop(id string, nextDelay time.Duration) {
Default.QueueLoop(id, nextDelay)
}

View File

@@ -0,0 +1,116 @@
package maillog
import (
"bytes"
"fmt"
"net"
"strings"
"testing"
"time"
)
var netAddr = &net.TCPAddr{
IP: net.ParseIP("1.2.3.4"),
Port: 4321,
}
func expect(t *testing.T, buf *bytes.Buffer, s string) {
if strings.Contains(buf.String(), s) {
return
}
t.Errorf("buffer mismatch:")
t.Errorf(" expected to contain: %q", s)
t.Errorf(" got: %q", buf.String())
}
func TestLogger(t *testing.T) {
buf := &bytes.Buffer{}
l := New(buf)
l.Listening("1.2.3.4:4321")
expect(t, buf, "daemon listening on 1.2.3.4:4321")
buf.Reset()
l.Auth(netAddr, "user@domain", false)
expect(t, buf, "1.2.3.4:4321 authentication failed for user@domain")
buf.Reset()
l.Auth(netAddr, "user@domain", true)
expect(t, buf, "1.2.3.4:4321 authentication successful for user@domain")
buf.Reset()
l.Rejected(netAddr, "from", []string{"to1", "to2"}, "error")
expect(t, buf, "1.2.3.4:4321 rejected from=from to=[to1 to2] - error")
buf.Reset()
l.Queued(netAddr, "from", []string{"to1", "to2"}, "qid")
expect(t, buf, "qid from=from queued ip=1.2.3.4:4321 to=[to1 to2]")
buf.Reset()
l.SendAttempt("qid", "from", "to", nil, false)
expect(t, buf, "qid from=from to=to sent successfully")
buf.Reset()
l.SendAttempt("qid", "from", "to", fmt.Errorf("error"), false)
expect(t, buf, "qid from=from to=to sent failed (temporary): error")
buf.Reset()
l.SendAttempt("qid", "from", "to", fmt.Errorf("error"), true)
expect(t, buf, "qid from=from to=to sent failed (permanent): error")
buf.Reset()
l.QueueLoop("qid", 17*time.Second)
expect(t, buf, "qid completed loop, next in 17s")
buf.Reset()
l.QueueLoop("qid", 0)
expect(t, buf, "qid all done")
buf.Reset()
}
// Test that the default actions go reasonably to the default logger.
// Unfortunately this is almost the same as TestLogger.
func TestDefault(t *testing.T) {
buf := &bytes.Buffer{}
Default = New(buf)
Listening("1.2.3.4:4321")
expect(t, buf, "daemon listening on 1.2.3.4:4321")
buf.Reset()
Auth(netAddr, "user@domain", false)
expect(t, buf, "1.2.3.4:4321 authentication failed for user@domain")
buf.Reset()
Auth(netAddr, "user@domain", true)
expect(t, buf, "1.2.3.4:4321 authentication successful for user@domain")
buf.Reset()
Rejected(netAddr, "from", []string{"to1", "to2"}, "error")
expect(t, buf, "1.2.3.4:4321 rejected from=from to=[to1 to2] - error")
buf.Reset()
Queued(netAddr, "from", []string{"to1", "to2"}, "qid")
expect(t, buf, "qid from=from queued ip=1.2.3.4:4321 to=[to1 to2]")
buf.Reset()
SendAttempt("qid", "from", "to", nil, false)
expect(t, buf, "qid from=from to=to sent successfully")
buf.Reset()
SendAttempt("qid", "from", "to", fmt.Errorf("error"), false)
expect(t, buf, "qid from=from to=to sent failed (temporary): error")
buf.Reset()
SendAttempt("qid", "from", "to", fmt.Errorf("error"), true)
expect(t, buf, "qid from=from to=to sent failed (permanent): error")
buf.Reset()
QueueLoop("qid", 17*time.Second)
expect(t, buf, "qid completed loop, next in 17s")
buf.Reset()
QueueLoop("qid", 0)
expect(t, buf, "qid all done")
buf.Reset()
}

View File

@@ -23,6 +23,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/aliases" "blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/courier" "blitiri.com.ar/go/chasquid/internal/courier"
"blitiri.com.ar/go/chasquid/internal/envelope" "blitiri.com.ar/go/chasquid/internal/envelope"
"blitiri.com.ar/go/chasquid/internal/maillog"
"blitiri.com.ar/go/chasquid/internal/protoio" "blitiri.com.ar/go/chasquid/internal/protoio"
"blitiri.com.ar/go/chasquid/internal/set" "blitiri.com.ar/go/chasquid/internal/set"
"blitiri.com.ar/go/chasquid/internal/trace" "blitiri.com.ar/go/chasquid/internal/trace"
@@ -328,6 +329,7 @@ func (item *Item) SendLoop(q *Queue) {
delay = nextDelay(delay) delay = nextDelay(delay)
tr.Printf("waiting for %v", delay) tr.Printf("waiting for %v", delay)
maillog.QueueLoop(item.ID, delay)
time.Sleep(delay) time.Sleep(delay)
} }
@@ -337,6 +339,7 @@ func (item *Item) SendLoop(q *Queue) {
} }
tr.Printf("all done") tr.Printf("all done")
maillog.QueueLoop(item.ID, 0)
q.Remove(item.ID) q.Remove(item.ID)
} }
@@ -353,12 +356,15 @@ func (item *Item) sendOneRcpt(wg *sync.WaitGroup, tr *trace.Trace, q *Queue, rcp
rcpt.LastFailureMessage = err.Error() rcpt.LastFailureMessage = err.Error()
if permanent { if permanent {
tr.Errorf("%s permanent error: %v", to, err) tr.Errorf("%s permanent error: %v", to, err)
maillog.SendAttempt(item.ID, item.From, to, err, true)
rcpt.Status = Recipient_FAILED rcpt.Status = Recipient_FAILED
} else { } else {
tr.Printf("%s temporary error: %v", to, err) tr.Printf("%s temporary error: %v", to, err)
maillog.SendAttempt(item.ID, item.From, to, err, false)
} }
} else { } else {
tr.Printf("%s sent", to) tr.Printf("%s sent", to)
maillog.SendAttempt(item.ID, item.From, to, nil, false)
rcpt.Status = Recipient_SENT rcpt.Status = Recipient_SENT
} }
item.Unlock() item.Unlock()

View File

@@ -22,6 +22,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/auth" "blitiri.com.ar/go/chasquid/internal/auth"
"blitiri.com.ar/go/chasquid/internal/domaininfo" "blitiri.com.ar/go/chasquid/internal/domaininfo"
"blitiri.com.ar/go/chasquid/internal/envelope" "blitiri.com.ar/go/chasquid/internal/envelope"
"blitiri.com.ar/go/chasquid/internal/maillog"
"blitiri.com.ar/go/chasquid/internal/normalize" "blitiri.com.ar/go/chasquid/internal/normalize"
"blitiri.com.ar/go/chasquid/internal/queue" "blitiri.com.ar/go/chasquid/internal/queue"
"blitiri.com.ar/go/chasquid/internal/set" "blitiri.com.ar/go/chasquid/internal/set"
@@ -42,11 +43,6 @@ var (
hookResults = expvar.NewMap("chasquid/smtpIn/hookResults") hookResults = expvar.NewMap("chasquid/smtpIn/hookResults")
) )
// Global event logs.
var (
authLog = trace.NewEventLog("Authentication", "Incoming SMTP")
)
// Mode for a socket (listening or connection). // Mode for a socket (listening or connection).
// We keep them distinct, as policies can differ between them. // We keep them distinct, as policies can differ between them.
type SocketMode string type SocketMode string
@@ -351,16 +347,22 @@ func (c *Conn) MAIL(params string) (code int, msg string) {
c.spfResult, c.spfError = c.checkSPF(addr) c.spfResult, c.spfError = c.checkSPF(addr)
if c.spfResult == spf.Fail { if c.spfResult == spf.Fail {
// https://tools.ietf.org/html/rfc7208#section-8.4 // https://tools.ietf.org/html/rfc7208#section-8.4
maillog.Rejected(c.conn.RemoteAddr(), addr, nil,
fmt.Sprintf("failed SPF: %v", c.spfError))
return 550, fmt.Sprintf( return 550, fmt.Sprintf(
"SPF check failed: %v", c.spfError) "SPF check failed: %v", c.spfError)
} }
if !c.secLevelCheck(addr) { if !c.secLevelCheck(addr) {
maillog.Rejected(c.conn.RemoteAddr(), addr, nil,
"security level check failed")
return 550, "security level check failed" return 550, "security level check failed"
} }
addr, err = normalize.DomainToUnicode(addr) addr, err = normalize.DomainToUnicode(addr)
if err != nil { if err != nil {
maillog.Rejected(c.conn.RemoteAddr(), addr, nil,
fmt.Sprintf("malformed address: %v", err))
return 501, "malformed address (IDNA conversion failed)" return 501, "malformed address (IDNA conversion failed)"
} }
} }
@@ -461,16 +463,22 @@ func (c *Conn) RCPT(params string) (code int, msg string) {
localDst := envelope.DomainIn(addr, c.localDomains) localDst := envelope.DomainIn(addr, c.localDomains)
if !localDst && !c.completedAuth { if !localDst && !c.completedAuth {
maillog.Rejected(c.conn.RemoteAddr(), c.mailFrom, []string{addr},
"relay not allowed")
return 503, "relay not allowed" return 503, "relay not allowed"
} }
if localDst { if localDst {
addr, err = normalize.Addr(addr) addr, err = normalize.Addr(addr)
if err != nil { if err != nil {
maillog.Rejected(c.conn.RemoteAddr(), c.mailFrom, []string{addr},
fmt.Sprintf("invalid address: %v", err))
return 550, "recipient invalid, please check the address for typos" return 550, "recipient invalid, please check the address for typos"
} }
if !c.userExists(addr) { if !c.userExists(addr) {
maillog.Rejected(c.conn.RemoteAddr(), c.mailFrom, []string{addr},
"local user does not exist")
return 550, "recipient unknown, please check the address for typos" return 550, "recipient unknown, please check the address for typos"
} }
} }
@@ -516,6 +524,7 @@ func (c *Conn) DATA(params string) (code int, msg string) {
c.tr.Debugf("-> ... %d bytes of data", len(c.data)) c.tr.Debugf("-> ... %d bytes of data", len(c.data))
if err := checkData(c.data); err != nil { if err := checkData(c.data); err != nil {
maillog.Rejected(c.conn.RemoteAddr(), c.mailFrom, c.rcptTo, err.Error())
return 554, err.Error() return 554, err.Error()
} }
@@ -523,6 +532,7 @@ func (c *Conn) DATA(params string) (code int, msg string) {
hookOut, err := c.runPostDataHook(c.data) hookOut, err := c.runPostDataHook(c.data)
if err != nil { if err != nil {
maillog.Rejected(c.conn.RemoteAddr(), c.mailFrom, c.rcptTo, err.Error())
return 554, err.Error() return 554, err.Error()
} }
c.data = append(hookOut, c.data...) c.data = append(hookOut, c.data...)
@@ -535,6 +545,7 @@ func (c *Conn) DATA(params string) (code int, msg string) {
} }
c.tr.Printf("Queued from %s to %s - %s", c.mailFrom, c.rcptTo, msgID) c.tr.Printf("Queued from %s to %s - %s", c.mailFrom, c.rcptTo, msgID)
maillog.Queued(c.conn.RemoteAddr(), c.mailFrom, c.rcptTo, msgID)
// It is very important that we reset the envelope before returning, // It is very important that we reset the envelope before returning,
// so clients can send other emails right away without needing to RSET. // so clients can send other emails right away without needing to RSET.
@@ -825,13 +836,11 @@ func (c *Conn) AUTH(params string) (code int, msg string) {
c.authUser = user c.authUser = user
c.authDomain = domain c.authDomain = domain
c.completedAuth = true c.completedAuth = true
authLog.Debugf("%s successful for %s@%s", maillog.Auth(c.conn.RemoteAddr(), user+"@"+domain, true)
c.conn.RemoteAddr().String(), user, domain)
return 235, "" return 235, ""
} }
authLog.Debugf("%s failed for %s@%s", maillog.Auth(c.conn.RemoteAddr(), user+"@"+domain, false)
c.conn.RemoteAddr().String(), user, domain)
return 535, "Incorrect user or password" return 535, "Incorrect user or password"
} }

View File

@@ -11,6 +11,7 @@ import (
"blitiri.com.ar/go/chasquid/internal/aliases" "blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/courier" "blitiri.com.ar/go/chasquid/internal/courier"
"blitiri.com.ar/go/chasquid/internal/domaininfo" "blitiri.com.ar/go/chasquid/internal/domaininfo"
"blitiri.com.ar/go/chasquid/internal/maillog"
"blitiri.com.ar/go/chasquid/internal/queue" "blitiri.com.ar/go/chasquid/internal/queue"
"blitiri.com.ar/go/chasquid/internal/set" "blitiri.com.ar/go/chasquid/internal/set"
"blitiri.com.ar/go/chasquid/internal/userdb" "blitiri.com.ar/go/chasquid/internal/userdb"
@@ -168,6 +169,7 @@ func (s *Server) ListenAndServe() {
} }
glog.Infof("Server listening on %s (%v)", addr, m) glog.Infof("Server listening on %s (%v)", addr, m)
maillog.Listening(addr)
go s.serve(l, m) go s.serve(l, m)
} }
} }
@@ -175,6 +177,7 @@ func (s *Server) ListenAndServe() {
for m, ls := range s.listeners { for m, ls := range s.listeners {
for _, l := range ls { for _, l := range ls {
glog.Infof("Server listening on %s (%v, via systemd)", l.Addr(), m) glog.Infof("Server listening on %s (%v, via systemd)", l.Addr(), m)
maillog.Listening(l.Addr().String())
go s.serve(l, m) go s.serve(l, m)
} }
} }

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data" data_dir: "../.data"
mail_log_path: "../.logs/mail_log"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data" data_dir: "../.data"
mail_log_path: "../.logs/mail_log"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data" data_dir: "../.data"
mail_log_path: "../.logs/mail_log"

View File

@@ -6,6 +6,7 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data" data_dir: "../.data"
mail_log_path: "../.logs/mail_log"
suffix_separators: "+-" suffix_separators: "+-"
drop_characters: "._" drop_characters: "._"

View File

@@ -8,6 +8,7 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data" data_dir: "../.data"
mail_log_path: "../.logs/mail_log"
suffix_separators: "+-" suffix_separators: "+-"
drop_characters: "._" drop_characters: "._"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data-A" data_dir: "../.data-A"
mail_log_path: "../.logs/mail_log-A"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data-B" data_dir: "../.data-B"
mail_log_path: "../.logs/mail_log-B"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data" data_dir: "../.data"
mail_log_path: "../.logs/mail_log"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data-A" data_dir: "../.data-A"
mail_log_path: "../.logs/mail_log-A"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data-B" data_dir: "../.data-B"
mail_log_path: "../.logs/mail_log-B"

View File

@@ -6,3 +6,4 @@ mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%to%" mail_delivery_agent_args: "%to%"
data_dir: "../.data" data_dir: "../.data"
mail_log_path: "../.logs/mail_log"