diff --git a/src/lib-sieve/util/Makefile.am b/src/lib-sieve/util/Makefile.am index 6e3bc959ba60135365baff4306a6d8a297e9998b..8b049f78556b248432f5a50d2abf2ab726948d2b 100644 --- a/src/lib-sieve/util/Makefile.am +++ b/src/lib-sieve/util/Makefile.am @@ -10,18 +10,12 @@ libsieve_util_la_DEPENDENCIES = $(LIBDOVECOT_STORAGE_DEPS) $(LIBDOVECOT_DEPS) libsieve_util_la_SOURCES = \ edit-mail.c \ rfc2822.c \ - program-client-local.c \ - program-client-remote.c \ - program-client.c \ realpath.c \ strtrim.c headers = \ edit-mail.h \ rfc2822.h \ - program-client-private.h \ - program-client.h \ - realpath.h \ strtrim.h pkginc_libdir=$(dovecot_pkgincludedir)/sieve diff --git a/src/lib-sieve/util/program-client-local.c b/src/lib-sieve/util/program-client-local.c deleted file mode 100644 index 0839a7a76bb9693aed3d2c746be482d30127c68b..0000000000000000000000000000000000000000 --- a/src/lib-sieve/util/program-client-local.c +++ /dev/null @@ -1,446 +0,0 @@ -/* Copyright (c) 2002-2016 Pigeonhole authors, see the included COPYING file - */ - -#include "lib.h" -#include "lib-signals.h" -#include "env-util.h" -#include "execv-const.h" -#include "array.h" -#include "net.h" -#include "istream.h" -#include "ostream.h" - -#include "program-client-private.h" - -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/stat.h> -#include <sys/wait.h> -#include <unistd.h> -#include <fcntl.h> -#include <grp.h> - -struct program_client_local { - struct program_client client; - - pid_t pid; -}; - -static void exec_child -(const char *bin_path, const char *const *args, const char *const *envs, - int in_fd, int out_fd, int *extra_fds, bool drop_stderr) -{ - ARRAY_TYPE(const_string) exec_args; - - /* Setup stdin/stdout */ - - if ( in_fd < 0 ) { - in_fd = open("/dev/null", O_RDONLY); - - if ( in_fd == -1 ) - i_fatal("open(/dev/null) failed: %m"); - } - if ( out_fd < 0 ) { - out_fd = open("/dev/null", O_WRONLY); - - if ( out_fd == -1 ) - i_fatal("open(/dev/null) failed: %m"); - } - - if ( in_fd != STDIN_FILENO && - dup2(in_fd, STDIN_FILENO) < 0 ) - i_fatal("dup2(stdin) failed: %m"); - if ( out_fd != STDOUT_FILENO && - dup2(out_fd, STDOUT_FILENO) < 0 ) - i_fatal("dup2(stdout) failed: %m"); - - if ( in_fd != STDIN_FILENO && - close(in_fd) < 0 ) - i_error("close(in_fd) failed: %m"); - if ( out_fd != STDOUT_FILENO && - (out_fd != in_fd) && close(out_fd) < 0 ) - i_error("close(out_fd) failed: %m"); - - /* Drop stderr if requested */ - if ( drop_stderr ) { - int err_fd = open("/dev/null", O_WRONLY); - if ( err_fd == -1 ) - i_fatal("open(/dev/null) failed: %m"); - if ( err_fd != STDERR_FILENO ) { - if ( dup2(err_fd, STDERR_FILENO) < 0 ) - i_fatal("dup2(stderr) failed: %m"); - if ( close(err_fd) < 0 ) - i_error("close(err_fd) failed: %m"); - } - } - - /* Setup extra fds */ - if ( extra_fds != NULL ) { - int *efd; - for (efd = extra_fds; *efd != -1; efd += 2 ) { - i_assert(efd[1] != STDIN_FILENO); - i_assert(efd[1] != STDOUT_FILENO); - i_assert(efd[1] != STDERR_FILENO); - if ( efd[0] != efd[1] ) { - if ( dup2(efd[0], efd[1]) < 0 ) - i_fatal("dup2(extra_fd=%d) failed: %m", efd[1]); - } - } - for (efd = extra_fds; *efd != -1; efd += 2 ) { - if ( efd[0] != efd[1] && efd[0] != STDIN_FILENO && - efd[0] != STDOUT_FILENO && efd[0] != STDERR_FILENO) { - if ( close(efd[0]) < 0 ) - i_error("close(extra_fd=%d) failed: %m", efd[1]); - } - } - } - - /* Compose argv */ - - t_array_init(&exec_args, 16); - array_append(&exec_args, &bin_path, 1); - if ( args != NULL ) { - for (; *args != NULL; args++) - array_append(&exec_args, args, 1); - } - (void)array_append_space(&exec_args); - - /* Setup environment */ - - env_clean(); - if ( envs != NULL ) { - for (; *envs != NULL; envs++) - env_put(*envs); - } - - /* Execute */ - - args = array_idx(&exec_args, 0); - execvp_const(args[0], args); -} - -static int program_client_local_connect -(struct program_client *pclient) -{ - struct program_client_local *slclient = - (struct program_client_local *) pclient; - int fd_in[2] = { -1, -1 }, fd_out[2] = { -1, -1 }; - struct program_client_extra_fd *efds = NULL; - int *parent_extra_fds = NULL, *child_extra_fds = NULL; - unsigned int xfd_count = 0, i; - - /* create normal I/O fds */ - if ( pclient->input != NULL ) { - if ( pipe(fd_in) < 0 ) { - i_error("pipe(in) failed: %m"); - return -1; - } - } - if ( pclient->output != NULL || pclient->output_seekable ) { - if ( pipe(fd_out) < 0 ) { - i_error("pipe(out) failed: %m"); - return -1; - } - } - - /* create pipes for additional output through side-channel fds */ - if ( array_is_created(&pclient->extra_fds) ) { - int extra_fd[2]; - - efds = array_get_modifiable(&pclient->extra_fds, &xfd_count); - if ( xfd_count > 0 ) { - parent_extra_fds = t_malloc0(sizeof(int) * xfd_count); - child_extra_fds = t_malloc0(sizeof(int) * xfd_count * 2 + 1); - for ( i = 0; i < xfd_count; i++ ) { - if ( pipe(extra_fd) < 0 ) { - i_error("pipe(extra=%d) failed: %m", extra_fd[1]); - return -1; - } - parent_extra_fds[i] = extra_fd[0]; - child_extra_fds[i*2+0] = extra_fd[1]; - child_extra_fds[i*2+1] = efds[i].child_fd; - } - child_extra_fds[xfd_count*2] = -1; - } - } - - /* fork child */ - if ( (slclient->pid = fork()) == (pid_t)-1 ) { - i_error("fork() failed: %m"); - - /* clean up */ - if ( fd_in[0] >= 0 && close(fd_in[0]) < 0 ) { - i_error("close(pipe:in:rd) failed: %m"); - } - if ( fd_in[1] >= 0 && close(fd_in[1]) < 0 ) { - i_error("close(pipe:in:wr) failed: %m"); - } - if ( fd_out[0] >= 0 && close(fd_out[0]) < 0 ) { - i_error("close(pipe:out:rd) failed: %m"); - } - if ( fd_out[1] >= 0 && close(fd_out[1]) < 0 ) { - i_error("close(pipe:out:wr) failed: %m"); - } - for ( i = 0; i < xfd_count; i++ ) { - if ( close(child_extra_fds[i*2]) < 0 ) { - i_error("close(pipe:extra=%d:wr) failed: %m", - child_extra_fds[i*2+1]); - } - if ( close(parent_extra_fds[i]) < 0 ) { - i_error("close(pipe:extra=%d:rd) failed: %m", - child_extra_fds[i*2+1]); - } - } - return -1; - } - - if ( slclient->pid == 0 ) { - unsigned int count; - const char *const *envs = NULL; - - /* child */ - if ( fd_in[1] >= 0 && close(fd_in[1]) < 0 ) - i_error("close(pipe:in:wr) failed: %m"); - if ( fd_out[0] >= 0 && close(fd_out[0]) < 0 ) - i_error("close(pipe:out:rd) failed: %m"); - for ( i = 0; i < xfd_count; i++ ) { - if ( close(parent_extra_fds[i]) < 0 ) { - i_error("close(pipe:extra=%d:rd) failed: %m", - child_extra_fds[i*2+1]); - } - } - - /* drop privileges if we have any */ - if ( getuid() == 0 ) { - uid_t uid; - gid_t gid; - - /* switch back to root */ - if (seteuid(0) < 0) - i_fatal("seteuid(0) failed: %m"); - - /* drop gids first */ - gid = getgid(); - if ( gid == 0 || gid != pclient->set.gid ) { - if ( pclient->set.gid != 0 ) - gid = pclient->set.gid; - else - gid = getegid(); - } - if ( setgroups(1, &gid) < 0 ) - i_fatal("setgroups(%d) failed: %m", gid); - if ( gid != 0 && setgid(gid) < 0 ) - i_fatal("setgid(%d) failed: %m", gid); - - /* drop uid */ - if ( pclient->set.uid != 0 ) - uid = pclient->set.uid; - else - uid = geteuid(); - if ( uid != 0 && setuid(uid) < 0 ) - i_fatal("setuid(%d) failed: %m", uid); - } - - i_assert(pclient->set.uid == 0 || getuid() != 0); - i_assert(pclient->set.gid == 0 || getgid() != 0); - - if ( array_is_created(&pclient->envs) ) - envs = array_get(&pclient->envs, &count); - - exec_child(pclient->path, pclient->args, envs, - fd_in[0], fd_out[1], child_extra_fds, - pclient->set.drop_stderr); - i_unreached(); - } - - /* parent */ - if ( fd_in[0] >= 0 && close(fd_in[0]) < 0 ) - i_error("close(pipe:in:rd) failed: %m"); - if ( fd_out[1] >= 0 && close(fd_out[1]) < 0 ) - i_error("close(pipe:out:wr) failed: %m"); - if ( fd_in[1] >= 0 ) { - net_set_nonblock(fd_in[1], TRUE); - pclient->fd_out = fd_in[1]; - } - if ( fd_out[0] >= 0 ) { - net_set_nonblock(fd_out[0], TRUE); - pclient->fd_in = fd_out[0]; - } - for ( i = 0; i < xfd_count; i++ ) { - if ( close(child_extra_fds[i*2]) < 0 ) { - i_error("close(pipe:extra=%d:wr) failed: %m", - child_extra_fds[i*2+1]); - } - net_set_nonblock(parent_extra_fds[i], TRUE); - efds[i].parent_fd = parent_extra_fds[i]; - } - - program_client_init_streams(pclient); - return program_client_connected(pclient); -} - -static int program_client_local_close_output(struct program_client *pclient) -{ - int fd_out = pclient->fd_out; - - pclient->fd_out = -1; - - /* Shutdown output; program stdin will get EOF */ - if ( fd_out >= 0 && close(fd_out) < 0 ) { - i_error("close(%s) failed: %m", pclient->path); - return -1; - } - return 1; -} - -static int program_client_local_disconnect -(struct program_client *pclient, bool force) -{ - struct program_client_local *slclient = - (struct program_client_local *) pclient; - pid_t pid = slclient->pid, ret; - time_t runtime, timeout = 0; - int status; - - if ( pid < 0 ) { - /* program never started */ - pclient->exit_code = 0; - return 0; - } - - slclient->pid = -1; - - /* Calculate timeout */ - runtime = ioloop_time - pclient->start_time; - if ( !force && pclient->set.input_idle_timeout_secs > 0 && - runtime < (time_t)pclient->set.input_idle_timeout_secs ) - timeout = pclient->set.input_idle_timeout_secs - runtime; - - if ( pclient->debug ) { - i_debug("waiting for program `%s' to finish after %llu seconds", - pclient->path, (unsigned long long int)runtime); - } - - /* Wait for child to exit */ - force = force || - (timeout == 0 && pclient->set.input_idle_timeout_secs > 0); - if ( !force ) { - alarm(timeout); - ret = waitpid(pid, &status, 0); - alarm(0); - } - if ( force || ret < 0 ) { - if ( !force && errno != EINTR ) { - i_error("waitpid(%s) failed: %m", pclient->path); - (void)kill(pid, SIGKILL); - return -1; - } - - /* Timed out */ - force = TRUE; - if ( pclient->error == PROGRAM_CLIENT_ERROR_NONE ) - pclient->error = PROGRAM_CLIENT_ERROR_RUN_TIMEOUT; - if ( pclient->debug ) { - i_debug("program `%s' execution timed out after %llu seconds: " - "sending TERM signal", pclient->path, - (unsigned long long int)pclient->set.input_idle_timeout_secs); - } - - /* Kill child gently first */ - if ( kill(pid, SIGTERM) < 0 ) { - i_error("failed to send SIGTERM signal to program `%s'", pclient->path); - (void)kill(pid, SIGKILL); - return -1; - } - - /* Wait for it to die (give it some more time) */ - alarm(5); - ret = waitpid(pid, &status, 0); - alarm(0); - if ( ret < 0 ) { - if ( errno != EINTR ) { - i_error("waitpid(%s) failed: %m", pclient->path); - (void)kill(pid, SIGKILL); - return -1; - } - - /* Timed out again */ - if ( pclient->debug ) { - i_debug("program `%s' execution timed out: sending KILL signal", - pclient->path); - } - - /* Kill it brutally now */ - if ( kill(pid, SIGKILL) < 0 ) { - i_error("failed to send SIGKILL signal to program `%s'", - pclient->path); - return -1; - } - - /* Now it will die immediately */ - if ( waitpid(pid, &status, 0) < 0 ) { - i_error("waitpid(%s) failed: %m", pclient->path); - return -1; - } - } - } - - /* Evaluate child exit status */ - pclient->exit_code = -1; - if ( WIFEXITED(status) ) { - /* Exited */ - int exit_code = WEXITSTATUS(status); - - if ( exit_code != 0 ) { - i_info("program `%s' terminated with non-zero exit code %d", - pclient->path, exit_code); - pclient->exit_code = 0; - return 0; - } - - pclient->exit_code = 1; - return 1; - - } else if ( WIFSIGNALED(status) ) { - /* Killed with a signal */ - - if ( force ) { - i_error("program `%s' was forcibly terminated with signal %d", - pclient->path, WTERMSIG(status)); - } else { - i_error("program `%s' terminated abnormally, signal %d", - pclient->path, WTERMSIG(status)); - } - return -1; - - } else if ( WIFSTOPPED(status) ) { - /* Stopped */ - i_error("program `%s' stopped, signal %d", - pclient->path, WSTOPSIG(status)); - return -1; - } - - /* Something else */ - i_error("program `%s' terminated abnormally, return status %d", - pclient->path, status); - return -1; -} - -struct program_client *program_client_local_create -(const char *bin_path, const char *const *args, - const struct program_client_settings *set) -{ - struct program_client_local *pclient; - pool_t pool; - - pool = pool_alloconly_create("program client local", 1024); - pclient = p_new(pool, struct program_client_local, 1); - program_client_init(&pclient->client, pool, bin_path, args, set); - pclient->client.connect = program_client_local_connect; - pclient->client.close_output = program_client_local_close_output; - pclient->client.disconnect = program_client_local_disconnect; - pclient->pid = -1; - - return &pclient->client; -} - diff --git a/src/lib-sieve/util/program-client-private.h b/src/lib-sieve/util/program-client-private.h deleted file mode 100644 index 06f91689dace92680355719eda3162e2a98901ee..0000000000000000000000000000000000000000 --- a/src/lib-sieve/util/program-client-private.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright (c) 2002-2016 Pigeonhole authors, see the included COPYING file - */ - -#ifndef __PROGRAM_CLIENT_PRIVATE_H -#define __PROGRAM_CLIENT_PRIVATE_H - -#include "program-client.h" - -enum program_client_error { - PROGRAM_CLIENT_ERROR_NONE, - PROGRAM_CLIENT_ERROR_CONNECT_TIMEOUT, - PROGRAM_CLIENT_ERROR_RUN_TIMEOUT, - PROGRAM_CLIENT_ERROR_IO, - PROGRAM_CLIENT_ERROR_OTHER -}; - -struct program_client_extra_fd { - struct program_client *pclient; - - int child_fd, parent_fd; - struct istream *input; - struct io *io; - - program_client_fd_callback_t *callback; - void *context; -}; - -struct program_client { - pool_t pool; - struct program_client_settings set; - - char *path; - const char **args; - ARRAY_TYPE(const_string) envs; - - int fd_in, fd_out; - struct io *io; - struct ioloop *ioloop; - struct timeout *to; - time_t start_time; - - struct istream *input, *program_input, *seekable_output; - struct ostream *output, *program_output; - char *temp_prefix; - - ARRAY(struct program_client_extra_fd) extra_fds; - - enum program_client_error error; - int exit_code; - - int (*connect)(struct program_client *pclient); - int (*close_output)(struct program_client *pclient); - int (*disconnect)(struct program_client *pclient, bool force); - - bool debug:1; - bool disconnected:1; - bool output_seekable:1; -}; - -void program_client_init - (struct program_client *pclient, pool_t pool, const char *path, - const char *const *args, const struct program_client_settings *set); - -void program_client_init_streams(struct program_client *pclient); - -int program_client_connected(struct program_client *pclient); - -void program_client_fail - (struct program_client *pclient, enum program_client_error error); - -#endif - diff --git a/src/lib-sieve/util/program-client-remote.c b/src/lib-sieve/util/program-client-remote.c deleted file mode 100644 index f1e87878eb5d133e526fcba6a212fc6536a69109..0000000000000000000000000000000000000000 --- a/src/lib-sieve/util/program-client-remote.c +++ /dev/null @@ -1,317 +0,0 @@ -/* Copyright (c) 2002-2016 Pigeonhole authors, see the included COPYING file - */ - -#include "lib.h" -#include "ioloop.h" -#include "str.h" -#include "net.h" -#include "write-full.h" -#include "eacces-error.h" -#include "istream-private.h" -#include "ostream.h" - -#include "program-client-private.h" - -#include <unistd.h> -#include <sys/wait.h> -#include <sysexits.h> - -/* - * Script client input stream - */ - -struct program_client_istream { - struct istream_private istream; - - struct stat statbuf; - - struct program_client *client; -}; - -static void program_client_istream_destroy(struct iostream_private *stream) -{ - struct program_client_istream *scstream = - (struct program_client_istream *)stream; - - i_stream_unref(&scstream->istream.parent); -} - -static ssize_t program_client_istream_read(struct istream_private *stream) -{ - struct program_client_istream *scstream = - (struct program_client_istream *)stream; - size_t pos, reserved; - ssize_t ret = 0; - - i_stream_skip(stream->parent, stream->skip); - stream->skip = 0; - - stream->buffer = i_stream_get_data(stream->parent, &pos); - - reserved = 0; - if ( stream->buffer != NULL && pos >= 1 ) { - /* retain/hide potential return code at end of buffer */ - reserved = ( stream->buffer[pos-1] == '\n' && pos > 1 ? 2 : 1 ); - pos -= reserved; - } - - if ( stream->parent->eof ) { - if (pos == 0) - i_stream_skip(stream->parent, reserved); - stream->istream.eof = TRUE; - ret = -1; - } else do { - if ((ret = i_stream_read(stream->parent)) == -2) { - return -2; /* input buffer full */ - } - - if ( ret == 0 || (ret < 0 && !stream->parent->eof) ) break; - - stream->istream.stream_errno = stream->parent->stream_errno; - stream->buffer = i_stream_get_data(stream->parent, &pos); - - if ( stream->parent->eof ) { - /* Check return code at EOF */ - if ( stream->buffer != NULL && pos >= 2 && - stream->buffer[pos-1] == '\n' ) { - switch ( stream->buffer[pos-2] ) { - case '+': - scstream->client->exit_code = 1; - break; - case '-': - scstream->client->exit_code = 0; - break; - default: - scstream->client->exit_code = -1; - } - } else { - scstream->client->exit_code = -1; - } - } - - if ( stream->buffer != NULL && pos >= 1 ) { - /* retain/hide potential return code at end of buffer */ - size_t old_reserved = reserved; - ssize_t reserve_mod; - - reserved = ( stream->buffer[pos-1] == '\n' && pos > 1 ? 2 : 1 ); - reserve_mod = reserved - old_reserved; - pos -= reserved; - - if (ret >= reserve_mod) { - ret -= reserve_mod; - } - } - - if ( ret <= 0 && stream->parent->eof ) { - /* Parent EOF and not more data to return; EOF here as well */ - if (pos == 0) - i_stream_skip(stream->parent, reserved); - stream->istream.eof = TRUE; - ret = -1; - } - } while ( ret == 0 ); - - stream->pos = pos; - - i_assert(ret != -1 || stream->istream.eof || - stream->istream.stream_errno != 0); - return ret; -} - -static void ATTR_NORETURN program_client_istream_sync -(struct istream_private *stream ATTR_UNUSED) -{ - i_panic("program_client_istream sync() not implemented"); -} - -static int program_client_istream_stat -(struct istream_private *stream, bool exact) -{ - struct program_client_istream *scstream = - (struct program_client_istream *)stream; - const struct stat *st; - int ret; - - /* Stat the original stream */ - ret = i_stream_stat(stream->parent, exact, &st); - if (ret < 0 || st->st_size == -1 || !exact) - return ret; - - scstream->statbuf = *st; - scstream->statbuf.st_size = -1; - - return ret; -} - -static struct istream *program_client_istream_create -(struct program_client *program_client, struct istream *input) -{ - struct program_client_istream *scstream; - - scstream = i_new(struct program_client_istream, 1); - scstream->client = program_client; - - scstream->istream.max_buffer_size = input->real_stream->max_buffer_size; - - scstream->istream.iostream.destroy = program_client_istream_destroy; - scstream->istream.read = program_client_istream_read; - scstream->istream.sync = program_client_istream_sync; - scstream->istream.stat = program_client_istream_stat; - - scstream->istream.istream.readable_fd = FALSE; - scstream->istream.istream.blocking = input->blocking; - scstream->istream.istream.seekable = FALSE; - - i_stream_seek(input, 0); - - return i_stream_create(&scstream->istream, input, -1); -} - -/* - * Program client - */ - -struct program_client_remote { - struct program_client client; - - bool noreply:1; -}; - -static void program_client_remote_connected(struct program_client *pclient) -{ - struct program_client_remote *slclient = - (struct program_client_remote *)pclient; - const char **args = pclient->args; - string_t *str; - - io_remove(&pclient->io); - program_client_init_streams(pclient); - - if ( !slclient->noreply ) { - pclient->program_input = program_client_istream_create - (pclient, pclient->program_input); - } - - str = t_str_new(1024); - str_append(str, "VERSION\tscript\t3\t0\n"); - if ( slclient->noreply ) - str_append(str, "noreply\n"); - else - str_append(str, "-\n"); - if ( args != NULL ) { - for (; *args != NULL; args++) { - str_append(str, *args); - str_append_c(str, '\n'); - } - } - str_append_c(str, '\n'); - - if ( o_stream_send - (pclient->program_output, str_data(str), str_len(str)) < 0 ) { - i_error("write(%s) failed: %s", - o_stream_get_name(pclient->program_output), - o_stream_get_error(pclient->program_output)); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - return; - } - - (void)program_client_connected(pclient); -} - -static int program_client_remote_connect(struct program_client *pclient) -{ - struct program_client_remote *slclient = - (struct program_client_remote *)pclient; - int fd; - - if ((fd = net_connect_unix_with_retries(pclient->path, 1000)) < 0) { - switch (errno) { - case EACCES: - i_error("%s", eacces_error_get("net_connect_unix", pclient->path)); - return -1; - default: - i_error("net_connect_unix(%s) failed: %m", pclient->path); - return -1; - } - } - - net_set_nonblock(fd, TRUE); - - pclient->fd_in = ( slclient->noreply && pclient->output == NULL && - !pclient->output_seekable ? -1 : fd ); - pclient->fd_out = fd; - pclient->io = io_add(fd, IO_WRITE, program_client_remote_connected, pclient); - return 0; -} - -static int program_client_remote_close_output(struct program_client *pclient) -{ - int fd_out = pclient->fd_out, fd_in = pclient->fd_in; - - pclient->fd_out = -1; - - /* Shutdown output; program stdin will get EOF */ - if ( fd_out >= 0 ) { - if ( fd_in >= 0 ) { - if ( shutdown(fd_out, SHUT_WR) < 0 && errno != ENOTCONN ) { - i_error("shutdown(%s, SHUT_WR) failed: %m", pclient->path); - return -1; - } - } else if ( close(fd_out) < 0 ) { - i_error("close(%s) failed: %m", pclient->path); - return -1; - } - } - - return 1; -} - -static int program_client_remote_disconnect -(struct program_client *pclient, bool force) -{ - struct program_client_remote *slclient = - (struct program_client_remote *)pclient; - int ret = 0; - - if ( pclient->error == PROGRAM_CLIENT_ERROR_NONE && !slclient->noreply && - pclient->program_input != NULL && !force) { - const unsigned char *data; - size_t size; - - /* Skip any remaining program output and parse the exit code */ - while ((ret = i_stream_read_more - (pclient->program_input, &data, &size)) > 0) { - i_stream_skip(pclient->program_input, size); - } - - /* Get exit code */ - if ( !pclient->program_input->eof ) - ret = -1; - else - ret = pclient->exit_code; - } else { - ret = 1; - } - - return ret; -} - -struct program_client *program_client_remote_create -(const char *socket_path, const char *const *args, - const struct program_client_settings *set, bool noreply) -{ - struct program_client_remote *pclient; - pool_t pool; - - pool = pool_alloconly_create("program client remote", 1024); - pclient = p_new(pool, struct program_client_remote, 1); - program_client_init(&pclient->client, pool, socket_path, args, set); - pclient->client.connect = program_client_remote_connect; - pclient->client.close_output = program_client_remote_close_output; - pclient->client.disconnect = program_client_remote_disconnect; - pclient->noreply = noreply; - - return &pclient->client; -} - diff --git a/src/lib-sieve/util/program-client.c b/src/lib-sieve/util/program-client.c deleted file mode 100644 index d2370ef252e93cb2b3ee214c844347f6f1c48401..0000000000000000000000000000000000000000 --- a/src/lib-sieve/util/program-client.c +++ /dev/null @@ -1,550 +0,0 @@ -/* Copyright (c) 2002-2016 Pigeonhole authors, see the included COPYING file - */ - -#include "lib.h" -#include "ioloop.h" -#include "array.h" -#include "str.h" -#include "safe-mkstemp.h" -#include "istream-private.h" -#include "istream-seekable.h" -#include "ostream.h" - -#include "program-client-private.h" - -#include <unistd.h> - -#define MAX_OUTPUT_BUFFER_SIZE 16384 -#define MAX_OUTPUT_MEMORY_BUFFER (1024*128) - -static int program_client_seekable_fd_callback -(const char **path_r, void *context) -{ - struct program_client *pclient = (struct program_client *)context; - string_t *path; - int fd; - - path = t_str_new(128); - str_append(path, pclient->temp_prefix); - fd = safe_mkstemp(path, 0600, (uid_t)-1, (gid_t)-1); - if (fd == -1) { - i_error("safe_mkstemp(%s) failed: %m", str_c(path)); - return -1; - } - - /* we just want the fd, unlink it */ - if (i_unlink(str_c(path)) < 0) { - /* shouldn't happen.. */ - i_close_fd(&fd); - return -1; - } - - *path_r = str_c(path); - return fd; -} - -static void program_client_timeout(struct program_client *pclient) -{ - i_error("program `%s' execution timed out (> %d secs)", - pclient->path, pclient->set.input_idle_timeout_secs); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_RUN_TIMEOUT); -} - -static void program_client_connect_timeout(struct program_client *pclient) -{ - i_error("program `%s' socket connection timed out (> %d msecs)", - pclient->path, pclient->set.client_connect_timeout_msecs); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_CONNECT_TIMEOUT); -} - -static int program_client_connect(struct program_client *pclient) -{ - int ret; - - if (pclient->set.client_connect_timeout_msecs != 0) { - pclient->to = timeout_add - (pclient->set.client_connect_timeout_msecs, - program_client_connect_timeout, pclient); - } - - if ( (ret=pclient->connect(pclient)) < 0 ) { - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - return -1; - } - return ret; -} - -static int program_client_close_output(struct program_client *pclient) -{ - int ret; - - if ( pclient->program_output != NULL ) - o_stream_destroy(&pclient->program_output); - if ( (ret=pclient->close_output(pclient)) < 0 ) - return -1; - pclient->program_output = NULL; - - return ret; -} - -static void program_client_disconnect_extra_fds -(struct program_client *pclient) -{ - struct program_client_extra_fd *efds; - unsigned int i, count; - - if (!array_is_created(&pclient->extra_fds)) - return; - - efds = array_get_modifiable(&pclient->extra_fds, &count); - for ( i = 0; i < count; i++ ) { - if ( efds[i].input != NULL ) - i_stream_unref(&efds[i].input); - if ( efds[i].io != NULL ) - io_remove(&efds[i].io); - if ( efds[i].parent_fd != -1 && close(efds[i].parent_fd) < 0 ) - i_error("close(fd=%d) failed: %m", efds[i].parent_fd); - } -} - -static void program_client_disconnect -(struct program_client *pclient, bool force) -{ - bool error = FALSE; - int ret; - - if ( pclient->ioloop != NULL ) - io_loop_stop(pclient->ioloop); - - if ( pclient->disconnected ) - return; - - if ( (ret=program_client_close_output(pclient)) < 0 ) - error = TRUE; - - program_client_disconnect_extra_fds(pclient); - if ( (ret=pclient->disconnect(pclient, force)) < 0 ) - error = TRUE; - - if ( pclient->program_input != NULL ) { - if (pclient->output_seekable) - i_stream_unref(&pclient->program_input); - else - i_stream_destroy(&pclient->program_input); - } - if ( pclient->program_output != NULL ) - o_stream_destroy(&pclient->program_output); - - if ( pclient->to != NULL ) - timeout_remove(&pclient->to); - if ( pclient->io != NULL ) - io_remove(&pclient->io); - - if (pclient->fd_in != -1 && close(pclient->fd_in) < 0) - i_error("close(%s) failed: %m", pclient->path); - if (pclient->fd_out != -1 && pclient->fd_out != pclient->fd_in - && close(pclient->fd_out) < 0) - i_error("close(%s/out) failed: %m", pclient->path); - pclient->fd_in = pclient->fd_out = -1; - - pclient->disconnected = TRUE; - if (error && pclient->error == PROGRAM_CLIENT_ERROR_NONE ) { - pclient->error = PROGRAM_CLIENT_ERROR_OTHER; - } -} - -void program_client_fail -(struct program_client *pclient, enum program_client_error error) -{ - if ( pclient->error != PROGRAM_CLIENT_ERROR_NONE ) - return; - - pclient->error = error; - program_client_disconnect(pclient, TRUE); -} - -static bool program_client_input_pending(struct program_client *pclient) -{ - struct program_client_extra_fd *efds = NULL; - unsigned int count, i; - - if ( pclient->program_input != NULL && - !pclient->program_input->closed && - !i_stream_is_eof(pclient->program_input) ) { - return TRUE; - } - - if ( array_is_created(&pclient->extra_fds) ) { - efds = array_get_modifiable(&pclient->extra_fds, &count); - for ( i = 0; i < count; i++ ) { - if ( efds[i].input != NULL && - !efds[i].input->closed && - !i_stream_is_eof(efds[i].input) ) { - return TRUE; - } - } - } - - return FALSE; -} - -static int program_client_program_output(struct program_client *pclient) -{ - struct istream *input = pclient->input; - struct ostream *output = pclient->program_output; - enum ostream_send_istream_result res; - int ret = 0; - - if ((ret = o_stream_flush(output)) <= 0) { - if (ret < 0) { - i_error("write(%s) failed: %s", - o_stream_get_name(output), - o_stream_get_error(output)); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - } - return ret; - } - - if ( input != NULL && output != NULL ) { - res = o_stream_send_istream(output, input); - - switch (res) { - case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: - i_stream_unref(&pclient->input); - input = NULL; - break; - case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: - return 1; - case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: - return 0; - case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: - i_error("read(%s) failed: %s", - i_stream_get_name(input), - i_stream_get_error(input)); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - return -1; - case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: - i_error("write(%s) failed: %s", - o_stream_get_name(output), - o_stream_get_error(output)); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - return -1; - } - } - - if ( input == NULL ) { - if ( !program_client_input_pending(pclient) ) { - program_client_disconnect(pclient, FALSE); - } else if (program_client_close_output(pclient) < 0) { - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_OTHER); - } - } - return 1; -} - -static void program_client_program_input(struct program_client *pclient) -{ - struct istream *input = pclient->program_input; - struct ostream *output = pclient->output; - enum ostream_send_istream_result res; - const unsigned char *data; - size_t size; - int ret = 0; - - if (pclient->output_seekable && pclient->seekable_output == NULL) { - struct istream *input_list[2] = { input, NULL }; - - input = i_stream_create_seekable(input_list, MAX_OUTPUT_MEMORY_BUFFER, - program_client_seekable_fd_callback, pclient); - i_stream_unref(&pclient->program_input); - pclient->program_input = input; - - pclient->seekable_output = input; - i_stream_ref(pclient->seekable_output); - } - - if ( input != NULL ) { - if ( output != NULL ) { - res = o_stream_send_istream(output, input); - - switch (res) { - case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: - break; - case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: - case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: - return; - case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: - i_error("read(%s) failed: %s", - i_stream_get_name(input), - i_stream_get_error(input)); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - return; - case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: - i_error("write(%s) failed: %s", - o_stream_get_name(output), - o_stream_get_error(output)); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - return; - } - } else { - while ( (ret=i_stream_read_more(input, &data, &size)) > 0 || - ret == -2 ) { - i_stream_skip(input, size); - } - - if ( ret == 0 ) - return; - if ( ret < 0 ) { - if ( input->stream_errno != 0 ) { - i_error("read(%s) failed: %s", - i_stream_get_name(input), - i_stream_get_error(input)); - program_client_fail(pclient, PROGRAM_CLIENT_ERROR_IO); - return; - } - } - } - if ( !program_client_input_pending(pclient) ) - program_client_disconnect(pclient, FALSE); - } -} - -static void program_client_extra_fd_input -(struct program_client_extra_fd *efd) -{ - struct program_client *pclient = efd->pclient; - - i_assert(efd->callback != NULL); - efd->callback(efd->context, efd->input); - - if (efd->input->closed || i_stream_is_eof(efd->input)) { - if ( !program_client_input_pending(pclient) ) - program_client_disconnect(pclient, FALSE); - } -} - -int program_client_connected -(struct program_client *pclient) -{ - int ret = 1; - - pclient->start_time = ioloop_time; - if (pclient->to != NULL) - timeout_remove(&pclient->to); - if ( pclient->set.input_idle_timeout_secs != 0 ) { - pclient->to = timeout_add(pclient->set.input_idle_timeout_secs*1000, - program_client_timeout, pclient); - } - - /* run output */ - if ( pclient->program_output != NULL && - (ret=program_client_program_output(pclient)) == 0 ) { - if ( pclient->program_output != NULL ) { - o_stream_set_flush_callback - (pclient->program_output, program_client_program_output, pclient); - } - } - - return ret; -} - -void program_client_init -(struct program_client *pclient, pool_t pool, const char *path, - const char *const *args, const struct program_client_settings *set) -{ - pclient->pool = pool; - pclient->path = p_strdup(pool, path); - if ( args != NULL ) - pclient->args = p_strarray_dup(pool, args); - pclient->set = *set; - pclient->debug = set->debug; - pclient->fd_in = -1; - pclient->fd_out = -1; -} - -void program_client_set_input -(struct program_client *pclient, struct istream *input) -{ - if ( pclient->input != NULL ) - i_stream_unref(&pclient->input); - if ( input != NULL ) - i_stream_ref(input); - pclient->input = input; -} - -void program_client_set_output -(struct program_client *pclient, struct ostream *output) -{ - if ( pclient->output != NULL ) - o_stream_unref(&pclient->output); - if ( output != NULL ) - o_stream_ref(output); - pclient->output = output; - pclient->output_seekable = FALSE; - i_free(pclient->temp_prefix); -} - -void program_client_set_output_seekable -(struct program_client *pclient, const char *temp_prefix) -{ - if ( pclient->output != NULL ) - o_stream_unref(&pclient->output); - pclient->temp_prefix = i_strdup(temp_prefix); - pclient->output_seekable = TRUE; -} - -struct istream *program_client_get_output_seekable -(struct program_client *pclient) -{ - struct istream *input = pclient->seekable_output; - - pclient->seekable_output = NULL; - - i_stream_seek(input, 0); - return input; -} - -#undef program_client_set_extra_fd -void program_client_set_extra_fd -(struct program_client *pclient, int fd, - program_client_fd_callback_t *callback, void *context) -{ - struct program_client_extra_fd *efds; - struct program_client_extra_fd *efd = NULL; - unsigned int i, count; - i_assert(fd > 1); - - if ( !array_is_created(&pclient->extra_fds) ) - p_array_init(&pclient->extra_fds, pclient->pool, 2); - - efds = array_get_modifiable(&pclient->extra_fds, &count); - for ( i = 0; i < count; i++ ) { - if ( efds[i].child_fd == fd ) { - efd = &efds[i]; - break; - } - } - - if ( efd == NULL ) { - efd = array_append_space(&pclient->extra_fds); - efd->pclient = pclient; - efd->child_fd = fd; - efd->parent_fd = -1; - } - efd->callback = callback; - efd->context = context; -} - -void program_client_set_env -(struct program_client *pclient, const char *name, const char *value) -{ - const char *env; - - if ( !array_is_created(&pclient->envs) ) - p_array_init(&pclient->envs, pclient->pool, 16); - - env = p_strdup_printf(pclient->pool, "%s=%s", name, value); - array_append(&pclient->envs, &env, 1); -} - -void program_client_init_streams(struct program_client *pclient) -{ - /* Create streams for normal program I/O */ - if ( pclient->fd_out >= 0 ) { - pclient->program_output = - o_stream_create_fd(pclient->fd_out, MAX_OUTPUT_BUFFER_SIZE); - o_stream_set_name(pclient->program_output, "program stdin"); - } - if ( pclient->fd_in >= 0 ) { - struct istream *input; - - input = i_stream_create_fd(pclient->fd_in, (size_t)-1); - - pclient->program_input = input; - i_stream_set_name(pclient->program_input, "program stdout"); - - pclient->io = io_add - (pclient->fd_in, IO_READ, program_client_program_input, pclient); - } - - /* Create streams for additional output through side-channel fds */ - if ( array_is_created(&pclient->extra_fds) ) { - struct program_client_extra_fd *efds = NULL; - unsigned int count, i; - - efds = array_get_modifiable(&pclient->extra_fds, &count); - for ( i = 0; i < count; i++ ) { - i_assert( efds[i].parent_fd >= 0 ); - efds[i].input = i_stream_create_fd - (efds[i].parent_fd, (size_t)-1); - i_stream_set_name(efds[i].input, - t_strdup_printf("program output fd=%d", efds[i].child_fd)); - efds[i].io = io_add - (efds[i].parent_fd, IO_READ, program_client_extra_fd_input, &efds[i]); - } - } -} - -void program_client_destroy(struct program_client **_pclient) -{ - struct program_client *pclient = *_pclient; - - program_client_disconnect(pclient, TRUE); - - if ( pclient->input != NULL ) - i_stream_unref(&pclient->input); - if ( pclient->output != NULL ) - o_stream_unref(&pclient->output); - if ( pclient->seekable_output != NULL ) - i_stream_unref(&pclient->seekable_output); - if ( pclient->io != NULL ) - io_remove(&pclient->io); - if ( pclient->ioloop != NULL ) - io_loop_destroy(&pclient->ioloop); - i_free(pclient->temp_prefix); - pool_unref(&pclient->pool); - *_pclient = NULL; -} - -int program_client_run(struct program_client *pclient) -{ - int ret; - - /* reset */ - pclient->disconnected = FALSE; - pclient->exit_code = 1; - pclient->error = PROGRAM_CLIENT_ERROR_NONE; - - pclient->ioloop = io_loop_create(); - - if ( (ret=program_client_connect(pclient)) >= 0 ) { - /* run output */ - if ( ret > 0 && pclient->program_output != NULL && - (ret=o_stream_flush(pclient->program_output)) == 0 ) { - o_stream_set_flush_callback - (pclient->program_output, program_client_program_output, pclient); - } - - /* run i/o event loop */ - if ( ret < 0 ) { - i_error("write(%s) failed: %s", - o_stream_get_name(pclient->program_output), - o_stream_get_error(pclient->program_output)); - pclient->error = PROGRAM_CLIENT_ERROR_IO; - } else if ( !pclient->disconnected && - (ret == 0 || program_client_input_pending(pclient)) ) { - io_loop_run(pclient->ioloop); - } - - /* finished */ - program_client_disconnect(pclient, FALSE); - } - - io_loop_destroy(&pclient->ioloop); - - if ( pclient->error != PROGRAM_CLIENT_ERROR_NONE ) - return -1; - - return pclient->exit_code; -} - diff --git a/src/lib-sieve/util/program-client.h b/src/lib-sieve/util/program-client.h deleted file mode 100644 index 9f7fdd91e736c0a08fe87d062f1fda822a06f1d3..0000000000000000000000000000000000000000 --- a/src/lib-sieve/util/program-client.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright (c) 2002-2016 Pigeonhole authors, see the included COPYING file - */ - -#ifndef __PROGRAM_CLIENT_H -#define __PROGRAM_CLIENT_H - -struct program_client; - -struct program_client_settings { - unsigned int client_connect_timeout_msecs; - unsigned int input_idle_timeout_secs; - - uid_t uid; - gid_t gid; - - bool debug:1; - bool drop_stderr:1; -}; - -typedef void program_client_fd_callback_t - (void *context, struct istream *input); - -struct program_client *program_client_local_create - (const char *bin_path, const char *const *args, - const struct program_client_settings *set); -struct program_client *program_client_remote_create - (const char *socket_path, const char *const *args, - const struct program_client_settings *set, bool noreply); - -void program_client_destroy(struct program_client **_pclient); - -void program_client_set_input - (struct program_client *pclient, struct istream *input); -void program_client_set_output - (struct program_client *pclient, struct ostream *output); - -void program_client_set_output_seekable - (struct program_client *pclient, const char *temp_prefix); -struct istream *program_client_get_output_seekable - (struct program_client *pclient); - -/* Program provides side-channel output through an extra fd */ -void program_client_set_extra_fd - (struct program_client *pclient, int fd, - program_client_fd_callback_t *callback, void *context); -#define program_client_set_extra_fd(pclient, fd, callback, context) \ - program_client_set_extra_fd(pclient, fd + \ - CALLBACK_TYPECHECK(callback, \ - void (*)(typeof(context), struct istream *input)), \ - (program_client_fd_callback_t *)callback, context) - -void program_client_set_env - (struct program_client *pclient, const char *name, const char *value); - -int program_client_run(struct program_client *pclient); - -#endif -