summaryrefslogtreecommitdiff
path: root/content/fetchers
diff options
context:
space:
mode:
authorVincent Sanders <vince@kyllikki.org>2016-06-27 12:37:27 +0100
committerVincent Sanders <vince@kyllikki.org>2016-06-27 13:39:07 +0100
commitab6c03f3112ef01e41a8a1e931a6990636edbae4 (patch)
treeb26d5866fa171107cda2d2a850ebc84b46e18ca5 /content/fetchers
parentd3cfbc30778647d287e9ee47359f18de716c9633 (diff)
downloadnetsurf-ab6c03f3112ef01e41a8a1e931a6990636edbae4.tar.gz
netsurf-ab6c03f3112ef01e41a8a1e931a6990636edbae4.tar.bz2
Fix handling of certificate chains
When processing a x509 certificate chain from openssl it is necessary to allow teh entire chain to be processed rather than halting processing at the first certificate with an error. This allows errors with a certificate authority to be examined.
Diffstat (limited to 'content/fetchers')
-rw-r--r--content/fetchers/curl.c396
1 files changed, 251 insertions, 145 deletions
diff --git a/content/fetchers/curl.c b/content/fetchers/curl.c
index 9ac390c6c..10a0d9918 100644
--- a/content/fetchers/curl.c
+++ b/content/fetchers/curl.c
@@ -64,6 +64,9 @@
/** maximum number of progress notifications per second */
#define UPDATES_PER_SECOND 2
+/** maximum number of X509 certificates in chain for TLS connection */
+#define MAX_CERTS 10
+
/** SSL certificate info */
struct cert_info {
X509 *cert; /**< Pointer to certificate */
@@ -89,28 +92,40 @@ struct curl_fetch_info {
char *post_urlenc; /**< Url encoded POST string, or 0. */
long http_code; /**< HTTP result code from cURL. */
struct curl_httppost *post_multipart; /**< Multipart post data, or 0. */
-#define MAX_CERTS 10
- struct cert_info cert_data[MAX_CERTS]; /**< HTTPS certificate data */
uint64_t last_progress_update; /**< Time of last progress update */
+ int cert_depth; /**< deepest certificate in use */
+ struct cert_info cert_data[MAX_CERTS]; /**< HTTPS certificate data */
};
+/** curl handle cache entry */
struct cache_handle {
CURL *handle; /**< The cached cURL handle */
- lwc_string *host; /**< The host for which this handle is cached */
+ lwc_string *host; /**< The host for which this handle is cached */
struct cache_handle *r_prev; /**< Previous cached handle in ring. */
struct cache_handle *r_next; /**< Next cached handle in ring. */
};
-CURLM *fetch_curl_multi; /**< Global cURL multi handle. */
+/** Global cURL multi handle. */
+CURLM *fetch_curl_multi;
+
/** Curl handle with default options set; not used for transfers. */
static CURL *fetch_blank_curl;
-static struct cache_handle *curl_handle_ring = 0; /**< Ring of cached handles */
+
+/** Ring of cached handles */
+static struct cache_handle *curl_handle_ring = 0;
+
+/** Count of how many schemes the curl fetcher is handling */
static int curl_fetchers_registered = 0;
+
+/** Flag for runtime detection of openssl usage */
static bool curl_with_openssl;
-static char fetch_error_buffer[CURL_ERROR_SIZE]; /**< Error buffer for cURL. */
-static char fetch_proxy_userpwd[100]; /**< Proxy authentication details. */
+/** Error buffer for cURL. */
+static char fetch_error_buffer[CURL_ERROR_SIZE];
+
+/** Proxy authentication details. */
+static char fetch_proxy_userpwd[100];
/**
@@ -243,6 +258,7 @@ fetch_curl_post_convert(const struct fetch_multipart_data *control)
return post;
}
+
/**
* Start fetching data for the given URL.
*
@@ -302,17 +318,22 @@ fetch_curl_setup(struct fetch *parent_fetch,
fetch->realm = NULL;
fetch->post_urlenc = NULL;
fetch->post_multipart = NULL;
- if (post_urlenc)
+ if (post_urlenc) {
fetch->post_urlenc = strdup(post_urlenc);
- else if (post_multipart)
+ } else if (post_multipart) {
fetch->post_multipart = fetch_curl_post_convert(post_multipart);
- memset(fetch->cert_data, 0, sizeof(fetch->cert_data));
+ }
fetch->last_progress_update = 0;
- if (fetch->host == NULL ||
- (post_multipart != NULL && fetch->post_multipart == NULL) ||
- (post_urlenc != NULL && fetch->post_urlenc == NULL))
+ /* TLS defaults */
+ memset(fetch->cert_data, 0, sizeof(fetch->cert_data));
+ fetch->cert_depth = -1;
+
+ if ((fetch->host == NULL) ||
+ (post_multipart != NULL && fetch->post_multipart == NULL) ||
+ (post_urlenc != NULL && fetch->post_urlenc == NULL)) {
goto failed;
+ }
#define APPEND(list, value) \
slist = curl_slist_append(list, value); \
@@ -374,32 +395,80 @@ failed:
/**
* OpenSSL Certificate verification callback
- * Stores certificate details in fetch struct.
+ *
+ * Called for each certificate in a chain being verified. OpenSSL
+ * calls this in deepest first order from the certificate authority to
+ * the peer certificate at position 0.
+ *
+ * Each certificate is stored in the fetch context the first time it
+ * is presented. If an error is encountered it is only returned for
+ * the peer certificate at position 0 allowing the enumeration of the
+ * entire chain not stopping early at the depth of the erroring
+ * certificate.
+ *
+ * \param verify_ok 0 if the caller has already determined the chain
+ * has errors else 1
+ * \param x509_ctx certificate context being verified
+ * \return 1 to indicate verification should continue and 0 to indicate
+ * verification should stop.
*/
static int
-fetch_curl_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
+fetch_curl_verify_callback(int verify_ok, X509_STORE_CTX *x509_ctx)
{
- X509 *cert = X509_STORE_CTX_get_current_cert(x509_ctx);
- int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
- int err = X509_STORE_CTX_get_error(x509_ctx);
- struct curl_fetch_info *f = X509_STORE_CTX_get_app_data(x509_ctx);
+ int depth;
+ struct curl_fetch_info *fetch;
+
+ depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+ fetch = X509_STORE_CTX_get_app_data(x509_ctx);
+
+ /* record the max depth */
+ if (depth > fetch->cert_depth) {
+ fetch->cert_depth = depth;
+ }
+
+ /* certificate chain is excessively deep so fail verification */
+ if (depth >= MAX_CERTS) {
+ X509_STORE_CTX_set_error(x509_ctx,
+ X509_V_ERR_CERT_CHAIN_TOO_LONG);
+ return 0;
+ }
/* save the certificate by incrementing the reference count and
* keeping a pointer.
*/
- if (depth < MAX_CERTS && !f->cert_data[depth].cert) {
- f->cert_data[depth].cert = cert;
- f->cert_data[depth].err = err;
- cert->references++;
+ if (!fetch->cert_data[depth].cert) {
+ fetch->cert_data[depth].cert = X509_STORE_CTX_get_current_cert(x509_ctx);
+ fetch->cert_data[depth].cert->references++;
+ fetch->cert_data[depth].err = X509_STORE_CTX_get_error(x509_ctx);
}
- return preverify_ok;
+ /* allow certificate chain to be completed */
+ if (depth > 0) {
+ verify_ok = 1;
+ } else {
+ /* search for deeper certificates in the chain with errors */
+ for (depth = fetch->cert_depth; depth > 0; depth--) {
+ if (fetch->cert_data[depth].err != 0) {
+ /* error in previous certificate so fail verification */
+ verify_ok = 0;
+ X509_STORE_CTX_set_error(x509_ctx, fetch->cert_data[depth].err);
+ }
+ }
+ }
+
+ return verify_ok;
}
/**
* OpenSSL certificate chain verification callback
- * Verifies certificate chain, setting up context for fetch_curl_verify_callback
+ *
+ * Verifies certificate chain by calling standard implementation after
+ * setting up context for the certificate callback.
+ *
+ * \param x509_ctx The certificate store to validate
+ * \param parm The fetch context.
+ * \return 1 to indicate verification success and 0 to indicate verification failure.
*/
static int fetch_curl_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm)
{
@@ -408,9 +477,10 @@ static int fetch_curl_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm)
/* Store fetch struct in context for verify callback */
ok = X509_STORE_CTX_set_app_data(x509_ctx, parm);
- /* and verify the certificate chain */
- if (ok)
+ /* verify the certificate chain using standard call */
+ if (ok) {
ok = X509_verify_cert(x509_ctx);
+ }
return ok;
}
@@ -431,7 +501,10 @@ fetch_curl_sslctxfun(CURL *curl_handle, void *_sslctx, void *parm)
SSL_CTX *sslctx = _sslctx;
long options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+ /* set verify callback for each certificate in chain */
SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, fetch_curl_verify_callback);
+
+ /* set callback used to verify certificate chain */
SSL_CTX_set_cert_verify_callback(sslctx,
fetch_curl_cert_verify_callback,
parm);
@@ -626,7 +699,6 @@ static bool fetch_curl_start(void *vfetch)
/**
* Cache a CURL handle for the provided host (if wanted)
*/
-
static void fetch_curl_cache_handle(CURL *handle, lwc_string *host)
{
#if LIBCURL_VERSION_NUM >= 0x071e00
@@ -726,23 +798,27 @@ static void fetch_curl_free(void *vf)
struct curl_fetch_info *f = (struct curl_fetch_info *)vf;
int i;
- if (f->curl_handle)
+ if (f->curl_handle) {
curl_easy_cleanup(f->curl_handle);
+ }
nsurl_unref(f->url);
lwc_string_unref(f->host);
free(f->location);
free(f->cookie_string);
free(f->realm);
- if (f->headers)
+ if (f->headers) {
curl_slist_free_all(f->headers);
+ }
free(f->post_urlenc);
- if (f->post_multipart)
+ if (f->post_multipart) {
curl_formfree(f->post_multipart);
+ }
for (i = 0; i < MAX_CERTS && f->cert_data[i].cert; i++) {
f->cert_data[i].cert->references--;
- if (f->cert_data[i].cert->references == 0)
+ if (f->cert_data[i].cert->references == 0) {
X509_free(f->cert_data[i].cert);
+ }
}
free(f);
@@ -810,6 +886,117 @@ static bool fetch_curl_process_headers(struct curl_fetch_info *f)
return false;
}
+/**
+ * setup callback to allow the user to examine certificates which have
+ * failed to validate during fetch.
+ */
+static void
+curl_start_cert_validate(struct curl_fetch_info *f,
+ struct cert_info *certs)
+{
+ int depth;
+ BIO *mem;
+ BUF_MEM *buf;
+ struct ssl_cert_info ssl_certs[MAX_CERTS];
+ fetch_msg msg;
+
+ for (depth = 0; depth <= f->cert_depth; depth++) {
+ assert(certs[depth].cert != NULL);
+
+ /* get certificate version */
+ ssl_certs[depth].version = X509_get_version(certs[depth].cert);
+
+ /* not before date */
+ mem = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(mem, X509_get_notBefore(certs[depth].cert));
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(ssl_certs[depth].not_before,
+ buf->data,
+ min(sizeof(ssl_certs[depth].not_before) - 1,
+ (unsigned)buf->length));
+ ssl_certs[depth].not_before[min(sizeof(ssl_certs[depth].not_before) - 1,
+ (unsigned)buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* not after date */
+ mem = BIO_new(BIO_s_mem());
+ ASN1_TIME_print(mem,
+ X509_get_notAfter(certs[depth].cert));
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(ssl_certs[depth].not_after,
+ buf->data,
+ min(sizeof(ssl_certs[depth].not_after) - 1,
+ (unsigned)buf->length));
+ ssl_certs[depth].not_after[min(sizeof(ssl_certs[depth].not_after) - 1,
+ (unsigned)buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* signature type */
+ ssl_certs[depth].sig_type =
+ X509_get_signature_type(certs[depth].cert);
+
+ /* serial number */
+ ssl_certs[depth].serial =
+ ASN1_INTEGER_get(
+ X509_get_serialNumber(certs[depth].cert));
+
+ /* issuer name */
+ mem = BIO_new(BIO_s_mem());
+ X509_NAME_print_ex(mem,
+ X509_get_issuer_name(certs[depth].cert),
+ 0, XN_FLAG_SEP_CPLUS_SPC |
+ XN_FLAG_DN_REV | XN_FLAG_FN_NONE);
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(ssl_certs[depth].issuer,
+ buf->data,
+ min(sizeof(ssl_certs[depth].issuer) - 1,
+ (unsigned) buf->length));
+ ssl_certs[depth].issuer[min(sizeof(ssl_certs[depth].issuer) - 1,
+ (unsigned) buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* subject */
+ mem = BIO_new(BIO_s_mem());
+ X509_NAME_print_ex(mem,
+ X509_get_subject_name(certs[depth].cert),
+ 0,
+ XN_FLAG_SEP_CPLUS_SPC |
+ XN_FLAG_DN_REV |
+ XN_FLAG_FN_NONE);
+ BIO_get_mem_ptr(mem, &buf);
+ (void) BIO_set_close(mem, BIO_NOCLOSE);
+ BIO_free(mem);
+ memcpy(ssl_certs[depth].subject,
+ buf->data,
+ min(sizeof(ssl_certs[depth].subject) - 1,
+ (unsigned)buf->length));
+ ssl_certs[depth].subject[min(sizeof(ssl_certs[depth].subject) - 1,
+ (unsigned) buf->length)] = 0;
+ BUF_MEM_free(buf);
+
+ /* type of certificate */
+ ssl_certs[depth].cert_type =
+ X509_certificate_type(certs[depth].cert,
+ X509_get_pubkey(certs[depth].cert));
+
+ /* and clean up */
+ certs[depth].cert->references--;
+ if (certs[depth].cert->references == 0) {
+ X509_free(certs[depth].cert);
+ }
+ }
+
+ msg.type = FETCH_CERT_ERR;
+ msg.data.cert_err.certs = ssl_certs;
+ msg.data.cert_err.num_certs = depth;
+ fetch_send_callback(&msg, f->fetch_handle);
+}
/**
* Handle a completed fetch (CURLMSG_DONE from curl_multi_info_read()).
@@ -819,7 +1006,6 @@ static bool fetch_curl_process_headers(struct curl_fetch_info *f)
*/
static void fetch_curl_done(CURL *curl_handle, CURLcode result)
{
- fetch_msg msg;
bool finished = false;
bool error = false;
bool cert = false;
@@ -828,7 +1014,6 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
char **_hideous_hack = (char **) (void *) &f;
CURLcode code;
struct cert_info certs[MAX_CERTS];
- memset(certs, 0, sizeof(certs));
/* find the structure associated with this fetch */
/* For some reason, cURL thinks CURLINFO_PRIVATE should be a string?! */
@@ -838,8 +1023,9 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
abort_fetch = f->abort;
LOG("done %s", nsurl_access(f->url));
- if (abort_fetch == false && (result == CURLE_OK ||
- (result == CURLE_WRITE_ERROR && f->stopped == false))) {
+ if ((abort_fetch == false) &&
+ (result == CURLE_OK ||
+ ((result == CURLE_WRITE_ERROR) && (f->stopped == false)))) {
/* fetch completed normally or the server fed us a junk gzip
* stream (usually in the form of garbage at the end of the
* stream). Curl will have fed us all but the last chunk of
@@ -850,15 +1036,16 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
* the content handlers to cope.
*/
if (f->stopped ||
- (!f->had_headers &&
- fetch_curl_process_headers(f)))
+ (!f->had_headers && fetch_curl_process_headers(f))) {
; /* redirect with no body or similar */
- else
+ } else {
finished = true;
+ }
} else if (result == CURLE_PARTIAL_FILE) {
/* CURLE_PARTIAL_FILE occurs if the received body of a
* response is smaller than that specified in the
- * Content-Length header. */
+ * Content-Length header.
+ */
if (!f->had_headers && fetch_curl_process_headers(f))
; /* redirect with partial body, or similar */
else {
@@ -866,10 +1053,15 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
}
} else if (result == CURLE_WRITE_ERROR && f->stopped) {
/* CURLE_WRITE_ERROR occurs when fetch_curl_data
- * returns 0, which we use to abort intentionally */
+ * returns 0, which we use to abort intentionally
+ */
;
} else if (result == CURLE_SSL_PEER_CERTIFICATE ||
result == CURLE_SSL_CACERT) {
+ /* CURLE_SSL_PEER_CERTIFICATE renamed to
+ * CURLE_PEER_FAILED_VERIFICATION
+ */
+ memset(certs, 0, sizeof(certs));
memcpy(certs, f->cert_data, sizeof(certs));
memset(f->cert_data, 0, sizeof(f->cert_data));
cert = true;
@@ -880,104 +1072,17 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result)
fetch_curl_stop(f);
- if (abort_fetch)
+ if (abort_fetch) {
; /* fetch was aborted: no callback */
- else if (finished) {
+ } else if (finished) {
+ fetch_msg msg;
msg.type = FETCH_FINISHED;
fetch_send_callback(&msg, f->fetch_handle);
} else if (cert) {
- int i;
- BIO *mem;
- BUF_MEM *buf;
- struct ssl_cert_info ssl_certs[MAX_CERTS];
-
- for (i = 0; i < MAX_CERTS && certs[i].cert; i++) {
- ssl_certs[i].version =
- X509_get_version(certs[i].cert);
-
- mem = BIO_new(BIO_s_mem());
- ASN1_TIME_print(mem,
- X509_get_notBefore(certs[i].cert));
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[i].not_before,
- buf->data,
- min(sizeof(ssl_certs[i].not_before) - 1,
- (unsigned)buf->length));
- ssl_certs[i].not_before[min(sizeof(ssl_certs[i].not_before) - 1,
- (unsigned)buf->length)] = 0;
- BUF_MEM_free(buf);
-
- mem = BIO_new(BIO_s_mem());
- ASN1_TIME_print(mem,
- X509_get_notAfter(certs[i].cert));
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[i].not_after,
- buf->data,
- min(sizeof(ssl_certs[i].not_after) - 1,
- (unsigned)buf->length));
- ssl_certs[i].not_after[min(sizeof(ssl_certs[i].not_after) - 1,
- (unsigned)buf->length)] = 0;
-
- BUF_MEM_free(buf);
-
- ssl_certs[i].sig_type =
- X509_get_signature_type(certs[i].cert);
- ssl_certs[i].serial =
- ASN1_INTEGER_get(
- X509_get_serialNumber(certs[i].cert));
- mem = BIO_new(BIO_s_mem());
- X509_NAME_print_ex(mem,
- X509_get_issuer_name(certs[i].cert),
- 0, XN_FLAG_SEP_CPLUS_SPC |
- XN_FLAG_DN_REV | XN_FLAG_FN_NONE);
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[i].issuer,
- buf->data,
- min(sizeof(ssl_certs[i].issuer) - 1,
- (unsigned) buf->length));
- ssl_certs[i].issuer[min(sizeof(ssl_certs[i].issuer) - 1,
- (unsigned) buf->length)] = 0;
- BUF_MEM_free(buf);
-
- mem = BIO_new(BIO_s_mem());
- X509_NAME_print_ex(mem,
- X509_get_subject_name(certs[i].cert),
- 0,
- XN_FLAG_SEP_CPLUS_SPC |
- XN_FLAG_DN_REV |
- XN_FLAG_FN_NONE);
- BIO_get_mem_ptr(mem, &buf);
- (void) BIO_set_close(mem, BIO_NOCLOSE);
- BIO_free(mem);
- memcpy(ssl_certs[i].subject,
- buf->data,
- min(sizeof(ssl_certs[i].subject) - 1,
- (unsigned)buf->length));
- ssl_certs[i].subject[min(sizeof(ssl_certs[i].subject) - 1,
- (unsigned) buf->length)] = 0;
- BUF_MEM_free(buf);
-
- ssl_certs[i].cert_type =
- X509_certificate_type(certs[i].cert,
- X509_get_pubkey(certs[i].cert));
-
- /* and clean up */
- certs[i].cert->references--;
- if (certs[i].cert->references == 0)
- X509_free(certs[i].cert);
- }
-
- msg.type = FETCH_CERT_ERR;
- msg.data.cert_err.certs = ssl_certs;
- msg.data.cert_err.num_certs = i;
- fetch_send_callback(&msg, f->fetch_handle);
+ /* user needs to validate certificate with issue */
+ curl_start_cert_validate(f, certs);
} else if (error) {
+ fetch_msg msg;
switch (result) {
case CURLE_SSL_CONNECT_ERROR:
msg.type = FETCH_SSL_ERR;
@@ -1080,9 +1185,12 @@ static void fetch_curl_poll(lwc_string *scheme_ignored)
/**
* Callback function for fetch progress.
*/
-
-static int fetch_curl_progress(void *clientp, double dltotal, double dlnow,
- double ultotal, double ulnow)
+static int
+fetch_curl_progress(void *clientp,
+ double dltotal,
+ double dlnow,
+ double ultotal,
+ double ulnow)
{
static char fetch_progress_buffer[256]; /**< Progress buffer for cURL */
struct curl_fetch_info *f = (struct curl_fetch_info *) clientp;
@@ -1147,8 +1255,7 @@ static size_t fetch_curl_data(char *data, size_t size, size_t nmemb, void *_f)
fetch_msg msg;
/* ensure we only have to get this information once */
- if (!f->http_code)
- {
+ if (!f->http_code) {
code = curl_easy_getinfo(f->curl_handle, CURLINFO_HTTP_CODE,
&f->http_code);
fetch_set_http_code(f->fetch_handle, f->http_code);
@@ -1158,8 +1265,7 @@ static size_t fetch_curl_data(char *data, size_t size, size_t nmemb, void *_f)
/* ignore body if this is a 401 reply by skipping it and reset
* the HTTP response code to enable follow up fetches.
*/
- if (f->http_code == 401)
- {
+ if (f->http_code == 401) {
f->http_code = 0;
return size * nmemb;
}
@@ -1189,8 +1295,8 @@ static size_t fetch_curl_data(char *data, size_t size, size_t nmemb, void *_f)
*
* See RFC 2616 4.2.
*/
-static size_t fetch_curl_header(char *data, size_t size, size_t nmemb,
- void *_f)
+static size_t
+fetch_curl_header(char *data, size_t size, size_t nmemb, void *_f)
{
struct curl_fetch_info *f = _f;
int i;
@@ -1321,8 +1427,8 @@ nserror fetch_curl_register(void)
#endif
/* Create a curl easy handle with the options that are common to all
- fetches.
- */
+ * fetches.
+ */
fetch_blank_curl = curl_easy_init();
if (!fetch_blank_curl) {
LOG("curl_easy_init failed");