// Filename: p3dInstanceManager.cxx // Created by: drose (29May09) // //////////////////////////////////////////////////////////////////// // // 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." // //////////////////////////////////////////////////////////////////// #include "p3dInstanceManager.h" #include "p3dInstance.h" #include "p3dSession.h" #include "p3dAuthSession.h" #include "p3dHost.h" #include "p3d_plugin_config.h" #include "p3dWinSplashWindow.h" #include "p3dUndefinedObject.h" #include "p3dNoneObject.h" #include "p3dBoolObject.h" #include "p3dPackage.h" #include "find_root_dir.h" #include "fileSpec.h" #include "get_tinyxml.h" #include "binaryXml.h" #include "mkdir_complete.h" #include "wstring_encode.h" // We can include this header file to get the DTOOL_PLATFORM // definition, even though we don't link with dtool. #include "dtool_platform.h" #ifdef _WIN32 #include #include // chmod() #include // rmdir() #else #include #include #include #endif #include static ofstream logfile; ostream *nout_stream = &logfile; P3DInstanceManager *P3DInstanceManager::_global_ptr; //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::Constructor // Access: Private // Description: //////////////////////////////////////////////////////////////////// P3DInstanceManager:: P3DInstanceManager() { init_xml(); _is_initialized = false; _created_runtime_environment = false; _api_version = 0; _next_temp_filename_counter = 1; _unique_id = 0; _trusted_environment = false; _console_environment = false; _plugin_major_version = 0; _plugin_minor_version = 0; _plugin_sequence_version = 0; _plugin_official_version = false; _coreapi_timestamp = 0; _notify_thread_continue = false; _started_notify_thread = false; INIT_THREAD(_notify_thread); // Initialize the singleton objects. _undefined_object = new P3DUndefinedObject(); _none_object = new P3DNoneObject(); _true_object = new P3DBoolObject(true); _false_object = new P3DBoolObject(false); _auth_session = NULL; // Seed the lame random number generator in rand(); we use it to // select a mirror for downloading. srand((unsigned int)time(NULL)); #ifdef _WIN32 // Ensure the appropriate Windows common controls are available to // this application. INITCOMMONCONTROLSEX icc; icc.dwSize = sizeof(icc); icc.dwICC = ICC_PROGRESS_CLASS; InitCommonControlsEx(&icc); #endif #ifndef _WIN32 // On Mac or Linux, we'd better ignore SIGPIPE, or this signal will // shut down the browser if the plugin exits unexpectedly. struct sigaction ignore; memset(&ignore, 0, sizeof(ignore)); ignore.sa_handler = SIG_IGN; sigaction(SIGPIPE, &ignore, &_old_sigpipe); #endif // _WIN32 } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::Destructor // Access: Private // Description: //////////////////////////////////////////////////////////////////// P3DInstanceManager:: ~P3DInstanceManager() { if (_started_notify_thread) { _notify_ready.acquire(); _notify_thread_continue = false; _notify_ready.notify(); _notify_ready.release(); JOIN_THREAD(_notify_thread); _started_notify_thread = false; } #ifndef _WIN32 // Restore the original SIGPIPE handler. sigaction(SIGPIPE, &_old_sigpipe, NULL); #endif // _WIN32 // force-finish any remaining instances. while (!_instances.empty()) { P3DInstance *inst = *(_instances.begin()); finish_instance(inst); } assert(_sessions.empty()); assert(_instances.empty()); if (_auth_session != NULL) { p3d_unref_delete(_auth_session); _auth_session = NULL; } Hosts::iterator hi; for (hi = _hosts.begin(); hi != _hosts.end(); ++hi) { delete (*hi).second; } _hosts.clear(); // Delete any remaining temporary files. TempFilenames::iterator ti; for (ti = _temp_filenames.begin(); ti != _temp_filenames.end(); ++ti) { const string &filename = (*ti); nout << "Removing delinquent temp file " << filename << "\n"; unlink(filename.c_str()); } _temp_filenames.clear(); nout << "counts: " << _undefined_object->_ref_count << " " << _none_object->_ref_count << " " << _true_object->_ref_count << " " << _false_object->_ref_count << "\n"; /* assert(_undefined_object->_ref_count == 1); assert(_none_object->_ref_count == 1); assert(_true_object->_ref_count == 1); assert(_false_object->_ref_count == 1); */ P3D_OBJECT_DECREF(_undefined_object); P3D_OBJECT_DECREF(_none_object); P3D_OBJECT_DECREF(_true_object); P3D_OBJECT_DECREF(_false_object); #ifdef _WIN32 P3DWinSplashWindow::unregister_window_class(); #endif } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::initialize // Access: Public // Description: Called by the plugin host at application startup. It // returns true if the DLL is successfully initialized, // false if it should be immediately shut down and // redownloaded. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: initialize(int api_version, const string &contents_filename, const string &host_url, P3D_verify_contents verify_contents, const string &platform, const string &log_directory, const string &log_basename, bool trusted_environment, bool console_environment, const string &root_dir, const string &host_dir) { _api_version = api_version; _host_url = host_url; _verify_contents = verify_contents; _platform = platform; _log_directory = log_directory; _log_basename = log_basename; _trusted_environment = trusted_environment; _console_environment = console_environment; if (_host_url.empty()) { _host_url = PANDA_PACKAGE_HOST_URL; } if (_platform.empty()) { _platform = DTOOL_PLATFORM; } #ifdef P3D_PLUGIN_LOG_DIRECTORY if (_log_directory.empty()) { _log_directory = P3D_PLUGIN_LOG_DIRECTORY; } #endif #ifdef P3D_PLUGIN_LOG_BASENAME2 if (_log_basename.empty()) { _log_basename = P3D_PLUGIN_LOG_BASENAME2; } #endif if (_log_basename.empty()) { _log_basename = "p3dcore"; } if (root_dir.empty()) { _root_dir = find_root_dir(); if (_root_dir.empty()) { cerr << "Could not find root directory.\n"; return false; } } else { _root_dir = root_dir; } _host_dir = host_dir; // Allow the caller (e.g. panda3d.exe) to specify a log directory. // Or, allow the developer to compile one in. // Failing that, we write logfiles to Panda3D/log. if (_log_directory.empty()) { _log_directory = _root_dir + "/log"; } // Ensure that the log directory ends with a slash. if (!_log_directory.empty() && _log_directory[_log_directory.size() - 1] != '/') { #ifdef _WIN32 if (_log_directory[_log_directory.size() - 1] != '\\') #endif _log_directory += "/"; } // Construct the logfile pathname. _log_pathname = _log_directory; _log_pathname += _log_basename; _log_pathname += ".log"; create_runtime_environment(); _is_initialized = true; if (!host_url.empty() && !contents_filename.empty()) { // Attempt to pre-read the supplied contents.xml file, to avoid an // unnecessary download later. P3DHost *host = get_host(host_url); if (!host->read_contents_file(contents_filename, false)) { nout << "Couldn't read " << contents_filename << "\n"; } } return true; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::set_plugin_version // Access: Public // Description: Specifies the version of the calling plugin, for // reporting to JavaScript and the like. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: set_plugin_version(int major, int minor, int sequence, bool official, const string &distributor, const string &coreapi_host_url, time_t coreapi_timestamp, const string &coreapi_set_ver) { reconsider_runtime_environment(); _plugin_major_version = major; _plugin_minor_version = minor; _plugin_sequence_version = sequence; _plugin_official_version = official; _plugin_distributor = distributor; // The Core API "host URL" is both compiled in, and comes in // externally; we trust the external source in the case of a // conflict. string internal_host_url = PANDA_PACKAGE_HOST_URL; if (coreapi_host_url != internal_host_url) { nout << "Warning! Downloaded Core API from " << coreapi_host_url << ", but its internal URL was " << internal_host_url << "\n"; } _coreapi_host_url = coreapi_host_url; if (_coreapi_host_url.empty()) { _coreapi_host_url = internal_host_url; } // The Core API timestamp is only available externally. _coreapi_timestamp = coreapi_timestamp; // The Core API "set ver", or version, is both compiled in and comes // in externally; for this one we trust the internal version in the // case of a conflict. string internal_set_ver = P3D_COREAPI_VERSION_STR; if (coreapi_set_ver != internal_set_ver && !coreapi_set_ver.empty() && !internal_set_ver.empty()) { nout << "Warning! contents.xml reports Core API version number " << coreapi_set_ver << ", but its actual version number is " << internal_set_ver << "\n"; } _coreapi_set_ver = internal_set_ver; if (_coreapi_set_ver.empty()) { _coreapi_set_ver = coreapi_set_ver; } nout << "Plugin version: " << _plugin_major_version << "." << _plugin_minor_version << "." << _plugin_sequence_version; if (!_plugin_official_version) { nout << "c"; } nout << "\n"; nout << "Plugin distributor: " << _plugin_distributor << "\n"; nout << "Core API host URL: " << _coreapi_host_url << "\n"; nout << "Core API version: " << _coreapi_set_ver << "\n"; const char *timestamp_string = ctime(&_coreapi_timestamp); if (timestamp_string == NULL) { timestamp_string = ""; } nout << "Core API date: " << timestamp_string << "\n"; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::set_super_mirror // Access: Public // Description: Specifies the "super mirror" URL. See p3d_plugin.h. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: set_super_mirror(const string &super_mirror_url) { reconsider_runtime_environment(); if (!super_mirror_url.empty()) { nout << "super_mirror = " << super_mirror_url << "\n"; } _super_mirror_url = super_mirror_url; // Make sure it ends with a slash. if (!_super_mirror_url.empty() && _super_mirror_url[_super_mirror_url.size() - 1] != '/') { _super_mirror_url += '/'; } } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::create_instance // Access: Public // Description: Returns a newly-allocated P3DInstance with the // indicated startup information. //////////////////////////////////////////////////////////////////// P3DInstance *P3DInstanceManager:: create_instance(P3D_request_ready_func *func, const P3D_token tokens[], size_t num_tokens, int argc, const char *argv[], void *user_data) { reconsider_runtime_environment(); P3DInstance *inst = new P3DInstance(func, tokens, num_tokens, argc, argv, user_data); inst->ref(); _instances.insert(inst); return inst; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::set_p3d_filename // Access: Public // Description: Sets the p3d_filename (or p3d_url) on a particular // instance. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: set_p3d_filename(P3DInstance *inst, bool is_local, const string &p3d_filename, const int &p3d_offset) { if (inst->is_started()) { nout << "Instance started twice: " << inst << "\n"; return false; } if (is_local) { inst->set_p3d_filename(p3d_filename, p3d_offset); } else { inst->set_p3d_url(p3d_filename); } return true; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::make_p3d_stream // Access: Public // Description: Indicates an intention to transmit the p3d data as a // stream. Should return a new unique stream ID to // receive it. //////////////////////////////////////////////////////////////////// int P3DInstanceManager:: make_p3d_stream(P3DInstance *inst, const string &p3d_url) { if (inst->is_started()) { nout << "Instance started twice: " << inst << "\n"; return -1; } return inst->make_p3d_stream(p3d_url); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::start_instance // Access: Public // Description: Actually starts the instance running on a particular // session. This is called by the P3DInstance when it // successfully loads its instance file. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: start_instance(P3DInstance *inst) { if (inst->is_failed()) { // Don't bother trying again. return false; } if (inst->is_started()) { // Already started. return true; } P3DSession *session; Sessions::iterator si = _sessions.find(inst->get_session_key()); if (si == _sessions.end()) { session = new P3DSession(inst); session->ref(); bool inserted = _sessions.insert(Sessions::value_type(session->get_session_key(), session)).second; assert(inserted); } else { session = (*si).second; } session->start_instance(inst); return inst->is_started(); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::finish_instance // Access: Public // Description: Terminates and removes a previously-returned // instance. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: finish_instance(P3DInstance *inst) { nout << "finish_instance: " << inst << "\n"; Instances::iterator ii; ii = _instances.find(inst); if (ii != _instances.end()) { _instances.erase(ii); } Sessions::iterator si = _sessions.find(inst->get_session_key()); if (si != _sessions.end()) { P3DSession *session = (*si).second; session->terminate_instance(inst); // If that was the last instance in this session, terminate the // session. if (session->get_num_instances() == 0) { _sessions.erase(session->get_session_key()); session->shutdown(); p3d_unref_delete(session); } } inst->cleanup(); p3d_unref_delete(inst); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::authorize_instance // Access: Public // Description: Creates a new P3DAuthSession object, to pop up a // window for the user to authorize the certificate on // this instance. Automatically terminates any // previously-created P3DAuthSession. //////////////////////////////////////////////////////////////////// P3DAuthSession *P3DInstanceManager:: authorize_instance(P3DInstance *inst) { if (_auth_session != NULL) { // We only want one auth_session window open at a time, to // minimize user confusion, so close any previous window. _auth_session->shutdown(true); p3d_unref_delete(_auth_session); _auth_session = NULL; } _auth_session = new P3DAuthSession(inst); _auth_session->ref(); return _auth_session; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::validate_instance // Access: Public // Description: Returns the P3DInstance pointer corresponding to the // indicated P3D_instance if it is valid, or NULL if it // is not. //////////////////////////////////////////////////////////////////// P3DInstance *P3DInstanceManager:: validate_instance(P3D_instance *instance) { Instances::iterator ii; ii = _instances.find((P3DInstance *)instance); if (ii != _instances.end()) { return (*ii); } return NULL; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::check_request // Access: Public // Description: If a request is currently pending on any instance, // returns its pointer. Otherwise, returns NULL. //////////////////////////////////////////////////////////////////// P3DInstance *P3DInstanceManager:: check_request() { Instances::iterator ii; for (ii = _instances.begin(); ii != _instances.end(); ++ii) { P3DInstance *inst = (*ii); if (inst->has_request()) { return inst; } } return NULL; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::wait_request // Access: Public // Description: Does not return until a request is pending on some // instance, or until no instances remain, or until the // indicated time in seconds has elapsed. Use // check_request to retrieve the pending request. Due // to the possibility of race conditions, it is possible // for this function to return when there is in fact no // request pending (another thread may have extracted // the request first). //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: wait_request(double timeout) { #ifdef _WIN32 int stop_tick = int(GetTickCount() + timeout * 1000.0); #else struct timeval stop_time; gettimeofday(&stop_time, NULL); int seconds = (int)floor(timeout); stop_time.tv_sec += seconds; stop_time.tv_usec += (int)((timeout - seconds) * 1000.0); if (stop_time.tv_usec > 1000) { stop_time.tv_usec -= 1000; ++stop_time.tv_sec; } #endif _request_ready.acquire(); if (check_request() != (P3DInstance *)NULL) { _request_ready.release(); return; } if (_instances.empty()) { _request_ready.release(); return; } // No pending requests; go to sleep. _request_ready.wait(timeout); while (true) { #ifdef _WIN32 int remaining_ticks = stop_tick - GetTickCount(); if (remaining_ticks <= 0) { break; } timeout = remaining_ticks * 0.001; #else struct timeval now; gettimeofday(&now, NULL); struct timeval remaining; remaining.tv_sec = stop_time.tv_sec - now.tv_sec; remaining.tv_usec = stop_time.tv_usec - now.tv_usec; if (remaining.tv_usec < 0) { remaining.tv_usec += 1000; --remaining.tv_sec; } if (remaining.tv_sec < 0) { break; } timeout = remaining.tv_sec + remaining.tv_usec * 0.001; #endif if (check_request() != (P3DInstance *)NULL) { _request_ready.release(); return; } if (_instances.empty()) { _request_ready.release(); return; } // No pending requests; go to sleep. _request_ready.wait(timeout); } _request_ready.release(); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_host // Access: Public // Description: Returns a (possibly shared) pointer to the indicated // download host. //////////////////////////////////////////////////////////////////// P3DHost *P3DInstanceManager:: get_host(const string &host_url) { Hosts::iterator pi = _hosts.find(host_url); if (pi != _hosts.end()) { return (*pi).second; } P3DHost *host = new P3DHost(host_url, _host_dir); bool inserted = _hosts.insert(Hosts::value_type(host_url, host)).second; assert(inserted); return host; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::forget_host // Access: Public // Description: Removes the indicated host from the cache. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: forget_host(P3DHost *host) { const string &host_url = host->get_host_url(); nout << "Forgetting host " << host_url << "\n"; // Hmm, this is a memory leak. But we allow it to remain, since // it's an unusual circumstance (uninstalling), and it's safer to // leak than to risk a floating pointer. _hosts.erase(host_url); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_unique_id // Access: Public // Description: Returns a number used to uniquify different // instances. This number is guaranteed to be different // at each call, at least until the int space rolls // over. //////////////////////////////////////////////////////////////////// int P3DInstanceManager:: get_unique_id() { ++_unique_id; return _unique_id; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::signal_request_ready // Access: Public // Description: May be called in any thread to indicate that a new // P3D_request is available in the indicated instance. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: signal_request_ready(P3DInstance *inst) { if (inst->get_request_ready_func() != NULL) { // This instance requires asynchronous notifications of requests. // Thus, we should tell the notify thread to wake up and make the // callback. _notify_ready.acquire(); _notify_instances.push_back(inst); _notify_ready.notify(); _notify_ready.release(); // Oh, and we should spawn the thread if we haven't already. if (!_started_notify_thread) { _notify_thread_continue = true; SPAWN_THREAD(_notify_thread, nt_thread_run, this); _started_notify_thread = true; } } // Then, wake up the main thread, in case it's sleeping on // wait_request(). _request_ready.acquire(); _request_ready.notify(); _request_ready.release(); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::make_class_definition // Access: Public // Description: //////////////////////////////////////////////////////////////////// P3D_class_definition *P3DInstanceManager:: make_class_definition() const { P3D_class_definition *new_class = new P3D_class_definition(P3DObject::_generic_class); // TODO: save this pointer so we can delete it on destruction. return new_class; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::make_temp_filename // Access: Public // Description: Constructs a new, unique temporary filename with the // indicated extension. You should use the // P3DTemporaryFilename interface instead of calling // this method directly. //////////////////////////////////////////////////////////////////// string P3DInstanceManager:: make_temp_filename(const string &extension) { string result; bool exists; do { int tid; #ifdef _WIN32 tid = GetCurrentProcessId(); #else tid = getpid(); #endif if (tid == 0) { tid = 1; } int hash = ((clock() + _next_temp_filename_counter) * ((time(NULL) * tid) >> 8)) & 0xffffff; ++_next_temp_filename_counter; char hex_code[10]; sprintf(hex_code, "%06x", hash); result = _temp_directory; result += "p3d_"; result += hex_code; result += extension; exists = false; if (_temp_filenames.find(result) != _temp_filenames.end()) { // We've previously allocated this file. exists = true; } else { // Check if the file exists on disk. #ifdef _WIN32 DWORD results = GetFileAttributes(result.c_str()); if (results != -1) { exists = true; } #else // _WIN32 struct stat this_buf; if (stat(result.c_str(), &this_buf) == 0) { exists = true; } #endif } } while (exists); nout << "make_temp_filename: " << result << "\n"; return result; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::release_temp_filename // Access: Public // Description: Releases a temporary filename assigned earlier via // make_temp_filename(). If the file exists, it will be // removed. You should use the P3DTemporaryFilename // interface instead of calling this method directly. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: release_temp_filename(const string &filename) { nout << "release_temp_filename: " << filename << "\n"; _temp_filenames.erase(filename); unlink(filename.c_str()); } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::find_cert // Access: Public // Description: Looks for the particular certificate in the cache of // recognized certificates. Returns true if it is // found, false if not. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: find_cert(X509 *cert) { // First, we need the DER representation. string der = cert_to_der(cert); // If we've previously found this certificate, we don't have to hit // disk again. ApprovedCerts::iterator ci = _approved_certs.find(der); if (ci != _approved_certs.end()) { return true; } // Well, we haven't found it already. Look for it on disk. For // this, we hash the cert into a hex string. This is similar to // OpenSSL's get_by_subject() approach, except we hash the whole // cert, not just the subject. (Since we also store self-signed // certs in this list, we can't trust the subject name alone.) string this_cert_dir = get_cert_dir(cert); nout << "looking in " << this_cert_dir << "\n"; vector contents; scan_directory(this_cert_dir, contents); // Now look at each of the files in this directory and see if any of // them matches the certificate. vector::iterator si; for (si = contents.begin(); si != contents.end(); ++si) { string filename = this_cert_dir + "/" + (*si); X509 *x509 = NULL; 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); } if (x509 != NULL) { string der2 = cert_to_der(x509); // We might as well save this cert in the table for next time, // even if it's not the one we're looking for right now. _approved_certs.insert(der2); if (der == der2) { return true; } } } // Nothing matched. return false; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::read_certlist // Access: Public // Description: Reads the pre-approved certificates in the certlist // package and adds them to the in-memory cache. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: read_certlist(P3DPackage *package) { nout << "reading certlist in " << package->get_package_dir() << "\n"; vector contents; scan_directory(package->get_package_dir(), contents); vector::iterator si; for (si = contents.begin(); si != contents.end(); ++si) { const string &basename = (*si); if (basename.length() > 4) { string suffix = basename.substr(basename.length() - 4); if (suffix == ".pem" || suffix == ".crt") { string filename = package->get_package_dir() + "/" + basename; X509 *x509 = NULL; 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); } if (x509 != NULL) { string der2 = cert_to_der(x509); _approved_certs.insert(der2); } } } } } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_cert_dir // Access: Public // Description: Returns the directory searched for this particular // certificate. //////////////////////////////////////////////////////////////////// string P3DInstanceManager:: get_cert_dir(X509 *cert) { string der = cert_to_der(cert); static const size_t hash_size = 16; unsigned char md[hash_size]; MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, der.data(), der.size()); MD5_Final(md, &ctx); string basename; static const size_t keep_hash = 6; for (size_t i = 0; i < keep_hash; ++i) { int high = (md[i] >> 4) & 0xf; int low = md[i] & 0xf; basename += P3DInstanceManager::encode_hexdigit(high); basename += P3DInstanceManager::encode_hexdigit(low); } return _certs_dir + "/" + basename; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::cert_to_der // Access: Public, Static // Description: Converts the indicated certificate to its binary DER // representation. //////////////////////////////////////////////////////////////////// string P3DInstanceManager:: cert_to_der(X509 *cert) { int buffer_size = i2d_X509(cert, NULL); unsigned char *buffer = new unsigned char[buffer_size]; unsigned char *p = buffer; i2d_X509(cert, &p); string result((char *)buffer, buffer_size); delete[] buffer; return result; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::uninstall_all // Access: Public // Description: Stops all active instances and removes *all* // downloaded files from all hosts, and empties the // current user's Panda3D directory as much as possible. // // This cannot remove the coreapi dll or directory on // Windows. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: uninstall_all() { Instances::iterator ii; for (ii = _instances.begin(); ii != _instances.end(); ++ii) { P3DInstance *inst = (*ii); inst->uninstall_host(); } Hosts::iterator hi; for (hi = _hosts.begin(); hi != _hosts.end(); ++hi) { P3DHost *host = (*hi).second; host->uninstall(); } // Close the logfile so we can remove that too. logfile.close(); if (!_root_dir.empty()) { // This won't be able to delete the coreapi directory on Windows, // because we're running that DLL right now. But it will delete // everything else. delete_directory_recursively(_root_dir); } _created_runtime_environment = false; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::get_global_ptr // Access: Public, Static // Description: //////////////////////////////////////////////////////////////////// P3DInstanceManager *P3DInstanceManager:: get_global_ptr() { if (_global_ptr == NULL) { _global_ptr = new P3DInstanceManager; } return _global_ptr; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::delete_global_ptr // Access: Public, Static // Description: This is called only at plugin shutdown time; it // deletes the global instance manager pointer and // clears it to NULL. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: delete_global_ptr() { if (_global_ptr != NULL) { delete _global_ptr; _global_ptr = NULL; } } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::scan_directory // Access: Public, Static // Description: Attempts to open the named filename as if it were a // directory and looks for the non-hidden files within // the directory. Fills the given vector up with the // sorted list of filenames that are local to this // directory. // // It is the user's responsibility to ensure that the // contents vector is empty before making this call; // otherwise, the new files will be appended to it. // // Returns true on success, false if the directory could // not be read for some reason. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: scan_directory(const string &dirname, vector &contents) { #ifdef _WIN32 // Use Windows' FindFirstFile() / FindNextFile() to walk through the // list of files in a directory. size_t orig_size = contents.size(); string match = dirname + "\\*.*"; WIN32_FIND_DATAW find_data; wstring match_w; string_to_wstring(match_w, match); HANDLE handle = FindFirstFileW(match_w.c_str(), &find_data); if (handle == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_NO_MORE_FILES) { // No matching files is not an error. return true; } return false; } do { string filename; wstring_to_string(filename, find_data.cFileName); if (filename != "." && filename != "..") { contents.push_back(filename); } } while (FindNextFileW(handle, &find_data)); bool scan_ok = (GetLastError() == ERROR_NO_MORE_FILES); FindClose(handle); sort(contents.begin() + orig_size, contents.end()); return scan_ok; #else // _WIN32 // Use Posix's opendir() / readdir() to walk through the list of // files in a directory. size_t orig_size = contents.size(); DIR *root = opendir(dirname.c_str()); if (root == (DIR *)NULL) { return false; } struct dirent *d; d = readdir(root); while (d != (struct dirent *)NULL) { if (d->d_name[0] != '.') { contents.push_back(d->d_name); } d = readdir(root); } closedir(root); sort(contents.begin() + orig_size, contents.end()); return true; #endif // _WIN32 } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::scan_directory_recursively // Access: Public, Static // Description: Fills up filename_contents with the list of all // files (but not directories), and dirname_contents // with the list of all directories, rooted at the // indicated dirname and below. The filenames generated // are relative to the root of the dirname, with slashes // (not backslashes) as the directory separator // character. // // Returns true on success, false if the original // dirname wasn't a directory or something like that. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: scan_directory_recursively(const string &dirname, vector &filename_contents, vector &dirname_contents, const string &prefix) { vector dir_contents; if (!scan_directory(dirname, dir_contents)) { // Apparently dirname wasn't a directory. return false; } // Walk through the contents of dirname. vector::const_iterator si; for (si = dir_contents.begin(); si != dir_contents.end(); ++si) { // Here's a particular file within dirname. Is it another // directory, or is it a regular file? string pathname = dirname + "/" + (*si); string rel_filename = prefix + (*si); if (scan_directory_recursively(pathname, filename_contents, dirname_contents, rel_filename + "/")) { // It's a directory, and it's just added its results to the // contents. dirname_contents.push_back(rel_filename); } else { // It's not a directory, so assume it's an ordinary file, and // add it to the contents. filename_contents.push_back(rel_filename); } } return true; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::delete_directory_recursively // Access: Public, Static // Description: Deletes all of the files and directories in the named // directory and below, like rm -rf. Use with extreme // caution. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: delete_directory_recursively(const string &root_dir) { vector contents, dirname_contents; if (!scan_directory_recursively(root_dir, contents, dirname_contents)) { // Maybe it's just a single file, not a directory. Delete it. #ifdef _WIN32 wstring root_dir_w; string_to_wstring(root_dir_w, root_dir); // Windows can't delete a file if it's read-only. _wchmod(root_dir_w.c_str(), 0644); int result = _wunlink(root_dir_w.c_str()); #else // _WIN32 int result = unlink(root_dir.c_str()); #endif // _WIN32 if (result == 0) { nout << "Deleted " << root_dir << "\n"; } else { #ifdef _WIN32 result = _waccess(root_dir_w.c_str(), 0); #else // _WIN32 result = access(root_dir.c_str(), 0); #endif // _WIN32 if (result == 0) { nout << "Could not delete " << root_dir << "\n"; } } return; } vector::iterator ci; for (ci = contents.begin(); ci != contents.end(); ++ci) { string filename = (*ci); string pathname = root_dir + "/" + filename; #ifdef _WIN32 wstring pathname_w; string_to_wstring(pathname_w, pathname); // Windows can't delete a file if it's read-only. _wchmod(pathname_w.c_str(), 0644); int result = _wunlink(pathname_w.c_str()); #else // _WIN32 int result = unlink(pathname.c_str()); #endif // _WIN32 if (result == 0) { nout << " Deleted " << filename << "\n"; } else { nout << " Could not delete " << filename << "\n"; } } // Now delete all of the directories too. They're already in // reverse order, so we remove deeper directories first. for (ci = dirname_contents.begin(); ci != dirname_contents.end(); ++ci) { string filename = (*ci); string pathname = root_dir + "/" + filename; #ifdef _WIN32 wstring pathname_w; string_to_wstring(pathname_w, pathname); _wchmod(pathname_w.c_str(), 0755); int result = _wrmdir(pathname_w.c_str()); #else // _WIN32 int result = rmdir(pathname.c_str()); #endif // _WIN32 if (result == 0) { nout << " Removed directory " << filename << "\n"; } else { nout << " Could not remove directory " << filename << "\n"; } } // Finally, delete the root directory itself. string pathname = root_dir; #ifdef _WIN32 wstring pathname_w; string_to_wstring(pathname_w, pathname); _wchmod(pathname_w.c_str(), 0755); int result = _wrmdir(pathname_w.c_str()); #else // _WIN32 int result = rmdir(pathname.c_str()); #endif // _WIN32 if (result == 0) { nout << "Removed directory " << root_dir << "\n"; } else { #ifdef _WIN32 result = _waccess(pathname_w.c_str(), 0); #else // _WIN32 result = access(pathname.c_str(), 0); #endif // _WIN32 if (result == 0) { nout << "Could not remove directory " << root_dir << "\n"; } } } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::remove_file_from_list // Access: Public, Static // Description: Removes the first instance of the indicated file // from the given list. Returns true if removed, false // if it was not found. // // On Windows, the directory separator characters are // changed from backslash to forward slash before // searching in the list; so it is assumed that the list // contains filenames with a forward slash used as a // separator. //////////////////////////////////////////////////////////////////// bool P3DInstanceManager:: remove_file_from_list(vector &contents, const string &filename) { #ifdef _WIN32 // Convert backslashes to slashes. string clean_filename; for (string::const_iterator pi = filename.begin(); pi != filename.end(); ++pi) { if ((*pi) == '\\') { clean_filename += '/'; } else { clean_filename += (*pi); } } #else const string &clean_filename = filename; #endif // _WIN32 vector::iterator ci; for (ci = contents.begin(); ci != contents.end(); ++ci) { if ((*ci) == clean_filename) { contents.erase(ci); return true; } } return false; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::append_safe_dir // Access: Public, Static // Description: Appends the indicated basename to the root directory // name, which is modified in-place. The basename is // allowed to contain nested slashes, but no directory // component of the basename may begin with a ".", thus // precluding ".." and hidden files. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: append_safe_dir(string &root, const string &basename) { if (basename.empty()) { return; } size_t p = 0; while (p < basename.length()) { size_t q = basename.find('/', p); if (q == string::npos) { if (q != p) { append_safe_dir_component(root, basename.substr(p)); } return; } if (q != p) { append_safe_dir_component(root, basename.substr(p, q - p)); } p = q + 1; } } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::create_runtime_environment // Access: Private // Description: Called during initialize, or after a previous call to // uninstall_all(), to make sure all needed // directories exist and the logfile is open. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: create_runtime_environment() { mkdir_complete(_log_directory, cerr); logfile.close(); logfile.clear(); #ifdef _WIN32 wstring log_pathname_w; string_to_wstring(log_pathname_w, _log_pathname); logfile.open(log_pathname_w.c_str(), ios::out | ios::trunc); #else logfile.open(_log_pathname.c_str(), ios::out | ios::trunc); #endif // _WIN32 if (logfile) { logfile.setf(ios::unitbuf); nout_stream = &logfile; } // Determine the temporary directory. #ifdef _WIN32 wchar_t buffer_1[MAX_PATH]; wstring temp_directory_w; // Figuring out the correct path for temporary files is a real mess // on Windows. We should be able to use GetTempPath(), but that // relies on $TMP or $TEMP being defined, and it appears that // Mozilla clears these environment variables for the plugin, which // forces GetTempPath() into $USERPROFILE instead. This is really // an inappropriate place for temporary files, so, GetTempPath() // isn't a great choice. // We could use SHGetSpecialFolderPath() instead to get us the path // to "Temporary Internet Files", which is acceptable. The trouble // is, if we happen to be running in "Protected Mode" on Vista, this // folder isn't actually writable by us! On Vista, we're supposed // to use IEGetWriteableFolderPath() instead, but *this* function // doesn't exist on XP and below. Good Lord. // We could go through a bunch of LoadLibrary() calls to try to find // the right path, like we do in find_root_dir(), but I'm just tired // of doing all that nonsense. We'll use a two-stage trick instead. // We'll check for $TEMP or $TMP being defined specifically, and if // they are, we'll use GetTempPath(); otherwise, we'll fall back to // SHGetSpecialFolderPath(). if (getenv("TEMP") != NULL || getenv("TMP") != NULL) { if (GetTempPathW(MAX_PATH, buffer_1) != 0) { temp_directory_w = buffer_1; } } if (temp_directory_w.empty()) { if (SHGetSpecialFolderPathW(NULL, buffer_1, CSIDL_INTERNET_CACHE, true)) { temp_directory_w = buffer_1; // That just *might* return a non-writable folder, if we're in // Protected Mode. We'll test this with GetTempFileName(). wchar_t temp_buffer[MAX_PATH]; if (!GetTempFileNameW(temp_directory_w.c_str(), L"p3d", 0, temp_buffer)) { nout << "GetTempFileName failed on " << temp_directory_w << ", switching to GetTempPath\n"; temp_directory_w.clear(); } else { DeleteFileW(temp_buffer); } } } // If both of the above failed, we'll fall back to GetTempPath() // once again as a last resort, which is supposed to return // *something* that works, even if $TEMP and $TMP are undefined. if (temp_directory_w.empty()) { if (GetTempPathW(MAX_PATH, buffer_1) != 0) { temp_directory_w = buffer_1; } } // Also insist that the temp directory is fully specified. size_t needs_size_2 = GetFullPathNameW(temp_directory_w.c_str(), 0, NULL, NULL); wchar_t *buffer_2 = new wchar_t[needs_size_2]; if (GetFullPathNameW(temp_directory_w.c_str(), needs_size_2, buffer_2, NULL) != 0) { temp_directory_w = buffer_2; } delete[] buffer_2; // And make sure the directory actually exists. mkdir_complete_w(temp_directory_w, nout); wstring_to_string(_temp_directory, temp_directory_w); #else _temp_directory = "/tmp/"; #endif // _WIN32 // Ensure that the temp directory ends with a slash. if (!_temp_directory.empty() && _temp_directory[_temp_directory.size() - 1] != '/') { #ifdef _WIN32 if (_temp_directory[_temp_directory.size() - 1] != '\\') #endif _temp_directory += "/"; } nout << "\n_root_dir = " << _root_dir << ", _temp_directory = " << _temp_directory << ", platform = " << _platform << ", host_url = " << _host_url << ", verify_contents = " << _verify_contents << "\n"; if (!_host_dir.empty()) { nout << "_host_dir = " << _host_dir << "\n"; } nout << "api_version = " << _api_version << "\n"; // Make the certificate directory. _certs_dir = _root_dir + "/certs"; if (!mkdir_complete(_certs_dir, nout)) { nout << "Couldn't mkdir " << _certs_dir << "\n"; } _created_runtime_environment = true; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::append_safe_dir_component // Access: Private, Static // Description: Appends a single directory component, implementing // append_safe_dir(), above. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: append_safe_dir_component(string &root, const string &component) { if (component.empty()) { return; } root += '/'; if (component[0] == '.') { root += 'x'; } root += component; } //////////////////////////////////////////////////////////////////// // Function: P3DInstanceManager::nt_thread_run // Access: Private // Description: The main function for the notify thread. //////////////////////////////////////////////////////////////////// void P3DInstanceManager:: nt_thread_run() { // The notify thread exists because we need to be able to send // asynchronous notifications of request events. These request // events were detected in the various read threads associated with // each session, but we can't call back into the plugin host space // from the read thread, since if the host immediately responds to a // callback by calling back into the p3d_plugin space, we will have // our read thread doing stuff in here that's not related to the // read thread. Even worse, some of the things it might need to do // might require a separate read thread to be running! _notify_ready.acquire(); while (_notify_thread_continue) { NotifyInstances instances; while (!_notify_instances.empty()) { instances.clear(); instances.swap(_notify_instances); // Go ahead and drop the lock while we make the callback, to // reduce the risk of deadlock. We don't want to be holding any // locks when we call into client code. _notify_ready.release(); NotifyInstances::iterator ni; for (ni = instances.begin(); ni != instances.end(); ++ni) { // TODO: a race condition here when instances are deleted. P3DInstance *inst = (*ni); assert(inst != NULL); P3D_request_ready_func *func = inst->get_request_ready_func(); if (func != NULL) { (*func)(inst); } } _notify_ready.acquire(); } _notify_ready.wait(); } _notify_ready.release(); }