Subversion Repositories tpanel

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2021 by Andreas Theofilu <andreas@theosys.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

#include <vector>

#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <openssl/err.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <poll.h>

#include "thttpclient.h"
#include "base64.h"
#include "terror.h"
#include "tresources.h"
#include "tconfig.h"

#define HTTP_DIRECTION_RECEIVE  1
#define HTTP_DIRECTION_SEND     2

using std::string;
using std::vector;
using std::min;

int _verify_callback(int preverify_ok, X509_STORE_CTX *ctx);

THTTPClient::THTTPClient()
{
    DECL_TRACER("THTTPClient::THTTPClient()");
    mBody.body = nullptr;
    mBody.len = 0;
    mRequest.code = 0;
    mRequest.direction = 0;
    mRequest.method = UNSUPPORTED;
    mRequest.path = nullptr;
    mRequest.status = nullptr;
    mRequest.version = nullptr;
}

THTTPClient::~THTTPClient()
{
    DECL_TRACER("THTTPClient::~THTTPClient()");
    mBody.clear();
    mRequest.clear();
}

#define MAX_BUFFER  65535
#define MAX_BLOCK   32767

char *THTTPClient::tcall(size_t *size, const string& URL, const string& user, const string& pw)
{
    DECL_TRACER("THTTPClient::tcall(size_t size, const string& URL, const string& user, const string& pw)");

    char *buffer = nullptr;
    size_t bufsize = 0;
    mUser = user;
    mPassword = pw;

    try
    {
        buffer = new char[MAX_BUFFER];
        bufsize = MAX_BUFFER;
    }
    catch (std::exception& e)
    {
        MSG_ERROR(e.what());
        return nullptr;
    }

    string request = makeRequest(URL);

    if (TError::isError())
    {
        delete[] buffer;
        return nullptr;
    }

    int fd;

    if ((fd = socket_connect()) == -1)
    {
        delete[] buffer;
        return nullptr;
    }

    int ret = 0;

    if ((ret = sockWrite(fd, (char *)request.c_str(), request.length())) < 0)
    {
        if (errno)
        {
            MSG_ERROR("Write error: " << strerror(errno));
        }
        else if (mURL.scheme.compare("https") == 0)
        {
            int err = SSL_get_error(mSsl, ret);

            switch (err)
            {
                case SSL_ERROR_ZERO_RETURN:     MSG_ERROR("The TLS/SSL peer has closed the connection for writing by sending the close_notify alert."); break;
                case SSL_ERROR_WANT_READ:
                case SSL_ERROR_WANT_WRITE:      MSG_ERROR("The operation did not complete and can be retried later."); break;
                case SSL_ERROR_WANT_CONNECT:
                case SSL_ERROR_WANT_ACCEPT:     MSG_ERROR("The operation did not complete; the same TLS/SSL I/O function should be called again later."); break;
                case SSL_ERROR_WANT_X509_LOOKUP:MSG_ERROR("The operation did not complete because an application callback set by SSL_CTX_set_client_cert_cb() has asked to be called again."); break;
                case SSL_ERROR_WANT_ASYNC:      MSG_ERROR("The operation did not complete because an asynchronous engine is still processing data."); break;
                case SSL_ERROR_WANT_ASYNC_JOB:  MSG_ERROR("The asynchronous job could not be started because there were no async jobs available in the pool."); break;
                case SSL_ERROR_WANT_CLIENT_HELLO_CB: MSG_ERROR("The operation did not complete because an application callback set by SSL_CTX_set_client_hello_cb() has asked to be called again."); break;
                case SSL_ERROR_SYSCALL:         MSG_ERROR("Some non-recoverable, fatal I/O error occurred."); break;
                case SSL_ERROR_SSL:             MSG_ERROR("A non-recoverable, fatal error in the SSL library occurred, usually a protocol error."); break;

                default:
                    MSG_ERROR("Unknown error " << err << " occured!");
            }
        }
        else
        {
            MSG_ERROR("Write error!");
        }

        shutdown(fd, SHUT_RDWR);
        close(fd);
        delete[] buffer;

        if (mURL.scheme.compare("https") == 0)
        {
            SSL_CTX_free(mCtx);
            mCtx = nullptr;
        }

        return nullptr;
    }

    char buf[1024];
    memset(buf, 0, sizeof(buf));
    size_t pos = 0, length = 0;
    struct pollfd pfd;
    int rlen, nfds = 1;

    pfd.fd = fd;
    pfd.events = POLLIN;

    try
    {
        if (poll(&pfd, nfds, 10000) != -1)  // FIXME: Make the timeout configurable.
        {
            while ((rlen = sockRead(fd, buf, sizeof(buf))) > 0)
            {
                long len = rlen;

                if ((pos + len) >= bufsize)
                {
                    buffer = (char *)renew(buffer, bufsize, bufsize + MAX_BLOCK);
                    bufsize += MAX_BLOCK;
                }

                if (len > 0)
                {
                    memcpy(buffer+pos, buf, len);
                    pos += len;
                    length += len;
                }

                memset(buf, 0, sizeof(buf));
            }
        }
    }
    catch (std::exception& e)
    {
        MSG_ERROR(e.what());

        if (buffer)
            delete[] buffer;

        if (mURL.scheme.compare("https") == 0)
        {
            SSL_free(mSsl);
            mSsl = nullptr;
        }

        shutdown(fd, SHUT_RDWR);
        close(fd);

        if (mURL.scheme.compare("https") == 0)
        {
            SSL_CTX_free(mCtx);
            mCtx = nullptr;
        }

        return nullptr;
    }

    int myerrno = errno;

    if (mURL.scheme.compare("https") == 0)
    {
        SSL_free(mSsl);
        mSsl = nullptr;
    }

    shutdown(fd, SHUT_RDWR);
    close(fd);

    if (mURL.scheme.compare("https") == 0)
    {
        SSL_CTX_free(mCtx);
        mCtx = nullptr;
    }

    if (pos == 0)
    {
        if (myerrno)
        {
            MSG_ERROR("Internal read error: " << strerror(myerrno));
        }
        else
        {
            MSG_ERROR("Internal read error: Received no data.");
        }

        if (buffer)
            delete[] buffer;

        return nullptr;
    }

    MSG_DEBUG("Read " << length << " bytes.");

    if (parseHeader(buffer, length) != 0)
    {
        if (buffer)
            delete[] buffer;

        return nullptr;
    }

    if (mRequest.code >= 300)
    {
        if (mRequest.status && *mRequest.status)
        {
            MSG_ERROR(mRequest.code << ": " << mRequest.status);
        }
        else
        {
            MSG_ERROR(mRequest.code << ": UNKNOWN");
        }

        return nullptr;
    }

    delete[] buffer;
    *size = mBody.len;
    return mBody.body;
}

