From: pcarana Date: Tue, 12 Nov 2019 23:42:29 +0000 (-0600) Subject: Add module to support HTTPS requests. X-Git-Tag: v1.2.0~54 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=9b703ab67b55a96c8f897cdcb236609822b3a097;p=thirdparty%2FFORT-validator.git Add module to support HTTPS requests. -New program arguments to configure http requests: +http.user-agent +http.connect-timeout +http.transfer-timeout +http.ca-path -Relocate functions that create a local directory structure from a local URI, so that can be utilized by rsync.c and http.c. -Expose a function to download a file from an HTTPS URL, the function is expected to write the bytes from the response into a file using a callback (defined by the caller). -Add libcurl dependency at makefile and docs (still needs an update for the distinct OSs installation). -Add unit test for the http module. -Update man and docs with new configuration properties. -Update configuration example with new configuration properties. --- diff --git a/configure.ac b/configure.ac index bf364c41..3b9ed766 100644 --- a/configure.ac +++ b/configure.ac @@ -40,6 +40,7 @@ AC_SEARCH_LIBS([backtrace],[execinfo],[], # But I couldn't make check work with AC_SEARCH_LIBS, and (probably due to # typical obscure bullshit autotools reasoning) I have no idea why. PKG_CHECK_MODULES([JANSSON], [jansson]) +PKG_CHECK_MODULES([CURL], [libcurl]) PKG_CHECK_MODULES([CHECK], [check], [usetests=yes], [usetests=no]) AM_CONDITIONAL([USE_TESTS], [test "x$usetests" = "xyes"]) diff --git a/docs/installation.md b/docs/installation.md index 8aa64a33..037098d3 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -28,6 +28,7 @@ The dependencies are 1. [jansson](http://www.digip.org/jansson/) 2. libcrypto (Either [LibreSSL](http://www.libressl.org/) or [OpenSSL](https://www.openssl.org/) >= 1.1) 3. [rsync](http://rsync.samba.org/) +4. [libcurl](https://curl.haxx.se/libcurl/) Fort is currently supported in *64-bit* OS. A 32-bit OS may face the [Year 2038 problem](https://en.wikipedia.org/wiki/Year_2038_problem) when handling dates at certificates, and currently there's no work around for this. @@ -79,7 +80,7 @@ etc. ### Debian version {% highlight bash %} -sudo apt install autoconf automake build-essential libjansson-dev libssl-dev pkg-config rsync +sudo apt install autoconf automake build-essential libjansson-dev libssl-dev pkg-config rsync libcurl4 wget https://github.com/NICMx/FORT-validator/releases/download/v{{ site.fort-latest-version }}/fort-{{ site.fort-latest-version }}.tar.gz tar xvzf fort-{{ site.fort-latest-version }}.tar.gz @@ -233,7 +234,7 @@ In case you wan't a fresh version of Fort validator, there's this third option. The following example is the processo to clone, compile and install in Debian OS. {% highlight bash %} -sudo apt install autoconf automake build-essential git libjansson-dev libssl-dev pkg-config rsync +sudo apt install autoconf automake build-essential git libjansson-dev libssl-dev pkg-config rsync libcurl4-openssl-dev git clone https://github.com/NICMx/FORT-validator.git cd FORT-validator/ diff --git a/docs/usage.md b/docs/usage.md index d646c068..be7872eb 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -34,14 +34,18 @@ command: fort 19. [`--log.output`](#--logoutput) 20. [`--log.color-output`](#--logcolor-output) 21. [`--log.file-name-format`](#--logfile-name-format) - 22. [`--output.roa`](#--outputroa) - 23. [`--output.bgpsec`](#--outputbgpsec) - 24. [`--asn1-decode-max-stack`](#--asn1-decode-max-stack) - 25. [`--configuration-file`](#--configuration-file) - 26. [`rsync.program`](#rsyncprogram) - 27. [`rsync.arguments-recursive`](#rsyncarguments-recursive) - 28. [`rsync.arguments-flat`](#rsyncarguments-flat) - 29. [`incidences`](#incidences) + 22. [`--http.user-agent`](#--httpuser-agent) + 23. [`--http.connect-timeout`](#--httpconnect-timeout) + 24. [`--http.transfer-timeout`](#--httptransfer-timeout) + 25. [`--http.ca-path`](#--httpca-path) + 26. [`--output.roa`](#--outputroa) + 27. [`--output.bgpsec`](#--outputbgpsec) + 28. [`--asn1-decode-max-stack`](#--asn1-decode-max-stack) + 29. [`--configuration-file`](#--configuration-file) + 30. [`rsync.program`](#rsyncprogram) + 31. [`rsync.arguments-recursive`](#rsyncarguments-recursive) + 32. [`rsync.arguments-flat`](#rsyncarguments-flat) + 33. [`incidences`](#incidences) ## Syntax @@ -69,6 +73,10 @@ command: fort [--log.output=syslog|console] [--log.color-output] [--log.file-name-format=global-url|local-path|file-name] + [--http.user-agent=] + [--http.connect-timeout=] + [--http.transfer-timeout=] + [--http.ca-path=] [--output.roa=] [--output.bgpsec=] ``` @@ -438,6 +446,63 @@ ERR: baz.cer: Certificate validation failed: certificate has expired This flag affects any of the log output configured at [`--log.output`](#--logoutput) (`syslog` and `console`). +### `--http.user-agent` + +- **Type:** String +- **Availability:** `argv` and JSON +- **Default:** `{{ page.command }}/{{ site.fort-latest-version }}` + +_**All requests are made using HTTPS, verifying the peer and the certificate name vs host**_ + +User-Agent to use at HTTP requests. + +The value specified (either by the argument or the default value) is utilized in libcurl's option [CURLOPT_USERAGENT](https://curl.haxx.se/libcurl/c/CURLOPT_USERAGENT.html). + +### `--http.connect-timeout` + +- **Type:** Integer +- **Availability:** `argv` and JSON +- **Default:** 30 +- **Range:** 1--[`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) + +_**All requests are made using HTTPS, verifying the peer and the certificate name vs host**_ + +Timeout (in seconds) for the connect phase. + +Whenever an HTTP connection will try to be established, the validator will wait a maximum of `http.connect-timeout` for the peer to respond to the connection request; if the timeout is reached, the connection attempt will be ceased. + +The value specified (either by the argument or the default value) is utilized in libcurl's option [CURLOPT_CONNECTTIMEOUT](https://curl.haxx.se/libcurl/c/CURLOPT_CONNECTTIMEOUT.html). + +### `--http.transfer-timeout` + +- **Type:** Integer +- **Availability:** `argv` and JSON +- **Default:** 30 +- **Range:** 0--[`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html) + +_**All requests are made using HTTPS, verifying the peer and the certificate name vs host**_ + +Maximum time in seconds (once the connection is established) that the request can last. + +Once the connection is established with the server, the request will last a maximum of `http.transfer-timeout` seconds. A value of 0 means unlimited time (use with caution). + +The value specified (either by the argument or the default value) is utilized in libcurl's option [CURLOPT_TIMEOUT](https://curl.haxx.se/libcurl/c/CURLOPT_TIMEOUT.html). + +### `--http.ca-path` + +- **Type:** String (Path to directory) +- **Availability:** `argv` and JSON + +_**All requests are made using HTTPS, verifying the peer and the certificate name vs host**_ + +Local path where the CA's utilized to verify the peers are located. + +Useful when the CA from the peer isn't located at the default OS certificate bundle. If specified, the peer certificate will be verified using the CAs at the path. The directory MUST be prepared using the `rehash` utility from the SSL library: +- OpenSSL command (with help): `$ openssl rehash -h` +- LibreSSL command (with help): `$ openssl certhash -h` + +The value specified is utilized in libcurl's option [CURLOPT_CAPATH](https://curl.haxx.se/libcurl/c/CURLOPT_CAPATH.html). + ### `--output.roa` - **Type:** String (Path to file) @@ -514,6 +579,13 @@ The configuration options are mostly the same as the ones from the `argv` interf "file-name-format": "file-name" }, + "http": { + "user-agent": "{{ page.command }}/{{ site.fort-latest-version }}", + "connect-timeout": 30, + "transfer-timeout": 30, + "ca-path": "/usr/local/ssl/certs" + }, + "rsync": { "program": "rsync", "arguments-recursive": [ diff --git a/examples/config.json b/examples/config.json index 45f2b4e5..188a581c 100644 --- a/examples/config.json +++ b/examples/config.json @@ -23,6 +23,12 @@ "color-output": false, "file-name-format": "global-url" }, + "http": { + "user-agent": "fort/1.2.0", + "connect-timeout": 30, + "transfer-timeout": 30, + "ca-path": "/usr/local/ssl/certs" + }, "rsync": { "program": "rsync", "arguments-recursive": [ diff --git a/man/fort.8 b/man/fort.8 index 67f513b6..ee4cc2ca 100644 --- a/man/fort.8 +++ b/man/fort.8 @@ -508,6 +508,72 @@ Will print the certificate's name as `baz.cer`. .P .RE +.BR \-\-http.user\-agent=\fISTRING\fR +.RS 4 +User-Agent to use at HTTP requests. +.P +The value specified (either by the argument or the default value) is utilized +in libcurl’s option \fICURLOPT_USERAGENT\fR. +.P +By default, the value is \fIfort/\fR. +.RE +.P + +.B \-\-http.connect\-timeout=\fIUNSIGNED_INTEGER\fR +.RS 4 +Timeout (in seconds) for the connect phase. +.P +Whenever an HTTP connection will try to be established, the validator will wait +a maximum of \fBhttp.connect-timeout\fR seconds for the peer to respond to the +connection request; if the timeout is reached, the connection attempt will be +ceased. +.P +The value specified (either by the argument or the default value) is utilized +in libcurl’s option \fICURLOPT_CONNECTTIMEOUT\fR. +.P +By default, it has a value of \fI30\fR. The minimum allowed value is \fI1\fR. +.RE +.P + +.B \-\-http.transfer\-timeout=\fIUNSIGNED_INTEGER\fR +.RS 4 +Maximum time in seconds (once the connection is established) that the request +can last. +.P +Once the connection is established with the server, the request will last a +maximum of \fBhttp.transfer-timeout\fR seconds. A value of \fI0\fR means +unlimited time (use with caution). +.P +The value specified (either by the argument or the default value) is utilized +in libcurl’s option \fICURLOPT_TIMEOUT\fR. +.P +By default, it has a value of \fI30\fR. The minimum allowed value is \fI0\fR. +.RE +.P + +.B \-\-http.ca-path=\fIDIRECTORY\fR +.RS 4 +Local path where the CA’s utilized to verify the peers are located. +.P +Useful when the CA from the peer isn’t located at the default OS certificate +bundle. If specified, the peer certificate will be verified using the CAs at +the path. The directory MUST be prepared using the \fIrehash\fR utility from +the SSL library: +.RS 4 +.br +\- OpenSSL command (with help): +.B $ openssl rehash \-h +.br +\- LibreSSL command (with help): +.B $ openssl certhash \-h +.RE +.P +The value specified is utilized in libcurl’s option \fICURLOPT_CAPATH\fR. +.P +By default, the path has a NULL value. +.RE +.P + .B \-\-output.roa=\fIFILE\fR .RS 4 File where the ROAs will be printed in CSV format. @@ -623,6 +689,12 @@ to a specific value: "color-output": true, "file-name-format": "local-path" }, + "http": { + "user-agent": "fort/1.2.0", + "connect-timeout": 30, + "transfer-timeout": 30, + "ca-path": "/usr/local/ssl/certs" + }, "rsync": { "program": "rsync", "arguments-recursive": [ diff --git a/src/Makefile.am b/src/Makefile.am index 59858ea3..08575b1e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -59,6 +59,8 @@ fort_SOURCES += data_structure/common.h fort_SOURCES += data_structure/uthash.h fort_SOURCES += data_structure/uthash_nonfatal.h +fort_SOURCES += http/http.h http/http.c + fort_SOURCES += incidence/incidence.h incidence/incidence.c fort_SOURCES += object/bgpsec.h object/bgpsec.c @@ -108,7 +110,7 @@ fort_CFLAGS = -Wall -Wno-cpp #fort_CFLAGS += $(GCC_WARNS) fort_CFLAGS += -std=gnu11 -O2 -g $(FORT_FLAGS) fort_LDFLAGS = $(LDFLAGS_DEBUG) -fort_LDADD = ${JANSSON_LIBS} +fort_LDADD = ${JANSSON_LIBS} ${CURL_LIBS} # I'm tired of scrolling up, but feel free to comment this out. GCC_WARNS = -fmax-errors=1 diff --git a/src/common.c b/src/common.c index 1282603c..741305e6 100644 --- a/src/common.c +++ b/src/common.c @@ -193,3 +193,89 @@ addr2str6(struct in6_addr const *addr, char *buffer) { return inet_ntop(AF_INET6, addr, buffer, INET6_ADDRSTRLEN); } + +static int +dir_exists(char const *path, bool *result) +{ + struct stat _stat; + char *last_slash; + + last_slash = strrchr(path, '/'); + if (last_slash == NULL) { + /* + * Simply because create_dir_recursive() has nothing meaningful + * to do when this happens. It's a pretty strange error. + */ + *result = true; + return 0; + } + + *last_slash = '\0'; + + if (stat(path, &_stat) == 0) { + if (!S_ISDIR(_stat.st_mode)) { + return pr_err("Path '%s' exists and is not a directory.", + path); + } + *result = true; + } else if (errno == ENOENT) { + *result = false; + } else { + return pr_errno(errno, "stat() failed"); + } + + *last_slash = '/'; + return 0; +} + +static int +create_dir(char *path) +{ + int error; + + error = mkdir(path, 0777); + + if (error && errno != EEXIST) + return pr_errno(errno, "Error while making directory '%s'", + path); + + return 0; +} + +/** + * Apparently, RSYNC does not like to create parent directories. + * This function fixes that. + */ +int +create_dir_recursive(char const *path) +{ + char *localuri; + int i, error; + bool exist = false; + + error = dir_exists(path, &exist); + if (error) + return error; + if (exist) + return 0; + + localuri = strdup(path); + if (localuri == NULL) + return pr_enomem(); + + for (i = 1; localuri[i] != '\0'; i++) { + if (localuri[i] == '/') { + localuri[i] = '\0'; + error = create_dir(localuri); + localuri[i] = '/'; + if (error) { + /* error msg already printed */ + free(localuri); + return error; + } + } + } + + free(localuri); + return 0; +} diff --git a/src/common.h b/src/common.h index 409520ac..7bb954af 100644 --- a/src/common.h +++ b/src/common.h @@ -16,7 +16,12 @@ * start supporting them.) */ #define ENOTRSYNC 3174 - +/* + * "URI was not HTTPS; ignore it." + * Not necessarily an error (just as ENOTRSYNC), since both type of URIs can + * still coexist in most scenarios. + */ +#define ENOTHTTPS 3175 /* * If you're wondering why I'm not using -abs(error), it's because abs(INT_MIN) * overflows, so gcc complains sometimes. @@ -36,7 +41,7 @@ void rwlock_write_lock(pthread_rwlock_t *); void rwlock_unlock(pthread_rwlock_t *); /** Also boilerplate. */ -void close_thread(pthread_t thread, char const *what); +void close_thread(pthread_t thread, char const *); typedef int (*process_file_cb)(char const *, void *); int process_file_or_dir(char const *, char const *, process_file_cb, void *); @@ -44,4 +49,6 @@ int process_file_or_dir(char const *, char const *, process_file_cb, void *); char const *addr2str4(struct in_addr const *, char *); char const *addr2str6(struct in6_addr const *, char *); +int create_dir_recursive(char const *); + #endif /* SRC_RTR_COMMON_H_ */ diff --git a/src/config.c b/src/config.c index b96364ec..2f966899 100644 --- a/src/config.c +++ b/src/config.c @@ -77,6 +77,17 @@ struct rpki_config { } args; } rsync; + struct { + /* User-Agent header set at requests */ + char *user_agent; + /* Timeout in seconds for the connect phase */ + unsigned int connect_timeout; + /* Maximum allowed time that a request can take */ + unsigned int transfer_timeout; + /* Directory where CA certs to verify peers are found */ + char *ca_path; + } http; + struct { /** Print ANSI color codes? */ bool color; @@ -326,6 +337,41 @@ static const struct option_field options[] = { .availability = AVAILABILITY_JSON, }, + /* HTTP requests parameters */ + { + .id = 9000, + .name = "http.user-agent", + .type = >_string, + .offset = offsetof(struct rpki_config, http.user_agent), + .doc = "User-Agent to use at HTTP requests, eg. Fort Validator Local/1.0", + }, + { + .id = 9001, + .name = "http.connect-timeout", + .type = >_uint, + .offset = offsetof(struct rpki_config, http.connect_timeout), + .doc = "Timeout for the connect phase", + .min = 1, + .max = UINT_MAX, + }, + { + .id = 9002, + .name = "http.transfer-timeout", + .type = >_uint, + .offset = offsetof(struct rpki_config, http.transfer_timeout), + .doc = "Maximum request time (once the connection is established) before dropping the connection", + .min = 0, + .max = UINT_MAX, + }, + { + .id = 9003, + .name = "http.ca-path", + .type = >_string, + .offset = offsetof(struct rpki_config, http.ca_path), + .doc = "Directory where CA certificates are found, used to verify the peer", + .arg_doc = "", + }, + /* Logging fields */ { .id = 'c', @@ -584,6 +630,15 @@ set_default_values(void) if (error) goto revert_recursive_array; + rpki_config.http.user_agent = strdup(PACKAGE_NAME "/" PACKAGE_VERSION); + if (rpki_config.http.user_agent == NULL) { + error = pr_enomem(); + goto revert_recursive_array; + } + rpki_config.http.connect_timeout = 30; + rpki_config.http.transfer_timeout = 30; + rpki_config.http.ca_path = NULL; /* Use system default */ + rpki_config.log.color = false; rpki_config.log.filename_format = FNF_GLOBAL; rpki_config.log.level = LOG_WARNING; @@ -881,6 +936,30 @@ config_get_rsync_args(bool is_ta) pr_crit("Invalid sync strategy: '%u'", rpki_config.sync_strategy); } +char const * +config_get_http_user_agent(void) +{ + return rpki_config.http.user_agent; +} + +unsigned int +config_get_http_connect_timeout(void) +{ + return rpki_config.http.connect_timeout; +} + +unsigned int +config_get_http_transfer_timeout(void) +{ + return rpki_config.http.transfer_timeout; +} + +char const * +config_get_http_ca_path(void) +{ + return rpki_config.http.ca_path; +} + char const * config_get_output_roa(void) { diff --git a/src/config.h b/src/config.h index 0ec3d7ac..ea4c5945 100644 --- a/src/config.h +++ b/src/config.h @@ -33,6 +33,10 @@ unsigned int config_get_max_cert_depth(void); enum mode config_get_mode(void); bool config_get_color_output(void); enum filename_format config_get_filename_format(void); +char const *config_get_http_user_agent(void); +unsigned int config_get_http_connect_timeout(void); +unsigned int config_get_http_transfer_timeout(void); +char const *config_get_http_ca_path(void); uint8_t config_get_log_level(void); enum log_output config_get_log_output(void); char *config_get_rsync_program(void); diff --git a/src/http/http.c b/src/http/http.c new file mode 100644 index 00000000..70865843 --- /dev/null +++ b/src/http/http.c @@ -0,0 +1,136 @@ +#include "http.h" + +#include +#include +#include "common.h" +#include "config.h" +#include "file.h" +#include "log.h" + +struct http_handler { + CURL *curl; + char errbuf[CURL_ERROR_SIZE]; +}; + +int +http_init(void) +{ + CURLcode res; + res = curl_global_init(CURL_GLOBAL_SSL); + if (res != CURLE_OK) + return pr_err("Error initializing global curl (%s)", + curl_easy_strerror(res)); + + return 0; +} + +void +http_cleanup(void) +{ + curl_global_cleanup(); +} + +static int +http_easy_init(struct http_handler *handler) +{ + CURL *tmp; + + tmp = curl_easy_init(); + if (tmp == NULL) + return pr_enomem(); + + /* Use header always */ + if (config_get_http_user_agent() != NULL) + curl_easy_setopt(tmp, CURLOPT_USERAGENT, + config_get_http_user_agent()); + /* Only utilizes if indicated, otherwise use system default */ + if (config_get_http_ca_path() != NULL) + curl_easy_setopt(tmp, CURLOPT_CAPATH, + config_get_http_ca_path()); + + curl_easy_setopt(tmp, CURLOPT_CONNECTTIMEOUT, + config_get_http_connect_timeout()); + curl_easy_setopt(tmp, CURLOPT_TIMEOUT, + config_get_http_transfer_timeout()); + curl_easy_setopt(tmp, CURLOPT_NOSIGNAL, 1); + + /* Always expect HTTPS usage */ + curl_easy_setopt(tmp, CURLOPT_SSL_VERIFYHOST, 2); + curl_easy_setopt(tmp, CURLOPT_SSL_VERIFYPEER, 1); + + /* Currently all requests use GET */ + curl_easy_setopt(tmp, CURLOPT_HTTPGET, 1); + + /* Refer to its error buffer */ + curl_easy_setopt(tmp, CURLOPT_ERRORBUFFER, handler->errbuf); + + handler->curl = tmp; + + return 0; +} + +/* + * Fetch data from @uri and write result using @cb (which will receive @arg). + */ +static int +http_fetch(struct http_handler *handler, char const *uri, http_write_cb cb, + void *arg) +{ + CURLcode res; + + handler->errbuf[0] = 0; + curl_easy_setopt(handler->curl, CURLOPT_URL, uri); + curl_easy_setopt(handler->curl, CURLOPT_WRITEFUNCTION, cb); + curl_easy_setopt(handler->curl, CURLOPT_WRITEDATA, arg); + + pr_debug("HTTP GET from '%s'.", uri); + res = curl_easy_perform(handler->curl); + if (res != CURLE_OK) + return pr_err("Error requesting URL %s: %s", uri, + strlen(handler->errbuf) > 0 ? + handler->errbuf : curl_easy_strerror(res)); + + return 0; +} + +static void +http_easy_cleanup(struct http_handler *handler) +{ + curl_easy_cleanup(handler->curl); +} + +/* + * Try to download from global @uri into a local directory structure created + * from local @uri. The @cb should be utilized to write into a file; the file + * will be sent to @cb as the last argument (its a FILE reference). + */ +int +http_download_file(struct rpki_uri *uri, http_write_cb cb) +{ + struct http_handler handler; + struct stat stat; + FILE *out; + int error; + + error = create_dir_recursive(uri_get_local(uri)); + if (error) + return error; + + error = file_write(uri_get_local(uri), &out, &stat); + if (error) + return error; + + error = http_easy_init(&handler); + if (error) + goto close_file; + + error = http_fetch(&handler, uri_get_global(uri), cb, out); + http_easy_cleanup(&handler); + file_close(out); + + /* Error 0 it's ok */ + return error; +close_file: + file_close(out); + return error; +} diff --git a/src/http/http.h b/src/http/http.h new file mode 100644 index 00000000..e9ea1a98 --- /dev/null +++ b/src/http/http.h @@ -0,0 +1,13 @@ +#ifndef SRC_HTTP_HTTP_H_ +#define SRC_HTTP_HTTP_H_ + +#include +#include "uri.h" + +int http_init(void); +void http_cleanup(void); + +typedef size_t (http_write_cb)(unsigned char *, size_t, size_t, void *); +int http_download_file(struct rpki_uri *, http_write_cb); + +#endif /* SRC_HTTP_HTTP_H_ */ diff --git a/src/main.c b/src/main.c index e5662920..d0e4736c 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include "extension.h" #include "nid.h" #include "thread_var.h" +#include "http/http.h" #include "rtr/rtr.h" #include "rtr/db/vrps.h" @@ -48,8 +49,13 @@ __main(int argc, char **argv) if (error) goto revert_nid; + error = http_init(); + if (error) + goto revert_nid; + error = start_rtr_server(); + http_cleanup(); revert_nid: nid_destroy(); revert_config: diff --git a/src/rsync/rsync.c b/src/rsync/rsync.c index 6326bb79..62a3ac16 100644 --- a/src/rsync/rsync.c +++ b/src/rsync/rsync.c @@ -8,6 +8,7 @@ #include #include +#include "common.h" #include "config.h" #include "log.h" #include "str.h" @@ -169,92 +170,6 @@ get_rsync_uri(struct rpki_uri *requested_uri, bool is_ta, pr_crit("Invalid sync strategy: %u", config_get_sync_strategy()); } -static int -dir_exists(char const *path, bool *result) -{ - struct stat _stat; - char *last_slash; - - last_slash = strrchr(path, '/'); - if (last_slash == NULL) { - /* - * Simply because create_dir_recursive() has nothing meaningful - * to do when this happens. It's a pretty strange error. - */ - *result = true; - return 0; - } - - *last_slash = '\0'; - - if (stat(path, &_stat) == 0) { - if (!S_ISDIR(_stat.st_mode)) { - return pr_err("Path '%s' exists and is not a directory.", - path); - } - *result = true; - } else if (errno == ENOENT) { - *result = false; - } else { - return pr_errno(errno, "stat() failed"); - } - - *last_slash = '/'; - return 0; -} - -static int -create_dir(char *path) -{ - int error; - - error = mkdir(path, 0777); - - if (error && errno != EEXIST) - return pr_errno(errno, "Error while making directory '%s'", - path); - - return 0; -} - -/** - * Apparently, RSYNC does not like to create parent directories. - * This function fixes that. - */ -static int -create_dir_recursive(struct rpki_uri *uri) -{ - char *localuri; - int i, error; - bool exist = false; - - error = dir_exists(uri_get_local(uri), &exist); - if (error) - return error; - if (exist) - return 0; - - localuri = strdup(uri_get_local(uri)); - if (localuri == NULL) - return pr_enomem(); - - for (i = 1; localuri[i] != '\0'; i++) { - if (localuri[i] == '/') { - localuri[i] = '\0'; - error = create_dir(localuri); - localuri[i] = '/'; - if (error) { - /* error msg already printed */ - free(localuri); - return error; - } - } - } - - free(localuri); - return 0; -} - static void handle_child_thread(struct rpki_uri *uri, bool is_ta) { @@ -314,7 +229,7 @@ do_rsync(struct rpki_uri *uri, bool is_ta) int error; child_status = 0; - error = create_dir_recursive(uri); + error = create_dir_recursive(uri_get_local(uri)); if (error) return error; diff --git a/test/Makefile.am b/test/Makefile.am index 42ec30ad..39624601 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -22,6 +22,7 @@ MY_LDADD = ${CHECK_LIBS} check_PROGRAMS = address.test check_PROGRAMS += clients.test check_PROGRAMS += db_table.test +check_PROGRAMS += http.test check_PROGRAMS += line_file.test check_PROGRAMS += pdu_handler.test check_PROGRAMS += rsync.test @@ -41,6 +42,9 @@ clients_test_LDADD = ${MY_LDADD} db_table_test_SOURCES = rtr/db/db_table_test.c db_table_test_LDADD = ${MY_LDADD} +http_test_SOURCES = http_test.c +http_test_LDADD = ${MY_LDADD} ${CURL_LIBS} + line_file_test_SOURCES = line_file_test.c line_file_test_LDADD = ${MY_LDADD} diff --git a/test/http_test.c b/test/http_test.c new file mode 100644 index 00000000..cb928f4a --- /dev/null +++ b/test/http_test.c @@ -0,0 +1,103 @@ +#include +#include +#include + +#include "common.c" +#include "file.c" +#include "impersonator.c" +#include "log.c" +#include "uri.c" +#include "http/http.c" + +struct response { + unsigned char *content; + size_t size; +}; + +static void +init_response(struct response *resp) +{ + resp->size = 0; + resp->content = malloc(sizeof(char)); +} + +static size_t +write_cb(unsigned char *content, size_t size, size_t nmemb, void *arg) +{ + struct response *resp = arg; + unsigned char *tmp; + size_t read = size * nmemb; + + tmp = realloc(resp->content, resp->size + read + 1); + if (tmp == NULL) + return -EINVAL; + + resp->content = tmp; + memcpy(&resp->content[resp->size], content, read); + resp->size += read; + resp->content[resp->size] = 0; + + return read; +} + +static int +local_download(char const *url, struct response *resp) +{ + struct http_handler handler; + int error; + + error = http_easy_init(&handler); + if (error) + return error; + + error = http_fetch(&handler, url, write_cb, resp); + http_easy_cleanup(&handler); + return error; +} + +START_TEST(http_fetch_normal) +{ + struct response resp; + char const *url = "https://rrdp.ripe.net/notification.xml"; + + init_response(&resp); + + ck_assert_int_eq(http_init(), 0); + ck_assert_int_eq(local_download(url, &resp), 0); + ck_assert_int_gt(resp.size, 0); + + http_cleanup(); + free(resp.content); +} +END_TEST + +Suite *http_load_suite(void) +{ + Suite *suite; + TCase *fetch; + + fetch = tcase_create("Fetch"); + tcase_add_test(fetch, http_fetch_normal); + tcase_set_timeout(fetch, 60); + + suite = suite_create("http_test()"); + suite_add_tcase(suite, fetch); + + return suite; +} + +int main(void) +{ + Suite *suite; + SRunner *runner; + int tests_failed; + + suite = http_load_suite(); + + runner = srunner_create(suite); + srunner_run_all(runner, CK_NORMAL); + tests_failed = srunner_ntests_failed(runner); + srunner_free(runner); + + return (tests_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/test/impersonator.c b/test/impersonator.c index 13d7d3ee..90a4c733 100644 --- a/test/impersonator.c +++ b/test/impersonator.c @@ -147,3 +147,27 @@ void print_stack_trace(void) { /* Nothing needed here */ } + +/* Impersonate HTTP config */ +char const * +config_get_http_user_agent(void) +{ + return "Test/0.1"; +} + +unsigned int +config_get_http_connect_timeout(void) +{ + return 30; +} + +unsigned int +config_get_http_transfer_timeout(void) +{ + return 30; +} +char const * +config_get_http_ca_path(void) +{ + return NULL; +}