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.
-# 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
--- /dev/null
+---
+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. |
<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>
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)
[--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>]
- **Type:** Boolean (`true`, `false`)
- **Availability:** `argv` and JSON
+- **Default:** `false`
Skip the repository cache update?
- **Type:** Boolean (`true`, `false`)
- **Availability:** `argv` and JSON
+- **Default:** `false`
Send process to the background?
- **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.
- **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.
- **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**_
- **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**_
- **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).
- **Type:** String (Path to file)
- **Availability:** `argv` and JSON
+- **Default:** `NULL` (disabled)
>  BGPsec certificate validation has been disabled in version 1.5.2 because of [this bug](https://github.com/NICMx/FORT-validator/issues/58).
- **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.
- **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.
"<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",
- **Type:** Integer
- **Availability:** `argv` and JSON
-- **Default:** 43200 (12 hours)
- **Range:** [0, [`UINT_MAX`](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/limits.h.html)]
>  This argument **is DEPRECATED**.
- **Type:** Enumeration (`strict`, `root`, `root-except-ta`)
- **Availability:** `argv` and JSON
-- **Default:** `root-except-ta`
>  This argument **is DEPRECATED**.
- **Type:** String array
- **Availability:** JSON only
-- **Default:** `[ "--times", "--contimeout=20", "--timeout=15", "--max-size=20MB", "--dirs", "$REMOTE", "$LOCAL" ]`
>  This argument **is DEPRECATED**.
- **Type:** Integer
- **Availability:** `argv` and JSON
-- **Default:** 5
- **Range:** [1, 100]
>  This argument **is DEPRECATED**.
"count": 2,
"interval": 5
},
+ "transfer-timeout": 900,
"program": "rsync",
"arguments-recursive": [
"--recursive",
-.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
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
.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
-# 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
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
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
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
#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)
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);
}
/*
#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 **,
#include "asn1/oid.h"
+#include <errno.h>
+
#include "alloc.h"
-#include "asn1/decode.h"
-#include "common.h"
#include "log.h"
void
-#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 {
#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 }
*/
#define ARCS_EQUAL_OIDS(a, b) arcs_equal_oids(a, b, ARRAY_LEN(b))
-#endif /* SRC_OID_H_ */
+#endif /* SRC_ASN1_OID_H_ */
#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)
}
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;
/*
* 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."
*/
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 */
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)
}
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.");
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;
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;
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;
}
/*
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);
-#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_ */
-#include "crypto/base64.h"
+#include "base64.h"
+#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/err.h>
#include <openssl/evp.h>
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;
}
/*
--- /dev/null
+#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(¬if->map, notif->mtim, &changed, &cache.rrdp.seq,
+ ¬if->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);
+}
--- /dev/null
+#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_ */
+++ /dev/null
-#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, ¬if);
- 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);
-}
+++ /dev/null
-#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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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_ */
+++ /dev/null
-#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);
-}
+++ /dev/null
-#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_ */
#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);
}
#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_ */
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)
{
*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;
}
* 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)
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;
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;
}
#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)
*/
#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 *);
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_ */
#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,
/**
* 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 */
/* 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 {
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);
.doc = "rsync's priority for repository file fetching. Higher value means higher priority.",
.min = 0,
.max = 100,
+ /* XXX deprecated? */
}, {
.id = 3002,
.name = "rsync.strategy",
.id = 3006,
.name = "rsync.arguments-recursive",
.type = >_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 = >_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 = >_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 */
.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",
.max = 100,
},
+ {
+ .id = 13000,
+ .name = "debug.validation-time",
+ .type = >_time,
+ .offset = offsetof(struct rpki_config, debug.validation_time),
+ },
+
{
.id = 13000,
.name = "file-type",
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)
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__
"::"
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 */
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;
return rpki_config.local_repository;
}
+time_t
+cfg_cache_threshold(void)
+{
+ return 86400; // XXX
+}
+
unsigned int
config_get_max_cert_depth(void)
{
}
enum filename_format
-config_get_op_log_filename_format(void)
+config_get_op_log_file_format(void)
{
return rpki_config.log.filename_format;
}
}
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)
{
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
return rpki_config.payload;
}
+time_t
+config_get_validation_time(void)
+{
+ return rpki_config.debug.validation_time;
+}
+
void
config_set_rsync_enabled(bool value)
{
#include <netdb.h>
#include <netinet/in.h>
#include <stdint.h>
+#include <sys/stat.h>
#include "config/file_type.h"
#include "config/filename_format.h"
#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 **);
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);
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);
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);
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);
#include "config/incidences.h"
-#include "incidence/incidence.h"
+#include "incidence.h"
static void
incidences_print(struct option_field const *field, void *_value)
#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,
--- /dev/null
+#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,
+};
--- /dev/null
+#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_ */
#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_ */
#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"
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");
}
#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)
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;
}
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)
{
}
/* 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)
{
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.
*/
{
int error;
- errno = 0;
- if (remove(path) != 0) {
+ if (remove(path) < 0) {
error = errno;
if (error != ENOENT)
return error;
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. */
/* 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);
+}
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);
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:
*
-#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"
}
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;
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
#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;
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);
-#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;
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,
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;
}
/*
- * 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.
* 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.");
--- /dev/null
+#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_ */
+++ /dev/null
-#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_ */
-#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;
"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
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,
};
#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)
#include "alloc.h"
#include "config.h"
-#include "config/types.h"
#include "log.h"
static json_t *
#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)
{
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;
}
if (error)
return error;
- return str2tt(str, result);
+ return str2time(str, result);
}
int
}
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;
}
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));
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 **);
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);
#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 *
asn1time2json(ASN1_TIME const *time)
{
BIO *bio;
- int success;
if (time == NULL)
return json_null();
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;
}
#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 */
#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;
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. */
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"
*/
#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;
}
}
int
-log_setup(bool unit_tests)
+log_setup(void)
{
/*
* Remember not to use any actual logging functions until logging has
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;
}
}
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
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 */
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) \
#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,
#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
* 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);
#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)
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)
/* Initializations */
- error = log_setup(false);
+ error = log_setup();
if (error)
goto just_quit;
error = vrps_init();
if (error)
goto revert_relax_ng;
+ error = cache_setup();
+ if (error)
+ goto revert_vrps;
/* Meat */
/* End */
+ cache_teardown();
+revert_vrps:
vrps_destroy();
revert_relax_ng:
relax_ng_cleanup();
#define SRC_OBJECT_BGPSEC_H_
#include "resource.h"
-#include "rpp.h"
+#include "types/rpp.h"
int handle_bgpsec(X509 *, struct resources *, struct rpp *);
#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;
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)
{
}
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;
}
* @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;
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.");
}
/*
static X509_PUBKEY *
decode_spki(struct tal *tal)
{
- X509_PUBKEY *spki = NULL;
+ X509_PUBKEY *spki;
unsigned char const *origin, *cursor;
size_t len;
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.");
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;
}
/*
#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");
validate_public_key(X509 *cert, enum cert_type type)
{
X509_PUBKEY *pubkey;
+ EVP_PKEY *evppkey;
X509_ALGOR *pa;
int ok;
int error;
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;
*/
/* 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;
* "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;
/* rfc6487#section-4.7 */
/* Fragment of rfc8630#section-2.3 */
- error = validate_public_key(cert, type);
+ error = validate_public_key(cert->x509, cert->type);
if (error)
return error;
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;
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;
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;
}
/*
* 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));
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.
*
* 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;
}
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);
}
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;
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;
/* 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) {
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;
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;
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
: 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
}
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);
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;
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;
* So we will store the URI in @refs, and validate it
* later.
*/
- return ia5s2string(str, &refs->crldp);
+ return ia5s2string(str, &sias->crldp);
}
}
* 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);
* 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)
* 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;
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:
}
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
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.
*
* 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) */
}
* 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.
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");
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;
}
#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 {
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
* 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.
* 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_ */
#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;
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;
}
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 },
};
}
static int
-crl_validate(X509_CRL *crl)
+crl_validate(X509_CRL *crl, X509 *parent)
{
long version;
int error;
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;
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;
}
#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_ */
#include "object/ghostbusters.h"
-#include "asn1/oid.h"
+#include <errno.h>
+
#include "log.h"
#include "object/signed_object.h"
#include "object/vcard.h"
}
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;
}
#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_ */
#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)
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,
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;
}
#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_ */
#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"
if (error)
return error;
- pr_val_debug("ROAIPAddress {");
pr_val_debug("address: %s/%u", v4addr2str(&prefix.addr), prefix.len);
if (roa_addr->maxLength != NULL) {
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 {
}
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
if (error)
return error;
- pr_val_debug("ROAIPAddress {");
pr_val_debug("address: %s/%u", v6addr2str(&prefix.addr), prefix.len);
if (roa_addr->maxLength != NULL) {
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 {
}
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
int a;
int error;
- pr_val_debug("eContent {");
if (roa->version != NULL) {
error = asn_INTEGER2ulong(roa->version, &version);
if (error) {
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;
}
}
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;
}
#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_ */
#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;
int
signed_object_validate(struct signed_object *sobj, struct oid_arcs const *oid,
- struct ee_cert *ee)
+ struct rpki_certificate *ee)
{
int error;
#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"
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_ */
#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 {
/* 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)
{
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] == '#') {
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 */
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;
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;
}
static void
tal_cleanup(struct tal *tal)
{
- cache_destroy(tal->cache);
free(tal->spki);
- maps_cleanup(&tal->maps);
+ strlist_cleanup(&tal->urls);
}
char const *
*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);
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;
}
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,
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 */
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) {
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;
}
-#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_ */
#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;
#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
{
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;
#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;
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;
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 *
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");
-#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"
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;
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) {
#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);
#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 {
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:
}
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);
}
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);
}
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);
}
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.");
}
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);
}
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);
}
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;
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;
}
int
-resources_add_ip(struct resources *resources, struct IPAddressFamily *obj)
+resources_add_ip(struct resources *resources, struct resources *parent,
+ struct IPAddressFamily *obj)
{
int family;
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);
}
}
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.");
}
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) {
}
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;
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;
}
}
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.");
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;
}
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)
{
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 *);
#include "resource/asn.h"
-#include "log.h"
-#include "sorted_array.h"
+#include "types/sorted_array.h"
struct asn_cb {
foreach_asn_cb cb;
#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.
#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 */
#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)
+++ /dev/null
-#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);
-}
+++ /dev/null
-#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_ */
#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 {
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;
};
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. */
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 *
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
}
static void
-update_notification_init(struct update_notification *notif,
- struct cache_mapping *map)
+update_notification_init(struct update_notification *notif, char const *url)
{
memset(¬if->session, 0, sizeof(notif->session));
memset(¬if->snapshot, 0, sizeof(notif->snapshot));
notification_deltas_init(¬if->deltas);
- notif->map = map_refget(map);
+ notif->url = url;
}
static void
{
metadata_cleanup(¬if->snapshot);
notification_deltas_cleanup(¬if->deltas, notification_delta_cleanup);
- map_refput(notif->map);
}
static void
}
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
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
}
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;
}
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;
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;
}
* 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;
}
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)
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;
}
{
int error;
- error = parse_file_metadata(reader, NULL, HR_MANDATORY, ¬if->snapshot);
+ error = parse_file_metadata(reader, ¬if->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;
}
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(¬if->deltas, &delta);
return 0;
+
+fail: serial_cleanup(&delta.serial);
+ metadata_cleanup(&delta.meta);
+ return error;
}
static int
}
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;
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)
}
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;
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(¬if->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;
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)
}
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;
return -ENOENT;
}
- pr_val_debug("Handling RRDP delta serials %s-%s.", serial->str,
- notif->session.serial.str);
+ old = &state->session.serial;
+ new = ¬if->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, ¬if->deltas.array[d]);
+ error = handle_delta(notif, ¬if->deltas.array[d], state);
if (error)
return error;
}
* 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;
}
static void
-drop_notif(struct cachefile_notification *notif)
+drop_notif(struct rrdp_state *state)
{
struct rrdp_hash *hash;
- session_cleanup(¬if->session);
- while (!STAILQ_EMPTY(¬if->delta_hashes)) {
- hash = STAILQ_FIRST(¬if->delta_hashes);
- STAILQ_REMOVE_HEAD(¬if->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);
}
}
* 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 */
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);
} 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;
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"
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(¬if->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, ¬if->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;
}
}
static void
-clear_delta_hashes(struct cachefile_notification *notif)
+clear_delta_hashes(struct rrdp_state *state)
{
struct rrdp_hash *hash;
- while (!STAILQ_EMPTY(¬if->delta_hashes)) {
- hash = STAILQ_FIRST(¬if->delta_hashes);
- STAILQ_REMOVE_HEAD(¬if->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(¬if->delta_hashes);
+ state = pzalloc(sizeof(struct rrdp_state));
+ STAILQ_INIT(&state->delta_hashes);
error = json_get_str(json, TAGNAME_SESSION, &str);
if (error) {
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) {
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(¬if->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;
}
error = json2dh(json_array_get(jdeltas, d), &hash);
if (error)
goto revert_deltas;
- STAILQ_INSERT_TAIL(¬if->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(¬if->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);
}
#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_ */
-#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
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
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;
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;
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;
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 {
error = create_pipes(fork_fds);
if (error)
- goto release_args;
+ return error;
/* Flush output (avoid locks between father and child) */
log_flush();
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 {
error, strerror(error));
if (child_status > 0)
break;
- goto release_args;
+ return error;
}
} while (0);
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++;
break;
} while (true);
- release_args(args, args_len);
-
if (WIFSIGNALED(child_status)) {
switch (WTERMSIG(child_status)) {
case SIGINT:
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;
}
#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_ */
#include <errno.h>
#include "alloc.h"
-#include "data_structure/uthash.h"
#include "log.h"
+#include "types/uthash.h"
struct hashable_roa {
struct vrp data;
#include "rtr/db/delta.h"
#include "types/address.h"
-#include "types/vrp.h"
struct db_table;
#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;
-#ifndef SRC_DELTA_H_
-#define SRC_DELTA_H_
+#ifndef SRC_RTR_DB_DELTA_H_
+#define SRC_RTR_DB_DELTA_H_
#include "types/delta.h"
delta_router_key_foreach_cb, void *);
void deltas_print(struct deltas *);
-#endif /* SRC_DELTA_H_ */
+#endif /* SRC_RTR_DB_DELTA_H_ */
#include "alloc.h"
#include "config.h"
-#include "log.h"
struct deltas_array {
struct deltas **array; /* It's a circular array. */
#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;
int
vrps_init(void)
{
- time_t now;
int error;
state.base = NULL;
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)
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
-#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.
* 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);
void vrps_print_base(void);
-#endif /* SRC_VRPS_H_ */
+#endif /* SRC_RTR_DB_VRPS_H_ */
#include <errno.h>
#include "alloc.h"
-#include "log.h"
#include "rtr/pdu_sender.h"
typedef enum rtr_error_code {
-#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,
+ errmsg_len;
}
-#endif /* RTR_PDU_H_ */
+#endif /* SRC_RTR_PDU_H_ */
#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;
#include <poll.h>
#include "alloc.h"
-#include "common.h"
#include "config.h"
#include "log.h"
#include "rtr/db/vrps.h"
#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);
#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. */
#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. */
-#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>
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_ */
#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;
-#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_ */
#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;
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);
-#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"
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_ */
#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"
#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"
#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;
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.
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;
{
X509_VERIFY_PARAM_free(state->x509_data.params);
X509_STORE_free(state->x509_data.store);
- certstack_destroy(state->certstack);
free(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)
{
#ifndef SRC_STATE_H_
#define SRC_STATE_H_
+#include <openssl/x509.h>
+
#include "object/tal.h"
#include "validation_handler.h"
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 *);
-#include "thread/thread_pool.h"
+#include "thread_pool.h"
#include <sys/queue.h>
#include <pthread.h>
#include "alloc.h"
-#include "config.h"
#include "log.h"
static pthread_key_t state_key;
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.
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));
}
return inet_ntop(af, addr, buffer_cb(state), INET6_ADDRSTRLEN);
}
-/**
+/*
* Returns @addr, converted to a printable string. Intended for minimal clutter
* address printing.
*
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)
{
#define SRC_THREAD_VAR_H_
#include "state.h"
+#include "types/map.h"
int thvar_init(void); /* This function does not need cleanup. */
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);
#include <stdbool.h>
#include <sys/socket.h>
-#include "asn1/asn1c/IPAddress.h"
#include "asn1/asn1c/IPAddressRange.h"
struct ipv4_prefix {
-#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>
#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_ */
-#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 { \
(index)++ \
)
-#endif /* SRC_DATA_STRUCTURE_ARRAY_LIST_H_ */
+#endif /* SRC_TYPES_ARRAYLIST_H_ */
-#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>
uint32_t max;
};
-#endif /* SRC_AS_NUMBER_H_ */
+#endif /* SRC_TYPES_ASN_H_ */
-#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>
BIO *BIO_new_seq(BIO *, BIO *);
-#endif /* TEST_TYPES_BIO_SEQ_H_ */
+#endif /* SRC_TYPES_BIO_SEQ_H_ */
#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, ¬if->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:
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);
}
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);
}
#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_ */
-#include "object/name.h"
+#include "types/name.h"
#include <openssl/asn1.h>
#include <openssl/obj_mac.h>
#include <syslog.h>
#include "alloc.h"
-#include "cert_stack.h"
#include "log.h"
#include "thread_var.h"
}
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;
* 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)
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 : "",
-#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>
/* 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_ */
-#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)
{
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;
+}
-#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;
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_ */
--- /dev/null
+#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;
+ }
+}
--- /dev/null
+#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_ */
-#include "sorted_array.h"
+#include "types/sorted_array.h"
#include "alloc.h"
#include "log.h"
-#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>
char const *sarray_err2str(int);
-#endif /* SRC_SORTED_ARRAY_H_ */
+#endif /* SRC_TYPES_SORTED_ARRAY_H_ */
-#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.
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);
+}
-#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.
*
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_ */
--- /dev/null
+#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;
+}
--- /dev/null
+#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_ */
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
(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)
unsigned hashv; /* result of hash-fcn(key) */
} UT_hash_handle;
-#endif /* UTHASH_H */
+#endif /* SRC_TYPES_UTHASH_H */
#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
#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.
# <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
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}
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}
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
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
#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,
+++ /dev/null
-/* 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;
-}
--- /dev/null
+/* 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;
+}
--- /dev/null
+#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;
+}
--- /dev/null
+#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_ */
-#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;
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;
}
SRunner *runner;
int tests_failed;
- suite = json_load_suite();
+ suite = common_load_suite();
runner = srunner_create(suite);
srunner_run_all(runner, CK_NORMAL);
#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)
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);
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));
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();
}
#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;
}
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);
+}
--- /dev/null
+#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;
+}
--- /dev/null
+#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;
+}
#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 */
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);
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);
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
--- /dev/null
+<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
--- /dev/null
+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
#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;
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(¬if->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(¬if->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)
ck_assert_uint_eq(0xa6, xmlstr[2]);
ck_assert_uint_eq('\0', xmlstr[3]);
xmlFree(xmlstr);
-
}
END_TEST
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]);
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
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);
}
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(¬if->session, 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;
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(¬if->delta_hashes);
+ hash = STAILQ_FIRST(&state->delta_hashes);
va_start(args, __serial);
while ((hash_byte = va_arg(args, int)) >= 0) {
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, ¬if));
+ ck_assert_int_eq(0, parse_notification("https://host/notification.xml",
+ "resources/rrdp/notif-ok.xml", ¬if));
- 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);
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, ¬if));
+ ck_assert_int_eq(0, parse_notification("https://host/notification.xml",
+ "resources/rrdp/notif-0deltas.xml", ¬if));
- 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);
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, ¬if));
+ ck_assert_int_eq(0, parse_notification("https://host/notification.xml",
+ "resources/rrdp/notif-large-serial.xml", ¬if));
- 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);
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, ¬if));
+ ck_assert_int_eq(-EINVAL,
+ parse_notification("https://host/notification.xml", file, ¬if));
relax_ng_cleanup();
}
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
* 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
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(¬if_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 = ¬if_map;
+ ck_assert_int_eq(-EINVAL, parse_snapshot(&session,
+ "resources/rrdp/snapshot-bad-publish.xml", &rpp));
- ck_assert_int_eq(-EINVAL, parse_snapshot(¬if));
-
- BN_free(notif.session.serial.num);
+ BN_free(session.serial.num);
relax_ng_cleanup();
}
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);
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, ¬if));
- ck_rrdp_session("session", "1234", ¬if->session);
- ck_assert_uint_eq(true, STAILQ_EMPTY(¬if->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
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);
ck_assert_str_eq("cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
json_string_value(json_array_get(jdeltas, 2)));
- ck_assert_int_eq(0, rrdp_json2notif(json, ¬if));
- ck_rrdp_session("session", "123456789012345678901234567890123456789012", ¬if->session);
- hash = STAILQ_FIRST(¬if->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);
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, ¬if));
+ 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(¬if));
- notif.session.session_id = "sid";
- ck_assert_ptr_eq(NULL, rrdp_notif2json(¬if));
-
- 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\" ] }");
--- /dev/null
+#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(¬if, 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(¬if, 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;
+}
--- /dev/null
+#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_ */
--- /dev/null
+#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;
+}
#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)
#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 -- */
#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 */
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);
#include "alloc.c"
#include "common.c"
#include "mock.c"
-#include "thread/thread_pool.c"
+#include "thread_pool.c"
static void
thread_work(void *arg)
+++ /dev/null
-#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(¬if, 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(¬if, 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;
-}
#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); \
--- /dev/null
+#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;
+}
#include <stdio.h>
#include <unistd.h>
-#include "data_structure/uthash.h"
+#include "types/uthash.h"
struct uthash_node {
int key;
#include <libxml/xmlreader.h>
#include "mock.c"
-#include "xml/relax_ng.c"
+#include "relax_ng.c"
struct reader_ctx {
unsigned int delta_count;
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;