string THTTPClient::makeURL(const string& scheme, const string& host, int port, const string& path)
{
    DECL_TRACER("THTTPClient::makeURL(const string& scheme, const string& host, int port, const string& path)");

    string url = scheme + "://" + host;

    if (port > 0)
        url += std::to_string(port);

    url += "/" + path;
    MSG_DEBUG("URL: " << url);
    return url;
}

int THTTPClient::input_timeout (int filedes, unsigned int seconds)
{
    DECL_TRACER("THTTPClient::input_timeout (int filedes, unsigned int seconds)");

    fd_set set;
    struct timeval timeout;


    /* Initialize the file descriptor set. */
    FD_ZERO (&set);
    FD_SET (filedes, &set);

    /* Initialize the timeout data structure. */
    timeout.tv_sec = seconds;
    timeout.tv_usec = 0;

    /* select returns 0 if timeout, 1 if input available, -1 if error. */
    return TEMP_FAILURE_RETRY (select (FD_SETSIZE, &set, NULL, NULL, &timeout));
}

int THTTPClient::socket_connect()
{
    struct addrinfo *ainfo = nullptr;
    int sock = -1;
    int on = 1;
    bool retry = true;

    MSG_DEBUG("Trying to connect to host " << mURL.host << " at port " << mURL.port);

    if ((ainfo = lookup_host(mURL.host, mURL.port)) == nullptr)
        return -1;

    while (ainfo && retry)
    {
        sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);

        if (sock == -1)
        {
            MSG_ERROR("Error opening socket: " << strerror(errno));
            ainfo = ainfo->ai_next;
            continue;
        }

        struct timeval tv;

        // FIXME: Make the timeouts configurable!
        tv.tv_sec = 10;
        tv.tv_usec = 0;

        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(int)) == -1)
        {
            MSG_ERROR("Error setting socket options for address reuse: " << strerror(errno));
            close(sock);
            return -1;
        }
