*/
RCSID("$Id$")
+#include <freeradius-devel/crl/crl.h>
#include <freeradius-devel/server/base.h>
#include <freeradius-devel/server/module_rlm.h>
+#include <freeradius-devel/io/coord_pair.h>
#include <freeradius-devel/tls/strerror.h>
#include <freeradius-devel/tls/utils.h>
#include <openssl/x509v3.h>
-#include <openssl/pem.h>
-#include <openssl/asn1.h>
-#include <openssl/bn.h>
/** Thread specific structure to hold requests awaiting CRL fetching */
typedef struct {
fr_rb_tree_t pending; //!< Requests yielded while the CRL is being fetched.
+ fr_coord_worker_t *cw; //!< Worker side of coordinator communication.
+ fr_rb_tree_t crls; //!< CRLs fetched from the coordinator.
+ fr_rb_tree_t fails; //!< Recent CRLs which have failed to fetch.
} rlm_crl_thread_t;
-/** Global tree of CRLs
- *
- * Separate from the instance data because that's protected.
- */
-typedef struct {
- fr_rb_tree_t *crls; //!< A tree of CRLs organised by CDP URL.
- fr_timer_list_t *timer_list; //!< The timer list to use for CRL expiry.
- ///< This gets serviced by the main loop.
- rlm_crl_thread_t *fetching; //!< Pointer to thread instance data of
- ///< thread which is fetching a CRL.
- pthread_mutex_t mutex;
-} rlm_crl_mutable_t;
-
typedef struct {
- CONF_SECTION *virtual_server; //!< Virtual server to use when retrieving CRLs
- fr_time_delta_t force_expiry; //!< Force expiry of CRLs after this time
- bool force_expiry_is_set;
- fr_time_delta_t force_delta_expiry; //!< Force expiry of delta CRLs after this time
- bool force_delta_expiry_is_set;
- fr_time_delta_t early_refresh; //!< Time interval before nextUpdate to refresh
- char const *ca_file; //!< File containing certs for verifying CRL signatures.
- char const *ca_path; //!< Directory containing certs for verifying CRL signatures.
- X509_STORE *verify_store; //!< Store of certificates to verify CRL signatures.
- rlm_crl_mutable_t *mutable; //!< Mutable data that's shared between all threads.
- CONF_SECTION *cs; //!< Module instance config.
- bool trigger_rate_limit; //!< Rate limit triggers.
+ fr_time_delta_t retry_delay; //!< Time to hold off between CRL fetching failures.
+ fr_coord_pair_reg_t *coord_pair_reg; //!< coord_pair registration for fetching CRLs.
+ fr_coord_reg_t *coord_reg; //!< coord registration for fetching CRLs.
} rlm_crl_t;
-/** A single CRL in the global list of CRLs */
+/** A single CRL in the thread specific list of CRLs */
typedef struct {
X509_CRL *crl; //!< The CRL.
char const *cdp_url; //!< The URL of the CRL.
- ASN1_INTEGER *crl_num; //!< The CRL number.
- fr_timer_t *ev; //!< When to expire the CRL
fr_rb_node_t node; //!< The node in the tree
fr_value_box_list_t delta_urls; //!< URLs from which a delta CRL can be retrieved.
- rlm_crl_t const *inst; //!< The instance of the CRL module.
- rlm_crl_thread_t *thread; //!< The thread which fetched this entry.
} crl_entry_t;
+/** Structure to record recent fetch failures
+ */
+typedef struct {
+ char const *cdp_url; //!< The URL which failed to fetch.
+ fr_rb_node_t node; //!< Node in the tree of failures.
+ fr_time_t fail_time; //!< When did the failure occur.
+} crl_fail_t;
+
/** Structure to record a request which is waiting for CRL fetching to complete */
typedef struct {
request_t *request;
} rlm_crl_rctx_t;
static conf_parser_t module_config[] = {
- { FR_CONF_OFFSET_IS_SET("force_expiry", FR_TYPE_TIME_DELTA, 0, rlm_crl_t, force_expiry) },
- { FR_CONF_OFFSET_IS_SET("force_delta_expiry", FR_TYPE_TIME_DELTA, 0, rlm_crl_t, force_delta_expiry) },
- { FR_CONF_OFFSET("early_refresh", rlm_crl_t, early_refresh) },
- { FR_CONF_OFFSET("ca_file", rlm_crl_t, ca_file) },
- { FR_CONF_OFFSET("ca_path", rlm_crl_t, ca_path) },
- { FR_CONF_OFFSET("trigger_rate_limit", rlm_crl_t, trigger_rate_limit), .dflt = "yes" },
+ { FR_CONF_OFFSET("retry_delay", rlm_crl_t, retry_delay), .dflt = "30s" },
CONF_PARSER_TERMINATOR
};
-static fr_dict_t const *dict_freeradius;
+/** Callback IDs used by CRL coordinator calls
+ */
+typedef enum {
+ CRL_COORD_PAIR_CALLBACK_ID = 0,
+} rlm_crl_coord_callback_t;
+
+static fr_dict_t const *dict_crl;
extern fr_dict_autoload_t rlm_crl_dict[];
fr_dict_autoload_t rlm_crl_dict[] = {
- { .out = &dict_freeradius, .proto = "freeradius" },
+ { .out = &dict_crl, .proto = "crl" },
DICT_AUTOLOAD_TERMINATOR
};
static fr_dict_attr_t const *attr_crl_data;
static fr_dict_attr_t const *attr_crl_cdp_url;
+static fr_dict_attr_t const *attr_base_crl;
+static fr_dict_attr_t const *attr_delta_crl;
+static fr_dict_attr_t const *attr_packet_type;
extern fr_dict_attr_autoload_t rlm_crl_dict_attr[];
fr_dict_attr_autoload_t rlm_crl_dict_attr[] = {
- { .out = &attr_crl_data, .name = "CRL.Data", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius },
- { .out = &attr_crl_cdp_url, .name = "CRL.CDP-URL", .type = FR_TYPE_STRING, .dict = &dict_freeradius },
+ { .out = &attr_crl_data, .name = "CRL-Data", .type = FR_TYPE_OCTETS, .dict = &dict_crl },
+ { .out = &attr_crl_cdp_url, .name = "CDP-URL", .type = FR_TYPE_STRING, .dict = &dict_crl },
+ { .out = &attr_base_crl, .name = "Base-CRL", .type = FR_TYPE_STRING, .dict = &dict_crl },
+ { .out = &attr_delta_crl, .name = "Delta-CRL", .type = FR_TYPE_STRING, .dict = &dict_crl },
+ { .out = &attr_packet_type, .name = "Packet-Type", .type = FR_TYPE_UINT32, .dict = &dict_crl },
DICT_AUTOLOAD_TERMINATOR
};
typedef struct {
- tmpl_t *http_exp; //!< The xlat expansion used to retrieve the CRL via http://
- tmpl_t *ldap_exp; //!< The xlat expansion used to retrieve the CRL via ldap://
- tmpl_t *ftp_exp; //!< The xlat expansion used to retrieve the CRL via ftp://
fr_value_box_t serial; //!< The serial to check
fr_value_box_list_head_t *cdp; //!< The CRL distribution points
} rlm_crl_env_t;
static const call_env_method_t crl_env = {
FR_CALL_ENV_METHOD_OUT(rlm_crl_env_t),
.env = (call_env_parser_t[]){
- { FR_CALL_ENV_SUBSECTION("source", NULL, CALL_ENV_FLAG_SUBSECTION | CALL_ENV_FLAG_PARSE_MISSING,
- ((call_env_parser_t[]) {
- { FR_CALL_ENV_SUBSECTION("dynamic", NULL, CALL_ENV_FLAG_SUBSECTION | CALL_ENV_FLAG_PARSE_MISSING,
- ((call_env_parser_t[]) {
- { FR_CALL_ENV_PARSE_ONLY_OFFSET("http", FR_TYPE_OCTETS, CALL_ENV_FLAG_REQUIRED, rlm_crl_env_t, http_exp )},
- { FR_CALL_ENV_PARSE_ONLY_OFFSET("ldap", FR_TYPE_OCTETS, CALL_ENV_FLAG_NONE, rlm_crl_env_t, ldap_exp )},
- { FR_CALL_ENV_PARSE_ONLY_OFFSET("ftp", FR_TYPE_OCTETS, CALL_ENV_FLAG_NONE, rlm_crl_env_t, ftp_exp )},
- CALL_ENV_TERMINATOR
- }))},
- CALL_ENV_TERMINATOR
- }))},
{ FR_CALL_ENV_OFFSET("serial", FR_TYPE_STRING, CALL_ENV_FLAG_ATTRIBUTE | CALL_ENV_FLAG_REQUIRED | CALL_ENV_FLAG_SINGLE, rlm_crl_env_t, serial),
.pair.dflt = "session-state.TLS-Certificate.Serial", .pair.dflt_quote = T_BARE_WORD },
{ FR_CALL_ENV_OFFSET("cdp", FR_TYPE_STRING, CALL_ENV_FLAG_BARE_WORD_ATTRIBUTE| CALL_ENV_FLAG_REQUIRED | CALL_ENV_FLAG_MULTI | CALL_ENV_FLAG_NULLABLE, rlm_crl_env_t, cdp),
return CMP(strcmp(crl_a->cdp_url, crl_b->cdp_url), 0);
}
-static void crl_free(void *data)
-{
- talloc_free(data);
-}
-
static int8_t crl_pending_cmp(void const *a, void const *b)
{
crl_pending_t const *pending_a = (crl_pending_t const *)a;
return CMP(pending_a->request, pending_b->request);
}
-static void crl_expire(fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
+static int8_t crl_fail_cmp(void const *a, void const *b)
{
- crl_entry_t *crl = talloc_get_type_abort(uctx, crl_entry_t);
+ crl_fail_t const *fail_a = (crl_fail_t const *)a;
+ crl_fail_t const *fail_b = (crl_fail_t const *)b;
- DEBUG2("CRL associated with CDP %s expired", crl->cdp_url);
-
- /*
- * If the mutex is locked and this thread is fetching a CRL, asynchonously,
- * insert a new timer event - otherwise the mutex will never be unlocked.
- */
- if (pthread_mutex_trylock(&crl->inst->mutable->mutex) != 0) {
- if (crl->inst->mutable->fetching == crl->thread) {
- if (fr_timer_in(crl, tl, &crl->ev, fr_time_delta_from_sec(1), false, crl_expire, crl) <0) {
- ERROR("Failed inserting CRL expiry event");
- }
- return;
- }
- pthread_mutex_lock(&crl->inst->mutable->mutex);
- }
- fr_rb_remove(crl->inst->mutable->crls, crl);
- pthread_mutex_unlock(&crl->inst->mutable->mutex);
-
- if (trigger_enabled()) {
- fr_pair_list_t args;
- fr_pair_t *vp;
- fr_pair_list_init(&args);
- MEM((vp = fr_pair_afrom_da_nested(crl, &args, attr_crl_cdp_url)));
- fr_value_box_strdup_shallow(&vp->data, NULL, crl->cdp_url, true);
- trigger(unlang_interpret_get_thread_default(), crl->inst->cs, NULL, "modules.crl.expired",
- crl->inst->trigger_rate_limit, &args);
- }
-
- talloc_free(crl);
-}
-
-/** Make sure we don't lock up the server if a request is cancelled
- */
-static void crl_signal(module_ctx_t const *mctx, UNUSED request_t *request, fr_signal_t action)
-{
- rlm_crl_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_crl_t);
-
- if (action == FR_SIGNAL_CANCEL) {
- pthread_mutex_unlock(&inst->mutable->mutex);
- pair_delete_request(attr_crl_cdp_url);
- }
+ return CMP(strcmp(fail_a->cdp_url, fail_b->cdp_url), 0);
}
/** See if a particular serial is present in a CRL list
static int _crl_entry_free(crl_entry_t *crl_entry)
{
X509_CRL_free(crl_entry->crl);
- if (crl_entry->crl_num) ASN1_INTEGER_free(crl_entry->crl_num);
return 0;
}
-/** Add an entry to the cdp_url -> crl tree
+/** Request a CRL from the coordinator
*
- * @note Must be called with the mutex held.
+ * @param inst Module instance
+ * @param t Thread data
+ * @param cdp_urls List of URLs to fetch the CRL from
+ * @param base_crl URL of the base CRL if a delta is being requested.
+ * @return
+ * - 0 on success
+ * - -1 on failure
*/
-static crl_entry_t *crl_entry_create(rlm_crl_t const *inst, fr_timer_list_t *tl, char const *url, uint8_t const *data,
- crl_entry_t *base_crl)
-{
- uint8_t const *our_data = data;
- crl_entry_t *crl;
- time_t next_update;
- fr_time_t now = fr_time();
- fr_time_delta_t expiry_time;
- int i;
- STACK_OF(DIST_POINT) *dps;
- X509_STORE_CTX *verify_ctx = NULL;
- X509_OBJECT *xobj;
- EVP_PKEY *pkey;
-
- MEM(crl = talloc_zero(inst->mutable->crls, crl_entry_t));
- crl->cdp_url = talloc_bstrdup(crl, url);
- crl->crl = d2i_X509_CRL(NULL, (const unsigned char **)&our_data, talloc_array_length(our_data));
- if (crl->crl == NULL) {
- fr_tls_strerror_printf("Failed to parse CRL from %s", url);
+static int crl_fetch_start(rlm_crl_t const *inst, rlm_crl_thread_t *t, fr_value_box_list_t *cdp_urls, char const *base_crl) {
+ fr_pair_list_t list;
+ fr_pair_t *vp;
+ TALLOC_CTX *local = talloc_new(NULL);
+ int ret;
+
+ fr_pair_list_init(&list);
+ fr_pair_list_append_by_da(local, vp, &list, attr_packet_type, (uint32_t)FR_CRL_CRL_FETCH, false);
+ if (!vp) {
error:
- talloc_free(crl);
- if (verify_ctx) X509_STORE_CTX_free(verify_ctx);
- return NULL;
- }
- talloc_set_destructor(crl, _crl_entry_free);
-
- verify_ctx = X509_STORE_CTX_new();
- if (!verify_ctx || !X509_STORE_CTX_init(verify_ctx, inst->verify_store, NULL, NULL)) {
- fr_tls_strerror_printf("Error initialising X509 store");
- goto error;
- }
-
- xobj = X509_STORE_CTX_get_obj_by_subject(verify_ctx, X509_LU_X509,
- X509_CRL_get_issuer(crl->crl));
- if (!xobj) {
- fr_tls_strerror_printf("CRL issuer certificate not in trusted store");
- goto error;
- }
- pkey = X509_get_pubkey(X509_OBJECT_get0_X509(xobj));
- X509_OBJECT_free(xobj);
- if (!pkey) {
- fr_tls_strerror_printf("Error getting CRL issuer public key");
- goto error;
- }
- i = X509_CRL_verify(crl->crl, pkey);
- EVP_PKEY_free(pkey);
-
- if (i < 0) {
- fr_tls_strerror_printf("Could not verify CRL signature");
- goto error;
- }
- if (i == 0) {
- fr_tls_strerror_printf("CRL certificate signature failed");
- goto error;
- }
-
- crl->crl_num = X509_CRL_get_ext_d2i(crl->crl, NID_crl_number, &i, NULL);
-
- /*
- * If we're passed a base_crl, then this is a delta - check the delta
- * relates to the correct base.
- */
- if (base_crl) {
- ASN1_INTEGER *base_num = X509_CRL_get_ext_d2i(crl->crl, NID_delta_crl, &i, NULL);
- if (!base_num) {
- fr_tls_strerror_printf("Delta CRL missing Delta CRL Indicator extension");
- goto error;
- }
- if (ASN1_INTEGER_cmp(base_num, base_crl->crl_num) > 0) {
- uint64_t delta_base, crl_num;
- ASN1_INTEGER_get_uint64(&delta_base, base_num);
- ASN1_INTEGER_get_uint64(&crl_num, base_crl->crl_num);
- fr_tls_strerror_printf("Delta CRL referrs to base CRL number %"PRIu64", current base is %"PRIu64,
- delta_base, crl_num);
- ASN1_INTEGER_free(base_num);
- goto error;
- }
- ASN1_INTEGER_free(base_num);
- if (ASN1_INTEGER_cmp(crl->crl_num, base_crl->crl_num) < 0) {
- uint64_t delta_num, crl_num;
- ASN1_INTEGER_get_uint64(&delta_num, crl->crl_num);
- ASN1_INTEGER_get_uint64(&crl_num, base_crl->crl_num);
- fr_tls_strerror_printf("Delta CRL number %"PRIu64" is less than base CRL number %"PRIu64,
- delta_num, crl_num);
- goto error;
- }
+ talloc_free(local);
+ return -1;
}
- if (fr_tls_utils_asn1time_to_epoch(&next_update, X509_CRL_get0_nextUpdate(crl->crl)) < 0) {
- fr_tls_strerror_printf("Failed to parse nextUpdate from CRL");
- goto error;
+ fr_value_box_list_foreach(cdp_urls, cdp) {
+ if (fr_pair_append_by_da(local, &vp, &list, attr_crl_cdp_url) < 0) goto error;
+ if (fr_value_box_copy(vp, &vp->data, cdp) < 0) goto error;
}
- if (!fr_rb_insert(inst->mutable->crls, crl)) {
- ERROR("Failed to insert CRL into tree of CRLs");
- goto error;
+ if (base_crl) {
+ if (fr_pair_append_by_da(local, &vp, &list, attr_base_crl) < 0) goto error;
+ fr_value_box_strdup(vp, &vp->data, NULL, base_crl, false);
}
- crl->inst = inst;
- /*
- * Check if this CRL has a Freshest CRL extension - the list of URIs to get deltas from
- */
- fr_value_box_list_init(&crl->delta_urls);
- if (!base_crl && (dps = X509_CRL_get_ext_d2i(crl->crl, NID_freshest_crl, NULL, NULL))) {
- DIST_POINT *dp;
- STACK_OF(GENERAL_NAME) *names;
- GENERAL_NAME *name;
- int j;
- fr_value_box_t *vb;
-
- for (i = 0; i < sk_DIST_POINT_num(dps); i++) {
- dp = sk_DIST_POINT_value(dps, i);
- names = dp->distpoint->name.fullname;
- for (j = 0; j < sk_GENERAL_NAME_num(names); j++) {
- name = sk_GENERAL_NAME_value(names, j);
- if (name->type != GEN_URI) continue;
- MEM(vb = fr_value_box_alloc_null(crl));
- fr_value_box_bstrndup(vb, vb, NULL,
- (char const *)ASN1_STRING_get0_data(name->d.uniformResourceIdentifier),
- ASN1_STRING_length(name->d.uniformResourceIdentifier), true);
- DEBUG3("CRL references delta URI %pV", vb);
- fr_value_box_list_insert_tail(&crl->delta_urls, vb);
- }
- }
- CRL_DIST_POINTS_free(dps);
- }
+ ret = fr_worker_to_coord_pair_send(t->cw, inst->coord_pair_reg, &list);
- expiry_time = fr_time_delta_sub(fr_time_sub(fr_time_from_sec(next_update), now), inst->early_refresh);
- if (base_crl && inst->force_delta_expiry_is_set) {
- if (fr_time_delta_cmp(expiry_time, inst->force_delta_expiry)) expiry_time = inst->force_delta_expiry;
- } else {
- if (inst->force_expiry_is_set &&
- (fr_time_delta_cmp(expiry_time, inst->force_expiry) > 0)) expiry_time = inst->force_expiry;
- }
+ talloc_free(local);
- DEBUG3("CRL from %s will expire in %pVs", url, fr_box_time_delta(expiry_time));
- if (fr_timer_in(crl, tl, &crl->ev, expiry_time, false, crl_expire, crl) <0) {
- ERROR("Failed to set timer to expire CRL");
- }
-
- X509_STORE_CTX_free(verify_ctx);
- return crl;
+ return ret;
}
-static unlang_action_t CC_HINT(nonnull) crl_process_cdp_data(unlang_result_t *p_result, module_ctx_t const *mctx,
- request_t *request);
-
-/** Yield to a tmpl to retrieve CRL data
- *
- * @param request the current request.
- * @param inst module instance data.
- * @param thread thread instance data.
- * @param env the call_env for this module call.
- * @param rctx the resume ctx for this module call.
- *
- * @returns
- * - 1 - new tmpl pushed.
- * - 0 - no tmpl pushed, soft fail.
- * - -1 - no tmpl pushed, hard fail
- */
-static int crl_tmpl_yield(request_t *request, rlm_crl_t const *inst, rlm_crl_thread_t *thread, rlm_crl_env_t *env,
- rlm_crl_rctx_t *rctx)
+static void crl_by_url_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
{
- fr_pair_t *vp;
- tmpl_t *vpt;
-
- MEM(pair_update_request(&vp, attr_crl_cdp_url) >= 0);
- MEM(fr_value_box_copy(vp, &vp->data, rctx->cdp_url) == 0);
-
- if (strncmp(rctx->cdp_url->vb_strvalue, "http", 4) == 0) {
- vpt = env->http_exp;
- } else if (strncmp(rctx->cdp_url->vb_strvalue, "ldap", 4) == 0) {
- if (!env->ldap_exp) {
- RWARN("CRL URI %pV requires LDAP, but the crl module ldap expansion is not configured", rctx->cdp_url);
- return 0;
- }
- vpt = env->ldap_exp;
- } else if (strncmp(rctx->cdp_url->vb_strvalue, "ftp", 3) == 0) {
- if (!env->ftp_exp) {
- RWARN("CRL URI %pV requires FTP, but the crl module ftp expansion is not configured", rctx->cdp_url);
- return 0;
- }
- vpt = env->ftp_exp;
- } else {
- RERROR("Unsupported URI scheme in CRL URI %pV", rctx->cdp_url);
- return -1;
- }
+ rlm_crl_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
+ crl_pending_t find, *found;
- trigger(unlang_interpret_get_thread_default(), inst->cs, NULL, "modules.crl.fetchuri", inst->trigger_rate_limit,
- &request->request_pairs);
+ find = (crl_pending_t) {
+ .request = request
+ };
- if (unlang_module_yield_to_tmpl(rctx, &rctx->crl_data, request, vpt,
- NULL, crl_process_cdp_data, crl_signal, 0, rctx) < 0) return -1;
- inst->mutable->fetching = thread;
- return 1;
-}
+ found = fr_rb_find(&t->pending, &find);
+ if (!found) return;
-static unlang_action_t crl_by_url_start(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request);
+ fr_rb_remove(&t->pending, found);
+ talloc_free(found);
+}
-/** Process the response from evaluating the cdp_url -> crl_data expansion
- *
- * This is the resumption function when we yield to get CRL data associated with a URL
- */
-static unlang_action_t CC_HINT(nonnull) crl_process_cdp_data(unlang_result_t *p_result, module_ctx_t const *mctx,
- request_t *request)
+static unlang_action_t CC_HINT(nonnull) mod_crl_by_url(unlang_result_t *p_result, module_ctx_t const *mctx,
+ request_t *request)
{
- rlm_crl_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_crl_t);
- rlm_crl_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
- rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
- rlm_crl_rctx_t *rctx = talloc_get_type_abort(mctx->rctx, rlm_crl_rctx_t);
- crl_ret_t ret = CRL_NOT_FOUND;
- rlm_rcode_t rcode = RLM_MODULE_FAIL;
- crl_pending_t *pending;
-
- inst->mutable->fetching = NULL;
- switch (fr_value_box_list_num_elements(&rctx->crl_data)) {
- case 0:
- REDEBUG("No CRL data returned from %pV, failing", rctx->cdp_url);
- trigger(unlang_interpret_get_thread_default(), inst->cs, NULL, "modules.crl.fetchfail",
- inst->trigger_rate_limit, &request->request_pairs);
- again:
- talloc_free(rctx->cdp_url);
-
- /*
- * If there are more URIs to try, push a new tmpl to expand.
- */
- rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
- if (rctx->cdp_url) {
- switch (crl_tmpl_yield(request, inst, t, env, rctx)) {
- case 0:
- goto again;
- case 1:
- return UNLANG_ACTION_PUSHED_CHILD;
- default:
- break;
- }
- }
- fail:
- fr_value_box_list_talloc_free(&rctx->crl_data);
- pair_delete_request(attr_crl_cdp_url);
- goto finish;
-
- case 1:
- {
- crl_entry_t *crl_entry;
- fr_value_box_t *crl_data = fr_value_box_list_pop_head(&rctx->crl_data);
-
- crl_entry = crl_entry_create(inst, unlang_interpret_event_list(request)->tl,
- rctx->cdp_url->vb_strvalue,
- crl_data->vb_octets, rctx->base_crl);
- talloc_free(crl_data);
- if (!crl_entry) {
- RPERROR("Failed to process returned CRL data");
- trigger(unlang_interpret_get_thread_default(), inst->cs, NULL, "modules.crl.fetchbad",
- inst->trigger_rate_limit, &request->request_pairs);
- goto again;
- }
-
- /*
- * We've successfully loaded a URI - so we can clear the list of missing crls
- * This can then be re-used to hold missing delta CRLs if needed.
- */
- fr_value_box_list_talloc_free(&rctx->missing_crls);
-
- if (fr_value_box_list_num_elements(&crl_entry->delta_urls) > 0) {
- crl_entry_t *delta, find;
- fr_value_box_t *vb = NULL, *delta_uri;
-
- rctx->status = CRL_CHECK_DELTA;
- while ((vb = fr_value_box_list_next(&crl_entry->delta_urls, vb))) {
- find.cdp_url = vb->vb_strvalue;
- delta = fr_rb_find(inst->mutable->crls, &find);
- if (delta) {
- ret = crl_check_entry(delta, request, env->serial.vb_octets);
- /*
- * The delta contained an entry for this serial - so this
- * is the return status.
- */
- if (ret != CRL_ENTRY_NOT_FOUND) break;
- } else {
- delta_uri = fr_value_box_acopy(rctx, vb);
- fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
- }
- }
+ rlm_crl_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_crl_t);
+ rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
+ rlm_crl_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
+ fr_value_box_t *cdp = NULL;
+ crl_entry_t *found;
+ fr_value_box_list_t missing;
+ fr_value_box_t *uri;
+ char const *base_url = NULL;
+ int ret;
+ crl_pending_t *pending;
+ crl_fail_t find, *fail;
- /*
- * None of the delta CRL URIs were found, so go and get one.
- * The list of URIs to fetch will now be in rctx->missing_crls
- */
- if (ret == CRL_NOT_FOUND) {
- rctx->status = CRL_CHECK_FETCH_DELTA;
- rctx->base_crl = crl_entry;
- goto again;
- }
- }
+ fr_value_box_list_init(&missing);
- if (rctx->status != CRL_CHECK_DELTA) ret = crl_check_entry(crl_entry, request, env->serial.vb_octets);
- check_return:
- switch (ret) {
+ while ((cdp = fr_value_box_list_next(env->cdp, cdp))) {
+ switch (crl_check_serial(&t->crls, request, cdp->vb_strvalue, env->serial.vb_octets, &found)) {
case CRL_ENTRY_FOUND:
- rcode = RLM_MODULE_REJECT;
- goto finish;
+ RETURN_UNLANG_REJECT;
case CRL_ENTRY_NOT_FOUND:
- /*
- * We have a CRL, but the serial is not in it.
- *
- * If this was after fetching a delta, go check the base
- */
- if (rctx->status == CRL_CHECK_FETCH_DELTA) {
- RDEBUG3("Certificate not in delta CRL, checking base CRL");
- rctx->status = CRL_CHECK_BASE;
- ret = crl_check_entry(rctx->base_crl, request, env->serial.vb_octets);
- goto check_return;
- }
- FALL_THROUGH;
-
case CRL_ENTRY_REMOVED:
- rcode = RLM_MODULE_OK;
- pair_delete_request(attr_crl_cdp_url);
- goto finish;
+ RETURN_UNLANG_OK;
case CRL_ERROR:
- goto fail;
+ continue;
- /*
- * This should never be returned by crl_check_entry because we provided the entry!
- */
- case CRL_MISSING_DELTA:
case CRL_NOT_FOUND:
- fr_assert(0);
- goto fail;
- }
+ uri = fr_value_box_acopy(NULL, cdp);
+ fr_value_box_list_insert_tail(&missing, uri);
+ continue;
+ case CRL_MISSING_DELTA:
+ fr_value_box_t *vb = NULL;
+ fr_value_box_list_talloc_free(&missing);
+ while ((vb = fr_value_box_list_next(&found->delta_urls, vb))) {
+ uri = fr_value_box_acopy(NULL, vb);
+ fr_value_box_list_insert_tail(&missing, uri);
+ }
+ base_url = cdp->vb_strvalue;
+ break;
+ }
}
- break;
- default:
- REDEBUG("Too many CRL values returned, failing");
- goto fail;
+ if (fr_value_box_list_num_elements(&missing) == 0) RETURN_UNLANG_FAIL;
+
+ find = (crl_fail_t) {
+ .cdp_url = fr_value_box_list_head(&missing)->vb_strvalue
+ };
+
+ /*
+ * Check to see if the missing CRL has failed to be fetched
+ * recently. If it has, within the retry delay time, then
+ * fail this request.
+ */
+ fail = fr_rb_find(&t->fails, &find);
+ if (fail) {
+ if (fr_time_gt(fr_time_add(fail->fail_time, inst->retry_delay), fr_time())) {
+ fr_value_box_list_talloc_free(&missing);
+ RETURN_UNLANG_FAIL;
+ }
+
+ fr_rb_delete(&t->fails, fail);
}
-finish:
- pthread_mutex_unlock(&inst->mutable->mutex);
- pending = fr_rb_first(&t->pending);
- if (pending) unlang_interpret_mark_runnable(pending->request);
- RETURN_UNLANG_RCODE(rcode);
-}
+ ret = crl_fetch_start(inst, t, &missing, base_url);
-static unlang_action_t CC_HINT(nonnull(1,2,3,4,6)) crl_by_url(unlang_result_t *p_result, rlm_crl_t const *inst,
- rlm_crl_thread_t *t, rlm_crl_env_t *env,
- rlm_crl_rctx_t *rctx, request_t *request)
-{
- rlm_rcode_t rcode = RLM_MODULE_NOOP;
- crl_entry_t *found;
+ fr_value_box_list_talloc_free(&missing);
- if (!rctx) rctx = talloc_zero(unlang_interpret_frame_talloc_ctx(request), rlm_crl_rctx_t);
- fr_value_box_list_init(&rctx->missing_crls);
+ if (ret < 0) RETURN_UNLANG_FAIL;
- pthread_mutex_lock(&inst->mutable->mutex);
+ if (unlang_module_yield(request, mod_crl_by_url, crl_by_url_cancel,
+ ~FR_SIGNAL_CANCEL, mctx->rctx) != UNLANG_ACTION_YIELD) RETURN_UNLANG_FAIL;
- /*
- * Fast path when we have a CRL.
- * All distribution points are considered equivalent, so check if
- * if we have any of them before attempting to fetch missing ones.
- */
- while ((rctx->cdp_url = fr_value_box_list_pop_head(env->cdp))) {
- switch (crl_check_serial(inst->mutable->crls, request, rctx->cdp_url->vb_strvalue,
- env->serial.vb_octets, &found)) {
- case CRL_ENTRY_FOUND:
- rcode = RLM_MODULE_REJECT;
- break;
+ MEM(pending = talloc_zero(t, crl_pending_t));
+ pending->request = request;
- case CRL_ENTRY_NOT_FOUND:
- case CRL_ENTRY_REMOVED:
- rcode = RLM_MODULE_OK;
- break;
+ if (!fr_rb_insert(&t->pending, pending)) {
+ talloc_free(pending);
+ RETURN_UNLANG_FAIL;
+ }
- case CRL_ERROR:
- continue;
+ RDEBUG3("Yielding request until CRL fetching completed");
- case CRL_NOT_FOUND:
- fr_value_box_list_insert_tail(&rctx->missing_crls, rctx->cdp_url);
- rcode = RLM_MODULE_NOTFOUND;
- continue;
+ return UNLANG_ACTION_YIELD;
+}
- case CRL_MISSING_DELTA:
- {
- /*
- * We found a base CRL, but it has a delta which
- * was not found. Populate the "missing" list with
- * the CDP for the delta and go get it.
- */
- fr_value_box_t *vb = NULL, *delta_uri;
- rctx->base_crl = found;
- rctx->status = CRL_CHECK_FETCH_DELTA;
- fr_value_box_list_talloc_free(&rctx->missing_crls);
- while ((vb = fr_value_box_list_next(&found->delta_urls, vb))) {
- delta_uri = fr_value_box_acopy(rctx, vb);
- fr_value_box_list_insert_tail(&rctx->missing_crls, delta_uri);
- }
- goto fetch_missing;
- }
- }
+/** Resume requests waiting for a CRL fetch.
+ */
+static void crl_pending_resume(rlm_crl_thread_t *thread)
+{
+ crl_pending_t *pending;
+ fr_rb_iter_inorder_t iter;
+
+ for (pending = fr_rb_iter_init_inorder(&thread->pending, &iter);
+ pending;
+ pending = fr_rb_iter_next_inorder(&thread->pending, &iter)) {
+ fr_rb_iter_delete_inorder(&thread->pending, &iter);
+ unlang_interpret_mark_runnable(pending->request);
+ talloc_free(pending);
+ }
+}
+
+/** Callback for worker receiving Fetch-OK packet from coordinator
+ */
+static void recv_crl_ok(UNUSED fr_coord_worker_t *cw, UNUSED fr_coord_pair_reg_t *coord_pair_reg,
+ fr_pair_list_t const *list, UNUSED fr_time_t now,
+ module_ctx_t *mctx, UNUSED void *uctx)
+{
+ fr_pair_t *url, *crl, *delta = NULL;
+ crl_entry_t *crl_entry, find;
+ rlm_crl_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
+ uint8_t const *data;
+
+ url = fr_pair_find_by_da(list, NULL, attr_crl_cdp_url);
+ if (!url) {
+ ERROR("Missing URL");
+ return;
}
- if (rcode != RLM_MODULE_NOTFOUND) {
- crl_pending_t *pending;
- finish:
- pthread_mutex_unlock(&inst->mutable->mutex);
+ find = (crl_entry_t) {
+ .cdp_url = url->vp_strvalue
+ };
- pending = fr_rb_first(&t->pending);
- if (pending) unlang_interpret_mark_runnable(pending->request);
+ crl_entry = fr_rb_find(&thread->crls, &find);
- RETURN_UNLANG_RCODE(rcode);
+ crl = fr_pair_find_by_da(list, NULL, attr_crl_data);
+ if (!crl) {
+ ERROR("No CRL data");
+ return;
}
/*
- * Need to convert a missing cdp_url to a CRL entry
- *
- * We yield to an expansion to allow this to happen, then parse the CRL data
- * and check if the serial has an entry in the CRL.
+ * If this CRL didn't previously exist, create the entry
*/
-fetch_missing:
- fr_value_box_list_init(&rctx->crl_data);
+ if (!crl_entry) {
+ MEM(crl_entry = talloc_zero(thread, crl_entry_t));
+ crl_entry->cdp_url = talloc_strdup(crl_entry, url->vp_strvalue);
+ fr_value_box_list_init(&crl_entry->delta_urls);
+ if (!fr_rb_insert(&thread->crls, crl_entry)) {
+ talloc_free(crl_entry);
+ return;
+ }
+ talloc_set_destructor(crl_entry, _crl_entry_free);
+ } else {
+ X509_CRL_free(crl_entry->crl);
+ fr_value_box_list_talloc_free(&crl_entry->delta_urls);
+ }
-again:
- rctx->cdp_url = fr_value_box_list_pop_head(&rctx->missing_crls);
+ data = crl->vp_octets;
+ crl_entry->crl = d2i_X509_CRL(NULL, (const unsigned char **)&data, crl->vp_size);
+ if (unlikely(!crl_entry->crl)) {
+ ERROR("Failed to parse CRL");
+ error:
+ fr_rb_remove(&thread->crls, crl_entry);
+ talloc_free(crl_entry);
+ }
- switch (crl_tmpl_yield(request, inst, t, env, rctx)) {
- case 0:
- goto again;
- case 1:
- /*
- * The lock is released after the pushed tmpl result is handled
- */
- /* coverity[missing_unlock] */
- return UNLANG_ACTION_PUSHED_CHILD;
- default:
- rcode = RLM_MODULE_FAIL;
- goto finish;
+ DEBUG3("CRL %pP refreshed", crl);
+
+ while ((delta = fr_pair_find_by_da(list, delta, attr_delta_crl))) {
+ fr_value_box_t *vb;
+ MEM(vb = fr_value_box_alloc_null(crl_entry));
+ if (unlikely(fr_value_box_copy(vb, vb, &delta->data) < 0)) goto error;
+ fr_value_box_list_insert_tail(&crl_entry->delta_urls, vb);
}
+
+ crl_pending_resume(thread);
}
-static unlang_action_t CC_HINT(nonnull) crl_by_url_resume(unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
+/** Callback for worker receiving Fetch-Fail packet from coordinator
+ */
+static void recv_crl_fail(UNUSED fr_coord_worker_t *cw, UNUSED fr_coord_pair_reg_t *coord_pair_reg,
+ fr_pair_list_t const *list, fr_time_t now, module_ctx_t *mctx, UNUSED void *uctx)
{
- rlm_crl_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_crl_t);
- rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
- rlm_crl_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
- crl_pending_t find, *found;
+ fr_pair_t *vp;
+ rlm_crl_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
- find.request = request;
- found = fr_rb_find(&t->pending, &find);
- if (!found) RETURN_UNLANG_NOOP;
+ /*
+ * Record the URL of the fetch that failed.
+ */
+ vp = fr_pair_find_by_da(list, NULL, attr_crl_cdp_url);
+ if (vp) {
+ crl_fail_t *fail;
+
+ MEM(fail = talloc_zero(thread, crl_fail_t));
+ fail->cdp_url = talloc_strdup(fail, vp->vp_strvalue);
+ fail->fail_time = now;
- fr_rb_delete(&t->pending, found);
- return crl_by_url(p_result, inst, t, env, mctx->rctx, request);
+ if (unlikely(!fr_rb_insert(&thread->fails, fail))) {
+ talloc_free(fail);
+ }
+ }
+
+ crl_pending_resume(thread);
}
-static void crl_by_url_cancel(module_ctx_t const *mctx, request_t *request, UNUSED fr_signal_t action)
+static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
{
rlm_crl_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
- crl_pending_t *found, find;
- find.request = request;
- found = fr_rb_find(&t->pending, &find);
- if (!found) return;
+ fr_rb_inline_init(&t->pending, crl_pending_t, node, crl_pending_cmp, NULL);
+ fr_rb_inline_init(&t->crls, crl_entry_t, node, crl_cmp, NULL);
+ fr_rb_inline_init(&t->fails, crl_fail_t, node, crl_fail_cmp, NULL);
- fr_rb_delete(&t->pending, found);
+ return 0;
}
-static unlang_action_t CC_HINT(nonnull) crl_by_url_start(unlang_result_t *p_result, module_ctx_t const *mctx,
- request_t *request)
+static int mod_coord_attach(module_thread_inst_ctx_t const *mctx)
{
- rlm_crl_t const *inst = talloc_get_type_abort_const(mctx->mi->data, rlm_crl_t);
- rlm_crl_env_t *env = talloc_get_type_abort(mctx->env_data, rlm_crl_env_t);
rlm_crl_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
- crl_pending_t *pending;
+ rlm_crl_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
- if (fr_value_box_list_num_elements(env->cdp) == 0) RETURN_UNLANG_NOOP;
+ t->cw = fr_coord_attach(t, mctx->el, inst->coord_reg);
- if (inst->mutable->fetching != t) return crl_by_url(p_result, inst, t, env, mctx->rctx, request);
-
- MEM(pending = talloc_zero(t, crl_pending_t));
- pending->request = request;
+ if (!t->cw) {
+ ERROR("Failed to attach to coordinator");
+ return -1;
+ }
- fr_rb_insert(&t->pending, pending);
- RDEBUG3("Yielding request until CRL fetching completed");
- return unlang_module_yield(request, crl_by_url_resume, crl_by_url_cancel, ~FR_SIGNAL_CANCEL, mctx->rctx);
+ return 0;
}
-static int mod_thread_instantiate(module_thread_inst_ctx_t const *mctx)
+static int mod_thread_detach(module_thread_inst_ctx_t const *mctx)
{
rlm_crl_thread_t *t = talloc_get_type_abort(mctx->thread, rlm_crl_thread_t);
- fr_rb_inline_init(&t->pending, crl_pending_t, node, crl_pending_cmp, NULL);
-
- return 0;
-}
+ if (!t->cw) return 0;
-static int mod_mutable_free(rlm_crl_mutable_t *mutable)
-{
- pthread_mutex_destroy(&mutable->mutex);
+ fr_coord_detach(t->cw, true);
+ t->cw = NULL;
return 0;
}
{
rlm_crl_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
- if (inst->verify_store) X509_STORE_free(inst->verify_store);
- talloc_free(inst->mutable);
+ fr_coord_deregister(inst->coord_reg);
return 0;
}
+
+static fr_coord_cb_reg_t coord_callbacks[] = {
+ FR_COORD_PAIR_CALLBACK(CRL_COORD_PAIR_CALLBACK_ID),
+ FR_COORD_CALLBACK_TERMINATOR
+};
+
+static fr_coord_worker_cb_reg_t worker_callbacks[] = {
+ FR_COORD_WORKER_PAIR_CALLBACK(CRL_COORD_PAIR_CALLBACK_ID),
+ FR_COORD_CALLBACK_TERMINATOR
+};
+
+static fr_coord_worker_pair_cb_reg_t worker_pair_callbacks[] = {
+ { .packet_type = FR_CRL_FETCH_OK, .callback = recv_crl_ok },
+ { .packet_type = FR_CRL_FETCH_FAIL, .callback = recv_crl_fail },
+ FR_COORD_CALLBACK_TERMINATOR
+};
#endif
/** Instantiate the module
#ifdef WITH_TLS
rlm_crl_t *inst = talloc_get_type_abort(mctx->mi->data, rlm_crl_t);
- MEM(inst->mutable = talloc_zero(NULL, rlm_crl_mutable_t));
- MEM(inst->mutable->crls = fr_rb_inline_talloc_alloc(inst->mutable, crl_entry_t, node, crl_cmp, crl_free));
- pthread_mutex_init(&inst->mutable->mutex, NULL);
- talloc_set_destructor(inst->mutable, mod_mutable_free);
-
- if (!inst->ca_file && !inst->ca_path) {
- cf_log_err(mctx->mi->conf, "Missing ca_file / ca_path option. One or other (or both) must be specified.");
- fail:
- talloc_free(inst->mutable);
- return -1;
- }
+ inst->coord_pair_reg = fr_coord_pair_register(inst,
+ &(fr_coord_pair_reg_ctx_t) {
+ .worker_cb = worker_pair_callbacks,
+ .cb_id = CRL_COORD_PAIR_CALLBACK_ID,
+ .root = fr_dict_root(dict_crl),
+ .cs = mctx->mi->conf,
+ }
+ );
+ if (!inst->coord_pair_reg) return -1;
- inst->verify_store = X509_STORE_new();
- if (!X509_STORE_load_locations(inst->verify_store, inst->ca_file, inst->ca_path)) {
- cf_log_err(mctx->mi->conf, "Failed reading Trusted root CA file \"%s\" and path \"%s\"",
- inst->ca_file, inst->ca_path);
- goto fail;
- }
+ FR_COORD_PAIR_CB_CTX_SET(coord_callbacks, worker_callbacks, inst->coord_pair_reg);
- X509_STORE_set_purpose(inst->verify_store, X509_PURPOSE_SSL_CLIENT);
+ inst->coord_reg = fr_coord_register(inst,
+ &(fr_coord_reg_ctx_t) {
+ .name = mctx->mi->name,
+ .coord_cb = coord_callbacks,
+ .worker_cb = worker_callbacks,
+ .mi = mctx->mi
+ });
- inst->cs = mctx->mi->conf;
+ if (!inst->coord_reg) return -1;
return 0;
#else
MODULE_THREAD_INST(rlm_crl_thread_t),
#ifdef WITH_TLS
.thread_instantiate = mod_thread_instantiate,
+ .thread_detach = mod_thread_detach,
+ .coord_attach = mod_coord_attach,
.detach = mod_detach,
#endif
},
#ifdef WITH_TLS
.method_group = {
.bindings = (module_method_binding_t[]){
- { .section = SECTION_NAME(CF_IDENT_ANY, CF_IDENT_ANY), .method = crl_by_url_start, .method_env = &crl_env },
+ { .section = SECTION_NAME(CF_IDENT_ANY, CF_IDENT_ANY), .method = mod_crl_by_url, .method_env = &crl_env },
MODULE_BINDING_TERMINATOR
}
}