2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Integrate FreeRADIUS with RESTfull APIs
21 * @copyright 2012-2013 Arran Cudbard-Bell <arran.cudbardb@freeradius.org>
23 #include <freeradius-devel/ident.h>
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/token.h>
35 static CONF_PARSER tls_config[] = {
36 { "cacertfile", PW_TYPE_FILENAME,
37 offsetof(rlm_rest_section_t,tls_cacertfile), NULL, NULL},
38 { "cacertdir", PW_TYPE_FILENAME,
39 offsetof(rlm_rest_section_t,tls_cacertdir), NULL, NULL},
40 { "certfile", PW_TYPE_FILENAME,
41 offsetof(rlm_rest_section_t,tls_certfile), NULL, NULL},
42 { "keyfile", PW_TYPE_FILENAME,
43 offsetof(rlm_rest_section_t,tls_keyfile), NULL, NULL },
44 { "keypassword", PW_TYPE_STRING_PTR,
45 offsetof(rlm_rest_section_t, tls_keypassword), NULL, NULL },
46 { "randfile", PW_TYPE_STRING_PTR, /* OK if it changes on HUP */
47 offsetof(rlm_rest_section_t,tls_randfile), NULL, NULL },
48 { "verify_cert", PW_TYPE_BOOLEAN,
49 offsetof(rlm_rest_section_t, tls_verify_cert), NULL, "yes" },
50 { "verify_cert_cn", PW_TYPE_BOOLEAN,
51 offsetof(rlm_rest_section_t, tls_verify_cert_cn), NULL, "yes" },
53 { NULL, -1, 0, NULL, NULL }
57 * A mapping of configuration file names to internal variables.
59 * Note that the string is dynamically allocated, so it MUST
60 * be freed. When the configuration file parse re-reads the string,
61 * it free's the old one, and strdup's the new one, placing the pointer
62 * to the strdup'd string into 'config.string'. This gets around
65 static const CONF_PARSER section_config[] = {
66 { "uri", PW_TYPE_STRING_PTR,
67 offsetof(rlm_rest_section_t, uri), NULL, "" },
68 { "method", PW_TYPE_STRING_PTR,
69 offsetof(rlm_rest_section_t, method_str), NULL, "GET" },
70 { "body", PW_TYPE_STRING_PTR,
71 offsetof(rlm_rest_section_t, body_str), NULL, "post" },
73 /* User authentication */
74 { "auth", PW_TYPE_STRING_PTR,
75 offsetof(rlm_rest_section_t, auth_str), NULL, "none" },
76 { "username", PW_TYPE_STRING_PTR,
77 offsetof(rlm_rest_section_t, username), NULL, "" },
78 { "password", PW_TYPE_STRING_PTR,
79 offsetof(rlm_rest_section_t, password), NULL, "" },
80 { "require_auth", PW_TYPE_BOOLEAN,
81 offsetof(rlm_rest_section_t, require_auth), NULL, "no"},
83 /* Transfer configuration */
84 { "timeout", PW_TYPE_INTEGER,
85 offsetof(rlm_rest_section_t, timeout), NULL, "0" },
86 { "chunk", PW_TYPE_INTEGER,
87 offsetof(rlm_rest_section_t, chunk), NULL, "0" },
90 { "tls", PW_TYPE_SUBSECTION, 0, NULL, (const void *) tls_config },
92 { NULL, -1, 0, NULL, NULL }
95 static const CONF_PARSER module_config[] = {
96 { "connect_uri", PW_TYPE_STRING_PTR,
97 offsetof(rlm_rest_t, connect_uri), NULL, "http://localhost/" },
99 { NULL, -1, 0, NULL, NULL }
102 static int rlm_rest_perform (rlm_rest_t *instance, rlm_rest_section_t *section,
103 void *handle, REQUEST *request)
106 char uri[REST_URI_MAX_LEN];
110 RDEBUG("Expanding URI components");
112 * Build xlat'd URI, this allows REST servers to be specified by
113 * request attributes.
115 uri_len = rest_uri_build(instance, section, request, uri, sizeof(uri));
116 if (uri_len <= 0) return -1;
118 RDEBUG("Sending HTTP %s to \"%s\"",
119 fr_int2str(http_method_table, section->method, NULL),
123 * Configure various CURL options, and initialise the read/write
126 ret = rest_request_config(instance, section, request,
131 if (ret <= 0) return -1;
134 * Send the CURL request, pre-parse headers, aggregate incoming
135 * HTTP body data into a single contiguous buffer.
137 ret = rest_request_perform(instance, section, handle);
138 if (ret <= 0) return -1;
143 static void rlm_rest_cleanup (rlm_rest_t *instance, rlm_rest_section_t *section,
146 rest_request_cleanup(instance, section, handle);
149 static int parse_sub_section(CONF_SECTION *parent,
150 rlm_rest_t *instance, rlm_rest_section_t *config,
151 rlm_components_t comp)
155 const char *name = section_type_value[comp].section;
157 cs = cf_section_sub_find(parent, name);
159 /* TODO: Should really setup section with default values */
163 if (cf_section_parse(cs, config, section_config) < 0) {
164 radlog(L_ERR, "rlm_rest (%s): Parsing config section failed",
165 instance->xlat_name);
170 * Add section name (Maybe add to headers later?).
175 * Convert HTTP method auth and body type strings into their
176 * integer equivalents.
178 config->auth = fr_str2int(http_auth_table, config->auth_str,
181 if (config->auth == HTTP_AUTH_UNKNOWN) {
182 radlog(L_ERR, "rlm_rest (%s): Unknown HTTP auth type \"%s\"",
183 instance->xlat_name, config->auth_str);
187 if (!http_curl_auth[config->auth]) {
188 radlog(L_ERR, "rlm_rest (%s): Unsupported HTTP auth type \"%s\""
189 ", check libcurl version, OpenSSL build configuration,"
190 " then recompile this module",
191 instance->xlat_name, config->auth_str);
195 config->method = fr_str2int(http_method_table, config->method_str,
198 config->body = fr_str2int(http_body_type_table, config->body_str,
201 if (config->body == HTTP_BODY_UNKNOWN) {
202 radlog(L_ERR, "rlm_rest (%s): Unknown HTTP body type \"%s\"",
203 instance->xlat_name, config->body_str);
207 if (http_body_type_supported[config->body] == HTTP_BODY_UNSUPPORTED) {
208 radlog(L_ERR, "rlm_rest (%s): Unsupported HTTP body type \"%s\""
209 ", please submit patches", instance->xlat_name,
218 * Do any per-module initialization that is separate to each
219 * configured instance of the module. e.g. set up connections
220 * to external databases, read configuration files, set up
221 * dictionary entries, etc.
223 * If configuration information is given in the config section
224 * that must be referenced in later calls, store a handle to it
225 * in *instance otherwise put a null pointer there.
227 static int rlm_rest_instantiate(CONF_SECTION *conf, void **instance)
230 const char *xlat_name;
233 * Allocate memory for instance data.
235 *instance = data = talloc_zero(conf, rlm_rest_t);
236 if (!data) return -1;
239 * If the configuration parameters can't be parsed, then
242 if (cf_section_parse(conf, data, module_config) < 0) {
246 xlat_name = cf_section_name2(conf);
247 if (xlat_name == NULL) {
248 xlat_name = cf_section_name1(conf);
251 data->xlat_name = xlat_name;
254 * Parse sub-section configs.
257 (parse_sub_section(conf, data, &data->authorize,
258 RLM_COMPONENT_AUTZ) < 0) ||
259 (parse_sub_section(conf, data, &data->authenticate,
260 RLM_COMPONENT_AUTH) < 0) ||
261 (parse_sub_section(conf, data, &data->accounting,
262 RLM_COMPONENT_ACCT) < 0) ||
263 (parse_sub_section(conf, data, &data->checksimul,
264 RLM_COMPONENT_SESS) < 0) ||
265 (parse_sub_section(conf, data, &data->postauth,
266 RLM_COMPONENT_POST_AUTH) < 0))
272 * Initialise REST libraries.
274 if (!rest_init(data)) {
278 data->conn_pool = fr_connection_pool_init(conf, data,
283 if (!data->conn_pool) {
291 * Find the named user in this modules database. Create the set
292 * of attribute-value pairs to check and reply with for this user
293 * from the database. The authentication code only needs to check
294 * the password, the rest is done here.
296 static rlm_rcode_t rlm_rest_authorize(void *instance, REQUEST *request)
298 rlm_rest_t *my_instance = instance;
299 rlm_rest_section_t *section = &my_instance->authorize;
303 int rcode = RLM_MODULE_OK;
306 handle = fr_connection_get(my_instance->conn_pool);
307 if (!handle) return RLM_MODULE_FAIL;
309 ret = rlm_rest_perform(instance, section, handle, request);
311 rcode = RLM_MODULE_FAIL;
315 hcode = rest_get_handle_code(handle);
320 rcode = RLM_MODULE_NOTFOUND;
323 rcode = RLM_MODULE_USERLOCK;
327 * Attempt to parse content if there was any.
329 ret = rest_request_decode(my_instance, section,
332 rcode = RLM_MODULE_FAIL;
336 rcode = RLM_MODULE_REJECT;
339 rcode = RLM_MODULE_OK;
343 * Attempt to parse content if there was any.
345 if ((hcode >= 200) && (hcode < 300)) {
346 ret = rest_request_decode(my_instance, section,
348 if (ret < 0) rcode = RLM_MODULE_FAIL;
349 else if (ret == 0) rcode = RLM_MODULE_OK;
350 else rcode = RLM_MODULE_UPDATED;
352 } else if (hcode < 500) {
353 rcode = RLM_MODULE_INVALID;
355 rcode = RLM_MODULE_FAIL;
361 rlm_rest_cleanup(instance, section, handle);
363 fr_connection_release(my_instance->conn_pool, handle);
369 * Authenticate the user with the given password.
371 static rlm_rcode_t rlm_rest_authenticate(void *instance, REQUEST *request)
373 /* quiet the compiler */
377 return RLM_MODULE_OK;
381 * Write accounting information to this modules database.
383 static rlm_rcode_t rlm_rest_accounting(UNUSED void *instance,
384 UNUSED REQUEST *request)
386 return RLM_MODULE_OK;
390 * See if a user is already logged in. Sets request->simul_count to the
391 * current session count for this user and sets request->simul_mpp to 2
392 * if it looks like a multilink attempt based on the requested IP
393 * address, otherwise leaves request->simul_mpp alone.
395 * Check twice. If on the first pass the user exceeds his
396 * max. number of logins, do a second pass and validate all
397 * logins by querying the terminal server (using eg. SNMP).
399 static rlm_rcode_t rlm_rest_checksimul(void *instance, REQUEST *request)
403 request->simul_count=0;
405 return RLM_MODULE_OK;
409 * Only free memory we allocated. The strings allocated via
410 * cf_section_parse() do not need to be freed.
412 static int rlm_rest_detach(void *instance)
414 rlm_rest_t *my_instance = instance;
416 fr_connection_pool_delete(my_instance->conn_pool);
418 /* Free any memory used by libcurl */
425 * The module name should be the only globally exported symbol.
426 * That is, everything else should be 'static'.
428 * If the module needs to temporarily modify it's instantiation
429 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
430 * The server will then take care of ensuring that the module
431 * is single-threaded.
433 module_t rlm_rest = {
436 RLM_TYPE_THREAD_SAFE, /* type */
437 rlm_rest_instantiate, /* instantiation */
438 rlm_rest_detach, /* detach */
440 rlm_rest_authenticate, /* authentication */
441 rlm_rest_authorize, /* authorization */
442 NULL, /* preaccounting */
443 rlm_rest_accounting, /* accounting */
444 rlm_rest_checksimul, /* checksimul */
445 NULL, /* pre-proxy */
446 NULL, /* post-proxy */