#ifndef __SVR4
        // SO_RCVTIMEO is not supported on Solaris10!
        if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(struct timeval)) == -1)
        {
            MSG_ERROR("Error setting socket options for receive: " << strerror(errno));
            close(sock);
            return -1;
        }
#endif

        if (connect(sock, ainfo->ai_addr, ainfo->ai_addrlen) == -1)
        {
            MSG_ERROR("Connect error: " << strerror(errno));
            close(sock);
            retry = false;
        }
        else
        {
            retry = false;
            break;
        }
    }

    if (ainfo == nullptr)
        return -1;

    if (mURL.scheme.compare("https") == 0)
    {
        int ret;

        MSG_DEBUG("Initializing SSL connection ...");
        mCtx = initCTX();

        if (mCtx == NULL)
        {
            MSG_ERROR("Error initializing CTX.");
            close(sock);
            return -1;
        }

        mSsl = SSL_new(mCtx);      /* create new SSL connection state */

        if (mSsl == NULL)
        {
            log_ssl_error();
            SSL_CTX_free(mCtx);
            close(sock);
            return -1;
        }

        SSL_set_fd(mSsl, sock);    /* attach the socket descriptor */

        if (TConfig::certCheck())
        {
            SSL_set_verify(mSsl, SSL_VERIFY_PEER, _verify_callback);
            MSG_TRACE("Verify on peer certificate was set.");
        }

        while ((ret = SSL_connect(mSsl)) < 0)
        {
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(sock, &fds);

            switch (SSL_get_error(mSsl, ret))
            {
                case SSL_ERROR_WANT_READ:
                    select(sock + 1, &fds, NULL, NULL, NULL);
                    break;

                case SSL_ERROR_WANT_WRITE:
                    select(sock + 1, NULL, &fds, NULL, NULL);
                    break;

                default:
                    MSG_ERROR("Error getting a new SSL handle.");
                    SSL_CTX_free(mCtx);
                    close(sock);
                    SSL_free(mSsl);
                    return -1;
            }
        }

        if (TConfig::certCheck())
        {
            if (SSL_get_peer_certificate(mSsl))
            {
                MSG_DEBUG("Result of peer certificate verification is checked ...");

                if ((ret = SSL_get_verify_result(mSsl)) != X509_V_OK)
                {
                    MSG_ERROR("Error verifiying peer.");
                    SSL_CTX_free(mCtx);
                    close(sock);
                    SSL_free(mSsl);
                    return -1;
                }

                MSG_TRACE("Certificate was valid.");
            }
            else
                MSG_WARNING("Peer offered no or invalid certificate!");
        }
    }

    return sock;
}

struct addrinfo *THTTPClient::lookup_host (const string& host, int port)
{
    DECL_TRACER("THTTPClient::lookup_host (const string& host, int port)");

    struct addrinfo *res;
    struct addrinfo hints;
    char sport[16];

    memset (&hints, 0, sizeof (hints));
    hints.ai_family = AF_INET;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_CANONNAME | AI_ALL | AI_ADDRCONFIG;
    snprintf(sport, sizeof(sport), "%d", port);

    if (getaddrinfo (host.c_str(), sport, &hints, &res) != 0)
    {
        MSG_ERROR("Getaddrinfo: " << strerror(errno));
        return nullptr;
    }

    return res;
}

int THTTPClient::sockWrite(int fd, char *buffer, size_t len)
{
    DECL_TRACER("THTTPClient::sockWrite(int fd, char *buffer, size_t len)");

    int proto = 0;

    if (buffer == nullptr || (mURL.scheme.compare("https") == 0 && mSsl == nullptr))
        return -1;

    if (mURL.scheme.compare("http") == 0)
        proto = 0;
    else if (mURL.scheme.compare("https") == 0)
        proto = 1;
    else
    {
        MSG_WARNING("Not supported protocol " << mURL.scheme << "!");
        proto = -1;
    }

    switch (proto)
    {
        case 0: return write(fd, buffer, len);
        case 1: return SSL_write(mSsl, buffer, len);
    }

    return -1;
}

