panda3d/panda/src/downloader/httpClient.cxx
2002-09-27 15:27:28 +00:00

424 lines
13 KiB
C++

// Filename: httpClient.cxx
// Created by: drose (24Sep02)
//
////////////////////////////////////////////////////////////////////
//
// 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
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////
#include "httpClient.h"
#include "config_downloader.h"
#include "filename.h"
#include "config_express.h"
#include "virtualFileSystem.h"
#ifdef HAVE_SSL
#ifndef NDEBUG
#include <openssl/err.h>
#endif
bool HTTPClient::_ssl_initialized = false;
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::load_certificates
// Access: Published
// Description: Reads the certificate(s) (delimited by -----BEGIN
// CERTIFICATE----- and -----END CERTIFICATE-----) from
// the indicated file and makes them known as trusted
// public keys for validating future connections.
// Returns true on success, false otherwise.
////////////////////////////////////////////////////////////////////
bool HTTPClient::
load_certificates(const Filename &filename) {
int result;
if (use_vfs) {
result = load_verify_locations(_ssl_ctx, filename);
} else {
string os_specific = filename.to_os_specific();
result =
SSL_CTX_load_verify_locations(_ssl_ctx, os_specific.c_str(), NULL);
}
if (result <= 0) {
downloader_cat.info()
<< "Could not load certificates from " << filename << ".\n";
#ifndef NDEBUG
ERR_print_errors_fp(stderr);
#endif
return false;
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_document
// Access: Published
// Description: Opens the named document for reading, or if body is
// nonempty, posts data for a particular URL and
// retrieves the response. Returns a new HTTPDocument
// object whether the document is successfully read or
// not; you can test is_valid() and get_return_code() to
// determine whether the document was retrieved.
////////////////////////////////////////////////////////////////////
PT(HTTPDocument) HTTPClient::
get_document(const URLSpec &url, const string &body) {
BIO *bio;
if (_proxy.empty()) {
if (url.get_scheme() == "https") {
bio = get_https(url, body);
} else {
bio = get_http(url, body);
}
} else {
if (url.get_scheme() == "https") {
bio = get_https_proxy(url, body);
} else {
bio = get_http_proxy(url, body);
}
}
return new HTTPDocument(bio, true);
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::make_ctx
// Access: Private
// Description: Creates the OpenSSL context object.
////////////////////////////////////////////////////////////////////
void HTTPClient::
make_ctx() {
if (!_ssl_initialized) {
initialize_ssl();
}
_ssl_ctx = SSL_CTX_new(SSLv23_client_method());
// Load in any default certificates listed in the Configrc file.
Config::ConfigTable::Symbol cert_files;
config_express.GetAll("ssl-certificates", cert_files);
// When we use GetAll(), we might inadvertently read duplicate
// lines. Filter them out with a set.
pset<string> already_read;
Config::ConfigTable::Symbol::iterator si;
for (si = cert_files.begin(); si != cert_files.end(); ++si) {
string cert_file = (*si).Val();
if (already_read.insert(cert_file).second) {
Filename filename = Filename::from_os_specific(cert_file);
if (load_certificates(filename)) {
downloader_cat.info()
<< "Appending SSL certificates from " << cert_file << "\n";
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::initialize_ssl
// Access: Private, Static
// Description: Called once the first time this class is used to
// initialize the OpenSSL library.
////////////////////////////////////////////////////////////////////
void HTTPClient::
initialize_ssl() {
#ifndef NDEBUG
ERR_load_crypto_strings();
ERR_load_SSL_strings();
#endif
OpenSSL_add_all_algorithms();
_ssl_initialized = true;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::load_verify_locations
// Access: Private, Static
// Description: An implementation of the OpenSSL-provided
// SSL_CTX_load_verify_locations() that takes a Filename
// (and supports Panda vfs).
//
// This reads the certificates from the named ca_file
// and makes them available to the given SSL context.
// It returns a positive number on success, or <= 0 on
// failure.
////////////////////////////////////////////////////////////////////
int HTTPClient::
load_verify_locations(SSL_CTX *ctx, const Filename &ca_file) {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
// First, read the complete file into memory.
string data;
if (!vfs->read_file(ca_file, data)) {
// Could not find or read file.
downloader_cat.info()
<< "Could not read " << ca_file << ".\n";
return 0;
}
STACK_OF(X509_INFO) *inf;
// Now create an in-memory BIO to read the "file" from the buffer we
// just read, and call the low-level routines to read the
// certificates from the BIO.
BIO *mbio = BIO_new_mem_buf((void *)data.data(), data.length());
inf = PEM_X509_INFO_read_bio(mbio, NULL, NULL, NULL);
BIO_free(mbio);
if (!inf) {
// Could not scan certificates.
return 0;
}
// Now add the certificates to the context.
X509_STORE *store = ctx->cert_store;
int count = 0;
for (int i = 0; i < sk_X509_INFO_num(inf); i++) {
X509_INFO *itmp = sk_X509_INFO_value(inf, i);
if (itmp->x509) {
X509_STORE_add_cert(store, itmp->x509);
count++;
} else if (itmp->crl) {
X509_STORE_add_crl(store, itmp->crl);
count++;
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
return count;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_https
// Access: Private
// Description: Opens the indicated URL directly as an ordinary http
// document.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
get_http(const URLSpec &url, const string &body) {
ostringstream server;
server << url.get_server() << ":" << url.get_port();
string server_str = server.str();
BIO *bio = BIO_new_connect((char *)server_str.c_str());
if (BIO_do_connect(bio) <= 0) {
downloader_cat.info()
<< "Could not contact server " << server_str << "\n";
#ifndef NDEBUG
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
send_get_request(bio, url.get_path(), url.get_server(), body);
return bio;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_https
// Access: Private
// Description: Opens the indicated URL directly as an https
// document.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
get_https(const URLSpec &url, const string &body) {
BIO *sbio = BIO_new_ssl_connect(_ssl_ctx);
SSL *ssl;
BIO_get_ssl(sbio, &ssl);
nassertr(ssl != (SSL *)NULL, NULL);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
ostringstream server;
server << url.get_server() << ":" << url.get_port();
string server_str = server.str();
BIO_set_conn_hostname(sbio, server_str.c_str());
if (BIO_do_connect(sbio) <= 0) {
downloader_cat.info()
<< "Could not contact server " << server_str << "\n";
#ifndef NDEBUG
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
if (BIO_do_handshake(sbio) <= 0) {
downloader_cat.info()
<< "Could not establish SSL handshake with " << server_str << "\n";
#ifndef NDEBUG
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
send_get_request(sbio, url.get_path(), url.get_server(), body);
return sbio;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_http_proxy
// Access: Private
// Description: Opens the indicated URL via the proxy as an ordinary
// http document.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
get_http_proxy(const URLSpec &url, const string &body) {
ostringstream proxy_server;
proxy_server << _proxy.get_server() << ":" << _proxy.get_port();
string proxy_server_str = proxy_server.str();
BIO *bio = BIO_new_connect((char *)proxy_server_str.c_str());
if (BIO_do_connect(bio) <= 0) {
downloader_cat.info()
<< "Could not contact proxy " << proxy_server_str << "\n";
#ifndef NDEBUG
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
send_get_request(bio, url, url.get_server(), body);
return bio;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::get_https_proxy
// Access: Private
// Description: Opens the indicated URL via the proxy as an https
// document.
////////////////////////////////////////////////////////////////////
BIO *HTTPClient::
get_https_proxy(const URLSpec &url, const string &body) {
// First, ask the proxy to open a connection for us.
ostringstream proxy_server;
proxy_server << _proxy.get_server() << ":" << _proxy.get_port();
string proxy_server_str = proxy_server.str();
BIO *bio = BIO_new_connect((char *)proxy_server_str.c_str());
if (BIO_do_connect(bio) <= 0) {
downloader_cat.info()
<< "Could not contact proxy " << proxy_server_str << "\n";
#ifndef NDEBUG
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
{
ostringstream request;
request
<< "CONNECT " << url.get_authority() << " HTTP/1.1\r\n"
<< "\r\n";
string request_str = request.str();
if (downloader_cat.is_debug()) {
downloader_cat.debug() << "send:\n" << request_str << "\n";
}
BIO_puts(bio, request_str.c_str());
}
// Create a temporary HTTPDocument to read the response from the
// proxy.
{
PT(HTTPDocument) doc = new HTTPDocument(bio, false);
if (!doc->is_valid()) {
downloader_cat.info()
<< "proxy would not open connection to " << url.get_authority()
<< ": " << doc->get_status_code() << " "
<< doc->get_status_string() << "\n";
// If the proxy refused to open a raw connection for us, see if
// it will handle the https communication directly. For other
// error codes, just return error.
if ((doc->get_status_code() / 100) == 4) {
BIO_free_all(bio);
return get_http_proxy(url, body);
}
return NULL;
}
}
// Ok, we now have a connection to our actual server, so start
// speaking SSL and then ask for the document we really want.
BIO *sbio = BIO_new_ssl(_ssl_ctx, true);
BIO_push(sbio, bio);
SSL *ssl;
BIO_get_ssl(sbio, &ssl);
nassertr(ssl != (SSL *)NULL, NULL);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
if (BIO_do_handshake(sbio) <= 0) {
downloader_cat.info()
<< "Could not establish SSL handshake with "
<< url.get_server() << ":" << url.get_port() << "\n";
#ifndef NDEBUG
ERR_print_errors_fp(stderr);
#endif
return NULL;
}
send_get_request(sbio, url.get_path(), url.get_server(), body);
return sbio;
}
////////////////////////////////////////////////////////////////////
// Function: HTTPClient::send_get_request
// Access: Private
// Description: Sends the appropriate GET or POST request to the
// server on the indicated connection.
////////////////////////////////////////////////////////////////////
void HTTPClient::
send_get_request(BIO *bio,
const string &path, const string &server,
const string &body) const {
ostringstream request;
if (body.empty()) {
request
<< "GET " << path << " HTTP/1.1\r\n"
<< "Host: " << server << "\r\n"
<< "Connection: close\r\n"
<< "\r\n";
} else {
request
<< "POST " << path << " HTTP/1.1\r\n"
<< "Host: " << server << "\r\n"
<< "Connection: close\r\n"
<< "Content-type: application/x-www-form-urlencoded\r\n"
<< "Content-Length: " << body.length() << "\r\n"
<< "\r\n"
<< body;
}
string request_str = request.str();
if (downloader_cat.is_debug()) {
downloader_cat.debug() << "send:\n" << request_str << "\n";
}
BIO_puts(bio, request_str.c_str());
}
#endif // HAVE_SSL