From 06d446098c2fbc23bb7dc2159568c39db1623c8b Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 27 Jul 2023 14:27:36 +0100 Subject: [PATCH] curl: Move configuration code to a separate file Previously making a change to the curl handle configuration involved updating code across several files. Centralise this code in one place. After this commit, configuration changes only affect plugins/curl/config.c, although you will still need to change the documentation in nbdkit-curl-plugin.pod. This change is just refactoring. No change in functionality is intended. (cherry picked from commit ef762737bcad713f6a55092d88285bdf466a781c) --- plugins/curl/Makefile.am | 1 + plugins/curl/config.c | 974 +++++++++++++++++++++++++++++++++++++++ plugins/curl/curl.c | 476 +------------------ plugins/curl/curldefs.h | 38 +- plugins/curl/pool.c | 455 +----------------- 5 files changed, 998 insertions(+), 946 deletions(-) create mode 100644 plugins/curl/config.c diff --git a/plugins/curl/Makefile.am b/plugins/curl/Makefile.am index f5f1cc1a..7529fb2f 100644 --- a/plugins/curl/Makefile.am +++ b/plugins/curl/Makefile.am @@ -38,6 +38,7 @@ if HAVE_CURL plugin_LTLIBRARIES = nbdkit-curl-plugin.la nbdkit_curl_plugin_la_SOURCES = \ + config.c \ curldefs.h \ curl.c \ pool.c \ diff --git a/plugins/curl/config.c b/plugins/curl/config.c new file mode 100644 index 00000000..742d6080 --- /dev/null +++ b/plugins/curl/config.c @@ -0,0 +1,974 @@ +/* nbdkit + * Copyright Red Hat + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of Red Hat nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "ascii-ctype.h" +#include "ascii-string.h" +#include "cleanup.h" + +#include "curldefs.h" + +/* Plugin configuration. */ +const char *url = NULL; /* required */ + +static const char *cainfo = NULL; +static const char *capath = NULL; +static char *cookie = NULL; +static const char *cookiefile = NULL; +static const char *cookiejar = NULL; +static bool followlocation = true; +static struct curl_slist *headers = NULL; +static long http_version = CURL_HTTP_VERSION_NONE; +static long ipresolve = CURL_IPRESOLVE_WHATEVER; +static char *password = NULL; +#ifndef HAVE_CURLOPT_PROTOCOLS_STR +static long protocols = CURLPROTO_ALL; +#else +static const char *protocols = NULL; +#endif +static const char *proxy = NULL; +static char *proxy_password = NULL; +static const char *proxy_user = NULL; +static struct curl_slist *resolves = NULL; +static bool sslverify = true; +static const char *ssl_cipher_list = NULL; +static long ssl_version = CURL_SSLVERSION_DEFAULT; +static const char *tls13_ciphers = NULL; +static bool tcp_keepalive = false; +static bool tcp_nodelay = true; +static uint32_t timeout = 0; +static const char *unix_socket_path = NULL; +static const char *user = NULL; +static const char *user_agent = NULL; + +static int debug_cb (CURL *handle, curl_infotype type, + const char *data, size_t size, void *); +static size_t write_cb (char *ptr, size_t size, size_t nmemb, void *opaque); +static size_t read_cb (void *ptr, size_t size, size_t nmemb, void *opaque); +static int get_content_length_accept_range (struct curl_handle *ch); +static bool try_fallback_GET_method (struct curl_handle *ch); +static size_t header_cb (void *ptr, size_t size, size_t nmemb, void *opaque); +static size_t error_cb (char *ptr, size_t size, size_t nmemb, void *opaque); + +/* Use '-D curl.verbose=1' to set. */ +NBDKIT_DLL_PUBLIC int curl_debug_verbose = 0; + +void +unload_config (void) +{ + free (cookie); + if (headers) + curl_slist_free_all (headers); + free (password); + free (proxy_password); + if (resolves) + curl_slist_free_all (resolves); +} + +#ifndef HAVE_CURLOPT_PROTOCOLS_STR +/* See */ +static struct { const char *name; long bitmask; } curl_protocols[] = { + { "http", CURLPROTO_HTTP }, + { "https", CURLPROTO_HTTPS }, + { "ftp", CURLPROTO_FTP }, + { "ftps", CURLPROTO_FTPS }, + { "scp", CURLPROTO_SCP }, + { "sftp", CURLPROTO_SFTP }, + { "telnet", CURLPROTO_TELNET }, + { "ldap", CURLPROTO_LDAP }, + { "ldaps", CURLPROTO_LDAPS }, + { "dict", CURLPROTO_DICT }, + { "file", CURLPROTO_FILE }, + { "tftp", CURLPROTO_TFTP }, + { "imap", CURLPROTO_IMAP }, + { "imaps", CURLPROTO_IMAPS }, + { "pop3", CURLPROTO_POP3 }, + { "pop3s", CURLPROTO_POP3S }, + { "smtp", CURLPROTO_SMTP }, + { "smtps", CURLPROTO_SMTPS }, + { "rtsp", CURLPROTO_RTSP }, + { "rtmp", CURLPROTO_RTMP }, + { "rtmpt", CURLPROTO_RTMPT }, + { "rtmpe", CURLPROTO_RTMPE }, + { "rtmpte", CURLPROTO_RTMPTE }, + { "rtmps", CURLPROTO_RTMPS }, + { "rtmpts", CURLPROTO_RTMPTS }, + { "gopher", CURLPROTO_GOPHER }, +#ifdef CURLPROTO_SMB + { "smb", CURLPROTO_SMB }, +#endif +#ifdef CURLPROTO_SMBS + { "smbs", CURLPROTO_SMBS }, +#endif +#ifdef CURLPROTO_MQTT + { "mqtt", CURLPROTO_MQTT }, +#endif + { NULL } +}; + +/* Parse the protocols parameter. */ +static int +parse_protocols (const char *value) +{ + size_t n, i; + + protocols = 0; + + while (*value) { + n = strcspn (value, ","); + for (i = 0; curl_protocols[i].name != NULL; ++i) { + if (strlen (curl_protocols[i].name) == n && + strncmp (value, curl_protocols[i].name, n) == 0) { + protocols |= curl_protocols[i].bitmask; + goto found; + } + } + nbdkit_error ("protocols: protocol name not found: %.*s", (int) n, value); + return -1; + + found: + value += n; + if (*value == ',') + value++; + } + + if (protocols == 0) { + nbdkit_error ("protocols: empty list of protocols is not allowed"); + return -1; + } + + nbdkit_debug ("curl: protocols: %ld", protocols); + + return 0; +} +#endif /* !HAVE_CURLOPT_PROTOCOLS_STR */ + +/* Called for each key=value passed on the command line. */ +int +curl_config (const char *key, const char *value) +{ + int r; + + if (strcmp (key, "cainfo") == 0) { + cainfo = value; + } + + else if (strcmp (key, "capath") == 0) { + capath = value; + } + + else if (strcmp (key, "connections") == 0) { + if (nbdkit_parse_unsigned ("connections", value, + &connections) == -1) + return -1; + if (connections == 0) { + nbdkit_error ("connections parameter must not be 0"); + return -1; + } + } + + else if (strcmp (key, "cookie") == 0) { + free (cookie); + if (nbdkit_read_password (value, &cookie) == -1) + return -1; + } + + else if (strcmp (key, "cookiefile") == 0) { + /* Reject cookiefile=- because it will cause libcurl to try to + * read from stdin when we connect. + */ + if (strcmp (value, "-") == 0) { + nbdkit_error ("cookiefile parameter cannot be \"-\""); + return -1; + } + cookiefile = value; + } + + else if (strcmp (key, "cookiejar") == 0) { + /* Reject cookiejar=- because it will cause libcurl to try to + * write to stdout. + */ + if (strcmp (value, "-") == 0) { + nbdkit_error ("cookiejar parameter cannot be \"-\""); + return -1; + } + cookiejar = value; + } + + else if (strcmp (key, "cookie-script") == 0) { + cookie_script = value; + } + + else if (strcmp (key, "cookie-script-renew") == 0) { + if (nbdkit_parse_unsigned ("cookie-script-renew", value, + &cookie_script_renew) == -1) + return -1; + } + + else if (strcmp (key, "followlocation") == 0) { + r = nbdkit_parse_bool (value); + if (r == -1) + return -1; + followlocation = r; + } + + else if (strcmp (key, "header") == 0) { + headers = curl_slist_append (headers, value); + if (headers == NULL) { + nbdkit_error ("curl_slist_append: %m"); + return -1; + } + } + + else if (strcmp (key, "header-script") == 0) { + header_script = value; + } + + else if (strcmp (key, "header-script-renew") == 0) { + if (nbdkit_parse_unsigned ("header-script-renew", value, + &header_script_renew) == -1) + return -1; + } + + else if (strcmp (key, "http-version") == 0) { + if (strcmp (value, "none") == 0) + http_version = CURL_HTTP_VERSION_NONE; + else if (strcmp (value, "1.0") == 0) + http_version = CURL_HTTP_VERSION_1_0; + else if (strcmp (value, "1.1") == 0) + http_version = CURL_HTTP_VERSION_1_1; +#ifdef HAVE_CURL_HTTP_VERSION_2_0 + else if (strcmp (value, "2.0") == 0) + http_version = CURL_HTTP_VERSION_2_0; +#endif +#ifdef HAVE_CURL_HTTP_VERSION_2TLS + else if (strcmp (value, "2TLS") == 0) + http_version = CURL_HTTP_VERSION_2TLS; +#endif +#ifdef HAVE_CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE + else if (strcmp (value, "2-prior-knowledge") == 0) + http_version = CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE; +#endif +#ifdef HAVE_CURL_HTTP_VERSION_3 + else if (strcmp (value, "3") == 0) + http_version = CURL_HTTP_VERSION_3; +#endif +#ifdef HAVE_CURL_HTTP_VERSION_3ONLY + else if (strcmp (value, "3only") == 0) + http_version = CURL_HTTP_VERSION_3ONLY; +#endif + else { + nbdkit_error ("unknown http-version: %s", value); + return -1; + } + } + + else if (strcmp (key, "ipresolve") == 0) { + if (strcmp (value, "any") == 0 || strcmp (value, "whatever") == 0) + ipresolve = CURL_IPRESOLVE_WHATEVER; + else if (strcmp (value, "v4") == 0 || strcmp (value, "4") == 0) + ipresolve = CURL_IPRESOLVE_V4; + else if (strcmp (value, "v6") == 0 || strcmp (value, "6") == 0) + ipresolve = CURL_IPRESOLVE_V6; + else { + nbdkit_error ("unknown ipresolve: %s", value); + return -1; + } + } + + else if (strcmp (key, "password") == 0) { + free (password); + if (nbdkit_read_password (value, &password) == -1) + return -1; + } + + else if (strcmp (key, "protocols") == 0) { +#ifndef HAVE_CURLOPT_PROTOCOLS_STR + if (parse_protocols (value) == -1) + return -1; +#else + protocols = value; +#endif + } + + else if (strcmp (key, "proxy") == 0) { + proxy = value; + } + + else if (strcmp (key, "proxy-password") == 0) { + free (proxy_password); + if (nbdkit_read_password (value, &proxy_password) == -1) + return -1; + } + + else if (strcmp (key, "proxy-user") == 0) + proxy_user = value; + + else if (strcmp (key, "resolve") == 0) { + resolves = curl_slist_append (headers, value); + if (resolves == NULL) { + nbdkit_error ("curl_slist_append: %m"); + return -1; + } + } + + else if (strcmp (key, "sslverify") == 0) { + r = nbdkit_parse_bool (value); + if (r == -1) + return -1; + sslverify = r; + } + + else if (strcmp (key, "ssl-version") == 0) { + if (strcmp (value, "default") == 0) + ssl_version = CURL_SSLVERSION_DEFAULT; + else if (strcmp (value, "tlsv1") == 0) + ssl_version = CURL_SSLVERSION_TLSv1; + else if (strcmp (value, "sslv2") == 0) + ssl_version = CURL_SSLVERSION_SSLv2; + else if (strcmp (value, "sslv3") == 0) + ssl_version = CURL_SSLVERSION_SSLv3; + else if (strcmp (value, "tlsv1.0") == 0) + ssl_version = CURL_SSLVERSION_TLSv1_0; + else if (strcmp (value, "tlsv1.1") == 0) + ssl_version = CURL_SSLVERSION_TLSv1_1; + else if (strcmp (value, "tlsv1.2") == 0) + ssl_version = CURL_SSLVERSION_TLSv1_2; + else if (strcmp (value, "tlsv1.3") == 0) + ssl_version = CURL_SSLVERSION_TLSv1_3; +#ifdef HAVE_CURL_SSLVERSION_MAX_DEFAULT + else if (strcmp (value, "max-default") == 0) + ssl_version = CURL_SSLVERSION_MAX_DEFAULT; +#endif +#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_0 + else if (strcmp (value, "max-tlsv1.0") == 0) + ssl_version = CURL_SSLVERSION_MAX_TLSv1_0; +#endif +#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_1 + else if (strcmp (value, "max-tlsv1.1") == 0) + ssl_version = CURL_SSLVERSION_MAX_TLSv1_1; +#endif +#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_2 + else if (strcmp (value, "max-tlsv1.2") == 0) + ssl_version = CURL_SSLVERSION_MAX_TLSv1_2; +#endif +#ifdef HAVE_CURL_SSLVERSION_MAX_TLSv1_3 + else if (strcmp (value, "max-tlsv1.3") == 0) + ssl_version = CURL_SSLVERSION_MAX_TLSv1_3; +#endif + else { + nbdkit_error ("unknown ssl-version: %s", value); + return -1; + } + } + + else if (strcmp (key, "ssl-cipher-list") == 0) + ssl_cipher_list = value; + + else if (strcmp (key, "tls13-ciphers") == 0) + tls13_ciphers = value; + + else if (strcmp (key, "tcp-keepalive") == 0) { + r = nbdkit_parse_bool (value); + if (r == -1) + return -1; + tcp_keepalive = r; + } + + else if (strcmp (key, "tcp-nodelay") == 0) { + r = nbdkit_parse_bool (value); + if (r == -1) + return -1; + tcp_nodelay = r; + } + + else if (strcmp (key, "timeout") == 0) { + if (nbdkit_parse_uint32_t ("timeout", value, &timeout) == -1) + return -1; +#if LONG_MAX < UINT32_MAX + /* C17 5.2.4.2.1 requires that LONG_MAX is at least 2^31 - 1. + * However a large positive number might still exceed the limit. + */ + if (timeout > LONG_MAX) { + nbdkit_error ("timeout is too large"); + return -1; + } +#endif + } + + else if (strcmp (key, "unix-socket-path") == 0 || + strcmp (key, "unix_socket_path") == 0) + unix_socket_path = value; + + else if (strcmp (key, "url") == 0) + url = value; + + else if (strcmp (key, "user") == 0) + user = value; + + else if (strcmp (key, "user-agent") == 0) + user_agent = value; + + else { + nbdkit_error ("unknown parameter '%s'", key); + return -1; + } + + return 0; +} + +/* Check the user did pass a url parameter. */ +int +curl_config_complete (void) +{ + if (url == NULL) { + nbdkit_error ("you must supply the url= parameter " + "after the plugin name on the command line"); + return -1; + } + + if (headers && header_script) { + nbdkit_error ("header and header-script cannot be used at the same time"); + return -1; + } + + if (!header_script && header_script_renew) { + nbdkit_error ("header-script-renew cannot be used without header-script"); + return -1; + } + + if (cookie && cookie_script) { + nbdkit_error ("cookie and cookie-script cannot be used at the same time"); + return -1; + } + + if (!cookie_script && cookie_script_renew) { + nbdkit_error ("cookie-script-renew cannot be used without cookie-script"); + return -1; + } + + return 0; +} + +const char *curl_config_help = + "cainfo= Path to Certificate Authority file.\n" + "capath= Path to directory with CA certificates.\n" + "connections= Number of libcurl connections to use.\n" + "cookie= Set HTTP/HTTPS cookies.\n" + "cookiefile= Enable cookie processing.\n" + "cookiefile= Read cookies from file.\n" + "cookiejar= Read and write cookies to jar.\n" + "cookie-script=