457 lines
11 KiB
C++
457 lines
11 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file p3dAuthSession.cxx
|
|
* @author drose
|
|
* @date 2009-09-17
|
|
*/
|
|
|
|
#include "p3dAuthSession.h"
|
|
#include "p3dInstance.h"
|
|
#include "p3dInstanceManager.h"
|
|
#include "p3dMultifileReader.h"
|
|
#include "p3d_plugin_config.h"
|
|
#include "mkdir_complete.h"
|
|
#include "wstring_encode.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <fcntl.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/select.h>
|
|
#include <signal.h>
|
|
#include <dlfcn.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
using std::string;
|
|
|
|
/**
|
|
*
|
|
*/
|
|
P3DAuthSession::
|
|
P3DAuthSession(P3DInstance *inst) :
|
|
_inst(inst)
|
|
{
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
#ifdef _WIN32
|
|
_p3dcert_handle = INVALID_HANDLE_VALUE;
|
|
#else
|
|
_p3dcert_pid = -1;
|
|
#endif
|
|
_p3dcert_started = false;
|
|
_p3dcert_running = false;
|
|
_started_wait_thread = false;
|
|
INIT_THREAD(_wait_thread);
|
|
|
|
// Write the cert chain to a temporary file.
|
|
_cert_filename = new P3DTemporaryFile(".crt");
|
|
string filename = _cert_filename->get_filename();
|
|
FILE *fp = fopen(filename.c_str(), "w");
|
|
if (fp == nullptr) {
|
|
nout << "Couldn't write temporary file\n";
|
|
return;
|
|
}
|
|
|
|
if (inst->_mf_reader.get_num_signatures() > 0) {
|
|
const P3DMultifileReader::CertChain &cert_chain =
|
|
inst->_mf_reader.get_signature(0);
|
|
|
|
if (cert_chain.size() > 0) {
|
|
// Save the cert_dir, this is where the p3dcert program will need to
|
|
// write the cert when it is approved.
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
_cert_dir = inst_mgr->get_cert_dir(cert_chain[0]._cert);
|
|
}
|
|
|
|
P3DMultifileReader::CertChain::const_iterator ci;
|
|
for (ci = cert_chain.begin(); ci != cert_chain.end(); ++ci) {
|
|
X509 *c = (*ci)._cert;
|
|
PEM_write_X509(fp, c);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
|
|
start_p3dcert();
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
P3DAuthSession::
|
|
~P3DAuthSession() {
|
|
shutdown(false);
|
|
|
|
if (_cert_filename != nullptr) {
|
|
delete _cert_filename;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Terminates the session by killing the subprocess.
|
|
*/
|
|
void P3DAuthSession::
|
|
shutdown(bool send_message) {
|
|
if (!send_message) {
|
|
// If we're not to send the instance the shutdown message as a result of
|
|
// this, then clear the _inst pointer now.
|
|
_inst = nullptr;
|
|
}
|
|
|
|
if (_p3dcert_running) {
|
|
nout << "Killing p3dcert process\n";
|
|
#ifdef _WIN32
|
|
TerminateProcess(_p3dcert_handle, 2);
|
|
CloseHandle(_p3dcert_handle);
|
|
|
|
#else // _WIN32
|
|
kill(_p3dcert_pid, SIGKILL);
|
|
|
|
// Wait a few milliseconds for the process to exit, and then get its
|
|
// return status to clean up the zombie status. If we don't wait long
|
|
// enough, don't sweat it.
|
|
struct timeval tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_usec = 100000;
|
|
select(0, nullptr, nullptr, nullptr, &tv);
|
|
int status;
|
|
waitpid(_p3dcert_pid, &status, WNOHANG);
|
|
#endif // _WIN32
|
|
|
|
_p3dcert_running = false;
|
|
}
|
|
_p3dcert_started = false;
|
|
|
|
// Now that the process has stopped, the thread should stop itself quickly
|
|
// too.
|
|
join_wait_thread();
|
|
|
|
// We're no longer bound to any particular instance.
|
|
_inst = nullptr;
|
|
}
|
|
|
|
/**
|
|
* Starts the p3dcert program running in a child process.
|
|
*/
|
|
void P3DAuthSession::
|
|
start_p3dcert() {
|
|
if (_p3dcert_started) {
|
|
// Already started.
|
|
return;
|
|
}
|
|
|
|
if (_inst->_p3dcert_package == nullptr) {
|
|
nout << "Couldn't start Python: no p3dcert package.\n";
|
|
return;
|
|
}
|
|
|
|
P3DInstanceManager *inst_mgr = P3DInstanceManager::get_global_ptr();
|
|
|
|
_start_dir = inst_mgr->get_start_dir();
|
|
|
|
string root_dir = _inst->_p3dcert_package->get_package_dir();
|
|
mkdir_complete(_start_dir, nout);
|
|
|
|
_p3dcert_exe = root_dir + "/p3dcert";
|
|
#ifdef _WIN32
|
|
_p3dcert_exe += ".exe";
|
|
#endif
|
|
#ifdef __APPLE__
|
|
// On OSX, run from the packaged bundle.
|
|
_p3dcert_exe = root_dir + "/P3DCert.app/Contents/MacOS/p3dcert";
|
|
#endif
|
|
|
|
// Populate the new process' environment.
|
|
|
|
#ifdef _WIN32
|
|
// These are the enviroment variables we forward from the current
|
|
// environment, if they are set.
|
|
const wchar_t *keep[] = {
|
|
L"TMP", L"TEMP", L"HOME", L"USER",
|
|
L"SYSTEMROOT", L"USERPROFILE", L"COMSPEC",
|
|
nullptr
|
|
};
|
|
|
|
std::wstring env_w;
|
|
|
|
for (int ki = 0; keep[ki] != nullptr; ++ki) {
|
|
wchar_t *value = _wgetenv(keep[ki]);
|
|
if (value != nullptr) {
|
|
env_w += keep[ki];
|
|
env_w += L"=";
|
|
env_w += value;
|
|
env_w += (wchar_t)'\0';
|
|
}
|
|
}
|
|
|
|
wstring_to_string(_env, env_w);
|
|
|
|
#else // _WIN32
|
|
|
|
_env = string();
|
|
|
|
// These are the enviroment variables we forward from the current
|
|
// environment, if they are set.
|
|
const char *keep[] = {
|
|
"TMP", "TEMP", "HOME", "USER",
|
|
"LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG",
|
|
#ifdef HAVE_X11
|
|
"DISPLAY", "XAUTHORITY",
|
|
#endif
|
|
nullptr
|
|
};
|
|
for (int ki = 0; keep[ki] != nullptr; ++ki) {
|
|
char *value = getenv(keep[ki]);
|
|
if (value != nullptr) {
|
|
_env += keep[ki];
|
|
_env += "=";
|
|
_env += value;
|
|
_env += '\0';
|
|
}
|
|
}
|
|
#endif // _WIN32
|
|
|
|
// Define some new environment variables.
|
|
_env += "PATH=";
|
|
_env += root_dir;
|
|
_env += '\0';
|
|
|
|
_env += "LD_LIBRARY_PATH=";
|
|
_env += root_dir;
|
|
_env += '\0';
|
|
|
|
_env += "DYLD_LIBRARY_PATH=";
|
|
_env += root_dir;
|
|
_env += '\0';
|
|
|
|
_env += "P3DCERT_ROOT=";
|
|
_env += root_dir;
|
|
_env += '\0';
|
|
|
|
nout << "Setting environment:\n";
|
|
write_env();
|
|
|
|
nout << "Attempting to start p3dcert from " << _p3dcert_exe << "\n";
|
|
|
|
bool started_p3dcert = false;
|
|
#ifdef _WIN32
|
|
_p3dcert_handle = win_create_process();
|
|
started_p3dcert = (_p3dcert_handle != INVALID_HANDLE_VALUE);
|
|
#else
|
|
_p3dcert_pid = posix_create_process();
|
|
started_p3dcert = (_p3dcert_pid > 0);
|
|
#endif
|
|
if (!started_p3dcert) {
|
|
nout << "Failed to create process.\n";
|
|
return;
|
|
}
|
|
|
|
_p3dcert_started = true;
|
|
_p3dcert_running = true;
|
|
|
|
spawn_wait_thread();
|
|
}
|
|
|
|
/**
|
|
* Starts the wait thread. This thread is responsible for waiting for the
|
|
* process to finish, and notifying the instance when it does.
|
|
*/
|
|
void P3DAuthSession::
|
|
spawn_wait_thread() {
|
|
SPAWN_THREAD(_wait_thread, wt_thread_run, this);
|
|
_started_wait_thread = true;
|
|
}
|
|
|
|
/**
|
|
* Waits for the wait thread to stop.
|
|
*/
|
|
void P3DAuthSession::
|
|
join_wait_thread() {
|
|
if (!_started_wait_thread) {
|
|
return;
|
|
}
|
|
|
|
JOIN_THREAD(_wait_thread);
|
|
_started_wait_thread = false;
|
|
}
|
|
|
|
/**
|
|
* Writes _env, which is formatted as a string containing zero-byte-terminated
|
|
* environment defintions, to the nout stream, one definition per line.
|
|
*/
|
|
void P3DAuthSession::
|
|
write_env() const {
|
|
size_t p = 0;
|
|
size_t zero = _env.find('\0', p);
|
|
while (zero != string::npos) {
|
|
nout << " ";
|
|
nout.write(_env.data() + p, zero - p);
|
|
nout << "\n";
|
|
p = zero + 1;
|
|
zero = _env.find('\0', p);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The main function for the wait thread.
|
|
*/
|
|
void P3DAuthSession::
|
|
wt_thread_run() {
|
|
// All we do here is wait for the process to terminate.
|
|
nout << "wt_thread_run in " << this << " beginning\n";
|
|
#ifdef _WIN32
|
|
DWORD result = WaitForSingleObject(_p3dcert_handle, INFINITE);
|
|
if (result != 0) {
|
|
nout << "Wait for process failed: " << GetLastError() << "\n";
|
|
}
|
|
CloseHandle(_p3dcert_handle);
|
|
_p3dcert_handle = INVALID_HANDLE_VALUE;
|
|
_p3dcert_running = false;
|
|
nout << "p3dcert process has successfully stopped.\n";
|
|
#else
|
|
int status;
|
|
pid_t result = waitpid(_p3dcert_pid, &status, 0);
|
|
if (result == -1) {
|
|
perror("waitpid");
|
|
}
|
|
_p3dcert_pid = -1;
|
|
_p3dcert_running = false;
|
|
|
|
nout << "p3dcert process has successfully stopped.\n";
|
|
if (WIFEXITED(status)) {
|
|
nout << " exited normally, status = "
|
|
<< WEXITSTATUS(status) << "\n";
|
|
} else if (WIFSIGNALED(status)) {
|
|
nout << " signalled by " << WTERMSIG(status) << ", core = "
|
|
<< WCOREDUMP(status) << "\n";
|
|
} else if (WIFSTOPPED(status)) {
|
|
nout << " stopped by " << WSTOPSIG(status) << "\n";
|
|
}
|
|
#endif // _WIN32
|
|
|
|
// Notify the instance that we're done.
|
|
P3DInstance *inst = _inst;
|
|
if (inst != nullptr) {
|
|
inst->auth_finished_sub_thread();
|
|
}
|
|
|
|
nout << "Exiting wt_thread_run in " << this << "\n";
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* Creates a sub-process to run _p3dcert_exe, with the appropriate command-
|
|
* line arguments, and the environment string defined in _env.
|
|
*
|
|
* Returns the handle to the created process on success, or
|
|
* INVALID_HANDLE_VALUE on falure.
|
|
*/
|
|
HANDLE P3DAuthSession::
|
|
win_create_process() {
|
|
// Make sure we see an error dialog if there is a missing DLL.
|
|
SetErrorMode(0);
|
|
|
|
STARTUPINFO startup_info;
|
|
ZeroMemory(&startup_info, sizeof(startup_info));
|
|
startup_info.cb = sizeof(startup_info);
|
|
|
|
// Make sure the initial window is *shown* for this graphical app.
|
|
startup_info.wShowWindow = SW_SHOW;
|
|
startup_info.dwFlags |= STARTF_USESHOWWINDOW;
|
|
|
|
const char *start_dir_cstr = _start_dir.c_str();
|
|
|
|
// Construct the command-line string, containing the quoted command-line
|
|
// arguments.
|
|
std::ostringstream stream;
|
|
stream << "\"" << _p3dcert_exe << "\" \""
|
|
<< _cert_filename->get_filename() << "\" \"" << _cert_dir << "\"";
|
|
|
|
// I'm not sure why CreateProcess wants a non-const char pointer for its
|
|
// command-line string, but I'm not taking chances. It gets a non-const
|
|
// char array that it can modify.
|
|
string command_line_str = stream.str();
|
|
char *command_line = new char[command_line_str.size() + 1];
|
|
memcpy(command_line, command_line_str.c_str(), command_line_str.size() + 1);
|
|
|
|
nout << "Command line: " << command_line_str << "\n";
|
|
|
|
// Something about p3dCert_wx tends to become crashy when we call it from
|
|
// CreateProcessW(). Something about the way wx parses the command-line
|
|
// parameters? Well, whatever, we don't really need the Unicode form
|
|
// anyway.
|
|
PROCESS_INFORMATION process_info;
|
|
BOOL result = CreateProcess
|
|
(_p3dcert_exe.c_str(), command_line, nullptr, nullptr, TRUE,
|
|
0, (void *)_env.c_str(),
|
|
start_dir_cstr, &startup_info, &process_info);
|
|
bool started_program = (result != 0);
|
|
|
|
delete[] command_line;
|
|
|
|
if (!started_program) {
|
|
nout << "CreateProcess failed, error: " << GetLastError() << "\n";
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
CloseHandle(process_info.hThread);
|
|
return process_info.hProcess;
|
|
}
|
|
#endif // _WIN32
|
|
|
|
|
|
#ifndef _WIN32
|
|
/**
|
|
* Creates a sub-process to run _p3dcert_exe, with the appropriate command-
|
|
* line arguments, and the environment string defined in _env.
|
|
*
|
|
* Returns the pid of the created process on success, or -1 on falure.
|
|
*/
|
|
int P3DAuthSession::
|
|
posix_create_process() {
|
|
// Fork and exec.
|
|
pid_t child = fork();
|
|
if (child < 0) {
|
|
perror("fork");
|
|
return -1;
|
|
}
|
|
|
|
if (child == 0) {
|
|
// Here we are in the child process.
|
|
|
|
if (chdir(_start_dir.c_str()) < 0) {
|
|
nout << "Could not chdir to " << _start_dir << "\n";
|
|
// This is a warning, not an error. We don't actually care that much
|
|
// about the starting directory.
|
|
}
|
|
|
|
// build up an array of char strings for the environment.
|
|
std::vector<const char *> ptrs;
|
|
size_t p = 0;
|
|
size_t zero = _env.find('\0', p);
|
|
while (zero != string::npos) {
|
|
ptrs.push_back(_env.data() + p);
|
|
p = zero + 1;
|
|
zero = _env.find('\0', p);
|
|
}
|
|
ptrs.push_back(nullptr);
|
|
|
|
execle(_p3dcert_exe.c_str(), _p3dcert_exe.c_str(),
|
|
_cert_filename->get_filename().c_str(), _cert_dir.c_str(),
|
|
(char *)0, &ptrs[0]);
|
|
nout << "Failed to exec " << _p3dcert_exe << "\n";
|
|
_exit(1);
|
|
}
|
|
|
|
return child;
|
|
}
|
|
#endif // _WIN32
|