Mercurial > dovecot > core-2.2
changeset 20785:3c8e6a9835d8
lib-program-client: Add program-client from pigeonhole
author | Aki Tuomi <aki.tuomi@dovecot.fi> |
---|---|
date | Mon, 12 Sep 2016 13:02:23 +0300 |
parents | 16517812f2cb |
children | 10510f5116ff |
files | configure.ac dovecot-config.in.in src/Makefile.am src/lib-program-client/Makefile.am src/lib-program-client/program-client-local.c src/lib-program-client/program-client-private.h src/lib-program-client/program-client-remote.c src/lib-program-client/program-client.c src/lib-program-client/program-client.h |
diffstat | 9 files changed, 1479 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.ac Thu Sep 29 09:51:40 2016 +0300 +++ b/configure.ac Mon Sep 12 13:02:23 2016 +0300 @@ -2547,7 +2547,7 @@ dnl ** -LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la' +LIBDOVECOT_LA_LIBS='$(top_builddir)/src/lib-dict-extra/libdict_extra.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-stats/libstats.la $(top_builddir)/src/lib-http/libhttp.la $(top_builddir)/src/lib-fs/libfs.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-dns/libdns.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-sasl/libsasl.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-ssl-iostream/libssl_iostream.la $(top_builddir)/src/lib-dcrypt/libdcrypt.la $(top_builddir)/src/lib-program-client/libprogram_client.la $(top_builddir)/src/lib-test/libtest.la $(top_builddir)/src/lib/liblib.la' if test "$want_shared_libs" = "yes"; then LIBDOVECOT_DEPS='$(top_builddir)/src/lib-dovecot/libdovecot.la' @@ -2909,6 +2909,7 @@ src/lib-mail/Makefile src/lib-master/Makefile src/lib-ntlm/Makefile +src/lib-program-client/Makefile src/lib-otp/Makefile src/lib-dovecot/Makefile src/lib-sasl/Makefile
--- a/dovecot-config.in.in Thu Sep 29 09:51:40 2016 +0300 +++ b/dovecot-config.in.in Mon Sep 12 13:02:23 2016 +0300 @@ -22,7 +22,7 @@ LIBDOVECOT_DSYNC_DEPS="@LIBDOVECOT_DSYNC@" LIBDOVECOT_LIBFTS_DEPS="@LIBDOVECOT_LIBFTS@" -LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-http -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-ssl-iostream -I$(incdir)/src/lib-compression -I$(incdir)/src/lib-settings -I$(incdir)/src/lib-test -I$(incdir)/src/lib-sasl -I$(incdir)/src/lib-stats -I$(incdir)/src/lib-dcrypt" +LIBDOVECOT_INCLUDE="-I$(incdir) -I$(incdir)/src/lib -I$(incdir)/src/lib-dict -I$(incdir)/src/lib-dns -I$(incdir)/src/lib-http -I$(incdir)/src/lib-mail -I$(incdir)/src/lib-imap -I$(incdir)/src/lib-fs -I$(incdir)/src/lib-charset -I$(incdir)/src/lib-auth -I$(incdir)/src/lib-master -I$(incdir)/src/lib-ssl-iostream -I$(incdir)/src/lib-compression -I$(incdir)/src/lib-settings -I$(incdir)/src/lib-test -I$(incdir)/src/lib-sasl -I$(incdir)/src/lib-stats -I$(incdir)/src/lib-dcrypt -I$(incdir)/src/lib-program-client" LIBDOVECOT_LDA_INCLUDE="-I$(incdir)/src/lib-lda -I$(incdir)/src/lda" LIBDOVECOT_AUTH_INCLUDE="-I$(incdir)/src/auth" LIBDOVECOT_DOVEADM_INCLUDE="-I$(incdir)/src/doveadm"
--- a/src/Makefile.am Thu Sep 29 09:51:40 2016 +0300 +++ b/src/Makefile.am Mon Sep 12 13:02:23 2016 +0300 @@ -19,7 +19,8 @@ lib-fs \ lib-mail \ lib-imap \ - lib-imap-storage + lib-imap-storage \ + lib-program-client SUBDIRS = \ $(LIBDOVECOT_SUBDIRS) \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-program-client/Makefile.am Mon Sep 12 13:02:23 2016 +0300 @@ -0,0 +1,18 @@ +noinst_LTLIBRARIES = libprogram_client.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib + +libprogram_client_la_SOURCES = \ + program-client.c \ + program-client-local.c \ + program-client-remote.c + +headers = \ + program-client.h + +noinst_HEADERS = \ + program-client-private.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-program-client/program-client-local.c Mon Sep 12 13:02:23 2016 +0300 @@ -0,0 +1,445 @@ +/* Copyright (c) 2002-2016 Dovecot 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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-program-client/program-client-private.h Mon Sep 12 13:02:23 2016 +0300 @@ -0,0 +1,69 @@ +/* Copyright (c) 2002-2016 Dovecot 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-program-client/program-client-remote.c Mon Sep 12 13:02:23 2016 +0300 @@ -0,0 +1,334 @@ +/* Copyright (c) 2002-2016 Dovecot 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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-program-client/program-client.c Mon Sep 12 13:02:23 2016 +0300 @@ -0,0 +1,554 @@ +/* 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) +{ + int ret, error = FALSE; + + 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; + const unsigned char *data; + size_t size; + 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) { + do { + while ((data=i_stream_get_data(input, &size)) != NULL) { + ssize_t sent; + + if ((sent=o_stream_send(output, data, size)) < 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 -1; + } + + if (sent == 0) + return 0; + i_stream_skip(input, sent); + } + } while ((ret=i_stream_read(input)) > 0); + + if (ret == 0) + return 1; + + 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 -1; + } else if (!i_stream_have_bytes_left(input)) { + i_stream_unref(&pclient->input); + input = NULL; + + 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) { + 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; + 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) { + while ((ret=i_stream_read_data(input, &data, &size, 0)) > 0) { + if (output != NULL) { + ssize_t sent; + + if ((sent=o_stream_send(output, data, size)) < 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; + } + size = (size_t)sent; + } + + i_stream_skip(input, size); + } + + 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); + } else { + 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, FALSE); + 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, FALSE); + + 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, FALSE); + 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; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-program-client/program-client.h Mon Sep 12 13:02:23 2016 +0300 @@ -0,0 +1,54 @@ +/* Copyright (c) 2002-2016 Dovecot 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