int THTTPClient::sockRead(int fd, char *buffer, size_t len)
{
    DECL_TRACER("THTTPClient::sockRead(int fd, char *buffer, size_t len)");

    int proto = 0;

    if (buffer == NULL || (mURL.scheme.compare("https") == 0 && mSsl == NULL))
        return -1;

    if (mURL.scheme.compare("http") == 0)
        proto = 0;
    else if (mURL.scheme.compare("https") == 0)
        proto = 1;

    switch (proto)
    {
        case 0: return read(fd, buffer, len);
        case 1: return SSL_read(mSsl, buffer, len);
    }

    return -1;
}

URL_t& THTTPClient::parseURL(const string& URL)
{
    DECL_TRACER("THTTPClient::parseURL(const string& URL, const string& user, const string& pw)");

    if (URL.empty())
    {
        MSG_ERROR("Invalid empty URL!");
        TError::setError();
        mURL.clear();
        return mURL;
    }

    size_t pos = URL.find("://");

    if (pos == string::npos)
    {
        MSG_ERROR("Invalid URL: " << URL);
        TError::setError();
        mURL.clear();
        return mURL;
    }

    mURL.scheme = URL.substr(0, pos);
    pos += 3;
    string part = URL.substr(pos);
    pos = part.find("/");
    string host;

    if (pos == string::npos)
    {
        host = part;
        part.clear();
    }
    else
    {
        host = part.substr(0, pos);
        part = part.substr(pos + 1);
    }

    pos = host.find(":");

    if (pos != string::npos)
    {
        string sport = host.substr(pos + 1);
        mURL.host = host.substr(0, pos);
        mURL.port = atoi(sport.c_str());
    }
    else
        mURL.host = host;

    if (!part.empty())
        mURL.path = part;

    pos = host.find("@");

    if (pos != string::npos)
    {
        mURL.user = host.substr(0, pos);
        mURL.host = host.substr(pos + 1);
    }

    if (mURL.port == 0)
    {
        if (mURL.scheme.compare("https") == 0)
            mURL.port = 443;
        else
            mURL.port = 80;
    }

    MSG_DEBUG("URL components: Scheme: " << mURL.scheme << ", Host: " << mURL.host << ", Port: " << mURL.port << ", Path: " << mURL.path << ", User: " << mURL.user << ", Password: " << ((mURL.pw.empty()) ? "" : "****"));
    return mURL;
}

