1
0
mirror of https://blitiri.com.ar/repos/chasquid synced 2025-12-18 14:47:03 +00:00

courier: Let the users configure the mail delivery agent

This patch adds configuration options for the MDA binary and command line
arguments, and changes the (soon to be renamed) procmail courier to make use
of them.
This commit is contained in:
Alberto Bertogli
2016-07-16 12:29:58 +01:00
parent bb08be4023
commit ff103c18c3
8 changed files with 119 additions and 18 deletions

View File

@@ -51,6 +51,9 @@ func main() {
go http.ListenAndServe(conf.MonitoringAddress, nil) go http.ListenAndServe(conf.MonitoringAddress, nil)
} }
courier.MailDeliveryAgentBin = conf.MailDeliveryAgentBin
courier.MailDeliveryAgentArgs = conf.MailDeliveryAgentArgs
s := NewServer() s := NewServer()
s.Hostname = conf.Hostname s.Hostname = conf.Hostname
s.MaxDataSize = conf.MaxDataSizeMb * 1024 * 1024 s.MaxDataSize = conf.MaxDataSizeMb * 1024 * 1024

View File

@@ -47,6 +47,14 @@ func Load(path string) (*Config, error) {
c.Address = append(c.Address, "systemd") c.Address = append(c.Address, "systemd")
} }
if c.MailDeliveryAgentBin == "" {
c.MailDeliveryAgentBin = "procmail"
}
if len(c.MailDeliveryAgentArgs) == 0 {
c.MailDeliveryAgentArgs = append(c.MailDeliveryAgentArgs,
"-d", "%user%")
}
logConfig(c) logConfig(c)
return c, nil return c, nil
} }
@@ -57,4 +65,5 @@ func logConfig(c *Config) {
glog.Infof(" Max data size (MB): %d", c.MaxDataSizeMb) glog.Infof(" Max data size (MB): %d", c.MaxDataSizeMb)
glog.Infof(" Addresses: %v", c.Address) glog.Infof(" Addresses: %v", c.Address)
glog.Infof(" Monitoring address: %s", c.MonitoringAddress) glog.Infof(" Monitoring address: %s", c.MonitoringAddress)
glog.Infof(" MDA: %s %v", c.MailDeliveryAgentBin, c.MailDeliveryAgentArgs)
} }

View File

