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;
}