run plugin safely under a non-ASCII username

This commit is contained in:
David Rose 2011-08-30 22:45:58 +00:00
parent 4d45de4f1b
commit c3936305e4
11 changed files with 197 additions and 181 deletions

View File

@ -246,12 +246,12 @@ find_root_dir_default() {
////////////////////////////////////////////////////////////////////
// Function: find_root_dir
// Function: find_root_dir_actual
// Description: Returns the path to the installable Panda3D directory
// on the user's machine.
////////////////////////////////////////////////////////////////////
string
find_root_dir() {
static string
find_root_dir_actual() {
string root = find_root_dir_default();
// Now look for a config.xml file in that directory, which might
@ -283,3 +283,30 @@ find_root_dir() {
// We've been redirected to another location. Respect that.
return new_root;
}
////////////////////////////////////////////////////////////////////
// Function: find_root_dir
// Description: This is the public interface to the above functions.
////////////////////////////////////////////////////////////////////
string
find_root_dir() {
string root = find_root_dir_actual();
#ifdef _WIN32
// Now map that (possibly utf-8) filename into its 8.3 equivalent,
// so we can safely pass it around to Python and other tools that
// might not understand Unicode filenames. Silly Windows, creating
// an entirely new and incompatible kind of filename.
wstring root_w;
string_to_wstring(root_w, root);
DWORD length = GetShortPathNameW(root_w.c_str(), NULL, 0);
wchar_t *short_name = new wchar_t[length];
GetShortPathNameW(root_w.c_str(), short_name, length);
wstring_to_string(root, short_name);
delete[] short_name;
#endif // _WIN32
return root;
}

View File

@ -177,21 +177,43 @@ start_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",
NULL
};
wstring env_w;
for (int ki = 0; keep[ki] != NULL; ++ki) {
wchar_t *value = _wgetenv(keep[ki]);
if (value != NULL) {
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",
#ifdef _WIN32
"SYSTEMROOT", "USERPROFILE", "COMSPEC",
#endif
#ifdef HAVE_X11
"DISPLAY", "XAUTHORITY",
#endif
NULL
};
for (int ki = 0; keep[ki] != NULL; ++ki) {
char *value = getenv(keep[ki]);
if (value != NULL) {
@ -201,6 +223,7 @@ start_p3dcert() {
_env += '\0';
}
}
#endif // _WIN32
// Define some new environment variables.
_env += "PATH=";
@ -219,6 +242,9 @@ start_p3dcert() {
_env += root_dir;
_env += '\0';
nout << "Setting environment:\n";
write_env();
nout << "Attempting to start p3dcert from " << _p3dcert_exe << "\n";
bool started_p3dcert = false;
@ -268,6 +294,27 @@ join_wait_thread() {
_started_wait_thread = false;
}
////////////////////////////////////////////////////////////////////
// Function: P3DAuthSession::write_env
// Access: Private
// Description: 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);
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DAuthSession::wt_thread_run
// Access: Private
@ -332,7 +379,7 @@ win_create_process() {
// Make sure we see an error dialog if there is a missing DLL.
SetErrorMode(0);
STARTUPINFOW startup_info;
STARTUPINFO startup_info;
ZeroMemory(&startup_info, sizeof(startup_info));
startup_info.cb = sizeof(startup_info);
@ -340,34 +387,32 @@ win_create_process() {
startup_info.wShowWindow = SW_SHOW;
startup_info.dwFlags |= STARTF_USESHOWWINDOW;
const wchar_t *start_dir_cstr;
wstring start_dir_w;
string_to_wstring(start_dir_w, _start_dir);
start_dir_cstr = start_dir_w.c_str();
const char *start_dir_cstr = _start_dir.c_str();
// Construct the command-line string, containing the quoted
// command-line arguments.
ostringstream stream;
stream << "\"" << _p3dcert_exe << "\" \""
<< _cert_filename->get_filename() << "\" \""
<< _cert_dir << "\"";
<< _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.
wstring command_line_str;
string_to_wstring(command_line_str, stream.str());
wchar_t *command_line = new wchar_t[command_line_str.size() + 1];
memcpy(command_line, command_line_str.c_str(), sizeof(wchar_t) * command_line_str.size() + 1);
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);
wstring p3dcert_exe_w;
string_to_wstring(p3dcert_exe_w, _p3dcert_exe);
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 = CreateProcessW
(p3dcert_exe_w.c_str(), command_line, NULL, NULL, TRUE, 0,
(void *)_env.c_str(), start_dir_cstr,
&startup_info, &process_info);
BOOL result = CreateProcess
(_p3dcert_exe.c_str(), command_line, NULL, NULL, TRUE,
0, (void *)_env.c_str(),
start_dir_cstr, &startup_info, &process_info);
bool started_program = (result != 0);
delete[] command_line;

View File

@ -44,6 +44,8 @@ private:
void spawn_wait_thread();
void join_wait_thread();
void write_env() const;
private:
// These methods run only within the read thread.
THREAD_CALLBACK_DECLARATION(P3DAuthSession, wt_thread_run);

View File

@ -124,8 +124,9 @@ OnInitCmdLine(wxCmdLineParser &parser) {
////////////////////////////////////////////////////////////////////
bool P3DCertApp::
OnCmdLineParsed(wxCmdLineParser &parser) {
_cert_filename = parser.GetParam(0);
_cert_dir = parser.GetParam(1);
_cert_filename = (const char *)parser.GetParam(0).mb_str();
_cert_dir = (const char *)parser.GetParam(1).mb_str();
return true;
}
@ -144,7 +145,7 @@ END_EVENT_TABLE()
// Description:
////////////////////////////////////////////////////////////////////
AuthDialog::
AuthDialog(const wxString &cert_filename, const wxString &cert_dir) :
AuthDialog(const string &cert_filename, const string &cert_dir) :
// I hate stay-on-top dialogs, but if we don't set this flag, it
// doesn't come to the foreground on OSX, and might be lost behind
// the browser window.
@ -232,8 +233,7 @@ approve_cert() {
assert(_cert != NULL);
// Make sure the directory exists.
string cert_dir_str = (const char *)_cert_dir.mb_str();
mkdir_complete(cert_dir_str, cerr);
mkdir_complete(_cert_dir, cerr);
// Look for an unused filename.
int i = 1;
@ -288,24 +288,24 @@ approve_cert() {
// passed on the command line into _cert and _stack.
////////////////////////////////////////////////////////////////////
void AuthDialog::
read_cert_file(const wxString &cert_filename) {
read_cert_file(const string &cert_filename) {
FILE *fp = NULL;
#ifdef _WIN32
wstring cert_filename_w;
if (string_to_wstring(cert_filename_w, (const char *)cert_filename.mb_str())) {
if (string_to_wstring(cert_filename_w, cert_filename)) {
fp = _wfopen(cert_filename_w.c_str(), L"r");
}
#else // _WIN32
fp = fopen(cert_filename.mb_str(), "r");
fp = fopen(cert_filename.c_str(), "r");
#endif // _WIN32
if (fp == NULL) {
cerr << "Couldn't read " << cert_filename.mb_str() << "\n";
cerr << "Couldn't read " << cert_filename << "\n";
return;
}
_cert = PEM_read_X509(fp, NULL, NULL, (void *)"");
if (_cert == NULL) {
cerr << "Could not read certificate in " << cert_filename.mb_str() << ".\n";
cerr << "Could not read certificate in " << cert_filename << ".\n";
fclose(fp);
return;
}

View File

@ -51,8 +51,8 @@ public:
virtual bool OnCmdLineParsed(wxCmdLineParser &parser);
private:
wxString _cert_filename;
wxString _cert_dir;
string _cert_filename;
string _cert_dir;
};
////////////////////////////////////////////////////////////////////
@ -67,7 +67,7 @@ private:
////////////////////////////////////////////////////////////////////
class AuthDialog : public wxDialog {
public:
AuthDialog(const wxString &cert_filename, const wxString &cert_dir);
AuthDialog(const string &cert_filename, const string &cert_dir);
virtual ~AuthDialog();
void run_clicked(wxCommandEvent &event);
@ -77,7 +77,7 @@ public:
void approve_cert();
private:
void read_cert_file(const wxString &cert_filename);
void read_cert_file(const string &cert_filename);
void get_friendly_name();
void verify_cert();
int load_certificates_from_der_ram(X509_STORE *store,
@ -93,7 +93,7 @@ private:
// any class wishing to process wxWidgets events must use this macro
DECLARE_EVENT_TABLE()
wxString _cert_dir;
string _cert_dir;
X509 *_cert;
STACK_OF(X509) *_stack;

View File

@ -839,7 +839,15 @@ find_cert(X509 *cert) {
for (si = contents.begin(); si != contents.end(); ++si) {
string filename = this_cert_dir + "/" + (*si);
X509 *x509 = NULL;
FILE *fp = fopen(filename.c_str(), "r");
FILE *fp = NULL;
#ifdef _WIN32
wstring filename_w;
if (string_to_wstring(filename_w, filename)) {
fp = _wfopen(filename_w.c_str(), L"r");
}
#else // _WIN32
fp = fopen(filename.c_str(), "r");
#endif // _WIN32
if (fp != NULL) {
x509 = PEM_read_X509(fp, NULL, NULL, (void *)"");
fclose(fp);
@ -882,7 +890,15 @@ read_certlist(P3DPackage *package) {
if (suffix == ".pem" || suffix == ".crt") {
string filename = package->get_package_dir() + "/" + basename;
X509 *x509 = NULL;
FILE *fp = fopen(filename.c_str(), "r");
FILE *fp = NULL;
#ifdef _WIN32
wstring filename_w;
if (string_to_wstring(filename_w, filename)) {
fp = _wfopen(filename_w.c_str(), L"r");
}
#else // _WIN32
fp = fopen(filename.c_str(), "r");
#endif // _WIN32
if (fp != NULL) {
x509 = PEM_read_X509(fp, NULL, NULL, (void *)"");
fclose(fp);

View File

@ -156,16 +156,16 @@ run_python() {
#ifdef _WIN32
// Of course it's already resident, so use that version.
string basename = Filename::dso_filename("libpandaexpress.so").to_os_specific();
HMODULE h = GetModuleHandle(basename.c_str());
wstring basename = Filename::dso_filename("libpandaexpress.so").to_os_specific_w();
HMODULE h = GetModuleHandleW(basename.c_str());
if (h == NULL) {
nout << "Can't find libpandaexpress in memory.\n";
} else {
static const int buffer_size = 4096;
char buffer[buffer_size];
GetModuleFileName(h, buffer, buffer_size);
libpandaexpress = Filename::from_os_specific(buffer);
wchar_t buffer[buffer_size];
GetModuleFileNameW(h, buffer, buffer_size);
libpandaexpress = Filename::from_os_specific_w(buffer);
}
#endif // _WIN32
@ -179,7 +179,7 @@ run_python() {
libpandaexpress.set_type(Filename::T_general);
#endif
}
if (!libpandaexpress.exists()) {
nout << "Can't find " << libpandaexpress << "\n";
return false;
@ -188,15 +188,22 @@ run_python() {
// We need the "imp" built-in module for that.
PyObject *imp_module = PyImport_ImportModule("imp");
if (imp_module == NULL) {
nout << "Failed to import module imp\n";
PyErr_Print();
return false;
}
// And here's where we run into a brick wall attempting to make the
// whole plugin system Unicode-safe for Windows. It turns out that
// this Python call, imp.load_dynamic(), will not accept a Unicode
// pathname. So if the DLL in question is in a location that
// contains non-ASCII characters, it can't be loaded.
string os_specific = libpandaexpress.to_os_specific();
PyObject *result = PyObject_CallMethod
(imp_module, (char *)"load_dynamic", (char *)"ss",
"libpandaexpress", os_specific.c_str());
if (result == NULL) {
nout << "Failed to import libpandaexpress as a module\n";
PyErr_Print();
return false;
}
@ -208,6 +215,7 @@ run_python() {
// available to import as well.
PyObject *vfsimporter = PyImport_ImportModule("_vfsimporter");
if (vfsimporter == NULL) {
nout << "Failed to import _vfsimporter\n";
PyErr_Print();
return false;
}
@ -216,6 +224,7 @@ run_python() {
// And now we can import the VFSImporter module that was so defined.
PyObject *vfsimporter_module = PyImport_ImportModule("VFSImporter");
if (vfsimporter_module == NULL) {
nout << "Failed to import VFSImporter\n";
PyErr_Print();
return false;
}
@ -223,6 +232,7 @@ run_python() {
// And register the VFSImporter.
result = PyObject_CallMethod(vfsimporter_module, (char *)"register", (char *)"");
if (result == NULL) {
nout << "Failed to call VFSImporter.register()\n";
PyErr_Print();
return false;
}
@ -248,6 +258,7 @@ run_python() {
// And finally, we can import the startup module.
PyObject *app_runner_module = PyImport_ImportModule("direct.p3d.AppRunner");
if (app_runner_module == NULL) {
nout << "Failed to import direct.p3d.AppRunner\n";
PyErr_Print();
return false;
}
@ -255,6 +266,7 @@ run_python() {
// Get the pointers to the objects needed within the module.
PyObject *app_runner_class = PyObject_GetAttrString(app_runner_module, "AppRunner");
if (app_runner_class == NULL) {
nout << "Failed to get AppRunner class\n";
PyErr_Print();
return false;
}
@ -262,6 +274,7 @@ run_python() {
// Construct an instance of AppRunner.
_runner = PyObject_CallFunction(app_runner_class, (char *)"");
if (_runner == NULL) {
nout << "Failed to construct AppRunner instance\n";
PyErr_Print();
return false;
}

View File

@ -795,21 +795,14 @@ start_p3dpython(P3DInstance *inst) {
// This allows the caller's on-disk Python files to shadow the
// similar-named files in the p3d file, allowing easy iteration on
// the code in the p3d file.
const char *pypath = getenv("PYTHONPATH");
if (pypath != (char *)NULL) {
python_path = pypath;
if (get_env(python_path, "PYTHONPATH")) {
replace_slashes(python_path);
python_path += sep;
python_path += search_path;
}
// We also preserve PRC_PATH.
const char *prcpath = getenv("PRC_PATH");
if (prcpath == NULL) {
prcpath = getenv("PANDA_PRC_PATH");
}
if (prcpath != (char *)NULL) {
prc_path = prcpath;
if (get_env(prc_path, "PRC_PATH") || get_env(prc_path, "PANDA_PRC_PATH")) {
replace_slashes(prc_path);
prc_path += sep;
prc_path += search_path;
@ -891,8 +884,8 @@ start_p3dpython(P3DInstance *inst) {
NULL
};
for (int ki = 0; keep[ki] != NULL; ++ki) {
char *value = getenv(keep[ki]);
if (value != NULL) {
string value;
if (get_env(value, keep[ki])) {
_env += keep[ki];
_env += "=";
_env += value;
@ -950,18 +943,18 @@ start_p3dpython(P3DInstance *inst) {
// definitions, even if keep_user_env is not set. This is necessary
// for os.system() and such to work as expected within the embedded
// app. It's also necessary for webbrowser on Linux.
char *orig_path = getenv("PATH");
if (orig_path != NULL) {
string orig_path;
if (get_env(orig_path, "PATH")) {
sys_path += sep;
sys_path += orig_path;
}
char *orig_ld_path = getenv("LD_LIBRARY_PATH");
if (orig_ld_path != NULL) {
string orig_ld_path;
if (get_env(orig_ld_path, "LD_LIBRARY_PATH")) {
ld_path += sep;
ld_path += orig_ld_path;
}
char *orig_dyld_path = getenv("DYLD_LIBRARY_PATH");
if (orig_dyld_path != NULL) {
string orig_dyld_path;
if (get_env(orig_dyld_path, "DYLD_LIBRARY_PATH")) {
dyld_path += sep;
dyld_path += orig_dyld_path;
}
@ -1541,14 +1534,18 @@ win_create_process() {
wchar_t *command_line = new wchar_t[command_line_str.size() + 1];
memcpy(command_line, command_line_str.c_str(), sizeof(wchar_t) * command_line_str.size() + 1);
nout << "Command line: " << command_line_str << "\n";
wstring p3dpython_exe_w;
string_to_wstring(p3dpython_exe_w, _p3dpython_exe);
wstring env_w;
string_to_wstring(env_w, _env);
PROCESS_INFORMATION process_info;
BOOL result = CreateProcessW
(p3dpython_exe_w.c_str(), command_line, NULL, NULL, TRUE, 0,
(void *)_env.c_str(), start_dir_cstr,
&startup_info, &process_info);
(p3dpython_exe_w.c_str(), command_line, NULL, NULL, TRUE,
CREATE_UNICODE_ENVIRONMENT, (void *)env_w.c_str(),
start_dir_cstr, &startup_info, &process_info);
bool started_program = (result != 0);
if (!started_program) {
@ -1810,6 +1807,35 @@ p3dpython_thread_run() {
}
}
////////////////////////////////////////////////////////////////////
// Function: P3DSession::get_env
// Access: Private, Static
// Description: Implements getenv(), respecting Windows' Unicode
// environment. Returns true if the variable is
// defined, false if it is not. If it is defined, fills
// value with its definition.
////////////////////////////////////////////////////////////////////
bool P3DSession::
get_env(string &value, const string &varname) {
#ifdef _WIN32
wstring varname_w;
string_to_wstring(varname_w, varname);
const wchar_t *vc = _wgetenv(varname_w.c_str());
if (vc == NULL) {
return false;
}
wstring_to_string(value, vc);
return true;
#else // _WIN32
const char *vc = getenv(varname.c_str());
if (vc == NULL) {
return false;
}
value = vc;
return true;
#endif
}
////////////////////////////////////////////////////////////////////
// Function: P3DSession::write_env
// Access: Private

View File

@ -90,6 +90,7 @@ private:
THREAD_CALLBACK_DECLARATION(P3DSession, p3dpython_thread_run);
void p3dpython_thread_run();
static bool get_env(string &value, const string &varname);
void write_env() const;
private:

View File

@ -72,111 +72,3 @@ string_to_wstring(wstring &result, const string &source) {
return success;
}
#endif // _WIN32
////////////////////////////////////////////////////////////////////
// Function: parse_hexdigit
// Description: Parses a single hex digit. Returns true on success,
// false on failure. On success, fills result with the
// parsed value, an integer in the range 0..15.
////////////////////////////////////////////////////////////////////
bool
parse_hexdigit(int &result, char digit) {
if (isdigit(digit)) {
result = digit - '0';
return true;
} else if (isxdigit(digit)) {
result = tolower(digit) - 'a' + 10;
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: url_quote
// Description: Applies URL quoting to source and stores the result
// in result.
////////////////////////////////////////////////////////////////////
void
url_quote(string &result, const string &source) {
ostringstream strm;
strm << hex << setfill('0');
for (size_t p = 0; p < source.length(); ++p) {
if (source[p] < 0x20 || source[p] >= 0x7f) {
strm << "%" << setw(2) << (unsigned int)(unsigned char)source[p];
} else {
switch (source[p]) {
// We could quote all of these punctuation marks too, the same
// way actual URL quoting does. Maybe we will one day in the
// future, though I don't think it matters much; mainly we're
// relying on quoting to protect the high-bit characters. For
// now, then, we leave these unquoted, for compatibility with
// the p3dpython from Panda3D 1.7, which didn't expect any
// quoting at all.
/*
case ' ':
case '<':
case '>':
case '#':
case '%':
case '{':
case '}':
case '|':
case '\\':
case '^':
case '~':
case '[':
case ']':
case '`':
case ';':
case '?':
case ':':
case '@':
case '=':
case '&':
case '$':
strm << "%" << setw(2) << (unsigned int)(unsigned char)source[p];
break;
*/
default:
strm << (char)source[p];
}
}
}
result = strm.str();
}
////////////////////////////////////////////////////////////////////
// Function: url_unquote
// Description: Removes URL quoting from source and stores the result
// in result.
////////////////////////////////////////////////////////////////////
void
url_unquote(string &result, const string &source) {
result = string();
size_t p = 0;
while (p < source.length()) {
if (source[p] == '%') {
++p;
int ch = 0;
if (p < source.length()) {
int digit;
if (parse_hexdigit(digit, source[p])) {
ch += (digit << 4);
}
++p;
}
if (p < source.length()) {
int digit;
if (parse_hexdigit(digit, source[p])) {
ch += digit;
}
++p;
}
result.push_back((char)ch);
} else {
result.push_back(source[p]);
++p;
}
}
}

View File

@ -37,12 +37,6 @@ inline ostream &operator << (ostream &out, const wstring &str) {
#endif // _WIN32
// Some handy functions for applying and removing URL escape codes,
// which are used to pass parameters safely to p3dCert and p3dpython
// on the command line.
void url_quote(string &result, const string &source);
void url_unquote(string &result, const string &source);
#endif