diff --git a/config.go b/config.go index d76841d..7d6050a 100644 --- a/config.go +++ b/config.go @@ -284,7 +284,7 @@ func defaultConfiguration() tConfiguration { } // Load and check the configuration file -func loadConfiguration(file string) (tConfiguration, error) { +func LoadConfiguration(file string) (tConfiguration, error) { cfg := defaultConfiguration() cfgData, err := ioutil.ReadFile(file) if err != nil { diff --git a/main.go b/main.go index dfc0ca9..195202b 100644 --- a/main.go +++ b/main.go @@ -60,7 +60,7 @@ func main() { log.WithField("error", err).Fatal("Failed to configure logging.") } - cfg, err := loadConfiguration(flags.cfgFile) + cfg, err := LoadConfiguration(flags.cfgFile) if err != nil { log.WithField("error", err).Fatal("Failed to load initial configuration.") } @@ -69,32 +69,32 @@ func main() { if err != nil { log.WithField("error", err).Fatal("Failed to initialize socket.") } - listener.Close() - - conn := NewLdapConnection(cfg.LdapConfig) - if conn == nil { - return - } - defer conn.Close() - for i := range cfg.Certificates { - builder := NewCertificateBuilder(conn, &cfg.Certificates[i]) - err := builder.Build() - if err != nil { - log.WithField("error", err).Error("Failed to build data for certificate '", cfg.Certificates[i].Path, "'") - continue - } - if builder.MustWrite() { - err := builder.WriteFile() + defer listener.Close() + for { + cmd := socketServer(&cfg, listener) + if cmd == CMD_QUIT { + break + } else if cmd == CMD_RELOAD { + new_cfg, err := LoadConfiguration(flags.cfgFile) if err != nil { - log.WithField("error", err).Error("Failed to write '", cfg.Certificates[i].Path, "'") - continue + log.WithField("error", err).Error("Failed to load updated configuration.") + } else { + replace_ok := true + if new_cfg.Socket.Path != cfg.Socket.Path { + new_listener, err := initSocket(new_cfg.Socket) + if err != nil { + log.WithField("error", err).Error("Failed to initialize new server socket.") + replace_ok = false + } else { + listener.Close() + listener = new_listener + } + } + if replace_ok { + cfg = new_cfg + log.Info("Configuration reloaded") + } } } - err = builder.UpdatePrivileges() - if err != nil { - log.WithField("error", err).Error("Failed to update privileges on '", cfg.Certificates[i].Path, "'") - continue - } - builder.RunCommandsIfChanged() } } diff --git a/socket.go b/socket.go index 5e8e078..81a668e 100644 --- a/socket.go +++ b/socket.go @@ -6,8 +6,27 @@ import ( "os" "os/user" "strconv" + "time" + "unicode/utf8" + + "github.com/sirupsen/logrus" ) +type TCommandType int + +const ( + CMD_IGNORE TCommandType = iota + CMD_QUIT + CMD_RELOAD + CMD_UPDATE +) + +type TCommand struct { + CommandType TCommandType + Force bool + Selector string +} + func configureSocket(cfg tSocketConfig) error { if cfg.Group != "" { group, err := user.LookupGroup(cfg.Group) @@ -47,3 +66,119 @@ func initSocket(cfg tSocketConfig) (net.Listener, error) { log.WithField("path", cfg.Path).Info("UNIX socket created") return listener, nil } + +func socketServer(cfg *tConfiguration, listener net.Listener) TCommandType { + for { + fd, err := listener.Accept() + if err != nil { + log.WithField("error", err).Fatal("Error while waiting for connections.") + } + cmd := executeFromSocket(cfg, fd) + if cmd != CMD_IGNORE { + return cmd + } + } +} + +func executeFromSocket(cfg *tConfiguration, conn net.Conn) TCommandType { + defer conn.Close() + log.Debug("Received connection") + + buf := make([]byte, 512) + conn.SetReadDeadline(time.Now().Add(1 * time.Second)) + n, err := conn.Read(buf) + if err != nil { + log.WithField("error", err).Error("Could not read from socket") + return CMD_IGNORE + } + command := parseCommand(n, buf) + if command == nil { + return CMD_IGNORE + } + if command.CommandType == CMD_UPDATE { + log.WithFields(logrus.Fields{ + "force": command.Force, + "selector": command.Selector, + }).Info("Update request received") + success := executeUpdate(cfg, command) + conn.SetWriteDeadline(time.Now().Add(1 * time.Second)) + var bval byte + if success { + bval = '1' + } else { + bval = '0' + } + conn.Write([]byte{bval}) + return CMD_IGNORE + } + return command.CommandType +} + +func parseCommand(n int, buf []byte) *TCommand { + if n == 512 { + log.Warn("Too much data received") + return nil + } + if n == 0 { + log.Warn("Not enough data received") + return nil + } + if n == 1 { + if buf[0] == 'Q' { + return &TCommand{CommandType: CMD_QUIT} + } else if buf[0] == 'R' { + return &TCommand{CommandType: CMD_RELOAD} + } + } else if n > 2 && buf[0] == 'U' { + res := &TCommand{CommandType: CMD_UPDATE} + if buf[1] == '!' { + res.Force = true + } + if utf8.Valid(buf[2:]) { + res.Selector = string(buf[2:n]) + return res + } + } + log.Warn("Invalid command received") + return nil +} + +func executeUpdate(cfg *tConfiguration, cmd *TCommand) bool { + conn := NewLdapConnection(cfg.LdapConfig) + if conn == nil { + return false + } + defer conn.Close() + + had_errors := false + for i := range cfg.Certificates { + // TODO apply selector + builder := NewCertificateBuilder(conn, &cfg.Certificates[i]) + err := builder.Build() + if err != nil { + log.WithField("error", err).Error("Failed to build data for certificate '", cfg.Certificates[i].Path, "'") + had_errors = true + continue + } + if builder.MustWrite() || cmd.Force { + err := builder.WriteFile() + if err != nil { + log.WithField("error", err).Error("Failed to write '", cfg.Certificates[i].Path, "'") + had_errors = true + continue + } + } + err = builder.UpdatePrivileges() + if err != nil { + log.WithField("error", err).Error("Failed to update privileges on '", cfg.Certificates[i].Path, "'") + had_errors = true + continue + } + err = builder.RunCommandsIfChanged() + if err != nil { + log.WithField("error", err).Error("Failed to run commands after update of '", cfg.Certificates[i].Path, "'") + had_errors = true + } + } + return !had_errors +}