diff options
Diffstat (limited to 'content/fetchers/curl.c')
-rw-r--r-- | content/fetchers/curl.c | 1257 |
1 files changed, 921 insertions, 336 deletions
diff --git a/content/fetchers/curl.c b/content/fetchers/curl.c index a6146edb3..680e60456 100644 --- a/content/fetchers/curl.c +++ b/content/fetchers/curl.c @@ -1,7 +1,7 @@ /* - * Copyright 2006 Daniel Silverstone <dsilvers@digital-scurf.org> + * Copyright 2006-2019 Daniel Silverstone <dsilvers@digital-scurf.org> + * Copyright 2010-2018 Vincent Sanders <vince@netsurf-browser.org> * Copyright 2007 James Bursa <bursa@users.sourceforge.net> - * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net> * * This file is part of NetSurf. * @@ -38,12 +38,12 @@ #include <strings.h> #include <time.h> #include <sys/stat.h> -#include <openssl/ssl.h> #include <libwapcaplet/libwapcaplet.h> #include <nsutils/time.h> #include "utils/corestrings.h" +#include "utils/hashmap.h" #include "utils/nsoption.h" #include "utils/log.h" #include "utils/messages.h" @@ -61,11 +61,149 @@ #include "content/fetchers/curl.h" #include "content/urldb.h" -/** maximum number of progress notifications per second */ +/** + * 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 +/** + * The ciphersuites the browser is prepared to use for TLS1.3 + */ +#define CIPHER_SUITES \ + "TLS_AES_256_GCM_SHA384:" \ + "TLS_CHACHA20_POLY1305_SHA256:" \ + "TLS_AES_128_GCM_SHA256" + +/** + * The ciphersuites the browser is prepared to use for TLS<1.3 + */ +#define CIPHER_LIST \ + /* disable everything */ \ + "-ALL:" \ + /* enable TLSv1.2 ECDHE AES GCM suites */ \ + "EECDH+AESGCM+TLSv1.2:" \ + /* enable ECDHE CHACHA20/POLY1305 suites */ \ + "EECDH+CHACHA20:" \ + /* Sort above by strength */ \ + "@STRENGTH:" \ + /* enable ECDHE (auth=RSA, mac=SHA1) AES CBC suites */ \ + "EECDH+aRSA+AES+SHA1" + +/** + * The legacy cipher suites the browser is prepared to use for TLS<1.3 + */ +#define CIPHER_LIST_LEGACY \ + /* as above */ \ + CIPHER_LIST":" \ + /* enable (non-PFS) RSA AES GCM suites */ \ + "RSA+AESGCM:" \ + /* enable (non-PFS) RSA (mac=SHA1) AES CBC suites */ \ + "RSA+AES+SHA1" + +/* Open SSL compatability for certificate handling */ +#ifdef WITH_OPENSSL + +#include <openssl/ssl.h> +#include <openssl/x509v3.h> + +#else /* WITH_OPENSSL */ + +typedef char X509; + +static void X509_free(X509 *cert) +{ + free(cert); +} + +#endif /* WITH_OPENSSL */ + +/* SSL certificate chain cache */ + +/* We're only interested in the hostname and port */ +static uint32_t +curl_fetch_ssl_key_hash(void *key) +{ + nsurl *url = key; + lwc_string *hostname = nsurl_get_component(url, NSURL_HOST); + lwc_string *port = nsurl_get_component(url, NSURL_PORT); + uint32_t hash; + + if (port == NULL) + port = lwc_string_ref(corestring_lwc_443); + + hash = lwc_string_hash_value(hostname) ^ lwc_string_hash_value(port); + + lwc_string_unref(hostname); + lwc_string_unref(port); + + return hash; +} + +/* We only compare the hostname and port */ +static bool +curl_fetch_ssl_key_eq(void *key1, void *key2) +{ + nsurl *url1 = key1; + nsurl *url2 = key2; + lwc_string *hostname1 = nsurl_get_component(url1, NSURL_HOST); + lwc_string *hostname2 = nsurl_get_component(url2, NSURL_HOST); + lwc_string *port1 = nsurl_get_component(url1, NSURL_PORT); + lwc_string *port2 = nsurl_get_component(url2, NSURL_PORT); + bool iseq = false; + + if (port1 == NULL) + port1 = lwc_string_ref(corestring_lwc_443); + if (port2 == NULL) + port2 = lwc_string_ref(corestring_lwc_443); + + if (lwc_string_isequal(hostname1, hostname2, &iseq) != lwc_error_ok || + iseq == false) + goto out; + + iseq = false; + if (lwc_string_isequal(port1, port2, &iseq) != lwc_error_ok) + goto out; + +out: + lwc_string_unref(hostname1); + lwc_string_unref(hostname2); + lwc_string_unref(port1); + lwc_string_unref(port2); + + return iseq; +} + +static void * +curl_fetch_ssl_value_alloc(void *key) +{ + struct cert_chain *out; + + if (cert_chain_alloc(0, &out) != NSERROR_OK) { + return NULL; + } + + return out; +} + +static void +curl_fetch_ssl_value_destroy(void *value) +{ + struct cert_chain *chain = value; + if (cert_chain_free(chain) != NSERROR_OK) { + NSLOG(netsurf, WARNING, "Problem freeing SSL certificate chain"); + } +} + +static hashmap_parameters_t curl_fetch_ssl_hashmap_parameters = { + .key_clone = (hashmap_key_clone_t)nsurl_ref, + .key_destroy = (hashmap_key_destroy_t)nsurl_unref, + .key_eq = curl_fetch_ssl_key_eq, + .key_hash = curl_fetch_ssl_key_hash, + .value_alloc = curl_fetch_ssl_value_alloc, + .value_destroy = curl_fetch_ssl_value_destroy, +}; + +static hashmap_t *curl_fetch_ssl_hashmap = NULL; /** SSL certificate info */ struct cert_info { @@ -73,15 +211,36 @@ struct cert_info { long err; /**< OpenSSL error code */ }; +#if LIBCURL_VERSION_NUM >= 0x072000 /* 7.32.0 depricated CURLOPT_PROGRESSFUNCTION*/ +#define NSCURLOPT_PROGRESS_FUNCTION CURLOPT_XFERINFOFUNCTION +#define NSCURLOPT_PROGRESS_DATA CURLOPT_XFERINFODATA +#define NSCURL_PROGRESS_T curl_off_t +#else +#define NSCURLOPT_PROGRESS_FUNCTION CURLOPT_PROGRESSFUNCTION +#define NSCURLOPT_PROGRESS_DATA CURLOPT_PROGRESSDATA +#define NSCURL_PROGRESS_T double +#endif + +#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 depricated curl_formadd */ +#define NSCURL_POSTDATA_T curl_mime +#define NSCURL_POSTDATA_CURLOPT CURLOPT_MIMEPOST +#define NSCURL_POSTDATA_FREE(x) curl_mime_free(x) +#else +#define NSCURL_POSTDATA_T struct curl_httppost +#define NSCURL_POSTDATA_CURLOPT CURLOPT_HTTPPOST +#define NSCURL_POSTDATA_FREE(x) curl_formfree(x) +#endif + /** Information for a single fetch. */ struct curl_fetch_info { struct fetch *fetch_handle; /**< The fetch handle we're parented by. */ CURL * curl_handle; /**< cURL handle if being fetched, or 0. */ + bool sent_ssl_chain; /**< Have we tried to send the SSL chain */ bool had_headers; /**< Headers have been processed. */ bool abort; /**< Abort requested. */ bool stopped; /**< Download stopped on purpose. */ bool only_2xx; /**< Only HTTP 2xx responses acceptable. */ - bool downgrade_tls; /**< Downgrade to TLS <= 1.0 */ + bool downgrade_tls; /**< Downgrade to TLS 1.2 */ nsurl *url; /**< URL of this fetch. */ lwc_string *host; /**< The hostname of this fetch. */ struct curl_slist *headers; /**< List of request headers. */ @@ -89,12 +248,14 @@ struct curl_fetch_info { unsigned long content_length; /**< Response Content-Length, or 0. */ char *cookie_string; /**< Cookie string for this fetch */ char *realm; /**< HTTP Auth Realm */ - char *post_urlenc; /**< Url encoded POST string, or 0. */ + struct fetch_postdata *postdata; /**< POST data */ + NSCURL_POSTDATA_T *curl_postdata; /**< POST data in curl representation */ + long http_code; /**< HTTP result code from cURL. */ - struct curl_httppost *post_multipart; /**< Multipart post data, or 0. */ + 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 */ + struct cert_info cert_data[MAX_CERT_DEPTH]; /**< HTTPS certificate data */ }; /** curl handle cache entry */ @@ -127,28 +288,9 @@ static char fetch_error_buffer[CURL_ERROR_SIZE]; /** Proxy authentication details. */ static char fetch_proxy_userpwd[100]; +/** Interlock to prevent initiation during callbacks */ +static bool inside_curl = false; -/* OpenSSL 1.0.x to 1.1.0 certificate reference counting changed - * LibreSSL declares its OpenSSL version as 2.1 but only supports the old way - */ -#if (defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x1010000fL)) -static int ns_X509_up_ref(X509 *cert) -{ - cert->references++; - return 1; -} - -static void ns_X509_free(X509 *cert) -{ - cert->references--; - if (cert->references == 0) { - X509_free(cert); - } -} -#else -#define ns_X509_up_ref X509_up_ref -#define ns_X509_free X509_free -#endif /** * Initialise a cURL fetcher. @@ -188,6 +330,10 @@ static void fetch_curl_finalise(lwc_string *scheme) "curl_multi_cleanup failed: ignoring"); curl_global_cleanup(); + + NSLOG(netsurf, DEBUG, "Cleaning up SSL cert chain hashmap"); + hashmap_destroy(curl_fetch_ssl_hashmap); + curl_fetch_ssl_hashmap = NULL; } /* Free anything remaining in the cached curl handle ring */ @@ -213,85 +359,95 @@ static bool fetch_curl_can_fetch(const nsurl *url) } + /** - * Convert a list of struct ::fetch_multipart_data to a list of - * struct curl_httppost for libcurl. + * allocate postdata */ -static struct curl_httppost * -fetch_curl_post_convert(const struct fetch_multipart_data *control) +static struct fetch_postdata * +fetch_curl_alloc_postdata(const char *post_urlenc, + const struct fetch_multipart_data *post_multipart) { - struct curl_httppost *post = 0, *last = 0; - CURLFORMcode code; - nserror ret; - - for (; control; control = control->next) { - if (control->file) { - char *leafname = NULL; - ret = guit->file->basename(control->value, &leafname, NULL); - if (ret != NSERROR_OK) { - continue; + struct fetch_postdata *postdata; + postdata = calloc(1, sizeof(struct fetch_postdata)); + if (postdata != NULL) { + + if (post_urlenc) { + postdata->type = FETCH_POSTDATA_URLENC; + postdata->data.urlenc = strdup(post_urlenc); + if (postdata->data.urlenc == NULL) { + free(postdata); + postdata = NULL; } - - /* We have to special case filenames of "", so curl - * a) actually attempts the fetch and - * b) doesn't attempt to open the file "" - */ - if (control->value[0] == '\0') { - /* dummy buffer - needs to be static so - * pointer's still valid when we go out - * of scope (not that libcurl should be - * attempting to access it, of course). - */ - static char buf; - - code = curl_formadd(&post, &last, - CURLFORM_COPYNAME, control->name, - CURLFORM_BUFFER, control->value, - /* needed, as basename("") == "." */ - CURLFORM_FILENAME, "", - CURLFORM_BUFFERPTR, &buf, - CURLFORM_BUFFERLENGTH, 0, - CURLFORM_CONTENTTYPE, - "application/octet-stream", - CURLFORM_END); - if (code != CURL_FORMADD_OK) - NSLOG(netsurf, INFO, - "curl_formadd: %d (%s)", code, - control->name); - } else { - char *mimetype = guit->fetch->mimetype(control->value); - code = curl_formadd(&post, &last, - CURLFORM_COPYNAME, control->name, - CURLFORM_FILE, control->rawfile, - CURLFORM_FILENAME, leafname, - CURLFORM_CONTENTTYPE, - (mimetype != 0 ? mimetype : "text/plain"), - CURLFORM_END); - if (code != CURL_FORMADD_OK) - NSLOG(netsurf, INFO, - "curl_formadd: %d (%s=%s)", - code, - control->name, - control->value); - free(mimetype); + } else if (post_multipart) { + postdata->type = FETCH_POSTDATA_MULTIPART; + postdata->data.multipart = fetch_multipart_data_clone(post_multipart); + if (postdata->data.multipart == NULL) { + free(postdata); + postdata = NULL; } - free(leafname); - } - else { - code = curl_formadd(&post, &last, - CURLFORM_COPYNAME, control->name, - CURLFORM_COPYCONTENTS, control->value, - CURLFORM_END); - if (code != CURL_FORMADD_OK) - NSLOG(netsurf, INFO, - "curl_formadd: %d (%s=%s)", code, - control->name, control->value); + } else { + postdata->type = FETCH_POSTDATA_NONE; } } + return postdata; +} - return post; +/** + * free postdata + */ +static void fetch_curl_free_postdata(struct fetch_postdata *postdata) +{ + if (postdata != NULL) { + switch (postdata->type) { + case FETCH_POSTDATA_NONE: + break; + case FETCH_POSTDATA_URLENC: + free(postdata->data.urlenc); + break; + case FETCH_POSTDATA_MULTIPART: + fetch_multipart_data_destroy(postdata->data.multipart); + break; + } + + free(postdata); + } } +/** + *construct a new fetch structure + */ +static struct curl_fetch_info *fetch_alloc(void) +{ + struct curl_fetch_info *fetch; + fetch = malloc(sizeof (*fetch)); + if (fetch == NULL) + return NULL; + + fetch->curl_handle = NULL; + fetch->sent_ssl_chain = false; + fetch->had_headers = false; + fetch->abort = false; + fetch->stopped = false; + fetch->only_2xx = false; + fetch->downgrade_tls = false; + fetch->headers = NULL; + fetch->url = NULL; + fetch->host = NULL; + fetch->location = NULL; + fetch->content_length = 0; + fetch->http_code = 0; + fetch->cookie_string = NULL; + fetch->realm = NULL; + fetch->last_progress_update = 0; + fetch->postdata = NULL; + fetch->curl_postdata = NULL; + + /* Clear certificate chain data */ + memset(fetch->cert_data, 0, sizeof(fetch->cert_data)); + fetch->cert_depth = -1; + + return fetch; +} /** * Start fetching data for the given URL. @@ -327,45 +483,22 @@ fetch_curl_setup(struct fetch *parent_fetch, struct curl_slist *slist; int i; - fetch = malloc(sizeof (*fetch)); + fetch = fetch_alloc(); if (fetch == NULL) - return 0; - - fetch->fetch_handle = parent_fetch; + return NULL; NSLOG(netsurf, INFO, "fetch %p, url '%s'", fetch, nsurl_access(url)); - /* construct a new fetch structure */ - fetch->curl_handle = NULL; - fetch->had_headers = false; - fetch->abort = false; - fetch->stopped = false; fetch->only_2xx = only_2xx; fetch->downgrade_tls = downgrade_tls; - fetch->headers = NULL; + fetch->fetch_handle = parent_fetch; fetch->url = nsurl_ref(url); fetch->host = nsurl_get_component(url, NSURL_HOST); - fetch->location = NULL; - fetch->content_length = 0; - fetch->http_code = 0; - fetch->cookie_string = NULL; - fetch->realm = NULL; - fetch->post_urlenc = NULL; - fetch->post_multipart = NULL; - if (post_urlenc) { - fetch->post_urlenc = strdup(post_urlenc); - } else if (post_multipart) { - fetch->post_multipart = fetch_curl_post_convert(post_multipart); + if (fetch->host == NULL) { + goto failed; } - fetch->last_progress_update = 0; - - /* 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)) { + fetch->postdata = fetch_curl_alloc_postdata(post_urlenc, post_multipart); + if (fetch->postdata == NULL) { goto failed; } @@ -418,15 +551,141 @@ failed: lwc_string_unref(fetch->host); nsurl_unref(fetch->url); - free(fetch->post_urlenc); - if (fetch->post_multipart) - curl_formfree(fetch->post_multipart); + fetch_curl_free_postdata(fetch->postdata); curl_slist_free_all(fetch->headers); free(fetch); return NULL; } +#ifdef WITH_OPENSSL + +/** + * Retrieve the ssl cert chain for the fetch, creating a blank one if needed + */ +static struct cert_chain * +fetch_curl_get_cached_chain(struct curl_fetch_info *f) +{ + struct cert_chain *chain; + + chain = hashmap_lookup(curl_fetch_ssl_hashmap, f->url); + if (chain == NULL) { + chain = hashmap_insert(curl_fetch_ssl_hashmap, f->url); + } + + return chain; +} + +/** + * Report the certificate information in the fetch to the users + */ +static void +fetch_curl_store_certs_in_cache(struct curl_fetch_info *f) +{ + size_t depth; + BIO *mem; + BUF_MEM *buf[MAX_CERT_DEPTH]; + struct cert_chain chain, *cached_chain; + struct cert_info *certs; + + memset(&chain, 0, sizeof(chain)); + + certs = f->cert_data; + chain.depth = f->cert_depth + 1; /* 0 indexed certificate depth */ + + for (depth = 0; depth < chain.depth; depth++) { + if (certs[depth].cert == NULL) { + /* This certificate is missing, skip it */ + chain.certs[depth].err = SSL_CERT_ERR_CERT_MISSING; + continue; + } + + /* error code (if any) */ + switch (certs[depth].err) { + case X509_V_OK: + chain.certs[depth].err = SSL_CERT_ERR_OK; + break; + + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + /* fallthrough */ + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + chain.certs[depth].err = SSL_CERT_ERR_BAD_ISSUER; + break; + + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + /* fallthrough */ + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + /* fallthrough */ + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + /* fallthrough */ + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + chain.certs[depth].err = SSL_CERT_ERR_BAD_SIG; + break; + + case X509_V_ERR_CERT_NOT_YET_VALID: + /* fallthrough */ + case X509_V_ERR_CRL_NOT_YET_VALID: + chain.certs[depth].err = SSL_CERT_ERR_TOO_YOUNG; + break; + + case X509_V_ERR_CERT_HAS_EXPIRED: + /* fallthrough */ + case X509_V_ERR_CRL_HAS_EXPIRED: + chain.certs[depth].err = SSL_CERT_ERR_TOO_OLD; + break; + + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + chain.certs[depth].err = SSL_CERT_ERR_SELF_SIGNED; + break; + + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + chain.certs[depth].err = SSL_CERT_ERR_CHAIN_SELF_SIGNED; + break; + + case X509_V_ERR_CERT_REVOKED: + chain.certs[depth].err = SSL_CERT_ERR_REVOKED; + break; + + case X509_V_ERR_HOSTNAME_MISMATCH: + chain.certs[depth].err = SSL_CERT_ERR_HOSTNAME_MISMATCH; + break; + + default: + chain.certs[depth].err = SSL_CERT_ERR_UNKNOWN; + break; + } + + /* + * get certificate in Distinguished Encoding Rules (DER) format. + */ + mem = BIO_new(BIO_s_mem()); + i2d_X509_bio(mem, certs[depth].cert); + BIO_get_mem_ptr(mem, &buf[depth]); + (void) BIO_set_close(mem, BIO_NOCLOSE); + BIO_free(mem); + + chain.certs[depth].der = (uint8_t *)buf[depth]->data; + chain.certs[depth].der_length = buf[depth]->length; + } + + /* Now dup that chain into the cache */ + cached_chain = fetch_curl_get_cached_chain(f); + if (cert_chain_dup_into(&chain, cached_chain) != NSERROR_OK) { + /* Something went wrong storing the chain, give up */ + hashmap_remove(curl_fetch_ssl_hashmap, f->url); + } + + /* release the openssl memory buffer */ + for (depth = 0; depth < chain.depth; depth++) { + if (chain.certs[depth].err == SSL_CERT_ERR_CERT_MISSING) { + continue; + } + if (buf[depth] != NULL) { + BUF_MEM_free(buf[depth]); + } + } +} + /** * OpenSSL Certificate verification callback * @@ -455,24 +714,24 @@ fetch_curl_verify_callback(int verify_ok, X509_STORE_CTX *x509_ctx) 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) { + if (depth >= MAX_CERT_DEPTH) { X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG); return 0; } + /* record the max depth */ + if (depth > fetch->cert_depth) { + fetch->cert_depth = depth; + } + /* save the certificate by incrementing the reference count and * keeping a pointer. */ if (!fetch->cert_data[depth].cert) { fetch->cert_data[depth].cert = X509_STORE_CTX_get_current_cert(x509_ctx); - ns_X509_up_ref(fetch->cert_data[depth].cert); + X509_up_ref(fetch->cert_data[depth].cert); fetch->cert_data[depth].err = X509_STORE_CTX_get_error(x509_ctx); } @@ -506,16 +765,30 @@ fetch_curl_verify_callback(int verify_ok, X509_STORE_CTX *x509_ctx) */ static int fetch_curl_cert_verify_callback(X509_STORE_CTX *x509_ctx, void *parm) { + struct curl_fetch_info *f = (struct curl_fetch_info *) parm; int ok; + X509_VERIFY_PARAM *vparam; + + /* Configure the verification parameters to include hostname */ + vparam = X509_STORE_CTX_get0_param(x509_ctx); + X509_VERIFY_PARAM_set_hostflags(vparam, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + + ok = X509_VERIFY_PARAM_set1_host(vparam, + lwc_string_data(f->host), + lwc_string_length(f->host)); /* Store fetch struct in context for verify callback */ - ok = X509_STORE_CTX_set_app_data(x509_ctx, parm); + if (ok) { + ok = X509_STORE_CTX_set_app_data(x509_ctx, parm); + } /* verify the certificate chain using standard call */ if (ok) { ok = X509_verify_cert(x509_ctx); } + fetch_curl_store_certs_in_cache(f); + return ok; } @@ -533,7 +806,8 @@ fetch_curl_sslctxfun(CURL *curl_handle, void *_sslctx, void *parm) { struct curl_fetch_info *f = (struct curl_fetch_info *) parm; SSL_CTX *sslctx = _sslctx; - long options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + long options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1; /* set verify callback for each certificate in chain */ SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, fetch_curl_verify_callback); @@ -544,12 +818,9 @@ fetch_curl_sslctxfun(CURL *curl_handle, void *_sslctx, void *parm) parm); if (f->downgrade_tls) { - /* Disable TLS 1.1/1.2 if the server can't cope with them */ -#ifdef SSL_OP_NO_TLSv1_1 - options |= SSL_OP_NO_TLSv1_1; -#endif -#ifdef SSL_OP_NO_TLSv1_2 - options |= SSL_OP_NO_TLSv1_2; + /* Disable TLS 1.3 if the server can't cope with it */ +#ifdef SSL_OP_NO_TLSv1_3 + options |= SSL_OP_NO_TLSv1_3; #endif #ifdef SSL_MODE_SEND_FALLBACK_SCSV /* Ensure server rejects the connection if downgraded too far */ @@ -559,10 +830,334 @@ fetch_curl_sslctxfun(CURL *curl_handle, void *_sslctx, void *parm) SSL_CTX_set_options(sslctx, options); +#ifdef SSL_OP_NO_TICKET + SSL_CTX_clear_options(sslctx, SSL_OP_NO_TICKET); +#endif + return CURLE_OK; } +#endif /* WITH_OPENSSL */ + + +/** + * Report the certificate information in the fetch to the users + */ +static void +fetch_curl_report_certs_upstream(struct curl_fetch_info *f) +{ + fetch_msg msg; + struct cert_chain *chain; + + chain = hashmap_lookup(curl_fetch_ssl_hashmap, f->url); + + if (chain != NULL) { + msg.type = FETCH_CERTS; + msg.data.chain = chain; + + fetch_send_callback(&msg, f->fetch_handle); + } + + f->sent_ssl_chain = true; +} + +#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 depricated curl_formadd */ + +/** + * curl mime data context + */ +struct curl_mime_ctx { + char *buffer; + curl_off_t size; + curl_off_t position; +}; + +static size_t mime_data_read_callback(char *buffer, size_t size, size_t nitems, void *arg) +{ + struct curl_mime_ctx *mctx = (struct curl_mime_ctx *) arg; + curl_off_t sz = mctx->size - mctx->position; + + nitems *= size; + if(sz > (curl_off_t)nitems) { + sz = nitems; + } + if(sz) { + memcpy(buffer, mctx->buffer + mctx->position, sz); + } + mctx->position += sz; + return sz; +} + +static int mime_data_seek_callback(void *arg, curl_off_t offset, int origin) +{ + struct curl_mime_ctx *mctx = (struct curl_mime_ctx *) arg; + + switch(origin) { + case SEEK_END: + offset += mctx->size; + break; + case SEEK_CUR: + offset += mctx->position; + break; + } + + if(offset < 0) { + return CURL_SEEKFUNC_FAIL; + } + mctx->position = offset; + return CURL_SEEKFUNC_OK; +} + +static void mime_data_free_callback(void *arg) +{ + struct curl_mime_ctx *mctx = (struct curl_mime_ctx *) arg; + free(mctx); +} + +/** + * Convert a POST data list to a libcurl curl_mime. + * + * \param chandle curl fetch handle. + * \param multipart limked list of struct ::fetch_multipart forming post data. + */ +static curl_mime * +fetch_curl_postdata_convert(CURL *chandle, + const struct fetch_multipart_data *multipart) +{ + curl_mime *cmime; + curl_mimepart *part; + CURLcode code = CURLE_OK; + size_t value_len; + + cmime = curl_mime_init(chandle); + if (cmime == NULL) { + NSLOG(netsurf, WARNING, "postdata conversion failed to curl mime context"); + return NULL; + } + + /* iterate post data */ + for (; multipart != NULL; multipart = multipart->next) { + part = curl_mime_addpart(cmime); + if (part == NULL) { + goto convert_failed; + } + + code = curl_mime_name(part, multipart->name); + if (code != CURLE_OK) { + goto convert_failed; + } + + value_len = strlen(multipart->value); + + if (multipart->file && value_len==0) { + /* file entries with no filename require special handling */ + code=curl_mime_data(part, multipart->value, value_len); + if (code != CURLE_OK) { + goto convert_failed; + } + + code = curl_mime_filename(part, ""); + if (code != CURLE_OK) { + goto convert_failed; + } + + code = curl_mime_type(part, "application/octet-stream"); + if (code != CURLE_OK) { + goto convert_failed; + } + + } else if(multipart->file) { + /* file entry */ + nserror ret; + char *leafname = NULL; + char *mimetype = NULL; + + code = curl_mime_filedata(part, multipart->rawfile); + if (code != CURLE_OK) { + goto convert_failed; + } + + ret = guit->file->basename(multipart->value, &leafname, NULL); + if (ret != NSERROR_OK) { + goto convert_failed; + } + code = curl_mime_filename(part, leafname); + free(leafname); + if (code != CURLE_OK) { + goto convert_failed; + } + + mimetype = guit->fetch->mimetype(multipart->value); + if (mimetype == NULL) { + mimetype=strdup("text/plain"); + } + if (mimetype == NULL) { + goto convert_failed; + } + code = curl_mime_type(part, mimetype); + free(mimetype); + if (code != CURLE_OK) { + goto convert_failed; + } + + } else { + /* make the curl mime reference the existing multipart + * data which requires use of a callback and context. + */ + struct curl_mime_ctx *cb_ctx; + cb_ctx = malloc(sizeof(struct curl_mime_ctx)); + if (cb_ctx == NULL) { + goto convert_failed; + } + cb_ctx->buffer = multipart->value; + cb_ctx->size = value_len; + cb_ctx->position = 0; + code = curl_mime_data_cb(part, + value_len, + mime_data_read_callback, + mime_data_seek_callback, + mime_data_free_callback, + cb_ctx); + if (code != CURLE_OK) { + free(cb_ctx); + goto convert_failed; + } + } + } + + return cmime; + +convert_failed: + NSLOG(netsurf, WARNING, "postdata conversion failed with curl code: %d", code); + curl_mime_free(cmime); + return NULL; +} + +#else /* LIBCURL_VERSION_NUM >= 0x073800 */ + +/** + * Convert a list of struct ::fetch_multipart_data to a list of + * struct curl_httppost for libcurl. + */ +static struct curl_httppost * +fetch_curl_postdata_convert(CURL *chandle, + const struct fetch_multipart_data *control) +{ + struct curl_httppost *post = NULL, *last = NULL; + CURLFORMcode code; + nserror ret; + + for (; control; control = control->next) { + if (control->file) { + char *leafname = NULL; + ret = guit->file->basename(control->value, &leafname, NULL); + if (ret != NSERROR_OK) { + continue; + } + + /* We have to special case filenames of "", so curl + * a) actually attempts the fetch and + * b) doesn't attempt to open the file "" + */ + if (control->value[0] == '\0') { + /* dummy buffer - needs to be static so + * pointer's still valid when we go out + * of scope (not that libcurl should be + * attempting to access it, of course). + */ + static char buf; + + code = curl_formadd(&post, &last, + CURLFORM_COPYNAME, control->name, + CURLFORM_BUFFER, control->value, + /* needed, as basename("") == "." */ + CURLFORM_FILENAME, "", + CURLFORM_BUFFERPTR, &buf, + CURLFORM_BUFFERLENGTH, 0, + CURLFORM_CONTENTTYPE, + "application/octet-stream", + CURLFORM_END); + if (code != CURL_FORMADD_OK) + NSLOG(netsurf, INFO, + "curl_formadd: %d (%s)", code, + control->name); + } else { + char *mimetype = guit->fetch->mimetype(control->value); + code = curl_formadd(&post, &last, + CURLFORM_COPYNAME, control->name, + CURLFORM_FILE, control->rawfile, + CURLFORM_FILENAME, leafname, + CURLFORM_CONTENTTYPE, + (mimetype != 0 ? mimetype : "text/plain"), + CURLFORM_END); + if (code != CURL_FORMADD_OK) + NSLOG(netsurf, INFO, + "curl_formadd: %d (%s=%s)", + code, + control->name, + control->value); + free(mimetype); + } + free(leafname); + } else { + code = curl_formadd(&post, &last, + CURLFORM_COPYNAME, control->name, + CURLFORM_COPYCONTENTS, control->value, + CURLFORM_END); + if (code != CURL_FORMADD_OK) + NSLOG(netsurf, INFO, + "curl_formadd: %d (%s=%s)", code, + control->name, control->value); + } + } + + return post; +} + +#endif /* LIBCURL_VERSION_NUM >= 0x073800 */ + +/** + * Setup multipart post data + */ +static CURLcode fetch_curl_set_postdata(struct curl_fetch_info *f) +{ + CURLcode code = CURLE_OK; + +#undef SETOPT +#define SETOPT(option, value) { \ + code = curl_easy_setopt(f->curl_handle, option, value); \ + if (code != CURLE_OK) \ + return code; \ + } + + switch (f->postdata->type) { + case FETCH_POSTDATA_NONE: + SETOPT(CURLOPT_POSTFIELDS, NULL); + SETOPT(NSCURL_POSTDATA_CURLOPT, NULL); + SETOPT(CURLOPT_HTTPGET, 1L); + break; + + case FETCH_POSTDATA_URLENC: + SETOPT(NSCURL_POSTDATA_CURLOPT, NULL); + SETOPT(CURLOPT_HTTPGET, 0L); + SETOPT(CURLOPT_POSTFIELDS, f->postdata->data.urlenc); + break; + + case FETCH_POSTDATA_MULTIPART: + SETOPT(CURLOPT_POSTFIELDS, NULL); + SETOPT(CURLOPT_HTTPGET, 0L); + if (f->curl_postdata == NULL) { + f->curl_postdata = + fetch_curl_postdata_convert(f->curl_handle, + f->postdata->data.multipart); + } + SETOPT(NSCURL_POSTDATA_CURLOPT, f->curl_postdata); + break; + } + return code; +} + /** * Set options specific for a fetch. * @@ -585,21 +1180,11 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f) SETOPT(CURLOPT_PRIVATE, f); SETOPT(CURLOPT_WRITEDATA, f); SETOPT(CURLOPT_WRITEHEADER, f); - SETOPT(CURLOPT_PROGRESSDATA, f); - SETOPT(CURLOPT_REFERER, fetch_get_referer_to_send(f->fetch_handle)); + SETOPT(NSCURLOPT_PROGRESS_DATA, f); SETOPT(CURLOPT_HTTPHEADER, f->headers); - if (f->post_urlenc) { - SETOPT(CURLOPT_HTTPPOST, NULL); - SETOPT(CURLOPT_HTTPGET, 0L); - SETOPT(CURLOPT_POSTFIELDS, f->post_urlenc); - } else if (f->post_multipart) { - SETOPT(CURLOPT_POSTFIELDS, NULL); - SETOPT(CURLOPT_HTTPGET, 0L); - SETOPT(CURLOPT_HTTPPOST, f->post_multipart); - } else { - SETOPT(CURLOPT_POSTFIELDS, NULL); - SETOPT(CURLOPT_HTTPPOST, NULL); - SETOPT(CURLOPT_HTTPGET, 1L); + code = fetch_curl_set_postdata(f); + if (code != CURLE_OK) { + return code; } f->cookie_string = urldb_get_cookie(f->url, true); @@ -610,7 +1195,7 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f) } if ((auth = urldb_get_auth_details(f->url, NULL)) != NULL) { - SETOPT(CURLOPT_HTTPAUTH, CURLAUTH_ANY); + SETOPT(CURLOPT_HTTPAUTH, CURLAUTH_BASIC); SETOPT(CURLOPT_USERPWD, auth); } else { SETOPT(CURLOPT_USERPWD, NULL); @@ -646,8 +1231,14 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f) SETOPT(CURLOPT_PROXY, NULL); } - /* Disable SSL session ID caching, as some servers can't cope. */ - SETOPT(CURLOPT_SSL_SESSIONID_CACHE, 0); + + if (curl_with_openssl) { + SETOPT(CURLOPT_SSL_CIPHER_LIST, + f->downgrade_tls ? CIPHER_LIST_LEGACY : CIPHER_LIST); + } + + /* Force-enable SSL session ID caching, as some distros are odd. */ + SETOPT(CURLOPT_SSL_SESSIONID_CACHE, 1); if (urldb_get_cert_permissions(f->url)) { /* Disable certificate verification */ @@ -661,10 +1252,12 @@ static CURLcode fetch_curl_set_options(struct curl_fetch_info *f) /* do verification */ SETOPT(CURLOPT_SSL_VERIFYPEER, 1L); SETOPT(CURLOPT_SSL_VERIFYHOST, 2L); +#ifdef WITH_OPENSSL if (curl_with_openssl) { SETOPT(CURLOPT_SSL_CTX_FUNCTION, fetch_curl_sslctxfun); SETOPT(CURLOPT_SSL_CTX_DATA, f); } +#endif } return CURLE_OK; @@ -689,6 +1282,9 @@ fetch_curl_initiate_fetch(struct curl_fetch_info *fetch, CURL *handle) code = fetch_curl_set_options(fetch); if (code != CURLE_OK) { fetch->curl_handle = 0; + /* The handle maybe went bad, eat it */ + NSLOG(netsurf, WARNING, "cURL handle maybe went bad, retry later"); + curl_easy_cleanup(handle); return false; } @@ -726,6 +1322,10 @@ static CURL *fetch_curl_get_handle(lwc_string *host) static bool fetch_curl_start(void *vfetch) { struct curl_fetch_info *fetch = (struct curl_fetch_info*)vfetch; + if (inside_curl) { + NSLOG(netsurf, DEBUG, "Deferring fetch because we're inside cURL"); + return false; + } return fetch_curl_initiate_fetch(fetch, fetch_curl_get_handle(fetch->host)); } @@ -782,23 +1382,6 @@ static void fetch_curl_cache_handle(CURL *handle, lwc_string *host) /** - * Abort a fetch. - */ -static void fetch_curl_abort(void *vf) -{ - struct curl_fetch_info *f = (struct curl_fetch_info *)vf; - assert(f); - NSLOG(netsurf, INFO, "fetch %p, url '%s'", f, nsurl_access(f->url)); - if (f->curl_handle) { - f->abort = true; - } else { - fetch_remove_from_queues(f->fetch_handle); - fetch_free(f->fetch_handle); - } -} - - -/** * Clean up the provided fetch object and free it. * * Will prod the queue afterwards to allow pending requests to be initiated. @@ -825,6 +1408,30 @@ static void fetch_curl_stop(struct curl_fetch_info *f) /** + * Abort a fetch. + */ +static void fetch_curl_abort(void *vf) +{ + struct curl_fetch_info *f = (struct curl_fetch_info *)vf; + assert(f); + NSLOG(netsurf, INFO, "fetch %p, url '%s'", f, nsurl_access(f->url)); + if (f->curl_handle) { + if (inside_curl) { + NSLOG(netsurf, DEBUG, "Deferring cleanup"); + f->abort = true; + } else { + NSLOG(netsurf, DEBUG, "Immediate abort"); + fetch_curl_stop(f); + fetch_free(f->fetch_handle); + } + } else { + fetch_remove_from_queues(f->fetch_handle); + fetch_free(f->fetch_handle); + } +} + + +/** * Free a fetch structure and associated resources. */ static void fetch_curl_free(void *vf) @@ -843,13 +1450,14 @@ static void fetch_curl_free(void *vf) if (f->headers) { curl_slist_free_all(f->headers); } - free(f->post_urlenc); - if (f->post_multipart) { - curl_formfree(f->post_multipart); - } + fetch_curl_free_postdata(f->postdata); + NSCURL_POSTDATA_FREE(f->curl_postdata); - for (i = 0; i < MAX_CERTS && f->cert_data[i].cert; i++) { - ns_X509_free(f->cert_data[i].cert); + /* free certificate data */ + for (i = 0; i < MAX_CERT_DEPTH; i++) { + if (f->cert_data[i].cert != NULL) { + X509_free(f->cert_data[i].cert); + } } free(f); @@ -878,7 +1486,7 @@ static bool fetch_curl_process_headers(struct curl_fetch_info *f) http_code = f->http_code; NSLOG(netsurf, INFO, "HTTP status code %li", http_code); - if (http_code == 304 && !f->post_urlenc && !f->post_multipart) { + if ((http_code == 304) && (f->postdata->type==FETCH_POSTDATA_NONE)) { /* Not Modified && GET request */ msg.type = FETCH_NOTMODIFIED; fetch_send_callback(&msg, f->fetch_handle); @@ -917,114 +1525,6 @@ 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 */ - ns_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()). @@ -1041,7 +1541,6 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result) struct curl_fetch_info *f; char **_hideous_hack = (char **) (void *) &f; CURLcode code; - struct cert_info certs[MAX_CERTS]; /* find the structure associated with this fetch */ /* For some reason, cURL thinks CURLINFO_PRIVATE should be a string?! */ @@ -1085,13 +1584,11 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result) */ ; } else if (result == CURLE_SSL_PEER_CERTIFICATE || - result == CURLE_SSL_CACERT) { - /* CURLE_SSL_PEER_CERTIFICATE renamed to - * CURLE_PEER_FAILED_VERIFICATION + result == CURLE_SSL_CACERT) { + /* Some kind of failure has occurred. If we don't know + * what happened, we'll have reported unknown errors up + * to the user already via the certificate chain error fields. */ - memset(certs, 0, sizeof(certs)); - memcpy(certs, f->cert_data, sizeof(certs)); - memset(f->cert_data, 0, sizeof(f->cert_data)); cert = true; } else { NSLOG(netsurf, INFO, "Unknown cURL response code %d", result); @@ -1100,6 +1597,10 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result) fetch_curl_stop(f); + if (f->sent_ssl_chain == false) { + fetch_curl_report_certs_upstream(f); + } + if (abort_fetch) { ; /* fetch was aborted: no callback */ } else if (finished) { @@ -1108,7 +1609,9 @@ static void fetch_curl_done(CURL *curl_handle, CURLcode result) fetch_send_callback(&msg, f->fetch_handle); } else if (cert) { /* user needs to validate certificate with issue */ - curl_start_cert_validate(f, certs); + fetch_msg msg; + msg.type = FETCH_CERT_ERR; + fetch_send_callback(&msg, f->fetch_handle); } else if (error) { fetch_msg msg; switch (result) { @@ -1158,7 +1661,7 @@ static void fetch_curl_poll(lwc_string *scheme_ignored) &exc_fd_set, &max_fd); assert(codem == CURLM_OK); - NSLOG(netsurf, INFO, + NSLOG(netsurf, DEEPDEBUG, "Curl file descriptor states (maxfd=%i):", max_fd); for (i = 0; i <= max_fd; i++) { bool read = false; @@ -1175,7 +1678,7 @@ static void fetch_curl_poll(lwc_string *scheme_ignored) error = true; } if (read || write || error) { - NSLOG(netsurf, INFO, " fd %i: %s %s %s", i, + NSLOG(netsurf, DEEPDEBUG, " fd %i: %s %s %s", i, read ? "read" : " ", write ? "write" : " ", error ? "error" : " "); @@ -1184,12 +1687,13 @@ static void fetch_curl_poll(lwc_string *scheme_ignored) } /* do any possible work on the current fetches */ + inside_curl = true; do { codem = curl_multi_perform(fetch_curl_multi, &running); if (codem != CURLM_OK && codem != CURLM_CALL_MULTI_PERFORM) { - NSLOG(netsurf, INFO, "curl_multi_perform: %i %s", + NSLOG(netsurf, WARNING, + "curl_multi_perform: %i %s", codem, curl_multi_strerror(codem)); - guit->misc->warning("MiscError", curl_multi_strerror(codem)); return; } } while (codem == CURLM_CALL_MULTI_PERFORM); @@ -1207,6 +1711,7 @@ static void fetch_curl_poll(lwc_string *scheme_ignored) } curl_msg = curl_multi_info_read(fetch_curl_multi, &queue); } + inside_curl = false; } @@ -1217,10 +1722,10 @@ static void fetch_curl_poll(lwc_string *scheme_ignored) */ static int fetch_curl_progress(void *clientp, - double dltotal, - double dlnow, - double ultotal, - double ulnow) + NSCURL_PROGRESS_T dltotal, + NSCURL_PROGRESS_T dlnow, + NSCURL_PROGRESS_T ultotal, + NSCURL_PROGRESS_T ulnow) { static char fetch_progress_buffer[256]; /**< Progress buffer for cURL */ struct curl_fetch_info *f = (struct curl_fetch_info *) clientp; @@ -1261,20 +1766,50 @@ fetch_curl_progress(void *clientp, /** - * Ignore everything given to it. - * - * Used to ignore cURL debug. + * Format curl debug for nslog */ -static int fetch_curl_ignore_debug(CURL *handle, - curl_infotype type, - char *data, - size_t size, - void *userptr) +static int +fetch_curl_debug(CURL *handle, + curl_infotype type, + char *data, + size_t size, + void *userptr) { + static const char s_infotype[CURLINFO_END][3] = { + "* ", "< ", "> ", "{ ", "} ", "{ ", "} " + }; + switch(type) { + case CURLINFO_TEXT: + case CURLINFO_HEADER_OUT: + case CURLINFO_HEADER_IN: + NSLOG(fetch, DEBUG, "%s%.*s", s_infotype[type], (int)size - 1, data); + break; + + default: + break; + } return 0; } +static curl_socket_t fetch_curl_socket_open(void *clientp, + curlsocktype purpose, struct curl_sockaddr *address) +{ + (void) clientp; + (void) purpose; + + return (curl_socket_t) guit->fetch->socket_open( + address->family, address->socktype, + address->protocol); +} + +static int fetch_curl_socket_close(void *clientp, curl_socket_t item) +{ + (void) clientp; + + return guit->fetch->socket_close((int) item); +} + /** * Callback function for cURL. */ @@ -1338,6 +1873,10 @@ fetch_curl_header(char *data, size_t size, size_t nmemb, void *_f) return 0; } + if (f->sent_ssl_chain == false) { + fetch_curl_report_certs_upstream(f); + } + msg.type = FETCH_HEADER; msg.data.header_or_data.buf = (const uint8_t *) data; msg.data.header_or_data.len = size; @@ -1441,6 +1980,18 @@ nserror fetch_curl_register(void) .finalise = fetch_curl_finalise }; +#if LIBCURL_VERSION_NUM >= 0x073800 + /* version 7.56.0 can select which SSL backend to use */ + CURLsslset setres; + + setres = curl_global_sslset(CURLSSLBACKEND_OPENSSL, NULL, NULL); + if (setres == CURLSSLSET_OK) { + curl_with_openssl = true; + } else { + curl_with_openssl = false; + } +#endif + NSLOG(netsurf, INFO, "curl_version %s", curl_version()); code = curl_global_init(CURL_GLOBAL_ALL); @@ -1465,8 +2016,10 @@ nserror fetch_curl_register(void) #undef SETOPT #define SETOPT(option, value) \ mcode = curl_multi_setopt(fetch_curl_multi, option, value); \ - if (mcode != CURLM_OK) \ - goto curl_multi_setopt_failed; + if (mcode != CURLM_OK) { \ + NSLOG(netsurf, ERROR, "attempting curl_multi_setopt(%s, ...)", #option); \ + goto curl_multi_setopt_failed; \ + } SETOPT(CURLMOPT_MAXCONNECTS, maxconnects); SETOPT(CURLMOPT_MAX_TOTAL_CONNECTIONS, maxconnects); @@ -1486,21 +2039,25 @@ nserror fetch_curl_register(void) #undef SETOPT #define SETOPT(option, value) \ code = curl_easy_setopt(fetch_blank_curl, option, value); \ - if (code != CURLE_OK) \ - goto curl_easy_setopt_failed; - - if (verbose_log) { - SETOPT(CURLOPT_VERBOSE, 1); - } else { - SETOPT(CURLOPT_VERBOSE, 0); + if (code != CURLE_OK) { \ + NSLOG(netsurf, ERROR, "attempting curl_easy_setopt(%s, ...)", #option); \ + goto curl_easy_setopt_failed; \ } + SETOPT(CURLOPT_ERRORBUFFER, fetch_error_buffer); + SETOPT(CURLOPT_DEBUGFUNCTION, fetch_curl_debug); if (nsoption_bool(suppress_curl_debug)) { - SETOPT(CURLOPT_DEBUGFUNCTION, fetch_curl_ignore_debug); + SETOPT(CURLOPT_VERBOSE, 0); + } else { + SETOPT(CURLOPT_VERBOSE, 1); } + + /* Currently we explode if curl uses HTTP2, so force 1.1. */ + SETOPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + SETOPT(CURLOPT_WRITEFUNCTION, fetch_curl_data); SETOPT(CURLOPT_HEADERFUNCTION, fetch_curl_header); - SETOPT(CURLOPT_PROGRESSFUNCTION, fetch_curl_progress); + SETOPT(NSCURLOPT_PROGRESS_FUNCTION, fetch_curl_progress); SETOPT(CURLOPT_NOPROGRESS, 0); SETOPT(CURLOPT_USERAGENT, user_agent_string()); SETOPT(CURLOPT_ENCODING, "gzip"); @@ -1508,6 +2065,8 @@ nserror fetch_curl_register(void) SETOPT(CURLOPT_LOW_SPEED_TIME, 180L); SETOPT(CURLOPT_NOSIGNAL, 1L); SETOPT(CURLOPT_CONNECTTIMEOUT, nsoption_uint(curl_fetch_timeout)); + SETOPT(CURLOPT_OPENSOCKETFUNCTION, fetch_curl_socket_open); + SETOPT(CURLOPT_CLOSESOCKETFUNCTION, fetch_curl_socket_close); if (nsoption_charp(ca_bundle) && strcmp(nsoption_charp(ca_bundle), "")) { @@ -1520,12 +2079,32 @@ nserror fetch_curl_register(void) SETOPT(CURLOPT_CAPATH, nsoption_charp(ca_path)); } - /* Detect whether the SSL CTX function API works */ - curl_with_openssl = true; - code = curl_easy_setopt(fetch_blank_curl, - CURLOPT_SSL_CTX_FUNCTION, NULL); +#if LIBCURL_VERSION_NUM < 0x073800 + /* + * before 7.56.0 Detect openssl from whether the SSL CTX + * function API works + */ + code = curl_easy_setopt(fetch_blank_curl, CURLOPT_SSL_CTX_FUNCTION, NULL); if (code != CURLE_OK) { curl_with_openssl = false; + } else { + curl_with_openssl = true; + } +#endif + + if (curl_with_openssl) { + /* only set the cipher list with openssl otherwise the + * fetch fails with "Unknown cipher in list" + */ +#if LIBCURL_VERSION_NUM >= 0x073d00 + /* Need libcurl 7.61.0 or later built against OpenSSL with + * TLS1.3 support */ + code = curl_easy_setopt(fetch_blank_curl, + CURLOPT_TLS13_CIPHERS, CIPHER_SUITES); + if (code != CURLE_OK && code != CURLE_NOT_BUILT_IN) + goto curl_easy_setopt_failed; +#endif + SETOPT(CURLOPT_SSL_CIPHER_LIST, CIPHER_LIST); } NSLOG(netsurf, INFO, "cURL %slinked against openssl", @@ -1535,6 +2114,12 @@ nserror fetch_curl_register(void) data = curl_version_info(CURLVERSION_NOW); + curl_fetch_ssl_hashmap = hashmap_create(&curl_fetch_ssl_hashmap_parameters); + if (curl_fetch_ssl_hashmap == NULL) { + NSLOG(netsurf, CRITICAL, "Unable to initialise SSL certificate hashmap"); + return NSERROR_NOMEM; + } + for (i = 0; data->protocols[i]; i++) { if (strcmp(data->protocols[i], "http") == 0) { scheme = lwc_string_ref(corestring_lwc_http); |