]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Add module to support HTTPS requests.
authorpcarana <pc.moreno2099@gmail.com>
Tue, 12 Nov 2019 23:42:29 +0000 (17:42 -0600)
committerpcarana <pc.moreno2099@gmail.com>
Tue, 12 Nov 2019 23:42:29 +0000 (17:42 -0600)
-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.

17 files changed:
configure.ac
docs/installation.md
docs/usage.md
examples/config.json
man/fort.8
src/Makefile.am
src/common.c
src/common.h
src/config.c
src/config.h
src/http/http.c [new file with mode: 0644]
src/http/http.h [new file with mode: 0644]
src/main.c
src/rsync/rsync.c
test/Makefile.am
test/http_test.c [new file with mode: 0644]
test/impersonator.c

index bf364c41b4f1bd9963c05f91d2c8735b0a8a2fd5..3b9ed766630dea7ab78246f54ede121b557dd311 100644 (file)
@@ -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"])
 
index 8aa64a335854b0f3f95a18149a8900e6ca4b0d45..037098d3ed3f90816ffa3772d3ebd9b080659d6f 100644 (file)
@@ -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/
index d646c068a334cacdc578d0f61ac8c8039004f059..be7872eb923feb2c530412e75282445befc3e9b6 100644 (file)
@@ -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=<string>]
+        [--http.connect-timeout=<unsigned integer>]
+        [--http.transfer-timeout=<unsigned integer>]
+        [--http.ca-path=<directory>]
         [--output.roa=<file>]
         [--output.bgpsec=<file>]
 ```
@@ -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
                "<a href="#--logfile-name-format">file-name-format</a>": "file-name"
        },
 
+       "http": {
+               "<a href="#--httpuser-agent">user-agent</a>": "{{ page.command }}/{{ site.fort-latest-version }}",
+               "<a href="#--httpconnect-timeout">connect-timeout</a>": 30,
+               "<a href="#--httptransfer-timeout">transfer-timeout</a>": 30,
+               "<a href="#--httpca-path">ca-path</a>": "/usr/local/ssl/certs"
+       },
+
        "rsync": {
                "<a href="#rsyncprogram">program</a>": "rsync",
                "<a href="#rsyncarguments-recursive">arguments-recursive</a>": [
index 45f2b4e56804664f7a78e0a5f4b6e407d7deb58b..188a581c7b6598e11d84c97aabcb198749efcee3 100644 (file)
     "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": [
index 67f513b631ad069c82fd54098ce4b78587bbddb9..ee4cc2ca42266531d8288f393e40ee967e38c68e 100644 (file)
@@ -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/<current-version>\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": [
index 59858ea36b58b3c26a2891ce4e1b25433722bba1..08575b1e57d8de5ae394e9cf82828311e4a73975 100644 (file)
@@ -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
index 1282603c49d99ca63cab8e4975c20742006eefe5..741305e60ccb65f00f3454fc435c3674e2aed019 100644 (file)
@@ -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;
+}
index 409520acc1fa12231da0d7efc1791792cc5eb984..7bb954afe7eb4d47e34844442bf539b146c32be1 100644 (file)
  * 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_ */
index b96364eca10bb1d42331672df5f1bbede21534b3..2f9668992ed3aa3fb7b279f0a650e44ebbefecd2 100644 (file)
@@ -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 = &gt_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 = &gt_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 = &gt_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 = &gt_string,
+               .offset = offsetof(struct rpki_config, http.ca_path),
+               .doc = "Directory where CA certificates are found, used to verify the peer",
+               .arg_doc = "<directory>",
+       },
+
        /* 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)
 {
index 0ec3d7acc381b9b56ef0a446bd8ab62ebd95ab9a..ea4c5945d3b5d4c24ebd6ac9ebc5030b82308adc 100644 (file)
@@ -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 (file)
index 0000000..7086584
--- /dev/null
@@ -0,0 +1,136 @@
+#include "http.h"
+
+#include <curl/curl.h>
+#include <sys/stat.h>
+#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 (file)
index 0000000..e9ea1a9
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef SRC_HTTP_HTTP_H_
+#define SRC_HTTP_HTTP_H_
+
+#include <stddef.h>
+#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_ */
index e5662920ef86685c83878f267535c8ba031a3e64..d0e4736c949fef8ecb35cc17e7beee0b19f044b3 100644 (file)
@@ -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:
index 6326bb79737d11e3554aed3977ee5bc121b2b7b1..62a3ac169463400111a7b4a325560d71c79238b4 100644 (file)
@@ -8,6 +8,7 @@
 #include <sys/stat.h>
 #include <sys/wait.h>
 
+#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;
 
index 42ec30adb72441d76386b58b169359919231d803..39624601b32ae4f252e9a19857994e15701044b7 100644 (file)
@@ -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 (file)
index 0000000..cb928f4
--- /dev/null
@@ -0,0 +1,103 @@
+#include <check.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#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;
+}
index 13d7d3eea771273240a5b5924045620ca2415634..90a4c73391f5e214f29b8e18ff10fa11270fe4fd 100644 (file)
@@ -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;
+}