1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-16 14:27:01 +00:00

chasquid: Load and resolve aliases

This patch integrates the aliases resolution into the daemon and queue.

The queue now will resolve aliases at Put time.
This commit is contained in:
Alberto Bertogli
2016-09-22 00:22:39 +01:00
parent bab8a8083c
commit c34c073c1c
15 changed files with 251 additions and 32 deletions

View File

@@ -19,6 +19,7 @@ import (
"syscall"
"time"
"blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/auth"
"blitiri.com.ar/go/chasquid/internal/config"
"blitiri.com.ar/go/chasquid/internal/courier"
@@ -80,6 +81,10 @@ func main() {
s.Hostname = conf.Hostname
s.MaxDataSize = conf.MaxDataSizeMb * 1024 * 1024
aliasesR := aliases.NewResolver()
aliasesR.SuffixSep = conf.SuffixSeparators
aliasesR.DropChars = conf.DropCharacters
// Load domains.
// They live inside the config directory, so the relative path works.
domainDirs, err := ioutil.ReadDir("domains/")
@@ -94,7 +99,7 @@ func main() {
for _, info := range domainDirs {
name := info.Name()
dir := filepath.Join("domains", name)
loadDomain(s, name, dir)
loadDomain(name, dir, s, aliasesR)
}
}
@@ -103,7 +108,7 @@ func main() {
// as a remote domain (for loops, alias resolutions, etc.).
s.AddDomain("localhost")
s.LoadQueue(conf.DataDir + "/queue")
s.InitQueue(conf.DataDir+"/queue", aliasesR)
// Load the addresses and listeners.
systemdLs, err := systemd.Listeners()
@@ -139,9 +144,10 @@ func loadAddresses(srv *Server, addrs []string, ls []net.Listener, mode SocketMo
}
// Helper to load a single domain configuration into the server.
func loadDomain(s *Server, name, dir string) {
func loadDomain(name, dir string, s *Server, aliasesR *aliases.Resolver) {
glog.Infof(" %s", name)
s.AddDomain(name)
aliasesR.AddDomain(name)
s.AddCerts(dir+"/cert.pem", dir+"/key.pem")
if _, err := os.Stat(dir + "/users"); err == nil {
@@ -154,6 +160,14 @@ func loadDomain(s *Server, name, dir string) {
// TODO: periodically reload the database.
}
}
if _, err := os.Stat(dir + "/aliases"); err == nil {
glog.Infof(" adding aliases")
err := aliasesR.AddAliasesFile(name, dir+"/aliases")
if err != nil {
glog.Errorf(" error: %v", err)
}
}
}
// Flush logs periodically, to help troubleshooting if there isn't that much
@@ -251,13 +265,24 @@ func (s *Server) AddUserDB(domain string, db *userdb.DB) {
s.userDBs[domain] = db
}
func (s *Server) LoadQueue(path string) {
q := queue.New(path, s.localDomains)
func (s *Server) InitQueue(path string, aliasesR *aliases.Resolver) {
q := queue.New(path, s.localDomains, aliasesR)
err := q.Load()
if err != nil {
glog.Fatalf("Error loading queue: %v", err)
}
s.queue = q
// Launch the periodic reload of aliases, now that the queue may care
// about them.
go func() {
for range time.Tick(1 * time.Minute) {
err := aliasesR.Reload()
if err != nil {
glog.Errorf("Error reloading aliases: %v")
}
}
}()
}
func (s *Server) getTLSConfig() (*tls.Config, error) {

View File

@@ -17,6 +17,7 @@ import (
"testing"
"time"
"blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/userdb"
"github.com/golang/glog"
@@ -428,7 +429,9 @@ func realMain(m *testing.M) int {
s.AddCerts(tmpDir+"/cert.pem", tmpDir+"/key.pem")
s.AddAddr(smtpAddr, ModeSMTP)
s.AddAddr(submissionAddr, ModeSubmission)
s.LoadQueue(tmpDir + "/queue")
ars := aliases.NewResolver()
s.InitQueue(tmpDir+"/queue", ars)
udb := userdb.New("/dev/null")
udb.AddUser("testuser", "testpasswd")

View File

@@ -191,6 +191,10 @@ func (v *Resolver) AddAliasesFile(domain, path string) error {
return nil
}
func (v *Resolver) AddAliasForTesting(addr, rcpt string, rType RType) {
v.aliases[addr] = append(v.aliases[addr], Recipient{rcpt, rType})
}
func (v *Resolver) Reload() error {
newAliases := map[string][]Recipient{}

View File

@@ -75,4 +75,6 @@ func logConfig(c *Config) {
glog.Infof(" Monitoring address: %s", c.MonitoringAddress)
glog.Infof(" MDA: %s %v", c.MailDeliveryAgentBin, c.MailDeliveryAgentArgs)
glog.Infof(" Data directory: %s", c.DataDir)
glog.Infof(" Suffix separators: %s", c.SuffixSeparators)
glog.Infof(" Drop characters: %s", c.DropCharacters)
}

View File

@@ -62,6 +62,16 @@ type Config struct {
// Directory where we store our persistent data.
// Default: "/var/lib/chasquid"
DataDir string `protobuf:"bytes,8,opt,name=data_dir,json=dataDir" json:"data_dir,omitempty"`
// Suffix separator, to perform suffix removal of local users.
// For example, if you set this to "-+", email to local user
// "user-blah" and "user+blah" will be delivered to "user".
// Default: none.
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.
// For example, if you set this to "._", email to local user
// "u.se_r" will be delivered to "user".
// Default: none.
DropCharacters string `protobuf:"bytes,10,opt,name=drop_characters,json=dropCharacters" json:"drop_characters,omitempty"`
}
func (m *Config) Reset() { *m = Config{} }
@@ -76,21 +86,24 @@ func init() {
func init() { proto.RegisterFile("config.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 251 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0x41, 0x2f, 0x04, 0x31,
0x14, 0xc7, 0xb3, 0x3b, 0xcc, 0x8e, 0x67, 0x25, 0xb6, 0x21, 0xca, 0x69, 0xb9, 0x70, 0xe1, 0x22,
0xe2, 0x3c, 0xcc, 0xd5, 0x65, 0x7d, 0x80, 0xa6, 0xd5, 0x1a, 0x2f, 0xd9, 0xb6, 0x9b, 0xbe, 0x12,
0x7c, 0x53, 0xdf, 0x46, 0x5b, 0xcc, 0x4a, 0x38, 0xbe, 0xff, 0xef, 0xf7, 0x6f, 0x5f, 0x1e, 0x4c,
0x1f, 0xbc, 0x7b, 0xc4, 0xfe, 0x62, 0x15, 0x7c, 0xf4, 0x27, 0x1f, 0x63, 0xa8, 0x6f, 0x4b, 0xc0,
0x8e, 0xa0, 0x79, 0xf2, 0x14, 0x9d, 0xb4, 0x86, 0x8f, 0xe6, 0xa3, 0xb3, 0xad, 0xc5, 0x30, 0xb3,
0x53, 0xd8, 0xb5, 0xf2, 0x55, 0x68, 0x19, 0xa5, 0x20, 0x7c, 0x37, 0xc2, 0x2a, 0x3e, 0x4e, 0x4e,
0xb5, 0xd8, 0x49, 0x79, 0x97, 0xe2, 0xfb, 0x94, 0xde, 0x29, 0x76, 0x0c, 0x53, 0xb2, 0x71, 0x25,
0xa4, 0xd6, 0xc1, 0x10, 0xf1, 0x6a, 0x5e, 0xa5, 0x87, 0xb6, 0x73, 0xd6, 0x7e, 0x45, 0xec, 0x1c,
0x18, 0x3d, 0x2b, 0x8b, 0x44, 0xe8, 0xdd, 0x20, 0x6e, 0x14, 0x71, 0xb6, 0x26, 0xbf, 0x74, 0xeb,
0x1d, 0x46, 0x1f, 0xd0, 0xf5, 0x83, 0xbe, 0x59, 0x16, 0x9c, 0xad, 0xc9, 0x8f, 0x7e, 0x05, 0x07,
0x56, 0xe2, 0x52, 0x68, 0xb3, 0xc4, 0x17, 0x13, 0xde, 0x84, 0xec, 0x8d, 0x8b, 0x42, 0xa1, 0xe3,
0x75, 0xe9, 0xec, 0x65, 0xdc, 0x7d, 0xd3, 0x36, 0xc3, 0x1b, 0x74, 0xec, 0x1a, 0xf8, 0x7f, 0x35,
0x19, 0x7a, 0xe2, 0x93, 0xb2, 0xda, 0xfe, 0x9f, 0x5e, 0x9b, 0x20, 0x3b, 0x84, 0xa6, 0x5c, 0x45,
0x63, 0xe0, 0x4d, 0xf9, 0x60, 0x92, 0xe7, 0x0e, 0x83, 0xaa, 0xcb, 0x89, 0x2f, 0x3f, 0x03, 0x00,
0x00, 0xff, 0xff, 0xc5, 0x2e, 0x79, 0xef, 0x72, 0x01, 0x00, 0x00,
// 296 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x91, 0x41, 0x4e, 0xeb, 0x30,
0x10, 0x86, 0xd5, 0x97, 0x47, 0x9a, 0x9a, 0x02, 0xad, 0x05, 0xc2, 0xb0, 0x2a, 0x6c, 0x40, 0x42,
0xb0, 0x41, 0x88, 0x75, 0x68, 0xb6, 0x6c, 0xda, 0x03, 0x58, 0x4e, 0xec, 0xa4, 0x23, 0xd5, 0x76,
0x64, 0x1b, 0x54, 0xb8, 0x14, 0x57, 0x64, 0xe2, 0x42, 0x8a, 0x04, 0xcb, 0xf9, 0xfe, 0x6f, 0xec,
0xf1, 0x98, 0x8c, 0x2b, 0x6b, 0x6a, 0x68, 0xee, 0x5a, 0x67, 0x83, 0xbd, 0xfc, 0x48, 0x48, 0x3a,
0x8f, 0x80, 0x9e, 0x93, 0x6c, 0x65, 0x7d, 0x30, 0x42, 0x2b, 0x36, 0x98, 0x0d, 0xae, 0x47, 0x8b,
0xbe, 0xa6, 0x57, 0x64, 0xa2, 0xc5, 0x86, 0x4b, 0x11, 0x04, 0xf7, 0xf0, 0xae, 0xb8, 0x2e, 0xd9,
0x3f, 0x74, 0x92, 0xc5, 0x01, 0xf2, 0x02, 0xf1, 0x12, 0xe9, 0x73, 0x49, 0x2f, 0xc8, 0xd8, 0xeb,
0xd0, 0x72, 0x21, 0xa5, 0x53, 0xde, 0xb3, 0x64, 0x96, 0xe0, 0x41, 0xfb, 0x1d, 0xcb, 0xb7, 0x88,
0xde, 0x12, 0xea, 0x5f, 0x4a, 0x0d, 0xde, 0x83, 0x35, 0xbd, 0xf8, 0x3f, 0x8a, 0xd3, 0x5d, 0xf2,
0x43, 0xd7, 0xd6, 0x40, 0xb0, 0x0e, 0x4c, 0xd3, 0xeb, 0x7b, 0x71, 0xc0, 0xe9, 0x2e, 0xf9, 0xd6,
0x1f, 0xc8, 0xa9, 0x16, 0xb0, 0xe6, 0x52, 0xad, 0xe1, 0x55, 0xb9, 0x37, 0x2e, 0x1a, 0x65, 0x02,
0x2f, 0xc1, 0xb0, 0x34, 0xf6, 0x1c, 0x77, 0x71, 0xf1, 0x95, 0xe6, 0x5d, 0xf8, 0x04, 0x86, 0x3e,
0x12, 0xf6, 0x57, 0x9b, 0x70, 0x8d, 0x67, 0xc3, 0x38, 0xda, 0xc9, 0xaf, 0xbe, 0x1c, 0x43, 0x7a,
0x46, 0xb2, 0xb8, 0x15, 0x09, 0x8e, 0x65, 0xf1, 0x82, 0x61, 0x57, 0x17, 0xe0, 0xe8, 0x0d, 0xc1,
0xe7, 0xd4, 0x35, 0x6c, 0xb8, 0x57, 0xad, 0x70, 0x02, 0x07, 0xf5, 0x6c, 0x14, 0x9d, 0xc9, 0x36,
0x58, 0xf6, 0x1c, 0x37, 0x7c, 0x24, 0x9d, 0x6d, 0x79, 0xb5, 0x42, 0x52, 0x05, 0x85, 0x2a, 0x89,
0xea, 0x61, 0x87, 0xe7, 0x3d, 0x2d, 0xd3, 0xf8, 0x71, 0xf7, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff,
0xd1, 0x68, 0xa2, 0x22, 0xc8, 0x01, 0x00, 0x00,
}

View File

@@ -42,5 +42,17 @@ message Config {
// Directory where we store our persistent data.
// Default: "/var/lib/chasquid"
string data_dir = 8;
// Suffix separator, to perform suffix removal of local users.
// For example, if you set this to "-+", email to local user
// "user-blah" and "user+blah" will be delivered to "user".
// Default: none.
string suffix_separators = 9;
// Characters to drop from the user part on local emails.
// For example, if you set this to "._", email to local user
// "u.se_r" will be delivered to "user".
// Default: none.
string drop_characters = 10;
}

View File

@@ -19,6 +19,7 @@ import (
"bytes"
"blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/courier"
"blitiri.com.ar/go/chasquid/internal/envelope"
"blitiri.com.ar/go/chasquid/internal/protoio"
@@ -91,10 +92,13 @@ type Queue struct {
// Path where we store the queue.
path string
// Aliases resolver.
aliases *aliases.Resolver
}
// Load the queue and launch the sending loops on startup.
func New(path string, localDomains *set.String) *Queue {
func New(path string, localDomains *set.String, aliases *aliases.Resolver) *Queue {
os.MkdirAll(path, 0700)
return &Queue{
@@ -103,6 +107,7 @@ func New(path string, localDomains *set.String) *Queue {
remoteC: &courier.SMTP{},
localDomains: localDomains,
path: path,
aliases: aliases,
}
}
@@ -151,11 +156,30 @@ func (q *Queue) Put(from string, to []string, data []byte) (string, error) {
}
for _, t := range to {
item.Rcpt = append(item.Rcpt, &Recipient{
Address: t,
Type: Recipient_EMAIL,
Status: Recipient_PENDING,
})
rcpts, err := q.aliases.Resolve(t)
if err != nil {
return "", fmt.Errorf("error resolving aliases for %q: %v", t, err)
}
// Add the recipients (after resolving aliases); this conversion is
// not very pretty but at least it's self contained.
for _, aliasRcpt := range rcpts {
r := &Recipient{
Address: aliasRcpt.Addr,
Status: Recipient_PENDING,
}
switch aliasRcpt.Type {
case aliases.EMAIL:
r.Type = Recipient_EMAIL
case aliases.PIPE:
r.Type = Recipient_PIPE
default:
glog.Errorf("unknown alias type %v when resolving %q",
aliasRcpt.Type, t)
return "", fmt.Errorf("internal error - unknown alias type")
}
item.Rcpt = append(item.Rcpt, r)
}
}
err := item.WriteTo(q.path)

View File

@@ -3,10 +3,12 @@ package queue
import (
"bytes"
"fmt"
"reflect"
"sync"
"testing"
"time"
"blitiri.com.ar/go/chasquid/internal/aliases"
"blitiri.com.ar/go/chasquid/internal/set"
)
@@ -58,7 +60,7 @@ func newTestCourier() *TestCourier {
func TestBasic(t *testing.T) {
localC := newTestCourier()
remoteC := newTestCourier()
q := New("/tmp/queue_test", set.NewString("loco"))
q := New("/tmp/queue_test", set.NewString("loco"), aliases.NewResolver())
q.localC = localC
q.remoteC = remoteC
@@ -99,7 +101,7 @@ func TestBasic(t *testing.T) {
}
func TestFullQueue(t *testing.T) {
q := New("/tmp/queue_test", set.NewString())
q := New("/tmp/queue_test", set.NewString(), aliases.NewResolver())
// Force-insert maxQueueSize items in the queue.
oneID := ""
@@ -136,8 +138,53 @@ func TestFullQueue(t *testing.T) {
q.Remove(id)
}
// Dumb courier, for when we don't care for the results.
type DumbCourier struct{}
func (c DumbCourier) Deliver(from string, to string, data []byte) error {
return nil
}
func TestAliases(t *testing.T) {
q := New("/tmp/queue_test", set.NewString("loco"), aliases.NewResolver())
q.localC = DumbCourier{}
q.remoteC = DumbCourier{}
q.aliases.AddDomain("loco")
q.aliases.AddAliasForTesting("ab@loco", "pq@loco", aliases.EMAIL)
q.aliases.AddAliasForTesting("ab@loco", "rs@loco", aliases.EMAIL)
q.aliases.AddAliasForTesting("ab@loco", "command", aliases.PIPE)
q.aliases.AddAliasForTesting("cd@loco", "ata@hualpa", aliases.EMAIL)
cases := []struct {
to []string
expected []*Recipient
}{
{[]string{"ab@loco"}, []*Recipient{
{"pq@loco", Recipient_EMAIL, Recipient_PENDING},
{"rs@loco", Recipient_EMAIL, Recipient_PENDING},
{"command", Recipient_PIPE, Recipient_PENDING}}},
{[]string{"ab@loco", "cd@loco"}, []*Recipient{
{"pq@loco", Recipient_EMAIL, Recipient_PENDING},
{"rs@loco", Recipient_EMAIL, Recipient_PENDING},
{"command", Recipient_PIPE, Recipient_PENDING},
{"ata@hualpa", Recipient_EMAIL, Recipient_PENDING}}},
}
for _, c := range cases {
id, err := q.Put("from", c.to, []byte("data"))
if err != nil {
t.Errorf("Put: %v", err)
}
item := q.q[id]
if !reflect.DeepEqual(item.Rcpt, c.expected) {
t.Errorf("case %q, expected %v, got %v", c.to, item.Rcpt, c.expected)
}
q.Remove(id)
}
}
func TestPipes(t *testing.T) {
q := New("/tmp/queue_test", set.NewString("loco"))
q := New("/tmp/queue_test", set.NewString("loco"), aliases.NewResolver())
item := &Item{
Message: Message{
ID: <-newID,

View File

@@ -0,0 +1,11 @@
smtp_address: ":1025"
submission_address: ":1587"
monitoring_address: ":1099"
mail_delivery_agent_bin: "test-mda"
mail_delivery_agent_args: "%user%@%domain%"
data_dir: "../.data"
suffix_separators: "+-"
drop_characters: "._"

View File

@@ -0,0 +1,12 @@
# Easy aliases.
pepe: jose
joan: juan
# UTF-8 aliases.
pitanga: ñangapirí
añil: azul, índigo
# Pipe aliases.
tubo: | writemailto ../.data/pipe_alias_worked

View File

@@ -0,0 +1,4 @@
Subject: Prueba desde el test
Crece desde el test el futuro
Crece desde el test

1
test/t-04-aliases/hosts Normal file
View File

@@ -0,0 +1 @@
testserver localhost

14
test/t-04-aliases/msmtprc Normal file
View File

@@ -0,0 +1,14 @@
account default
host testserver
port 1587
tls on
tls_trust_file config/domains/testserver/cert.pem
from user@testserver
auth on
user user@testserver
password secretpassword

43
test/t-04-aliases/run.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
set -e
. $(dirname ${0})/../util/lib.sh
init
generate_certs_for testserver
add_user testserver user secretpassword
mkdir -p .logs
chasquid -v=2 --log_dir=.logs --config_dir=config &
wait_until_ready 1025
function send_and_check() {
run_msmtp $1@testserver < content
shift
for i in $@; do
wait_for_file .mail/$i@testserver
mail_diff content .mail/$i@testserver
rm -f .mail/$i@testserver
done
}
# Test email aliases.
send_and_check pepe jose
send_and_check joan juan
send_and_check pitanga ñangapirí
send_and_check añil azul índigo
# Test suffix separators and drop characters.
send_and_check a.ñi_l azul índigo
send_and_check añil-blah azul índigo
send_and_check añil+blah azul índigo
# Test the pipe alias separately.
rm -f .data/pipe_alias_worked
run_msmtp tubo@testserver < content
wait_for_file .data/pipe_alias_worked
mail_diff content .data/pipe_alias_worked
success

4
test/util/writemailto Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
echo "From writemailto" > "$1"
exec cat >> "$1"