Browse Source

new security enhancements for toontown client and server

David Rose 22 years ago
parent
commit
577c5ac28c
37 changed files with 1018 additions and 403 deletions
  1. 9 7
      panda/src/downloader/Sources.pp
  2. 1 0
      panda/src/downloader/bioPtr.cxx
  3. 1 1
      panda/src/downloader/bioStreamPtr.cxx
  4. 11 0
      panda/src/downloader/config_downloader.cxx
  5. 2 0
      panda/src/downloader/config_downloader.h
  6. 6 2
      panda/src/downloader/decompressor.cxx
  7. 5 0
      panda/src/downloader/decompressor.h
  8. 12 34
      panda/src/downloader/downloadDb.cxx
  9. 1 1
      panda/src/downloader/downloadDb.h
  10. 9 5
      panda/src/downloader/download_utils.cxx
  11. 9 5
      panda/src/downloader/download_utils.h
  12. 5 6
      panda/src/downloader/downloader_composite1.cxx
  13. 4 1
      panda/src/downloader/downloader_composite2.cxx
  14. 0 3
      panda/src/downloader/downloader_composite4.cxx
  15. 21 1
      panda/src/downloader/httpChannel.cxx
  16. 46 0
      panda/src/downloader/httpClient.I
  17. 126 0
      panda/src/downloader/httpClient.cxx
  18. 14 0
      panda/src/downloader/httpClient.h
  19. 7 5
      panda/src/downloader/patcher.cxx
  20. 11 8
      panda/src/downloader/patcher.h
  21. 4 4
      panda/src/downloadertools/Sources.pp
  22. 105 18
      panda/src/downloadertools/check_md5.cxx
  23. 13 8
      panda/src/express/Sources.pp
  24. 0 10
      panda/src/express/buffer.I
  25. 1 32
      panda/src/express/buffer.cxx
  26. 3 20
      panda/src/express/buffer.h
  27. 0 134
      panda/src/express/crypto_utils.cxx
  28. 0 4
      panda/src/express/express_composite1.cxx
  29. 6 0
      panda/src/express/express_composite2.cxx
  30. 189 23
      panda/src/express/hashVal.I
  31. 221 5
      panda/src/express/hashVal.cxx
  32. 46 14
      panda/src/express/hashVal.h
  33. 20 25
      panda/src/express/patchfile.cxx
  34. 8 7
      panda/src/express/patchfile.h
  35. 11 20
      panda/src/express/ramfile.I
  36. 47 0
      panda/src/express/ramfile.cxx
  37. 44 0
      panda/src/express/ramfile.h

+ 9 - 7
panda/src/downloader/Sources.pp

@@ -6,8 +6,7 @@
 #begin lib_target
   #define TARGET downloader
 
-  #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx \
-    $[if $[HAVE_ZLIB], $[TARGET]_composite4.cxx] \
+  #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx
 
   #define SOURCES \
     config_downloader.h \
@@ -16,8 +15,10 @@
     bioStreamPtr.I bioStreamPtr.h \
     bioStream.I bioStream.h bioStreamBuf.h \
     chunkedStream.I chunkedStream.h chunkedStreamBuf.h \
+    decompressor.h \
     documentSpec.I documentSpec.h \
     downloadDb.I downloadDb.h \
+    download_utils.h \
     extractor.h \
     httpAuthorization.I httpAuthorization.h \
     httpBasicAuthorization.I httpBasicAuthorization.h \
@@ -30,10 +31,9 @@
     identityStream.I identityStream.h identityStreamBuf.h \
     multiplexStream.I multiplexStream.h \
     multiplexStreamBuf.I multiplexStreamBuf.h \
+    patcher.h patcher.I \
     socketStream.h socketStream.I \
-    urlSpec.I urlSpec.h \
-    $[if $[HAVE_ZLIB], decompressor.h download_utils.h] \
-    $[if $[HAVE_CRYPTO], patcher.cxx patcher.h patcher.I]
+    urlSpec.I urlSpec.h
     
   #define INCLUDED_SOURCES                 \
     config_downloader.cxx \
@@ -42,8 +42,10 @@
     bioStreamPtr.cxx \
     bioStream.cxx bioStreamBuf.cxx \
     chunkedStream.cxx chunkedStreamBuf.cxx \
+    decompressor.cxx \
     documentSpec.cxx \
     downloadDb.cxx \
+    download_utils.cxx \
     extractor.cxx \
     httpAuthorization.cxx \
     httpBasicAuthorization.cxx \
@@ -55,9 +57,9 @@
     httpEnum.cxx \
     identityStream.cxx identityStreamBuf.cxx \
     multiplexStream.cxx multiplexStreamBuf.cxx \
+    patcher.cxx \
     socketStream.cxx \
-    urlSpec.cxx \
-    $[if $[HAVE_ZLIB], decompressor.cxx download_utils.cxx]
+    urlSpec.cxx
 
   #define INSTALL_HEADERS \
     asyncUtility.h asyncUtility.I \

+ 1 - 0
panda/src/downloader/bioPtr.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "bioPtr.h"
+#include "urlSpec.h"
 
 #ifdef HAVE_SSL
 

+ 1 - 1
panda/src/downloader/bioStreamPtr.cxx

@@ -16,7 +16,7 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#include "bioPtr.h"
+#include "bioStreamPtr.h"
 
 #ifdef HAVE_SSL
 

+ 11 - 0
panda/src/downloader/config_downloader.cxx

@@ -115,6 +115,17 @@ config_downloader.GetDouble("http-timeout", 20.0);
 const int http_max_connect_count =
 config_downloader.GetInt("http-max-connect-count", 10);
 
+// These provide a default client certificate to offer up should an
+// SSL server demand one.  The files references a PEM-formatted file
+// that includes a public and private key specification.  A
+// connection-specific certificate may also be specified at runtime on
+// the HTTPClient object, but this will require having a different
+// HTTPClient object for each differently-certificated connection.
+const string http_client_certificate_filename =
+config_downloader.GetString("http-client-certificate-filename", "");
+const string http_client_certificate_passphrase =
+config_downloader.GetString("http-client-certificate-passphrase", "");
+
 ConfigureFn(config_downloader) {
 #ifdef HAVE_SSL
   HTTPChannel::init_type();

+ 2 - 0
panda/src/downloader/config_downloader.h

@@ -51,5 +51,7 @@ extern const bool http_proxy_tunnel;
 extern const double connect_timeout;
 extern const double http_timeout;
 extern const int http_max_connect_count;
+extern const string http_client_certificate_filename;
+extern const string http_client_certificate_passphrase;
 
 #endif

+ 6 - 2
panda/src/downloader/decompressor.cxx

@@ -16,13 +16,15 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-// This file is compiled only if we have zlib installed.
+#include "pandabase.h"
+
+#ifdef HAVE_ZLIB
 
 #include "config_downloader.h"
 
 #include "error_utils.h"
 #include "filename.h"
-#include "buffer.h"
+#include "ramfile.h"
 #include "zStream.h"
 #include "config_express.h"
 
@@ -273,3 +275,5 @@ cleanup() {
     _decompress = NULL;
   }
 }
+
+#endif  // HAVE_ZLIB

+ 5 - 0
panda/src/downloader/decompressor.h

@@ -20,6 +20,9 @@
 #define DECOMPRESSOR_H
 
 #include "pandabase.h"
+
+#ifdef HAVE_ZLIB
+
 #include "filename.h"
 
 class Ramfile;
@@ -58,4 +61,6 @@ private:
 
 #include "decompressor.I"
 
+#endif  // HAVE_ZLIB
+
 #endif

+ 12 - 34
panda/src/downloader/downloadDb.cxx

@@ -20,6 +20,7 @@
 #include "downloadDb.h"
 #include "streamReader.h"
 #include "streamWriter.h"
+#include "ramfile.h"
 
 #include <algorithm>
 
