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 mod_conn_delete
34 * @see fr_connection_pool_init
35 * @see fr_connection_create_t
38 * @param[in] ctx to allocate connection data from.
39 * @param[in] instance configuration data.
40 * @return connection handle or NULL if the connection failed or couldn't
43 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
45 rlm_yubikey_t *inst = instance;
47 ykclient_handle_t *yandle, **marker;
49 status = ykclient_handle_init(inst->ykc, &yandle);
50 if (status != YKCLIENT_OK) {
51 ERROR("rlm_yubikey (%s): %s", inst->name, ykclient_strerror(status));
55 marker = talloc(ctx, ykclient_handle_t *);
56 talloc_set_destructor(marker, _mod_conn_free);
62 int rlm_yubikey_ykclient_init(CONF_SECTION *conf, rlm_yubikey_t *inst)
65 CONF_SECTION *servers;
71 if (!inst->client_id) {
72 ERROR("rlm_yubikey (%s): validation.client_id must be set (to a valid id) when validation is enabled",
78 if (!inst->api_key || !*inst->api_key || is_zero(inst->api_key)) {
79 ERROR("rlm_yubikey (%s): validation.api_key must be set (to a valid key) when validation is enabled",
85 DEBUG("rlm_yubikey (%s): Initialising ykclient", inst->name);
87 status = ykclient_global_init();
88 if (status != YKCLIENT_OK) {
90 ERROR("rlm_yubikey (%s): %s", ykclient_strerror(status), inst->name);
95 status = ykclient_init(&inst->ykc);
96 if (status != YKCLIENT_OK) {
100 servers = cf_section_sub_find(conf, "servers");
102 CONF_PAIR *uri, *first;
104 * If there were no uris configured we just use the default
105 * ykclient uris which point to the yubico servers.
107 first = uri = cf_pair_find(servers, "uri");
114 uri = cf_pair_find_next(servers, uri, "uri");
116 inst->uris = talloc_zero_array(inst, char const *, count);
121 inst->uris[count++] = cf_pair_value(uri);
122 uri = cf_pair_find_next(servers, uri, "uri");
125 status = ykclient_set_url_templates(inst->ykc, count, inst->uris);
126 if (status != YKCLIENT_OK) {
133 status = ykclient_set_client_b64(inst->ykc, inst->client_id, inst->api_key);
134 if (status != YKCLIENT_OK) {
135 ERROR("rlm_yubikey (%s): Failed setting API credentials: %s", ykclient_strerror(status), inst->name);
140 snprintf(prefix, sizeof(prefix), "rlm_yubikey (%s)", inst->name);
141 inst->conn_pool = fr_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, prefix);
142 if (!inst->conn_pool) {
143 ykclient_done(&inst->ykc);
151 int rlm_yubikey_ykclient_detach(rlm_yubikey_t *inst)
153 fr_connection_pool_delete(inst->conn_pool);
154 ykclient_done(&inst->ykc);
155 ykclient_global_done();
160 rlm_rcode_t rlm_yubikey_validate(rlm_yubikey_t *inst, REQUEST *request, char const *passcode)
162 rlm_rcode_t rcode = RLM_MODULE_OK;
164 ykclient_handle_t *yandle;
166 yandle = fr_connection_get(inst->conn_pool);
167 if (!yandle) return RLM_MODULE_FAIL;
170 * The libcurl multi-handle interface will tear down the TCP sockets for any partially completed
171 * requests when their easy handle is removed from the multistack.
173 * For performance reasons ykclient will stop processing the request immediately after receiving
174 * a response from one of the servers. If we then immediately call ykclient_handle_cleanup
175 * the connections are destroyed and will need to be re-established the next time the handle
178 * To try and prevent this from happening, we leave cleanup until the *next* time
179 * the handle is used, by which time the requests will of hopefully completed and the connections
183 ykclient_handle_cleanup(yandle);
185 status = ykclient_request_process(inst->ykc, yandle, passcode);
186 if (status != YKCLIENT_OK) {
187 REDEBUG("%s", ykclient_strerror(status));
189 case YKCLIENT_BAD_OTP:
190 case YKCLIENT_REPLAYED_OTP:
191 rcode = RLM_MODULE_REJECT;
194 case YKCLIENT_NO_SUCH_CLIENT:
195 rcode = RLM_MODULE_NOTFOUND;
199 rcode = RLM_MODULE_FAIL;
203 fr_connection_release(inst->conn_pool, yandle);