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'
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
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"
lib-fs \
lib-mail \
lib-imap \
- lib-imap-storage
+ lib-imap-storage \
+ lib-program-client
SUBDIRS = \
$(LIBDOVECOT_SUBDIRS) \
--- /dev/null
+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
+/* 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
+/* 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
+/* 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
+/* 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
+/* 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