int THTTPClient::parseHeader(const char *buffer, size_t len)
{
    DECL_TRACER("THTTPClient::parseHeader(const char *buffer, size_t len)");

    if (buffer == nullptr || len == 0)
    {
        MSG_DEBUG("Empty receive buffer!");
        return -1;
    }

    int blen = 0, receive = 0, code = 0;
    char *raw = (char *)buffer;
    char *path, *version, *status;
    int direction = HTTP_DIRECTION_SEND;
    METHOD_t method;
    mHeader.clear();
    mBody.clear();
    mRequest.clear();

    // Method
    size_t meth_len = strcspn(raw, " ");

    if (meth_len >= len)
    {
        MSG_ERROR("Buffer contains no valid HTTP response!");
        return -1;
    }

    if (memcmp(raw, "GET", 3) == 0)
        method = GET;
    else if (memcmp(raw, "PUT", 3) == 0)
        method = PUT;
    else if (memcmp(raw, "POST", 4) == 0)
        method = POST;
    else if (memcmp(raw, "HEAD", 4) == 0)
        method = HEAD;
    else
    {
        method = UNSUPPORTED;
        direction = HTTP_DIRECTION_RECEIVE;
        receive = 1;
        MSG_DEBUG("Detected a receive buffer");
    }

    mRequest.method = method;
    mRequest.direction = direction;
    raw += meth_len + 1; // move past <SP>
    status = path = version = nullptr;

    if (!receive)
    {
        // Request-URI
        size_t path_len = strcspn(raw, " ");

        try
        {
            path = new char[path_len+1];
            memcpy(path, raw, path_len);
            path[path_len] = '\0';
            raw += path_len + 1; // move past <SP>

            // HTTP-Version
            size_t ver_len = strcspn(raw, "\r\n");
            version = new char[ver_len + 1];
            memcpy(version, raw, ver_len);
            version[ver_len] = '\0';
            raw += ver_len + 2; // move past <CR><LF>
            mRequest.path = path;
            mRequest.version = version;
        }
        catch (std::exception& e)
        {
            MSG_ERROR("Error allocating memory: " << e.what());

            if (path)
                delete[] path;

            if (version)
                delete[] version;

            mRequest.path = nullptr;
            mRequest.version = nullptr;
            return -1;
        }
    }
    else
    {
        char scode[16];
        memset(scode, 0, sizeof(scode));
        size_t code_len = strcspn(raw, " ");
        strncpy(scode, raw, min(code_len, sizeof(scode)));
        scode[sizeof(scode)-1] = 0;
        code = atoi(scode);

        MSG_DEBUG("Received code " << code);

        if (strstr(buffer, "\r\n\r\n") == NULL)
        {
            MSG_ERROR("Received no content!");
            return -1;
        }

        if (code_len >= len)
        {
            MSG_ERROR("Buffer contains no valid HTTP response!");
            return -1;
        }

        raw += code_len + 1;
        size_t stat_len = strcspn(raw, "\r\n");

        try
        {
            status = new char[stat_len + 1];
            memcpy(status, raw, stat_len);
            status[stat_len] = '\0';
            raw += stat_len + 2;
            mRequest.status = status;
        }
        catch (std::exception& e)
        {
            MSG_ERROR("Error allocating memory: " << e.what());
            return -1;
        }
    }

    char *end = strstr(raw, "\r\n\r\n");

    while (raw < end)
    {
        HTTPHEAD_t head;
        char hv0[1024];
        // name
        size_t name_len = strcspn(raw, ":");

        size_t mylen = min(name_len, sizeof(hv0));
        memcpy(hv0, raw, mylen);
        hv0[mylen] = '\0';
        head.name = hv0;
        raw += name_len + 1; // move past :

        while (*raw == ' ')
            raw++;

        // value
        size_t value_len = strcspn(raw, "\r\n");
        mylen = min(value_len, sizeof(hv0));
        memcpy(hv0, raw, mylen);
        hv0[mylen] = '\0';
        head.content = hv0;
        raw += value_len + 2; // move past <CR><LF>

        if (head.name.compare("Content-Length") == 0)
            blen = atoi(head.content.c_str());

        // next
        mHeader.push_back(head);
    }

    if (blen == 0)
    {
        size_t head_len = strcspn(buffer, "\r\n\r\n");

        if (head_len < len)
            blen = len - head_len + 4;
    }

    MSG_DEBUG("Content length: " << blen);

    if (blen > 0)
    {
        raw = end + 4;

        try
        {
            mBody.body = new char[blen +1];
            memcpy(mBody.body, raw, blen);
            mBody.body[blen] = '\0';
            mBody.len = blen;
        }
        catch(std::exception& e)
        {
            MSG_ERROR(e.what());

            if (status)
                delete[] status;

            if (path)
                delete[] path;

            if (version)
                delete[] version;

            mRequest.status = nullptr;
            mRequest.path = nullptr;
            mRequest.version = nullptr;
            return -1;
        }
    }

    return 0;
}

string THTTPClient::getHeadParameter(const string& name)
{
    DECL_TRACER("THTTPClient::getHeadParameter(const string& name)");

    if (mHeader.empty())
        return string();

    vector<HTTPHEAD_t>::iterator iter;

    for (iter = mHeader.begin(); iter != mHeader.end(); iter++)
    {
        if (iter->name.compare(name) == 0)
            return iter->content;
    }

    return string();
}

char *THTTPClient::getContent(const char* buffer)
{
    DECL_TRACER("THTTPClient::getContent(const char* buffer)");

    char *ctnt = strstr((char *)buffer, "\r\n\r\n");
    return ctnt;
}

void THTTPClient::initSSL()
{
    DECL_TRACER("THTTPClient::initSSL()");

    if (mSSLInitialized)
        return;

    SSL_library_init();
    ERR_load_BIO_strings();
    ERR_load_crypto_strings();
    SSL_load_error_strings();
    mSSLInitialized = true;
}

string THTTPClient::makeRequest(const string& url)
{
    DECL_TRACER("THTTPClient::makeRequest(const string& url)");

    URL_t uparts = parseURL(url);
    string request = "GET " + uparts.path + " HTTP/1.1\r\n";
    request += "Host: " + uparts.host;

    if (uparts.port > 0 && uparts.port != 80 && uparts.port != 443)
        request += ":" + std::to_string(uparts.port);

    request += "\r\n";

    if (!mUser.empty())
    {
        string clearname = mUser + ":" + mPassword;
        string enc = Base64::encode((BYTE *)clearname.c_str(), clearname.size());
        request += "Authorization: Basic " + enc + "\r\n";
    }

    request += "User-Agent: tpanel/" + std::to_string(V_MAJOR) + "." + std::to_string(V_MINOR) + "\r\n";
    request += "Accept: image/*\r\n";
    request += "\r\n";
    MSG_DEBUG("Requesting: " << std::endl << request << "------------------------------------------");
    return request;
}

