]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
director: Support flush socket
authorAki Tuomi <aki.tuomi@dovecot.fi>
Thu, 13 Oct 2016 13:11:48 +0000 (16:11 +0300)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Mon, 24 Oct 2016 11:26:03 +0000 (14:26 +0300)
This allows specifying an URI to execute
on user kill. It can be of form
exec:/path/to/bin, unix:/path/to/socket or
tcp:ip:port

The location is sent FLUSH username-hash
per killed user. You can execute some
action there, and you are expected to
return '+\nOK\n' as reply once you are
done.

src/director/Makefile.am
src/director/director-settings.c
src/director/director-settings.h
src/director/director.c

index e1d969407cc185750e1481344f9eceff33eb63f6..6bae2adafa10a1b309f6b5efbab11a0b77ab08d4 100644 (file)
@@ -9,7 +9,8 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib-imap \
        -I$(top_srcdir)/src/lib-settings \
        -I$(top_srcdir)/src/lib-master \
-       -I$(top_srcdir)/src/lib-mail
+       -I$(top_srcdir)/src/lib-mail \
+       -I$(top_srcdir)/src/lib-program-client
 
 director_LDADD = $(LIBDOVECOT)
 director_DEPENDENCIES = $(LIBDOVECOT_DEPS)
index 40512b7022c7255c577e10ba30624adca6696dff..fc1a25172207cf77925b3b3c7fa1d38e3cbf92bb 100644 (file)
@@ -71,6 +71,7 @@ static const struct setting_define director_setting_defines[] = {
        DEF(SET_STR, director_servers),
        DEF(SET_STR, director_mail_servers),
        DEF(SET_STR, director_username_hash),
+       DEF(SET_STR, director_flush_socket),
        DEF(SET_TIME, director_user_expire),
        DEF(SET_TIME, director_user_kick_delay),
        DEF(SET_IN_PORT, director_doveadm_port),
@@ -85,6 +86,7 @@ const struct director_settings director_default_settings = {
        .director_servers = "",
        .director_mail_servers = "",
        .director_username_hash = "%Lu",
+       .director_flush_socket = "",
        .director_user_expire = 60*15,
        .director_user_kick_delay = 2,
        .director_doveadm_port = 0
index a95e07c6faf2bb2d08bf069e86d46323a6b5df00..17cbb79016e4aee4b17b3b0492c18fc9d53a2ab1 100644 (file)
@@ -9,6 +9,8 @@ struct director_settings {
        const char *director_servers;
        const char *director_mail_servers;
        const char *director_username_hash;
+       const char *director_flush_socket;
+
        unsigned int director_user_expire;
        unsigned int director_user_kick_delay;
        in_port_t director_doveadm_port;
index b0e6c952fca988b611d50996970cbc357eb2e5b3..97367ea0b368efa369df672080523d95b00258d2 100644 (file)
@@ -7,6 +7,11 @@
 #include "strescape.h"
 #include "log-throttle.h"
 #include "ipc-client.h"
+#include "program-client.h"
+#include "var-expand.h"
+#include "istream.h"
+#include "ostream.h"
+#include "iostream-temp.h"
 #include "user-directory.h"
 #include "mail-host.h"
 #include "director-host.h"
@@ -14,7 +19,6 @@
 #include "director.h"
 
 #define DIRECTOR_IPC_PROXY_PATH "ipc"
-
 #define DIRECTOR_RECONNECT_RETRY_SECS 60
 #define DIRECTOR_RECONNECT_TIMEOUT_MSECS (30*1000)
 #define DIRECTOR_USER_MOVE_TIMEOUT_MSECS (30*1000)
@@ -33,6 +37,10 @@ static const struct log_throttle_settings director_log_throttle_settings = {
        .unthrottle_at_max_per_interval = 2,
 };
 
+static void
+director_user_kill_finish_delayed(struct director *dir, struct user *user,
+                                 bool skip_delay);
+
 static bool director_is_self_ip_set(struct director *dir)
 {
        struct ip_addr ip;
@@ -694,32 +702,135 @@ void director_update_user_weak(struct director *dir, struct director_host *src,
 
 struct director_user_kill_finish_ctx {
        struct director *dir;
+       unsigned int username_hash;
        struct user *user;
+       struct program_client *pclient;
+       struct ostream *reply;
+       char *socket_path;
 };
 
+static void
+director_flush_user_continue(int result,
+                            struct director_user_kill_finish_ctx *ctx)
+{
+       struct user *user =
+               user_directory_lookup(ctx->dir->users, ctx->username_hash);
+
+       if (user != NULL)
+               director_user_kill_finish_delayed(ctx->dir, user,
+                                                 result == 1);
+       if (result == 0) {
+               struct istream *is = iostream_temp_finish(&ctx->reply, (size_t)-1);
+               char *data;
+               i_stream_set_return_partial_line(is, TRUE);
+               data = i_stream_read_next_line(is);
+               i_error("%s: Failed to flush user hash %u in host %s: %s",
+                       ctx->socket_path,
+                       user->username_hash,
+                       net_ip2addr(&user->host->ip),
+                       data == NULL ? "(no output to stdout)" : data);
+               while((data = i_stream_read_next_line(is)) != NULL) {
+                       i_error("%s: Failed to flush user hash %u in host %s: %s",
+                               ctx->socket_path,
+                               user->username_hash,
+                               net_ip2addr(&user->host->ip),
+                               data);
+               }
+               i_stream_unref(&is);
+       } else {
+               o_stream_unref(&ctx->reply);
+       }
+       program_client_destroy(&ctx->pclient);
+       i_free(ctx->socket_path);
+       i_free(ctx);
+}
+
+static void
+director_flush_user(struct director *dir, struct user *user)
+{
+       struct var_expand_table tab[] = {
+               { 'i', net_ip2addr(&user->host->ip), "ip" },
+               { 'h', user->host->hostname, "host" },
+               { '\0', NULL, NULL }
+       };
+
+       /* execute flush script, if set */
+       if (*dir->set->director_flush_socket == '\0') {
+               director_user_kill_finish_delayed(dir, user, FALSE);
+               return;
+       }
+
+       struct director_user_kill_finish_ctx *ctx =
+               i_new(struct director_user_kill_finish_ctx, 1);
+       ctx->username_hash = user->username_hash;
+       ctx->dir = dir;
+
+       string_t *s_sock = str_new(default_pool, 32);
+       var_expand(s_sock, dir->set->director_flush_socket, tab);
+       ctx->socket_path = str_free_without_data(&s_sock);
+
+       const char *error;
+       struct program_client_settings set = {
+               .client_connect_timeout_msecs = 10000,
+       };
+
+       restrict_access_init(&set.restrict_set);
+
+       const char *const args[] = {"FLUSH",
+               t_strdup_printf("%u", user->username_hash), NULL};
+
+       if ((program_client_create(ctx->socket_path, args, &set, FALSE,
+                                  &ctx->pclient, &error)) != 0) {
+               i_error("%s: Failed to flush user hash %u in host %s: %s",
+                       ctx->socket_path,
+                       user->username_hash,
+                       net_ip2addr(&user->host->ip),
+                       error);
+               director_flush_user_continue(0, ctx);
+               return;
+       }
+
+       ctx->reply =
+               iostream_temp_create_named("/tmp", 0,
+                                          t_strdup_printf("flush response from %s",
+                                                          net_ip2addr(&user->host->ip)));
+       o_stream_set_no_error_handling(ctx->reply, TRUE);
+       program_client_set_output(ctx->pclient, ctx->reply);
+       program_client_run_async(ctx->pclient, director_flush_user_continue, ctx);
+}
+
 static void
 director_user_kill_finish_delayed_to(struct director_user_kill_finish_ctx *ctx)
 {
        i_assert(ctx->user->kill_state == USER_KILL_STATE_DELAY);
 
        ctx->user->kill_state = USER_KILL_STATE_NONE;
-       timeout_remove(&ctx->user->to_move);
+       if (ctx->user->to_move != NULL)
+               timeout_remove(&ctx->user->to_move);
 
        ctx->dir->state_change_callback(ctx->dir);
        i_free(ctx);
 }
 
 static void
-director_user_kill_finish_delayed(struct director *dir, struct user *user)
+director_user_kill_finish_delayed(struct director *dir, struct user *user,
+                                 bool skip_delay)
 {
        struct director_user_kill_finish_ctx *ctx;
 
+       timeout_remove(&user->to_move);
+
+       if (skip_delay) {
+               user->kill_state = USER_KILL_STATE_NONE;
+               dir->state_change_callback(dir);
+               return;
+       }
+
        ctx = i_new(struct director_user_kill_finish_ctx, 1);
        ctx->dir = dir;
        ctx->user = user;
 
        user->kill_state = USER_KILL_STATE_DELAY;
-       timeout_remove(&user->to_move);
 
        /* wait for a while for the kills to finish in the backend server,
           so there are no longer any processes running for the user before we
@@ -741,7 +852,7 @@ director_finish_user_kill(struct director *dir, struct user *user, bool self)
 
        if (dir->right == NULL) {
                /* we're alone */
-               director_user_kill_finish_delayed(dir, user);
+               director_flush_user(dir, user);
        } else if (self ||
                   user->kill_state == USER_KILL_STATE_KILLING_NOTIFY_RECEIVED) {
                director_connection_send(dir->right, t_strdup_printf(
@@ -993,7 +1104,7 @@ void director_user_killed_everywhere(struct director *dir,
            user->kill_state != USER_KILL_STATE_KILLED_WAITING_FOR_EVERYONE)
                return;
 
-       director_user_kill_finish_delayed(dir, user);
+       director_flush_user(dir, user);
 
        if (orig_src == NULL) {
                orig_src = dir->self_host;