summaryrefslogtreecommitdiff
path: root/src/libstrongswan/utils/process.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstrongswan/utils/process.c')
-rw-r--r--src/libstrongswan/utils/process.c592
1 files changed, 592 insertions, 0 deletions
diff --git a/src/libstrongswan/utils/process.c b/src/libstrongswan/utils/process.c
new file mode 100644
index 000000000..c863bdd10
--- /dev/null
+++ b/src/libstrongswan/utils/process.c
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2014 Martin Willi
+ * Copyright (C) 2014 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+/* vasprintf() */
+#define _GNU_SOURCE
+#include "process.h"
+
+#include <library.h>
+#include <utils/debug.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+typedef struct private_process_t private_process_t;
+
+/**
+ * Ends of a pipe()
+ */
+enum {
+ PIPE_READ = 0,
+ PIPE_WRITE = 1,
+ PIPE_ENDS,
+};
+
+#ifndef WIN32
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+/**
+ * Private data of an process_t object.
+ */
+struct private_process_t {
+
+ /**
+ * Public process_t interface.
+ */
+ process_t public;
+
+ /**
+ * child stdin pipe
+ */
+ int in[PIPE_ENDS];
+
+ /**
+ * child stdout pipe
+ */
+ int out[PIPE_ENDS];
+
+ /**
+ * child stderr pipe
+ */
+ int err[PIPE_ENDS];
+
+ /**
+ * child process
+ */
+ int pid;
+};
+
+/**
+ * Close a file descriptor if it is not -1
+ */
+static void close_if(int *fd)
+{
+ if (*fd != -1)
+ {
+ close(*fd);
+ *fd = -1;
+ }
+}
+
+/**
+ * Destroy a process structure, close all pipes
+ */
+static void process_destroy(private_process_t *this)
+{
+ close_if(&this->in[PIPE_READ]);
+ close_if(&this->in[PIPE_WRITE]);
+ close_if(&this->out[PIPE_READ]);
+ close_if(&this->out[PIPE_WRITE]);
+ close_if(&this->err[PIPE_READ]);
+ close_if(&this->err[PIPE_WRITE]);
+ free(this);
+}
+
+METHOD(process_t, wait_, bool,
+ private_process_t *this, int *code)
+{
+ int status, ret;
+
+ ret = waitpid(this->pid, &status, 0);
+ process_destroy(this);
+ if (ret == -1)
+ {
+ return FALSE;
+ }
+ if (!WIFEXITED(status))
+ {
+ return FALSE;
+ }
+ if (code)
+ {
+ *code = WEXITSTATUS(status);
+ }
+ return TRUE;
+}
+
+/**
+ * See header
+ */
+process_t* process_start(char *const argv[], char *const envp[],
+ int *in, int *out, int *err, bool close_all)
+{
+ private_process_t *this;
+ char *empty[] = { NULL };
+
+ INIT(this,
+ .public = {
+ .wait = _wait_,
+ },
+ .in = { -1, -1 },
+ .out = { -1, -1 },
+ .err = { -1, -1 },
+ );
+
+ if (in && pipe(this->in) != 0)
+ {
+ DBG1(DBG_LIB, "creating stdin pipe failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ }
+ if (out && pipe(this->out) != 0)
+ {
+ DBG1(DBG_LIB, "creating stdout pipe failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ }
+ if (err && pipe(this->err) != 0)
+ {
+ DBG1(DBG_LIB, "creating stderr pipe failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ }
+
+ this->pid = fork();
+ switch (this->pid)
+ {
+ case -1:
+ DBG1(DBG_LIB, "forking process failed: %s", strerror(errno));
+ process_destroy(this);
+ return NULL;
+ case 0:
+ /* child */
+ close_if(&this->in[PIPE_WRITE]);
+ close_if(&this->out[PIPE_READ]);
+ close_if(&this->err[PIPE_READ]);
+ if (this->in[PIPE_READ] != -1)
+ {
+ if (dup2(this->in[PIPE_READ], 0) == -1)
+ {
+ raise(SIGKILL);
+ }
+ }
+ if (this->out[PIPE_WRITE] != -1)
+ {
+ if (dup2(this->out[PIPE_WRITE], 1) == -1)
+ {
+ raise(SIGKILL);
+ }
+ }
+ if (this->err[PIPE_WRITE] != -1)
+ {
+ if (dup2(this->err[PIPE_WRITE], 2) == -1)
+ {
+ raise(SIGKILL);
+ }
+ }
+ if (close_all)
+ {
+ closefrom(3);
+ }
+ if (execve(argv[0], argv, envp ?: empty) == -1)
+ {
+ raise(SIGKILL);
+ }
+ /* not reached */
+ default:
+ /* parent */
+ close_if(&this->in[PIPE_READ]);
+ close_if(&this->out[PIPE_WRITE]);
+ close_if(&this->err[PIPE_WRITE]);
+ if (in)
+ {
+ *in = this->in[PIPE_WRITE];
+ this->in[PIPE_WRITE] = -1;
+ }
+ if (out)
+ {
+ *out = this->out[PIPE_READ];
+ this->out[PIPE_READ] = -1;
+ }
+ if (err)
+ {
+ *err = this->err[PIPE_READ];
+ this->err[PIPE_READ] = -1;
+ }
+ return &this->public;
+ }
+}
+
+/**
+ * See header
+ */
+process_t* process_start_shell(char *const envp[], int *in, int *out, int *err,
+ char *fmt, ...)
+{
+ char *argv[] = {
+ "/bin/sh",
+ "-c",
+ NULL,
+ NULL
+ };
+ process_t *process;
+ va_list args;
+ int len;
+
+ va_start(args, fmt);
+ len = vasprintf(&argv[2], fmt, args);
+ va_end(args);
+ if (len < 0)
+ {
+ return NULL;
+ }
+
+ process = process_start(argv, envp, in, out, err, TRUE);
+ free(argv[2]);
+ return process;
+}
+
+#else /* WIN32 */
+
+/**
+ * Private data of an process_t object.
+ */
+struct private_process_t {
+
+ /**
+ * Public process_t interface.
+ */
+ process_t public;
+
+ /**
+ * child stdin pipe
+ */
+ HANDLE in[PIPE_ENDS];
+
+ /**
+ * child stdout pipe
+ */
+ HANDLE out[PIPE_ENDS];
+
+ /**
+ * child stderr pipe
+ */
+ HANDLE err[PIPE_ENDS];
+
+ /**
+ * child process information
+ */
+ PROCESS_INFORMATION pi;
+};
+
+/**
+ * Clean up state associated to child process
+ */
+static void process_destroy(private_process_t *this)
+{
+ if (this->in[PIPE_READ])
+ {
+ CloseHandle(this->in[PIPE_READ]);
+ }
+ if (this->in[PIPE_WRITE])
+ {
+ CloseHandle(this->in[PIPE_WRITE]);
+ }
+ if (this->out[PIPE_READ])
+ {
+ CloseHandle(this->out[PIPE_READ]);
+ }
+ if (this->out[PIPE_WRITE])
+ {
+ CloseHandle(this->out[PIPE_WRITE]);
+ }
+ if (this->err[PIPE_READ])
+ {
+ CloseHandle(this->err[PIPE_READ]);
+ }
+ if (this->err[PIPE_WRITE])
+ {
+ CloseHandle(this->err[PIPE_WRITE]);
+ }
+ if (this->pi.hProcess)
+ {
+ CloseHandle(this->pi.hProcess);
+ CloseHandle(this->pi.hThread);
+ }
+ free(this);
+}
+
+METHOD(process_t, wait_, bool,
+ private_process_t *this, int *code)
+{
+ DWORD ec;
+
+ if (WaitForSingleObject(this->pi.hProcess, INFINITE) != WAIT_OBJECT_0)
+ {
+ DBG1(DBG_LIB, "waiting for child process failed: 0x%08x",
+ GetLastError());
+ process_destroy(this);
+ return FALSE;
+ }
+ if (code)
+ {
+ if (!GetExitCodeProcess(this->pi.hProcess, &ec))
+ {
+ DBG1(DBG_LIB, "getting child process exit code failed: 0x%08x",
+ GetLastError());
+ process_destroy(this);
+ return FALSE;
+ }
+ *code = ec;
+ }
+ process_destroy(this);
+ return TRUE;
+}
+
+/**
+ * Append a command line argument to buf, optionally quoted
+ */
+static void append_arg(char *buf, u_int len, char *arg, char *quote)
+{
+ char *space = "";
+ int current;
+
+ current = strlen(buf);
+ if (current)
+ {
+ space = " ";
+ }
+ snprintf(buf + current, len - current, "%s%s%s%s", space, quote, arg, quote);
+}
+
+/**
+ * Append a null-terminate env string to buf
+ */
+static void append_env(char *buf, u_int len, char *env)
+{
+ char *pos = buf;
+ int current;
+
+ while (TRUE)
+ {
+ pos += strlen(pos);
+ if (!pos[1])
+ {
+ if (pos == buf)
+ {
+ current = 0;
+ }
+ else
+ {
+ current = pos - buf + 1;
+ }
+ snprintf(buf + current, len - current, "%s", env);
+ break;
+ }
+ pos++;
+ }
+}
+
+/**
+ * See header
+ */
+process_t* process_start(char *const argv[], char *const envp[],
+ int *in, int *out, int *err, bool close_all)
+{
+ private_process_t *this;
+ char arg[32768], env[32768];
+ SECURITY_ATTRIBUTES sa = {
+ .nLength = sizeof(SECURITY_ATTRIBUTES),
+ .bInheritHandle = TRUE,
+ };
+ STARTUPINFO sui = {
+ .cb = sizeof(STARTUPINFO),
+ };
+ int i;
+
+ memset(arg, 0, sizeof(arg));
+ memset(env, 0, sizeof(env));
+
+ for (i = 0; argv[i]; i++)
+ {
+ if (!strchr(argv[i], ' '))
+ { /* no spaces, fine for appending */
+ append_arg(arg, sizeof(arg) - 1, argv[i], "");
+ }
+ else if (argv[i][0] == '"' &&
+ argv[i][strlen(argv[i]) - 1] == '"' &&
+ strchr(argv[i] + 1, '"') == argv[i] + strlen(argv[i]) - 1)
+ { /* already properly quoted */
+ append_arg(arg, sizeof(arg) - 1, argv[i], "");
+ }
+ else if (strchr(argv[i], ' ') && !strchr(argv[i], '"'))
+ { /* spaces, but no quotes; append quoted */
+ append_arg(arg, sizeof(arg) - 1, argv[i], "\"");
+ }
+ else
+ {
+ DBG1(DBG_LIB, "invalid command line argument: %s", argv[i]);
+ return NULL;
+ }
+ }
+ if (envp)
+ {
+ for (i = 0; envp[i]; i++)
+ {
+ append_env(env, sizeof(env) - 1, envp[i]);
+ }
+ }
+
+ INIT(this,
+ .public = {
+ .wait = _wait_,
+ },
+ );
+
+ if (in)
+ {
+ sui.dwFlags = STARTF_USESTDHANDLES;
+ if (!CreatePipe(&this->in[PIPE_READ], &this->in[PIPE_WRITE], &sa, 0))
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ if (!SetHandleInformation(this->in[PIPE_WRITE], HANDLE_FLAG_INHERIT, 0))
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ sui.hStdInput = this->in[PIPE_READ];
+ *in = _open_osfhandle((uintptr_t)this->in[PIPE_WRITE], 0);
+ if (*in == -1)
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ }
+ if (out)
+ {
+ sui.dwFlags = STARTF_USESTDHANDLES;
+ if (!CreatePipe(&this->out[PIPE_READ], &this->out[PIPE_WRITE], &sa, 0))
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ if (!SetHandleInformation(this->out[PIPE_READ], HANDLE_FLAG_INHERIT, 0))
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ sui.hStdOutput = this->out[PIPE_WRITE];
+ *out = _open_osfhandle((uintptr_t)this->out[PIPE_READ], 0);
+ if (*out == -1)
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ }
+ if (err)
+ {
+ sui.dwFlags = STARTF_USESTDHANDLES;
+ if (!CreatePipe(&this->err[PIPE_READ], &this->err[PIPE_WRITE], &sa, 0))
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ if (!SetHandleInformation(this->err[PIPE_READ], HANDLE_FLAG_INHERIT, 0))
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ sui.hStdError = this->err[PIPE_WRITE];
+ *err = _open_osfhandle((uintptr_t)this->err[PIPE_READ], 0);
+ if (*err == -1)
+ {
+ process_destroy(this);
+ return NULL;
+ }
+ }
+
+ if (!CreateProcess(argv[0], arg, NULL, NULL, TRUE,
+ NORMAL_PRIORITY_CLASS, env, NULL, &sui, &this->pi))
+ {
+ DBG1(DBG_LIB, "creating process '%s' failed: 0x%08x",
+ argv[0], GetLastError());
+ process_destroy(this);
+ return NULL;
+ }
+
+ /* close child process end of pipes */
+ if (this->in[PIPE_READ])
+ {
+ CloseHandle(this->in[PIPE_READ]);
+ this->in[PIPE_READ] = NULL;
+ }
+ if (this->out[PIPE_WRITE])
+ {
+ CloseHandle(this->out[PIPE_WRITE]);
+ this->out[PIPE_WRITE] = NULL;
+ }
+ if (this->err[PIPE_WRITE])
+ {
+ CloseHandle(this->err[PIPE_WRITE]);
+ this->err[PIPE_WRITE] = NULL;
+ }
+ /* our side gets closed over the osf_handle closed by caller */
+ this->in[PIPE_WRITE] = NULL;
+ this->out[PIPE_READ] = NULL;
+ this->err[PIPE_READ] = NULL;
+ return &this->public;
+}
+
+/**
+ * See header
+ */
+process_t* process_start_shell(char *const envp[], int *in, int *out, int *err,
+ char *fmt, ...)
+{
+ char path[MAX_PATH], *exe = "system32\\cmd.exe";
+ char *argv[] = {
+ path,
+ "/C",
+ NULL,
+ NULL
+ };
+ process_t *process;
+ va_list args;
+ int len;
+
+ len = GetSystemWindowsDirectory(path, sizeof(path));
+ if (len == 0 || len >= sizeof(path) - strlen(exe))
+ {
+ DBG1(DBG_LIB, "resolving Windows directory failed: 0x%08x",
+ GetLastError());
+ return NULL;
+ }
+ if (path[len + 1] != '\\')
+ {
+ strncat(path, "\\", sizeof(path) - len++);
+ }
+ strncat(path, exe, sizeof(path) - len);
+
+ va_start(args, fmt);
+ len = vasprintf(&argv[2], fmt, args);
+ va_end(args);
+ if (len < 0)
+ {
+ return NULL;
+ }
+
+ process = process_start(argv, envp, in, out, err, TRUE);
+ free(argv[2]);
+ return process;
+}
+
+#endif /* WIN32 */