Minor connection API fixes
[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 fr_connection_pool_init
34  * @see fr_connection_create_t
35  * @see connection.c
36  *
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
40  *      be initialised.
41  */
42 static void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
43 {
44         rlm_yubikey_t *inst = instance;
45         ykclient_rc status;
46         ykclient_handle_t *yandle, **marker;
47
48         status = ykclient_handle_init(inst->ykc, &yandle);
49         if (status != YKCLIENT_OK) {
50                 ERROR("rlm_yubikey (%s): %s", inst->name, ykclient_strerror(status));
51
52                 return NULL;
53         }
54         marker = talloc(ctx, ykclient_handle_t *);
55         talloc_set_destructor(marker, _mod_conn_free);
56         *marker = yandle;
57
58         return yandle;
59 }
60
61 int rlm_yubikey_ykclient_init(CONF_SECTION *conf, rlm_yubikey_t *inst)
62 {
63         ykclient_rc status;
64         CONF_SECTION *servers;
65
66         char prefix[100];
67
68         int count = 0;
69
70         if (!inst->client_id) {
71                 ERROR("rlm_yubikey (%s): validation.client_id must be set (to a valid id) when validation is enabled",
72                       inst->name);
73
74                 return -1;
75         }
76
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",
79                       inst->name);
80
81                 return -1;
82         }
83
84         DEBUG("rlm_yubikey (%s): Initialising ykclient", inst->name);
85
86         status = ykclient_global_init();
87         if (status != YKCLIENT_OK) {
88 yk_error:
89                 ERROR("rlm_yubikey (%s): %s", ykclient_strerror(status), inst->name);
90
91                 return -1;
92         }
93
94         status = ykclient_init(&inst->ykc);
95         if (status != YKCLIENT_OK) {
96                 goto yk_error;
97         }
98
99         servers = cf_section_sub_find(conf, "servers");
100         if (servers) {
101                 CONF_PAIR *uri, *first;
102                 /*
103                  *      If there were no uris configured we just use the default
104                  *      ykclient uris which point to the yubico servers.
105                  */
106                 first = uri = cf_pair_find(servers, "uri");
107                 if (!uri) {
108                         goto init;
109                 }
110
111                 while (uri) {
112                         count++;
113                         uri = cf_pair_find_next(servers, uri, "uri");
114                 }
115                 inst->uris = talloc_zero_array(inst, char const *, count);
116
117                 uri = first;
118                 count = 0;
119                 while (uri) {
120                         inst->uris[count++] = cf_pair_value(uri);
121                         uri = cf_pair_find_next(servers, uri, "uri");
122                 }
123                 if (count) {
124                         status = ykclient_set_url_templates(inst->ykc, count, inst->uris);
125                         if (status != YKCLIENT_OK) {
126                                 goto yk_error;
127                         }
128                 }
129         }
130
131 init:
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);
135
136                 return -1;
137         }
138
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);
143
144                 return -1;
145         }
146
147         return 0;
148 }
149
150 int rlm_yubikey_ykclient_detach(rlm_yubikey_t *inst)
151 {
152         fr_connection_pool_delete(inst->conn_pool);
153         ykclient_done(&inst->ykc);
154         ykclient_global_done();
155
156         return 0;
157 }
158
159 rlm_rcode_t rlm_yubikey_validate(rlm_yubikey_t *inst, REQUEST *request,  char const *passcode)
160 {
161         rlm_rcode_t rcode = RLM_MODULE_OK;
162         ykclient_rc status;
163         ykclient_handle_t *yandle;
164
165         yandle = fr_connection_get(inst->conn_pool);
166         if (!yandle) return RLM_MODULE_FAIL;
167
168         /*
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.
171          *
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
175          *      is used.
176          *
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
179          *      can be re-used.
180          *
181          */
182         ykclient_handle_cleanup(yandle);
183
184         status = ykclient_request_process(inst->ykc, yandle, passcode);
185         if (status != YKCLIENT_OK) {
186                 REDEBUG("%s", ykclient_strerror(status));
187                 switch (status) {
188                 case YKCLIENT_BAD_OTP:
189                 case YKCLIENT_REPLAYED_OTP:
190                         rcode = RLM_MODULE_REJECT;
191                         break;
192
193                 case YKCLIENT_NO_SUCH_CLIENT:
194                         rcode = RLM_MODULE_NOTFOUND;
195                         break;
196
197                 default:
198                         rcode = RLM_MODULE_FAIL;
199                 }
200         }
201
202         fr_connection_release(inst->conn_pool, yandle);
203
204         return rcode;
205 }
206 #endif