author | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-07-16 11:29:58 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-07-16 11:33:51 UTC |
parent | bb08be40231c32cbd9f9d1193751352a17896ee3 |
chasquid.go | +3 | -0 |
internal/config/config.go | +9 | -0 |
internal/config/config.pb.go | +43 | -3 |
internal/config/config.proto | +14 | -0 |
internal/courier/procmail.go | +6 | -4 |
internal/courier/procmail_test.go | +11 | -9 |
internal/userdb/userdb.go | +12 | -1 |
internal/userdb/userdb_test.go | +21 | -1 |
diff --git a/chasquid.go b/chasquid.go index 9beacf1..5dd5721 100644 --- a/chasquid.go +++ b/chasquid.go @@ -51,6 +51,9 @@ func main() { go http.ListenAndServe(conf.MonitoringAddress, nil) } + courier.MailDeliveryAgentBin = conf.MailDeliveryAgentBin + courier.MailDeliveryAgentArgs = conf.MailDeliveryAgentArgs + s := NewServer() s.Hostname = conf.Hostname s.MaxDataSize = conf.MaxDataSizeMb * 1024 * 1024 diff --git a/internal/config/config.go b/internal/config/config.go index 3f8c3e2..0b48a69 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -47,6 +47,14 @@ func Load(path string) (*Config, error) { 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) return c, nil } @@ -57,4 +65,5 @@ func logConfig(c *Config) { glog.Infof(" Max data size (MB): %d", c.MaxDataSizeMb) glog.Infof(" Addresses: %v", c.Address) glog.Infof(" Monitoring address: %s", c.MonitoringAddress) + glog.Infof(" MDA: %s %v", c.MailDeliveryAgentBin, c.MailDeliveryAgentArgs) } diff --git a/internal/config/config.pb.go b/internal/config/config.pb.go index ddad207..ba208c0 100644 --- a/internal/config/config.pb.go +++ b/internal/config/config.pb.go @@ -22,6 +22,12 @@ var _ = proto.Marshal var _ = fmt.Errorf 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 { // Hostname to use when we say hello. // 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. // Default: no monitoring http server. 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) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func init() { + proto.RegisterType((*Config)(nil), "Config") } -func (m *Config) Reset() { *m = Config{} } -func (m *Config) String() string { return proto.CompactTextString(m) } -func (*Config) ProtoMessage() {} +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, +} diff --git a/internal/config/config.proto b/internal/config/config.proto index d87682c..507c06d 100644 --- a/internal/config/config.proto +++ b/internal/config/config.proto @@ -18,5 +18,19 @@ message Config { // Address for the monitoring http server. // Default: no monitoring http server. 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; } diff --git a/internal/courier/procmail.go b/internal/courier/procmail.go index 06c3a52..c307500 100644 --- a/internal/courier/procmail.go +++ b/internal/courier/procmail.go @@ -14,8 +14,10 @@ import ( var ( // Location of the procmail binary, and arguments to use. // The string "%user%" will be replaced with the local user. - procmailBin = "procmail" - procmailArgs = []string{"-d", "%user%"} + // TODO: Make these a part of the courier instance itself? Why do they + // have to be global? + MailDeliveryAgentBin = "procmail" + MailDeliveryAgentArgs = []string{"-d", "%user%"} // Give procmail 1m to deliver mail. 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. args := []string{} - for _, a := range procmailArgs { + for _, a := range MailDeliveryAgentArgs { args = append(args, strings.Replace(a, "%user%", user, -1)) } - cmd := exec.Command(procmailBin, args...) + cmd := exec.Command(MailDeliveryAgentBin, args...) cmdStdin, err := cmd.StdinPipe() if err != nil { diff --git a/internal/courier/procmail_test.go b/internal/courier/procmail_test.go index f700a18..c806acc 100644 --- a/internal/courier/procmail_test.go +++ b/internal/courier/procmail_test.go @@ -15,8 +15,8 @@ func TestProcmail(t *testing.T) { } defer os.RemoveAll(dir) - procmailBin = "tee" - procmailArgs = []string{dir + "/%user%"} + MailDeliveryAgentBin = "tee" + MailDeliveryAgentArgs = []string{dir + "/%user%"} p := Procmail{} err = p.Deliver("from@x", "to@y", []byte("data")) @@ -31,8 +31,8 @@ func TestProcmail(t *testing.T) { } func TestProcmailTimeout(t *testing.T) { - procmailBin = "/bin/sleep" - procmailArgs = []string{"1"} + MailDeliveryAgentBin = "/bin/sleep" + MailDeliveryAgentArgs = []string{"1"} procmailTimeout = 100 * time.Millisecond p := Procmail{} @@ -48,19 +48,21 @@ func TestProcmailBadCommandLine(t *testing.T) { p := Procmail{} // Non-existent binary. - procmailBin = "thisdoesnotexist" + MailDeliveryAgentBin = "thisdoesnotexist" err := p.Deliver("from", "to", []byte("data")) if err == nil { - t.Errorf("Unexpected success: %q %v", procmailBin, procmailArgs) + t.Errorf("Unexpected success: %q %v", + MailDeliveryAgentBin, MailDeliveryAgentArgs) } // Incorrect arguments. - procmailBin = "cat" - procmailArgs = []string{"--fail_unknown_option"} + MailDeliveryAgentBin = "cat" + MailDeliveryAgentArgs = []string{"--fail_unknown_option"} err = p.Deliver("from", "to", []byte("data")) if err == nil { - t.Errorf("Unexpected success: %q %v", procmailBin, procmailArgs) + t.Errorf("Unexpected success: %q %v", + MailDeliveryAgentBin, MailDeliveryAgentArgs) } } diff --git a/internal/userdb/userdb.go b/internal/userdb/userdb.go index 46ca213..d73f0e8 100644 --- a/internal/userdb/userdb.go +++ b/internal/userdb/userdb.go @@ -79,6 +79,13 @@ var ( 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. // Return the database, a list of warnings (if any), and a fatal error if the // database could not be loaded. @@ -194,7 +201,11 @@ func (db *DB) Write() error { 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? diff --git a/internal/userdb/userdb_test.go b/internal/userdb/userdb_test.go index b5cfe3c..e3a1ef2 100644 --- a/internal/userdb/userdb_test.go +++ b/internal/userdb/userdb_test.go @@ -1,6 +1,7 @@ package userdb import ( + "fmt" "io/ioutil" "os" "testing" @@ -41,7 +42,7 @@ func dbEquals(a, b *DB) bool { for k, av := range a.users { bv, ok := b.users[k] - if !ok || av != bv { + if !ok || av.name != bv.name || av.password != bv.password { 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) { fname := mustCreateDB(t, "") defer removeIfSuccessful(t, fname)