SSL_CTX *THTTPClient::initCTX()
{
    DECL_TRACER("THTTPClient::initCTX()");

    SSL_CTX *ctx;
    #if OPENSSL_VERSION_NUMBER >= 0x1010000fL
    const SSL_METHOD *method = TLS_client_method();
    #else
    const SSL_METHOD *method = TLSv1_2_client_method();
    #endif
    ctx = SSL_CTX_new(method);   /* Create new context */

    if ( ctx == NULL )
    {
        log_ssl_error();
        return NULL;
    }

    char *cert_check = getenv("CERT_CHECK");

    if (cert_check && strcmp(cert_check, "ON") == 0)
    {
        char *cert_path = getenv("CERT_PATH");
        char *cert_chain = getenv("CERT_CHAIN");
        char *cert_file = getenv("CERT_FILE");
        char *cert_type = getenv("CERT_TYPE");

        if (cert_path == NULL)
        {
            MSG_WARNING("Missing environment variable \"CERT_PATH\" defining the path to the cerificates.");
            return ctx;
        }

        if (cert_chain == NULL)
        {
            MSG_WARNING("Certificate check is enabled but no certificate chain file is set! No certificate verification was made.");
            return ctx;
        }

        SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, _verify_callback);

        if (cert_type == NULL)
            cert_type = (char *)"PEM";

        if (SSL_CTX_load_verify_locations(ctx, cert_chain, cert_path) != 1)
        {
            MSG_ERROR("Error with certificate " << cert_path << "/" << cert_chain);
            log_ssl_error();
            SSL_CTX_free(ctx);
            return NULL;
        }

        int type = SSL_FILETYPE_PEM;

        if (strcmp(cert_type, "ASN1") == 0)
            type = SSL_FILETYPE_ASN1;

        if (cert_file && SSL_CTX_use_certificate_file(ctx, cert_file, type) != 1)
        {
            MSG_ERROR("Error with certificate " << cert_file);
            log_ssl_error();
            SSL_CTX_free(ctx);
            return NULL;
        }
    }

    return ctx;
}

void THTTPClient::log_ssl_error()
{
    DECL_TRACER("THTTPClient::log_ssl_error()");
    unsigned long int err;
    char errstr[512];

    while ((err = ERR_get_error()) != 0)
    {
        ERR_error_string_n(err, &errstr[0], sizeof(errstr));
        MSG_ERROR(errstr);
    }
}

string THTTPClient::getSocketError(int err, const string data)
{
    DECL_TRACER("getSocketError(int err, const string data)");

    switch(err)
    {
        // h_errno
        case HOST_NOT_FOUND: return string("Host ") + data + " not found!";
        case NO_DATA:        return string("The requested name ") + data + " does not have an IP address!";
        case NO_RECOVERY:    return string("A nonrecoverable name server error occurred.");
        case TRY_AGAIN:      return string("A temporary error occurred on an authoritative name server.  Try again later.");

        default:
            return string("Unkown error");
    }

    return string();
}

/*
 * Callback fuction for SSL connections.
 */
int _verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
    DECL_TRACER("_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)");
    char    buf[256];
    X509   *err_cert;
    int     err, depth;
    SSL    *ssl;

    err_cert = X509_STORE_CTX_get_current_cert(ctx);
    err = X509_STORE_CTX_get_error(ctx);
    depth = X509_STORE_CTX_get_error_depth(ctx);

    /*
     * Retrieve the pointer to the SSL of the connection currently treated
     * and the application specific data stored into the SSL object.
     */
    ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
    X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);

    if (!preverify_ok)
    {
        MSG_WARNING("verify error:num=" << err << ":" << X509_verify_cert_error_string(err) << ":depth=" << depth << ":" << buf);
    }

    /*
     * At this point, err contains the last verification error. We can use
     * it for something special
     */
    if (!preverify_ok && (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT))
    {
        X509_NAME_oneline(X509_get_issuer_name(err_cert), buf, 256);
        MSG_WARNING("issuer= " << buf);
    }

    return preverify_ok;
}