From 658ee30bc6ab0533658a5e5a5e0a08da18599f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Sat, 6 Nov 2021 17:12:08 +0100 Subject: [PATCH] Server socket * The UNIX socket will be listened on for control messages * A message containing a single 'Q' will cause it to exit * A message containing a single 'R' will cause a configuration reload. If the new configuration is incorrect, the old configuration will be kept. A new socket will be opened if the path has changed (failure when doing so will restore the previous configuration as well). * A message starting with 'U' requests an update. The next character may be '!' to force updates or anything else to update only as needed. The rest of the string is the selector: either a DN or '*'. * The selector is ignored in this commit; all certificates are re-examined. --- config.go | 2 +- main.go | 50 ++++++++++---------- socket.go | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 26 deletions(-) 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 +}