@@ -481,11 +482,7 @@ write(ostream &out) const {
       << "    phase: " << _phase   << endl
       << "     size: " << _size    << endl
       << "   status: " << _status  << endl
-      << "     hash: [" << _hash.get_value(0)
-      << " " << _hash.get_value(1)
-      << " " << _hash.get_value(2)
-      << " " << _hash.get_value(3)
-      << "]" << endl;
+      << "     hash: " << _hash << endl;
   out << "--------------------------------------------------" << endl;
   pvector< PT(FileRecord) >::const_iterator i = _file_records.begin();
   for(; i != _file_records.end(); ++i) {
@@ -753,12 +750,7 @@ parse_mfr(const string &data) {
   mfr->_name = back_to_front_slash(mfr->_name);
   
   // Read the hash value
-  HashVal hash;
-  hash.set_value(0, di.get_uint32());
-  hash.set_value(1, di.get_uint32());
-  hash.set_value(2, di.get_uint32());
-  hash.set_value(3, di.get_uint32());
-  mfr->_hash = hash;
+  mfr->_hash.read_datagram(di);
 
   downloader_cat.debug()
     << "Parsed multifile record: " << mfr->_name << " phase: " << mfr->_phase
@@ -812,7 +804,7 @@ read(StreamReader &sr, bool want_server_info) {
   // Read the header
   string header;
   header = sr.extract_bytes(_header_length);
-  if (header.size() != _header_length) {
+  if (header.size() != (size_t)_header_length) {
     downloader_cat.error() << "truncated db file" << endl;
     return false;
   }
@@ -832,7 +824,7 @@ read(StreamReader &sr, bool want_server_info) {
     int mfr_header_length = sizeof(PN_int32);
 
     string mfr_header = sr.extract_bytes(mfr_header_length);
-    if (mfr_header.size() != mfr_header_length) {
+    if (mfr_header.size() != (size_t)mfr_header_length) {
       downloader_cat.error() << "invalid mfr header" << endl;
       return false;
     }
@@ -845,7 +837,7 @@ read(StreamReader &sr, bool want_server_info) {
     // do not count the header length twice
     int read_length = (mfr_length - mfr_header_length);
     string mfr_record = sr.extract_bytes(read_length);
-    if (mfr_record.size() != read_length) {
+    if (mfr_record.size() != (size_t)read_length) {
       downloader_cat.error() << "invalid mfr record" << endl;
       return false;
     }
@@ -864,7 +856,7 @@ read(StreamReader &sr, bool want_server_info) {
 
         // Read the header
         string fr_header = sr.extract_bytes(fr_header_length);
-        if (fr_header.size() != fr_header_length) {
+        if (fr_header.size() != (size_t)fr_header_length) {
           downloader_cat.error() << "invalid fr header" << endl;
           return false;
         }
@@ -877,7 +869,7 @@ read(StreamReader &sr, bool want_server_info) {
         int read_length = (fr_length - fr_header_length);
 
         string fr_record = sr.extract_bytes(read_length);
-        if (fr_record.size() != read_length) {
+        if (fr_record.size() != (size_t)read_length) {
           downloader_cat.error() << "invalid fr record" << endl;
           return false;
         }
@@ -914,7 +906,6 @@ write(StreamWriter &sw, bool want_server_info) {
   PN_int32 num_files;
   PN_int32 name_length;
   PN_int32 header_length;
-  HashVal hash;
 
   // Iterate over the multifiles writing them to the stream
   pvector< PT(MultifileRecord) >::const_iterator i = _mfile_records.begin();
@@ -949,11 +940,7 @@ write(StreamWriter &sw, bool want_server_info) {
     sw.add_int32(status);
     sw.add_int32(num_files);
     
-    hash = (*i)->_hash;
-    sw.add_uint32(hash.get_value(0));
-    sw.add_uint32(hash.get_value(1));
-    sw.add_uint32(hash.get_value(2));
-    sw.add_uint32(hash.get_value(3));
+    (*i)->_hash.write_stream(sw);
 
     // Only write out the file information if you are the server
     if (want_server_info) {
@@ -1217,7 +1204,6 @@ write_version_map(StreamWriter &sw) {
   VersionMap::iterator vmi;
   VectorHash::iterator i;
   string name;
-  HashVal hash;
 
   sw.add_int32(_versions.size());
   for (vmi = _versions.begin(); vmi != _versions.end(); ++vmi) {
@@ -1230,12 +1216,7 @@ write_version_map(StreamWriter &sw) {
     sw.add_int32((*vmi).second.size());
     for (i = (*vmi).second.begin(); i != (*vmi).second.end(); ++i) {
       // *i will point to a HashVal
-      hash = *i;
-      // Write out each uint separately
-      sw.add_uint32(hash.get_value(0));
-      sw.add_uint32(hash.get_value(1));
-      sw.add_uint32(hash.get_value(2));
-      sw.add_uint32(hash.get_value(3));
+      (*i).write_stream(sw);
     }
   }
 }
@@ -1279,10 +1260,7 @@ read_version_map(StreamReader &sr) {
 
     for (int j = 0; j < length; j++) {
       HashVal hash;
-      hash.set_value(0, sr.get_uint32());
-      hash.set_value(1, sr.get_uint32());
-      hash.set_value(2, sr.get_uint32());
-      hash.set_value(3, sr.get_uint32());
+      hash.read_stream(sr);
       if (sr.get_istream()->fail()) {
         return false;
       }
@@ -1306,7 +1284,7 @@ write_version_map(ostream &out) const {
     out << "  " << (*vmi).first << endl;
     for (i = (*vmi).second.begin(); i != (*vmi).second.end(); ++i) {
       HashVal hash = *i;
-      out << "    " << hash << endl;
+      out << "    " << hash.as_dec() << endl;
     }
   }
   out << endl;

+ 1 - 1
panda/src/downloader/downloadDb.h

@@ -30,11 +30,11 @@
 #include "pmap.h"
 
 #include "hashVal.h"
-#include "buffer.h"
 
 class StreamReader;
 class StreamWriter;
 typedef float Phase;
+class Ramfile;
 
 /*
 //////////////////////////////////////////////////

+ 9 - 5
panda/src/downloader/download_utils.cxx

@@ -16,13 +16,15 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-// This file is compiled only if we have zlib installed.
+#include "pandabase.h"
+
+#ifdef HAVE_ZLIB
 
 #include "download_utils.h"
 #include "config_downloader.h"
 #include <zlib.h>
 
-ulong
+unsigned long
 check_crc(Filename name) {
   ifstream read_stream;
   name.set_binary();
@@ -40,7 +42,7 @@ check_crc(Filename name) {
   read_stream.read(buffer, buffer_length);
 
   // Compute the crc
-  ulong crc = crc32(0L, Z_NULL, 0);
+  unsigned long crc = crc32(0L, Z_NULL, 0);
   crc = crc32(crc, (uchar *)buffer, buffer_length);
 
   delete buffer;
@@ -48,7 +50,7 @@ check_crc(Filename name) {
   return crc;
 }
 
-ulong
+unsigned long
 check_adler(Filename name) {
   ifstream read_stream;
   name.set_binary();
@@ -66,10 +68,12 @@ check_adler(Filename name) {
   read_stream.read(buffer, buffer_length);
 
   // Compute the adler checksum
-  ulong adler = adler32(0L, Z_NULL, 0);
+  unsigned long adler = adler32(0L, Z_NULL, 0);
   adler = adler32(adler, (uchar *)buffer, buffer_length);
 
   delete buffer;
 
   return adler;
 }
+
+#endif  // HAVE_ZLIB

+ 9 - 5
panda/src/downloader/download_utils.h

@@ -19,16 +19,20 @@
 #ifndef DOWNLOAD_UTILS_H
 #define DOWNLOAD_UTILS_H
 
-#include <pandabase.h>
-#include <filename.h>
-#include <typedef.h>
+#include "pandabase.h"
+
+#ifdef HAVE_ZLIB
+
+#include "filename.h"
 
 BEGIN_PUBLISH
 
-EXPCL_PANDAEXPRESS ulong check_crc(Filename name);
-EXPCL_PANDAEXPRESS ulong check_adler(Filename name);
+EXPCL_PANDAEXPRESS unsigned long check_crc(Filename name);
+EXPCL_PANDAEXPRESS unsigned long check_adler(Filename name);
 
 END_PUBLISH
 
+#endif  // HAVE_ZLIB
+
 #endif
 

+ 5 - 6
panda/src/downloader/downloader_composite1.cxx

@@ -1,14 +1,13 @@
-#include "config_downloader.cxx"
 #include "asyncUtility.cxx"
 #include "bioPtr.cxx"
-#include "bioStreamPtr.cxx"
 #include "bioStream.cxx"
 #include "bioStreamBuf.cxx"
+#include "bioStreamPtr.cxx"
 #include "chunkedStream.cxx"
 #include "chunkedStreamBuf.cxx"
+#include "config_downloader.cxx"
+#include "decompressor.cxx"
+#include "documentSpec.cxx"
 #include "downloadDb.cxx"
+#include "download_utils.cxx"
 #include "extractor.cxx"
-#include "httpDate.cxx"
-#include "httpEntityTag.cxx"
-#include "documentSpec.cxx"
-

+ 4 - 1
panda/src/downloader/downloader_composite2.cxx

@@ -1,12 +1,15 @@
 #include "httpAuthorization.cxx"
 #include "httpBasicAuthorization.cxx"
-#include "httpClient.cxx"
 #include "httpChannel.cxx"
+#include "httpClient.cxx"
+#include "httpDate.cxx"
 #include "httpDigestAuthorization.cxx"
+#include "httpEntityTag.cxx"
 #include "httpEnum.cxx"
 #include "identityStream.cxx"
 #include "identityStreamBuf.cxx"
 #include "multiplexStream.cxx"
 #include "multiplexStreamBuf.cxx"
+#include "patcher.cxx"
 #include "socketStream.cxx"
 #include "urlSpec.cxx"

+ 0 - 3
panda/src/downloader/downloader_composite4.cxx

@@ -1,3 +0,0 @@
-#include "download_utils.cxx"
-#include "decompressor.cxx"
-

+ 21 - 1
panda/src/downloader/httpChannel.cxx

@@ -23,7 +23,7 @@
 #include "identityStream.h"
 #include "config_downloader.h"
 #include "clockObject.h"
-#include "buffer.h"  // for Ramfile
+#include "ramfile.h"
 
 #ifdef HAVE_SSL
 #ifdef REPORT_OPENSSL_ERRORS
@@ -54,6 +54,7 @@ HTTPChannel(HTTPClient *client) :
   _max_updates_per_second = 1.0f / _seconds_per_update;
   _bytes_per_update = int(_max_bytes_per_second * _seconds_per_update);
   _nonblocking = false;
+
   _want_ssl = false;
   _proxy_serves_document = false;
   _proxy_tunnel_now = false;
@@ -646,6 +647,7 @@ downcase(const string &s) {
   return result;
 }
 
+
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPChannel::reached_done_state
 //       Access: Private
@@ -1191,6 +1193,24 @@ run_setup_ssl() {
     return false;
   }
 
+  // It would be nice to use something like SSL_set_client_cert_cb()
+  // here to set a callback to provide the certificate should it be
+  // requested, or even to potentially provide any of a number of
+  // certificates according to the server's CA presented, but that
+  // interface as provided by OpenSSL is broken since there's no way
+  // to pass additional data to the callback function (and hence no
+  // way to tie it back to the HTTPChannel object, other than by
+  // building a messy mapping of SSL pointers back to HTTPChannel
+  // pointers).
+  if (_client->load_client_certificate()) {
+    SSL_use_certificate(ssl, _client->_client_certificate_pub);
+    SSL_use_PrivateKey(ssl, _client->_client_certificate_priv);
+    if (!SSL_check_private_key(ssl)) {
+      downloader_cat.warning()
+        << "Client private key does not match public key!\n";
+    }
+  }
+
   if (downloader_cat.is_spam()) {
     downloader_cat.spam()
       << "SSL Ciphers available:\n";

+ 46 - 0
panda/src/downloader/httpClient.I

@@ -44,6 +44,52 @@ get_try_all_direct() const {
   return _try_all_direct;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::set_client_certificate_filename
+//       Access: Published
+//  Description: Sets the filename of the pem-formatted file that will
+//               be read for the client public and private keys if an
+//               SSL server requests a certificate.  Either this or
+//               set_client_certificate_pem() may be used to specify a
+//               client certificate.
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPClient::
+set_client_certificate_filename(const Filename &filename) {
+  _client_certificate_filename = filename;
+  _client_certificate_pem = string();
+  unload_client_certificate();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::set_client_certificate_pem
+//       Access: Published
+//  Description: Sets the pem-formatted contents of the certificate
+//               that will be parsed for the client public and private
+//               keys if an SSL server requests a certificate.  Either
+//               this or set_client_certificate_filename() may be used
+//               to specify a client certificate.
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPClient::
+set_client_certificate_pem(const string &pem) {
+  _client_certificate_pem = pem;
+  _client_certificate_filename = Filename();
+  unload_client_certificate();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::set_client_certificate_passphrase
+//       Access: Published
+//  Description: Sets the passphrase used to decrypt the private key
+//               in the certificate named by
+//               set_client_certificate_filename() or
+//               set_client_certificate_pem().
+////////////////////////////////////////////////////////////////////
+INLINE void HTTPClient::
+set_client_certificate_passphrase(const string &passphrase) {
+  _client_certificate_passphrase = passphrase;
+  unload_client_certificate();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPClient::set_http_version
 //       Access: Published

+ 126 - 0
panda/src/downloader/httpClient.cxx

@@ -128,6 +128,13 @@ HTTPClient() {
     }
   }
 
+  _client_certificate_filename = http_client_certificate_filename;
+  _client_certificate_passphrase = http_client_certificate_passphrase;
+
+  _client_certificate_loaded = false;
+  _client_certificate_pub = NULL;
+  _client_certificate_priv = NULL;
+
   // The first time we create an HTTPClient, we must initialize the
   // OpenSSL library.
   if (!_ssl_initialized) {
@@ -190,6 +197,8 @@ HTTPClient::
 
   // Free all of the expected server definitions.
   clear_expected_servers();
+
+  unload_client_certificate();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -599,6 +608,93 @@ get_username(const string &server, const string &realm) const {
   return string();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::load_client_certificate
+//       Access: Published
+//  Description: Attempts to load the certificate named by
+//               set_client_certificate_filename() immediately, and
+//               returns true if successful, false otherwise.
+//
+//               Normally this need not be explicitly called, since it
+//               will be called automatically if the server requests a
+//               certificate, but it may be useful to determine ahead
+//               of time if the certificate can be loaded correctly.
+////////////////////////////////////////////////////////////////////
+bool HTTPClient::
+load_client_certificate() {
+  if (!_client_certificate_loaded) {
+    _client_certificate_loaded = true;
+
+    if (!_client_certificate_filename.empty()) {
+      _client_certificate_filename.set_text();
+
+      // First, read the complete file into memory.
+      VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+      if (!vfs->read_file(_client_certificate_filename, 
+                          _client_certificate_pem)) {
+        // Could not find or read file.
+        downloader_cat.warning()
+          << "Could not read " << _client_certificate_filename << ".\n";
+        return false;
+      }
+    }
+
+    // Create an in-memory BIO to read the "file" from the memory
+    // buffer, and call the low-level routines to read the
+    // keys from the BIO.
+    BIO *mbio = BIO_new_mem_buf((void *)_client_certificate_pem.data(), 
+                                _client_certificate_pem.length());
+
+    ERR_clear_error();
+    _client_certificate_priv = 
+      PEM_read_bio_PrivateKey(mbio, NULL, NULL, 
+                              (char *)_client_certificate_passphrase.c_str());
+
+    // Rewind the "file" to the beginning in order to read the public
+    // key (which might appear first in the file).
+    BIO_reset(mbio);
+
+    ERR_clear_error();
+    _client_certificate_pub = 
+      PEM_read_bio_X509(mbio, NULL, NULL, NULL);
+
+    BIO_free(mbio);
+
+    
+    NotifySeverity sev = NS_debug;
+    string source = "memory";
+    if (!_client_certificate_filename.empty()) {
+      // Only report status to "info" severity if we have read the
+      // certificate from a file.  If it came from an in-memory image,
+      // a failure will presumably be handled by whoever set the
+      // image.
+      sev = NS_info;
+      source = _client_certificate_filename;
+    }
+
+    if (_client_certificate_priv != (EVP_PKEY *)NULL &&
+        _client_certificate_pub != (X509 *)NULL) {
+      downloader_cat.out(sev) 
+        << "Read client certificate from " << source << "\n";
+
+    } else {
+      if (_client_certificate_priv == (EVP_PKEY *)NULL) {
+        downloader_cat.out(sev)
+          << "Could not read private key from " << source << "\n";
+      }
+      
+      if (_client_certificate_pub == (X509 *)NULL) {
+        downloader_cat.out(sev)
+          << "Could not read public key from " << source << "\n";
+      }
+    }
+  }
+
+  return (_client_certificate_priv != (EVP_PKEY *)NULL &&
+          _client_certificate_pub != (X509 *)NULL);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPClient::get_http_version_string
 //       Access: Published
@@ -1108,6 +1204,28 @@ generate_auth(const URLSpec &url, bool is_proxy, const string &challenge) {
   return auth;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: HTTPClient::unload_client_certificate
+//       Access: Private
+//  Description: Frees the resources allocated by a previous call to
+//               load_client_certificate(), and marks the certificate
+//               unloaded.
+////////////////////////////////////////////////////////////////////
+void HTTPClient::
+unload_client_certificate() {
+  if (_client_certificate_priv != (EVP_PKEY *)NULL) {
+    EVP_PKEY_free(_client_certificate_priv);
+    _client_certificate_priv = NULL;
+  }
+
+  if (_client_certificate_pub != (X509 *)NULL) {
+    X509_free(_client_certificate_pub);
+    _client_certificate_pub = NULL;
+  }
+
+  _client_certificate_loaded = false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: HTTPClient::initialize_ssl
 //       Access: Private, Static
@@ -1209,6 +1327,14 @@ load_verify_locations(SSL_CTX *ctx, const Filename &ca_file) {
           << "Entry " << i << " is crl\n";
       }
 
+    } else if (itmp->x_pkey) {
+      //      X509_STORE_add_crl(store, itmp->x_pkey);
+      //      count++;
+      if (downloader_cat.is_spam()) {
+        downloader_cat.spam()
+          << "Entry " << i << " is pkey\n";
+      }
+
     } else {
       if (downloader_cat.is_spam()) {
         downloader_cat.spam()

+ 14 - 0
panda/src/downloader/httpClient.h

@@ -82,6 +82,11 @@ PUBLISHED:
   void set_username(const string &server, const string &realm, const string &username);
   string get_username(const string &server, const string &realm) const;
 
+  INLINE void set_client_certificate_filename(const Filename &filename);
+  INLINE void set_client_certificate_pem(const string &pem);
+  INLINE void set_client_certificate_passphrase(const string &passphrase);
+  bool load_client_certificate();
+
   INLINE void set_http_version(HTTPEnum::HTTPVersion version);
   INLINE HTTPEnum::HTTPVersion get_http_version() const;
   string get_http_version_string() const;
@@ -125,6 +130,8 @@ private:
   PT(HTTPAuthorization) generate_auth(const URLSpec &url, bool is_proxy,
                                       const string &challenge);
 
+  void unload_client_certificate();
+
   static void initialize_ssl();
   static int load_verify_locations(SSL_CTX *ctx, const Filename &ca_file);
 
@@ -158,12 +165,19 @@ private:
   typedef pmap<string, Domain> Domains;
   Domains _proxy_domains, _www_domains;
 
+  Filename _client_certificate_filename;
+  string _client_certificate_pem;
+  string _client_certificate_passphrase;
+
   // List of allowable SSL servers to connect to.  If the list is
   // empty, any server is acceptable.
   typedef pvector<X509_NAME *> ExpectedServers;
   ExpectedServers _expected_servers;
 
   SSL_CTX *_ssl_ctx;
+  bool _client_certificate_loaded;
+  X509 *_client_certificate_pub;
+  EVP_PKEY *_client_certificate_priv;
 
   static bool _ssl_initialized;
   static X509_STORE *_x509_store;

+ 7 - 5
panda/src/downloader/patcher.cxx

@@ -16,13 +16,13 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#include "pandabase.h"
+
+#ifdef HAVE_SSL
+
 #include "config_downloader.h"
-#include <filename.h>
 #include "patcher.h"
-
-////////////////////////////////////////////////////////////////////
-// Defines
-////////////////////////////////////////////////////////////////////
+#include "filename.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Patcher::Constructor
@@ -88,3 +88,5 @@ int Patcher::
 run(void) {
   return _patchfile->run();
 }
+
+#endif  // HAVE_SSL

+ 11 - 8
panda/src/downloader/patcher.h

@@ -15,16 +15,17 @@
 // [email protected] .
 //
 ////////////////////////////////////////////////////////////////////
+
 #ifndef PATCHER_H
 #define PATCHER_H
-//
-////////////////////////////////////////////////////////////////////
-// Includes
-////////////////////////////////////////////////////////////////////
-#include <pandabase.h>
-#include <filename.h>
-#include <buffer.h>
-#include <patchfile.h>
+
+#include "pandabase.h"
+
+#ifdef HAVE_SSL
+
+#include "filename.h"
+#include "buffer.h"
+#include "patchfile.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : Patcher
@@ -49,4 +50,6 @@ private:
 
 #include "patcher.I"
 
+#endif  // HAVE_SSL
+
 #endif

+ 4 - 4
panda/src/downloadertools/Sources.pp

@@ -4,7 +4,7 @@
 
 #begin bin_target
   #define TARGET apply_patch
-  #define BUILD_TARGET $[HAVE_CRYPTO]
+  #define BUILD_TARGET $[HAVE_SSL]
 
   #define SOURCES \
     apply_patch.cxx
@@ -15,7 +15,7 @@
 
 #begin bin_target
   #define TARGET build_patch
-  #define BUILD_TARGET $[HAVE_CRYPTO]
+  #define BUILD_TARGET $[HAVE_SSL]
 
   #define SOURCES \
     build_patch.cxx
@@ -24,7 +24,7 @@
 
 #begin bin_target
   #define TARGET show_ddb
-  #define BUILD_TARGET $[HAVE_CRYPTO]
+  #define BUILD_TARGET $[HAVE_SSL]
 
   #define SOURCES \
     show_ddb.cxx
@@ -53,7 +53,7 @@
 
 #begin bin_target
   #define TARGET check_md5
-  #define BUILD_TARGET $[HAVE_CRYPTO]
+  #define BUILD_TARGET $[HAVE_SSL]
   #define USE_PACKAGES $[USE_PACKAGES] crypto
 
   #define SOURCES \

+ 105 - 18
panda/src/downloadertools/check_md5.cxx

@@ -16,34 +16,121 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#include "crypto_utils.h"
+#include "pandabase.h"
+
 #include "hashVal.h"
 #include "filename.h"
 
+#ifndef HAVE_GETOPT
+#include <gnu_getopt.h>
+#else
+#include <getopt.h>
+#endif
+
+void
+usage() {
+  cerr << 
+    "\n"
+    "Usage:\n\n"
+    "check_md5 [-d] [-i \"input string\"] [file1 file2 ...]\n"
+    "check_md5 -h\n\n";
+}
+
+void
+help() {
+  usage();
+  cerr << 
+    "This program outputs the MD5 hash of one or more files (or of a string\n"
+    "passed on the command line with -i).\n\n"
+
+    "An MD5 hash is a 128-bit value.  The output is presented as a 32-digit\n"
+    "hexadecimal string by default, but with -d, it is presented as four\n"
+    "big-endian unsigned 32-bit decimal integers.\n\n";
+}
+
+void
+output_hash(const string &filename, const HashVal &hash, bool output_decimal) {
+  if (!filename.empty()) {
+    cout << filename << " ";
+  }
+  if (output_decimal) {
+    hash.output_dec(cout);
+  } else {
+    hash.output_hex(cout);
+  }
+  cout << "\n";
+}
+  
+
 int
 main(int argc, char *argv[]) {
-  const char *usagestr = "Usage: check_md5 <file>";
-  if (argc < 2) {
-    cerr << usagestr << endl;
-    return 1;
+  extern char *optarg;
+  extern int optind;
+  const char *optstr = "i:dh";
+
+  bool got_input_string = false;
+  string input_string;
+  bool output_decimal = false;
+
+  int flag = getopt(argc, argv, optstr);
+
+  while (flag != EOF) {
+    switch (flag) {
+    case 'i':
+      got_input_string = true;
+      input_string = optarg;
+      break;
+
+    case 'd':
+      output_decimal = true;
+      break;
+
+    case 'h':
+      help();
+      exit(1);
+
+    default:
+      exit(1);
+    }
+    flag = getopt(argc, argv, optstr);
   }
 
-  Filename source_file;
-  source_file = Filename::from_os_specific(argv[1]);
+  argc -= (optind-1);
+  argv += (optind-1);
 
-  if(!source_file.exists()) {
-       cerr << usagestr << endl;
-       cerr << source_file << " not found!\n";
-       return 2;
+  if (argc < 2 && !got_input_string) {
+    usage();
+    exit(1);
   }
 
-  HashVal hash;
-  md5_a_file(source_file, hash);
+  if (got_input_string) {
+    HashVal hash;
+    hash.hash_string(input_string);
+    output_hash("", hash, output_decimal);
+  }
+
+  bool okflag = true;
+
+  for (int i = 1; i < argc; i++) {
+    Filename source_file = Filename::from_os_specific(argv[i]);
+
+    if (!source_file.exists()) {
+      cerr << source_file << " not found!\n";
+      okflag = false;
+    } else {
+      HashVal hash;
+      if (!hash.hash_file(source_file)) {
+        cerr << "Unable to read " << source_file << "\n";
+        okflag = false;
+      } else {
+        output_hash(source_file.get_basename(), hash, output_decimal);
+      }
+    }
+  }
+
+  if (!okflag) {
+    exit(1);
+  }
 
-  // output base of Filename along w/md5
-  cout << source_file.get_basename() << " ";
-  hash.output(cout);
-  cout << endl;
-  
   return 0;
 }

+ 13 - 8
panda/src/express/Sources.pp

@@ -4,7 +4,7 @@
 
 #begin lib_target
   #define TARGET express
-  #define USE_PACKAGES nspr crypto net zlib
+  #define USE_PACKAGES nspr crypto net zlib ssl
   
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx
 
@@ -41,10 +41,13 @@
     namable.h nativeNumericData.I nativeNumericData.h \
     numeric_types.h \
     ordered_vector.h ordered_vector.I ordered_vector.T \
+    patchfile.I patchfile.h \
     pointerTo.I pointerTo.h \
     pointerToArray.I pointerToArray.h \
     profileTimer.I profileTimer.h \
-    pta_uchar.h referenceCount.I referenceCount.h \
+    pta_uchar.h \
+    ramfile.I ramfile.h \
+    referenceCount.I referenceCount.h \
     register_type.I register_type.h \
     reversedNumericData.I reversedNumericData.h \
     selectThreadImpl.h \
@@ -69,10 +72,7 @@
     virtualFileMountSystem.I virtualFileSimple.h virtualFileSimple.I \
     virtualFileSystem.h virtualFileSystem.I \
     windowsRegistry.h \
-    zStream.I zStream.h zStreamBuf.h \
-    $[if $[HAVE_CRYPTO], \
-       crypto_utils.cxx crypto_utils.h patchfile.I \
-       patchfile.cxx patchfile.h ]
+    zStream.I zStream.h zStreamBuf.h
 
   #define INCLUDED_SOURCES  \
     atomicAdjust.cxx atomicAdjustDummyImpl.cxx atomicAdjustNsprImpl.cxx \
@@ -90,8 +90,11 @@
     namable.cxx \
     nativeNumericData.cxx \
     ordered_vector.cxx \
+    patchfile.cxx \
     profileTimer.cxx \
-    pta_uchar.cxx referenceCount.cxx register_type.cxx \
+    pta_uchar.cxx \
+    ramfile.cxx \
+    referenceCount.cxx register_type.cxx \
     reversedNumericData.cxx \
     streamReader.cxx streamWriter.cxx \
     stringDecoder.cxx \
@@ -141,7 +144,9 @@
     ordered_vector.h ordered_vector.I ordered_vector.T \
     patchfile.I patchfile.h pointerTo.I pointerTo.h \
     pointerToArray.I pointerToArray.h profileTimer.I \
-    profileTimer.h pta_uchar.h referenceCount.I referenceCount.h \
+    profileTimer.h pta_uchar.h \
+    ramfile.I ramfile.h \
+    referenceCount.I referenceCount.h \
     register_type.I register_type.h \
     reversedNumericData.I reversedNumericData.h \
     selectThreadImpl.h \

+ 0 - 10
panda/src/express/buffer.I

@@ -25,13 +25,3 @@ INLINE int Buffer::
 get_length(void) const {
   return _length;
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: Ramfile::constructor
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE Ramfile::
-Ramfile(void) {
-  _pos = 0;
-}

+ 1 - 32
panda/src/express/buffer.cxx

@@ -15,11 +15,8 @@
 // [email protected] .
 //
 ////////////////////////////////////////////////////////////////////
-#include "buffer.h"
 
-////////////////////////////////////////////////////////////////////
-// Defines
-////////////////////////////////////////////////////////////////////
+#include "buffer.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Buffer::Constructor
@@ -42,31 +39,3 @@ Buffer::
   delete _buffer;
 }
 
-
-////////////////////////////////////////////////////////////////////
-//     Function: Ramfile::readline
-//       Access: Published
-//  Description: Assumes the stream represents a text file, and
-//               extracts one line up to and including the trailing
-//               newline character.  Returns empty string when the end
-//               of file is reached.
-//
-//               The interface here is intentionally designed to be
-//               similar to that for Python's File.readline()
-//               function.
-////////////////////////////////////////////////////////////////////
-string Ramfile::
-readline() {
-  size_t start = _pos;
-  while (_pos < _data.length() && _data[_pos] != '\n') {
-    ++_pos;
-  }
-
-  if (_pos < _data.length() && _data[_pos] == '\n') {
-    // Include the newline character also.
-    ++_pos;
-  }
-
-  return _data.substr(start, _pos - start);
-}
-

+ 3 - 20
panda/src/express/buffer.h

@@ -15,13 +15,11 @@
 // [email protected] .
 //
 ////////////////////////////////////////////////////////////////////
+
 #ifndef BUFFER_H
 #define BUFFER_H
-//
-////////////////////////////////////////////////////////////////////
-// Includes
-////////////////////////////////////////////////////////////////////
-#include <pandabase.h>
+
+#include "pandabase.h"
 #include "typedef.h"
 #include "referenceCount.h"
 
@@ -46,21 +44,6 @@ private:
   int _length;
 };
 
-////////////////////////////////////////////////////////////////////
-//       Class : Ramfile
-// Description :
-////////////////////////////////////////////////////////////////////
-class EXPCL_PANDAEXPRESS Ramfile {
-PUBLISHED:
-  INLINE Ramfile();
-
-  string readline();
-
-public:
-  size_t _pos;
-  string _data;
-};
-
 #include "buffer.I"
 
 #endif

+ 0 - 134
panda/src/express/crypto_utils.cxx

@@ -1,134 +0,0 @@
-// Filename: crypto_utils.cxx
-// Created by:  drose (07Nov00)
-//
-////////////////////////////////////////////////////////////////////
-//
-// PANDA 3D SOFTWARE
-// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
-//
-// All use of this software is subject to the terms of the Panda 3d
-// Software license.  You should have received a copy of this license
-// along with this source code; you will also find a current copy of
-// the license at http://www.panda3d.org/license.txt .
-//
-// To contact the maintainers of this program write to
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-// This file is compiled only if we have crypto++ installed.
-
-
-#include "hashVal.h"
-#include "config_express.h"
-#include "crypto_utils.h"
-
-#include <md5.h>
-#include <hex.h>
-#include <files.h>
-
-// md5 defines this, but we'd rather do without it, because it
-// conflicts with a similar macro defined in NSPR.
-#undef IS_LITTLE_ENDIAN
-
-USING_NAMESPACE(CryptoPP);
-USING_NAMESPACE(std);
-
-static uint
-read32(istream& is) {
-  unsigned int ret = 0x0;
-  for (int i=0; i<8; ++i) {
-    char b;
-    int n = 0;
-    is >> b;
-    switch (b) {
-    case '0':
-    case '1':
-    case '2':
-    case '3':
-    case '4':
-    case '5':
-    case '6':
-    case '7':
-    case '8':
-    case '9':
-      n = b - '0';
-      break;
-    case 'A':
-    case 'B':
-    case 'C':
-    case 'D':
-    case 'E':
-    case 'F':
-      n = b - 'A' + 10;
-      break;
-    case 'a':
-    case 'b':
-    case 'c':
-    case 'd':
-    case 'e':
-    case 'f':
-      n = b - 'a' + 10;
-      break;
-    default:
-      cerr << "illegal hex nibble '" << b << "'" << endl;
-    }
-    ret = (ret << 4) | (n & 0x0f);
-  }
-  return ret;
-}
-
-void
-md5_a_file(const Filename &name, HashVal &ret) {
-  ostringstream os;
-  MD5 md5;
-
-  string fs = name.to_os_specific();
-
-  try {
-    FileSource f(fs.c_str(), true,
-                 new HashFilter(md5, new HexEncoder(new FileSink(os))));
-  } catch (...) {
-    express_cat.warning()
-      << "Unable to read " << name << " to compute md5 hash.\n";
-    if (!name.exists()) {
-      express_cat.warning()
-        << "(file does not exist.)\n";
-    } else {
-      express_cat.warning()
-        << "(file exists but cannot be read.)\n";
-    }
-    ret.hv[0] = 0;
-    ret.hv[1] = 0;
-    ret.hv[2] = 0;
-    ret.hv[3] = 0;
-    return;
-  }
-
-  istringstream is(os.str());
-
-  ret.hv[0] = read32(is);
-  ret.hv[1] = read32(is);
-  ret.hv[2] = read32(is);
-  ret.hv[3] = read32(is);
-}
-
-void
-md5_a_buffer(const unsigned char* buf, unsigned long len, HashVal& ret) {
-  MD5 md5;
-
-  HashFilter hash(md5);
-  hash.Put((byte*)buf, len);
-  hash.Close();
-
-  unsigned char* outb;
-  unsigned long outl = hash.MaxRetrieveable();
-  outb = new uchar[outl];
-  hash.Get((byte*)outb, outl);
-  ret.hv[0] = (outb[0] << 24) | (outb[1] << 16) | (outb[2] << 8) | outb[3];
-  ret.hv[1] = (outb[4] << 24) | (outb[5] << 16) | (outb[6] << 8) | outb[7];
-  ret.hv[2] = (outb[8] << 24) | (outb[9] << 16) | (outb[10] << 8) | outb[11];
-  ret.hv[3] = (outb[12] << 24) | (outb[13] << 16) | (outb[14] << 8) | outb[15];
-  delete[] outb;
-}
-

+ 0 - 4
panda/src/express/express_composite1.cxx

@@ -24,13 +24,9 @@
 #include "memoryUsagePointerCounts.cxx"
 #include "memoryUsagePointers.cxx"
 #include "multifile.cxx"
-#include "pmutex.cxx"
 #include "mutexHolder.cxx"
 #include "mutexDummyImpl.cxx"
 #include "mutexNsprImpl.cxx"
 #include "namable.cxx"
 #include "nativeNumericData.cxx"
 #include "ordered_vector.cxx"
-#include "profileTimer.cxx"
-#include "pta_uchar.cxx"
-#include "referenceCount.cxx"

+ 6 - 0
panda/src/express/express_composite2.cxx

@@ -1,3 +1,9 @@
+#include "patchfile.cxx"
+#include "pmutex.cxx"
+#include "profileTimer.cxx"
+#include "pta_uchar.cxx"
+#include "ramfile.cxx"
+#include "referenceCount.cxx"
 #include "register_type.cxx"
 #include "reversedNumericData.cxx"
 #include "streamReader.cxx"

+ 189 - 23
panda/src/express/hashVal.I

@@ -24,7 +24,7 @@
 ////////////////////////////////////////////////////////////////////
 INLINE HashVal::
 HashVal() {
-  hv[0] = hv[1] = hv[2] = hv[3] = 0;
+  _hv[0] = _hv[1] = _hv[2] = _hv[3] = 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -34,10 +34,23 @@ HashVal() {
 ////////////////////////////////////////////////////////////////////
 INLINE HashVal::
 HashVal(const HashVal &copy) {
-  hv[0] = copy.hv[0];
-  hv[1] = copy.hv[1];
-  hv[2] = copy.hv[2];
-  hv[3] = copy.hv[3];
+  _hv[0] = copy._hv[0];
+  _hv[1] = copy._hv[1];
+  _hv[2] = copy._hv[2];
+  _hv[3] = copy._hv[3];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+operator = (const HashVal &copy) {
+  _hv[0] = copy._hv[0];
+  _hv[1] = copy._hv[1];
+  _hv[2] = copy._hv[2];
+  _hv[3] = copy._hv[3];
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -47,10 +60,10 @@ HashVal(const HashVal &copy) {
 ////////////////////////////////////////////////////////////////////
 INLINE bool HashVal::
 operator == (const HashVal &other) const {
-  return (hv[0] == other.hv[0] &&
-          hv[1] == other.hv[1] &&
-          hv[2] == other.hv[2] &&
-          hv[3] == other.hv[3]);
+  return (_hv[0] == other._hv[0] &&
+          _hv[1] == other._hv[1] &&
+          _hv[2] == other._hv[2] &&
+          _hv[3] == other._hv[3]);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -64,36 +77,189 @@ operator != (const HashVal &other) const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: HashVal::get_value
+//     Function: HashVal::operator <
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool HashVal::
+operator < (const HashVal &other) const {
+  return compare_to(other) < 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::compare_to
 //       Access: Published
-//  Description: Returns the integer value of the indicated component.
+//  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE uint HashVal::
-get_value(int val) const {
-  nassertr(val >= 0 && val < 4, 0);
-  return hv[val];
+INLINE int HashVal::
+compare_to(const HashVal &other) const {
+  if (_hv[0] != other._hv[0]) {
+    return (int)_hv[0] - (int)other._hv[0];
+  }
+  if (_hv[1] != other._hv[1]) {
+    return (int)_hv[1] - (int)other._hv[1];
+  }
+  if (_hv[2] != other._hv[2]) {
+    return (int)_hv[2] - (int)other._hv[2];
+  }
+  return (int)_hv[3] - (int)other._hv[3];
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::merge_with
+//       Access: Published
+//  Description: Generates a new HashVal representing the xor of this
+//               one and the other one.
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+merge_with(const HashVal &other) {
+  _hv[0] ^= other._hv[0];
+  _hv[1] ^= other._hv[1];
+  _hv[2] ^= other._hv[2];
+  _hv[3] ^= other._hv[3];
+}
 
 ////////////////////////////////////////////////////////////////////
-//     Function: HashVal::set_value
+//     Function: HashVal::output_dec
 //       Access: Published
-//  Description: Sets the hash value at index val
+//  Description: Outputs the HashVal as four unsigned decimal
+//               integers.
 ////////////////////////////////////////////////////////////////////
 INLINE void HashVal::
-set_value(int val, uint hashval) {
-  nassertv(val >= 0 && val < 4);
-  hv[val] = hashval;
+output_dec(ostream &out) const {
+  out << _hv[0] << " " << _hv[1] << " " << _hv[2] << " " << _hv[3];
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::input
+//       Access: Published
+//  Description: Inputs the HashVal as four unsigned decimal integers.
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+input_dec(istream &in) {
+  in >> _hv[0] >> _hv[1] >> _hv[2] >> _hv[3];
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: HashVal::output
 //       Access: Published
-//  Description: The output method does not itself output enclosing
-//               brackets, but the ostream operator << does.
+//  Description: 
 ////////////////////////////////////////////////////////////////////
 INLINE void HashVal::
 output(ostream &out) const {
-  out << hv[0] << " " << hv[1] << " " << hv[2] << " " << hv[3];
+  output_hex(out);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::write_datagram
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+write_datagram(Datagram &destination) const {
+  destination.add_uint32(_hv[0]);
+  destination.add_uint32(_hv[1]);
+  destination.add_uint32(_hv[2]);
+  destination.add_uint32(_hv[3]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::read_datagram
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+read_datagram(DatagramIterator &source) {
+  _hv[0] = source.get_uint32();
+  _hv[1] = source.get_uint32();
+  _hv[2] = source.get_uint32();
+  _hv[3] = source.get_uint32();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::write_stream
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+write_stream(StreamWriter &destination) const {
+  destination.add_uint32(_hv[0]);
+  destination.add_uint32(_hv[1]);
+  destination.add_uint32(_hv[2]);
+  destination.add_uint32(_hv[3]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::read_stream
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+read_stream(StreamReader &source) {
+  _hv[0] = source.get_uint32();
+  _hv[1] = source.get_uint32();
+  _hv[2] = source.get_uint32();
+  _hv[3] = source.get_uint32();
+}
+
+#ifdef HAVE_SSL
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::hash_ramfile
+//       Access: Published
+//  Description: Generates the hash value by hashing the indicated
+//               data.  This method is only defined if we have the
+//               OpenSSL library (which provides md5 functionality)
+//               available.
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+hash_ramfile(const Ramfile &ramfile) {
+  return hash_buffer(ramfile._data.data(), ramfile._data.length());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::hash_string
+//       Access: Published
+//  Description: Generates the hash value by hashing the indicated
+//               data.  This method is only defined if we have the
+//               OpenSSL library (which provides md5 functionality)
+//               available.
+////////////////////////////////////////////////////////////////////
+INLINE void HashVal::
+hash_string(const string &data) {
+  return hash_buffer(data.data(), data.length());
+}
+#endif  // HAVE_SSL
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::tohex
+//       Access: Private, Static
+//  Description: Converts a single nibble to a hex digit.
+////////////////////////////////////////////////////////////////////
+INLINE char HashVal::
+tohex(unsigned int nibble) {
+  nibble &= 0xf;
+  if (nibble < 10) {
+    return nibble + '0';
+  }
+  return nibble - 10 + 'a';
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::fromhex
+//       Access: Private, Static
+//  Description: Converts a single hex digit to a numerical value.
+////////////////////////////////////////////////////////////////////
+INLINE unsigned int HashVal::
+fromhex(char digit) {
+  if (isdigit(digit)) {
+    return digit - '0';
+  } else {
+    return tolower(digit) - 'a' + 10;
+  }
+}
+
+
+INLINE ostream &operator << (ostream &out, const HashVal &hv) {
+  hv.output(out);
+  return out;
 }

+ 221 - 5
panda/src/express/hashVal.cxx

@@ -16,17 +16,233 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-
 #include "hashVal.h"
+#include "ctype.h"
+#include "virtualFileSystem.h"
+
+#ifdef HAVE_SSL
+#include <openssl/md5.h>
+#endif  // HAVE_SSL
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::output_hex
+//       Access: Published
+//  Description: Outputs the HashVal as a 32-digit hexadecimal number.
+////////////////////////////////////////////////////////////////////
+void HashVal::
+output_hex(ostream &out) const {
+  char buffer[32];
+  encode_hex(_hv[0], buffer);
+  encode_hex(_hv[1], buffer + 8);
+  encode_hex(_hv[2], buffer + 16);
+  encode_hex(_hv[3], buffer + 24);
+  out.write(buffer, 32);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::input
+//       Access: Published
+//  Description: Inputs the HashVal as a 32-digit hexadecimal number.
+////////////////////////////////////////////////////////////////////
+void HashVal::
+input_hex(istream &in) {
+  in >> ws;
+  char buffer[32];
+  size_t i = 0;
+  int ch = in.get();
+
+  while (!in.eof() && !in.fail() && isxdigit(ch)) {
+    if (i < 32) {
+      buffer[i] = ch;
+    }
+    i++;
+    ch = in.get();
+  }
+
+  if (i != 32) {
+    in.setstate(ios::failbit);
+    return;
+  }
+
+  if (!in.eof()) {
+    in.unget();
+  } else {
+    in.clear();
+  }
+
+  decode_hex(buffer, _hv[0]);
+  decode_hex(buffer + 8, _hv[1]);
+  decode_hex(buffer + 16, _hv[2]);
+  decode_hex(buffer + 24, _hv[3]);
+}
 
 ////////////////////////////////////////////////////////////////////
-//     Function: HashVal::as_string
+//     Function: HashVal::as_dec
 //       Access: Published
-//  Description: Returns the HashVal as a string with four numbers.
+//  Description: Returns the HashVal as a string with four decimal
+//               numbers.
 ////////////////////////////////////////////////////////////////////
 string HashVal::
-as_string() const {
+as_dec() const {
   ostringstream strm;
-  output(strm);
+  output_dec(strm);
   return strm.str();
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::set_from_dec
+//       Access: Published
+//  Description: Sets the HashVal from a string with four decimal
+//               numbers.  Returns true if valid, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool HashVal::
+set_from_dec(const string &text) {
+  istringstream strm(text);
+  input_dec(strm);
+  return !strm.fail();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::as_hex
+//       Access: Published
+//  Description: Returns the HashVal as a 32-byte hexadecimal string.
+////////////////////////////////////////////////////////////////////
+string HashVal::
+as_hex() const {
+  char buffer[32];
+  encode_hex(_hv[0], buffer);
+  encode_hex(_hv[1], buffer + 8);
+  encode_hex(_hv[2], buffer + 16);
+  encode_hex(_hv[3], buffer + 24);
+  return string(buffer, 32);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::set_from_hex
+//       Access: Published
+//  Description: Sets the HashVal from a 32-byte hexademical string.
+//               Returns true if successful, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool HashVal::
+set_from_hex(const string &text) {
+  istringstream strm(text);
+  input_hex(strm);
+  return !strm.fail();
+}
+
+#ifdef HAVE_SSL
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::hash_file
+//       Access: Published
+//  Description: Generates the hash value from the indicated file.
+//               Returns true on success, false if the file cannot be
+//               read.  This method is only defined if we have the
+//               OpenSSL library (which provides md5 functionality)
+//               available.
+////////////////////////////////////////////////////////////////////
+bool HashVal::
+hash_file(const Filename &filename) {
+  Filename bin_filename = Filename::binary_filename(filename);
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  istream *istr = vfs->open_read_file(bin_filename);
+  if (istr == (istream *)NULL) {
+    (*this) = HashVal();
+    return false;
+  }
+
+  unsigned char md[16];
+
+  MD5_CTX ctx;
+  MD5_Init(&ctx);
+
+  static const int buffer_size = 1024;
+  char buffer[buffer_size];
+
+  istr->read(buffer, buffer_size);
+  size_t count = istr->gcount();
+  while (count != 0) {
+    MD5_Update(&ctx, buffer, count);
+    istr->read(buffer, buffer_size);
+    count = istr->gcount();
+  }
+
+  delete istr;
+  MD5_Final(md, &ctx);
+
+  // Store the individual bytes as big-endian ints, from historical
+  // convention.
+  _hv[0] = (md[0] << 24) | (md[1] << 16) | (md[2] << 8) | (md[3]);
+  _hv[1] = (md[4] << 24) | (md[5] << 16) | (md[6] << 8) | (md[7]);
+  _hv[2] = (md[8] << 24) | (md[9] << 16) | (md[10] << 8) | (md[11]);
+  _hv[3] = (md[12] << 24) | (md[13] << 16) | (md[14] << 8) | (md[15]);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::hash_buffer
+//       Access: Published
+//  Description: Generates the hash value by hashing the indicated
+//               data.  This method is only defined if we have the
+//               OpenSSL library (which provides md5 functionality)
+//               available.
+////////////////////////////////////////////////////////////////////
+void HashVal::
+hash_buffer(const char *buffer, int length) {
+  unsigned char md[16];
+  MD5((const unsigned char *)buffer, length, md);
+
+  // Store the individual bytes as big-endian ints, from historical
+  // convention.
+  _hv[0] = (md[0] << 24) | (md[1] << 16) | (md[2] << 8) | (md[3]);
+  _hv[1] = (md[4] << 24) | (md[5] << 16) | (md[6] << 8) | (md[7]);
+  _hv[2] = (md[8] << 24) | (md[9] << 16) | (md[10] << 8) | (md[11]);
+  _hv[3] = (md[12] << 24) | (md[13] << 16) | (md[14] << 8) | (md[15]);
+}
+
+#endif  // HAVE_SSL
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::encode_hex
+//       Access: Private, Static
+//  Description: Encodes the indicated unsigned int into an
+//               eight-digit hex string, stored at the indicated
+//               buffer and the following 8 positions.
+////////////////////////////////////////////////////////////////////
+void HashVal::
+encode_hex(unsigned int val, char *buffer) {
+  buffer[0] = tohex(val >> 28);
+  buffer[1] = tohex(val >> 24);
+  buffer[2] = tohex(val >> 20);
+  buffer[3] = tohex(val >> 16);
+  buffer[4] = tohex(val >> 12);
+  buffer[5] = tohex(val >> 8);
+  buffer[6] = tohex(val >> 4);
+  buffer[7] = tohex(val);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: HashVal::decode_hex
+//       Access: Private, Static
+//  Description: Decodes the indicated eight-digit hex string into an
+//               unsigned integer.
+////////////////////////////////////////////////////////////////////
+void HashVal::
+decode_hex(const char *buffer, unsigned int &val) {
+  unsigned int bytes[8];
+  for (int i = 0; i < 8; i++) {
+    bytes[i] = fromhex(buffer[i]);
+  }
+
+  val = ((bytes[0] << 28) |
+         (bytes[1] << 24) |
+         (bytes[2] << 20) |
+         (bytes[3] << 16) |
+         (bytes[4] << 12) |
+         (bytes[5] << 8) |
+         (bytes[6] << 4) |
+         (bytes[7]));
+}
+

+ 46 - 14
panda/src/express/hashVal.h

@@ -19,36 +19,68 @@
 #ifndef HASHVAL_H
 #define HASHVAL_H
 
-#include <pandabase.h>
+#include "pandabase.h"
 #include "typedef.h"
-#include <notify.h>
+#include "notify.h"
+#include "ramfile.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "streamWriter.h"
+#include "streamReader.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : HashVal
-// Description : A sixteen-byte hash value sent to the crypt library.
+// Description : Stores a 128-bit value that represents the hashed
+//               contents (typically MD5) of a file or buffer.
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDAEXPRESS HashVal {
 PUBLISHED:
   INLINE HashVal();
   INLINE HashVal(const HashVal &copy);
+  INLINE void operator = (const HashVal &copy);
 
   INLINE bool operator == (const HashVal &other) const;
   INLINE bool operator != (const HashVal &other) const;
-  INLINE uint get_value(int val) const;
-  INLINE void set_value(int val, uint hash);
+  INLINE bool operator < (const HashVal &other) const;
+  INLINE int compare_to(const HashVal &other) const;
+
+  INLINE void merge_with(const HashVal &other);
+
+  INLINE void output_dec(ostream &out) const;
+  INLINE void input_dec(istream &in);
+  void output_hex(ostream &out) const;
+  void input_hex(istream &in);
+
   INLINE void output(ostream &out) const;
-  string as_string() const;
 
-public:
-  uint hv[4];
+  string as_dec() const;
+  bool set_from_dec(const string &text);
+
+  string as_hex() const;
+  bool set_from_hex(const string &text);
+
+  INLINE void write_datagram(Datagram &destination) const;
+  INLINE void read_datagram(DatagramIterator &source);
+  INLINE void write_stream(StreamWriter &destination) const;
+  INLINE void read_stream(StreamReader &source);
+
+#ifdef HAVE_SSL
+  bool hash_file(const Filename &filename);
+  INLINE void hash_ramfile(const Ramfile &ramfile);
+  INLINE void hash_string(const string &data);
+  void hash_buffer(const char *buffer, int length);
+#endif  // HAVE_SSL
+
+private:
+  static void encode_hex(unsigned int val, char *buffer);
+  static void decode_hex(const char *buffer, unsigned int &val);
+  INLINE static char tohex(unsigned int nibble);
+  INLINE static unsigned int fromhex(char digit);
+
+  PN_uint32 _hv[4];
 };
 
-INLINE ostream &operator << (ostream &out, const HashVal &hv) {
-  out << "[";
-  hv.output(out);
-  out << "]";
-  return out;
-}
+INLINE ostream &operator << (ostream &out, const HashVal &hv);
 
 #include "hashVal.I"
 

+ 20 - 25
panda/src/express/patchfile.cxx

@@ -16,15 +16,22 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#include "pandabase.h"
+
+#ifdef HAVE_SSL
+
 #include "config_express.h"
 #include "error_utils.h"
 #include "patchfile.h"
-#include "crypto_utils.h" // MD5 stuff
 #include "streamReader.h"
 #include "streamWriter.h"
 
 #include <stdio.h> // for tempnam
 
+#ifdef WIN32_VC
+#define tempnam _tempnam
+#endif
+
 // PROFILING ///////////////////////////////////////////////////////
 //#define PROFILE_PATCH_BUILD
 #ifdef PROFILE_PATCH_BUILD
@@ -200,7 +207,7 @@ initiate(const Filename &patch_file, const Filename &file) {
 
   // Open the temp file for write
   {
-    char *tempfilename = _tempnam(".", "pf");
+    char *tempfilename = tempnam(".", "pf");
     if (NULL == tempfilename) {
       express_cat.error()
         << "Patchfile::initiate() - Failed to create temp file name, using default" << endl;
@@ -356,7 +363,7 @@ run(void) {
       // check the MD5 from the patch file against the newly patched file
       {
         HashVal MD5_actual;
-        md5_a_file(_temp_file, MD5_actual);
+        MD5_actual.hash_file(_temp_file);
         if (_MD5_ofResult != MD5_actual) {
           // Whoops, patching screwed up somehow.
           _origfile_stream.close();
@@ -376,7 +383,7 @@ run(void) {
               << "No source hash in patch file to verify.\n";
           } else {
             HashVal MD5_orig;
-            md5_a_file(_orig_file, MD5_orig);
+            MD5_orig.hash_file(_orig_file);
             if (MD5_orig != get_source_hash()) {
               express_cat.info()
                 << "Started from incorrect source file.  Got:\n"
@@ -496,20 +503,14 @@ internal_read_header(const Filename &patch_file) {
     _source_file_length = patch_reader.get_uint32();
     
     // get the MD5 of the source file.
-    _MD5_ofSource.set_value(0, patch_reader.get_uint32());
-    _MD5_ofSource.set_value(1, patch_reader.get_uint32());
-    _MD5_ofSource.set_value(2, patch_reader.get_uint32());
-    _MD5_ofSource.set_value(3, patch_reader.get_uint32());
+    _MD5_ofSource.read_stream(patch_reader);
   }
 
   // get the length of the patched result file
   _result_file_length = patch_reader.get_uint32();
 
   // get the MD5 of the resultant patched file
-  _MD5_ofResult.set_value(0, patch_reader.get_uint32());
-  _MD5_ofResult.set_value(1, patch_reader.get_uint32());
-  _MD5_ofResult.set_value(2, patch_reader.get_uint32());
-  _MD5_ofResult.set_value(3, patch_reader.get_uint32());
+  _MD5_ofResult.read_stream(patch_reader);
 
   express_cat.debug()
     << "Patchfile::initiate() - valid patchfile" << endl;
@@ -530,8 +531,7 @@ PN_uint16 Patchfile::
 calc_hash(const char *buffer) {
 #ifdef USE_MD5_FOR_HASHTABLE_INDEX_VALUES
   HashVal hash;
-
-  md5_a_buffer((const unsigned char*)buffer, (int)_footprint_length, hash);
+  hash.hash_buffer(buffer, _footprint_length);
 
   //cout << PN_uint16(hash.get_value(0)) << " ";
 
@@ -869,22 +869,16 @@ build(Filename file_orig, Filename file_new, Filename patch_name) {
   patch_writer.add_uint32(_source_file_length);
   {
     // calc MD5 of original file
-    md5_a_buffer((const unsigned char*)buffer_orig, (int)_source_file_length, _MD5_ofSource);
+    _MD5_ofSource.hash_buffer(buffer_orig, _source_file_length);
     // add it to the header
-    patch_writer.add_uint32(_MD5_ofSource.get_value(0));
-    patch_writer.add_uint32(_MD5_ofSource.get_value(1));
-    patch_writer.add_uint32(_MD5_ofSource.get_value(2));
-    patch_writer.add_uint32(_MD5_ofSource.get_value(3));
+    _MD5_ofSource.write_stream(patch_writer);
   }
   patch_writer.add_uint32(_result_file_length);
   {
     // calc MD5 of resultant patched file
-    md5_a_buffer((const unsigned char*)buffer_new, (int)_result_file_length, _MD5_ofResult);
+    _MD5_ofResult.hash_buffer(buffer_new, _result_file_length);
     // add it to the header
-    patch_writer.add_uint32(_MD5_ofResult.get_value(0));
-    patch_writer.add_uint32(_MD5_ofResult.get_value(1));
-    patch_writer.add_uint32(_MD5_ofResult.get_value(2));
-    patch_writer.add_uint32(_MD5_ofResult.get_value(3));
+    _MD5_ofResult.write_stream(patch_writer);
   }
 
   END_PROFILE(writeHeader, "writing patch file header");
@@ -941,7 +935,7 @@ build(Filename file_orig, Filename file_new, Filename patch_name) {
   }
 
   // are there still more bytes left in the new file?
-  if ((int)ADD_pos != _result_file_length) {
+  if (ADD_pos != _result_file_length) {
     // emit ADD for all remaining bytes
     emit_ADD(write_stream, _result_file_length - ADD_pos, &buffer_new[ADD_pos],
              ADD_pos);
@@ -961,3 +955,4 @@ build(Filename file_orig, Filename file_new, Filename patch_name) {
   return true;
 }
 
+#endif // HAVE_SSL

+ 8 - 7
panda/src/express/patchfile.h

@@ -17,15 +17,14 @@
 ////////////////////////////////////////////////////////////////////
 #ifndef PATCHFILE_H
 #define PATCHFILE_H
-//
-////////////////////////////////////////////////////////////////////
-// Includes
-////////////////////////////////////////////////////////////////////
-#include <pandabase.h>
+
+#include "pandabase.h"
+
+#ifdef HAVE_SSL
 
 #include "typedef.h"
-#include <notify.h>
-#include <filename.h>
+#include "notify.h"
+#include "filename.h"
 #include "plist.h"
 #include "datagram.h"
 #include "datagramIterator.h"
@@ -133,4 +132,6 @@ protected:
 
 #include "patchfile.I"
 
+#endif // HAVE_SSL
+
 #endif

+ 11 - 20
panda/src/express/crypto_utils.h → panda/src/express/ramfile.I

@@ -1,5 +1,5 @@
-// Filename: crypto_utils.h
-// Created by:  drose (07Nov00)
+// Filename: ramfile.I
+// Created by:  mike (09Jan97)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -16,21 +16,12 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#ifndef CRYPTO_UTILS_H
-#define CRYPTO_UTILS_H
-
-#include <pandabase.h>
-#include <filename.h>
-#include "typedef.h"
-
-class HashVal;
-
-BEGIN_PUBLISH
-
-EXPCL_PANDAEXPRESS void md5_a_file(const Filename &fname, HashVal &ret);
-EXPCL_PANDAEXPRESS void md5_a_buffer(const uchar *buf, ulong len, HashVal &ret);
-
-END_PUBLISH
-
-#endif
-
+////////////////////////////////////////////////////////////////////
+//     Function: Ramfile::constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE Ramfile::
+Ramfile() {
+  _pos = 0;
+}

+ 47 - 0
panda/src/express/ramfile.cxx

@@ -0,0 +1,47 @@
+// Filename: ramfile.cxx
+// Created by:  mike (09Jan97)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "ramfile.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: Ramfile::readline
+//       Access: Published
+//  Description: Assumes the stream represents a text file, and
+//               extracts one line up to and including the trailing
+//               newline character.  Returns empty string when the end
+//               of file is reached.
+//
+//               The interface here is intentionally designed to be
+//               similar to that for Python's File.readline()
+//               function.
+////////////////////////////////////////////////////////////////////
+string Ramfile::
+readline() {
+  size_t start = _pos;
+  while (_pos < _data.length() && _data[_pos] != '\n') {
+    ++_pos;
+  }
+
+  if (_pos < _data.length() && _data[_pos] == '\n') {
+    // Include the newline character also.
+    ++_pos;
+  }
+
+  return _data.substr(start, _pos - start);
+}
+

+ 44 - 0
panda/src/express/ramfile.h

@@ -0,0 +1,44 @@
+// Filename: ramfile.h
+// Created by:  mike (09Jan97)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef RAMFILE_H
+#define RAMFILE_H
+
+#include "pandabase.h"
+#include "typedef.h"
+#include "referenceCount.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : Ramfile
+// Description : An in-memory buffer specifically designed for
+//               downloading files to memory.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS Ramfile {
+PUBLISHED:
+  INLINE Ramfile();
+
+  string readline();
+
+public:
+  size_t _pos;
+  string _data;
+};
+
+#include "ramfile.I"
+
+#endif