Pass a threadsafe ctx into fr_connection_pool create callback
[freeradius.git] / src / modules / rlm_yubikey / validate.c
1 /**
2  * $Id$
3  * @file validate.c
4  * @brief Authentication for yubikey OTP tokens using the ykclient library.
5  *
6  * @author Arran Cudbard-Bell <a.cudbardb@networkradius.com>
7  * @copyright 2013 The FreeRADIUS server project
8  * @copyright 2013 Network RADIUS <info@networkradius.com>
9  */
10 #include "rlm_yubikey.h"
11
12 #ifdef HAVE_YKCLIENT
13 #include <freeradius-devel/connection.h>
14
15 /** Frees a ykclient handle
16  *
17  * @param[in] yandle rlm_yubikey_handle_t to close and free.
18  * @return returns 0.
19  */
20 static int _mod_conn_free(ykclient_handle_t **yandle)
21 {
22         ykclient_handle_done(yandle);
23
24         return 0;
25 }
26
27 /** Creates a new connection handle for use by the FR connection API.
28  *
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.
32  *
33  * @see mod_conn_delete
34  * @see fr_connection_pool_init
35  * @see fr_connection_create_t
36  * @see connection.c
37  *
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
41  *      be initialised.
42  */
43 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
44 {
45         rlm_yubikey_t *inst = instance;
46         ykclient_rc status;
47         ykclient_handle_t *yandle, **marker;
48
49         status = ykclient_handle_init(inst->ykc, &yandle);
50         if (status != YKCLIENT_OK) {
51                 ERROR("rlm_yubikey (%s): %s", inst->name, ykclient_strerror(status));
52
53                 return NULL;
54         }
55         marker = talloc(ctx, ykclient_handle_t *);
56         talloc_set_destructor(marker, _mod_conn_free);
57         *marker = yandle;
58
59         return yandle;
60 }
61
62 int rlm_yubikey_ykclient_init(CONF_SECTION *conf, rlm_yubikey_t *inst)
63 {
64         ykclient_rc status;
65         CONF_SECTION *servers;
66
67         char prefix[100];
68
69         int count = 0;
70
71         if (!inst->client_id) {
72                 ERROR("rlm_yubikey (%s): validation.client_id must be set (to a valid id) when validation is enabled",
73                       inst->name);
74
75                 return -1;
76         }
77
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",
80                       inst->name);
81
82                 return -1;
83         }
84
85         DEBUG("rlm_yubikey (%s): Initialising ykclient", inst->name);
86
87         status = ykclient_global_init();
88         if (status != YKCLIENT_OK) {
89 yk_error:
90                 ERROR("rlm_yubikey (%s): %s", ykclient_strerror(status), inst->name);
91
92                 return -1;
93         }
94
95         status = ykclient_init(&inst->ykc);
96         if (status != YKCLIENT_OK) {
97                 goto yk_error;
98         }
99
100         servers = cf_section_sub_find(conf, "servers");
101         if (servers) {
102                 CONF_PAIR *uri, *first;
103                 /*
104                  *      If there were no uris configured we just use the default
105                  *      ykclient uris which point to the yubico servers.
106                  */
107                 first = uri = cf_pair_find(servers, "uri");
108                 if (!uri) {
109                         goto init;
110                 }
111
112                 while (uri) {
113                         count++;
114                         uri = cf_pair_find_next(servers, uri, "uri");
115                 }
116                 inst->uris = talloc_zero_array(inst, char const *, count);
117
118                 uri = first;
119                 count = 0;
120                 while (uri) {
121                         inst->uris[count++] = cf_pair_value(uri);
122                         uri = cf_pair_find_next(servers, uri, "uri");
123                 }
124                 if (count) {
125                         status = ykclient_set_url_templates(inst->ykc, count, inst->uris);
126                         if (status != YKCLIENT_OK) {
127                                 goto yk_error;
128                         }
129                 }
130         }
131
132 init:
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);
136
137                 return -1;
138         }
139
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);
144
145                 return -1;
146         }
147
148         return 0;
149 }
150
151 int rlm_yubikey_ykclient_detach(rlm_yubikey_t *inst)
152 {
153         fr_connection_pool_delete(inst->conn_pool);
154         ykclient_done(&inst->ykc);
155         ykclient_global_done();
156
157         return 0;
158 }
159
160 rlm_rcode_t rlm_yubikey_validate(rlm_yubikey_t *inst, REQUEST *request,  char const *passcode)
161 {
162         rlm_rcode_t rcode = RLM_MODULE_OK;
163         ykclient_rc status;
164         ykclient_handle_t *yandle;
165
166         yandle = fr_connection_get(inst->conn_pool);
167         if (!yandle) return RLM_MODULE_FAIL;
168
169         /*
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.
172          *
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
176          *      is used.
177          *
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
180          *      can be re-used.
181          *
182          */
183         ykclient_handle_cleanup(yandle);
184
185         status = ykclient_request_process(inst->ykc, yandle, passcode);
186         if (status != YKCLIENT_OK) {
187                 REDEBUG("%s", ykclient_strerror(status));
188                 switch (status) {
189                 case YKCLIENT_BAD_OTP:
190                 case YKCLIENT_REPLAYED_OTP:
191                         rcode = RLM_MODULE_REJECT;
192                         break;
193
194                 case YKCLIENT_NO_SUCH_CLIENT:
195                         rcode = RLM_MODULE_NOTFOUND;
196                         break;
197
198                 default:
199                         rcode = RLM_MODULE_FAIL;
200                 }
201         }
202
203         fr_connection_release(inst->conn_pool, yandle);
204
205         return rcode;
206 }
207 #endif