diff options
Diffstat (limited to 'src/libstrongswan/utils/process.c')
-rw-r--r-- | src/libstrongswan/utils/process.c | 592 |
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 */ |