]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add TLS engine configuration code
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 19 Feb 2021 22:51:53 +0000 (22:51 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 19 Feb 2021 23:50:11 +0000 (23:50 +0000)
src/lib/tls/all.mk
src/lib/tls/base.c
src/lib/tls/engine.c [new file with mode: 0644]
src/lib/tls/engine.h [new file with mode: 0644]

index 9b8673a6830998645850db2e3e8b3770bb79ed81..3874873b5c224175a1fe2570f8fae7a32bedc17e 100644 (file)
@@ -9,6 +9,7 @@ SOURCES := \
        cache.c \
        conf.c \
        ctx.c \
+       engine.c \
        log.c \
        ocsp.c \
        session.c \
index d63c0e00399a9145d6c861833f07c210d0261e2c..da773a0833cf0798247b5090ebde92aa88794065 100644 (file)
@@ -33,9 +33,10 @@ USES_APPLE_DEPRECATED_API    /* OpenSSL API has been deprecated by Apple */
 #include <openssl/conf.h>
 
 #include <freeradius-devel/server/base.h>
+#include <freeradius-devel/tls/attrs.h>
+#include <freeradius-devel/tls/base.h>
+#include <freeradius-devel/tls/engine.h>
 #include <freeradius-devel/util/debug.h>
-#include "base.h"
-#include "attrs.h"
 
 static uint32_t instance_count = 0;
 
@@ -467,10 +468,15 @@ void fr_openssl_free(void)
         *      exiting to prevent memory leaks.
         */
        ERR_remove_thread_state(NULL);
-       ENGINE_cleanup();
+
+       fr_tls_engine_free_all();
+
        CONF_modules_unload(1);
+
        ERR_free_strings();
+
        EVP_cleanup();
+
        CRYPTO_cleanup_all_ex_data();
 
        TALLOC_FREE(global_mutexes);
@@ -487,13 +493,16 @@ void fr_openssl_free(void)
  */
 void fr_openssl_free(void)
 {
+       if (--instance_count > 0) return;
+
+       fr_tls_engine_free_all();
+
        OPENSSL_cleanup();
+
        fr_dict_autofree(tls_dict);
 }
 #endif
 
-
-
 /** Add all the default ciphers and message digests to our context.
  *
  * This should be called exactly once from main, before reading the main config
@@ -501,8 +510,6 @@ void fr_openssl_free(void)
  */
 int fr_openssl_init(void)
 {
-       ENGINE *rand_engine;
-
        if (instance_count > 0) {
                instance_count++;
                return 0;
@@ -521,8 +528,6 @@ int fr_openssl_init(void)
        SSL_load_error_strings();       /* Readable error messages (examples show call before library_init) */
        SSL_library_init();             /* Initialize library */
        OpenSSL_add_all_algorithms();   /* Required for SHA2 in OpenSSL < 0.9.8o and 1.0.0.a */
-       ENGINE_load_builtin_engines();  /* Needed to load AES-NI engine (also loads rdrand, boo) */
-
 #  ifdef HAVE_OPENSSL_EVP_SHA256
        /*
         *      SHA256 is in all versions of OpenSSL, but isn't
@@ -544,17 +549,14 @@ int fr_openssl_init(void)
 
        OPENSSL_config(NULL);
 #else
-       OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_ENGINE_ALL_BUILTIN, NULL);
+       OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL);
 #endif
 
        /*
-        *      Mirror the paranoia found elsewhere on the net,
-        *      and disable rdrand as the default random number
-        *      generator.
+        *      FIXME - This should be done _after_
+        *      running any engine controls.
         */
-       rand_engine = ENGINE_get_default_RAND();
-       if (rand_engine && (strcmp(ENGINE_get_id(rand_engine), "rdrand") == 0)) ENGINE_unregister_RAND(rand_engine);
-       ENGINE_register_all_complete();
+       fr_tls_engine_load_builtin();
 
        instance_count++;
 
diff --git a/src/lib/tls/engine.c b/src/lib/tls/engine.c
new file mode 100644 (file)
index 0000000..ec7df9a
--- /dev/null
@@ -0,0 +1,491 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file tls/engine.c
+ * @brief Initialise and manage OpenSSL engines
+ *
+ * @copyright 2021 The FreeRADIUS server project
+ * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+RCSID("$Id$")
+USES_APPLE_DEPRECATED_API      /* OpenSSL API has been deprecated by Apple */
+
+#ifdef WITH_TLS
+#define LOG_PREFIX "tls - "
+
+#include <freeradius-devel/tls/base.h>
+#include <freeradius-devel/tls/engine.h>
+#include <freeradius-devel/tls/log.h>
+#include <freeradius-devel/util/dlist.h>
+#include <freeradius-devel/util/rbtree.h>
+#include <freeradius-devel/util/strerror.h>
+#include <freeradius-devel/util/value.h>
+
+#include <openssl/engine.h>
+
+/** Our wrapper around an OpenSSL engine
+ *
+ */
+typedef struct {
+       fr_rb_node_t                    node;           //!< rbtree node fields.
+
+       char const                      *id;            //!< Engine identifier.
+       char const                      *instance;      //!< Instance identifier for the engine.
+
+       ENGINE                          *e;             //!< Engine that was loaded.
+       fr_tls_engine_ctrl_list_t       *pre_ctrls;     //!< Pre controls applied to the engine.
+       fr_tls_engine_ctrl_list_t       *post_ctrls;    //!< Post controls applied to the engine.
+} tls_engine_t;
+
+/** Engines that we've loaded
+ *
+ * This is the global set of OpenSSL engines that are in use by the
+ * current configuration.
+ *
+ * We could use OpenSSL's reference counting system, but this doesn't
+ * work well for dynamically loaded engines, where we may want one
+ * instance of the engine per thread.
+ */
+static rbtree_t        *tls_engines;
+
+/** Compares two engines
+ *
+ */
+static int tls_engine_cmp(void const *a, void const *b)
+{
+       tls_engine_t const *our_a = talloc_get_type_abort(a, tls_engine_t);
+       tls_engine_t const *our_b = talloc_get_type_abort(b, tls_engine_t);
+       int8_t ret;
+
+       ret = strcmp(our_a->id, our_b->id);
+       if (ret != 0) return ret;
+
+       /*
+        *      May not have an instance ID
+        */
+       if (!our_a->instance && !our_b->instance) return 0;
+       if (!our_a->instance) return -1;
+       if (!our_b->instance) return +1;
+
+       return strcmp(our_a->instance, our_b->instance);
+}
+
+/** Add the list of supported engine commands to the error stack
+ *
+ * Uses OpenSSL's ridiculously complex ENGINE_ctrl API to provide useful
+ * information about the controls the given engine provides.
+ *
+ * @param[in] e                Engine to return commands for.
+ */
+static void CC_HINT(nonnull) tls_engine_control_notfound_strerror(ENGINE *e, char const *bad_ctrl)
+{
+       int             cmd, ret;
+       TALLOC_CTX      *pool;
+
+       /*
+        *      ENGINE_HAS_CTRL_FUNCTION doesn't seem
+        *      to be available in OpenSSL 1.1.0 so
+        *      we fudge it with this.
+        */
+       bool            first = true;
+
+       pool = talloc_pool(NULL, 256);  /* Avoid lots of mallocs */
+       if (unlikely(!pool)) return;
+
+       fr_strerror_printf("engine %s does not export control %s", ENGINE_get_id(e), bad_ctrl);
+
+       for (cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_FIRST_CMD_TYPE, 0, NULL, NULL);
+            cmd > 0;
+            cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_NEXT_CMD_TYPE, cmd, NULL, NULL)) {
+               size_t name_len, desc_len;
+               char *name, *desc;
+               char const *flags;
+
+               if (!ENGINE_cmd_is_executable(e, cmd)) continue;
+
+               /*
+                *      Copy the ctrl name out to a temporary buffer
+                */
+               name_len = ENGINE_ctrl(e, ENGINE_CTRL_GET_NAME_LEN_FROM_CMD, 0, NULL, NULL);
+               if (unlikely(name_len == 0)) continue;
+
+               name = talloc_array(pool, char, name_len + 1);
+               if (unlikely(!name)) break;
+
+               if (unlikely(ENGINE_ctrl(e, ENGINE_CTRL_GET_NAME_FROM_CMD, 0, name, NULL) <= 0)) break;
+
+               /*
+                *      Copy the ctrl description out to a temporary buffer
+                */
+               desc_len = ENGINE_ctrl(e, ENGINE_CTRL_GET_DESC_LEN_FROM_CMD, 0, NULL, NULL);
+               if (desc_len > 0) {
+                       desc = talloc_array(pool, char, desc_len + 1);
+                       if (unlikely(!desc)) break;
+
+                       if (unlikely(ENGINE_ctrl(e, ENGINE_CTRL_GET_DESC_FROM_CMD, 0, desc, NULL) <= 0)) break;
+               } else {
+                       desc = NULL;
+               }
+
+               ret = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, 0, NULL, NULL);
+               if (ret & ENGINE_CMD_FLAG_NO_INPUT) {
+                       flags = "none";
+               } else if ((ret & ENGINE_CMD_FLAG_NUMERIC) && (ret & ENGINE_CMD_FLAG_STRING)) {
+                       flags = "number and string";
+               } else if (ret & ENGINE_CMD_FLAG_NUMERIC) {
+                       flags = "number";
+               } else if (ret & ENGINE_CMD_FLAG_STRING) {
+                       flags = "string";
+               } else {
+                       flags = "unavailable";
+               }
+
+               if (first) {
+                       fr_strerror_const_push("available controls are:");
+                       first = false;
+               }
+               fr_strerror_printf_push("%s, arg(s) %s%s%s", name, flags, desc ? " - " : "", desc);
+               talloc_free_children(pool);
+       }
+       if (first) fr_strerror_const_push("no controls available");
+
+       talloc_free(pool);
+}
+
+/** Duplicate an engine control
+ *
+ * @param[in] ctx      To allocate new control in.
+ * @param[in] in       control to copy.
+ * @return
+ *     - A copy of the engine control on success.
+ *     - NULL on failure.
+ */
+static inline CC_HINT(always_inline) fr_tls_engine_ctrl_t *tls_engine_ctrl_dup(TALLOC_CTX *ctx,
+                                                                              fr_tls_engine_ctrl_t const *in)
+{
+       fr_tls_engine_ctrl_t *n;
+
+       n = talloc(ctx, fr_tls_engine_ctrl_t);
+       if (unlikely(!n)) {
+               fr_strerror_const("Out of memory");
+               return n;
+       }
+
+       *n = (fr_tls_engine_ctrl_t){
+               .name = talloc_typed_strdup(n, in->name),
+               .value = talloc_typed_strdup(n, in->value)
+       };
+
+       return n;
+}
+
+/** Unloads the underlying OpenSSL engine
+ *
+ */
+static int _tls_engine_free(tls_engine_t *our_e)
+{
+       /*
+        *      Make memory leaks very explicit
+        *      so someone will investigate.
+        */
+       if (unlikely(ENGINE_finish(our_e->e) != 1)) {
+               fr_tls_log_error(NULL, "de-init on engine %s failed", our_e->id);
+               return -1;
+       }
+
+       if (unlikely(ENGINE_free(our_e->e) != 1)) {
+               fr_tls_log_error(NULL, "free on engine %s failed", our_e->id);
+               return -1;
+       }
+
+       return 0;
+}
+
+/** Initialise an OpenSSL engine, adding it to our list of engines
+ *
+ * @note Errors should be retrieved with fr_strerror().
+ *
+ * @param[out] e_out           The engine that was just initialised.
+ *                             The caller must not free/finish this engine
+ *                             it will be called up when the server exits.
+ * @param[in] id               Engine identifier.  This is usually identifier
+ *                             that matches OpenSSL's engine ID e.g. "pkcs11".
+ * @param[in] instance         Instance identifier for a given engine.
+ *                             This is useful for "dynamic" engines, i.e. ones
+ *                             OpenSSL dynamically loads.
+ * @param[in] pre_ctrls                Engine ctls to be used after obtaining a
+ *                             structural reference but before obtaining a
+ *                             functional reference (after loading before init).
+ *                             Will be duplicated to avoid ordering issues.
+ * @param[in] post_ctrls       Engine ctls to be used before unloading an
+ *                             engine (to shut it down in a graceful way).
+ *                             Will be duplicated to avoid ordering issues.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int fr_tls_engine_init(ENGINE **e_out,
+                      char const *id, char const *instance,
+                      fr_tls_engine_ctrl_list_t const *pre_ctrls, fr_tls_engine_ctrl_list_t const *post_ctrls)
+{
+       tls_engine_t            *our_e = NULL;
+       ENGINE                  *e;
+       fr_tls_engine_ctrl_t    *ctrl, *n;
+
+       if (!tls_engines) {
+               tls_engines = rbtree_alloc(NULL, tls_engine_t, node, tls_engine_cmp, rbtree_node_talloc_free, 0);
+               if (unlikely(!tls_engines)) {
+               oom:
+                       fr_strerror_const("Out of memory");
+                       return -1;
+               }
+       } else {
+               tls_engine_t *found = NULL;
+
+               found = rbtree_finddata(tls_engines, &(tls_engine_t){ .id = id, .instance = instance });
+               if (found) {
+                       fr_strerror_printf("engine %s%s%s%salready initialised", id,
+                                          instance ? " (" : "",
+                                          instance ? instance : "",
+                                          instance ? ") " : "");
+                       return -1;
+               }
+       }
+
+       e = ENGINE_by_id(id);
+       if (!e) {
+               fr_strerror_printf("%s engine is not available", id);
+               return -1;
+       }
+
+       if (pre_ctrls) while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) {
+               int cmd, flags, ret;
+
+               cmd = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FROM_NAME, 0, UNCONST(void *, ctrl->name), NULL);
+               if (cmd == 0) {
+                       fr_strerror_printf("%s engine does not implement \"%s\" control", id, ctrl->name);
+                       /*
+                        *      Dumps all available controls to
+                        *      the error stack.
+                        */
+                       tls_engine_control_notfound_strerror(e, ctrl->name);
+               error:
+                       ENGINE_free(e);
+                       return -1;
+               }
+
+               /*
+                *      If the command has the ENGINE_CMD_FLAG_NO_INPUT flag set,
+                *      arg must be NULL and ENGINE_ctrl() is called with i set to
+                *      0 and p set to NULL. Otherwise, arg must not be NULL.
+                *      If the command accepts string input, i is set to 0 and arg
+                *      is passed as the p argument to ENGINE_ctrl(). Otherwise, arg
+                *      is converted with strtol(3) and passed as the i argument to
+                *      ENGINE_ctrl(), setting p to NULL.
+                */
+               flags = ENGINE_ctrl(e, ENGINE_CTRL_GET_CMD_FLAGS, 0, NULL, NULL);
+               if (flags & ENGINE_CMD_FLAG_NO_INPUT) {
+                       ret = ENGINE_ctrl(e, cmd, 0, NULL, NULL);
+               /*
+                *      Do an explicit sanity check for this
+                */
+               } else if (unlikely((flags & ENGINE_CMD_FLAG_STRING) && (flags & ENGINE_CMD_FLAG_NUMERIC))) {
+                       fr_strerror_printf("File bug against freeradius-server stating "
+                                          "both numeric and string commands needed for OpenSSL engine controls");
+                       goto error;
+               /*
+                *      We do an explicit conversion to provide more useful feedback
+                *      to the user in case the log
+                */
+               } else if (flags & ENGINE_CMD_FLAG_NUMERIC) {
+                       fr_value_box_t  vb;
+
+                       if (fr_value_box_cast(NULL, &vb, FR_TYPE_INT32, NULL, fr_box_strvalue(ctrl->value)) < 0) {
+                               fr_strerror_printf_push("control %s requires an integer value", ctrl->name);
+                               goto error;
+                       }
+                       ret = ENGINE_ctrl(e, cmd, vb.vb_int32, NULL, 0);
+               } else if (flags & ENGINE_CMD_FLAG_STRING) {
+                       ret = ENGINE_ctrl(e, cmd, 0, UNCONST(void *, ctrl->value), NULL);
+               } else {
+                       fr_strerror_printf("control %s exports invalid flags", ctrl->name);
+                       goto error;
+               }
+
+               /*
+                *      ENGINE_ctrl_cmd() and ENGINE_ctrl_cmd_string() return 1 on
+                *      success or 0 on error.
+                */
+               if (ret != 1) {
+                       tls_strerror_printf("control %s failed (%i)", ctrl->name, ret);
+                       goto error;
+               }
+       }
+
+       if (unlikely(ENGINE_init(e) != 1)) {
+               tls_strerror_printf("failed initialising engine %s", id);
+               goto error;
+       }
+
+       our_e = talloc(tls_engines, tls_engine_t);
+       if (unlikely(!our_e)) goto oom;
+
+       *our_e = (tls_engine_t){
+               .id = talloc_typed_strdup(our_e, id),
+               .instance = talloc_typed_strdup(our_e, instance),
+               .e = e
+       };
+       talloc_set_destructor(our_e, _tls_engine_free);
+
+       /*
+        *      Duplicate pre and post ctrl lists
+        *
+        *      This will allow us to create thread-specific
+        *      dynamic engines later.
+        */
+       fr_dlist_talloc_init(our_e->pre_ctrls, fr_tls_engine_ctrl_t, entry);
+       fr_dlist_talloc_init(our_e->post_ctrls, fr_tls_engine_ctrl_t, entry);
+
+       if (pre_ctrls) {
+               ctrl = NULL;
+               while ((ctrl = fr_dlist_next(pre_ctrls, ctrl))) {
+                       n = tls_engine_ctrl_dup(our_e, ctrl);
+                       if (unlikely(!n)) {
+                               talloc_free(our_e);
+                               return -1;
+                       }
+                       fr_dlist_insert_tail(our_e->pre_ctrls, n);
+               }
+       }
+
+       if (post_ctrls) {
+               ctrl = NULL;
+               while ((ctrl = fr_dlist_next(post_ctrls, ctrl))) {
+                       n = tls_engine_ctrl_dup(our_e, ctrl);
+                       if (unlikely(!n)) {
+                               talloc_free(our_e);
+                               return -1;
+                       }
+                       fr_dlist_insert_tail(our_e->post_ctrls, n);
+               }
+       }
+
+       *e_out = e;
+       return 0;
+}
+
+/** Retrieve a pointer to an OpenSSL engine
+ *
+ * Does not change the reference count to the engine (we don't use this
+ * particular OpenSSL feature).
+ *
+ * If the engine is not found in the current engine tree and auto_init
+ * if true then it will be initialised with no pre or post ctrls.
+ *
+ * @note Errors should be retrieved with fr_strerror().
+ *
+ * @param[out] e_out           The engine that was just initialised.
+ *                             The caller must not free/finish this engine
+ *                             it will be called up when the server exits.
+ * @param[in] id               Engine identifier.  This is usually identifier
+ *                             that matches OpenSSL's engine ID e.g. "pkcs11".
+ * @param[in] instance         Instance identifier for a given engine.
+ *                             This is useful for "dynamic" engines, i.e. ones
+ *                             OpenSSL dl loads.
+ * @param[in] auto_init                If the engine hasn't already been initialised
+ *                             auto-initialise it now, with no pre or post
+ *                             ctrls.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int fr_tls_engine(ENGINE **e_out, char const *id, char const *instance, bool auto_init)
+{
+       tls_engine_t *found = NULL;
+
+       if (!tls_engines) {
+               if (!auto_init) {
+               not_init:
+                       fr_strerror_printf("engine %s%s%s%snot initialised", id,
+                                          instance ? " (" : "",
+                                          instance ? instance : "",
+                                          instance ? ") " : "");
+                       return -1;
+               }
+
+       do_init:
+               return fr_tls_engine_init(e_out, id, instance, NULL, NULL);
+       }
+
+
+       found = rbtree_finddata(tls_engines, &(tls_engine_t){ .id = id, .instance = instance });
+       if (!found) {
+               if (!auto_init) goto not_init;
+               goto do_init;
+       }
+
+       *e_out = found->e;
+       return 0;
+}
+
+/** Should be called after any engine configuration has been completed
+ *
+ */
+void fr_tls_engine_load_builtin(void)
+{
+       ENGINE_load_builtin_engines();  /* Needed to load AES-NI engine (also loads rdrand, boo) */
+
+       /*
+        *      Mitigate against CrossTalk (CVE-2020-0543)
+        */
+       if (!tls_engines || !rbtree_finddata(tls_engines, &(tls_engine_t){ .id = "rdrand" })) {
+               ENGINE *rand_engine;
+
+               ENGINE_register_all_RAND();     /* Give rand engines a chance to register */
+
+               /*
+                *      If OpenSSL settled on Intel's rdrand
+                *      unregister it and unload rdrand.
+                */
+               rand_engine = ENGINE_get_default_RAND();
+               if (rand_engine && (strcmp(ENGINE_get_id(rand_engine), "rdrand") == 0)) {
+                       ENGINE_unregister_RAND(rand_engine);
+                       ENGINE_finish(rand_engine);     /* Unload rdrand */
+               }
+       }
+       ENGINE_register_all_complete();
+}
+
+/** Free any engines we've loaded
+ *
+ */
+void fr_tls_engine_free_all(void)
+{
+       TALLOC_FREE(tls_engines);
+
+#if OPENSSL_API_COMPAT < 0x10100000L
+       /*
+        *      Free any lingering memory
+        *      OpenSSL man pages say to do this.
+        */
+       ENGINE_cleanup();
+#endif
+}
+
+#endif
diff --git a/src/lib/tls/engine.h b/src/lib/tls/engine.h
new file mode 100644 (file)
index 0000000..e2291a0
--- /dev/null
@@ -0,0 +1,61 @@
+#pragma once
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+#ifdef WITH_TLS
+/**
+ * $Id$
+ *
+ * @file lib/tls/engine.h
+ * @brief Initialise and manage OpenSSL engines
+ *
+ * @copyright 2021 The FreeRADIUS server project
+ * @copyright 2021 Arran Cudbard-Bell <a.cudbardb@freeradius.org>
+ */
+RCSIDH(tls_engine_h, "$Id$")
+
+#include <freeradius-devel/util/dlist.h>
+#include <openssl/engine.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Engine configuration parameters
+ *
+ * Used by fr_tls_global_engine_conf_t.
+ */
+typedef struct {
+       fr_dlist_t      entry;                  //!< Entry in list of controls.
+       char const      *name;                  //!< Name of control.
+       char const      *value;                 //!< Value to pass to control.
+} fr_tls_engine_ctrl_t;
+
+typedef fr_dlist_head_t fr_tls_engine_ctrl_list_t;
+
+int fr_tls_engine_init(ENGINE **e_out,
+                      char const *id, char const *instance,
+                      fr_tls_engine_ctrl_list_t const *pre_ctrls, fr_tls_engine_ctrl_list_t const *post_ctrls);
+
+int fr_tls_engine(ENGINE **e_out, char const *id, char const *instance, bool auto_init);
+
+void fr_tls_engine_load_builtin(void);
+
+void fr_tls_engine_free_all(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif