diff options
Diffstat (limited to 'src/libstrongswan/utils')
-rw-r--r-- | src/libstrongswan/utils/backtrace.c | 1 | ||||
-rw-r--r-- | src/libstrongswan/utils/chunk.c | 31 | ||||
-rw-r--r-- | src/libstrongswan/utils/chunk.h | 25 | ||||
-rw-r--r-- | src/libstrongswan/utils/leak_detective.c | 2 | ||||
-rw-r--r-- | src/libstrongswan/utils/process.c | 592 | ||||
-rw-r--r-- | src/libstrongswan/utils/process.h | 97 | ||||
-rw-r--r-- | src/libstrongswan/utils/utils.h | 17 |
7 files changed, 764 insertions, 1 deletions
diff --git a/src/libstrongswan/utils/backtrace.c b/src/libstrongswan/utils/backtrace.c index e694caec7..6dd68d60e 100644 --- a/src/libstrongswan/utils/backtrace.c +++ b/src/libstrongswan/utils/backtrace.c @@ -319,6 +319,7 @@ static bfd_entry_t *get_bfd_entry(char *filename) if (size == 0) { size = bfd_get_dynamic_symtab_upper_bound(entry->abfd); + dynamic = TRUE; } if (size >= 0) { diff --git a/src/libstrongswan/utils/chunk.c b/src/libstrongswan/utils/chunk.c index 1a9674f4d..4b24b37c2 100644 --- a/src/libstrongswan/utils/chunk.c +++ b/src/libstrongswan/utils/chunk.c @@ -990,6 +990,37 @@ u_int32_t chunk_hash_static(chunk_t chunk) /** * Described in header. */ +u_int16_t chunk_internet_checksum_inc(chunk_t data, u_int16_t checksum) +{ + u_int32_t sum = ntohs(~checksum); + + while (data.len > 1) + { + sum += untoh16(data.ptr); + data = chunk_skip(data, 2); + } + if (data.len) + { + sum += (u_int16_t)*data.ptr << 8; + } + while (sum >> 16) + { + sum = (sum & 0xffff) + (sum >> 16); + } + return htons(~sum); +} + +/** + * Described in header. + */ +u_int16_t chunk_internet_checksum(chunk_t data) +{ + return chunk_internet_checksum_inc(data, 0xffff); +} + +/** + * Described in header. + */ int chunk_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec, const void *const *args) { diff --git a/src/libstrongswan/utils/chunk.h b/src/libstrongswan/utils/chunk.h index 9951ff31f..48405b77e 100644 --- a/src/libstrongswan/utils/chunk.h +++ b/src/libstrongswan/utils/chunk.h @@ -412,6 +412,31 @@ u_int32_t chunk_hash_static_inc(chunk_t chunk, u_int32_t hash); u_int64_t chunk_mac(chunk_t chunk, u_char *key); /** + * Calculate the Internet Checksum according to RFC 1071 for the given chunk. + * + * If the result is used with chunk_internet_checksum_inc() and the data length + * is not a multiple of 16 bit the checksum bytes have to be swapped to + * compensate the even/odd alignment. + * + * @param data data to process + * @return checksum (one's complement, network order) + */ +u_int16_t chunk_internet_checksum(chunk_t data); + +/** + * Extend the given Internet Checksum (one's complement, in network byte order) + * with the given data. + * + * If data is not a multiple of 16 bits the checksum may have to be swapped to + * compensate even/odd alignment (see chunk_internet_checksum()). + * + * @param data data to process + * @param checksum previous checksum (one's complement, network order) + * @return checksum (one's complement, network order) + */ +u_int16_t chunk_internet_checksum_inc(chunk_t data, u_int16_t checksum); + +/** * printf hook function for chunk_t. * * Arguments are: diff --git a/src/libstrongswan/utils/leak_detective.c b/src/libstrongswan/utils/leak_detective.c index a2bca193d..bc8432aea 100644 --- a/src/libstrongswan/utils/leak_detective.c +++ b/src/libstrongswan/utils/leak_detective.c @@ -561,6 +561,8 @@ char *whitelist[] = { "ECDSA_do_sign_ex", "ECDSA_verify", "RSA_new_method", + /* OpenSSL libssl */ + "SSL_COMP_get_compression_methods", /* NSPR */ "PR_CallOnce", /* libapr */ 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 */ diff --git a/src/libstrongswan/utils/process.h b/src/libstrongswan/utils/process.h new file mode 100644 index 000000000..81719201c --- /dev/null +++ b/src/libstrongswan/utils/process.h @@ -0,0 +1,97 @@ +/* + * 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. + */ + +/** + * @defgroup process process + * @{ @ingroup utils + */ + +#ifndef PROCESS_H_ +#define PROCESS_H_ + +#include <utils/utils.h> + +typedef struct process_t process_t; + +/** + * Child process spawning abstraction + */ +struct process_t { + + /** + * Wait for a started process to terminate. + * + * The process object gets destroyed by this call, regardless of the + * return value. + * + * The returned code is the exit code, not the status returned by waitpid(). + * If the program could not be executed or has terminated abnormally + * (by signals etc.), FALSE is returned. + * + * @param code process exit code, set only if TRUE returned + * @return TRUE if program exited normally through exit() + */ + bool (*wait)(process_t *this, int *code); +}; + +/** + * Spawn a child process with redirected I/O. + * + * Forks the current process, optionally redirects stdin/out/err to the current + * process, and executes the provided program with arguments. + * + * The process to execute is specified as argv[0], followed by the process + * arguments, followed by NULL. envp[] has a NULL terminated list of arguments + * to invoke the process with. + * + * If any of in/out/err is given, stdin/out/err from the child process get + * connected over pipe()s to the caller. If close_all is TRUE, all other + * open file descriptors get closed, regardless of any CLOEXEC setting. + * + * A caller must close all of the returned file descriptors to avoid file + * descriptor leaks. + * + * A non-NULL return value does not guarantee that the process has been + * invoked successfully. + * + * @param argv NULL terminated process arguments, with argv[0] as program + * @param envp NULL terminated list of environment variables + * @param in pipe fd returned for redirecting data to child stdin + * @param out pipe fd returned to redirect child stdout data to + * @param err pipe fd returned to redirect child stderr data to + * @param close_all close all open file descriptors above 2 before execve() + * @return process, NULL on failure + */ +process_t* process_start(char *const argv[], char *const envp[], + int *in, int *out, int *err, bool close_all); + +/** + * Spawn a command in a shell child process. + * + * Same as process_start(), but passes a single command to a shell, such as + * "sh -c". See process_start() for I/O redirection notes. + * + * @param envp NULL terminated list of environment variables + * @param in pipe fd returned for redirecting data to child stdin + * @param out pipe fd returned to redirect child stdout data to + * @param err pipe fd returned to redirect child stderr data to + * @param fmt printf format string for command + * @param ... arguments for fmt + * @return process, NULL on failure + */ +process_t* process_start_shell(char *const envp[], int *in, int *out, int *err, + char *fmt, ...); + +#endif /** PROCESS_H_ @}*/ diff --git a/src/libstrongswan/utils/utils.h b/src/libstrongswan/utils/utils.h index 1b822dd61..da253cc35 100644 --- a/src/libstrongswan/utils/utils.h +++ b/src/libstrongswan/utils/utils.h @@ -60,6 +60,20 @@ #define BUF_LEN 512 /** + * Build assertion macro for integer expressions, evaluates to 0 + */ +#define BUILD_ASSERT(x) (sizeof(char[(x) ? 0 : -1])) + +/** + * Build time check to assert a is an array, evaluates to 0 + * + * The address of an array element has a pointer type, which is not compatible + * to the array type. + */ +#define BUILD_ASSERT_ARRAY(a) \ + BUILD_ASSERT(!__builtin_types_compatible_p(typeof(a), typeof(&(a)[0]))) + +/** * General purpose boolean type. */ #ifdef HAVE_STDBOOL_H @@ -342,7 +356,8 @@ static inline void *memset_noop(void *s, int c, size_t n) /** * Get the number of elements in an array */ -#define countof(array) (sizeof(array)/sizeof(array[0])) +#define countof(array) (sizeof(array)/sizeof((array)[0]) \ + + BUILD_ASSERT_ARRAY(array)) /** * Ignore result of functions tagged with warn_unused_result attributes |