@@ -22,6 +22,12 @@ var _ = proto.Marshal
var _ = fmt.Errorf var _ = fmt.Errorf
var _ = math.Inf var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Config struct { type Config struct {
// Hostname to use when we say hello. // Hostname to use when we say hello.
// For aesthetic purposes, but may help if our ip address resolves to it. // For aesthetic purposes, but may help if our ip address resolves to it.
@@ -36,8 +42,42 @@ type Config struct {
// Address for the monitoring http server. // Address for the monitoring http server.
// Default: no monitoring http server. // Default: no monitoring http server.
MonitoringAddress string `protobuf:"bytes,4,opt,name=monitoring_address" json:"monitoring_address,omitempty"` MonitoringAddress string `protobuf:"bytes,4,opt,name=monitoring_address" json:"monitoring_address,omitempty"`
// Mail delivery agent (MDA, also known as LDA) to use.
// This should point to the binary to use to deliver email to local users.
// The content of the email will be passed via stdin.
// If it exits unsuccessfully, we assume the mail was not delivered.
// Default: "procmail".
MailDeliveryAgentBin string `protobuf:"bytes,5,opt,name=mail_delivery_agent_bin" json:"mail_delivery_agent_bin,omitempty"`
// Command line arguments for the mail delivery agent. One per argument.
// Some replacements will be done:
// - "%user%" -> local user (anything before the @)
// - "%domain%" -> domain (anything after the @)
// Default: "-d", "%user" (adequate for procmail)
MailDeliveryAgentArgs []string `protobuf:"bytes,6,rep,name=mail_delivery_agent_args" json:"mail_delivery_agent_args,omitempty"`
} }
func (m *Config) Reset() { *m = Config{} } func (m *Config) Reset() { *m = Config{} }
func (m *Config) String() string { return proto.CompactTextString(m) } func (m *Config) String() string { return proto.CompactTextString(m) }
func (*Config) ProtoMessage() {} func (*Config) ProtoMessage() {}
func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func init() {
proto.RegisterType((*Config)(nil), "Config")
}
func init() { proto.RegisterFile("config.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 169 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x8e, 0x41, 0xca, 0xc2, 0x30,
0x10, 0x46, 0xe9, 0xdf, 0xdf, 0xaa, 0x83, 0x60, 0xc9, 0xc6, 0xc1, 0x8d, 0xc5, 0x95, 0x2b, 0x37,
0x1e, 0xc1, 0x83, 0x0c, 0x53, 0x13, 0xe3, 0x40, 0x93, 0x48, 0x12, 0x44, 0x3d, 0x8f, 0x07, 0xb5,
0x06, 0xdc, 0xb9, 0xfd, 0xde, 0xe3, 0xf1, 0xc1, 0xe2, 0x14, 0xfc, 0x59, 0xec, 0xfe, 0x1a, 0x43,
0x0e, 0xdb, 0x57, 0x05, 0xcd, 0xb1, 0x0c, 0xaa, 0x85, 0xd9, 0x25, 0xa4, 0xec, 0xd9, 0x19, 0xac,
0xba, 0x6a, 0x37, 0x57, 0x08, 0xad, 0xe3, 0x3b, 0x69, 0xce, 0x4c, 0x49, 0x9e, 0x86, 0x5c, 0x8f,
0x7f, 0x23, 0xa9, 0xd5, 0x12, 0xa6, 0xac, 0x75, 0x34, 0x29, 0x61, 0xdd, 0xd5, 0xa3, 0xba, 0x06,
0xe5, 0x82, 0x97, 0x1c, 0xa2, 0x78, 0x4b, 0x5f, 0xf6, 0x5f, 0x32, 0x1b, 0x58, 0x39, 0x96, 0x81,
0xb4, 0x19, 0xe4, 0x66, 0xe2, 0x83, 0xd8, 0x1a, 0x9f, 0xa9, 0x17, 0x8f, 0x93, 0x22, 0x74, 0x80,
0xbf, 0x04, 0x8e, 0x36, 0x61, 0xf3, 0xc9, 0xf7, 0x4d, 0x79, 0x7b, 0x78, 0x07, 0x00, 0x00, 0xff,
0xff, 0xa3, 0xe4, 0x58, 0xd3, 0xbd, 0x00, 0x00, 0x00,
}

View File

@@ -18,5 +18,19 @@ message Config {
// Address for the monitoring http server. // Address for the monitoring http server.
// Default: no monitoring http server. // Default: no monitoring http server.
string monitoring_address = 4; string monitoring_address = 4;
// Mail delivery agent (MDA, also known as LDA) to use.
// This should point to the binary to use to deliver email to local users.
// The content of the email will be passed via stdin.
// If it exits unsuccessfully, we assume the mail was not delivered.
// Default: "procmail".
string mail_delivery_agent_bin = 5;
// Command line arguments for the mail delivery agent. One per argument.
// Some replacements will be done:
// - "%user%" -> local user (anything before the @)
// - "%domain%" -> domain (anything after the @)
// Default: "-d", "%user" (adequate for procmail)
repeated string mail_delivery_agent_args = 6;
} }

View File

@@ -14,8 +14,10 @@ import (
var ( var (
// Location of the procmail binary, and arguments to use. // Location of the procmail binary, and arguments to use.
// The string "%user%" will be replaced with the local user. // The string "%user%" will be replaced with the local user.
procmailBin = "procmail" // TODO: Make these a part of the courier instance itself? Why do they
procmailArgs = []string{"-d", "%user%"} // have to be global?
MailDeliveryAgentBin = "procmail"
MailDeliveryAgentArgs = []string{"-d", "%user%"}
// Give procmail 1m to deliver mail. // Give procmail 1m to deliver mail.
procmailTimeout = 1 * time.Minute procmailTimeout = 1 * time.Minute
@@ -39,10 +41,10 @@ func (p *Procmail) Deliver(from string, to string, data []byte) error {
// Prepare the command, replacing the necessary arguments. // Prepare the command, replacing the necessary arguments.
args := []string{} args := []string{}
for _, a := range procmailArgs { for _, a := range MailDeliveryAgentArgs {
args = append(args, strings.Replace(a, "%user%", user, -1)) args = append(args, strings.Replace(a, "%user%", user, -1))
} }
cmd := exec.Command(procmailBin, args...) cmd := exec.Command(MailDeliveryAgentBin, args...)
cmdStdin, err := cmd.StdinPipe() cmdStdin, err := cmd.StdinPipe()
if err != nil { if err != nil {

View File

@@ -15,8 +15,8 @@ func TestProcmail(t *testing.T) {
} }
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
procmailBin = "tee" MailDeliveryAgentBin = "tee"
procmailArgs = []string{dir + "/%user%"} MailDeliveryAgentArgs = []string{dir + "/%user%"}
p := Procmail{} p := Procmail{}
err = p.Deliver("from@x", "to@y", []byte("data")) err = p.Deliver("from@x", "to@y", []byte("data"))
@@ -31,8 +31,8 @@ func TestProcmail(t *testing.T) {
} }
func TestProcmailTimeout(t *testing.T) { func TestProcmailTimeout(t *testing.T) {
procmailBin = "/bin/sleep" MailDeliveryAgentBin = "/bin/sleep"
procmailArgs = []string{"1"} MailDeliveryAgentArgs = []string{"1"}
procmailTimeout = 100 * time.Millisecond procmailTimeout = 100 * time.Millisecond
p := Procmail{} p := Procmail{}
@@ -48,19 +48,21 @@ func TestProcmailBadCommandLine(t *testing.T) {
p := Procmail{} p := Procmail{}
// Non-existent binary. // Non-existent binary.
procmailBin = "thisdoesnotexist" MailDeliveryAgentBin = "thisdoesnotexist"
err := p.Deliver("from", "to", []byte("data")) err := p.Deliver("from", "to", []byte("data"))
if err == nil { if err == nil {
t.Errorf("Unexpected success: %q %v", procmailBin, procmailArgs) t.Errorf("Unexpected success: %q %v",
MailDeliveryAgentBin, MailDeliveryAgentArgs)
} }
// Incorrect arguments. // Incorrect arguments.
procmailBin = "cat" MailDeliveryAgentBin = "cat"
procmailArgs = []string{"--fail_unknown_option"} MailDeliveryAgentArgs = []string{"--fail_unknown_option"}
err = p.Deliver("from", "to", []byte("data")) err = p.Deliver("from", "to", []byte("data"))
if err == nil { if err == nil {
t.Errorf("Unexpected success: %q %v", procmailBin, procmailArgs) t.Errorf("Unexpected success: %q %v",
MailDeliveryAgentBin, MailDeliveryAgentArgs)
} }
} }

View File

@@ -79,6 +79,13 @@ var (
InvalidUsernameErr = errors.New("username contains invalid characters") InvalidUsernameErr = errors.New("username contains invalid characters")
) )
func New(fname string) *DB {
return &DB{
fname: fname,
users: map[string]user{},
}
}
// Load the database from the given file. // Load the database from the given file.
// Return the database, a list of warnings (if any), and a fatal error if the // Return the database, a list of warnings (if any), and a fatal error if the
// database could not be loaded. // database could not be loaded.
@@ -194,7 +201,11 @@ func (db *DB) Write() error {
base64.StdEncoding.EncodeToString([]byte(user.password))) base64.StdEncoding.EncodeToString([]byte(user.password)))
} }
return safeio.WriteFile(db.fname, buf.Bytes(), db.finfo.Mode()) mode := os.FileMode(0660)
if db.finfo != nil {
mode = db.finfo.Mode()
}
return safeio.WriteFile(db.fname, buf.Bytes(), mode)
} }
// Does this user exist in the database? // Does this user exist in the database?

