]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Merge branch 'main' into issue82
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Wed, 5 Jun 2024 18:53:38 +0000 (12:53 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Thu, 6 Jun 2024 21:52:59 +0000 (15:52 -0600)
30 files changed:
1  2 
man/fort.8
src/Makefile.am
src/alloc.c
src/alloc.h
src/asn1/content_info.c
src/asn1/signed_data.c
src/cache/local_cache.c
src/common.c
src/config.c
src/config.h
src/data_structure/path_builder.c
src/data_structure/path_builder.h
src/extension.c
src/extension.h
src/file.c
src/file.h
src/http/http.c
src/init.c
src/json_util.c
src/json_util.h
src/main.c
src/object/certificate.c
src/object/manifest.c
src/print_file.c
src/rrdp.c
src/slurm/db_slurm.h
src/slurm/slurm_parser.c
src/types/uri.c
test/Makefile.am
test/cache/local_cache_test.c

diff --cc man/fort.8
Simple merge
diff --cc src/Makefile.am
index 9706d5a91dd4b102896a37b3b6f368d58128890f,987acb4bfa567b53d373e864de4f5432f4aabc94..ac948798fff1c81451f004c70326587064740360
@@@ -18,6 -18,8 +18,7 @@@ fort_SOURCES += extension.h extension.
  fort_SOURCES += file.h file.c
  fort_SOURCES += init.h init.c
  fort_SOURCES += json_util.c json_util.h
 -fort_SOURCES += line_file.h line_file.c
+ fort_SOURCES += libcrypto_util.h libcrypto_util.c
  fort_SOURCES += log.h log.c
  fort_SOURCES += nid.h nid.c
  fort_SOURCES += output_printer.h output_printer.c
diff --cc src/alloc.c
index 8bc33ff5dca19c751f8736474c2dec3abed825ad,e6fd2bd0985ae99e17a7a27d1a47cb3e88a93916..6c1876630babfb12c40aa3511336c483af0b432c
@@@ -62,7 -62,7 +62,7 @@@ pstrdup(const char *s
  }
  
  char *
- pstrndup(char const *s, size_t size)
 -pstrndup(const char *s, size_t n)
++pstrndup(char const *s, size_t n)
  {
        char *result;
  
diff --cc src/alloc.h
index 5bd04975b9dac16777d4858bd845e7786093026f,2f4fd58bfbdae6dbb0b727d5789864da6ba719cf..07d6d07bb4959b6855455849024764416412e69c
@@@ -20,6 -20,7 +20,7 @@@ void *prealloc(void *ptr, size_t size)
  
  /* strdup(), but panic on allocation failure. */
  char *pstrdup(char const *s);
- char *pstrndup(char const *s, size_t size);
+ /* strndup(), but panic on allocation failure. */
 -char *pstrndup(const char *s, size_t n);
++char *pstrndup(char const *s, size_t n);
  
  #endif /* SRC_ALLOC_H_ */
index a2e0f95c3a20336c0dadf86f06cad81d0f4fe058,8088c632d18dc653e1f11e869c106fa35a34625d..b279160d7eeb3491f1adc9eda2291bb95320cb14
@@@ -54,7 -55,7 +55,7 @@@ content_info_load(char const *file, str
        struct file_contents fc;
        int error;
  
-       error = file_load(uri_get_local(uri), &fc, true);
 -      error = file_load(file, &fc);
++      error = file_load(file, &fc, true);
        if (error)
                return error;
  
Simple merge
index 0085f7d9dd7be09e602c1ec338c05b0bc22cf1dc,506f7309389e5793ee7ebc031e465591ba64878d..71d9cf2b8295acd1d433a41ede899424b5e95139
@@@ -416,42 -380,17 +416,40 @@@ static json_t 
  node2json(struct cache_node *node)
  {
        json_t *json;
 +      char const *type;
 +      json_t *notification;
  
-       json = json_object();
+       json = json_obj_new();
        if (json == NULL)
-               enomem_panic();
+               return NULL;
  
 +      switch (uri_get_type(node->url)) {
 +      case UT_TA_RSYNC:
 +              type = TYPEVALUE_TA_RSYNC;
 +              break;
 +      case UT_TA_HTTP:
 +              type = TYPEVALUE_TA_HTTP;
 +              break;
 +      case UT_RPP:
 +              type = TYPEVALUE_RPP;
 +              break;
 +      case UT_NOTIF:
 +              type = TYPEVALUE_NOTIF;
 +              break;
 +      default:
 +              goto cancel;
 +      }
 +
 +      if (json_add_str(json, TAGNAME_TYPE, type))
 +              goto cancel;
        if (json_add_str(json, TAGNAME_URL, uri_get_global(node->url)))
                goto cancel;
 -      if (uri_is_notif(node->url))
 -              if (json_add_bool(json, TAGNAME_IS_NOTIF, true))
 +      if (node->notif != NULL) {
 +              notification = rrdp_notif2json(node->notif);
-               if (notification == NULL)
-                       goto cancel;
-               if (json_add_obj(json, TAGNAME_NOTIF, notification))
++              if (json_object_add(json, TAGNAME_NOTIF, notification))
                        goto cancel;
-       if (json_add_date(json, TAGNAME_ATTEMPT_TS, node->attempt.ts))
 +      }
+       if (json_add_ts(json, TAGNAME_ATTEMPT_TS, node->attempt.ts))
                goto cancel;
        if (json_add_int(json, TAGNAME_ATTEMPT_ERR, node->attempt.result))
                goto cancel;
@@@ -666,21 -593,18 +662,21 @@@ cache_download(struct rpki_cache *cache
        }
  
        switch (uri_get_type(url)) {
 -      case UT_RSYNC:
 +      case UT_TA_HTTP:
 +      case UT_NOTIF:
 +      case UT_TMP:
 +              error = config_get_http_enabled()
 +                 ? http_download(url, node->success.ts, changed)
 +                 : cache_check(url);
 +              break;
 +      case UT_TA_RSYNC:
 +      case UT_RPP:
                error = config_get_rsync_enabled()
-                   ? rsync_download(url)
+                   ? rsync_download(uri_get_global(url), uri_get_local(url), true)
                    : cache_check(url);
                break;
 -      case UT_HTTPS:
 -              error = config_get_http_enabled()
 -                  ? http_download(url, changed)
 -                  : cache_check(url);
 -              break;
        default:
 -              pr_crit("Unexpected URI type: %d", uri_get_type(url));
 +              pr_crit("URI type not downloadable: %d", uri_get_type(url));
        }
  
        node->attempt.ts = time(NULL);
diff --cc src/common.c
Simple merge
diff --cc src/config.c
Simple merge
diff --cc src/config.h
Simple merge
index d75daa10549ef446a4f19919b9941ec001f35485,4653379b9777cd7d8a141228177703c8107b5c68..e53d415841726a3ec266589f16ed7854705e01d3
@@@ -4,9 -4,11 +4,9 @@@
  
  #include "alloc.h"
  #include "config.h"
- #include "log.h"
  #include "crypto/hash.h"
+ #include "log.h"
  
 -#define SHA256_LEN (256 >> 3) /* 256 / 8, bits -> bytes */
 -
  /* These are arbitrary; feel free to change them. */
  #ifndef INITIAL_CAPACITY /* Unit tests want to override this */
  #define INITIAL_CAPACITY 128u
Simple merge
diff --cc src/extension.c
index 97cc6c852c024ff6f8b79a040f235bf3573387e5,99045fa99f8c883991f0018fd02453f56016d8bb..c3e6060ae2df8ba5877ec814639d19f5d334abe0
@@@ -306,38 -1000,23 +996,23 @@@ validate_public_key_hash(X509 *cert, AS
  }
  
  int
- handle_aki(X509_EXTENSION *ext, void *arg)
+ handle_aki(void *ext, void *arg)
  {
-       AUTHORITY_KEYID *aki;
-       struct validation *state;
+       AUTHORITY_KEYID *aki = ext;
        X509 *parent;
-       int error;
-       aki = X509V3_EXT_d2i(ext);
-       if (aki == NULL)
-               return cannot_decode(ext_aki());
  
        if (aki->issuer != NULL) {
-               error = pr_val_err("%s extension contains an authorityCertIssuer.",
+               return pr_val_err("%s extension contains an authorityCertIssuer.",
                    ext_aki()->name);
-               goto end;
        }
        if (aki->serial != NULL) {
-               error = pr_val_err("%s extension contains an authorityCertSerialNumber.",
+               return pr_val_err("%s extension contains an authorityCertSerialNumber.",
                    ext_aki()->name);
-               goto end;
-       }
-       state = state_retrieve();
-       parent = x509stack_peek(validation_certstack(state));
-       if (parent == NULL) {
-               error = pr_val_err("Certificate has no parent.");
-               goto end;
        }
  
-       error = validate_public_key_hash(parent, aki->keyid, "AKI");
+       parent = x509stack_peek(validation_certstack(state_retrieve()));
+       if (parent == NULL)
+               return pr_val_err("Certificate has no parent.");
  
- end:
-       AUTHORITY_KEYID_free(aki);
-       return error;
 -      return validate_public_key_hash(parent, aki->keyid);
++      return validate_public_key_hash(parent, aki->keyid, "AKI");
  }
diff --cc src/extension.h
index 6dada3b89ec4d6520ab3269093c199660bdd4437,7b9362e29cf7114725b2227e8b267c01eacc2ba5..0baf2d3d9691a96ff572966ffc1d0e3a58b21732
@@@ -44,7 -49,7 +49,7 @@@ int handle_extensions(struct extension_
      STACK_OF(X509_EXTENSION) const *);
  
  int cannot_decode(struct extension_metadata const *);
 -int validate_public_key_hash(X509 *, ASN1_OCTET_STRING *);
 +int validate_public_key_hash(X509 *, ASN1_OCTET_STRING *, char const *);
- int handle_aki(X509_EXTENSION *, void *);
+ int handle_aki(void *, void *);
  
  #endif /* SRC_EXTENSION_H_ */
diff --cc src/file.c
index 768dd9550a546f872adfd679feeb79af47d7d9fd,72c34b490d5a6afced64cf60a09825e614998bf4..b23e90e514e1810cad6330169ad6286ed9540d43
@@@ -133,45 -127,6 +133,24 @@@ file_exists(char const *path
        return (stat(path, &meta) == 0) ? 0 : errno;
  }
  
- /*
-  * Validate @file_name, if it doesn't exist, this function will create it and
-  * close it.
-  */
- bool
- file_valid(char const *file_name)
- {
-       FILE *tmp;
-       int error;
-       if (file_name == NULL)
-               return false;
-       error = file_write(file_name, &tmp);
-       if (error)
-               return false;
-       file_close(tmp);
-       return true;
- }
 +/*
 + * Like remove(), but don't care if the file is already deleted.
 + */
 +int
 +file_rm_f(char const *path)
 +{
 +      int error;
 +
 +      errno = 0;
 +      if (remove(path) != 0) {
 +              error = errno;
 +              if (error != ENOENT)
 +                      return error;
 +      }
 +
 +      return 0;
 +}
 +
  static int
  rm(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
  {
diff --cc src/file.h
index d0d603bd6bf749b3e93c79cec8c7911ef1fefb7b,0379a424813509a3f3f357e17408199c7ba77ee5..db3e9623cc93e63926273063d739f7363f2a1687
@@@ -24,16 -23,14 +24,15 @@@ struct file_contents 
  };
  
  int file_open(char const *, FILE **, struct stat *);
- int file_write(char const *, FILE **);
+ int file_write(char const *, char const *, FILE **);
  void file_close(FILE *);
  
 -int file_load(char const *, struct file_contents *);
 +int file_load(char const *, struct file_contents *, bool);
  void file_free(struct file_contents *);
  
  int file_exists(char const *);
- bool file_valid(char const *);
  
 +int file_rm_f(char const *);
  int file_rm_rf(char const *);
  
  /*
diff --cc src/http/http.c
index b783ba1b168beb7f2a82ee3180abc24e73b281d9,bd4069b56d29e2cef8ede34cce13a9a229742a4a..e901f75c89632d0798b7e5c80aaeea3d522d5f3d
@@@ -1,12 -1,14 +1,12 @@@
  #include "http/http.h"
  
 -#include <curl/curl.h>
 -
  #include "alloc.h"
+ #include "cache/local_cache.h"
  #include "common.h"
  #include "config.h"
+ #include "data_structure/uthash.h"
  #include "file.h"
  #include "log.h"
- #include "data_structure/uthash.h"
- #include "cache/local_cache.h"
  
  struct http_handler {
        CURL *curl;
diff --cc src/init.c
index 4b444eeadfd5e84b6be1fc9fc8bc8bfbcac82b39,dd90fcec2ab16b6ef0ca6703bce5b3b4f5e92f40..5cb9ff4bfe4e9d0a44589fd41aeb94a9f86ef861
@@@ -1,62 -1,43 +1,43 @@@
  #include "init.h"
  
- #include "alloc.h"
  #include "config.h"
- #include "log.h"
+ #include "data_structure/path_builder.h"
  #include "http/http.h"
+ #include "log.h"
  
  static int
- fetch_url(char const *url)
+ fetch_url(char const *url, char const *filename)
  {
-       char const *prefix = "https://";
-       char const *dest_dir;
-       char const *dest_file;
-       char *dest;
-       size_t prefix_len;
-       size_t url_len;
-       size_t dest_dir_len;
-       size_t extra_slash;
-       size_t offset;
+       struct path_builder pb;
        int error;
  
-       prefix_len = strlen(prefix);
-       url_len = strlen(url);
-       dest_dir = config_get_tal();
-       dest_dir_len = strlen(dest_dir);
-       if (url_len <= prefix_len ||
-           strncasecmp(url, prefix, prefix_len) != 0)
-               return pr_op_err("Invalid HTTPS URL: '%s'", url);
-       dest_file = strrchr(url, '/') + 1;
-       if (*dest_file == '\0')
-               return pr_op_err("HTTPS URL '%s' must be a file location", url);
-       extra_slash = (dest_dir[dest_dir_len - 1] == '/') ? 0 : 1;
-       dest = pmalloc(dest_dir_len + extra_slash + strlen(dest_file) + 1);
-       offset = 0;
-       strcpy(dest + offset, dest_dir);
-       offset += dest_dir_len;
-       if (extra_slash) {
-               strcpy(dest + offset, "/");
-               offset += extra_slash;
-       }
-       strcpy(dest + offset, dest_file);
-       offset += strlen(dest_file);
-       dest[offset] = '\0';
+       pb_init(&pb);
+       error = pb_append(&pb, config_get_tal());
+       if (error)
+               goto pbfail;
+       error = pb_append(&pb, filename);
+       if (error)
+               goto pbfail;
  
-       error = http_download_direct(url, dest);
-       if (error) {
-               fprintf(stderr, "Couldn't fetch '%s'.\n", dest);
-               free(dest);
-               return error;
-       }
 -      error = http_direct_download(url, pb.string);
++      error = http_download_direct(url, pb.string);
+       if (error)
+               goto dlfail;
  
-       fprintf(stdout, "Successfully fetched '%s'!\n\n", dest);
-       free(dest);
+       fprintf(stdout, "Successfully fetched '%s'!\n\n", pb.string);
+       pb_cleanup(&pb);
        return 0;
+ pbfail:
+       fprintf(stderr, "Cannot determine destination path: %s\n",
+               strerror(abs(error)));
+       pb_cleanup(&pb);
+       return error;
+ dlfail:
+       fprintf(stderr, "Couldn't fetch '%s': %s\n", pb.string,
+               strerror(abs(error)));
+       pb_cleanup(&pb);
+       return error;
  }
  
  int
diff --cc src/json_util.c
Simple merge
diff --cc src/json_util.h
index 93a41d01e855a288f10ba7ba8b465f794fba39c5,23afc304e19ab96f1bc005636b616a7f1df8f215..b67da934879e14f5bffd8bd19d01c12cdb3eda79
@@@ -33,9 -34,17 +33,16 @@@ int json_get_object(json_t *, char cons
  
  bool json_valid_members_count(json_t *, size_t);
  
 -int json_add_bool(json_t *, char const *, bool);
  int json_add_int(json_t *, char const *, int);
  int json_add_str(json_t *, char const *, char const *);
- int json_add_date(json_t *, char const *, time_t);
- int json_add_obj(json_t *, char const *, json_t *);
+ int json_add_ts(json_t *, char const *, time_t);
+ json_t *json_obj_new(void);
+ json_t *json_array_new(void);
+ json_t *json_int_new(json_int_t);
+ json_t *json_str_new(const char *);
+ json_t *json_strn_new(const char *, size_t);
+ int json_object_add(json_t *, char const *, json_t *);
+ int json_array_add(json_t *, json_t *);
  
  #endif /* SRC_JSON_UTIL_H_ */
diff --cc src/main.c
index 88c56379f121128d44dd1a86ec373a21008f3508,4336a129fe9efd6f6c901f4ba949c0adab53f92e..7f69d0a7812d8de737d739f3b3faceb3bce7b329
@@@ -1,15 -1,15 +1,16 @@@
  #include <errno.h>
  
  #include "config.h"
- #include "extension.h"
- #include "log.h"
- #include "nid.h"
- #include "thread_var.h"
 +#include "crypto/hash.h"
+ #include "extension.h"
  #include "http/http.h"
  #include "incidence/incidence.h"
- #include "rtr/rtr.h"
+ #include "log.h"
+ #include "nid.h"
+ #include "print_file.h"
  #include "rtr/db/vrps.h"
+ #include "rtr/rtr.h"
+ #include "thread_var.h"
  #include "xml/relax_ng.h"
  
  static int
index d1a7d92c4e51b4a3632b0a0545442442f93989d8,876eb8354df58e31353739137d1574d64fcd43b3..218f35a1cdfd527c502387e5b79f6450c9c7ab67
@@@ -1251,22 -1253,9 +1253,9 @@@ handle_bc(void *ext, void *arg
  }
  
  static int
- handle_ski_ca(X509_EXTENSION *ext, void *arg)
+ handle_ski_ca(void *ext, void *arg)
  {
-       ASN1_OCTET_STRING *ski;
-       int error;
-       ski = X509V3_EXT_d2i(ext);
-       if (ski == NULL)
-               return cannot_decode(ext_ski());
-       error = validate_public_key_hash(arg, ski, "SKI");
-       ASN1_OCTET_STRING_free(ski);
-       return error;
 -      return validate_public_key_hash(arg, ext);
++      return validate_public_key_hash(arg, ext, "SKI");
  }
  
  static int
@@@ -1277,14 -1266,9 +1266,9 @@@ handle_ski_ee(void *ext, void *arg
        OCTET_STRING_t *sid;
        int error;
  
-       ski = X509V3_EXT_d2i(ext);
-       if (ski == NULL)
-               return cannot_decode(ext_ski());
-       args = arg;
 -      error = validate_public_key_hash(args->cert, ski);
 +      error = validate_public_key_hash(args->cert, ski, "SKI");
        if (error)
-               goto end;
+               return error;
  
        /* rfc6488#section-2.1.6.2 */
        /* rfc6488#section-3.1.c 2/2 */
Simple merge
index 0000000000000000000000000000000000000000,404cd4ac6992491b9bd0ceda19ed039f7ecd28dc..b5aaaa4d8b8b839bd266773eee258a3171ad152a
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,312 +1,316 @@@
 -      error = uri_create(&uri, tal, UT_RSYNC, false, NULL, src);
+ #include "print_file.h"
+ #include "asn1/asn1c/CRL.h"
+ #include "asn1/asn1c/Certificate.h"
+ #include "asn1/asn1c/ber_decoder.h"
+ #include "asn1/asn1c/json_encoder.h"
+ #include "asn1/content_info.h"
+ #include "common.h"
+ #include "config.h"
+ #include "data_structure/path_builder.h"
+ #include "file.h"
+ #include "log.h"
+ #include "rsync/rsync.h"
+ #include "types/bio_seq.h"
+ #include "types/uri.h"
+ #define HDRSIZE 32
+ static BIO *
+ __rsync2bio(char const *src, char const *dst)
+ {
+       int error;
+       error = rsync_download(src, dst, false);
+       if (error) {
+               pr_op_err("rysnc download failed: %s", strerror(abs(error)));
+               return NULL;
+       }
+       return BIO_new_file(dst, "rb");
+ }
+ static BIO *
+ rsync2bio_tmpdir(char const *src)
+ {
+ #define TMPDIR "/tmp/fort-XXXXXX"
+       struct path_builder pb;
+       char buf[strlen(TMPDIR) + 1];
+       char *tmpdir;
+       BIO *result = NULL;
+       int error;
+       strcpy(buf, TMPDIR);
+       tmpdir = mkdtemp(buf);
+       if (tmpdir == NULL) {
+               pr_op_err("Unable to create " TMPDIR ": %s", strerror(errno));
+               return NULL;
+       }
+       pb_init(&pb);
+       error = pb_append(&pb, tmpdir);
+       if (error)
+               goto end;
+       error = pb_append(&pb, strrchr(src, '/') + 1);
+       if (error)
+               goto end;
+       result = __rsync2bio(src, pb.string);
+ end:  pb_cleanup(&pb);
+       return result;
+ }
+ static BIO *
+ rsync2bio_cache(char const *src)
+ {
+       char const *tal;
+       struct rpki_uri *uri;
+       BIO *bio;
+       int error;
+       tal = strrchr(config_get_tal(), '/');
+       tal = (tal != NULL) ? (tal + 1) : config_get_tal();
+       uri = NULL;
++      /*
++       * TODO (#82) maybe rename UT_TA_RSYNC into single rsync.
++       * If applies and it's going to survive.
++       */
++      error = uri_create(&uri, tal, UT_TA_RSYNC, NULL, src);
+       if (error) {
+               pr_op_err("Unparseable rsync URI: %s", strerror(abs(error)));
+               return NULL;
+       }
+       bio = __rsync2bio(uri_get_global(uri), uri_get_local(uri));
+       uri_refput(uri);
+       return bio;
+ }
+ static BIO *
+ rsync2bio(char const *src)
+ {
+       return (config_get_tal() && config_get_local_repository())
+            ? rsync2bio_cache(src)
+            : rsync2bio_tmpdir(src);
+ }
+ static BIO *
+ filename2bio(char const *filename)
+ {
+       if (filename == NULL || strcmp(filename, "-") == 0)
+               return BIO_new_fp(stdin, BIO_NOCLOSE);
+       if (str_starts_with(filename, "rsync://"))
+               return rsync2bio(filename);
+       return BIO_new_file(filename, "rb");
+ }
+ static unsigned char *
+ skip_sequence(unsigned char *buf, unsigned char *cursor)
+ {
+       ber_tlv_len_t len;
+       ssize_t len_len;
+       len_len = ber_fetch_length(1, cursor, HDRSIZE - (cursor - buf), &len);
+       if (len_len <= 0)
+               return NULL;
+       cursor += len_len;
+       return (cursor <= (buf + HDRSIZE)) ? cursor : NULL;
+ }
+ static unsigned char *
+ skip_integer(unsigned char *buf, unsigned char *cursor)
+ {
+       ber_tlv_len_t len;
+       ssize_t len_len;
+       len_len = ber_fetch_length(0, cursor, HDRSIZE - (cursor - buf), &len);
+       if (len_len <= 0)
+               return NULL;
+       cursor += len_len + len;
+       return (cursor <= (buf + HDRSIZE)) ? cursor : NULL;
+ }
+ static int
+ guess_file_type(BIO **bio, unsigned char *hdrbuf)
+ {
+       unsigned char *ptr;
+       int res;
+       if (config_get_file_type() != FT_UNK)
+               return config_get_file_type();
+       res = BIO_read(*bio, hdrbuf, HDRSIZE);
+       if (res <= 0)
+               return op_crypto_err("Cannot guess file type; IO error.");
+       *bio = BIO_new_seq(BIO_new_mem_buf(hdrbuf, res), *bio);
+       if ((*bio) == NULL)
+               return op_crypto_err("BIO_new_seq() returned NULL.");
+       if (hdrbuf[0] != 0x30) {
+               pr_op_debug("File doesn't start with a SEQUENCE.");
+               return FT_UNK;
+       }
+       ptr = skip_sequence(hdrbuf, hdrbuf + 1);
+       if (ptr == NULL) {
+               pr_op_debug("Cannot skip first sequence length.");
+               return FT_UNK;
+       }
+       if (*ptr == 0x06) {
+               pr_op_debug("SEQ containing OID.");
+               return FT_ROA; /* Same parser for mfts and gbrs */
+       }
+       if (*ptr != 0x30) {
+               pr_op_debug("SEQ containing unexpected: 0x%x", *ptr);
+               return FT_UNK;
+       }
+       ptr = skip_sequence(hdrbuf, ptr + 1);
+       if (ptr == NULL) {
+               pr_op_debug("Cannot skip second sequence length.");
+               return FT_UNK;
+       }
+       ptr = skip_integer(hdrbuf, ptr + 1);
+       if (ptr == NULL) {
+               pr_op_debug("Cannot skip version number.");
+               return FT_UNK;
+       }
+       if (*ptr == 0x02) {
+               pr_op_debug("SEQ containing SEQ containing (INT, INT).");
+               return FT_CER;
+       }
+       if (*ptr == 0x30) {
+               pr_op_debug("SEQ containing SEQ containing (INT, SEQ).");
+               return FT_CRL;
+       }
+       pr_op_debug("SEQ containing SEQ containing unexpected: 0x%x", *ptr);
+       return FT_UNK;
+ }
+ static struct ContentInfo *
+ bio2ci(BIO *bio)
+ {
+ #define BUFFER_SIZE 4096
+       struct ContentInfo *ci = NULL;
+       unsigned char buffer[BUFFER_SIZE];
+       int res1;
+       asn_dec_rval_t res2;
+       do {
+               res1 = BIO_read(bio, buffer, BUFFER_SIZE);
+               if (res1 <= 0) {
+                       op_crypto_err("IO error.");
+                       goto fail;
+               }
+               res2 = ber_decode(&asn_DEF_ContentInfo, (void **)&ci,
+                                 buffer, res1);
+               pr_op_debug("Consumed: %zu", res2.consumed);
+               switch (res2.code) {
+               case RC_OK:
+                       return ci;
+               case RC_WMORE:
+                       break;
+               case RC_FAIL:
+                       pr_op_err("Unsuccessful parse.");
+                       goto fail;
+               }
+       } while (true);
+ fail: ASN_STRUCT_FREE(asn_DEF_ContentInfo, ci);
+       return NULL;
+ }
+ static json_t *
+ asn1c2json(BIO *bio)
+ {
+       struct ContentInfo *ci;
+       json_t *json;
+       ci = bio2ci(bio);
+       if (ci == NULL)
+               return NULL;
+       json = json_encode(&asn_DEF_ContentInfo, ci);
+       ASN_STRUCT_FREE(asn_DEF_ContentInfo, ci);
+       return json;
+ }
+ static int
+ __print_file(void)
+ {
+       BIO *bio;
+       unsigned char hdrbuf[HDRSIZE];
+       json_t *json = NULL;
+       int error;
+       bio = filename2bio(config_get_payload());
+       if (bio == NULL)
+               return pr_op_err("BIO_new_*() returned NULL.");
+       switch (guess_file_type(&bio, hdrbuf)) {
+       case FT_UNK:
+               BIO_free_all(bio);
+               return pr_op_err("Unrecognized file type.");
+       case FT_ROA:
+       case FT_MFT:
+       case FT_GBR:
+               json = asn1c2json(bio);
+               break;
+       case FT_CER:
+               json = Certificate_bio2json(bio);
+               break;
+       case FT_CRL:
+               json = CRL_bio2json(bio);
+               break;
+       }
+       BIO_free_all(bio);
+       if (json == NULL)
+               return pr_op_err("Unable to parse.");
+       errno = 0;
+       if (json_dumpf(json, stdout, JSON_INDENT(4)) < 0) {
+               error = errno;
+               if (error)
+                       pr_op_err("Error writing JSON to file: %s", strerror(error));
+               else
+                       pr_op_err("Unknown error writing JSON to file.");
+       } else {
+               error = 0;
+               printf("\n");
+       }
+       json_decref(json);
+       return error;
+ }
+ int
+ print_file(void)
+ {
+       int error;
+       error = bioseq_setup();
+       if (error)
+               return error;
+       error = __print_file();
+       bioseq_teardown();
+       return error;
+ }
diff --cc src/rrdp.c
index f99849d09d82c1b2f4c84a9cad8bde9be668201f,4e2a74ebd97a13a12f46d7c4deef5763ddbff93b..52c218e57bc8b8f288d332ec63b11177f623d07a
        fnstack_pop();
        return error;
  }
-       if (json_add_obj(json, TAGNAME_DELTAS, deltas))
 +
 +#define TAGNAME_SESSION "session_id"
 +#define TAGNAME_SERIAL "serial"
 +#define TAGNAME_DELTAS "deltas"
 +
 +/* binary to char */
 +static char
 +hash_b2c(unsigned char bin)
 +{
 +      bin &= 0xF;
 +      return (bin < 10) ? (bin + '0') : (bin + 'a' - 10);
 +}
 +
 +json_t *
 +rrdp_notif2json(struct cachefile_notification *notif)
 +{
 +      json_t *json;
 +      json_t *deltas;
 +      char hash_str[2 * RRDP_HASH_LEN + 1];
 +      struct rrdp_hash *hash;
 +      size_t i;
 +
 +      if (notif == NULL)
 +              return NULL;
 +
 +      json = json_object();
 +      if (json == NULL)
 +              enomem_panic();
 +
 +      if (json_add_str(json, TAGNAME_SESSION, notif->session.session_id))
 +              goto fail;
 +      if (json_add_str(json, TAGNAME_SERIAL, notif->session.serial.str))
 +              goto fail;
 +
 +      if (STAILQ_EMPTY(&notif->delta_hashes))
 +              return json; /* Happy path, but unlikely. */
 +
 +      deltas = json_array();
 +      if (deltas == NULL)
 +              enomem_panic();
++      if (json_object_add(json, TAGNAME_DELTAS, deltas))
 +              goto fail;
 +
 +      hash_str[2 * RRDP_HASH_LEN] = '\0';
 +      STAILQ_FOREACH(hash, &notif->delta_hashes, hook) {
 +              for (i = 0; i < RRDP_HASH_LEN; i++) {
 +                      hash_str[2 * i    ] = hash_b2c(hash->bytes[i] >> 4);
 +                      hash_str[2 * i + 1] = hash_b2c(hash->bytes[i]     );
 +              }
 +              if (json_array_append(deltas, json_string(hash_str)))
 +                      goto fail;
 +      }
 +
 +      return json;
 +
 +fail:
 +      json_decref(json);
 +      return NULL;
 +}
 +
 +static char
 +hash_c2b(char chara)
 +{
 +      if ('a' <= chara && chara <= 'f')
 +              return chara - 'a' + 10;
 +      if ('A' <= chara && chara <= 'F')
 +              return chara - 'A' + 10;
 +      if ('0' <= chara && chara <= '9')
 +              return chara - '0';
 +      return -1;
 +}
 +
 +static int
 +json2dh(json_t *json, struct rrdp_hash **result)
 +{
 +      char const *src;
 +      size_t srclen;
 +      struct rrdp_hash *dst;
 +      char digit;
 +      size_t i;
 +
 +      src = json_string_value(json);
 +      if (src == NULL)
 +              return pr_op_err("Hash is not a string.");
 +
 +      srclen = strlen(src);
 +      if (srclen != 2 * RRDP_HASH_LEN)
 +              return pr_op_err("Hash is not %d characters long.", 2 * RRDP_HASH_LEN);
 +
 +      dst = pmalloc(sizeof(struct rrdp_hash));
 +      for (i = 0; i < RRDP_HASH_LEN; i++) {
 +              digit = hash_c2b(src[2 * i]);
 +              if (digit == -1)
 +                      goto bad_char;
 +              dst->bytes[i] = digit << 4;
 +              digit = hash_c2b(src[2 * i + 1]);
 +              if (digit == -1)
 +                      goto bad_char;
 +              dst->bytes[i] |= digit;
 +      }
 +
 +      *result = dst;
 +      return 0;
 +
 +bad_char:
 +      free(dst);
 +      return pr_op_err("Invalid characters in hash: %c%c", src[2 * i], src[2 * i] + 1);
 +}
 +
 +static void
 +clear_delta_hashes(struct cachefile_notification *notif)
 +{
 +      struct rrdp_hash *hash;
 +
 +      while (!STAILQ_EMPTY(&notif->delta_hashes)) {
 +              hash = STAILQ_FIRST(&notif->delta_hashes);
 +              STAILQ_REMOVE_HEAD(&notif->delta_hashes, hook);
 +              free(hash);
 +      }
 +}
 +
 +int
 +rrdp_json2notif(json_t *json, struct cachefile_notification **result)
 +{
 +      struct cachefile_notification *notif;
 +      char const *str;
 +      json_t *jdeltas;
 +      size_t d, dn;
 +      struct rrdp_hash *hash;
 +      int error;
 +
 +      notif = pzalloc(sizeof(struct cachefile_notification));
 +      STAILQ_INIT(&notif->delta_hashes);
 +
 +      error = json_get_str(json, TAGNAME_SESSION, &str);
 +      if (error) {
 +              if (error > 0)
 +                      pr_op_err("Node is missing the '" TAGNAME_SESSION "' tag.");
 +              goto revert_notif;
 +      }
 +      notif->session.session_id = pstrdup(str);
 +
 +      error = json_get_str(json, TAGNAME_SERIAL, &str);
 +      if (error) {
 +              if (error > 0)
 +                      pr_op_err("Node is missing the '" TAGNAME_SERIAL "' tag.");
 +              goto revert_session;
 +      }
 +      notif->session.serial.str = pstrdup(str);
 +
 +      notif->session.serial.num = BN_create();
 +      if (!BN_dec2bn(&notif->session.serial.num, notif->session.serial.str)) {
 +              error = pr_op_err("Not a serial number: %s", notif->session.serial.str);
 +              goto revert_serial;
 +      }
 +
 +      error = json_get_array(json, TAGNAME_DELTAS, &jdeltas);
 +      if (error) {
 +              if (error > 0)
 +                      goto success;
 +              goto revert_serial;
 +      }
 +
 +      dn = json_array_size(jdeltas);
 +      if (dn == 0)
 +              goto success;
 +      if (dn > config_get_rrdp_delta_threshold())
 +              dn = config_get_rrdp_delta_threshold();
 +
 +      for (d = 0; d < dn; d++) {
 +              error = json2dh(json_array_get(jdeltas, d), &hash);
 +              if (error)
 +                      goto revert_deltas;
 +              STAILQ_INSERT_TAIL(&notif->delta_hashes, hash, hook);
 +      }
 +
 +success:
 +      *result = notif;
 +      return 0;
 +
 +revert_deltas:
 +      clear_delta_hashes(notif);
 +revert_serial:
 +      BN_free(notif->session.serial.num);
 +      free(notif->session.serial.str);
 +revert_session:
 +      free(notif->session.session_id);
 +revert_notif:
 +      free(notif);
 +      return error;
 +}
 +
 +void
 +rrdp_notif_free(struct cachefile_notification *notif)
 +{
 +      if (notif == NULL)
 +              return;
 +
 +      session_cleanup(&notif->session);
 +      clear_delta_hashes(notif);
 +      free(notif);
 +}
Simple merge
Simple merge
diff --cc src/types/uri.c
Simple merge
index a6e9971eec3f35af28966e0647cec81e2e8cb7d4,f1d7af946a625d2385ef985c9919a6f6a4cd3991..7419fdb3eca2498f95b46830f776518ea459db95
@@@ -20,14 -20,14 +20,15 @@@ AM_CFLAGS += -I../src -DUNIT_TESTING ${
  # autotools will even reprehend us if we declare it. Therefore, I came up with
  # "my" own "ldadd". Unlike AM_CFLAGS, it needs to be manually added to every
  # target.
- MY_LDADD = ${CHECK_LIBS}
+ MY_LDADD = ${CHECK_LIBS} ${JANSSON_LIBS}
  
  check_PROGRAMS  = address.test
 +check_PROGRAMS += base64.test
  check_PROGRAMS += cache.test
  check_PROGRAMS += db_table.test
  check_PROGRAMS += deltas_array.test
 -check_PROGRAMS += line_file.test
 +check_PROGRAMS += hash.test
+ check_PROGRAMS += json.test
  check_PROGRAMS += pb.test
  check_PROGRAMS += pdu_handler.test
  check_PROGRAMS += pdu_stream.test
@@@ -57,9 -54,12 +58,12 @@@ db_table_test_LDADD = ${MY_LDADD
  deltas_array_test_SOURCES = rtr/db/deltas_array_test.c
  deltas_array_test_LDADD = ${MY_LDADD}
  
 -line_file_test_SOURCES = line_file_test.c
 -line_file_test_LDADD = ${MY_LDADD}
 -
 +hash_test_SOURCES = crypto/hash_test.c
 +hash_test_LDADD = ${MY_LDADD}
 +
+ json_test_SOURCES = json_util_test.c
+ json_test_LDADD = ${MY_LDADD}
  pb_test_SOURCES = data_structure/path_builder_test.c
  pb_test_LDADD = ${MY_LDADD}
  
@@@ -97,10 -97,9 +101,7 @@@ xml_test_SOURCES = xml_test.
  xml_test_LDADD = ${MY_LDADD} ${XML2_LIBS}
  
  EXTRA_DIST  = mock.c mock.h
 -EXTRA_DIST += line_file/core.txt
 -EXTRA_DIST += line_file/empty.txt
 -EXTRA_DIST += line_file/error.txt
 +EXTRA_DIST += resources/lorem-ipsum.txt
- EXTRA_DIST += resources/line_file/core.txt
- EXTRA_DIST += resources/line_file/empty.txt
- EXTRA_DIST += resources/line_file/error.txt
  EXTRA_DIST += rtr/db/rtr_db_mock.c
  EXTRA_DIST += tal/lacnic.tal
  EXTRA_DIST += xml/notification.xml
index 824818fbd79bcfefbbf61654c30dee715f8ebb4e,7d175d065a6455486c481cafe4a8c9596aa71add..92d1736af8ca1796a2797816193aee61668f8557
@@@ -57,17 -57,8 +57,17 @@@ file_rm_rf(char const *file
        return ENOENT;
  }
  
 +int
 +file_rm_f(char const *file)
 +{
 +      file_rm_rf(file);
 +      return 0;
 +}
 +
 +MOCK_ABORT_INT(file_get_mtim, char const *file, time_t *ims)
 +
  static int
- pretend_download(struct rpki_uri *uri)
+ pretend_download(char const *local)
  {
        struct downloaded_path *dl;