]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
New cache design
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 7 Oct 2024 21:58:23 +0000 (15:58 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 7 Oct 2024 21:58:23 +0000 (15:58 -0600)
For #82.

It seems I'm finally done making dramatic wide-reaching changes to the
codebase. There's still plenty to add and test, but I would like to
start pushing atomic commits from now on.

This is a squashed version of development brach "issue82". It includes
a few merges with main.

```
cache/
    rsync/              # rsync refresh
        0/              # rsync module: rsync://a.b.c/mod1
            rpp1/       # Repository Publication Point 1
                d.mft
                d.crl
                d1.roa
            rpp2/
                e.mft
                e.crl
            ta.cer      # Trust Anchor
        1/              # rsync module: rsync://a.b.c/mod2
            ...
        2/              # rsync module: rsync://x.y.z/mod1
            ...
    https/              # HTTPS refresh
        0               # https://a.b.c/repo/ta.cer
        1               # https://x.y.z/repo/ta.cer
    rrdp/               # RRDP refresh
        0/              # https://m.n.o/notification.xml
            0           # rsync://m.n.o/mod1/rpp1/a.mft
            1           # rsync://m.n.o/mod1/rpp1/b.crl
            2           # rsync://m.n.o/mod1/rpp2/c.mft
            3           # rsync://m.n.o/mod1/rpp2/d.cer
        1/              # https://p.q.r/notification.xml
            ...
    fallback/           # Committed RPPs
        0/              # Fallback of rsync://a.b.c/mod1/rpp1
            0           # Hard link: cache/rsync/0/rpp1/d.mft
            1           # Hard link: cache/rsync/0/rpp1/d.crl
            2           # rsync://a.b.c/mod1/rpp1/d2.roa
                        # (Unique because of refresh)
        1               # Hard link: cache/https/0
        2/              # Fallback of m.n.o's rsync://m.n.o/mod1/rpp1
            0           # Hard link: cache/rrdp/0/0
            1           # Hard link: cache/rrdp/0/1
    index.json          # URL/path mappings and some metadata
```

- `cache/rsync`, `cache/https` and `cache/rrdp` contain "refreshes"
  (the exact latest files according to the servers). RRDP withdraws are
  honored, and rsyncs run without --compare-dest.
- "Refresh" files marked as valid are backed up in `cache/fallback`
  at the end of each validation cycle.
- Validation first tests fallback+refresh. (If a file exists in both,
  refresh wins.) If that fails, it retries with fallback only.
- The index is not a tree; everything is caged in numbered directories
  and indexed by exact URL, to prevent file overriding by URL hacking.

There's also a `cache/tmp` directory, where Fort temporarily dumps
notifications, snapshots and deltas. This directory will be removed
once #127 is fixed.

164 files changed:
Makefile.am
docs/CVE.md [new file with mode: 0644]
docs/_layouts/default.html
docs/usage.md
examples/config.json
man/fort.8
src/Makefile.am
src/abbreviations.txt
src/asn1/decode.c
src/asn1/decode.h
src/asn1/oid.c
src/asn1/oid.h
src/asn1/signed_data.c
src/asn1/signed_data.h
src/base64.c [moved from src/crypto/base64.c with 96% similarity]
src/base64.h [moved from src/crypto/base64.h with 100% similarity]
src/cache.c [new file with mode: 0644]
src/cache.h [new file with mode: 0644]
src/cache/local_cache.c [deleted file]
src/cache/local_cache.h [deleted file]
src/cachetmp [new file with mode: 0644]
src/cachetmp.c [new file with mode: 0644]
src/cachetmp.h [new file with mode: 0644]
src/cert_stack.c [deleted file]
src/cert_stack.h [deleted file]
src/certificate_refs.c
src/certificate_refs.h
src/common.c
src/common.h
src/config.c
src/config.h
src/config/incidences.c
src/config/string_array.c
src/config/time.c [new file with mode: 0644]
src/config/time.h [new file with mode: 0644]
src/config/work_offline.h
src/extension.c
src/file.c
src/file.h
src/hash.c [moved from src/crypto/hash.c with 87% similarity]
src/hash.h [moved from src/crypto/hash.h with 73% similarity]
src/http.c [moved from src/http/http.c with 94% similarity]
src/http.h [new file with mode: 0644]
src/http/http.h [deleted file]
src/incidence.c [moved from src/incidence/incidence.c with 94% similarity]
src/incidence.h [moved from src/incidence/incidence.h with 85% similarity]
src/init.c
src/json_handler.c
src/json_util.c
src/json_util.h
src/libcrypto_util.c
src/libcrypto_util.h
src/log.c
src/log.h
src/main.c
src/object/bgpsec.h
src/object/certificate.c
src/object/certificate.h
src/object/crl.c
src/object/crl.h
src/object/ghostbusters.c
src/object/ghostbusters.h
src/object/manifest.c
src/object/manifest.h
src/object/roa.c
src/object/roa.h
src/object/signed_object.c
src/object/signed_object.h
src/object/tal.c
src/object/tal.h
src/output_printer.c
src/print_file.c
src/relax_ng.c [moved from src/xml/relax_ng.c with 93% similarity]
src/relax_ng.h [moved from src/xml/relax_ng.h with 98% similarity]
src/resource.c
src/resource.h
src/resource/asn.c
src/resource/asn.h
src/resource/ip4.c
src/resource/ip6.c
src/rpp.c [deleted file]
src/rpp.h [deleted file]
src/rrdp.c
src/rrdp.h
src/rsync.c [moved from src/rsync/rsync.c with 50% similarity]
src/rsync.h [moved from src/rsync/rsync.h with 53% similarity]
src/rtr/db/db_table.c
src/rtr/db/db_table.h
src/rtr/db/delta.c
src/rtr/db/delta.h
src/rtr/db/deltas_array.c
src/rtr/db/vrps.c
src/rtr/db/vrps.h
src/rtr/err_pdu.c
src/rtr/pdu.h
src/rtr/pdu_handler.c
src/rtr/pdu_sender.c
src/rtr/pdu_sender.h
src/rtr/pdu_stream.c
src/rtr/pdu_stream.h
src/rtr/primitive_writer.h
src/rtr/rtr.c
src/rtr/rtr.h
src/slurm/db_slurm.c
src/slurm/db_slurm.h
src/slurm/slurm_loader.c
src/slurm/slurm_parser.c
src/state.c
src/state.h
src/thread_pool.c [moved from src/thread/thread_pool.c with 99% similarity]
src/thread_pool.h [moved from src/thread/thread_pool.h with 100% similarity]
src/thread_var.c
src/thread_var.h
src/types/address.h
src/types/array.h [moved from src/data_structure/common.h with 54% similarity]
src/types/arraylist.h [moved from src/data_structure/array_list.h with 91% similarity]
src/types/asn.h [moved from src/as_number.h with 63% similarity]
src/types/bio_seq.h
src/types/map.c
src/types/map.h
src/types/name.c [moved from src/object/name.c with 90% similarity]
src/types/name.h [moved from src/object/name.h with 79% similarity]
src/types/path.c [moved from src/data_structure/path_builder.c with 69% similarity]
src/types/path.h [moved from src/data_structure/path_builder.h with 50% similarity]
src/types/rpp.c [new file with mode: 0644]
src/types/rpp.h [new file with mode: 0644]
src/types/sorted_array.c [moved from src/sorted_array.c with 99% similarity]
src/types/sorted_array.h [moved from src/sorted_array.h with 92% similarity]
src/types/str.c [moved from src/str_token.c with 79% similarity]
src/types/str.h [moved from src/str_token.h with 75% similarity]
src/types/url.c [new file with mode: 0644]
src/types/url.h [new file with mode: 0644]
src/types/uthash.h [moved from src/data_structure/uthash.h with 99% similarity]
src/types/vrp.h
src/validation_handler.h
test/Makefile.am
test/base64_test.c [moved from test/crypto/base64_test.c with 99% similarity]
test/cache/local_cache_test.c [deleted file]
test/cache_test.c [new file with mode: 0644]
test/cache_util.c [new file with mode: 0644]
test/cache_util.h [new file with mode: 0644]
test/common_test.c [moved from test/json_util_test.c with 63% similarity]
test/hash_test.c [moved from test/crypto/hash_test.c with 80% similarity]
test/mock.c
test/mock_https.c [new file with mode: 0644]
test/object/manifest_test.c [new file with mode: 0644]
test/object/tal_test.c [moved from test/tal_test.c with 73% similarity]
test/resources/rrdp/notif-bad-uri-4.xml [new file with mode: 0644]
test/resources/tal/4urls-ignored-protos.tal [new file with mode: 0644]
test/resources/xml/notification.xml [moved from test/xml/notification.xml with 100% similarity]
test/rrdp_test.c
test/rrdp_update_test.c [new file with mode: 0644]
test/rrdp_util.h [new file with mode: 0644]
test/rsync_test.c [new file with mode: 0644]
test/rtr/db/deltas_array_test.c
test/rtr/db/vrps_test.c
test/rtr/pdu_handler_test.c
test/rtr/pdu_stream_test.c
test/thread_pool_test.c
test/types/map_test.c [deleted file]
test/types/path_test.c [moved from test/data_structure/path_builder_test.c with 99% similarity]
test/types/url_test.c [new file with mode: 0644]
test/types/uthash_test.c [moved from test/data_structure/uthash_test.c with 99% similarity]
test/xml_test.c

index 99305e9c259e2ff4100917b1bb466473843eae4c..df54e5d58e11d55db97b53dd85a09fbe8294cb57 100644 (file)
@@ -1,14 +1,4 @@
-# GNU wants us to include some files that we really don't want; therefore
-# "foreign". The files are
-#
-# - AUTHORS: This should be inferred from the (automatic) git history, not some
-#   error prone, manually-maintained file!
-# - ChangeLog: This is included in the main page of the site, which can be found
-#   in the gh-pages branch. Don't want to (nor should I) repeat myself.
-# - NEWS: Same as ChangeLog.
-# - README: We prefer the much gayer "README.md" version, so no thanks.
-#
-# Man, GNU conventions need a 21 century overhaul badly.
+# Don't want AUTHORS, ChangeLog, NEWS, README.
 AUTOMAKE_OPTIONS = foreign
 
 SUBDIRS = src man test
diff --git a/docs/CVE.md b/docs/CVE.md
new file mode 100644 (file)
index 0000000..a398eb5
--- /dev/null
@@ -0,0 +1,59 @@
+---
+title: CVE
+---
+
+# CVEs
+
+## CVE-2024-45238
+
+Certificate containing a malformed `subjectPublicKey` crashes Fort 1.6.2-, when compiled with OpenSSL < 3.
+
+| Description | A malicious RPKI repository that descends from a (trusted) Trust Anchor can serve (via rsync or RRDP) a resource certificate containing a bit string that doesn't properly decode into a Subject Public Key. OpenSSL does [not report this problem during parsing](https://github.com/openssl/openssl/blob/OpenSSL_1_1_1w/crypto/x509/x_pubkey.c#L152-L157), and when compiled with OpenSSL libcrypto versions below 3, Fort was recklessly dereferencing the pointer. |
+| Impact | Crash. (Potential unavailability of Route Origin Validation.) |
+| Patch | Commit [5689dea](https://github.com/NICMx/FORT-validator/commit/5689dea5e878fed28c5f338a27d7cda4151a14f1), released in Fort 1.6.3. |
+| Acknowledgments | Thanks to Niklas Vogel and Haya Schulmann for their research and disclosure. |
+
+## CVE-2024-45237
+
+Certificate containing a Key Usage bit string longer than 2 bytes causes buffer overflow on Fort 1.6.2-.
+
+| Description | A malicious RPKI repository that descends from a (trusted) Trust Anchor can serve (via rsync or RRDP) a resource certificate containing a [Key Usage extension](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3) consisting of more than two bytes of data. Fort used to write this string on a 2-byte buffer without properly sanitizing its length, leading to buffer overflow. |
+| Impact | Depending on compilation options, the vulnerability would lead to a crash (which might in turn lead to unavailability of Route Origin Validation), incorrect validation results or arbitrary code execution. |
+| Patch | Commit [939d988](https://github.com/NICMx/FORT-validator/commit/939d988551d17996be73f52c376a70a3d6ba69f9), released in Fort 1.6.3. |
+| Acknowledgments | Thanks to Niklas Vogel and Haya Schulmann for their research and disclosure. |
+
+## CVE-2024-45235
+
+Certificate containing an Authority Key Identifier missing a `keyIdentifier` crashes Fort 1.6.2-.
+
+| Description | A malicious RPKI repository that descends from a (trusted) Trust Anchor can serve (via rsync or RRDP) a resource certificate containing an [Authority Key Identifier extension](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1) missing the `keyIdentifier` field. Fort was referencing the pointer without sanitizing it first. |
+| Impact | Crash. (Potential unavailability of Route Origin Validation.) |
+| Patch | Commit [b1eb3c5](https://github.com/NICMx/FORT-validator/commit/b1eb3c507ae920859bbe294776ebc2bb30bb7e56), released in Fort 1.6.3. |
+| Acknowledgments | Thanks to Niklas Vogel and Haya Schulmann for their research and disclosure. |
+
+## CVE-2024-45236
+
+Signed Object containing empty `signedAttrs` crashes Fort 1.6.2-.
+
+| Description | A malicious RPKI repository that descends from a (trusted) Trust Anchor can serve (via rsync or RRDP) a signed object containing an empty [`signedAttributes`](https://datatracker.ietf.org/doc/html/rfc6488#section-2.1.6.4). Fort was accessing the set's elements without sanitizing it first. |
+| Impact | Crash. (Potential unavailability of Route Origin Validation.) |
+| Patch | Commit [4dafbd9](https://github.com/NICMx/FORT-validator/commit/4dafbd9de64a5a0616af97365bc1751465b29d2e), released in Fort 1.6.3. |
+| Acknowledgments | Thanks to Niklas Vogel and Haya Schulmann for their research and disclosure. |
+
+## CVE-2024-45239
+
+Signed Object containing null `eContent` crashes Fort 1.6.2-.
+
+| Description | A malicious RPKI repository that descends from a (trusted) Trust Anchor can serve (via rsync or RRDP) a ROA or Manifest containing a null [`eContent`](https://datatracker.ietf.org/doc/html/rfc6488#section-2.1.3.2). Fort was dereferencing the pointer without sanitizing it first. |
+| Impact | Crash. (Potential unavailability of Route Origin Validation.) |
+| Patch | Commit [942f921](https://github.com/NICMx/FORT-validator/commit/942f921ba7244cdcf4574cedc4c16392a7cc594b), released in Fort 1.6.3. |
+| Acknowledgments | Thanks to Niklas Vogel and Haya Schulmann for their research and disclosure. |
+
+## CVE-2024-45234
+
+Certificate containing `signedAttrs` not in canonical form crashes Fort 1.6.2-.
+
+| Description | A malicious RPKI repository that descends from a (trusted) Trust Anchor can serve (via rsync or RRDP) a ROA or Manifest containing a `signedAttrs` encoded in non-canonical form. This bypassed the BER-decoder, reaching a point in the code that panicked when faced with data not encoded in DER. |
+| Impact | Crash. (Potential unavailability of Route Origin Validation.) |
+| Patch | Commit [521b1a0](https://github.com/NICMx/FORT-validator/commit/521b1a0db5041258096fbabdf8fc1e10ecc793cf), released in Fort 1.6.3. |
+| Acknowledgments | Thanks to Niklas Vogel and Haya Schulmann for their research and disclosure. |
index 633ac07f0c5ffddcd9afbabe7ffa51bec25a4e6e..2b96e0de95cacf4973316cffa111176ab21dcc75 100644 (file)
@@ -36,9 +36,7 @@
                                <div class="navigation">
                                        <nav class="site-nav">
                                                <ul>
-                                                       <li>
-                                                               <a class="active-item" href="./index.html">FORT Validator</a>
-                                                       </li>
+                                                       <li><a class="active-item" href="./index.html">FORT Validator</a></li>
                                                </ul>
                                        </nav>
 
                        <div class="col-lg-3">
                                <aside class="site-aside">
                                        <ul class="list-bullet">
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/' or page.url == '/index.html' %} active{% endif %}" href="index.html">Home</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/intro-rpki.html' %} active{% endif %}" href="intro-rpki.html">Introduction to RPKI</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/intro-fort.html' %} active{% endif %}" href="intro-fort.html">Introduction to Fort</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/installation.html' %} active{% endif %}" href="installation.html">Compilation and Installation</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/run.html' %} active{% endif %}" href="run.html">Basic Usage</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/usage.html' %} active{% endif %}" href="usage.html">Program Arguments</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/logging.html' %} active{% endif %}" href="logging.html">Logging</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/slurm.html' %} active{% endif %}" href="slurm.html">SLURM</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/incidence.html' %} active{% endif %}" href="incidence.html">Incidences</a>
-                                               </li>
-                                               <li>
-                                                       <a class="item-menu{% if page.url == '/routers.html' %} active{% endif %}" href="routers.html">Routers</a>
-                                               </li>
+                                               <li><a class="item-menu{% if page.url == '/' or page.url == '/index.html' %} active{% endif %}" href="index.html">Home</a></li>
+                                               <li><a class="item-menu{% if page.url == '/intro-rpki.html' %} active{% endif %}" href="intro-rpki.html">Introduction to RPKI</a></li>
+                                               <li><a class="item-menu{% if page.url == '/intro-fort.html' %} active{% endif %}" href="intro-fort.html">Introduction to Fort</a></li>
+                                               <li><a class="item-menu{% if page.url == '/installation.html' %} active{% endif %}" href="installation.html">Compilation and Installation</a></li>
+                                               <li><a class="item-menu{% if page.url == '/run.html' %} active{% endif %}" href="run.html">Basic Usage</a></li>
+                                               <li><a class="item-menu{% if page.url == '/usage.html' %} active{% endif %}" href="usage.html">Program Arguments</a></li>
+                                               <li><a class="item-menu{% if page.url == '/logging.html' %} active{% endif %}" href="logging.html">Logging</a></li>
+                                               <li><a class="item-menu{% if page.url == '/slurm.html' %} active{% endif %}" href="slurm.html">SLURM</a></li>
+                                               <li><a class="item-menu{% if page.url == '/incidence.html' %} active{% endif %}" href="incidence.html">Incidences</a></li>
+                                               <li><a class="item-menu{% if page.url == '/routers.html' %} active{% endif %}" href="routers.html">Routers</a></li>
+                                               <li><a class="item-menu{% if page.url == '/CVE.html' %} active{% endif %}" href="CVE.html">CVEs</a></li>
                                        </ul>
                                </aside>
                        </div>
index 9c3dfb3add16fa63f6c01265ba203a4958628cef..ab1edd38db50803c7dfb3d4eeda806ccea6b575b 100644 (file)
@@ -65,6 +65,7 @@ description: Guide to use arguments of FORT Validator.
        51. [`--rsync.priority`](#--rsyncpriority)
        53. [`--rsync.retry.count`](#--rsyncretrycount)
        54. [`--rsync.retry.interval`](#--rsyncretryinterval)
+       40. [`--rsync.transfer-timeout`](#--rsynctransfer-timeout)
        55. [`--configuration-file`](#--configuration-file)
        56. [`rsync.program`](#rsyncprogram)
        57. [`rsync.arguments-recursive`](#rsyncarguments-recursive)
@@ -103,6 +104,7 @@ description: Guide to use arguments of FORT Validator.
        [--rsync.priority=<unsigned integer>]
        [--rsync.retry.count=<unsigned integer>]
        [--rsync.retry.interval=<unsigned integer>]
+       [--rsync.transfer-timeout=<unsigned integer>]
        [--http.enabled=true|false]
        [--http.priority=<unsigned integer>]
        [--http.retry.count=<unsigned integer>]
@@ -305,6 +307,7 @@ Assuming not much time has passed since the last time the repository was cached,
 
 - **Type:** Boolean (`true`, `false`)
 - **Availability:** `argv` and JSON
+- **Default:** `false`
 
 Skip the repository cache update?
 
@@ -316,6 +319,7 @@ Mostly intended for debugging. See [`--rsync.enabled`](#--rsyncenabled) and [`--
 
 - **Type:** Boolean (`true`, `false`)
 - **Availability:** `argv` and JSON
+- **Default:** `false`
 
 Send process to the background?
 
@@ -646,7 +650,7 @@ See [`--rsync.priority`](#--rsyncpriority).
 
 - **Type:** Integer
 - **Availability:** `argv` and JSON
-- **Default:** 0
+- **Default:** 1
 - **Range:** [0, [`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html)]
 
 Number of additional HTTP requests after a failed attempt.
@@ -657,7 +661,7 @@ If a transient error is returned when Fort tries to perform an HTTP transfer, it
 
 - **Type:** Integer
 - **Availability:** `argv` and JSON
-- **Default:** 5
+- **Default:** 4
 - **Range:** [0, [`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html)]
 
 Period of time (in seconds) to wait between each retry to request an HTTP URI.
@@ -704,7 +708,7 @@ The value specified (either by the argument or the default value) is utilized in
 
 - **Type:** Integer
 - **Availability:** `argv` and JSON
-- **Default:** 0
+- **Default:** 900
 - **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**_
@@ -774,6 +778,7 @@ Watch out for the following warning in the operation logs:
 
 - **Type:** String (Path to directory)
 - **Availability:** `argv` and JSON
+- **Default:** `NULL` (disabled)
 
 _**All requests are made using HTTPS, verifying the peer and the certificate name vs host**_
 
@@ -790,6 +795,7 @@ The value specified is utilized in libcurl's option [CURLOPT_CAPATH](https://cur
 
 - **Type:** String (Path to file)
 - **Availability:** `argv` and JSON
+- **Default:** `NULL` (disabled)
 
 File where the ROAs (found during each validation run) will be stored. See [`--output.format`](#--outputformat).
 
@@ -822,6 +828,7 @@ If `--output.roa` is omitted, the ROAs are not printed.
 
 - **Type:** String (Path to file)
 - **Availability:** `argv` and JSON
+- **Default:** `NULL` (disabled)
 
 > ![Warning!](img/warn.svg) BGPsec certificate validation has been disabled in version 1.5.2 because of [this bug](https://github.com/NICMx/FORT-validator/issues/58).
 
@@ -913,7 +920,7 @@ See [`--http.priority`](#--httppriority).
 
 - **Type:** Integer
 - **Availability:** `argv` and JSON
-- **Default:** 0
+- **Default:** 1
 - **Range:** [0, [`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html)]
 
 Maximum number of retries whenever there's an error executing an RSYNC.
@@ -926,15 +933,27 @@ Whenever is necessary to execute an RSYNC, the validator will try at least one t
 
 - **Type:** Integer
 - **Availability:** `argv` and JSON
-- **Default:** 5
+- **Default:** 4
 - **Range:** [0, [`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html)]
 
 Period of time (in seconds) to wait between each retry to execute an RSYNC.
 
+### `--rsync.transfer-timeout`
+
+- **Type:** Integer
+- **Availability:** `argv` and JSON
+- **Default:** 900
+- **Range:** [0, [`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html)]
+
+Maximum time in seconds that the rsync transfer can last.
+
+Once the connection is established with the server, the request will last a maximum of `rsync.transfer-timeout` seconds. A value of 0 means unlimited time.
+
 ### `--configuration-file`
 
 - **Type:** String (Path to file)
 - **Availability:** `argv` only
+- **Default:** `NULL` (disabled)
 
 Path to a JSON file from which additional configuration will be read.
 
@@ -974,6 +993,7 @@ The configuration options are mostly the same as the ones from the `argv` interf
                        "<a href="#--rsyncretrycount">count</a>": 1,
                        "<a href="#--rsyncretryinterval">interval</a>": 4
                },
+               "<a href="#--rsynctransfer-timeout">transfer-timeout</a>": 0,
                "<a href="#rsyncprogram">program</a>": "rsync",
                "<a href="#rsyncarguments-recursive">arguments-recursive</a>": [
                        "-rtz",
@@ -1146,7 +1166,6 @@ Does nothing as of Fort 1.6.0.
 
 - **Type:** Integer
 - **Availability:** `argv` and JSON
-- **Default:** 43200 (12 hours)
 - **Range:** [0, [`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html)]
 
 > ![img/warn.svg](img/warn.svg) This argument **is DEPRECATED**.
@@ -1157,7 +1176,6 @@ Does nothing as of Fort 1.6.0.
 
 - **Type:** Enumeration (`strict`, `root`, `root-except-ta`)
 - **Availability:** `argv` and JSON
-- **Default:** `root-except-ta`
 
 > ![img/warn.svg](img/warn.svg) This argument **is DEPRECATED**.
 
@@ -1167,7 +1185,6 @@ Does nothing as of Fort 1.6.0.
 
 - **Type:** String array
 - **Availability:** JSON only
-- **Default:** `[ "--times", "--contimeout=20", "--timeout=15", "--max-size=20MB", "--dirs", "$REMOTE", "$LOCAL" ]`
 
 > ![img/warn.svg](img/warn.svg) This argument **is DEPRECATED**.
 
@@ -1177,7 +1194,6 @@ Does nothing as of Fort 1.6.0.
 
 - **Type:** Integer
 - **Availability:** `argv` and JSON
-- **Default:** 5
 - **Range:** [1, 100]
 
 > ![img/warn.svg](img/warn.svg) This argument **is DEPRECATED**.
index 0c9ca9f28a700c2b09926d4039cd72ec40cbc849..034fff5c092f0dc9b682fee848fa14dfde929af3 100644 (file)
@@ -56,6 +56,7 @@
       "count": 2,
       "interval": 5
     },
+    "transfer-timeout": 900,
     "program": "rsync",
     "arguments-recursive": [
       "--recursive",
index 8a41857222a3d71c34ef95c491f1f922790f2ae3..43a66581988f268e36e15e0c18ac4cbfafe64ca1 100644 (file)
@@ -1,4 +1,4 @@
-.TH fort 8 "2024-05-24" "v1.6.2" "FORT validator"
+.TH fort 8 "2024-08-19" "v1.6.3" "FORT validator"
 
 .SH NAME
 fort \- RPKI validator and RTR server
@@ -813,7 +813,7 @@ unlimited time (default value).
 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 \fI0\fR.
+By default, it has a value of \fI900\fR.
 .RE
 .P
 
@@ -1022,6 +1022,18 @@ By default, the value is \fI5\fR.
 .RE
 .P
 
+.B \-\-rsync.transfer\-timeout=\fIUNSIGNED_INTEGER\fR
+.RS 4
+Maximum time in seconds that the rsync process can last.
+.P
+Once the connection is established with the server, the request will last a
+maximum of \fBrsync.transfer-timeout\fR seconds. A value of \fI0\fR means
+unlimited time (default value).
+.P
+By default, it has a value of \fI900\fR.
+.RE
+.P
+
 .B \-\-output.roa=\fIFILE\fR
 .RS 4
 File where the ROAs will be printed in the configured format (see
index 7bbc61b33cba7a72f7c66dd6b2852781f365b430..f30c90c2d9baacbb7e34db60bd286f1514675ed9 100644 (file)
-# Comment these out during releases.
-# Also increase optimization I guess (-O0 -> -On)
-LDFLAGS_DEBUG = -rdynamic
-
 bin_PROGRAMS = fort
 
 fort_SOURCES  = main.c
 
-fort_SOURCES += as_number.h
 fort_SOURCES += algorithm.h algorithm.c
 fort_SOURCES += alloc.h alloc.c
-fort_SOURCES += certificate_refs.h certificate_refs.c
-fort_SOURCES += cert_stack.h cert_stack.c
-fort_SOURCES += common.c common.h
-fort_SOURCES += config.h config.c
-fort_SOURCES += daemon.h daemon.c
-fort_SOURCES += extension.h extension.c
-fort_SOURCES += file.h file.c
-fort_SOURCES += init.h init.c
-fort_SOURCES += json_util.c json_util.h
-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
-fort_SOURCES += print_file.h print_file.c
-fort_SOURCES += resource.h resource.c
-fort_SOURCES += rpp.h rpp.c
-fort_SOURCES += rrdp.h rrdp.c
-fort_SOURCES += sorted_array.h sorted_array.c
-fort_SOURCES += state.h state.c
-fort_SOURCES += str_token.h str_token.c
-fort_SOURCES += thread_var.h thread_var.c
-fort_SOURCES += json_handler.h json_handler.c
-fort_SOURCES += validation_handler.h validation_handler.c
-
 fort_SOURCES += asn1/content_info.h asn1/content_info.c
 fort_SOURCES += asn1/decode.h asn1/decode.c
 fort_SOURCES += asn1/oid.h asn1/oid.c
 fort_SOURCES += asn1/signed_data.h asn1/signed_data.c
-
-fort_SOURCES += types/address.h types/address.c
-fort_SOURCES += types/bio_seq.c types/bio_seq.h
-fort_SOURCES += types/delta.c types/delta.h
-fort_SOURCES += types/router_key.c types/router_key.h
-fort_SOURCES += types/serial.h types/serial.c
-fort_SOURCES += types/map.h types/map.c
-fort_SOURCES += types/vrp.c types/vrp.h
-
-fort_SOURCES += cache/local_cache.c cache/local_cache.h
-
+fort_SOURCES += base64.h base64.c
+fort_SOURCES += cache.c cache.h
+fort_SOURCES += cachetmp.c cachetmp.h
+fort_SOURCES += certificate_refs.h certificate_refs.c
+fort_SOURCES += common.c common.h
 fort_SOURCES += config/boolean.c config/boolean.h
-fort_SOURCES += config/file_type.h config/file_type.c
 fort_SOURCES += config/filename_format.h config/filename_format.c
+fort_SOURCES += config/file_type.h config/file_type.c
+fort_SOURCES += config.h config.c
+fort_SOURCES += config/incidences.h config/incidences.c
 fort_SOURCES += config/log_conf.h config/log_conf.c
 fort_SOURCES += config/mode.c config/mode.h
-fort_SOURCES += config/incidences.h config/incidences.c
 fort_SOURCES += config/output_format.h config/output_format.c
 fort_SOURCES += config/str.c config/str.h
 fort_SOURCES += config/string_array.h config/string_array.c
+fort_SOURCES += config/time.h config/time.c
 fort_SOURCES += config/types.h
 fort_SOURCES += config/uint.c config/uint.h
 fort_SOURCES += config/work_offline.c config/work_offline.h
-
-fort_SOURCES += crypto/base64.h crypto/base64.c
-fort_SOURCES += crypto/hash.h crypto/hash.c
-
-fort_SOURCES += data_structure/array_list.h
-fort_SOURCES += data_structure/common.h
-fort_SOURCES += data_structure/path_builder.h data_structure/path_builder.c
-fort_SOURCES += data_structure/uthash.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
+fort_SOURCES += daemon.h daemon.c
+fort_SOURCES += extension.h extension.c
+fort_SOURCES += file.h file.c
+fort_SOURCES += hash.h hash.c
+fort_SOURCES += http.h http.c
+fort_SOURCES += incidence.h incidence.c
+fort_SOURCES += init.h init.c
+fort_SOURCES += json_handler.h json_handler.c
+fort_SOURCES += json_util.c json_util.h
+fort_SOURCES += libcrypto_util.h libcrypto_util.c
+fort_SOURCES += log.h log.c
+fort_SOURCES += nid.h nid.c
+#fort_SOURCES += object/bgpsec.h object/bgpsec.c
 fort_SOURCES += object/certificate.h object/certificate.c
 fort_SOURCES += object/crl.h object/crl.c
 fort_SOURCES += object/ghostbusters.h object/ghostbusters.c
 fort_SOURCES += object/manifest.h object/manifest.c
-fort_SOURCES += object/name.h object/name.c
 fort_SOURCES += object/roa.h object/roa.c
 fort_SOURCES += object/signed_object.h object/signed_object.c
 fort_SOURCES += object/tal.h object/tal.c
 fort_SOURCES += object/vcard.h object/vcard.c
-
+fort_SOURCES += output_printer.h output_printer.c
+fort_SOURCES += print_file.h print_file.c
+fort_SOURCES += relax_ng.c relax_ng.h
+fort_SOURCES += resource/asn.h resource/asn.c
+fort_SOURCES += resource.h resource.c
 fort_SOURCES += resource/ip4.h resource/ip4.c
 fort_SOURCES += resource/ip6.h resource/ip6.c
-fort_SOURCES += resource/asn.h resource/asn.c
-
-fort_SOURCES += rsync/rsync.h rsync/rsync.c
-
-fort_SOURCES += rtr/pdu_stream.c rtr/pdu_stream.h
+fort_SOURCES += rrdp.h rrdp.c
+fort_SOURCES += rsync.h rsync.c
+fort_SOURCES += rtr/db/db_table.c rtr/db/db_table.h
+fort_SOURCES += rtr/db/delta.c rtr/db/delta.h
+fort_SOURCES += rtr/db/deltas_array.c rtr/db/deltas_array.h
+fort_SOURCES += rtr/db/vrps.c rtr/db/vrps.h
 fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h
+fort_SOURCES += rtr/pdu.c rtr/pdu.h
 fort_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h
 fort_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h
-fort_SOURCES += rtr/pdu.c rtr/pdu.h
+fort_SOURCES += rtr/pdu_stream.c rtr/pdu_stream.h
 fort_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h
 fort_SOURCES += rtr/rtr.c rtr/rtr.h
-
-fort_SOURCES += rtr/db/db_table.c rtr/db/db_table.h
-fort_SOURCES += rtr/db/delta.c rtr/db/delta.h
-fort_SOURCES += rtr/db/deltas_array.c rtr/db/deltas_array.h
-fort_SOURCES += rtr/db/vrps.c rtr/db/vrps.h
-
 fort_SOURCES += slurm/db_slurm.c slurm/db_slurm.h
 fort_SOURCES += slurm/slurm_loader.c slurm/slurm_loader.h
 fort_SOURCES += slurm/slurm_parser.c slurm/slurm_parser.h
-
-fort_SOURCES += thread/thread_pool.c thread/thread_pool.h
-
-fort_SOURCES += xml/relax_ng.c xml/relax_ng.h
+fort_SOURCES += state.h state.c
+fort_SOURCES += thread_pool.c thread_pool.h
+fort_SOURCES += thread_var.h thread_var.c
+fort_SOURCES += types/address.h types/address.c
+fort_SOURCES += types/arraylist.h
+fort_SOURCES += types/asn.h
+fort_SOURCES += types/bio_seq.c types/bio_seq.h
+fort_SOURCES += types/delta.c types/delta.h
+fort_SOURCES += types/map.h types/map.c
+fort_SOURCES += types/name.h types/name.c
+fort_SOURCES += types/path.h types/path.c
+fort_SOURCES += types/router_key.c types/router_key.h
+fort_SOURCES += types/rpp.h types/rpp.c
+fort_SOURCES += types/serial.h types/serial.c
+fort_SOURCES += types/sorted_array.h types/sorted_array.c
+fort_SOURCES += types/str.h types/str.c
+fort_SOURCES += types/url.c types/url.h
+fort_SOURCES += types/uthash.h
+fort_SOURCES += types/vrp.c types/vrp.h
+fort_SOURCES += validation_handler.h validation_handler.c
 
 include asn1/asn1c/Makefile.include
 fort_SOURCES += $(ASN_MODULE_SRCS) $(ASN_MODULE_HDRS)
 
+# -Werror -Wno-unused
 fort_CFLAGS  = -Wall -Wpedantic
 #fort_CFLAGS += $(GCC_WARNS)
 fort_CFLAGS += -std=c99 -D_DEFAULT_SOURCE=1 -D_XOPEN_SOURCE=700 -D_BSD_SOURCE=1
 fort_CFLAGS += -O2 -g $(FORT_FLAGS) ${XML2_CFLAGS}
 if BACKTRACE_ENABLED
 fort_CFLAGS += -DBACKTRACE_ENABLED
+fort_LDFLAGS = -rdynamic
 endif
-fort_LDFLAGS = $(LDFLAGS_DEBUG)
 fort_LDADD   = ${JANSSON_LIBS} ${CURL_LIBS} ${XML2_LIBS}
 
-# I'm tired of scrolling up, but feel free to comment this out.
-GCC_WARNS  = -fmax-errors=1
-
+GCC_WARNS  = -fmax-errors=1 -fanalyzer
 GCC_WARNS += -pedantic-errors -Waddress -Walloc-zero -Walloca
 GCC_WARNS += -Wno-aggressive-loop-optimizations -Warray-bounds=2 -Wbool-compare
 GCC_WARNS += -Wbool-operation -Wno-builtin-declaration-mismatch -Wcast-align
-GCC_WARNS += -Wcast-qual -Wchar-subscripts -Wchkp -Wclobbered -Wcomment
+GCC_WARNS += -Wcast-qual -Wchar-subscripts -Wclobbered -Wcomment
 GCC_WARNS += -Wdangling-else -Wdate-time -Wdisabled-optimization
 GCC_WARNS += -Wdouble-promotion -Wduplicated-branches -Wduplicated-cond
 GCC_WARNS += -Wempty-body -Wenum-compare -Wexpansion-to-defined -Wfloat-equal
@@ -138,26 +118,16 @@ GCC_WARNS += -Wformat -Wformat-nonliteral -Wformat-overflow=2 -Wformat-security
 GCC_WARNS += -Wformat-signedness -Wformat-truncation=2 -Wformat-y2k
 GCC_WARNS += -Wframe-address -Wjump-misses-init -Wignored-qualifiers
 GCC_WARNS += -Wignored-attributes -Wincompatible-pointer-types
-
-# This is a fun one. Write "/* fallthrough */" to prevent a warning whenever
-# switch cases do not break.
 GCC_WARNS += -Wimplicit-fallthrough
-
 GCC_WARNS += -Wimplicit-function-declaration -Wimplicit-int -Winit-self -Winline
 GCC_WARNS += -Wint-in-bool-context -Winvalid-memory-model -Winvalid-pch
 GCC_WARNS += -Wlogical-op -Wlogical-not-parentheses -Wlong-long -Wmain
 GCC_WARNS += -Wmaybe-uninitialized -Wmemset-elt-size -Wmemset-transposed-args
 GCC_WARNS += -Wmisleading-indentation -Wmissing-braces -Wmissing-include-dirs
 GCC_WARNS += -Wnonnull -Wnonnull-compare -Wnormalized -Wnull-dereference
-
-# This one seems to be undocumented.
+# Seems undocumented
 GCC_WARNS += -Wodr
-
-# "Warn if the vectorizer cost model overrides the OpenMP or the Cilk Plus simd
-# directive set by user."
-# ... What?
 GCC_WARNS += -Wopenmp-simd
-
 GCC_WARNS += -Woverride-init-side-effects -Woverlength-strings -Wpacked
 GCC_WARNS += -Wpacked-bitfield-compat -Wparentheses -Wpointer-arith
 GCC_WARNS += -Wpointer-compare -Wredundant-decls -Wrestrict -Wreturn-type
@@ -176,18 +146,11 @@ GCC_WARNS += -Wunused-value -Wunused-variable -Wunused-const-variable=2
 GCC_WARNS += -Wunused-but-set-parameter -Wunused-but-set-variable
 GCC_WARNS += -Wvariadic-macros -Wvector-operation-performance -Wvla
 GCC_WARNS += -Wvolatile-register-var -Wwrite-strings
-
-# "Issue a warning when HSAIL cannot be emitted for the compiled function or
-# OpenMP construct."
-# Uh-huh.
 GCC_WARNS += -Whsa
-
-# I don't mind too much increasing these.
-# Just make sure that you know what you're doing.
+# Relatively arbitrary
 GCC_WARNS += -Wlarger-than=2048 -Walloc-size-larger-than=4096
 GCC_WARNS += -Wframe-larger-than=1024 -Wstack-usage=1024
-
 # Can't use because of dependencies: -Waggregate-return
-# Want to add, but needs work: -Wconversion, -Wsign-compare, -Wsign-conversion
+# Want to add, but need work: -Wconversion, -Wsign-compare, -Wsign-conversion
 # Seem to require other compiler features: -Wchkp, -Wstack-protector,
 #     -Wstrict-aliasing
index effa6d8cec1de7090e3a3c0202253033684819d0..cfdb82dcea634bad45cbb95daba7afb742724edc 100644 (file)
@@ -7,16 +7,19 @@ addr6: IPv6 address
 be: Big Endian
 cert: certificate
 certstack: certificate stack
+ck: check (test assertion, inherited from the unit test framework)
 db: database
 dl: download
 eof: end of file
 err: error
+fb: (Cached) fallback
 fd: File Descriptor (see `man 2 accept`)
 hdr: header
 hh: hash (table) hook
 ht: hash table
 id: identifier
 len: length
+map: mapping (between a URL and a local path in the cache)
 max: maximum
 min: minimum
 msg: message
index 1de93080e081b7f39f0d7e0a3b938cdda53bfb6e..e531cf26c02448f6eff4ad133b47abc86da660b5 100644 (file)
@@ -1,9 +1,6 @@
 #include "asn1/decode.h"
 
 #include "asn1/asn1c/ber_decoder.h"
-#include "asn1/asn1c/constraints.h"
-#include "common.h"
-#include "incidence/incidence.h"
 #include "log.h"
 
 #define COND_LOG(log, pr) (log ? pr : -EINVAL)
@@ -65,14 +62,18 @@ int
 asn1_decode_any(ANY_t *any, asn_TYPE_descriptor_t const *descriptor,
     void **result, bool log)
 {
-       return asn1_decode(any->buf, any->size, descriptor, result, log);
+       return (any != NULL)
+           ? asn1_decode(any->buf, any->size, descriptor, result, log)
+           : pr_val_err("ANY '%s' is NULL.", descriptor->name);
 }
 
 int
 asn1_decode_octet_string(OCTET_STRING_t *string,
     asn_TYPE_descriptor_t const *descriptor, void **result, bool log)
 {
-       return asn1_decode(string->buf, string->size, descriptor, result, log);
+       return (string != NULL)
+           ? asn1_decode(string->buf, string->size, descriptor, result, log)
+           : pr_val_err("Octet String '%s' is NULL.", descriptor->name);
 }
 
 /*
index f16cbccb88bebdc619099725b88b7856b4110a1c..2ec8b7e1c83fb9747c32afa8d9db8e42db75cbfa 100644 (file)
@@ -1,10 +1,7 @@
 #ifndef SRC_ASN1_DECODE_H_
 #define SRC_ASN1_DECODE_H_
 
-#include <stdbool.h>
-
 #include "asn1/asn1c/ANY.h"
-#include "asn1/asn1c/constr_TYPE.h"
 #include "file.h"
 
 int asn1_decode(const void *, size_t, asn_TYPE_descriptor_t const *, void **,
index 5a331c06fbe4eb9562287e043800a5167a7ea1b1..a57dda89a04d703a81481042279e9afb9e6397ae 100644 (file)
@@ -1,8 +1,8 @@
 #include "asn1/oid.h"
 
+#include <errno.h>
+
 #include "alloc.h"
-#include "asn1/decode.h"
-#include "common.h"
 #include "log.h"
 
 void
index 4fb051c5abe2f0bd0ab2756a9defa314ba79322b..c1d6dc0f8516a012ade2d143cfd86948fc93b262 100644 (file)
@@ -1,9 +1,10 @@
-#ifndef SRC_OID_H_
-#define SRC_OID_H_
+#ifndef SRC_ASN1_OID_H_
+#define SRC_ASN1_OID_H_
+
+#include <stdbool.h>
 
-#include "asn1/asn1c/ANY.h"
 #include "asn1/asn1c/OBJECT_IDENTIFIER.h"
-#include "common.h"
+#include "types/array.h"
 
 /* These objects are expected to live on the stack. */
 struct oid_arcs {
@@ -32,7 +33,6 @@ typedef asn_oid_arc_t OID[];
 #define OID_CONTENT_TYPE_ATTR        { 1, 2, 840, 113549, 1, 9, 3 }
 #define OID_MESSAGE_DIGEST_ATTR      { 1, 2, 840, 113549, 1, 9, 4 }
 #define OID_SIGNING_TIME_ATTR        { 1, 2, 840, 113549, 1, 9, 5 }
-#define OID_BINARY_SIGNING_TIME_ATTR { 1, 2, 840, 113549, 1, 9, 16, 2, 46 }
 
 #define OID_ROA                      { 1, 2, 840, 113549, 1, 9, 16, 1, 24 }
 #define OID_MANIFEST                 { 1, 2, 840, 113549, 1, 9, 16, 1, 26 }
@@ -51,4 +51,4 @@ bool arcs_equal_oids(struct oid_arcs *, asn_oid_arc_t const *, size_t);
  */
 #define ARCS_EQUAL_OIDS(a, b) arcs_equal_oids(a, b, ARRAY_LEN(b))
 
-#endif /* SRC_OID_H_ */
+#endif /* SRC_ASN1_OID_H_ */
index d19d8b126c50a1d011ba430f9d4eaa7aeaeb2f08..d7637910899259e5af44807008663cd2ec27db57 100644 (file)
@@ -2,37 +2,19 @@
 
 #include "algorithm.h"
 #include "alloc.h"
-#include "asn1/asn1c/ContentType.h"
 #include "asn1/asn1c/ContentTypePKCS7.h"
 #include "asn1/asn1c/MessageDigest.h"
 #include "asn1/asn1c/SignedDataPKCS7.h"
 #include "asn1/decode.h"
 #include "asn1/oid.h"
-#include "config.h"
-#include "crypto/hash.h"
+#include "hash.h"
 #include "log.h"
 #include "object/certificate.h"
-#include "thread_var.h"
+#include "types/name.h"
 
 static const OID oid_cta = OID_CONTENT_TYPE_ATTR;
 static const OID oid_mda = OID_MESSAGE_DIGEST_ATTR;
 static const OID oid_sta = OID_SIGNING_TIME_ATTR;
-static const OID oid_bsta = OID_BINARY_SIGNING_TIME_ATTR;
-
-void
-eecert_init(struct ee_cert *ee, STACK_OF(X509_CRL) *crls, bool force_inherit)
-{
-       ee->res = resources_create(RPKI_POLICY_RFC6484, force_inherit);
-       ee->crls = crls;
-       memset(&ee->refs, 0, sizeof(ee->refs));
-}
-
-void
-eecert_cleanup(struct ee_cert *ee)
-{
-       resources_destroy(ee->res);
-       refs_cleanup(&ee->refs);
-}
 
 static int
 get_sid(struct SignerInfo *sinfo, OCTET_STRING_t **result)
@@ -51,12 +33,10 @@ get_sid(struct SignerInfo *sinfo, OCTET_STRING_t **result)
 }
 
 static int
-handle_sdata_certificate(ANY_t *cert_encoded, struct ee_cert *ee,
+handle_sdata_certificate(ANY_t *cert_encoded, struct rpki_certificate *ee,
     OCTET_STRING_t *sid, ANY_t *signedData, SignatureValue_t *signature)
 {
        const unsigned char *otmp, *tmp;
-       X509 *cert;
-       enum rpki_policy policy;
        int error;
 
        /*
@@ -64,8 +44,6 @@ handle_sdata_certificate(ANY_t *cert_encoded, struct ee_cert *ee,
         * to a tree leaf. Loops aren't possible.
         */
 
-       pr_val_debug("EE Certificate (embedded) {");
-
        /*
         * "If the call is successful *in is incremented to the byte following
         * the parsed data."
@@ -75,45 +53,31 @@ handle_sdata_certificate(ANY_t *cert_encoded, struct ee_cert *ee,
         */
        tmp = (const unsigned char *) cert_encoded->buf;
        otmp = tmp;
-       cert = d2i_X509(NULL, &tmp, cert_encoded->size);
-       if (cert == NULL) {
-               error = val_crypto_err("Signed object's 'certificate' element does not decode into a Certificate");
-               goto end1;
-       }
-       if (tmp != otmp + cert_encoded->size) {
-               error = val_crypto_err("Signed object's 'certificate' element contains trailing garbage");
-               goto end2;
-       }
+       ee->x509 = d2i_X509(NULL, &tmp, cert_encoded->size);
+       if (ee->x509 == NULL)
+               return val_crypto_err("Signed object's 'certificate' element does not decode into a Certificate");
+       if (tmp != otmp + cert_encoded->size)
+               return val_crypto_err("Signed object's 'certificate' element contains trailing garbage");
 
-       x509_name_pr_debug("Issuer", X509_get_issuer_name(cert));
+       x509_name_pr_debug("Issuer", X509_get_issuer_name(ee->x509));
 
-       error = certificate_validate_chain(cert, ee->crls);
+       error = certificate_validate_chain(ee);
        if (error)
-               goto end2;
-       error = certificate_validate_rfc6487(cert, CERTYPE_EE);
-       if (error)
-               goto end2;
-       error = certificate_validate_extensions_ee(cert, sid, &ee->refs,
-           &policy);
+               return error;
+       error = certificate_validate_rfc6487(ee);
        if (error)
-               goto end2;
-       error = certificate_validate_aia(ee->refs.caIssuers, cert);
+               return error;
+       error = certificate_validate_extensions_ee(ee, sid);
        if (error)
-               goto end2;
-       error = certificate_validate_signature(cert, signedData, signature);
+               return error;
+       error = certificate_validate_aia(ee);
        if (error)
-               goto end2;
-
-       resources_set_policy(ee->res, policy);
-       error = certificate_get_resources(cert, ee->res, CERTYPE_EE);
+               return error;
+       error = certificate_validate_signature(ee->x509, signedData, signature);
        if (error)
-               goto end2;
-
-end2:
-       X509_free(cert);
-end1:
-       pr_val_debug("}");
-       return error;
+               return error;
+       resources_set_policy(ee->resources, ee->policy);
+       return certificate_get_resources(ee);
 }
 
 /* rfc6488#section-2.1.6.4.1 */
@@ -172,7 +136,6 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci)
        bool content_type_found = false;
        bool message_digest_found = false;
        bool signing_time_found = false;
-       bool binary_signing_time_found = false;
        int error;
 
        if (sinfo->signedAttrs == NULL)
@@ -222,15 +185,6 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci)
                        }
                        error = 0; /* No validations needed for now. */
                        signing_time_found = true;
-
-               } else if (ARCS_EQUAL_OIDS(&attrType, oid_bsta)) {
-                       if (binary_signing_time_found) {
-                               pr_val_err("Multiple BinarySigningTimes found.");
-                               goto illegal_attrType;
-                       }
-                       error = 0; /* No validations needed for now. */
-                       binary_signing_time_found = true;
-
                } else {
                        /* rfc6488#section-3.1.g */
                        pr_val_err("Illegal attrType OID in SignerInfo.");
@@ -248,6 +202,8 @@ validate_signed_attrs(struct SignerInfo *sinfo, EncapsulatedContentInfo_t *eci)
                return pr_val_err("SignerInfo lacks a ContentType attribute.");
        if (!message_digest_found)
                return pr_val_err("SignerInfo lacks a MessageDigest attribute.");
+       if (!signing_time_found)
+               return pr_val_err("SignerInfo lacks a SigningTime attribute.");
 
        return 0;
 
@@ -258,7 +214,7 @@ illegal_attrType:
 
 int
 signed_data_validate(ANY_t *encoded, struct SignedData *sdata,
-                    struct ee_cert *ee)
+     struct rpki_certificate *ee)
 {
        struct SignerInfo *sinfo;
        OCTET_STRING_t *sid = NULL;
@@ -383,12 +339,8 @@ signed_data_validate(ANY_t *encoded, struct SignedData *sdata,
                    sdata->certificates->list.count);
        }
 
-       error = handle_sdata_certificate(sdata->certificates->list.array[0],
+       return handle_sdata_certificate(sdata->certificates->list.array[0],
            ee, sid, encoded, &sinfo->signature);
-       if (error)
-               return error;
-
-       return 0;
 }
 
 /*
@@ -469,30 +421,32 @@ get_content_type_attr(struct SignedData *sdata, OBJECT_IDENTIFIER_t **result)
        bool equal;
 
        if (sdata == NULL)
-               return -EINVAL;
+               return pr_val_err("SignedData is NULL.");
        if (sdata->signerInfos.list.array == NULL)
-               return -EINVAL;
+               return pr_val_err("SignerInfos array is NULL.");
        if (sdata->signerInfos.list.array[0] == NULL)
-               return -EINVAL;
+               return pr_val_err("SignerInfos array first element is NULL.");
 
        signedAttrs = sdata->signerInfos.list.array[0]->signedAttrs;
+       if (signedAttrs == NULL)
+               return pr_val_err("signedAttrs is NULL.");
        if (signedAttrs->list.array == NULL)
-               return -EINVAL;
+               return pr_val_err("signedAttrs array is NULL.");
 
        for (i = 0; i < signedAttrs->list.count; i++) {
                attr = signedAttrs->list.array[i];
                if (!attr)
-                       return -EINVAL;
+                       return pr_val_err("signedAttrs array element %d is NULL.", i);
                error = oid2arcs(&attr->attrType, &arcs);
                if (error)
-                       return -EINVAL;
+                       return error;
                equal = ARCS_EQUAL_OIDS(&arcs, oid_cta);
                free_arcs(&arcs);
                if (equal) {
                        if (attr->attrValues.list.array == NULL)
-                               return -EINVAL;
+                               return pr_val_err("signedAttrs attrValue array is NULL.");
                        if (attr->attrValues.list.array[0] == NULL)
-                               return -EINVAL;
+                               return pr_val_err("signedAttrs attrValue array first element is NULL.");
                        return asn1_decode_any(attr->attrValues.list.array[0],
                            &asn_DEF_OBJECT_IDENTIFIER,
                            (void **) result, true);
index 3ab2d03c2ed87ca3839e33b21d5704f541106299..fc22061c777aa5febb63128af6cd1926402e3b7d 100644 (file)
@@ -1,30 +1,15 @@
-#ifndef SRC_SIGNED_DATA_H_
-#define SRC_SIGNED_DATA_H_
+#ifndef SRC_ASN1_SIGNED_DATA_H_
+#define SRC_ASN1_SIGNED_DATA_H_
 
 /* Some wrappers for asn1/asn1c/SignedData.h. */
 
 #include "asn1/asn1c/SignedData.h"
 #include "object/certificate.h"
-#include "resource.h"
-
-struct ee_cert {
-       /** CRL that might or might not revoke the EE certificate. */
-       STACK_OF(X509_CRL) *crls;
-       /** A copy of the resources carried by the EE certificate. */
-       struct resources *res;
-       /**
-        * A bunch of URLs found in the EE certificate's extensions,
-        * recorded for future validation.
-        */
-       struct certificate_refs refs;
-};
-
-void eecert_init(struct ee_cert *, STACK_OF(X509_CRL) *, bool);
-void eecert_cleanup(struct ee_cert *);
 
 int signed_data_decode(ANY_t *, struct SignedData **);
-int signed_data_validate(ANY_t *, struct SignedData *, struct ee_cert *);
+int signed_data_validate(ANY_t *, struct SignedData *,
+    struct rpki_certificate *);
 
 int get_content_type_attr(struct SignedData *, OBJECT_IDENTIFIER_t **);
 
-#endif /* SRC_SIGNED_DATA_H_ */
+#endif /* SRC_ASN1_SIGNED_DATA_H_ */
similarity index 96%
rename from src/crypto/base64.c
rename to src/base64.c
index c6fb61705e9281f73936d79e718c5e101a81c05a..560a1a5097c1e2f35c54ba8cd7f541abf4600081 100644 (file)
@@ -1,5 +1,6 @@
-#include "crypto/base64.h"
+#include "base64.h"
 
+#include <openssl/bio.h>
 #include <openssl/buffer.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -33,17 +34,21 @@ base64_decode(char *in, size_t in_len, unsigned char **out, size_t *out_len)
 
        status = EVP_DecodeUpdate(ctx, result, &outl, (unsigned char *)in, in_len);
        if (status == -1)
-               return false;
+               goto cancel;
 
        *out_len = outl;
 
        status = EVP_DecodeFinal(ctx, result + outl, &outl);
        if (status != 1)
-               return false;
+               goto cancel;
 
+       EVP_ENCODE_CTX_free(ctx);
        *out = result;
        *out_len += outl;
        return true;
+
+cancel:        EVP_ENCODE_CTX_free(ctx);
+       return false;
 }
 
 /*
similarity index 100%
rename from src/crypto/base64.h
rename to src/base64.h
diff --git a/src/cache.c b/src/cache.c
new file mode 100644 (file)
index 0000000..e1f2d4c
--- /dev/null
@@ -0,0 +1,1182 @@
+#include "cache.h"
+
+#include <ftw.h>
+#include <stdbool.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include "alloc.h"
+#include "cachetmp.h"
+#include "common.h"
+#include "config.h"
+#include "configure_ac.h"
+#include "file.h"
+#include "http.h"
+#include "json_util.h"
+#include "log.h"
+#include "rrdp.h"
+#include "rsync.h"
+#include "types/array.h"
+#include "types/path.h"
+#include "types/url.h"
+#include "types/uthash.h"
+
+struct cache_node {
+       struct cache_mapping map;
+
+       /* XXX change to boolean? */
+       int fresh;              /* Refresh already attempted? */
+       int dlerr;              /* Result code of recent download attempt */
+       time_t mtim;            /* Last successful download time, or zero */
+
+       struct rrdp_state *rrdp;
+
+       UT_hash_handle hh;      /* Hash table hook */
+};
+
+typedef int (*dl_cb)(struct cache_node *rpp);
+
+struct cache_table {
+       char const *name;
+       bool enabled;
+       struct cache_sequence seq;
+       struct cache_node *nodes; /* Hash Table */
+       dl_cb download;
+};
+
+static struct rpki_cache {
+       /* Latest view of the remote rsync modules */
+       struct cache_table rsync;
+       /* Latest view of the remote HTTPS TAs */
+       struct cache_table https;
+       /* Latest view of the remote RRDP cages */
+       struct cache_table rrdp;
+       /* Committed RPPs and TAs (offline fallback hard links) */
+       struct cache_table fallback;
+} cache;
+
+struct cache_cage {
+       struct cache_node *refresh;
+       struct cache_node *fallback;
+};
+
+struct cache_commit {
+       char *caRepository;
+       struct cache_mapping *files;
+       size_t nfiles;
+       STAILQ_ENTRY(cache_commit) lh;
+};
+
+STAILQ_HEAD(cache_commits, cache_commit) commits = STAILQ_HEAD_INITIALIZER(commits);
+
+#define CACHE_METAFILE "cache.json"
+#define TAGNAME_VERSION "fort-version"
+#define TAL_METAFILE "tal.json"
+
+#ifdef UNIT_TESTING
+static void __delete_node_cb(struct cache_node const *);
+#endif
+
+static void
+delete_node(struct cache_table *tbl, struct cache_node *node)
+{
+#ifdef UNIT_TESTING
+       __delete_node_cb(node);
+#endif
+
+       HASH_DEL(tbl->nodes, node);
+
+       map_cleanup(&node->map);
+       rrdp_state_free(node->rrdp);
+       free(node);
+}
+
+static char *
+get_cache_filename(char const *name, bool fatal)
+{
+       struct path_builder pb;
+       int error;
+
+       error = pb_init_cache(&pb, name);
+       if (error) {
+               if (fatal) {
+                       pr_crit("Cannot create path to %s: %s", name,
+                           strerror(error));
+               } else {
+                       pr_op_err("Cannot create path to %s: %s", name,
+                           strerror(error));
+                       return NULL;
+               }
+       }
+
+       return pb.string;
+}
+
+char *
+get_rsync_module(char const *url)
+{
+       array_index u;
+       unsigned int slashes;
+
+       slashes = 0;
+       for (u = 0; url[u] != 0; u++)
+               if (url[u] == '/') {
+                       slashes++;
+                       if (slashes == 4)
+                               return pstrndup(url, u);
+               }
+
+       if (slashes == 3 && url[u - 1] != '/')
+               return pstrdup(url);
+
+       pr_val_err("Url '%s' does not appear to have an rsync module.", url);
+       return NULL;
+}
+
+char const *
+strip_rsync_module(char const *url)
+{
+       array_index u;
+       unsigned int slashes;
+
+       slashes = 0;
+       for (u = 0; url[u] != 0; u++)
+               if (url[u] == '/') {
+                       slashes++;
+                       if (slashes == 4)
+                               return url + u + 1;
+               }
+
+       return NULL;
+}
+
+static int
+write_simple_file(char const *filename, char const *content)
+{
+       FILE *file;
+       int error;
+
+       file = fopen(filename, "w");
+       if (file == NULL)
+               goto fail;
+
+       if (fprintf(file, "%s", content) < 0)
+               goto fail;
+
+       fclose(file);
+       return 0;
+
+fail:
+       error = errno;
+       pr_op_err("Cannot write %s: %s", filename, strerror(error));
+       if (file != NULL)
+               fclose(file);
+       return error;
+}
+
+static int dl_rsync(struct cache_node *);
+static int dl_http(struct cache_node *);
+static int dl_rrdp(struct cache_node *);
+
+static void
+init_table(struct cache_table *tbl, char const *name, bool enabled, dl_cb dl)
+{
+       memset(tbl, 0, sizeof(*tbl));
+       tbl->name = name;
+       tbl->enabled = enabled;
+       cseq_init(&tbl->seq, path_join(config_get_local_repository(), name));
+       tbl->download = dl;
+}
+
+static void
+init_tables(void)
+{
+       init_table(&cache.rsync, "rsync", config_get_rsync_enabled(), dl_rsync);
+       init_table(&cache.https, "https", config_get_http_enabled(), dl_http);
+       init_table(&cache.rrdp, "rrdp", config_get_http_enabled(), dl_rrdp);
+       init_table(&cache.fallback, "fallback", true, NULL);
+}
+
+static void
+init_cache_metafile(void)
+{
+       char *filename;
+       json_t *root;
+       json_error_t jerror;
+       char const *file_version;
+       int error;
+
+       filename = get_cache_filename(CACHE_METAFILE, true);
+       root = json_load_file(filename, 0, &jerror);
+
+       if (root == NULL) {
+               if (json_error_code(&jerror) == json_error_cannot_open_file)
+                       pr_op_debug("%s does not exist.", filename);
+               else
+                       pr_op_err("Json parsing failure at %s (%d:%d): %s",
+                           filename, jerror.line, jerror.column, jerror.text);
+               goto invalid_cache;
+       }
+       if (json_typeof(root) != JSON_OBJECT) {
+               pr_op_err("The root tag of %s is not an object.", filename);
+               goto invalid_cache;
+       }
+
+       error = json_get_str(root, TAGNAME_VERSION, &file_version);
+       if (error) {
+               if (error > 0)
+                       pr_op_err("%s is missing the " TAGNAME_VERSION " tag.",
+                           filename);
+               goto invalid_cache;
+       }
+
+       if (strcmp(file_version, PACKAGE_VERSION) == 0)
+               goto end;
+
+invalid_cache:
+       pr_op_info("The cache appears to have been built by a different version of Fort. I'm going to clear it, just to be safe.");
+       file_rm_rf(config_get_local_repository());
+
+end:   json_decref(root);
+       free(filename);
+}
+
+static void
+init_cachedir_tag(void)
+{
+       char *filename;
+
+       filename = get_cache_filename("CACHEDIR.TAG", false);
+       if (filename == NULL)
+               return;
+
+       if (file_exists(filename) == ENOENT)
+               write_simple_file(filename,
+                  "Signature: 8a477f597d28d172789f06886806bc55\n"
+                  "# This file is a cache directory tag created by Fort.\n"
+                  "# For information about cache directory tags, see:\n"
+                  "#   https://bford.info/cachedir/\n");
+
+       free(filename);
+}
+
+static int
+init_tmp_dir(void)
+{
+       char *dirname;
+       int error;
+
+       dirname = get_cache_filename(CACHE_TMPDIR, true);
+
+       if (mkdir(dirname, CACHE_FILEMODE) < 0) {
+               error = errno;
+               if (error != EEXIST)
+                       return pr_op_err("Cannot create '%s': %s",
+                           dirname, strerror(error));
+       }
+
+       free(dirname);
+       return 0;
+}
+
+int
+cache_setup(void)
+{
+       // XXX Lock the cache directory
+       init_tables();
+       init_cache_metafile();
+       init_tmp_dir();
+       init_cachedir_tag();
+       return 0;
+}
+
+void
+cache_teardown(void)
+{
+       char *filename;
+
+       filename = get_cache_filename(CACHE_METAFILE, false);
+       if (filename == NULL)
+               return;
+
+       write_simple_file(filename, "{ \"" TAGNAME_VERSION "\": \""
+           PACKAGE_VERSION "\" }\n");
+       free(filename);
+}
+
+static char *
+get_tal_json_filename(void)
+{
+       struct path_builder pb;
+       return pb_init_cache(&pb, TAL_METAFILE) ? NULL : pb.string;
+}
+
+static struct cache_node *
+json2node(json_t *json)
+{
+       struct cache_node *node;
+       char const *str;
+       json_t *rrdp;
+       int error;
+
+       node = pzalloc(sizeof(struct cache_node));
+
+       if (json_get_str(json, "url", &str))
+               goto fail;
+       node->map.url = pstrdup(str);
+       if (json_get_str(json, "path", &str))
+               goto fail;
+       node->map.path = pstrdup(str);
+       if (json_get_int(json, "dlerr", &node->dlerr))
+               goto fail;
+       if (json_get_ts(json, "mtim", &node->mtim))
+               goto fail;
+       error = json_get_object(json, "rrdp", &rrdp);
+       if (error < 0)
+               goto fail;
+       if (error == 0 && rrdp_json2state(rrdp, &node->rrdp))
+               goto fail;
+
+       return node;
+
+fail:  map_cleanup(&node->map);
+       return NULL;
+}
+
+static void
+json2tbl(json_t *root, struct cache_table *tbl)
+{
+       json_t *array, *child;
+       int index;
+       struct cache_node *node;
+       size_t urlen;
+
+       // XXX load (and save) seqs
+       if (json_get_ulong(root, "next", &tbl->seq.next_id))
+               return;
+       if (json_get_array(root, tbl->name, &array))
+               return;
+
+       json_array_foreach(array, index, child) {
+               node = json2node(child);
+               if (node == NULL)
+                       continue;
+               urlen = strlen(node->map.url);
+               // XXX worry about dupes
+               HASH_ADD_KEYPTR(hh, tbl->nodes, node->map.url, urlen, node);
+       }
+}
+
+static void
+load_tal_json(void)
+{
+       char *filename;
+       json_t *root;
+       json_error_t jerror;
+
+       /*
+        * Note: Loading TAL_METAFILE is one of few things Fort can fail at
+        * without killing itself. It's just a cache of a cache.
+        */
+
+       filename = get_tal_json_filename();
+       if (filename == NULL)
+               return;
+
+       pr_op_debug("Loading %s.", filename);
+
+       root = json_load_file(filename, 0, &jerror);
+
+       if (root == NULL) {
+               if (json_error_code(&jerror) == json_error_cannot_open_file)
+                       pr_op_debug("%s does not exist.", filename);
+               else
+                       pr_op_err("Json parsing failure at %s (%d:%d): %s",
+                           filename, jerror.line, jerror.column, jerror.text);
+               goto end;
+       }
+       if (json_typeof(root) != JSON_OBJECT) {
+               pr_op_err("The root tag of %s is not an object.", filename);
+               goto end;
+       }
+
+       json2tbl(root, &cache.rsync);
+       json2tbl(root, &cache.https);
+       json2tbl(root, &cache.rrdp);
+       json2tbl(root, &cache.fallback);
+
+end:   json_decref(root);
+       free(filename);
+}
+
+void
+cache_prepare(void)
+{
+       load_tal_json();
+}
+
+static json_t *
+node2json(struct cache_node *node)
+{
+       json_t *json;
+
+       json = json_obj_new();
+       if (json == NULL)
+               return NULL;
+
+       if (json_add_str(json, "url", node->map.url))
+               goto fail;
+       if (json_add_str(json, "path", node->map.path))
+               goto fail;
+       if (json_add_int(json, "dlerr", node->dlerr)) // XXX relevant?
+               goto fail;
+       if (json_add_ts(json, "mtim", node->mtim))
+               goto fail;
+       if (node->rrdp)
+               if (json_object_add(json, "rrdp", rrdp_state2json(node->rrdp)))
+                       goto fail;
+
+       return json;
+
+fail:  json_decref(json);
+       return NULL;
+}
+
+static json_t *
+tbl2json(struct cache_table *tbl)
+{
+       struct json_t *json, *nodes;
+       struct cache_node *node, *tmp;
+
+       json = json_obj_new();
+       if (!json)
+               return NULL;
+
+       if (json_add_ulong(json, "next", tbl->seq.next_id))
+               goto fail;
+
+       nodes = json_array_new();
+       if (!nodes)
+               goto fail;
+       if (json_object_add(json, "nodes", nodes))
+               goto fail;
+
+       HASH_ITER(hh, tbl->nodes, node, tmp)
+               if (json_array_add(nodes, node2json(node)))
+                       goto fail;
+
+       return json;
+
+fail:  json_decref(json);
+       return NULL;
+}
+
+static json_t *
+build_tal_json(void)
+{
+       json_t *json;
+
+       json = json_obj_new();
+       if (json == NULL)
+               return NULL;
+
+       if (json_object_add(json, "rsync", tbl2json(&cache.rsync)))
+               goto fail;
+       if (json_object_add(json, "https", tbl2json(&cache.https)))
+               goto fail;
+       if (json_object_add(json, "rrdp", tbl2json(&cache.rrdp)))
+               goto fail;
+       if (json_object_add(json, "fallback", tbl2json(&cache.fallback)))
+               goto fail;
+
+       return json;
+
+fail:  json_decref(json);
+       return NULL;
+}
+
+static void
+write_tal_json(void)
+{
+       char *filename;
+       struct json_t *json;
+
+       json = build_tal_json();
+       if (json == NULL)
+               return;
+
+       filename = get_tal_json_filename();
+       if (filename == NULL)
+               goto end;
+
+       if (json_dump_file(json, filename, JSON_INDENT(2)))
+               pr_op_err("Unable to write %s; unknown cause.", filename);
+
+end:   json_decref(json);
+       free(filename);
+}
+
+static int
+dl_rsync(struct cache_node *module)
+{
+       int error;
+
+       error = rsync_download(module->map.url, module->map.path);
+       if (error)
+               return error;
+
+       module->mtim = time_nonfatal(); /* XXX probably not needed */
+       return 0;
+}
+
+static int
+dl_rrdp(struct cache_node *notif)
+{
+       time_t mtim;
+       bool changed;
+       int error;
+
+       mtim = time_nonfatal();
+
+       error = rrdp_update(&notif->map, notif->mtim, &changed, &cache.rrdp.seq,
+           &notif->rrdp);
+       if (error)
+               return error;
+
+       if (changed)
+               notif->mtim = mtim;
+       return 0;
+}
+
+static int
+dl_http(struct cache_node *file)
+{
+       time_t mtim;
+       bool changed;
+       int error;
+
+       mtim = time_nonfatal();
+
+       error = http_download(file->map.url, file->map.path,
+           file->mtim, &changed);
+       if (error)
+               return error;
+
+       if (changed)
+               file->mtim = mtim;
+       return 0;
+}
+
+static struct cache_node *
+find_node(struct cache_table *tbl, char const *url, size_t urlen)
+{
+       struct cache_node *node;
+       HASH_FIND(hh, tbl->nodes, url, urlen, node);
+       return node;
+}
+
+static struct cache_node *
+provide_node(struct cache_table *tbl, char const *url)
+{
+       size_t urlen;
+       struct cache_node *node;
+
+       urlen = strlen(url);
+       node = find_node(tbl, url, urlen);
+       if (node)
+               return node;
+
+       node = pzalloc(sizeof(struct cache_node));
+       node->map.url = pstrdup(url);
+       node->map.path = cseq_next(&tbl->seq);
+       if (!node->map.path) {
+               free(node->map.url);
+               free(node);
+               return NULL;
+       }
+       HASH_ADD_KEYPTR(hh, tbl->nodes, node->map.url, urlen, node);
+
+       return node;
+}
+
+/* @uri is either a caRepository or a rpkiNotify */
+static struct cache_node *
+do_refresh(struct cache_table *tbl, char const *uri)
+{
+       struct cache_node *node;
+
+       pr_val_debug("Trying %s (online)...", uri);
+
+       if (!tbl->enabled) {
+               pr_val_debug("Protocol disabled.");
+               return NULL;
+       }
+
+       if (tbl == &cache.rsync) {
+               char *module = get_rsync_module(uri);
+               if (module == NULL)
+                       return NULL;
+               node = provide_node(tbl, module);
+               free(module);
+       } else {
+               node = provide_node(tbl, uri);
+       }
+       if (!node)
+               return NULL;
+
+       if (!node->fresh) {
+               node->fresh = true;
+               node->dlerr = tbl->download(node);
+       }
+
+       pr_val_debug(node->dlerr ? "Refresh failed." : "Refresh succeeded.");
+       return node;
+}
+
+static struct cache_node *
+get_fallback(char const *caRepository)
+{
+       struct cache_node *node;
+
+       pr_val_debug("Retrieving %s fallback...", caRepository);
+       node = find_node(&cache.fallback, caRepository, strlen(caRepository));
+       pr_val_debug(node ? "Fallback found." : "Fallback unavailable.");
+
+       return node;
+}
+
+/* Do not free nor modify the result. */
+char *
+cache_refresh_url(char const *url)
+{
+       struct cache_node *node = NULL;
+
+       // XXX mutex
+       // XXX review result signs
+       // XXX Normalize @url
+
+       if (url_is_https(url))
+               node = do_refresh(&cache.https, url);
+       else if (url_is_rsync(url))
+               node = do_refresh(&cache.rsync, url);
+
+       // XXX Maybe strdup path so the caller can't corrupt our string
+       return (node && !node->dlerr) ? node->map.path : NULL;
+}
+
+/* Do not free nor modify the result. */
+char *
+cache_fallback_url(char const *url)
+{
+       struct cache_node *node;
+
+       pr_val_debug("Trying %s (offline)...", url);
+
+       node = find_node(&cache.fallback, url, strlen(url));
+       if (!node) {
+               pr_val_debug("Cache data unavailable.");
+               return NULL;
+       }
+
+       return node->map.path;
+}
+
+/*
+ * Attempts to refresh the RPP described by @sias, returns the resulting
+ * repository's mapper.
+ *
+ * XXX Need to normalize the sias.
+ * XXX Fallback only if parent is fallback
+ */
+struct cache_cage *
+cache_refresh_sias(struct sia_uris *sias)
+{
+       struct cache_cage *cage;
+       struct cache_node *node;
+
+       // XXX Make sure somewhere validates rpkiManifest matches caRepository.
+       // XXX mutex
+       // XXX review result signs
+       // XXX normalize rpkiNotify & caRepository?
+
+       cage = pzalloc(sizeof(struct cache_cage));
+       cage->fallback = get_fallback(sias->caRepository);
+
+       if (sias->rpkiNotify) {
+               node = do_refresh(&cache.rrdp, sias->rpkiNotify);
+               if (node && !node->dlerr) {
+                       cage->refresh = node;
+                       return cage; /* RRDP + optional fallback happy path */
+               }
+       }
+
+       node = do_refresh(&cache.rsync, sias->caRepository);
+       if (node && !node->dlerr) {
+               cage->refresh = node;
+               return cage; /* rsync + optional fallback happy path */
+       }
+
+       if (cage->fallback == NULL) {
+               free(cage);
+               return NULL;
+       }
+
+       return cage; /* fallback happy path */
+}
+
+char const *
+node2file(struct cache_node *node, char const *url)
+{
+       if (node == NULL)
+               return NULL;
+       // XXX RRDP is const, rsync needs to be freed
+       return (node->rrdp)
+           ? /* RRDP  */ rrdp_file(node->rrdp, url)
+           : /* rsync */ path_join(node->map.path, strip_rsync_module(url));
+}
+
+char const *
+cage_map_file(struct cache_cage *cage, char const *url)
+{
+       char const *file;
+
+       file = node2file(cage->refresh, url);
+       if (!file)
+               file = node2file(cage->fallback, url);
+
+       return file;
+}
+
+/* Returns true if fallback should be attempted */
+bool
+cage_disable_refresh(struct cache_cage *cage)
+{
+       bool enabled = (cage->refresh != NULL);
+       cage->refresh = NULL;
+
+       if (cage->fallback == NULL) {
+               pr_val_debug("There is no fallback.");
+               return false;
+       }
+       if (!enabled) {
+               pr_val_debug("Fallback exhausted.");
+               return false;
+       }
+
+       pr_val_debug("Attempting fallback.");
+       return true;
+}
+
+/*
+ * Steals ownership of @rpp->files and @rpp->nfiles, but they're not going to be
+ * modified nor deleted until the cache cleanup.
+ */
+void
+cache_commit_rpp(char const *caRepository, struct rpp *rpp)
+{
+       struct cache_commit *commit;
+
+       commit = pmalloc(sizeof(struct cache_commit));
+       // XXX missing context
+       commit->caRepository = pstrdup(caRepository);
+       commit->files = rpp->files;
+       commit->nfiles = rpp->nfiles;
+       STAILQ_INSERT_TAIL(&commits, commit, lh);
+
+       rpp->files = NULL;
+       rpp->nfiles = 0;
+}
+
+void
+cache_commit_file(struct cache_mapping *map)
+{
+       struct cache_commit *commit;
+
+       commit = pmalloc(sizeof(struct cache_commit));
+       // XXX missing context
+       commit->caRepository = NULL;
+       commit->files = pmalloc(sizeof(*map));
+       commit->files[0].url = pstrdup(map->url);
+       commit->files[0].path = pstrdup(map->path);
+       commit->nfiles = 1;
+       STAILQ_INSERT_TAIL(&commits, commit, lh);
+}
+
+static void
+cachent_print(struct cache_node *node)
+{
+       if (!node)
+               return;
+
+       printf("\t%s (%s): ", node->map.url, node->map.path);
+       if (node->fresh)
+               printf("fresh (errcode %d)", node->dlerr);
+       else
+               printf("stale");
+       printf("\n");
+}
+
+static void
+table_print(struct cache_table *tbl)
+{
+       struct cache_node *node, *tmp;
+
+       if (HASH_COUNT(tbl->nodes) == 0)
+               return;
+
+       printf("    %s (%s):\n", tbl->name, tbl->enabled ? "enabled" : "disabled");
+       HASH_ITER(hh, tbl->nodes, node, tmp)
+               cachent_print(node);
+}
+
+void
+cache_print(void)
+{
+       table_print(&cache.rsync);
+       table_print(&cache.https);
+       table_print(&cache.rrdp);
+       table_print(&cache.fallback);
+}
+
+//static void
+//cleanup_node(struct rpki_cache *cache, struct cache_node *node,
+//    time_t last_week)
+//{
+//     char const *path;
+//     int error;
+//
+//     path = map_get_path(node->map);
+//     if (map_get_type(node->map) == MAP_NOTIF)
+//             goto skip_file;
+//
+//     error = file_exists(path);
+//     switch (error) {
+//     case 0:
+//             break;
+//     case ENOENT:
+//             /* Node exists but file doesn't: Delete node */
+//             pr_op_debug("Node exists but file doesn't: %s", path);
+//             delete_node_and_cage(cache, node);
+//             return;
+//     default:
+//             pr_op_err("Trouble cleaning '%s'; stat() returned errno %d: %s",
+//                 map_op_get_printable(node->map), error, strerror(error));
+//     }
+//
+//skip_file:
+//     if (!is_node_fresh(node, last_week)) {
+//             pr_op_debug("Deleting expired cache element %s.", path);
+//             file_rm_rf(path);
+//             delete_node_and_cage(cache, node);
+//     }
+//}
+//
+///*
+// * "Do not clean." List of mappings that should not be deleted from the cache.
+// * Global because nftw doesn't have a generic argument.
+// */
+//static struct map_list dnc;
+//static pthread_mutex_t dnc_lock = PTHREAD_MUTEX_INITIALIZER;
+//
+//static bool
+//is_cached(char const *_fpath)
+//{
+//     struct cache_mapping **node;
+//     char const *fpath, *npath;
+//     size_t c;
+//
+//     /*
+//      * This relies on paths being normalized, which is currently done by the
+//      * struct cache_mapping constructors.
+//      */
+//
+//     ARRAYLIST_FOREACH(&dnc, node) {
+//             fpath = _fpath;
+//             npath = map_get_path(*node);
+//
+//             for (c = 0; fpath[c] == npath[c]; c++)
+//                     if (fpath[c] == '\0')
+//                             return true;
+//             if (fpath[c] == '\0' && npath[c] == '/')
+//                     return true;
+//             if (npath[c] == '\0' && fpath[c] == '/')
+//                     return true;
+//     }
+//
+//     return false;
+//}
+
+static int
+rmf(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
+{
+       if (remove(fpath) < 0)
+               pr_op_warn("Can't remove %s: %s", fpath, strerror(errno));
+       else
+               pr_op_debug("Removed %s.", fpath);
+       return 0;
+}
+
+static void
+cleanup_tmp(void)
+{
+       char *tmpdir = get_cache_filename(CACHE_TMPDIR, true);
+       if (nftw(tmpdir, rmf, 32, FTW_DEPTH | FTW_PHYS))
+               pr_op_warn("Cannot empty the cache's tmp directory: %s",
+                   strerror(errno));
+       free(tmpdir);
+}
+
+static bool
+is_fallback(char const *path)
+{
+       // XXX just cd to the freaking cache, ffs
+       path += strlen(config_get_local_repository());
+       return str_starts_with(path, "fallback/") ||
+              str_starts_with(path, "/fallback/");
+}
+
+/* Hard-links @rpp's approved files into the fallback directory. */
+static void
+commit_rpp(struct cache_commit *commit, struct cache_node *fb)
+{
+       struct cache_mapping *src;
+       char const *dst;
+       array_index i;
+
+       for (i = 0; i < commit->nfiles; i++) {
+               src = commit->files + i;
+
+               if (is_fallback(src->path))
+                       continue;
+
+               /*
+                * (fine)
+                * Note, this is accidentally working perfectly for rsync too.
+                * Might want to rename some of this.
+                */
+               dst = rrdp_create_fallback(fb->map.path, &fb->rrdp, src->url);
+               if (!dst)
+                       goto skip;
+
+               pr_op_debug("Hard-linking: %s -> %s", src->path, dst);
+               if (link(src->path, dst) < 0)
+                       pr_op_warn("Could not hard-link cache file: %s",
+                           strerror(errno));
+
+skip:          free(src->path);
+               src->path = pstrdup(dst);
+       }
+}
+
+/* Deletes abandoned (ie. no longer ref'd by manifests) fallback hard links. */
+static void
+discard_trash(struct cache_commit *commit, struct cache_node *fallback)
+{
+       DIR *dir;
+       struct dirent *file;
+       char *file_path;
+       array_index i;
+
+       dir = opendir(fallback->map.path);
+       if (dir == NULL) {
+               pr_op_err("opendir() error: %s", strerror(errno));
+               return;
+       }
+
+       FOREACH_DIR_FILE(dir, file) {
+               if (S_ISDOTS(file))
+                       continue;
+
+               /*
+                * TODO (fine) Bit slow; wants a hash table,
+                * and maybe skip @file_path's reallocation.
+                */
+
+               file_path = path_join(fallback->map.path, file->d_name);
+
+               for (i = 0; i < commit->nfiles; i++) {
+                       if (commit->files[i].path == NULL)
+                               continue;
+                       if (strcmp(file_path, commit->files[i].path) == 0)
+                               goto next;
+               }
+
+               /*
+                * Uh... maybe keep the file until an expiration threshold?
+                * None of the current requirements seem to mandate it.
+                * It sounds pretty unreasonable for a signed valid manifest to
+                * "forget" a file, then legitimately relist it without actually
+                * providing it.
+                */
+               pr_op_debug("Removing hard link: %s", file_path);
+               if (unlink(file_path) < 0)
+                       pr_op_warn("Could not unlink %s: %s",
+                           file_path, strerror(errno));
+
+next:          free(file_path);
+       }
+
+       if (errno)
+               pr_op_err("Fallback directory traversal errored: %s",
+                   strerror(errno));
+       closedir(dir);
+}
+
+static void
+commit_fallbacks(void)
+{
+       struct cache_commit *commit;
+       struct cache_node *fb, *tmp;
+       array_index i;
+
+       while (!STAILQ_EMPTY(&commits)) {
+               commit = STAILQ_FIRST(&commits);
+               STAILQ_REMOVE_HEAD(&commits, lh);
+
+               if (commit->caRepository) {
+                       fb = provide_node(&cache.fallback, commit->caRepository);
+
+                       if (mkdir(fb->map.path, CACHE_FILEMODE) < 0) {
+                               if (errno != EEXIST) {
+                                       pr_op_warn("Failed to create %s: %s",
+                                           fb->map.path, strerror(errno));
+                                       goto skip;
+                               }
+                       }
+
+                       commit_rpp(commit, fb);
+                       discard_trash(commit, fb);
+
+               } else { /* TA */
+                       struct cache_mapping *map = &commit->files[0];
+
+                       fb = provide_node(&cache.fallback, map->url);
+                       if (is_fallback(map->path))
+                               goto freshen;
+
+                       pr_op_debug("Hard-linking TA: %s -> %s",
+                           map->path, fb->map.path);
+                       if (link(map->path, fb->map.path) < 0)
+                               pr_op_warn("Could not hard-link cache file: %s",
+                                   strerror(errno));
+               }
+
+freshen:       fb->fresh = 1;
+skip:          free(commit->caRepository);
+               for (i = 0; i < commit->nfiles; i++) {
+                       free(commit->files[i].url);
+                       free(commit->files[i].path);
+               }
+               free(commit->files);
+               free(commit);
+       }
+
+       HASH_ITER(hh, cache.fallback.nodes, fb, tmp) {
+               if (fb->fresh)
+                       continue;
+
+               /*
+                * XXX This one, on the other hand, would definitely benefit
+                * from an expiration threshold.
+                */
+               pr_op_debug("Removing orphaned fallback: %s", fb->map.path);
+               if (file_rm_rf(fb->map.path) < 0)
+                       pr_op_warn("Could not remove %s; unknown cause.",
+                           fb->map.path);
+               delete_node(&cache.fallback, fb);
+       }
+}
+
+static void
+remove_abandoned(void)
+{
+       // XXX no need to recurse anymore.
+       /*
+       char *rootpath;
+
+       rootpath = join_paths(config_get_local_repository(), "rsync");
+
+       nftw_root = cache.rsync;
+       nftw(rootpath, nftw_remove_abandoned, 32, FTW_DEPTH | FTW_PHYS); // XXX
+
+       strcpy(rootpath + strlen(rootpath) - 5, "https");
+
+       nftw_root = cache.https;
+       nftw(rootpath, nftw_remove_abandoned, 32, FTW_DEPTH | FTW_PHYS); // XXX
+
+       free(rootpath);
+       */
+}
+
+static void
+remove_orphaned(struct cache_table *table, struct cache_node *node)
+{
+       if (file_exists(node->map.path) == ENOENT) {
+               pr_op_debug("Missing file; deleting node: %s", node->map.path);
+               delete_node(table, node);
+       }
+}
+
+static void
+cache_foreach(void (*cb)(struct cache_table *, struct cache_node *))
+{
+       struct cache_node *node, *tmp;
+
+       HASH_ITER(hh, cache.rsync.nodes, node, tmp)
+               cb(&cache.rsync, node);
+       HASH_ITER(hh, cache.https.nodes, node, tmp)
+               cb(&cache.https, node);
+       HASH_ITER(hh, cache.rrdp.nodes, node, tmp)
+               cb(&cache.rrdp, node);
+       HASH_ITER(hh, cache.fallback.nodes, node, tmp)
+               cb(&cache.fallback, node);
+}
+
+/*
+ * Deletes unknown and old untraversed cached files, writes metadata into XML.
+ */
+static void
+cleanup_cache(void)
+{
+       // XXX Review
+       pr_op_debug("Cleaning up temporal files.");
+       cleanup_tmp();
+
+       pr_op_debug("Creating fallbacks for valid RPPs.");
+       commit_fallbacks();
+
+       pr_op_debug("Cleaning up old abandoned and unknown cache files.");
+       remove_abandoned();
+
+       pr_op_debug("Cleaning up orphaned nodes.");
+       cache_foreach(remove_orphaned);
+}
+
+void
+cache_commit(void)
+{
+       cleanup_cache();
+       write_tal_json();
+       cache_foreach(delete_node);
+       free(cache.rsync.seq.prefix);
+       free(cache.https.seq.prefix);
+       free(cache.rrdp.seq.prefix);
+       free(cache.fallback.seq.prefix);
+}
+
+void
+sias_init(struct sia_uris *sias)
+{
+       memset(sias, 0, sizeof(*sias));
+}
+
+void
+sias_cleanup(struct sia_uris *sias)
+{
+       free(sias->caRepository);
+       free(sias->rpkiNotify);
+       free(sias->rpkiManifest);
+       free(sias->crldp);
+       free(sias->caIssuers);
+       free(sias->signedObject);
+}
diff --git a/src/cache.h b/src/cache.h
new file mode 100644 (file)
index 0000000..743901a
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef SRC_CACHE_LOCAL_CACHE_H_
+#define SRC_CACHE_LOCAL_CACHE_H_
+
+#include <stdbool.h>
+#include "types/map.h"
+#include "types/rpp.h"
+
+int cache_setup(void);         /* Init this module */
+void cache_teardown(void);     /* Destroy this module */
+
+void cache_prepare(void);      /* Prepare cache for new validation cycle */
+void cache_commit(void);       /* Finish validation cycle */
+
+/* XXX might wanna rename */
+struct sia_uris {
+       char *caRepository;     /* RPP cage */
+       char *rpkiNotify;       /* RRDP Notification */
+       char *rpkiManifest;
+
+       /**
+        * CRL Distribution Points's fullName. Non-TA certificates only.
+        * RFC 6487, section 4.8.6.
+        */
+       char *crldp;
+       /**
+        * AIA's caIssuers. Non-TA certificates only.
+        * RFC 6487, section 4.8.7.
+        */
+       char *caIssuers;
+       /**
+        * SIA's signedObject. EE certificates only.
+        * RFC 6487, section 4.8.8.2.
+        */
+       char *signedObject;
+};
+
+void sias_init(struct sia_uris *);
+void sias_cleanup(struct sia_uris *);
+
+char *cache_refresh_url(char const *);
+char *cache_fallback_url(char const *);
+
+struct cache_cage;
+struct cache_cage *cache_refresh_sias(struct sia_uris *);
+char const *cage_map_file(struct cache_cage *, char const *);
+bool cage_disable_refresh(struct cache_cage *);
+void cache_commit_rpp(char const *, struct rpp *);
+void cache_commit_file(struct cache_mapping *);
+
+void cache_print(void);                /* Dump cache in stdout */
+
+#endif /* SRC_CACHE_LOCAL_CACHE_H_ */
diff --git a/src/cache/local_cache.c b/src/cache/local_cache.c
deleted file mode 100644 (file)
index 6df1c5a..0000000
+++ /dev/null
@@ -1,1110 +0,0 @@
-#include "cache/local_cache.h"
-
-#include <ftw.h>
-#include <stdatomic.h>
-#include <time.h>
-
-#include "alloc.h"
-#include "common.h"
-#include "config.h"
-#include "configure_ac.h"
-#include "file.h"
-#include "json_util.h"
-#include "log.h"
-#include "rrdp.h"
-#include "data_structure/array_list.h"
-#include "data_structure/path_builder.h"
-#include "data_structure/uthash.h"
-#include "http/http.h"
-#include "rsync/rsync.h"
-
-struct cache_node {
-       struct cache_mapping *map;
-
-       struct {
-               time_t ts; /* Last download attempt's timestamp */
-               int result; /* Last download attempt's result status code */
-       } attempt;
-
-       struct {
-               /* Has a download attempt ever been successful? */
-               bool happened;
-               /* Last successful download timestamp. (Only if @happened.) */
-               time_t ts;
-       } success;
-
-       struct cachefile_notification *notif;
-
-       UT_hash_handle hh; /* Hash table hook */
-};
-
-struct rpki_cache {
-       struct cache_node *ht;
-       time_t startup_ts; /* When we started the last validation */
-};
-
-#define CACHE_METAFILE "cache.json"
-#define TAGNAME_VERSION "fort-version"
-
-#define CACHEDIR_TAG "CACHEDIR.TAG"
-#define TMPDIR "tmp"
-
-#define TAL_METAFILE "tal.json"
-#define TAGNAME_TYPE "type"
-#define TAGNAME_URL "url"
-#define TAGNAME_ATTEMPT_TS "attempt-timestamp"
-#define TAGNAME_ATTEMPT_ERR "attempt-result"
-#define TAGNAME_SUCCESS_TS "success-timestamp"
-#define TAGNAME_NOTIF "notification"
-
-#define TYPEVALUE_TA_RSYNC "TA (rsync)"
-#define TYPEVALUE_TA_HTTP "TA (HTTP)"
-#define TYPEVALUE_RPP "RPP"
-#define TYPEVALUE_NOTIF "RRDP Notification"
-
-static atomic_uint file_counter;
-
-static char *
-get_cache_filename(char const *name, bool fatal)
-{
-       struct path_builder pb;
-       int error;
-
-       error = pb_init_cache(&pb, name);
-       if (error) {
-               if (fatal) {
-                       pr_crit("Cannot create path to %s: %s", name,
-                           strerror(error));
-               } else {
-                       pr_op_err("Cannot create path to %s: %s", name,
-                           strerror(error));
-                       return NULL;
-               }
-       }
-
-       return pb.string;
-}
-
-static int
-write_simple_file(char const *filename, char const *content)
-{
-       FILE *file;
-       int error;
-
-       file = fopen(filename, "w");
-       if (file == NULL)
-               goto fail;
-
-       if (fprintf(file, "%s", content) < 0)
-               goto fail;
-
-       fclose(file);
-       return 0;
-
-fail:
-       error = errno;
-       pr_op_err("Cannot write %s: %s", filename, strerror(error));
-       if (file != NULL)
-               fclose(file);
-       return error;
-}
-
-static void
-init_cache_metafile(void)
-{
-       char *filename;
-       json_t *root;
-       json_error_t jerror;
-       char const *file_version;
-       int error;
-
-       filename = get_cache_filename(CACHE_METAFILE, true);
-       root = json_load_file(filename, 0, &jerror);
-
-       if (root == NULL) {
-               if (json_error_code(&jerror) == json_error_cannot_open_file)
-                       pr_op_debug("%s does not exist.", filename);
-               else
-                       pr_op_err("Json parsing failure at %s (%d:%d): %s",
-                           filename, jerror.line, jerror.column, jerror.text);
-               goto invalid_cache;
-       }
-       if (json_typeof(root) != JSON_OBJECT) {
-               pr_op_err("The root tag of %s is not an object.", filename);
-               goto invalid_cache;
-       }
-
-       error = json_get_str(root, TAGNAME_VERSION, &file_version);
-       if (error) {
-               if (error > 0)
-                       pr_op_err("%s is missing the " TAGNAME_VERSION " tag.",
-                           filename);
-               goto invalid_cache;
-       }
-
-       if (strcmp(file_version, PACKAGE_VERSION) == 0)
-               goto end;
-
-invalid_cache:
-       pr_op_info("The cache appears to have been built by a different version of Fort. I'm going to clear it, just to be safe.");
-       file_rm_rf(config_get_local_repository());
-
-end:   json_decref(root);
-       free(filename);
-}
-
-static void
-init_cachedir_tag(void)
-{
-       char *filename;
-
-       filename = get_cache_filename(CACHEDIR_TAG, false);
-       if (filename == NULL)
-               return;
-
-       if (file_exists(filename) == ENOENT)
-               write_simple_file(filename,
-                  "Signature: 8a477f597d28d172789f06886806bc55\n"
-                  "# This file is a cache directory tag created by Fort.\n"
-                  "# For information about cache directory tags, see:\n"
-                  "#   https://bford.info/cachedir/\n");
-
-       free(filename);
-}
-
-static void
-init_tmp_dir(void)
-{
-       char *dirname;
-       int error;
-
-       dirname = get_cache_filename(TMPDIR, true);
-
-       error = mkdir_p(dirname, true);
-       if (error)
-               pr_crit("Cannot create %s: %s", dirname, strerror(error));
-
-       free(dirname);
-}
-
-void
-cache_setup(void)
-{
-       init_cache_metafile();
-       init_tmp_dir();
-       init_cachedir_tag();
-}
-
-void
-cache_teardown(void)
-{
-       char *filename;
-
-       filename = get_cache_filename(CACHE_METAFILE, false);
-       if (filename == NULL)
-               return;
-
-       write_simple_file(filename, "{ \"" TAGNAME_VERSION "\": \""
-           PACKAGE_VERSION "\" }\n");
-       free(filename);
-}
-
-/*
- * Returns a unique temporary file name in the local cache.
- *
- * The file will not be automatically deleted when it is closed or the program
- * terminates.
- *
- * The name of the function is inherited from tmpfile(3).
- *
- * The resulting string needs to be released.
- */
-int
-cache_tmpfile(char **filename)
-{
-       struct path_builder pb;
-       int error;
-
-       error = pb_init_cache(&pb, TMPDIR);
-       if (error)
-               return error;
-
-       error = pb_append_u32(&pb, atomic_fetch_add(&file_counter, 1u));
-       if (error) {
-               pb_cleanup(&pb);
-               return error;
-       }
-
-       *filename = pb.string;
-       return 0;
-}
-
-static char *
-get_tal_json_filename(void)
-{
-       struct path_builder pb;
-       return pb_init_cache(&pb, TAL_METAFILE) ? NULL : pb.string;
-}
-
-static struct cache_node *
-json2node(json_t *json)
-{
-       struct cache_node *node;
-       char const *type_str;
-       enum map_type type;
-       char const *url;
-       json_t *notif;
-       int error;
-
-       node = pzalloc(sizeof(struct cache_node));
-
-       error = json_get_str(json, TAGNAME_TYPE, &type_str);
-       if (error) {
-               if (error > 0)
-                       pr_op_err("Node is missing the '" TAGNAME_TYPE "' tag.");
-               goto fail;
-       }
-
-       if (strcmp(type_str, TYPEVALUE_TA_RSYNC) == 0)
-               type = MAP_TA_RSYNC;
-       else if (strcmp(type_str, TYPEVALUE_TA_HTTP) == 0)
-               type = MAP_TA_HTTP;
-       else if (strcmp(type_str, TYPEVALUE_RPP) == 0)
-               type = MAP_RPP;
-       else if (strcmp(type_str, TYPEVALUE_NOTIF) == 0)
-               type = MAP_NOTIF;
-       else {
-               pr_op_err("Unknown node type: %s", type_str);
-               goto fail;
-       }
-
-       error = json_get_str(json, TAGNAME_URL, &url);
-       if (error) {
-               if (error > 0)
-                       pr_op_err("Node is missing the '" TAGNAME_URL "' tag.");
-               goto fail;
-       }
-
-       if (type == MAP_NOTIF) {
-               error = json_get_object(json, TAGNAME_NOTIF, &notif);
-               switch (error) {
-               case 0:
-                       error = rrdp_json2notif(notif, &node->notif);
-                       if (error)
-                               goto fail;
-                       break;
-               case ENOENT:
-                       node->notif = NULL;
-                       break;
-               default:
-                       goto fail;
-               }
-       }
-
-       error = map_create(&node->map, type, NULL, url);
-       if (error) {
-               pr_op_err("Cannot parse '%s' into a URI.", url);
-               goto fail;
-       }
-
-       error = json_get_ts(json, TAGNAME_ATTEMPT_TS, &node->attempt.ts);
-       if (error) {
-               if (error > 0)
-                       pr_op_err("Node '%s' is missing the '"
-                           TAGNAME_ATTEMPT_TS "' tag.", url);
-               goto fail;
-       }
-
-       if (json_get_int(json, TAGNAME_ATTEMPT_ERR, &node->attempt.result) < 0)
-               goto fail;
-
-       error = json_get_ts(json, TAGNAME_SUCCESS_TS, &node->success.ts);
-       if (error < 0)
-               goto fail;
-       node->success.happened = (error == 0);
-
-       pr_op_debug("Node '%s' loaded successfully.", url);
-       return node;
-
-fail:
-       map_refput(node->map);
-       rrdp_notif_free(node->notif);
-       free(node);
-       return NULL;
-}
-
-static struct cache_node *
-find_node(struct rpki_cache *cache, struct cache_mapping *map)
-{
-       char const *key;
-       struct cache_node *result;
-
-       key = map_get_url(map);
-       HASH_FIND_STR(cache->ht, key, result);
-
-       return result;
-}
-
-static void
-add_node(struct rpki_cache *cache, struct cache_node *node)
-{
-       char const *key = map_get_url(node->map);
-       size_t keylen = strlen(key);
-       HASH_ADD_KEYPTR(hh, cache->ht, key, keylen, node);
-}
-
-static void
-load_tal_json(struct rpki_cache *cache)
-{
-       char *filename;
-       json_t *root;
-       json_error_t jerror;
-       size_t n;
-       struct cache_node *node;
-
-       /*
-        * Note: Loading TAL_METAFILE is one of few things Fort can fail at
-        * without killing itself. It's just a cache of a cache.
-        */
-
-       filename = get_tal_json_filename();
-       if (filename == NULL)
-               return;
-
-       pr_op_debug("Loading %s.", filename);
-
-       root = json_load_file(filename, 0, &jerror);
-
-       if (root == NULL) {
-               if (json_error_code(&jerror) == json_error_cannot_open_file)
-                       pr_op_debug("%s does not exist.", filename);
-               else
-                       pr_op_err("Json parsing failure at %s (%d:%d): %s",
-                           filename, jerror.line, jerror.column, jerror.text);
-               goto end;
-       }
-       if (json_typeof(root) != JSON_ARRAY) {
-               pr_op_err("The root tag of %s is not an array.", filename);
-               goto end;
-       }
-
-       for (n = 0; n < json_array_size(root); n++) {
-               node = json2node(json_array_get(root, n));
-               if (node != NULL)
-                       add_node(cache, node);
-       }
-
-end:   json_decref(root);
-       free(filename);
-}
-
-struct rpki_cache *
-cache_create(void)
-{
-       struct rpki_cache *cache;
-       cache = pzalloc(sizeof(struct rpki_cache));
-       cache->startup_ts = time(NULL);
-       if (cache->startup_ts == (time_t) -1)
-               pr_crit("time(NULL) returned (time_t) -1.");
-       load_tal_json(cache);
-       return cache;
-}
-
-static json_t *
-node2json(struct cache_node *node)
-{
-       json_t *json;
-       char const *type;
-       json_t *notification;
-
-       json = json_obj_new();
-       if (json == NULL)
-               return NULL;
-
-       switch (map_get_type(node->map)) {
-       case MAP_TA_RSYNC:
-               type = TYPEVALUE_TA_RSYNC;
-               break;
-       case MAP_TA_HTTP:
-               type = TYPEVALUE_TA_HTTP;
-               break;
-       case MAP_RPP:
-               type = TYPEVALUE_RPP;
-               break;
-       case MAP_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, map_get_url(node->map)))
-               goto cancel;
-       if (node->notif != NULL) {
-               notification = rrdp_notif2json(node->notif);
-               if (json_object_add(json, TAGNAME_NOTIF, notification))
-                       goto cancel;
-       }
-       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;
-       if (node->success.happened)
-               if (json_add_ts(json, TAGNAME_SUCCESS_TS, node->success.ts))
-                       goto cancel;
-
-       return json;
-
-cancel:
-       json_decref(json);
-       return NULL;
-}
-
-static json_t *
-build_tal_json(struct rpki_cache *cache)
-{
-       struct cache_node *node, *tmp;
-       json_t *root, *child;
-
-       root = json_array_new();
-       if (root == NULL)
-               return NULL;
-
-       HASH_ITER(hh, cache->ht, node, tmp) {
-               child = node2json(node);
-               if (child != NULL && json_array_append_new(root, child)) {
-                       pr_op_err("Cannot push %s json node into json root; unknown cause.",
-                           map_op_get_printable(node->map));
-                       continue;
-               }
-       }
-
-       return root;
-}
-
-static void
-write_tal_json(struct rpki_cache *cache)
-{
-       char *filename;
-       struct json_t *json;
-
-       json = build_tal_json(cache);
-       if (json == NULL)
-               return;
-
-       filename = get_tal_json_filename();
-       if (filename == NULL)
-               goto end;
-
-       if (json_dump_file(json, filename, JSON_INDENT(2)))
-               pr_op_err("Unable to write %s; unknown cause.", filename);
-
-end:   json_decref(json);
-       free(filename);
-}
-
-static int
-fix_url(struct cache_mapping *map, struct cache_mapping **result)
-{
-       char const *url, *c;
-       char *dup;
-       unsigned int slashes;
-       int error;
-
-       if (map_get_type(map) != MAP_RPP)
-               goto reuse_mapping;
-
-       /*
-        * Careful with this code. rsync(1):
-        *
-        * > A trailing slash on the source changes this behavior to avoid
-        * > creating an additional directory level at the destination. You can
-        * > think of a trailing / on a source as meaning "copy the contents of
-        * > this directory" as opposed to "copy the directory by name", but in
-        * > both cases the attributes of the containing directory are
-        * > transferred to the containing directory on the destination. In
-        * > other words, each of the following commands copies the files in the
-        * > same way, including their setting of the attributes of /dest/foo:
-        * >
-        * >     rsync -av /src/foo  /dest
-        * >     rsync -av /src/foo/ /dest/foo
-        *
-        * This quirk does not behave consistently. In practice, if you rsync
-        * at the module level, rsync servers behave as if the trailing slash
-        * always existed.
-        *
-        * ie. the two following rsyncs behave identically:
-        *
-        *      rsync -rtz rsync://repository.lacnic.net/rpki  potatoes
-        *              (Copies the content of rpki to potatoes.)
-        *      rsync -rtz rsync://repository.lacnic.net/rpki/ potatoes
-        *              (Copies the content of rpki to potatoes.)
-        *
-        * Even though the following do not:
-        *
-        *      rsync -rtz rsync://repository.lacnic.net/rpki/lacnic  potatoes
-        *              (Copies lacnic to potatoes.)
-        *      rsync -rtz rsync://repository.lacnic.net/rpki/lacnic/ potatoes
-        *              (Copies the content of lacnic to potatoes.)
-        *
-        * This is important to us, because an inconsistent missing directory
-        * component will screw our URLs-to-cache mappings.
-        *
-        * My solution is to add the slash myself. That's all I can do to force
-        * it to behave consistently, it seems.
-        *
-        * But note: This only works if we're synchronizing a directory.
-        * But this is fine, because this hack stacks with the minimum common
-        * path performance hack.
-        *
-        * Minimum common path performance hack: rsync the rsync module root,
-        * not every RPP separately. The former is much faster.
-        */
-
-       url = map_get_url(map);
-       slashes = 0;
-       for (c = url; *c != '\0'; c++) {
-               if (*c == '/') {
-                       slashes++;
-                       if (slashes == 4) {
-                               if (c[1] == '\0')
-                                       goto reuse_mapping;
-                               dup = pstrndup(url, c - url + 1);
-                               goto dup2url;
-                       }
-               }
-       }
-
-       if (slashes == 3 && c[-1] != '/') {
-               dup = pmalloc(c - url + 2);
-               memcpy(dup, url, c - url);
-               dup[c - url] = '/';
-               dup[c - url + 1] = '\0';
-               goto dup2url;
-       }
-
-       return pr_val_err("Can't rsync URL '%s': The URL seems to be missing a domain or rsync module.",
-           url);
-
-reuse_mapping:
-       map_refget(map);
-       *result = map;
-       return 0;
-
-dup2url:
-       error = map_create(result, MAP_RPP, NULL, dup);
-       free(dup);
-       return error;
-}
-
-static bool
-was_recently_downloaded(struct rpki_cache *cache, struct cache_node *node)
-{
-       return difftime(cache->startup_ts, node->attempt.ts) <= 0;
-}
-
-static int
-cache_check(struct cache_mapping *url)
-{
-       int error;
-
-       error = file_exists(map_get_path(url));
-       switch (error) {
-       case 0:
-               pr_val_debug("Offline mode, file is cached.");
-               break;
-       case ENOENT:
-               pr_val_debug("Offline mode, file is not cached.");
-               break;
-       default:
-               pr_val_debug("Offline mode, unknown result %d (%s)",
-                   error, strerror(error));
-       }
-
-       return error;
-}
-
-/**
- * @ims and @changed only on HTTP.
- * @ims can be zero, which means "no IMS."
- * @changed can be NULL.
- */
-int
-cache_download(struct rpki_cache *cache, struct cache_mapping *map,
-    bool *changed, struct cachefile_notification ***notif)
-{
-       struct cache_mapping *map2;
-       struct cache_node *node;
-       int error;
-
-       if (changed != NULL)
-               *changed = false;
-
-       error = fix_url(map, &map2);
-       if (error)
-               return error;
-
-       node = find_node(cache, map2);
-       if (node != NULL) {
-               if (was_recently_downloaded(cache, node)) {
-                       error = node->attempt.result;
-                       goto end;
-               }
-       } else {
-               node = pzalloc(sizeof(struct cache_node));
-               node->map = map2;
-               map_refget(map2);
-               add_node(cache, node);
-       }
-
-       switch (map_get_type(map2)) {
-       case MAP_TA_HTTP:
-       case MAP_NOTIF:
-       case MAP_TMP:
-               error = config_get_http_enabled()
-                  ? http_download(map2, node->success.ts, changed)
-                  : cache_check(map2);
-               break;
-       case MAP_TA_RSYNC:
-       case MAP_RPP:
-               error = config_get_rsync_enabled()
-                   ? rsync_download(map_get_url(map2), map_get_path(map2), true)
-                   : cache_check(map2);
-               break;
-       default:
-               pr_crit("Mapping type not downloadable: %d", map_get_type(map2));
-       }
-
-       node->attempt.ts = time(NULL);
-       if (node->attempt.ts == (time_t) -1)
-               pr_crit("time(NULL) returned (time_t) -1");
-       node->attempt.result = error;
-       if (!error) {
-               node->success.happened = true;
-               node->success.ts = node->attempt.ts;
-       }
-
-end:
-       map_refput(map2);
-       if (!error && (notif != NULL))
-               *notif = &node->notif;
-       return error;
-}
-
-static int
-download(struct rpki_cache *cache, struct cache_mapping *map, maps_dl_cb cb,
-    void *arg)
-{
-       int error;
-
-       pr_val_debug("Trying URL %s...", map_get_url(map));
-
-       switch (map_get_type(map)) {
-       case MAP_TA_HTTP:
-       case MAP_TA_RSYNC:
-       case MAP_RPP:
-               error = cache_download(cache, map, NULL, NULL);
-               break;
-       case MAP_NOTIF:
-               error = rrdp_update(map);
-               break;
-       default:
-               pr_crit("Mapping type is not a legal alt candidate: %u",
-                   map_get_type(map));
-       }
-
-       return error ? 1 : cb(map, arg);
-}
-
-static int
-download_maps(struct rpki_cache *cache, struct map_list *maps,
-    enum map_type type, maps_dl_cb cb, void *arg)
-{
-       struct cache_mapping **map;
-       int error;
-
-       ARRAYLIST_FOREACH(maps, map) {
-               if (map_get_type(*map) == type) {
-                       error = download(cache, *map, cb, arg);
-                       if (error <= 0)
-                               return error;
-               }
-       }
-
-       return 1;
-}
-
-/**
- * Assumes the mappings represent different ways to access the same content.
- *
- * Sequentially (in the order dictated by their priorities) attempts to update
- * (in the cache) the content pointed by each mapping's URL.
- * If a download succeeds, calls cb on it. If cb succeeds, returns without
- * trying more URLs.
- *
- * If none of the URLs download and callback properly, attempts to find one
- * that's already cached, and callbacks it.
- */
-int
-cache_download_alt(struct rpki_cache *cache, struct map_list *maps,
-    enum map_type http_type, enum map_type rsync_type, maps_dl_cb cb, void *arg)
-{
-       struct cache_mapping **cursor, *map;
-       int error;
-
-       if (config_get_http_priority() > config_get_rsync_priority()) {
-               error = download_maps(cache, maps, http_type, cb, arg);
-               if (error <= 0)
-                       return error;
-               error = download_maps(cache, maps, rsync_type, cb, arg);
-               if (error <= 0)
-                       return error;
-
-       } else if (config_get_http_priority() < config_get_rsync_priority()) {
-               error = download_maps(cache, maps, rsync_type, cb, arg);
-               if (error <= 0)
-                       return error;
-               error = download_maps(cache, maps, http_type, cb, arg);
-               if (error <= 0)
-                       return error;
-
-       } else {
-               ARRAYLIST_FOREACH(maps, cursor) {
-                       error = download(cache, *cursor, cb, arg);
-                       if (error <= 0)
-                               return error;
-               }
-       }
-
-       map = cache_recover(cache, maps);
-       return (map != NULL) ? cb(map, arg) : ESRCH;
-}
-
-/*
- * Highest to lowest priority:
- *
- * 1. Recent Success: !error, CNF_SUCCESS, high ts_success.
- * 2. Old Success: !error, CNF_SUCCESS, low ts_success.
- * 3. Previous Recent Success: error, CNF_SUCCESS, high ts_success.
- * 4. Previous Old Success: error, CNF_SUCCESS, old ts_success.
- * 5. No Success: !CNF_SUCCESS (completely unviable)
- */
-static struct cache_node *
-choose_better(struct cache_node *old, struct cache_node *new)
-{
-       if (!new->success.happened)
-               return old;
-       if (old == NULL)
-               return new;
-
-       /*
-        * We're gonna have to get subjective here.
-        * Should we prioritize a candidate that was successfully downloaded a
-        * long time ago (with no retries since), or one that failed recently?
-        * Both are terrible, but returning something is still better than
-        * returning nothing, because the validator might manage to salvage
-        * remnant cached ROAs that haven't expired yet.
-        */
-
-       if (old->attempt.result && !new->attempt.result)
-               return new;
-       if (!old->attempt.result && new->attempt.result)
-               return old;
-       return (difftime(old->success.ts, new->success.ts) < 0) ? new : old;
-}
-
-struct map_and_node {
-       struct cache_mapping *map;
-       struct cache_node *node;
-};
-
-/* Separated because of unit tests. */
-static void
-__cache_recover(struct rpki_cache *cache, struct map_list *maps,
-    struct map_and_node *best)
-{
-       struct cache_mapping **map;
-       struct cache_mapping *fixed;
-       struct map_and_node cursor;
-
-       ARRAYLIST_FOREACH(maps, map) {
-               cursor.map = *map;
-
-               if (fix_url(cursor.map, &fixed) != 0)
-                       continue;
-               cursor.node = find_node(cache, fixed);
-               map_refput(fixed);
-               if (cursor.node == NULL)
-                       continue;
-
-               if (choose_better(best->node, cursor.node) == cursor.node)
-                       *best = cursor;
-       }
-}
-
-struct cache_mapping *
-cache_recover(struct rpki_cache *cache, struct map_list *maps)
-{
-       struct map_and_node best = { 0 };
-       __cache_recover(cache, maps, &best);
-       return best.map;
-}
-
-void
-cache_print(struct rpki_cache *cache)
-{
-       struct cache_node *node, *tmp;
-
-       HASH_ITER(hh, cache->ht, node, tmp)
-               printf("- %s (%s): %ssuccess error:%d\n",
-                   map_get_path(node->map),
-                   map_get_url(node->map),
-                   node->success.happened ? "" : "!",
-                   node->attempt.result);
-}
-
-static bool
-is_node_fresh(struct cache_node *node, time_t epoch)
-{
-       /* TODO This is a startup; probably complicate this. */
-       return difftime(epoch, node->attempt.ts) < 0;
-}
-
-static void
-delete_node(struct rpki_cache *cache, struct cache_node *node)
-{
-       HASH_DEL(cache->ht, node);
-       map_refput(node->map);
-       rrdp_notif_free(node->notif);
-       free(node);
-}
-
-static void
-delete_node_and_cage(struct rpki_cache *cache, struct cache_node *node)
-{
-       struct cache_mapping *cage;
-
-       if (map_get_type(node->map) == MAP_NOTIF) {
-               if (map_create_cage(&cage, node->map) == 0) {
-                       pr_op_debug("Deleting cage %s.", map_get_path(cage));
-                       file_rm_rf(map_get_path(cage));
-                       map_refput(cage);
-               }
-       }
-
-       delete_node(cache, node);
-}
-
-static time_t
-get_days_ago(int days)
-{
-       time_t tt_now, last_week;
-       struct tm tm;
-       int error;
-
-       tt_now = time(NULL);
-       if (tt_now == (time_t) -1)
-               pr_crit("time(NULL) returned (time_t) -1.");
-       if (localtime_r(&tt_now, &tm) == NULL) {
-               error = errno;
-               pr_crit("localtime_r(tt, &tm) returned error: %s",
-                   strerror(error));
-       }
-       tm.tm_mday -= days;
-       last_week = mktime(&tm);
-       if (last_week == (time_t) -1)
-               pr_crit("mktime(tm) returned (time_t) -1.");
-
-       return last_week;
-}
-
-static void
-cleanup_tmp(struct rpki_cache *cache, struct cache_node *node)
-{
-       enum map_type type;
-       char const *path;
-       int error;
-
-       type = map_get_type(node->map);
-       if (type != MAP_NOTIF && type != MAP_TMP)
-               return;
-
-       path = map_get_path(node->map);
-       pr_op_debug("Deleting temporal file '%s'.", path);
-       error = file_rm_f(path);
-       if (error)
-               pr_op_err("Could not delete '%s': %s", path, strerror(error));
-
-       if (type != MAP_NOTIF)
-               delete_node(cache, node);
-}
-
-static void
-cleanup_node(struct rpki_cache *cache, struct cache_node *node,
-    time_t last_week)
-{
-       char const *path;
-       int error;
-
-       path = map_get_path(node->map);
-       if (map_get_type(node->map) == MAP_NOTIF)
-               goto skip_file;
-
-       error = file_exists(path);
-       switch (error) {
-       case 0:
-               break;
-       case ENOENT:
-               /* Node exists but file doesn't: Delete node */
-               pr_op_debug("Node exists but file doesn't: %s", path);
-               delete_node_and_cage(cache, node);
-               return;
-       default:
-               pr_op_err("Trouble cleaning '%s'; stat() returned errno %d: %s",
-                   map_op_get_printable(node->map), error, strerror(error));
-       }
-
-skip_file:
-       if (!is_node_fresh(node, last_week)) {
-               pr_op_debug("Deleting expired cache element %s.", path);
-               file_rm_rf(path);
-               delete_node_and_cage(cache, node);
-       }
-}
-
-/*
- * "Do not clean." List of mappings that should not be deleted from the cache.
- * Global because nftw doesn't have a generic argument.
- */
-static struct map_list dnc;
-static pthread_mutex_t dnc_lock = PTHREAD_MUTEX_INITIALIZER;
-
-static bool
-is_cached(char const *_fpath)
-{
-       struct cache_mapping **node;
-       char const *fpath, *npath;
-       size_t c;
-
-       /*
-        * This relies on paths being normalized, which is currently done by the
-        * struct cache_mapping constructors.
-        */
-
-       ARRAYLIST_FOREACH(&dnc, node) {
-               fpath = _fpath;
-               npath = map_get_path(*node);
-
-               for (c = 0; fpath[c] == npath[c]; c++)
-                       if (fpath[c] == '\0')
-                               return true;
-               if (fpath[c] == '\0' && npath[c] == '/')
-                       return true;
-               if (npath[c] == '\0' && fpath[c] == '/')
-                       return true;
-       }
-
-       return false;
-}
-
-static int
-delete_if_unknown(const char *fpath, const struct stat *sb, int typeflag,
-    struct FTW *ftw)
-{
-       if (!is_cached(fpath)) {
-               pr_op_debug("Deleting untracked file or directory %s.", fpath);
-               errno = 0;
-               if (remove(fpath) != 0)
-                       pr_op_err("Cannot delete '%s': %s", fpath, strerror(errno));
-       }
-
-       return 0;
-}
-
-/*
- * FIXME this needs to account I'm merging the TAL directories.
- * It might already work.
- */
-static void
-delete_unknown_files(struct rpki_cache *cache)
-{
-       struct cache_node *node, *tmp;
-       struct cache_mapping *cage;
-       struct path_builder pb;
-       int error;
-
-       error = pb_init_cache(&pb, TAL_METAFILE);
-       if (error) {
-               pr_op_err("Cannot delete unknown files from the cache: %s",
-                   strerror(error));
-               return;
-       }
-
-       mutex_lock(&dnc_lock);
-       maps_init(&dnc);
-
-       maps_add(&dnc, map_create_cache(pb.string));
-       HASH_ITER(hh, cache->ht, node, tmp) {
-               map_refget(node->map);
-               maps_add(&dnc, node->map);
-
-               if (map_get_type(node->map) != MAP_NOTIF)
-                       continue;
-
-               if (map_create_cage(&cage, node->map) != 0) {
-                       pr_op_err("Cannot generate %s's cage. I'm probably going to end up deleting it from the cache.",
-                           map_op_get_printable(node->map));
-                       continue;
-               }
-               maps_add(&dnc, cage);
-       }
-
-       pb_pop(&pb, true);
-       /* TODO (performance) optimize that 32 */
-       error = nftw(pb.string, delete_if_unknown, 32, FTW_PHYS);
-       if (error)
-               pr_op_warn("The cache cleanup ended prematurely with error code %d (%s)",
-                   error, strerror(error));
-
-       maps_cleanup(&dnc);
-       mutex_unlock(&dnc_lock);
-
-       pb_cleanup(&pb);
-}
-
-/*
- * Deletes unknown and old untraversed cached files, writes metadata into XML.
- */
-static void
-cache_cleanup(struct rpki_cache *cache)
-{
-       struct cache_node *node, *tmp;
-       time_t last_week;
-
-       pr_op_debug("Cleaning up temporal files.");
-       HASH_ITER(hh, cache->ht, node, tmp)
-               cleanup_tmp(cache, node);
-
-       pr_op_debug("Cleaning up old abandoned cache files.");
-       last_week = get_days_ago(7);
-       HASH_ITER(hh, cache->ht, node, tmp)
-               cleanup_node(cache, node, last_week);
-
-       pr_op_debug("Cleaning up unknown cache files.");
-       delete_unknown_files(cache);
-}
-
-void
-cache_destroy(struct rpki_cache *cache)
-{
-       struct cache_node *node, *tmp;
-
-       cache_cleanup(cache);
-       write_tal_json(cache);
-
-       HASH_ITER(hh, cache->ht, node, tmp)
-               delete_node(cache, node);
-       free(cache);
-}
diff --git a/src/cache/local_cache.h b/src/cache/local_cache.h
deleted file mode 100644 (file)
index 35d18cb..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef SRC_CACHE_LOCAL_CACHE_H_
-#define SRC_CACHE_LOCAL_CACHE_H_
-
-#include "types/map.h"
-
-struct rpki_cache;
-
-void cache_setup(void);
-void cache_teardown(void);
-
-int cache_tmpfile(char **);
-
-struct rpki_cache *cache_create(void);
-/* Will destroy the cache object, but not the cache directory itself, obv. */
-void cache_destroy(struct rpki_cache *);
-
-struct cachefile_notification; /* FIXME */
-
-/* Downloads @map into the cache */
-int cache_download(struct rpki_cache *, struct cache_mapping *map, bool *,
-    struct cachefile_notification ***);
-
-/*
- * The callback should return
- *
- * - 0 on success ("Mapping handled successfully")
- * - > 0 on soft errors ("Try another mapping")
- * - < 0 on hard errors ("Abandon foreach")
- */
-typedef int (*maps_dl_cb)(struct cache_mapping *, void *);
-int cache_download_alt(struct rpki_cache *, struct map_list *, enum map_type,
-    enum map_type, maps_dl_cb, void *);
-
-/* Returns the most recent successfully cached mapping of the list */
-struct cache_mapping *cache_recover(struct rpki_cache *, struct map_list *);
-/* Prints the cache in standard output. */
-void cache_print(struct rpki_cache *);
-
-#endif /* SRC_CACHE_LOCAL_CACHE_H_ */
diff --git a/src/cachetmp b/src/cachetmp
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/cachetmp.c b/src/cachetmp.c
new file mode 100644 (file)
index 0000000..9547152
--- /dev/null
@@ -0,0 +1,38 @@
+#include "cachetmp.h"
+
+#include <stdatomic.h>
+
+#include "types/path.h"
+
+static atomic_uint file_counter;
+
+/*
+ * Returns a unique temporary file name in the local cache. Note, it's a name,
+ * and it's pretty much reserved. The file itself will not be created.
+ *
+ * The file will not be automatically deleted when it is closed or the program
+ * terminates.
+ *
+ * The name of the function is inherited from tmpfile(3).
+ *
+ * The resulting string needs to be released.
+ */
+int
+cache_tmpfile(char **filename)
+{
+       struct path_builder pb;
+       int error;
+
+       error = pb_init_cache(&pb, CACHE_TMPDIR);
+       if (error)
+               return error;
+
+       error = pb_append_u32(&pb, atomic_fetch_add(&file_counter, 1u));
+       if (error) {
+               pb_cleanup(&pb);
+               return error;
+       }
+
+       *filename = pb.string;
+       return 0;
+}
diff --git a/src/cachetmp.h b/src/cachetmp.h
new file mode 100644 (file)
index 0000000..dec35d1
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef SRC_CACHETMP_H_
+#define SRC_CACHETMP_H_
+
+#define CACHE_TMPDIR "tmp"
+
+int cache_tmpfile(char **);    /* Return new unique path in <cache>/tmp/ */
+
+#endif /* SRC_CACHETMP_H_ */
diff --git a/src/cert_stack.c b/src/cert_stack.c
deleted file mode 100644 (file)
index b1a725f..0000000
+++ /dev/null
@@ -1,457 +0,0 @@
-#include "cert_stack.h"
-
-#include <errno.h>
-#include <sys/queue.h>
-
-#include "alloc.h"
-#include "data_structure/array_list.h"
-#include "object/name.h"
-#include "resource.h"
-#include "str_token.h"
-#include "thread_var.h"
-
-enum defer_node_type {
-       DNT_SEPARATOR,
-       DNT_CERT,
-};
-
-struct defer_node {
-       enum defer_node_type type;
-
-       /**
-        * This field is only relevant if @type == PCT_CERT.
-        * Do not dereference members otherwise.
-        */
-       struct deferred_cert deferred;
-
-       /** Used by certstack. Points to the next stacked certificate. */
-       SLIST_ENTRY(defer_node) next;
-};
-
-SLIST_HEAD(defer_stack, defer_node);
-
-struct serial_number {
-       BIGNUM *number;
-       char *file; /* File where this serial number was found. */
-};
-
-STATIC_ARRAY_LIST(serial_numbers, struct serial_number)
-
-/**
- * Cached certificate data.
- */
-struct metadata_node {
-       struct cache_mapping *map;
-       struct resources *resources;
-       /*
-        * Serial numbers of the children.
-        * This is an unsorted array list for two reasons: Certificates usually
-        * don't have many children, and I'm running out of time.
-        */
-       struct serial_numbers serials;
-
-       /** Used by certstack. Points to the next stacked certificate. */
-       SLIST_ENTRY(metadata_node) next;
-};
-
-SLIST_HEAD(metadata_stack, metadata_node);
-
-/**
- * This is the foundation through which we pull off our iterative traversal,
- * as opposed to a stack-threatening recursive one.
- *
- * It is a bunch of data that replaces the one that would normally be allocated
- * in the function stack.
- */
-struct cert_stack {
-       /**
-        * Defer stack. Certificates we haven't iterated through yet.
-        *
-        * Every time a certificate validates successfully, its children are
-        * stored here so they can be traversed later.
-        */
-       struct defer_stack defers;
-
-       /**
-        * x509 stack. Parents of the certificate we're currently iterating
-        * through.
-        * Formatted for immediate libcrypto consumption.
-        */
-       STACK_OF(X509) *x509s;
-
-       /**
-        * Stacked additional data to each @x509 certificate.
-        *
-        * (These two stacks should always have the same size. The reason why I
-        * don't combine them is because libcrypto's validation function needs
-        * the X509 stack, and I'm not creating it over and over again.)
-        *
-        * (This is a SLIST and not a STACK_OF because the OpenSSL stack
-        * implementation is different than the LibreSSL one, and the latter is
-        * seemingly not intended to be used outside of its library.)
-        */
-       struct metadata_stack metas;
-};
-
-int
-certstack_create(struct cert_stack **result)
-{
-       struct cert_stack *stack;
-
-       stack = pmalloc(sizeof(struct cert_stack));
-
-       stack->x509s = sk_X509_new_null();
-       if (stack->x509s == NULL) {
-               free(stack);
-               return val_crypto_err("sk_X509_new_null() returned NULL");
-       }
-
-       SLIST_INIT(&stack->defers);
-       SLIST_INIT(&stack->metas);
-
-       *result = stack;
-       return 0;
-}
-
-static void
-defer_destroy(struct defer_node *defer)
-{
-       switch (defer->type) {
-       case DNT_SEPARATOR:
-               break;
-       case DNT_CERT:
-               map_refput(defer->deferred.map);
-               rpp_refput(defer->deferred.pp);
-               break;
-       }
-
-       free(defer);
-}
-
-static void
-serial_cleanup(struct serial_number *serial)
-{
-       BN_free(serial->number);
-       free(serial->file);
-}
-
-static void
-meta_destroy(struct metadata_node *meta)
-{
-       map_refput(meta->map);
-       resources_destroy(meta->resources);
-       serial_numbers_cleanup(&meta->serials, serial_cleanup);
-       free(meta);
-}
-
-void
-certstack_destroy(struct cert_stack *stack)
-{
-       unsigned int stack_size;
-       struct metadata_node *meta;
-       struct defer_node *post;
-
-       stack_size = 0;
-       while (!SLIST_EMPTY(&stack->defers)) {
-               post = SLIST_FIRST(&stack->defers);
-               SLIST_REMOVE_HEAD(&stack->defers, next);
-               defer_destroy(post);
-               stack_size++;
-       }
-       pr_val_debug("Deleted %u deferred certificates.", stack_size);
-
-       pr_val_debug("Deleting %d stacked x509s.", sk_X509_num(stack->x509s));
-       sk_X509_pop_free(stack->x509s, X509_free);
-
-       stack_size = 0;
-       while (!SLIST_EMPTY(&stack->metas)) {
-               meta = SLIST_FIRST(&stack->metas);
-               SLIST_REMOVE_HEAD(&stack->metas, next);
-               meta_destroy(meta);
-               stack_size++;
-       }
-       pr_val_debug("Deleted %u metadatas.", stack_size);
-
-       free(stack);
-}
-
-void
-deferstack_push(struct cert_stack *stack, struct deferred_cert *deferred)
-{
-       struct defer_node *node;
-
-       node = pmalloc(sizeof(struct defer_node));
-
-       node->type = DNT_CERT;
-       node->deferred = *deferred;
-       map_refget(deferred->map);
-       rpp_refget(deferred->pp);
-       SLIST_INSERT_HEAD(&stack->defers, node, next);
-}
-
-static void
-x509stack_pop(struct cert_stack *stack)
-{
-       X509 *cert;
-       struct metadata_node *meta;
-
-       cert = sk_X509_pop(stack->x509s);
-       if (cert == NULL)
-               pr_crit("Attempted to pop empty X509 stack");
-       X509_free(cert);
-
-       meta = SLIST_FIRST(&stack->metas);
-       if (meta == NULL)
-               pr_crit("Attempted to pop empty metadata stack");
-       SLIST_REMOVE_HEAD(&stack->metas, next);
-       meta_destroy(meta);
-}
-
-/**
- * Contract: Returns either 0 or -ENOENT. No other outcomes.
- */
-int
-deferstack_pop(struct cert_stack *stack, struct deferred_cert *result)
-{
-       struct defer_node *node;
-
-again: node = SLIST_FIRST(&stack->defers);
-       if (node == NULL)
-               return -ENOENT;
-
-       if (node->type == DNT_SEPARATOR) {
-               x509stack_pop(stack);
-
-               SLIST_REMOVE_HEAD(&stack->defers, next);
-               defer_destroy(node);
-               goto again;
-       }
-
-       *result = node->deferred;
-       map_refget(node->deferred.map);
-       rpp_refget(node->deferred.pp);
-
-       SLIST_REMOVE_HEAD(&stack->defers, next);
-       defer_destroy(node);
-       return 0;
-}
-
-bool
-deferstack_is_empty(struct cert_stack *stack)
-{
-       return SLIST_EMPTY(&stack->defers);
-}
-
-static int
-init_resources(X509 *x509, enum rpki_policy policy, enum cert_type type,
-    struct resources **_result)
-{
-       struct resources *result;
-       int error;
-
-       result = resources_create(policy, false);
-
-       error = certificate_get_resources(x509, result, type);
-       if (error)
-               goto fail;
-
-       /*
-        * rfc8630#section-2.3
-        * "The INR extension(s) of this TA MUST contain a non-empty set of
-        * number resources."
-        * The "It MUST NOT use the "inherit" form of the INR extension(s)"
-        * part is already handled in certificate_get_resources().
-        */
-       if (type == CERTYPE_TA && resources_empty(result)) {
-               error = pr_val_err("Trust Anchor certificate does not define any number resources.");
-               goto fail;
-       }
-
-       *_result = result;
-       return 0;
-
-fail:
-       resources_destroy(result);
-       return error;
-}
-
-static struct defer_node *
-create_separator(void)
-{
-       struct defer_node *result;
-
-       result = pmalloc(sizeof(struct defer_node));
-       result->type = DNT_SEPARATOR;
-
-       return result;
-}
-
-/** Steals ownership of @x509 on success. */
-int
-x509stack_push(struct cert_stack *stack, struct cache_mapping *map, X509 *x509,
-    enum rpki_policy policy, enum cert_type type)
-{
-       struct metadata_node *meta;
-       struct defer_node *defer_separator;
-       int ok;
-       int error;
-
-       meta = pmalloc(sizeof(struct metadata_node));
-
-       meta->map = map_refget(map);
-       serial_numbers_init(&meta->serials);
-
-       error = init_resources(x509, policy, type, &meta->resources);
-       if (error)
-               goto cleanup_serial;
-
-       defer_separator = create_separator();
-
-       ok = sk_X509_push(stack->x509s, x509);
-       if (ok <= 0) {
-               error = val_crypto_err(
-                   "Could not add certificate to trusted stack: %d", ok);
-               goto destroy_separator;
-       }
-
-       SLIST_INSERT_HEAD(&stack->defers, defer_separator, next);
-       SLIST_INSERT_HEAD(&stack->metas, meta, next);
-
-       return 0;
-
-destroy_separator:
-       free(defer_separator);
-       resources_destroy(meta->resources);
-cleanup_serial:
-       serial_numbers_cleanup(&meta->serials, serial_cleanup);
-       map_refput(meta->map);
-       free(meta);
-       return error;
-}
-
-/**
- * This one is intended to revert a recent x509 push.
- * Reverts that particular push.
- *
- * (x509 stack elements are otherwise indirectly popped through
- * deferstack_pop().)
- */
-void
-x509stack_cancel(struct cert_stack *stack)
-{
-       struct defer_node *defer_separator;
-
-       x509stack_pop(stack);
-
-       defer_separator = SLIST_FIRST(&stack->defers);
-       if (defer_separator == NULL)
-               pr_crit("Attempted to pop empty defer stack");
-       SLIST_REMOVE_HEAD(&stack->defers, next);
-       defer_destroy(defer_separator);
-}
-
-X509 *
-x509stack_peek(struct cert_stack *stack)
-{
-       return sk_X509_value(stack->x509s, sk_X509_num(stack->x509s) - 1);
-}
-
-/** Does not grab reference. */
-struct cache_mapping *
-x509stack_peek_map(struct cert_stack *stack)
-{
-       struct metadata_node *meta = SLIST_FIRST(&stack->metas);
-       return (meta != NULL) ? meta->map : NULL;
-}
-
-struct resources *
-x509stack_peek_resources(struct cert_stack *stack)
-{
-       struct metadata_node *meta = SLIST_FIRST(&stack->metas);
-       return (meta != NULL) ? meta->resources : NULL;
-}
-
-static char *
-get_current_file_name(void)
-{
-       char const *file_name;
-
-       file_name = fnstack_peek();
-       if (file_name == NULL)
-               pr_crit("The file name stack is empty.");
-
-       return pstrdup(file_name);
-}
-
-/**
- * Intended to validate serial number uniqueness.
- * "Stores" the serial number in the current relevant certificate metadata,
- * and complains if there's a collision. That's all.
- *
- * This function will steal ownership of @number on success.
- */
-void
-x509stack_store_serial(struct cert_stack *stack, BIGNUM *number)
-{
-       struct metadata_node *meta;
-       struct serial_number *cursor;
-       struct serial_number duplicate;
-       char *string;
-
-       /* Remember to free @number if you return 0 but don't store it. */
-
-       meta = SLIST_FIRST(&stack->metas);
-       if (meta == NULL) {
-               BN_free(number);
-               return; /* The TA lacks siblings, so serial is unique. */
-       }
-
-       /*
-        * Note: This is is reported as a warning, even though duplicate serial
-        * numbers are clearly a violation of the RFC and common sense.
-        *
-        * But it cannot be simply upgraded into an error because we are
-        * realizing the problem too late; our traversal is depth-first, so we
-        * already approved the other bogus certificate and its children.
-        * (I don't think changing to a breath-first traversal would be a good
-        * idea; the RAM usage would skyrocket because, since we need the entire
-        * certificate path to the root to validate any certificate, we would
-        * end up having the entire tree loaded in memory by the time we're done
-        * traversing.)
-        *
-        * So instead of arbitrarily approving one certificate but not the
-        * other, we will accept both but report a warning.
-        *
-        * Also: It's pretty odd; a significant amount of certificates seem to
-        * be breaking this rule. Maybe we're the only ones catching it?
-        *
-        * TODO I haven't seen this warning in a while. Review.
-        */
-       ARRAYLIST_FOREACH(&meta->serials, cursor) {
-               if (BN_cmp(cursor->number, number) == 0) {
-                       BN2string(number, &string);
-                       pr_val_warn("Serial number '%s' is not unique. (Also found in '%s'.)",
-                           string, cursor->file);
-                       BN_free(number);
-                       free(string);
-                       return;
-               }
-       }
-
-       duplicate.number = number;
-       duplicate.file = get_current_file_name();
-
-       serial_numbers_add(&meta->serials, &duplicate);
-}
-
-STACK_OF(X509) *
-certstack_get_x509s(struct cert_stack *stack)
-{
-       return stack->x509s;
-}
-
-int
-certstack_get_x509_num(struct cert_stack *stack)
-{
-       return sk_X509_num(stack->x509s);
-}
diff --git a/src/cert_stack.h b/src/cert_stack.h
deleted file mode 100644 (file)
index 518b375..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-#ifndef SRC_CERT_STACK_H_
-#define SRC_CERT_STACK_H_
-
-#include <openssl/bn.h>
-
-#include "object/certificate.h"
-#include "object/name.h"
-#include "resource.h"
-#include "types/map.h"
-
-/*
- * One certificate stack is allocated per validation cycle, and it is used
- * through its entirety to hold the certificates relevant to the ongoing
- * validation.
- *
- * Keep in mind: This module deals with two different (but correlated) stack
- * data structures, and they both store "certificates" (albeit in different
- * representations):
- *
- * - Defer stack: This one stores certificates whose validation has been
- *   postponed during the validation cycle. (They were found in some manifest
- *   list, and haven't been opened yet.)
- *   It prevents us from having to validate the RPKI tree in a recursive manner,
- *   which would be prone to stack overflow.
- * - x509 stack: It is a chain of certificates, ready to be validated by
- *   libcrypto.
- *   For any given certificate being validated, this stack stores all of its
- *   parents.
- */
-
-struct cert_stack;
-
-struct deferred_cert {
-       struct cache_mapping *map;
-       struct rpp *pp;
-};
-
-int certstack_create(struct cert_stack **);
-void certstack_destroy(struct cert_stack *);
-
-void deferstack_push(struct cert_stack *, struct deferred_cert *cert);
-int deferstack_pop(struct cert_stack *, struct deferred_cert *cert);
-bool deferstack_is_empty(struct cert_stack *);
-
-int x509stack_push(struct cert_stack *, struct cache_mapping *, X509 *,
-    enum rpki_policy, enum cert_type);
-void x509stack_cancel(struct cert_stack *);
-X509 *x509stack_peek(struct cert_stack *);
-struct cache_mapping *x509stack_peek_map(struct cert_stack *);
-struct resources *x509stack_peek_resources(struct cert_stack *);
-void x509stack_store_serial(struct cert_stack *, BIGNUM *);
-typedef int (*subject_pk_check_cb)(bool *, char const *, void *);
-int x509stack_store_subject(struct cert_stack *, struct rfc5280_name *,
-    subject_pk_check_cb, void *);
-
-STACK_OF(X509) *certstack_get_x509s(struct cert_stack *);
-int certstack_get_x509_num(struct cert_stack *);
-
-#endif /* SRC_CERT_STACK_H_ */
index cac3313eeb1e3354fd2b7c30a46817bb8658a549..0f3403bfd5729e158af1359e759feadfcc8a142b 100644 (file)
 #include "certificate_refs.h"
 
-#include "log.h"
-#include "thread_var.h"
-
-void
-refs_init(struct certificate_refs *refs)
-{
-       memset(refs, 0, sizeof(struct certificate_refs));
-}
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
 
-void
-refs_cleanup(struct certificate_refs *refs)
-{
-       free(refs->crldp);
-       if (refs->caIssuers != NULL)
-               map_refput(refs->caIssuers);
-       if (refs->signedObject != NULL)
-               map_refput(refs->signedObject);
-}
+#include "log.h"
 
-static int
-validate_cdp(struct certificate_refs *refs, struct rpp const *pp)
+int
+validate_cdp(struct sia_uris *sias, char const *crl_url)
 {
-       struct cache_mapping *pp_crl;
-
-       if (refs->crldp == NULL)
+       if (sias->crldp == NULL)
                pr_crit("Certificate's CRL Distribution Point was not recorded.");
 
-       pp_crl = rpp_get_crl(pp);
-       if (pp_crl == NULL)
+       if (crl_url == NULL)
                pr_crit("Manifest's CRL was not recorded.");
 
-       if (strcmp(refs->crldp, map_get_url(pp_crl)) != 0) {
+       if (strcmp(sias->crldp, crl_url) != 0) {
                return pr_val_err("Certificate's CRL Distribution Point ('%s') does not match manifest's CRL ('%s').",
-                   refs->crldp, map_get_url(pp_crl));
+                   sias->crldp, crl_url);
        }
 
        return 0;
 }
 
 static int
-validate_signedObject(struct certificate_refs *refs,
-    struct cache_mapping *signedObject_map)
+validate_signedObject(struct sia_uris *sias, char const *url)
 {
-       if (refs->signedObject == NULL)
+       if (sias->signedObject == NULL)
                pr_crit("Certificate's signedObject was not recorded.");
 
-       if (!map_equals(refs->signedObject, signedObject_map)) {
+       /* XXX the left one is no longer normalized */
+       if (strcmp(sias->signedObject, url) != 0) {
                return pr_val_err("Certificate's signedObject ('%s') does not match the URI of its own signed object (%s).",
-                   map_val_get_printable(refs->signedObject),
-                   map_val_get_printable(signedObject_map));
+                   sias->signedObject, url);
        }
 
        return 0;
 }
 
-/**
- * Ensures the @refs URIs match the parent Manifest's URIs. Assumes @refs came
- * from a (non-TA) CA certificate.
- *
- * @refs: References you want validated.
- * @pp: Repository Publication Point, as described by the parent Manifest.
- */
-int
-refs_validate_ca(struct certificate_refs *refs, struct rpp const *pp)
-{
-       int error;
-
-       error = validate_cdp(refs, pp);
-       if (error)
-               return error;
-
-       if (refs->signedObject != NULL)
-               pr_crit("CA summary has a signedObject ('%s').",
-                   map_op_get_printable(refs->signedObject));
-
-       return 0;
-}
-
 /**
  * Ensures the @refs URIs match the Manifest URIs. Assumes @refs came from an
  * EE certificate.
  *
  * @refs: References you want validated.
- * @pp: Repository Publication Point, as described by the Manifest.
- * @map: Mapping of the signed object that contains the EE certificate.
+ * @url: URL of the signed object that contains the EE certificate.
  */
 int
-refs_validate_ee(struct certificate_refs *refs, struct rpp const *pp,
-    struct cache_mapping *map)
+refs_validate_ee(struct sia_uris *sias, char const *crl_url, char const *url)
 {
        int error;
 
-       error = validate_cdp(refs, pp);
+       error = validate_cdp(sias, crl_url);
        if (error)
                return error;
 
-       return validate_signedObject(refs, map);
+       return validate_signedObject(sias, url);
 }
index 01794d12508fadc6b3b5c3dc23f687d64b1dbb20..2df0890ba00748c3a0259c1f087ab07f7b95a383 100644 (file)
@@ -1,45 +1,11 @@
 #ifndef SRC_CERTIFICATE_REFS_H_
 #define SRC_CERTIFICATE_REFS_H_
 
-#include "rpp.h"
+/* XXX delete this */
 
-/**
- * Some of the URLs defined in Access Descriptions of a certificate's
- * extensions.
- *
- * It's intended to address some awkward RFC requirements:
- * RFC 6487 defines that these "MUST reference" certain files. I think the best
- * way to validate this is to check that they equal the respective URLs from the
- * manifest (because these will at some point be validated as part of the
- * traversal anyway). Problem is, these URLs are not guaranteed to be parsed by
- * the time the extension validation kicks in. So we store them in this
- * structure and handle them later.
- *
- * It makes a mess out of the code, and I'm not even sure that validating them
- * is our responsibility, but there you go.
- */
-struct certificate_refs {
-       /**
-        * CRL Distribution Points's fullName. Non-TA certificates only.
-        * RFC 6487, section 4.8.6.
-        */
-       char *crldp;
-       /**
-        * AIA's caIssuers. Non-TA certificates only.
-        * RFC 6487, section 4.8.7.
-        */
-       struct cache_mapping *caIssuers;
-       /**
-        * SIA's signedObject. EE certificates only.
-        * RFC 6487, section 4.8.8.2.
-        */
-       struct cache_mapping *signedObject;
-};
+#include "cache.h"
 
-void refs_init(struct certificate_refs *);
-void refs_cleanup(struct certificate_refs *);
-int refs_validate_ca(struct certificate_refs *, struct rpp const *);
-int refs_validate_ee(struct certificate_refs *, struct rpp const *,
-    struct cache_mapping *);
+int validate_cdp(struct sia_uris *, char const *);
+int refs_validate_ee(struct sia_uris *, char const *, char const *);
 
 #endif /* SRC_CERTIFICATE_REFS_H_ */
index 74e7383131181537b78e6a763f07a8620efe5793..ca10dc5cc14c0ae1457e6ac76e40bf40218564ef 100644 (file)
@@ -238,51 +238,41 @@ valid_file_or_dir(char const *location, bool check_file)
        return result;
 }
 
-/*
- * > 0: exists
- * = 0: !exists
- * < 0: error
- */
 static int
-dir_exists(char const *path)
+stat_dir(char const *path)
 {
        struct stat meta;
-       int error;
 
-       if (stat(path, &meta) != 0) {
-               error = errno;
-               if (error == ENOENT)
-                       return 0;
-               pr_op_err_st("stat() failed: %s", strerror(error));
-               return -error;
-       }
-
-       if (!S_ISDIR(meta.st_mode)) {
-               return pr_op_err_st("Path '%s' exists and is not a directory.",
-                   path);
-       }
-
-       return 1;
+       if (stat(path, &meta) != 0)
+               return errno;
+       if (!S_ISDIR(meta.st_mode))
+               return ENOTDIR;
+       return 0;
 }
 
 static int
-create_dir(char const *path)
+ensure_dir(char const *path)
 {
        int error;
 
-       if (mkdir(path, 0777) != 0) {
+       /*
+        * Perplexingly, mkdir() returns EEXIST instead of ENOTDIR when the
+        * path actually refers to a file.
+        * So it looks like this stat() is unavoidable.
+        */
+       if (stat_dir(path) == ENOTDIR && remove(path) < 0)
+               return errno;
+
+       if (mkdir(path, CACHE_FILEMODE) < 0) {
                error = errno;
-               if (error != EEXIST) {
-                       pr_op_err_st("Error while making directory '%s': %s",
-                           path, strerror(error));
-                       return error;
-               }
+               return (error == EEXIST) ? 0 : error;
        }
 
        return 0;
 }
 
 /* mkdir -p $_path */
+/* XXX Maybe also short-circuit by parent? */
 int
 mkdir_p(char const *_path, bool include_basename)
 {
@@ -298,27 +288,30 @@ mkdir_p(char const *_path, bool include_basename)
                *last_slash = '\0';
        }
 
-       result = dir_exists(path); /* short circuit */
-       if (result > 0) {
-               result = 0;
-               goto end;
-       } else if (result < 0) {
+       result = stat_dir(path); /* short circuit */
+       if (result == 0)
+               goto end; /* Already exists */
+       if (result != ENOENT && result != ENOTDIR) {
+               pr_op_err_st("Error during stat(%s): %s",
+                   path, strerror(result));
                goto end;
        }
 
        for (i = 1; path[i] != '\0'; i++) {
                if (path[i] == '/') {
                        path[i] = '\0';
-                       result = create_dir(path);
+                       result = ensure_dir(path);
                        path[i] = '/';
-                       if (result != 0)
-                               goto end; /* error msg already printed */
+                       if (result != 0) {
+                               pr_op_err_st("Error during mkdir(%s): %s",
+                                   path, strerror(result));
+                               goto end;
+                       }
                }
        }
-       result = create_dir(path);
+       result = ensure_dir(path);
 
-end:
-       free(path);
+end:   free(path);
        return result;
 }
 
@@ -328,7 +321,7 @@ end:
  * If parent's parent is now empty, delete parent's parent.
  * And so on.
  *
- * FIXME this should be done by the cache cleaner instead.
+ * XXX this should be done by the cache cleaner instead.
  */
 int
 delete_dir_recursive_bottom_up(char const *path)
@@ -338,8 +331,7 @@ delete_dir_recursive_bottom_up(char const *path)
        size_t config_len;
        int error;
 
-       errno = 0;
-       if (remove(path) != 0) {
+       if (remove(path) < 0) {
                error = errno;
                pr_val_err("Couldn't delete '%s': %s", path, strerror(error));
                return error;
@@ -387,20 +379,67 @@ release_str:
        return error;
 }
 
+time_t
+time_nonfatal(void)
+{
+       time_t result;
+
+       result = time(NULL);
+       if (result == ((time_t)-1)) {
+               pr_val_warn("time(NULL) returned -1: %s", strerror(errno));
+               result = 0;
+       }
+
+       return result;
+}
+
+time_t
+time_fatal(void)
+{
+       time_t result;
+
+       result = time(NULL);
+       if (result == ((time_t)-1))
+               pr_crit("time(NULL) returned -1: %s", strerror(errno));
+
+       return result;
+}
+
+int
+time2str(time_t tt, char *str)
+{
+       struct tm tmbuffer, *tm;
+
+       memset(&tmbuffer, 0, sizeof(tmbuffer));
+       tm = gmtime_r(&tt, &tmbuffer);
+       if (tm == NULL)
+               return errno;
+       if (strftime(str, FORT_TS_LEN, FORT_TS_FORMAT, tm) == 0)
+               return ENOSPC;
+
+       return 0;
+}
+
 int
-get_current_time(time_t *result)
+str2time(char const *str, time_t *tt)
 {
-       time_t now;
+       char const *consumed;
+       struct tm tm;
+       time_t time;
        int error;
 
-       now = time(NULL);
-       if (now == ((time_t) -1)) {
+       memset(&tm, 0, sizeof(tm));
+       consumed = strptime(str, FORT_TS_FORMAT, &tm);
+       if (consumed == NULL || (*consumed) != 0)
+               return pr_op_err("String '%s' does not appear to be a timestamp.",
+                   str);
+       time = timegm(&tm);
+       if (time == ((time_t) -1)) {
                error = errno;
-               pr_val_err("Error getting the current time: %s",
-                   strerror(errno));
-               return error;
+               return pr_op_err("String '%s' does not appear to be a timestamp: %s",
+                   str, strerror(error));
        }
 
-       *result = now;
+       *tt = time;
        return 0;
 }
index 01c3a788731faff2b2622c92e591630c7b2ca30f..4ed0b2647c92e5601615060da673df968fcb0e5a 100644 (file)
 #define ENOTSUPPORTED 3172
 /* "I haven't implemented this yet." */
 #define ENOTIMPLEMENTED 3173
-/* "URI was not RSYNC." */
-#define ENOTRSYNC 3174
-/* "URI was not HTTPS." */
-#define ENOTHTTPS 3175
 
 /*
  * If you're wondering why I'm not using -abs(error), it's because abs(INT_MIN)
@@ -23,8 +19,6 @@
  */
 #define ENSURE_NEGATIVE(error) (((error) < 0) ? (error) : -(error))
 
-#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0]))
-
 bool str_starts_with(char const *, char const *);
 bool str_ends_with(char const *, char const *);
 
@@ -45,14 +39,27 @@ int rwlock_read_lock(pthread_rwlock_t *);
 void rwlock_write_lock(pthread_rwlock_t *);
 void rwlock_unlock(pthread_rwlock_t *);
 
+#define CACHE_FILEMODE 0755
+
 typedef int (*foreach_file_cb)(char const *, void *);
 int foreach_file(char const *, char const *, bool, foreach_file_cb, void *);
 
+// XXX
 bool valid_file_or_dir(char const *, bool);
 
 int mkdir_p(char const *, bool);
 int delete_dir_recursive_bottom_up(char const *);
 
-int get_current_time(time_t *);
+time_t time_nonfatal(void);
+time_t time_fatal(void);
+
+/*
+ * Careful with this; several of the conversion specification characters
+ * documented in the Linux man page are not actually portable.
+ */
+#define FORT_TS_FORMAT "%Y-%m-%dT%H:%M:%SZ"
+#define FORT_TS_LEN 21 /* strlen("YYYY-mm-ddTHH:MM:SSZ") + 1 */
+int time2str(time_t, char *);
+int str2time(char const *, time_t *);
 
 #endif /* SRC_RTR_COMMON_H_ */
index fafaab243043328215f371d60fd052e1d2146a7f..e21ebbd13d1935a79d9af00408ad5f4c5be482e1 100644 (file)
@@ -1,8 +1,10 @@
 #include "config.h"
 
+#include <curl/curl.h>
+#include <errno.h>
 #include <getopt.h>
-#include <limits.h>
-#include <sys/socket.h>
+#include <libxml/xmlreader.h>
+#include <openssl/opensslv.h>
 #include <syslog.h>
 
 #include "alloc.h"
 #include "config/boolean.h"
 #include "config/incidences.h"
 #include "config/str.h"
+#include "config/time.h"
 #include "config/uint.h"
 #include "config/work_offline.h"
 #include "configure_ac.h"
 #include "daemon.h"
-#include "file.h"
 #include "init.h"
 #include "json_handler.h"
 #include "log.h"
+#include "state.h"
+#include "thread_pool.h"
+#include "types/array.h"
+#include "types/path.h"
 
 /**
  * To add a member to this structure,
@@ -39,6 +45,8 @@ struct rpki_config {
        /**
         * rfc6487#section-7.2, last paragraph.
         * Prevents arbitrarily long paths and loops.
+        *
+        * XXX X509_VERIFY_MAX_CHAIN_CERTS
         */
        unsigned int maximum_certificate_depth;
        /** File or directory where the .slurm file(s) is(are) located */
@@ -87,11 +95,9 @@ struct rpki_config {
                        /* Interval (in seconds) between each retry */
                        unsigned int interval;
                } retry;
+               unsigned int transfer_timeout;
                char *program;
-               struct {
-                       struct string_array flat; /* Deprecated */
-                       struct string_array recursive;
-               } args;
+               struct string_array args;
        } rsync;
 
        struct {
@@ -211,6 +217,16 @@ struct rpki_config {
 
        enum file_type ft;
        char *payload;
+
+       struct {
+               /*
+                * If nonzero, all RPKI object expiration dates are compared to
+                * this number instead of the current time.
+                * Meant for test repositories we don't want to have to keep
+                * regenerating.
+                */
+               time_t validation_time;
+       } debug;
 };
 
 static void print_usage(FILE *, bool);
@@ -451,6 +467,7 @@ static const struct option_field options[] = {
                .doc = "rsync's priority for repository file fetching. Higher value means higher priority.",
                .min = 0,
                .max = 100,
+               /* XXX deprecated? */
        }, {
                .id = 3002,
                .name = "rsync.strategy",
@@ -488,21 +505,26 @@ static const struct option_field options[] = {
                .id = 3006,
                .name = "rsync.arguments-recursive",
                .type = &gt_string_array,
-               .offset = offsetof(struct rpki_config, rsync.args.recursive),
-               .doc = "RSYNC program arguments",
+               .offset = offsetof(struct rpki_config, rsync.args),
+               .doc = "Deprecated; does nothing.",
                .availability = AVAILABILITY_JSON,
-               /* Unlimited */
-               .max = 0,
+               .deprecated = true,
        }, {
                .id = 3007,
                .name = "rsync.arguments-flat",
                .type = &gt_string_array,
-               .offset = offsetof(struct rpki_config, rsync.args.flat),
+               .offset = offsetof(struct rpki_config, rsync.args),
                .doc = "Deprecated; does nothing.",
                .availability = AVAILABILITY_JSON,
-               /* Unlimited */
-               .max = 0,
                .deprecated = true,
+       }, {
+               .id = 3008,
+               .name = "rsync.transfer-timeout",
+               .type = &gt_uint,
+               .offset = offsetof(struct rpki_config, rsync.transfer_timeout),
+               .doc = "Maximum transfer time before killing the rsync process",
+               .min = 0,
+               .max = UINT_MAX,
        },
 
        /* HTTP requests parameters */
@@ -520,6 +542,7 @@ static const struct option_field options[] = {
                .doc = "HTTP's priority for repository file fetching. Higher value means higher priority.",
                .min = 0,
                .max = 100,
+               /* XXX deprecated? */
        }, {
                .id = 9002,
                .name = "http.retry.count",
@@ -796,6 +819,13 @@ static const struct option_field options[] = {
                .max = 100,
        },
 
+       {
+               .id = 13000,
+               .name = "debug.validation-time",
+               .type = &gt_time,
+               .offset = offsetof(struct rpki_config, debug.validation_time),
+       },
+
        {
                .id = 13000,
                .name = "file-type",
@@ -915,6 +945,11 @@ print_config(void)
        struct option_field const *opt;
 
        pr_op_info(PACKAGE_STRING);
+       pr_op_info("  libcrypto: " OPENSSL_VERSION_TEXT);
+       pr_op_info("  jansson:   " JANSSON_VERSION);
+       pr_op_info("  libcurl:   " LIBCURL_VERSION);
+       pr_op_info("  libxml:    " LIBXML_DOTTED_VERSION);
+
        pr_op_info("Configuration {");
 
        FOREACH_OPTION(options, opt, 0xFFFF)
@@ -927,18 +962,7 @@ print_config(void)
 static void
 set_default_values(void)
 {
-       static char const *recursive_rsync_args[] = {
-               "-rtz", "--delete", "--omit-dir-times",
-
-               "--contimeout=20", "--max-size=20MB", "--timeout=15",
-
-               "--include=*/", "--include=*.cer", "--include=*.crl",
-               "--include=*.gbr", "--include=*.mft", "--include=*.roa",
-               "--exclude=*",
-
-               "$REMOTE", "$LOCAL",
-       };
-       static char const *flat_rsync_args[] = { "<deprecated>" };
+       static char const *trash[] = { "<deprecated>" };
        static char const *addrs[] = {
 #ifdef __linux__
                "::"
@@ -975,11 +999,9 @@ set_default_values(void)
        rpki_config.rsync.strategy = pstrdup("<deprecated>");
        rpki_config.rsync.retry.count = 1;
        rpki_config.rsync.retry.interval = 4;
+       rpki_config.rsync.transfer_timeout = 900;
        rpki_config.rsync.program = pstrdup("rsync");
-       string_array_init(&rpki_config.rsync.args.flat,
-           flat_rsync_args, ARRAY_LEN(flat_rsync_args));
-       string_array_init(&rpki_config.rsync.args.recursive,
-           recursive_rsync_args, ARRAY_LEN(recursive_rsync_args));
+       string_array_init(&rpki_config.rsync.args, trash, ARRAY_LEN(trash));
 
        rpki_config.http.enabled = true;
        /* Higher priority than rsync by default */
@@ -989,7 +1011,7 @@ set_default_values(void)
        rpki_config.http.user_agent = pstrdup(PACKAGE_NAME "/" PACKAGE_VERSION);
        rpki_config.http.max_redirs = 10;
        rpki_config.http.connect_timeout = 30;
-       rpki_config.http.transfer_timeout = 0;
+       rpki_config.http.transfer_timeout = 900;
        rpki_config.http.low_speed_limit = 100000;
        rpki_config.http.low_speed_time = 10;
        rpki_config.http.max_file_size = 1000000000;
@@ -1260,6 +1282,12 @@ config_get_local_repository(void)
        return rpki_config.local_repository;
 }
 
+time_t
+cfg_cache_threshold(void)
+{
+       return 86400; // XXX
+}
+
 unsigned int
 config_get_max_cert_depth(void)
 {
@@ -1285,7 +1313,7 @@ config_get_op_log_color_output(void)
 }
 
 enum filename_format
-config_get_op_log_filename_format(void)
+config_get_op_log_file_format(void)
 {
        return rpki_config.log.filename_format;
 }
@@ -1327,11 +1355,19 @@ config_get_val_log_color_output(void)
 }
 
 enum filename_format
-config_get_val_log_filename_format(void)
+config_get_val_log_file_format(void)
 {
        return rpki_config.validation_log.filename_format;
 }
 
+char const *
+logv_filename(char const *path)
+{
+       return (rpki_config.validation_log.filename_format == FNF_NAME)
+            ? path_filename(path)
+            : path;
+}
+
 uint8_t
 config_get_val_log_level(void)
 {
@@ -1374,16 +1410,16 @@ config_get_rsync_retry_interval(void)
        return rpki_config.rsync.retry.interval;
 }
 
-char *
-config_get_rsync_program(void)
+long
+config_get_rsync_transfer_timeout(void)
 {
-       return rpki_config.rsync.program;
+       return rpki_config.rsync.transfer_timeout;
 }
 
-struct string_array const *
-config_get_rsync_args(void)
+char const *
+config_get_rsync_program(void)
 {
-       return &rpki_config.rsync.args.recursive;
+       return rpki_config.rsync.program;
 }
 
 bool
@@ -1506,6 +1542,12 @@ config_get_payload(void)
        return rpki_config.payload;
 }
 
+time_t
+config_get_validation_time(void)
+{
+       return rpki_config.debug.validation_time;
+}
+
 void
 config_set_rsync_enabled(bool value)
 {
index 8467eaea3b3b63cd5b766f8c4d111d4444f7d3c7..a67c9a3cccee2888a214cbfaccee80a30ff7618f 100644 (file)
@@ -5,6 +5,7 @@
 #include <netdb.h>
 #include <netinet/in.h>
 #include <stdint.h>
+#include <sys/stat.h>
 
 #include "config/file_type.h"
 #include "config/filename_format.h"
@@ -12,7 +13,6 @@
 #include "config/mode.h"
 #include "config/output_format.h"
 #include "config/string_array.h"
-#include "config/types.h"
 
 /* Init/destroy */
 int handle_flags_config(int , char **);
@@ -31,6 +31,7 @@ char const *config_get_slurm(void);
 
 char const *config_get_tal(void);
 char const *config_get_local_repository(void);
+time_t cfg_cache_threshold(void);
 unsigned int config_get_max_cert_depth(void);
 enum mode config_get_mode(void);
 char const *config_get_http_user_agent(void);
@@ -46,8 +47,8 @@ bool config_get_rsync_enabled(void);
 unsigned int config_get_rsync_priority(void);
 unsigned int config_get_rsync_retry_count(void);
 unsigned int config_get_rsync_retry_interval(void);
-char *config_get_rsync_program(void);
-struct string_array const *config_get_rsync_args(void);
+long config_get_rsync_transfer_timeout(void);
+char const *config_get_rsync_program(void);
 bool config_get_http_enabled(void);
 unsigned int config_get_http_priority(void);
 unsigned int config_get_http_retry_count(void);
@@ -59,12 +60,13 @@ unsigned int config_get_asn1_decode_max_stack(void);
 unsigned int config_get_thread_pool_server_max(void);
 enum file_type config_get_file_type(void);
 char const *config_get_payload(void);
+time_t config_get_validation_time(void);
 
 /* Logging getters */
 bool config_get_op_log_enabled(void);
 char const * config_get_op_log_tag(void);
 bool config_get_op_log_color_output(void);
-enum filename_format config_get_op_log_filename_format(void);
+enum filename_format config_get_op_log_file_format(void);
 uint8_t config_get_op_log_level(void);
 enum log_output config_get_op_log_output(void);
 uint32_t config_get_op_log_facility(void);
@@ -72,7 +74,8 @@ uint32_t config_get_op_log_facility(void);
 bool config_get_val_log_enabled(void);
 char const * config_get_val_log_tag(void);
 bool config_get_val_log_color_output(void);
-enum filename_format config_get_val_log_filename_format(void);
+enum filename_format config_get_val_log_file_format(void);
+char const *logv_filename(char const *);
 uint8_t config_get_val_log_level(void);
 enum log_output config_get_val_log_output(void);
 uint32_t config_get_val_log_facility(void);
index 99e17c4085d572151d6041bcf600d51f8ffd64be..7d20fe07e357cc3021a5bccd047904c8e130484d 100644 (file)
@@ -1,6 +1,6 @@
 #include "config/incidences.h"
 
-#include "incidence/incidence.h"
+#include "incidence.h"
 
 static void
 incidences_print(struct option_field const *field, void *_value)
index 7293b7f8e99e72875619366fc6285c50fd0f5dbf..80ccb415d1227315bc21fd3bffc620ae99475adb 100644 (file)
@@ -2,10 +2,10 @@
 
 #include <getopt.h>
 
-#include "alloc.h"
 #include "config/str.h"
 #include "log.h"
-#include "str_token.h"
+#include "types/path.h"
+#include "types/str.h"
 
 void
 string_array_init(struct string_array *array, char const *const *values,
diff --git a/src/config/time.c b/src/config/time.c
new file mode 100644 (file)
index 0000000..ed6605a
--- /dev/null
@@ -0,0 +1,55 @@
+#include "config/time.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <time.h>
+
+#include "common.h"
+#include "log.h"
+
+static void
+print_time(struct option_field const *field, void *value)
+{
+       time_t tt;
+       char str[FORT_TS_LEN];
+       int error;
+
+       tt = *((time_t *)value);
+       if (tt == 0)
+               return;
+
+       error = time2str(tt, str);
+       if (error)
+               pr_crit("time2str: %d", error);
+
+       pr_op_info("%s: %s", field->name, str);
+}
+
+static int
+parse_argv_time(struct option_field const *field, char const *str,
+    void *result)
+{
+       if (str == NULL || strlen(str) == 0)
+               return pr_op_err("--%s needs an argument.", field->name);
+
+       return str2time(str, result);
+}
+
+static int
+parse_json_time(struct option_field const *opt, json_t *json, void *result)
+{
+       if (!json_is_string(json))
+               return pr_op_err("The '%s' element is not a JSON string.",
+                   opt->name);
+
+       return str2time(json_string_value(json), result);
+}
+
+const struct global_type gt_time = {
+       .has_arg = required_argument,
+       .size = sizeof(time_t),
+       .print = print_time,
+       .parse.argv = parse_argv_time,
+       .parse.json = parse_json_time,
+       .arg_doc = FORT_TS_FORMAT,
+};
diff --git a/src/config/time.h b/src/config/time.h
new file mode 100644 (file)
index 0000000..e59b05e
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef SRC_CONFIG_TIME_H_
+#define SRC_CONFIG_TIME_H_
+
+#include "config/types.h"
+
+extern const struct global_type gt_time;
+
+#endif /* SRC_CONFIG_TIME_H_ */
index 3815f0fd3824de2273fe29a947e6e8c76c80ea0e..f9797c30cd7fb7f61818fa9580f6f9eff757c24a 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef SRC_CONFIG_WORK_OFFLINE_H_
 #define SRC_CONFIG_WORK_OFFLINE_H_
 
+#include "config/types.h"
+
 extern const struct global_type gt_work_offline;
 
 #endif /* SRC_CONFIG_WORK_OFFLINE_H_ */
index c3e6060ae2df8ba5877ec814639d19f5d334abe0..b9de9f269409849fd8efce26fca9b66f1db0a336 100644 (file)
@@ -4,9 +4,7 @@
 #include <openssl/obj_mac.h>
 #include <openssl/objects.h>
 
-#include "cert_stack.h"
-#include "common.h"
-#include "crypto/hash.h"
+#include "hash.h"
 #include "json_util.h"
 #include "libcrypto_util.h"
 #include "log.h"
@@ -999,20 +997,20 @@ int
 handle_aki(void *ext, void *arg)
 {
        AUTHORITY_KEYID *aki = ext;
-       X509 *parent;
+       X509 *parent = arg;
 
+       if (aki->keyid == NULL) {
+               return pr_val_err("The %s lacks a keyIdentifier.",
+                   ext_aki()->name);
+       }
        if (aki->issuer != NULL) {
-               return pr_val_err("%s extension contains an authorityCertIssuer.",
+               return pr_val_err("The %s contains an authorityCertIssuer.",
                    ext_aki()->name);
        }
        if (aki->serial != NULL) {
-               return pr_val_err("%s extension contains an authorityCertSerialNumber.",
+               return pr_val_err("The %s contains an authorityCertSerialNumber.",
                    ext_aki()->name);
        }
 
-       parent = x509stack_peek(validation_certstack(state_retrieve()));
-       if (parent == NULL)
-               return pr_val_err("Certificate has no parent.");
-
        return validate_public_key_hash(parent, aki->keyid, "AKI");
 }
index b23e90e514e1810cad6330169ad6286ed9540d43..140d11740b959f2799c7d48335eb02de4710723b 100644 (file)
@@ -1,10 +1,13 @@
 #include "file.h"
 
+#include <fcntl.h>
 #include <ftw.h>
 
 #include "alloc.h"
+#include "common.h"
+#include "config/mode.h"
 #include "log.h"
-#include "data_structure/uthash.h"
+#include "types/path.h"
 
 int
 file_open(char const *file_name, FILE **result, struct stat *stat)
@@ -26,7 +29,7 @@ file_open(char const *file_name, FILE **result, struct stat *stat)
                goto fail;
        }
        if (!S_ISREG(stat->st_mode)) {
-               error = pr_val_err("%s does not seem to be a file", file_name);
+               error = pr_val_err("'%s' does not seem to be a file.", file_name);
                goto fail;
        }
 
@@ -57,6 +60,36 @@ file_write(char const *file_name, char const *mode, FILE **result)
        return 0;
 }
 
+int
+file_write_full(char const *path, unsigned char const *content,
+    size_t content_len)
+{
+       FILE *out;
+       size_t written;
+       int error;
+
+       pr_val_debug("Writing file: %s", path);
+
+       error = mkdir_p(path, false);
+       if (error)
+               return error;
+
+       error = file_write(path, "wb", &out);
+       if (error)
+               return error;
+
+       written = fwrite(content, sizeof(unsigned char), content_len, out);
+       file_close(out);
+
+       if (written != content_len)
+               return pr_val_err(
+                   "Couldn't write file '%s' (error code not available)",
+                   path
+               );
+
+       return 0;
+}
+
 void
 file_close(FILE *file)
 {
@@ -126,6 +159,7 @@ file_free(struct file_contents *fc)
 }
 
 /* Wrapper for stat(), mostly for the sake of unit test mocking. */
+/* XXX needs a rename, because it returns errno. */
 int
 file_exists(char const *path)
 {
@@ -133,6 +167,83 @@ file_exists(char const *path)
        return (stat(path, &meta) == 0) ? 0 : errno;
 }
 
+/* strlen("cache/tmp/123"), ie. 13 */
+static size_t src_offset;
+/* cache/rsync/a.b.c/d/e */
+static char const *merge_dst;
+
+/* Moves cache/tmp/123/z into cache/rsync/a.b.c/d/e/z. */
+static int
+merge_into(const char *src, const struct stat *st, int typeflag,
+    struct FTW *ftw)
+{
+       char *dst;
+       struct timespec times[2];
+
+       dst = path_join(merge_dst, &src[src_offset]);
+
+       if (S_ISDIR(st->st_mode)) {
+               pr_op_debug("mkdir -p %s", dst);
+               if (mkdir_p(dst, true)) {
+                       PR_DEBUG_MSG("Failed: %s", strerror(errno));
+                       goto end;
+               }
+
+               times[0] = st->st_atim;
+               times[1] = st->st_mtim;
+               if (utimensat(AT_FDCWD, dst, times, AT_SYMLINK_NOFOLLOW) < 0)
+                       PR_DEBUG_MSG("utimensat: %s", strerror(errno));
+       } else {
+               pr_op_debug("rename: %s -> %s", src, dst);
+               if (rename(src, dst) < 0) {
+                       if (errno == EISDIR) {
+                               /* XXX stacked nftw()s */
+                               if (file_rm_rf(dst) != 0)
+                                       PR_DEBUG_MSG("%s", "AAAAAAAAAAA");
+                               if (rename(src, dst) < 0)
+                                       PR_DEBUG_MSG("rename: %s", strerror(errno));
+                       }
+               }
+       }
+
+end:   free(dst);
+       return 0;
+}
+
+/*
+ * Move all the files contained in @src to @dst, overwriting when necessary,
+ * not touching files that exist in @dst but not in @src.
+ *
+ * @src must exist.
+ *
+ * @src: cache/tmp/123
+ * @dst: cache/rsync/a.b.c/d/e
+ */
+int
+file_merge_into(char const *src, char const *dst)
+{
+       int error;
+
+       error = mkdir_p(dst, false);
+       if (error)
+               return error;
+
+       if (file_exists(dst) == ENOENT) {
+               if (rename(src, dst) < 0) {
+                       error = errno;
+                       pr_op_err("Could not move %s to %s: %s",
+                           src, dst, strerror(error));
+                       return error;
+               }
+               return 0;
+       }
+
+       src_offset = strlen(src);
+       merge_dst = dst;
+       /* TODO (performance) optimize that 32 */
+       return nftw(src, merge_into, 32, FTW_PHYS);
+}
+
 /*
  * Like remove(), but don't care if the file is already deleted.
  */
@@ -141,8 +252,7 @@ file_rm_f(char const *path)
 {
        int error;
 
-       errno = 0;
-       if (remove(path) != 0) {
+       if (remove(path) < 0) {
                error = errno;
                if (error != ENOENT)
                        return error;
@@ -155,8 +265,7 @@ static int
 rm(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
 {
        pr_op_debug("Deleting %s.", fpath);
-       errno = 0;
-       return (remove(fpath) != 0) ? errno : 0;
+       return (remove(fpath) < 0) ? errno : 0;
 }
 
 /* Same as `system("rm -rf <path>")`, but more portable and maaaaybe faster. */
@@ -166,3 +275,37 @@ file_rm_rf(char const *path)
        /* TODO (performance) optimize that 32 */
        return nftw(path, rm, 32, FTW_DEPTH | FTW_PHYS);
 }
+
+void
+cseq_init(struct cache_sequence *seq, char *prefix)
+{
+       seq->prefix = prefix;
+       seq->next_id = 0;
+       seq->pathlen = strlen(prefix) + 4;
+}
+
+char *
+cseq_next(struct cache_sequence *seq)
+{
+       char *path;
+       int len;
+
+       do {
+               path = pmalloc(seq->pathlen);
+
+               // XXX not generic enough
+               len = snprintf(path, seq->pathlen, "%s/%lX",
+                   seq->prefix, seq->next_id);
+               if (len < 0) {
+                       pr_val_err("Cannot compute new cache path: Unknown cause.");
+                       return NULL;
+               }
+               if (len < seq->pathlen) {
+                       seq->next_id++;
+                       return path; /* Happy path */
+               }
+
+               seq->pathlen++;
+               free(path);
+       } while (true);
+}
index db3e9623cc93e63926273063d739f7363f2a1687..bcebcd3b97cb8add23bf1283e8b5c960c2929382 100644 (file)
@@ -25,6 +25,7 @@ struct file_contents {
 
 int file_open(char const *, FILE **, struct stat *);
 int file_write(char const *, char const *, FILE **);
+int file_write_full(char const *, unsigned char const *, size_t);
 void file_close(FILE *);
 
 int file_load(char const *, struct file_contents *, bool);
@@ -32,9 +33,19 @@ void file_free(struct file_contents *);
 
 int file_exists(char const *);
 
+int file_merge_into(char const *, char const *);
 int file_rm_f(char const *);
 int file_rm_rf(char const *);
 
+struct cache_sequence {
+       char *prefix;
+       unsigned long next_id;
+       size_t pathlen;
+};
+
+void cseq_init(struct cache_sequence *, char *);
+char *cseq_next(struct cache_sequence *);
+
 /*
  * Remember that this API is awkward:
  *
similarity index 87%
rename from src/crypto/hash.c
rename to src/hash.c
index 1b608f1df467d4bcb87524f885013e489456e3f6..21e5d837a9cb3680b9ef8c80335275af9258a336 100644 (file)
@@ -1,8 +1,5 @@
-#include "crypto/hash.h"
+#include "hash.h"
 
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
 #include <openssl/evp.h>
 
 #include "alloc.h"
@@ -156,15 +153,20 @@ end:
 }
 
 int
-hash_validate_file(struct hash_algorithm const *algorithm,
-    struct cache_mapping *map, unsigned char const *expected,
-    size_t expected_len)
+hash_validate_file(struct hash_algorithm const *algorithm, char const *path,
+    unsigned char const *expected, size_t expected_len)
 {
        unsigned char actual[EVP_MAX_MD_SIZE];
        size_t actual_len;
        int error;
 
-       error = hash_file(algorithm, map_get_path(map), actual, &actual_len);
+       pr_val_debug("Validating file hash: %s", path);
+
+       if (expected_len != hash_get_size(algorithm))
+               return pr_val_err("%s string has bogus size: %zu",
+                   hash_get_name(algorithm), expected_len);
+
+       error = hash_file(algorithm, path, actual, &actual_len);
        if (error)
                return error;
 
@@ -176,8 +178,18 @@ hash_validate_file(struct hash_algorithm const *algorithm,
        return 0;
 
 fail:
-       return pr_val_err("File '%s' does not match its expected hash.",
-           map_val_get_printable(map));
+       error = pr_val_err("File '%s' does not match its expected hash.", path);
+#ifdef UNIT_TESTING
+       size_t i;
+       printf("Expected: ");
+       for (i = 0; i < expected_len; i++)
+               printf("%02x", expected[i]);
+       printf("\nActual:   ");
+       for (i = 0; i < actual_len; i++)
+               printf("%02x", actual[i]);
+       printf("\n");
+#endif
+       return error;
 }
 
 static int
similarity index 73%
rename from src/crypto/hash.h
rename to src/hash.h
index 8d4c6c2ec888474b16b6596dc93fb64bebb1b4c8..386ecb413bc8cfa04a2855103f873255ce20172d 100644 (file)
@@ -1,8 +1,13 @@
 #ifndef SRC_HASH_H_
 #define SRC_HASH_H_
 
-#include <openssl/evp.h>
-#include "types/map.h"
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 struct hash_algorithm;
 
@@ -15,7 +20,7 @@ struct hash_algorithm const *hash_get_sha256(void);
 int hash_file(struct hash_algorithm const *, char const *, unsigned char *,
     size_t *);
 
-int hash_validate_file(struct hash_algorithm const *, struct cache_mapping *,
+int hash_validate_file(struct hash_algorithm const *, char const *,
     unsigned char const *, size_t);
 int hash_validate(struct hash_algorithm const *, unsigned char const *, size_t,
     unsigned char const *, size_t);
similarity index 94%
rename from src/http/http.c
rename to src/http.c
index c58e7bc05bb790a70b9ccf03cb45f454298d3044..ef7d32756c28f18ae6463abdb3bf13f0b518b4bb 100644 (file)
@@ -1,12 +1,11 @@
-#include "http/http.h"
+#include "http.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 "types/url.h"
 
 struct http_handler {
        CURL *curl;
@@ -131,6 +130,11 @@ http_easy_init(struct http_handler *handler, curl_off_t ims)
 
        setopt_str(result, CURLOPT_USERAGENT, config_get_http_user_agent());
 
+       setopt_str(result, CURLOPT_ACCEPT_ENCODING, "");
+
+       setopt_long(result, CURLOPT_FOLLOWLOCATION, 1);
+       setopt_long(result, CURLOPT_MAXREDIRS, config_get_max_redirs());
+
        setopt_long(result, CURLOPT_CONNECTTIMEOUT,
            config_get_http_connect_timeout());
        setopt_long(result, CURLOPT_TIMEOUT,
@@ -335,7 +339,7 @@ http_fetch(char const *src, char const *dst, curl_off_t ims, bool *changed)
 
                if (redirect == NULL)
                        break;
-               if (!str_same_origin(src, redirect)) {
+               if (!url_same_origin(src, redirect)) {
                        error = pr_val_err("%s is redirecting to %s; disallowing because of different origin.",
                           src, redirect);
                        redirect = NULL;
@@ -367,7 +371,7 @@ end:        http_easy_cleanup(&handler);
 }
 
 /*
- * Download @map->url into @map->path; HTTP assumed.
+ * Download @url into @path; HTTP assumed.
  *
  * If @changed returns true, the file was downloaded normally.
  * If @changed returns false, the file has not been modified since @ims.
@@ -377,22 +381,22 @@ end:      http_easy_cleanup(&handler);
  * If @changed is not NULL, initialize it to false.
  */
 int
-http_download(struct cache_mapping *map, curl_off_t ims, bool *changed)
+http_download(char const *url, char const *path, curl_off_t ims, bool *changed)
 {
        unsigned int r;
        int error;
 
-       pr_val_info("HTTP GET: %s -> %s", map_get_url(map), map_get_path(map));
+       pr_val_info("HTTP GET: %s -> %s", url, path);
 
-       error = mkdir_p(map_get_path(map), false);
+       /* XXX might not be needed anymore */
+       error = mkdir_p(path, false);
        if (error)
                return error;
 
        for (r = 0; true; r++) {
                pr_val_debug("Download attempt #%u...", r + 1);
 
-               error = http_fetch(map_get_url(map), map_get_path(map),
-                   ims, changed);
+               error = http_fetch(url, path, ims, changed);
                switch (error) {
                case 0:
                        pr_val_debug("Download successful.");
diff --git a/src/http.h b/src/http.h
new file mode 100644 (file)
index 0000000..ccab132
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef SRC_HTTP_H_
+#define SRC_HTTP_H_
+
+#include <curl/curl.h>
+#include <stdbool.h>
+
+int http_init(void);
+void http_cleanup(void);
+
+int http_download(char const *, char const *, curl_off_t, bool *);
+int http_download_direct(char const *, char const *);
+
+#endif /* SRC_HTTP_H_ */
diff --git a/src/http/http.h b/src/http/http.h
deleted file mode 100644 (file)
index c1a0a21..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-#ifndef SRC_HTTP_HTTP_H_
-#define SRC_HTTP_HTTP_H_
-
-#include <curl/curl.h>
-#include "types/map.h"
-
-int http_init(void);
-void http_cleanup(void);
-
-int http_download(struct cache_mapping *, curl_off_t, bool *);
-int http_download_direct(char const *, char const *);
-
-#endif /* SRC_HTTP_HTTP_H_ */
similarity index 94%
rename from src/incidence/incidence.c
rename to src/incidence.c
index 7b0c13e1527523453a0c5b57ee33cfbf9858e0f4..b9a35b2c870bd00b9021f01942da1848d1b44c9c 100644 (file)
@@ -1,11 +1,10 @@
-#include "incidence/incidence.h"
+#include "incidence.h"
 
 #include <assert.h>
 
-#include "common.h"
-#include "data_structure/common.h"
 #include "json_util.h"
 #include "log.h"
+#include "types/array.h"
 
 struct incidence {
        const enum incidence_id id;
@@ -46,12 +45,6 @@ static struct incidence incidences[__INID_MAX] = {
                "The current time is after the nextUpdate field at the manifest",
                INAC_ERROR,
        },
-       {
-               INID_CRL_STALE,
-               "incid-crl-stale",
-               "The current time is after the nextUpdate field at the CRL",
-               INAC_ERROR,
-       },
 };
 
 static int
similarity index 85%
rename from src/incidence/incidence.h
rename to src/incidence.h
index 015c303e7f5ad21075c272aaae178f5ad2c7c4ea..f0170aea9374338365d6446aa4d337b229e09b2a 100644 (file)
 enum incidence_id {
        INID_HASHALG_HAS_PARAMS,
        INID_OBJ_NOT_DER,
-       INID_MFT_FILE_NOT_FOUND,
-       INID_MFT_FILE_HASH_NOT_MATCH,
+       INID_MFT_FILE_NOT_FOUND, // XXX deprecate and no-op
+       INID_MFT_FILE_HASH_NOT_MATCH, // XXX deprecate and no-op
        INID_MFT_STALE,
-       INID_CRL_STALE,
+       // XXX Document elimination of INID_CRL_STALE
 
        __INID_MAX,
 };
index 5cb9ff4bfe4e9d0a44589fd41aeb94a9f86ef861..4faf458735ed62e03d7f8b4565577b6ee02dfb2e 100644 (file)
@@ -1,9 +1,8 @@
 #include "init.h"
 
 #include "config.h"
-#include "data_structure/path_builder.h"
-#include "http/http.h"
-#include "log.h"
+#include "http.h"
+#include "types/path.h"
 
 static int
 fetch_url(char const *url, char const *filename)
index 79504090880ba92ee1002182168837c22f74480d..5c56c2a1fb55d8446a058fb3eb1cf6038cd6a86b 100644 (file)
@@ -4,7 +4,6 @@
 
 #include "alloc.h"
 #include "config.h"
-#include "config/types.h"
 #include "log.h"
 
 static json_t *
index 1cdc2f382236a48552393b03b2b790f4a0499710..5976bf967601dff969a1253a743cd20b6efc2567 100644 (file)
@@ -4,15 +4,9 @@
 #include <limits.h>
 #include <time.h>
 
+#include "common.h"
 #include "log.h"
 
-/*
- * Careful with this; several of the conversion specification characters
- * documented in the Linux man page are not actually portable.
- */
-#define JSON_TS_FORMAT "%Y-%m-%dT%H:%M:%SZ"
-#define JSON_TS_LEN 21 /* strlen("YYYY-mm-ddTHH:MM:SSZ") + 1 */
-
 int
 json_get_str(json_t *parent, char const *name, char const **result)
 {
@@ -89,27 +83,23 @@ json_get_u32(json_t *parent, char const *name, uint32_t *result)
        return 0;
 }
 
-static int
-str2tt(char const *str, time_t *tt)
+int
+json_get_ulong(json_t *parent, char const *name, unsigned long *result)
 {
-       char const *consumed;
-       struct tm tm;
-       time_t time;
+       json_int_t json_int;
        int error;
 
-       memset(&tm, 0, sizeof(tm));
-       consumed = strptime(str, JSON_TS_FORMAT, &tm);
-       if (consumed == NULL || (*consumed) != 0)
-               return pr_op_err("String '%s' does not appear to be a timestamp.",
-                   str);
-       time = timegm(&tm);
-       if (time == ((time_t) -1)) {
-               error = errno;
-               return pr_op_err("String '%s' does not appear to be a timestamp: %s",
-                   str, strerror(error));
-       }
+       *result = 0;
 
-       *tt = time;
+       error = json_get_int_t(parent, name, &json_int);
+       if (error)
+               return error;
+       if (json_int < 0 || ULONG_MAX < json_int)
+               return pr_op_err("Tag '%s' (%" JSON_INTEGER_FORMAT
+                   ") is out of range [0, %lu].",
+                   name, json_int, ULONG_MAX);
+
+       *result = json_int;
        return 0;
 }
 
@@ -123,7 +113,7 @@ json_get_ts(json_t *parent, char const *name, time_t *result)
        if (error)
                return error;
 
-       return str2tt(str, result);
+       return str2time(str, result);
 }
 
 int
@@ -187,28 +177,25 @@ json_add_int(json_t *parent, char const *name, int value)
 }
 
 int
-json_add_str(json_t *parent, char const *name, char const *value)
+json_add_ulong(json_t *parent, char const *name, unsigned long value)
 {
-       if (json_object_set_new(parent, name, json_string(value)))
+       if (json_object_set_new(parent, name, json_integer(value)))
                return pr_op_err(
-                   "Cannot convert %s '%s' to json; unknown cause.",
+                   "Cannot convert %s '%lu' to json; unknown cause.",
                    name, value
                );
 
        return 0;
 }
 
-static int
-tt2str(time_t tt, char *str)
+int
+json_add_str(json_t *parent, char const *name, char const *value)
 {
-       struct tm tmbuffer, *tm;
-
-       memset(&tmbuffer, 0, sizeof(tmbuffer));
-       tm = gmtime_r(&tt, &tmbuffer);
-       if (tm == NULL)
-               return errno;
-       if (strftime(str, JSON_TS_LEN, JSON_TS_FORMAT, tm) == 0)
-               return ENOSPC;
+       if (json_object_set_new(parent, name, json_string(value)))
+               return pr_op_err(
+                   "Cannot convert %s '%s' to json; unknown cause.",
+                   name, value
+               );
 
        return 0;
 }
@@ -216,10 +203,10 @@ tt2str(time_t tt, char *str)
 int
 json_add_ts(json_t *parent, char const *name, time_t value)
 {
-       char str[JSON_TS_LEN];
+       char str[FORT_TS_LEN];
        int error;
 
-       error = tt2str(value, str);
+       error = time2str(value, str);
        if (error) {
                pr_op_err("Cannot convert timestamp '%s' to json: %s",
                    name, strerror(error));
index b67da934879e14f5bffd8bd19d01c12cdb3eda79..9ed85cdb5faa7903748eab715a318d59c40bf55c 100644 (file)
@@ -26,6 +26,7 @@
 
 int json_get_int(json_t *, char const *, int *);
 int json_get_u32(json_t *, char const *, uint32_t *);
+int json_get_ulong(json_t *, char const *, unsigned long *);
 int json_get_ts(json_t *, char const *, time_t *);
 int json_get_str(json_t *, char const *, char const **);
 int json_get_array(json_t *, char const *, json_t **);
@@ -34,6 +35,7 @@ int json_get_object(json_t *, char const *, json_t **);
 bool json_valid_members_count(json_t *, size_t);
 
 int json_add_int(json_t *, char const *, int);
+int json_add_ulong(json_t *, char const *, unsigned long);
 int json_add_str(json_t *, char const *, char const *);
 int json_add_ts(json_t *, char const *, time_t);
 
index db2e344b988e875dca0a2755f2e77740d332f8b8..74437c55711aecefbbd8971c349c497507d068da 100644 (file)
 #include "asn1/asn1c/OBJECT_IDENTIFIER.h"
 #include "extension.h"
 #include "json_util.h"
+#include "log.h"
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+#define BIO_PR_TIME(bio, tm) ASN1_TIME_print_ex(bio, tm, ASN1_DTFLGS_ISO8601)
+#else
+#define BIO_PR_TIME(bio, tm) ASN1_TIME_print(bio, tm)
+#endif
+
+char *
+asn1time2str(ASN1_TIME const *tm)
+{
+       BIO *bio;
+       BUF_MEM *buf;
+       char *res;
+
+       bio = BIO_new(BIO_s_mem());
+       if (bio == NULL)
+               enomem_panic();
+
+       if (BIO_PR_TIME(bio, tm) <= 0)
+               return NULL;
+
+       BIO_flush(bio);
+       BIO_get_mem_ptr(bio, &buf);
+       res = pstrndup(buf->data, buf->length);
+
+       BIO_free_all(bio);
+       return res;
+}
 
 /* Swallows @bio. */
 static json_t *
@@ -84,7 +113,6 @@ json_t *
 asn1time2json(ASN1_TIME const *time)
 {
        BIO *bio;
-       int success;
 
        if (time == NULL)
                return json_null();
@@ -93,12 +121,7 @@ asn1time2json(ASN1_TIME const *time)
        if (bio == NULL)
                return NULL;
 
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
-       success = ASN1_TIME_print_ex(bio, time, ASN1_DTFLGS_ISO8601);
-#else
-       success = ASN1_TIME_print(bio, time); /* Kill me */
-#endif
-       if (!success) {
+       if (BIO_PR_TIME(bio, time) <= 0) {
                BIO_free_all(bio);
                return NULL;
        }
index 493640c80dbf4ffef08c29d76a492a193cfd8642..e942bcca551f2109e83da04d46ca92da566a9f15 100644 (file)
@@ -8,6 +8,8 @@
 #include <openssl/x509.h>
 #include <openssl/x509v3.h>
 
+char *asn1time2str(ASN1_TIME const *);
+
 json_t *oid2json(ASN1_OBJECT const *);
 json_t *asn1int2json(ASN1_INTEGER const *);
 json_t *asn1str2json(ASN1_STRING const *); /* octet string, bit string, etc */
index ecff284eb3a192ac63711c4f48b5c5e789bf3168..741d508be9899c931afe1f4e8f1679b30d17719a 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -8,12 +8,12 @@
 #include <pthread.h>
 #include <signal.h>
 #include <stdarg.h>
-#include <sys/stat.h>
 #include <syslog.h>
 #include <time.h>
 
 #include "config.h"
 #include "thread_var.h"
+#include "types/path.h"
 
 struct level {
        char const *label;
@@ -21,22 +21,22 @@ struct level {
        FILE *stream;
 };
 
-static struct level DBG = { "DBG", "\x1B[36m" }; /* Cyan */
-static struct level INF = { "INF", "\x1B[37m" }; /* White */
-static struct level WRN = { "WRN", "\x1B[33m" }; /* Yellow */
-static struct level ERR = { "ERR", "\x1B[31m" }; /* Red */
-static struct level CRT = { "CRT", "\x1B[35m" }; /* Purple */
+static struct level DBG = { "DBG", PR_COLOR_DBG };
+static struct level INF = { "INF", PR_COLOR_INF };
+static struct level WRN = { "WRN", PR_COLOR_WRN };
+static struct level ERR = { "ERR", PR_COLOR_ERR };
+static struct level CRT = { "CRT", PR_COLOR_CRT };
 static struct level UNK = { "UNK", "" };
-#define COLOR_RESET "\x1B[0m"
 
 struct log_config {
        bool fprintf_enabled; /* Print on the standard streams? */
        bool syslog_enabled; /* Print on syslog? */
 
        uint8_t level;
-       char const *prefix;
+       char const *tag;
        bool color;
        int facility;
+       bool rm_filepath;
 };
 
 /* Configuration for the operation logs. */
@@ -45,10 +45,8 @@ static struct log_config op_config;
 static struct log_config val_config;
 
 /*
- * Note: Normally, fprintf and syslog would have separate locks.
- *
- * However, fprintf and syslog are rarely enabled at the same time, so I don't
- * think it's worth it. So I'm reusing the lock.
+ * fprintf and syslog are rarely enabled at the same time, so I reused the
+ * mutex.
  *
  * "log" + "lock" = "logck"
  */
@@ -104,12 +102,12 @@ print_stack_trace(char const *title)
 #endif /* BACKTRACE_ENABLED */
 }
 
-static void init_config(struct log_config *cfg, bool unit_tests)
+static void init_config(struct log_config *cfg)
 {
        cfg->fprintf_enabled = true;
-       cfg->syslog_enabled = !unit_tests;
+       cfg->syslog_enabled = true;
        cfg->level = LOG_DEBUG;
-       cfg->prefix = NULL;
+       cfg->tag = NULL;
        cfg->color = false;
        cfg->facility = LOG_DAEMON;
 }
@@ -208,7 +206,7 @@ register_signal_handlers(void)
 }
 
 int
-log_setup(bool unit_tests)
+log_setup(void)
 {
        /*
         * Remember not to use any actual logging functions until logging has
@@ -224,25 +222,21 @@ log_setup(bool unit_tests)
        CRT.stream = stderr;
        UNK.stream = stdout;
 
-       if (unit_tests)
-               openlog("fort", LOG_CONS | LOG_PID, LOG_DAEMON);
-
-       init_config(&op_config, unit_tests);
-       init_config(&val_config, unit_tests);
+       init_config(&op_config);
+       init_config(&val_config);
 
        error = pthread_mutex_init(&logck, NULL);
        if (error) {
-               fprintf(ERR.stream, "pthread_mutex_init() returned %d: %s\n",
+               fprintf(ERR.stream,
+                   "pthread_mutex_init() returned %d: %s\n",
+                   error, strerror(error));
+               syslog(LOG_ERR | op_config.facility,
+                   "pthread_mutex_init() returned %d: %s",
                    error, strerror(error));
-               if (!unit_tests)
-                       syslog(LOG_ERR | op_config.facility,
-                           "pthread_mutex_init() returned %d: %s",
-                           error, strerror(error));
                return error;
        }
 
-       if (!unit_tests)
-               register_signal_handlers();
+       register_signal_handlers();
 
        return 0;
 }
@@ -295,13 +289,15 @@ log_start(void)
        }
 
        op_config.level = config_get_op_log_level();
-       op_config.prefix = config_get_op_log_tag();
+       op_config.tag = config_get_op_log_tag();
        op_config.color = config_get_op_log_color_output();
        op_config.facility = config_get_op_log_facility();
+       op_config.rm_filepath = config_get_op_log_file_format() == FNF_NAME;
        val_config.level = config_get_val_log_level();
-       val_config.prefix = config_get_val_log_tag();
+       val_config.tag = config_get_val_log_tag();
        val_config.color = config_get_val_log_color_output();
        val_config.facility = config_get_val_log_facility();
+       val_config.rm_filepath = config_get_val_log_file_format() == FNF_NAME;
 }
 
 void
@@ -399,24 +395,28 @@ __vfprintf(int level, struct log_config *cfg, char const *format, va_list args)
 
        now = time(NULL);
        if (now != ((time_t) -1)) {
+               // XXX not catching any errors
                localtime_r(&now, &stm_buff);
                strftime(time_buff, sizeof(time_buff), "%b %e %T", &stm_buff);
                fprintf(lvl->stream, "%s ", time_buff);
        }
 
        fprintf(lvl->stream, "%s", lvl->label);
-       if (cfg->prefix)
-               fprintf(lvl->stream, " [%s]", cfg->prefix);
+       if (cfg->tag)
+               fprintf(lvl->stream, " [%s]", cfg->tag);
        fprintf(lvl->stream, ": ");
 
        file_name = fnstack_peek();
-       if (file_name != NULL)
+       if (file_name != NULL) {
+               if (cfg->rm_filepath)
+                       file_name = path_filename(file_name);
                fprintf(lvl->stream, "%s: ", file_name);
+       }
 
        vfprintf(lvl->stream, format, args);
 
        if (cfg->color)
-               fprintf(lvl->stream, COLOR_RESET);
+               fprintf(lvl->stream, PR_COLOR_RST);
        fprintf(lvl->stream, "\n");
 
        /* Force flush */
@@ -426,35 +426,46 @@ __vfprintf(int level, struct log_config *cfg, char const *format, va_list args)
        unlock_mutex();
 }
 
-#define MSG_LEN 1024
+/*
+ * TODO (fine) Optimize. Notice the buffer is static, which seems to be the
+ * reason why it's (probably ill-advisedly) mutexing.
+ */
+#define MSG_LEN 512
 
 static void
 __syslog(int level, struct log_config *cfg, const char *format, va_list args)
 {
        static char msg[MSG_LEN];
-       char const *file_name;
+       char const *file;
+       int res;
 
-       file_name = fnstack_peek();
+       level |= cfg->facility;
+       file = fnstack_peek();
+       if (file && cfg->rm_filepath)
+               file = path_filename(file);
 
        lock_mutex();
 
        /* Can't use vsyslog(); it's not portable. */
-       vsnprintf(msg, MSG_LEN, format, args);
-       if (file_name != NULL) {
-               if (cfg->prefix != NULL)
-                       syslog(level | cfg->facility, "[%s] %s: %s",
-                           cfg->prefix, file_name, msg);
+       res = vsnprintf(msg, MSG_LEN, format, args);
+       if (res < 0)
+               goto end;
+       if (res >= MSG_LEN)
+               msg[MSG_LEN - 1] = '\0';
+
+       if (file != NULL) {
+               if (cfg->tag != NULL)
+                       syslog(level, "[%s] %s: %s", cfg->tag, file, msg);
                else
-                       syslog(level | cfg->facility, "%s: %s", file_name, msg);
+                       syslog(level, "%s: %s", file, msg);
        } else {
-               if (cfg->prefix != NULL)
-                       syslog(level | cfg->facility, "[%s] %s",
-                           cfg->prefix, msg);
+               if (cfg->tag != NULL)
+                       syslog(level, "[%s] %s", cfg->tag, msg);
                else
-                       syslog(level | cfg->facility, "%s", msg);
+                       syslog(level, "%s", msg);
        }
 
-       unlock_mutex();
+end:   unlock_mutex();
 }
 
 #define PR_SIMPLE(lvl, config)                                         \
index a8ac304dcc527c0e72d3e51389f01f9864a14a62..5c94dd4326bed8b0ab7975f2e01a32db87708816 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -4,7 +4,14 @@
 #include <stdbool.h>
 #include <stdio.h>
 
-#include "incidence/incidence.h"
+#include "incidence.h"
+
+#define PR_COLOR_DBG   "\x1B[36m"      /* Cyan */
+#define PR_COLOR_INF   "\x1B[37m"      /* White */
+#define PR_COLOR_WRN   "\x1B[33m"      /* Yellow */
+#define PR_COLOR_ERR   "\x1B[31m"      /* Red */
+#define PR_COLOR_CRT   "\x1B[35m"      /* Purple */
+#define PR_COLOR_RST   "\x1B[0m"
 
 /*
  * According to BSD style, __dead is supposed to be defined in sys/cdefs.h,
@@ -40,7 +47,7 @@
 #endif
 
 /*
- * Only call this group of functions when you know there's only one thread.
+ * Only call this group of functions while you know there's only one thread.
  *
  * log_setup() is an incomplete initialization meant to be called when the
  * program starts. Logging can be performed after log_setup(), but it will use
@@ -48,7 +55,7 @@
  * log_init() finishes initialization by loading the user's intended config.
  * log_teardown() reverts initialization.
  */
-int log_setup(bool);
+int log_setup(void);
 void log_start(void);
 void log_teardown(void);
 
index 7f69d0a7812d8de737d739f3b3faceb3bce7b329..0184d209c3e812b4a37ab4da5a4c22cbe46b8a5b 100644 (file)
@@ -1,17 +1,16 @@
 #include <errno.h>
 
+#include "cache.h"
 #include "config.h"
-#include "crypto/hash.h"
 #include "extension.h"
-#include "http/http.h"
-#include "incidence/incidence.h"
+#include "hash.h"
+#include "http.h"
 #include "log.h"
 #include "nid.h"
 #include "print_file.h"
-#include "rtr/db/vrps.h"
+#include "relax_ng.h"
 #include "rtr/rtr.h"
 #include "thread_var.h"
-#include "xml/relax_ng.h"
 
 static int
 fort_standalone(void)
@@ -77,7 +76,7 @@ fort_server(void)
        return error;
 }
 
-/**
+/*
  * Shells don't like it when we return values other than 0-255.
  * In fact, bash also has its own meanings for 126-255.
  * (See man 1 bash > EXIT STATUS)
@@ -117,7 +116,7 @@ main(int argc, char **argv)
 
        /* Initializations */
 
-       error = log_setup(false);
+       error = log_setup();
        if (error)
                goto just_quit;
 
@@ -149,6 +148,9 @@ main(int argc, char **argv)
        error = vrps_init();
        if (error)
                goto revert_relax_ng;
+       error = cache_setup();
+       if (error)
+               goto revert_vrps;
 
        /* Meat */
 
@@ -166,6 +168,8 @@ main(int argc, char **argv)
 
        /* End */
 
+       cache_teardown();
+revert_vrps:
        vrps_destroy();
 revert_relax_ng:
        relax_ng_cleanup();
index 0485c23b2c081335746010ecc7a0d67773e859fb..41829ade2f113947dcbf6996d605008063b2d86e 100644 (file)
@@ -2,7 +2,7 @@
 #define SRC_OBJECT_BGPSEC_H_
 
 #include "resource.h"
-#include "rpp.h"
+#include "types/rpp.h"
 
 int handle_bgpsec(X509 *, struct resources *, struct rpp *);
 
index cf7294c9014bc9fbc954fe78af2f2a3ffbf47b78..5ad8af7d44390057c40e5bbfdb0ae22db9e57904 100644 (file)
@@ -5,34 +5,30 @@
 #if OPENSSL_VERSION_MAJOR >= 3
 #include <openssl/core_names.h>
 #endif
-#include <openssl/evp.h>
 #include <openssl/obj_mac.h>
 #include <openssl/objects.h>
 #include <openssl/rsa.h>
-#include <openssl/x509v3.h>
 #include <syslog.h>
+#include <time.h>
 
 #include "algorithm.h"
-#include "alloc.h"
 #include "asn1/asn1c/IPAddrBlocks.h"
 #include "asn1/decode.h"
-#include "asn1/oid.h"
-#include "cache/local_cache.h"
-#include "cert_stack.h"
+#include "cache.h"
+#include "common.h"
 #include "config.h"
-#include "crypto/hash.h"
-#include "data_structure/array_list.h"
 #include "extension.h"
-#include "incidence/incidence.h"
+#include "libcrypto_util.h"
 #include "log.h"
 #include "nid.h"
-#include "object/bgpsec.h"
+#include "object/ghostbusters.h"
 #include "object/manifest.h"
-#include "object/name.h"
-#include "object/signed_object.h"
-#include "rrdp.h"
-#include "str_token.h"
+#include "object/roa.h"
 #include "thread_var.h"
+#include "types/name.h"
+#include "types/path.h"
+#include "types/str.h"
+#include "types/url.h"
 
 /*
  * The X509V3_EXT_METHOD that references NID_sinfo_access uses the AIA item.
  */
 typedef AUTHORITY_INFO_ACCESS SIGNATURE_INFO_ACCESS;
 
+/* Certificates that need to be postponed during a validation cycle. */
+SLIST_HEAD(cert_stack, rpki_certificate);
+
 struct ski_arguments {
        X509 *cert;
        OCTET_STRING_t *sid;
 };
 
-struct sia_uris {
-       struct map_list rpp;
-       struct cache_mapping *mft;
-};
-
 struct bgpsec_ski {
        X509 *cert;
        unsigned char **ski_data;
@@ -62,98 +56,45 @@ typedef int (access_method_exec)(struct sia_uris *);
 struct ad_metadata {
        char const *name;
        char const *ia_name;
-       enum map_type type;
-       char const *type_str;
+       char const *type;
        bool required;
 };
 
 static const struct ad_metadata CA_ISSUERS = {
        .name = "caIssuers",
        .ia_name = "AIA",
-       .type = MAP_AIA,
-       .type_str = "rsync",
+       .type = "rsync",
        .required = true,
 };
 
 static const struct ad_metadata SIGNED_OBJECT = {
        .name = "signedObject",
        .ia_name = "SIA",
-       .type = MAP_SO,
-       .type_str = "rsync",
+       .type = "rsync",
        .required = true,
 };
 
 static const struct ad_metadata CA_REPOSITORY = {
        .name = "caRepository",
        .ia_name = "SIA",
-       .type = MAP_RPP,
-       .type_str = "rsync",
+       .type = "rsync",
        .required = false,
 };
 
 static const struct ad_metadata RPKI_NOTIFY = {
        .name = "rpkiNotify",
        .ia_name = "SIA",
-       .type = MAP_NOTIF,
-       .type_str = "HTTPS",
+       .type = "HTTPS",
        .required = false,
 };
 
 static const struct ad_metadata RPKI_MANIFEST = {
        .name = "rpkiManifest",
        .ia_name = "SIA",
-       .type = MAP_MFT,
-       .type_str = "rsync",
+       .type = "rsync",
        .required = true,
 };
 
-static void
-sia_uris_init(struct sia_uris *uris)
-{
-       maps_init(&uris->rpp);
-       uris->mft = NULL;
-}
-
-static void
-sia_uris_cleanup(struct sia_uris *uris)
-{
-       maps_cleanup(&uris->rpp);
-       map_refput(uris->mft);
-}
-
-static void
-debug_serial_number(BIGNUM *number)
-{
-       char *number_str;
-
-       number_str = BN_bn2dec(number);
-       if (number_str == NULL) {
-               val_crypto_err("Could not convert BN to string");
-               return;
-       }
-
-       pr_val_debug("serial Number: %s", number_str);
-       free(number_str);
-}
-
-static int
-validate_serial_number(X509 *cert)
-{
-       struct validation *state;
-       BIGNUM *number;
-
-       number = ASN1_INTEGER_to_BN(X509_get0_serialNumber(cert), NULL);
-       if (number == NULL)
-               return val_crypto_err("Could not parse certificate serial number");
-
-       if (log_val_enabled(LOG_DEBUG))
-               debug_serial_number(number);
-
-       state = state_retrieve();
-       x509stack_store_serial(validation_certstack(state), number);
-       return 0;
-}
-
 static int
 validate_signature_algorithm(X509 *cert)
 {
@@ -165,25 +106,23 @@ validate_signature_algorithm(X509 *cert)
 }
 
 static int
-validate_issuer(X509 *cert, bool is_ta)
+validate_issuer(struct rpki_certificate *cert)
 {
        X509_NAME *issuer;
        struct rfc5280_name *name;
        int error;
 
-       issuer = X509_get_issuer_name(cert);
-
-       if (!is_ta)
-               return validate_issuer_name("Certificate", issuer);
+       issuer = X509_get_issuer_name(cert->x509);
 
-       /* TODO wait. Shouldn't we check subject == issuer? */
+       if (cert->type != CERTYPE_TA)
+               return validate_issuer_name(issuer, cert->parent->x509);
 
        error = x509_name_decode(issuer, "issuer", &name);
        if (error)
                return error;
        pr_val_debug("Issuer: %s", x509_name_commonName(name));
-
        x509_name_put(name);
+
        return 0;
 }
 
@@ -192,8 +131,7 @@ validate_issuer(X509 *cert, bool is_ta)
  * @diff_pk_cb when the public key is different; return 0 if both are equal.
  */
 static int
-spki_cmp(X509_PUBKEY *tal_spki, X509_PUBKEY *cert_spki,
-    int (*diff_alg_cb)(void), int (*diff_pk_cb)(void))
+spki_cmp(X509_PUBKEY *tal_spki, X509_PUBKEY *cert_spki)
 {
        ASN1_OBJECT *tal_alg;
        ASN1_OBJECT *cert_alg;
@@ -213,13 +151,18 @@ spki_cmp(X509_PUBKEY *tal_spki, X509_PUBKEY *cert_spki,
                return val_crypto_err("X509_PUBKEY_get0_param() 2 returned %d", ok);
 
        if (OBJ_cmp(tal_alg, cert_alg) != 0)
-               return diff_alg_cb();
+               goto root_different_alg_err;
        if (tal_spk_len != cert_spk_len)
-               return diff_pk_cb();
+               goto root_different_pk_err;
        if (memcmp(tal_spk, cert_spk, cert_spk_len) != 0)
-               return diff_pk_cb();
+               goto root_different_pk_err;
 
        return 0;
+
+root_different_alg_err:
+       return pr_val_err("TAL's public key algorithm is different than the root certificate's public key algorithm.");
+root_different_pk_err:
+       return pr_val_err("TAL's public key is different than the root certificate's public key.");
 }
 
 /*
@@ -245,7 +188,7 @@ validate_subject(X509 *cert)
 static X509_PUBKEY *
 decode_spki(struct tal *tal)
 {
-       X509_PUBKEY *spki = NULL;
+       X509_PUBKEY *spki;
        unsigned char const *origin, *cursor;
        size_t len;
 
@@ -271,28 +214,14 @@ fail:     fnstack_pop();
        return NULL;
 }
 
-static int
-root_different_alg_err(void)
-{
-       return pr_val_err("TAL's public key algorithm is different than the root certificate's public key algorithm.");
-}
-
-static int
-root_different_pk_err(void)
-{
-       return pr_val_err("TAL's public key is different than the root certificate's public key.");
-}
-
 static int
 validate_spki(X509_PUBKEY *cert_spki)
 {
-       struct validation *state;
        struct tal *tal;
        X509_PUBKEY *tal_spki;
+       int error;
 
-       state = state_retrieve();
-
-       tal = validation_tal(state);
+       tal = validation_tal(state_retrieve());
        if (tal == NULL)
                pr_crit("Validation state has no TAL.");
 
@@ -318,16 +247,10 @@ validate_spki(X509_PUBKEY *cert_spki)
        if (tal_spki == NULL)
                return -EINVAL;
 
-       if (spki_cmp(tal_spki, cert_spki, root_different_alg_err,
-           root_different_pk_err) != 0) {
-               X509_PUBKEY_free(tal_spki);
-               validation_pubkey_invalid(state);
-               return -EINVAL;
-       }
+       error = spki_cmp(tal_spki, cert_spki);
 
        X509_PUBKEY_free(tal_spki);
-       validation_pubkey_valid(state);
-       return 0;
+       return error;
 }
 
 /*
@@ -421,13 +344,18 @@ validate_subject_public_key(X509_PUBKEY *pubkey)
 
 #define MODULUS 2048
 #define EXPONENT "65537"
+       EVP_PKEY *pkey;
        const RSA *rsa;
        const BIGNUM *exp;
        char *exp_str;
        int modulus;
        int error;
 
-       rsa = EVP_PKEY_get0_RSA(X509_PUBKEY_get0(pubkey));
+       pkey = X509_PUBKEY_get0(pubkey);
+       if (pkey == NULL)
+               return val_crypto_err("The certificate's Subject Public Key is missing or malformed.");
+
+       rsa = EVP_PKEY_get0_RSA(pkey);
        if (rsa == NULL)
                return val_crypto_err("EVP_PKEY_get0_RSA() returned NULL");
 
@@ -467,6 +395,7 @@ static int
 validate_public_key(X509 *cert, enum cert_type type)
 {
        X509_PUBKEY *pubkey;
+       EVP_PKEY *evppkey;
        X509_ALGOR *pa;
        int ok;
        int error;
@@ -507,13 +436,17 @@ validate_public_key(X509 *cert, enum cert_type type)
                error = validate_spki(pubkey);
                if (error)
                        return error;
+               if ((evppkey = X509_get0_pubkey(cert)) == NULL)
+                       return val_crypto_err("X509_get0_pubkey() returned NULL");
+               if (X509_verify(cert, evppkey) != 1)
+                       return -EINVAL;
        }
 
        return 0;
 }
 
 int
-certificate_validate_rfc6487(X509 *cert, enum cert_type type)
+certificate_validate_rfc6487(struct rpki_certificate *cert)
 {
        int error;
 
@@ -526,21 +459,19 @@ certificate_validate_rfc6487(X509 *cert, enum cert_type type)
         */
 
        /* rfc6487#section-4.1 */
-       if (X509_get_version(cert) != 2)
+       if (X509_get_version(cert->x509) != 2)
                return pr_val_err("Certificate version is not v3.");
 
        /* rfc6487#section-4.2 */
-       error = validate_serial_number(cert);
-       if (error)
-               return error;
+       /* <Redacted> */
 
        /* rfc6487#section-4.3 */
-       error = validate_signature_algorithm(cert);
+       error = validate_signature_algorithm(cert->x509);
        if (error)
                return error;
 
        /* rfc6487#section-4.4 */
-       error = validate_issuer(cert, type == CERTYPE_TA);
+       error = validate_issuer(cert);
        if (error)
                return error;
 
@@ -550,7 +481,7 @@ certificate_validate_rfc6487(X509 *cert, enum cert_type type)
         * "An issuer SHOULD use a different subject name if the subject's
         * key pair has changed" (it's a SHOULD, so [for now] avoid validation)
         */
-       error = validate_subject(cert);
+       error = validate_subject(cert->x509);
        if (error)
                return error;
 
@@ -559,7 +490,7 @@ certificate_validate_rfc6487(X509 *cert, enum cert_type type)
 
        /* rfc6487#section-4.7 */
        /* Fragment of rfc8630#section-2.3 */
-       error = validate_public_key(certtype);
+       error = validate_public_key(cert->x509, cert->type);
        if (error)
                return error;
 
@@ -572,50 +503,46 @@ struct progress {
        size_t remaining;
 };
 
-/**
- * Skip the "T" part of a TLV.
- */
-static void
+/* Skip the "T" part of a TLV. */
+static int
 skip_t(ANY_t *content, struct progress *p, unsigned int tag)
 {
-       /*
-        * BTW: I made these errors critical because the signedData is supposed
-        * to be validated by this point.
-        */
+       /* These errors happen when the object is not DER-encoded */
 
        if (content->buf[p->offset] != tag)
-               pr_crit("Expected tag 0x%x, got 0x%x", tag,
-                   content->buf[p->offset]);
-
+               return pr_val_err("Expected tag 0x%x, got 0x%x.",
+                   tag, content->buf[p->offset]);
        if (p->remaining == 0)
-               pr_crit("Buffer seems to be truncated");
+               return pr_val_err("Buffer seems truncated.");
+
        p->offset++;
        p->remaining--;
+       return 0;
 }
 
-/**
- * Skip the "TL" part of a TLV.
- */
-static void
+/* Skip the "TL" part of a TLV. */
+static int
 skip_tl(ANY_t *content, struct progress *p, unsigned int tag)
 {
        ssize_t len_len; /* Length of the length field */
        ber_tlv_len_t value_len; /* Length of the value */
 
-       skip_t(content, p, tag);
+       if (skip_t(content, p, tag) != 0)
+               return -EINVAL;
 
        len_len = ber_fetch_length(true, &content->buf[p->offset], p->remaining,
            &value_len);
        if (len_len == -1)
-               pr_crit("Could not decipher length (Cause is unknown)");
+               return pr_val_err("Could not decipher length (Unknown cause).");
        if (len_len == 0)
-               pr_crit("Buffer seems to be truncated");
+               return pr_val_err("Buffer seems truncated.");
 
        p->offset += len_len;
        p->remaining -= len_len;
+       return 0;
 }
 
-static void
+static int
 skip_tlv(ANY_t *content, struct progress *p, unsigned int tag)
 {
        int is_constructed;
@@ -623,33 +550,33 @@ skip_tlv(ANY_t *content, struct progress *p, unsigned int tag)
 
        is_constructed = BER_TLV_CONSTRUCTED(&content->buf[p->offset]);
 
-       skip_t(content, p, tag);
+       if (skip_t(content, p, tag) != 0)
+               return -EINVAL;
 
        skip = ber_skip_length(NULL, is_constructed, &content->buf[p->offset],
            p->remaining);
        if (skip == -1)
-               pr_crit("Could not skip length (Cause is unknown)");
+               return pr_val_err("Could not skip length (Unknown cause).");
        if (skip == 0)
-               pr_crit("Buffer seems to be truncated");
+               return pr_val_err("Buffer seems truncated.");
 
        p->offset += skip;
        p->remaining -= skip;
+       return 0;
 }
 
-/**
- * A structure that points to the LV part of a signedAttrs TLV.
- */
+/* A structure that points to the LV part of a signedAttrs TLV. */
 struct encoded_signedAttrs {
        const uint8_t *buffer;
        ber_tlv_len_t size;
 };
 
-static void
+static int
 find_signedAttrs(ANY_t *signedData, struct encoded_signedAttrs *result)
 {
-#define INTEGER_TAG            0x02
-#define SEQUENCE_TAG           0x30
-#define SET_TAG                        0x31
+       static const unsigned int INTEGER_TAG = 0x02;
+       static const unsigned int SEQUENCE_TAG = 0x30;
+       static const unsigned int SET_TAG = 0x31;
 
        struct progress p;
        ssize_t len_len;
@@ -660,43 +587,55 @@ find_signedAttrs(ANY_t *signedData, struct encoded_signedAttrs *result)
        p.remaining = signedData->size;
 
        /* SignedData: SEQUENCE */
-       skip_tl(signedData, &p, SEQUENCE_TAG);
+       if (skip_tl(signedData, &p, SEQUENCE_TAG) != 0)
+               return -EINVAL;
 
        /* SignedData.version: CMSVersion -> INTEGER */
-       skip_tlv(signedData, &p, INTEGER_TAG);
+       if (skip_tlv(signedData, &p, INTEGER_TAG) != 0)
+               return -EINVAL;
        /* SignedData.digestAlgorithms: DigestAlgorithmIdentifiers -> SET */
-       skip_tlv(signedData, &p, SET_TAG);
+       if (skip_tlv(signedData, &p, SET_TAG) != 0)
+               return -EINVAL;
        /* SignedData.encapContentInfo: EncapsulatedContentInfo -> SEQUENCE */
-       skip_tlv(signedData, &p, SEQUENCE_TAG);
+       if (skip_tlv(signedData, &p, SEQUENCE_TAG) != 0)
+               return -EINVAL;
        /* SignedData.certificates: CertificateSet -> SET */
-       skip_tlv(signedData, &p, 0xA0);
+       if (skip_tlv(signedData, &p, 0xA0) != 0)
+               return -EINVAL;
        /* SignedData.signerInfos: SignerInfos -> SET OF SEQUENCE */
-       skip_tl(signedData, &p, SET_TAG);
-       skip_tl(signedData, &p, SEQUENCE_TAG);
+       if (skip_tl(signedData, &p, SET_TAG) != 0)
+               return -EINVAL;
+       if (skip_tl(signedData, &p, SEQUENCE_TAG) != 0)
+               return -EINVAL;
 
        /* SignedData.signerInfos.version: CMSVersion -> INTEGER */
-       skip_tlv(signedData, &p, INTEGER_TAG);
+       if (skip_tlv(signedData, &p, INTEGER_TAG) != 0)
+               return -EINVAL;
        /*
         * SignedData.signerInfos.sid: SignerIdentifier -> CHOICE -> always
         * subjectKeyIdentifier, which is a [0].
         */
-       skip_tlv(signedData, &p, 0x80);
+       if (skip_tlv(signedData, &p, 0x80) != 0)
+               return -EINVAL;
        /* SignedData.signerInfos.digestAlgorithm: DigestAlgorithmIdentifier
         * -> AlgorithmIdentifier -> SEQUENCE */
-       skip_tlv(signedData, &p, SEQUENCE_TAG);
+       if (skip_tlv(signedData, &p, SEQUENCE_TAG) != 0)
+               return -EINVAL;
 
        /* SignedData.signerInfos.signedAttrs: SignedAttributes -> SET */
        /* We will need to replace the tag 0xA0 with 0x31, so skip it as well */
-       skip_t(signedData, &p, 0xA0);
+       if (skip_t(signedData, &p, 0xA0) != 0)
+               return -EINVAL;
 
        result->buffer = &signedData->buf[p.offset];
        len_len = ber_fetch_length(true, result->buffer,
            p.remaining, &result->size);
        if (len_len == -1)
-               pr_crit("Could not decipher length (Cause is unknown)");
+               return pr_val_err("Could not decipher length (Unknown cause.)");
        if (len_len == 0)
-               pr_crit("Buffer seems to be truncated");
+               return pr_val_err("Buffer seems truncated.");
        result->size += len_len;
+       return 0;
 }
 
 /*
@@ -778,7 +717,9 @@ certificate_validate_signature(X509 *cert, ANY_t *signedData,
         * Second option it is.
         */
 
-       find_signedAttrs(signedData, &signedAttrs);
+       error = find_signedAttrs(signedData, &signedAttrs);
+       if (error)
+               goto end;
 
        error = EVP_DigestVerifyUpdate(ctx, &EXPLICIT_SET_OF_TAG,
            sizeof(EXPLICIT_SET_OF_TAG));
@@ -806,180 +747,204 @@ end:
        return error;
 }
 
-static int
-certificate_load(struct cache_mapping *map, X509 **result)
+static X509 *
+certificate_load(char const *path)
 {
        X509 *cert = NULL;
        BIO *bio;
-       int error;
-
-       *result = NULL;
 
        bio = BIO_new(BIO_s_file());
-       if (bio == NULL)
-               return val_crypto_err("BIO_new(BIO_s_file()) returned NULL");
-       if (BIO_read_filename(bio, map_get_path(map)) <= 0) {
-               error = val_crypto_err("Error reading certificate");
+       if (bio == NULL) {
+               val_crypto_err("BIO_new(BIO_s_file()) returned NULL");
+               return NULL;
+       }
+       if (BIO_read_filename(bio, path) <= 0) {
+               val_crypto_err("Error reading certificate");
                goto end;
        }
 
        cert = d2i_X509_bio(bio, NULL);
        if (cert == NULL) {
-               error = val_crypto_err("Error parsing certificate");
+               val_crypto_err("Error parsing certificate");
                goto end;
        }
 
-       *result = cert;
-       error = 0;
-end:
-       BIO_free(bio);
-       return error;
+end:   BIO_free(bio);
+       return cert;
 }
 
-/*
- * Allocates a clone of @original_crl and pushes it to @crls.
- *
- * Don't forget to pop from @crls and release the popped CRL.
- */
-static int
-update_crl_time(STACK_OF(X509_CRL) *crls, X509_CRL *original_crl)
+static void
+certificate_stack_push(struct cert_stack *stack, struct cache_mapping *map,
+    struct rpki_certificate *parent)
 {
-       ASN1_TIME *tm;
-       X509_CRL *clone;
-       time_t t;
-       int error;
+       struct rpki_certificate *cert;
 
-       error = get_current_time(&t);
-       if (error)
-               return error;
+       cert = pzalloc(sizeof(*cert));
+       map_copy(&cert->map, map);
+       cert->parent = parent;
+       cert->refcount = 1;
+       SLIST_INSERT_HEAD(stack, cert, lh);
 
-       /*
-        * Yes, this is an awful hack. The other options were:
-        * - Use X509_V_FLAG_NO_CHECK_TIME parameter, but this avoids also the
-        *   time check for the certificate.
-        * - Avoid whole CRL check, but since we don't implement the
-        *   certificate chain validation, we can't assure that the CRL has
-        *   only the nextUpdate field wrong (maybe there are other invalid
-        *   things).
-        */
-       tm = ASN1_TIME_adj(NULL, t, 0, 60);
-       if (tm == NULL)
-               return val_crypto_err("ASN1_TIME_adj() returned NULL.");
-
-       clone = X509_CRL_dup(original_crl);
-       if (clone == NULL) {
-               ASN1_STRING_free(tm);
-               return val_crypto_err("X509_CRL_dup() returned NULL.");
+       parent->refcount++;
+}
+
+void
+rpki_certificate_init_ee(struct rpki_certificate *ee,
+    struct rpki_certificate *parent, bool force_inherit)
+{
+       memset(ee, 0, sizeof(*ee));
+       ee->type = CERTYPE_EE;
+       ee->policy = RPKI_POLICY_RFC6484;
+       ee->resources = resources_create(RPKI_POLICY_RFC6484, force_inherit);
+       ee->parent = parent;
+       ee->refcount = 1;
+
+       parent->refcount++;
+}
+
+void
+rpki_certificate_cleanup(struct rpki_certificate *cert)
+{
+       map_cleanup(&cert->map);
+       if (cert->x509 != NULL)
+               X509_free(cert->x509);
+       resources_destroy(cert->resources);
+       sias_cleanup(&cert->sias);
+       // XXX Recursive. Try refcounting the resources.
+       if (cert->parent)
+               rpki_certificate_free(cert->parent);
+       rpp_cleanup(&cert->rpp);
+}
+
+void
+rpki_certificate_free(struct rpki_certificate *cert)
+{
+       cert->refcount--;
+       if (cert->refcount == 0) {
+               rpki_certificate_cleanup(cert);
+               free(cert);
        }
+}
 
-       X509_CRL_set1_nextUpdate(clone, tm);
-       ASN1_STRING_free(tm);
+static STACK_OF(X509) *
+build_trusted_stack(struct rpki_certificate *cert)
+{
+       STACK_OF(X509) *stack;
+       int ret;
 
-       error = sk_X509_CRL_push(crls, clone);
-       if (error <= 0) {
-               X509_CRL_free(clone);
-               return val_crypto_err("Error calling sk_X509_CRL_push()");
+       stack = sk_X509_new_null();
+       if (!stack) {
+               val_crypto_err("sk_X509_new_null() returned NULL.");
+               return NULL;
        }
 
-       return 0;
+       for (cert = cert->parent; cert != NULL; cert = cert->parent) {
+               ret = sk_X509_push(stack, cert->x509);
+               if (ret <= 0) {
+                       val_crypto_err("sk_X509_push returned %d.", ret);
+                       sk_X509_pop_free(stack, X509_free);
+                       return NULL;
+               }
+       }
+
+       return stack;
 }
 
-/*
- * Retry certificate validation without CRL time validation.
- */
-static int
-verify_cert_crl_stale(struct validation *state, X509 *cert,
-    STACK_OF(X509_CRL) *crls)
+static STACK_OF(X509_CRL) *
+build_crl_stack(struct rpki_certificate *cert)
 {
-       X509_STORE_CTX *ctx;
-       X509_CRL *original_crl, *clone;
-       int error;
+       STACK_OF(X509_CRL) *stack;
        int ok;
 
-       ctx = X509_STORE_CTX_new();
-       if (ctx == NULL) {
-               val_crypto_err("X509_STORE_CTX_new() returned NULL");
-               return -EINVAL;
+       stack = sk_X509_CRL_new_null();
+       if (!stack) {
+               val_crypto_err("sk_X509_CRL_new_null() returned NULL.");
+               return NULL;
        }
-
-       /* Returns 0 or 1 , all callers test ! only. */
-       ok = X509_STORE_CTX_init(ctx, validation_store(state), cert, NULL);
-       if (!ok) {
-               error = val_crypto_err("X509_STORE_CTX_init() returned %d", ok);
-               goto release_ctx;
+       ok = sk_X509_CRL_push(stack, cert->parent->rpp.crl.obj);
+       if (ok != 1) {
+               val_crypto_err("sk_X509_CRL_push() returned %d.", ok);
+               return NULL;
        }
 
-       original_crl = sk_X509_CRL_pop(crls);
-       error = update_crl_time(crls, original_crl);
-       if (error)
-               goto push_original;
+       return stack;
+}
 
-       X509_STORE_CTX_trusted_stack(ctx,
-           certstack_get_x509s(validation_certstack(state)));
-       X509_STORE_CTX_set0_crls(ctx, crls);
+static void
+pr_debug_x509_dates(X509 *x509)
+{
+       char *nb, *na;
 
-       ok = X509_verify_cert(ctx);
-       if (ok > 0) {
-               error = 0; /* Happy path */
-               goto pop_clone;
-       }
+       nb = asn1time2str(X509_get0_notBefore(x509));
+       na = asn1time2str(X509_get0_notAfter(x509));
 
-       error = X509_STORE_CTX_get_error(ctx);
-       if (error)
-               error = pr_val_err("Certificate validation failed: %s",
-                   X509_verify_cert_error_string(error));
-       else
-               error = val_crypto_err("Certificate validation failed: %d", ok);
-
-pop_clone:
-       clone = sk_X509_CRL_pop(crls);
-       if (clone == NULL)
-               error = pr_val_err("Error calling sk_X509_CRL_pop()");
-       else
-               X509_CRL_free(clone);
-push_original:
-       /* Try to return to the "regular" CRL chain */
-       ok = sk_X509_CRL_push(crls, original_crl);
-       if (ok <= 0)
-               error = val_crypto_err("Could not return CRL to a CRL stack");
-release_ctx:
-       X509_STORE_CTX_free(ctx);
-       return error;
+       pr_val_debug("Valid range: [%s, %s]", nb, na);
 
+       free(nb);
+       free(na);
+}
+
+static void
+complain_crl_stale(X509_CRL *crl)
+{
+       char *lu;
+       char *nu;
+
+       lu = asn1time2str(X509_CRL_get0_lastUpdate(crl));
+       nu = asn1time2str(X509_CRL_get0_nextUpdate(crl));
+
+       pr_val_err("CRL is stale/expired. (lastUpdate:%s, nextUpdate:%s)",
+           lu, nu);
+
+       free(lu);
+       free(nu);
 }
 
 int
-certificate_validate_chain(X509 *cert, STACK_OF(X509_CRL) *crls)
+certificate_validate_chain(struct rpki_certificate *cert)
 {
        /* Reference: openbsd/src/usr.bin/openssl/verify.c */
 
-       struct validation *state;
        X509_STORE_CTX *ctx;
+       STACK_OF(X509) *trusted;
+       STACK_OF(X509_CRL) *crls;
        int ok;
        int error;
 
-       if (crls == NULL)
-               return 0; /* Certificate is TA; no chain validation needed. */
-
-       state = state_retrieve();
+       if (cert->type == CERTYPE_TA)
+               return 0; /* No chain to validate. */
 
        ctx = X509_STORE_CTX_new();
        if (ctx == NULL) {
                val_crypto_err("X509_STORE_CTX_new() returned NULL");
-               return -EINVAL;
+               return EINVAL;
        }
 
        /* Returns 0 or 1 , all callers test ! only. */
-       ok = X509_STORE_CTX_init(ctx, validation_store(state), cert, NULL);
+       ok = X509_STORE_CTX_init(ctx, validation_store(state_retrieve()),
+           cert->x509, NULL);
        if (!ok) {
-               val_crypto_err("X509_STORE_CTX_init() returned %d", ok);
-               goto abort;
+               error = val_crypto_err("X509_STORE_CTX_init() returned %d", ok);
+               goto end1;
+       }
+
+       trusted = build_trusted_stack(cert);
+       if (!trusted) {
+               error = EINVAL;
+               goto end1;
        }
+       X509_STORE_CTX_trusted_stack(ctx, trusted);
 
-       X509_STORE_CTX_trusted_stack(ctx,
-           certstack_get_x509s(validation_certstack(state)));
+       crls = build_crl_stack(cert);
+       if (!crls) {
+               error = EINVAL;
+               goto end2;
+       }
        X509_STORE_CTX_set0_crls(ctx, crls);
 
+       if (log_val_enabled(LOG_DEBUG))
+               pr_debug_x509_dates(cert->x509);
+
        /*
         * HERE'S THE MEAT OF LIBCRYPTO'S VALIDATION.
         *
@@ -996,41 +961,33 @@ certificate_validate_chain(X509 *cert, STACK_OF(X509_CRL) *crls)
                 * error code is stored in the context.
                 */
                error = X509_STORE_CTX_get_error(ctx);
-               if (error) {
-                       if (error != X509_V_ERR_CRL_HAS_EXPIRED) {
-                               pr_val_err("Certificate validation failed: %s",
-                                   X509_verify_cert_error_string(error));
-                               goto abort;
-                       }
-                       if (incidence(INID_CRL_STALE, "CRL is stale/expired"))
-                               goto abort;
-
-                       X509_STORE_CTX_free(ctx);
-                       if (incidence_get_action(INID_CRL_STALE) == INAC_WARN)
-                               pr_val_info("Re-validating avoiding CRL time check");
-                       return verify_cert_crl_stale(state, cert, crls);
-               } else {
+               if (error == X509_V_ERR_CRL_HAS_EXPIRED)
+                       complain_crl_stale(cert->parent->rpp.crl.obj);
+               else if (error)
+                       pr_val_err("Certificate validation failed: %s",
+                           X509_verify_cert_error_string(error));
+               else {
                        /*
                         * ...But don't trust X509_STORE_CTX_get_error() either.
                         * That said, there's not much to do about !error,
                         * so hope for the best.
                         */
                        val_crypto_err("Certificate validation failed: %d", ok);
+                       error = EINVAL;
                }
-
-               goto abort;
+               goto end3;
        }
 
-       X509_STORE_CTX_free(ctx);
-       return 0;
+       error = 0;
 
-abort:
-       X509_STORE_CTX_free(ctx);
-       return -EINVAL;
+end3:  sk_X509_CRL_free(crls);
+end2:  sk_X509_free(trusted);
+end1:  X509_STORE_CTX_free(ctx);
+       return error;
 }
 
 static int
-handle_ip_extension(X509_EXTENSION *ext, struct resources *resources)
+handle_ip_extension(struct rpki_certificate *cert, X509_EXTENSION *ext)
 {
        ASN1_OCTET_STRING *string;
        struct IPAddrBlocks *blocks;
@@ -1071,7 +1028,9 @@ handle_ip_extension(X509_EXTENSION *ext, struct resources *resources)
        }
 
        for (i = 0; i < blocks->list.count && !error; i++)
-               error = resources_add_ip(resources, blocks->list.array[i]);
+               error = resources_add_ip(cert->resources,
+                   cert->parent ? cert->parent->resources : NULL,
+                   blocks->list.array[i]);
 
 end:
        ASN_STRUCT_FREE(asn_DEF_IPAddrBlocks, blocks);
@@ -1079,8 +1038,7 @@ end:
 }
 
 static int
-handle_asn_extension(X509_EXTENSION *ext, struct resources *resources,
-    bool allow_inherit)
+handle_asn_extension(struct rpki_certificate *cert, X509_EXTENSION *ext)
 {
        ASN1_OCTET_STRING *string;
        struct ASIdentifiers *ids;
@@ -1092,16 +1050,18 @@ handle_asn_extension(X509_EXTENSION *ext, struct resources *resources,
        if (error)
                return error;
 
-       error = resources_add_asn(resources, ids, allow_inherit);
+       error = resources_add_asn(cert->resources,
+           cert->parent ? cert->parent->resources : NULL,
+           ids, cert->type != CERTYPE_BGPSEC);
 
        ASN_STRUCT_FREE(asn_DEF_ASIdentifiers, ids);
        return error;
 }
 
 static int
-__certificate_get_resources(X509 *cert, struct resources *resources,
+__certificate_get_resources(struct rpki_certificate *cert,
     int addr_nid, int asn_nid, int bad_addr_nid, int bad_asn_nid,
-    char const *policy_rfc, char const *bad_ext_rfc, bool allow_asn_inherit)
+    char const *policy_rfc, char const *bad_ext_rfc)
 {
        X509_EXTENSION *ext;
        int nid;
@@ -1113,8 +1073,8 @@ __certificate_get_resources(X509 *cert, struct resources *resources,
        /* Reference: X509_get_ext_d2i */
        /* rfc6487#section-2 */
 
-       for (i = 0; i < X509_get_ext_count(cert); i++) {
-               ext = X509_get_ext(cert, i);
+       for (i = 0; i < X509_get_ext_count(cert->x509); i++) {
+               ext = X509_get_ext(cert->x509, i);
                nid = OBJ_obj2nid(X509_EXTENSION_get_object(ext));
 
                if (nid == addr_nid) {
@@ -1123,11 +1083,9 @@ __certificate_get_resources(X509 *cert, struct resources *resources,
                        if (!X509_EXTENSION_get_critical(ext))
                                return pr_val_err("The IP extension is not marked as critical.");
 
-                       pr_val_debug("IP {");
-                       error = handle_ip_extension(ext, resources);
-                       pr_val_debug("}");
                        ip_ext_found = true;
 
+                       error = handle_ip_extension(cert, ext);
                        if (error)
                                return error;
 
@@ -1137,12 +1095,9 @@ __certificate_get_resources(X509 *cert, struct resources *resources,
                        if (!X509_EXTENSION_get_critical(ext))
                                return pr_val_err("The AS extension is not marked as critical.");
 
-                       pr_val_debug("ASN {");
-                       error = handle_asn_extension(ext, resources,
-                           allow_asn_inherit);
-                       pr_val_debug("}");
                        asn_ext_found = true;
 
+                       error = handle_asn_extension(cert, ext);
                        if (error)
                                return error;
 
@@ -1161,30 +1116,24 @@ __certificate_get_resources(X509 *cert, struct resources *resources,
        return 0;
 }
 
-/**
- * Copies the resources from @cert to @resources.
- */
+/* Copies the resources from @cert to @resources. */
 int
-certificate_get_resources(X509 *cert, struct resources *resources,
-    enum cert_type type)
+certificate_get_resources(struct rpki_certificate *cert)
 {
-       enum rpki_policy policy;
-
-       policy = resources_get_policy(resources);
-       switch (policy) {
+       switch (cert->policy) {
        case RPKI_POLICY_RFC6484:
-               return __certificate_get_resources(cert, resources,
+               return __certificate_get_resources(cert,
                    NID_sbgp_ipAddrBlock, NID_sbgp_autonomousSysNum,
                    nid_ipAddrBlocksv2(), nid_autonomousSysIdsv2(),
-                   "6484", "8360", type != CERTYPE_BGPSEC);
+                   "6484", "8360");
        case RPKI_POLICY_RFC8360:
-               return __certificate_get_resources(cert, resources,
+               return __certificate_get_resources(cert,
                    nid_ipAddrBlocksv2(), nid_autonomousSysIdsv2(),
                    NID_sbgp_ipAddrBlock, NID_sbgp_autonomousSysNum,
-                   "8360", "6484", type != CERTYPE_BGPSEC);
+                   "8360", "6484");
        }
 
-       pr_crit("Unknown policy: %u", policy);
+       pr_crit("Unknown policy: %u", cert->policy);
 }
 
 static bool
@@ -1198,41 +1147,57 @@ is_rsync(ASN1_IA5STRING *uri)
            : false;
 }
 
-static int
-handle_rpkiManifest(struct cache_mapping *map, void *arg)
+static void
+handle_rpkiManifest(char *uri, void *arg)
 {
        struct sia_uris *uris = arg;
-       uris->mft = map_refget(map);
-       return 0;
+
+       pr_val_debug("rpkiManifest: %s", uri);
+
+       if (uris->rpkiManifest != NULL) {
+               pr_val_warn("Ignoring additional rpkiManifest: %s", uri);
+               free(uri);
+       } else {
+               uris->rpkiManifest = uri;
+       }
 }
 
-static int
-handle_caRepository(struct cache_mapping *map, void *arg)
+static void
+handle_caRepository(char *uri, void *arg)
 {
        struct sia_uris *uris = arg;
-       pr_val_debug("caRepository: %s", map_val_get_printable(map));
-       maps_add(&uris->rpp, map);
-       map_refget(map);
-       return 0;
+
+       pr_val_debug("caRepository: %s", uri);
+
+       if (uris->caRepository != NULL) {
+               pr_val_warn("Ignoring additional caRepository: %s", uri);
+               free(uri);
+       } else {
+               uris->caRepository = uri;
+       }
 }
 
-static int
-handle_rpkiNotify(struct cache_mapping *map, void *arg)
+static void
+handle_rpkiNotify(char *uri, void *arg)
 {
        struct sia_uris *uris = arg;
-       pr_val_debug("rpkiNotify: %s", map_val_get_printable(map));
-       maps_add(&uris->rpp, map);
-       map_refget(map);
-       return 0;
+
+       pr_val_debug("rpkiNotify: %s", uri);
+
+       if (uris->rpkiNotify != NULL) {
+               pr_val_warn("Ignoring additional rpkiNotify: %s", uri);
+               free(uri);
+       } else {
+               uris->rpkiNotify = uri;
+       }
 }
 
-static int
-handle_signedObject(struct cache_mapping *map, void *arg)
+static void
+handle_signedObject(char *uri, void *arg)
 {
-       struct certificate_refs *refs = arg;
-       pr_val_debug("signedObject: %s", map_val_get_printable(map));
-       refs->signedObject = map_refget(map);
-       return 0;
+       struct sia_uris *sias = arg;
+       pr_val_debug("signedObject: %s", uri);
+       sias->signedObject = uri;
 }
 
 static int
@@ -1301,7 +1266,8 @@ handle_aki_ta(void *ext, void *arg)
        }
 
        error = (ASN1_OCTET_STRING_cmp(aki->keyid, ski) != 0)
-             ? pr_val_err("The '%s' does not equal the '%s'.", ext_aki()->name, ext_ski()->name)
+             ? pr_val_err("The '%s' does not equal the '%s'.",
+                          ext_aki()->name, ext_ski()->name)
              : 0;
 
        ASN1_BIT_STRING_free(ski);
@@ -1319,21 +1285,21 @@ handle_ku(ASN1_BIT_STRING *ku, unsigned char byte1)
 
        unsigned char data[2];
 
-       if (ku->length == 0) {
-               return pr_val_err("%s bit string has no enabled bits.",
-                   ext_ku()->name);
+       if (ku->length != 2 && ku->length != 1) {
+               return pr_val_err("Bogus %s length: %d",
+                   ext_ku()->name, ku->length);
        }
 
        memset(data, 0, sizeof(data));
        memcpy(data, ku->data, ku->length);
 
-       if (ku->data[0] != byte1) {
+       if (data[0] != byte1 || data[1] != 0) {
                return pr_val_err("Illegal key usage flag string: %d%d%d%d%d%d%d%d%d",
-                   !!(ku->data[0] & 0x80u), !!(ku->data[0] & 0x40u),
-                   !!(ku->data[0] & 0x20u), !!(ku->data[0] & 0x10u),
-                   !!(ku->data[0] & 0x08u), !!(ku->data[0] & 0x04u),
-                   !!(ku->data[0] & 0x02u), !!(ku->data[0] & 0x01u),
-                   !!(ku->data[1] & 0x80u));
+                   !!(data[0] & 0x80u), !!(data[0] & 0x40u),
+                   !!(data[0] & 0x20u), !!(data[0] & 0x10u),
+                   !!(data[0] & 0x08u), !!(data[0] & 0x04u),
+                   !!(data[0] & 0x02u), !!(data[0] & 0x01u),
+                   !!(data[1] & 0x80u));
        }
 
        return 0;
@@ -1355,7 +1321,7 @@ static int
 handle_cdp(void *ext, void *arg)
 {
        STACK_OF(DIST_POINT) *crldp = ext;
-       struct certificate_refs *refs = arg;
+       struct sia_uris *sias = arg;
        DIST_POINT *dp;
        GENERAL_NAMES *names;
        GENERAL_NAME *name;
@@ -1416,7 +1382,7 @@ handle_cdp(void *ext, void *arg)
                         * So we will store the URI in @refs, and validate it
                         * later.
                         */
-                       return ia5s2string(str, &refs->crldp);
+                       return ia5s2string(str, &sias->crldp);
                }
        }
 
@@ -1431,13 +1397,10 @@ dist_point_error:
  * Create @map from the @ad
  */
 static int
-map_create_ad(struct cache_mapping **map, ACCESS_DESCRIPTION *ad,
-    enum map_type type)
+ad2uri(char **uri, ACCESS_DESCRIPTION *ad)
 {
        ASN1_STRING *asn1str;
-       char *str;
        int ptype;
-       int error;
 
        asn1str = GENERAL_NAME_get0_value(ad->location, &ptype);
 
@@ -1474,17 +1437,19 @@ map_create_ad(struct cache_mapping **map, ACCESS_DESCRIPTION *ad,
         * conversion, so we should be looking at precisely the IA5String
         * directory our g2l version of @asn1_string should contain.
         * But ask the testers to keep an eye on it anyway.
+        *
+        * XXX There used to be a map_create() here. Make sure validations are
+        * restored somewhere:
+        * 1. ascii
+        * 2. "rsync://" or "https://" prefix (ENOTRSYNC, ENOTHTTPS)
+        * 3. URL normalization
         */
-       str = pstrndup((char const *)ASN1_STRING_get0_data(asn1str),
+       *uri = pstrndup((char const *)ASN1_STRING_get0_data(asn1str),
            ASN1_STRING_length(asn1str));
-
-       error = map_create(map, type, NULL, str);
-
-       free(str);
-       return error;
+       return 0;
 }
 
-/**
+/*
  * The RFC does not explain AD validation very well. This is personal
  * interpretation, influenced by Tim Bruijnzeels's response
  * (https://mailarchive.ietf.org/arch/msg/sidr/4ycmff9jEU4VU9gGK5RyhZ7JYsQ)
@@ -1500,13 +1465,17 @@ map_create_ad(struct cache_mapping **map, ACCESS_DESCRIPTION *ad,
  *    supposed to be ignored.
  * 5. Other access descriptions that match the NID but do not have recognized
  *    URLs are also allowed, and also supposed to be ignored.
+ *
+ * cb() always steals ownership of the URL string.
+ *
+ * TODO (test) is this tested somewhere?
  */
 static int
 handle_ad(int nid, struct ad_metadata const *meta, SIGNATURE_INFO_ACCESS *ia,
-    int (*cb)(struct cache_mapping *, void *), void *arg)
+    void (*cb)(char *, void *), void *arg)
 {
        ACCESS_DESCRIPTION *ad;
-       struct cache_mapping *map;
+       char *uri;
        bool found;
        unsigned int i;
        int error;
@@ -1515,12 +1484,10 @@ handle_ad(int nid, struct ad_metadata const *meta, SIGNATURE_INFO_ACCESS *ia,
        for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ia); i++) {
                ad = sk_ACCESS_DESCRIPTION_value(ia, i);
                if (OBJ_obj2nid(ad->method) == nid) {
-                       error = map_create_ad(&map, ad, meta->type);
+                       error = ad2uri(&uri, ad);
                        switch (error) {
                        case 0:
                                break;
-                       case ENOTRSYNC:
-                       case ENOTHTTPS:
                        case ENOTSUPPORTED:
                                continue;
                        default:
@@ -1528,42 +1495,37 @@ handle_ad(int nid, struct ad_metadata const *meta, SIGNATURE_INFO_ACCESS *ia,
                        }
 
                        if (found) {
-                               map_refput(map);
+                               free(uri);
                                return pr_val_err("Extension '%s' has multiple '%s' %s URIs.",
-                                   meta->ia_name, meta->name, meta->type_str);
+                                   meta->ia_name, meta->name, meta->type);
                        }
 
-                       error = cb(map, arg);
-                       if (error) {
-                               map_refput(map);
-                               return error;
-                       }
-
-                       map_refput(map);
+                       cb(uri, arg); /* Ownership of uri stolen */
                        found = true;
                }
        }
 
        if (meta->required && !found) {
                pr_val_err("Extension '%s' lacks a '%s' valid %s URI.",
-                   meta->ia_name, meta->name, meta->type_str);
+                   meta->ia_name, meta->name, meta->type);
                return -ESRCH;
        }
 
        return 0;
 }
 
-static int
-handle_caIssuers(struct cache_mapping *map, void *arg)
+static void
+handle_caIssuers(char *uri, void *arg)
 {
-       struct certificate_refs *refs = arg;
+       struct sia_uris *sias = arg;
        /*
         * Bringing the parent certificate's URI all the way
         * over here is too much trouble, so do the handle_cdp()
         * hack.
+        *
+        * XXX Uh... it's extremely easy now.
         */
-       refs->caIssuers = map_refget(map);
-       return 0;
+       sias->caIssuers = uri;
 }
 
 static int
@@ -1652,33 +1614,30 @@ handle_cp(void *ext, void *arg)
        return 0;
 }
 
-/**
- * Validates the certificate extensions, Trust Anchor style.
- */
+/* Validates the certificate extensions, Trust Anchor style. */
 static int
-certificate_validate_extensions_ta(X509 *cert, struct sia_uris *sia_uris,
-    enum rpki_policy *policy)
+validate_ta_extensions(struct rpki_certificate *cert)
 {
        struct extension_handler handlers[] = {
-          /* ext        reqd   handler        arg       */
-           { ext_bc(),  true,  handle_bc,               },
-           { ext_ski(), true,  handle_ski_ca, cert      },
-           { ext_aki(), false, handle_aki_ta, cert      },
-           { ext_ku(),  true,  handle_ku_ca,            },
-           { ext_sia(), true,  handle_sia_ca, sia_uris  },
-           { ext_cp(),  true,  handle_cp,     policy    },
+          /* ext        reqd   handler        arg           */
+           { ext_bc(),  true,  handle_bc,                    },
+           { ext_ski(), true,  handle_ski_ca, cert->x509     },
+           { ext_aki(), false, handle_aki_ta, cert->x509     },
+           { ext_ku(),  true,  handle_ku_ca,                 },
+           { ext_sia(), true,  handle_sia_ca, &cert->sias    },
+           { ext_cp(),  true,  handle_cp,     &cert->policy  },
            /* These are handled by certificate_get_resources(). */
-           { ext_ir(),  false,                          },
-           { ext_ar(),  false,                          },
-           { ext_ir2(), false,                          },
-           { ext_ar2(), false,                          },
+           { ext_ir(),  false,                               },
+           { ext_ar(),  false,                               },
+           { ext_ir2(), false,                               },
+           { ext_ar2(), false,                               },
            { NULL },
        };
 
-       return handle_extensions(handlers, X509_get0_extensions(cert));
+       return handle_extensions(handlers, X509_get0_extensions(cert->x509));
 }
 
-/**
+/*
  * Validates the certificate extensions, (intermediate) Certificate Authority
  * style.
  *
@@ -1686,71 +1645,65 @@ certificate_validate_extensions_ta(X509 *cert, struct sia_uris *sia_uris,
  * extensions.
  */
 static int
-certificate_validate_extensions_ca(X509 *cert, struct sia_uris *sia_uris,
-    enum rpki_policy *policy, struct rpp *rpp_parent)
+validate_ca_extensions(struct rpki_certificate *cert)
 {
-       struct certificate_refs refs = { 0 };
        struct extension_handler handlers[] = {
-          /* ext        reqd   handler        arg       */
-           { ext_bc(),  true,  handle_bc,               },
-           { ext_ski(), true,  handle_ski_ca, cert      },
-           { ext_aki(), true,  handle_aki,              },
-           { ext_ku(),  true,  handle_ku_ca,            },
-           { ext_cdp(), true,  handle_cdp,    &refs     },
-           { ext_aia(), true,  handle_aia,    &refs     },
-           { ext_sia(), true,  handle_sia_ca, sia_uris  },
-           { ext_cp(),  true,  handle_cp,     policy    },
-           { ext_ir(),  false,                          },
-           { ext_ar(),  false,                          },
-           { ext_ir2(), false,                          },
-           { ext_ar2(), false,                          },
+          /* ext        reqd   handler        arg                */
+           { ext_bc(),  true,  handle_bc,                         },
+           { ext_ski(), true,  handle_ski_ca, cert->x509          },
+           { ext_aki(), true,  handle_aki,    cert->parent->x509  },
+           { ext_ku(),  true,  handle_ku_ca,                      },
+           { ext_cdp(), true,  handle_cdp,    &cert->sias         },
+           { ext_aia(), true,  handle_aia,    &cert->sias         },
+           { ext_sia(), true,  handle_sia_ca, &cert->sias         },
+           { ext_cp(),  true,  handle_cp,     &cert->policy       },
+           /* These are handled by certificate_get_resources(). */
+           { ext_ir(),  false,                                    },
+           { ext_ar(),  false,                                    },
+           { ext_ir2(), false,                                    },
+           { ext_ar2(), false,                                    },
            { NULL },
        };
        int error;
 
-       error = handle_extensions(handlers, X509_get0_extensions(cert));
+       error = handle_extensions(handlers, X509_get0_extensions(cert->x509));
        if (error)
-               goto end;
-       error = certificate_validate_aia(refs.caIssuers, cert);
+               return error;
+       error = certificate_validate_aia(cert);
        if (error)
-               goto end;
-       error = refs_validate_ca(&refs, rpp_parent);
-
-end:
-       refs_cleanup(&refs);
-       return error;
+               return error;
+       return validate_cdp(&cert->sias, cert->parent->rpp.crl.map->url);
 }
 
 int
-certificate_validate_extensions_ee(X509 *cert, OCTET_STRING_t *sid,
-    struct certificate_refs *refs, enum rpki_policy *policy)
+certificate_validate_extensions_ee(struct rpki_certificate *cert,
+    OCTET_STRING_t *sid)
 {
        struct ski_arguments ski_args;
        struct extension_handler handlers[] = {
-          /* ext        reqd   handler        arg       */
-           { ext_ski(), true,  handle_ski_ee, &ski_args },
-           { ext_aki(), true,  handle_aki,              },
-           { ext_ku(),  true,  handle_ku_ee,            },
-           { ext_cdp(), true,  handle_cdp,    refs      },
-           { ext_aia(), true,  handle_aia,    refs      },
-           { ext_sia(), true,  handle_sia_ee, refs      },
-           { ext_cp(),  true,  handle_cp,     policy    },
-           { ext_ir(),  false,                          },
-           { ext_ar(),  false,                          },
-           { ext_ir2(), false,                          },
-           { ext_ar2(), false,                          },
+          /* ext        reqd   handler        arg                */
+           { ext_ski(), true,  handle_ski_ee, &ski_args           },
+           { ext_aki(), true,  handle_aki,    cert->parent->x509  },
+           { ext_ku(),  true,  handle_ku_ee,                      },
+           { ext_cdp(), true,  handle_cdp,    &cert->sias         },
+           { ext_aia(), true,  handle_aia,    &cert->sias         },
+           { ext_sia(), true,  handle_sia_ee, &cert->sias         },
+           { ext_cp(),  true,  handle_cp,     &cert->policy       },
+           { ext_ir(),  false,                                    },
+           { ext_ar(),  false,                                    },
+           { ext_ir2(), false,                                    },
+           { ext_ar2(), false,                                    },
            { NULL },
        };
 
-       ski_args.cert = cert;
+       ski_args.cert = cert->x509;
        ski_args.sid = sid;
 
-       return handle_extensions(handlers, X509_get0_extensions(cert));
+       return handle_extensions(handlers, X509_get0_extensions(cert->x509));
 }
 
 int
-certificate_validate_extensions_bgpsec(X509 *cert, unsigned char **ski,
-    enum rpki_policy *policy, struct rpp *pp)
+certificate_validate_extensions_bgpsec(void)
 {
        return 0; /* TODO (#58) */
 }
@@ -1783,34 +1736,26 @@ has_bgpsec_router_eku(X509 *cert)
  * Assumption: Meant to be used exclusively in the context of parsing a .cer
  * certificate.
  */
-static int
-get_certificate_type(X509 *cert, bool is_ta, enum cert_type *result)
+static enum cert_type
+get_certificate_type(struct rpki_certificate *cert)
 {
-       if (is_ta) {
-               *result = CERTYPE_TA;
-               return 0;
-       }
+       if (cert->parent == NULL)
+               return CERTYPE_TA;
 
-       if (X509_check_purpose(cert, -1, -1) <= 0)
-               goto err;
+       if (X509_check_purpose(cert->x509, -1, -1) <= 0)
+               return CERTYPE_UNKNOWN;
 
-       if (X509_check_ca(cert) == 1) {
-               *result = CERTYPE_CA;
-               return 0;
-       }
+       if (X509_check_ca(cert->x509) == 1)
+               return CERTYPE_CA;
 
-       if (has_bgpsec_router_eku(cert)) {
-               *result = CERTYPE_BGPSEC;
-               return 0;
-       }
+       if (has_bgpsec_router_eku(cert->x509))
+               return CERTYPE_BGPSEC;
 
-err:
-       *result = CERTYPE_EE; /* Shuts up nonsense gcc 8.3 warning */
-       return pr_val_err("Certificate is not TA, CA nor BGPsec. Ignoring...");
+       return CERTYPE_UNKNOWN;
 }
 
 int
-certificate_validate_aia(struct cache_mapping *caIssuers, X509 *cert)
+certificate_validate_aia(struct rpki_certificate *cert)
 {
        /*
         * FIXME Compare the AIA to the parent's URI.
@@ -1820,80 +1765,61 @@ certificate_validate_aia(struct cache_mapping *caIssuers, X509 *cert)
        return 0;
 }
 
-static int
-retrieve_mapping(struct cache_mapping *map, void *arg)
+static unsigned int
+chain_length(struct rpki_certificate *cert)
 {
-       struct cache_mapping **result = arg;
-       *result = map;
-       return 0;
+       unsigned int a;
+       for (a = 0; cert != NULL; a++)
+               cert = cert->parent;
+       return a;
 }
 
-static struct cache_mapping *
-download_rpp(struct sia_uris *uris)
+static int
+init_resources(struct rpki_certificate *cert)
 {
-       struct cache_mapping *map;
        int error;
 
-       if (uris->rpp.len == 0) {
-               pr_val_err("SIA lacks both caRepository and rpkiNotify.");
-               return NULL;
-       }
+       cert->resources = resources_create(cert->policy, false);
+
+       error = certificate_get_resources(cert);
+       if (error)
+               return error;
 
-       error = cache_download_alt(validation_cache(state_retrieve()),
-           &uris->rpp, MAP_NOTIF, MAP_RPP, retrieve_mapping, &map);
-       return error ? NULL : map;
+       /*
+        * rfc8630#section-2.3
+        * "The INR extension(s) of this TA MUST contain a non-empty set of
+        * number resources."
+        * The "It MUST NOT use the "inherit" form of the INR extension(s)"
+        * part is already handled in certificate_get_resources().
+        */
+       if (cert->type == CERTYPE_TA && resources_empty(cert->resources))
+               return pr_val_err("Trust Anchor certificate does not define any number resources.");
+
+       return 0;
 }
 
-/** Boilerplate code for CA certificate validation and recursive traversal. */
-int
-certificate_traverse(struct rpp *rpp_parent, struct cache_mapping *cert_map)
+static int
+certificate_validate(struct rpki_certificate *cert)
 {
-       struct validation *state;
-       int total_parents;
-       STACK_OF(X509_CRL) *rpp_parent_crl;
-       X509 *cert;
-       struct sia_uris sia_uris;
-       struct cache_mapping *downloaded;
-       enum rpki_policy policy;
-       enum cert_type certype;
-       struct rpp *pp;
        int error;
 
-       state = state_retrieve();
-
-       total_parents = certstack_get_x509_num(validation_certstack(state));
-       if (total_parents >= config_get_max_cert_depth())
+       if (chain_length(cert) >= config_get_max_cert_depth())
                return pr_val_err("Certificate chain maximum depth exceeded.");
 
-       /* Debug cert type */
-       if (rpp_parent == NULL)
-               pr_val_debug("TA Certificate '%s' {",
-                   map_val_get_printable(cert_map));
-       else
-               pr_val_debug("Certificate '%s' {",
-                   map_val_get_printable(cert_map));
-
-       fnstack_push_map(cert_map);
+       fnstack_push_map(&cert->map);
 
-       error = rpp_crl(rpp_parent, &rpp_parent_crl);
-       if (error)
-               goto revert_fnstack_and_debug;
-
-       /* -- Validate the certificate (@cert) -- */
-       error = certificate_load(cert_map, &cert);
-       if (error)
-               goto revert_fnstack_and_debug;
-       error = certificate_validate_chain(cert, rpp_parent_crl);
-       if (error)
-               goto revert_cert;
+       cert->x509 = certificate_load(cert->map.path);
+       if (!cert->x509)
+               return -EINVAL;
+       cert->type = get_certificate_type(cert);
 
-       error = get_certificate_type(cert, rpp_parent == NULL, &certype);
+       error = certificate_validate_chain(cert);
        if (error)
-               goto revert_cert;
+               goto end;
 
-       /* Debug cert type */
-       switch (certype) {
+       switch (cert->type) {
        case CERTYPE_TA:
+               pr_val_debug("Type: TA");
                break;
        case CERTYPE_CA:
                pr_val_debug("Type: CA");
@@ -1902,55 +1828,119 @@ certificate_traverse(struct rpp *rpp_parent, struct cache_mapping *cert_map)
                pr_val_debug("Type: BGPsec EE. Ignoring...");
 //             error = handle_bgpsec(cert, x509stack_peek_resources(
 //                 validation_certstack(state)), rpp_parent);
-               goto revert_cert;
+               goto end;
        default:
                pr_val_debug("Type: Unknown. Ignoring...");
-               goto revert_cert;
+               goto end;
        }
 
-       error = certificate_validate_rfc6487(cert, certype);
+       error = certificate_validate_rfc6487(cert);
        if (error)
-               goto revert_cert;
+               goto end;
 
-       sia_uris_init(&sia_uris);
-       error = (certype == CERTYPE_TA)
-           ? certificate_validate_extensions_ta(cert, &sia_uris, &policy)
-           : certificate_validate_extensions_ca(cert, &sia_uris, &policy,
-                                                rpp_parent);
+       error = (cert->type == CERTYPE_TA)
+           ? validate_ta_extensions(cert)
+           : validate_ca_extensions(cert);
        if (error)
-               goto revert_uris;
+               goto end;
 
-       downloaded = download_rpp(&sia_uris);
-       if (downloaded == NULL) {
-               error = EINVAL;
-               goto revert_uris;
-       }
+       error = init_resources(cert);
 
-       error = x509stack_push(validation_certstack(state), cert_map, cert,
-           policy, certype);
+end:   fnstack_pop();
+       return error;
+}
+
+static int
+certificate_traverse(struct rpki_certificate *ca, struct cert_stack *stack)
+{
+       struct cache_cage *cage;
+       char const *mft;
+       array_index i;
+       struct cache_mapping *map;
+       char const *ext;
+       int error;
+
+       error = certificate_validate(ca);
        if (error)
-               goto revert_uris;
-       cert = NULL; /* Ownership stolen */
+               return error;
+
+       if (ca->type != CERTYPE_TA && ca->type != CERTYPE_CA)
+               return 0;
 
-       error = handle_manifest(sia_uris.mft,
-           (map_get_type(downloaded) == MAP_NOTIF) ? downloaded : NULL,
-           &pp);
+       cage = cache_refresh_sias(&ca->sias);
+       if (!cage)
+               return pr_val_err("caRepository '%s' could not be refreshed, "
+                   "and there is no fallback in the cache. "
+                   "I'm going to have to skip it.", ca->sias.caRepository);
+
+retry: mft = cage_map_file(cage, ca->sias.rpkiManifest);
+       if (!mft) {
+               if (cage_disable_refresh(cage))
+                       goto retry;
+               error = pr_val_err("caRepository '%s' is missing a manifest.",
+                   ca->sias.caRepository);
+               goto end;
+       }
+
+       error = manifest_traverse(ca->sias.rpkiManifest, mft, cage, ca);
        if (error) {
-               x509stack_cancel(validation_certstack(state));
-               goto revert_uris;
+               if (cage_disable_refresh(cage))
+                       goto retry;
+               goto end;
        }
 
-       /* -- Validate & traverse the RPP (@pp) described by the manifest -- */
-       rpp_traverse(pp);
-       rpp_refput(pp);
+       for (i = 0; i < ca->rpp.nfiles; i++) {
+               map = ca->rpp.files + i;
+               ext = map->url + strlen(map->url) - 4;
+               if (strcmp(ext, ".cer") == 0)
+                       certificate_stack_push(stack, map, ca);
+               else if (strcmp(ext, ".roa") == 0)
+                       roa_traverse(map, ca);
+               else if (strcmp(ext, ".gbr") == 0)
+                       ghostbusters_traverse(map, ca);
+       }
 
-revert_uris:
-       sia_uris_cleanup(&sia_uris);
-revert_cert:
-       if (cert != NULL)
-               X509_free(cert);
-revert_fnstack_and_debug:
-       fnstack_pop();
-       pr_val_debug("}");
+       cache_commit_rpp(ca->sias.caRepository, &ca->rpp);
+
+end:   free(cage);
+       return error;
+}
+
+int
+traverse_tree(struct cache_mapping const *ta_map, struct validation *state)
+{
+       struct cert_stack stack;
+       struct rpki_certificate *ta;
+       struct rpki_certificate *ca;
+       int error;
+
+       SLIST_INIT(&stack);
+
+       /* == Root certificate == */
+       ta = pzalloc(sizeof(struct rpki_certificate));
+       map_copy(&ta->map, ta_map);
+       ta->refcount = 1;
+
+       error = certificate_traverse(ta, &stack);
+       if (error)
+               goto end;
+
+       /*
+        * From now on, the tree should be considered valid, even if subsequent
+        * certificates fail.
+        * (the root validated successfully; subtrees are isolated problems.)
+        */
+
+       /* == Every other certificate == */
+       while (!SLIST_EMPTY(&stack)) {
+               ca = SLIST_FIRST(&stack);
+               SLIST_REMOVE_HEAD(&stack, lh);
+
+               certificate_traverse(ca, &stack);
+
+               rpki_certificate_free(ca);
+       }
+
+end:   rpki_certificate_free(ta);
        return error;
 }
index b2b68acea795d5e73db9d481bf24a0c280f0510a..e4cc99940418edefdc7c406fea0a43ee32d6126f 100644 (file)
@@ -1,12 +1,16 @@
 #ifndef SRC_OBJECT_CERTIFICATE_H_
 #define SRC_OBJECT_CERTIFICATE_H_
 
+#include <openssl/x509.h>
+#include <sys/queue.h>
+
 #include "asn1/asn1c/ANY.h"
 #include "asn1/asn1c/SignatureValue.h"
+#include "cache.h"
 #include "certificate_refs.h"
 #include "resource.h"
-#include "rpp.h"
-#include "types/map.h"
+#include "state.h"
+#include "types/rpp.h"
 
 /* Certificate types in the RPKI */
 enum cert_type {
@@ -14,23 +18,45 @@ enum cert_type {
        CERTYPE_CA,             /* Certificate Authority */
        CERTYPE_BGPSEC,         /* BGPsec certificates */
        CERTYPE_EE,             /* End Entity certificates */
+       CERTYPE_UNKNOWN,
 };
 
+struct rpki_certificate {
+       struct cache_mapping map;               /* Nonexistent on EEs */
+       X509 *x509;                             /* Initializes after dequeue */
+
+       enum cert_type type;
+       enum rpki_policy policy;                /* XXX seems redundant */
+       struct resources *resources;
+       struct sia_uris sias;
+
+       struct rpki_certificate *parent;
+       struct rpp rpp;                         /* Nonexistent on EEs */
+
+       SLIST_ENTRY(rpki_certificate) lh;       /* List Hook */
+       unsigned int refcount;
+};
+
+void rpki_certificate_init_ee(struct rpki_certificate *,
+    struct rpki_certificate *, bool);
+void rpki_certificate_cleanup(struct rpki_certificate *);
+void rpki_certificate_free(struct rpki_certificate *);
+
 /**
  * Performs the basic (RFC 5280, presumably) chain validation.
  * (Ignores the IP and AS extensions.)
  */
-int certificate_validate_chain(X509 *, STACK_OF(X509_CRL) *);
+int certificate_validate_chain(struct rpki_certificate *);
 /**
  * Validates RFC 6487 compliance.
  * (Except extensions.)
  */
-int certificate_validate_rfc6487(X509 *, enum cert_type);
+int certificate_validate_rfc6487(struct rpki_certificate *);
 
 int certificate_validate_signature(X509 *, ANY_t *coded, SignatureValue_t *);
 
 /**
- * Returns the IP and AS resources declared in the respective extensions.
+ * Extracts the resources from cert->x509 into cert->resources.
  *
  * Note: One reason why this is separate from the validate_extensions functions
  * is because it needs to be handled after the policy has been extracted from
@@ -38,7 +64,7 @@ int certificate_validate_signature(X509 *, ANY_t *coded, SignatureValue_t *);
  * not care about order. I don't know if you'll find other reasons if you choose
  * to migrate it.
  */
-int certificate_get_resources(X509 *, struct resources *, enum cert_type);
+int certificate_get_resources(struct rpki_certificate *cert);
 
 /**
  * Validates the certificate extensions, End-Entity style.
@@ -46,17 +72,16 @@ int certificate_get_resources(X509 *, struct resources *, enum cert_type);
  * Also initializes the second argument with the references found in the
  * extensions.
  */
-int certificate_validate_extensions_ee(X509 *, OCTET_STRING_t *,
-    struct certificate_refs *, enum rpki_policy *);
-int certificate_validate_extensions_bgpsec(X509 *, unsigned char **,
-    enum rpki_policy *, struct rpp *);
+int certificate_validate_extensions_ee(struct rpki_certificate *,
+    OCTET_STRING_t *);
+int certificate_validate_extensions_bgpsec(void);
 
 /*
  * Specific validation of AIA (rfc6487#section-4.8.7) extension, public so that
  * CAs and EEs can access it.
  */
-int certificate_validate_aia(struct cache_mapping *, X509 *);
+int certificate_validate_aia(struct rpki_certificate *);
 
-int certificate_traverse(struct rpp *, struct cache_mapping *);
+int traverse_tree(struct cache_mapping const *, struct validation *);
 
 #endif /* SRC_OBJECT_CERTIFICATE_H_ */
index 4b0aa8895a2530d4f2ca66dd261d50fe962447df..c0316a3a67c093ec1266b50264a99a1bc56187ce 100644 (file)
@@ -7,11 +7,11 @@
 #include "algorithm.h"
 #include "extension.h"
 #include "log.h"
-#include "object/name.h"
 #include "thread_var.h"
+#include "types/name.h"
 
 static int
-__crl_load(struct cache_mapping *map, X509_CRL **result)
+__crl_load(char const *path, X509_CRL **result)
 {
        X509_CRL *crl;
        BIO *bio;
@@ -20,16 +20,14 @@ __crl_load(struct cache_mapping *map, X509_CRL **result)
        bio = BIO_new(BIO_s_file());
        if (bio == NULL)
                return val_crypto_err("BIO_new(BIO_s_file()) returned NULL");
-       if (BIO_read_filename(bio, map_get_path(map)) <= 0) {
-               error = val_crypto_err("Error reading CRL '%s'",
-                   map_val_get_printable(map));
+       if (BIO_read_filename(bio, path) <= 0) {
+               error = val_crypto_err("Error reading CRL");
                goto end;
        }
 
        crl = d2i_X509_CRL_bio(bio, NULL);
        if (crl == NULL) {
-               error = val_crypto_err("Error parsing CRL '%s'",
-                   map_val_get_printable(map));
+               error = val_crypto_err("Error parsing CRL");
                goto end;
        }
 
@@ -106,18 +104,33 @@ static int
 handle_crlnum(void *ext, void *arg)
 {
        /*
-        * We're allowing only one CRL per RPP, so there's nothing to do here I
-        * think.
+        * TODO (fine) update RFC name later
+        *
+        * From draft-spaghetti-sidrops-rpki-crl-numbers:
+        *
+        * In the RPKI, a wellformed Manifest FileList contains exactly one
+        * entry for its associated CRL, together with a collision-resistant
+        * message digest of that CRLs contents (see Section 2.2 of RFC6481
+        * and Section 2 of RFC9286). Additionally, the target of the CRL
+        * Distribution Points extension in an RPKI Resource Certificate is the
+        * same CRL object listed on the issuing CAs current manifest (see
+        * Section 4.8.6 of RFC6487). Together, these properties guarantee
+        * that RPKI RPs will always be able to unambiguously identify exactly
+        * one current CRL for each RPKI CA. Thus, in the RPKI, the ordering
+        * functionality provided by CRL Numbers is fully subsumed by monotonically
+        * increasing Manifest Numbers (Section 4.2.1 of RFC9286), thereby
+        * obviating the need for RPKI RPs to process CRL Number extensions.
         */
+
        return 0;
 }
 
 static int
-validate_extensions(X509_CRL *crl)
+validate_extensions(X509_CRL *crl, X509 *parent)
 {
        struct extension_handler handlers[] = {
           /* ext        reqd   handler        arg */
-           { ext_aki(), true,  handle_aki,              },
+           { ext_aki(), true,  handle_aki,    parent    },
            { ext_cn(),  true,  handle_crlnum,           },
            { NULL },
        };
@@ -126,7 +139,7 @@ validate_extensions(X509_CRL *crl)
 }
 
 static int
-crl_validate(X509_CRL *crl)
+crl_validate(X509_CRL *crl, X509 *parent)
 {
        long version;
        int error;
@@ -140,7 +153,7 @@ crl_validate(X509_CRL *crl)
        if (error)
                return error;
 
-       error = validate_issuer_name("CRL", X509_CRL_get_issuer(crl));
+       error = validate_issuer_name(X509_CRL_get_issuer(crl), parent);
        if (error)
                return error;
 
@@ -148,25 +161,24 @@ crl_validate(X509_CRL *crl)
        if (error)
                return error;
 
-       return validate_extensions(crl);
+       return validate_extensions(crl, parent);
 }
 
 int
-crl_load(struct cache_mapping *map, X509_CRL **result)
+crl_load(struct cache_mapping *map, X509 *parent, X509_CRL **result)
 {
        int error;
 
-       pr_val_debug("CRL '%s' {", map_val_get_printable(map));
+       fnstack_push_map(map);
 
-       error = __crl_load(map, result);
+       error = __crl_load(map->path, result);
        if (error)
                goto end;
 
-       error = crl_validate(*result);
+       error = crl_validate(*result, parent);
        if (error)
                X509_CRL_free(*result);
 
-end:
-       pr_val_debug("}");
+end:   fnstack_pop();
        return error;
 }
index 6e4915bc6b112a4f1e745734f2a47c957d05c3b6..04240ff3d01d83f5dbe648b94454d214d6031e45 100644 (file)
@@ -2,9 +2,8 @@
 #define SRC_OBJECT_CRL_H_
 
 #include <openssl/x509.h>
-
 #include "types/map.h"
 
-int crl_load(struct cache_mapping *, X509_CRL **);
+int crl_load(struct cache_mapping *, X509 *, X509_CRL **);
 
 #endif /* SRC_OBJECT_CRL_H_ */
index ec8ec64c9a96578391fc226301b10ad36525e236..305d9d4f73be4fbd3e0bcb07169e8e1b7747abf3 100644 (file)
@@ -1,6 +1,7 @@
 #include "object/ghostbusters.h"
 
-#include "asn1/oid.h"
+#include <errno.h>
+
 #include "log.h"
 #include "object/signed_object.h"
 #include "object/vcard.h"
@@ -15,45 +16,37 @@ handle_vcard(struct signed_object *sobj)
 }
 
 int
-ghostbusters_traverse(struct cache_mapping *map, struct rpp *pp)
+ghostbusters_traverse(struct cache_mapping *map,
+    struct rpki_certificate *parent)
 {
        static OID oid = OID_GHOSTBUSTERS;
        struct oid_arcs arcs = OID2ARCS("ghostbusters", oid);
        struct signed_object sobj;
-       struct ee_cert ee;
-       STACK_OF(X509_CRL) *crl;
+       struct rpki_certificate ee;
        int error;
 
        /* Prepare */
-       pr_val_debug("Ghostbusters '%s' {", map_val_get_printable(map));
        fnstack_push_map(map);
 
        /* Decode */
-       error = signed_object_decode(&sobj, map);
+       error = signed_object_decode(&sobj, map->path);
        if (error)
-               goto revert_log;
+               goto end1;
 
        /* Prepare validation arguments */
-       error = rpp_crl(pp, &crl);
-       if (error)
-               goto revert_sobj;
-       eecert_init(&ee, crl, true);
+       rpki_certificate_init_ee(&ee, parent, true);
 
        /* Validate everything */
        error = signed_object_validate(&sobj, &arcs, &ee);
        if (error)
-               goto revert_args;
+               goto end3;
        error = handle_vcard(&sobj);
        if (error)
-               goto revert_args;
-       error = refs_validate_ee(&ee.refs, pp, map);
+               goto end3;
+       error = refs_validate_ee(&ee.sias, parent->rpp.crl.map->url, map->url);
 
-revert_args:
-       eecert_cleanup(&ee);
-revert_sobj:
+end3:  rpki_certificate_cleanup(&ee);
        signed_object_cleanup(&sobj);
-revert_log:
-       pr_val_debug("}");
-       fnstack_pop();
+end1:  fnstack_pop();
        return error;
 }
index 7f0454d65648900e55398fca47874011a7215e94..39350737e479881a5615a4a63a77882faadb024b 100644 (file)
@@ -1,9 +1,8 @@
 #ifndef SRC_OBJECT_GHOSTBUSTERS_H_
 #define SRC_OBJECT_GHOSTBUSTERS_H_
 
-#include "rpp.h"
-#include "types/map.h"
+#include "object/certificate.h"
 
-int ghostbusters_traverse(struct cache_mapping *, struct rpp *);
+int ghostbusters_traverse(struct cache_mapping *, struct rpki_certificate *);
 
 #endif /* SRC_OBJECT_GHOSTBUSTERS_H_ */
index e1ed8575a1e84a023761c5963989d8cdbb8d3fca..221483d2d1d29e6b9ac6ff93b3460d86bfbf25ab 100644 (file)
@@ -1,30 +1,17 @@
 #include "object/manifest.h"
 
 #include "algorithm.h"
-#include "asn1/asn1c/GeneralizedTime.h"
+#include "alloc.h"
 #include "asn1/asn1c/Manifest.h"
 #include "asn1/decode.h"
-#include "asn1/oid.h"
 #include "common.h"
-#include "crypto/hash.h"
+#include "config.h"
+#include "hash.h"
 #include "log.h"
-#include "object/certificate.h"
 #include "object/crl.h"
-#include "object/roa.h"
 #include "object/signed_object.h"
 #include "thread_var.h"
-
-static int
-cage(struct cache_mapping **map, struct cache_mapping *notif)
-{
-       if (notif == NULL) {
-               /* No need to cage */
-               map_refget(*map);
-               return 0;
-       }
-
-       return map_create_caged(map, notif, map_get_url(*map));
-}
+#include "types/path.h"
 
 static int
 decode_manifest(struct signed_object *sobj, struct Manifest **result)
@@ -89,10 +76,10 @@ validate_dates(GeneralizedTime_t *this, GeneralizedTime_t *next)
                    TM_ARGS(nextUpdate));
        }
 
-       now_tt = 0;
-       error = get_current_time(&now_tt);
-       if (error)
-               return error;
+       now_tt = config_get_validation_time();
+       if (now_tt == 0)
+               now_tt = time_fatal();
+
        if (gmtime_r(&now_tt, &now) == NULL) {
                error = errno;
                return pr_val_err("gmtime_r(now) error %d: %s", error,
@@ -185,204 +172,210 @@ validate_manifest(struct Manifest *manifest)
        return 0;
 }
 
-/**
- * Computes the hash of the file @map, and compares it to @expected (The
- * "expected" hash).
- *
- * Returns:
- *   0 if no errors happened and the hashes match, or the hash doesn't match
- *     but there's an incidence to ignore such error.
- * < 0 if there was an error that can't be ignored.
- * > 0 if there was an error but it can be ignored (file not found and there's
- *     an incidence to ignore this).
- */
+static void
+shuffle_mft_files(struct Manifest *mft)
+{
+       int i, j;
+       unsigned int seed, rnd;
+       struct FileAndHash *tmpfah;
+
+       seed = time(NULL) ^ getpid();
+
+       /* Fisher-Yates shuffle with modulo bias */
+       for (i = 0; i < mft->fileList.list.count - 1; i++) {
+               rnd = rand_r(&seed);
+               j = i + rnd % (mft->fileList.list.count - i);
+               tmpfah = mft->fileList.list.array[j];
+               mft->fileList.list.array[j] = mft->fileList.list.array[i];
+               mft->fileList.list.array[i] = tmpfah;
+       }
+}
+
+static bool
+is_valid_mft_file_chara(uint8_t chara)
+{
+       return ('a' <= chara && chara <= 'z')
+           || ('A' <= chara && chara <= 'Z')
+           || ('0' <= chara && chara <= '9')
+           || (chara == '-')
+           || (chara == '_');
+}
+
+/* RFC 9286, section 4.2.2 */
 static int
-hash_validate_mft_file(struct cache_mapping *map, BIT_STRING_t const *expected)
+validate_mft_filename(IA5String_t *ia5)
 {
-       struct hash_algorithm const *algorithm;
-       size_t hash_size;
-       unsigned char actual[EVP_MAX_MD_SIZE];
-       int error;
+       size_t dot;
+       size_t i;
 
-       algorithm = hash_get_sha256();
-       hash_size = hash_get_size(algorithm);
+       if (ia5->size < 5)
+               return pr_val_err("File name is too short (%zu < 5).", ia5->size);
+       dot = ia5->size - 4;
+       if (ia5->buf[dot] != '.')
+               return pr_val_err("File name is missing three-letter extension.");
 
-       if (expected->size != hash_size)
-               return pr_val_err("%s string has bogus size: %zu",
-                   hash_get_name(algorithm), expected->size);
-       if (expected->bits_unused != 0)
-               return pr_val_err("Hash string has unused bits.");
+       for (i = 0; i < ia5->size; i++)
+               if (i != dot && !is_valid_mft_file_chara(ia5->buf[i]))
+                       return pr_val_err("File name contains illegal character #%u",
+                           ia5->buf[i]);
 
        /*
-        * TODO (#82) This is atrocious. Implement RFC 9286, and probably reuse
-        * hash_validate_file().
+        * Well... the RFC says the extension must match a IANA listing,
+        * but rejecting unknown extensions is a liability since they keep
+        * adding new ones, and people rarely updates.
+        * If we don't have a handler, we'll naturally ignore the file.
         */
+       return 0;
+}
 
-       error = hash_file(algorithm, map_get_path(map), actual, NULL);
-       if (error) {
-               if (error == EACCES || error == ENOENT) {
-                       /* FIXME .................. */
-                       if (incidence(INID_MFT_FILE_NOT_FOUND,
-                           "File '%s' listed at manifest doesn't exist.",
-                           map_val_get_printable(map)))
-                               return -EINVAL;
-
-                       return error;
-               }
-               /* Any other error (crypto, file read) */
-               return ENSURE_NEGATIVE(error);
-       }
-
-       if (memcmp(expected->buf, actual, hash_size) != 0) {
-               return incidence(INID_MFT_FILE_HASH_NOT_MATCH,
-                   "File '%s' does not match its manifest hash.",
-                   map_val_get_printable(map));
-       }
+static int
+check_file_and_hash(struct FileAndHash *fah, char const *path)
+{
+       if (fah->hash.bits_unused != 0)
+               return pr_val_err("Hash string has unused bits.");
 
-       return 0;
+       /* Includes file exists validation, obv. */
+       return hash_validate_file(hash_get_sha256(), path,
+           fah->hash.buf, fah->hash.size);
 }
 
+/*
+ * XXX
+ *
+ * revoked manifest: 6.6
+ * CRL not in fileList: 6.6
+ * fileList file in different folder: 6.6
+ * manifest is identified by id-ad-rpkiManifest. (A directory will have more
+ * than 1 on rollover.)
+ * id-ad-rpkiManifest not found: 6.6
+ * invalid manifest: 6.6
+ * stale manifest: 6.6
+ * fileList file not found: 6.6
+ * bad hash: 6.6
+ * 6.6: warning, fallback to previous version. Children inherit this.
+ */
+
 static int
-build_rpp(struct Manifest *mft, struct cache_mapping *notif,
-    struct cache_mapping *mft_map, struct rpp **pp)
+build_rpp(char const *mft_url, struct Manifest *mft, struct cache_cage *cage,
+    struct rpki_certificate *parent)
 {
-       int i;
-       struct FileAndHash *fah;
-       struct cache_mapping *map;
+       struct rpp *rpp;
+       char *rpp_url;
+       unsigned int i;
+       struct FileAndHash *src;
+       struct cache_mapping *dst;
+       char const *path;
        int error;
 
-       *pp = rpp_create();
+       shuffle_mft_files(mft);
+
+       rpp = &parent->rpp;
+       rpp_url = path_parent(mft_url);
+       rpp->nfiles = mft->fileList.list.count;
+       rpp->files = pzalloc(rpp->nfiles * sizeof(*rpp->files));
 
        for (i = 0; i < mft->fileList.list.count; i++) {
-               fah = mft->fileList.list.array[i];
+               src = mft->fileList.list.array[i];
+               dst = &rpp->files[i];
 
-               error = map_create_mft(&map, notif, mft_map, &fah->file);
                /*
-                * Not handling ENOTRSYNC is fine because the manifest URL
-                * should have been RSYNC. Something went wrong if an RSYNC URL
-                * plus a relative path is not RSYNC.
+                * IA5String is a subset of ASCII. However, IA5String_t doesn't
+                * seem to be guaranteed to be NULL-terminated.
                 */
-               if (error)
-                       goto fail;
 
-               /*
-                * Expect:
-                * - Negative value: an error not to be ignored, the whole
-                *   manifest will be discarded.
-                * - Zero value: hash at manifest matches file's hash, or it
-                *   doesn't match its hash but there's an incidence to ignore
-                *   such error.
-                * - Positive value: file doesn't exist and keep validating
-                *   manifest.
-                */
-               error = hash_validate_mft_file(map, &fah->hash);
-               if (error < 0) {
-                       map_refput(map);
-                       goto fail;
-               }
-               if (error > 0) {
-                       map_refput(map);
-                       continue;
+               error = validate_mft_filename(&src->file);
+               if (error)
+                       goto revert;
+
+               dst->url = path_childn(rpp_url,
+                   (char const *)src->file.buf,
+                   src->file.size);
+
+               path = cage_map_file(cage, dst->url);
+               if (!path) {
+                       error = pr_val_err(
+                           "Manifest file '%s' is absent from the cache.",
+                           dst->url);
+                       goto revert;
                }
+               dst->path = pstrdup(path);
 
-               if (map_has_extension(map, ".cer"))
-                       rpp_add_cert(*pp, map);
-               else if (map_has_extension(map, ".roa"))
-                       rpp_add_roa(*pp, map);
-               else if (map_has_extension(map, ".crl"))
-                       error = rpp_add_crl(*pp, map);
-               else if (map_has_extension(map, ".gbr"))
-                       rpp_add_ghostbusters(*pp, map);
-               else
-                       map_refput(map); /* ignore it. */
+               error = check_file_and_hash(src, dst->path);
+               if (error)
+                       goto revert;
 
-               if (error) {
-                       map_refput(map);
-                       goto fail;
-               } /* Otherwise ownership was transferred to @pp. */
+               if (strcmp(((char const *)src->file.buf) + src->file.size - 4, ".crl") == 0) {
+                       if (rpp->crl.map != NULL) {
+                               error = pr_val_err(
+                                   "Manifest has more than one CRL.");
+                               goto revert;
+                       }
+                       rpp->crl.map = dst;
+               }
        }
 
        /* rfc6486#section-7 */
-       if (rpp_get_crl(*pp) == NULL) {
+       if (rpp->crl.map == NULL) {
                error = pr_val_err("Manifest lacks a CRL.");
-               goto fail;
+               goto revert;
        }
 
+       error = crl_load(rpp->crl.map, parent->x509, &rpp->crl.obj);
+       if (error)
+               goto revert;
+
+       free(rpp_url);
        return 0;
 
-fail:
-       rpp_refput(*pp);
+revert:        rpp_cleanup(rpp);
+       free(rpp_url);
        return error;
 }
 
-/**
- * Validates the manifest pointed by @map, returns the RPP described by it in
- * @pp.
- */
 int
-handle_manifest(struct cache_mapping *map, struct cache_mapping *notif,
-    struct rpp **pp)
+manifest_traverse(char const *url, char const *path, struct cache_cage *cage,
+    struct rpki_certificate *parent)
 {
        static OID oid = OID_MANIFEST;
        struct oid_arcs arcs = OID2ARCS("manifest", oid);
        struct signed_object sobj;
-       struct ee_cert ee;
+       struct rpki_certificate ee;
        struct Manifest *mft;
-       STACK_OF(X509_CRL) *crl;
        int error;
 
        /* Prepare */
-       error = cage(&map, notif); /* ref++ */
-       if (error)
-               return error;
-       pr_val_debug("Manifest '%s' {", map_val_get_printable(map));
-       fnstack_push_map(map);
+       fnstack_push(url); // XXX
 
        /* Decode */
-       error = signed_object_decode(&sobj, map);
+       error = signed_object_decode(&sobj, path);
        if (error)
-               goto revert_log;
+               goto end1;
        error = decode_manifest(&sobj, &mft);
        if (error)
-               goto revert_sobj;
+               goto end2;
 
-       /* Initialize out parameter (@pp) */
-       error = build_rpp(mft, notif, map, pp);
+       /* Initialize @summary */
+       error = build_rpp(url, mft, cage, parent);
        if (error)
-               goto revert_manifest;
+               goto end3;
 
        /* Prepare validation arguments */
-       error = rpp_crl(*pp, &crl);
-       if (error)
-               goto revert_rpp;
-       eecert_init(&ee, crl, false);
+       rpki_certificate_init_ee(&ee, parent, false);
 
        /* Validate everything */
        error = signed_object_validate(&sobj, &arcs, &ee);
        if (error)
-               goto revert_args;
+               goto end5;
        error = validate_manifest(mft);
        if (error)
-               goto revert_args;
-       error = refs_validate_ee(&ee.refs, *pp, map);
+               goto end5;
+       error = refs_validate_ee(&ee.sias, parent->rpp.crl.map->url, url);
+
+end5:  rpki_certificate_cleanup(&ee);
        if (error)
-               goto revert_args;
-
-       /* Success */
-       eecert_cleanup(&ee);
-       goto revert_manifest;
-
-revert_args:
-       eecert_cleanup(&ee);
-revert_rpp:
-       rpp_refput(*pp);
-revert_manifest:
-       ASN_STRUCT_FREE(asn_DEF_Manifest, mft);
-revert_sobj:
-       signed_object_cleanup(&sobj);
-revert_log:
-       pr_val_debug("}");
-       fnstack_pop();
-       map_refput(map); /* ref-- */
+               rpp_cleanup(&parent->rpp);
+end3:  ASN_STRUCT_FREE(asn_DEF_Manifest, mft);
+end2:  signed_object_cleanup(&sobj);
+end1:  fnstack_pop();
        return error;
 }
index 6ec44118ea3adb9a3ff04ef34709b83526adca0a..e3b28b03460230dc87475c4575738182600fc332 100644 (file)
@@ -1,8 +1,13 @@
 #ifndef SRC_OBJECT_MANIFEST_H_
 #define SRC_OBJECT_MANIFEST_H_
 
-#include "rpp.h"
+#include <openssl/sha.h>
+#include <openssl/x509.h>
 
-int handle_manifest(struct cache_mapping *, struct cache_mapping *, struct rpp **);
+#include "cache.h"
+#include "object/certificate.h"
+
+int manifest_traverse(char const *url, char const *path,
+    struct cache_cage *cage, struct rpki_certificate *parent);
 
 #endif /* SRC_OBJECT_MANIFEST_H_ */
index 63b810d2300d785c6db6a1230be0e7820b816a26..6319fe578d072b788ed88c79c51faf523e97ffbf 100644 (file)
@@ -2,8 +2,6 @@
 
 #include "asn1/asn1c/RouteOriginAttestation.h"
 #include "asn1/decode.h"
-#include "asn1/oid.h"
-#include "config.h"
 #include "log.h"
 #include "object/signed_object.h"
 #include "thread_var.h"
@@ -31,7 +29,6 @@ ____handle_roa_v4(struct resources *parent, unsigned long asn,
        if (error)
                return error;
 
-       pr_val_debug("ROAIPAddress {");
        pr_val_debug("address: %s/%u", v4addr2str(&prefix.addr), prefix.len);
 
        if (roa_addr->maxLength != NULL) {
@@ -41,21 +38,18 @@ ____handle_roa_v4(struct resources *parent, unsigned long asn,
                                pr_val_err("Error casting ROA's IPv4 maxLength: %s",
                                    strerror(errno));
                        }
-                       error = pr_val_err("The ROA's IPv4 maxLength isn't a valid unsigned long");
-                       goto end_error;
+                       return pr_val_err("The ROA's IPv4 maxLength isn't a valid unsigned long");
                }
                pr_val_debug("maxLength: %lu", max_length);
 
                if (max_length > 32) {
-                       error = pr_val_err("maxLength (%lu) is out of bounds (0-32).",
+                       return pr_val_err("maxLength (%lu) is out of bounds (0-32).",
                            max_length);
-                       goto end_error;
                }
 
                if (prefix.len > max_length) {
-                       error = pr_val_err("Prefix length (%u) > maxLength (%lu)",
+                       return pr_val_err("Prefix length (%u) > maxLength (%lu)",
                            prefix.len, max_length);
-                       goto end_error;
                }
 
        } else {
@@ -63,16 +57,11 @@ ____handle_roa_v4(struct resources *parent, unsigned long asn,
        }
 
        if (!resources_contains_ipv4(parent, &prefix)) {
-               error = pr_val_err("ROA is not allowed to advertise %s/%u.",
+               return pr_val_err("ROA is not allowed to advertise %s/%u.",
                    v4addr2str(&prefix.addr), prefix.len);
-               goto end_error;
        }
 
-       pr_val_debug("}");
        return vhandler_handle_roa_v4(asn, &prefix, max_length);
-end_error:
-       pr_val_debug("}");
-       return error;
 }
 
 static int
@@ -87,7 +76,6 @@ ____handle_roa_v6(struct resources *parent, unsigned long asn,
        if (error)
                return error;
 
-       pr_val_debug("ROAIPAddress {");
        pr_val_debug("address: %s/%u", v6addr2str(&prefix.addr), prefix.len);
 
        if (roa_addr->maxLength != NULL) {
@@ -97,21 +85,18 @@ ____handle_roa_v6(struct resources *parent, unsigned long asn,
                                pr_val_err("Error casting ROA's IPv6 maxLength: %s",
                                    strerror(errno));
                        }
-                       error = pr_val_err("The ROA's IPv6 maxLength isn't a valid unsigned long");
-                       goto end_error;
+                       return pr_val_err("The ROA's IPv6 maxLength isn't a valid unsigned long");
                }
                pr_val_debug("maxLength: %lu", max_length);
 
                if (max_length > 128) {
-                       error = pr_val_err("maxLength (%lu) is out of bounds (0-128).",
+                       return pr_val_err("maxLength (%lu) is out of bounds (0-128).",
                            max_length);
-                       goto end_error;
                }
 
                if (prefix.len > max_length) {
-                       error = pr_val_err("Prefix length (%u) > maxLength (%lu)",
+                       return pr_val_err("Prefix length (%u) > maxLength (%lu)",
                            prefix.len, max_length);
-                       goto end_error;
                }
 
        } else {
@@ -119,16 +104,11 @@ ____handle_roa_v6(struct resources *parent, unsigned long asn,
        }
 
        if (!resources_contains_ipv6(parent, &prefix)) {
-               error = pr_val_err("ROA is not allowed to advertise %s/%u.",
+               return pr_val_err("ROA is not allowed to advertise %s/%u.",
                    v6addr2str(&prefix.addr), prefix.len);
-               goto end_error;
        }
 
-       pr_val_debug("}");
        return vhandler_handle_roa_v6(asn, &prefix, max_length);
-end_error:
-       pr_val_debug("}");
-       return error;
 }
 
 static int
@@ -155,7 +135,6 @@ __handle_roa(struct RouteOriginAttestation *roa, struct resources *parent)
        int a;
        int error;
 
-       pr_val_debug("eContent {");
        if (roa->version != NULL) {
                error = asn_INTEGER2ulong(roa->version, &version);
                if (error) {
@@ -163,14 +142,12 @@ __handle_roa(struct RouteOriginAttestation *roa, struct resources *parent)
                                pr_val_err("Error casting ROA's version: %s",
                                    strerror(errno));
                        }
-                       error = pr_val_err("The ROA's version isn't a valid long");
-                       goto end_error;
+                       return pr_val_err("The ROA's version isn't a valid long");
                }
                /* rfc6482#section-3.1 */
                if (version != 0) {
-                       error = pr_val_err("ROA's version (%lu) is nonzero.",
+                       return pr_val_err("ROA's version (%lu) is nonzero.",
                            version);
-                       goto end_error;
                }
        }
 
@@ -180,118 +157,84 @@ __handle_roa(struct RouteOriginAttestation *roa, struct resources *parent)
                        pr_val_err("Error casting ROA's AS ID value: %s",
                            strerror(errno));
                }
-               error = pr_val_err("ROA's AS ID couldn't be parsed as unsigned long");
-               goto end_error;
+               return pr_val_err("ROA's AS ID couldn't be parsed as unsigned long");
        }
 
-       if (asn > UINT32_MAX) {
-               error = pr_val_err("AS value (%lu) is out of range.", asn);
-               goto end_error;
-       }
-       pr_val_debug("asId: %lu", asn);
+       if (asn > UINT32_MAX)
+               return pr_val_err("AS value (%lu) is out of range.", asn);
 
        /* rfc6482#section-3.3 */
 
-       if (roa->ipAddrBlocks.list.array == NULL) {
-               error = pr_val_err("ipAddrBlocks array is NULL.");
-               goto end_error;
-       }
+       if (roa->ipAddrBlocks.list.array == NULL)
+               return pr_val_err("ipAddrBlocks array is NULL.");
 
-       pr_val_debug("ipAddrBlocks {");
        for (b = 0; b < roa->ipAddrBlocks.list.count; b++) {
                block = roa->ipAddrBlocks.list.array[b];
-               if (block == NULL) {
-                       error = pr_val_err("Address block array element is NULL.");
-                       goto ip_error;
-               }
+               if (block == NULL)
+                       return pr_val_err("Address block array element is NULL.");
 
                if (block->addressFamily.size != 2)
                        goto family_error;
                if (block->addressFamily.buf[0] != 0)
                        goto family_error;
-               if (block->addressFamily.buf[1] != 1
-                   && block->addressFamily.buf[1] != 2)
+               if (block->addressFamily.buf[1] != 1 &&
+                   block->addressFamily.buf[1] != 2)
                        goto family_error;
-               pr_val_debug("%s {",
-                   block->addressFamily.buf[1] == 1 ? "v4" : "v6");
 
-               if (block->addresses.list.array == NULL) {
-                       error = pr_val_err("ROA's address list array is NULL.");
-                       pr_val_debug("}");
-                       goto ip_error;
-               }
+               if (block->addresses.list.array == NULL)
+                       return pr_val_err("ROA's address list array is NULL.");
 
                for (a = 0; a < block->addresses.list.count; a++) {
                        error = ____handle_roa(parent, asn,
                            block->addressFamily.buf[1],
                            block->addresses.list.array[a]);
-                       if (error) {
-                               pr_val_debug("}");
-                               goto ip_error;
-                       }
+                       if (error)
+                               return error;
                }
-               pr_val_debug("}");
        }
 
-       /* Error 0 it's ok */
-       error = 0;
-       goto ip_error;
+       return 0;
 
 family_error:
-       error = pr_val_err("ROA's IP family is not v4 or v6.");
-ip_error:
-       pr_val_debug("}");
-end_error:
-       pr_val_debug("}");
-       return error;
+       return pr_val_err("ROA's IP family is not v4 or v6.");
 }
 
 int
-roa_traverse(struct cache_mapping *map, struct rpp *pp)
+roa_traverse(struct cache_mapping *map, struct rpki_certificate *parent)
 {
        static OID oid = OID_ROA;
        struct oid_arcs arcs = OID2ARCS("roa", oid);
        struct signed_object sobj;
-       struct ee_cert ee;
+       struct rpki_certificate ee;
        struct RouteOriginAttestation *roa;
-       STACK_OF(X509_CRL) *crl;
        int error;
 
        /* Prepare */
-       pr_val_debug("ROA '%s' {", map_val_get_printable(map));
        fnstack_push_map(map);
 
        /* Decode */
-       error = signed_object_decode(&sobj, map);
+       error = signed_object_decode(&sobj, map->path);
        if (error)
-               goto revert_log;
+               goto end1;
        error = decode_roa(&sobj, &roa);
        if (error)
-               goto revert_sobj;
+               goto end2;
 
        /* Prepare validation arguments */
-       error = rpp_crl(pp, &crl);
-       if (error)
-               goto revert_roa;
-       eecert_init(&ee, crl, false);
+       rpki_certificate_init_ee(&ee, parent, false);
 
        /* Validate and handle everything */
        error = signed_object_validate(&sobj, &arcs, &ee);
        if (error)
-               goto revert_args;
-       error = __handle_roa(roa, ee.res);
+               goto end4;
+       error = __handle_roa(roa, ee.resources);
        if (error)
-               goto revert_args;
-       error = refs_validate_ee(&ee.refs, pp, map);
+               goto end4;
+       error = refs_validate_ee(&ee.sias, parent->rpp.crl.map->url, map->url);
 
-revert_args:
-       eecert_cleanup(&ee);
-revert_roa:
+end4:  rpki_certificate_cleanup(&ee);
        ASN_STRUCT_FREE(asn_DEF_RouteOriginAttestation, roa);
-revert_sobj:
-       signed_object_cleanup(&sobj);
-revert_log:
-       fnstack_pop();
-       pr_val_debug("}");
+end2:  signed_object_cleanup(&sobj);
+end1:  fnstack_pop();
        return error;
 }
index 310930053612e1807d4046d518f4dae26d5e669b..9642f3cc935b8bfd518c2b7ae41078c6129129e3 100644 (file)
@@ -1,10 +1,8 @@
 #ifndef SRC_OBJECT_ROA_H_
 #define SRC_OBJECT_ROA_H_
 
-#include "rpp.h"
-#include "types/address.h"
-#include "types/map.h"
+#include "object/certificate.h"
 
-int roa_traverse(struct cache_mapping *, struct rpp *);
+int roa_traverse(struct cache_mapping *, struct rpki_certificate *);
 
 #endif /* SRC_OBJECT_ROA_H_ */
index cb80aa81755605d224b6c2ecd4b9ee405c745872..8947a4e166f7455af0e650e63e6e3a07679e65b5 100644 (file)
@@ -4,11 +4,11 @@
 #include "log.h"
 
 int
-signed_object_decode(struct signed_object *sobj, struct cache_mapping *map)
+signed_object_decode(struct signed_object *sobj, char const *path)
 {
        int error;
 
-       error = content_info_load(map_get_path(map), &sobj->cinfo);
+       error = content_info_load(path, &sobj->cinfo);
        if (error)
                return error;
 
@@ -68,7 +68,7 @@ validate_content_type(struct SignedData *sdata, struct oid_arcs const *oid)
 
 int
 signed_object_validate(struct signed_object *sobj, struct oid_arcs const *oid,
-    struct ee_cert *ee)
+    struct rpki_certificate *ee)
 {
        int error;
 
index e6c2aba9063bfd0e6f19739516222563843ca468..35670ab0b6605e6a624f7c6d95e7c6bcc116b916 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef SRC_OBJECT_SIGNED_OBJECT_H_
 #define SRC_OBJECT_SIGNED_OBJECT_H_
 
+#include "asn1/asn1c/ContentInfo.h"
 #include "asn1/oid.h"
 #include "asn1/signed_data.h"
 
@@ -9,9 +10,9 @@ struct signed_object {
        struct SignedData *sdata;
 };
 
-int signed_object_decode(struct signed_object *, struct cache_mapping *);
+int signed_object_decode(struct signed_object *, char const *);
 int signed_object_validate(struct signed_object *, struct oid_arcs const *,
-    struct ee_cert *);
+    struct rpki_certificate *);
 void signed_object_cleanup(struct signed_object *);
 
 #endif /* SRC_OBJECT_SIGNED_OBJECT_H_ */
index ee072b935b4a2c8a5740a54527129de7fc073d7b..cd2e3b0e4e99fd2fcd711cf79d108d39126a2968 100644 (file)
@@ -1,34 +1,26 @@
 #include "object/tal.h"
 
 #include <ctype.h>
-#include <errno.h>
-#include <openssl/evp.h>
 #include <sys/queue.h>
 #include <time.h>
 
-#include "alloc.h"
-#include "cert_stack.h"
+#include "base64.h"
+#include "cache.h"
 #include "common.h"
 #include "config.h"
 #include "file.h"
 #include "log.h"
-#include "state.h"
-#include "thread_var.h"
-#include "validation_handler.h"
-#include "crypto/base64.h"
 #include "object/certificate.h"
-#include "rtr/db/vrps.h"
-#include "cache/local_cache.h"
-
-typedef int (*foreach_map_cb)(struct tal *, struct cache_mapping *, void *);
+#include "thread_var.h"
+#include "types/path.h"
+#include "types/str.h"
+#include "types/url.h"
 
 struct tal {
        char const *file_name;
-       struct map_list maps;
+       struct strlist urls;
        unsigned char *spki; /* Decoded; not base64. */
        size_t spki_len;
-
-       struct rpki_cache *cache;
 };
 
 struct validation_thread {
@@ -43,11 +35,6 @@ struct validation_thread {
 /* List of threads, one per TAL file */
 SLIST_HEAD(threads_list, validation_thread);
 
-struct handle_tal_args {
-       struct tal tal;
-       struct db_table *db;
-};
-
 static char *
 find_newline(char *str)
 {
@@ -70,31 +57,11 @@ is_blank(char const *str)
        return true;
 }
 
-static int
-add_url(struct tal *tal, char *url)
-{
-       struct cache_mapping *new = NULL;
-       int error;
-
-       if (str_starts_with(url, "rsync://"))
-               error = map_create(&new, MAP_TA_RSYNC, NULL, url);
-       else if (str_starts_with(url, "https://"))
-               error = map_create(&new, MAP_TA_HTTP, NULL, url);
-       else
-               return pr_op_err("TAL has non-rsync/HTTPS URI: %s", url);
-       if (error)
-               return error;
-
-       maps_add(&tal->maps, new);
-       return 0;
-}
-
 static int
 read_content(char *fc /* File Content */, struct tal *tal)
 {
        char *nl; /* New Line */
        bool cr; /* Carriage return */
-       int error;
 
        /* Comment section */
        while (fc[0] == '#') {
@@ -115,16 +82,15 @@ read_content(char *fc /* File Content */, struct tal *tal)
                if (is_blank(fc))
                        break;
 
-               error = add_url(tal, fc);
-               if (error)
-                       return error;
+               if (url_is_https(fc) || url_is_rsync(fc))
+                       strlist_add(&tal->urls, pstrdup(fc));
 
                fc = nl + cr + 1;
                if (*fc == '\0')
                        return pr_op_err("The TAL seems to be missing the public key.");
        } while (true);
 
-       if (tal->maps.len == 0)
+       if (tal->urls.len == 0)
                return pr_op_err("There seems to be an empty/blank line before the end of the URI section.");
 
        /* subjectPublicKeyInfo section */
@@ -138,13 +104,10 @@ premature:
        return pr_op_err("The TAL seems to end prematurely at line '%s'.", fc);
 }
 
-/**
- * @file_name is expected to outlive the result.
- */
+/* @file_path is expected to outlive @tal. */
 static int
 tal_init(struct tal *tal, char const *file_path)
 {
-       char const *file_name;
        struct file_contents file;
        int error;
 
@@ -152,20 +115,13 @@ tal_init(struct tal *tal, char const *file_path)
        if (error)
                return error;
 
-       file_name = strrchr(file_path, '/');
-       file_name = (file_name != NULL) ? (file_name + 1) : file_path;
-       tal->file_name = file_name;
+       tal->file_name = path_filename(file_path);
 
-       maps_init(&tal->maps);
+       strlist_init(&tal->urls);
        error = read_content((char *)file.buffer, tal);
-       if (error) {
-               maps_cleanup(&tal->maps);
-               goto end;
-       }
-
-       tal->cache = cache_create();
+       if (error)
+               strlist_cleanup(&tal->urls);
 
-end:
        file_free(&file);
        return error;
 }
@@ -173,9 +129,8 @@ end:
 static void
 tal_cleanup(struct tal *tal)
 {
-       cache_destroy(tal->cache);
        free(tal->spki);
-       maps_cleanup(&tal->maps);
+       strlist_cleanup(&tal->urls);
 }
 
 char const *
@@ -191,105 +146,65 @@ tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len)
        *len = tal->spki_len;
 }
 
-struct rpki_cache *
-tal_get_cache(struct tal *tal)
-{
-       return tal->cache;
-}
-
-/**
- * Performs the whole validation walkthrough on the @map mapping, which is
- * assumed to have been extracted from TAL @tal.
- */
-static int
-handle_tal_map(struct tal *tal, struct cache_mapping *map, struct db_table *db)
+static void
+__do_file_validation(struct validation_thread *thread)
 {
-       struct validation_handler validation_handler;
+       struct tal tal;
+       struct validation_handler collector;
+       struct db_table *db;
        struct validation *state;
-       struct cert_stack *certstack;
-       struct deferred_cert deferred;
-       int error;
-
-       pr_val_debug("TAL URI '%s' {", map_val_get_printable(map));
+       char **url;
+       struct cache_mapping map;
 
-       validation_handler.handle_roa_v4 = handle_roa_v4;
-       validation_handler.handle_roa_v6 = handle_roa_v6;
-       validation_handler.handle_router_key = handle_router_key;
-       validation_handler.arg = db;
+       thread->error = tal_init(&tal, thread->tal_file);
+       if (thread->error)
+               return;
 
-       error = validation_prepare(&state, tal, &validation_handler);
-       if (error)
-               return ENSURE_NEGATIVE(error);
+       collector.handle_roa_v4 = handle_roa_v4;
+       collector.handle_roa_v6 = handle_roa_v6;
+       collector.handle_router_key = handle_router_key;
+       collector.arg = db = db_table_create();
 
-       if (!map_is_certificate(map)) {
-               pr_op_err("TAL URI does not point to a certificate. (Expected .cer, got '%s')",
-                   map_op_get_printable(map));
-               error = EINVAL;
-               goto end;
+       thread->error = validation_prepare(&state, &tal, &collector);
+       if (thread->error) {
+               db_table_destroy(db);
+               goto end1;
        }
 
-       /* Handle root certificate. */
-       error = certificate_traverse(NULL, map);
-       if (error) {
-               switch (validation_pubkey_state(state)) {
-               case PKS_INVALID:
-                       error = EINVAL;
-                       goto end;
-               case PKS_VALID:
-               case PKS_UNTESTED:
-                       error = ENSURE_NEGATIVE(error);
-                       goto end;
-               }
-               pr_crit("Unknown public key state: %u",
-                   validation_pubkey_state(state));
+       ARRAYLIST_FOREACH(&tal.urls, url) {
+               map.url = *url;
+               map.path = cache_refresh_url(*url);
+               if (!map.path)
+                       continue;
+               if (traverse_tree(&map, state) != 0)
+                       continue;
+               goto end2; /* Happy path */
        }
 
-       /*
-        * From now on, the tree should be considered valid, even if subsequent
-        * certificates fail.
-        * (the root validated successfully; subtrees are isolated problems.)
-        */
-
-       /* Handle every other certificate. */
-       certstack = validation_certstack(state);
-       if (certstack == NULL)
-               pr_crit("Validation state has no certificate stack");
-
-       do {
-               error = deferstack_pop(certstack, &deferred);
-               if (error == -ENOENT) {
-                       error = 0; /* No more certificates left; we're done */
-                       goto end;
-               } else if (error) /* All other errors are critical, currently */
-                       pr_crit("deferstack_pop() returned illegal %d.", error);
-
-               /*
-                * Ignore result code; remaining certificates are unrelated,
-                * so they should not be affected.
-                */
-               certificate_traverse(deferred.pp, deferred.map);
-
-               map_refput(deferred.map);
-               rpp_refput(deferred.pp);
-       } while (true);
+       ARRAYLIST_FOREACH(&tal.urls, url) {
+               map.url = *url;
+               map.path = cache_fallback_url(*url);
+               if (!map.path)
+                       continue;
+               if (traverse_tree(&map, state) != 0)
+                       continue;
+               goto end2; /* Happy path */
+       }
 
-end:   validation_destroy(state);
-       pr_val_debug("}");
-       return error;
-}
+       pr_op_err("None of the TAL URIs yielded a successful traversal.");
+       thread->error = EINVAL;
+       db_table_destroy(db);
+       db = NULL;
 
-static int
-__handle_tal_map(struct cache_mapping *map, void *arg)
-{
-       struct handle_tal_args *args = arg;
-       return handle_tal_map(&args->tal, map, args->db);
+end2:  thread->db = db;
+       validation_destroy(state);
+end1:  tal_cleanup(&tal);
 }
 
 static void *
 do_file_validation(void *arg)
 {
        struct validation_thread *thread = arg;
-       struct handle_tal_args args;
        time_t start, finish;
 
        start = time(NULL);
@@ -297,28 +212,15 @@ do_file_validation(void *arg)
        fnstack_init();
        fnstack_push(thread->tal_file);
 
-       thread->error = tal_init(&args.tal, thread->tal_file);
-       if (thread->error)
-               goto end;
-
-       args.db = db_table_create();
-       thread->error = cache_download_alt(args.tal.cache, &args.tal.maps,
-           MAP_TA_HTTP, MAP_TA_RSYNC, __handle_tal_map, &args);
-       if (thread->error) {
-               pr_op_err("None of the URIs of the TAL '%s' yielded a successful traversal.",
-                   thread->tal_file);
-               db_table_destroy(args.db);
-       } else {
-               thread->db = args.db;
-       }
+       __do_file_validation(thread);
 
-       tal_cleanup(&args.tal);
-end:   fnstack_cleanup();
+       fnstack_cleanup();
 
        finish = time(NULL);
        if (start != ((time_t) -1) && finish != ((time_t) -1))
                pr_op_debug("The %s tree took %.0lf seconds.",
-                   args.tal.file_name, difftime(finish, start));
+                   path_filename(thread->tal_file),
+                   difftime(finish, start));
        return NULL;
 }
 
@@ -365,7 +267,7 @@ perform_standalone_validation(void)
        int error = 0;
        int tmperr;
 
-       cache_setup();
+       cache_prepare();
 
        /* TODO (fine) Maybe don't spawn threads if there's only one TAL */
        if (foreach_file(config_get_tal(), ".tal", true, spawn_tal_thread,
@@ -375,7 +277,12 @@ perform_standalone_validation(void)
                        SLIST_REMOVE_HEAD(&threads, next);
                        thread_destroy(thread);
                }
-               return NULL;
+
+               /*
+                * Commit even on failure, as there's no reason to throw away
+                * something we recently downloaded if it's marked as valid.
+                */
+               goto end;
        }
 
        /* Wait for all */
@@ -383,13 +290,14 @@ perform_standalone_validation(void)
                thread = SLIST_FIRST(&threads);
                tmperr = pthread_join(thread->pid, NULL);
                if (tmperr)
-                       pr_crit("pthread_join() threw %d (%s) on the '%s' thread.",
-                           tmperr, strerror(tmperr), thread->tal_file);
+                       pr_crit("pthread_join() threw '%s' on the '%s' thread.",
+                           strerror(tmperr), thread->tal_file);
                SLIST_REMOVE_HEAD(&threads, next);
                if (thread->error) {
                        error = thread->error;
-                       pr_op_warn("Validation from TAL '%s' yielded error %d (%s); discarding all validation results.",
-                           thread->tal_file, error, strerror(abs(error)));
+                       pr_op_warn("Validation from TAL '%s' yielded '%s'; "
+                           "discarding all validation results.",
+                           thread->tal_file, strerror(abs(error)));
                }
 
                if (!error) {
@@ -404,13 +312,12 @@ perform_standalone_validation(void)
                thread_destroy(thread);
        }
 
-       cache_teardown();
-
-       /* If one thread has errors, we can't keep the resulting table. */
+       /* If at least one thread had a fatal error, the table is unusable. */
        if (error) {
                db_table_destroy(db);
                db = NULL;
        }
 
+end:   cache_commit();
        return db;
 }
index 38becd0c517f56dc29e70544a0b9e8d47fcfd39e..0bf235cd5bac6d29f49a83dd6cd53fafec4bde4b 100644 (file)
@@ -1,17 +1,15 @@
-#ifndef TAL_OBJECT_H_
-#define TAL_OBJECT_H_
+#ifndef SRC_OBJECT_TAL_H_
+#define SRC_OBJECT_TAL_H_
 
 /* This is RFC 8630. */
 
 #include "rtr/db/db_table.h"
-#include "types/map.h"
 
 struct tal;
 
 char const *tal_get_file_name(struct tal *);
 void tal_get_spki(struct tal *, unsigned char const **, size_t *);
-struct rpki_cache *tal_get_cache(struct tal *);
 
 struct db_table *perform_standalone_validation(void);
 
-#endif /* TAL_OBJECT_H_ */
+#endif /* SRC_OBJECT_TAL_H_ */
index c4f8fb8d17a1b87112627c73891f13ad0ebed47f..c9ecaf9fa94949425223cf5f0087b7f68a264f69 100644 (file)
@@ -1,11 +1,9 @@
 #include "output_printer.h"
 
-#include "common.h"
+#include "base64.h"
 #include "config.h"
-#include "crypto/base64.h"
 #include "file.h"
 #include "log.h"
-#include "types/vrp.h"
 
 typedef struct json_out {
        FILE *file;
index 6db8eaaec0106dfbdadc1299f3e9b609be8d16ec..1739b7397d3f7378287280d3510dd176d6ca0e45 100644 (file)
@@ -1,18 +1,19 @@
 #include "print_file.h"
 
+#include <errno.h>
+
 #include "asn1/asn1c/CRL.h"
 #include "asn1/asn1c/Certificate.h"
+#include "asn1/asn1c/ContentInfo.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 "rsync.h"
 #include "types/bio_seq.h"
-#include "types/map.h"
+#include "types/path.h"
+#include "types/url.h"
 
 #define HDRSIZE 32
 
@@ -21,7 +22,9 @@ __rsync2bio(char const *src, char const *dst)
 {
        int error;
 
-       error = rsync_download(src, dst, false);
+       // XXX use the cache
+
+       error = rsync_download(src, dst);
        if (error) {
                pr_op_err("rysnc download failed: %s", strerror(abs(error)));
                return NULL;
@@ -36,7 +39,7 @@ rsync2bio_tmpdir(char const *src)
 #define TMPDIR "/tmp/fort-XXXXXX"
 
        struct path_builder pb;
-       char buf[strlen(TMPDIR) + 1];
+       char buf[sizeof(TMPDIR)];
        char *tmpdir;
        BIO *result = NULL;
        int error;
@@ -52,7 +55,7 @@ rsync2bio_tmpdir(char const *src)
        error = pb_append(&pb, tmpdir);
        if (error)
                goto end;
-       error = pb_append(&pb, strrchr(src, '/') + 1);
+       error = pb_append(&pb, path_filename(src));
        if (error)
                goto end;
 
@@ -65,24 +68,22 @@ end:        pb_cleanup(&pb);
 static BIO *
 rsync2bio_cache(char const *src)
 {
-       struct cache_mapping *map = NULL;
-       BIO *bio;
-       int error;
-
-       /*
-        * TODO (#82) maybe rename MAP_TA_RSYNC into single rsync.
-        * If applies and it's going to survive.
-        */
-       error = map_create(&map, MAP_TA_RSYNC, NULL, src);
-       if (error) {
-               pr_op_err("Unparseable rsync URI: %s", strerror(abs(error)));
-               return NULL;
-       }
-
-       bio = __rsync2bio(map_get_url(map), map_get_path(map));
+       pr_op_err("Disabled for now."); // XXX
+       return NULL;
 
-       map_refput(map);
-       return bio;
+//     char *dst;
+//     BIO *bio;
+//
+//     dst = url2path(src);
+//     if (!dst) {
+//             pr_op_err("Unparseable rsync URI.");
+//             return NULL;
+//     }
+//
+//     bio = __rsync2bio(src, dst);
+//
+//     free(dst);
+//     return bio;
 }
 
 static BIO *
@@ -99,7 +100,7 @@ filename2bio(char const *filename)
        if (filename == NULL || strcmp(filename, "-") == 0)
                return BIO_new_fp(stdin, BIO_NOCLOSE);
 
-       if (str_starts_with(filename, "rsync://"))
+       if (url_is_rsync(filename))
                return rsync2bio(filename);
 
        return BIO_new_file(filename, "rb");
similarity index 93%
rename from src/xml/relax_ng.c
rename to src/relax_ng.c
index 21f283145345c4440a86f3a80b1275428eecc3c7..5839cdf392177b81523761f5f895180dd8d790f3 100644 (file)
@@ -1,8 +1,8 @@
-#include "xml/relax_ng.h"
+#include "relax_ng.h"
 
 #include <stdarg.h>
 #include <stddef.h>
-#include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include "log.h"
@@ -56,7 +56,7 @@ relax_ng_init(void)
 
        xmlInitParser();
 
-       rngparser = xmlRelaxNGNewMemParserCtxt(RRDP_V1_RNG, RRDP_V1_RNG_SIZE);
+       rngparser = xmlRelaxNGNewMemParserCtxt(RRDP_V1_RNG, strlen(RRDP_V1_RNG));
        if (rngparser == NULL) {
                error = pr_op_err("XML parser init error: xmlRelaxNGNewMemParserCtxt() returned NULL");
                goto cleanup_parser;
@@ -91,9 +91,10 @@ relax_ng_parse(const char *path, xml_read_cb cb, void *arg)
        int read;
        int error;
 
+       /* TODO (warning) This uses "XML_CHAR_ENCODING_NONE" */
        reader = xmlNewTextReaderFilename(path);
        if (reader == NULL)
-               return pr_val_err("Couldn't get XML '%s' file.", path);
+               return pr_val_err("Unable to open %s (Cause unavailable).", path);
 
        error = xmlTextReaderRelaxNGSetSchema(reader, schema);
        if (error) {
similarity index 98%
rename from src/xml/relax_ng.h
rename to src/relax_ng.h
index 4a3f780a91fa0bbadb538c8975936915410b210b..71d21bc69fefe7e893ce5e7d0b9d5e121a9e33c1 100644 (file)
@@ -2,7 +2,6 @@
 #define SRC_XML_RELAX_NG_H_
 
 #include <libxml/xmlreader.h>
-#include <string.h>
 
 /*
  * Schema obtained from RFC 8182, converted using the tool rnc2rng
 "  </define>"                                                          \
 "</grammar>"
 
-#define RRDP_V1_RNG_SIZE strlen(RRDP_V1_RNG)
-
 
 int relax_ng_init(void);
 void relax_ng_cleanup(void);
index 33ee85e639c88b5f32ac0b82c53d76dc2a1319d2..67d76f615780182905ffcab25bd833f18b70bac4 100644 (file)
@@ -3,13 +3,11 @@
 #include <errno.h>
 
 #include "alloc.h"
-#include "cert_stack.h"
 #include "log.h"
 #include "resource/ip4.h"
 #include "resource/ip6.h"
-#include "sorted_array.h"
 #include "thread_var.h"
-#include "types/address.h"
+#include "types/sorted_array.h"
 
 /* The resources we extracted from one certificate. */
 struct resources {
@@ -80,20 +78,11 @@ unknown:
        return -1;
 }
 
-static struct resources *
-get_parent_resources(void)
-{
-       return x509stack_peek_resources(validation_certstack(state_retrieve()));
-}
-
 static int
-inherit_aors(struct resources *resources, int family)
+inherit_aors(struct resources *resources, struct resources *parent, int family)
 {
-       struct resources *parent;
-
-       parent = get_parent_resources();
        if (parent == NULL)
-               pr_crit("Parent has no resources.");
+               return pr_val_err("Root certificate is trying to inherit IP resources from a parent.");
 
        switch (family) {
        case AF_INET:
@@ -120,15 +109,13 @@ inherit_aors(struct resources *resources, int family)
 }
 
 static int
-add_prefix4(struct resources *resources, IPAddress_t *addr)
+add_prefix4(struct resources *resources, struct resources *parent,
+    IPAddress_t *addr)
 {
-       struct resources *parent;
        struct ipv4_prefix prefix;
        int error;
 
-       parent = get_parent_resources();
-
-       if ((parent != NULL) && (resources->ip4s == parent->ip4s))
+       if (parent && (resources->ip4s == parent->ip4s))
                return pr_val_err("Certificate defines IPv4 prefixes while also inheriting his parent's.");
 
        error = prefix4_decode(addr, &prefix);
@@ -162,15 +149,13 @@ add_prefix4(struct resources *resources, IPAddress_t *addr)
 }
 
 static int
-add_prefix6(struct resources *resources, IPAddress_t *addr)
+add_prefix6(struct resources *resources, struct resources *parent,
+    IPAddress_t *addr)
 {
-       struct resources *parent;
        struct ipv6_prefix prefix;
        int error;
 
-       parent = get_parent_resources();
-
-       if ((parent != NULL) && (resources->ip6s == parent->ip6s))
+       if (parent && (resources->ip6s == parent->ip6s))
                return pr_val_err("Certificate defines IPv6 prefixes while also inheriting his parent's.");
 
        error = prefix6_decode(addr, &prefix);
@@ -204,13 +189,14 @@ add_prefix6(struct resources *resources, IPAddress_t *addr)
 }
 
 static int
-add_prefix(struct resources *resources, int family, IPAddress_t *addr)
+add_prefix(struct resources *resources, struct resources *parent,
+    int family, IPAddress_t *addr)
 {
        switch (family) {
        case AF_INET:
-               return add_prefix4(resources, addr);
+               return add_prefix4(resources, parent, addr);
        case AF_INET6:
-               return add_prefix6(resources, addr);
+               return add_prefix6(resources, parent, addr);
        }
 
        pr_crit("Unknown address family '%d'", family);
@@ -218,14 +204,12 @@ add_prefix(struct resources *resources, int family, IPAddress_t *addr)
 }
 
 static int
-add_range4(struct resources *resources, IPAddressRange_t *input)
+add_range4(struct resources *resources, struct resources *parent,
+    IPAddressRange_t *input)
 {
-       struct resources *parent;
        struct ipv4_range range;
        int error;
 
-       parent = get_parent_resources();
-
        if (parent && (resources->ip4s == parent->ip4s))
                return pr_val_err("Certificate defines IPv4 ranges while also inheriting his parent's.");
 
@@ -261,15 +245,13 @@ add_range4(struct resources *resources, IPAddressRange_t *input)
 }
 
 static int
-add_range6(struct resources *resources, IPAddressRange_t *input)
+add_range6(struct resources *resources, struct resources *parent,
+    IPAddressRange_t *input)
 {
-       struct resources *parent;
        struct ipv6_range range;
        int error;
 
-       parent = get_parent_resources();
-
-       if ((parent != NULL) && (resources->ip6s == parent->ip6s))
+       if (parent && (resources->ip6s == parent->ip6s))
                return pr_val_err("Certificate defines IPv6 ranges while also inheriting his parent's.");
 
        error = range6_decode(input, &range);
@@ -304,13 +286,14 @@ add_range6(struct resources *resources, IPAddressRange_t *input)
 }
 
 static int
-add_range(struct resources *resources, int family, IPAddressRange_t *range)
+add_range(struct resources *resources, struct resources *parent,
+    int family, IPAddressRange_t *range)
 {
        switch (family) {
        case AF_INET:
-               return add_range4(resources, range);
+               return add_range4(resources, parent, range);
        case AF_INET6:
-               return add_range6(resources, range);
+               return add_range6(resources, parent, range);
        }
 
        pr_crit("Unknown address family '%d'", family);
@@ -318,7 +301,7 @@ add_range(struct resources *resources, int family, IPAddressRange_t *range)
 }
 
 static int
-add_aors(struct resources *resources, int family,
+add_aors(struct resources *resources, struct resources *parent, int family,
     struct IPAddressChoice__addressesOrRanges *aors)
 {
        struct IPAddressOrRange *aor;
@@ -334,13 +317,13 @@ add_aors(struct resources *resources, int family,
                aor = aors->list.array[i];
                switch (aor->present) {
                case IPAddressOrRange_PR_addressPrefix:
-                       error = add_prefix(resources, family,
+                       error = add_prefix(resources, parent, family,
                            &aor->choice.addressPrefix);
                        if (error)
                                return error;
                        break;
                case IPAddressOrRange_PR_addressRange:
-                       error = add_range(resources, family,
+                       error = add_range(resources, parent, family,
                            &aor->choice.addressRange);
                        if (error)
                                return error;
@@ -356,7 +339,8 @@ add_aors(struct resources *resources, int family,
 }
 
 int
-resources_add_ip(struct resources *resources, struct IPAddressFamily *obj)
+resources_add_ip(struct resources *resources, struct resources *parent,
+    struct IPAddressFamily *obj)
 {
        int family;
 
@@ -368,9 +352,9 @@ resources_add_ip(struct resources *resources, struct IPAddressFamily *obj)
        case IPAddressChoice_PR_NOTHING:
                break;
        case IPAddressChoice_PR_inherit:
-               return inherit_aors(resources, family);
+               return inherit_aors(resources, parent, family);
        case IPAddressChoice_PR_addressesOrRanges:
-               return add_aors(resources, family,
+               return add_aors(resources, parent, family,
                    &obj->ipAddressChoice.choice.addressesOrRanges);
        }
 
@@ -380,13 +364,10 @@ resources_add_ip(struct resources *resources, struct IPAddressFamily *obj)
 }
 
 static int
-inherit_asiors(struct resources *resources)
+inherit_asiors(struct resources *resources, struct resources *parent)
 {
-       struct resources *parent;
-
-       parent = get_parent_resources();
        if (parent == NULL)
-               pr_crit("Parent has no resources.");
+               return pr_val_err("Root certificate is trying to inherit AS resources from a parent.");
 
        if (resources->asns != NULL)
                return pr_val_err("Certificate inherits ASN resources while also defining others of its own.");
@@ -463,15 +444,13 @@ add_asn(struct resources *resources, struct asn_range const *asns,
 }
 
 static int
-add_asior(struct resources *resources, struct ASIdOrRange *obj)
+add_asior(struct resources *resources, struct resources *parent,
+    struct ASIdOrRange *obj)
 {
-       struct resources *parent;
        struct asn_range asns;
        int error;
 
-       parent = get_parent_resources();
-
-       if ((parent != NULL) && (resources->asns == parent->asns))
+       if (parent && (resources->asns == parent->asns))
                return pr_val_err("Certificate defines ASN resources while also inheriting his parent's.");
 
        switch (obj->present) {
@@ -499,7 +478,8 @@ add_asior(struct resources *resources, struct ASIdOrRange *obj)
 }
 
 static int
-add_asiors(struct resources *resources, struct ASIdentifiers *ids)
+add_asiors(struct resources *resources, struct resources *parent,
+    struct ASIdentifiers *ids)
 {
        struct ASIdentifierChoice__asIdsOrRanges *iors;
        int i;
@@ -513,7 +493,7 @@ add_asiors(struct resources *resources, struct ASIdentifiers *ids)
                return pr_val_err("AS extension's set of AS number records is empty.");
 
        for (i = 0; i < iors->list.count; i++) {
-               error = add_asior(resources, iors->list.array[i]);
+               error = add_asior(resources, parent, iors->list.array[i]);
                if (error)
                        return error;
        }
@@ -522,8 +502,8 @@ add_asiors(struct resources *resources, struct ASIdentifiers *ids)
 }
 
 int
-resources_add_asn(struct resources *resources, struct ASIdentifiers *ids,
-    bool allow_inherit)
+resources_add_asn(struct resources *resources, struct resources *parent,
+    struct ASIdentifiers *ids, bool allow_inherit)
 {
        if (ids->asnum == NULL)
                return pr_val_err("ASN extension lacks 'asnum' element.");
@@ -535,9 +515,9 @@ resources_add_asn(struct resources *resources, struct ASIdentifiers *ids,
                if (!allow_inherit)
                        return pr_val_err("ASIdentifierChoice %u isn't allowed",
                            ids->asnum->present);
-               return inherit_asiors(resources);
+               return inherit_asiors(resources, parent);
        case ASIdentifierChoice_PR_asIdsOrRanges:
-               return add_asiors(resources, ids);
+               return add_asiors(resources, parent, ids);
        case ASIdentifierChoice_PR_NOTHING:
                break;
        }
@@ -571,12 +551,6 @@ resources_contains_ipv6(struct resources *res, struct ipv6_prefix const *prefix)
        return res6_contains_prefix(res->ip6s, prefix);
 }
 
-enum rpki_policy
-resources_get_policy(struct resources *res)
-{
-       return res->policy;
-}
-
 void
 resources_set_policy(struct resources *res, enum rpki_policy policy)
 {
index 661e3b7bae0280e1704f79dc803ef496ac557085..5d811c23057de7f4694a088eb0bd2339f6789ba9 100644 (file)
@@ -26,15 +26,16 @@ struct resources;
 struct resources *resources_create(enum rpki_policy, bool);
 void resources_destroy(struct resources *);
 
-int resources_add_ip(struct resources *, struct IPAddressFamily *);
-int resources_add_asn(struct resources *, struct ASIdentifiers *, bool);
+int resources_add_ip(struct resources *, struct resources *,
+    struct IPAddressFamily *);
+int resources_add_asn(struct resources *, struct resources *,
+    struct ASIdentifiers *, bool);
 
 bool resources_empty(struct resources *);
 bool resources_contains_asns(struct resources *, struct asn_range const *);
 bool resources_contains_ipv4(struct resources *, struct ipv4_prefix const *);
 bool resources_contains_ipv6(struct resources *, struct ipv6_prefix const *);
 
-enum rpki_policy resources_get_policy(struct resources *);
 void resources_set_policy(struct resources *, enum rpki_policy);
 
 int resources_foreach_asn(struct resources *, foreach_asn_cb, void *);
index cdc513463070adc6cb3671672ee1c7bf212a9f9f..43303a87465ad2dea39dab54c43fcb2e269aedf0 100644 (file)
@@ -1,7 +1,6 @@
 #include "resource/asn.h"
 
-#include "log.h"
-#include "sorted_array.h"
+#include "types/sorted_array.h"
 
 struct asn_cb {
        foreach_asn_cb cb;
index 058cbd33be2be785dcf228761215ed827d53d03d..696b146112e069c8eec415bae81a7d86b1375566 100644 (file)
@@ -3,8 +3,7 @@
 
 #include <stdbool.h>
 
-#include "as_number.h"
-#include "asn1/asn1c/ASId.h"
+#include "types/asn.h"
 
 /*
  * Implementation note: This is just a casted struct sorted_array.
index 4bab3fa3c5107057ffea1287d4164b241d25d657..57ec2a16ae5a2dc8ed0c028716a00c5608e5d6b8 100644 (file)
@@ -1,6 +1,6 @@
 #include "resource/ip4.h"
 
-#include "sorted_array.h"
+#include "types/sorted_array.h"
 
 struct r4_node {
        uint32_t min; /* This is an IPv4 address in host byte order */
index aad0aaa10360e57a330b1ca15899bfcb0955ccf7..6b6b96ade6b7ed3f4138221dbb5cf19038ec8b91 100644 (file)
@@ -1,6 +1,6 @@
 #include "resource/ip6.h"
 
-#include "sorted_array.h"
+#include "types/sorted_array.h"
 
 static int
 addr_cmp(struct in6_addr const *a, struct in6_addr const *b)
diff --git a/src/rpp.c b/src/rpp.c
deleted file mode 100644 (file)
index 85d5bb7..0000000
--- a/src/rpp.c
+++ /dev/null
@@ -1,261 +0,0 @@
-#include "rpp.h"
-
-#include "alloc.h"
-#include "cert_stack.h"
-#include "data_structure/array_list.h"
-#include "log.h"
-#include "object/certificate.h"
-#include "object/crl.h"
-#include "object/ghostbusters.h"
-#include "object/roa.h"
-#include "thread_var.h"
-#include "types/map.h"
-
-/** A Repository Publication Point (RFC 6481), as described by some manifest. */
-struct rpp {
-       struct map_list certs; /* Certificates */
-
-       /*
-        * map NULL implies stack NULL and error 0.
-        * If map is set, stack might or might not be set.
-        * error is only relevant when map is set and stack is unset.
-        */
-       struct { /* Certificate Revocation List */
-               struct cache_mapping *map;
-               /*
-                * CRL in libcrypto-friendly form.
-                * Initialized lazily; access via rpp_crl().
-                */
-               STACK_OF(X509_CRL) *stack;
-               /*
-                * Some error code if we already tried to initialize @stack but
-                * failed. Prevents us from wasting time doing it again, and
-                * flooding the log with identical error messages.
-                */
-               int error;
-       } crl;
-
-       /* The Manifest is not needed for now. */
-
-       struct map_list roas; /* Route Origin Attestations */
-
-       struct map_list ghostbusters;
-
-       /*
-        * Note that the reference counting functions are not prepared for
-        * multithreading, because this is not atomic.
-        */
-       unsigned int references;
-};
-
-struct rpp *
-rpp_create(void)
-{
-       struct rpp *result;
-
-       result = pmalloc(sizeof(struct rpp));
-
-       maps_init(&result->certs);
-       result->crl.map = NULL;
-       result->crl.stack = NULL;
-       result->crl.error = 0;
-       maps_init(&result->roas);
-       maps_init(&result->ghostbusters);
-       result->references = 1;
-
-       return result;
-}
-
-void
-rpp_refget(struct rpp *pp)
-{
-       pp->references++;
-}
-
-void
-rpp_refput(struct rpp *pp)
-{
-       pp->references--;
-       if (pp->references == 0) {
-               maps_cleanup(&pp->certs);
-               if (pp->crl.map != NULL)
-                       map_refput(pp->crl.map);
-               if (pp->crl.stack != NULL)
-                       sk_X509_CRL_pop_free(pp->crl.stack, X509_CRL_free);
-               maps_cleanup(&pp->roas);
-               maps_cleanup(&pp->ghostbusters);
-               free(pp);
-       }
-}
-
-/** Steals ownership of @map. */
-void
-rpp_add_cert(struct rpp *pp, struct cache_mapping *map)
-{
-       maps_add(&pp->certs, map);
-}
-
-/** Steals ownership of @map. */
-void
-rpp_add_roa(struct rpp *pp, struct cache_mapping *map)
-{
-       maps_add(&pp->roas, map);
-}
-
-/** Steals ownership of @map. */
-void
-rpp_add_ghostbusters(struct rpp *pp, struct cache_mapping *map)
-{
-       maps_add(&pp->ghostbusters, map);
-}
-
-/** Steals ownership of @map. */
-int
-rpp_add_crl(struct rpp *pp, struct cache_mapping *map)
-{
-       /* rfc6481#section-2.2 */
-       if (pp->crl.map)
-               return pr_val_err("Repository Publication Point has more than one CRL.");
-
-       pp->crl.map = map;
-       return 0;
-}
-
-struct cache_mapping *
-rpp_get_crl(struct rpp const *pp)
-{
-       return pp->crl.map;
-}
-
-static int
-add_crl_to_stack(struct rpp *pp, STACK_OF(X509_CRL) *crls)
-{
-       X509_CRL *crl;
-       int error;
-       int idx;
-
-       fnstack_push_map(pp->crl.map);
-
-       error = crl_load(pp->crl.map, &crl);
-       if (error)
-               goto end;
-
-       idx = sk_X509_CRL_push(crls, crl);
-       if (idx <= 0) {
-               error = val_crypto_err("Could not add CRL to a CRL stack");
-               X509_CRL_free(crl);
-               goto end;
-       }
-
-end:
-       fnstack_pop();
-       return error;
-}
-
-/**
- * Returns the pp's CRL in stack form (which is how libcrypto functions want
- * it).
- * The stack belongs to @pp and should not be released. Can be NULL, in which
- * case you're currently validating the TA (since it lacks governing CRL).
- */
-int
-rpp_crl(struct rpp *pp, STACK_OF(X509_CRL) **result)
-{
-       STACK_OF(X509_CRL) *stack;
-
-       /* -- Short circuits -- */
-       if (pp == NULL) {
-               /* No pp = currently validating TA. There's no CRL. */
-               *result = NULL;
-               return 0;
-       }
-       if (pp->crl.map == NULL) {
-               /* rpp_crl() assumes the rpp has been populated already. */
-               pr_crit("RPP lacks a CRL.");
-       }
-       if (pp->crl.stack != NULL) {
-               /* Result already cached. */
-               *result = pp->crl.stack;
-               return 0;
-       }
-       if (pp->crl.error) {
-               /* Pretend that we did everything below. */
-               return pp->crl.error;
-       }
-
-       /* -- Actually initialize pp->crl.stack. -- */
-       stack = sk_X509_CRL_new_null();
-       if (stack == NULL)
-               enomem_panic();
-       pp->crl.error = add_crl_to_stack(pp, stack);
-       if (pp->crl.error) {
-               sk_X509_CRL_pop_free(stack, X509_CRL_free);
-               return pp->crl.error;
-       }
-
-       pp->crl.stack = stack;
-       *result = stack;
-       return 0;
-}
-
-static int
-__cert_traverse(struct rpp *pp)
-{
-       struct validation *state;
-       struct cert_stack *certstack;
-       ssize_t i;
-       struct deferred_cert deferred;
-
-       if (pp->certs.len == 0)
-               return 0;
-
-       state = state_retrieve();
-       certstack = validation_certstack(state);
-
-       deferred.pp = pp;
-       /*
-        * The for is inverted, to achieve FIFO behavior since the separator.
-        * Not really important; it simply makes the traversal order more
-        * intuitive.
-        */
-       for (i = pp->certs.len - 1; i >= 0; i--) {
-               deferred.map = pp->certs.array[i];
-               deferstack_push(certstack, &deferred);
-       }
-
-       return 0;
-}
-
-/**
- * Traverses through all of @pp's known files, validating them.
- */
-void
-rpp_traverse(struct rpp *pp)
-{
-       struct cache_mapping **map;
-
-       /*
-        * A subtree should not invalidate the rest of the tree, so error codes
-        * are ignored.
-        * (Errors log messages anyway.)
-        */
-
-       /*
-        * Certificates cannot be validated now, because then the algorithm
-        * would be recursive.
-        * Store them in the defer stack (see cert_stack.h), will get back to
-        * them later.
-        */
-       __cert_traverse(pp);
-
-       /* Validate ROAs, apply validation_handler on them. */
-       ARRAYLIST_FOREACH(&pp->roas, map)
-               roa_traverse(*map, pp);
-
-       /*
-        * We don't do much with the ghostbusters right now.
-        * Just validate them.
-        */
-       ARRAYLIST_FOREACH(&pp->ghostbusters, map)
-               ghostbusters_traverse(*map, pp);
-}
diff --git a/src/rpp.h b/src/rpp.h
deleted file mode 100644 (file)
index 5f78a2b..0000000
--- a/src/rpp.h
+++ /dev/null
@@ -1,25 +0,0 @@
-#ifndef SRC_RPP_H_
-#define SRC_RPP_H_
-
-#include <openssl/safestack.h>
-#include <openssl/x509.h>
-
-#include "types/map.h"
-
-struct rpp;
-
-struct rpp *rpp_create(void);
-void rpp_refget(struct rpp *pp);
-void rpp_refput(struct rpp *pp);
-
-void rpp_add_cert(struct rpp *, struct cache_mapping *);
-int rpp_add_crl(struct rpp *, struct cache_mapping *);
-void rpp_add_roa(struct rpp *, struct cache_mapping *);
-void rpp_add_ghostbusters(struct rpp *, struct cache_mapping *);
-
-struct cache_mapping *rpp_get_crl(struct rpp const *);
-int rpp_crl(struct rpp *, STACK_OF(X509_CRL) **);
-
-void rpp_traverse(struct rpp *);
-
-#endif /* SRC_RPP_H_ */
index dc6a7a9401daeaa2952d8e1be3f2ed163db0e93e..affd1b95d60ed6a0e25990c0ff33d0ee86fb7e88 100644 (file)
@@ -1,21 +1,25 @@
 #include "rrdp.h"
 
-#include <ctype.h>
 #include <openssl/bn.h>
-#include <openssl/evp.h>
+#include <openssl/sha.h>
 #include <sys/queue.h>
 
-#include "alloc.h"
+#include "base64.h"
+#include "cache.h"
+#include "cachetmp.h"
 #include "common.h"
 #include "config.h"
 #include "file.h"
+#include "hash.h"
+#include "http.h"
 #include "json_util.h"
 #include "log.h"
+#include "relax_ng.h"
 #include "thread_var.h"
-#include "cache/local_cache.h"
-#include "crypto/base64.h"
-#include "crypto/hash.h"
-#include "xml/relax_ng.h"
+#include "types/arraylist.h"
+#include "types/path.h"
+#include "types/url.h"
+#include "types/uthash.h"
 
 /* RRDP's XML namespace */
 #define RRDP_NAMESPACE         "http://www.ripe.net/rpki/rrdp"
 #define RRDP_ATTR_URI          "uri"
 #define RRDP_ATTR_HASH         "hash"
 
-/* These are supposed to be unbounded */
 struct rrdp_serial {
        BIGNUM *num;
-       char *str; /* String version of @num. */
+       char *str;                      /* String version of @num. */
 };
 
 struct rrdp_session {
@@ -45,8 +48,36 @@ struct rrdp_session {
        struct rrdp_serial serial;
 };
 
+#define RRDP_HASH_LEN SHA256_DIGEST_LENGTH
+
+struct rrdp_hash {
+       unsigned char bytes[RRDP_HASH_LEN];
+       STAILQ_ENTRY(rrdp_hash) hook;
+};
+
+struct cache_file {
+       struct cache_mapping map;
+       UT_hash_handle hh;              /* Hash table hook */
+};
+
+/* Subset of the notification that is relevant to the TAL's cachefile */
+struct rrdp_state {
+       struct rrdp_session session;
+
+       struct cache_file *files;       /* Hash table */
+       struct cache_sequence seq;
+
+       /*
+        * The 1st one contains the hash of the session.serial delta.
+        * The 2nd one contains the hash of the session.serial - 1 delta.
+        * The 3rd one contains the hash of the session.serial - 2 delta.
+        * And so on.
+        */
+       STAILQ_HEAD(, rrdp_hash) delta_hashes;
+};
+
 struct file_metadata {
-       struct cache_mapping *uri;
+       char *uri;
        unsigned char *hash; /* Array. Sometimes omitted. */
        size_t hash_len;
 };
@@ -65,7 +96,7 @@ struct update_notification {
        struct rrdp_session session;
        struct file_metadata snapshot;
        struct notification_deltas deltas;
-       struct cache_mapping *map;
+       char const *url;
 };
 
 /* A deserialized <publish> tag, from a snapshot or delta. */
@@ -80,37 +111,9 @@ struct withdraw {
        struct file_metadata meta;
 };
 
-/* Helpful context while reading a snapshot or delta. */
-struct rrdp_ctx {
-       struct cache_mapping *notif;
-       struct rrdp_session session;
-};
-
-typedef enum {
-       HR_MANDATORY,
-       HR_OPTIONAL,
-       HR_IGNORE,
-} hash_requirement;
-
-#define RRDP_HASH_LEN SHA256_DIGEST_LENGTH
-
-struct rrdp_hash {
-       unsigned char bytes[RRDP_HASH_LEN];
-       STAILQ_ENTRY(rrdp_hash) hook;
-};
-
-/*
- * Subset of the notification that is relevant to the TAL's cachefile.
- */
-struct cachefile_notification {
-       struct rrdp_session session;
-       /*
-        * The 1st one contains the hash of the session.serial delta.
-        * The 2nd one contains the hash of the session.serial - 1 delta.
-        * The 3rd one contains the hash of the session.serial - 2 delta.
-        * And so on.
-        */
-       STAILQ_HEAD(, rrdp_hash) delta_hashes;
+struct parser_args {
+       struct rrdp_session *session;
+       struct rrdp_state *state;
 };
 
 static BIGNUM *
@@ -139,11 +142,19 @@ session_cleanup(struct rrdp_session *meta)
        free(meta->serial.str);
 }
 
+static struct cache_file *
+state_find_file(struct rrdp_state *state, char const *url, size_t len)
+{
+       struct cache_file *file;
+       HASH_FIND(hh, state->files, url, len, file);
+       return file;
+}
+
 static void
 metadata_cleanup(struct file_metadata *meta)
 {
+       free(meta->uri);
        free(meta->hash);
-       map_refput(meta->uri);
 }
 
 static void
@@ -154,13 +165,12 @@ notification_delta_cleanup(struct notification_delta *delta)
 }
 
 static void
-update_notification_init(struct update_notification *notif,
-    struct cache_mapping *map)
+update_notification_init(struct update_notification *notif, char const *url)
 {
        memset(&notif->session, 0, sizeof(notif->session));
        memset(&notif->snapshot, 0, sizeof(notif->snapshot));
        notification_deltas_init(&notif->deltas);
-       notif->map = map_refget(map);
+       notif->url = url;
 }
 
 static void
@@ -168,7 +178,6 @@ __update_notification_cleanup(struct update_notification *notif)
 {
        metadata_cleanup(&notif->snapshot);
        notification_deltas_cleanup(&notif->deltas, notification_delta_cleanup);
-       map_refput(notif->map);
 }
 
 static void
@@ -179,10 +188,10 @@ update_notification_cleanup(struct update_notification *notif)
 }
 
 static int
-validate_hash(struct file_metadata *meta)
+validate_hash(struct file_metadata *meta, char const *path)
 {
-       return hash_validate_file(hash_get_sha256(), meta->uri, meta->hash,
-           meta->hash_len);
+       return hash_validate_file(hash_get_sha256(), path,
+           meta->hash, meta->hash_len);
 }
 
 static int
@@ -254,22 +263,20 @@ parse_string(xmlTextReaderPtr reader, char const *attr)
        return result;
 }
 
-static int
-parse_uri(xmlTextReaderPtr reader, struct cache_mapping *notif,
-    struct cache_mapping **result)
+static char *
+parse_uri(xmlTextReaderPtr reader)
 {
        xmlChar *xmlattr;
-       int error;
+       char *result;
 
        xmlattr = parse_string(reader, RRDP_ATTR_URI);
        if (xmlattr == NULL)
-               return -EINVAL;
+               return NULL;
 
-       error = map_create(result, (notif != NULL) ? MAP_CAGED : MAP_TMP, notif,
-                          (char const *)xmlattr);
+       result = pstrdup((char const *)xmlattr);
 
        xmlFree(xmlattr);
-       return error;
+       return result;
 }
 
 static unsigned int
@@ -318,25 +325,18 @@ fail:
 }
 
 static int
-parse_hash(xmlTextReaderPtr reader, hash_requirement hr,
-    unsigned char **result, size_t *result_len)
+parse_hash(xmlTextReaderPtr reader, unsigned char **result, size_t *result_len)
 {
        xmlChar *xmlattr;
        int error;
 
-       if (hr == HR_IGNORE)
-               return 0;
-
        xmlattr = xmlTextReaderGetAttribute(reader, BAD_CAST RRDP_ATTR_HASH);
        if (xmlattr == NULL)
-               return (hr == HR_MANDATORY)
-                   ? pr_val_err("Tag is missing the '" RRDP_ATTR_HASH "' attribute.")
-                   : 0;
+               return 0;
 
        error = hexstr2sha256(xmlattr, result, result_len);
 
        xmlFree(xmlattr);
-
        if (error)
                return pr_val_err("The '" RRDP_ATTR_HASH "' xml attribute does not appear to be a SHA-256 hash.");
        return 0;
@@ -422,7 +422,7 @@ parse_session(xmlTextReaderPtr reader, struct rrdp_session *meta)
 }
 
 static int
-validate_session(xmlTextReaderPtr reader, struct rrdp_session *expected)
+validate_session(xmlTextReaderPtr reader, struct parser_args *args)
 {
        struct rrdp_session actual = { 0 };
        int error;
@@ -431,15 +431,15 @@ validate_session(xmlTextReaderPtr reader, struct rrdp_session *expected)
        if (error)
                return error;
 
-       if (strcmp(expected->session_id, actual.session_id) != 0) {
+       if (strcmp(args->session->session_id, actual.session_id) != 0) {
                error = pr_val_err("File session id [%s] doesn't match notification's session id [%s]",
-                   expected->session_id, actual.session_id);
+                   args->session->session_id, actual.session_id);
                goto end;
        }
 
-       if (BN_cmp(actual.serial.num, expected->serial.num) != 0) {
+       if (BN_cmp(actual.serial.num, args->session->serial.num) != 0) {
                error = pr_val_err("File serial [%s] doesn't match notification's serial [%s]",
-                   actual.serial.str, expected->serial.str);
+                   actual.serial.str, args->session->serial.str);
                goto end;
        }
 
@@ -455,20 +455,19 @@ end:
  * 2. "hash" (optional, depending on @hr)
  */
 static int
-parse_file_metadata(xmlTextReaderPtr reader, struct cache_mapping *notif,
-    hash_requirement hr, struct file_metadata *meta)
+parse_file_metadata(xmlTextReaderPtr reader, struct file_metadata *meta)
 {
        int error;
 
        memset(meta, 0, sizeof(*meta));
 
-       error = parse_uri(reader, notif, &meta->uri);
-       if (error)
-               return error;
+       meta->uri = parse_uri(reader);
+       if (meta->uri == NULL)
+               return -EINVAL;
 
-       error = parse_hash(reader, hr, &meta->hash, &meta->hash_len);
+       error = parse_hash(reader, &meta->hash, &meta->hash_len);
        if (error) {
-               map_refput(meta->uri);
+               free(meta->uri);
                meta->uri = NULL;
                return error;
        }
@@ -476,24 +475,23 @@ parse_file_metadata(xmlTextReaderPtr reader, struct cache_mapping *notif,
        return 0;
 }
 
+/* Does not clean @tag on failure. */
 static int
-parse_publish(xmlTextReaderPtr reader, struct cache_mapping *notif,
-    hash_requirement hr, struct publish *tag)
+parse_publish(xmlTextReaderPtr reader, struct publish *tag)
 {
        xmlChar *base64_str;
        int error;
 
-       error = parse_file_metadata(reader, notif, hr, &tag->meta);
+       error = parse_file_metadata(reader, &tag->meta);
        if (error)
                return error;
 
        /* Read the text */
-       if (xmlTextReaderRead(reader) != 1) {
+       if (xmlTextReaderRead(reader) != 1)
                return pr_val_err(
                    "Couldn't read publish content of element '%s'",
-                   map_get_url(tag->meta.uri)
+                   tag->meta.uri
                );
-       }
 
        base64_str = parse_string(reader, NULL);
        if (base64_str == NULL)
@@ -501,89 +499,141 @@ parse_publish(xmlTextReaderPtr reader, struct cache_mapping *notif,
        if (!base64_decode((char *)base64_str, 0, &tag->content, &tag->content_len))
                error = pr_val_err("Cannot decode publish tag's base64.");
        xmlFree(base64_str);
-       if (error)
-               return error;
 
-       /* rfc8181#section-2.2 but considering optional hash */
-       return (tag->meta.hash != NULL) ? validate_hash(&tag->meta) : 0;
+       return error;
 }
 
+/* Does not clean @tag on failure. */
 static int
-parse_withdraw(xmlTextReaderPtr reader, struct cache_mapping *notif,
-    struct withdraw *tag)
+parse_withdraw(xmlTextReaderPtr reader, struct withdraw *tag)
 {
        int error;
 
-       error = parse_file_metadata(reader, notif, HR_MANDATORY, &tag->meta);
+       error = parse_file_metadata(reader, &tag->meta);
        if (error)
                return error;
 
-       return validate_hash(&tag->meta);
+       if (!tag->meta.hash)
+               return pr_val_err("Withdraw '%s' is missing a hash.",
+                   tag->meta.uri);
+
+       return 0;
 }
 
 static int
-write_file(struct cache_mapping *map, unsigned char *content, size_t content_len)
+handle_publish(xmlTextReaderPtr reader, struct parser_args *args)
 {
-       FILE *out;
-       size_t written;
+       struct publish tag = { 0 };
+       struct cache_file *file;
+       size_t len;
        int error;
 
-       error = mkdir_p(map_get_path(map), false);
+       error = parse_publish(reader, &tag);
        if (error)
-               return error;
+               goto end;
 
-       error = file_write(map_get_path(map), "wb", &out);
-       if (error)
-               return error;
+       pr_val_debug("Publish %s", logv_filename(tag.meta.uri));
 
-       written = fwrite(content, sizeof(unsigned char), content_len, out);
-       file_close(out);
+       len = strlen(tag.meta.uri);
+       file = state_find_file(args->state, tag.meta.uri, len);
 
-       if (written != content_len) {
-               return pr_val_err(
-                   "Couldn't write file '%s' (error code not available)",
-                   map_get_path(map)
-               );
-       }
+       /* rfc8181#section-2.2 */
+       if (file) {
+               if (tag.meta.hash == NULL) {
+                       // XXX watch out for this in the log before release
+                       error = pr_val_err("RRDP desync: "
+                           "<publish> is attempting to create '%s', "
+                           "but the file is already cached.",
+                           tag.meta.uri);
+                       goto end;
+               }
 
-       return 0;
-}
+               error = validate_hash(&tag.meta, file->map.path);
+               if (error)
+                       goto end;
 
-/* Remove a local file and its directory tree (if empty) */
-static int
-delete_file(struct cache_mapping *map)
-{
-       /* Delete parent dirs only if empty. */
-       return delete_dir_recursive_bottom_up(map_get_path(map));
-}
+               /*
+                * Reminder: This is needed because the file might be
+                * hard-linked. Our repo file write should not propagate
+                * to the fallback.
+                */
+               if (remove(file->map.path) < 0) {
+                       error = errno;
+                       pr_val_err("Cannot delete %s: %s",
+                           file->map.path, strerror(error));
+                       if (error != ENOENT)
+                               goto end;
+               }
 
-static int
-handle_publish(xmlTextReaderPtr reader, struct cache_mapping *notif,
-    hash_requirement hr)
-{
-       struct publish tag = { 0 };
-       int error;
+       } else {
+               if (tag.meta.hash != NULL) {
+                       // XXX watch out for this in the log before release
+                       error = pr_val_err("RRDP desync: "
+                           "<publish> is attempting to overwrite '%s', "
+                           "but the file is absent in the cache.",
+                           tag.meta.uri);
+                       goto end;
+               }
+
+               file = pzalloc(sizeof(struct cache_file));
+               file->map.url = pstrdup(tag.meta.uri);
+               file->map.path = cseq_next(&args->state->seq);
+               if (!file->map.path) {
+                       free(file->map.url);
+                       free(file);
+                       error = -EINVAL;
+                       goto end;
+               }
 
-       error = parse_publish(reader, notif, hr, &tag);
-       if (!error)
-               error = write_file(tag.meta.uri, tag.content, tag.content_len);
+               HASH_ADD_KEYPTR(hh, args->state->files, file->map.url, len, file);
+       }
+
+       error = file_write_full(file->map.path, tag.content, tag.content_len);
 
-       metadata_cleanup(&tag.meta);
+end:   metadata_cleanup(&tag.meta);
        free(tag.content);
        return error;
 }
 
 static int
-handle_withdraw(xmlTextReaderPtr reader, struct cache_mapping *notif)
+handle_withdraw(xmlTextReaderPtr reader, struct parser_args *args)
 {
        struct withdraw tag = { 0 };
+       struct cache_file *file;
+       size_t len;
        int error;
 
-       error = parse_withdraw(reader, notif, &tag);
-       if (!error)
-               error = delete_file(tag.meta.uri);
+       error = parse_withdraw(reader, &tag);
+       if (error)
+               goto end;
 
-       metadata_cleanup(&tag.meta);
+       pr_val_debug("Withdraw %s", logv_filename(tag.meta.uri));
+
+       len = strlen(tag.meta.uri);
+       file = state_find_file(args->state, tag.meta.uri, len);
+
+       if (!file) {
+               error = pr_val_err("Broken RRDP: "
+                   "<withdraw> is attempting to delete unknown file '%s'.",
+                   tag.meta.uri);
+               goto end;
+       }
+
+       error = validate_hash(&tag.meta, file->map.path);
+       if (error)
+               goto end;
+
+       if (remove(file->map.path) < 0) {
+               pr_val_warn("Cannot delete %s: %s", file->map.path,
+                   strerror(errno));
+               /* It's fine; keep going. */
+       }
+
+       HASH_DEL(args->state->files, file);
+       map_cleanup(&file->map);
+       free(file);
+
+end:   metadata_cleanup(&tag.meta);
        return error;
 }
 
@@ -593,13 +643,17 @@ parse_notification_snapshot(xmlTextReaderPtr reader,
 {
        int error;
 
-       error = parse_file_metadata(reader, NULL, HR_MANDATORY, &notif->snapshot);
+       error = parse_file_metadata(reader, &notif->snapshot);
        if (error)
                return error;
 
-       if (!map_same_origin(notif->map, notif->snapshot.uri))
-               return pr_val_err("Notification %s and Snapshot %s are not hosted by the same origin.",
-                   map_get_url(notif->map), map_get_url(notif->snapshot.uri));
+       if (!notif->snapshot.hash)
+               return pr_val_err("Snapshot '%s' is missing a hash.",
+                   notif->snapshot.uri);
+
+       if (!url_same_origin(notif->url, notif->snapshot.uri))
+               return pr_val_err("Notification '%s' and Snapshot '%s' are not hosted by the same origin.",
+                   notif->url, notif->snapshot.uri);
 
        return 0;
 }
@@ -608,25 +662,35 @@ static int
 parse_notification_delta(xmlTextReaderPtr reader,
     struct update_notification *notif)
 {
-       struct notification_delta delta;
+       struct notification_delta delta = { 0 };
        int error;
 
        error = parse_serial(reader, &delta.serial);
        if (error)
                return error;
 
-       error = parse_file_metadata(reader, NULL, HR_MANDATORY, &delta.meta);
-       if (error) {
-               serial_cleanup(&delta.serial);
-               return error;
+       error = parse_file_metadata(reader, &delta.meta);
+       if (error)
+               goto fail;
+
+       if (!delta.meta.hash) {
+               error = pr_val_err("Delta '%s' is missing a hash.",
+                   delta.meta.uri);
+               goto fail;
        }
 
-       if (!map_same_origin(notif->map, delta.meta.uri))
-               return pr_val_err("Notification %s and Delta %s are not hosted by the same origin.",
-                   map_get_url(notif->map), map_get_url(delta.meta.uri));
+       if (!url_same_origin(notif->url, delta.meta.uri)) {
+               error = pr_val_err("Notification %s and Delta %s are not hosted by the same origin.",
+                   notif->url, delta.meta.uri);
+               goto fail;
+       }
 
        notification_deltas_add(&notif->deltas, &delta);
        return 0;
+
+fail:  serial_cleanup(&delta.serial);
+       metadata_cleanup(&delta.meta);
+       return error;
 }
 
 static int
@@ -747,32 +811,23 @@ xml_read_notif(xmlTextReaderPtr reader, void *arg)
 }
 
 static int
-parse_notification(struct cache_mapping *map, struct update_notification *result)
+parse_notification(char const *url, char const *path,
+    struct update_notification *result)
 {
        int error;
 
-       update_notification_init(result, map);
+       update_notification_init(result, url);
 
-       error = relax_ng_parse(map_get_path(map), xml_read_notif, result);
+       error = relax_ng_parse(path, xml_read_notif, result);
        if (error)
                update_notification_cleanup(result);
 
        return error;
 }
 
-static void
-delete_rpp(struct cache_mapping *notif)
-{
-       char *path = map_get_rrdp_workspace(notif);
-       pr_val_debug("Snapshot: Deleting cached RPP '%s'.", path);
-       file_rm_rf(path);
-       free(path);
-}
-
 static int
 xml_read_snapshot(xmlTextReaderPtr reader, void *arg)
 {
-       struct rrdp_ctx *ctx = arg;
        xmlReaderTypes type;
        xmlChar const *name;
        int error;
@@ -782,9 +837,9 @@ xml_read_snapshot(xmlTextReaderPtr reader, void *arg)
        switch (type) {
        case XML_READER_TYPE_ELEMENT:
                if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_PUBLISH))
-                       error = handle_publish(reader, ctx->notif, HR_IGNORE);
+                       error = handle_publish(reader, arg);
                else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_SNAPSHOT))
-                       error = validate_session(reader, &ctx->session);
+                       error = validate_session(reader, arg);
                else
                        return pr_val_err("Unexpected '%s' element", name);
                if (error)
@@ -798,19 +853,15 @@ xml_read_snapshot(xmlTextReaderPtr reader, void *arg)
 }
 
 static int
-parse_snapshot(struct update_notification *notif)
+parse_snapshot(struct rrdp_session *session, char const *path,
+    struct rrdp_state *state)
 {
-       struct rrdp_ctx ctx;
-
-       ctx.notif = notif->map;
-       ctx.session = notif->session;
-
-       return relax_ng_parse(map_get_path(notif->snapshot.uri),
-           xml_read_snapshot, &ctx);
+       struct parser_args args = { .session = session, .state = state };
+       return relax_ng_parse(path, xml_read_snapshot, &args);
 }
 
 static int
-validate_session_desync(struct cachefile_notification *old_notif,
+validate_session_desync(struct rrdp_state *old_notif,
     struct update_notification *new_notif)
 {
        struct rrdp_hash *old_delta;
@@ -844,44 +895,49 @@ validate_session_desync(struct cachefile_notification *old_notif,
        return 0; /* First $delta_threshold delta hashes match */
 }
 
+/* TODO (performance) Stream instead of caching notifs, snapshots & deltas. */
 static int
-handle_snapshot(struct update_notification *notif)
+dl_tmp(char const *url, char **path)
 {
-       struct cache_mapping *map;
        int error;
 
-       delete_rpp(notif->map);
+       error = cache_tmpfile(path);
+       if (error)
+               return error;
 
-       map = notif->snapshot.uri;
+       error = http_download(url, *path, 0, NULL);
+       if (error)
+               free(*path);
 
-       pr_val_debug("Processing snapshot '%s'.", map_val_get_printable(map));
-       fnstack_push_map(map);
+       return error;
+}
 
-       /*
-        * TODO (performance) Is there a point in caching the snapshot?
-        * Especially considering we delete it 4 lines afterwards.
-        * Maybe stream it instead.
-        * Same for deltas.
-        */
-       error = cache_download(validation_cache(state_retrieve()), map, NULL,
-                              NULL);
+static int
+handle_snapshot(struct update_notification *new, struct rrdp_state *state)
+{
+       char *tmppath;
+       int error;
+
+       pr_val_debug("Processing snapshot.");
+       fnstack_push(new->snapshot.uri);
+
+       error = dl_tmp(new->snapshot.uri, &tmppath);
        if (error)
-               goto end;
-       error = validate_hash(&notif->snapshot);
+               goto end1;
+       error = validate_hash(&new->snapshot, tmppath);
        if (error)
-               goto end;
-       error = parse_snapshot(notif);
-       delete_file(map);
+               goto end2;
+       error = parse_snapshot(&new->session, tmppath, state);
+//     delete_file(tmppath); XXX
 
-end:
-       fnstack_pop();
+end2:  free(tmppath);
+end1:  fnstack_pop();
        return error;
 }
 
 static int
 xml_read_delta(xmlTextReaderPtr reader, void *arg)
 {
-       struct rrdp_ctx *ctx = arg;
        xmlReaderTypes type;
        xmlChar const *name;
        int error;
@@ -891,11 +947,11 @@ xml_read_delta(xmlTextReaderPtr reader, void *arg)
        switch (type) {
        case XML_READER_TYPE_ELEMENT:
                if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_PUBLISH))
-                       error = handle_publish(reader, ctx->notif, HR_OPTIONAL);
+                       error = handle_publish(reader, arg);
                else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_WITHDRAW))
-                       error = handle_withdraw(reader, ctx->notif);
+                       error = handle_withdraw(reader, arg);
                else if (xmlStrEqual(name, BAD_CAST RRDP_ELEM_DELTA))
-                       error = validate_session(reader, &ctx->session);
+                       error = validate_session(reader, arg);
                else
                        return pr_val_err("Unexpected '%s' element", name);
                if (error)
@@ -909,48 +965,51 @@ xml_read_delta(xmlTextReaderPtr reader, void *arg)
 }
 
 static int
-parse_delta(struct update_notification *notif, struct notification_delta *delta)
+parse_delta(struct update_notification *notif, struct notification_delta *delta,
+    char const *path, struct rrdp_state *state)
 {
-       struct rrdp_ctx ctx;
+       struct parser_args args;
+       struct rrdp_session session;
        int error;
 
-       error = validate_hash(&delta->meta);
+       error = validate_hash(&delta->meta, path);
        if (error)
                return error;
 
-       ctx.notif = notif->map;
-       ctx.session.session_id = notif->session.session_id;
-       ctx.session.serial = delta->serial;
+       session.session_id = notif->session.session_id;
+       session.serial = delta->serial;
+       args.session = &session;
+       args.state = state;
 
-       return relax_ng_parse(map_get_path(delta->meta.uri), xml_read_delta,
-           &ctx);
+       return relax_ng_parse(path, xml_read_delta, &args);
 }
 
 static int
-handle_delta(struct update_notification *notif, struct notification_delta *delta)
+handle_delta(struct update_notification *notif,
+    struct notification_delta *delta, struct rrdp_state *state)
 {
-       struct cache_mapping *map;
+       char *tmppath;
        int error;
 
-       map = delta->meta.uri;
+       pr_val_debug("Processing delta '%s'.", delta->meta.uri);
+       fnstack_push(delta->meta.uri);
 
-       pr_val_debug("Processing delta '%s'.", map_val_get_printable(map));
-       fnstack_push_map(map);
-
-       error = cache_download(validation_cache(state_retrieve()), map, NULL, NULL);
+       error = dl_tmp(delta->meta.uri, &tmppath);
        if (error)
                goto end;
-       error = parse_delta(notif, delta);
-       delete_file(map);
+       error = parse_delta(notif, delta, tmppath, state);
+//     delete_file(tmppath); XXX
 
-end:
-       fnstack_pop();
+       free(tmppath);
+end:   fnstack_pop();
        return error;
 }
 
 static int
-handle_deltas(struct update_notification *notif, struct rrdp_serial *serial)
+handle_deltas(struct update_notification *notif, struct rrdp_state *state)
 {
+       struct rrdp_serial *old;
+       struct rrdp_serial *new;
        BIGNUM *diff_bn;
        BN_ULONG diff;
        array_index d;
@@ -961,28 +1020,30 @@ handle_deltas(struct update_notification *notif, struct rrdp_serial *serial)
                return -ENOENT;
        }
 
-       pr_val_debug("Handling RRDP delta serials %s-%s.", serial->str,
-           notif->session.serial.str);
+       old = &state->session.serial;
+       new = &notif->session.serial;
+
+       pr_val_debug("Handling RRDP delta serials %s-%s.", old->str, new->str);
 
        diff_bn = BN_create();
-       if (!BN_sub(diff_bn, notif->session.serial.num, serial->num)) {
+       if (!BN_sub(diff_bn, new->num, old->num)) {
                BN_free(diff_bn);
                return pr_val_err("Could not subtract %s - %s; unknown cause.",
-                   notif->session.serial.str, serial->str);
+                   new->str, old->str);
        }
        if (BN_is_negative(diff_bn)) {
                BN_free(diff_bn);
                return pr_val_err("Cached delta's serial [%s] is larger than Notification's current serial [%s].",
-                   serial->str, notif->session.serial.str);
+                  old->str, new->str);
        }
        diff = BN_get_word(diff_bn);
        BN_free(diff_bn);
        if (diff > config_get_rrdp_delta_threshold() || diff > notif->deltas.len)
                return pr_val_err("Cached RPP is too old. (Cached serial: %s; current serial: %s)",
-                   serial->str, notif->session.serial.str);
+                   old->str, new->str);
 
        for (d = notif->deltas.len - diff; d < notif->deltas.len; d++) {
-               error = handle_delta(notif, &notif->deltas.array[d]);
+               error = handle_delta(notif, &notif->deltas.array[d], state);
                if (error)
                        return error;
        }
@@ -995,7 +1056,7 @@ handle_deltas(struct update_notification *notif, struct rrdp_serial *serial)
  * Consumes @new.
  */
 static void
-init_notif(struct cachefile_notification *old, struct update_notification *new)
+init_notif(struct rrdp_state *old, struct update_notification *new)
 {
        size_t dn;
        size_t i;
@@ -1018,14 +1079,14 @@ init_notif(struct cachefile_notification *old, struct update_notification *new)
 }
 
 static void
-drop_notif(struct cachefile_notification *notif)
+drop_notif(struct rrdp_state *state)
 {
        struct rrdp_hash *hash;
 
-       session_cleanup(&notif->session);
-       while (!STAILQ_EMPTY(&notif->delta_hashes)) {
-               hash = STAILQ_FIRST(&notif->delta_hashes);
-               STAILQ_REMOVE_HEAD(&notif->delta_hashes, hook);
+       session_cleanup(&state->session);
+       while (!STAILQ_EMPTY(&state->delta_hashes)) {
+               hash = STAILQ_FIRST(&state->delta_hashes);
+               STAILQ_REMOVE_HEAD(&state->delta_hashes, hook);
                free(hash);
        }
 }
@@ -1035,7 +1096,7 @@ drop_notif(struct cachefile_notification *notif)
  * Consumes @new on success.
  */
 static int
-update_notif(struct cachefile_notification *old, struct update_notification *new)
+update_notif(struct rrdp_state *old, struct update_notification *new)
 {
        BIGNUM *diff_bn;
        BN_ULONG diff; /* difference between the old and new serials */
@@ -1083,59 +1144,102 @@ update_notif(struct cachefile_notification *old, struct update_notification *new
        return 0;
 }
 
+static int
+dl_notif(struct cache_mapping const *map,  time_t mtim, bool *changed,
+    struct update_notification *new)
+{
+       char *tmppath;
+       int error;
+
+       error = cache_tmpfile(&tmppath);
+       if (error)
+               return error;
+
+       *changed = false;
+       error = http_download(map->url, tmppath, mtim, changed);
+       if (error)
+               goto end;
+       if (!(*changed)) {
+               pr_val_debug("The Notification has not changed.");
+               goto end;
+       }
+
+       error = parse_notification(map->url, tmppath, new);
+       if (error)
+               goto end;
+
+       if (remove(tmppath) < 0) {
+               pr_val_warn("Can't remove notification's temporal file: %s",
+                  strerror(errno));
+               update_notification_cleanup(new);
+               /* Nonfatal; fall through */
+       }
+
+end:   free(tmppath);
+       return error;
+}
+
 /*
- * Downloads the Update Notification pointed by @map, and updates the cache
+ * Downloads the Update Notification @notif->url, and updates the cache
  * accordingly.
  *
  * "Updates the cache accordingly" means it downloads the missing deltas or
- * snapshot, and explodes them into the corresponding RPP's local directory.
+ * snapshot, and explodes them into @notif->path.
  */
 int
-rrdp_update(struct cache_mapping *map)
+rrdp_update(struct cache_mapping const *notif, time_t mtim, bool *changed,
+    struct cache_sequence *rrdp_seq, struct rrdp_state **state)
 {
-       struct cachefile_notification **cached, *old;
+       struct rrdp_state *old;
        struct update_notification new;
-       bool changed;
        int serial_cmp;
        int error;
 
-       fnstack_push_map(map);
+       fnstack_push(notif->url);
        pr_val_debug("Processing notification.");
 
-       error = cache_download(validation_cache(state_retrieve()), map,
-           &changed, &cached);
+       error = dl_notif(notif, mtim, changed, &new);
        if (error)
                goto end;
-       if (!changed) {
-               pr_val_debug("The Notification has not changed.");
+       if (!(*changed))
                goto end;
-       }
 
-       error = parse_notification(map, &new);
-       if (error)
-               goto end;
        pr_val_debug("New session/serial: %s/%s", new.session.session_id,
            new.session.serial.str);
 
-       old = *cached;
-       if (old == NULL) {
+       if ((*state) == NULL) {
+               char *cage;
+
                pr_val_debug("This is a new Notification.");
-               error = handle_snapshot(&new);
-               if (error)
+
+               cage = cseq_next(rrdp_seq);
+               if (!cage)
                        goto clean_notif;
 
-               *cached = pmalloc(sizeof(struct cachefile_notification));
-               init_notif(*cached, &new);
+               old = pzalloc(sizeof(struct rrdp_state));
+               /* session postponed! */
+               cseq_init(&old->seq, cage);
+               STAILQ_INIT(&old->delta_hashes);
+
+               error = handle_snapshot(&new, old);
+               if (error) {
+                       rrdp_state_free(old);
+                       goto clean_notif;
+               }
+
+               init_notif(old, &new);
+               *state = old;
                goto end;
        }
 
+       old = *state;
        serial_cmp = BN_cmp(old->session.serial.num, new.session.serial.num);
        if (serial_cmp < 0) {
                pr_val_debug("The Notification's serial changed.");
                error = validate_session_desync(old, &new);
                if (error)
                        goto snapshot_fallback;
-               error = handle_deltas(&new, &old->session.serial);
+               error = handle_deltas(&new, old);
                if (error)
                        goto snapshot_fallback;
                error = update_notif(old, &new);
@@ -1157,12 +1261,13 @@ rrdp_update(struct cache_mapping *map)
 
        } else {
                pr_val_debug("The Notification changed, but the session ID and serial didn't, and no session desync was detected.");
+               *changed = false;
                goto clean_notif;
        }
 
 snapshot_fallback:
        pr_val_debug("Falling back to snapshot.");
-       error = handle_snapshot(&new);
+       error = handle_snapshot(&new, old);
        if (error)
                goto clean_notif;
 
@@ -1174,11 +1279,47 @@ reset_notif:
 clean_notif:
        update_notification_cleanup(&new);
 
-end:
-       fnstack_pop();
+end:   fnstack_pop();
        return error;
 }
 
+char const *
+rrdp_file(struct rrdp_state *state, char const *url)
+{
+       struct cache_file *file;
+       file = state_find_file(state, url, strlen(url));
+       return file ? file->map.path : NULL;
+}
+
+char const *
+rrdp_create_fallback(char const *cage, struct rrdp_state **_state,
+    char const *url)
+{
+       struct rrdp_state *state;
+       struct cache_file *file;
+       size_t len;
+
+       state = *_state;
+       if (state == NULL) {
+               *_state = state = pzalloc(sizeof(struct rrdp_state));
+               cseq_init(&state->seq, pstrdup(cage));
+       }
+
+       file = pzalloc(sizeof(struct cache_file));
+       file->map.url = pstrdup(url);
+       file->map.path = cseq_next(&state->seq);
+       if (!file->map.path) {
+               free(file->map.url);
+               free(file);
+               return NULL;
+       }
+
+       len = strlen(file->map.url);
+       HASH_ADD_KEYPTR(hh, state->files, file->map.url, len, file);
+
+       return file->map.path;
+}
+
 #define TAGNAME_SESSION "session_id"
 #define TAGNAME_SERIAL "serial"
 #define TAGNAME_DELTAS "deltas"
@@ -1191,50 +1332,79 @@ hash_b2c(unsigned char bin)
        return (bin < 10) ? (bin + '0') : (bin + 'a' - 10);
 }
 
-json_t *
-rrdp_notif2json(struct cachefile_notification *notif)
+static json_t *
+files2json(struct rrdp_state *state)
 {
        json_t *json;
-       json_t *deltas;
-       char hash_str[2 * RRDP_HASH_LEN + 1];
-       struct rrdp_hash *hash;
-       size_t i;
+       struct cache_file *file, *tmp;
 
-       if (notif == NULL)
+       json = json_obj_new();
+       if (json == NULL)
                return NULL;
 
-       json = json_object();
-       if (json == NULL)
-               enomem_panic();
+       HASH_ITER(hh, state->files, file, tmp)
+               if (json_add_str(json, file->map.url, file->map.path))
+                       goto fail;
 
-       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;
+       return json;
 
-       if (STAILQ_EMPTY(&notif->delta_hashes))
-               return json; /* Happy path, but unlikely. */
+fail:  json_decref(json);
+       return NULL;
+}
 
-       deltas = json_array();
-       if (deltas == NULL)
-               enomem_panic();
-       if (json_object_add(json, TAGNAME_DELTAS, deltas))
-               goto fail;
+static json_t *
+dh2json(struct rrdp_state *state)
+{
+       json_t *json;
+       char hash_str[2 * RRDP_HASH_LEN + 1];
+       struct rrdp_hash *hash;
+       array_index i;
+
+       json = json_array_new();
+       if (json == NULL)
+               return NULL;
 
        hash_str[2 * RRDP_HASH_LEN] = '\0';
-       STAILQ_FOREACH(hash, &notif->delta_hashes, hook) {
+       STAILQ_FOREACH(hash, &state->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)))
+               if (json_array_add(json, json_string(hash_str)))
                        goto fail;
        }
 
        return json;
 
-fail:
-       json_decref(json);
+fail:  json_decref(json);
+       return NULL;
+}
+
+json_t *
+rrdp_state2json(struct rrdp_state *state)
+{
+       json_t *json;
+
+       json = json_object();
+       if (json == NULL)
+               enomem_panic();
+
+       if (json_add_str(json, TAGNAME_SESSION, state->session.session_id))
+               goto fail;
+       if (json_add_str(json, TAGNAME_SERIAL, state->session.serial.str))
+               goto fail;
+       if (state->files)
+               if (json_object_add(json, "files", files2json(state)))
+                       goto fail;
+       if (json_add_ulong(json, "next", state->seq.next_id))
+               goto fail;
+       if (!STAILQ_EMPTY(&state->delta_hashes))
+               if (json_object_add(json, TAGNAME_DELTAS, dh2json(state)))
+                       goto fail;
+
+       return json;
+
+fail:  json_decref(json);
        return NULL;
 }
 
@@ -1288,29 +1458,29 @@ bad_char:
 }
 
 static void
-clear_delta_hashes(struct cachefile_notification *notif)
+clear_delta_hashes(struct rrdp_state *state)
 {
        struct rrdp_hash *hash;
 
-       while (!STAILQ_EMPTY(&notif->delta_hashes)) {
-               hash = STAILQ_FIRST(&notif->delta_hashes);
-               STAILQ_REMOVE_HEAD(&notif->delta_hashes, hook);
+       while (!STAILQ_EMPTY(&state->delta_hashes)) {
+               hash = STAILQ_FIRST(&state->delta_hashes);
+               STAILQ_REMOVE_HEAD(&state->delta_hashes, hook);
                free(hash);
        }
 }
 
 int
-rrdp_json2notif(json_t *json, struct cachefile_notification **result)
+rrdp_json2state(json_t *json, struct rrdp_state **result)
 {
-       struct cachefile_notification *notif;
+       struct rrdp_state *state;
        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);
+       state = pzalloc(sizeof(struct rrdp_state));
+       STAILQ_INIT(&state->delta_hashes);
 
        error = json_get_str(json, TAGNAME_SESSION, &str);
        if (error) {
@@ -1318,7 +1488,7 @@ rrdp_json2notif(json_t *json, struct cachefile_notification **result)
                        pr_op_err("Node is missing the '" TAGNAME_SESSION "' tag.");
                goto revert_notif;
        }
-       notif->session.session_id = pstrdup(str);
+       state->session.session_id = pstrdup(str);
 
        error = json_get_str(json, TAGNAME_SERIAL, &str);
        if (error) {
@@ -1326,11 +1496,11 @@ rrdp_json2notif(json_t *json, struct cachefile_notification **result)
                        pr_op_err("Node is missing the '" TAGNAME_SERIAL "' tag.");
                goto revert_session;
        }
-       notif->session.serial.str = pstrdup(str);
+       state->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);
+       state->session.serial.num = BN_create();
+       if (!BN_dec2bn(&state->session.serial.num, state->session.serial.str)) {
+               error = pr_op_err("Not a serial number: %s", state->session.serial.str);
                goto revert_serial;
        }
 
@@ -1351,32 +1521,40 @@ rrdp_json2notif(json_t *json, struct cachefile_notification **result)
                error = json2dh(json_array_get(jdeltas, d), &hash);
                if (error)
                        goto revert_deltas;
-               STAILQ_INSERT_TAIL(&notif->delta_hashes, hash, hook);
+               STAILQ_INSERT_TAIL(&state->delta_hashes, hash, hook);
        }
 
 success:
-       *result = notif;
+       *result = state;
        return 0;
 
 revert_deltas:
-       clear_delta_hashes(notif);
+       clear_delta_hashes(state);
 revert_serial:
-       BN_free(notif->session.serial.num);
-       free(notif->session.serial.str);
+       BN_free(state->session.serial.num);
+       free(state->session.serial.str);
 revert_session:
-       free(notif->session.session_id);
+       free(state->session.session_id);
 revert_notif:
-       free(notif);
+       free(state);
        return error;
 }
 
 void
-rrdp_notif_free(struct cachefile_notification *notif)
+rrdp_state_free(struct rrdp_state *state)
 {
-       if (notif == NULL)
+       struct cache_file *file, *tmp;
+
+       if (state == NULL)
                return;
 
-       session_cleanup(&notif->session);
-       clear_delta_hashes(notif);
-       free(notif);
+       session_cleanup(&state->session);
+       HASH_ITER(hh, state->files, file, tmp) {
+               HASH_DEL(state->files, file);
+               map_cleanup(&file->map);
+               free(file);
+       }
+       free(state->seq.prefix);
+       clear_delta_hashes(state);
+       free(state);
 }
index fa7877ffd0185f35e8f857471f880a7d3bb9f8b9..64ee1820c5ba8fcad9d8a754e3b472bebafa6673 100644 (file)
@@ -1,15 +1,25 @@
 #ifndef SRC_RRDP_H_
 #define SRC_RRDP_H_
 
+#include <jansson.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include "file.h"
 #include "types/map.h"
 
-struct cachefile_notification;
+struct rrdp_state;
+
+int rrdp_update(struct cache_mapping const *, time_t, bool *,
+    struct cache_sequence *, struct rrdp_state **);
+char const *rrdp_file(struct rrdp_state *, char const *);
 
-int rrdp_update(struct cache_mapping *);
+char const *rrdp_create_fallback(char const *, struct rrdp_state **,
+    char const *);
 
-json_t *rrdp_notif2json(struct cachefile_notification *);
-int rrdp_json2notif(json_t *, struct cachefile_notification **);
+json_t *rrdp_state2json(struct rrdp_state *);
+int rrdp_json2state(json_t *, struct rrdp_state **);
 
-void rrdp_notif_free(struct cachefile_notification *);
+void rrdp_state_free(struct rrdp_state *);
 
 #endif /* SRC_RRDP_H_ */
similarity index 50%
rename from src/rsync/rsync.c
rename to src/rsync.c
index 2425a95bf6b507cd6d108318e71ff92739c3e90c..6141f9b8fc26b323b9752d029ff2832dfc2dfa14 100644 (file)
@@ -1,7 +1,8 @@
-#include "rsync/rsync.h"
+#include "rsync.h"
 
 #include <errno.h>
 #include <fcntl.h>
+#include <poll.h>
 #include <signal.h>
 #include <sys/wait.h>
 #include <syslog.h>
 #include "config.h"
 #include "log.h"
 
+#define STDERR_WRITE(fds) fds[0][1]
+#define STDOUT_WRITE(fds) fds[1][1]
+#define STDERR_READ(fds)  fds[0][0]
+#define STDOUT_READ(fds)  fds[1][0]
+
 /*
  * Duplicate parent FDs, to pipe rsync output:
  * - fds[0] = stderr
@@ -20,60 +26,53 @@ static void
 duplicate_fds(int fds[2][2])
 {
        /* Use the loop to catch interruptions */
-       while ((dup2(fds[0][1], STDERR_FILENO) == -1)
+       while ((dup2(STDERR_WRITE(fds), STDERR_FILENO) == -1)
                && (errno == EINTR)) {}
-       close(fds[0][1]);
-       close(fds[0][0]);
+       close(STDERR_WRITE(fds));
+       close(STDERR_READ(fds));
 
-       while ((dup2(fds[1][1], STDOUT_FILENO) == -1)
+       while ((dup2(STDOUT_WRITE(fds), STDOUT_FILENO) == -1)
            && (errno == EINTR)) {}
-       close(fds[1][1]);
-       close(fds[1][0]);
+       close(STDOUT_WRITE(fds));
+       close(STDOUT_READ(fds));
 }
 
 static void
-release_args(char **args, unsigned int size)
+prepare_rsync(char **args, char const *url, char const *path)
 {
-       unsigned int i;
-
-       /* args[0] wasn't allocated */
-       for (i = 1; i < size + 1; i++)
-               free(args[i]);
-       free(args);
-}
+       size_t i = 0;
 
-static void
-prepare_rsync(char const *src, char const *dst, char ***args, size_t *args_len)
-{
-       struct string_array const *config_args;
-       char **copy_args;
-       unsigned int i;
-
-       config_args = config_get_rsync_args();
        /*
-        * We need to work on a copy, because the config args are immutable,
-        * and we need to add the program name (for some reason) and NULL
-        * elements, and replace $REMOTE and $LOCAL.
+        * execvp() is not going to tweak these strings;
+        * stop angsting over the const-to-raw conversion.
         */
-       copy_args = pcalloc(config_args->length + 2, sizeof(char *));
-
-       copy_args[0] = config_get_rsync_program();
-       copy_args[config_args->length + 1] = NULL;
-
-       memcpy(copy_args + 1, config_args->array,
-           config_args->length * sizeof(char *));
-
-       for (i = 0; i < config_args->length; i++) {
-               if (strcmp(config_args->array[i], "$REMOTE") == 0)
-                       copy_args[i + 1] = pstrdup(src);
-               else if (strcmp(config_args->array[i], "$LOCAL") == 0)
-                       copy_args[i + 1] = pstrdup(dst);
-               else
-                       copy_args[i + 1] = pstrdup(config_args->array[i]);
-       }
 
-       *args = copy_args;
-       *args_len = config_args->length;
+       /* XXX review */
+       args[i++] = (char *)config_get_rsync_program();
+#ifdef UNIT_TESTING
+       /* Note... --bwlimit does not seem to exist in openrsync */
+       args[i++] = "--bwlimit=1K";
+       args[i++] = "-vvv";
+#else
+       args[i++] = "-rtz";
+       args[i++] = "--omit-dir-times";
+       args[i++] = "--contimeout";
+       args[i++] = "20";
+       args[i++] = "--max-size";
+       args[i++] = "20MB";
+       args[i++] = "--timeout";
+       args[i++] = "15";
+       args[i++] = "--include=*/";
+       args[i++] = "--include=*.cer";
+       args[i++] = "--include=*.crl";
+       args[i++] = "--include=*.gbr";
+       args[i++] = "--include=*.mft";
+       args[i++] = "--include=*.roa";
+       args[i++] = "--exclude=*";
+#endif
+       args[i++] = (char *)url;
+       args[i++] = (char *)path;
+       args[i++] = NULL;
 }
 
 __dead static void
@@ -109,8 +108,8 @@ create_pipes(int fds[2][2])
                error = errno;
 
                /* Close pipe previously created */
-               close(fds[0][0]);
-               close(fds[0][1]);
+               close(STDERR_READ(fds));
+               close(STDERR_WRITE(fds));
 
                pr_op_err_st("Piping rsync stdout: %s", strerror(error));
                return -error;
@@ -119,8 +118,17 @@ create_pipes(int fds[2][2])
        return 0;
 }
 
+static long
+get_current_millis(void)
+{
+       struct timespec now;
+       if (clock_gettime(CLOCK_MONOTONIC, &now) < 0)
+               pr_crit("clock_gettime() returned %d", errno);
+       return 1000L * now.tv_sec + now.tv_nsec / 1000000L;
+}
+
 static void
-log_buffer(char const *buffer, ssize_t read, int type)
+log_buffer(char const *buffer, ssize_t read, bool is_error)
 {
 #define PRE_RSYNC "[RSYNC exec]: "
        char *cpy, *cur, *tmp;
@@ -138,79 +146,137 @@ log_buffer(char const *buffer, ssize_t read, int type)
                        cur = tmp + 1;
                        continue;
                }
-               if (type == 0) {
+               if (is_error)
                        pr_val_err(PRE_RSYNC "%s", cur);
-               } else {
+               else
                        pr_val_debug(PRE_RSYNC "%s", cur);
-               }
                cur = tmp + 1;
        }
        free(cpy);
 #undef PRE_RSYNC
 }
 
+#define DROP_FD(f, fail)               \
+       do {                            \
+               pfd[f].fd = -1;         \
+               error |= fail;          \
+       } while (0)
+#define CLOSE_FD(f, fail)              \
+       do {                            \
+               close(pfd[f].fd);       \
+               DROP_FD(f, fail);       \
+       } while (0)
+
+/*
+ * Consumes (and throws away) all the bytes in read streams @fderr and @fdout,
+ * then closes them once they reach end of stream.
+ *
+ * Returns: ok -> 0, error -> 1, timeout -> 2.
+ */
 static int
-read_pipe(int fd_pipe[2][2], int type)
+exhaust_read_fds(int fderr, int fdout)
 {
-       char buffer[4096];
-       ssize_t count;
-       int error;
+       struct pollfd pfd[2];
+       int error, nready, f;
+       long epoch, delta, timeout;
+
+       memset(&pfd, 0, sizeof(pfd));
+       pfd[0].fd = fderr;
+       pfd[0].events = POLLIN;
+       pfd[1].fd = fdout;
+       pfd[1].events = POLLIN;
+
+       error = 0;
+
+       epoch = get_current_millis();
+       delta = 0;
+       timeout = 1000 * config_get_rsync_transfer_timeout();
 
        while (1) {
-               count = read(fd_pipe[type][0], buffer, sizeof(buffer));
-               if (count == -1) {
+               nready = poll(pfd, 2, timeout - delta);
+               if (nready == 0)
+                       goto timed_out;
+               if (nready == -1) {
                        error = errno;
                        if (error == EINTR)
                                continue;
-                       close(fd_pipe[type][0]); /* Close read end */
-                       pr_val_err("rsync buffer read error: %s",
-                           strerror(error));
-                       return -error;
+                       pr_val_err("rsync bad poll: %s", strerror(error));
+                       error = 1;
+                       goto fail;
                }
-               if (count == 0)
-                       break;
 
-               log_buffer(buffer, count, type);
+               for (f = 0; f < 2; f++) {
+                       if (pfd[f].revents & POLLNVAL) {
+                               pr_val_err("rsync bad fd: %i", pfd[f].fd);
+                               DROP_FD(f, 1);
+
+                       } else if (pfd[f].revents & POLLERR) {
+                               pr_val_err("Generic error during rsync poll.");
+                               CLOSE_FD(f, 1);
+
+                       } else if (pfd[f].revents & (POLLIN|POLLHUP)) {
+                               char buffer[4096];
+                               ssize_t count;
+
+                               count = read(pfd[f].fd, buffer, sizeof(buffer));
+                               if (count == -1) {
+                                       error = errno;
+                                       if (error == EINTR)
+                                               continue;
+                                       pr_val_err("rsync buffer read error: %s",
+                                           strerror(error));
+                                       CLOSE_FD(f, 1);
+                                       continue;
+                               }
+
+                               if (count == 0)
+                                       CLOSE_FD(f, 0);
+                               log_buffer(buffer, count, pfd[f].fd == fderr);
+                       }
+               }
+
+               if (pfd[0].fd == -1 && pfd[1].fd == -1)
+                       return error; /* Happy path! */
+
+               delta = get_current_millis() - epoch;
+               if (delta < 0) {
+                       pr_val_err("This clock does not seem monotonic. "
+                           "I'm going to have to give up this rsync.");
+                       error = 1;
+                       goto fail;
+               }
+               if (delta >= timeout)
+                       goto timed_out; /* Read took too long */
        }
 
-       close(fd_pipe[type][0]); /* Close read end */
-       return 0;
+timed_out:
+       pr_val_err("rsync transfer timeout reached");
+       error = 2;
+fail:  for (f = 0; f < 2; f++)
+               if (pfd[f].fd != -1)
+                       close(pfd[f].fd);
+       return error;
 }
 
 /*
- * Read the piped output from the child, assures that all pipes are closed on
- * success and on error.
+ * Completely consumes @fds' streams, and closes them.
+ *
+ * Allegedly, this is a portable way to wait for the child process to finish.
+ * (IIRC, waitpid() doesn't do this reliably.)
  */
 static int
-read_pipes(int fds[2][2])
+exhaust_pipes(int fds[2][2])
 {
-       int error;
-
-       /* Won't be needed (sterr/stdout write ends) */
-       close(fds[0][1]);
-       close(fds[1][1]);
-
-       /* stderr pipe */
-       error = read_pipe(fds, 0);
-       if (error) {
-               /* Close the other pipe pending to read */
-               close(fds[1][0]);
-               return error;
-       }
-
-       /* stdout pipe, always logs to info */
-       return read_pipe(fds, 1);
+       close(STDERR_WRITE(fds));
+       close(STDOUT_WRITE(fds));
+       return exhaust_read_fds(STDERR_READ(fds), STDOUT_READ(fds));
 }
 
-/*
- * Downloads @src @dst. @src is supposed to be an rsync URL, and @dst is
- * supposed to be a filesystem path.
- */
+/* rsync @url @path */
 int
-rsync_download(char const *src, char const *dst, bool is_directory)
+rsync_download(char const *url, char const *path)
 {
-       char **args;
-       size_t args_len;
+       char *args[32];
        /* Descriptors to pipe stderr (first element) and stdout (second) */
        int fork_fds[2][2];
        pid_t child_pid;
@@ -220,20 +286,18 @@ rsync_download(char const *src, char const *dst, bool is_directory)
        int error;
 
        /* Prepare everything for the child exec */
-       args = NULL;
-       args_len = 0;
-       prepare_rsync(src, dst, &args, &args_len);
+       prepare_rsync(args, url, path);
 
-       pr_val_info("rsync: %s", src);
+       pr_val_info("rsync: %s -> %s", url, path);
        if (log_val_enabled(LOG_DEBUG)) {
                pr_val_debug("Executing rsync:");
-               for (i = 0; i < args_len + 1; i++)
+               for (i = 0; args[i] != NULL; i++)
                        pr_val_debug("    %s", args[i]);
        }
 
-       error = mkdir_p(dst, is_directory);
+       error = mkdir_p(path, true);
        if (error)
-               goto release_args;
+               return error;
 
        retries = 0;
        do {
@@ -241,7 +305,7 @@ rsync_download(char const *src, char const *dst, bool is_directory)
 
                error = create_pipes(fork_fds);
                if (error)
-                       goto release_args;
+                       return error;
 
                /* Flush output (avoid locks between father and child) */
                log_flush();
@@ -266,17 +330,17 @@ rsync_download(char const *src, char const *dst, bool is_directory)
                        pr_op_err_st("Couldn't fork to execute rsync: %s",
                           strerror(error));
                        /* Close all ends from the created pipes */
-                       close(fork_fds[0][0]);
-                       close(fork_fds[1][0]);
-                       close(fork_fds[0][1]);
-                       close(fork_fds[1][1]);
-                       goto release_args;
+                       close(STDERR_READ(fork_fds));
+                       close(STDOUT_READ(fork_fds));
+                       close(STDERR_WRITE(fork_fds));
+                       close(STDOUT_WRITE(fork_fds));
+                       return error;
                }
 
                /* This code is run by us. */
-               error = read_pipes(fork_fds);
+               error = exhaust_pipes(fork_fds);
                if (error)
-                       kill(child_pid, SIGCHLD); /* Stop the child */
+                       kill(child_pid, SIGTERM); /* Stop the child */
 
                error = waitpid(child_pid, &child_status, 0);
                do {
@@ -286,7 +350,7 @@ rsync_download(char const *src, char const *dst, bool is_directory)
                                    error, strerror(error));
                                if (child_status > 0)
                                        break;
-                               goto release_args;
+                               return error;
                        }
                } while (0);
 
@@ -296,17 +360,16 @@ rsync_download(char const *src, char const *dst, bool is_directory)
                        pr_val_debug("The rsync sub-process terminated with error code %d.",
                            error);
                        if (!error)
-                               goto release_args;
+                               return 0;
 
                        if (retries == config_get_rsync_retry_count()) {
                                if (retries > 0)
                                        pr_val_warn("Max RSYNC retries (%u) reached on '%s', won't retry again.",
-                                           retries, src);
-                               error = EIO;
-                               goto release_args;
+                                           retries, url);
+                               return EIO;
                        }
                        pr_val_warn("Retrying RSYNC '%s' in %u seconds, %u attempts remaining.",
-                           src,
+                           url,
                            config_get_rsync_retry_interval(),
                            config_get_rsync_retry_count() - retries);
                        retries++;
@@ -316,8 +379,6 @@ rsync_download(char const *src, char const *dst, bool is_directory)
                break;
        } while (true);
 
-       release_args(args, args_len);
-
        if (WIFSIGNALED(child_status)) {
                switch (WTERMSIG(child_status)) {
                case SIGINT:
@@ -339,8 +400,4 @@ rsync_download(char const *src, char const *dst, bool is_directory)
 
        pr_op_err_st("The RSYNC command died in a way I don't have a handler for. Dunno; guess I'll die as well.");
        return -EINVAL;
-release_args:
-       /* The happy path also falls here */
-       release_args(args, args_len);
-       return error;
 }
similarity index 53%
rename from src/rsync/rsync.h
rename to src/rsync.h
index 3476c9c0de6c97c705fedd224e1fb626e9b14bd3..ef4fcc712530e767a4ed2e9163ed130c18f508bd 100644 (file)
@@ -1,8 +1,6 @@
 #ifndef SRC_RSYNC_RSYNC_H_
 #define SRC_RSYNC_RSYNC_H_
 
-#include <stdbool.h>
-
-int rsync_download(char const *, char const *, bool);
+int rsync_download(char const *, char const *);
 
 #endif /* SRC_RSYNC_RSYNC_H_ */
index f2736e4a62c0cd7be840c152d278c84a2ce5f3ea..abb1ef49c4ff968a25d8e73f53db147538413ca3 100644 (file)
@@ -3,8 +3,8 @@
 #include <errno.h>
 
 #include "alloc.h"
-#include "data_structure/uthash.h"
 #include "log.h"
+#include "types/uthash.h"
 
 struct hashable_roa {
        struct vrp data;
index af86cdfd8d7a6d64e2cb18d15a543e806b0eab58..a763c957085a5459dee487e4970364a9f41845ae 100644 (file)
@@ -3,7 +3,6 @@
 
 #include "rtr/db/delta.h"
 #include "types/address.h"
-#include "types/vrp.h"
 
 struct db_table;
 
index 1689e3f534a408b3b9d1726aea5e80431e679fb8..09eb72d7e76c6b239856b991dd9b8bfaf9e4f371 100644 (file)
@@ -2,9 +2,9 @@
 
 #include <stdatomic.h>
 
-#include "alloc.h"
-#include "data_structure/array_list.h"
+#include "log.h"
 #include "types/address.h"
+#include "types/arraylist.h"
 
 struct delta_v4 {
        uint32_t as;
index cdaa9bcb2c832901b795edcf5e85a240740fa9f1..1a0bc96287f13f8c429c1e74c8b860442550912d 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef SRC_DELTA_H_
-#define SRC_DELTA_H_
+#ifndef SRC_RTR_DB_DELTA_H_
+#define SRC_RTR_DB_DELTA_H_
 
 #include "types/delta.h"
 
@@ -18,4 +18,4 @@ int deltas_foreach(struct deltas *, delta_vrp_foreach_cb,
     delta_router_key_foreach_cb, void *);
 void deltas_print(struct deltas *);
 
-#endif /* SRC_DELTA_H_ */
+#endif /* SRC_RTR_DB_DELTA_H_ */
index 48b9069e4f17848313e04328c2079ab7b78ebe81..85025fe31fc1c28c659019465b5dca9ec69186e9 100644 (file)
@@ -5,7 +5,6 @@
 
 #include "alloc.h"
 #include "config.h"
-#include "log.h"
 
 struct deltas_array {
        struct deltas **array; /* It's a circular array. */
index 97a95175832b4aa6155785ea7e3e5ee655679b10..d43751c60ee052c279236cde0b2335acc22463f1 100644 (file)
@@ -6,14 +6,12 @@
 #include "alloc.h"
 #include "common.h"
 #include "config.h"
-#include "data_structure/array_list.h"
+#include "log.h"
 #include "object/tal.h"
 #include "output_printer.h"
-#include "rtr/db/db_table.h"
-#include "rtr/rtr.h"
+#include "rtr/db/deltas_array.h"
+#include "rtr/pdu.h"
 #include "slurm/slurm_loader.h"
-#include "types/router_key.h"
-#include "validation_handler.h"
 
 struct vrp_node {
        struct delta_vrp delta;
@@ -82,7 +80,6 @@ static pthread_rwlock_t state_lock;
 int
 vrps_init(void)
 {
-       time_t now;
        int error;
 
        state.base = NULL;
@@ -96,11 +93,7 @@ vrps_init(void)
        state.serial = 0;
 
        /* Get the bits that'll fit in session_id */
-       now = 0;
-       error = get_current_time(&now);
-       if (error)
-               goto revert_deltas;
-       state.v0_session_id = now & 0xFFFF;
+       state.v0_session_id = time_fatal() & 0xFFFF;
 
        /* Minus 1 to prevent same ID */
        state.v1_session_id = (state.v0_session_id != 0)
@@ -113,14 +106,11 @@ vrps_init(void)
        if (error) {
                pr_op_err("state pthread_rwlock_init() errored: %s",
                    strerror(error));
-               goto revert_deltas;
+               darray_destroy(state.deltas);
+               return error;
        }
 
        return 0;
-
-revert_deltas:
-       darray_destroy(state.deltas);
-       return error;
 }
 
 void
index 0020f6d1a63c284d208e61255c1d0e894c13d677..b10e20a387fa206417d686c0fa0d4e6ba63bdd01 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef SRC_VRPS_H_
-#define SRC_VRPS_H_
+#ifndef SRC_RTR_DB_VRPS_H_
+#define SRC_RTR_DB_VRPS_H_
 
 /*
  * "VRPs" = "Validated ROA Payloads." See RFC 6811.
@@ -7,9 +7,10 @@
  * This module stores VRPs and their serials.
  */
 
-#include "as_number.h"
-#include "rtr/db/deltas_array.h"
 #include "types/address.h"
+#include "types/asn.h"
+#include "types/delta.h"
+#include "types/serial.h"
 
 int vrps_init(void);
 void vrps_destroy(void);
@@ -36,4 +37,4 @@ uint16_t get_current_session_id(uint8_t);
 
 void vrps_print_base(void);
 
-#endif /* SRC_VRPS_H_ */
+#endif /* SRC_RTR_DB_VRPS_H_ */
index bf032defbb7c458e2655526d73522e8f28daa229..6af11313fae4d2b5a6ddacab97004cb480547466 100644 (file)
@@ -3,7 +3,6 @@
 #include <errno.h>
 
 #include "alloc.h"
-#include "log.h"
 #include "rtr/pdu_sender.h"
 
 typedef enum rtr_error_code {
index b25c1dda00ef69d48035d9c567dd0b465e15f4cd..9890e75e4102f6669fd15dd7aa0e85bd74fe0990 100644 (file)
@@ -1,17 +1,18 @@
-#ifndef RTR_PDU_H_
-#define RTR_PDU_H_
+#ifndef SRC_RTR_PDU_H_
+#define SRC_RTR_PDU_H_
 
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
+#include <sys/types.h>
 #include <unistd.h>
 
-#include "common.h"
-#include "rtr/rtr.h"
-#include "types/router_key.h"
-
 enum rtr_version {
        RTR_V0                  = 0,
        RTR_V1                  = 1,
@@ -79,4 +80,4 @@ rtrpdu_error_report_len(uint32_t errpdu_len, uint32_t errmsg_len)
            + errmsg_len;
 }
 
-#endif /* RTR_PDU_H_ */
+#endif /* SRC_RTR_PDU_H_ */
index dd82eb016fb984e5301727247795b7e0dd560eb8..5a419140cf67fc8bdbbb89da144dfd1e80207200 100644 (file)
@@ -3,9 +3,9 @@
 #include <errno.h>
 
 #include "log.h"
+#include "rtr/db/vrps.h"
 #include "rtr/err_pdu.h"
 #include "rtr/pdu_sender.h"
-#include "rtr/pdu_stream.h"
 
 struct send_delta_args {
        int fd;
index dfdf8343f268bc863d910bb89ad48ed7e40e0766..41e10e6b2713f974708d346df51a5b7441edbdcb 100644 (file)
@@ -4,7 +4,6 @@
 #include <poll.h>
 
 #include "alloc.h"
-#include "common.h"
 #include "config.h"
 #include "log.h"
 #include "rtr/db/vrps.h"
index a3009be140fcb736272da36600319f866495145c..ed1b709efec2ae849781672cb528f2bf02bb7882 100644 (file)
@@ -1,9 +1,10 @@
 #ifndef SRC_RTR_PDU_SENDER_H_
 #define SRC_RTR_PDU_SENDER_H_
 
-#include "rtr/db/vrps.h"
 #include "rtr/pdu.h"
 #include "types/router_key.h"
+#include "types/serial.h"
+#include "types/vrp.h"
 
 int send_serial_notify_pdu(int, uint8_t, serial_t);
 int send_cache_reset_pdu(int, uint8_t);
index f28906f378cc4c6f1790c39b2cf261e3d65195e7..416dc05d35585f9b16fa7509626ab9a8a325c6ed 100644 (file)
@@ -5,7 +5,6 @@
 #include "alloc.h"
 #include "log.h"
 #include "rtr/err_pdu.h"
-#include "rtr/pdu.h"
 
 enum buffer_state {
        /* We've read all available bytes for now. */
index 4622291427708a06c6999461d9d7f40ea44993f7..e827aa9e443506bda64098a389211ac7816ca3c3 100644 (file)
@@ -1,9 +1,9 @@
 #ifndef SRC_RTR_PDU_STREAM_H_
 #define SRC_RTR_PDU_STREAM_H_
 
-#include "data_structure/array_list.h"
+#include <stdbool.h>
+
 #include "rtr/pdu.h"
-#include "rtr/rtr.h"
 
 struct pdu_stream; /* It's an *input* stream. */
 
index f1b541ad14402d623ab9c72d70f2711bf4d6c46e..c8a4254420708421884f1c3207ca6972d998a9d5 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef RTR_PRIMITIVE_WRITER_H_
-#define RTR_PRIMITIVE_WRITER_H_
+#ifndef SRC_RTR_PRIMITIVE_WRITER_H_
+#define SRC_RTR_PRIMITIVE_WRITER_H_
 
 #include <arpa/inet.h>
 #include <netdb.h>
@@ -12,4 +12,4 @@ unsigned char *write_uint32(unsigned char *, uint32_t);
 unsigned char *write_in_addr(unsigned char *, struct in_addr);
 unsigned char *write_in6_addr(unsigned char *, struct in6_addr const *);
 
-#endif /* RTR_PRIMITIVE_WRITER_H_ */
+#endif /* SRC_RTR_PRIMITIVE_WRITER_H_ */
index 69d9e96a4881b71d479de8e1fd07f1d418803331..15648244705fbae6a7e226d75564361a9fd33d9b 100644 (file)
@@ -4,18 +4,14 @@
 #include <fcntl.h>
 #include <poll.h>
 
-#include "alloc.h"
+#include "common.h"
 #include "config.h"
-#include "data_structure/array_list.h"
+#include "log.h"
 #include "rtr/db/vrps.h"
-#include "rtr/err_pdu.h"
-#include "rtr/pdu.h"
 #include "rtr/pdu_handler.h"
 #include "rtr/pdu_sender.h"
-#include "rtr/pdu_stream.h"
-#include "thread/thread_pool.h"
-#include "types/address.h"
-#include "types/serial.h"
+#include "thread_pool.h"
+#include "types/arraylist.h"
 
 struct rtr_server {
        int fd;
index f85aa16fa8b8f85025fcfb0fac641d955f15e62d..ada4222dd42ab27b5cfe305963e6c4285361478b 100644 (file)
@@ -1,9 +1,9 @@
-#ifndef RTR_RTR_H_
-#define RTR_RTR_H_
+#ifndef SRC_RTR_RTR_H_
+#define SRC_RTR_RTR_H_
 
 int rtr_start(void);
 void rtr_stop(void);
 
 void rtr_notify(void);
 
-#endif /* RTR_RTR_H_ */
+#endif /* SRC_RTR_RTR_H_ */
index 44d38dcaba18311f820df596145b2220e47e54df..6816c993f52ff49b09d49b845099aafca7b797f2 100644 (file)
@@ -3,11 +3,10 @@
 #include <errno.h>
 #include <time.h>
 
-#include "alloc.h"
+#include "base64.h"
 #include "common.h"
-#include "crypto/base64.h"
-#include "data_structure/array_list.h"
-#include "types/router_key.h"
+#include "log.h"
+#include "types/arraylist.h"
 
 struct slurm_prefix_wrap {
        struct slurm_prefix element;
@@ -96,15 +95,10 @@ int
 db_slurm_create(struct slurm_csum_list *csums, struct db_slurm **result)
 {
        struct db_slurm *db;
-       int error;
 
        db = pmalloc(sizeof(struct db_slurm));
 
-       error = get_current_time(&db->loaded_date);
-       if (error) {
-               free(db);
-               return error;
-       }
+       db->loaded_date = time_nonfatal();
 
        /* Not ready yet (nor required yet) for multithreading */
        al_filter_prefix_init(&db->lists.filter_pfx_al);
index 69940dfd7fd7172104fe05e01a6f108414c11c29..7c18aecedfeee9f3971a798cb80ddc42d0f4e870 100644 (file)
@@ -1,8 +1,15 @@
-#ifndef SRC_SLURM_db_slurm_H_
-#define SRC_SLURM_db_slurm_H_
+#ifndef SRC_SLURM_DB_SLURM_H_
+#define SRC_SLURM_DB_SLURM_H_
 
 #include <openssl/evp.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
 #include <sys/queue.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "types/router_key.h"
 #include "types/vrp.h"
@@ -82,4 +89,4 @@ void db_slurm_destroy(struct db_slurm *);
 
 void db_slurm_get_csum_list(struct db_slurm *, struct slurm_csum_list *);
 
-#endif /* SRC_SLURM_db_slurm_H_ */
+#endif /* SRC_SLURM_DB_SLURM_H_ */
index 06e498198f7ed1eb70cc56fb7f28d2ea0e70dd1b..6fca57d30133312f766558797c6dc4263f98a7c5 100644 (file)
@@ -4,10 +4,10 @@
 #include <openssl/sha.h>
 
 #include "alloc.h"
-#include "log.h"
-#include "config.h"
 #include "common.h"
-#include "crypto/hash.h"
+#include "config.h"
+#include "hash.h"
+#include "log.h"
 #include "slurm/slurm_parser.h"
 
 #define SLURM_FILE_EXTENSION   ".slurm"
index 40898814ac019f30d16133795ae34f2514937789..8aeb98302953b3bb65d80da374ba4ae07d068bdc 100644 (file)
@@ -5,12 +5,11 @@
 
 #include "algorithm.h"
 #include "alloc.h"
-#include "crypto/base64.h"
+#include "base64.h"
 #include "json_util.h"
 #include "log.h"
 #include "slurm/db_slurm.h"
 #include "types/address.h"
-#include "types/router_key.h"
 
 /* JSON members */
 #define SLURM_VERSION                  "slurmVersion"
index 5b1d30f0f159648a2541bc5d176a3890ef9e7be7..f32901bed9a06649c2286cf32e86de04de64666f 100644 (file)
@@ -1,17 +1,15 @@
 #include "state.h"
 
 #include "alloc.h"
-#include "cache/local_cache.h"
-#include "cert_stack.h"
+#include "config.h"
 #include "log.h"
 #include "thread_var.h"
 
 /**
- * The current state of the validation cycle.
+ * Just a bunch of thread-specific variables that are too much of a pain
+ * to keep passing around.
  *
- * It is one of the core objects in this project. Every time a trust anchor
- * triggers a validation cycle, the validator creates one of these objects and
- * uses it to traverse the tree and keep track of validated data.
+ * Should be refactored away, honestly.
  */
 struct validation {
        struct tal *tal;
@@ -22,11 +20,6 @@ struct validation {
                X509_VERIFY_PARAM *params;
        } x509_data;
 
-       struct cert_stack *certstack;
-
-       /* Did the TAL's public key match the root certificate's public key? */
-       enum pubkey_state pubkey_state;
-
        /**
         * Two buffers calling code will store stringified IP addresses in,
         * to prevent proliferation of similar buffers on the stack.
@@ -106,23 +99,17 @@ validation_prepare(struct validation **out, struct tal *tal,
                enomem_panic();
 
        X509_VERIFY_PARAM_set_flags(params, X509_V_FLAG_CRL_CHECK);
+       if (config_get_validation_time() != 0)
+               X509_VERIFY_PARAM_set_time(params, config_get_validation_time());
        X509_STORE_set1_param(result->x509_data.store, params);
        X509_STORE_set_verify_cb(result->x509_data.store, cb);
 
-       error = certstack_create(&result->certstack);
-       if (error)
-               goto undo_crypto;
-
-       result->pubkey_state = PKS_UNTESTED;
        result->validation_handler = *validation_handler;
        result->x509_data.params = params; /* Ownership transfered */
 
        *out = result;
        return 0;
 
-undo_crypto:
-       X509_VERIFY_PARAM_free(params);
-       X509_STORE_free(result->x509_data.store);
 undo_result:
        free(result);
        return error;
@@ -133,7 +120,6 @@ validation_destroy(struct validation *state)
 {
        X509_VERIFY_PARAM_free(state->x509_data.params);
        X509_STORE_free(state->x509_data.store);
-       certstack_destroy(state->certstack);
        free(state);
 }
 
@@ -143,42 +129,12 @@ validation_tal(struct validation *state)
        return (state != NULL) ? state->tal : NULL;
 }
 
-struct rpki_cache *
-validation_cache(struct validation *state)
-{
-       return tal_get_cache(state->tal);
-}
-
 X509_STORE *
 validation_store(struct validation *state)
 {
        return state->x509_data.store;
 }
 
-struct cert_stack *
-validation_certstack(struct validation *state)
-{
-       return state->certstack;
-}
-
-void
-validation_pubkey_valid(struct validation *state)
-{
-       state->pubkey_state = PKS_VALID;
-}
-
-void
-validation_pubkey_invalid(struct validation *state)
-{
-       state->pubkey_state = PKS_INVALID;
-}
-
-enum pubkey_state
-validation_pubkey_state(struct validation *state)
-{
-       return state->pubkey_state;
-}
-
 char *
 validation_get_ip_buffer1(struct validation *state)
 {
index c11828d1d7dfe338ba2e02f16e93e72d1f3033f8..d168a71535551349bbc37bde6d83a44a3d7ed0ed 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef SRC_STATE_H_
 #define SRC_STATE_H_
 
+#include <openssl/x509.h>
+
 #include "object/tal.h"
 #include "validation_handler.h"
 
@@ -11,19 +13,10 @@ int validation_prepare(struct validation **, struct tal *,
 void validation_destroy(struct validation *);
 
 struct tal *validation_tal(struct validation *);
-struct rpki_cache *validation_cache(struct validation *);
 X509_STORE *validation_store(struct validation *);
-struct cert_stack *validation_certstack(struct validation *);
-
-enum pubkey_state {
-       PKS_VALID,
-       PKS_INVALID,
-       PKS_UNTESTED,
-};
 
 void validation_pubkey_valid(struct validation *);
 void validation_pubkey_invalid(struct validation *);
-enum pubkey_state validation_pubkey_state(struct validation *);
 
 char *validation_get_ip_buffer1(struct validation *);
 char *validation_get_ip_buffer2(struct validation *);
similarity index 99%
rename from src/thread/thread_pool.c
rename to src/thread_pool.c
index 7ec30d102fcd8f43583a21c4a3dd6447b96f5656..447fa5ff9030485bc7864a35cc7b5e3664416bc9 100644 (file)
@@ -1,4 +1,4 @@
-#include "thread/thread_pool.h"
+#include "thread_pool.h"
 
 #include <sys/queue.h>
 
similarity index 100%
rename from src/thread/thread_pool.h
rename to src/thread_pool.h
index 46ac0a1b51d03749a5ccc94bb2d20528ae726630..ab09259c8bdff7e32160d5bde8a8f204ffe6a4a8 100644 (file)
@@ -3,7 +3,6 @@
 #include <pthread.h>
 
 #include "alloc.h"
-#include "config.h"
 #include "log.h"
 
 static pthread_key_t state_key;
@@ -113,7 +112,7 @@ fnstack_cleanup(void)
                pr_op_err("pthread_setspecific() returned %d.", error);
 }
 
-/**
+/*
  * Call this function every time you're about to start processing a new file.
  * Any pr_op_err()s and friends will now include the new file name.
  * Use fnstack_pop() to revert back to the previously stacked file name.
@@ -157,14 +156,9 @@ fnstack_push(char const *file)
        files->filenames[files->len++] = file;
 }
 
-/**
- * See fnstack_push().
- *
- * This function cannot claim a reference for @map, so @map will have to outlive
- * the push/pop.
- */
+/* See fnstack_push(). @map needs to outlive the push/pop. */
 void
-fnstack_push_map(struct cache_mapping *map)
+fnstack_push_map(struct cache_mapping const *map)
 {
        fnstack_push(map_val_get_printable(map));
 }
@@ -202,7 +196,7 @@ addr2str(int af, void const *addr, char *(*buffer_cb)(struct validation *))
        return inet_ntop(af, addr, buffer_cb(state), INET6_ADDRSTRLEN);
 }
 
-/**
+/*
  * Returns @addr, converted to a printable string. Intended for minimal clutter
  * address printing.
  *
@@ -218,27 +212,21 @@ v4addr2str(struct in_addr const *addr)
        return addr2str(AF_INET, addr, validation_get_ip_buffer1);
 }
 
-/**
- * Same as v4addr2str(), except a different buffer is used.
- */
+/* Same as v4addr2str(), except a different buffer is used. */
 char const *
 v4addr2str2(struct in_addr const *addr)
 {
        return addr2str(AF_INET, addr, validation_get_ip_buffer2);
 }
 
-/**
- * See v4addr2str().
- */
+/* See v4addr2str(). */
 char const *
 v6addr2str(struct in6_addr const *addr)
 {
        return addr2str(AF_INET6, addr, validation_get_ip_buffer1);
 }
 
-/**
- * See v4addr2str2().
- */
+/* See v4addr2str2(). */
 char const *
 v6addr2str2(struct in6_addr const *addr)
 {
index 0d5d389c2767c790155a8db2bdbd66c45e062328..23bca5ae6cede447c98c229877bc53bfd901dbf1 100644 (file)
@@ -2,6 +2,7 @@
 #define SRC_THREAD_VAR_H_
 
 #include "state.h"
+#include "types/map.h"
 
 int thvar_init(void); /* This function does not need cleanup. */
 
@@ -12,7 +13,7 @@ void fnstack_init(void);
 void fnstack_cleanup(void);
 
 void fnstack_push(char const *);
-void fnstack_push_map(struct cache_mapping *);
+void fnstack_push_map(struct cache_mapping const *);
 char const *fnstack_peek(void);
 void fnstack_pop(void);
 
index 188c89b2d399114564c6c6436b70a75173f85a07..7dbe6ecf79d6e4ad56c9d38da42243ba885ae5ad 100644 (file)
@@ -5,7 +5,6 @@
 #include <stdbool.h>
 #include <sys/socket.h>
 
-#include "asn1/asn1c/IPAddress.h"
 #include "asn1/asn1c/IPAddressRange.h"
 
 struct ipv4_prefix {
similarity index 54%
rename from src/data_structure/common.h
rename to src/types/array.h
index f1ffa8f5be9e9756c63d9443b6c618d7f57bbec3..2e8f68b72c5b3fba9dfae7bedb982c5dc2fe48d0 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef SRC_DATA_STRUCTURE_COMMON_H_
-#define SRC_DATA_STRUCTURE_COMMON_H_
+#ifndef SRC_TYPES_ARRAY_H_
+#define SRC_TYPES_ARRAY_H_
 
 #include <stddef.h>
 #include <stdio.h>
@@ -10,5 +10,6 @@
 #include <unistd.h>
 
 typedef size_t array_index;
+#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0]))
 
-#endif /* SRC_DATA_STRUCTURE_COMMON_H_ */
+#endif /* SRC_TYPES_ARRAY_H_ */
similarity index 91%
rename from src/data_structure/array_list.h
rename to src/types/arraylist.h
index 7777ec2593f0aa3f332b9fa635963db3148b89cb..686f58ef2093c1ce8733397ae25fdef95442958d 100644 (file)
@@ -1,8 +1,8 @@
-#ifndef SRC_DATA_STRUCTURE_ARRAY_LIST_H_
-#define SRC_DATA_STRUCTURE_ARRAY_LIST_H_
+#ifndef SRC_TYPES_ARRAYLIST_H_
+#define SRC_TYPES_ARRAYLIST_H_
 
-#include "data_structure/common.h"
-#include "log.h"
+#include "alloc.h"
+#include "types/array.h"
 
 #define DEFINE_ARRAY_LIST_STRUCT(name, elem_type)                      \
        struct name {                                                   \
@@ -75,4 +75,4 @@
        (index)++                                                       \
 )
 
-#endif /* SRC_DATA_STRUCTURE_ARRAY_LIST_H_ */
+#endif /* SRC_TYPES_ARRAYLIST_H_ */
similarity index 63%
rename from src/as_number.h
rename to src/types/asn.h
index 28aec481e3662607f78ff9c6d17679f1a4932781..2a90bac07848ee9d1f8b2e94081dda53c100d041 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef SRC_AS_NUMBER_H_
-#define SRC_AS_NUMBER_H_
+#ifndef SRC_TYPES_ASN_H_
+#define SRC_TYPES_ASN_H_
 
 #include <arpa/inet.h>
 #include <netdb.h>
@@ -11,4 +11,4 @@ struct asn_range {
        uint32_t max;
 };
 
-#endif /* SRC_AS_NUMBER_H_ */
+#endif /* SRC_TYPES_ASN_H_ */
index ad3ba81926cce4d7b9cbfe8363494895b83341fb..86280bcfe61de6d94bd859bdaa0bdcdd23556614 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef TEST_TYPES_BIO_SEQ_H_
-#define TEST_TYPES_BIO_SEQ_H_
+#ifndef SRC_TYPES_BIO_SEQ_H_
+#define SRC_TYPES_BIO_SEQ_H_
 
 #include <openssl/bio.h>
 
@@ -8,4 +8,4 @@ void bioseq_teardown(void);
 
 BIO *BIO_new_seq(BIO *, BIO *);
 
-#endif /* TEST_TYPES_BIO_SEQ_H_ */
+#endif /* SRC_TYPES_BIO_SEQ_H_ */
index 503087a7f5a1dc6cbc111e6152010156e0ddf155..4eddaa56ff0d52d0125ae8c9cd8e52340c94d986 100644 (file)
 #include "types/map.h"
 
 #include "alloc.h"
-#include "common.h"
 #include "config.h"
 #include "log.h"
-#include "rrdp.h"
-#include "state.h"
-#include "str_token.h"
-#include "thread_var.h"
-#include "cache/local_cache.h"
-#include "config/filename_format.h"
-#include "data_structure/path_builder.h"
-
-/**
- * Aside from the reference counter, instances are meant to be immutable.
- *
- * TODO (fine) This structure is so intertwined with the cache module,
- * nowadays it feels like it should be moved there.
- */
-struct cache_mapping {
-       /**
-        * The one that always starts with "rsync://" or "https://".
-        * Normalized, ASCII-only, NULL-terminated.
-        */
-       char *url;
-
-       /**
-        * Cache location where we downloaded the file.
-        * Normalized, ASCII-only, NULL-terminated.
-        * Sometimes NULL, depending on @type.
-        */
-       char *path;
-
-       enum map_type type;
-
-       unsigned int references; /* Reference counter */
-};
-
-/*
- * @character is an integer because we sometimes receive signed chars, and other
- * times we get unsigned chars.
- * Casting a negative char into a unsigned char is undefined behavior.
- */
-static int
-validate_url_character(int character)
-{
-       /*
-        * RFCs 1738 and 3986 define a very specific range of allowed
-        * characters, but I don't think we're that concerned about URL
-        * correctness. Validating the URL properly is more involved than simply
-        * checking legal characters, anyway.
-        *
-        * What I really need this validation for is ensure that we won't get
-        * any trouble later, when we attempt to map the URL to a path.
-        *
-        * Sample trouble: Getting UTF-8 characters. Why are they trouble?
-        * Because we don't have any guarantees that the system's file name
-        * encoding is UTF-8. URIs are not supposed to contain UTF-8 in the
-        * first place, so we have no reason to deal with encoding conversion.
-        *
-        * To be perfectly fair, we have no guarantees that the system's file
-        * name encoding is ASCII-compatible either, but I need to hang onto
-        * SOMETHING.
-        *
-        * (Asking users to use UTF-8 is fine, but asking users to use something
-        * ASCII-compatible is a little better.)
-        *
-        * So just make sure that the character is printable ASCII.
-        *
-        * TODO (next iteration) Consider exhaustive URL validation.
-        */
-       return (0x20 <= character && character <= 0x7E)
-           ? 0
-           : pr_val_err("URL has non-printable character code '%d'.", character);
-}
-
-static int normalize_url(struct path_builder *, char const *, char const *, int);
-
-/**
- * Initializes @map->url by building a normalized version of @str.
- */
-static int
-init_url(struct cache_mapping *map, char const *str)
-{
-#define SCHEMA_LEN 8 /* strlen("rsync://"), strlen("https://") */
-
-       char const *s;
-       char const *pfx;
-       int error;
-       struct path_builder pb;
-
-       if (str == NULL){
-               map->url = NULL;
-               return 0;
-       }
-
-       for (s = str; s[0] != '\0'; s++) {
-               error = validate_url_character(s[0]);
-               if (error)
-                       return error;
-       }
-
-       pfx = NULL;
-       error = 0;
-
-       switch (map->type) {
-       case MAP_TA_RSYNC:
-       case MAP_RPP:
-       case MAP_CAGED:
-       case MAP_AIA:
-       case MAP_SO:
-       case MAP_MFT:
-               pfx = "rsync://";
-               error = ENOTRSYNC;
-               break;
-       case MAP_TA_HTTP:
-       case MAP_NOTIF:
-       case MAP_TMP:
-               pfx = "https://";
-               error = ENOTHTTPS;
-               break;
-       }
-
-       if (pfx == NULL)
-               pr_crit("Unknown mapping type: %u", map->type);
-
-       __pb_init(&pb, SCHEMA_LEN - 1);
-       error = normalize_url(&pb, str, pfx, error);
-       if (error) {
-               pb_cleanup(&pb);
-               return error;
-       }
-
-       map->url = strncpy(pb.string, str, SCHEMA_LEN);
-       return 0;
-}
-
-static bool
-is_valid_mft_file_chara(uint8_t chara)
-{
-       return ('a' <= chara && chara <= 'z')
-           || ('A' <= chara && chara <= 'Z')
-           || ('0' <= chara && chara <= '9')
-           || (chara == '-')
-           || (chara == '_');
-}
-
-/* RFC 6486bis, section 4.2.2 */
-static int
-validate_mft_file(IA5String_t *ia5)
-{
-       size_t dot;
-       size_t i;
-
-       if (ia5->size < 5)
-               return pr_val_err("File name is too short (%zu < 5).", ia5->size);
-       dot = ia5->size - 4;
-       if (ia5->buf[dot] != '.')
-               return pr_val_err("File name seems to lack a three-letter extension.");
-
-       for (i = 0; i < ia5->size; i++) {
-               if (i != dot && !is_valid_mft_file_chara(ia5->buf[i])) {
-                       return pr_val_err("File name contains illegal character #%u",
-                           ia5->buf[i]);
-               }
-       }
-
-       /*
-        * Actual extension doesn't matter; if there's no handler,
-        * we'll naturally ignore the file.
-        */
-       return 0;
-}
-
-/**
- * Initializes @map->url given manifest path @mft and its referenced file @ia5.
- *
- * ie. if @mft is "rsync://a/b/c.mft" and @ia5 is "d.cer", @map->url will be
- * "rsync://a/b/d.cer".
- *
- * Assumes @mft is already normalized.
- */
-static int
-ia5str2url(struct cache_mapping *map, char const *mft, IA5String_t *ia5)
-{
-       char *joined;
-       char const *slash_pos;
-       int dir_len;
-       int error;
-
-       /*
-        * IA5String is a subset of ASCII. However, IA5String_t doesn't seem to
-        * be guaranteed to be NULL-terminated.
-        * `(char *) ia5->buf` is fair, but `strlen(ia5->buf)` is not.
-        */
-
-       error = validate_mft_file(ia5);
-       if (error)
-               return error;
-
-       slash_pos = strrchr(mft, '/');
-       if (slash_pos == NULL)
-               return pr_val_err("Manifest URL '%s' contains no slashes.", mft);
-
-       dir_len = (slash_pos + 1) - mft;
-       joined = pmalloc(dir_len + ia5->size + 1);
-
-       strncpy(joined, mft, dir_len);
-       strncpy(joined + dir_len, (char *) ia5->buf, ia5->size);
-       joined[dir_len + ia5->size] = '\0';
-
-       map->url = joined;
-       return 0;
-}
-
-struct path_parser {
-       char const *token;
-       char const *slash;
-       size_t len;
-};
-
-/* Return true if there's a new token, false if we're done. */
-static bool
-path_next(struct path_parser *parser)
-{
-       if (parser->slash == NULL)
-               return false;
-
-       parser->token = parser->slash + 1;
-       parser->slash = strchr(parser->token, '/');
-       parser->len = (parser->slash != NULL)
-           ? (parser->slash - parser->token)
-           : strlen(parser->token);
-
-       return parser->token[0] != 0;
-}
-
-static bool
-path_is_dot(struct path_parser *parser)
-{
-       return parser->len == 1 && parser->token[0] == '.';
-}
-
-static bool
-path_is_dotdots(struct path_parser *parser)
-{
-       return parser->len == 2
-           && parser->token[0] == '.'
-           && parser->token[1] == '.';
-}
-
-static int
-normalize_url(struct path_builder *pb, char const *url, char const *pfx,
-    int errnot)
-{
-       struct path_parser parser;
-       size_t dot_dot_limit;
-       int error;
-
-       /* Schema */
-       if (!str_starts_with(url, pfx)) {
-               pr_val_err("URL '%s' does not begin with '%s'.", url, pfx);
-               return errnot;
-       }
-
-       /* Domain */
-       parser.slash = url + 7;
-       if (!path_next(&parser))
-               return pr_val_err("URL '%s' seems to lack a domain.", url);
-       if (path_is_dot(&parser)) {
-               /* Dumping files to the cache root is unsafe. */
-               return pr_val_err("URL '%s' employs the root domain. This is not really cacheable, so I'm going to distrust it.",
-                   url);
-       }
-       if (path_is_dotdots(&parser)) {
-               return pr_val_err("URL '%s' seems to be dot-dotting past its own schema.",
-                   url);
-       }
-       error = pb_appendn(pb, parser.token, parser.len);
-       if (error)
-               return error;
-
-       /* Other components */
-       dot_dot_limit = pb->len;
-       while (path_next(&parser)) {
-               if (path_is_dotdots(&parser)) {
-                       error = pb_pop(pb, false);
-                       if (error)
-                               return error;
-                       if (pb->len < dot_dot_limit) {
-                               return pr_val_err("URL '%s' seems to be dot-dotting past its own domain.",
-                                   url);
-                       }
-               } else if (!path_is_dot(&parser)) {
-                       error = pb_appendn(pb, parser.token, parser.len);
-                       if (error)
-                               return error;
-               }
-       }
-
-       return 0;
-}
-
-static int
-get_rrdp_workspace(struct path_builder *pb, struct cache_mapping *notif)
-{
-       int error;
-
-       error = pb_init_cache(pb, "rrdp");
-       if (error)
-               return error;
-
-       error = pb_append(pb, &notif->url[SCHEMA_LEN]);
-       if (error)
-               pb_cleanup(pb);
-
-       return error;
-}
-
-/*
- * Maps "rsync://a.b.c/d/e.cer" into "<local-repository>/rsync/a.b.c/d/e.cer".
- */
-static int
-map_simple(struct cache_mapping *map, char const *subdir)
-{
-       struct path_builder pb;
-       int error;
-
-       error = pb_init_cache(&pb, subdir);
-       if (error)
-               return error;
-
-       error = pb_append(&pb, &map->url[SCHEMA_LEN]);
-       if (error) {
-               pb_cleanup(&pb);
-               return error;
-       }
-
-       map->path = pb.string;
-       return 0;
-}
-
-/*
- * Maps "rsync://a.b.c/d/e.cer" into
- * "<local-repository>/rrdp/<notification-path>/a.b.c/d/e.cer".
- */
-static int
-map_caged(struct cache_mapping *map, struct cache_mapping *notif)
-{
-       struct path_builder pb;
-       int error;
-
-       error = get_rrdp_workspace(&pb, notif);
-       if (error)
-               return error;
-
-       if (map->url == NULL)
-               goto success; /* Caller is only interested in the cage. */
-
-       error = pb_append(&pb, &map->url[SCHEMA_LEN]);
-       if (error) {
-               pb_cleanup(&pb);
-               return error;
-       }
-
-success:
-       map->path = pb.string;
-       return 0;
-}
-
-static int
-init_path(struct cache_mapping *map, struct cache_mapping *notif)
-{
-       switch (map->type) {
-       case MAP_TA_RSYNC:
-       case MAP_RPP:
-       case MAP_MFT:
-               return map_simple(map, "rsync");
-
-       case MAP_TA_HTTP:
-               return map_simple(map, "https");
-
-       case MAP_NOTIF:
-       case MAP_TMP:
-               return cache_tmpfile(&map->path);
-
-       case MAP_CAGED:
-               return map_caged(map, notif);
-
-       case MAP_AIA:
-       case MAP_SO:
-               map->path = NULL;
-               return 0;
-       }
-
-       pr_crit("Unknown URL type: %u", map->type);
-}
-
-int
-map_create(struct cache_mapping **result, enum map_type type,
-    struct cache_mapping *notif, char const *url)
-{
-       struct cache_mapping *map;
-       int error;
-
-       map = pmalloc(sizeof(struct cache_mapping));
-       map->type = type;
-       map->references = 1;
-
-       error = init_url(map, url);
-       if (error) {
-               free(map);
-               return error;
-       }
-
-       error = init_path(map, notif);
-       if (error) {
-               free(map->url);
-               free(map);
-               return error;
-       }
-
-       *result = map;
-       return 0;
-}
-
-/*
- * Manifest fileList entries are a little special in that they're just file
- * names. This function will infer the rest of the URL.
- */
-int
-map_create_mft(struct cache_mapping **result, struct cache_mapping *notif,
-              struct cache_mapping *mft, IA5String_t *ia5)
-{
-       struct cache_mapping *map;
-       int error;
-
-       map = pmalloc(sizeof(struct cache_mapping));
-       map->type = (notif == NULL) ? MAP_RPP : MAP_CAGED;
-       map->references = 1;
-
-       error = ia5str2url(map, mft->url, ia5);
-       if (error) {
-               free(map);
-               return error;
-       }
-
-       error = init_path(map, notif);
-       if (error) {
-               free(map->url);
-               free(map);
-               return error;
-       }
-
-       *result = map;
-       return 0;
-}
-
-/* Cache-only; url and type are meaningless. */
-struct cache_mapping *
-map_create_cache(char const *path)
-{
-       struct cache_mapping *map;
-
-       map = pzalloc(sizeof(struct cache_mapping));
-       map->path = pstrdup(path);
-       map->references = 1;
-
-       return map;
-}
-
-struct cache_mapping *
-map_refget(struct cache_mapping *map)
-{
-       map->references++;
-       return map;
-}
-
-void
-map_refput(struct cache_mapping *map)
-{
-       if (map == NULL)
-               return;
-
-       map->references--;
-       if (map->references == 0) {
-               free(map->url);
-               free(map->path);
-               free(map);
-       }
-}
-
-char const *
-map_get_url(struct cache_mapping *map)
-{
-       return map->url;
-}
-
-char const *
-map_get_path(struct cache_mapping *map)
-{
-       return map->path;
-}
-
-bool
-map_equals(struct cache_mapping *m1, struct cache_mapping *m2)
-{
-       return strcmp(m1->url, m2->url) == 0;
-}
-
-bool
-str_same_origin(char const *url1, char const *url2)
-{
-       size_t c, slashes;
-
-       slashes = 0;
-       for (c = 0; url1[c] == url2[c]; c++) {
-               switch (url1[c]) {
-               case '/':
-                       slashes++;
-                       if (slashes == 3)
-                               return true;
-                       break;
-               case '\0':
-                       return slashes == 2;
-               }
-       }
-
-       if (url1[c] == '\0')
-               return (slashes == 2) && url2[c] == '/';
-       if (url2[c] == '\0')
-               return (slashes == 2) && url1[c] == '/';
-
-       return false;
-}
-
-bool
-map_same_origin(struct cache_mapping *m1, struct cache_mapping *m2)
-{
-       return str_same_origin(m1->url, m2->url);
-}
-
-/* @ext must include the period. */
-bool
-map_has_extension(struct cache_mapping *map, char const *ext)
-{
-       return str_ends_with(map->url, ext);
-}
-
-bool
-map_is_certificate(struct cache_mapping *map)
-{
-       return map_has_extension(map, ".cer");
-}
-
-enum map_type
-map_get_type(struct cache_mapping *map)
-{
-       return map->type;
-}
+#include "types/path.h"
 
 static char const *
-get_filename(char const *file_path)
-{
-       char *slash = strrchr(file_path, '/');
-       return (slash != NULL) ? (slash + 1) : file_path;
-}
-
-static char const *
-map_get_printable(struct cache_mapping *map, enum filename_format format)
+map_get_printable(struct cache_mapping const *map, enum filename_format format)
 {
        switch (format) {
        case FNF_GLOBAL:
@@ -576,7 +14,7 @@ map_get_printable(struct cache_mapping *map, enum filename_format format)
        case FNF_LOCAL:
                return map->path;
        case FNF_NAME:
-               return get_filename(map->url);
+               return path_filename(map->url);
        }
 
        pr_crit("Unknown file name format: %u", format);
@@ -584,53 +22,27 @@ map_get_printable(struct cache_mapping *map, enum filename_format format)
 }
 
 char const *
-map_val_get_printable(struct cache_mapping *map)
+map_val_get_printable(struct cache_mapping const *map)
 {
-       enum filename_format format;
-
-       format = config_get_val_log_filename_format();
-       return map_get_printable(map, format);
+       return map_get_printable(map, config_get_val_log_file_format());
 }
 
 char const *
-map_op_get_printable(struct cache_mapping *map)
-{
-       enum filename_format format;
-
-       format = config_get_op_log_filename_format();
-       return map_get_printable(map, format);
-}
-
-char *
-map_get_rrdp_workspace(struct cache_mapping *notif)
-{
-       struct path_builder pb;
-       return (get_rrdp_workspace(&pb, notif) == 0) ? pb.string : NULL;
-}
-
-DEFINE_ARRAY_LIST_FUNCTIONS(map_list, struct cache_mapping *, static)
-
-void
-maps_init(struct map_list *maps)
-{
-       map_list_init(maps);
-}
-
-static void
-__map_refput(struct cache_mapping **map)
+map_op_get_printable(struct cache_mapping const *map)
 {
-       map_refput(*map);
+       return map_get_printable(map, config_get_op_log_file_format());
 }
 
 void
-maps_cleanup(struct map_list *maps)
+map_copy(struct cache_mapping *dst, struct cache_mapping const *src)
 {
-       map_list_cleanup(maps, __map_refput);
+       dst->url = pstrdup(src->url);
+       dst->path = pstrdup(src->path);
 }
 
-/* Swallows @map. */
 void
-maps_add(struct map_list *maps, struct cache_mapping *map)
+map_cleanup(struct cache_mapping *map)
 {
-       map_list_add(maps, &map);
+       free(map->url);
+       free(map->path);
 }
index 16443a58004bed90f190329525466603cee35c9c..c7d9a4e61f96ac6b84c8712f0e2fc307b5bef880 100644 (file)
@@ -1,95 +1,18 @@
 #ifndef SRC_TYPES_MAP_H_
 #define SRC_TYPES_MAP_H_
 
-#include "asn1/asn1c/IA5String.h"
-#include "data_structure/array_list.h"
-
-/*
- * "Long" time = seven days.
- * Currently hardcoded, but queued for tweakability.
- */
-enum map_type {
-       /*
-        * TAL's TA URL.
-        * The file is cached until it's untraversed for a "long" time.
-        */
-       MAP_TA_RSYNC,
-       MAP_TA_HTTP,
-
-       /*
-        * (rsync) Repository Publication Point. RFC 6481.
-        * The directory is cached until it's untraversed for a "long" time.
-        */
-       MAP_RPP,
-
-       /*
-        * An RRDP notification file; downloaded via HTTP.
-        * The file itself is not cached, but we preserve a handful of metadata
-        * that is needed in subsequent iterations.
-        * The metadata is cached until it's untraversed for a "long" time.
-        */
-       MAP_NOTIF,
-
-       /*
-        * RRDP Snapshot or Delta; downloaded via HTTP.
-        * The file itself is not cached, but we preserve some small metadata.
-        * The metadata is destroyed once the iteration finishes.
-        */
-       MAP_TMP,
-
-       /*
-        * Endangered species; bound to be removed once RFC 9286 is implemented.
-        */
-       MAP_CAGED,
-
-       MAP_AIA, /* caIssuers. Not directly downloaded. */
-       MAP_SO, /* signedObject. Not directly downloaded. */
-       MAP_MFT, /* rpkiManifest. Not directly downloaded. */
+// XXX document this better
+struct cache_mapping {
+       /* Normalized, ASCII-only, NULL-terminated. */
+       char *url;
+       /* Normalized, ASCII-only, NULL-terminated. */
+       char *path;
 };
 
-struct cache_mapping;
-
-int map_create(struct cache_mapping **, enum map_type, struct cache_mapping *,
-              char const *);
-int map_create_mft(struct cache_mapping **, struct cache_mapping *, struct cache_mapping *,
-                  IA5String_t *);
-struct cache_mapping *map_create_cache(char const *);
-
-#define map_create_caged(map, notif, url) \
-       map_create(map, MAP_CAGED, notif, url)
-#define map_create_cage(map, notif) \
-       map_create_caged(map, notif, NULL)
-
-struct cache_mapping *map_refget(struct cache_mapping *);
-void map_refput(struct cache_mapping *);
-
-/*
- * Note that, if you intend to print some mapping, you're likely supposed to use
- * map_get_printable() instead.
- */
-char const *map_get_url(struct cache_mapping *);
-char const *map_get_path(struct cache_mapping *);
-
-bool map_equals(struct cache_mapping *, struct cache_mapping *);
-bool str_same_origin(char const *, char const *);
-bool map_same_origin(struct cache_mapping *, struct cache_mapping *);
-bool map_has_extension(struct cache_mapping *, char const *);
-bool map_is_certificate(struct cache_mapping *);
-
-enum map_type map_get_type(struct cache_mapping *);
-
-char const *map_val_get_printable(struct cache_mapping *);
-char const *map_op_get_printable(struct cache_mapping *);
-
-char *map_get_rrdp_workspace(struct cache_mapping *);
-
-/* Plural */
-
-DEFINE_ARRAY_LIST_STRUCT(map_list, struct cache_mapping *);
-
-void maps_init(struct map_list *);
-void maps_cleanup(struct map_list *);
+char const *map_val_get_printable(struct cache_mapping const *);
+char const *map_op_get_printable(struct cache_mapping const *);
 
-void maps_add(struct map_list *, struct cache_mapping *);
+void map_copy(struct cache_mapping *, struct cache_mapping const *);
+void map_cleanup(struct cache_mapping *);
 
 #endif /* SRC_TYPES_MAP_H_ */
similarity index 90%
rename from src/object/name.c
rename to src/types/name.c
index f1b66b39584c7dc1cbd4236e6edf08476830809d..556b553bcb5b7c5ad6f6c3874789f807a682c37b 100644 (file)
@@ -1,4 +1,4 @@
-#include "object/name.h"
+#include "types/name.h"
 
 #include <openssl/asn1.h>
 #include <openssl/obj_mac.h>
@@ -6,7 +6,6 @@
 #include <syslog.h>
 
 #include "alloc.h"
-#include "cert_stack.h"
 #include "log.h"
 #include "thread_var.h"
 
@@ -141,10 +140,8 @@ x509_name_equals(struct rfc5280_name *a, struct rfc5280_name *b)
 }
 
 int
-validate_issuer_name(char const *container, X509_NAME *issuer)
+validate_issuer_name(X509_NAME *issuer, X509 *parent)
 {
-       struct validation *state;
-       X509 *parent;
        struct rfc5280_name *parent_subject;
        struct rfc5280_name *child_issuer;
        int error;
@@ -156,13 +153,6 @@ validate_issuer_name(char const *container, X509_NAME *issuer)
         * But let's check it anyway.
         */
 
-       state = state_retrieve();
-       parent = x509stack_peek(validation_certstack(state));
-       if (parent == NULL) {
-               return pr_val_err("%s appears to have no parent certificate.",
-                   container);
-       }
-
        error = x509_name_decode(X509_get_subject_name(parent), "subject",
            &parent_subject);
        if (error)
@@ -179,8 +169,7 @@ validate_issuer_name(char const *container, X509_NAME *issuer)
                parent_serial = x509_name_serialNumber(parent_subject);
                child_serial = x509_name_serialNumber(child_issuer);
 
-               error = pr_val_err("%s's issuer name ('%s%s%s') does not equal issuer certificate's name ('%s%s%s').",
-                   container,
+               error = pr_val_err("Issuer name ('%s%s%s') does not equal issuer certificate's name ('%s%s%s').",
                    x509_name_commonName(child_issuer),
                    (child_serial != NULL) ? "/" : "",
                    (child_serial != NULL) ? child_serial : "",
similarity index 79%
rename from src/object/name.h
rename to src/types/name.h
index 4ded9d2ec7e443f8f35b3d3f22ea694c55dc4144..ca2d84d0d3cad0d1d1b5c170c96997412d9e8cfc 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef SRC_OBJECT_NAME_H_
-#define SRC_OBJECT_NAME_H_
+#ifndef SRC_TYPES_NAME_H_
+#define SRC_TYPES_NAME_H_
 
 #include <openssl/x509.h>
 #include <stdbool.h>
@@ -20,8 +20,8 @@ bool x509_name_equals(struct rfc5280_name *, struct rfc5280_name *);
 
 
 /* X509_NAME utils */
-int validate_issuer_name(char const *, X509_NAME *);
+int validate_issuer_name(X509_NAME *, X509 *);
 
 void x509_name_pr_debug(char const *, X509_NAME *);
 
-#endif /* SRC_OBJECT_NAME_H_ */
+#endif /* SRC_TYPES_NAME_H_ */
similarity index 69%
rename from src/data_structure/path_builder.c
rename to src/types/path.c
index c1a226bc230840a21d3cbe04c4ca17966c2e2d09..1fe54acf6dc62e58d2e32aa87b8452a22cd6c928 100644 (file)
@@ -1,10 +1,9 @@
-#include "data_structure/path_builder.h"
+#include "types/path.h"
 
 #include <errno.h>
 
 #include "alloc.h"
 #include "config.h"
-#include "crypto/hash.h"
 #include "log.h"
 
 /* These are arbitrary; feel free to change them. */
 #endif
 #define MAX_CAPACITY 4096u
 
+static bool
+is_delimiter(char chara)
+{
+       return chara == '/' || chara == '\0';
+}
+
+void
+token_init(struct tokenizer *tkn, char const *str)
+{
+       tkn->str = str;
+       tkn->len = 0;
+}
+
+/* Like strtok_r(), but doesn't corrupt the string. */
+bool
+token_next(struct tokenizer *tkn)
+{
+       tkn->str += tkn->len;
+       while (tkn->str[0] == '/')
+               tkn->str++;
+       if (tkn->str[0] == '\0')
+               return false;
+       for (tkn->len = 1; !is_delimiter(tkn->str[tkn->len]); tkn->len++)
+               ;
+       return true;
+}
+
 /* @reserve needs to be < INITIAL_CAPACITY. */
 void
 __pb_init(struct path_builder *pb, size_t reserve)
@@ -188,3 +214,61 @@ pb_cleanup(struct path_builder *pb)
 {
        free(pb->string);
 }
+
+/* Note, fatal is hardcoded as 1. */
+char *
+path_parent(char const *child)
+{
+       struct path_builder pb;
+       pb.string = pstrdup(child);
+       pb.len = pb.capacity = strlen(pb.string);
+       pb_pop(&pb, true);
+       return pb.string;
+}
+
+char *
+path_childn(char const *p1, char const *p2, size_t p2len)
+{
+       struct path_builder pb;
+
+       pb_init(&pb);
+       pb_append(&pb, p1); // XXX
+       pb_appendn(&pb, p2, p2len); // XXX
+
+       return pb.string;
+}
+
+char const *
+path_filename(char const *path)
+{
+       char *slash = strrchr(path, '/');
+       return slash ? (slash + 1) : path;
+}
+
+/*
+ * Cannot return NULL.
+ *
+ * XXX I'm starting to use this more. Probably clean the slashes.
+ */
+char *
+path_join(char const *path1, char const *path2)
+{
+       size_t n;
+       char *result;
+       int written;
+
+       // XXX needed?
+       if (path1[0] == 0)
+               return pstrdup(path2);
+       if (path2 == NULL || path2[0] == 0)
+               return pstrdup(path1);
+
+       n = strlen(path1) + strlen(path2) + 2;
+       result = pmalloc(n);
+
+       written = snprintf(result, n, "%s/%s", path1, path2);
+       if (written != n - 1)
+               pr_crit("join_paths: %zu %d %s %s", n, written, path1, path2);
+
+       return result;
+}
similarity index 50%
rename from src/data_structure/path_builder.h
rename to src/types/path.h
index 1ddc4bee195ade88b9ff41f893be9cf3bfe9e877..7c9619beef88fefedbf3763d2d341a647f7a5cd3 100644 (file)
@@ -1,9 +1,27 @@
-#ifndef SRC_DATA_STRUCTURE_PATH_BUILDER_H_
-#define SRC_DATA_STRUCTURE_PATH_BUILDER_H_
+#ifndef SRC_TYPES_PATH_H_
+#define SRC_TYPES_PATH_H_
 
+#include <arpa/inet.h>
 #include <netdb.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// XXX rename
+struct tokenizer {
+       char const *str;
+       size_t len;
+};
 
-#include "types/map.h"
+void token_init(struct tokenizer *, char const *);
+bool token_next(struct tokenizer *tkn);
 
 struct path_builder {
        char *string;
@@ -30,4 +48,9 @@ void pb_reverse(struct path_builder *);
 
 void pb_cleanup(struct path_builder *);
 
-#endif /* SRC_DATA_STRUCTURE_PATH_BUILDER_H_ */
+char *path_parent(char const *);
+char *path_childn(char const *, char const *, size_t);
+char const *path_filename(char const *);
+char *path_join(char const *, char const *);
+
+#endif /* SRC_TYPES_PATH_H_ */
diff --git a/src/types/rpp.c b/src/types/rpp.c
new file mode 100644 (file)
index 0000000..ed61166
--- /dev/null
@@ -0,0 +1,21 @@
+#include "types/rpp.h"
+
+#include "types/array.h"
+
+void
+rpp_cleanup(struct rpp *rpp)
+{
+       array_index i;
+
+       for (i = 0; i < rpp->nfiles; i++)
+               map_cleanup(&rpp->files[i]);
+       free(rpp->files);
+       rpp->files = NULL;
+       rpp->nfiles = 0;
+
+       rpp->crl.map = NULL;
+       if (rpp->crl.obj != NULL) {
+               X509_CRL_free(rpp->crl.obj);
+               rpp->crl.obj = NULL;
+       }
+}
diff --git a/src/types/rpp.h b/src/types/rpp.h
new file mode 100644 (file)
index 0000000..8440700
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef SRC_RPP_H_
+#define SRC_RPP_H_
+
+#include <openssl/x509.h>
+
+#include "types/map.h"
+
+/* Repository Publication Point */
+struct rpp {
+       struct cache_mapping *files;
+       size_t nfiles;                          /* Number of maps in @files */
+
+       struct {
+               struct cache_mapping *map;      /* Points to @files entry */
+               X509_CRL *obj;
+       } crl;
+};
+
+void rpp_cleanup(struct rpp *);
+
+#endif /* SRC_RPP_H_ */
similarity index 99%
rename from src/sorted_array.c
rename to src/types/sorted_array.c
index c5447e605238d8df4bb28b02316ecde9fb0b5845..ee6c4bfe7c4e073df237c66249500c8fcdcbb215 100644 (file)
@@ -1,4 +1,4 @@
-#include "sorted_array.h"
+#include "types/sorted_array.h"
 
 #include "alloc.h"
 #include "log.h"
similarity index 92%
rename from src/sorted_array.h
rename to src/types/sorted_array.h
index 12db6c98493111f33a34e5cd297014de0bbb2aaf..c590e15155f610f2e83bf736962a5253ef02012f 100644 (file)
@@ -1,5 +1,5 @@
-#ifndef SRC_SORTED_ARRAY_H_
-#define SRC_SORTED_ARRAY_H_
+#ifndef SRC_TYPES_SORTED_ARRAY_H_
+#define SRC_TYPES_SORTED_ARRAY_H_
 
 #include <stdbool.h>
 #include <stddef.h>
@@ -53,4 +53,4 @@ int sarray_foreach(struct sorted_array *, sarray_foreach_cb, void *);
 
 char const *sarray_err2str(int);
 
-#endif /* SRC_SORTED_ARRAY_H_ */
+#endif /* SRC_TYPES_SORTED_ARRAY_H_ */
similarity index 79%
rename from src/str_token.c
rename to src/types/str.c
index 9f01dc8ae5a88d2e4b4f74116e11df5fc00d7393..3c74edda2207c74302559da5fe3d36227fdbc79c 100644 (file)
@@ -1,10 +1,9 @@
-#include "str_token.h"
+#include "types/str.h"
 
 #include <openssl/bio.h>
-#include <stdint.h>
 
-#include "alloc.h"
 #include "log.h"
+#include "types/path.h"
 
 /**
  * Does not assume that @string is NULL-terminated.
@@ -139,3 +138,39 @@ token_count(struct string_tokenizer *tokenizer)
 
        return count;
 }
+
+void
+strlist_init(struct strlist *list)
+{
+       list->array = NULL;
+       list->len = 0;
+       list->capacity = 0;
+}
+
+void
+strlist_add(struct strlist *list, char *str)
+{
+       if (list->array == NULL) {
+               list->capacity = 8;
+               list->array = pmalloc(list->capacity * sizeof(char *));
+       }
+
+       list->len++;
+       while (list->len >= list->capacity) {
+               list->capacity *= 2;
+               list->array = prealloc(list->array,
+                   list->capacity * sizeof(char *));
+       }
+
+       list->array[list->len - 1] = str;
+}
+
+/* Call strlist_init() again if you want to reuse the list. */
+void
+strlist_cleanup(struct strlist *list)
+{
+       array_index i;
+       for (i = 0; i < list->len; i++)
+               free(list->array[i]);
+       free(list->array);
+}
similarity index 75%
rename from src/str_token.h
rename to src/types/str.h
index aaaf10dd758e254389a7fdb6b60dd92beb5cbe07..0489a103bd8ecc5eeb2a05ace4b9632e8aeb3f34 100644 (file)
@@ -1,22 +1,15 @@
-#ifndef SRC_STR_TOKEN_H_
-#define SRC_STR_TOKEN_H_
+#ifndef SRC_TYPES_STR_H_
+#define SRC_TYPES_STR_H_
 
 #include <openssl/asn1.h>
 #include <openssl/bn.h>
 #include <stdbool.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <strings.h>
-#include <sys/types.h>
-#include <unistd.h>
+
+#include "types/arraylist.h"
 
 int ia5s2string(ASN1_IA5STRING *, char **);
 int BN2string(BIGNUM *, char **);
 
-/* This file is named "str_token.h" because "string.h" collides with <string.h>. */
-
 /**
  * Do not modify fields directly; this should be private.
  *
@@ -42,4 +35,12 @@ bool token_equals(struct string_tokenizer *, struct string_tokenizer *);
 char *token_read(struct string_tokenizer *);
 size_t token_count(struct string_tokenizer *);
 
-#endif /* SRC_STR_TOKEN_H_ */
+/* Plural */
+
+DEFINE_ARRAY_LIST_STRUCT(strlist, char *);
+
+void strlist_init(struct strlist *);
+void strlist_cleanup(struct strlist *);
+void strlist_add(struct strlist *, char *);
+
+#endif /* SRC_TYPES_STR_H_ */
diff --git a/src/types/url.c b/src/types/url.c
new file mode 100644 (file)
index 0000000..46c4dff
--- /dev/null
@@ -0,0 +1,160 @@
+#include "types/url.h"
+
+#include "alloc.h"
+#include "common.h"
+#include "types/path.h"
+
+bool
+url_is_rsync(char const *url)
+{
+       return str_starts_with(url, "rsync://");
+}
+
+bool
+url_is_https(char const *url)
+{
+       return str_starts_with(url, "https://");
+}
+
+/*
+ * XXX use this:
+ *
+ *     for (s = str; s[0] != '\0'; s++) {
+               error = validate_url_character(s[0]);
+               if (error)
+                       return error;
+       }
+ *
+ * @character is an integer because we sometimes receive signed chars, and other
+ * times we get unsigned chars.
+ * Casting a negative char into a unsigned char is undefined behavior.
+ */
+//static int
+//validate_url_character(int character)
+//{
+//     /*
+//      * RFCs 1738 and 3986 define a very specific range of allowed
+//      * characters, but I don't think we're that concerned about URL
+//      * correctness. Validating the URL properly is more involved than simply
+//      * checking legal characters, anyway.
+//      *
+//      * What I really need this validation for is ensure that we won't get
+//      * any trouble later, when we attempt to map the URL to a path.
+//      *
+//      * Sample trouble: Getting UTF-8 characters. Why are they trouble?
+//      * Because we don't have any guarantees that the system's file name
+//      * encoding is UTF-8. URIs are not supposed to contain UTF-8 in the
+//      * first place, so we have no reason to deal with encoding conversion.
+//      *
+//      * To be perfectly fair, we have no guarantees that the system's file
+//      * name encoding is ASCII-compatible either, but I need to hang onto
+//      * SOMETHING.
+//      *
+//      * (Asking users to use UTF-8 is fine, but asking users to use something
+//      * ASCII-compatible is a little better.)
+//      *
+//      * So just make sure that the character is printable ASCII.
+//      *
+//      * TODO (next iteration) Consider exhaustive URL validation.
+//      */
+//     return (0x20 <= character && character <= 0x7E)
+//         ? 0
+//         : pr_val_err("URL has non-printable character code '%d'.", character);
+//}
+
+static char *
+path_rewind(char const *root, char *cursor)
+{
+       for (cursor -= 2; root <= cursor; cursor--)
+               if (*cursor == '/')
+                       return cursor + 1;
+       return NULL;
+}
+
+static bool
+has_bad_prefix(char const *url)
+{
+       // XXX what happens if code expects one but url is the other
+       if (strncmp(url, "rsync://", RPKI_SCHEMA_LEN) &&
+           strncmp(url, "https://", RPKI_SCHEMA_LEN))
+               return true;
+
+       /* Disallow the root domain */
+       url += RPKI_SCHEMA_LEN;
+       if (url[0] == '/')
+               return true;
+       if (url[0] == '.' && url[1] == '/')
+               return true;
+
+       // XXX read the standard and reject more bad URLs
+       return false;
+}
+
+/*
+ * Collapses '//' (except from the schema), '.' and '..'.
+ *
+ * "rsync://a.b/./c//.././/d/." -> "rsync://a.b/d"
+ */
+char *
+url_normalize(char const *url)
+{
+       char *normal, *dst;
+       struct tokenizer tkn;
+
+       if (has_bad_prefix(url))
+               return NULL;
+
+       normal = pstrdup(url);
+       dst = normal + RPKI_SCHEMA_LEN;
+       token_init(&tkn, url + RPKI_SCHEMA_LEN);
+
+       while (token_next(&tkn)) {
+               if (tkn.len == 1 && tkn.str[0] == '.')
+                       continue;
+               if (tkn.len == 2 && tkn.str[0] == '.' && tkn.str[1] == '.') {
+                       dst = path_rewind(normal + RPKI_SCHEMA_LEN, dst);
+                       if (!dst)
+                               goto fail;
+                       continue;
+               }
+               strncpy(dst, tkn.str, tkn.len);
+               dst[tkn.len] = '/';
+               dst += tkn.len + 1;
+       }
+
+       /* Reject URL if there's nothing after the schema. Maybe unnecessary. */
+       if (dst == normal + RPKI_SCHEMA_LEN)
+               goto fail;
+
+       dst[-1] = '\0';
+       return normal;
+
+fail:  free(normal);
+       return NULL;
+}
+
+bool
+url_same_origin(char const *url1, char const *url2)
+{
+       size_t c, slashes;
+
+       slashes = 0;
+       for (c = 0; url1[c] == url2[c]; c++) {
+               switch (url1[c]) {
+               case '/':
+                       slashes++;
+                       if (slashes == 3)
+                               return true;
+                       break;
+               case '\0':
+                       return slashes == 2;
+               }
+       }
+
+       if (url1[c] == '\0')
+               return (slashes == 2) && url2[c] == '/';
+       if (url2[c] == '\0')
+               return (slashes == 2) && url1[c] == '/';
+
+       return false;
+}
diff --git a/src/types/url.h b/src/types/url.h
new file mode 100644 (file)
index 0000000..a2b0ea5
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef SRC_TYPES_URL_H_
+#define SRC_TYPES_URL_H_
+
+#include <stdbool.h>
+
+#define RPKI_SCHEMA_LEN 8 /* strlen("rsync://"), strlen("https://") */
+
+bool url_is_rsync(char const *);
+bool url_is_https(char const *);
+
+char *url_normalize(char const *);
+bool url_same_origin(char const *, char const *);
+
+#endif /* SRC_TYPES_URL_H_ */
similarity index 99%
rename from src/data_structure/uthash.h
rename to src/types/uthash.h
index 0013ed37304dcee950221b61d7d6638d55e1c116..d02632ca9ce58b15e50e5bcbae357673a57928ae 100644 (file)
@@ -21,8 +21,8 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
-#ifndef UTHASH_H
-#define UTHASH_H
+#ifndef SRC_TYPES_UTHASH_H
+#define SRC_TYPES_UTHASH_H
 
 #define UTHASH_VERSION 2.1.0
 
@@ -1133,6 +1133,8 @@ for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL));
   (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))
 #endif
 
+/* #define HASH_ITER_UNSAFE(head, el) for ((el)=(head); (el)!=NULL; (el)=(el)->hh.next) */
+
 /* obtain a count of items in the hash */
 #define HASH_COUNT(head) HASH_CNT(hh,head)
 #define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)
@@ -1205,4 +1207,4 @@ typedef struct UT_hash_handle {
    unsigned hashv;                   /* result of hash-fcn(key)        */
 } UT_hash_handle;
 
-#endif /* UTHASH_H */
+#endif /* SRC_TYPES_UTHASH_H */
index a1673c5a5a48647dc0ee0ce3dedad831d48f6262..bdb612ebfd1fded64b97eb754d475dd43a03454f 100644 (file)
@@ -8,7 +8,7 @@
 #include <stdint.h>
 
 /*
- * A ROA.
+ * A ROA payload.
  *
  * I think it's called "VRP" ("Validated ROA Payload") because it was originally
  * meant to represent an already validated ROA, and used exclusively by the RTR
index 9beec0a07fd3640b7158e65f7cf018d43627a631..2e284c1f923297e00185646fc1b0ed953d6fc87e 100644 (file)
@@ -1,10 +1,7 @@
 #ifndef SRC_VALIDATION_HANDLER_H_
 #define SRC_VALIDATION_HANDLER_H_
 
-#include "as_number.h"
-#include "object/name.h"
-#include "types/address.h"
-#include "types/router_key.h"
+#include "rtr/db/vrps.h"
 
 /**
  * Functions that handle validation results.
index 98d84c93d129caf347e193244cbf762a67025c30..afb3833808194ce272e11f033c6f8a32ffc0cc1a 100644 (file)
@@ -12,8 +12,7 @@ if USE_TESTS
 # <mumble>_CFLAGS is not defined.
 # Otherwise it must be included manually:
 #      mumble_mumble_CFLAGS = ${AM_CFLAGS} flag1 flag2 flag3 ...
-AM_CFLAGS =  -pedantic -Wall
-#AM_CFLAGS += -Wno-unused
+AM_CFLAGS =  -Wall -Wpedantic
 AM_CFLAGS += -std=c99 -D_DEFAULT_SOURCE=1 -D_XOPEN_SOURCE=700 -D_BSD_SOURCE=1
 AM_CFLAGS += -I../src -DUNIT_TESTING ${CHECK_CFLAGS} ${XML2_CFLAGS} ${JANSSON_CFLAGS}
 # Reminder: As opposed to AM_CFLAGS, "AM_LDADD" is not idiomatic automake, and
@@ -25,32 +24,40 @@ MY_LDADD = ${CHECK_LIBS} ${JANSSON_LIBS}
 check_PROGRAMS  = address.test
 check_PROGRAMS += base64.test
 check_PROGRAMS += cache.test
+check_PROGRAMS += common.test
 check_PROGRAMS += db_table.test
 check_PROGRAMS += deltas_array.test
 check_PROGRAMS += hash.test
-check_PROGRAMS += json.test
-check_PROGRAMS += pb.test
+check_PROGRAMS += mft.test
+check_PROGRAMS += path.test
 check_PROGRAMS += pdu_handler.test
 check_PROGRAMS += pdu_stream.test
 check_PROGRAMS += rrdp.test
+check_PROGRAMS += rrdp_update.test
+check_PROGRAMS += rsync.test
 check_PROGRAMS += serial.test
 check_PROGRAMS += tal.test
 check_PROGRAMS += thread_pool.test
-check_PROGRAMS += map.test
+check_PROGRAMS += url.test
 check_PROGRAMS += uthash.test
 check_PROGRAMS += vcard.test
 check_PROGRAMS += vrps.test
 check_PROGRAMS += xml.test
 TESTS = ${check_PROGRAMS}
 
+###############################################################################
+
 address_test_SOURCES = types/address_test.c
 address_test_LDADD = ${MY_LDADD}
 
-base64_test_SOURCES = crypto/base64_test.c
+base64_test_SOURCES = base64_test.c
 base64_test_LDADD = ${MY_LDADD}
 
-cache_test_SOURCES = cache/local_cache_test.c
-cache_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
+cache_test_SOURCES = cache_test.c
+cache_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} ${XML2_LIBS}
+
+common_test_SOURCES = common_test.c
+common_test_LDADD = ${MY_LDADD}
 
 db_table_test_SOURCES = rtr/db/db_table_test.c
 db_table_test_LDADD = ${MY_LDADD}
@@ -58,14 +65,14 @@ db_table_test_LDADD = ${MY_LDADD}
 deltas_array_test_SOURCES = rtr/db/deltas_array_test.c
 deltas_array_test_LDADD = ${MY_LDADD}
 
-hash_test_SOURCES = crypto/hash_test.c
+hash_test_SOURCES = hash_test.c
 hash_test_LDADD = ${MY_LDADD}
 
-json_test_SOURCES = json_util_test.c
-json_test_LDADD = ${MY_LDADD}
+mft_test_SOURCES = object/manifest_test.c
+mft_test_LDADD = ${MY_LDADD}
 
-pb_test_SOURCES = data_structure/path_builder_test.c
-pb_test_LDADD = ${MY_LDADD}
+path_test_SOURCES = types/path_test.c
+path_test_LDADD = ${MY_LDADD}
 
 pdu_handler_test_SOURCES = rtr/pdu_handler_test.c
 pdu_handler_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
@@ -76,19 +83,25 @@ pdu_stream_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
 rrdp_test_SOURCES = rrdp_test.c
 rrdp_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} ${XML2_LIBS}
 
+rrdp_update_test_SOURCES = rrdp_update_test.c
+rrdp_update_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS} ${XML2_LIBS}
+
+rsync_test_SOURCES = rsync_test.c
+rsync_test_LDADD = ${MY_LDADD}
+
 serial_test_SOURCES = types/serial_test.c
 serial_test_LDADD = ${MY_LDADD}
 
-tal_test_SOURCES = tal_test.c
+tal_test_SOURCES = object/tal_test.c
 tal_test_LDADD = ${MY_LDADD}
 
 thread_pool_test_SOURCES = thread_pool_test.c
 thread_pool_test_LDADD = ${MY_LDADD}
 
-map_test_SOURCES = types/map_test.c
-map_test_LDADD = ${MY_LDADD}
+url_test_SOURCES = types/url_test.c
+url_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
 
-uthash_test_SOURCES = data_structure/uthash_test.c
+uthash_test_SOURCES = types/uthash_test.c
 uthash_test_LDADD = ${MY_LDADD}
 
 vcard_test_SOURCES = vcard_test.c
@@ -100,6 +113,8 @@ vrps_test_LDADD = ${MY_LDADD} ${JANSSON_LIBS}
 xml_test_SOURCES = xml_test.c
 xml_test_LDADD = ${MY_LDADD} ${XML2_LIBS}
 
+###############################################################################
+
 EXTRA_DIST  = mock.c mock.h
 EXTRA_DIST += resources/lorem-ipsum.txt
 EXTRA_DIST += rtr/db/rtr_db_mock.c
similarity index 99%
rename from test/crypto/base64_test.c
rename to test/base64_test.c
index dc40b33c076e0ac473badb0c67b281173d2b64cd..5055046b75f0a92744efb2da94a3ebfbc7622d80 100644 (file)
@@ -1,9 +1,10 @@
 #include <check.h>
 
 #include "alloc.c"
+#include "base64.c"
 #include "common.h"
 #include "mock.c"
-#include "crypto/base64.c"
+#include "types/array.h"
 
 static void
 ck_uchar_array(unsigned char *expected, size_t expected_len,
diff --git a/test/cache/local_cache_test.c b/test/cache/local_cache_test.c
deleted file mode 100644 (file)
index 71ea725..0000000
+++ /dev/null
@@ -1,964 +0,0 @@
-/* This test will create temporal directory "tmp/". Needs permissions. */
-
-#include "cache/local_cache.c"
-
-#include <check.h>
-#include <stdarg.h>
-#include <sys/queue.h>
-
-#include "alloc.c"
-#include "common.c"
-#include "json_util.c"
-#include "mock.c"
-#include "data_structure/path_builder.c"
-#include "types/map.c"
-
-/* Mocks */
-
-static struct rpki_cache *cache;
-
-static bool dl_error; /* Download should return error? */
-
-struct downloaded_path {
-       char *path;
-       bool visited;
-       SLIST_ENTRY(downloaded_path) hook;
-};
-
-/* Paths downloaded during the test */
-static SLIST_HEAD(downloaded_paths, downloaded_path) downloaded;
-
-static unsigned int rsync_counter; /* Times the rsync function was called */
-static unsigned int https_counter; /* Times the https function was called */
-
-int
-file_exists(char const *file)
-{
-       struct downloaded_path *path;
-       SLIST_FOREACH(path, &downloaded, hook)
-               if (strcmp(file, path->path) == 0)
-                       return 0;
-       return ENOENT;
-}
-
-int
-file_rm_rf(char const *file)
-{
-       struct downloaded_path *path;
-       SLIST_FOREACH(path, &downloaded, hook)
-               if (strcmp(file, path->path) == 0) {
-                       SLIST_REMOVE(&downloaded, path, downloaded_path, hook);
-                       free(path->path);
-                       free(path);
-                       return 0;
-               }
-       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(char const *local)
-{
-       struct downloaded_path *dl;
-
-       if (dl_error)
-               return -EINVAL;
-       if (file_exists(local) == 0)
-               return 0;
-
-       dl = pmalloc(sizeof(struct downloaded_path));
-       dl->path = pstrdup(local);
-       dl->visited = false;
-       SLIST_INSERT_HEAD(&downloaded, dl, hook);
-       return 0;
-}
-
-int
-rsync_download(char const *src, char const *dst, bool is_directory)
-{
-       rsync_counter++;
-       return pretend_download(dst);
-}
-
-int
-http_download(struct cache_mapping *map, curl_off_t ims, bool *changed)
-{
-       int error;
-       https_counter++;
-       error = pretend_download(map_get_path(map));
-       if (changed != NULL)
-               *changed = error ? false : true;
-       return error;
-}
-
-MOCK_ABORT_INT(rrdp_update, struct cache_mapping *map)
-__MOCK_ABORT(rrdp_notif2json, json_t *, NULL, struct cachefile_notification *notif)
-MOCK_VOID(rrdp_notif_free, struct cachefile_notification *notif)
-MOCK_ABORT_INT(rrdp_json2notif, json_t *json, struct cachefile_notification **result)
-
-/* Helpers */
-
-static void
-setup_test(void)
-{
-       ck_assert_int_eq(0, system("rm -rf tmp/"));
-
-       dl_error = false;
-       cache = cache_create();
-       ck_assert_ptr_ne(NULL, cache);
-       SLIST_INIT(&downloaded);
-}
-
-static void
-run_cache_download(char const *url, int expected_error,
-    unsigned int rsync_calls, unsigned int https_calls)
-{
-       struct cache_mapping *map;
-       enum map_type type;
-
-       if (str_starts_with(url, "https://"))
-               type = MAP_TA_HTTP;
-       else if (str_starts_with(url, "rsync://"))
-               type = MAP_RPP;
-       else
-               ck_abort_msg("Bad protocol: %s", url);
-
-       rsync_counter = 0;
-       https_counter = 0;
-
-       ck_assert_int_eq(0, map_create(&map, type, NULL, url));
-       ck_assert_int_eq(expected_error, cache_download(cache, map, NULL, NULL));
-       ck_assert_uint_eq(rsync_calls, rsync_counter);
-       ck_assert_uint_eq(https_calls, https_counter);
-
-       map_refput(map);
-}
-
-static struct cache_node *
-node(char const *url, time_t attempt, int err, bool succeeded, time_t success,
-    bool is_notif)
-{
-       enum map_type type;
-       struct cache_node *result;
-
-       if (str_starts_with(url, "https://"))
-               type = is_notif ? MAP_NOTIF : MAP_TA_HTTP;
-       else if (str_starts_with(url, "rsync://"))
-               type = MAP_RPP;
-       else
-               ck_abort_msg("Bad protocol: %s", url);
-
-       result = pzalloc(sizeof(struct cache_node));
-       ck_assert_int_eq(0, map_create(&result->map, type, NULL, url));
-       result->attempt.ts = attempt;
-       result->attempt.result = err;
-       result->success.happened = succeeded;
-       result->success.ts = success;
-
-       return result;
-}
-
-#define NODE(url, err, succeeded, has_file) \
-       node(url, has_file, err, succeeded, 0, 0)
-
-static void
-reset_visiteds(void)
-{
-       struct downloaded_path *path;
-       SLIST_FOREACH(path, &downloaded, hook)
-               path->visited = false;
-}
-
-static struct downloaded_path *
-find_downloaded_path(struct cache_node *node)
-{
-       struct downloaded_path *path;
-
-       SLIST_FOREACH(path, &downloaded, hook)
-               if (strcmp(map_get_path(node->map), path->path) == 0) {
-                       if (path->visited)
-                               return NULL;
-                       else {
-                               path->visited = true;
-                               return path;
-                       }
-               }
-
-       return NULL;
-}
-
-static void
-fail_if_nonvisited(void)
-{
-       struct downloaded_path *path;
-       SLIST_FOREACH(path, &downloaded, hook)
-               if (!path->visited)
-                       ck_abort_msg("Unexpected cache file: %s", path->path);
-}
-
-static void
-validate_node(struct cache_node *expected, struct cache_node *actual)
-{
-       if (expected == NULL) {
-               ck_assert_ptr_eq(NULL, actual);
-               return;
-       }
-
-       ck_assert_str_eq(map_get_url(expected->map), map_get_url(actual->map));
-       /* ck_assert_int_eq(expected->attempt.ts, actual->attempt.ts); */
-       ck_assert_int_eq(expected->attempt.result, actual->attempt.result);
-       ck_assert_int_eq(expected->success.happened, actual->success.happened);
-       /* ck_assert_int_eq(expected->success.ts, actual->success.ts); */
-}
-
-static void
-validate_cache(int trash, ...)
-{
-       struct cache_node *expected = NULL;
-       struct cache_node *e, *a, *tmp;
-       struct downloaded_path *path;
-       char const *key;
-       va_list args;
-
-       printf("------------------------------\n");
-       printf("Expected nodes:\n");
-
-       va_start(args, trash);
-       while ((e = va_arg(args, struct cache_node *)) != NULL) {
-               printf("- %s %s error:%u success:%u\n",
-                   map_get_url(e->map), map_get_path(e->map),
-                   e->attempt.result, e->success.happened);
-
-               key = map_get_url(e->map);
-               HASH_ADD_KEYPTR(hh, expected, key, strlen(key), e);
-       }
-       va_end(args);
-       printf("\n");
-
-       printf("Actual nodes:\n");
-       HASH_ITER(hh, cache->ht, a, tmp)
-               printf("- %s %s attempt:%u success:%u\n",
-                   map_get_url(a->map), map_get_path(a->map),
-                   a->attempt.result, a->success.happened);
-       printf("\n");
-
-       printf("Files in cache:\n");
-       SLIST_FOREACH(path, &downloaded, hook)
-               printf("- %s\n", path->path);
-       printf("\n");
-
-       /* Compare expected and cache */
-       reset_visiteds();
-
-       HASH_ITER(hh, expected, e, tmp) {
-               path = find_downloaded_path(e);
-               if (e->attempt.ts) { /* "if should have cache file" */
-                       if (path == NULL)
-                               ck_abort_msg("Cached file is missing: %s",
-                                   map_get_path(e->map));
-                       path->visited = true;
-               } else {
-                       if (path != NULL) {
-                               ck_abort_msg("Cached file should not exist: %s",
-                                   path->path);
-                       }
-               }
-       }
-
-       fail_if_nonvisited();
-
-       /* Compare expected and actual */
-       HASH_ITER(hh, cache->ht, a, tmp) {
-               key = map_get_url(a->map);
-               HASH_FIND_STR(expected, key, e);
-               if (e == NULL)
-                       ck_abort_msg("Unexpected actual: %s", key);
-
-               validate_node(e, a);
-
-               HASH_DEL(expected, e);
-               map_refput(e->map);
-               free(e);
-       }
-
-       if (HASH_COUNT(expected) != 0)
-               ck_abort_msg("Actual node is mising: %s",
-                   map_get_url(expected->map));
-}
-
-static void
-new_iteration(bool outdate)
-{
-       struct cache_node *node, *tmp;
-       time_t epoch;
-
-       epoch = outdate ? get_days_ago(30) : get_days_ago(1);
-       HASH_ITER(hh, cache->ht, node, tmp)
-               node->attempt.ts = epoch;
-}
-
-static void
-cache_reset(struct rpki_cache *cache)
-{
-       struct cache_node *node, *tmp;
-       HASH_ITER(hh, cache->ht, node, tmp)
-               delete_node(cache, node);
-}
-
-static void
-cleanup_test(void)
-{
-       struct downloaded_path *path;
-
-       dl_error = false;
-       cache_destroy(cache);
-
-       while (!SLIST_EMPTY(&downloaded)) {
-               path = SLIST_FIRST(&downloaded);
-               SLIST_REMOVE_HEAD(&downloaded, hook);
-               free(path->path);
-               free(path);
-       }
-}
-
-/* Tests */
-
-START_TEST(test_cache_download_rsync)
-{
-       setup_test();
-
-       run_cache_download("rsync://a.b.c/d", 0, 1, 0);
-       validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
-
-       /* Redownload same file, nothing should happen */
-       run_cache_download("rsync://a.b.c/d", 0, 0, 0);
-       validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
-
-       /*
-        * rsyncs are recursive, which means if we've been recently asked to
-        * download d, we needn't bother redownloading d/e.
-        */
-       run_cache_download("rsync://a.b.c/d/e", 0, 0, 0);
-       validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
-
-       /*
-        * rsyncs get truncated, because it results in much faster
-        * synchronization in practice.
-        * This is not defined in any RFCs; it's an effective standard,
-        * and there would be consequences for violating it.
-        */
-       run_cache_download("rsync://x.y.z/m/n/o", 0, 1, 0);
-       validate_cache(0,
-           NODE("rsync://a.b.c/d/", 0, 1, true),
-           NODE("rsync://x.y.z/m/", 0, 1, true),
-           NULL);
-
-       /* Sibling */
-       run_cache_download("rsync://a.b.c/e/f", 0, 1, 0);
-       validate_cache(0,
-           NODE("rsync://a.b.c/d/", 0, 1, true),
-           NODE("rsync://a.b.c/e/", 0, 1, true),
-           NODE("rsync://x.y.z/m/", 0, 1, true),
-           NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_cache_download_rsync_error)
-{
-       setup_test();
-
-       dl_error = false;
-       run_cache_download("rsync://a.b.c/d", 0, 1, 0);
-       dl_error = true;
-       run_cache_download("rsync://a.b.c/e", -EINVAL, 1, 0);
-       validate_cache(0,
-           NODE("rsync://a.b.c/d/", 0, 1, true),
-           NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
-           NULL);
-
-       /* Regardless of error, not reattempted because same iteration */
-       dl_error = true;
-       run_cache_download("rsync://a.b.c/e", -EINVAL, 0, 0);
-       validate_cache(0,
-           NODE("rsync://a.b.c/d/", 0, 1, true),
-           NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
-           NULL);
-
-       dl_error = false;
-       run_cache_download("rsync://a.b.c/e", -EINVAL, 0, 0);
-       validate_cache(0,
-           NODE("rsync://a.b.c/d/", 0, 1, true),
-           NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
-           NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_cache_cleanup_rsync)
-{
-       setup_test();
-
-       /*
-        * First iteration: Tree is created. No prunes, because nothing's
-        * outdated.
-        */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/d", 0, 1, 0);
-       run_cache_download("rsync://a.b.c/e", 0, 1, 0);
-       cache_cleanup(cache);
-       validate_cache(0,
-           NODE("rsync://a.b.c/d/", 0, 1, true),
-           NODE("rsync://a.b.c/e/", 0, 1, true),
-           NULL);
-
-       /* One iteration with no changes, for paranoia */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/d", 0, 1, 0);
-       run_cache_download("rsync://a.b.c/e", 0, 1, 0);
-       cache_cleanup(cache);
-       validate_cache(0,
-               NODE("rsync://a.b.c/d/", 0, 1, true),
-               NODE("rsync://a.b.c/e/", 0, 1, true),
-               NULL);
-
-       /* Add one sibling */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/d", 0, 1, 0);
-       run_cache_download("rsync://a.b.c/e", 0, 1, 0);
-       run_cache_download("rsync://a.b.c/f", 0, 1, 0);
-       cache_cleanup(cache);
-       validate_cache(0,
-               NODE("rsync://a.b.c/d/", 0, 1, true),
-               NODE("rsync://a.b.c/e/", 0, 1, true),
-               NODE("rsync://a.b.c/f/", 0, 1, true),
-               NULL);
-
-       /* Nodes don't get updated, but they're still too young. */
-       new_iteration(false);
-       cache_cleanup(cache);
-       validate_cache(0,
-               NODE("rsync://a.b.c/d/", 0, 1, true),
-               NODE("rsync://a.b.c/e/", 0, 1, true),
-               NODE("rsync://a.b.c/f/", 0, 1, true),
-               NULL);
-
-       /* Remove some branches */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/d", 0, 1, 0);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
-
-       /* Remove old branch and add sibling at the same time */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/e", 0, 1, 0);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
-
-       /* Try child */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/e/f/g", 0, 1, 0);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
-
-       /* Parent again */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/e", 0, 1, 0);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
-
-       /* Empty the tree */
-       new_iteration(true);
-       cache_cleanup(cache);
-       validate_cache(0, NULL);
-
-       /* Node exists, but file doesn't */
-       new_iteration(true);
-       run_cache_download("rsync://a.b.c/e", 0, 1, 0);
-       run_cache_download("rsync://a.b.c/f", 0, 1, 0);
-       validate_cache(0,
-               NODE("rsync://a.b.c/e/", 0, 1, true),
-               NODE("rsync://a.b.c/f/", 0, 1, true),
-               NULL);
-       ck_assert_int_eq(0, file_rm_rf("tmp/rsync/a.b.c/f"));
-       cache_cleanup(cache);
-       validate_cache(0, NODE("rsync://a.b.c/e/", 0, 1, true), NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_cache_cleanup_rsync_error)
-{
-       setup_test();
-
-       /* Set up */
-       dl_error = false;
-       run_cache_download("rsync://a.b.c/d", 0, 1, 0);
-       dl_error = true;
-       run_cache_download("rsync://a.b.c/e", -EINVAL, 1, 0);
-       validate_cache(0,
-               NODE("rsync://a.b.c/d/", 0, 1, true),
-               NODE("rsync://a.b.c/e/", -EINVAL, 0, false),
-               NULL);
-
-       /* Node gets deleted because cached file doesn't exist */
-       cache_cleanup(cache);
-       validate_cache(0, NODE("rsync://a.b.c/d/", 0, 1, true), NULL);
-
-       /*
-        * Node and file do not get deleted, because the failure is still not
-        * that old.
-        * Deletion does not depend on success or failure.
-        */
-       new_iteration(false);
-       dl_error = true;
-       run_cache_download("rsync://a.b.c/d", -EINVAL, 1, 0);
-       validate_cache(0, NODE("rsync://a.b.c/d/", -EINVAL, 1, true), NULL);
-
-       /* Error is old; gets deleted */
-       new_iteration(true);
-       cache_cleanup(cache);
-       validate_cache(0, NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_cache_download_https)
-{
-       setup_test();
-
-       /* Download *file* e. */
-       run_cache_download("https://a.b.c/d/e", 0, 0, 1);
-       validate_cache(0, NODE("https://a.b.c/d/e", 0, 1, 1), NULL);
-
-       /* Download something else 1 */
-       run_cache_download("https://a.b.c/e", 0, 0, 1);
-       validate_cache(0,
-           NODE("https://a.b.c/d/e", 0, 1, 1),
-           NODE("https://a.b.c/e", 0, 1, 1),
-           NULL);
-
-       /* Download something else 2 */
-       run_cache_download("https://x.y.z/e", 0, 0, 1);
-       validate_cache(0,
-           NODE("https://a.b.c/d/e", 0, 1, 1),
-           NODE("https://a.b.c/e", 0, 1, 1),
-           NODE("https://x.y.z/e", 0, 1, 1),
-           NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_cache_download_https_error)
-{
-       setup_test();
-
-       dl_error = false;
-       run_cache_download("https://a.b.c/d", 0, 0, 1);
-       dl_error = true;
-       run_cache_download("https://a.b.c/e", -EINVAL, 0, 1);
-       validate_cache(0,
-           NODE("https://a.b.c/d", 0, 1, 1),
-           NODE("https://a.b.c/e", -EINVAL, 0, 0),
-           NULL);
-
-       /* Regardless of error, not reattempted because same iteration */
-       dl_error = true;
-       run_cache_download("https://a.b.c/d", 0, 0, 0);
-       dl_error = false;
-       run_cache_download("https://a.b.c/e", -EINVAL, 0, 0);
-       validate_cache(0,
-           NODE("https://a.b.c/d", 0, 1, 1),
-           NODE("https://a.b.c/e", -EINVAL, 0, 0),
-           NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_cache_cleanup_https)
-{
-       setup_test();
-
-       /* First iteration; make a tree and clean it */
-       new_iteration(true);
-       run_cache_download("https://a.b.c/d", 0, 0, 1);
-       run_cache_download("https://a.b.c/e", 0, 0, 1);
-       cache_cleanup(cache);
-       validate_cache(0,
-               NODE("https://a.b.c/d", 0, 1, 1),
-               NODE("https://a.b.c/e", 0, 1, 1),
-               NULL);
-
-       /* Remove one branch */
-       new_iteration(true);
-       run_cache_download("https://a.b.c/d", 0, 0, 1);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
-
-       /* Change the one branch */
-       new_iteration(true);
-       run_cache_download("https://a.b.c/e", 0, 0, 1);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("https://a.b.c/e", 0, 1, 1), NULL);
-
-       /* Add a child to the same branch, do not update the old one */
-       new_iteration(true);
-       run_cache_download("https://a.b.c/e/f/g", 0, 0, 1);
-       cache_cleanup(cache);
-       validate_cache(0,
-               NODE("https://a.b.c/e/f/g", 0, 1, 1), NULL);
-
-       /*
-        * Download parent, do not update child.
-        * Children need to die, because parent is now a file.
-        */
-       new_iteration(true);
-       run_cache_download("https://a.b.c/e/f", 0, 0, 1);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("https://a.b.c/e/f", 0, 1, 1), NULL);
-
-       /* Do it again. */
-       new_iteration(true);
-       run_cache_download("https://a.b.c/e", 0, 0, 1);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("https://a.b.c/e", 0, 1, 1), NULL);
-
-       /* Empty the tree */
-       new_iteration(true);
-       cache_cleanup(cache);
-       validate_cache(0, NULL);
-
-       /* Node exists, but file doesn't */
-       new_iteration(true);
-       run_cache_download("https://a.b.c/e", 0, 0, 1);
-       run_cache_download("https://a.b.c/f/g/h", 0, 0, 1);
-       validate_cache(0,
-           NODE("https://a.b.c/e", 0, 1, 1),
-           NODE("https://a.b.c/f/g/h", 0, 1, 1),
-           NULL);
-       ck_assert_int_eq(0, file_rm_rf("tmp/https/a.b.c/f/g/h"));
-       cache_cleanup(cache);
-       validate_cache(0, NODE("https://a.b.c/e", 0, 1, 1), NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_cache_cleanup_https_error)
-{
-       setup_test();
-
-       /* Set up */
-       dl_error = false;
-       run_cache_download("https://a.b.c/d", 0, 0, 1);
-       dl_error = true;
-       run_cache_download("https://a.b.c/e", -EINVAL, 0, 1);
-       validate_cache(0,
-           NODE("https://a.b.c/d", 0, 1, 1),
-           NODE("https://a.b.c/e", -EINVAL, 0, 0),
-           NULL);
-
-       /* Deleted because file ENOENT. */
-       cache_cleanup(cache);
-       validate_cache(0,
-           NODE("https://a.b.c/d", 0, 1, 1),
-           NULL);
-
-       /* Fail d */
-       new_iteration(false);
-       dl_error = true;
-       run_cache_download("https://a.b.c/d", -EINVAL, 0, 1);
-       validate_cache(0, NODE("https://a.b.c/d", -EINVAL, 1, 1), NULL);
-
-       /* Not deleted, because not old */
-       new_iteration(false);
-       cache_cleanup(cache);
-       validate_cache(0, NODE("https://a.b.c/d", -EINVAL, 1, 1), NULL);
-
-       /* Become old */
-       new_iteration(true);
-       cache_cleanup(cache);
-       validate_cache(0, NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_dots)
-{
-       setup_test();
-
-       run_cache_download("https://a.b.c/d", 0, 0, 1);
-       validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
-
-       run_cache_download("https://a.b.c/d/.", 0, 0, 0);
-       validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
-
-       run_cache_download("https://a.b.c/d/e/..", 0, 0, 0);
-       validate_cache(0, NODE("https://a.b.c/d", 0, 1, 1), NULL);
-
-       run_cache_download("https://a.b.c/./d/../e", 0, 0, 1);
-       validate_cache(0,
-           NODE("https://a.b.c/d", 0, 1, 1),
-           NODE("https://a.b.c/./d/../e", 0, 1, 1),
-           NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-START_TEST(test_tal_json)
-{
-       json_t *json;
-       char *str;
-
-       setup_test();
-
-       ck_assert_int_eq(0, system("rm -rf tmp/"));
-       ck_assert_int_eq(0, system("mkdir -p tmp"));
-
-       add_node(cache, NODE("rsync://a.b.c/d", 0, 1, 0));
-       add_node(cache, NODE("rsync://a.b.c/e", 1, 0, 0));
-       add_node(cache, NODE("rsync://x.y.z/e", 0, 1, 0));
-       add_node(cache, NODE("https://a/b", 1, 1, 0));
-       add_node(cache, node("https://a/c", 0, 0, 1, 0, 1));
-
-       json = build_tal_json(cache);
-       ck_assert_int_eq(0, json_dump_file(json, "tmp/" TAL_METAFILE, JSON_COMPACT));
-
-       str = json_dumps(json, /* JSON_INDENT(4) */ JSON_COMPACT);
-       json_decref(json);
-
-       ck_assert_str_eq(
-           "[{\"type\":\"RPP\",\"url\":\"rsync://a.b.c/d\",\"attempt-timestamp\":\"1970-01-01T00:00:00Z\",\"attempt-result\":0,\"success-timestamp\":\"1970-01-01T00:00:00Z\"},"
-           "{\"type\":\"RPP\",\"url\":\"rsync://a.b.c/e\",\"attempt-timestamp\":\"1970-01-01T00:00:00Z\",\"attempt-result\":1},"
-           "{\"type\":\"RPP\",\"url\":\"rsync://x.y.z/e\",\"attempt-timestamp\":\"1970-01-01T00:00:00Z\",\"attempt-result\":0,\"success-timestamp\":\"1970-01-01T00:00:00Z\"},"
-           "{\"type\":\"TA (HTTP)\",\"url\":\"https://a/b\",\"attempt-timestamp\":\"1970-01-01T00:00:00Z\",\"attempt-result\":1,\"success-timestamp\":\"1970-01-01T00:00:00Z\"},"
-           "{\"type\":\"RRDP Notification\",\"url\":\"https://a/c\",\"attempt-timestamp\":\"1970-01-01T00:00:00Z\",\"attempt-result\":0,\"success-timestamp\":\"1970-01-01T00:00:00Z\"}]",
-           str);
-       free(str);
-
-       cache_reset(cache);
-
-       load_tal_json(cache);
-       ck_assert_ptr_ne(NULL, cache->ht);
-
-       validate_cache(0,
-           NODE("rsync://a.b.c/d", 0, 1, 0),
-           NODE("rsync://a.b.c/e", 1, 0, 0),
-           NODE("rsync://x.y.z/e", 0, 1, 0),
-           NODE("https://a/b", 1, 1, 0),
-           NODE("https://a/c", 0, 1, 0),
-           NULL);
-
-       cleanup_test();
-}
-END_TEST
-
-static void
-prepare_map_list(struct map_list *maps, ...)
-{
-       char const *str;
-       enum map_type type;
-       struct cache_mapping *map;
-       va_list args;
-
-       maps_init(maps);
-
-       va_start(args, maps);
-       while ((str = va_arg(args, char const *)) != NULL) {
-               if (str_starts_with(str, "https://"))
-                       type = MAP_TA_HTTP;
-               else if (str_starts_with(str, "rsync://"))
-                       type = MAP_RPP;
-               else
-                       ck_abort_msg("Bad protocol: %s", str);
-               ck_assert_int_eq(0, map_create(&map, type, NULL, str));
-               maps_add(maps, map);
-       }
-       va_end(args);
-}
-
-#define PREPARE_MAP_LIST(maps, ...) prepare_map_list(maps, ##__VA_ARGS__, NULL)
-
-START_TEST(test_recover)
-{
-       struct map_list maps;
-
-       setup_test();
-
-       /* Query on empty database */
-       PREPARE_MAP_LIST(&maps, "rsync://a.b.c/d", "https://a.b.c/d");
-       ck_assert_ptr_eq(NULL, cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* Only first URI is cached */
-       cache_reset(cache);
-       run_cache_download("rsync://a/b/c", 0, 1, 0);
-
-       PREPARE_MAP_LIST(&maps, "rsync://a/b/c", "https://d/e", "https://f");
-       ck_assert_ptr_eq(maps.array[0], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* Only second URI is cached */
-       cache_reset(cache);
-       run_cache_download("https://d/e", 0, 0, 1);
-
-       PREPARE_MAP_LIST(&maps, "rsync://a/b/c", "https://d/e", "https://f");
-       ck_assert_ptr_eq(maps.array[1], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* Only third URI is cached */
-       cache_reset(cache);
-       run_cache_download("https://f", 0, 0, 1);
-
-       PREPARE_MAP_LIST(&maps, "rsync://a/b/c", "https://d/e", "https://f");
-       ck_assert_ptr_eq(maps.array[2], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* None was cached */
-       cache_reset(cache);
-       run_cache_download("rsync://d/e", 0, 1, 0);
-
-       PREPARE_MAP_LIST(&maps, "rsync://a/b/c", "https://d/e", "https://f");
-       ck_assert_ptr_eq(NULL, cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /*
-        * At present, cache_recover() can only be called after all of a
-        * download's URLs yielded failure.
-        * However, node.error can still be zero. This happens when the download
-        * was successful, but the RRDP code wasn't able to expand the snapshot
-        * or deltas.
-        */
-       cache_reset(cache);
-
-       add_node(cache, node("rsync://a/1", 100, 0, 1, 100, 0));
-       add_node(cache, node("rsync://a/2", 100, 1, 1, 100, 0));
-       add_node(cache, node("rsync://a/3", 200, 0, 1, 100, 0));
-       add_node(cache, node("rsync://a/4", 200, 1, 1, 100, 0));
-       add_node(cache, node("rsync://a/5", 100, 0, 1, 200, 0));
-       add_node(cache, node("rsync://a/6", 100, 1, 1, 200, 0));
-       add_node(cache, node("rsync://b/1", 100, 0, 0, 100, 0));
-       add_node(cache, node("rsync://b/2", 100, 1, 0, 100, 0));
-       add_node(cache, node("rsync://b/3", 200, 0, 0, 100, 0));
-       add_node(cache, node("rsync://b/4", 200, 1, 0, 100, 0));
-       add_node(cache, node("rsync://b/5", 100, 0, 0, 200, 0));
-       add_node(cache, node("rsync://b/6", 100, 1, 0, 200, 0));
-
-       /* Multiple successful caches: Prioritize the most recent one */
-       PREPARE_MAP_LIST(&maps, "rsync://a/1", "rsync://a/3", "rsync://a/5");
-       ck_assert_ptr_eq(maps.array[2], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       PREPARE_MAP_LIST(&maps, "rsync://a/5", "rsync://a/1", "rsync://a/3");
-       ck_assert_ptr_eq(maps.array[0], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* No successful caches: No viable candidates */
-       PREPARE_MAP_LIST(&maps, "rsync://b/2", "rsync://b/4", "rsync://b/6");
-       ck_assert_ptr_eq(NULL, cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* Status: CNF_SUCCESS is better than 0. */
-       PREPARE_MAP_LIST(&maps, "rsync://b/1", "rsync://a/1");
-       ck_assert_ptr_eq(maps.array[1], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /*
-        * If CNF_SUCCESS && error, Fort will probably run into a problem
-        * reading the cached directory, because it's either outdated or
-        * recently corrupted.
-        * But it should still TRY to read it, as there's a chance the
-        * outdatedness is not that severe.
-        */
-       PREPARE_MAP_LIST(&maps, "rsync://a/2", "rsync://b/2");
-       ck_assert_ptr_eq(maps.array[0], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* Parents of downloaded nodes */
-       PREPARE_MAP_LIST(&maps, "rsync://a", "rsync://b");
-       ck_assert_ptr_eq(NULL, cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       /* Try them all at the same time */
-       PREPARE_MAP_LIST(&maps,
-           "rsync://a", "rsync://a/1", "rsync://a/2", "rsync://a/3",
-           "rsync://a/4", "rsync://a/5", "rsync://a/6",
-           "rsync://b", "rsync://b/1", "rsync://b/2", "rsync://b/3",
-           "rsync://b/4", "rsync://b/5", "rsync://b/6",
-           "rsync://e/1");
-       ck_assert_ptr_eq(maps.array[5], cache_recover(cache, &maps));
-       maps_cleanup(&maps);
-
-       cleanup_test();
-}
-END_TEST
-
-/* Boilerplate */
-
-static Suite *thread_pool_suite(void)
-{
-       Suite *suite;
-       TCase *rsync , *https, *dot, *meta, *recover;
-
-       rsync = tcase_create("rsync");
-       tcase_add_test(rsync, test_cache_download_rsync);
-       tcase_add_test(rsync, test_cache_download_rsync_error);
-       tcase_add_test(rsync, test_cache_cleanup_rsync);
-       tcase_add_test(rsync, test_cache_cleanup_rsync_error);
-
-       https = tcase_create("https");
-       tcase_add_test(https, test_cache_download_https);
-       tcase_add_test(https, test_cache_download_https_error);
-       tcase_add_test(https, test_cache_cleanup_https);
-       tcase_add_test(https, test_cache_cleanup_https_error);
-
-       dot = tcase_create("dot");
-       tcase_add_test(dot, test_dots);
-
-       meta = tcase_create(TAL_METAFILE);
-       tcase_add_test(meta, test_tal_json);
-
-       recover = tcase_create("recover");
-       tcase_add_test(recover, test_recover);
-
-       suite = suite_create("local-cache");
-       suite_add_tcase(suite, rsync);
-       suite_add_tcase(suite, https);
-       suite_add_tcase(suite, dot);
-       suite_add_tcase(suite, meta);
-       suite_add_tcase(suite, recover);
-
-       return suite;
-}
-
-int main(void)
-{
-       Suite *suite;
-       SRunner *runner;
-       int tests_failed;
-
-       suite = thread_pool_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/cache_test.c b/test/cache_test.c
new file mode 100644 (file)
index 0000000..1340007
--- /dev/null
@@ -0,0 +1,720 @@
+/* This will create some test files in "tmp/". Needs permissions. */
+
+#include <check.h>
+#include <sys/queue.h>
+
+#include "alloc.c"
+#include "base64.c"
+#include "common.c"
+#include "cache.c"
+#include "cachetmp.c"
+#include "file.c"
+#include "hash.c"
+#include "json_util.c"
+#include "mock.c"
+#include "mock_https.c"
+#include "rrdp_util.h"
+#include "relax_ng.c"
+#include "rrdp.c"
+#include "types/map.c"
+#include "types/path.c"
+#include "types/str.c"
+#include "types/url.c"
+
+/* Mocks */
+
+static unsigned int rsync_counter; /* Times the rsync function was called */
+
+static void
+touch_file(char const *dir)
+{
+       char cmd[64];
+       ck_assert(snprintf(cmd, sizeof(cmd), "touch %s/file", dir) < sizeof(cmd));
+       ck_assert_int_eq(0, system(cmd));
+}
+
+int
+rsync_download(char const *url, char const *path)
+{
+       rsync_counter++;
+
+       if (dl_error)
+               return dl_error;
+
+       ck_assert_int_eq(0, mkdir_p(path, true));
+       touch_file(path);
+
+       return 0;
+}
+
+MOCK_VOID(__delete_node_cb, struct cache_node const *node)
+
+/* Helpers */
+
+static void
+setup_test(void)
+{
+       dl_error = 0;
+       ck_assert_int_eq(0, system("rm -rf tmp"));
+       init_tables();
+       ck_assert_int_eq(0, system("mkdir -p tmp/rsync tmp/https tmp/rrdp tmp/fallback"));
+}
+
+static struct cache_cage *
+run_dl_rsync(char *caRepository, unsigned int expected_calls)
+{
+       struct sia_uris sias = { .caRepository = caRepository };
+       struct cache_cage *cage;
+
+       rsync_counter = 0;
+       https_counter = 0;
+       printf("---- Downloading... ----\n");
+       cage = cache_refresh_sias(&sias);
+       printf("---- Downloaded. ----\n");
+       ck_assert_uint_eq(expected_calls, rsync_counter);
+       ck_assert_uint_eq(0, https_counter);
+
+       return cage;
+}
+
+static void
+run_dl_https(char const *url, unsigned int expected_calls,
+    char const *expected_result)
+{
+       char const *result;
+
+       rsync_counter = 0;
+       https_counter = 0;
+       printf("---- Downloading... ----\n");
+       result = cache_refresh_url(url);
+       printf("---- Downloaded. ----\n");
+       ck_assert_uint_eq(0, rsync_counter);
+       ck_assert_uint_eq(expected_calls, https_counter);
+
+       ck_assert_str(expected_result, result);
+       ck_assert_str(NULL, cache_fallback_url(url));
+}
+
+
+static void
+ck_cage(struct cache_cage *cage, char const *url,
+    char const *refresh, char const *fallback)
+{
+       struct cache_node *bkp;
+
+       ck_assert_str(refresh, cage_map_file(cage, url));
+
+       bkp = cage->refresh;
+       cage_disable_refresh(cage);
+
+       ck_assert_str(fallback, cage_map_file(cage, url));
+
+       cage->refresh = bkp;
+}
+
+static int
+print_file(const char *fpath, const struct stat *sb, int typeflag,
+    struct FTW *ftwbuf)
+{
+       printf("- %s\n", fpath);
+       return 0;
+}
+
+static void
+print_tree(void)
+{
+       printf("Tree nodes:\n");
+       cache_print();
+       printf("\n");
+
+       printf("Files in cache:\n");
+       ck_assert_int_eq(0, nftw("tmp/", print_file, 32, FTW_PHYS));
+       printf("\n");
+}
+
+static void
+queue_commit(char const *caRepository, char const *path1, char const *path2)
+{
+       struct rpp rpp = { 0 };
+
+       rpp.nfiles = 2;
+       rpp.files = pzalloc(rpp.nfiles * sizeof(struct cache_mapping));
+       rpp.files[0].url = path_join(caRepository, "manifest.mft");
+       rpp.files[0].path = pstrdup(path1);
+       rpp.files[1].url = path_join(caRepository, "cert.cer");
+       rpp.files[1].path = pstrdup(path2);
+
+       cache_commit_rpp(caRepository, &rpp);
+}
+
+/* Only validates the first character of the file. */
+static void
+ck_file(char const *path, char const *expected)
+{
+       FILE *file;
+       char actual[2];
+
+       file = fopen(path, "rb");
+       if (!file)
+               ck_abort_msg("fopen(%s): %s", path, strerror(errno));
+       ck_assert_int_eq(1, fread(actual, 1, 1, file));
+       fclose(file);
+       actual[1] = 0;
+
+       ck_assert_str_eq(expected, actual);
+}
+
+static va_list fs_valist;
+
+static int
+ck_filesystem_file(const char *fpath, const struct stat *sb, int typeflag,
+    struct FTW *ftwbuf)
+{
+       static va_list args;
+       char const *path;
+       bool found = false;
+
+       if ((sb->st_mode & S_IFMT) != S_IFREG)
+               return 0;
+
+       va_copy(args, fs_valist);
+       while ((path = va_arg(args, char const *)) != NULL)
+               if (strcmp(fpath, path) == 0) {
+                       found = true;
+                       break;
+               }
+       va_end(args);
+
+       if (!found)
+               ck_abort_msg("Unexpected file: %s", fpath);
+       return 0;
+}
+
+static void
+ck_filesystem(char const *root, ...)
+{
+       char const *path;
+       int error;
+
+       va_start(fs_valist, root);
+       while ((path = va_arg(fs_valist, char const *)) != NULL)
+               ck_file(path, va_arg(fs_valist, char const *));
+       va_end(fs_valist);
+
+       va_start(fs_valist, root);
+       errno = 0;
+       error = nftw(root, ck_filesystem_file, 32, FTW_PHYS);
+       if (error)
+               ck_abort_msg("nftw: %d %d", error, errno);
+       va_end(fs_valist);
+}
+
+static void
+init_node_rsync(struct cache_node *node, char *url, char *path,
+    int fresh, int dlerr)
+{
+       node->map.url = url;
+       node->map.path = path;
+       node->fresh = fresh;
+       node->dlerr = dlerr;
+       node->rrdp = NULL;
+}
+
+static void
+init_node_https(struct cache_node *node, char *url, char *path,
+    int fresh, int dlerr)
+{
+       node->map.url = url;
+       node->map.path = path;
+       node->fresh = fresh;
+       node->dlerr = dlerr;
+       node->rrdp = NULL;
+}
+
+static void
+ck_cache_node_eq(struct cache_node *expected, struct cache_node *actual)
+{
+       ck_assert_str_eq(expected->map.url, actual->map.url);
+       ck_assert_str_eq(expected->map.path, actual->map.path);
+       ck_assert_int_eq(expected->fresh, actual->fresh);
+       ck_assert_int_eq(expected->dlerr, actual->dlerr);
+       if (expected->rrdp == NULL)
+               ck_assert_ptr_eq(expected->rrdp, actual->rrdp);
+       // XXX else
+}
+
+static void
+ck_cache(struct cache_node *expecteds, struct cache_table *tbl)
+{
+       struct cache_node *actual, *tmp;
+       unsigned int n;
+
+       for (n = 0; expecteds[n].map.url != NULL; n++)
+               ;
+       ck_assert_uint_eq(n, HASH_COUNT(tbl->nodes));
+
+       n = 0;
+       HASH_ITER(hh, tbl->nodes, actual, tmp) {
+               ck_cache_node_eq(&expecteds[n], actual);
+               n++;
+       }
+}
+
+static void
+ck_cache_rsync(struct cache_node *expected)
+{
+       ck_cache(expected, &cache.rsync);
+}
+
+static void
+ck_cache_https(struct cache_node *expected)
+{
+       ck_cache(expected, &cache.https);
+}
+
+static time_t
+get_days_ago(int days)
+{
+       time_t tt_now, last_week;
+       struct tm tm;
+       int error;
+
+       tt_now = time_fatal();
+       if (localtime_r(&tt_now, &tm) == NULL) {
+               error = errno;
+               pr_crit("localtime_r(tt, &tm) returned error: %s",
+                   strerror(error));
+       }
+       tm.tm_mday -= days;
+       last_week = mktime(&tm);
+       if (last_week == (time_t) -1)
+               pr_crit("mktime(tm) returned (time_t) -1.");
+
+       return last_week;
+}
+
+static time_t epoch;
+
+static void
+unfreshen(struct cache_table *tbl, struct cache_node *node)
+{
+       node->fresh = 0;
+}
+
+static int
+nftw_unfreshen(const char *fpath, const struct stat *sb, int typeflag,
+    struct FTW *ftwbuf)
+{
+       struct timespec times[2];
+
+       times[0].tv_sec = epoch;
+       times[0].tv_nsec = 0;
+       times[1].tv_sec = epoch;
+       times[1].tv_nsec = 0;
+
+       ck_assert_int_eq(0, utimensat(AT_FDCWD, fpath, times, AT_SYMLINK_NOFOLLOW));
+
+       return 0;
+}
+
+static void
+new_iteration(bool outdate)
+{
+       epoch = outdate ? get_days_ago(30) : get_days_ago(1);
+
+       pr_op_debug("--- Unfreshening... ---");
+       cache_foreach(unfreshen);
+       ck_assert_int_eq(0, nftw("tmp/rsync", nftw_unfreshen, 32, FTW_PHYS));
+
+       pr_op_debug("---- Tree now stale. ----");
+       cache_print();
+}
+
+static void
+cleanup_test(void)
+{
+       dl_error = 0;
+       cache_commit();
+}
+
+/* Tests */
+
+START_TEST(test_cache_download_rsync)
+{
+       struct cache_node nodes[4] = { 0 };
+       struct cache_cage *cage;
+
+       setup_test();
+
+       printf("==== Startup ====\n");
+       cage = run_dl_rsync("rsync://a.b.c/d", 1);
+       ck_assert_ptr_ne(NULL, cage);
+       ck_cage(cage, "rsync://a.b.c/d", "tmp/rsync/0", NULL);
+       ck_cage(cage, "rsync://a.b.c/d/e/f.cer", "tmp/rsync/0/e/f.cer", NULL);
+       init_node_rsync(&nodes[0], "rsync://a.b.c/d", "tmp/rsync/0", 1, 0);
+       ck_cache_rsync(nodes);
+       free(cage);
+
+       printf("==== Redownload same file, nothing should happen ====\n");
+       cage = run_dl_rsync("rsync://a.b.c/d", 0);
+       ck_assert_ptr_ne(NULL, cage);
+       ck_cage(cage, "rsync://a.b.c/d", "tmp/rsync/0", NULL);
+       ck_cage(cage, "rsync://a.b.c/d/e/f.cer", "tmp/rsync/0/e/f.cer", NULL);
+       ck_cache_rsync(nodes);
+       free(cage);
+
+       /*
+        * rsyncs are recursive, which means if we've been recently asked to
+        * download d, we needn't bother redownloading d/e.
+        */
+       printf("==== Don't redownload child ====\n");
+       cage = run_dl_rsync("rsync://a.b.c/d/e", 0);
+       ck_assert_ptr_ne(NULL, cage);
+       ck_cage(cage, "rsync://a.b.c/d", "tmp/rsync/0", NULL);
+       ck_cage(cage, "rsync://a.b.c/d/e/f.cer", "tmp/rsync/0/e/f.cer", NULL);
+       ck_cache_rsync(nodes);
+       free(cage);
+
+       /*
+        * rsyncs get truncated, because it results in much faster
+        * synchronization in practice.
+        * This is not defined in any RFCs; it's an effective standard,
+        * and there would be consequences for violating it.
+        */
+       printf("==== rsync truncated ====\n");
+       cage = run_dl_rsync("rsync://x.y.z/m/n/o", 1);
+       ck_assert_ptr_ne(NULL, cage);
+       ck_cage(cage, "rsync://x.y.z/m", "tmp/rsync/1", NULL);
+       ck_cage(cage, "rsync://x.y.z/m/n/o", "tmp/rsync/1/n/o", NULL);
+       init_node_rsync(&nodes[1], "rsync://x.y.z/m", "tmp/rsync/1", 1, 0);
+       ck_cache_rsync(nodes);
+       free(cage);
+
+       printf("==== Sibling ====\n");
+       cage = run_dl_rsync("rsync://a.b.c/e/f", 1);
+       ck_assert_ptr_ne(NULL, cage);
+       ck_cage(cage, "rsync://a.b.c/e", "tmp/rsync/2", NULL);
+       ck_cage(cage, "rsync://a.b.c/e/f/x/y/z", "tmp/rsync/2/f/x/y/z", NULL);
+       init_node_rsync(&nodes[2], "rsync://a.b.c/e", "tmp/rsync/2", 1, 0);
+       ck_cache_rsync(nodes);
+       free(cage);
+
+       cleanup_test();
+}
+END_TEST
+
+START_TEST(test_cache_download_rsync_error)
+{
+       struct cache_node nodes[3] = { 0 };
+
+       setup_test();
+
+       init_node_rsync(&nodes[0], "rsync://a.b.c/d", "tmp/rsync/0", 1, 0);
+       init_node_rsync(&nodes[1], "rsync://a.b.c/e", "tmp/rsync/1", 1, EINVAL);
+
+       printf("==== Startup ====\n");
+       dl_error = 0;
+       free(run_dl_rsync("rsync://a.b.c/d", 1));
+       dl_error = EINVAL;
+       ck_assert_ptr_eq(NULL, run_dl_rsync("rsync://a.b.c/e", 1));
+       ck_cache_rsync(nodes);
+
+       printf("==== Regardless of error, not reattempted because same iteration ====\n");
+       dl_error = EINVAL;
+       ck_assert_ptr_eq(NULL, run_dl_rsync("rsync://a.b.c/e", 0));
+       ck_cache_rsync(nodes);
+       dl_error = 0;
+       ck_assert_ptr_eq(NULL, run_dl_rsync("rsync://a.b.c/e", 0));
+       ck_cache_rsync(nodes);
+
+       cleanup_test();
+}
+END_TEST
+
+START_TEST(test_rsync_commit)
+{
+       unsigned int i;
+
+       setup_test();
+
+       ck_assert_int_eq(0, system("mkdir -p tmp/rsync/0 tmp/rsync/1 tmp/rsync/2 tmp/rsync/3"));
+
+       /* RPP0: Will remain constant */
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/0/0", "A"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/0/1", "B"));
+       /* RPP1: Will be added in its second cycle */
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/1/0", "C"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/1/1", "D"));
+       /* RPP2: Will be removed in its second cycle */
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/2/0", "E"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/2/1", "F"));
+       /* RPP3: Will be updated in its second cycle */
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/3/0", "G")); /* Keeper */
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/3/1", "H")); /* Added */
+       ck_assert_int_eq(0, write_simple_file("tmp/rsync/3/2", "I")); /* Removed */
+
+       /* Commit 1: Empty -> Empty */
+       /* Commit 2: Empty -> Empty (just free noise) */
+       for (i = 0; i < 2; i++) {
+               commit_fallbacks();
+               ck_filesystem("tmp/fallback", NULL);
+
+               new_iteration(false);
+       }
+
+       /* Commit 3: Empty -> Populated */
+       queue_commit("rsync://domain/mod/rpp0", "tmp/rsync/0/0", "tmp/rsync/0/1");
+       queue_commit("rsync://domain/mod/rpp2", "tmp/rsync/2/0", "tmp/rsync/2/1");
+       queue_commit("rsync://domain/mod/rpp3", "tmp/rsync/3/0", "tmp/rsync/3/2");
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback",
+           /* RPP0 */ "tmp/fallback/0/0", "A", "tmp/fallback/0/1", "B",
+           /* RPP2 */ "tmp/fallback/1/0", "E", "tmp/fallback/1/1", "F",
+           /* RPP3 */ "tmp/fallback/2/0", "G", "tmp/fallback/2/1", "I",
+           NULL);
+
+       new_iteration(false);
+
+       /* Commit 4: Populated -> Populated */
+       /* XXX check the refresh does, in fact, only return fallbacks when the RPP doesn't change */
+       queue_commit("rsync://domain/mod/rpp0", "tmp/fallback/0/0", "tmp/fallback/0/1");
+       queue_commit("rsync://domain/mod/rpp1", "tmp/rsync/1/0", "tmp/rsync/1/1");
+       queue_commit("rsync://domain/mod/rpp3", "tmp/fallback/2/0", "tmp/rsync/3/1");
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback",
+           /* RPP0 */ "tmp/fallback/0/0", "A", "tmp/fallback/0/1", "B",
+           /* RPP3 */ "tmp/fallback/2/0", "G", "tmp/fallback/2/2", "H",
+           /* RPP1 */ "tmp/fallback/3/0", "C", "tmp/fallback/3/1", "D",
+           NULL);
+
+       new_iteration(false);
+
+       /* Commit 5: Populated -> Empty */
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback", NULL);
+
+       cache_foreach(delete_node);
+}
+END_TEST
+
+START_TEST(test_cache_download_https)
+{
+       struct cache_node nodes[4] = { 0 };
+
+       setup_test();
+
+       printf("==== Download file ====\n");
+       run_dl_https("https://a.b.c/d/e", 1, "tmp/https/0");
+       init_node_https(&nodes[0], "https://a.b.c/d/e", "tmp/https/0", 1, 0);
+       ck_cache_https(nodes);
+
+       printf("==== Download same file ====\n");
+       run_dl_https("https://a.b.c/d/e", 0, "tmp/https/0");
+       ck_cache_https(nodes);
+
+       printf("==== Download something else 1 ====\n");
+       run_dl_https("https://a.b.c/e", 1, "tmp/https/1");
+       init_node_https(&nodes[1], "https://a.b.c/e", "tmp/https/1", 1, 0);
+       ck_cache_https(nodes);
+
+       printf("==== Download something else 2 ====\n");
+       run_dl_https("https://x.y.z/e", 1, "tmp/https/2");
+       init_node_https(&nodes[2], "https://x.y.z/e", "tmp/https/2", 1, 0);
+       ck_cache_https(nodes);
+
+       cleanup_test();
+}
+END_TEST
+
+START_TEST(test_cache_download_https_error)
+{
+       struct cache_node nodes[3] = { 0 };
+
+       setup_test();
+
+       init_node_https(&nodes[0], "https://a.b.c/d", "tmp/https/0", 1, 0);
+       init_node_https(&nodes[1], "https://a.b.c/e", "tmp/https/1", 1, EINVAL);
+
+       printf("==== Startup ====\n");
+       dl_error = 0;
+       run_dl_https("https://a.b.c/d", 1, "tmp/https/0");
+       dl_error = EINVAL;
+       run_dl_https("https://a.b.c/e", 1, NULL);
+       ck_cache_https(nodes);
+
+       printf("==== Regardless of error, not reattempted because same iteration ====\n");
+       dl_error = -EINVAL;
+       run_dl_https("https://a.b.c/d", 0, "tmp/https/0");
+       run_dl_https("https://a.b.c/e", 0, NULL);
+       dl_error = 0;
+       run_dl_https("https://a.b.c/d", 0, "tmp/https/0");
+       run_dl_https("https://a.b.c/e", 0, NULL);
+       ck_cache_https(nodes);
+
+       cleanup_test();
+}
+END_TEST
+
+/* See comments at test_rsync_commit(). */
+START_TEST(test_https_commit)
+{
+       struct cache_mapping map;
+       unsigned int i;
+
+       setup_test();
+
+       ck_assert_int_eq(0, write_simple_file("tmp/https/50", "A")); /* Keeper */
+       ck_assert_int_eq(0, write_simple_file("tmp/https/51", "B")); /* Added */
+       ck_assert_int_eq(0, write_simple_file("tmp/https/52", "C")); /* Removed */
+
+       /* 1, 2 */
+       for (i = 0; i < 2; i++) {
+               commit_fallbacks();
+               ck_filesystem("tmp/fallback", NULL);
+
+               new_iteration(false);
+       }
+
+       /* 3 */
+       map.url = "https://domain/rpki/ta50.cer";
+       map.path = "tmp/https/50";
+       cache_commit_file(&map);
+       map.url = "https://domain/rpki/ta52.cer";
+       map.path = "tmp/https/52";
+       cache_commit_file(&map);
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback",
+           "tmp/fallback/0", "A",
+           "tmp/fallback/1", "C",
+           NULL);
+
+       new_iteration(false);
+
+       /* 4 */
+       map.url = "https://domain/rpki/ta50.cer";
+       map.path = "tmp/fallback/0";
+       cache_commit_file(&map);
+       map.url = "https://domain/rpki/ta51.cer";
+       map.path = "tmp/https/51";
+       cache_commit_file(&map);
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback",
+           "tmp/fallback/0", "A",
+           "tmp/fallback/2", "B",
+           NULL);
+
+       new_iteration(false);
+
+       /* 5 */
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback", NULL);
+
+       cache_foreach(delete_node);
+}
+END_TEST
+
+/* See comments at test_rsync_commit(). */
+START_TEST(test_rrdp_commit)
+{
+       unsigned int i;
+
+       setup_test();
+
+       ck_assert_int_eq(0, system("mkdir -p tmp/rrdp/0 tmp/rrdp/1 tmp/rrdp/2 tmp/rrdp/3"));
+
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/0/0", "A"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/0/1", "B"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/1/0", "C"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/1/1", "D"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/2/0", "E"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/2/1", "F"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/3/0", "G"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/3/1", "H"));
+       ck_assert_int_eq(0, write_simple_file("tmp/rrdp/3/2", "I"));
+
+       /* 1, 2 */
+       for (i = 0; i < 2; i++) {
+               commit_fallbacks();
+               ck_filesystem("tmp/fallback", NULL);
+
+               new_iteration(false);
+       }
+
+       /* 3 */
+       queue_commit("rsync://domain/mod/rpp0", "tmp/rrdp/0/0", "tmp/rrdp/0/1");
+       queue_commit("rsync://domain/mod/rpp2", "tmp/rrdp/2/0", "tmp/rrdp/2/1");
+       queue_commit("rsync://domain/mod/rpp3", "tmp/rrdp/3/0", "tmp/rrdp/3/2");
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback",
+           "tmp/fallback/0/0", "A", "tmp/fallback/0/1", "B",
+           "tmp/fallback/1/0", "E", "tmp/fallback/1/1", "F",
+           "tmp/fallback/2/0", "G", "tmp/fallback/2/1", "I",
+           NULL);
+
+       new_iteration(false);
+
+       /* 4 */
+       queue_commit("rsync://domain/mod/rpp0", "tmp/fallback/0/0", "tmp/fallback/0/1");
+       queue_commit("rsync://domain/mod/rpp1", "tmp/rrdp/1/0", "tmp/rrdp/1/1");
+       queue_commit("rsync://domain/mod/rpp3", "tmp/fallback/2/0", "tmp/rrdp/3/1");
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback",
+           "tmp/fallback/0/0", "A", "tmp/fallback/0/1", "B",
+           "tmp/fallback/2/0", "G", "tmp/fallback/2/2", "H",
+           "tmp/fallback/3/0", "C", "tmp/fallback/3/1", "D",
+           NULL);
+
+       new_iteration(false);
+
+       /* 5 */
+       commit_fallbacks();
+       ck_filesystem("tmp/fallback", NULL);
+
+       cache_foreach(delete_node);
+}
+END_TEST
+
+/* Boilerplate */
+
+static Suite *cache_suite(void)
+{
+       Suite *suite;
+       TCase *rsync, *https, *rrdp;
+
+       rsync = tcase_create("rsync");
+       tcase_add_test(rsync, test_cache_download_rsync);
+       tcase_add_test(rsync, test_cache_download_rsync_error);
+       tcase_add_test(rsync, test_rsync_commit);
+
+       https = tcase_create("https");
+       tcase_add_test(https, test_cache_download_https);
+       tcase_add_test(https, test_cache_download_https_error);
+       tcase_add_test(https, test_https_commit);
+
+       rrdp = tcase_create("rrdp");
+       tcase_add_test(rrdp, test_rrdp_commit);
+
+       suite = suite_create("local-cache");
+       suite_add_tcase(suite, rsync);
+       suite_add_tcase(suite, https);
+       suite_add_tcase(suite, rrdp);
+
+       return suite;
+}
+
+int main(void)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       suite = cache_suite();
+       dls[0] = "Fort\n";
+
+       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/cache_util.c b/test/cache_util.c
new file mode 100644 (file)
index 0000000..1adb4a0
--- /dev/null
@@ -0,0 +1,140 @@
+#include "cache_util.h"
+
+/* XXX Might wanna delete this */
+
+#include <check.h>
+#include <string.h>
+#include "types/uthash.h"
+
+void
+ck_assert_cachent_eq(struct cache_node *expected, struct cache_node *actual)
+{
+       struct cache_node *echild, *achild, *tmp;
+
+       PR_DEBUG_MSG("Comparing %s vs %s", expected->url, actual->url);
+
+       ck_assert_str_eq(expected->url, actual->url);
+       ck_assert_str_eq(expected->path, actual->path);
+       ck_assert_str_eq(expected->name, actual->name);
+       ck_assert_int_eq(expected->flags, actual->flags);
+       ck_assert_str(expected->tmppath, actual->tmmpath);
+
+       HASH_ITER(hh, expected->children, echild, tmp) {
+               HASH_FIND(hh, actual->children, echild->name,
+                   strlen(echild->name), achild);
+               if (achild == NULL)
+                       ck_abort_msg("Expected not found: %s", echild->url);
+               ck_assert_cachent_eq(echild, achild);
+       }
+
+       HASH_ITER(hh, actual->children, achild, tmp) {
+               HASH_FIND(hh, expected->children, achild->name,
+                   strlen(achild->name), echild);
+               if (echild == NULL)
+                       ck_abort_msg("Actual not found: %s", achild->url);
+       }
+}
+
+static struct cache_node *
+vnode(char const *url, char const *path, int flags, char const *tmppath,
+    va_list children)
+{
+       struct cache_node *result;
+       struct cache_node *child;
+       char buffer[64];
+
+       result = pzalloc(sizeof(struct cache_node));
+
+       result->url = (char *)url;
+       result->path = (char *)path;
+       result->name = path_filename(result->path);
+       ck_assert_ptr_ne(NULL, result->name);
+       result->flags = flags;
+       result->tmppath = (char *)tmppath;
+
+       while ((child = va_arg(children, struct cache_node *)) != NULL) {
+               HASH_ADD_KEYPTR(hh, result->children, child->name,
+                   strlen(child->name), child);
+               child->parent = result;
+       }
+
+       return result;
+}
+
+struct cache_node *
+rftnode(char const *url, char const *path, int flags, char const *tmppath, ...)
+{
+       struct cache_node *result;
+       va_list children;
+
+       va_start(children, tmppath);
+       result = vnode(url, path, flags, tmppath, children);
+       va_end(children);
+
+       return result;
+}
+
+struct cache_node *
+rfnode(char const *url, char const *path, int flags, ...)
+{
+       struct cache_node *result;
+       va_list children;
+
+       va_start(children, flags);
+       result = vnode(url, path, flags, NULL, children);
+       va_end(children);
+
+       return result;
+}
+
+struct cache_node *
+rnode(char const *url, char const *path, ...)
+{
+       struct cache_node *result;
+       va_list children;
+
+       va_start(children, path);
+       result = vnode(url, path, 0, NULL, children);
+       va_end(children);
+
+       return result;
+}
+
+struct cache_node *
+hftnode(char const *url, char const *path, int flags, char const *tmppath, ...)
+{
+       struct cache_node *result;
+       va_list children;
+
+       va_start(children, tmppath);
+       result = vnode(url, path, flags, tmppath, children);
+       va_end(children);
+
+       return result;
+}
+
+struct cache_node *
+hfnode(char const *url, char const *path, int flags, ...)
+{
+       struct cache_node *result;
+       va_list children;
+
+       va_start(children, flags);
+       result = vnode(url, path, flags, NULL, children);
+       va_end(children);
+
+       return result;
+}
+
+struct cache_node *
+hnode(char const *url, char const *path, ...)
+{
+       struct cache_node *result;
+       va_list children;
+
+       va_start(children, path);
+       result = vnode(url, path, 0, NULL, children);
+       va_end(children);
+
+       return result;
+}
diff --git a/test/cache_util.h b/test/cache_util.h
new file mode 100644 (file)
index 0000000..1d76e0e
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef TEST_CACHE_UTIL_H_
+#define TEST_CACHE_UTIL_H_
+
+#include <stdarg.h>
+
+void ck_assert_cachent_eq(struct cache_node *, struct cache_node *);
+
+struct cache_node *rftnode(char const *, char const *, int, char const *, ...);
+struct cache_node *rfnode(char const *, char const *, int, ...);
+struct cache_node *rnode(char const *, char const *, ...);
+
+struct cache_node *hftnode(char const *, char const *, int, char const *, ...);
+struct cache_node *hfnode(char const *, char const *, int, ...);
+struct cache_node *hnode(char const *, char const *, ...);
+
+/* rsync offset to url + path */
+#define RO2UP(offset) "rsync://" offset, "tmp/rsync/" offset
+/* https offset to url + path */
+#define HO2UP(offset) "https://" offset, "tmp/https/" offset
+
+/* rsync empty to url + path */
+#define RE2UP "rsync://", "tmp/rsync"
+/* https empty to url + path */
+#define HE2UP "https://", "tmp/https"
+
+#endif /* TEST_CACHE_UTIL_H_ */
similarity index 63%
rename from test/json_util_test.c
rename to test/common_test.c
index fbdcd8bada1cf2b79daf8bde24879bb0f716cc62..2e93c551229f4bbca00f1bbc47943bbc3f410439 100644 (file)
@@ -1,24 +1,25 @@
-#include "json_util.c"
+#include "common.c"
 
 #include <check.h>
 
+#include "alloc.c"
 #include "mock.c"
 
 START_TEST(test_tt)
 {
-       char str[JSON_TS_LEN + 1];
+       char str[FORT_TS_LEN + 1];
        time_t tt;
 
-       ck_assert_int_eq(0, str2tt("2024-03-14T17:51:16Z", &tt));
+       ck_assert_int_eq(0, str2time("2024-03-14T17:51:16Z", &tt));
 
        memset(str, 'f', sizeof(str));
-       ck_assert_int_eq(0, tt2str(tt, str));
+       ck_assert_int_eq(0, time2str(tt, str));
        ck_assert_str_eq("2024-03-14T17:51:16Z", str);
-       ck_assert_int_eq('f', str[JSON_TS_LEN]); /* Tests JSON_TS_LEN. */
+       ck_assert_int_eq('f', str[FORT_TS_LEN]); /* Tests FORT_TS_LEN. */
 }
 END_TEST
 
-static Suite *json_load_suite(void)
+static Suite *common_load_suite(void)
 {
        Suite *suite;
        TCase *core;
@@ -26,7 +27,7 @@ static Suite *json_load_suite(void)
        core = tcase_create("utils");
        tcase_add_test(core, test_tt);
 
-       suite = suite_create("JSON util");
+       suite = suite_create("commons");
        suite_add_tcase(suite, core);
        return suite;
 }
@@ -37,7 +38,7 @@ int main(void)
        SRunner *runner;
        int tests_failed;
 
-       suite = json_load_suite();
+       suite = common_load_suite();
 
        runner = srunner_create(suite);
        srunner_run_all(runner, CK_NORMAL);
similarity index 80%
rename from test/crypto/hash_test.c
rename to test/hash_test.c
index ec979f57381189850340fa9a51f3d1cff2c69be9..eb1a3b5b714ff6745050784c3f4cfe83d7d792dc 100644 (file)
@@ -4,12 +4,9 @@
 #include "alloc.c"
 #include "common.c"
 #include "file.c"
+#include "hash.c"
 #include "mock.c"
-#include "data_structure/path_builder.c"
-#include "types/map.c"
-#include "crypto/hash.c"
-
-MOCK_ABORT_INT(cache_tmpfile, char **filename)
+#include "types/path.c"
 
 /* Actually mostly tests libcrypto's sanity, not Fort's. */
 START_TEST(test_hash)
@@ -40,15 +37,10 @@ START_TEST(test_hash)
        struct hash_algorithm const *ha;
        char const *name;
        char const *input = "Fort";
-       struct cache_mapping map = { 0 };
+       char const *file = "resources/lorem-ipsum.txt";
 
        hash_setup();
 
-       map.url = "https://example.com/resources/lorem-ipsum.txt";
-       map.path = "resources/lorem-ipsum.txt";
-       map.type = MAP_TA_HTTP;
-       map.references = 1;
-
        ha = hash_get_sha1();
        ck_assert_uint_eq(20, hash_get_size(ha));
        name = hash_get_name(ha);
@@ -59,10 +51,10 @@ START_TEST(test_hash)
        FORT_SHA1[1] = 1;
        ck_assert_int_eq(EINVAL, hash_validate(ha, (unsigned char *)input, strlen(input), FORT_SHA1, sizeof(FORT_SHA1)));
 
-       ck_assert_int_eq(0, hash_validate_file(ha, &map, FILE_SHA1, sizeof(FILE_SHA1)));
-       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, &map, FILE_SHA1, sizeof(FILE_SHA1) - 10));
+       ck_assert_int_eq(0, hash_validate_file(ha, file, FILE_SHA1, sizeof(FILE_SHA1)));
+       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, file, FILE_SHA1, sizeof(FILE_SHA1) - 10));
        FILE_SHA1[19] = 0;
-       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, &map, FILE_SHA1, sizeof(FILE_SHA1)));
+       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, file, FILE_SHA1, sizeof(FILE_SHA1)));
 
        ha = hash_get_sha256();
        ck_assert_uint_eq(32, hash_get_size(ha));
@@ -74,10 +66,10 @@ START_TEST(test_hash)
        FORT_SHA256[10] = 0;
        ck_assert_int_eq(EINVAL, hash_validate(ha, (unsigned char *)input, strlen(input), FORT_SHA256, sizeof(FORT_SHA256)));
 
-       ck_assert_int_eq(0, hash_validate_file(ha, &map, FILE_SHA256, sizeof(FILE_SHA256)));
-       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, &map, FILE_SHA256, sizeof(FILE_SHA256) - 1));
+       ck_assert_int_eq(0, hash_validate_file(ha, file, FILE_SHA256, sizeof(FILE_SHA256)));
+       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, file, FILE_SHA256, sizeof(FILE_SHA256) - 1));
        FILE_SHA256[31] = 10;
-       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, &map, FILE_SHA256, sizeof(FILE_SHA256)));
+       ck_assert_int_eq(-EINVAL, hash_validate_file(ha, file, FILE_SHA256, sizeof(FILE_SHA256)));
 
        hash_teardown();
 }
index b0bb560df20c64bb9909575d04372c28603af268..51550e0cb26268f368fd35139617f214585f2009 100644 (file)
@@ -3,65 +3,63 @@
 #include <errno.h>
 #include <arpa/inet.h>
 #include "config.h"
+#include "incidence.h"
+#include "log.h"
 #include "state.h"
 #include "thread_var.h"
-#include "config/filename_format.h"
-#include "config/mode.h"
-#include "incidence/incidence.h"
 
-/**
- * Some core functions, as linked from unit tests.
- */
+/* Some core functions, as linked from unit tests. */
 
 MOCK_TRUE(log_val_enabled, unsigned int l)
 MOCK_TRUE(log_op_enabled, unsigned int l)
 
 /* CFLAGS=-DPRINT_PRS make check */
 #ifdef PRINT_PRS
-#define MOCK_PRINT                                                     \
+#define MOCK_PRINT(color)                                              \
        do {                                                            \
                va_list args;                                           \
+               printf(color);                                          \
                va_start(args, format);                                 \
                vfprintf(stdout, format, args);                         \
                va_end(args);                                           \
-               printf("\n");                                           \
+               printf(PR_COLOR_RST "\n");                              \
        } while (0)
 #else
-#define MOCK_PRINT
+#define MOCK_PRINT(color)
 #endif
 
-#define MOCK_VOID_PRINT(name)                                          \
+#define MOCK_VOID_PRINT(name, color)                                   \
        void                                                            \
        name(const char *format, ...)                                   \
        {                                                               \
-               MOCK_PRINT;                                             \
+               MOCK_PRINT(color);                                      \
        }
 
-#define MOCK_INT_PRINT(name, result)                                   \
+#define MOCK_INT_PRINT(name, color, result)                            \
        int                                                             \
        name(const char *format, ...)                                   \
        {                                                               \
-               MOCK_PRINT;                                             \
+               MOCK_PRINT(color);                                      \
                return result;                                          \
        }
 
-MOCK_VOID_PRINT(pr_op_debug)
-MOCK_VOID_PRINT(pr_op_info)
-MOCK_INT_PRINT(pr_op_warn, 0)
-MOCK_INT_PRINT(pr_op_err, -EINVAL)
-MOCK_INT_PRINT(pr_op_err_st, -EINVAL)
-MOCK_INT_PRINT(op_crypto_err, -EINVAL)
+MOCK_VOID_PRINT(pr_op_debug, PR_COLOR_DBG)
+MOCK_VOID_PRINT(pr_op_info, PR_COLOR_INF)
+MOCK_INT_PRINT(pr_op_warn, PR_COLOR_WRN, 0)
+MOCK_INT_PRINT(pr_op_err, PR_COLOR_ERR, -EINVAL)
+MOCK_INT_PRINT(pr_op_err_st, PR_COLOR_ERR, -EINVAL)
+MOCK_INT_PRINT(op_crypto_err, PR_COLOR_ERR, -EINVAL)
 
-MOCK_VOID_PRINT(pr_val_debug)
-MOCK_VOID_PRINT(pr_val_info)
-MOCK_INT_PRINT(pr_val_warn, 0)
-MOCK_INT_PRINT(pr_val_err, -EINVAL)
-MOCK_INT_PRINT(val_crypto_err, -EINVAL)
+MOCK_VOID_PRINT(pr_val_debug, PR_COLOR_DBG)
+MOCK_VOID_PRINT(pr_val_info, PR_COLOR_INF)
+MOCK_INT_PRINT(pr_val_warn, PR_COLOR_WRN, 0)
+MOCK_INT_PRINT(pr_val_err, PR_COLOR_ERR, -EINVAL)
+MOCK_INT_PRINT(val_crypto_err, PR_COLOR_ERR, -EINVAL)
 
 int
 incidence(enum incidence_id id, const char *format, ...)
 {
-       MOCK_PRINT;
+       MOCK_PRINT(PR_COLOR_ERR);
        return -EINVAL;
 }
 
@@ -113,12 +111,30 @@ v6addr2str2(struct in6_addr const *addr)
 MOCK_NULL(config_get_slurm, char const *, void)
 MOCK(config_get_tal, char const *, "tal/", void)
 MOCK(config_get_local_repository, char const *, "tmp", void)
+MOCK(cfg_cache_threshold, time_t, 60 * 60 * 24 * 7, void)
 MOCK(config_get_mode, enum mode, STANDALONE, void)
+MOCK_UINT(config_get_rrdp_delta_threshold, 5, void)
 MOCK_TRUE(config_get_rsync_enabled, void)
 MOCK_UINT(config_get_rsync_priority, 50, void)
 MOCK_TRUE(config_get_http_enabled, void)
 MOCK_UINT(config_get_http_priority, 60, void)
 MOCK_NULL(config_get_output_roa, char const *, void)
 MOCK_NULL(config_get_output_bgpsec, char const *, void)
-MOCK(config_get_op_log_filename_format, enum filename_format, FNF_NAME, void)
-MOCK(config_get_val_log_filename_format, enum filename_format, FNF_NAME, void)
+MOCK(config_get_op_log_file_format, enum filename_format, FNF_NAME, void)
+MOCK(config_get_val_log_file_format, enum filename_format, FNF_NAME, void)
+MOCK(logv_filename, char const *, path, char const *path)
+
+MOCK_VOID(fnstack_init, void)
+MOCK_VOID(fnstack_push, char const *file)
+MOCK_VOID(fnstack_push_map, struct cache_mapping const *map)
+MOCK_VOID(fnstack_pop, void)
+MOCK_VOID(fnstack_cleanup, void)
+
+void
+ck_assert_str(char const *expected, char const *actual)
+{
+       if (expected)
+               ck_assert_str_eq(expected, actual);
+       else
+               ck_assert_ptr_eq(NULL, actual);
+}
diff --git a/test/mock_https.c b/test/mock_https.c
new file mode 100644 (file)
index 0000000..c7fefbd
--- /dev/null
@@ -0,0 +1,35 @@
+#include "http.h"
+
+#include <check.h>
+#include "file.h"
+
+static int dl_error;
+static char const *dls[8];
+static unsigned int https_counter; /* Times http_download() was called */
+
+int
+http_download(char const *url, char const *path, curl_off_t ims, bool *changed)
+{
+       char const *content;
+
+       if (dl_error) {
+               printf("Simulating failed HTTP download.\n");
+               https_counter++;
+               if (changed)
+                       *changed = false;
+               return dl_error;
+       }
+
+       printf("Simulating HTTP download: %s -> %s\n", url, path);
+
+       content = dls[https_counter++];
+       if (!content)
+               ck_abort_msg("Test was not expecting an HTTP download.");
+
+       ck_assert_int_eq(0, file_write_full(path,
+           (unsigned char const *)content, strlen(content)));
+
+       if (changed)
+               *changed = true;
+       return 0;
+}
diff --git a/test/object/manifest_test.c b/test/object/manifest_test.c
new file mode 100644 (file)
index 0000000..a655759
--- /dev/null
@@ -0,0 +1,96 @@
+#include <errno.h>
+#include <check.h>
+
+#include "alloc.c"
+#include "common.c"
+#include "mock.c"
+#include "object/manifest.c"
+#include "types/map.c"
+#include "types/path.c"
+#include "types/url.c"
+
+MOCK_ABORT_INT(signed_object_decode, struct signed_object *sobj, char const *path)
+MOCK_ABORT_VOID(signed_object_cleanup, struct signed_object *sobj)
+
+#define BUFFER_LEN 128
+static uint8_t buffer[BUFFER_LEN];
+
+static int
+__test_validate(char const *src, size_t len)
+{
+       IA5String_t dst;
+       unsigned int i;
+
+       memcpy(buffer, src, len);
+       for (i = len; i < BUFFER_LEN - 1; i++)
+               buffer[i] = '_';
+       buffer[BUFFER_LEN - 1] = 0;
+
+       dst.buf = buffer;
+       dst.size = len;
+
+       return validate_mft_filename(&dst);
+}
+
+#define test_validate(str) __test_validate(str, sizeof(str) - 1)
+
+START_TEST(check_validate_current_directory)
+{
+       ck_assert_int_eq(-EINVAL, test_validate(""));
+       ck_assert_int_eq(-EINVAL, test_validate("."));
+       ck_assert_int_eq(-EINVAL, test_validate(".."));
+
+       ck_assert_int_eq(-EINVAL, test_validate("filename"));
+       ck_assert_int_eq(-EINVAL, test_validate("filename."));
+       ck_assert_int_eq(-EINVAL, test_validate("filename.a"));
+       ck_assert_int_eq(-EINVAL, test_validate("filename.ab"));
+       ck_assert_int_eq(0, test_validate("filename.abc"));
+       ck_assert_int_eq(-EINVAL, test_validate("file.abcd"));
+
+       ck_assert_int_eq(0, test_validate("file-name.ABC"));
+       ck_assert_int_eq(0, test_validate("file_name.123"));
+       ck_assert_int_eq(0, test_validate("file0name.aB2"));
+       ck_assert_int_eq(0, test_validate("file9name.---"));
+       ck_assert_int_eq(0, test_validate("FileName.A3_"));
+       ck_assert_int_eq(-EINVAL, test_validate("file.name.abc"));
+       ck_assert_int_eq(-EINVAL, test_validate("file/name.abc"));
+       ck_assert_int_eq(-EINVAL, test_validate("file\0name.abc"));
+       ck_assert_int_eq(-EINVAL, test_validate("filename.abc\0filename.abc"));
+       ck_assert_int_eq(-EINVAL, test_validate("filenameabc\0filename.abc"));
+       ck_assert_int_eq(0, test_validate("-.---"));
+
+       ck_assert_int_eq(0, test_validate("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.-_-"));
+       ck_assert_int_eq(0, test_validate("vixxBTS_TVXQ-2pmGOT7.cer"));
+}
+END_TEST
+
+static Suite *
+mft_suite(void)
+{
+       Suite *suite;
+       TCase *core;
+
+       core = tcase_create("core");
+       tcase_add_test(core, check_validate_current_directory);
+
+       suite = suite_create("mft");
+       suite_add_tcase(suite, core);
+       return suite;
+}
+
+int
+main(int argc, char **argv)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       suite = mft_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;
+}
similarity index 73%
rename from test/tal_test.c
rename to test/object/tal_test.c
index b1e639d83fdd5963992edcd4c8384bf2effd0fbb..f71cf4ca2aed83baf2347920ba2df9814265759b 100644 (file)
@@ -3,52 +3,32 @@
 #include <check.h>
 
 #include "alloc.c"
+#include "base64.c"
 #include "common.c"
 #include "file.c"
 #include "mock.c"
-#include "data_structure/path_builder.c"
 #include "types/map.c"
-#include "crypto/base64.c"
+#include "types/path.c"
+#include "types/str.c"
+#include "types/url.c"
 
 /* Mocks */
 
-MOCK_ABORT_VOID(cache_setup, void)
-MOCK(cache_create, struct rpki_cache *, NULL, void)
-MOCK_VOID(cache_destroy, struct rpki_cache *cache)
-MOCK_ABORT_INT(cache_download, struct rpki_cache *cache,
-    struct cache_mapping *map, bool *changed,
-    struct cachefile_notification ***notif)
-MOCK_ABORT_INT(cache_download_alt, struct rpki_cache *cache,
-    struct map_list *maps, enum map_type http_type, enum map_type rsync_type,
-    maps_dl_cb cb, void *arg)
-MOCK_ABORT_PTR(cache_recover, cache_mapping, struct rpki_cache *cache,
-    struct map_list *maps)
-MOCK_ABORT_INT(cache_tmpfile, char **filename)
-MOCK_ABORT_VOID(cache_teardown, void)
-MOCK_ABORT_INT(certificate_traverse, struct rpp *rpp_parent,
-    struct cache_mapping *cert_map)
+MOCK_ABORT_VOID(cache_prepare, void)
+MOCK_ABORT_VOID(cache_commit, void)
 MOCK_ABORT_PTR(db_table_create, db_table, void)
 MOCK_VOID(db_table_destroy, struct db_table *table)
 MOCK_ABORT_INT(db_table_join, struct db_table *dst, struct db_table *src)
-MOCK_ABORT_INT(deferstack_pop, struct cert_stack *stack,
-    struct deferred_cert *result)
-MOCK_ABORT_VOID(fnstack_cleanup, void)
-MOCK_ABORT_VOID(fnstack_init, void)
-MOCK_ABORT_VOID(fnstack_push, char const *f)
 MOCK_ABORT_INT(handle_roa_v4, uint32_t as, struct ipv4_prefix const *prefix,
     uint8_t max_length, void *arg)
 MOCK_ABORT_INT(handle_roa_v6, uint32_t as, struct ipv6_prefix const *prefix,
     uint8_t max_length, void *arg)
 MOCK_ABORT_INT(handle_router_key, unsigned char const *ski,
     struct asn_range const *asns, unsigned char const *spk, void *arg)
-MOCK_ABORT_VOID(rpp_refput, struct rpp *pp)
-MOCK_ABORT_INT(rrdp_update, struct cache_mapping *map)
 MOCK(state_retrieve, struct validation *, NULL, void)
-MOCK_ABORT_PTR(validation_certstack, cert_stack, struct validation *state)
 MOCK_ABORT_VOID(validation_destroy, struct validation *state)
 MOCK_ABORT_INT(validation_prepare, struct validation **out, struct tal *tal,
     struct validation_handler *validation_handler)
-MOCK_ABORT_ENUM(validation_pubkey_state, pubkey_state, struct validation *state)
 MOCK(validation_tal, struct tal *, NULL, struct validation *state)
 
 /* Tests */
@@ -100,8 +80,8 @@ test_1url(char const *file)
 
        ck_assert_int_eq(0, tal_init(&tal, file));
 
-       ck_assert_uint_eq(1, tal.maps.len);
-       ck_assert_str_eq("rsync://example.com/rpki/ta.cer", tal.maps.array[0]->url);
+       ck_assert_uint_eq(1, tal.urls.len);
+       ck_assert_str_eq("rsync://example.com/rpki/ta.cer", tal.urls.array[0]);
        check_spki(&tal);
 
        tal_cleanup(&tal);
@@ -121,11 +101,11 @@ test_4urls(char const *file)
 
        ck_assert_int_eq(0, tal_init(&tal, file));
 
-       ck_assert_uint_eq(4, tal.maps.len);
-       ck_assert_str_eq("rsync://example.com/rpki/ta.cer", tal.maps.array[0]->url);
-       ck_assert_str_eq("https://example.com/rpki/ta.cer", tal.maps.array[1]->url);
-       ck_assert_str_eq("rsync://www.example.com/potato/ta.cer", tal.maps.array[2]->url);
-       ck_assert_str_eq("https://wx3.example.com/tomato/ta.cer", tal.maps.array[3]->url);
+       ck_assert_uint_eq(4, tal.urls.len);
+       ck_assert_str_eq("rsync://example.com/rpki/ta.cer", tal.urls.array[0]);
+       ck_assert_str_eq("https://example.com/rpki/ta.cer", tal.urls.array[1]);
+       ck_assert_str_eq("rsync://www.example.com/potato/ta.cer", tal.urls.array[2]);
+       ck_assert_str_eq("https://wx3.example.com/tomato/ta.cer", tal.urls.array[3]);
 
        check_spki(&tal);
 
@@ -138,6 +118,7 @@ START_TEST(test_tal_load_4urls)
        test_4urls("resources/tal/4urls-crlf.tal");
        test_4urls("resources/tal/4urls-lf-comment.tal");
        test_4urls("resources/tal/4urls-lf-comment-utf8.tal");
+       test_4urls("resources/tal/4urls-ignored-protos.tal");
 }
 END_TEST
 
diff --git a/test/resources/rrdp/notif-bad-uri-4.xml b/test/resources/rrdp/notif-bad-uri-4.xml
new file mode 100644 (file)
index 0000000..5438847
--- /dev/null
@@ -0,0 +1,9 @@
+<notification
+       xmlns="http://www.ripe.net/rpki/rrdp"
+       version="1"
+       session_id="9df4b597-af9e-4dca-bdda-719cce2c4e28"
+       serial="3">
+<snapshot
+       uri="https://different-host/9d-8/3/snapshot.xml"
+       hash="0123456789abcdefABCDEF0123456789abcdefABCDEF0123456789abcdefABCD"/>
+</notification>
\ No newline at end of file
diff --git a/test/resources/tal/4urls-ignored-protos.tal b/test/resources/tal/4urls-ignored-protos.tal
new file mode 100644 (file)
index 0000000..eb7122b
--- /dev/null
@@ -0,0 +1,14 @@
+rsync://example.com/rpki/ta.cer
+https://example.com/rpki/ta.cer
+ftp://example.com/rpki/ta.cer
+rsync://www.example.com/potato/ta.cer
+http://example.com/rpki/ta.cer
+https://wx3.example.com/tomato/ta.cer
+
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZEzhYK0+PtDOPfub/KR
+c3MeWx3neXx4/wbnJWGbNAtbYqXg3uU5J4HFzPgk/VIppgSKAhlO0H60DRP48by9
+gr5/yDHu2KXhOmnMg46sYsUIpfgtBS9+VtrqWziJfb+pkGtuOWeTnj6zBmBNZKK+
+5AlMCW1WPhrylIcB+XSZx8tk9GS/3SMQ+YfMVwwAyYjsex14Uzto4GjONALE5oh1
+M3+glRQduD6vzSwOD+WahMbc9vCOTED+2McLHRKgNaQf0YJ9a1jG9oJIvDkKXEqd
+fqDRktwyoD74cV57bW3tBAexB7GglITbInyQAsmdngtfg2LUMrcROHHP86QPZINj
+DQIDAQAB
index 8622e7d79f99ab6032f0ba35d8fc252374b9dcaa..d2176e3f16c5a3b6a45848baf6e88decaa893dfe 100644 (file)
@@ -3,50 +3,29 @@
 #include <stdlib.h>
 
 #include "alloc.c"
+#include "base64.c"
+#include "cachetmp.c"
 #include "common.c"
 #include "file.c"
+#include "hash.c"
 #include "json_util.c"
 #include "mock.c"
+#include "relax_ng.c"
 #include "rrdp.c"
-#include "crypto/base64.c"
-#include "crypto/hash.c"
-#include "data_structure/path_builder.c"
 #include "types/map.c"
-#include "xml/relax_ng.c"
+#include "types/path.c"
+#include "types/url.c"
 
 /* Mocks */
 
-int
-cache_tmpfile(char **filename)
-{
-       static unsigned int file_counter = 0;
-       char *result;
-       int written;
-
-       result = pmalloc(10);
-       written = snprintf(result, 10, "tmp/%u", file_counter);
-       ck_assert(4 < written && written < 10);
-
-       *filename = result;
-       return 0;
-}
-
-MOCK_ABORT_INT(cache_download, struct rpki_cache *cache,
-    struct cache_mapping *map, bool *changed,
-    struct cachefile_notification ***notif)
-MOCK_ABORT_VOID(fnstack_pop, void)
-MOCK_ABORT_VOID(fnstack_push_map, struct cache_mapping *map)
-MOCK_ABORT_PTR(validation_cache, rpki_cache, struct validation *state)
-
-MOCK(state_retrieve, struct validation *, NULL, void)
-MOCK(validation_tal, struct tal *, NULL, struct validation *state)
-MOCK(tal_get_file_name, char const *, "", struct tal *tal)
-MOCK_UINT(config_get_rrdp_delta_threshold, 5, void)
+MOCK_ABORT_INT(http_download, char const *url, char const *path, curl_off_t ims,
+    bool *changed)
 
 /* Mocks end */
 
 static void
-ck_rrdp_session(char const *session, char const *serial, struct rrdp_session *actual)
+ck_rrdp_session(char const *session, char const *serial,
+    struct rrdp_session *actual)
 {
        BIGNUM *bn;
 
@@ -60,30 +39,31 @@ ck_rrdp_session(char const *session, char const *serial, struct rrdp_session *ac
        BN_free(bn);
 }
 
-static struct cachefile_notification *
-create_cachefile_notif(char const *session, char const *serial, ...)
+static struct rrdp_state *
+create_rrdp_state(char const *session, char const *serial, ...)
 {
-       struct cachefile_notification *notif;
+       struct rrdp_state *state;
        struct rrdp_hash *hash;
        int dh_byte;
        va_list args;
 
-       notif = pmalloc(sizeof(struct cachefile_notification));
+       state = pmalloc(sizeof(struct rrdp_state));
 
-       notif->session.session_id = pstrdup(session);
-       notif->session.serial.str = pstrdup(serial);
-       notif->session.serial.num = NULL; /* Not needed for now. */
-       STAILQ_INIT(&notif->delta_hashes);
+       state->session.session_id = pstrdup(session);
+       state->session.serial.str = pstrdup(serial);
+       state->session.serial.num = NULL; /* Not needed for now. */
+       state->files = NULL;
+       STAILQ_INIT(&state->delta_hashes);
 
        va_start(args, serial);
        while ((dh_byte = va_arg(args, int)) != 0) {
                hash = pmalloc(sizeof(struct rrdp_hash));
                memset(hash->bytes, dh_byte, sizeof(hash->bytes));
-               STAILQ_INSERT_TAIL(&notif->delta_hashes, hash, hook);
+               STAILQ_INSERT_TAIL(&state->delta_hashes, hash, hook);
        }
        va_end(args);
 
-       return notif;
+       return state;
 }
 
 START_TEST(test_xmlChar_NULL_assumption)
@@ -125,7 +105,6 @@ START_TEST(test_xmlChar_NULL_assumption)
        ck_assert_uint_eq(0xa6, xmlstr[2]);
        ck_assert_uint_eq('\0', xmlstr[3]);
        xmlFree(xmlstr);
-
 }
 END_TEST
 
@@ -137,12 +116,12 @@ START_TEST(test_hexstr2sha256)
        unsigned int i;
 
        hex = "01";
-       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len));
+       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
        ck_assert_ptr_eq(NULL, sha);
 
        hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
        sha_len = 0;
-       ck_assert_int_eq(0, hexstr2sha256((xmlChar *) hex, &sha, &sha_len));
+       ck_assert_int_eq(0, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
        ck_assert_ptr_ne(NULL, sha);
        for (i = 0; i < 32; i++)
                ck_assert_uint_eq(i, sha[i]);
@@ -150,24 +129,34 @@ START_TEST(test_hexstr2sha256)
        free(sha);
        sha = NULL;
 
+       /* Unwanted prefix */
        hex = "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
-       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len));
+       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
        ck_assert_ptr_eq(NULL, sha);
 
+       /* Padding left */
        hex = " 00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
-       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len));
+       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
+       ck_assert_ptr_eq(NULL, sha);
+
+       /* Padding right */
+       hex = "00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f ";
+       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
        ck_assert_ptr_eq(NULL, sha);
 
+       /* Illegal hex character 'g' */
        hex = "0001020g0405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
-       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len));
+       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
        ck_assert_ptr_eq(NULL, sha);
 
-       hex = "0001020g0405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1";
-       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len));
+       /* Slightly too short */
+       hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1";
+       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
        ck_assert_ptr_eq(NULL, sha);
 
-       hex = "0001020g0405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2";
-       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *) hex, &sha, &sha_len));
+       /* Slightly too long */
+       hex = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2";
+       ck_assert_int_eq(EINVAL, hexstr2sha256((xmlChar *)hex, &sha, &sha_len));
        ck_assert_ptr_eq(NULL, sha);
 }
 END_TEST
@@ -260,13 +249,13 @@ START_TEST(test_sort_deltas)
        ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 0, "0"));
 
        /* More than 1 delta, already sorted */
-       add_serials(&deltas, 1, 3, 4, END);
-       ck_assert_int_eq(0, __sort_deltas(&deltas, 4, "4"));
-       validate_serials(&deltas, 1, 2, 3, 4, END);
+       add_serials(&deltas, 3, 4, 5, END);
+       ck_assert_int_eq(0, __sort_deltas(&deltas, 5, "5"));
+       validate_serials(&deltas, 2, 3, 4, 5, END);
 
        /* More than 1 delta, they don't match session serial */
-       ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 5, "5"));
-       ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 3, "3"));
+       ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 6, "6"));
+       ck_assert_int_eq(-EINVAL, __sort_deltas(&deltas, 4, "4"));
 
        notification_deltas_cleanup(&deltas, notification_delta_cleanup);
        notification_deltas_init(&deltas);
@@ -342,15 +331,16 @@ init_rrdp_session(struct rrdp_session *session, unsigned long serial)
 }
 
 static void
-init_cachefile_notif(struct cachefile_notification **result, unsigned long serial, ...)
+init_rrdp_state(struct rrdp_state **result,
+    unsigned long serial, ...)
 {
-       struct cachefile_notification *notif;
+       struct rrdp_state *notif;
        va_list args;
        int hash_byte;
        struct rrdp_hash *hash;
        size_t i;
 
-       notif = pmalloc(sizeof(struct cachefile_notification));
+       notif = pmalloc(sizeof(struct rrdp_state));
        *result = notif;
 
        init_rrdp_session(&notif->session, serial);
@@ -392,7 +382,7 @@ init_regular_notif(struct update_notification *notif, unsigned long serial, ...)
 }
 
 static void
-validate_cachefile_notif(struct cachefile_notification *notif, unsigned long __serial, ...)
+validate_rrdp_state(struct rrdp_state *state, unsigned long __serial, ...)
 {
        struct rrdp_serial serial;
        va_list args;
@@ -400,13 +390,13 @@ validate_cachefile_notif(struct cachefile_notification *notif, unsigned long __s
        struct rrdp_hash *hash;
        size_t i;
 
-       ck_assert_str_eq("session", notif->session.session_id);
+       ck_assert_str_eq("session", state->session.session_id);
        init_serial(&serial, __serial);
-       ck_assert_str_eq(serial.str, notif->session.serial.str);
-       ck_assert_int_eq(0, BN_cmp(serial.num, notif->session.serial.num));
+       ck_assert_str_eq(serial.str, state->session.serial.str);
+       ck_assert_int_eq(0, BN_cmp(serial.num, state->session.serial.num));
        serial_cleanup(&serial);
 
-       hash = STAILQ_FIRST(&notif->delta_hashes);
+       hash = STAILQ_FIRST(&state->delta_hashes);
 
        va_start(args, __serial);
        while ((hash_byte = va_arg(args, int)) >= 0) {
@@ -419,82 +409,73 @@ validate_cachefile_notif(struct cachefile_notification *notif, unsigned long __s
 
        ck_assert_ptr_eq(NULL, hash);
 
-       rrdp_notif_free(notif);
+       rrdp_state_free(state);
 }
 
 START_TEST(test_update_notif)
 {
-       struct cachefile_notification *old;
+       struct rrdp_state *old;
        struct update_notification new;
 
        /* No changes */
-       init_cachefile_notif(&old, 5555, 1, 2, 3, -1);
+       init_rrdp_state(&old, 5555, 1, 2, 3, -1);
        init_regular_notif(&new, 5555, 1, 2, 3, -1);
        ck_assert_int_eq(0, update_notif(old, &new));
-       validate_cachefile_notif(old, 5555, 1, 2, 3, -1);
+       validate_rrdp_state(old, 5555, 1, 2, 3, -1);
 
        /* Add a few serials */
-       init_cachefile_notif(&old, 5555, 1, 2, 3, -1);
+       init_rrdp_state(&old, 5555, 1, 2, 3, -1);
        init_regular_notif(&new, 5557, 3, 4, 5, -1);
        ck_assert_int_eq(0, update_notif(old, &new));
-       validate_cachefile_notif(old, 5557, 1, 2, 3, 4, 5, -1);
+       validate_rrdp_state(old, 5557, 1, 2, 3, 4, 5, -1);
 
        /* Add serials, delta threshold exceeded */
-       init_cachefile_notif(&old, 5555, 1, 2, 3, -1);
+       init_rrdp_state(&old, 5555, 1, 2, 3, -1);
        init_regular_notif(&new, 5558, 3, 4, 5, 6, -1);
        ck_assert_int_eq(0, update_notif(old, &new));
-       validate_cachefile_notif(old, 5558, 2, 3, 4, 5, 6, -1);
+       validate_rrdp_state(old, 5558, 2, 3, 4, 5, 6, -1);
 
        /* All new serials, but no hashes skipped */
-       init_cachefile_notif(&old, 5555, 1, 2, 3, -1);
+       init_rrdp_state(&old, 5555, 1, 2, 3, -1);
        init_regular_notif(&new, 5557, 4, 5, -1);
        ck_assert_int_eq(0, update_notif(old, &new));
-       validate_cachefile_notif(old, 5557, 1, 2, 3, 4, 5, -1);
+       validate_rrdp_state(old, 5557, 1, 2, 3, 4, 5, -1);
 
        /* 2 previous tests combined */
-       init_cachefile_notif(&old, 5555, 1, 2, 3, 4, 5, -1);
+       init_rrdp_state(&old, 5555, 1, 2, 3, 4, 5, -1);
        init_regular_notif(&new, 5560, 6, 7, 8, 9, 10, -1);
        ck_assert_int_eq(0, update_notif(old, &new));
-       validate_cachefile_notif(old, 5560, 6, 7, 8, 9, 10, -1);
+       validate_rrdp_state(old, 5560, 6, 7, 8, 9, 10, -1);
 }
 END_TEST
 
-static void
-init_map(struct cache_mapping *map, char *global, char *path, enum map_type type)
-{
-       map->url = global;
-       map->path = path;
-       map->type = type;
-       map->references = 1;
-}
-
-#define init_notif_map(u, g, l) init_map(u, g, l, MAP_NOTIF)
-
 START_TEST(test_parse_notification_ok)
 {
-       struct cache_mapping map;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       init_notif_map(&map, "https://host/notification.xml", "resources/rrdp/notif-ok.xml");
-       ck_assert_int_eq(0, parse_notification(&map, &notif));
+       ck_assert_int_eq(0, parse_notification("https://host/notification.xml",
+           "resources/rrdp/notif-ok.xml", &notif));
 
-       ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28", (char const *)notif.session.session_id);
+       ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28",
+           (char const *)notif.session.session_id);
        ck_assert_str_eq("3", (char const *)notif.session.serial.str);
 
-       ck_assert_str_eq("https://host/9d-8/3/snapshot.xml", notif.snapshot.uri->url);
+       ck_assert_str_eq("https://host/9d-8/3/snapshot.xml", notif.snapshot.uri);
        ck_assert_uint_eq(32, notif.snapshot.hash_len);
        validate_aaaa_hash(notif.snapshot.hash);
 
        ck_assert_uint_eq(2, notif.deltas.len);
 
        ck_assert_str_eq("2", (char const *)notif.deltas.array[0].serial.str);
-       ck_assert_str_eq("https://host/9d-8/2/delta.xml", notif.deltas.array[0].meta.uri->url);
+       ck_assert_str_eq("https://host/9d-8/2/delta.xml",
+           notif.deltas.array[0].meta.uri);
        ck_assert_uint_eq(32, notif.deltas.array[0].meta.hash_len);
        validate_01234_hash(notif.deltas.array[0].meta.hash);
 
        ck_assert_str_eq("3", (char const *)notif.deltas.array[1].serial.str);
-       ck_assert_str_eq("https://host/9d-8/3/delta.xml", notif.deltas.array[1].meta.uri->url);
+       ck_assert_str_eq("https://host/9d-8/3/delta.xml",
+           notif.deltas.array[1].meta.uri);
        ck_assert_uint_eq(32, notif.deltas.array[1].meta.hash_len);
        validate_01234_hash(notif.deltas.array[0].meta.hash);
 
@@ -505,17 +486,17 @@ END_TEST
 
 START_TEST(test_parse_notification_0deltas)
 {
-       struct cache_mapping map;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       init_notif_map(&map, "https://host/notification.xml", "resources/rrdp/notif-0deltas.xml");
-       ck_assert_int_eq(0, parse_notification(&map, &notif));
+       ck_assert_int_eq(0, parse_notification("https://host/notification.xml",
+           "resources/rrdp/notif-0deltas.xml", &notif));
 
-       ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28", (char const *)notif.session.session_id);
+       ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28",
+           (char const *)notif.session.session_id);
        ck_assert_str_eq("3", (char const *)notif.session.serial.str);
 
-       ck_assert_str_eq("https://host/9d-8/3/snapshot.xml", notif.snapshot.uri->url);
+       ck_assert_str_eq("https://host/9d-8/3/snapshot.xml", notif.snapshot.uri);
        ck_assert_uint_eq(32, notif.snapshot.hash_len);
        validate_01234_hash(notif.snapshot.hash);
 
@@ -528,23 +509,24 @@ END_TEST
 
 START_TEST(test_parse_notification_large_serial)
 {
-       struct cache_mapping map;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       init_notif_map(&map, "https://host/notification.xml", "resources/rrdp/notif-large-serial.xml");
-       ck_assert_int_eq(0, parse_notification(&map, &notif));
+       ck_assert_int_eq(0, parse_notification("https://host/notification.xml",
+           "resources/rrdp/notif-large-serial.xml", &notif));
 
-       ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28", (char const *)notif.session.session_id);
+       ck_assert_str_eq("9df4b597-af9e-4dca-bdda-719cce2c4e28",
+           (char const *)notif.session.session_id);
        /*
         * This seems to be the largest positive integer libxml2 supports,
         * at least by default. It's significantly larger than 2^64.
         * It's not as many digits as I was expecting though.
         * Maybe research if it's possible to increase it further.
         */
-       ck_assert_str_eq("999999999999999999999999", (char const *)notif.session.serial.str);
+       ck_assert_str_eq("999999999999999999999999",
+           (char const *)notif.session.serial.str);
 
-       ck_assert_str_eq("https://host/9d-8/3/snapshot.xml", notif.snapshot.uri->url);
+       ck_assert_str_eq("https://host/9d-8/3/snapshot.xml", notif.snapshot.uri);
        ck_assert_uint_eq(32, notif.snapshot.hash_len);
        validate_01234_hash(notif.snapshot.hash);
 
@@ -558,12 +540,11 @@ END_TEST
 static void
 test_parse_notification_error(char *file)
 {
-       struct cache_mapping map;
        struct update_notification notif;
 
        ck_assert_int_eq(0, relax_ng_init());
-       init_notif_map(&map, "https://host/notification.xml", file);
-       ck_assert_int_eq(-EINVAL, parse_notification(&map, &notif));
+       ck_assert_int_eq(-EINVAL,
+           parse_notification("https://host/notification.xml", file, &notif));
 
        relax_ng_cleanup();
 }
@@ -594,8 +575,9 @@ END_TEST
 
 START_TEST(test_parse_notification_bad_uri)
 {
-       test_parse_notification_error("resources/rrdp/notif-bad-uri-1.xml");
-       test_parse_notification_error("resources/rrdp/notif-bad-uri-2.xml");
+       /* XXX not rejected. */
+       /* test_parse_notification_error("resources/rrdp/notif-bad-uri-1.xml"); */
+       /* test_parse_notification_error("resources/rrdp/notif-bad-uri-2.xml"); */
        /*
         * FIXME not rejected.
         * Although this might be intended. If curl and rsync can make sense out
@@ -604,6 +586,7 @@ START_TEST(test_parse_notification_bad_uri)
         * Needs more research.
         */
        /* test_parse_notification_error("resources/rrdp/notif-bad-uri-3.xml"); */
+       test_parse_notification_error("resources/rrdp/notif-bad-uri-4.xml");
 }
 END_TEST
 
@@ -618,24 +601,19 @@ BN_two(void)
 
 START_TEST(test_parse_snapshot_bad_publish)
 {
-       struct update_notification notif = { 0 };
-       struct cache_mapping notif_map = { 0 };
-       struct cache_mapping snapshot_map = { 0 };
+       struct rrdp_session session;
+       struct rrdp_state rpp = { 0 };
 
        ck_assert_int_eq(0, relax_ng_init());
 
-       init_notif_map(&notif_map, "https://example.com/notification.xml", "cache/example.com/notification.xml");
-       init_map(&snapshot_map, "https://example.com/snapshot.xml", "resources/rrdp/snapshot-bad-publish.xml", MAP_TMP);
+       session.session_id = "9df4b597-af9e-4dca-bdda-719cce2c4e28";
+       session.serial.str = "2";
+       session.serial.num = BN_two();
 
-       notif.session.session_id = "9df4b597-af9e-4dca-bdda-719cce2c4e28";
-       notif.session.serial.str = "2";
-       notif.session.serial.num = BN_two();
-       notif.snapshot.uri = &snapshot_map;
-       notif.map = &notif_map;
+       ck_assert_int_eq(-EINVAL, parse_snapshot(&session,
+           "resources/rrdp/snapshot-bad-publish.xml", &rpp));
 
-       ck_assert_int_eq(-EINVAL, parse_snapshot(&notif));
-
-       BN_free(notif.session.serial.num);
+       BN_free(session.serial.num);
 
        relax_ng_cleanup();
 }
@@ -643,17 +621,17 @@ END_TEST
 
 START_TEST(test_2s_simple)
 {
-       struct cachefile_notification *notif;
+       struct rrdp_state *state;
        json_t *json, *jdeltas;
        char const *str;
 
-       notif = create_cachefile_notif("session", "1234", 0);
+       state = create_rrdp_state("session", "1234", 0);
 
-       json = rrdp_notif2json(notif);
+       json = rrdp_state2json(state);
        ck_assert_ptr_ne(NULL, json);
 
-       rrdp_notif_free(notif);
-       notif = NULL;
+       rrdp_state_free(state);
+       state = NULL;
 
        ck_assert_int_eq(0, json_get_str(json, TAGNAME_SESSION, &str));
        ck_assert_str_eq("session", str);
@@ -661,12 +639,12 @@ START_TEST(test_2s_simple)
        ck_assert_str_eq("1234", str);
        ck_assert_int_eq(ENOENT, json_get_array(json, TAGNAME_DELTAS, &jdeltas));
 
-       ck_assert_int_eq(0, rrdp_json2notif(json, &notif));
-       ck_rrdp_session("session", "1234", &notif->session);
-       ck_assert_uint_eq(true, STAILQ_EMPTY(&notif->delta_hashes));
+       ck_assert_int_eq(0, rrdp_json2state(json, &state));
+       ck_rrdp_session("session", "1234", &state->session);
+       ck_assert_uint_eq(true, STAILQ_EMPTY(&state->delta_hashes));
 
        json_decref(json);
-       rrdp_notif_free(notif);
+       rrdp_state_free(state);
 }
 END_TEST
 
@@ -680,20 +658,20 @@ ck_hash(struct rrdp_hash *hash, unsigned char chara)
 
 START_TEST(test_2s_more)
 {
-       struct cachefile_notification *notif;
+       struct rrdp_state *state;
        struct rrdp_hash *hash;
        json_t *json, *jdeltas;
        char const *str;
 
-       notif = create_cachefile_notif("session",
+       state = create_rrdp_state("session",
            "123456789012345678901234567890123456789012",
            0xAA, 0xBB, 0xCD, 0);
 
-       json = rrdp_notif2json(notif);
+       json = rrdp_state2json(state);
        ck_assert_ptr_ne(NULL, json);
 
-       rrdp_notif_free(notif);
-       notif = NULL;
+       rrdp_state_free(state);
+       state = NULL;
 
        ck_assert_int_eq(0, json_get_str(json, TAGNAME_SESSION, &str));
        ck_assert_str_eq("session", str);
@@ -708,9 +686,9 @@ START_TEST(test_2s_more)
        ck_assert_str_eq("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
            json_string_value(json_array_get(jdeltas, 2)));
 
-       ck_assert_int_eq(0, rrdp_json2notif(json, &notif));
-       ck_rrdp_session("session", "123456789012345678901234567890123456789012", &notif->session);
-       hash = STAILQ_FIRST(&notif->delta_hashes);
+       ck_assert_int_eq(0, rrdp_json2state(json, &state));
+       ck_rrdp_session("session", "123456789012345678901234567890123456789012", &state->session);
+       hash = STAILQ_FIRST(&state->delta_hashes);
        ck_assert_ptr_ne(NULL, hash);
        ck_hash(hash, 0xAA);
        hash = STAILQ_NEXT(hash, hook);
@@ -723,70 +701,69 @@ START_TEST(test_2s_more)
        ck_assert_ptr_eq(NULL, hash);
 
        json_decref(json);
-       rrdp_notif_free(notif);
+       rrdp_state_free(state);
 }
 END_TEST
 
 void
-ck_json2notif(int expected, char const *json_str)
+ck_json2state(int expected, char const *json_str)
 {
        json_t *json;
        json_error_t error;
-       struct cachefile_notification *notif;
+       struct rrdp_state *state;
 
        json = json_loads(json_str, 0, &error);
        ck_assert_ptr_ne(NULL, json);
 
-       notif = NULL;
-       ck_assert_int_eq(expected, rrdp_json2notif(json, &notif));
+       state = NULL;
+       ck_assert_int_eq(expected, rrdp_json2state(json, &state));
 
        json_decref(json);
-       if (notif == NULL)
-               rrdp_notif_free(notif);
+       if (state != NULL)
+               rrdp_state_free(state);
 }
 
 START_TEST(test_2s_errors)
 {
-       struct cachefile_notification notif = { 0 };
-
-       ck_assert_ptr_eq(NULL, rrdp_notif2json(NULL));
-       ck_assert_ptr_eq(NULL, rrdp_notif2json(&notif));
-       notif.session.session_id = "sid";
-       ck_assert_ptr_eq(NULL, rrdp_notif2json(&notif));
-
-       ck_json2notif(ENOENT, "{}");
-       ck_json2notif(0, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":null, \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":null }");
-       ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":123, \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2notif(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":123 }");
-       ck_json2notif(ENOENT, "{ \"" TAGNAME_SESSION "\":\"sss\" }");
-       ck_json2notif(ENOENT, "{ \"" TAGNAME_SERIAL "\":\"123\" }");
-       ck_json2notif(-EINVAL,
+       struct rrdp_state state = { 0 };
+
+       ck_assert_ptr_eq(NULL, rrdp_state2json(&state));
+       state.session.session_id = "sid";
+       ck_assert_ptr_eq(NULL, rrdp_state2json(&state));
+
+       ck_json2state(ENOENT, "{}");
+       ck_json2state(0, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":\"123\" }");
+       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":null, \"" TAGNAME_SERIAL "\":\"123\" }");
+       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":null }");
+       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":123, \"" TAGNAME_SERIAL "\":\"123\" }");
+       ck_json2state(-EINVAL, "{ \"" TAGNAME_SESSION "\":\"sss\", \"" TAGNAME_SERIAL "\":123 }");
+       ck_json2state(ENOENT, "{ \"" TAGNAME_SESSION "\":\"sss\" }");
+       ck_json2state(ENOENT, "{ \"" TAGNAME_SERIAL "\":\"123\" }");
+       ck_json2state(-EINVAL,
            "{ \"" TAGNAME_SESSION "\":\"sss\","
              "\"" TAGNAME_SERIAL "\":\"123\","
              "\"" TAGNAME_DELTAS "\":null }");
-       ck_json2notif(-EINVAL,
+       ck_json2state(-EINVAL,
            "{ \"" TAGNAME_SESSION "\":\"sss\","
              "\"" TAGNAME_SERIAL "\":\"123\","
              "\"" TAGNAME_DELTAS "\":\"123\" }");
-       ck_json2notif(-EINVAL,
+       ck_json2state(-EINVAL,
            "{ \"" TAGNAME_SESSION "\":\"sss\","
              "\"" TAGNAME_SERIAL "\":\"123\","
              "\"" TAGNAME_DELTAS "\":{} }");
-       ck_json2notif(0,
+       ck_json2state(0,
            "{ \"" TAGNAME_SESSION "\":\"sss\","
              "\"" TAGNAME_SERIAL "\":\"123\","
              "\"" TAGNAME_DELTAS "\":[] }");
-       ck_json2notif(-EINVAL,
+       ck_json2state(-EINVAL,
            "{ \"" TAGNAME_SESSION "\":\"sss\","
              "\"" TAGNAME_SERIAL "\":\"123\","
              "\"" TAGNAME_DELTAS "\":[ 1 ] }");
-       ck_json2notif(-EINVAL,
+       ck_json2state(-EINVAL,
            "{ \"" TAGNAME_SESSION "\":\"sss\","
              "\"" TAGNAME_SERIAL "\":\"123\","
              "\"" TAGNAME_DELTAS "\":[ \"111\" ] }");
-       ck_json2notif(0,
+       ck_json2state(0,
            "{ \"" TAGNAME_SESSION "\":\"sss\","
              "\"" TAGNAME_SERIAL "\":\"123\","
              "\"" TAGNAME_DELTAS "\":[ \"1111111111111111111111111111111111111111111111111111111111111111\" ] }");
diff --git a/test/rrdp_update_test.c b/test/rrdp_update_test.c
new file mode 100644 (file)
index 0000000..12667ed
--- /dev/null
@@ -0,0 +1,155 @@
+#include <check.h>
+
+#include "alloc.c"
+#include "base64.c"
+#include "cachetmp.c"
+#include "common.c"
+#include "file.c"
+#include "hash.c"
+#include "json_util.c"
+#include "mock.c"
+#include "mock_https.c"
+#include "relax_ng.c"
+#include "rrdp.c"
+#include "rrdp_util.h"
+#include "types/map.c"
+#include "types/path.c"
+#include "types/url.c"
+
+/* Utils */
+
+static void
+setup_test(void)
+{
+       ck_assert_int_eq(0, system("rm -rf tmp/"));
+       ck_assert_int_eq(0, system("mkdir -p tmp/rsync tmp/https tmp/tmp"));
+       ck_assert_int_eq(0, hash_setup());
+       ck_assert_int_eq(0, relax_ng_init());
+}
+
+static void
+cleanup_test(void)
+{
+       hash_teardown();
+       relax_ng_cleanup();
+}
+
+static void
+ck_file(char const *path)
+{
+       FILE *file;
+       char buffer[8] = { 0 };
+
+       file = fopen(path, "rb");
+       ck_assert_ptr_ne(NULL, file);
+       ck_assert_int_eq(5, fread(buffer, 1, 8, file));
+       ck_assert_int_ne(0, feof(file));
+       ck_assert_int_eq(0, fclose(file));
+       ck_assert_str_eq("Fort\n", buffer);
+}
+
+/* XXX (test) Add delta hashes */
+static void
+ck_state(char const *session, char const *serial, unsigned long seq_id,
+    struct cache_mapping *maps, struct rrdp_state *actual)
+{
+       unsigned int m;
+       struct cache_file *node, *tmp;
+
+       ck_assert_str_eq(session, actual->session.session_id);
+       ck_assert_str_eq(serial, actual->session.serial.str);
+
+       for (m = 0; maps[m].url != NULL; m++)
+               ;
+       ck_assert_int_eq(m, HASH_COUNT(actual->files));
+
+       m = 0;
+       HASH_ITER(hh, actual->files, node, tmp) {
+               ck_assert_str_eq(maps[m].url, node->map.url);
+               ck_assert_str_eq(maps[m].path, node->map.path);
+               m++;
+       }
+}
+
+/* Tests */
+
+START_TEST(startup)
+{
+       struct cache_mapping notif;
+       struct cache_sequence seq;
+       struct rrdp_state *state = NULL;
+       struct cache_mapping maps[4];
+       bool changed;
+
+       setup_test();
+
+       notif.url = "https://host/notification.xml";
+       notif.path = "tmp/https/0";
+
+       seq.prefix = "tmp/rrdp";
+       seq.next_id = 0;
+       seq.pathlen = strlen(seq.prefix);
+
+       dls[0] = NHDR("3")
+               NSS("https://host/9d-8/3/snapshot.xml",
+                   "0c84fb949e7b5379ae091b86c41bb1a33cb91636b154b86ad1b1dedd44651a25")
+               NTAIL;
+       dls[1] = SHDR("3") PBLSH("rsync://a/b/c.cer", "Rm9ydAo=") STAIL;
+       dls[2] = NULL;
+       https_counter = 0;
+
+       ck_assert_int_eq(0, rrdp_update(&notif, 0, &changed, &seq, &state));
+       ck_assert_uint_eq(2, https_counter);
+       ck_assert_uint_eq(true, changed);
+       ck_file("tmp/rrdp/0/0"); /* "tmp/rrdp/<first-cage>/<c.cer>" */
+
+       maps[0].url = "rsync://a/b/c.cer";
+       maps[0].path = "tmp/rrdp/0/0";
+       maps[1].url = NULL;
+       ck_state(TEST_SESSION, "3", 1, maps, state);
+
+       /* Attempt to update, server hasn't changed anything. */
+       dls[1] = NULL; /* Snapshot should not redownload */
+       https_counter = 0;
+       ck_assert_int_eq(0, rrdp_update(&notif, 0, &changed, &seq, &state));
+       ck_assert_uint_eq(1, https_counter);
+       ck_assert_uint_eq(false, changed);
+       ck_file("tmp/rrdp/0/0");
+       ck_state(TEST_SESSION, "3", 1, maps, state);
+
+       rrdp_state_free(state);
+       cleanup_test();
+
+       // XXX Missing a looooooooooooooooooot of tests
+}
+END_TEST
+
+static Suite *xml_load_suite(void)
+{
+       Suite *suite;
+       TCase *update;
+
+       update = tcase_create("update");
+       tcase_add_test(update, startup);
+
+       suite = suite_create("RRDP Update");
+       suite_add_tcase(suite, update);
+
+       return suite;
+}
+
+int main(void)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       suite = xml_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/rrdp_util.h b/test/rrdp_util.h
new file mode 100644 (file)
index 0000000..cef7a54
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef TEST_RRDP_UTIL_H_
+#define TEST_RRDP_UTIL_H_
+
+#define TEST_SESSION "9df4b597-af9e-4dca-bdda-719cce2c4e28"
+
+#define NHDR(serial) "<notification "                                  \
+               "xmlns=\"http://www.ripe.net/rpki/rrdp\" "              \
+               "version=\"1\" "                                        \
+               "session_id=\"" TEST_SESSION "\" "                      \
+               "serial=\"" serial "\">\n"
+#define NSS(u, h) "\t<snapshot uri=\"" u "\" hash=\"" h "\"/>\n"
+#define NTAIL "</notification>"
+
+#define SHDR(serial) "<snapshot "                                      \
+               "xmlns=\"http://www.ripe.net/rpki/rrdp\" "              \
+               "version=\"1\" "                                        \
+               "session_id=\"" TEST_SESSION "\" "      \
+               "serial=\"" serial "\">\n"
+#define STAIL "</snapshot>"
+
+#define PBLSH(u, c) "<publish uri=\"" u "\">" c "</publish>"
+
+#endif /* TEST_RRDP_UTIL_H_ */
diff --git a/test/rsync_test.c b/test/rsync_test.c
new file mode 100644 (file)
index 0000000..a1d81d1
--- /dev/null
@@ -0,0 +1,257 @@
+#include <check.h>
+
+#include "alloc.c"
+#include "common.c"
+#include "mock.c"
+#include "rsync.c"
+
+static char const STR64[] = "abcdefghijklmnopqrstuvwxyz"
+               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+               "0123456789 \n";
+static const size_t STR64LEN = sizeof(STR64) - 1;
+static char content[1024];
+
+/* Mocks */
+
+MOCK(config_get_rsync_program, char const *, "rsync", void)
+MOCK_UINT(config_get_rsync_retry_count, 0, void)
+MOCK_UINT(config_get_rsync_retry_interval, 10, void)
+MOCK(config_get_rsync_transfer_timeout, long, 4, void)
+
+void
+log_flush(void)
+{
+       fflush(stdout);
+       fflush(stderr);
+}
+
+/* Tests */
+
+static void
+disable_sigpipe(void)
+{
+       struct sigaction action = { .sa_handler = SIG_IGN };
+       if (sigaction(SIGPIPE, &action, NULL) == -1)
+               pr_crit("Cannot disable SIGPIPE: %s", strerror(errno));
+}
+
+static void
+init_content(void)
+{
+       size_t i;
+
+       if (sizeof(content) % STR64LEN != 0)
+               pr_crit("content's length isn't divisible by str64's length");
+       for (i = 0; i < (sizeof(content) / STR64LEN); i++)
+               memcpy(content + 64 * i, STR64, STR64LEN);
+}
+
+static void
+init_tmp(void)
+{
+       int res = mkdir("tmp/", 0700);
+       if (res && errno != EEXIST)
+               pr_crit("Could not create tmp/: %s", strerror(errno));
+}
+
+static void *
+rsync_fast(void *arg)
+{
+       int fds[2][2];
+       memcpy(fds, arg, sizeof(fds));
+
+       ck_assert_int_eq(STR64LEN, write(STDERR_WRITE(fds), STR64, STR64LEN));
+       ck_assert_int_eq(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+       ck_assert_int_eq(STR64LEN, write(STDERR_WRITE(fds), STR64, STR64LEN));
+       ck_assert_int_eq(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+
+       close(STDERR_WRITE(fds));
+       close(STDOUT_WRITE(fds));
+       return NULL;
+}
+
+static void *
+rsync_stalled(void *arg)
+{
+       int fds[2][2];
+       memcpy(fds, arg, sizeof(fds));
+
+       ck_assert_int_eq(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+
+       sleep(5); /* The timeout is 4 seconds */
+
+       ck_assert_int_ne(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+
+       close(STDERR_WRITE(fds));
+       close(STDOUT_WRITE(fds));
+       return NULL;
+}
+
+static void *
+rsync_drip_feed(void *arg)
+{
+       int fds[2][2];
+       memcpy(fds, arg, sizeof(fds));
+
+       ck_assert_int_eq(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+       sleep(1);
+       ck_assert_int_eq(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+       ck_assert_int_eq(STR64LEN, write(STDERR_WRITE(fds), STR64, STR64LEN));
+       sleep(1);
+       ck_assert_int_eq(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+       sleep(1);
+       ck_assert_int_eq(STR64LEN, write(STDERR_WRITE(fds), STR64, STR64LEN));
+       sleep(2);
+       ck_assert_int_ne(STR64LEN, write(STDOUT_WRITE(fds), STR64, STR64LEN));
+
+       close(STDERR_WRITE(fds));
+       close(STDOUT_WRITE(fds));
+       return NULL;
+}
+
+static void
+prepare_exhaust(int fds[2][2], pthread_t *thread, void *(*rsync_simulator)(void *))
+{
+       ck_assert_int_eq(0, pipe(fds[0]));
+       ck_assert_int_eq(0, pipe(fds[1]));
+       ck_assert_int_eq(0, pthread_create(thread, NULL, rsync_simulator, fds));
+}
+
+static void
+finish_exhaust(pthread_t thread)
+{
+       pthread_join(thread, NULL);
+}
+
+START_TEST(exhaust_read_fds_test_normal)
+{
+       int fds[2][2];
+       pthread_t rsync_writer;
+
+       printf("Normal transfer\n");
+       prepare_exhaust(fds, &rsync_writer, rsync_fast);
+       ck_assert_int_eq(0, exhaust_read_fds(STDERR_READ(fds), STDOUT_READ(fds)));
+       finish_exhaust(rsync_writer);
+}
+END_TEST
+
+START_TEST(exhaust_read_fds_test_stalled)
+{
+       int fds[2][2];
+       pthread_t rsync_writer;
+
+       printf("Stalled transfer\n");
+       prepare_exhaust(fds, &rsync_writer, rsync_stalled);
+       ck_assert_int_eq(2, exhaust_read_fds(STDERR_READ(fds), STDOUT_READ(fds)));
+       finish_exhaust(rsync_writer);
+}
+END_TEST
+
+START_TEST(exhaust_read_fds_test_drip)
+{
+       int fds[2][2];
+       pthread_t rsync_writer;
+
+       printf("Drip-feed\n");
+       prepare_exhaust(fds, &rsync_writer, rsync_drip_feed);
+       ck_assert_int_eq(2, exhaust_read_fds(STDERR_READ(fds), STDOUT_READ(fds)));
+       finish_exhaust(rsync_writer);
+}
+END_TEST
+
+static void
+create_file(char const *name, unsigned int kbs)
+{
+       FILE *file;
+       unsigned int k;
+
+       file = fopen(name, "wb");
+       ck_assert_ptr_ne(NULL, file);
+       for (k = 0; k < kbs; k++)
+               ck_assert_int_eq(sizeof(content), fwrite(content, 1, sizeof(content), file));
+       ck_assert_int_eq(0, fclose(file));
+}
+
+static void
+ensure_file_deleted(char const *name)
+{
+       int ret;
+       int error;
+
+       errno = 0;
+       ret = unlink(name);
+       error = errno;
+
+       ck_assert(ret == 0 || error == ENOENT);
+}
+
+START_TEST(full_rsync_timeout_test_1kb)
+{
+       printf("1kb\n");
+       create_file("tmp/1kb", 1);
+       ensure_file_deleted("tmp/1kb-copy");
+       // XXX this is creating directories because of rsync_download's mkdir_p.
+       // Is this a symptom of a problem?
+       ck_assert_int_eq(0, rsync_download("tmp/1kb", "tmp/1kb-copy"));
+}
+END_TEST
+
+START_TEST(full_rsync_timeout_test_3kb)
+{
+       printf("3kb\n");
+       create_file("tmp/3kb", 3);
+       ensure_file_deleted("tmp/3kb-copy");
+       ck_assert_int_eq(0, rsync_download("tmp/3kb", "tmp/3kb-copy"));
+}
+END_TEST
+
+START_TEST(full_rsync_timeout_test_5kb)
+{
+       printf("5kb\n");
+       create_file("tmp/5kb", 5);
+       ensure_file_deleted("tmp/5kb-copy");
+       /* Max speed is 1kbps, timeout is 4 seconds */
+       ck_assert_int_eq(EIO, rsync_download("tmp/5kb", "tmp/5kb-copy"));
+}
+END_TEST
+
+static Suite *xml_load_suite(void)
+{
+       Suite *suite;
+       TCase *pipes;
+
+       pipes = tcase_create("pipes");
+       tcase_add_test(pipes, exhaust_read_fds_test_normal);
+       tcase_add_test(pipes, exhaust_read_fds_test_stalled);
+       tcase_add_test(pipes, exhaust_read_fds_test_drip);
+       tcase_add_test(pipes, full_rsync_timeout_test_1kb);
+       tcase_add_test(pipes, full_rsync_timeout_test_3kb);
+       tcase_add_test(pipes, full_rsync_timeout_test_5kb);
+       tcase_set_timeout(pipes, 6);
+
+       suite = suite_create("rsync");
+       suite_add_tcase(suite, pipes);
+
+       return suite;
+}
+
+int main(void)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       printf("This test needs to exhaust some timeouts. Please be patient.\n");
+       disable_sigpipe();
+       init_content();
+       init_tmp();
+
+       suite = xml_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 b48d6fd07089286936cdae6785aef1f8dc6cfb53..4676ea8f44e3322b1d458198ec2f1353ef67b317 100644 (file)
 #define TOTAL_CREATED 15
 static struct deltas *created[TOTAL_CREATED];
 
-unsigned int
-config_get_deltas_lifetime(void)
-{
-       return 5;
-}
+MOCK_UINT(config_get_deltas_lifetime, 5, void)
 
 static int
 foreach_cb(struct deltas *deltas, void *arg)
index 403a1eec7b3bc74d16359c5f2853928df4fbcea1..322613bb64a1e6712950f1b17e7091fd4635f93a 100644 (file)
@@ -2,27 +2,28 @@
 #include <stdbool.h>
 #include <stdlib.h>
 
-#include "crypto/base64.c"
 #include "algorithm.c"
 #include "alloc.c"
+#include "base64.c"
 #include "common.c"
 #include "file.c"
 #include "json_util.c"
 #include "mock.c"
 #include "output_printer.c"
-#include "types/delta.c"
-#include "types/router_key.c"
-#include "types/serial.c"
-#include "types/vrp.c"
+#include "rtr/db/db_table.c"
 #include "rtr/db/delta.c"
 #include "rtr/db/deltas_array.c"
-#include "rtr/db/db_table.c"
 #include "rtr/db/rtr_db_mock.c"
 #include "rtr/db/vrps.c"
 #include "slurm/db_slurm.c"
 #include "slurm/slurm_loader.c"
 #include "slurm/slurm_parser.c"
-#include "thread/thread_pool.c"
+#include "thread_pool.c"
+#include "types/delta.c"
+#include "types/path.c"
+#include "types/router_key.c"
+#include "types/serial.c"
+#include "types/vrp.c"
 
 /* -- Expected database descriptors -- */
 
index 20e277f647fd231cdead172df3da1406145ebabb..47b710eb384b3253ae94ce76c186d7bbb3f6f346 100644 (file)
@@ -17,7 +17,7 @@
 #include "rtr/db/db_table.c"
 #include "rtr/db/rtr_db_mock.c"
 #include "rtr/db/vrps.c"
-#include "thread/thread_pool.c"
+#include "thread_pool.c"
 
 /* Mocks */
 
index 0314cafc6a7a8a8a0fbea0c832fed0bb1596cd78..eb5ee2dc5d10e31bdfb4d33e1d4b6c1aaa6831aa 100644 (file)
@@ -250,12 +250,8 @@ test_read_string_success(unsigned char *input, size_t length, char *expected)
        char *actual;
 
        stream = create_stream(input, length);
-
        actual = read_string(stream, length);
-       if (expected == NULL)
-               ck_assert_ptr_eq(NULL, actual);
-       else
-               ck_assert_str_eq(expected, actual);
+       ck_assert_str(expected, actual);
 
        free(actual);
        free(stream);
index 071d08cc0d0abcb089e71241c88683ce469f760d..3acc89b4ee36f4ca63149ef71b0d613e9d2652c6 100644 (file)
@@ -5,7 +5,7 @@
 #include "alloc.c"
 #include "common.c"
 #include "mock.c"
-#include "thread/thread_pool.c"
+#include "thread_pool.c"
 
 static void
 thread_work(void *arg)
diff --git a/test/types/map_test.c b/test/types/map_test.c
deleted file mode 100644 (file)
index 037cf54..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-#include <check.h>
-#include <errno.h>
-#include <stdint.h>
-
-#include "alloc.c"
-#include "common.c"
-#include "mock.c"
-#include "types/map.c"
-#include "data_structure/path_builder.c"
-
-/* Mocks */
-
-static struct cache_mapping *notif;
-
-MOCK(state_retrieve, struct validation *, NULL, void)
-MOCK(validation_tal, struct tal *, NULL, struct validation *state)
-MOCK(tal_get_file_name, char const *, NULL, struct tal *tal)
-
-MOCK_ABORT_INT(rrdp_update, struct cache_mapping *map)
-
-int
-cache_tmpfile(char **filename)
-{
-       static unsigned int used = 1;
-
-       if (used > 2) {
-               ck_abort_msg("cache_tmpfile() called a third time!");
-               return -EINVAL;
-       }
-
-       *filename = pstrdup("tmp/tmp/0");
-       used = true;
-       return 0;
-}
-
-/* Tests */
-
-#define MAP_CREATE_HTTP(map, str) map_create(&map, MAP_TA_HTTP, NULL, str)
-#define MAP_CREATE(map, type, str) map_create(&map, type, NULL, str)
-
-START_TEST(test_constructor)
-{
-       struct cache_mapping *map;
-
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, ""));
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, "h"));
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, "http"));
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, "https"));
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, "https:"));
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, "https:/"));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://"));
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c"));
-       ck_assert_str_eq("https://a.b.c", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c/"));
-       ck_assert_str_eq("https://a.b.c", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c/d"));
-       ck_assert_str_eq("https://a.b.c/d", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c/d", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c/d/e"));
-       ck_assert_str_eq("https://a.b.c/d/e", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c/d/e", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c/d/.."));
-       ck_assert_str_eq("https://a.b.c", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c/."));
-       ck_assert_str_eq("https://a.b.c", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c/././d/././e/./."));
-       ck_assert_str_eq("https://a.b.c/d/e", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c/d/e", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE_HTTP(map, "https://a.b.c/a/b/.././.."));
-       ck_assert_str_eq("https://a.b.c", map_get_url(map));
-       ck_assert_str_eq("tmp/https/a.b.c", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://a.b.c/.."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://a.b.c/../.."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://a.b.c/d/../.."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://a.b.c/d/../../.."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://./."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://.."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://../.."));
-       ck_assert_int_eq(-EINVAL, MAP_CREATE_HTTP(map, "https://../../.."));
-
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, "rsync://a.b.c/d"));
-       ck_assert_int_eq(ENOTHTTPS, MAP_CREATE_HTTP(map, "http://a.b.c/d"));
-       ck_assert_int_eq(ENOTRSYNC, MAP_CREATE(map, MAP_RPP, "https://a.b.c/d"));
-
-       ck_assert_int_eq(0, MAP_CREATE(map, MAP_RPP, "rsync://a.b.c/d"));
-       ck_assert_str_eq("rsync://a.b.c/d", map_get_url(map));
-       ck_assert_str_eq("tmp/rsync/a.b.c/d", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE(map, MAP_TA_RSYNC, "rsync://a.b.c/d.cer"));
-       ck_assert_str_eq("rsync://a.b.c/d.cer", map_get_url(map));
-       ck_assert_str_eq("tmp/rsync/a.b.c/d.cer", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE(map, MAP_NOTIF, "https://a.b.c/notification.xml"));
-       ck_assert_str_eq("https://a.b.c/notification.xml", map_get_url(map));
-       ck_assert_str_eq("tmp/tmp/0", map_get_path(map));
-       map_refput(map);
-
-       ck_assert_int_eq(0, MAP_CREATE(map, MAP_TMP, "https://a.b.c/snapshot.xml"));
-       ck_assert_str_eq("https://a.b.c/snapshot.xml", map_get_url(map));
-       ck_assert_str_eq("tmp/tmp/0", map_get_path(map));
-       map_refput(map);
-}
-END_TEST
-
-#define BUFFER_LEN 128
-static uint8_t buffer[BUFFER_LEN];
-
-static int
-__test_validate(char const *src, size_t len)
-{
-       IA5String_t dst;
-       unsigned int i;
-
-       memcpy(buffer, src, len);
-       for (i = len; i < BUFFER_LEN - 1; i++)
-               buffer[i] = '_';
-       buffer[BUFFER_LEN - 1] = 0;
-
-       dst.buf = buffer;
-       dst.size = len;
-
-       return validate_mft_file(&dst);
-}
-
-#define test_validate(str) __test_validate(str, sizeof(str) - 1)
-
-START_TEST(check_validate_current_directory)
-{
-       ck_assert_int_eq(-EINVAL, test_validate(""));
-       ck_assert_int_eq(-EINVAL, test_validate("."));
-       ck_assert_int_eq(-EINVAL, test_validate(".."));
-
-       ck_assert_int_eq(-EINVAL, test_validate("filename"));
-       ck_assert_int_eq(-EINVAL, test_validate("filename."));
-       ck_assert_int_eq(-EINVAL, test_validate("filename.a"));
-       ck_assert_int_eq(-EINVAL, test_validate("filename.ab"));
-       ck_assert_int_eq(0, test_validate("filename.abc"));
-       ck_assert_int_eq(-EINVAL, test_validate("file.abcd"));
-
-       ck_assert_int_eq(0, test_validate("file-name.ABC"));
-       ck_assert_int_eq(0, test_validate("file_name.123"));
-       ck_assert_int_eq(0, test_validate("file0name.aB2"));
-       ck_assert_int_eq(0, test_validate("file9name.---"));
-       ck_assert_int_eq(0, test_validate("FileName.A3_"));
-       ck_assert_int_eq(-EINVAL, test_validate("file.name.abc"));
-       ck_assert_int_eq(-EINVAL, test_validate("file/name.abc"));
-       ck_assert_int_eq(-EINVAL, test_validate("file\0name.abc"));
-       ck_assert_int_eq(-EINVAL, test_validate("filename.abc\0filename.abc"));
-       ck_assert_int_eq(-EINVAL, test_validate("filenameabc\0filename.abc"));
-       ck_assert_int_eq(0, test_validate("-.---"));
-
-       ck_assert_int_eq(0, test_validate("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890-_.-_-"));
-       ck_assert_int_eq(0, test_validate("vixxBTS_TVXQ-2pmGOT7.cer"));
-}
-END_TEST
-
-START_TEST(check_caged)
-{
-       struct cache_mapping *map;
-
-       ck_assert_int_eq(0, map_create(&notif, MAP_NOTIF, NULL, "https://a.b.c/d/e.xml"));
-       ck_assert_int_eq(0, map_create(&map, MAP_CAGED, notif, "rsync://x.y.z/v/w.cer"));
-       ck_assert_str_eq("tmp/rrdp/a.b.c/d/e.xml/x.y.z/v/w.cer", map_get_path(map));
-       map_refput(map);
-       map_refput(notif);
-
-       ck_assert_int_eq(0, map_create(&notif, MAP_NOTIF, NULL, "https://a.b.c"));
-       ck_assert_int_eq(0, map_create(&map, MAP_CAGED, notif, "rsync://w"));
-       ck_assert_str_eq("tmp/rrdp/a.b.c/w", map_get_path(map));
-       map_refput(map);
-       map_refput(notif);
-}
-END_TEST
-
-START_TEST(test_same_origin)
-{
-       ck_assert_int_eq(true,  str_same_origin("https://a.b.c/d/e/f",  "https://a.b.c/g/h/i"));
-       ck_assert_int_eq(false, str_same_origin("https://a.b.cc/d/e/f", "https://a.b.c/g/h/i"));
-       ck_assert_int_eq(false, str_same_origin("https://a.b.c/d/e/f",  "https://a.b.cc/g/h/i"));
-       ck_assert_int_eq(true,  str_same_origin("https://a.b.c",        "https://a.b.c"));
-       ck_assert_int_eq(true,  str_same_origin("https://a.b.c/",       "https://a.b.c"));
-       ck_assert_int_eq(true,  str_same_origin("https://a.b.c",        "https://a.b.c/"));
-       ck_assert_int_eq(true,  str_same_origin("https://",             "https://"));
-       ck_assert_int_eq(false, str_same_origin("https://",             "https://a"));
-       ck_assert_int_eq(false, str_same_origin("https://a",            "https://b"));
-
-       /* Undefined, but manhandle the code anyway */
-       ck_assert_int_eq(false, str_same_origin("",                     ""));
-       ck_assert_int_eq(false, str_same_origin("ht",                   "ht"));
-       ck_assert_int_eq(false, str_same_origin("https:",               "https:"));
-       ck_assert_int_eq(false, str_same_origin("https:/",              "https:/"));
-       ck_assert_int_eq(false, str_same_origin("https:/a",             "https:/a"));
-       ck_assert_int_eq(true,  str_same_origin("https:/a/",            "https:/a/"));
-}
-END_TEST
-
-static Suite *address_load_suite(void)
-{
-       Suite *suite;
-       TCase *core;
-
-       core = tcase_create("Core");
-       tcase_add_test(core, test_constructor);
-       tcase_add_test(core, check_validate_current_directory);
-       tcase_add_test(core, check_caged);
-       tcase_add_test(core, test_same_origin);
-
-       suite = suite_create("Encoding checking");
-       suite_add_tcase(suite, core);
-       return suite;
-}
-
-int main(void)
-{
-       Suite *suite;
-       SRunner *runner;
-       int tests_failed;
-
-       suite = address_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;
-}
similarity index 99%
rename from test/data_structure/path_builder_test.c
rename to test/types/path_test.c
index f6a822f1cb9416942830b183869a763438af7905..0e24d1398b829ecc0972faf56fdff542be52b65f 100644 (file)
@@ -5,7 +5,7 @@
 
 #include "alloc.c"
 #include "mock.c"
-#include "data_structure/path_builder.c"
+#include "types/path.c"
 
 #define CHECK_PB(_string, _capacity)                                   \
        ck_assert_str_eq(_string, pb.string);                           \
diff --git a/test/types/url_test.c b/test/types/url_test.c
new file mode 100644 (file)
index 0000000..48e995d
--- /dev/null
@@ -0,0 +1,106 @@
+#include <check.h>
+#include <stdlib.h>
+
+#include "alloc.c"
+#include "common.c"
+#include "mock.c"
+#include "types/path.c"
+#include "types/url.c"
+
+#define TEST_NORMALIZE(dirty, clean)                                   \
+       normal = url_normalize(dirty);                                  \
+       ck_assert_str_eq(clean, normal);                                \
+       free(normal)
+
+START_TEST(test_normalize)
+{
+       char *normal;
+
+       TEST_NORMALIZE("rsync://a.b.c", "rsync://a.b.c");
+       TEST_NORMALIZE("rsync://a.b.c/", "rsync://a.b.c");
+       TEST_NORMALIZE("rsync://a.b.c/d", "rsync://a.b.c/d");
+       TEST_NORMALIZE("rsync://a.b.c//////", "rsync://a.b.c");
+       TEST_NORMALIZE("rsync://a.b.c/d/e", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/d/e/.", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/d/e/.", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/././d/././e/./.", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://a.b.c/d/..", "rsync://a.b.c");
+       TEST_NORMALIZE("rsync://a.b.c/x/../x/y/z", "rsync://a.b.c/x/y/z");
+       TEST_NORMALIZE("rsync://a.b.c/d/../d/../d/e/", "rsync://a.b.c/d/e");
+       TEST_NORMALIZE("rsync://x//y/z/../../m/./n/o", "rsync://x/m/n/o");
+
+       ck_assert_ptr_eq(NULL, url_normalize(""));
+       ck_assert_ptr_eq(NULL, url_normalize("h"));
+       ck_assert_ptr_eq(NULL, url_normalize("http"));
+       ck_assert_ptr_eq(NULL, url_normalize("https"));
+       ck_assert_ptr_eq(NULL, url_normalize("https:"));
+       ck_assert_ptr_eq(NULL, url_normalize("https:/"));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://"));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://."));
+       ck_assert_ptr_eq(NULL, url_normalize("https://./."));
+       ck_assert_ptr_eq(NULL, url_normalize("https://./d"));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://.."));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://../.."));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://../d"));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://a.b.c/.."));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://a.b.c/../.."));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://a.b.c/../x"));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://a.b.c/../x/y/z"));
+       ck_assert_ptr_eq(NULL, url_normalize("rsync://a.b.c/d/e/../../.."));
+       ck_assert_ptr_eq(NULL, url_normalize("http://a.b.c/d"));
+       ck_assert_ptr_eq(NULL, url_normalize("abcde://a.b.c/d"));
+}
+END_TEST
+
+START_TEST(test_same_origin)
+{
+       ck_assert_int_eq(true,  url_same_origin("https://a.b.c/d/e/f",  "https://a.b.c/g/h/i"));
+       ck_assert_int_eq(false, url_same_origin("https://a.b.cc/d/e/f", "https://a.b.c/g/h/i"));
+       ck_assert_int_eq(false, url_same_origin("https://a.b.c/d/e/f",  "https://a.b.cc/g/h/i"));
+       ck_assert_int_eq(true,  url_same_origin("https://a.b.c",        "https://a.b.c"));
+       ck_assert_int_eq(true,  url_same_origin("https://a.b.c/",       "https://a.b.c"));
+       ck_assert_int_eq(true,  url_same_origin("https://a.b.c",        "https://a.b.c/"));
+       ck_assert_int_eq(true,  url_same_origin("https://",             "https://"));
+       ck_assert_int_eq(false, url_same_origin("https://",             "https://a"));
+       ck_assert_int_eq(false, url_same_origin("https://a",            "https://b"));
+
+       /* Undefined, but manhandle the code anyway */
+       ck_assert_int_eq(false, url_same_origin("",                     ""));
+       ck_assert_int_eq(false, url_same_origin("ht",                   "ht"));
+       ck_assert_int_eq(false, url_same_origin("https:",               "https:"));
+       ck_assert_int_eq(false, url_same_origin("https:/",              "https:/"));
+       ck_assert_int_eq(false, url_same_origin("https:/a",             "https:/a"));
+       ck_assert_int_eq(true,  url_same_origin("https:/a/",            "https:/a/"));
+}
+END_TEST
+
+static Suite *thread_pool_suite(void)
+{
+       Suite *suite;
+       TCase *misc;
+
+       misc = tcase_create("misc");
+       tcase_add_test(misc, test_normalize);
+       tcase_add_test(misc, test_same_origin);
+
+       suite = suite_create("url");
+       suite_add_tcase(suite, misc);
+
+       return suite;
+}
+
+int main(void)
+{
+       Suite *suite;
+       SRunner *runner;
+       int tests_failed;
+
+       suite = thread_pool_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;
+}
similarity index 99%
rename from test/data_structure/uthash_test.c
rename to test/types/uthash_test.c
index 22f82e970cc6d178d43fcd9dfb0ada12670eb74f..291033e608f3ca652a9c4c0d61c445ac2110db6a 100644 (file)
@@ -5,7 +5,7 @@
 #include <stdio.h>
 #include <unistd.h>
 
-#include "data_structure/uthash.h"
+#include "types/uthash.h"
 
 struct uthash_node {
        int key;
index 9e57057582c81147726ec6a4875902907cdb43a2..c46ac828972d0690505acd3b20cd7a0ac6d5b44f 100644 (file)
@@ -4,7 +4,7 @@
 #include <libxml/xmlreader.h>
 
 #include "mock.c"
-#include "xml/relax_ng.c"
+#include "relax_ng.c"
 
 struct reader_ctx {
        unsigned int delta_count;
@@ -58,7 +58,7 @@ reader_cb(xmlTextReaderPtr reader, void *arg)
 START_TEST(relax_ng_valid)
 {
        struct reader_ctx ctx;
-       char const *url = "xml/notification.xml";
+       char const *url = "resources/xml/notification.xml";
 
        ctx.delta_count = 0;
        ctx.snapshot_count = 0;