View File

@@ -1,6 +1,7 @@
package userdb package userdb
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
@@ -41,7 +42,7 @@ func dbEquals(a, b *DB) bool {
for k, av := range a.users { for k, av := range a.users {
bv, ok := b.users[k] bv, ok := b.users[k]
if !ok || av != bv { if !ok || av.name != bv.name || av.password != bv.password {
return false return false
} }
} }
@@ -203,6 +204,25 @@ func TestWrite(t *testing.T) {
} }
} }
func TestNew(t *testing.T) {
fname := fmt.Sprintf("%s/userdb_test-%d", os.TempDir(), os.Getpid())
db1 := New(fname)
db1.AddUser("user", "passwd")
db1.Write()
db2, ws, err := Load(fname)
if err != nil {
t.Fatalf("error loading: %v", err)
}
if len(ws) != 0 {
t.Errorf("warnings loading: %v", ws)
}
if !dbEquals(db1, db2) {
t.Errorf("databases differ. db1:%v != db2:%v", db1, db2)
}
}
func TestInvalidUsername(t *testing.T) { func TestInvalidUsername(t *testing.T) {
fname := mustCreateDB(t, "") fname := mustCreateDB(t, "")
defer removeIfSuccessful(t, fname) defer removeIfSuccessful(t, fname)