]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
"script" service API changed to provide more functionality.
authorTimo Sirainen <tss@iki.fi>
Thu, 28 Jul 2011 13:59:56 +0000 (16:59 +0300)
committerTimo Sirainen <tss@iki.fi>
Thu, 28 Jul 2011 13:59:56 +0000 (16:59 +0300)
src/plugins/quota/quota.c
src/util/script.c

index cdcd3ae6b85d4bb1f495fe4df2b454ebf294be1c..874bf9679d46ad846d3d9f33a8cdc9f5ce4d127c 100644 (file)
@@ -1013,7 +1013,7 @@ static void quota_warning_execute(struct quota_root *root, const char *cmd)
        }
 
        str = t_str_new(1024);
-       str_append(str, "VERSION\tscript\t2\t0\n");
+       str_append(str, "VERSION\tscript\t3\t0\nnoreply\n");
        for (; *args != NULL; args++) {
                str_append(str, *args);
                str_append_c(str, '\n');
index 2ac617a8ad207a80e4905565916f360e7677fbfa..a637b4eb461243a0a9a46ab5bad29dfb85a7fd75 100644 (file)
@@ -5,14 +5,18 @@
 #include "str.h"
 #include "env-util.h"
 #include "execv-const.h"
+#include "write-full.h"
 #include "restrict-access.h"
 #include "master-interface.h"
 #include "master-service.h"
 
 #include <stdlib.h>
 #include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
 
-#define SCRIPT_MAJOR_VERSION 2
+#define SCRIPT_MAJOR_VERSION 3
 #define SCRIPT_READ_TIMEOUT_SECS 10
 
 static ARRAY_TYPE(const_string) exec_args;
@@ -26,15 +30,47 @@ static void script_verify_version(const char *line)
        }
 }
 
