aboutsummaryrefslogtreecommitdiff
path: root/rcynic/rcynic.c
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2011-09-15 00:04:23 +0000
committerRob Austein <sra@hactrn.net>2011-09-15 00:04:23 +0000
commit0c4a98c56785c2f4a6e4fba3941083fc466da893 (patch)
tree4a188735b9d63422cf8573f1efbc5ec5b8e15162 /rcynic/rcynic.c
parentbb6769c8315a193d7940837606951f1c2942e095 (diff)
Merge rcynic-ng/ back into rcynic/. Used svn merge --ignore-ancestry
to minimize problems for people mirroring the repository. svn path=/configure; revision=3985
Diffstat (limited to 'rcynic/rcynic.c')
-rw-r--r--rcynic/rcynic.c3852
1 files changed, 2382 insertions, 1470 deletions
diff --git a/rcynic/rcynic.c b/rcynic/rcynic.c
index 6dfaee83..09fe1360 100644
--- a/rcynic/rcynic.c
+++ b/rcynic/rcynic.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 Internet Systems Consortium ("ISC")
+ * Copyright (C) 2009--2011 Internet Systems Consortium ("ISC")
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -60,6 +60,8 @@
#include <fcntl.h>
#include <signal.h>
#include <utime.h>
+#include <glob.h>
+#include <sys/param.h>
#define SYSLOG_NAMES /* defines CODE prioritynames[], facilitynames[] */
#include <syslog.h>
@@ -75,7 +77,14 @@
#include <openssl/asn1t.h>
#include <openssl/cms.h>
-#ifndef FILENAME_MAX
+#include "bio_f_linebreak.h"
+
+#include "defstack.h"
+#include "defasn1.h"
+
+#if !defined(FILENAME_MAX) && defined(PATH_MAX) && PATH_MAX > 1024
+#define FILENAME_MAX PATH_MAX
+#elif !defined(FILENAME_MAX)
#define FILENAME_MAX 1024
#endif
@@ -93,8 +102,8 @@
*/
#define KILL_MAX 10
-#ifndef HOST_NAME_MAX
-#define HOST_NAME_MAX 256
+#ifndef HOSTNAME_MAX
+#define HOSTNAME_MAX 256
#endif
/**
@@ -182,87 +191,79 @@ static const struct {
QV(X509_V_ERR_UNNESTED_RESOURCE)
/**
- * MIB counters specific to rcynic. "validation_ok" is not used as a
- * counter, but is used as a validation status code.
+ * MIB counters specific to rcynic.
*/
#define MIB_COUNTERS \
- QG(validation_ok, "OK") \
- QG(backup_cert_accepted, "Backup certificates accepted") \
- QB(backup_cert_rejected, "Backup certificates rejected") \
- QG(backup_crl_accepted, "Backup CRLs accepted") \
- QB(backup_crl_rejected, "Backup CRLs rejected") \
- QG(current_cert_accepted, "Current certificates accepted") \
- QB(current_cert_rejected, "Current certificates rejected") \
- QG(current_crl_accepted, "Current CRLs accepted") \
- QB(current_crl_rejected, "Current CRLs rejected") \
- QG(current_manifest_accepted, "Current Manifests accepted") \
- QB(current_manifest_rejected, "Current Manifests rejected") \
- QG(backup_manifest_accepted, "Backup Manifests accepted") \
- QB(backup_manifest_rejected, "Backup Manifests rejected") \
- QB(rsync_failed, "rsync transfers failed") \
- QG(rsync_succeeded, "rsync transfers succeeded") \
- QB(rsync_timed_out, "rsync transfers timed out") \
- QW(stale_crl, "Stale CRLs") \
- QB(malformed_sia, "Malformed SIA extensions") \
- QB(sia_missing, "SIA extensions missing") \
- QB(aia_missing, "AIA extensions missing") \
- QB(crldp_missing, "CRLDP extensions missing") \
- QB(aia_mismatch, "Mismatched AIA extensions") \
- QB(unknown_verify_error, "Unknown OpenSSL verify error") \
- QG(current_cert_recheck, "Certificates rechecked") \
- QB(manifest_invalid_ee, "Invalid manifest certificates") \
- QB(manifest_invalid_cms, "Manifest validation failures") \
- QB(manifest_decode_error, "Manifest decode errors") \
- QW(stale_manifest, "Stale manifests") \
- QB(manifest_not_yet_valid, "Manifests not yet valid") \
- QB(manifest_bad_econtenttype, "Bad manifest eContentType") \
- QB(manifest_missing_signer, "Missing manifest signers") \
- QB(manifest_missing_crldp, "Missing manifest CRLDP") \
- QB(manifest_malformed_crldp, "Malformed manifest CRLDP") \
- QB(certificate_digest_mismatch, "Certificate digest mismatches") \
- QB(crl_digest_mismatch, "CRL digest mismatches") \
+ MIB_COUNTERS_FROM_OPENSSL \
+ QB(aia_mismatch, "Mismatched AIA extension") \
+ QB(aia_missing, "AIA extension missing") \
+ QB(certificate_bad_crl, "Bad certificate CRL") \
+ QB(certificate_bad_signature, "Bad certificate signature") \
+ QB(certificate_digest_mismatch, "Certificate digest mismatch") \
+ QB(certificate_failed_validation, "Certificate failed validation") \
+ QB(crl_digest_mismatch, "CRL digest mismatch") \
QB(crl_not_in_manifest, "CRL not listed in manifest") \
- QB(roa_invalid_ee, "Invalid ROA certificates") \
- QB(roa_invalid_cms, "ROA validation failures") \
- QB(roa_decode_error, "ROA decode errors") \
- QB(roa_bad_econtenttype, "Bad ROA eContentType") \
- QB(roa_missing_signer, "Missing ROA signers") \
- QB(roa_digest_mismatch, "ROA digest mismatches") \
- QG(current_roa_accepted, "Current ROAs accepted") \
- QB(current_roa_rejected, "Current ROAs rejected") \
- QG(backup_roa_accepted, "Backup ROAs accepted") \
- QB(backup_roa_rejected, "Backup ROAs rejected") \
- QB(malformed_roa_addressfamily, "Malformed ROA addressFamilys") \
- QB(manifest_wrong_version, "Wrong manifest versions") \
- QB(roa_wrong_version, "Wrong ROA versions") \
- QW(trust_anchor_not_self_signed, "Trust anchor not self-signed") \
- QB(uri_too_long, "URI too long") \
+ QB(crl_not_yet_valid, "CRL not yet valid") \
+ QB(crldp_mismatch, "CRLDP doesn't match issuer's SIA") \
+ QB(crldp_missing, "CRLDP extension missing") \
+ QB(disallowed_extension, "Disallowed X.509v3 extension") \
+ QB(ghostbuster_bad_crl, "Ghostbuster EE has bad CRL") \
+ QB(ghostbuster_bad_econtenttype, "Bad Ghostbuster eContentType") \
+ QB(ghostbuster_digest_mismatch, "Ghostbuster digest mismatch") \
+ QB(ghostbuster_invalid_cms, "Ghostbuster validation failure") \
+ QB(ghostbuster_invalid_ee, "Invalid Ghostbuster certificate") \
+ QB(ghostbuster_missing_signer, "Missing Ghostbuster signer") \
+ QB(hash_too_long, "Hash value is too long") \
QB(malformed_crldp, "Malformed CRDLP extension") \
- QB(certificate_bad_signature, "Bad certificate signature") \
- QB(certificate_bad_crl, "Bad certificate CRL") \
- QB(manifest_bad_crl, "Manifest has bad CRL") \
- QB(roa_resources_malformed, "ROA resources malformed") \
+ QB(malformed_roa_addressfamily, "Malformed ROA addressFamily") \
+ QB(malformed_sia, "Malformed SIA extension") \
+ QB(manifest_bad_econtenttype, "Bad manifest eContentType") \
+ QB(manifest_decode_error, "Manifest decode error") \
+ QB(manifest_invalid_cms, "Manifest validation failure") \
+ QB(manifest_invalid_ee, "Invalid manifest certificate") \
+ QB(manifest_malformed_crldp, "Malformed manifest CRLDP") \
+ QB(manifest_mismatch, "Manifest doesn't match SIA") \
+ QB(manifest_missing, "Manifest pointer missing") \
+ QB(manifest_missing_crldp, "Missing manifest CRLDP") \
+ QB(manifest_missing_signer, "Missing manifest signer") \
+ QB(manifest_not_yet_valid, "Manifest not yet valid") \
+ QB(manifest_wrong_version, "Wrong manifest version") \
+ QB(object_rejected, "Object rejected") \
QB(roa_bad_afi, "ROA contains bad AFI value") \
- QB(roa_not_nested, "ROA resources not in EE") \
QB(roa_bad_crl, "ROA EE has bad CRL") \
- QB(ghostbuster_digest_mismatch, "Ghostbuster digest mismatches") \
- QB(ghostbuster_bad_econtenttype, "Bad Ghostbuster eContentType") \
- QB(ghostbuster_invalid_cms, "Ghostbuster validation failures") \
- QB(ghostbuster_missing_signer, "Missing Ghostbuster signers") \
- QB(ghostbuster_bad_crl, "Ghostbuster EE has bad CRL") \
- QB(ghostbuster_invalid_ee, "Invalid Ghostbuster certificates") \
- QG(current_ghostbuster_accepted, "Current Ghostbusters accepted") \
- QB(current_ghostbuster_rejected, "Current Ghostbusters rejected") \
- QG(backup_ghostbuster_accepted, "Backup Ghostbusters accepted") \
- QB(backup_ghostbuster_rejected, "Backup Ghostbusters rejected") \
- QB(disallowed_extension, "Disallowed X.509v3 extension") \
- QB(crldp_mismatch, "CRLDP doesn't match issuer's SIA") \
- QB(manifest_missing, "Manifest pointer missing") \
- QB(manifest_mismatch, "Manifest doesn't match SIA") \
+ QB(roa_bad_econtenttype, "Bad ROA eContentType") \
+ QB(roa_decode_error, "ROA decode error") \
+ QB(roa_digest_mismatch, "ROA digest mismatch") \
+ QB(roa_invalid_cms, "ROA validation failure") \
+ QB(roa_invalid_ee, "Invalid ROA certificate") \
+ QB(roa_missing_signer, "Missing ROA signer") \
+ QB(roa_not_nested, "ROA resource not in EE") \
+ QB(roa_resources_malformed, "ROA resources malformed") \
+ QB(roa_wrong_version, "Wrong ROA version") \
+ QB(rsync_failed, "rsync transfer failed") \
+ QB(rsync_timed_out, "rsync transfer timed out") \
+ QB(sia_missing, "SIA extension missing") \
+ QB(trust_anchor_key_mismatch, "Trust anchor key mismatch") \
QB(trust_anchor_with_crldp, "Trust anchor can't have CRLDP") \
- QW(object_not_in_manifest, "Object not in manifest") \
- MIB_COUNTERS_FROM_OPENSSL
+ QB(unknown_verify_error, "Unknown OpenSSL verify error") \
+ QB(unreadable_trust_anchor, "Unreadable trust anchor") \
+ QB(unreadable_trust_anchor_locator, "Unreadable trust anchor locator") \
+ QB(uri_too_long, "URI too long") \
+ QW(nonconformant_issuer_name, "Nonconformant X.509 issuer name") \
+ QW(nonconformant_subject_name, "Nonconformant X.509 subject name") \
+ QW(rsync_skipped, "rsync transfer skipped") \
+ QW(stale_crl, "Stale CRL") \
+ QW(stale_manifest, "Stale manifest") \
+ QW(tainted_by_stale_crl, "Tainted by stale CRL") \
+ QW(tainted_by_stale_manifest, "Tainted by stale manifest") \
+ QW(tainted_by_not_being_in_manifest, "Tainted by not being in manifest") \
+ QW(trust_anchor_not_self_signed, "Trust anchor not self-signed") \
+ QW(unknown_object_type_skipped, "Unknown object type skipped") \
+ QG(current_cert_recheck, "Certificate rechecked") \
+ QG(object_accepted, "Object accepted") \
+ QG(rsync_succeeded, "rsync transfer succeeded") \
+ QG(validation_ok, "OK")
#define QV(x) QB(mib_openssl_##x, 0)
@@ -304,50 +305,148 @@ static const long mib_counter_openssl[] = { MIB_COUNTERS 0 };
#undef QQ
/**
- * Per-host MIB counter object.
- * hostname[] must be first element.
+ * Object sources. We always try to get fresh copies of objects using
+ * rsync, but if that fails we try using backup copies from what
+ * worked the last time we were run. This means that a URI
+ * potentially represents two different objects, so we need to
+ * distinguish them for tracking purposes in our validation log.
*/
-typedef struct host_mib_counter {
- char hostname[URI_MAX];
- unsigned long counters[MIB_COUNTER_T_MAX];
-} HOST_MIB_COUNTER;
-DECLARE_STACK_OF(HOST_MIB_COUNTER)
+#define OBJECT_GENERATIONS \
+ QQ(null) \
+ QQ(current) \
+ QQ(backup)
+
+#define QQ(x) object_generation_##x ,
+typedef enum object_generation { OBJECT_GENERATIONS OBJECT_GENERATION_MAX } object_generation_t;
+#undef QQ
+
+#define QQ(x) #x ,
+static const char * const object_generation_label[] = { OBJECT_GENERATIONS NULL };
+#undef QQ
+
+/**
+ * Type-safe string wrapper for URIs.
+ */
+typedef struct { char s[URI_MAX]; } uri_t;
+
+/**
+ * Type-safe string wrapper for filename paths.
+ */
+typedef struct { char s[FILENAME_MAX]; } path_t;
+
+/**
+ * Type-safe string wrapper for hostnames.
+ */
+typedef struct { char s[HOSTNAME_MAX]; } hostname_t;
+
+/**
+ * Type-safe wrapper for hash buffers.
+ */
+typedef struct { unsigned char h[EVP_MAX_MD_SIZE]; } hashbuf_t;
/**
* Per-URI validation status object.
+ * uri must be first element.
*/
typedef struct validation_status {
- char uri[URI_MAX];
+ uri_t uri;
+ object_generation_t generation;
time_t timestamp;
- mib_counter_t code;
-} VALIDATION_STATUS;
+ unsigned char events[(MIB_COUNTER_T_MAX + 7) / 8];
+} validation_status_t;
-DECLARE_STACK_OF(VALIDATION_STATUS)
+DECLARE_STACK_OF(validation_status_t)
/**
* Structure to hold data parsed out of a certificate.
*/
typedef struct certinfo {
int ca, ta;
- char uri[URI_MAX], sia[URI_MAX], aia[URI_MAX], crldp[URI_MAX], manifest[URI_MAX];
+ object_generation_t generation;
+ uri_t uri, sia, aia, crldp, manifest;
} certinfo_t;
+typedef struct rcynic_ctx rcynic_ctx_t;
+
/**
- * Program context that would otherwise be a mess of global variables.
+ * States that a walk_ctx_t can be in.
*/
-typedef struct rcynic_ctx {
- char *authenticated, *old_authenticated, *unauthenticated;
- char *jane, *rsync_program;
- STACK_OF(OPENSSL_STRING) *rsync_cache, *backup_cache, *stale_cache;
- STACK_OF(HOST_MIB_COUNTER) *host_counters;
- STACK_OF(VALIDATION_STATUS) *validation_status;
- int indent, use_syslog, allow_stale_crl, allow_stale_manifest, use_links;
- int require_crl_in_manifest, rsync_timeout, priority[LOG_LEVEL_T_MAX];
- int allow_non_self_signed_trust_anchor, allow_object_not_in_manifest;
- log_level_t log_level;
- X509_STORE *x509_store;
-} rcynic_ctx_t;
+typedef enum {
+ walk_state_initial, /**< Initial state */
+ walk_state_rsync, /**< rsyncing certinfo.sia */
+ walk_state_ready, /**< Ready to traverse outputs */
+ walk_state_current, /**< prefix = rc->unauthenticated */
+ walk_state_backup, /**< prefix = rc->old_authenticated */
+ walk_state_done /**< Done walking this cert's outputs */
+} walk_state_t;
+
+/**
+ * Context for certificate tree walks. This includes all the stuff
+ * that we would keep as automatic variables on the call stack if we
+ * didn't have to use callbacks to support multiple rsync processes.
+ */
+typedef struct walk_ctx {
+ unsigned refcount;
+ certinfo_t certinfo;
+ X509 *cert;
+ Manifest *manifest;
+ STACK_OF(OPENSSL_STRING) *filenames;
+ int manifest_iteration, filename_iteration, stale_manifest;
+ walk_state_t state;
+} walk_ctx_t;
+
+DECLARE_STACK_OF(walk_ctx_t)
+
+/**
+ * Return codes from rsync functions.
+ */
+typedef enum {
+ rsync_status_done, /* Request completed */
+ rsync_status_failed, /* Request failed */
+ rsync_status_timed_out, /* Request timed out */
+ rsync_status_pending, /* Request in progress */
+ rsync_status_skipped /* Request not attempted */
+} rsync_status_t;
+
+/**
+ * Context for asyncronous rsync.
+ */
+typedef struct rsync_ctx {
+ uri_t uri;
+ void (*handler)(const rcynic_ctx_t *, const struct rsync_ctx *, const rsync_status_t, const uri_t *, STACK_OF(walk_ctx_t) *);
+ STACK_OF(walk_ctx_t) *wsk;
+ enum {
+ rsync_state_initial, /* Must be first */
+ rsync_state_running,
+ rsync_state_conflict_wait,
+ rsync_state_retry_wait,
+ rsync_state_terminating
+ } state;
+ enum {
+ rsync_problem_none, /* Must be first */
+ rsync_problem_timed_out,
+ rsync_problem_refused
+ } problem;
+ unsigned tries;
+ pid_t pid;
+ int fd;
+ time_t started, deadline;
+ char buffer[URI_MAX * 4];
+ size_t buflen;
+} rsync_ctx_t;
+
+DECLARE_STACK_OF(rsync_ctx_t)
+
+/**
+ * Deferred task.
+ */
+typedef struct task {
+ void (*handler)(rcynic_ctx_t *, STACK_OF(walk_ctx_t) *);
+ STACK_OF(walk_ctx_t) *wsk;
+} task_t;
+
+DECLARE_STACK_OF(task_t)
/**
* Extended context for verify callbacks. This is a wrapper around
@@ -364,6 +463,25 @@ typedef struct rcynic_x509_store_ctx {
} rcynic_x509_store_ctx_t;
/**
+ * Program context that would otherwise be a mess of global variables.
+ */
+struct rcynic_ctx {
+ path_t authenticated, old_authenticated, new_authenticated, unauthenticated;
+ char *jane, *rsync_program;
+ STACK_OF(OPENSSL_STRING) *rsync_cache, *backup_cache, *dead_host_cache;
+ STACK_OF(validation_status_t) *validation_status;
+ STACK_OF(rsync_ctx_t) *rsync_queue;
+ STACK_OF(task_t) *task_queue;
+ int use_syslog, allow_stale_crl, allow_stale_manifest, use_links;
+ int require_crl_in_manifest, rsync_timeout, priority[LOG_LEVEL_T_MAX];
+ int allow_non_self_signed_trust_anchor, allow_object_not_in_manifest;
+ int max_parallel_fetches, max_retries, retry_wait_min, run_rsync;
+ log_level_t log_level;
+ X509_STORE *x509_store;
+};
+
+
+/**
* Subversion ID data.
*/
static const char svn_id[] = "$Id$";
@@ -406,6 +524,14 @@ static const unsigned char id_sha256[] =
*/
static const char rpki_policy_oid[] = "1.3.6.1.5.5.7.14.2";
+/**
+ * Suffix we use temporarily during the symlink shuffle. Could be
+ * almost anything, but we want to do the length check early, before
+ * we waste a lot of work we'll just have to throw away, so we just
+ * wire in something short and obvious.
+ */
+static const char authenticated_symlink_suffix[] = ".new";
+
/**
@@ -429,22 +555,11 @@ static void sk_OPENSSL_STRING_remove(STACK_OF(OPENSSL_STRING) *sk, const char *s
}
/**
- * Allocate a new HOST_MIB_COUNTER object.
+ * Allocate a new validation_status_t object.
*/
-static HOST_MIB_COUNTER *HOST_MIB_COUNTER_new(void)
+static validation_status_t *validation_status_t_new(void)
{
- HOST_MIB_COUNTER *h = malloc(sizeof(*h));
- if (h)
- memset(h, 0, sizeof(*h));
- return h;
-}
-
-/**
- * Allocate a new VALIDATION_STATUS object.
- */
-static VALIDATION_STATUS *VALIDATION_STATUS_new(void)
-{
- VALIDATION_STATUS *v = malloc(sizeof(*v));
+ validation_status_t *v = malloc(sizeof(*v));
if (v)
memset(v, 0, sizeof(*v));
return v;
@@ -453,235 +568,25 @@ static VALIDATION_STATUS *VALIDATION_STATUS_new(void)
/**
* Type-safe wrapper around free() to keep safestack macros happy.
*/
-static void HOST_MIB_COUNTER_free(HOST_MIB_COUNTER *h)
-{
- if (h)
- free(h);
-}
-
-/**
- * Type-safe wrapper around free() to keep safestack macros happy.
- */
-static void VALIDATION_STATUS_free(VALIDATION_STATUS *v)
+static void validation_status_t_free(validation_status_t *v)
{
if (v)
free(v);
}
-/*
- * Safestack macros for HOST_MIB_COUNTER.
- */
-
-#define sk_HOST_MIB_COUNTER_new(st) SKM_sk_new(HOST_MIB_COUNTER, (st))
-#define sk_HOST_MIB_COUNTER_new_null() SKM_sk_new_null(HOST_MIB_COUNTER)
-#define sk_HOST_MIB_COUNTER_free(st) SKM_sk_free(HOST_MIB_COUNTER, (st))
-#define sk_HOST_MIB_COUNTER_num(st) SKM_sk_num(HOST_MIB_COUNTER, (st))
-#define sk_HOST_MIB_COUNTER_value(st, i) SKM_sk_value(HOST_MIB_COUNTER, (st), (i))
-#define sk_HOST_MIB_COUNTER_set(st, i, val) SKM_sk_set(HOST_MIB_COUNTER, (st), (i), (val))
-#define sk_HOST_MIB_COUNTER_zero(st) SKM_sk_zero(HOST_MIB_COUNTER, (st))
-#define sk_HOST_MIB_COUNTER_push(st, val) SKM_sk_push(HOST_MIB_COUNTER, (st), (val))
-#define sk_HOST_MIB_COUNTER_unshift(st, val) SKM_sk_unshift(HOST_MIB_COUNTER, (st), (val))
-#define sk_HOST_MIB_COUNTER_find(st, val) SKM_sk_find(HOST_MIB_COUNTER, (st), (val))
-#define sk_HOST_MIB_COUNTER_find_ex(st, val) SKM_sk_find_ex(HOST_MIB_COUNTER, (st), (val))
-#define sk_HOST_MIB_COUNTER_delete(st, i) SKM_sk_delete(HOST_MIB_COUNTER, (st), (i))
-#define sk_HOST_MIB_COUNTER_delete_ptr(st, ptr) SKM_sk_delete_ptr(HOST_MIB_COUNTER, (st), (ptr))
-#define sk_HOST_MIB_COUNTER_insert(st, val, i) SKM_sk_insert(HOST_MIB_COUNTER, (st), (val), (i))
-#define sk_HOST_MIB_COUNTER_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(HOST_MIB_COUNTER, (st), (cmp))
-#define sk_HOST_MIB_COUNTER_dup(st) SKM_sk_dup(HOST_MIB_COUNTER, st)
-#define sk_HOST_MIB_COUNTER_pop_free(st, free_func) SKM_sk_pop_free(HOST_MIB_COUNTER, (st), (free_func))
-#define sk_HOST_MIB_COUNTER_shift(st) SKM_sk_shift(HOST_MIB_COUNTER, (st))
-#define sk_HOST_MIB_COUNTER_pop(st) SKM_sk_pop(HOST_MIB_COUNTER, (st))
-#define sk_HOST_MIB_COUNTER_sort(st) SKM_sk_sort(HOST_MIB_COUNTER, (st))
-#define sk_HOST_MIB_COUNTER_is_sorted(st) SKM_sk_is_sorted(HOST_MIB_COUNTER, (st))
-
-/*
- * Safestack macros for VALIDATION_STATUS.
- */
-
-#define sk_VALIDATION_STATUS_new(st) SKM_sk_new(VALIDATION_STATUS, (st))
-#define sk_VALIDATION_STATUS_new_null() SKM_sk_new_null(VALIDATION_STATUS)
-#define sk_VALIDATION_STATUS_free(st) SKM_sk_free(VALIDATION_STATUS, (st))
-#define sk_VALIDATION_STATUS_num(st) SKM_sk_num(VALIDATION_STATUS, (st))
-#define sk_VALIDATION_STATUS_value(st, i) SKM_sk_value(VALIDATION_STATUS, (st), (i))
-#define sk_VALIDATION_STATUS_set(st, i, val) SKM_sk_set(VALIDATION_STATUS, (st), (i), (val))
-#define sk_VALIDATION_STATUS_zero(st) SKM_sk_zero(VALIDATION_STATUS, (st))
-#define sk_VALIDATION_STATUS_push(st, val) SKM_sk_push(VALIDATION_STATUS, (st), (val))
-#define sk_VALIDATION_STATUS_unshift(st, val) SKM_sk_unshift(VALIDATION_STATUS, (st), (val))
-#define sk_VALIDATION_STATUS_find(st, val) SKM_sk_find(VALIDATION_STATUS, (st), (val))
-#define sk_VALIDATION_STATUS_find_ex(st, val) SKM_sk_find_ex(VALIDATION_STATUS, (st), (val))
-#define sk_VALIDATION_STATUS_delete(st, i) SKM_sk_delete(VALIDATION_STATUS, (st), (i))
-#define sk_VALIDATION_STATUS_delete_ptr(st, ptr) SKM_sk_delete_ptr(VALIDATION_STATUS, (st), (ptr))
-#define sk_VALIDATION_STATUS_insert(st, val, i) SKM_sk_insert(VALIDATION_STATUS, (st), (val), (i))
-#define sk_VALIDATION_STATUS_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(VALIDATION_STATUS, (st), (cmp))
-#define sk_VALIDATION_STATUS_dup(st) SKM_sk_dup(VALIDATION_STATUS, st)
-#define sk_VALIDATION_STATUS_pop_free(st, free_func) SKM_sk_pop_free(VALIDATION_STATUS, (st), (free_func))
-#define sk_VALIDATION_STATUS_shift(st) SKM_sk_shift(VALIDATION_STATUS, (st))
-#define sk_VALIDATION_STATUS_pop(st) SKM_sk_pop(VALIDATION_STATUS, (st))
-#define sk_VALIDATION_STATUS_sort(st) SKM_sk_sort(VALIDATION_STATUS, (st))
-#define sk_VALIDATION_STATUS_is_sorted(st) SKM_sk_is_sorted(VALIDATION_STATUS, (st))
-
/*
- * ASN.1 templates. Not sure that ASN1_EXP_OPT() is the right macro
- * for these defaulted "version" fields, but it's what the examples
- * for this construction use. Probably doesn't matter since this
- * program only decodes manifests, never encodes them.
- *
- * Putting this section under conditional compilation is a hack to
- * keep Doxygen's parser from becoming hopelessly confused by the
- * weird OpenSSL ASN.1 macros. Someday perhaps I'll have time to
- * track down the problem in Doxygen's parser, but this works for now.
- */
-
-#ifndef DOXYGEN_GETS_HOPELESSLY_CONFUSED_BY_THIS_SECTION
-
-typedef struct FileAndHash_st {
- ASN1_IA5STRING *file;
- ASN1_BIT_STRING *hash;
-} FileAndHash;
-
-DECLARE_STACK_OF(FileAndHash)
-
-ASN1_SEQUENCE(FileAndHash) = {
- ASN1_SIMPLE(FileAndHash, file, ASN1_IA5STRING),
- ASN1_SIMPLE(FileAndHash, hash, ASN1_BIT_STRING)
-} ASN1_SEQUENCE_END(FileAndHash)
-
-typedef struct Manifest_st {
- ASN1_INTEGER *version, *manifestNumber;
- ASN1_GENERALIZEDTIME *thisUpdate, *nextUpdate;
- ASN1_OBJECT *fileHashAlg;
- STACK_OF(FileAndHash) *fileList;
-} Manifest;
-
-ASN1_SEQUENCE(Manifest) = {
- ASN1_EXP_OPT(Manifest, version, ASN1_INTEGER, 0),
- ASN1_SIMPLE(Manifest, manifestNumber, ASN1_INTEGER),
- ASN1_SIMPLE(Manifest, thisUpdate, ASN1_GENERALIZEDTIME),
- ASN1_SIMPLE(Manifest, nextUpdate, ASN1_GENERALIZEDTIME),
- ASN1_SIMPLE(Manifest, fileHashAlg, ASN1_OBJECT),
- ASN1_SEQUENCE_OF(Manifest, fileList, FileAndHash)
-} ASN1_SEQUENCE_END(Manifest)
-
-DECLARE_ASN1_FUNCTIONS(FileAndHash)
-DECLARE_ASN1_FUNCTIONS(Manifest)
-
-IMPLEMENT_ASN1_FUNCTIONS(FileAndHash)
-IMPLEMENT_ASN1_FUNCTIONS(Manifest)
-
-#define sk_FileAndHash_new(st) SKM_sk_new(FileAndHash, (st))
-#define sk_FileAndHash_new_null() SKM_sk_new_null(FileAndHash)
-#define sk_FileAndHash_free(st) SKM_sk_free(FileAndHash, (st))
-#define sk_FileAndHash_num(st) SKM_sk_num(FileAndHash, (st))
-#define sk_FileAndHash_value(st, i) SKM_sk_value(FileAndHash, (st), (i))
-#define sk_FileAndHash_set(st, i, val) SKM_sk_set(FileAndHash, (st), (i), (val))
-#define sk_FileAndHash_zero(st) SKM_sk_zero(FileAndHash, (st))
-#define sk_FileAndHash_push(st, val) SKM_sk_push(FileAndHash, (st), (val))
-#define sk_FileAndHash_unshift(st, val) SKM_sk_unshift(FileAndHash, (st), (val))
-#define sk_FileAndHash_find(st, val) SKM_sk_find(FileAndHash, (st), (val))
-#define sk_FileAndHash_find_ex(st, val) SKM_sk_find_ex(FileAndHash, (st), (val))
-#define sk_FileAndHash_delete(st, i) SKM_sk_delete(FileAndHash, (st), (i))
-#define sk_FileAndHash_delete_ptr(st, ptr) SKM_sk_delete_ptr(FileAndHash, (st), (ptr))
-#define sk_FileAndHash_insert(st, val, i) SKM_sk_insert(FileAndHash, (st), (val), (i))
-#define sk_FileAndHash_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(FileAndHash, (st), (cmp))
-#define sk_FileAndHash_dup(st) SKM_sk_dup(FileAndHash, st)
-#define sk_FileAndHash_pop_free(st, free_func) SKM_sk_pop_free(FileAndHash, (st), (free_func))
-#define sk_FileAndHash_shift(st) SKM_sk_shift(FileAndHash, (st))
-#define sk_FileAndHash_pop(st) SKM_sk_pop(FileAndHash, (st))
-#define sk_FileAndHash_sort(st) SKM_sk_sort(FileAndHash, (st))
-#define sk_FileAndHash_is_sorted(st) SKM_sk_is_sorted(FileAndHash, (st))
-
-typedef struct ROAIPAddress_st {
- ASN1_BIT_STRING *IPAddress;
- ASN1_INTEGER *maxLength;
-} ROAIPAddress;
-
-DECLARE_STACK_OF(ROAIPAddress)
-
-ASN1_SEQUENCE(ROAIPAddress) = {
- ASN1_SIMPLE(ROAIPAddress, IPAddress, ASN1_BIT_STRING),
- ASN1_OPT(ROAIPAddress, maxLength, ASN1_INTEGER)
-} ASN1_SEQUENCE_END(ROAIPAddress)
-
-typedef struct ROAIPAddressFamily_st {
- ASN1_OCTET_STRING *addressFamily;
- STACK_OF(ROAIPAddress) *addresses;
-} ROAIPAddressFamily;
-
-DECLARE_STACK_OF(ROAIPAddressFamily)
-
-ASN1_SEQUENCE(ROAIPAddressFamily) = {
- ASN1_SIMPLE(ROAIPAddressFamily, addressFamily, ASN1_OCTET_STRING),
- ASN1_SEQUENCE_OF(ROAIPAddressFamily, addresses, ROAIPAddress)
-} ASN1_SEQUENCE_END(ROAIPAddressFamily)
-
-typedef struct ROA_st {
- ASN1_INTEGER *version, *asID;
- STACK_OF(ROAIPAddressFamily) *ipAddrBlocks;
-} ROA;
-
-ASN1_SEQUENCE(ROA) = {
- ASN1_EXP_OPT(ROA, version, ASN1_INTEGER, 0),
- ASN1_SIMPLE(ROA, asID, ASN1_INTEGER),
- ASN1_SEQUENCE_OF(ROA, ipAddrBlocks, ROAIPAddressFamily)
-} ASN1_SEQUENCE_END(ROA)
-
-DECLARE_ASN1_FUNCTIONS(ROAIPAddress)
-DECLARE_ASN1_FUNCTIONS(ROAIPAddressFamily)
-DECLARE_ASN1_FUNCTIONS(ROA)
-
-IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddress)
-IMPLEMENT_ASN1_FUNCTIONS(ROAIPAddressFamily)
-IMPLEMENT_ASN1_FUNCTIONS(ROA)
-
-#define sk_ROAIPAddress_new(st) SKM_sk_new(ROAIPAddress, (st))
-#define sk_ROAIPAddress_new_null() SKM_sk_new_null(ROAIPAddress)
-#define sk_ROAIPAddress_free(st) SKM_sk_free(ROAIPAddress, (st))
-#define sk_ROAIPAddress_num(st) SKM_sk_num(ROAIPAddress, (st))
-#define sk_ROAIPAddress_value(st, i) SKM_sk_value(ROAIPAddress, (st), (i))
-#define sk_ROAIPAddress_set(st, i, val) SKM_sk_set(ROAIPAddress, (st), (i), (val))
-#define sk_ROAIPAddress_zero(st) SKM_sk_zero(ROAIPAddress, (st))
-#define sk_ROAIPAddress_push(st, val) SKM_sk_push(ROAIPAddress, (st), (val))
-#define sk_ROAIPAddress_unshift(st, val) SKM_sk_unshift(ROAIPAddress, (st), (val))
-#define sk_ROAIPAddress_find(st, val) SKM_sk_find(ROAIPAddress, (st), (val))
-#define sk_ROAIPAddress_find_ex(st, val) SKM_sk_find_ex(ROAIPAddress, (st), (val))
-#define sk_ROAIPAddress_delete(st, i) SKM_sk_delete(ROAIPAddress, (st), (i))
-#define sk_ROAIPAddress_delete_ptr(st, ptr) SKM_sk_delete_ptr(ROAIPAddress, (st), (ptr))
-#define sk_ROAIPAddress_insert(st, val, i) SKM_sk_insert(ROAIPAddress, (st), (val), (i))
-#define sk_ROAIPAddress_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ROAIPAddress, (st), (cmp))
-#define sk_ROAIPAddress_dup(st) SKM_sk_dup(ROAIPAddress, st)
-#define sk_ROAIPAddress_pop_free(st, free_func) SKM_sk_pop_free(ROAIPAddress, (st), (free_func))
-#define sk_ROAIPAddress_shift(st) SKM_sk_shift(ROAIPAddress, (st))
-#define sk_ROAIPAddress_pop(st) SKM_sk_pop(ROAIPAddress, (st))
-#define sk_ROAIPAddress_sort(st) SKM_sk_sort(ROAIPAddress, (st))
-#define sk_ROAIPAddress_is_sorted(st) SKM_sk_is_sorted(ROAIPAddress, (st))
-
-#define sk_ROAIPAddressFamily_new(st) SKM_sk_new(ROAIPAddressFamily, (st))
-#define sk_ROAIPAddressFamily_new_null() SKM_sk_new_null(ROAIPAddressFamily)
-#define sk_ROAIPAddressFamily_free(st) SKM_sk_free(ROAIPAddressFamily, (st))
-#define sk_ROAIPAddressFamily_num(st) SKM_sk_num(ROAIPAddressFamily, (st))
-#define sk_ROAIPAddressFamily_value(st, i) SKM_sk_value(ROAIPAddressFamily, (st), (i))
-#define sk_ROAIPAddressFamily_set(st, i, val) SKM_sk_set(ROAIPAddressFamily, (st), (i), (val))
-#define sk_ROAIPAddressFamily_zero(st) SKM_sk_zero(ROAIPAddressFamily, (st))
-#define sk_ROAIPAddressFamily_push(st, val) SKM_sk_push(ROAIPAddressFamily, (st), (val))
-#define sk_ROAIPAddressFamily_unshift(st, val) SKM_sk_unshift(ROAIPAddressFamily, (st), (val))
-#define sk_ROAIPAddressFamily_find(st, val) SKM_sk_find(ROAIPAddressFamily, (st), (val))
-#define sk_ROAIPAddressFamily_find_ex(st, val) SKM_sk_find_ex(ROAIPAddressFamily, (st), (val))
-#define sk_ROAIPAddressFamily_delete(st, i) SKM_sk_delete(ROAIPAddressFamily, (st), (i))
-#define sk_ROAIPAddressFamily_delete_ptr(st, ptr) SKM_sk_delete_ptr(ROAIPAddressFamily, (st), (ptr))
-#define sk_ROAIPAddressFamily_insert(st, val, i) SKM_sk_insert(ROAIPAddressFamily, (st), (val), (i))
-#define sk_ROAIPAddressFamily_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ROAIPAddressFamily, (st), (cmp))
-#define sk_ROAIPAddressFamily_dup(st) SKM_sk_dup(ROAIPAddressFamily, st)
-#define sk_ROAIPAddressFamily_pop_free(st, free_func) SKM_sk_pop_free(ROAIPAddressFamily, (st), (free_func))
-#define sk_ROAIPAddressFamily_shift(st) SKM_sk_shift(ROAIPAddressFamily, (st))
-#define sk_ROAIPAddressFamily_pop(st) SKM_sk_pop(ROAIPAddressFamily, (st))
-#define sk_ROAIPAddressFamily_sort(st) SKM_sk_sort(ROAIPAddressFamily, (st))
-#define sk_ROAIPAddressFamily_is_sorted(st) SKM_sk_is_sorted(ROAIPAddressFamily, (st))
-
-#endif /* DOXYGEN_GETS_HOPELESSLY_CONFUSED_BY_THIS_SECTION */
+ * GCC attributes to help catch format string errors.
+ */
-
+#ifdef __GNUC__
+
+static void logmsg(const rcynic_ctx_t *rc,
+ const log_level_t level,
+ const char *fmt, ...)
+ __attribute__ ((format (printf, 3, 4)));
+#endif
/**
* Logging.
@@ -707,8 +612,6 @@ static void vlogmsg(const rcynic_ctx_t *rc,
fprintf(stderr, "%s: ", tad);
if (rc->jane)
fprintf(stderr, "%s: ", rc->jane);
- if (rc->indent)
- fprintf(stderr, "%*s", rc->indent, " ");
vfprintf(stderr, fmt, ap);
putc('\n', stderr);
}
@@ -743,9 +646,9 @@ static void log_openssl_errors(const rcynic_ctx_t *rc)
while ((code = ERR_get_error_line_data(&file, &line, &data, &flags))) {
ERR_error_string_n(code, error, sizeof(error));
if (data && (flags & ERR_TXT_STRING))
- logmsg(rc, log_sys_err, "OpenSSL error %s:%d: %s", file, line, error, data);
+ logmsg(rc, log_sys_err, "OpenSSL error %s:%d: %s: %s", file, line, error, data);
else
- logmsg(rc, log_sys_err, "OpenSSL error %s:%d", file, line, error);
+ logmsg(rc, log_sys_err, "OpenSSL error %s:%d: %s", file, line, error);
}
}
@@ -841,28 +744,29 @@ static int configure_integer(const rcynic_ctx_t *rc,
/**
* Make a directory if it doesn't already exist.
*/
-static int mkdir_maybe(const rcynic_ctx_t *rc, const char *name)
+static int mkdir_maybe(const rcynic_ctx_t *rc, const path_t *name)
{
- char *b, buffer[FILENAME_MAX];
+ path_t path;
+ char *s;
assert(name != NULL);
- if (strlen(name) >= sizeof(buffer)) {
- logmsg(rc, log_data_err, "Pathname %s too long", name);
+ if (strlen(name->s) >= sizeof(path.s)) {
+ logmsg(rc, log_data_err, "Pathname %s too long", name->s);
return 0;
}
- strcpy(buffer, name);
- b = buffer[0] == '/' ? buffer + 1 : buffer;
- if ((b = strrchr(b, '/')) == NULL)
+ strcpy(path.s, name->s);
+ s = path.s[0] == '/' ? path.s + 1 : path.s;
+ if ((s = strrchr(s, '/')) == NULL)
return 1;
- *b = '\0';
- if (!mkdir_maybe(rc, buffer)) {
- logmsg(rc, log_sys_err, "Failed to make directory %s", buffer);
+ *s = '\0';
+ if (!mkdir_maybe(rc, &path)) {
+ logmsg(rc, log_sys_err, "Failed to make directory %s", path.s);
return 0;
}
- if (!access(buffer, F_OK))
+ if (!access(path.s, F_OK))
return 1;
- logmsg(rc, log_verbose, "Creating directory %s", buffer);
- return mkdir(buffer, 0777) == 0;
+ logmsg(rc, log_verbose, "Creating directory %s", path.s);
+ return mkdir(path.s, 0777) == 0;
}
/**
@@ -903,49 +807,65 @@ static int is_rsync(const char *uri)
* the log, not the MIB.
*/
static int uri_to_filename(const rcynic_ctx_t *rc,
- const char *uri,
- char *buffer,
- const size_t buflen,
- const char *prefix)
+ const uri_t *uri,
+ path_t *path,
+ const path_t *prefix)
{
const char *u;
size_t n;
- buffer[0] = '\0';
+ path->s[0] = '\0';
- if (!is_rsync(uri)) {
- logmsg(rc, log_telemetry, "%s is not an rsync URI, not converting to filename", uri);
+ if (!is_rsync(uri->s)) {
+ logmsg(rc, log_telemetry, "%s is not an rsync URI, not converting to filename", uri->s);
return 0;
}
- u = uri + SIZEOF_RSYNC;
+ u = uri->s + SIZEOF_RSYNC;
n = strlen(u);
if (u[0] == '/' || u[0] == '.' || strstr(u, "/../") ||
(n >= 3 && !strcmp(u + n - 3, "/.."))) {
- logmsg(rc, log_data_err, "Dangerous URI %s, not converting to filename", uri);
+ logmsg(rc, log_data_err, "Dangerous URI %s, not converting to filename", uri->s);
return 0;
}
if (prefix)
- n += strlen(prefix);
+ n += strlen(prefix->s);
- if (n >= buflen) {
- logmsg(rc, log_data_err, "URI %s too long, not converting to filename", uri);
+ if (n >= sizeof(path->s)) {
+ logmsg(rc, log_data_err, "URI %s too long, not converting to filename", uri->s);
return 0;
}
if (prefix) {
- strcpy(buffer, prefix);
- strcat(buffer, u);
+ strcpy(path->s, prefix->s);
+ strcat(path->s, u);
} else {
- strcpy(buffer, u);
+ strcpy(path->s, u);
}
return 1;
}
/**
+ * Extract a hostname from a URI.
+ */
+static int uri_to_hostname(const uri_t *uri,
+ hostname_t *hostname)
+{
+ size_t n;
+
+ if (!uri || !hostname || !is_rsync(uri->s) ||
+ (n = strcspn(uri->s + SIZEOF_RSYNC, "/")) >= sizeof(hostname->s))
+ return 0;
+
+ strncpy(hostname->s, uri->s + SIZEOF_RSYNC, n);
+ hostname->s[n] = '\0';
+ return 1;
+}
+
+/**
* OID comparison.
*/
static int oid_cmp(const ASN1_OBJECT *obj, const unsigned char *oid, const size_t oidlen)
@@ -958,198 +878,176 @@ static int oid_cmp(const ASN1_OBJECT *obj, const unsigned char *oid, const size_
}
/**
- * Host MIB counter comparision.
+ * Get value of code in a validation_status_t.
*/
-static int host_mib_counter_cmp(const HOST_MIB_COUNTER * const *a, const HOST_MIB_COUNTER * const *b)
+static int validation_status_get_code(const validation_status_t *v,
+ const mib_counter_t code)
{
- return strcasecmp((*a)->hostname, (*b)->hostname);
+ assert(v && code < MIB_COUNTER_T_MAX);
+ return (v->events[code / 8] & (1 << (code % 8))) != 0;
}
/**
- * MIB counter manipulation.
+ * Set value of code in a validation_status_t.
*/
-static void mib_increment(const rcynic_ctx_t *rc,
- const char *uri,
- const mib_counter_t counter)
+static void validation_status_set_code(validation_status_t *v,
+ const mib_counter_t code,
+ int value)
{
- HOST_MIB_COUNTER *h = NULL, hn;
- char *s;
-
- assert(rc && uri && strlen(uri) < URI_MAX);
-
- if (!rc->host_counters)
- return;
-
- memset(&hn, 0, sizeof(hn));
-
- if (!uri_to_filename(rc, uri, hn.hostname, sizeof(hn.hostname), NULL)) {
- logmsg(rc, log_data_err, "Couldn't convert URI %s to hostname", uri);
- return;
- }
-
- if ((s = strchr(hn.hostname, '/')) != NULL)
- *s = '\0';
-
- h = sk_HOST_MIB_COUNTER_value(rc->host_counters,
- sk_HOST_MIB_COUNTER_find(rc->host_counters,
- &hn));
- if (!h) {
- if ((h = HOST_MIB_COUNTER_new()) == NULL) {
- logmsg(rc, log_sys_err, "Couldn't allocate MIB counters for %s", uri);
- return;
- }
- strcpy(h->hostname, hn.hostname);
- if (!sk_HOST_MIB_COUNTER_push(rc->host_counters, h)) {
- logmsg(rc, log_sys_err, "Couldn't store MIB counters for %s", uri);
- free(h);
- return;
- }
- }
-
- h->counters[counter]++;
+ assert(v && code < MIB_COUNTER_T_MAX);
+ if (value)
+ v->events[code / 8] |= (1 << (code % 8));
+ else
+ v->events[code / 8] &= ~(1 << (code % 8));
}
/**
* Add a validation status entry to internal log.
*/
static void log_validation_status(const rcynic_ctx_t *rc,
- const char *uri,
- const mib_counter_t code)
+ const uri_t *uri,
+ const mib_counter_t code,
+ const object_generation_t generation)
{
- VALIDATION_STATUS *v = NULL;
+ validation_status_t v_, *v = NULL;
+ int was_set;
- assert(rc && uri && strlen(uri) < URI_MAX);
+ assert(rc && uri && code < MIB_COUNTER_T_MAX && generation < OBJECT_GENERATION_MAX);
if (!rc->validation_status)
return;
- if ((v = VALIDATION_STATUS_new()) == NULL) {
- logmsg(rc, log_sys_err, "Couldn't allocate validation status entry for %s", uri);
- goto punt;
- }
+ memset(&v_, 0, sizeof(v_));
+ v_.uri = *uri;
+ v_.generation = generation;
- strcpy(v->uri, uri);
- v->timestamp = time(0);
- v->code = code;
-
- if (!sk_VALIDATION_STATUS_push(rc->validation_status, v)) {
- logmsg(rc, log_sys_err, "Couldn't store validation status entry for %s", uri);
- goto punt;
+ v = sk_validation_status_t_value(rc->validation_status, sk_validation_status_t_find(rc->validation_status, &v_));
+ if (v == NULL) {
+ if ((v = validation_status_t_new()) == NULL) {
+ logmsg(rc, log_sys_err, "Couldn't allocate validation status entry for %s", uri->s);
+ return;
+ }
+ *v = v_;
+ if (!sk_validation_status_t_push(rc->validation_status, v)) {
+ logmsg(rc, log_sys_err, "Couldn't store validation status entry for %s", uri->s);
+ free(v);
+ return;
+ }
}
- v = NULL;
+ was_set = validation_status_get_code(v, code);
- punt:
- if (v)
- free(v);
+ v->timestamp = time(0);
+ validation_status_set_code(v, code, 1);
+
+ if (!was_set)
+ logmsg(rc, log_verbose, "Recording \"%s\" for %s%s%s",
+ (mib_counter_desc[code]
+ ? mib_counter_desc[code]
+ : X509_verify_cert_error_string(mib_counter_openssl[code])),
+ (generation != object_generation_null ? object_generation_label[generation] : ""),
+ (generation != object_generation_null ? " " : ""),
+ uri->s);
}
/**
- * Reject an object.
+ * Validation status object comparision.
*/
-static void reject(const rcynic_ctx_t *rc,
- const char *uri,
- const mib_counter_t code,
- const char *fmt, ...)
+static int validation_status_cmp(const validation_status_t * const *a, const validation_status_t * const *b)
{
- char format[URI_MAX * 2];
- va_list ap;
-
- assert(fmt && strlen(fmt) + sizeof("Rejected %s") < sizeof(format));
- snprintf(format, sizeof(format), "Rejected %s %s", uri, fmt);
- log_validation_status(rc, uri, code);
- va_start(ap, fmt);
- vlogmsg(rc, log_data_err, format, ap);
- va_end(ap);
+ int cmp = strcmp((*a)->uri.s, (*b)->uri.s);
+ if (cmp)
+ return cmp;
+ cmp = (int) ((*a)->generation) - (int) ((*b)->generation);
+ if (cmp)
+ return cmp;
+ return 0;
}
/**
- * Copy a file
+ * Copy or link a file, as the case may be.
*/
-static int cp(const rcynic_ctx_t *rc, const char *source, const char *target)
+static int cp_ln(const rcynic_ctx_t *rc, const path_t *source, const path_t *target)
{
struct stat statbuf;
struct utimbuf utimebuf;
FILE *in = NULL, *out = NULL;
- int c, ret = 0;
+ int c, ok = 0;
+
+ if (rc->use_links) {
+ (void) unlink(target->s);
+ ok = link(source->s, target->s) == 0;
+ if (!ok)
+ logmsg(rc, log_sys_err, "Couldn't link %s to %s: %s",
+ source->s, target->s, strerror(errno));
+ return ok;
+ }
- if ((in = fopen(source, "rb")) == NULL ||
- (out = fopen(target, "wb")) == NULL)
+ if ((in = fopen(source->s, "rb")) == NULL ||
+ (out = fopen(target->s, "wb")) == NULL)
goto done;
while ((c = getc(in)) != EOF)
if (putc(c, out) == EOF)
goto done;
- ret = 1;
+ ok = 1;
done:
- ret &= !(in != NULL && fclose(in) == EOF);
- ret &= !(out != NULL && fclose(out) == EOF);
+ ok &= !(in != NULL && fclose(in) == EOF);
+ ok &= !(out != NULL && fclose(out) == EOF);
+
+ if (!ok) {
+ logmsg(rc, log_sys_err, "Couldn't copy %s to %s: %s",
+ source->s, target->s, strerror(errno));
+ return ok;
+ }
/*
* Perserve the file modification time to allow for detection of
* changed objects in the authenticated directory. Failure to reset
* the times is not optimal, but is also not critical, thus no
- * failure return. Errors are reported with log_sys_err because
- * there is no log type for warnings.
+ * failure return.
*/
- if (ret && (stat(source, &statbuf) < 0 ||
- (utimebuf.actime = statbuf.st_atime,
- utimebuf.modtime = statbuf.st_mtime,
- utime(target, &utimebuf) < 0)))
+ if (stat(source->s, &statbuf) < 0 ||
+ (utimebuf.actime = statbuf.st_atime,
+ utimebuf.modtime = statbuf.st_mtime,
+ utime(target->s, &utimebuf) < 0))
logmsg(rc, log_sys_err, "Couldn't copy inode timestamp from %s to %s: %s",
- source, target, strerror(errno));
+ source->s, target->s, strerror(errno));
- return ret;
-}
-
-/**
- * Link a file
- */
-static int ln(const char *source, const char *target)
-{
- unlink(target);
- return link(source, target) == 0;
+ return ok;
}
/**
- * Install an object. It'd be nice if we could just use link(), but
- * that would require us to trust rsync never to do anything bad. For
- * now we just copy in the simplest way possible. Come back to this
- * if profiling shows a hotspot here.
- *
- * Well, ok, profiling didn't show an issue, but inode exhaustion did.
- * So we now make copy vs link a configuration choice.
+ * Install an object.
*/
static int install_object(const rcynic_ctx_t *rc,
- const char *uri,
- const char *source)
+ const uri_t *uri,
+ const path_t *source,
+ const mib_counter_t code,
+ const object_generation_t generation)
{
- char target[FILENAME_MAX];
+ path_t target;
- if (!uri_to_filename(rc, uri, target, sizeof(target), rc->authenticated)) {
- logmsg(rc, log_data_err, "Couldn't generate installation name for %s", uri);
+ if (!uri_to_filename(rc, uri, &target, &rc->new_authenticated)) {
+ logmsg(rc, log_data_err, "Couldn't generate installation name for %s", uri->s);
return 0;
}
- if (!mkdir_maybe(rc, target)) {
- logmsg(rc, log_sys_err, "Couldn't create directory for %s", target);
+ if (!mkdir_maybe(rc, &target)) {
+ logmsg(rc, log_sys_err, "Couldn't create directory for %s", target.s);
return 0;
}
- if (rc->use_links ? !ln(source, target) : !cp(rc, source, target)) {
- logmsg(rc, log_sys_err, "Couldn't %s %s to %s",
- (rc->use_links ? "link" : "copy"), source, target);
+ if (!cp_ln(rc, source, &target))
return 0;
- }
- log_validation_status(rc, uri, validation_ok);
- logmsg(rc, log_telemetry, "Accepted %s", uri);
+ log_validation_status(rc, uri, code, generation);
return 1;
}
/**
- * Check str for a trailing suffix.
+ * Check str for a suffix.
*/
static int endswith(const char *str, const char *suffix)
{
@@ -1174,46 +1072,57 @@ static int startswith(const char *str, const char *prefix)
/**
- * Set a directory name, making sure it has the trailing slash we
- * require in various other routines.
+ * Set a directory name, adding or stripping trailing slash as needed.
*/
-static void set_directory(char **out, const char *in)
+static int set_directory(const rcynic_ctx_t *rc, path_t *out, const char *in, const int want_slash)
{
- int need_slash;
+ int has_slash, need_slash;
size_t n;
- char *s;
- assert(in && out);
+ assert(rc && in && out);
+
n = strlen(in);
- assert(n > 0);
- need_slash = in[n - 1] != '/';
- s = malloc(n + need_slash + 1);
- assert(s != NULL);
- strcpy(s, in);
+
+ if (n == 0) {
+ logmsg(rc, log_usage_err, "Empty path");
+ return 0;
+ }
+
+ has_slash = in[n - 1] == '/';
+
+ need_slash = want_slash && !has_slash;
+
+ if (n + need_slash + 1 > sizeof(out->s)) {
+ logmsg(rc, log_usage_err, "Path \"%s\" too long", in);
+ return 0;
+ }
+
+ strcpy(out->s, in);
if (need_slash)
- strcat(s, "/");
- if (*out)
- free(*out);
- *out = s;
+ strcat(out->s, "/");
+ else if (has_slash && !want_slash)
+ out->s[n - 1] = '\0';
+
+ return 1;
}
/**
* Remove a directory tree, like rm -rf.
*/
-static int rm_rf(const char *name)
+static int rm_rf(const path_t *name)
{
- char path[FILENAME_MAX];
+ path_t path;
struct dirent *d;
size_t len;
DIR *dir;
int ret = 0, need_slash;
assert(name);
- len = strlen(name);
- assert(len > 0 && len < sizeof(path));
- need_slash = name[len - 1] != '/';
+ len = strlen(name->s);
+ assert(len > 0 && len < sizeof(path.s));
+ need_slash = name->s[len - 1] != '/';
- if (rmdir(name) == 0)
+ if (rmdir(name->s) == 0)
return 1;
switch (errno) {
@@ -1225,40 +1134,581 @@ static int rm_rf(const char *name)
return 0;
}
- if ((dir = opendir(name)) == NULL)
+ if ((dir = opendir(name->s)) == NULL)
return 0;
while ((d = readdir(dir)) != NULL) {
if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0')))
continue;
- if (len + strlen(d->d_name) + need_slash >= sizeof(path))
+ if (len + strlen(d->d_name) + need_slash >= sizeof(path.s))
goto done;
- strcpy(path, name);
+ strcpy(path.s, name->s);
if (need_slash)
- strcat(path, "/");
- strcat(path, d->d_name);
+ strcat(path.s, "/");
+ strcat(path.s, d->d_name);
switch (d->d_type) {
case DT_DIR:
- if (!rm_rf(path))
+ if (!rm_rf(&path))
goto done;
continue;
default:
- if (unlink(path) < 0)
+ if (unlink(path.s) < 0)
goto done;
continue;
}
}
- ret = rmdir(name) == 0;
+ ret = rmdir(name->s) == 0;
done:
closedir(dir);
return ret;
}
+/**
+ * Construct names for the directories not directly settable by the
+ * user.
+ *
+ * This function also checks for an old-style rc->authenticated
+ * directory, to simplify upgrade from older versions of rcynic.
+ */
+static int construct_directory_names(rcynic_ctx_t *rc)
+{
+ struct stat st;
+ ssize_t n;
+ path_t p;
+ time_t t = time(0);
+
+ p = rc->authenticated;
+
+ n = strlen(p.s);
+
+ if (n + sizeof(authenticated_symlink_suffix) >= sizeof(p.s)) {
+ logmsg(rc, log_usage_err, "Symlink name would be too long");
+ return 0;
+ }
+
+ if (strftime(p.s + n, sizeof(p.s) - n - 1, ".%Y-%m-%dT%H:%M:%SZ", gmtime(&t)) == 0) {
+ logmsg(rc, log_usage_err, "Generated path with timestamp would be too long");
+ return 0;
+ }
+
+ if (!set_directory(rc, &rc->new_authenticated, p.s, 1))
+ return 0;
+
+ if (!set_directory(rc, &rc->old_authenticated, rc->authenticated.s, 1))
+ return 0;
+
+ if (lstat(rc->authenticated.s, &st) == 0 && (st.st_mode & S_IFDIR) != 0 &&
+ strlen(rc->authenticated.s) + sizeof(".old") < sizeof(p.s)) {
+ p = rc->authenticated;
+ strcat(p.s, ".old");
+ rm_rf(&p);
+ (void) rename(rc->authenticated.s, p.s);
+ }
+
+ if (lstat(rc->authenticated.s, &st) == 0 && (st.st_mode & S_IFDIR) != 0) {
+ logmsg(rc, log_usage_err,
+ "Existing %s directory is in the way, please remove it",
+ rc->authenticated.s);
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * Do final symlink shuffle and cleanup of output directories.
+ */
+static int finalize_directories(const rcynic_ctx_t *rc)
+{
+ path_t path, sym, real_old, real_new;
+ const char *dir;
+ size_t n;
+ glob_t g;
+ int i;
+
+ if (!realpath(rc->old_authenticated.s, real_old.s))
+ real_old.s[0] = '\0';
+
+ if (!realpath(rc->new_authenticated.s, real_new.s))
+ real_old.s[0] = '\0';
+
+ path = rc->new_authenticated;
+
+ n = strlen(path.s);
+ assert(n > 1 && path.s[n - 1] == '/');
+ path.s[n - 1] = '\0';
+
+ if ((dir = strrchr(path.s, '/')) == NULL)
+ dir = path.s;
+ else
+ dir++;
+
+ sym = rc->authenticated;
+
+ assert(strlen(sym.s) + sizeof(authenticated_symlink_suffix) < sizeof(sym.s));
+ strcat(sym.s, authenticated_symlink_suffix);
+
+ (void) unlink(sym.s);
+
+ if (symlink(dir, sym.s) < 0) {
+ logmsg(rc, log_sys_err, "Couldn't link %s to %s: %s",
+ sym.s, dir, strerror(errno));
+ return 0;
+ }
+
+ if (rename(sym.s, rc->authenticated.s) < 0) {
+ logmsg(rc, log_sys_err, "Couldn't rename %s to %s: %s",
+ sym.s, rc->authenticated.s, strerror(errno));
+ return 0;
+ }
+
+ path = rc->authenticated;
+ assert(strlen(path.s) + sizeof(".*") < sizeof(path.s));
+ strcat(path.s, ".*");
+
+ memset(&g, 0, sizeof(g));
+
+ if (real_new.s[0] && glob(path.s, 0, 0, &g) == 0)
+ for (i = 0; i < g.gl_pathc; i++)
+ if (realpath(g.gl_pathv[i], path.s) &&
+ strcmp(path.s, real_old.s) &&
+ strcmp(path.s, real_new.s))
+ rm_rf(&path);
+
+ return 1;
+}
+
+
+
+/**
+ * Check to see whether a hostname is in the dead host cache.
+ */
+static int dead_host_check(const rcynic_ctx_t *rc, const uri_t *uri)
+{
+ hostname_t hostname;
+
+ assert(rc && uri && rc->dead_host_cache);
+
+ return (uri_to_hostname(uri, &hostname) &&
+ sk_OPENSSL_STRING_find(rc->dead_host_cache, hostname.s) >= 0);
+}
+
+
+/**
+ * Add an entry to the dead host cache.
+ */
+static void dead_host_add(const rcynic_ctx_t *rc, const uri_t *uri)
+{
+ hostname_t hostname;
+
+ assert(rc && uri && rc->dead_host_cache);
+
+ if (dead_host_check(rc, uri))
+ return;
+
+ if (!uri_to_hostname(uri, &hostname))
+ return;
+
+ (void) sk_OPENSSL_STRING_push_strdup(rc->dead_host_cache, hostname.s);
+}
+
+
+
+/**
+ * Test whether a pair of URIs "conflict", that is, whether attempting
+ * to rsync both of them at the same time in parallel might cause
+ * unpredictable behavior. Might need a better name for this test.
+ *
+ * Returns non-zero iff the two URIs "conflict".
+ */
+static int conflicting_uris(const uri_t *a, const uri_t *b)
+{
+ size_t len_a, len_b;
+
+ assert(a && is_rsync(a->s) && b && is_rsync(b->s));
+
+ len_a = strlen(a->s);
+ len_b = strlen(b->s);
+
+ assert(len_a < sizeof(a->s) && len_b < sizeof(b->s));
+
+ return !strncmp(a->s, b->s, len_a < len_b ? len_a : len_b);
+}
+
+
+
+/**
+ * Read non-directory filenames from a directory, so we can check to
+ * see what's missing from a manifest.
+ */
+static STACK_OF(OPENSSL_STRING) *directory_filenames(const rcynic_ctx_t *rc,
+ const walk_state_t state,
+ const uri_t *uri)
+{
+ STACK_OF(OPENSSL_STRING) *result = NULL;
+ path_t path;
+ const path_t *prefix = NULL;
+ DIR *dir = NULL;
+ struct dirent *d;
+ int ok = 0;
+
+ assert(rc && uri);
+
+ switch (state) {
+ case walk_state_current:
+ prefix = &rc->unauthenticated;
+ break;
+ case walk_state_backup:
+ prefix = &rc->old_authenticated;
+ break;
+ default:
+ goto done;
+ }
+
+ if (!uri_to_filename(rc, uri, &path, prefix) ||
+ (dir = opendir(path.s)) == NULL ||
+ (result = sk_OPENSSL_STRING_new(uri_cmp)) == NULL)
+ goto done;
+
+ while ((d = readdir(dir)) != NULL)
+ if (d->d_type != DT_DIR && !sk_OPENSSL_STRING_push_strdup(result, d->d_name))
+ goto done;
+
+ ok = 1;
+
+ done:
+ if (dir != NULL)
+ closedir(dir);
+
+ if (ok)
+ return result;
+
+ sk_OPENSSL_STRING_pop_free(result, OPENSSL_STRING_free);
+ return NULL;
+}
+
+
+
+/**
+ * Increment walk context reference count.
+ */
+static void walk_ctx_attach(walk_ctx_t *w)
+{
+ if (w != NULL) {
+ w->refcount++;
+ assert(w->refcount != 0);
+ }
+}
+
+/**
+ * Decrement walk context reference count; freeing the context if the
+ * reference count is now zero.
+ */
+static void walk_ctx_detach(walk_ctx_t *w)
+{
+ if (w != NULL && --(w->refcount) == 0) {
+ assert(w->refcount == 0);
+ X509_free(w->cert);
+ Manifest_free(w->manifest);
+ sk_OPENSSL_STRING_pop_free(w->filenames, OPENSSL_STRING_free);
+ free(w);
+ }
+}
+
+/**
+ * Return top context of a walk context stack.
+ */
+static walk_ctx_t *walk_ctx_stack_head(STACK_OF(walk_ctx_t) *wsk)
+{
+ return sk_walk_ctx_t_value(wsk, sk_walk_ctx_t_num(wsk) - 1);
+}
+
+/**
+ * Walk context iterator. Think of this as the thing you call in the
+ * third clause of a conceptual "for" loop: this reinitializes as
+ * necessary for the next pass through the loop.
+ *
+ * This is still under construction, but general idea is that we have
+ * several state variables in a walk context which collectively define
+ * the current pass, product URI, etc, and we want to be able to
+ * iterate through this sequence via the event system. So we need a
+ * function which steps to the next state.
+ */
+static void walk_ctx_loop_next(const rcynic_ctx_t *rc, STACK_OF(walk_ctx_t) *wsk)
+{
+ walk_ctx_t *w = walk_ctx_stack_head(wsk);
+
+ assert(rc && wsk && w);
+
+ if (w->manifest && w->manifest_iteration + 1 < sk_FileAndHash_num(w->manifest->fileList)) {
+ w->manifest_iteration++;
+ return;
+ }
+
+ if (w->filenames && w->filename_iteration + 1 < sk_OPENSSL_STRING_num(w->filenames)) {
+ w->filename_iteration++;
+ return;
+ }
+
+ if (w->state < walk_state_done) {
+ w->state++;
+ w->manifest_iteration = 0;
+ w->filename_iteration = 0;
+ sk_OPENSSL_STRING_pop_free(w->filenames, OPENSSL_STRING_free);
+ w->filenames = directory_filenames(rc, w->state, &w->certinfo.sia);
+ }
+}
+
+/**
+ * Whether we're done iterating over a walk context. Think of this as
+ * the thing you call (negated) in the second clause of a conceptual
+ * "for" loop.
+ */
+static int walk_ctx_loop_done(STACK_OF(walk_ctx_t) *wsk)
+{
+ walk_ctx_t *w = walk_ctx_stack_head(wsk);
+ return wsk == NULL || w == NULL || w->state >= walk_state_done;
+}
+
+static Manifest *check_manifest(const rcynic_ctx_t *rc,
+ STACK_OF(walk_ctx_t) *wsk);
+
+/**
+ * Loop initializer for walk context. Think of this as the thing you
+ * call in the first clause of a conceptual "for" loop.
+ */
+static void walk_ctx_loop_init(const rcynic_ctx_t *rc, STACK_OF(walk_ctx_t) *wsk)
+{
+ walk_ctx_t *w = walk_ctx_stack_head(wsk);
+
+ assert(rc && wsk && w && w->state == walk_state_ready);
+
+ assert(w->manifest == NULL);
+ if ((w->manifest = check_manifest(rc, wsk)) == NULL)
+ logmsg(rc, log_telemetry, "Couldn't get manifest %s, blundering onward", w->certinfo.manifest.s);
+
+ assert(w->filenames == NULL);
+ w->filenames = directory_filenames(rc, w->state, &w->certinfo.sia);
+
+ w->stale_manifest = w->manifest != NULL && X509_cmp_current_time(w->manifest->nextUpdate) < 0;
+
+ w->manifest_iteration = 0;
+ w->filename_iteration = 0;
+ w->state++;
+
+ assert(w->state == walk_state_current);
+
+ while (!walk_ctx_loop_done(wsk) &&
+ (w->manifest == NULL || w->manifest_iteration >= sk_FileAndHash_num(w->manifest->fileList)) &&
+ (w->filenames == NULL || w->filename_iteration >= sk_OPENSSL_STRING_num(w->filenames)))
+ walk_ctx_loop_next(rc, wsk);
+}
+
+/**
+ * Extract URI and hash values from walk context.
+ */
+static int walk_ctx_loop_this(const rcynic_ctx_t *rc,
+ STACK_OF(walk_ctx_t) *wsk,
+ uri_t *uri,
+ const unsigned char **hash,
+ size_t *hashlen)
+{
+ const walk_ctx_t *w = walk_ctx_stack_head(wsk);
+ const char *name = NULL;
+ FileAndHash *fah = NULL;
+
+ assert(rc && wsk && w && uri && hash && hashlen);
+
+ if (w->manifest != NULL && w->manifest_iteration < sk_FileAndHash_num(w->manifest->fileList)) {
+ fah = sk_FileAndHash_value(w->manifest->fileList, w->manifest_iteration);
+ name = (const char *) fah->file->data;
+ } else if (w->filenames != NULL && w->filename_iteration < sk_OPENSSL_STRING_num(w->filenames)) {
+ name = sk_OPENSSL_STRING_value(w->filenames, w->filename_iteration);
+ }
+
+ if (name == NULL) {
+ logmsg(rc, log_sys_err, "Can't find a URI in walk context, this shouldn't happen: state %d, manifest_iteration %d, filename_iteration %d",
+ (int) w->state, w->manifest_iteration, w->filename_iteration);
+ return 0;
+ }
+
+ if (strlen(w->certinfo.sia.s) + strlen(name) >= sizeof(uri->s)) {
+ logmsg(rc, log_data_err, "URI %s%s too long, skipping", w->certinfo.sia.s, uri->s);
+ return 0;
+ }
+
+ strcpy(uri->s, w->certinfo.sia.s);
+ strcat(uri->s, name);
+
+ if (fah != NULL) {
+ sk_OPENSSL_STRING_remove(w->filenames, name);
+ *hash = fah->hash->data;
+ *hashlen = fah->hash->length;
+ } else {
+ *hash = NULL;
+ *hashlen = 0;
+ }
+
+ return 1;
+}
+
+/**
+ * Create a new walk context stack.
+ */
+static STACK_OF(walk_ctx_t) *walk_ctx_stack_new(void)
+{
+ return sk_walk_ctx_t_new_null();
+}
+
+/**
+ * Push a walk context onto a walk context stack, return the new context.
+ */
+static walk_ctx_t *walk_ctx_stack_push(STACK_OF(walk_ctx_t) *wsk,
+ X509 *x,
+ const certinfo_t *certinfo)
+{
+ walk_ctx_t *w;
+
+ if (x == NULL || certinfo == NULL)
+ return NULL;
+
+ if ((w = malloc(sizeof(*w))) == NULL)
+ return NULL;
+
+ memset(w, 0, sizeof(*w));
+ w->cert = x;
+ w->certinfo = *certinfo;
+
+ if (!sk_walk_ctx_t_push(wsk, w)) {
+ free(w);
+ return NULL;
+ }
+
+ walk_ctx_attach(w);
+ return w;
+}
+
+/**
+ * Pop and discard a walk context from a walk context stack.
+ */
+static void walk_ctx_stack_pop(STACK_OF(walk_ctx_t) *wsk)
+{
+ walk_ctx_detach(sk_walk_ctx_t_pop(wsk));
+}
+
+/**
+ * Clone a stack of walk contexts.
+ */
+static STACK_OF(walk_ctx_t) *walk_ctx_stack_clone(STACK_OF(walk_ctx_t) *old_wsk)
+{
+ STACK_OF(walk_ctx_t) *new_wsk;
+ int i;
+ if (old_wsk == NULL || (new_wsk = sk_walk_ctx_t_dup(old_wsk)) == NULL)
+ return NULL;
+ for (i = 0; i < sk_walk_ctx_t_num(new_wsk); i++)
+ walk_ctx_attach(sk_walk_ctx_t_value(new_wsk, i));
+ return new_wsk;
+}
+
+/**
+ * Extract certificate stack from walk context stack. Returns a newly
+ * created STACK_OF(X509) pointing to the existing cert objects (ie,
+ * this is a shallow copy, so only free the STACK_OF(X509), not the
+ * certificates themselves).
+ */
+static STACK_OF(X509) *walk_ctx_stack_certs(STACK_OF(walk_ctx_t) *wsk)
+{
+ STACK_OF(X509) *xsk = sk_X509_new_null();
+ walk_ctx_t *w;
+ int i;
+
+ for (i = 0; i < sk_walk_ctx_t_num(wsk); i++)
+ if ((w = sk_walk_ctx_t_value(wsk, i)) == NULL ||
+ (w->cert != NULL && !sk_X509_push(xsk, w->cert)))
+ goto fail;
+
+ return xsk;
+
+ fail:
+ sk_X509_free(xsk);
+ return NULL;
+}
+
+/**
+ * Free a walk context stack, decrementing reference counts of each
+ * frame on it.
+ */
+static void walk_ctx_stack_free(STACK_OF(walk_ctx_t) *wsk)
+{
+ sk_walk_ctx_t_pop_free(wsk, walk_ctx_detach);
+}
+
+
+
+static int rsync_count_running(const rcynic_ctx_t *);
+
+/**
+ * Add a task to the task queue.
+ */
+static int task_add(const rcynic_ctx_t *rc,
+ void (*handler)(rcynic_ctx_t *, STACK_OF(walk_ctx_t) *),
+ STACK_OF(walk_ctx_t) *wsk)
+{
+ task_t *t = malloc(sizeof(*t));
+
+ assert(rc && rc->task_queue && handler);
+
+ assert(rsync_count_running(rc) <= rc->max_parallel_fetches);
+
+ if (!t)
+ return 0;
+
+ t->handler = handler;
+ t->wsk = wsk;
+
+ if (sk_task_t_push(rc->task_queue, t))
+ return 1;
+
+ free(t);
+ return 0;
+}
+
+/**
+ * Run tasks until queue is empty.
+ */
+static void task_run_q(rcynic_ctx_t *rc)
+{
+ task_t *t;
+ assert(rc && rc->task_queue);
+ while ((t = sk_task_t_shift(rc->task_queue)) != NULL) {
+ t->handler(rc, t->wsk);
+ free(t);
+ }
+}
+
/**
+ * Record that we've already synced a particular rsync URI.
+ */
+
+static void rsync_cache_add(const rcynic_ctx_t *rc, const uri_t *uri)
+{
+ uri_t uribuf;
+ char *s;
+
+ assert(rc && uri && rc->rsync_cache);
+ uribuf = *uri;
+ while ((s = strrchr(uribuf.s, '/')) != NULL && s[1] == '\0')
+ *s = '\0';
+ assert(strlen(uribuf.s) > SIZEOF_RSYNC);
+ if (!sk_OPENSSL_STRING_push_strdup(rc->rsync_cache, uribuf.s + SIZEOF_RSYNC))
+ logmsg(rc, log_sys_err, "Couldn't cache URI %s, blundering onward", uri->s);
+}
+
+/**
* Maintain a cache of URIs we've already fetched.
*/
static int rsync_cached_string(const rcynic_ctx_t *rc,
@@ -1282,235 +1732,610 @@ static int rsync_cached_string(const rcynic_ctx_t *rc,
* Check whether a particular URI has been cached.
*/
static int rsync_cached_uri(const rcynic_ctx_t *rc,
- const char *uri)
+ const uri_t *uri)
{
- return is_rsync(uri) && rsync_cached_string(rc, uri + SIZEOF_RSYNC);
+ return is_rsync(uri->s) && rsync_cached_string(rc, uri->s + SIZEOF_RSYNC);
}
+/**
+ * Return count of how many rsync contexts are in running.
+ */
+static int rsync_count_running(const rcynic_ctx_t *rc)
+{
+ const rsync_ctx_t *ctx;
+ int i, n = 0;
+
+ assert(rc && rc->rsync_queue);
+
+ for (i = 0; (ctx = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i) {
+ switch (ctx->state) {
+ case rsync_state_running:
+ case rsync_state_terminating:
+ n++;
+ default:
+ continue;
+ }
+ }
+
+ return n;
+}
/**
- * Run rsync. This is fairly nasty, because we need to:
- *
- * @li Construct the argument list for rsync;
- *
- * @li Run rsync in a child process;
- *
- * @li Sit listening to rsync's output, logging whatever we get;
- *
- * @li Impose an optional time limit on rsync's execution time
- *
- * @li Clean up from (b), (c), and (d); and
- *
- * @li Keep track of which URIs we've already fetched, so we don't
- * have to do it again.
- *
- * Taken all together, this is pretty icky. Breaking it into separate
- * functions wouldn't help much. Don't read this on a full stomach.
+ * Test whether an rsync context conflicts with anything that's
+ * currently runable.
+ */
+static int rsync_conflicts(const rcynic_ctx_t *rc,
+ const rsync_ctx_t *ctx)
+{
+ const rsync_ctx_t *c;
+ int i;
+
+ assert(rc && ctx && rc->rsync_queue);
+
+ for (i = 0; (c = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i)
+ if (c != ctx &&
+ (c->state == rsync_state_initial ||
+ c->state == rsync_state_running) &&
+ conflicting_uris(&c->uri, &ctx->uri))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * Test whether a rsync context is runable at this time.
+ */
+static int rsync_runable(const rcynic_ctx_t *rc,
+ const rsync_ctx_t *ctx)
+{
+ assert(rc && ctx);
+
+ switch (ctx->state) {
+
+ case rsync_state_initial:
+ case rsync_state_running:
+ return 1;
+
+ case rsync_state_retry_wait:
+ return ctx->deadline <= time(0);
+
+ case rsync_state_terminating:
+ return 0;
+
+ case rsync_state_conflict_wait:
+ return !rsync_conflicts(rc, ctx);
+ }
+
+ return 0;
+}
+
+/**
+ * Return count of runable rsync contexts.
*/
-static int rsync(const rcynic_ctx_t *rc,
- const char * const *args,
- const char *uri)
+static int rsync_count_runable(const rcynic_ctx_t *rc)
{
- static const char *rsync_cmd[] = {
- "rsync", "--update", "--times", "--copy-links", "--itemize-changes", NULL
+ const rsync_ctx_t *ctx;
+ int i, n = 0;
+
+ assert(rc && rc->rsync_queue);
+
+ for (i = 0; (ctx = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i)
+ if (rsync_runable(rc, ctx))
+ n++;
+
+ return n;
+}
+
+/**
+ * Run an rsync process.
+ */
+static void rsync_run(const rcynic_ctx_t *rc,
+ rsync_ctx_t *ctx)
+{
+ static const char * const rsync_cmd[] = {
+ "rsync", "--update", "--times", "--copy-links", "--itemize-changes"
+ };
+ static const char * const rsync_tree_args[] = {
+ "--recursive", "--delete"
};
- const char *argv[100];
- char *s, *b, buffer[URI_MAX * 4], path[FILENAME_MAX];
- int i, n, ret, pipe_fds[2], argc = 0, pid_status = -1;
- time_t now, deadline;
- struct timeval tv;
- pid_t pid, wpid;
- fd_set rfds;
+ const char *argv[10];
+ path_t path;
+ int i, argc = 0, flags, pipe_fds[2];
- assert(rc && uri);
+ pipe_fds[0] = pipe_fds[1] = -1;
+
+ assert(rc && ctx && ctx->pid == 0 && ctx->state != rsync_state_running && rsync_runable(rc, ctx));
+
+ assert(rsync_count_running(rc) < rc->max_parallel_fetches);
+
+ logmsg(rc, log_telemetry, "Fetching %s", ctx->uri.s);
memset(argv, 0, sizeof(argv));
- for (i = 0; rsync_cmd[i]; i++) {
+ for (i = 0; i < sizeof(rsync_cmd)/sizeof(*rsync_cmd); i++) {
assert(argc < sizeof(argv)/sizeof(*argv));
argv[argc++] = rsync_cmd[i];
}
- if (args) {
- for (i = 0; args[i]; i++) {
+ if (endswith(ctx->uri.s, "/")) {
+ for (i = 0; i < sizeof(rsync_tree_args)/sizeof(*rsync_tree_args); i++) {
assert(argc < sizeof(argv)/sizeof(*argv));
- argv[argc++] = args[i];
+ argv[argc++] = rsync_tree_args[i];
}
}
if (rc->rsync_program)
argv[0] = rc->rsync_program;
- if (!uri_to_filename(rc, uri, path, sizeof(path), rc->unauthenticated)) {
- logmsg(rc, log_data_err, "Couldn't extract filename from URI: %s", uri);
- return 0;
+ if (!uri_to_filename(rc, &ctx->uri, &path, &rc->unauthenticated)) {
+ logmsg(rc, log_data_err, "Couldn't extract filename from URI: %s", ctx->uri.s);
+ goto lose;
}
assert(argc < sizeof(argv)/sizeof(*argv));
- argv[argc++] = uri;
+ argv[argc++] = ctx->uri.s;
assert(argc < sizeof(argv)/sizeof(*argv));
- argv[argc++] = path;
+ argv[argc++] = path.s;
- assert(strlen(uri) > SIZEOF_RSYNC);
- if (rsync_cached_uri(rc, uri)) {
- logmsg(rc, log_verbose, "rsync cache hit for %s", uri);
- return 1;
+ if (!mkdir_maybe(rc, &path)) {
+ logmsg(rc, log_sys_err, "Couldn't make target directory: %s", path.s);
+ goto lose;
}
- if (!mkdir_maybe(rc, path)) {
- logmsg(rc, log_sys_err, "Couldn't make target directory: %s", path);
- return 0;
- }
-
- logmsg(rc, log_telemetry, "Fetching %s", uri);
-
for (i = 0; i < argc; i++)
logmsg(rc, log_verbose, "rsync argv[%d]: %s", i, argv[i]);
if (pipe(pipe_fds) < 0) {
logmsg(rc, log_sys_err, "pipe() failed: %s", strerror(errno));
- return 0;
+ goto lose;
}
+ ctx->fd = pipe_fds[0];
- if ((i = fcntl(pipe_fds[0], F_GETFL, 0)) == -1 ||
- fcntl(pipe_fds[0], F_SETFL, i | O_NONBLOCK) == -1) {
- logmsg(rc, log_sys_err,
- "Couldn't set rsync's output stream non-blocking: %s",
+ if ((flags = fcntl(ctx->fd, F_GETFL, 0)) == -1) {
+ logmsg(rc, log_sys_err, "fcntl(F_GETFL) failed: %s",
strerror(errno));
- close(pipe_fds[0]);
- close(pipe_fds[1]);
- return 0;
+ goto lose;
+ }
+ flags |= O_NONBLOCK;
+ if (fcntl(ctx->fd, F_SETFL, flags) == -1) {
+ logmsg(rc, log_sys_err, "fcntl(F_SETFL) failed: %s",
+ strerror(errno));
+ goto lose;
}
- switch ((pid = vfork())) {
+ switch ((ctx->pid = vfork())) {
+
case -1:
logmsg(rc, log_sys_err, "vfork() failed: %s", strerror(errno));
- close(pipe_fds[0]);
- close(pipe_fds[1]);
- return 0;
+ goto lose;
+
case 0:
-#define whine(msg) write(2, msg, sizeof(msg) - 1)
- close(pipe_fds[0]);
- if (dup2(pipe_fds[1], 1) < 0)
- whine("dup2(1) failed\n");
+ /*
+ * Child
+ */
+#define whine(msg) ((void) write(2, msg, sizeof(msg) - 1))
+ if (close(pipe_fds[0]) < 0)
+ whine("close(pipe_fds[0]) failed\n");
+ else if (dup2(pipe_fds[1], 1) < 0)
+ whine("dup2(pipe_fds[1], 1) failed\n");
else if (dup2(pipe_fds[1], 2) < 0)
- whine("dup2(2) failed\n");
+ whine("dup2(pipe_fds[1], 2) failed\n");
+ else if (close(pipe_fds[1]) < 0)
+ whine("close(pipe_fds[1]) failed\n");
else if (execvp(argv[0], (char * const *) argv) < 0)
- whine("execvp() failed\n");
+ whine("execvp(argv[0], (char * const *) argv) failed\n");
whine("last system error: ");
write(2, strerror(errno), strlen(strerror(errno)));
whine("\n");
_exit(1);
#undef whine
+
+ default:
+ /*
+ * Parent
+ */
+ (void) close(pipe_fds[1]);
+ pipe_fds[1] = -1;
+ ctx->state = rsync_state_running;
+ ctx->problem = rsync_problem_none;
+ if (rc->rsync_timeout)
+ ctx->deadline = time(0) + rc->rsync_timeout;
+ logmsg(rc, log_debug, "Subprocess %u started, queued %d, runable %d, running %d, max %d, URI %s",
+ (unsigned) ctx->pid, sk_rsync_ctx_t_num(rc->rsync_queue), rsync_count_runable(rc), rsync_count_running(rc), rc->max_parallel_fetches, ctx->uri.s);
+ if (ctx->handler)
+ ctx->handler(rc, ctx, rsync_status_pending, &ctx->uri, ctx->wsk);
+ return;
+
}
- close(pipe_fds[1]);
+ lose:
+ if (pipe_fds[0] != -1)
+ (void) close(pipe_fds[0]);
+ if (pipe_fds[1] != -1)
+ (void) close(pipe_fds[1]);
+ if (rc->rsync_queue && ctx)
+ (void) sk_rsync_ctx_t_delete_ptr(rc->rsync_queue, ctx);
+ if (ctx && ctx->handler)
+ ctx->handler(rc, ctx, rsync_status_failed, &ctx->uri, ctx->wsk);
+ if (ctx)
+ free(ctx);
+}
+
+/**
+ * Process one line of rsync's output. This is a separate function
+ * primarily to centralize scraping for magic error strings.
+ */
+static void do_one_rsync_log_line(const rcynic_ctx_t *rc,
+ rsync_ctx_t *ctx)
+{
+ unsigned u;
+ char *s;
- now = time(0);
- deadline = now + rc->rsync_timeout;
+ /*
+ * Send line to our log unless it's empty.
+ */
+ if (ctx->buffer[strspn(ctx->buffer, " \t\n\r")] != '\0')
+ logmsg(rc, log_telemetry, "rsync[%u]: %s", ctx->pid, ctx->buffer);
- n = -1;
- i = 0;
- while ((wpid = waitpid(pid, &pid_status, WNOHANG)) == 0 &&
- (!rc->rsync_timeout || (now = time(0)) < deadline)) {
- FD_ZERO(&rfds);
- FD_SET(pipe_fds[0], &rfds);
- if (rc->rsync_timeout) {
- tv.tv_sec = deadline - now;
- tv.tv_usec = 0;
- n = select(pipe_fds[0] + 1, &rfds, NULL, NULL, &tv);
- } else {
- n = select(pipe_fds[0] + 1, &rfds, NULL, NULL, NULL);
+ /*
+ * Check for magic error strings
+ */
+ if ((s = strstr(ctx->buffer, "@ERROR: max connections")) != NULL) {
+ ctx->problem = rsync_problem_refused;
+ if (sscanf(s, "@ERROR: max connections (%u) reached -- try again later", &u) == 1)
+ logmsg(rc, log_debug, "Subprocess %u reported limit of %u for %s", ctx->pid, u, ctx->uri.s);
+ }
+}
+
+/**
+ * Construct select() arguments.
+ */
+static int rsync_construct_select(const rcynic_ctx_t *rc,
+ const time_t now,
+ fd_set *rfds,
+ struct timeval *tv)
+{
+ rsync_ctx_t *ctx;
+ time_t when = 0;
+ int i, n = 0;
+
+ assert(rc && rc->rsync_queue && rfds && tv);
+
+ FD_ZERO(rfds);
+
+ for (i = 0; (ctx = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i) {
+ switch (ctx->state) {
+
+ case rsync_state_running:
+ if (ctx->fd >= 0) {
+ FD_SET(ctx->fd, rfds);
+ if (ctx->fd > n)
+ n = ctx->fd;
+ }
+ if (!rc->rsync_timeout)
+ continue;
+ /* Fall through */
+
+ case rsync_state_retry_wait:
+ if (when == 0 || ctx->deadline < when)
+ when = ctx->deadline;
+ /* Fall through */
+
+ default:
+ continue;
}
- if (n == 0 || (n < 0 && errno == EINTR))
+ }
+
+ tv->tv_sec = when ? when - now : 0;
+ tv->tv_usec = 0;
+ return n;
+}
+
+/**
+ * Manager for queue of rsync tasks in progress.
+ *
+ * General plan here is to process one completed child, or output
+ * accumulated from children, or block if there is absolutely nothing
+ * to do, on the theory that caller had nothing to do either or would
+ * not have called us. Once we've done something allegedly useful, we
+ * return, because this is not the event loop; if and when the event
+ * loop has nothing more important to do, we'll be called again.
+ *
+ * So this is the only place where the program blocks waiting for
+ * children, but we only do it when we know there's nothing else
+ * useful that we could be doing while we wait.
+ */
+static void rsync_mgr(const rcynic_ctx_t *rc)
+{
+ time_t now = time(0);
+ int i, n, pid_status = -1;
+ rsync_ctx_t *ctx = NULL;
+ struct timeval tv;
+ fd_set rfds;
+ pid_t pid;
+ char *s;
+
+ assert(rc && rc->rsync_queue);
+
+ /*
+ * Check for exited subprocesses.
+ */
+
+ while ((pid = waitpid(-1, &pid_status, WNOHANG)) > 0) {
+
+ /*
+ * Child exited, handle it.
+ */
+
+ logmsg(rc, log_debug, "Subprocess %d exited with status %d", pid, WEXITSTATUS(pid_status));
+
+ for (i = 0; (ctx = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i)
+ if (ctx->pid == pid)
+ break;
+ if (ctx == NULL) {
+ assert(i == sk_rsync_ctx_t_num(rc->rsync_queue));
+ logmsg(rc, log_sys_err, "Couldn't find rsync context for pid %d", pid);
continue;
- if (n < 0)
+ }
+
+ close(ctx->fd);
+ ctx->fd = -1;
+
+ if (ctx->buflen > 0) {
+ assert(ctx->buflen < sizeof(ctx->buffer));
+ ctx->buffer[ctx->buflen] = '\0';
+ do_one_rsync_log_line(rc, ctx);
+ ctx->buflen = 0;
+ }
+
+ switch (WEXITSTATUS(pid_status)) {
+
+ case 0:
+ log_validation_status(rc, &ctx->uri,
+ (ctx->problem == rsync_problem_timed_out
+ ? rsync_timed_out
+ : rsync_succeeded),
+ object_generation_null);
+ break;
+
+ case 5: /* "Error starting client-server protocol" */
+ /*
+ * Handle remote rsyncd refusing to talk to us because we've
+ * exceeded its connection limit. Back off for a short
+ * interval, then retry.
+ */
+ if (ctx->problem == rsync_problem_refused && ctx->tries < rc->max_retries) {
+ unsigned char r;
+ if (!RAND_bytes(&r, sizeof(r)))
+ r = 60;
+ ctx->deadline = time(0) + rc->retry_wait_min + r;
+ ctx->state = rsync_state_retry_wait;
+ ctx->problem = rsync_problem_none;
+ ctx->pid = 0;
+ ctx->tries++;
+ logmsg(rc, log_telemetry, "Scheduling retry for %s", ctx->uri.s);
+ continue;
+ }
+
+ /* Otherwise, fall through */
+
+ case 2: /* "Protocol incompatibility" */
+ case 4: /* "Requested action not supported" */
+ case 10: /* "Error in socket I/O" */
+ case 11: /* "Error in file I/O" */
+ case 12: /* "Error in rsync protocol data stream" */
+ case 21: /* "Some error returned by waitpid()" */
+ case 30: /* "Timeout in data send/receive" */
+ case 35: /* "Timeout waiting for daemon connection" */
+ logmsg(rc, log_telemetry, "Adding %s to dead host cache", ctx->uri.s);
+ dead_host_add(rc, &ctx->uri);
+
+ /* Fall through */
+
+ default:
+ logmsg(rc, log_data_err, "rsync %u exited with status %d fetching %s",
+ (unsigned) pid, WEXITSTATUS(pid_status), ctx->uri.s);
+ log_validation_status(rc, &ctx->uri,
+ (rc->rsync_timeout && now >= ctx->deadline
+ ? rsync_timed_out
+ : rsync_failed),
+ object_generation_null);
break;
- while ((n = read(pipe_fds[0], buffer + i, sizeof(buffer) - i - 1)) > 0) {
- n += i;
- assert(n < sizeof(buffer));
- buffer[n] = '\0';
- for (b = buffer; (s = strchr(b, '\n')) != NULL; b = s) {
- *s++ = '\0';
- logmsg(rc, log_telemetry, "%s", b);
+ }
+
+ rsync_cache_add(rc, &ctx->uri);
+ if (ctx->handler)
+ ctx->handler(rc, ctx, (ctx->problem == rsync_problem_timed_out
+ ? rsync_status_timed_out
+ : WEXITSTATUS(pid_status) != 0
+ ? rsync_status_failed
+ : rsync_status_done),
+ &ctx->uri, ctx->wsk);
+ (void) sk_rsync_ctx_t_delete_ptr(rc->rsync_queue, ctx);
+ free(ctx);
+ ctx = NULL;
+ }
+
+ if (pid == -1 && errno != EINTR && errno != ECHILD)
+ logmsg(rc, log_sys_err, "waitpid() returned error: %s", strerror(errno));
+
+ assert(rsync_count_running(rc) <= rc->max_parallel_fetches);
+
+ /*
+ * Look for rsync contexts that have become runable.
+ */
+ for (i = 0; (ctx = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i)
+ if (ctx->state != rsync_state_running &&
+ rsync_runable(rc, ctx) &&
+ rsync_count_running(rc) < rc->max_parallel_fetches)
+ rsync_run(rc, ctx);
+
+ assert(rsync_count_running(rc) <= rc->max_parallel_fetches);
+
+ /*
+ * Check for log text from subprocesses.
+ */
+
+ n = rsync_construct_select(rc, now, &rfds, &tv);
+
+ if (n > 0 || tv.tv_sec)
+ n = select(n + 1, &rfds, NULL, NULL, tv.tv_sec ? &tv : NULL);
+
+ if (n > 0) {
+
+ for (i = 0; (ctx = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i) {
+ if (ctx->fd <= 0 || !FD_ISSET(ctx->fd, &rfds))
+ continue;
+
+ assert(ctx->buflen < sizeof(ctx->buffer) - 1);
+
+ while ((n = read(ctx->fd, ctx->buffer + ctx->buflen, sizeof(ctx->buffer) - 1 - ctx->buflen)) > 0) {
+ ctx->buflen += n;
+ assert(ctx->buflen < sizeof(ctx->buffer));
+ ctx->buffer[ctx->buflen] = '\0';
+
+ while ((s = strchr(ctx->buffer, '\n')) != NULL) {
+ *s++ = '\0';
+ do_one_rsync_log_line(rc, ctx);
+ assert(s > ctx->buffer && s < ctx->buffer + sizeof(ctx->buffer));
+ ctx->buflen -= s - ctx->buffer;
+ assert(ctx->buflen < sizeof(ctx->buffer));
+ if (ctx->buflen > 0)
+ memmove(ctx->buffer, s, ctx->buflen);
+ ctx->buffer[ctx->buflen] = '\0';
+ }
+
+ if (ctx->buflen == sizeof(ctx->buffer) - 1) {
+ ctx->buffer[sizeof(ctx->buffer) - 1] = '\0';
+ do_one_rsync_log_line(rc, ctx);
+ ctx->buflen = 0;
+ }
}
- i = strlen(b);
- assert(i < sizeof(buffer) && b + i < buffer + sizeof(buffer));
- if (b == buffer && i == sizeof(buffer) - 1) {
- logmsg(rc, log_telemetry, "%s\\", b);
- i = 0;
+
+ if (n == 0) {
+ (void) close(ctx->fd);
+ ctx->fd = -1;
}
- if (i > 0) {
- memmove(buffer, b, i);
+ }
+ }
+
+ assert(rsync_count_running(rc) <= rc->max_parallel_fetches);
+
+ /*
+ * Deal with children that have been running too long.
+ */
+ if (rc->rsync_timeout) {
+ for (i = 0; (ctx = sk_rsync_ctx_t_value(rc->rsync_queue, i)) != NULL; ++i) {
+ int sig;
+ if (ctx->pid <= 0 || now < ctx->deadline)
+ continue;
+ sig = ctx->tries++ < KILL_MAX ? SIGTERM : SIGKILL;
+ if (ctx->state != rsync_state_terminating) {
+ ctx->problem = rsync_problem_timed_out;
+ ctx->state = rsync_state_terminating;
+ ctx->tries = 0;
+ logmsg(rc, log_telemetry, "Subprocess %u is taking too long fetching %s, whacking it", (unsigned) ctx->pid, ctx->uri.s);
+ dead_host_add(rc, &ctx->uri);
+ } else if (sig == SIGTERM) {
+ logmsg(rc, log_telemetry, "Whacking subprocess %u again", (unsigned) ctx->pid);
+ } else {
+ logmsg(rc, log_telemetry, "Whacking subprocess %u with big hammer", (unsigned) ctx->pid);
}
+ (void) kill(ctx->pid, sig);
+ ctx->deadline = now + 1;
}
- if (n == 0 || (n < 0 && errno != EAGAIN))
- break;
}
-
- close(pipe_fds[0]);
+}
+
+/**
+ * Set up rsync context and attempt to start it.
+ */
+static void rsync_init(const rcynic_ctx_t *rc,
+ const uri_t *uri,
+ STACK_OF(walk_ctx_t) *wsk,
+ void (*handler)(const rcynic_ctx_t *, const rsync_ctx_t *, const rsync_status_t, const uri_t *, STACK_OF(walk_ctx_t) *))
+{
+ rsync_ctx_t *ctx = NULL;
- assert(i >= 0 && i < sizeof(buffer));
- if (i) {
- buffer[i] = '\0';
- logmsg(rc, log_telemetry, "%s", buffer);
+ assert(rc && uri && strlen(uri->s) > SIZEOF_RSYNC);
+
+ if (!rc->run_rsync) {
+ logmsg(rc, log_verbose, "rsync disabled, skipping %s", uri->s);
+ rsync_cache_add(rc, uri);
+ if (handler)
+ handler(rc, NULL, rsync_status_skipped, uri, wsk);
+ return;
}
- if (n < 0 && errno != EAGAIN)
- logmsg(rc, log_sys_err, "Problem reading rsync's output: %s",
- strerror(errno));
+ if (rsync_cached_uri(rc, uri)) {
+ logmsg(rc, log_verbose, "rsync cache hit for %s", uri->s);
+ if (handler)
+ handler(rc, NULL, rsync_status_done, uri, wsk);
+ return;
+ }
+
+ if (dead_host_check(rc, uri)) {
+ logmsg(rc, log_verbose, "Dead host cache hit for %s", uri->s);
+ rsync_cache_add(rc, uri);
+ if (handler)
+ handler(rc, NULL, rsync_status_skipped, uri, wsk);
+ return;
+ }
- if (rc->rsync_timeout && now >= deadline)
- logmsg(rc, log_data_err,
- "Fetch of %s took longer than %d seconds, terminating fetch",
- uri, rc->rsync_timeout);
+ if ((ctx = malloc(sizeof(*ctx))) == NULL) {
+ logmsg(rc, log_sys_err, "malloc(rsync_ctxt_t) failed");
+ if (handler)
+ handler(rc, NULL, rsync_status_failed, uri, wsk);
+ return;
+ }
- assert(pid > 0);
- for (i = 0; i < KILL_MAX && wpid == 0; i++) {
- if ((wpid = waitpid(pid, &pid_status, WNOHANG)) != 0 && WIFEXITED(pid_status))
- break;
- kill(pid, SIGTERM);
- sleep(1);
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->uri = *uri;
+ ctx->handler = handler;
+ ctx->wsk = wsk;
+ ctx->fd = -1;
+
+ if (!sk_rsync_ctx_t_push(rc->rsync_queue, ctx)) {
+ logmsg(rc, log_sys_err, "Couldn't push rsync state object onto queue, punting %s", ctx->uri.s);
+ if (handler)
+ handler(rc, ctx, rsync_status_failed, uri, wsk);
+ free(ctx);
+ return;
}
- if (WEXITSTATUS(pid_status)) {
- logmsg(rc, log_data_err, "rsync exited with status %d fetching %s",
- WEXITSTATUS(pid_status), uri);
- ret = 0;
- mib_increment(rc, uri, (rc->rsync_timeout && now >= deadline
- ? rsync_timed_out
- : rsync_failed));
- } else {
- ret = 1;
- mib_increment(rc, uri, rsync_succeeded);
+ if (rsync_conflicts(rc, ctx)) {
+ logmsg(rc, log_debug, "New rsync context %s is feeling conflicted", ctx->uri.s);
+ ctx->state = rsync_state_conflict_wait;
}
- assert(strlen(uri) > SIZEOF_RSYNC);
- strcpy(buffer, uri + SIZEOF_RSYNC);
- if ((s = strrchr(buffer, '/')) != NULL && s[1] == '\0')
- *s = '\0';
- if (!sk_OPENSSL_STRING_push_strdup(rc->rsync_cache, buffer))
- logmsg(rc, log_sys_err, "Couldn't cache URI %s, blundering onward", uri);
- return ret;
+#if 0
+ if (rsync_runable(rc, ctx) && rsync_count_running(rc) < rc->max_parallel_fetches);
+ rsync_run(rc, ctx);
+#endif
}
/**
- * rsync a single file (CRL, manifest, ROA, whatever).
+ * rsync a single file (trust anchor, CRL, manifest, ROA, whatever).
*/
-static int rsync_file(const rcynic_ctx_t *rc, const char *uri)
+static void rsync_file(const rcynic_ctx_t *rc,
+ const uri_t *uri)
{
- return rsync(rc, NULL, uri);
+ assert(!endswith(uri->s, "/"));
+ rsync_init(rc, uri, NULL, NULL);
}
/**
* rsync an entire subtree, generally rooted at a SIA collection.
*/
-static int rsync_tree(const rcynic_ctx_t *rc, const char *uri)
+static void rsync_tree(const rcynic_ctx_t *rc,
+ const uri_t *uri,
+ STACK_OF(walk_ctx_t) *wsk,
+ void (*handler)(const rcynic_ctx_t *, const rsync_ctx_t *, const rsync_status_t, const uri_t *, STACK_OF(walk_ctx_t) *))
{
- static const char * const rsync_args[] = { "--recursive", "--delete", NULL };
- return rsync(rc, rsync_args, uri);
+ assert(endswith(uri->s, "/"));
+ rsync_init(rc, uri, wsk, handler);
}
@@ -1520,76 +2345,76 @@ static int rsync_tree(const rcynic_ctx_t *rc, const char *uri)
* if the URI changes and we never visit the old URI again.
*/
static int prune_unauthenticated(const rcynic_ctx_t *rc,
- const char *name,
+ const path_t *name,
const size_t baselen)
{
- char path[FILENAME_MAX];
+ path_t path;
struct dirent *d;
size_t len;
DIR *dir;
int need_slash;
assert(rc && name && baselen > 0);
- len = strlen(name);
- assert(len >= baselen && len < sizeof(path));
- need_slash = name[len - 1] != '/';
+ len = strlen(name->s);
+ assert(len >= baselen && len < sizeof(path.s));
+ need_slash = name->s[len - 1] != '/';
- if (rsync_cached_string(rc, name + baselen)) {
- logmsg(rc, log_debug, "prune: cache hit for %s, not cleaning", name);
+ if (rsync_cached_string(rc, name->s + baselen)) {
+ logmsg(rc, log_debug, "prune: cache hit for %s, not cleaning", name->s);
return 1;
}
- if (rmdir(name) == 0) {
- logmsg(rc, log_debug, "prune: removed %s", name);
+ if (rmdir(name->s) == 0) {
+ logmsg(rc, log_debug, "prune: removed %s", name->s);
return 1;
}
switch (errno) {
case ENOENT:
- logmsg(rc, log_debug, "prune: nonexistant %s", name);
+ logmsg(rc, log_debug, "prune: nonexistant %s", name->s);
return 1;
case ENOTEMPTY:
break;
default:
- logmsg(rc, log_debug, "prune: other error %s: %s", name, strerror(errno));
+ logmsg(rc, log_debug, "prune: other error %s: %s", name->s, strerror(errno));
return 0;
}
- if ((dir = opendir(name)) == NULL)
+ if ((dir = opendir(name->s)) == NULL)
return 0;
while ((d = readdir(dir)) != NULL) {
if (d->d_name[0] == '.' && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0')))
continue;
if (len + strlen(d->d_name) + need_slash >= sizeof(path)) {
- logmsg(rc, log_debug, "prune: %s%s%s too long", name, (need_slash ? "/" : ""), d->d_name);
+ logmsg(rc, log_debug, "prune: %s%s%s too long", name->s, (need_slash ? "/" : ""), d->d_name);
goto done;
}
- strcpy(path, name);
+ strcpy(path.s, name->s);
if (need_slash)
- strcat(path, "/");
- strcat(path, d->d_name);
+ strcat(path.s, "/");
+ strcat(path.s, d->d_name);
switch (d->d_type) {
case DT_DIR:
- if (!prune_unauthenticated(rc, path, baselen))
+ if (!prune_unauthenticated(rc, &path, baselen))
goto done;
continue;
default:
- if (rsync_cached_string(rc, path + baselen)) {
- logmsg(rc, log_debug, "prune: cache hit %s", path);
+ if (rsync_cached_string(rc, path.s + baselen)) {
+ logmsg(rc, log_debug, "prune: cache hit %s", path.s);
continue;
}
- if (unlink(path) < 0) {
- logmsg(rc, log_debug, "prune: removing %s failed: %s", path, strerror(errno));
+ if (unlink(path.s) < 0) {
+ logmsg(rc, log_debug, "prune: removing %s failed: %s", path.s, strerror(errno));
goto done;
}
- logmsg(rc, log_debug, "prune: removed %s", path);
+ logmsg(rc, log_debug, "prune: removed %s", path.s);
continue;
}
}
- if (rmdir(name) < 0 && errno != ENOTEMPTY)
- logmsg(rc, log_debug, "prune: couldn't remove %s: %s", name, strerror(errno));
+ if (rmdir(name->s) < 0 && errno != ENOTEMPTY)
+ logmsg(rc, log_debug, "prune: couldn't remove %s: %s", name->s, strerror(errno));
done:
closedir(dir);
@@ -1604,16 +2429,15 @@ static int prune_unauthenticated(const rcynic_ctx_t *rc,
* sets the hash buffer (if specified) as a side effect. The default
* hash algorithm is SHA-256.
*/
-static void *read_file_with_hash(const char *filename,
+static void *read_file_with_hash(const path_t *filename,
const ASN1_ITEM *it,
const EVP_MD *md,
- unsigned char *hash,
- const size_t hashlen)
+ hashbuf_t *hash)
{
void *result = NULL;
BIO *b;
- if ((b = BIO_new_file(filename, "rb")) == NULL)
+ if ((b = BIO_new_file(filename->s, "rb")) == NULL)
goto error;
if (hash != NULL) {
@@ -1634,8 +2458,8 @@ static void *read_file_with_hash(const char *filename,
goto error;
if (hash != NULL) {
- memset(hash, 0, hashlen);
- BIO_gets(b, (char *) hash, hashlen);
+ memset(hash, 0, sizeof(*hash));
+ BIO_gets(b, (char *) hash, sizeof(hash->h));
}
error:
@@ -1646,25 +2470,25 @@ static void *read_file_with_hash(const char *filename,
/**
* Read and hash a certificate.
*/
-static X509 *read_cert(const char *filename, unsigned char *hash, const size_t hashlen)
+static X509 *read_cert(const path_t *filename, hashbuf_t *hash)
{
- return read_file_with_hash(filename, ASN1_ITEM_rptr(X509), NULL, hash, hashlen);
+ return read_file_with_hash(filename, ASN1_ITEM_rptr(X509), NULL, hash);
}
/**
* Read and hash a CRL.
*/
-static X509_CRL *read_crl(const char *filename, unsigned char *hash, const size_t hashlen)
+static X509_CRL *read_crl(const path_t *filename, hashbuf_t *hash)
{
- return read_file_with_hash(filename, ASN1_ITEM_rptr(X509_CRL), NULL, hash, hashlen);
+ return read_file_with_hash(filename, ASN1_ITEM_rptr(X509_CRL), NULL, hash);
}
/**
* Read and hash a CMS message.
*/
-static CMS_ContentInfo *read_cms(const char *filename, unsigned char *hash, const size_t hashlen)
+static CMS_ContentInfo *read_cms(const path_t *filename, hashbuf_t *hash)
{
- return read_file_with_hash(filename, ASN1_ITEM_rptr(CMS_ContentInfo), NULL, hash, hashlen);
+ return read_file_with_hash(filename, ASN1_ITEM_rptr(CMS_ContentInfo), NULL, hash);
}
@@ -1673,10 +2497,10 @@ static CMS_ContentInfo *read_cms(const char *filename, unsigned char *hash, cons
* Extract CRLDP data from a certificate.
*/
static void extract_crldp_uri(const rcynic_ctx_t *rc,
- const char *uri,
+ const uri_t *uri,
+ const object_generation_t generation,
const STACK_OF(DIST_POINT) *crldp,
- char *result,
- const int resultlen)
+ uri_t *result)
{
DIST_POINT *d;
int i;
@@ -1684,17 +2508,14 @@ static void extract_crldp_uri(const rcynic_ctx_t *rc,
assert(crldp);
if (sk_DIST_POINT_num(crldp) != 1) {
- logmsg(rc, log_data_err, "CRLDistributionPoints sequence length is %d (should be 1) for %s",
- sk_DIST_POINT_num(crldp), uri);
- mib_increment(rc, uri, malformed_crldp);
+ log_validation_status(rc, uri, malformed_crldp, generation);
return;
}
d = sk_DIST_POINT_value(crldp, 0);
if (d->reasons || d->CRLissuer || !d->distpoint || d->distpoint->type != 0) {
- logmsg(rc, log_data_err, "CRLDP does not match RPKI certificate profile for %s", uri);
- mib_increment(rc, uri, malformed_crldp);
+ log_validation_status(rc, uri, malformed_crldp, generation);
return;
}
@@ -1702,22 +2523,19 @@ static void extract_crldp_uri(const rcynic_ctx_t *rc,
GENERAL_NAME *n = sk_GENERAL_NAME_value(d->distpoint->name.fullname, i);
assert(n != NULL);
if (n->type != GEN_URI) {
- logmsg(rc, log_data_err, "CRLDP contains non-URI GeneralName for %s", uri);
- mib_increment(rc, uri, malformed_crldp);
+ log_validation_status(rc, uri, malformed_crldp, generation);
return;
}
if (!is_rsync((char *) n->d.uniformResourceIdentifier->data)) {
logmsg(rc, log_verbose, "Skipping non-rsync URI %s for %s",
- (char *) n->d.uniformResourceIdentifier->data, uri);
+ (char *) n->d.uniformResourceIdentifier->data, uri->s);
continue;
}
- if (resultlen <= n->d.uniformResourceIdentifier->length) {
- logmsg(rc, log_data_err, "Skipping improbably long URI %s for %s",
- (char *) n->d.uniformResourceIdentifier->data, uri);
- mib_increment(rc, uri, uri_too_long);
+ if (sizeof(result->s) <= n->d.uniformResourceIdentifier->length) {
+ log_validation_status(rc, uri, uri_too_long, generation);
continue;
}
- strcpy(result, (char *) n->d.uniformResourceIdentifier->data);
+ strcpy(result->s, (char *) n->d.uniformResourceIdentifier->data);
return;
}
}
@@ -1726,12 +2544,12 @@ static void extract_crldp_uri(const rcynic_ctx_t *rc,
* Extract SIA or AIA data from a certificate.
*/
static void extract_access_uri(const rcynic_ctx_t *rc,
- const char *uri,
+ const uri_t *uri,
+ const object_generation_t generation,
const AUTHORITY_INFO_ACCESS *xia,
const unsigned char *oid,
const int oidlen,
- char *result,
- const int resultlen)
+ uri_t *result)
{
int i;
@@ -1747,16 +2565,14 @@ static void extract_access_uri(const rcynic_ctx_t *rc,
continue;
if (!is_rsync((char *) a->location->d.uniformResourceIdentifier->data)) {
logmsg(rc, log_verbose, "Skipping non-rsync URI %s for %s",
- a->location->d.uniformResourceIdentifier->data, uri);
+ a->location->d.uniformResourceIdentifier->data, uri->s);
continue;
}
- if (resultlen <= a->location->d.uniformResourceIdentifier->length) {
- logmsg(rc, log_data_err, "Skipping improbably long URI %s for %s",
- a->location->d.uniformResourceIdentifier->data, uri);
- mib_increment(rc, uri, uri_too_long);
+ if (sizeof(result->s) <= a->location->d.uniformResourceIdentifier->length) {
+ log_validation_status(rc, uri, uri_too_long, generation);
continue;
}
- strcpy(result, (char *) a->location->d.uniformResourceIdentifier->data);
+ strcpy(result->s, (char *) a->location->d.uniformResourceIdentifier->data);
return;
}
}
@@ -1764,7 +2580,7 @@ static void extract_access_uri(const rcynic_ctx_t *rc,
/**
* Parse interesting stuff from a certificate.
*/
-static void parse_cert(const rcynic_ctx_t *rc, X509 *x, certinfo_t *c, const char *uri)
+static void parse_cert(const rcynic_ctx_t *rc, X509 *x, certinfo_t *c, const uri_t *uri, const object_generation_t generation)
{
STACK_OF(DIST_POINT) *crldp;
AUTHORITY_INFO_ACCESS *xia;
@@ -1773,23 +2589,22 @@ static void parse_cert(const rcynic_ctx_t *rc, X509 *x, certinfo_t *c, const cha
memset(c, 0, sizeof(*c));
c->ca = X509_check_ca(x) == 1;
-
- assert(strlen(uri) < sizeof(c->uri));
- strcpy(c->uri, uri);
+ c->uri = *uri;
+ c->generation = generation;
if ((xia = X509_get_ext_d2i(x, NID_info_access, NULL, NULL)) != NULL) {
- extract_access_uri(rc, uri, xia, id_ad_caIssuers, sizeof(id_ad_caIssuers), c->aia, sizeof(c->aia));
+ extract_access_uri(rc, uri, generation, xia, id_ad_caIssuers, sizeof(id_ad_caIssuers), &c->aia);
sk_ACCESS_DESCRIPTION_pop_free(xia, ACCESS_DESCRIPTION_free);
}
if ((xia = X509_get_ext_d2i(x, NID_sinfo_access, NULL, NULL)) != NULL) {
- extract_access_uri(rc, uri, xia, id_ad_caRepository, sizeof(id_ad_caRepository), c->sia, sizeof(c->sia));
- extract_access_uri(rc, uri, xia, id_ad_rpkiManifest, sizeof(id_ad_rpkiManifest), c->manifest, sizeof(c->manifest));
+ extract_access_uri(rc, uri, generation, xia, id_ad_caRepository, sizeof(id_ad_caRepository), &c->sia);
+ extract_access_uri(rc, uri, generation, xia, id_ad_rpkiManifest, sizeof(id_ad_rpkiManifest), &c->manifest);
sk_ACCESS_DESCRIPTION_pop_free(xia, ACCESS_DESCRIPTION_free);
}
if ((crldp = X509_get_ext_d2i(x, NID_crl_distribution_points, NULL, NULL)) != NULL) {
- extract_crldp_uri(rc, uri, crldp, c->crldp, sizeof(c->crldp));
+ extract_crldp_uri(rc, uri, generation, crldp, &c->crldp);
sk_DIST_POINT_pop_free(crldp, DIST_POINT_free);
}
}
@@ -1801,37 +2616,53 @@ static void parse_cert(const rcynic_ctx_t *rc, X509 *x, certinfo_t *c, const cha
*/
static X509_CRL *check_crl_1(const rcynic_ctx_t *rc,
- const char *uri,
- char *path, const int pathlen,
- const char *prefix,
+ const uri_t *uri,
+ path_t *path,
+ const path_t *prefix,
X509 *issuer,
const unsigned char *hash,
- const size_t hashlen)
+ const size_t hashlen,
+ const object_generation_t generation)
{
- unsigned char hashbuf[EVP_MAX_MD_SIZE];
+ hashbuf_t hashbuf;
X509_CRL *crl = NULL;
EVP_PKEY *pkey;
int ret;
- assert(uri && path && issuer && hashlen <= sizeof(hashbuf));
+ assert(uri && path && issuer);
- if (!uri_to_filename(rc, uri, path, pathlen, prefix))
+ if (!uri_to_filename(rc, uri, path, prefix))
goto punt;
+ if (hashlen > sizeof(hashbuf.h)) {
+ log_validation_status(rc, uri, hash_too_long, generation);
+ goto punt;
+ }
+
if (hash)
- crl = read_crl(path, hashbuf, sizeof(hashbuf));
+ crl = read_crl(path, &hashbuf);
else
- crl = read_crl(path, NULL, 0);
+ crl = read_crl(path, NULL);
if (!crl)
goto punt;
- if (hash && memcmp(hashbuf, hash, hashlen)) {
- reject(rc, uri, crl_digest_mismatch,
- "because digest of CRL did not match value from manifest");
+ if (hash && memcmp(hashbuf.h, hash, hashlen)) {
+ log_validation_status(rc, uri, crl_digest_mismatch, generation);
+ goto punt;
+ }
+
+ if (X509_cmp_current_time(X509_CRL_get_lastUpdate(crl)) > 0) {
+ log_validation_status(rc, uri, crl_not_yet_valid, generation);
goto punt;
}
+ if (X509_cmp_current_time(X509_CRL_get_nextUpdate(crl)) < 0) {
+ log_validation_status(rc, uri, stale_crl, generation);
+ if (!rc->allow_stale_crl)
+ goto punt;
+ }
+
if ((pkey = X509_get_pubkey(issuer)) == NULL)
goto punt;
ret = X509_CRL_verify(crl, pkey);
@@ -1850,38 +2681,34 @@ static X509_CRL *check_crl_1(const rcynic_ctx_t *rc,
* and check issuer's signature if we don't.
*/
static X509_CRL *check_crl(const rcynic_ctx_t *rc,
- const char *uri,
+ const uri_t *uri,
X509 *issuer,
const unsigned char *hash,
const size_t hashlen)
{
- char path[FILENAME_MAX];
+ path_t path;
X509_CRL *crl;
- if (uri_to_filename(rc, uri, path, sizeof(path), rc->authenticated) &&
- (crl = read_crl(path, NULL, 0)) != NULL)
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
+ (crl = read_crl(&path, NULL)) != NULL)
return crl;
- logmsg(rc, log_telemetry, "Checking CRL %s", uri);
-
- assert(rsync_cached_uri(rc, uri));
+ logmsg(rc, log_telemetry, "Checking CRL %s", uri->s);
- if ((crl = check_crl_1(rc, uri, path, sizeof(path), rc->unauthenticated,
- issuer, hash, hashlen))) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, current_crl_accepted);
+ if ((crl = check_crl_1(rc, uri, &path, &rc->unauthenticated,
+ issuer, hash, hashlen, object_generation_current))) {
+ install_object(rc, uri, &path, object_accepted, object_generation_current);
return crl;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, current_crl_rejected);
+ } else if (!access(path.s, F_OK)) {
+ log_validation_status(rc, uri, object_rejected, object_generation_current);
}
- if ((crl = check_crl_1(rc, uri, path, sizeof(path), rc->old_authenticated,
- issuer, hash, hashlen))) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, backup_crl_accepted);
+ if ((crl = check_crl_1(rc, uri, &path, &rc->old_authenticated,
+ issuer, hash, hashlen, object_generation_backup))) {
+ install_object(rc, uri, &path, object_accepted, object_generation_backup);
return crl;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, backup_crl_rejected);
+ } else if (!access(path.s, F_OK)) {
+ log_validation_status(rc, uri, object_rejected, object_generation_backup);
}
return NULL;
@@ -1890,12 +2717,93 @@ static X509_CRL *check_crl(const rcynic_ctx_t *rc,
/**
+ * Check whether extensions in a certificate are allowed by profile.
+ * Also returns failure in a few null-pointer cases that can't
+ * possibly conform to profile.
+ */
+static int check_allowed_extensions(const X509 *x, const int allow_eku)
+{
+ int i;
+
+ if (x == NULL || x->cert_info == NULL || x->cert_info->extensions == NULL)
+ return 0;
+
+ for (i = 0; i < sk_X509_EXTENSION_num(x->cert_info->extensions); i++) {
+ switch (OBJ_obj2nid(sk_X509_EXTENSION_value(x->cert_info->extensions,
+ i)->object)) {
+ case NID_basic_constraints:
+ case NID_subject_key_identifier:
+ case NID_authority_key_identifier:
+ case NID_key_usage:
+ case NID_crl_distribution_points:
+ case NID_info_access:
+ case NID_sinfo_access:
+ case NID_certificate_policies:
+ case NID_sbgp_ipAddrBlock:
+ case NID_sbgp_autonomousSysNum:
+ continue;
+ case NID_ext_key_usage:
+ if (allow_eku)
+ continue;
+ else
+ return 0;
+ default:
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/**
+ * Check whether a Distinguished Name conforms to the rescert profile.
+ * The profile is very restrictive: it only allows one mandatory
+ * CommonName field and one optional SerialNumber field, both of which
+ * must be of type PrintableString.
+ */
+static int check_allowed_dn(X509_NAME *dn)
+{
+ X509_NAME_ENTRY *ne;
+ ASN1_STRING *s;
+ int loc;
+
+ if (dn == NULL)
+ return 0;
+
+ switch (X509_NAME_entry_count(dn)) {
+
+ case 2:
+ if ((loc = X509_NAME_get_index_by_NID(dn, NID_serialNumber, -1)) < 0 ||
+ (ne = X509_NAME_get_entry(dn, loc)) == NULL ||
+ (s = X509_NAME_ENTRY_get_data(ne)) == NULL ||
+ ASN1_STRING_type(s) != V_ASN1_PRINTABLESTRING)
+ return 0;
+
+ /* Fall through */
+
+ case 1:
+ if ((loc = X509_NAME_get_index_by_NID(dn, NID_commonName, -1)) < 0 ||
+ (ne = X509_NAME_get_entry(dn, loc)) == NULL ||
+ (s = X509_NAME_ENTRY_get_data(ne)) == NULL ||
+ ASN1_STRING_type(s) != V_ASN1_PRINTABLESTRING)
+ return 0;
+
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+
+
+/**
* Validation callback function for use with x509_verify_cert().
*/
static int check_x509_cb(int ok, X509_STORE_CTX *ctx)
{
rcynic_x509_store_ctx_t *rctx = (rcynic_x509_store_ctx_t *) ctx;
- mib_counter_t counter;
+ mib_counter_t code;
assert(rctx != NULL);
@@ -1913,25 +2821,20 @@ static int check_x509_cb(int ok, X509_STORE_CTX *ctx)
case X509_V_ERR_CRL_HAS_EXPIRED:
/*
- * This may not be an error at all. CRLs don't really "expire",
- * although the signatures over them do. What OpenSSL really
- * means by this error is just "it's now later than this source
- * said it intended to publish a new CRL. Unclear whether this
- * should be an error; current theory is that it should not be.
+ * This isn't really an error, exactly. CRLs don't really
+ * "expire". What OpenSSL really means by this error is just
+ * "it's now later than the issuer said it intended to publish a
+ * new CRL". Whether we treat this as an error or not is
+ * configurable, see the allow_stale_crl parameter.
+ *
+ * Deciding whether to allow stale CRLs is check_crl_1()'s job,
+ * not ours. By the time this callback occurs, we've already
+ * accepted the CRL; this callback is just notifying us that the
+ * object being checked is tainted by a stale CRL. So we mark the
+ * object as tainted and carry on.
*/
- if (rctx->rc->allow_stale_crl) {
- ok = 1;
- if (sk_OPENSSL_STRING_find(rctx->rc->stale_cache, rctx->subject->crldp) >= 0)
- return ok;
- if (!sk_OPENSSL_STRING_push_strdup(rctx->rc->stale_cache, rctx->subject->crldp))
- logmsg(rctx->rc, log_sys_err,
- "Couldn't cache stale CRLDP %s, blundering onward", rctx->subject->crldp);
- }
- logmsg(rctx->rc, log_data_err, "Stale CRL %s", rctx->subject->crldp);
- if (ok)
- mib_increment(rctx->rc, rctx->subject->uri, stale_crl);
- else
- reject(rctx->rc, rctx->subject->uri, stale_crl, "due to stale CRL %s", rctx->subject->crldp);
+ log_validation_status(rctx->rc, &rctx->subject->uri, tainted_by_stale_crl, rctx->subject->generation);
+ ok = 1;
return ok;
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
@@ -1950,11 +2853,7 @@ static int check_x509_cb(int ok, X509_STORE_CTX *ctx)
*/
if (rctx->rc->allow_non_self_signed_trust_anchor)
ok = 1;
- if (ok)
- mib_increment(rctx->rc, rctx->subject->uri, trust_anchor_not_self_signed);
- else
- reject(rctx->rc, rctx->subject->uri, trust_anchor_not_self_signed,
- "because trust anchor was not self-signed");
+ log_validation_status(rctx->rc, &rctx->subject->uri, trust_anchor_not_self_signed, rctx->subject->generation);
return ok;
/*
@@ -1964,106 +2863,144 @@ static int check_x509_cb(int ok, X509_STORE_CTX *ctx)
*/
#define QV(x) \
case x: \
- counter = mib_openssl_##x; \
+ code = mib_openssl_##x; \
break;
MIB_COUNTERS_FROM_OPENSSL;
#undef QV
default:
- counter = unknown_verify_error;
+ code = unknown_verify_error;
break;
}
- if (ok)
- mib_increment(rctx->rc, rctx->subject->uri, counter);
- else
- reject(rctx->rc, rctx->subject->uri, counter,
- "due to validation failure at depth %d: %s",
- ctx->error_depth,
- X509_verify_cert_error_string(ctx->error));
-
+ log_validation_status(rctx->rc, &rctx->subject->uri, code, rctx->subject->generation);
return ok;
}
/**
- * Check crypto aspects of a certificate, including policy checks
- * and RFC 3779 path validation.
+ * Check crypto aspects of a certificate, policy OID, RFC 3779 path
+ * validation, and conformance to the RPKI certificate profile.
*/
static int check_x509(const rcynic_ctx_t *rc,
STACK_OF(X509) *certs,
X509 *x,
- const certinfo_t *subject)
+ const certinfo_t *subject,
+ const certinfo_t *issuer_certinfo)
{
rcynic_x509_store_ctx_t rctx;
STACK_OF(X509_CRL) *crls = NULL;
EVP_PKEY *pkey = NULL;
X509_CRL *crl = NULL;
+ unsigned long flags = (X509_V_FLAG_POLICY_CHECK | X509_V_FLAG_EXPLICIT_POLICY | X509_V_FLAG_X509_STRICT);
X509 *issuer;
int ret = 0;
- assert(rc && certs && x && subject && subject->crldp[0]);
-
- issuer = sk_X509_value(certs, sk_X509_num(certs) - 1);
- assert(issuer != NULL);
+ assert(rc && certs && x && subject);
if (!X509_STORE_CTX_init(&rctx.ctx, rc->x509_store, x, NULL))
return 0;
rctx.rc = rc;
rctx.subject = subject;
+ issuer = sk_X509_value(certs, sk_X509_num(certs) - 1);
+ assert(issuer != NULL);
+
+ if (subject->sia.s[0] && subject->sia.s[strlen(subject->sia.s) - 1] != '/') {
+ log_validation_status(rc, &subject->uri, malformed_sia, subject->generation);
+ goto done;
+ }
+
+ if (!subject->ta && !subject->aia.s[0]) {
+ log_validation_status(rc, &subject->uri, aia_missing, subject->generation);
+ goto done;
+ }
+
+ if (!issuer_certinfo->ta && strcmp(issuer_certinfo->uri.s, subject->aia.s)) {
+ log_validation_status(rc, &subject->uri, aia_mismatch, subject->generation);
+ goto done;
+ }
+
+ if (subject->ca && !subject->sia.s[0]) {
+ log_validation_status(rc, &subject->uri, sia_missing, subject->generation);
+ goto done;
+ }
+
+ if (subject->ca && !subject->manifest.s[0]) {
+ log_validation_status(rc, &subject->uri, manifest_missing, subject->generation);
+ goto done;
+ }
+
+ if (subject->ca && !startswith(subject->manifest.s, subject->sia.s)) {
+ log_validation_status(rc, &subject->uri, manifest_mismatch, subject->generation);
+ goto done;
+ }
+
+ if (!check_allowed_extensions(x, !subject->ca)) {
+ log_validation_status(rc, &subject->uri, disallowed_extension, subject->generation);
+ goto done;
+ }
+
+ if (!check_allowed_dn(X509_get_subject_name(x)))
+ log_validation_status(rc, &subject->uri, nonconformant_subject_name, subject->generation);
+
+ if (!check_allowed_dn(X509_get_issuer_name(x)))
+ log_validation_status(rc, &subject->uri, nonconformant_issuer_name, subject->generation);
+
if (subject->ta) {
- if (subject->crldp[0]) {
- reject(rc, subject->uri, trust_anchor_with_crldp,
- "because it's a trust anchor but has a CRLDP extension");
+ if (subject->crldp.s[0]) {
+ log_validation_status(rc, &subject->uri, trust_anchor_with_crldp, subject->generation);
goto done;
}
} else {
+ if (!subject->crldp.s[0]) {
+ log_validation_status(rc, &subject->uri, crldp_missing, subject->generation);
+ goto done;
+ }
+
+ if (!subject->ca && !startswith(subject->crldp.s, issuer_certinfo->sia.s)) {
+ log_validation_status(rc, &subject->uri, crldp_mismatch, subject->generation);
+ goto done;
+ }
+
+ flags |= X509_V_FLAG_CRL_CHECK;
+
if ((pkey = X509_get_pubkey(issuer)) == NULL || X509_verify(x, pkey) <= 0) {
- reject(rc, subject->uri, certificate_bad_signature,
- "because it failed signature check prior to CRL fetch");
+ log_validation_status(rc, &subject->uri, certificate_bad_signature, subject->generation);
goto done;
}
- if ((crl = check_crl(rc, subject->crldp, issuer, NULL, 0)) == NULL) {
- reject(rc, subject->uri, certificate_bad_crl,
- "due to bad CRL %s", subject->crldp);
+ if ((crl = check_crl(rc, &subject->crldp, issuer, NULL, 0)) == NULL) {
+ log_validation_status(rc, &subject->uri, certificate_bad_crl, subject->generation);
goto done;
}
- }
- if ((crls = sk_X509_CRL_new_null()) == NULL ||
- !sk_X509_CRL_push(crls, crl)) {
- logmsg(rc, log_sys_err,
- "Internal allocation error setting up CRL for validation");
- goto done;
+ if ((crls = sk_X509_CRL_new_null()) == NULL || !sk_X509_CRL_push(crls, crl)) {
+ logmsg(rc, log_sys_err,
+ "Internal allocation error setting up CRL for validation");
+ goto done;
+ }
+ crl = NULL;
+
+ X509_STORE_CTX_set0_crls(&rctx.ctx, crls);
+
}
- crl = NULL;
X509_STORE_CTX_trusted_stack(&rctx.ctx, certs);
- X509_STORE_CTX_set0_crls(&rctx.ctx, crls);
X509_STORE_CTX_set_verify_cb(&rctx.ctx, check_x509_cb);
- X509_VERIFY_PARAM_set_flags(rctx.ctx.param,
- X509_V_FLAG_CRL_CHECK |
- X509_V_FLAG_POLICY_CHECK |
- X509_V_FLAG_EXPLICIT_POLICY |
- X509_V_FLAG_X509_STRICT);
+ X509_VERIFY_PARAM_set_flags(rctx.ctx.param, flags);
X509_VERIFY_PARAM_add0_policy(rctx.ctx.param, OBJ_txt2obj(rpki_policy_oid, 1));
- if (X509_verify_cert(&rctx.ctx) <= 0) {
- /*
- * Redundant error message?
- */
- logmsg(rc, log_data_err, "Validation failure for %s",
- subject->uri[0] ? subject->uri : subject->ta ? "[Trust anchor]" : "[???]");
+ if (X509_verify_cert(&rctx.ctx) <= 0) {
+ log_validation_status(rc, &subject->uri, certificate_failed_validation, subject->generation);
goto done;
}
- ret = 1;
+ ret = 1;
done:
sk_X509_CRL_pop_free(crls, X509_CRL_free);
@@ -2075,152 +3012,55 @@ static int check_x509(const rcynic_ctx_t *rc,
}
/**
- * Check whether extensions in a certificate are allowed by profile.
- * Also returns failure in a few null-pointer cases that can't
- * possibly conform to profile.
- */
-static int check_cert_only_allowed_extensions(const X509 *x, const int allow_eku)
-{
- int i;
-
- if (x == NULL || x->cert_info == NULL || x->cert_info->extensions == NULL)
- return 0;
-
- for (i = 0; i < sk_X509_EXTENSION_num(x->cert_info->extensions); i++) {
- switch (OBJ_obj2nid(sk_X509_EXTENSION_value(x->cert_info->extensions,
- i)->object)) {
- case NID_basic_constraints:
- case NID_subject_key_identifier:
- case NID_authority_key_identifier:
- case NID_key_usage:
- case NID_crl_distribution_points:
- case NID_info_access:
- case NID_sinfo_access:
- case NID_certificate_policies:
- case NID_sbgp_ipAddrBlock:
- case NID_sbgp_autonomousSysNum:
- continue;
- case NID_ext_key_usage:
- if (allow_eku)
- continue;
- else
- return 0;
- default:
- return 0;
- }
- }
-
- return 1;
-}
-
-/**
- * Check a certificate for conformance to the RPKI certificate profile.
+ * Load certificate, check against manifest, then run it through all
+ * the check_x509() tests.
*/
static X509 *check_cert_1(const rcynic_ctx_t *rc,
- const char *uri,
- char *path,
- const int pathlen,
- const char *prefix,
+ const uri_t *uri,
+ path_t *path,
+ const path_t *prefix,
STACK_OF(X509) *certs,
const certinfo_t *issuer,
certinfo_t *subject,
const unsigned char *hash,
- const size_t hashlen)
+ const size_t hashlen,
+ object_generation_t generation)
{
- unsigned char hashbuf[EVP_MAX_MD_SIZE];
+ hashbuf_t hashbuf;
X509 *x = NULL;
assert(uri && path && certs && issuer && subject);
- if (!uri_to_filename(rc, uri, path, pathlen, prefix)) {
- logmsg(rc, log_data_err, "Can't convert URI %s to filename", uri);
+ if (!uri_to_filename(rc, uri, path, prefix))
return NULL;
- }
- if (access(path, R_OK))
+ if (access(path->s, R_OK))
return NULL;
- if (hash)
- x = read_cert(path, hashbuf, sizeof(hashbuf));
- else
- x = read_cert(path, NULL, 0);
-
- if (!x) {
- logmsg(rc, log_sys_err, "Can't read certificate %s", path);
- goto punt;
- }
-
- if (hash && memcmp(hashbuf, hash, hashlen)) {
- reject(rc, uri, certificate_digest_mismatch,
- "because digest did not match value in manifest");
- goto punt;
- }
-
- parse_cert(rc, x, subject, uri);
-
- if (subject->sia[0] && subject->sia[strlen(subject->sia) - 1] != '/') {
- reject(rc, uri, malformed_sia,
- "due to malformed SIA %s", subject->sia);
- goto punt;
- }
-
- if (!subject->aia[0]) {
- reject(rc, uri, aia_missing, "due to missing AIA extension");
- goto punt;
- }
-
- if (!issuer->ta && strcmp(issuer->uri, subject->aia)) {
- reject(rc, uri, aia_mismatch,
- "because AIA %s doesn't match parent", subject->aia);
- goto punt;
- }
-
- if (subject->ca && !subject->sia[0]) {
- reject(rc, uri, sia_missing,
- "because SIA extension repository pointer is missing");
- goto punt;
- }
-
- if (!subject->crldp[0]) {
- reject(rc, uri, crldp_missing, "because CRLDP extension is missing");
+ if (hashlen > sizeof(hashbuf.h)) {
+ log_validation_status(rc, uri, hash_too_long, generation);
goto punt;
}
- if (subject->ca && !startswith(subject->crldp, issuer->sia)) {
- reject(rc, uri, crldp_mismatch,
- "because CRLDP %s points outside issuer's publication point %s",
- subject->crldp, issuer->sia);
- goto punt;
- }
+ if (hash)
+ x = read_cert(path, &hashbuf);
+ else
+ x = read_cert(path, NULL);
- if (subject->ca && !subject->manifest[0]) {
- reject(rc, uri, manifest_missing,
- "because SIA extension manifest pointer is missing");
+ if (!x) {
+ logmsg(rc, log_sys_err, "Can't read certificate %s", path->s);
goto punt;
}
- if (subject->ca && !startswith(subject->manifest, subject->sia)) {
- reject(rc, uri, manifest_mismatch,
- "because SIA manifest %s points outside publication point %s",
- subject->manifest, subject->sia);
+ if (hash && memcmp(hashbuf.h, hash, hashlen)) {
+ log_validation_status(rc, uri, certificate_digest_mismatch, generation);
goto punt;
}
- if (!check_cert_only_allowed_extensions(x, !subject->ca)) {
- reject(rc, uri, disallowed_extension,
- "due to disallowed X.509v3 extension");
- goto punt;
- }
+ parse_cert(rc, x, subject, uri, generation);
- if (!check_x509(rc, certs, x, subject)) {
- /*
- * Redundant error message?
- */
- logmsg(rc, log_data_err, "Certificate %s failed validation", uri);
- goto punt;
- }
-
- return x;
+ if (check_x509(rc, certs, x, subject, issuer))
+ return x;
punt:
X509_free(x);
@@ -2232,53 +3072,69 @@ static X509 *check_cert_1(const rcynic_ctx_t *rc,
* backup data from a previous run of this program.
*/
static X509 *check_cert(rcynic_ctx_t *rc,
- char *uri,
- STACK_OF(X509) *certs,
- const certinfo_t *issuer,
+ uri_t *uri,
+ STACK_OF(walk_ctx_t) *wsk,
certinfo_t *subject,
- const char *prefix,
- const int backup,
const unsigned char *hash,
const size_t hashlen)
{
- char path[FILENAME_MAX];
+ walk_ctx_t *w = walk_ctx_stack_head(wsk);
+ object_generation_t generation;
+ const certinfo_t *issuer = NULL;
+ STACK_OF(X509) *certs = NULL;
+ const path_t *prefix = NULL;
+ path_t path;
X509 *x;
- assert(rc && uri && certs && issuer && subject && prefix);
+ assert(rc && uri && wsk && w && subject);
+
+ issuer = &w->certinfo;
+
+ switch (w->state) {
+ case walk_state_current:
+ prefix = &rc->unauthenticated;
+ generation = object_generation_current;
+ break;
+ case walk_state_backup:
+ prefix = &rc->old_authenticated;
+ generation = object_generation_backup;
+ break;
+ default:
+ return NULL;
+ }
/*
* If target file already exists and we're not here to recheck with
* better data, just get out now.
*/
- if (uri_to_filename(rc, uri, path, sizeof(path), rc->authenticated) &&
- !access(path, R_OK)) {
- if (backup || sk_OPENSSL_STRING_find(rc->backup_cache, uri) < 0)
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
+ !access(path.s, R_OK)) {
+ if (w->state == walk_state_backup || sk_OPENSSL_STRING_find(rc->backup_cache, uri->s) < 0)
return NULL;
- mib_increment(rc, uri, current_cert_recheck);
- logmsg(rc, log_telemetry, "Rechecking %s", uri);
+ assert(generation == object_generation_current);
+ log_validation_status(rc, uri, current_cert_recheck, generation);
+ logmsg(rc, log_telemetry, "Rechecking %s", uri->s);
} else {
- logmsg(rc, log_telemetry, "Checking %s", uri);
+ logmsg(rc, log_telemetry, "Checking %s", uri->s);
}
- rc->indent++;
+ if ((certs = walk_ctx_stack_certs(wsk)) == NULL)
+ return NULL;
- if ((x = check_cert_1(rc, uri, path, sizeof(path), prefix,
- certs, issuer, subject, hash, hashlen)) != NULL) {
- install_object(rc, uri, path);
- mib_increment(rc, uri,
- (backup ? backup_cert_accepted : current_cert_accepted));
- if (!backup)
- sk_OPENSSL_STRING_remove(rc->backup_cache, uri);
- else if (!sk_OPENSSL_STRING_push_strdup(rc->backup_cache, uri))
- logmsg(rc, log_sys_err, "Couldn't cache URI %s, blundering onward", uri);
+ if ((x = check_cert_1(rc, uri, &path, prefix, certs, issuer, subject, hash, hashlen, generation)) != NULL) {
+ install_object(rc, uri, &path, object_accepted, generation);
+ if (w->state == walk_state_current)
+ sk_OPENSSL_STRING_remove(rc->backup_cache, uri->s);
+ else if (!sk_OPENSSL_STRING_push_strdup(rc->backup_cache, uri->s))
+ logmsg(rc, log_sys_err, "Couldn't cache URI %s, blundering onward", uri->s);
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri,
- (backup ? backup_cert_rejected : current_cert_rejected));
+ } else if (!access(path.s, F_OK)) {
+ log_validation_status(rc, uri, object_rejected, generation);
}
- rc->indent--;
+ sk_X509_free(certs);
+ certs = NULL;
return x;
}
@@ -2289,11 +3145,11 @@ static X509 *check_cert(rcynic_ctx_t *rc,
* Read and check one manifest from disk.
*/
static Manifest *check_manifest_1(const rcynic_ctx_t *rc,
- const char *uri,
- char *path,
- const int pathlen,
- const char *prefix,
- STACK_OF(X509) *certs)
+ const uri_t *uri,
+ path_t *path,
+ const path_t *prefix,
+ STACK_OF(X509) *certs,
+ const object_generation_t generation)
{
CMS_ContentInfo *cms = NULL;
const ASN1_OBJECT *eContentType = NULL;
@@ -2310,78 +3166,63 @@ static Manifest *check_manifest_1(const rcynic_ctx_t *rc,
assert(rc && uri && path && prefix && certs && sk_X509_num(certs));
- if (!uri_to_filename(rc, uri, path, pathlen, prefix) ||
- (cms = read_cms(path, NULL, 0)) == NULL)
+ if (!uri_to_filename(rc, uri, path, prefix) ||
+ (cms = read_cms(path, NULL)) == NULL)
goto done;
if ((eContentType = CMS_get0_eContentType(cms)) == NULL ||
oid_cmp(eContentType, id_ct_rpkiManifest, sizeof(id_ct_rpkiManifest))) {
- reject(rc, uri, manifest_bad_econtenttype,
- "due to bad manifest eContentType");
+ log_validation_status(rc, uri, manifest_bad_econtenttype, generation);
goto done;
}
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
- logmsg(rc, log_sys_err, "Couldn't allocate BIO for manifest %s", uri);
+ logmsg(rc, log_sys_err, "Couldn't allocate BIO for manifest %s", uri->s);
goto done;
}
if (CMS_verify(cms, NULL, NULL, NULL, bio, CMS_NO_SIGNER_CERT_VERIFY) <= 0) {
- reject(rc, uri, manifest_invalid_cms,
- "due to validation failure for manifest CMS message");
+ log_validation_status(rc, uri, manifest_invalid_cms, generation);
goto done;
}
if ((signers = CMS_get0_signers(cms)) == NULL || sk_X509_num(signers) != 1) {
- reject(rc, uri, manifest_missing_signer,
- "because could not couldn't extract manifest EE certificate from CMS");
+ log_validation_status(rc, uri, manifest_missing_signer, generation);
goto done;
}
- parse_cert(rc, sk_X509_value(signers, 0), &certinfo, uri);
+ parse_cert(rc, sk_X509_value(signers, 0), &certinfo, uri, generation);
- if (!certinfo.crldp[0]) {
- reject(rc, uri, manifest_missing_crldp,
- "due to missing CRLDP in manifest EE certificate");
+ if (!certinfo.crldp.s[0]) {
+ log_validation_status(rc, uri, manifest_missing_crldp, generation);
goto done;
}
- if ((crl_tail = strrchr(certinfo.crldp, '/')) == NULL) {
- reject(rc, uri, manifest_malformed_crldp,
- "due to malformed CRLDP %s in manifest EE certificate",
- certinfo.crldp);
+ if ((crl_tail = strrchr(certinfo.crldp.s, '/')) == NULL) {
+ log_validation_status(rc, uri, manifest_malformed_crldp, generation);
goto done;
}
crl_tail++;
if ((manifest = ASN1_item_d2i_bio(ASN1_ITEM_rptr(Manifest), bio, NULL)) == NULL) {
- reject(rc, uri, manifest_decode_error, "because unable to decode manifest");
+ log_validation_status(rc, uri, manifest_decode_error, generation);
goto done;
}
if (manifest->version) {
- reject(rc, uri, manifest_wrong_version,
- "because manifest version should be defaulted zero, not %ld",
- ASN1_INTEGER_get(manifest->version));
+ log_validation_status(rc, uri, manifest_wrong_version, generation);
goto done;
}
if (X509_cmp_current_time(manifest->thisUpdate) > 0) {
- reject(rc, uri, manifest_not_yet_valid, "because manifest not yet valid");
+ log_validation_status(rc, uri, manifest_not_yet_valid, generation);
goto done;
}
- if (X509_cmp_current_time(manifest->nextUpdate) < 0 &&
- sk_OPENSSL_STRING_find(rc->stale_cache, uri) < 0) {
- if (!sk_OPENSSL_STRING_push_strdup(rc->stale_cache, uri))
- logmsg(rc, log_sys_err, "Couldn't cache stale manifest %s, blundering onward", uri);
- if (!rc->allow_stale_manifest) {
- reject(rc, uri, stale_manifest,
- "because it is a stale manifest");
+ if (X509_cmp_current_time(manifest->nextUpdate) < 0) {
+ log_validation_status(rc, uri, stale_manifest, generation);
+ if (!rc->allow_stale_manifest)
goto done;
- }
- logmsg(rc, log_data_err, "Stale manifest %s", uri);
- mib_increment(rc, uri, stale_manifest);
}
if (manifest->fileHashAlg == NULL ||
@@ -2393,23 +3234,20 @@ static Manifest *check_manifest_1(const rcynic_ctx_t *rc,
break;
if (fah) {
- crl = check_crl(rc, certinfo.crldp, sk_X509_value(certs, sk_X509_num(certs) - 1),
+ crl = check_crl(rc, &certinfo.crldp,
+ sk_X509_value(certs, sk_X509_num(certs) - 1),
fah->hash->data, fah->hash->length);
- } else if (rc->require_crl_in_manifest) {
- reject(rc, uri, crl_not_in_manifest,
- "because CRL %s missing from manifest", certinfo.crldp);
- goto done;
} else {
- logmsg(rc, log_data_err, "Manifest %s is missing entry for CRL %s", uri, certinfo.crldp);
- mib_increment(rc, uri, crl_not_in_manifest);
- crl = check_crl(rc, certinfo.crldp,
- sk_X509_value(certs, sk_X509_num(certs) - 1), NULL, 0);
+ log_validation_status(rc, uri, crl_not_in_manifest, generation);
+ if (rc->require_crl_in_manifest)
+ goto done;
+ crl = check_crl(rc, &certinfo.crldp,
+ sk_X509_value(certs, sk_X509_num(certs) - 1),
+ NULL, 0);
}
- if (!crl) {
- reject(rc, uri, manifest_bad_crl, "due to bad manifest CRL %s", certinfo.crldp);
+ if (!crl)
goto done;
- }
if ((crls = sk_X509_CRL_new_null()) == NULL || !sk_X509_CRL_push(crls, crl))
goto done;
@@ -2437,8 +3275,7 @@ static Manifest *check_manifest_1(const rcynic_ctx_t *rc,
/*
* Redundant error message?
*/
- logmsg(rc, log_data_err, "Validation failure for manifest %s EE certificate",uri);
- mib_increment(rc, uri, manifest_invalid_ee);
+ log_validation_status(rc, uri, manifest_invalid_ee, generation);
goto done;
}
@@ -2462,16 +3299,22 @@ static Manifest *check_manifest_1(const rcynic_ctx_t *rc,
* and check issuer's signature if we don't.
*/
static Manifest *check_manifest(const rcynic_ctx_t *rc,
- const char *uri,
- STACK_OF(X509) *certs)
+ STACK_OF(walk_ctx_t) *wsk)
{
+ walk_ctx_t *w = walk_ctx_stack_head(wsk);
CMS_ContentInfo *cms = NULL;
Manifest *manifest = NULL;
- char path[FILENAME_MAX];
+ STACK_OF(X509) *certs = NULL;
BIO *bio = NULL;
+ path_t path;
+ uri_t *uri;
+
+ assert(rc && wsk && w);
- if (uri_to_filename(rc, uri, path, sizeof(path), rc->authenticated) &&
- (cms = read_cms(path, NULL, 0)) != NULL &&
+ uri = &w->certinfo.manifest;
+
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
+ (cms = read_cms(&path, NULL)) != NULL &&
(bio = BIO_new(BIO_s_mem()))!= NULL &&
CMS_verify(cms, NULL, NULL, NULL, bio,
CMS_NO_SIGNER_CERT_VERIFY |
@@ -2485,29 +3328,31 @@ static Manifest *check_manifest(const rcynic_ctx_t *rc,
if (manifest != NULL)
return manifest;
- logmsg(rc, log_telemetry, "Checking manifest %s", uri);
+ logmsg(rc, log_telemetry, "Checking manifest %s", uri->s);
- assert(rsync_cached_uri(rc, uri));
+ if ((certs = walk_ctx_stack_certs(wsk)) == NULL)
+ return NULL;
- if ((manifest = check_manifest_1(rc, uri, path, sizeof(path),
- rc->unauthenticated, certs))) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, current_manifest_accepted);
- return manifest;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, current_manifest_rejected);
+ if (manifest == NULL) {
+ if ((manifest = check_manifest_1(rc, uri, &path,
+ &rc->unauthenticated, certs, object_generation_current)) != NULL)
+ install_object(rc, uri, &path, object_accepted, object_generation_current);
+ else if (!access(path.s, F_OK))
+ log_validation_status(rc, uri, object_rejected, object_generation_current);
}
- if ((manifest = check_manifest_1(rc, uri, path, sizeof(path),
- rc->old_authenticated, certs))) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, backup_manifest_accepted);
- return manifest;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, backup_manifest_rejected);
+ if (manifest == NULL) {
+ if ((manifest = check_manifest_1(rc, uri, &path,
+ &rc->old_authenticated, certs, object_generation_backup)) != NULL)
+ install_object(rc, uri, &path, object_accepted, object_generation_backup);
+ else if (!access(path.s, F_OK))
+ log_validation_status(rc, uri, object_rejected, object_generation_backup);
}
- return NULL;
+ sk_X509_free(certs);
+ certs = NULL;
+
+ return manifest;
}
@@ -2550,21 +3395,22 @@ static int extract_roa_prefix(unsigned char *addr,
* Read and check one ROA from disk.
*/
static int check_roa_1(const rcynic_ctx_t *rc,
- const char *uri,
- char *path,
- const int pathlen,
- const char *prefix,
+ const uri_t *uri,
+ path_t *path,
+ const path_t *prefix,
STACK_OF(X509) *certs,
const unsigned char *hash,
- const size_t hashlen)
+ const size_t hashlen,
+ const object_generation_t generation)
{
- unsigned char hashbuf[EVP_MAX_MD_SIZE], addrbuf[ADDR_RAW_BUF_LEN];
+ unsigned char addrbuf[ADDR_RAW_BUF_LEN];
const ASN1_OBJECT *eContentType = NULL;
STACK_OF(IPAddressFamily) *roa_resources = NULL, *ee_resources = NULL;
STACK_OF(X509_CRL) *crls = NULL;
STACK_OF(X509) *signers = NULL;
CMS_ContentInfo *cms = NULL;
X509_CRL *crl = NULL;
+ hashbuf_t hashbuf;
ROA *roa = NULL;
BIO *bio = NULL;
rcynic_x509_store_ctx_t rctx;
@@ -2576,58 +3422,58 @@ static int check_roa_1(const rcynic_ctx_t *rc,
assert(rc && uri && path && prefix && certs && sk_X509_num(certs));
- if (!uri_to_filename(rc, uri, path, pathlen, prefix))
+ if (!uri_to_filename(rc, uri, path, prefix))
goto error;
+ if (hashlen > sizeof(hashbuf.h)) {
+ log_validation_status(rc, uri, hash_too_long, generation);
+ goto error;
+ }
+
if (hash)
- cms = read_cms(path, hashbuf, sizeof(hashbuf));
+ cms = read_cms(path, &hashbuf);
else
- cms = read_cms(path, NULL, 0);
+ cms = read_cms(path, NULL);
if (!cms)
goto error;
- if (hash && memcmp(hashbuf, hash, hashlen)) {
- reject(rc, uri, roa_digest_mismatch,
- "because ROA does not match manifest digest");
+ if (hash && memcmp(hashbuf.h, hash, hashlen)) {
+ log_validation_status(rc, uri, roa_digest_mismatch, generation);
goto error;
}
if (!(eContentType = CMS_get0_eContentType(cms)) ||
oid_cmp(eContentType, id_ct_routeOriginAttestation,
sizeof(id_ct_routeOriginAttestation))) {
- reject(rc, uri, roa_bad_econtenttype,
- "because ROA has bad eContentType");
+ log_validation_status(rc, uri, roa_bad_econtenttype, generation);
goto error;
}
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
- logmsg(rc, log_sys_err, "Couldn't allocate BIO for ROA %s", uri);
+ logmsg(rc, log_sys_err, "Couldn't allocate BIO for ROA %s", uri->s);
goto error;
}
if (CMS_verify(cms, NULL, NULL, NULL, bio, CMS_NO_SIGNER_CERT_VERIFY) <= 0) {
- reject(rc, uri, roa_invalid_cms, "because ROA CMS failed validation");
+ log_validation_status(rc, uri, roa_invalid_cms, generation);
goto error;
}
if (!(signers = CMS_get0_signers(cms)) || sk_X509_num(signers) != 1) {
- reject(rc, uri, roa_missing_signer,
- "because couldn't extract CMS signer from ROA");
+ log_validation_status(rc, uri, roa_missing_signer, generation);
goto error;
}
- parse_cert(rc, sk_X509_value(signers, 0), &certinfo, uri);
+ parse_cert(rc, sk_X509_value(signers, 0), &certinfo, uri, generation);
if (!(roa = ASN1_item_d2i_bio(ASN1_ITEM_rptr(ROA), bio, NULL))) {
- reject(rc, uri, roa_decode_error, "because could not decode ROA");
+ log_validation_status(rc, uri, roa_decode_error, generation);
goto error;
}
if (roa->version) {
- reject(rc, uri, roa_wrong_version,
- "because ROA version should be defaulted zero, not %ld",
- ASN1_INTEGER_get(roa->version));
+ log_validation_status(rc, uri, roa_wrong_version, generation);
goto error;
}
@@ -2648,9 +3494,7 @@ static int check_roa_1(const rcynic_ctx_t *rc,
for (i = 0; i < sk_ROAIPAddressFamily_num(roa->ipAddrBlocks); i++) {
rf = sk_ROAIPAddressFamily_value(roa->ipAddrBlocks, i);
if (!rf || !rf->addressFamily || rf->addressFamily->length < 2 || rf->addressFamily->length > 3) {
- reject(rc, uri, malformed_roa_addressfamily,
- "because ROA addressFamily length should be 2 or 3, not %lu",
- (unsigned long) rf->addressFamily->length);
+ log_validation_status(rc, uri, malformed_roa_addressfamily, generation);
goto error;
}
afi = (rf->addressFamily->data[0] << 8) | (rf->addressFamily->data[1]);
@@ -2661,8 +3505,7 @@ static int check_roa_1(const rcynic_ctx_t *rc,
if (!ra ||
!extract_roa_prefix(addrbuf, &prefixlen, ra->IPAddress, afi) ||
!v3_addr_add_prefix(roa_resources, afi, safi, addrbuf, prefixlen)) {
- reject(rc, uri, roa_resources_malformed,
- "because ROA resources appear malformed");
+ log_validation_status(rc, uri, roa_resources_malformed, generation);
goto error;
}
}
@@ -2682,8 +3525,7 @@ static int check_roa_1(const rcynic_ctx_t *rc,
IPAddressFamily *f = sk_IPAddressFamily_value(roa_resources, i);
if ((afi = v3_addr_get_afi(f)) == 0) {
- reject(rc, uri, roa_bad_afi,
- "because found bad AFI while extracting data from ROA");
+ log_validation_status(rc, uri, roa_bad_afi, generation);
goto error;
}
@@ -2701,7 +3543,7 @@ static int check_roa_1(const rcynic_ctx_t *rc,
if ((length = v3_addr_get_range(a, afi, a_min, a_max, ADDR_RAW_BUF_LEN)) == 0 ||
(length = v3_addr_get_range(b, afi, b_min, b_max, ADDR_RAW_BUF_LEN)) == 0) {
- reject(rc, uri, roa_resources_malformed, "because ROA resources appear malformed");
+ log_validation_status(rc, uri, roa_resources_malformed, generation);
goto error;
}
@@ -2715,18 +3557,17 @@ static int check_roa_1(const rcynic_ctx_t *rc,
}
if (!v3_addr_canonize(roa_resources)) {
- reject(rc, uri, roa_resources_malformed, "because ROA resources appear malformed");
+ log_validation_status(rc, uri, roa_resources_malformed, generation);
goto error;
}
if (!v3_addr_subset(roa_resources, ee_resources)) {
- reject(rc, uri, roa_not_nested,
- "because ROA's resources are not a subset of its signing EE certificate's resources");
+ log_validation_status(rc, uri, roa_not_nested, generation);
goto error;
}
- if (!(crl = check_crl(rc, certinfo.crldp, sk_X509_value(certs, sk_X509_num(certs) - 1), NULL, 0))) {
- reject(rc, uri, roa_bad_crl, "because ROA EE certificate has bad CRL %s", certinfo.crldp);
+ if (!(crl = check_crl(rc, &certinfo.crldp, sk_X509_value(certs, sk_X509_num(certs) - 1), NULL, 0))) {
+ log_validation_status(rc, uri, roa_bad_crl, generation);
goto error;
}
@@ -2756,8 +3597,7 @@ static int check_roa_1(const rcynic_ctx_t *rc,
/*
* Redundant error message?
*/
- logmsg(rc, log_data_err, "Validation failure for ROA %s EE certificate",uri);
- mib_increment(rc, uri, roa_invalid_ee);
+ log_validation_status(rc, uri, roa_invalid_ee, generation);
goto error;
}
@@ -2782,38 +3622,43 @@ static int check_roa_1(const rcynic_ctx_t *rc,
* and check issuer's signature if we don't.
*/
static void check_roa(const rcynic_ctx_t *rc,
- const char *uri,
- STACK_OF(X509) *certs,
+ const uri_t *uri,
+ STACK_OF(walk_ctx_t) *wsk,
const unsigned char *hash,
const size_t hashlen)
{
- char path[FILENAME_MAX];
+ STACK_OF(X509) *certs = NULL;
+ path_t path;
- if (uri_to_filename(rc, uri, path, sizeof(path), rc->authenticated) &&
- !access(path, F_OK))
- return;
+ assert(rc && uri && wsk);
- logmsg(rc, log_telemetry, "Checking ROA %s", uri);
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
+ !access(path.s, F_OK))
+ return;
- assert(rsync_cached_uri(rc, uri));
+ logmsg(rc, log_telemetry, "Checking ROA %s", uri->s);
- if (check_roa_1(rc, uri, path, sizeof(path), rc->unauthenticated,
- certs, hash, hashlen)) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, current_roa_accepted);
+ if ((certs = walk_ctx_stack_certs(wsk)) == NULL)
return;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, current_roa_rejected);
+
+ if (check_roa_1(rc, uri, &path, &rc->unauthenticated,
+ certs, hash, hashlen, object_generation_current)) {
+ install_object(rc, uri, &path, object_accepted, object_generation_current);
+ goto done;
+ } else if (!access(path.s, F_OK)) {
+ log_validation_status(rc, uri, object_rejected, object_generation_current);
}
- if (check_roa_1(rc, uri, path, sizeof(path), rc->old_authenticated,
- certs, hash, hashlen)) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, backup_roa_accepted);
- return;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, backup_roa_rejected);
+ if (check_roa_1(rc, uri, &path, &rc->old_authenticated,
+ certs, hash, hashlen, object_generation_backup)) {
+ install_object(rc, uri, &path, object_accepted, object_generation_backup);
+ goto done;
+ } else if (!access(path.s, F_OK)) {
+ log_validation_status(rc, uri, object_rejected, object_generation_backup);
}
+
+ done:
+ sk_X509_free(certs);
}
@@ -2822,20 +3667,20 @@ static void check_roa(const rcynic_ctx_t *rc,
* Read and check one Ghostbuster record from disk.
*/
static int check_ghostbuster_1(const rcynic_ctx_t *rc,
- const char *uri,
- char *path,
- const int pathlen,
- const char *prefix,
+ const uri_t *uri,
+ path_t *path,
+ const path_t *prefix,
STACK_OF(X509) *certs,
const unsigned char *hash,
- const size_t hashlen)
+ const size_t hashlen,
+ const object_generation_t generation)
{
- unsigned char hashbuf[EVP_MAX_MD_SIZE];
const ASN1_OBJECT *eContentType = NULL;
STACK_OF(X509_CRL) *crls = NULL;
STACK_OF(X509) *signers = NULL;
CMS_ContentInfo *cms = NULL;
X509_CRL *crl = NULL;
+ hashbuf_t hashbuf;
BIO *bio = NULL;
rcynic_x509_store_ctx_t rctx;
certinfo_t certinfo;
@@ -2843,28 +3688,31 @@ static int check_ghostbuster_1(const rcynic_ctx_t *rc,
assert(rc && uri && path && prefix && certs && sk_X509_num(certs));
- if (!uri_to_filename(rc, uri, path, pathlen, prefix))
+ if (!uri_to_filename(rc, uri, path, prefix))
+ goto error;
+
+ if (hashlen > sizeof(hashbuf.h)) {
+ log_validation_status(rc, uri, hash_too_long, generation);
goto error;
+ }
if (hash)
- cms = read_cms(path, hashbuf, sizeof(hashbuf));
+ cms = read_cms(path, &hashbuf);
else
- cms = read_cms(path, NULL, 0);
+ cms = read_cms(path, NULL);
if (!cms)
goto error;
- if (hash && memcmp(hashbuf, hash, hashlen)) {
- reject(rc, uri, ghostbuster_digest_mismatch,
- "because Ghostbuster record does not match manifest digest");
+ if (hash && memcmp(hashbuf.h, hash, hashlen)) {
+ log_validation_status(rc, uri, ghostbuster_digest_mismatch, generation);
goto error;
}
if (!(eContentType = CMS_get0_eContentType(cms)) ||
oid_cmp(eContentType, id_ct_rpkiGhostbusters,
sizeof(id_ct_rpkiGhostbusters))) {
- reject(rc, uri, ghostbuster_bad_econtenttype,
- "because Ghostbuster record has bad eContentType");
+ log_validation_status(rc, uri, ghostbuster_bad_econtenttype, generation);
goto error;
}
@@ -2874,23 +3722,22 @@ static int check_ghostbuster_1(const rcynic_ctx_t *rc,
* just leave this NULL and the right thing should happen.
*/
if ((bio = BIO_new(BIO_s_mem())) == NULL) {
- logmsg(rc, log_sys_err, "Couldn't allocate BIO for Ghostbuster record %s", uri);
+ logmsg(rc, log_sys_err, "Couldn't allocate BIO for Ghostbuster record %s", uri->s);
goto error;
}
#endif
if (CMS_verify(cms, NULL, NULL, NULL, bio, CMS_NO_SIGNER_CERT_VERIFY) <= 0) {
- reject(rc, uri, ghostbuster_invalid_cms, "because Ghostbuster record CMS failed validation");
+ log_validation_status(rc, uri, ghostbuster_invalid_cms, generation);
goto error;
}
if (!(signers = CMS_get0_signers(cms)) || sk_X509_num(signers) != 1) {
- reject(rc, uri, ghostbuster_missing_signer,
- "because couldn't extract CMS signer from Ghostbuster record");
+ log_validation_status(rc, uri, ghostbuster_missing_signer, generation);
goto error;
}
- parse_cert(rc, sk_X509_value(signers, 0), &certinfo, uri);
+ parse_cert(rc, sk_X509_value(signers, 0), &certinfo, uri, generation);
#if 0
/*
@@ -2899,8 +3746,8 @@ static int check_ghostbuster_1(const rcynic_ctx_t *rc,
*/
#endif
- if (!(crl = check_crl(rc, certinfo.crldp, sk_X509_value(certs, sk_X509_num(certs) - 1), NULL, 0))) {
- reject(rc, uri, ghostbuster_bad_crl, "because Ghostbuster record EE certificate has bad CRL %s", certinfo.crldp);
+ if (!(crl = check_crl(rc, &certinfo.crldp, sk_X509_value(certs, sk_X509_num(certs) - 1), NULL, 0))) {
+ log_validation_status(rc, uri, ghostbuster_bad_crl, generation);
goto error;
}
@@ -2927,11 +3774,7 @@ static int check_ghostbuster_1(const rcynic_ctx_t *rc,
X509_VERIFY_PARAM_add0_policy(rctx.ctx.param, OBJ_txt2obj(rpki_policy_oid, 1));
if (X509_verify_cert(&rctx.ctx) <= 0) {
- /*
- * Redundant error message?
- */
- logmsg(rc, log_data_err, "Validation failure for Ghostbuster record %s EE certificate", uri);
- mib_increment(rc, uri, ghostbuster_invalid_ee);
+ log_validation_status(rc, uri, ghostbuster_invalid_ee, generation);
goto error;
}
@@ -2953,217 +3796,277 @@ static int check_ghostbuster_1(const rcynic_ctx_t *rc,
* attempt to fetch it and check issuer's signature if we don't.
*/
static void check_ghostbuster(const rcynic_ctx_t *rc,
- const char *uri,
- STACK_OF(X509) *certs,
- const unsigned char *hash,
- const size_t hashlen)
+ const uri_t *uri,
+ STACK_OF(walk_ctx_t) *wsk,
+ const unsigned char *hash,
+ const size_t hashlen)
{
- char path[FILENAME_MAX];
+ STACK_OF(X509) *certs = NULL;
+ path_t path;
- if (uri_to_filename(rc, uri, path, sizeof(path), rc->authenticated) &&
- !access(path, F_OK))
- return;
+ assert(rc && uri && wsk);
- logmsg(rc, log_telemetry, "Checking Ghostbuster record %s", uri);
+ if (uri_to_filename(rc, uri, &path, &rc->new_authenticated) &&
+ !access(path.s, F_OK))
+ return;
- assert(rsync_cached_uri(rc, uri));
+ logmsg(rc, log_telemetry, "Checking Ghostbuster record %s", uri->s);
- if (check_ghostbuster_1(rc, uri, path, sizeof(path), rc->unauthenticated,
- certs, hash, hashlen)) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, current_ghostbuster_accepted);
+ if ((certs = walk_ctx_stack_certs(wsk)) == NULL)
return;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, current_ghostbuster_rejected);
+
+ if (check_ghostbuster_1(rc, uri, &path, &rc->unauthenticated,
+ certs, hash, hashlen, object_generation_current)) {
+ install_object(rc, uri, &path, object_accepted, object_generation_current);
+ goto done;
+ } else if (!access(path.s, F_OK)) {
+ log_validation_status(rc, uri, object_rejected, object_generation_current);
}
- if (check_ghostbuster_1(rc, uri, path, sizeof(path), rc->old_authenticated,
- certs, hash, hashlen)) {
- install_object(rc, uri, path);
- mib_increment(rc, uri, backup_ghostbuster_accepted);
- return;
- } else if (!access(path, F_OK)) {
- mib_increment(rc, uri, backup_ghostbuster_rejected);
+ if (check_ghostbuster_1(rc, uri, &path, &rc->old_authenticated,
+ certs, hash, hashlen, object_generation_backup)) {
+ install_object(rc, uri, &path, object_accepted, object_generation_backup);
+ goto done;
+ } else if (!access(path.s, F_OK)) {
+ log_validation_status(rc, uri, object_rejected, object_generation_backup);
}
+
+ done:
+ sk_X509_free(certs);
}
-static void walk_cert(rcynic_ctx_t *rc,
- const certinfo_t *parent,
- STACK_OF(X509) *certs);
+static void walk_cert(rcynic_ctx_t *, STACK_OF(walk_ctx_t) *);
/**
- * Recursive walk of certificate hierarchy (core of the program). The
- * daisy chain recursion is to avoid having to duplicate the stack
- * manipulation and error handling.
+ * rsync callback for fetching SIA tree.
*/
-static void walk_cert_1(rcynic_ctx_t *rc,
- char *uri,
- STACK_OF(X509) *certs,
- const certinfo_t *parent,
- const char *prefix,
- const int backup,
- const unsigned char *hash,
- const size_t hashlen)
+static void rsync_sia_callback(const rcynic_ctx_t *rc,
+ const rsync_ctx_t *ctx,
+ const rsync_status_t status,
+ const uri_t *uri,
+ STACK_OF(walk_ctx_t) *wsk)
{
- certinfo_t child;
- X509 *x;
+ walk_ctx_t *w = walk_ctx_stack_head(wsk);
- if ((x = check_cert(rc, uri, certs, parent, &child, prefix, backup, hash, hashlen)) == NULL)
- return;
+ assert(rc && wsk);
- if (!sk_X509_push(certs, x)) {
- logmsg(rc, log_sys_err,
- "Internal allocation failure recursing over certificate");
+ switch (status) {
+
+ case rsync_status_pending:
+ if (rsync_count_runable(rc) >= rc->max_parallel_fetches)
+ return;
+
+ assert(rsync_count_running(rc) < rc->max_parallel_fetches);
+
+ if ((wsk = walk_ctx_stack_clone(wsk)) == NULL) {
+ logmsg(rc, log_sys_err, "walk_ctx_stack_clone() failed, probably memory exhaustion, blundering onwards without forking stack");
+ return;
+ }
+
+ walk_ctx_stack_pop(wsk);
+ task_add(rc, walk_cert, wsk);
return;
+
+ case rsync_status_failed:
+ log_validation_status(rc, uri, rsync_failed, object_generation_null);
+ break;
+
+ case rsync_status_timed_out:
+ log_validation_status(rc, uri, rsync_timed_out, object_generation_null);
+ break;
+
+ case rsync_status_skipped:
+ log_validation_status(rc, uri, rsync_skipped, object_generation_null);
+ break;
+
+ case rsync_status_done:
+ break;
}
- walk_cert(rc, &child, certs);
- X509_free(sk_X509_pop(certs));
+ w->state++;
+ task_add(rc, walk_cert, wsk);
}
/**
- * Recursive walk of certificate hierarchy (core of the program). The
- * daisy chain recursion is to avoid having to duplicate the stack
- * manipulation and error handling.
+ * Recursive walk of certificate hierarchy (core of the program).
+ *
+ * Walk all products of the current certificate, starting with the
+ * ones named in the manifest and continuing with any that we find in
+ * the publication directory but which are not named in the manifest.
+ *
+ * Dispatch to correct checking code for the object named by URI,
+ * based on the filename extension in the uri. CRLs are a special
+ * case because we've already checked them by the time we get here, so
+ * we just ignore them. Other objects are either certificates or
+ * CMS-signed objects of one kind or another.
*/
-static void walk_cert_2(rcynic_ctx_t *rc,
- char *uri,
- STACK_OF(X509) *certs,
- const certinfo_t *parent,
- const char *prefix,
- const int backup,
- const unsigned char *hash,
- const size_t hashlen)
+static void walk_cert(rcynic_ctx_t *rc, STACK_OF(walk_ctx_t) *wsk)
{
- if (endswith(uri, ".cer"))
- walk_cert_1(rc, uri, certs, parent, prefix, backup, hash, hashlen);
- else if (endswith(uri, ".roa"))
- check_roa(rc, uri, certs, hash, hashlen);
- else if (endswith(uri, ".gbr"))
- check_ghostbuster(rc, uri, certs, hash, hashlen);
- else if (!endswith(uri, ".crl"))
- logmsg(rc, log_telemetry, "Don't know how to check object %s, ignoring", uri);
-}
+ const unsigned char *hash = NULL;
+ object_generation_t generation;
+ size_t hashlen;
+ walk_ctx_t *w;
+ uri_t uri;
-/**
- * Recursive walk of certificate hierarchy (core of the program). The
- * daisy chain recursion is to avoid having to duplicate the stack
- * manipulation and error handling.
- */
-static void walk_cert_3(rcynic_ctx_t *rc,
- STACK_OF(X509) *certs,
- const certinfo_t *parent,
- const char *prefix,
- const int backup,
- Manifest *manifest)
-{
- char uri[URI_MAX], path[FILENAME_MAX];
- FileAndHash *fah;
- STACK_OF(OPENSSL_STRING) *stray_ducks = NULL;
- DIR *dir = NULL;
- struct dirent *d;
- int i;
+ assert(rc && wsk);
- /*
- * Pull all non-directory filenames from the publication point directory.
- */
- if ((stray_ducks = sk_OPENSSL_STRING_new(uri_cmp)) == NULL)
- logmsg(rc, log_sys_err, "Couldn't allocate stray_ducks stack");
- else if (!uri_to_filename(rc, parent->sia, path, sizeof(path), prefix) || (dir = opendir(path)) == NULL)
- logmsg(rc, log_data_err, "Couldn't list directory %s, skipping check for out-of-manifest data", path);
- else
- while ((d = readdir(dir)) != NULL)
- if (d->d_type != DT_DIR && !sk_OPENSSL_STRING_push_strdup(stray_ducks, d->d_name))
- logmsg(rc, log_sys_err, "Couldn't strdup() string \"%s\", blundering onwards", d->d_name);
+ while ((w = walk_ctx_stack_head(wsk)) != NULL) {
- if (dir != NULL)
- closedir(dir);
-
- /*
- * Loop over manifest, checking everything it lists. Remove any
- * filenames we find in the manifest from our list of objects found
- * in the publication point directory, so we don't check stuff twice.
- */
- for (i = 0; (fah = sk_FileAndHash_value(manifest->fileList, i)) != NULL; i++) {
- sk_OPENSSL_STRING_remove(stray_ducks, (char *) fah->file->data);
- if (strlen(parent->sia) + strlen((char *) fah->file->data) >= sizeof(uri)) {
- logmsg(rc, log_data_err, "URI %s%s too long, skipping", parent->sia, fah->file->data);
- } else {
- strcpy(uri, parent->sia);
- strcat(uri, (char *) fah->file->data);
- walk_cert_2(rc, uri, certs, parent, prefix, backup, fah->hash->data, fah->hash->length);
+ switch (w->state) {
+ case walk_state_current:
+ generation = object_generation_current;
+ break;
+ case walk_state_backup:
+ generation = object_generation_backup;
+ break;
+ default:
+ generation = object_generation_null;
+ break;
}
- }
- /*
- * Whine about and maybe check any object that was in the directory
- * but not in the manifest, except for the manifest itself.
- */
- for (i = 0; i < sk_OPENSSL_STRING_num(stray_ducks); i++) {
- char *s = sk_OPENSSL_STRING_value(stray_ducks, i);
- if (strlen(parent->sia) + strlen(s) >= sizeof(uri)) {
- logmsg(rc, log_data_err, "URI %s%s too long, skipping", parent->sia, s);
+ switch (w->state) {
+
+ case walk_state_initial:
+
+ if (!w->certinfo.sia.s[0] || !w->certinfo.ca) {
+ w->state = walk_state_done;
+ continue;
+ }
+
+ if (!w->certinfo.manifest.s[0]) {
+ log_validation_status(rc, &w->certinfo.uri, manifest_missing, w->certinfo.generation);
+ w->state = walk_state_done;
+ continue;
+ }
+
+ w->state++;
continue;
- }
- strcpy(uri, parent->sia);
- strcat(uri, s);
- if (!strcmp(uri, parent->manifest))
+
+ case walk_state_rsync:
+
+ rsync_tree(rc, &w->certinfo.sia, wsk, rsync_sia_callback);
+ return;
+
+ case walk_state_ready:
+
+ walk_ctx_loop_init(rc, wsk); /* sets w->state */
+ continue;
+
+ case walk_state_current:
+ case walk_state_backup:
+
+ if (!walk_ctx_loop_this(rc, wsk, &uri, &hash, &hashlen)) {
+ walk_ctx_loop_next(rc, wsk);
+ continue;
+ }
+
+ if (endswith(uri.s, ".crl") || endswith(uri.s, ".mft") || endswith(uri.s, ".mnf")) {
+ walk_ctx_loop_next(rc, wsk);
+ continue; /* CRLs and manifests checked elsewhere */
+ }
+
+ if (hash == NULL)
+ log_validation_status(rc, &uri, tainted_by_not_being_in_manifest, generation);
+ else if (w->stale_manifest)
+ log_validation_status(rc, &uri, tainted_by_stale_manifest, generation);
+
+ if (hash == NULL && !rc->allow_object_not_in_manifest) {
+ walk_ctx_loop_next(rc, wsk);
+ continue;
+ }
+
+ if (endswith(uri.s, ".roa")) {
+ check_roa(rc, &uri, wsk, hash, hashlen);
+ walk_ctx_loop_next(rc, wsk);
+ continue;
+ }
+
+ if (endswith(uri.s, ".gbr")) {
+ check_ghostbuster(rc, &uri, wsk, hash, hashlen);
+ walk_ctx_loop_next(rc, wsk);
+ continue;
+ }
+
+ if (endswith(uri.s, ".cer")) {
+ certinfo_t subject;
+ X509 *x = check_cert(rc, &uri, wsk, &subject, hash, hashlen);
+ if (!walk_ctx_stack_push(wsk, x, &subject))
+ walk_ctx_loop_next(rc, wsk);
+ continue;
+ }
+
+ log_validation_status(rc, &uri, unknown_object_type_skipped, object_generation_null);
+ walk_ctx_loop_next(rc, wsk);
+ continue;
+
+ case walk_state_done:
+
+ walk_ctx_stack_pop(wsk); /* Resume our issuer's state */
continue;
- logmsg(rc, log_telemetry, "Object %s present in publication directory but not in manifest", uri);
- mib_increment(rc, uri, object_not_in_manifest);
- if (rc->allow_object_not_in_manifest)
- walk_cert_2(rc, uri, certs, parent, prefix, backup, NULL, 0);
+
+ }
}
- sk_OPENSSL_STRING_pop_free(stray_ducks, OPENSSL_STRING_free);
+ assert(walk_ctx_stack_head(wsk) == NULL);
+ walk_ctx_stack_free(wsk);
}
/**
- * Recursive walk of certificate hierarchy (core of the program). The
- * daisy chain recursion is to avoid having to duplicate the stack
- * manipulation and error handling.
+ * Check a trust anchor. Yes, we trust it, by definition, but it
+ * still needs to conform to the certificate profile, the
+ * self-signature must be correct, etcetera.
*/
-static void walk_cert(rcynic_ctx_t *rc,
- const certinfo_t *parent,
- STACK_OF(X509) *certs)
+static void check_ta(rcynic_ctx_t *rc, STACK_OF(walk_ctx_t) *wsk)
{
- assert(parent && certs);
-
- if (parent->sia[0] && parent->ca) {
- int n_cert = sk_X509_num(certs);
- Manifest *manifest = NULL;
+ STACK_OF(X509) *certs = walk_ctx_stack_certs(wsk);
+ walk_ctx_t *w = walk_ctx_stack_head(wsk);
+ int ok = 0;
- rc->indent++;
+ if (certs != NULL && w != NULL)
+ ok = check_x509(rc, certs, w->cert, &w->certinfo, &w->certinfo);
- rsync_tree(rc, parent->sia);
+ sk_X509_free(certs);
- if (!parent->manifest[0]) {
-
- logmsg(rc, log_data_err, "Parent certificate does not specify a manifest, skipping collection");
-
- } else if ((manifest = check_manifest(rc, parent->manifest, certs)) == NULL) {
-
- logmsg(rc, log_data_err, "Couldn't get manifest %s, skipping collection", parent->manifest);
+ if (!ok)
+ return;
- } else {
+ task_add(rc, walk_cert, wsk);
- logmsg(rc, log_debug, "Walking unauthenticated store");
- walk_cert_3(rc, certs, parent, rc->unauthenticated, 0, manifest);
- logmsg(rc, log_debug, "Done walking unauthenticated store");
+ while (sk_task_t_num(rc->task_queue) > 0 || sk_rsync_ctx_t_num(rc->rsync_queue) > 0) {
+ task_run_q(rc);
+ rsync_mgr(rc);
+ }
+}
- logmsg(rc, log_debug, "Walking old authenticated store");
- walk_cert_3(rc, certs, parent, rc->old_authenticated, 1, manifest);
- logmsg(rc, log_debug, "Done walking old authenticated store");
+
- Manifest_free(manifest);
- }
+/**
+ * Read a trust anchor from disk and compare with known public key.
+ * NB: EVP_PKEY_cmp() returns 1 for match, not 0 like every other
+ * xyz_cmp() function in the entire OpenSSL library. Go figure.
+ */
+static X509 *read_ta(const rcynic_ctx_t *rc, const uri_t *uri, const path_t *path, const EVP_PKEY *pkey, object_generation_t generation)
- assert(sk_X509_num(certs) == n_cert);
+{
+ EVP_PKEY *xpkey = NULL;
+ X509 *x = NULL;
+ int match = 0;
- rc->indent--;
+ if ((x = read_cert(path, NULL)) == NULL || (xpkey = X509_get_pubkey(x)) == NULL) {
+ log_validation_status(rc, uri, unreadable_trust_anchor, generation);
+ } else {
+ match = EVP_PKEY_cmp(pkey, xpkey) == 1;
+ if (!match)
+ log_validation_status(rc, uri, trust_anchor_key_mismatch, generation);
}
+
+ EVP_PKEY_free(xpkey);
+ if (match)
+ return x;
+ X509_free(x);
+ return NULL;
}
@@ -3181,8 +4084,9 @@ int main(int argc, char *argv[])
char *lockfile = NULL, *xmlfile = NULL;
int c, i, j, ret = 1, jitter = 600, lockfd = -1;
STACK_OF(CONF_VALUE) *cfg_section = NULL;
- STACK_OF(X509) *certs = NULL;
+ STACK_OF(walk_ctx_t) *wsk = NULL;
CONF *cfg_handle = NULL;
+ walk_ctx_t *w = NULL;
time_t start = 0, finish;
unsigned long hash;
rcynic_ctx_t rc;
@@ -3197,17 +4101,23 @@ int main(int argc, char *argv[])
else
rc.jane++;
- set_directory(&rc.authenticated, "rcynic-data/authenticated/");
- set_directory(&rc.old_authenticated, "rcynic-data/authenticated.old/");
- set_directory(&rc.unauthenticated, "rcynic-data/unauthenticated/");
- rc.log_level = log_telemetry;
+ rc.log_level = log_data_err;
rc.allow_stale_crl = 1;
rc.allow_stale_manifest = 1;
+ rc.max_parallel_fetches = 1;
+ rc.max_retries = 3;
+ rc.retry_wait_min = 30;
+ rc.run_rsync = 1;
+ rc.rsync_timeout = 300;
#define QQ(x,y) rc.priority[x] = y;
LOG_LEVELS;
#undef QQ
+ if (!set_directory(&rc, &rc.authenticated, "rcynic-data/authenticated", 0) ||
+ !set_directory(&rc, &rc.unauthenticated, "rcynic-data/unauthenticated/", 1))
+ goto done;
+
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
@@ -3272,19 +4182,22 @@ int main(int argc, char *argv[])
assert(val && val->name && val->value);
- if (!name_cmp(val->name, "authenticated"))
- set_directory(&rc.authenticated, val->value);
-
- else if (!name_cmp(val->name, "old-authenticated"))
- set_directory(&rc.old_authenticated, val->value);
+ if (!name_cmp(val->name, "authenticated") &&
+ !set_directory(&rc, &rc.authenticated, val->value, 0))
+ goto done;
- else if (!name_cmp(val->name, "unauthenticated"))
- set_directory(&rc.unauthenticated, val->value);
+ else if (!name_cmp(val->name, "unauthenticated") &&
+ !set_directory(&rc, &rc.unauthenticated, val->value, 1))
+ goto done;
else if (!name_cmp(val->name, "rsync-timeout") &&
!configure_integer(&rc, &rc.rsync_timeout, val->value))
goto done;
+ else if (!name_cmp(val->name, "max-parallel-fetches") &&
+ !configure_integer(&rc, &rc.max_parallel_fetches, val->value))
+ goto done;
+
else if (!name_cmp(val->name, "rsync-program"))
rc.rsync_program = strdup(val->value);
@@ -3347,6 +4260,10 @@ int main(int argc, char *argv[])
!configure_boolean(&rc, &prune, val->value))
goto done;
+ else if (!name_cmp(val->name, "run-rsync") &&
+ !configure_boolean(&rc, &rc.run_rsync, val->value))
+ goto done;
+
/*
* Ugly, but the easiest way to handle all these strings.
*/
@@ -3373,29 +4290,30 @@ int main(int argc, char *argv[])
goto done;
}
- if ((rc.stale_cache = sk_OPENSSL_STRING_new(uri_cmp)) == NULL) {
- logmsg(&rc, log_sys_err, "Couldn't allocate stale_cache stack");
+ if ((rc.dead_host_cache = sk_OPENSSL_STRING_new(uri_cmp)) == NULL) {
+ logmsg(&rc, log_sys_err, "Couldn't allocate dead_host_cache stack");
goto done;
}
if (xmlfile != NULL) {
- if ((rc.host_counters = sk_HOST_MIB_COUNTER_new(host_mib_counter_cmp)) == NULL) {
- logmsg(&rc, log_sys_err, "Couldn't allocate host_counters stack");
- goto done;
- }
- if ((rc.validation_status = sk_VALIDATION_STATUS_new_null()) == NULL) {
+ if ((rc.validation_status = sk_validation_status_t_new(validation_status_cmp)) == NULL) {
logmsg(&rc, log_sys_err, "Couldn't allocate validation_status stack");
goto done;
}
}
- if ((certs = sk_X509_new_null()) == NULL) {
- logmsg(&rc, log_sys_err, "Couldn't allocate certificate stack");
+ if ((rc.x509_store = X509_STORE_new()) == NULL) {
+ logmsg(&rc, log_sys_err, "Couldn't allocate X509_STORE");
goto done;
}
- if ((rc.x509_store = X509_STORE_new()) == NULL) {
- logmsg(&rc, log_sys_err, "Couldn't allocate X509_STORE");
+ if ((rc.rsync_queue = sk_rsync_ctx_t_new_null()) == NULL) {
+ logmsg(&rc, log_sys_err, "Couldn't allocate rsync_queue");
+ goto done;
+ }
+
+ if ((rc.task_queue = sk_task_t_new_null()) == NULL) {
+ logmsg(&rc, log_sys_err, "Couldn't allocate task_queue");
goto done;
}
@@ -3431,177 +4349,181 @@ int main(int argc, char *argv[])
start = time(0);
logmsg(&rc, log_telemetry, "Starting");
- if (!rm_rf(rc.old_authenticated)) {
- logmsg(&rc, log_sys_err, "Couldn't remove %s: %s",
- rc.old_authenticated, strerror(errno));
+ if (!construct_directory_names(&rc))
goto done;
- }
- if (rename(rc.authenticated, rc.old_authenticated) < 0 &&
- errno != ENOENT) {
- logmsg(&rc, log_sys_err, "Couldn't rename %s to %s: %s",
- rc.old_authenticated, rc.authenticated, strerror(errno));
+ if (!access(rc.new_authenticated.s, F_OK)) {
+ logmsg(&rc, log_sys_err, "Timestamped output directory %s already exists! Clock went backwards?", rc.new_authenticated.s);
goto done;
}
- if (!access(rc.authenticated, F_OK) || !mkdir_maybe(&rc, rc.authenticated)) {
+ if (!mkdir_maybe(&rc, &rc.new_authenticated)) {
logmsg(&rc, log_sys_err, "Couldn't prepare directory %s: %s",
- rc.authenticated, strerror(errno));
+ rc.new_authenticated.s, strerror(errno));
goto done;
}
for (i = 0; i < sk_CONF_VALUE_num(cfg_section); i++) {
CONF_VALUE *val = sk_CONF_VALUE_value(cfg_section, i);
- char path1[FILENAME_MAX], path2[FILENAME_MAX], uri[URI_MAX];
- certinfo_t ta_info;
+ object_generation_t generation = object_generation_null;
+ path_t path1, path2;
+ certinfo_t ta_certinfo;
+ uri_t uri;
X509 *x = NULL;
assert(val && val->name && val->value);
- uri[0] = '\0';
+ if (!name_cmp(val->name, "trust-anchor-uri-with-key") ||
+ !name_cmp(val->name, "indirect-trust-anchor")) {
+ /*
+ * Obsolete syntax. If you're reading this comment because you
+ * had an old rcynic.conf and got this error message:
+ *
+ * "indirect-trust-anchor" is exactly the same as
+ * "trust-anchor-locator", the name was changed to settle a
+ * terminology fight in the IETF SIDR WG.
+ *
+ * "trust-anchor-uri-with-key" is semantically identical to
+ * "trust-anchor-locator" (and was the original form of this
+ * mechanism), but the syntax and local file format is
+ * different.
+ *
+ * If you're seeing this error, you should just obtain current
+ * TAL files. Also see the "make-tal.sh" script.
+ */
+ logmsg(&rc, log_usage_err,
+ "Directive \"%s\" is obsolete -- please use \"trust-anchor-locator\" instead",
+ val->name);
+ goto done;
+ }
if (!name_cmp(val->name, "trust-anchor")) {
/*
- * Old local file trust anchor method.
+ * Local file trust anchor method.
*/
logmsg(&rc, log_telemetry, "Processing trust anchor from local file %s", val->value);
- if (strlen(val->value) >= sizeof(path1)) {
+ if (strlen(val->value) >= sizeof(path1.s)) {
logmsg(&rc, log_usage_err, "Trust anchor path name too long %s", val->value);
goto done;
}
- strcpy(path1, val->value);
- if ((x = read_cert(path1, NULL, 0)) == NULL) {
- logmsg(&rc, log_usage_err, "Couldn't read trust anchor %s", path1);
- goto done;
+ strcpy(path1.s, val->value);
+
+ /* Construct file:// URI for logging */
+ assert(sizeof("file://") < sizeof(uri.s));
+ strcpy(uri.s, "file://");
+ if (path1.s[0] != '/') {
+ if (getcwd(uri.s + strlen(uri.s), sizeof(uri.s) - strlen(uri.s)) == NULL ||
+ (!endswith(uri.s, "/") && strlen(uri.s) >= sizeof(uri.s) - 1))
+ uri.s[0] = '\0';
+ else
+ strcat(uri.s, "/");
+ }
+ if (uri.s[0] != '\0' && strlen(uri.s) + strlen(path1.s) < sizeof(uri.s))
+ strcat(uri.s, path1.s);
+ else
+ uri.s[0] = '\0';
+
+ if ((x = read_cert(&path1, NULL)) == NULL) {
+ log_validation_status(&rc, &uri, unreadable_trust_anchor, generation);
+ continue;
}
hash = X509_subject_name_hash(x);
for (j = 0; j < INT_MAX; j++) {
- if (snprintf(path2, sizeof(path2), "%s%lx.%d.cer",
- rc.authenticated, hash, j) == sizeof(path2)) {
+ if (snprintf(path2.s, sizeof(path2.s), "%s%lx.%d.cer",
+ rc.new_authenticated.s, hash, j) == sizeof(path2.s)) {
logmsg(&rc, log_sys_err,
- "Couldn't construct path name for trust anchor %s", path1);
+ "Couldn't construct path name for trust anchor %s", path1.s);
goto done;
}
- if (access(path2, F_OK))
+ if (access(path2.s, F_OK))
break;
}
if (j == INT_MAX) {
- logmsg(&rc, log_sys_err, "Couldn't find a free name for trust anchor %s", path1);
+ logmsg(&rc, log_sys_err, "Couldn't find a free name for trust anchor %s", path1.s);
goto done;
}
- assert(sizeof("file://") < sizeof(uri));
- strcpy(uri, "file://");
- if (path1[0] != '/') {
- if (getcwd(uri + strlen(uri), sizeof(uri) - strlen(uri)) == NULL ||
- (!endswith(uri, "/") && strlen(uri) >= sizeof(uri) - 1))
- uri[0] = '\0';
- else
- strcat(uri, "/");
- }
- if (uri[0] != '\0' && strlen(uri) + strlen(path1) < sizeof(uri))
- strcat(uri, path1);
- else
- uri[0] = '\0';
}
- if (!name_cmp(val->name, "trust-anchor-uri-with-key") ||
- !name_cmp(val->name, "indirect-trust-anchor") ||
- !name_cmp(val->name, "trust-anchor-locator")) {
+ if (!name_cmp(val->name, "trust-anchor-locator")) {
/*
- * Newfangled URI + public key method. Two different versions
- * of essentially the same mechanism.
- *
- * NB: EVP_PKEY_cmp() returns 1 for success, not 0 like every
- * other xyz_cmp() function in the entire OpenSSL library.
- * Go figure.
+ * Trust anchor locator (URI + public key) method.
*/
- int unified = (!name_cmp(val->name, "indirect-trust-anchor") ||
- !name_cmp(val->name, "trust-anchor-locator"));
- EVP_PKEY *pkey = NULL, *xpkey = NULL;
+ EVP_PKEY *pkey = NULL;
char *fn;
- if (unified) {
- fn = val->value;
- bio = BIO_new_file(fn, "r");
- if (!bio || BIO_gets(bio, uri, sizeof(uri)) <= 0) {
- logmsg(&rc, log_usage_err, "Couldn't read trust anchor URI from %s", fn);
- goto done;
- }
- uri[strcspn(uri, " \t\r\n")] = '\0';
- bio = BIO_push(BIO_new(BIO_f_base64()), bio);
- } else {
- j = strcspn(val->value, " \t");
- if (j >= sizeof(uri)) {
- logmsg(&rc, log_usage_err, "Trust anchor URI too long %s", val->value);
- goto done;
- }
- memcpy(uri, val->value, j);
- uri[j] = '\0';
- j += strspn(val->value + j, " \t");
- fn = val->value + j;
- bio = BIO_new_file(fn, "rb");
- }
- if (!uri_to_filename(&rc, uri, path1, sizeof(path1), rc.unauthenticated) ||
- !uri_to_filename(&rc, uri, path2, sizeof(path2), rc.authenticated)) {
- logmsg(&rc, log_usage_err, "Couldn't convert trust anchor URI %s to filename", uri);
- goto done;
+ path_t path3;
+
+ fn = val->value;
+ bio = BIO_new_file(fn, "r");
+ if (!bio || BIO_gets(bio, uri.s, sizeof(uri.s)) <= 0) {
+ log_validation_status(&rc, &uri, unreadable_trust_anchor_locator, object_generation_null);
+ BIO_free(bio);
+ bio = NULL;
+ continue;
}
- logmsg(&rc, log_telemetry, "Processing trust anchor from URI %s", uri);
- if (!rsync_file(&rc, uri)) {
- logmsg(&rc, log_data_err, "Could not fetch trust anchor from %s", uri);
+ uri.s[strcspn(uri.s, " \t\r\n")] = '\0';
+ bio = BIO_push(BIO_new(BIO_f_linebreak()), bio);
+ bio = BIO_push(BIO_new(BIO_f_base64()), bio);
+ if (!uri_to_filename(&rc, &uri, &path1, &rc.unauthenticated) ||
+ !uri_to_filename(&rc, &uri, &path2, &rc.new_authenticated) ||
+ !uri_to_filename(&rc, &uri, &path3, &rc.old_authenticated)) {
+ log_validation_status(&rc, &uri, unreadable_trust_anchor_locator, object_generation_null);
+ BIO_free_all(bio);
+ bio = NULL;
continue;
}
+ logmsg(&rc, log_telemetry, "Processing trust anchor from URI %s", uri.s);
+ rsync_file(&rc, &uri);
+ while (sk_rsync_ctx_t_num(rc.rsync_queue) > 0)
+ rsync_mgr(&rc);
if (bio)
pkey = d2i_PUBKEY_bio(bio, NULL);
BIO_free_all(bio);
bio = NULL;
if (!pkey) {
- logmsg(&rc, log_usage_err, "Couldn't read trust anchor public key for %s from %s", uri, fn);
- goto done;
+ log_validation_status(&rc, &uri, unreadable_trust_anchor_locator, object_generation_null);
+ continue;
+ }
+ generation = object_generation_current;
+ if ((x = read_ta(&rc, &uri, &path1, pkey, generation)) == NULL) {
+ generation = object_generation_backup;
+ path1 = path3;
+ x = read_ta(&rc, &uri, &path1, pkey, generation);
}
- if ((x = read_cert(path1, NULL, 0)) == NULL)
- logmsg(&rc, log_data_err, "Couldn't read trust anchor %s", path1);
- if (x && (xpkey = X509_get_pubkey(x)) == NULL)
- logmsg(&rc, log_data_err, "Rejected %s because couldn't read public key from trust anchor locator", uri);
- j = (xpkey && EVP_PKEY_cmp(pkey, xpkey) == 1);
EVP_PKEY_free(pkey);
- EVP_PKEY_free(xpkey);
- if (!j) {
- logmsg(&rc, log_data_err, "Rejected %s because known public key didn't match trust anchor locator", uri);
- X509_free(x);
+ if (!x)
continue;
- }
}
if (!x)
continue;
- logmsg(&rc, log_telemetry, "Copying trust anchor %s to %s", path1, path2);
+ logmsg(&rc, log_telemetry, "Copying trust anchor %s to %s", path1.s, path2.s);
- if (!mkdir_maybe(&rc, path2) ||
- !(rc.use_links ? ln(path1, path2) : cp(&rc, path1, path2))) {
- logmsg(&rc, log_sys_err, "Couldn't %s trust anchor %s",
- (rc.use_links ? "link" : "copy"), path1);
+ if (!mkdir_maybe(&rc, &path2) || !cp_ln(&rc, &path1, &path2))
+ goto done;
+
+ if ((wsk = walk_ctx_stack_new()) == NULL) {
+ logmsg(&rc, log_sys_err, "Couldn't allocate walk context stack");
goto done;
}
- parse_cert(&rc, x, &ta_info, uri);
- ta_info.ta = 1;
- sk_X509_push(certs, x);
+ parse_cert(&rc, x, &ta_certinfo, &uri, generation);
+ ta_certinfo.ta = 1;
- if (ta_info.crldp[0] && !check_x509(&rc, certs, x, &ta_info)) {
- logmsg(&rc, log_data_err, "Couldn't get CRL for trust anchor %s", path1);
- } else {
- if (*uri)
- log_validation_status(&rc, uri, validation_ok);
- walk_cert(&rc, &ta_info, certs);
+ if ((w = walk_ctx_stack_push(wsk, x, &ta_certinfo)) == NULL) {
+ logmsg(&rc, log_sys_err, "Couldn't push walk context stack");
+ goto done;
}
- X509_free(sk_X509_pop(certs));
- assert(sk_X509_num(certs) == 0);
+ check_ta(&rc, wsk);
+ wsk = NULL; /* Ownership of wsk passed to check_ta() */
}
- if (prune && !prune_unauthenticated(&rc, rc.unauthenticated,
- strlen(rc.unauthenticated))) {
+ if (!finalize_directories(&rc))
+ goto done;
+
+ if (prune && !prune_unauthenticated(&rc, &rc.unauthenticated,
+ strlen(rc.unauthenticated.s))) {
logmsg(&rc, log_sys_err, "Trouble pruning old unauthenticated data");
goto done;
}
@@ -3614,15 +4536,16 @@ int main(int argc, char *argv[])
if (xmlfile != NULL) {
char tad[sizeof("2006-10-13T11:22:33Z") + 1];
- char hostname[HOST_NAME_MAX];
time_t tad_time = time(0);
struct tm *tad_tm = gmtime(&tad_time);
int ok = 1, use_stdout = !strcmp(xmlfile, "-");
+ hostname_t hostname;
+ mib_counter_t code;
FILE *f = NULL;
strftime(tad, sizeof(tad), "%Y-%m-%dT%H:%M:%SZ", tad_tm);
- ok &= gethostname(hostname, sizeof(hostname)) == 0;
+ ok &= gethostname(hostname.s, sizeof(hostname.s)) == 0;
if (use_stdout)
f = stdout;
@@ -3637,47 +4560,41 @@ int main(int argc, char *argv[])
ok &= fprintf(f, "<?xml version=\"1.0\" ?>\n"
"<rcynic-summary date=\"%s\" rcynic-version=\"%s\""
" summary-version=\"%d\" reporting-hostname=\"%s\">\n"
- " <labels>\n"
- " <hostname>Publication Repository</hostname>\n",
- tad, svn_id, XML_SUMMARY_VERSION, hostname) != EOF;
+ " <labels>\n",
+ tad, svn_id, XML_SUMMARY_VERSION, hostname.s) != EOF;
for (j = 0; ok && j < MIB_COUNTER_T_MAX; ++j)
- ok &= fprintf(f, " <%s kind=\"%s\">%s</%s>\n",
- mib_counter_label[j], mib_counter_kind[j],
- (mib_counter_desc[j]
- ? mib_counter_desc[j]
- : X509_verify_cert_error_string(mib_counter_openssl[j])),
- mib_counter_label[j]) != EOF;
+ if (ok)
+ ok &= fprintf(f, " <%s kind=\"%s\">%s</%s>\n",
+ mib_counter_label[j], mib_counter_kind[j],
+ (mib_counter_desc[j]
+ ? mib_counter_desc[j]
+ : X509_verify_cert_error_string(mib_counter_openssl[j])),
+ mib_counter_label[j]) != EOF;
if (ok)
ok &= fprintf(f, " </labels>\n") != EOF;
- for (i = 0; ok && i < sk_HOST_MIB_COUNTER_num(rc.host_counters); i++) {
- HOST_MIB_COUNTER *h = sk_HOST_MIB_COUNTER_value(rc.host_counters, i);
- assert(h);
-
- if (ok)
- ok &= fprintf(f, " <host>\n <hostname>%s</hostname>\n",
- h->hostname) != EOF;
-
- for (j = 0; ok && j < MIB_COUNTER_T_MAX; ++j)
- ok &= fprintf(f, " <%s>%lu</%s>\n", mib_counter_label[j],
- h->counters[j], mib_counter_label[j]) != EOF;
-
- if (ok)
- ok &= fprintf(f, " </host>\n") != EOF;
- }
-
-
- for (i = 0; ok && i < sk_VALIDATION_STATUS_num(rc.validation_status); i++) {
- VALIDATION_STATUS *v = sk_VALIDATION_STATUS_value(rc.validation_status, i);
+ for (i = 0; ok && i < sk_validation_status_t_num(rc.validation_status); i++) {
+ validation_status_t *v = sk_validation_status_t_value(rc.validation_status, i);
assert(v);
tad_tm = gmtime(&v->timestamp);
strftime(tad, sizeof(tad), "%Y-%m-%dT%H:%M:%SZ", tad_tm);
- ok &= fprintf(f, " <validation_status timestamp=\"%s\" status=\"%s\">%s</validation_status>\n",
- tad, mib_counter_label[v->code], v->uri) != EOF;
+ for (code = (mib_counter_t) 0; ok && code < MIB_COUNTER_T_MAX; code++) {
+ if (validation_status_get_code(v, code)) {
+ if (ok)
+ ok &= fprintf(f, " <validation_status timestamp=\"%s\" status=\"%s\"",
+ tad, mib_counter_label[code]) != EOF;
+ if (ok && (v->generation == object_generation_current ||
+ v->generation == object_generation_backup))
+ ok &= fprintf(f, " generation=\"%s\"",
+ object_generation_label[v->generation]) != EOF;
+ if (ok)
+ ok &= fprintf(f, ">%s</validation_status>\n", v->uri.s) != EOF;
+ }
+ }
}
if (ok)
@@ -3695,21 +4612,16 @@ int main(int argc, char *argv[])
/*
* Do NOT free cfg_section, NCONF_free() takes care of that
*/
- sk_X509_pop_free(certs, X509_free);
sk_OPENSSL_STRING_pop_free(rc.rsync_cache, OPENSSL_STRING_free);
sk_OPENSSL_STRING_pop_free(rc.backup_cache, OPENSSL_STRING_free);
- sk_OPENSSL_STRING_pop_free(rc.stale_cache, OPENSSL_STRING_free);
- sk_HOST_MIB_COUNTER_pop_free(rc.host_counters, HOST_MIB_COUNTER_free);
- sk_VALIDATION_STATUS_pop_free(rc.validation_status, VALIDATION_STATUS_free);
+ sk_OPENSSL_STRING_pop_free(rc.dead_host_cache, OPENSSL_STRING_free);
+ sk_validation_status_t_pop_free(rc.validation_status, validation_status_t_free);
X509_STORE_free(rc.x509_store);
NCONF_free(cfg_handle);
CONF_modules_free();
BIO_free(bio);
EVP_cleanup();
ERR_free_strings();
- free(rc.authenticated);
- free(rc.old_authenticated);
- free(rc.unauthenticated);
if (rc.rsync_program)
free(rc.rsync_program);
if (lockfile && lockfd >= 0)
@@ -3722,10 +4634,10 @@ int main(int argc, char *argv[])
if (start) {
finish = time(0);
logmsg(&rc, log_telemetry,
- "Finished, elapsed time %d:%02d:%02d",
- (finish - start) / 3600,
- (finish - start) / 60 % 60,
- (finish - start) % 60);
+ "Finished, elapsed time %u:%02u:%02u",
+ (unsigned) ((finish - start) / 3600),
+ (unsigned) ((finish - start) / 60 % 60),
+ (unsigned) ((finish - start) % 60));
}
return ret;