4 * @brief Authentication for yubikey OTP tokens using the ykclient library.
6 * @author Arran Cudbard-Bell <a.cudbardb@networkradius.com>
7 * @copyright 2013 The FreeRADIUS server project
8 * @copyright 2013 Network RADIUS <info@networkradius.com>
10 #include "rlm_yubikey.h"
13 #include <freeradius-devel/connection.h>
15 /** Frees a ykclient handle
17 * @param[in] yandle rlm_yubikey_handle_t to close and free.
20 static int _mod_conn_free(ykclient_handle_t **yandle)
22 ykclient_handle_done(yandle);
27 /** Creates a new connection handle for use by the FR connection API.
29 * Matches the fr_connection_create_t function prototype, is passed to
30 * fr_connection_pool_init, and called when a new connection is required by the
31 * connection pool API.
33 * @see fr_connection_pool_init
34 * @see fr_connection_create_t
37 * @param[in] ctx to allocate connection data from.
38 * @param[in] instance configuration data.
39 * @return connection handle or NULL if the connection failed or couldn't
42 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
44 rlm_yubikey_t *inst = instance;
46 ykclient_handle_t *yandle, **marker;
48 status = ykclient_handle_init(inst->ykc, &yandle);
49 if (status != YKCLIENT_OK) {
50 ERROR("rlm_yubikey (%s): %s", inst->name, ykclient_strerror(status));
54 marker = talloc(ctx, ykclient_handle_t *);
55 talloc_set_destructor(marker, _mod_conn_free);
61 int rlm_yubikey_ykclient_init(CONF_SECTION *conf, rlm_yubikey_t *inst)
64 CONF_SECTION *servers;
70 if (!inst->client_id) {
71 ERROR("rlm_yubikey (%s): validation.client_id must be set (to a valid id) when validation is enabled",
77 if (!inst->api_key || !*inst->api_key || is_zero(inst->api_key)) {
78 ERROR("rlm_yubikey (%s): validation.api_key must be set (to a valid key) when validation is enabled",
84 DEBUG("rlm_yubikey (%s): Initialising ykclient", inst->name);
86 status = ykclient_global_init();
87 if (status != YKCLIENT_OK) {
89 ERROR("rlm_yubikey (%s): %s", ykclient_strerror(status), inst->name);
94 status = ykclient_init(&inst->ykc);
95 if (status != YKCLIENT_OK) {
99 servers = cf_section_sub_find(conf, "servers");
101 CONF_PAIR *uri, *first;
103 * If there were no uris configured we just use the default
104 * ykclient uris which point to the yubico servers.
106 first = uri = cf_pair_find(servers, "uri");
113 uri = cf_pair_find_next(servers, uri, "uri");
115 inst->uris = talloc_zero_array(inst, char const *, count);
120 inst->uris[count++] = cf_pair_value(uri);
121 uri = cf_pair_find_next(servers, uri, "uri");
124 status = ykclient_set_url_templates(inst->ykc, count, inst->uris);
125 if (status != YKCLIENT_OK) {
132 status = ykclient_set_client_b64(inst->ykc, inst->client_id, inst->api_key);
133 if (status != YKCLIENT_OK) {
134 ERROR("rlm_yubikey (%s): Failed setting API credentials: %s", ykclient_strerror(status), inst->name);
139 snprintf(prefix, sizeof(prefix), "rlm_yubikey (%s)", inst->name);
140 inst->conn_pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, prefix);
141 if (!inst->conn_pool) {
142 ykclient_done(&inst->ykc);
150 int rlm_yubikey_ykclient_detach(rlm_yubikey_t *inst)
152 fr_connection_pool_delete(inst->conn_pool);
153 ykclient_done(&inst->ykc);
154 ykclient_global_done();
159 rlm_rcode_t rlm_yubikey_validate(rlm_yubikey_t *inst, REQUEST *request, char const *passcode)
161 rlm_rcode_t rcode = RLM_MODULE_OK;
163 ykclient_handle_t *yandle;
165 yandle = fr_connection_get(inst->conn_pool);
166 if (!yandle) return RLM_MODULE_FAIL;
169 * The libcurl multi-handle interface will tear down the TCP sockets for any partially completed
170 * requests when their easy handle is removed from the multistack.
172 * For performance reasons ykclient will stop processing the request immediately after receiving
173 * a response from one of the servers. If we then immediately call ykclient_handle_cleanup
174 * the connections are destroyed and will need to be re-established the next time the handle
177 * To try and prevent this from happening, we leave cleanup until the *next* time
178 * the handle is used, by which time the requests will of hopefully completed and the connections
182 ykclient_handle_cleanup(yandle);
184 status = ykclient_request_process(inst->ykc, yandle, passcode);
185 if (status != YKCLIENT_OK) {
186 REDEBUG("%s", ykclient_strerror(status));
188 case YKCLIENT_BAD_OTP:
189 case YKCLIENT_REPLAYED_OTP:
190 rcode = RLM_MODULE_REJECT;
193 case YKCLIENT_NO_SUCH_CLIENT:
194 rcode = RLM_MODULE_NOTFOUND;
198 rcode = RLM_MODULE_FAIL;
202 fr_connection_release(inst->conn_pool, yandle);