-static void client_connected(struct master_service_connection *conn)
+
+static void
+exec_child(struct master_service_connection *conn, const char *const *args)
+{
+       unsigned int i, socket_count;
+
+       if (dup2(conn->fd, STDIN_FILENO) < 0)
+               i_fatal("dup2() failed: %m");
+       if (dup2(conn->fd, STDOUT_FILENO) < 0)
+               i_fatal("dup2() failed: %m");
+
+       /* close all fds */
+       socket_count = master_service_get_socket_count(master_service);
+       for (i = 0; i < socket_count; i++) {
+               if (close(MASTER_LISTEN_FD_FIRST + i) < 0)
+                       i_error("close(listener) failed: %m");
+       }
+       if (close(MASTER_STATUS_FD) < 0)
+               i_error("close(status) failed: %m");
+       if (close(conn->fd) < 0)
+               i_error("close(conn->fd) failed: %m");
+
+       for (; *args != NULL; args++)
+               array_append(&exec_args, args, 1);
+       (void)array_append_space(&exec_args);
+
+       env_clean();
+       args = array_idx(&exec_args, 0);
+       execvp_const(args[0], args);
+}
+
+static bool client_exec_script(struct master_service_connection *conn)
 {
-       const unsigned char *end;
        const char *const *args;
-       buffer_t *input;
+       string_t *input;
        void *buf;
-       unsigned int i, socket_count;
-       size_t prev_size;
+       size_t prev_size, scanpos;
+       bool header_complete = FALSE;
        ssize_t ret;
+       int status;
+       pid_t pid;
 
        net_set_nonblock(conn->fd, FALSE);
        input = buffer_create_dynamic(pool_datastack_create(), IO_BLOCK_SIZE);
@@ -42,62 +78,116 @@ static void client_connected(struct master_service_connection *conn)
        /* Input contains:
 
           VERSION .. <lf>
+
           arg 1 <lf>
           arg 2 <lf>
           ...
           <lf>
-          [data]
-       */
+          DATA
+       */              
        alarm(SCRIPT_READ_TIMEOUT_SECS);
-       do {
+       scanpos = 1;
+       while (!header_complete) {
+               const unsigned char *pos, *end;
+
                prev_size = input->used;
                buf = buffer_append_space_unsafe(input, IO_BLOCK_SIZE);
-               ret = read(conn->fd, buf, IO_BLOCK_SIZE);
+
+               /* peek in socket input buffer */
+               ret = recv(conn->fd, buf, IO_BLOCK_SIZE, MSG_PEEK);
                if (ret <= 0) {
                        buffer_set_used_size(input, prev_size);
                        if (strchr(str_c(input), '\n') != NULL)
-                               script_verify_version(t_strcut(str_c(input), '\t'));
+                               script_verify_version(t_strcut(str_c(input), '\n'));
 
                        if (ret < 0)
-                               i_fatal("read() failed: %m");
-                       else
-                               i_fatal("read() failed: disconnected");
+                               i_fatal("recv(MSG_PEEK) failed: %m");
+
+                       i_fatal("recv(MSG_PEEK) failed: disconnected");
+               }
+
+               /* scan for final \n\n */
+               pos = CONST_PTR_OFFSET(input->data, scanpos);
+               end = CONST_PTR_OFFSET(input->data, prev_size + ret);
+               for (; pos < end; pos++) {
+                       if (pos[-1] == '\n' && pos[0] == '\n') {
+                               header_complete = TRUE;
+                               pos++;
+                               break;
+                       }
                }
-               buffer_set_used_size(input, prev_size + ret);
-               end = CONST_PTR_OFFSET(input->data, input->used);
-       } while (!(end[-1] == '\n' && (input->used == 1 || end[-2] == '\n')));
+               scanpos = pos - (const unsigned char *)input->data;
+
+               /* read data for real (up to and including \n\n) */
+               ret = recv(conn->fd, buf, scanpos-prev_size, 0);
+               if (prev_size+ret != scanpos) {
+                       if (ret < 0)
+                               i_fatal("recv() failed: %m");
+                       if (ret == 0)
+                               i_fatal("recv() failed: disconnected");
+                       i_fatal("recv() failed: size of definitive recv() differs from peek");
+               }
+               buffer_set_used_size(input, scanpos);
+       }
 
        /* drop the last LF */
-       buffer_set_used_size(input, input->used - 1);
+       buffer_set_used_size(input, scanpos-1);
 
        args = t_strsplit(str_c(input), "\n");
-       script_verify_version(*args);
+       script_verify_version(*args); args++;
+       if (*args != NULL) {
+               if (strcmp(*args, "noreply") == 0) {
+                       /* no need to fork and check exit status */
+                       exec_child(conn, args + 1);
+                       i_unreached();
+               }
+               args++;
+       }
 
-       for (args++; *args != NULL; args++)
-               array_append(&exec_args, args, 1);
-       (void)array_append_space(&exec_args);
+       if ((pid = fork()) == (pid_t)-1) {
+               i_error("fork() failed: %m");
+               return FALSE;
+       }
 
-       /* close all fds */
-       socket_count = master_service_get_socket_count(master_service);
-       for (i = 0; i < socket_count; i++) {
-               if (close(MASTER_LISTEN_FD_FIRST + i) < 0)
-                       i_error("close(listener) failed: %m");
+       if (pid == 0) {
+               /* child */
+               exec_child(conn, args);
+               i_unreached();
        }
-       if (close(MASTER_STATUS_FD) < 0)
-               i_error("close(status) failed: %m");
 
-       if (write(conn->fd, "1\n", 2) != 2)
-               i_error("write() failed: %m");
+       /* parent */
+
+       /* check script exit status */
+       if (waitpid(pid, &status, 0) < 0) {
+               i_error("waitpid() failed: %m");
+               return FALSE;
+       } else if (WIFEXITED(status)) {
+               ret = WEXITSTATUS(status);
+               if (ret != 0) {
+                       i_error("Script terminated abnormally, exit status %d", (int)ret);
+                       return FALSE;
+               }
+       } else if (WIFSIGNALED(status)) {
+               i_error("Script terminated abnormally, signal %d", WTERMSIG(status));
+               return FALSE;
+       } else if (WIFSTOPPED(status)) {
+               i_fatal("Script stopped, signal %d", WSTOPSIG(status));
+               return FALSE;
+       } else {
+               i_fatal("Script terminated abnormally, return status %d", status);
+               return FALSE;
+       }
+       return TRUE;
+}
 
-       if (dup2(conn->fd, STDIN_FILENO) < 0 ||
-           dup2(conn->fd, STDOUT_FILENO) < 0)
-               i_error("dup2() failed: %m");
-       if (close(conn->fd) < 0)
-               i_error("close() failed: %m");
+static void client_connected(struct master_service_connection *conn)
+{
+       char response[2];
 
-       env_clean();
-       args = array_idx(&exec_args, 0);
-       execvp_const(args[0], args);
+       response[0] = client_exec_script(conn) ? '+' : '-';
+       response[1] = '\n';
+       if (write_full(conn->fd, &response, 2) < 0)
+               i_error("write(response) failed: %m");
 }
 
 int main(int argc, char *argv[])
@@ -136,5 +226,5 @@ int main(int argc, char *argv[])
 
        master_service_run(master_service, client_connected);
        master_service_deinit(&master_service);
-        return 0;
+       return 0;
 }