593 lines
11 KiB
C
593 lines
11 KiB
C
/*
|
|
* 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 */
|