diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 14:11:00 +0000 |
commit | af754e596a8dbb05ed8580c342e7fe02e08b28e0 (patch) | |
tree | b2f334c2b55ede42081aa6710a72da784547d8ea /src/modules/rlm_rest/rlm_rest.c | |
parent | Initial commit. (diff) | |
download | freeradius-upstream/3.2.3+dfsg.tar.xz freeradius-upstream/3.2.3+dfsg.zip |
Adding upstream version 3.2.3+dfsg.upstream/3.2.3+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/modules/rlm_rest/rlm_rest.c')
-rw-r--r-- | src/modules/rlm_rest/rlm_rest.c | 1011 |
1 files changed, 1011 insertions, 0 deletions
diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c new file mode 100644 index 0000000..1942749 --- /dev/null +++ b/src/modules/rlm_rest/rlm_rest.c @@ -0,0 +1,1011 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file rlm_rest.c + * @brief Integrate FreeRADIUS with RESTfull APIs + * + * @copyright 2012-2014 Arran Cudbard-Bell <arran.cudbardb@freeradius.org> + */ +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> +#include <freeradius-devel/token.h> +#include <freeradius-devel/rad_assert.h> + +#include <ctype.h> +#include "rest.h" + +/* + * TLS Configuration + */ +static CONF_PARSER tls_config[] = { + { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_file), NULL }, + { "ca_info_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_info_file), NULL }, + { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_path), NULL }, + { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_certificate_file), NULL }, + { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_private_key_file), NULL }, + { "private_key_password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_rest_section_t, tls_private_key_password), NULL }, + { "random_file", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, tls_random_file), NULL }, + { "check_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert), "yes" }, + { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert_cn), "yes" }, + CONF_PARSER_TERMINATOR +}; + +/* + * A mapping of configuration file names to internal variables. + * + * Note that the string is dynamically allocated, so it MUST + * be freed. When the configuration file parse re-reads the string, + * it free's the old one, and strdup's the new one, placing the pointer + * to the strdup'd string into 'config.string'. This gets around + * buffer over-flows. + */ +static const CONF_PARSER section_config[] = { + { "uri", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, uri), "" }, + { "method", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, method_str), "GET" }, + { "body", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, body_str), "none" }, + { "attr_num", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, attr_num), "no" }, + { "raw_value", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, raw_value), "no" }, + { "data", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, data), NULL }, + { "force_to", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, force_to_str), NULL }, + + /* User authentication */ + { "auth", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, auth_str), "none" }, + { "username", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, username), NULL }, + { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, password), NULL }, + { "require_auth", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, require_auth), "no" }, + + /* Transfer configuration */ + { "timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_section_t, timeout_tv), "4.0" }, + { "chunk", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_rest_section_t, chunk), "0" }, + + /* TLS Parameters */ + { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config }, + + /* Xlat specific */ + { "body_uri_encode", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, body_encode), "yes" }, + CONF_PARSER_TERMINATOR +}; + +static const CONF_PARSER module_config[] = { + { "connect_uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, connect_uri), NULL }, + { "connect_timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout_tv), "4.0" }, + { "http_negotiation", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, http_negotiation_str), "default" }, + + CONF_PARSER_TERMINATOR +}; + +static int rlm_rest_perform(rlm_rest_t *instance, rlm_rest_section_t *section, void *handle, REQUEST *request, + char const *username, char const *password) +{ + ssize_t uri_len; + char *uri = NULL; + + int ret; + + RDEBUG("Expanding URI components"); + + /* + * Build xlat'd URI, this allows REST servers to be specified by + * request attributes. + */ + uri_len = rest_uri_build(&uri, instance, request, section->uri); + if (uri_len <= 0) return -1; + + RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section->method, NULL), uri); + + /* + * Configure various CURL options, and initialise the read/write + * context data. + */ + ret = rest_request_config(instance, section, request, handle, section->method, section->body, + uri, username, password); + talloc_free(uri); + if (ret < 0) return -1; + + /* + * Send the CURL request, pre-parse headers, aggregate incoming + * HTTP body data into a single contiguous buffer. + */ + ret = rest_request_perform(instance, section, request, handle); + if (ret < 0) return -1; + + return 0; +} + +static void rlm_rest_cleanup(rlm_rest_t *instance, rlm_rest_section_t *section, void *handle) +{ + rest_request_cleanup(instance, section, handle); +} + +static ssize_t jsonquote_xlat(UNUSED void *instance, UNUSED REQUEST *request, + char const *fmt, char *out, size_t outlen) +{ + char const *p; + size_t freespace = outlen; + size_t len; + + for (p = fmt; *p != '\0'; p++) { + /* Indicate truncation */ + if (freespace < 3) { + *out = '\0'; + return outlen + 1; + } + + if (*p == '"') { + *out++ = '\\'; + *out++ = '"'; + freespace -= 2; + } else if (*p == '\\') { + *out++ = '\\'; + *out++ = '\\'; + freespace -= 2; + } else if (*p == '/') { + *out++ = '\\'; + *out++ = '/'; + freespace -= 2; + } else if (*p >= ' ') { + *out++ = *p; + freespace--; + /* + * Unprintable chars + */ + } else { + *out++ = '\\'; + freespace--; + + switch (*p) { + case '\b': + *out++ = 'b'; + freespace--; + break; + + case '\f': + *out++ = 'f'; + freespace--; + break; + + case '\n': + *out++ = 'n'; + freespace--; + break; + + case '\r': + *out++ = 'r'; + freespace--; + break; + + case '\t': + *out++ = 't'; + freespace--; + break; + + default: + len = snprintf(out, freespace, "u%04X", (uint8_t) *p); + if (is_truncated(len, freespace)) return (outlen - freespace) + len; + out += len; + freespace -= len; + } + } + } + + *out = '\0'; + + return outlen - freespace; +} +/* + * Simple xlat to read text data from a URL + */ +static ssize_t rest_xlat(void *instance, REQUEST *request, + char const *fmt, char *out, size_t freespace) +{ + rlm_rest_t *inst = instance; + void *handle; + int hcode; + int ret; + ssize_t len, outlen = 0; + char *uri = NULL, *request_body = NULL; + char const *p = fmt, *q; + char const *body; + http_method_t method; + + /* + * Start with xlat "section" config. + * The provided string will then be parsed to populate URI etc. + */ + rlm_rest_section_t section = inst->xlat; + *out = '\0'; + + rad_assert(fmt); + + RDEBUG("Expanding URI components"); + + handle = fr_connection_get(inst->pool); + if (!handle) return -1; + + /* + * Extract the method from the start of the format string (if there is one) + */ + method = fr_substr2int(http_method_table, p, HTTP_METHOD_UNKNOWN, -1); + if (method != HTTP_METHOD_UNKNOWN) { + section.method = method; + p += strlen(http_method_table[method].name); + } + + /* + * Trim whitespace + */ + while (isspace((uint8_t) *p) && p++); + + /* + * Unescape parts of xlat'd URI, this allows REST servers to be specified by + * request attributes. + */ + len = rest_uri_host_unescape(&uri, instance, request, handle, p); + if (len <= 0) { + outlen = -1; + goto finish; + } + + /* + * Extract freeform body data (url can't contain spaces) + */ + q = strchr(p, ' '); + if (q && (*++q != '\0')) { + rlm_rest_handle_t *randle = handle; + + /* + * As all input was escaped, this is already encoded. + * Un-escape if the body is to be sent as is. + */ + if (section.body_encode) { + section.data = q; + } else { + request_body = curl_easy_unescape(randle->handle, q, strlen(q), NULL); + section.data = request_body; + } + section.body = HTTP_BODY_CUSTOM_LITERAL; + } + + RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section.method, NULL), uri); + + /* + * Configure various CURL options, and initialise the read/write + * context data. + * + * @todo We could extract the User-Name and password from the URL string. + */ + ret = rest_request_config(instance, §ion, request, handle, section.method, section.body, + uri, NULL, NULL); + talloc_free(uri); + if (ret < 0) { + outlen = -1; + goto finish; + } + + /* + * Send the CURL request, pre-parse headers, aggregate incoming + * HTTP body data into a single contiguous buffer. + */ + ret = rest_request_perform(instance, §ion, request, handle); + if (ret < 0) { + outlen = -1; + goto finish; + } + + hcode = rest_get_handle_code(handle); + switch (hcode) { + case 404: + case 410: + case 403: + case 401: + { + outlen = -1; +error: + rest_response_error(request, handle); + goto finish; + } + case 204: + goto finish; + + default: + /* + * Attempt to parse content if there was any. + */ + if ((hcode >= 200) && (hcode < 300)) { + break; + } else if (hcode < 500) { + outlen = -2; + goto error; + } else { + outlen = -1; + goto error; + } + } + + len = rest_get_handle_data(&body, handle); + if ((size_t) len >= freespace) { + REDEBUG("Insufficient space to write HTTP response, needed %zu bytes, have %zu bytes", len + 1, + freespace); + outlen = -1; + goto finish; + } + if (len > 0) { + outlen = len; + strlcpy(out, body, len + 1); /* strlcpy takes the size of the buffer */ + } + +finish: + rlm_rest_cleanup(instance, §ion, handle); + if (request_body) curl_free(request_body); + + fr_connection_release(inst->pool, handle); + + return outlen; +} + +/* + * Find the named user in this modules database. Create the set + * of attribute-value pairs to check and reply with for this user + * from the database. The authentication code only needs to check + * the password, the rest is done here. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->authorize; + + void *handle; + int hcode; + int rcode = RLM_MODULE_OK; + int ret; + + if (!section->name) return RLM_MODULE_NOOP; + + handle = fr_connection_get(inst->pool); + if (!handle) return RLM_MODULE_FAIL; + + ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL); + if (ret < 0) { + rcode = RLM_MODULE_FAIL; + goto finish; + } + + hcode = rest_get_handle_code(handle); + switch (hcode) { + case 404: + case 410: + rcode = RLM_MODULE_NOTFOUND; + break; + + case 403: + rcode = RLM_MODULE_USERLOCK; + break; + + case 401: + /* + * Attempt to parse content if there was any. + */ + ret = rest_response_decode(inst, section, request, handle); + if (ret < 0) { + rcode = RLM_MODULE_FAIL; + break; + } + + rcode = RLM_MODULE_REJECT; + break; + + case 204: + rcode = RLM_MODULE_OK; + break; + + default: + /* + * Attempt to parse content if there was any. + */ + if ((hcode >= 200) && (hcode < 300)) { + ret = rest_response_decode(inst, section, request, handle); + if (ret < 0) rcode = RLM_MODULE_FAIL; + else if (ret == 0) rcode = RLM_MODULE_OK; + else rcode = RLM_MODULE_UPDATED; + break; + } else if (hcode < 500) { + rcode = RLM_MODULE_INVALID; + } else { + rcode = RLM_MODULE_FAIL; + } + } + +finish: + switch (rcode) { + case RLM_MODULE_INVALID: + case RLM_MODULE_FAIL: + case RLM_MODULE_USERLOCK: + rest_response_error(request, handle); + break; + + default: + break; + } + + rlm_rest_cleanup(instance, section, handle); + + fr_connection_release(inst->pool, handle); + + return rcode; +} + +/* + * Authenticate the user with the given password. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->authenticate; + + void *handle; + int hcode; + int rcode = RLM_MODULE_OK; + int ret; + + VALUE_PAIR const *username; + VALUE_PAIR const *password; + + if (!section->name) return RLM_MODULE_NOOP; + + username = request->username; + if (!request->username) { + REDEBUG("Can't perform authentication, 'User-Name' attribute not found in the request"); + + return RLM_MODULE_INVALID; + } + + password = request->password; + if (!password || + (password->da->attr != PW_USER_PASSWORD)) { + REDEBUG("You set 'Auth-Type = REST' for a request that does not contain a User-Password attribute!"); + return RLM_MODULE_INVALID; + } + + handle = fr_connection_get(inst->pool); + if (!handle) return RLM_MODULE_FAIL; + + ret = rlm_rest_perform(instance, section, handle, request, username->vp_strvalue, password->vp_strvalue); + if (ret < 0) { + rcode = RLM_MODULE_FAIL; + goto finish; + } + + hcode = rest_get_handle_code(handle); + switch (hcode) { + case 404: + case 410: + rcode = RLM_MODULE_NOTFOUND; + break; + + case 403: + rcode = RLM_MODULE_USERLOCK; + break; + + case 401: + /* + * Attempt to parse content if there was any. + */ + ret = rest_response_decode(inst, section, request, handle); + if (ret < 0) { + rcode = RLM_MODULE_FAIL; + break; + } + + rcode = RLM_MODULE_REJECT; + break; + + case 204: + rcode = RLM_MODULE_OK; + break; + + default: + /* + * Attempt to parse content if there was any. + */ + if ((hcode >= 200) && (hcode < 300)) { + ret = rest_response_decode(inst, section, request, handle); + if (ret < 0) rcode = RLM_MODULE_FAIL; + else if (ret == 0) rcode = RLM_MODULE_OK; + else rcode = RLM_MODULE_UPDATED; + break; + } else if (hcode < 500) { + rcode = RLM_MODULE_INVALID; + } else { + rcode = RLM_MODULE_FAIL; + } + } + +finish: + switch (rcode) { + case RLM_MODULE_INVALID: + case RLM_MODULE_FAIL: + case RLM_MODULE_USERLOCK: + rest_response_error(request, handle); + break; + + default: + break; + } + + rlm_rest_cleanup(instance, section, handle); + + fr_connection_release(inst->pool, handle); + + return rcode; +} + +/* + * Do common work. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_common(rlm_rest_t *inst, REQUEST *request, rlm_rest_section_t *section) +{ + void *handle; + int hcode; + int rcode = RLM_MODULE_OK; + int ret; + + if (!section->name) return RLM_MODULE_NOOP; + + handle = fr_connection_get(inst->pool); + if (!handle) return RLM_MODULE_FAIL; + + ret = rlm_rest_perform(inst, section, handle, request, NULL, NULL); + if (ret < 0) { + rcode = RLM_MODULE_FAIL; + goto finish; + } + + hcode = rest_get_handle_code(handle); + if (hcode >= 500) { + rcode = RLM_MODULE_FAIL; + } else if (hcode == 204) { + rcode = RLM_MODULE_OK; + } else if ((hcode >= 200) && (hcode < 300)) { + ret = rest_response_decode(inst, section, request, handle); + if (ret < 0) rcode = RLM_MODULE_FAIL; + else if (ret == 0) rcode = RLM_MODULE_OK; + else rcode = RLM_MODULE_UPDATED; + } else { + rcode = RLM_MODULE_INVALID; + } + +finish: + switch (rcode) { + case RLM_MODULE_INVALID: + case RLM_MODULE_FAIL: + rest_response_error(request, handle); + break; + + default: + break; + } + + rlm_rest_cleanup(inst, section, handle); + + fr_connection_release(inst->pool, handle); + + return rcode; +} + + +/* + * Send preacct info to a REST API endpoint + */ +static rlm_rcode_t CC_HINT(nonnull) mod_preacct(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->preacct; + + return mod_common(inst, request, section); +} +/* + * Send accounting info to a REST API endpoint + */ +static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->accounting; + + return mod_common(inst, request, section); +} + +/* + * Send post-auth info to a REST API endpoint + */ +static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->post_auth; + + return mod_common(inst, request, section); +} + +/* + * Send pre-proxy info to a REST API endpoint + */ +static rlm_rcode_t CC_HINT(nonnull) mod_pre_proxy(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->pre_proxy; + + return mod_common(inst, request, section); +} + +/* + * Send post-proxy info to a REST API endpoint + */ +static rlm_rcode_t CC_HINT(nonnull) mod_post_proxy(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->post_proxy; + + return mod_common(inst, request, section); +} + +#ifdef WITH_COA +/* + * Create the set of attribute-value pairs to check and reply + * with for this user from the database. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_recv_coa(void *instance, REQUEST *request) +{ + rlm_rest_t *inst = instance; + rlm_rest_section_t *section = &inst->recv_coa; + + void *handle; + int hcode; + int rcode = RLM_MODULE_OK; + int ret; + + if (!section->name) return RLM_MODULE_NOOP; + + handle = fr_connection_get(inst->pool); + if (!handle) return RLM_MODULE_FAIL; + + ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL); + if (ret < 0) { + rcode = RLM_MODULE_FAIL; + goto finish; + } + + hcode = rest_get_handle_code(handle); + switch (hcode) { + case 404: + case 410: + rcode = RLM_MODULE_NOTFOUND; + break; + + case 403: + rcode = RLM_MODULE_USERLOCK; + break; + + case 401: + /* + * Attempt to parse content if there was any. + */ + ret = rest_response_decode(inst, section, request, handle); + if (ret < 0) { + rcode = RLM_MODULE_FAIL; + break; + } + + rcode = RLM_MODULE_REJECT; + break; + + case 204: + rcode = RLM_MODULE_OK; + break; + + default: + /* + * Attempt to parse content if there was any. + */ + if ((hcode >= 200) && (hcode < 300)) { + ret = rest_response_decode(inst, section, request, handle); + if (ret < 0) rcode = RLM_MODULE_FAIL; + else if (ret == 0) rcode = RLM_MODULE_OK; + else rcode = RLM_MODULE_UPDATED; + break; + } else if (hcode < 500) { + rcode = RLM_MODULE_INVALID; + } else { + rcode = RLM_MODULE_FAIL; + } + } + +finish: + switch (rcode) { + case RLM_MODULE_INVALID: + case RLM_MODULE_FAIL: + case RLM_MODULE_USERLOCK: + rest_response_error(request, handle); + break; + + default: + break; + } + + rlm_rest_cleanup(instance, section, handle); + + fr_connection_release(inst->pool, handle); + + return rcode; +} +#endif + +static int parse_sub_section(CONF_SECTION *parent, rlm_rest_section_t *config, char const *name) +{ + CONF_SECTION *cs; + + cs = cf_section_sub_find(parent, name); + if (!cs) { + config->name = NULL; + return 0; + } + + if (cf_section_parse(cs, config, section_config) < 0) { + config->name = NULL; + return -1; + } + + /* + * Add section name (Maybe add to headers later?). + */ + config->name = name; + + /* + * Sanity check + */ + if ((config->username && !config->password) || (!config->username && config->password)) { + cf_log_err_cs(cs, "'username' and 'password' must both be set or both be absent"); + + return -1; + } + + /* + * Convert HTTP method auth and body type strings into their integer equivalents. + */ + config->auth = fr_str2int(http_auth_table, config->auth_str, HTTP_AUTH_UNKNOWN); + if (config->auth == HTTP_AUTH_UNKNOWN) { + cf_log_err_cs(cs, "Unknown HTTP auth type '%s'", config->auth_str); + + return -1; + } else if ((config->auth != HTTP_AUTH_NONE) && !http_curl_auth[config->auth]) { + cf_log_err_cs(cs, "Unsupported HTTP auth type \"%s\", check libcurl version, OpenSSL build " + "configuration, then recompile this module", config->auth_str); + + return -1; + } + + config->method = fr_str2int(http_method_table, config->method_str, HTTP_METHOD_CUSTOM); + config->timeout = ((config->timeout_tv.tv_usec / 1000) + (config->timeout_tv.tv_sec * 1000)); + + /* + * We don't have any custom user data, so we need to select the right encoder based + * on the body type. + * + * To make this slightly more/less confusing, we accept both canonical body_types, + * and content_types. + */ + if (!config->data) { + config->body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN); + if (config->body == HTTP_BODY_UNKNOWN) { + config->body = fr_str2int(http_content_type_table, config->body_str, HTTP_BODY_UNKNOWN); + } + + if (config->body == HTTP_BODY_UNKNOWN) { + cf_log_err_cs(cs, "Unknown HTTP body type '%s'", config->body_str); + return -1; + } + + switch (http_body_type_supported[config->body]) { + case HTTP_BODY_UNSUPPORTED: + cf_log_err_cs(cs, "Unsupported HTTP body type \"%s\", please submit patches", + config->body_str); + return -1; + + case HTTP_BODY_INVALID: + cf_log_err_cs(cs, "Invalid HTTP body type. \"%s\" is not a valid web API data " + "markup format", config->body_str); + return -1; + + case HTTP_BODY_UNAVAILABLE: + cf_log_err_cs(cs, "Unavailable HTTP body type. \"%s\" is not available in this " + "build", config->body_str); + return -1; + + default: + break; + } + /* + * We have custom body data so we set HTTP_BODY_CUSTOM_XLAT, but also need to try and + * figure out what content-type to use. So if they've used the canonical form we + * need to convert it back into a proper HTTP content_type value. + */ + } else { + http_body_type_t body; + + config->body = HTTP_BODY_CUSTOM_XLAT; + + body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN); + if (body != HTTP_BODY_UNKNOWN) { + config->body_str = fr_int2str(http_content_type_table, body, config->body_str); + } + } + + if (config->force_to_str) { + config->force_to = fr_str2int(http_body_type_table, config->force_to_str, HTTP_BODY_UNKNOWN); + if (config->force_to == HTTP_BODY_UNKNOWN) { + config->force_to = fr_str2int(http_content_type_table, config->force_to_str, HTTP_BODY_UNKNOWN); + } + + if (config->force_to == HTTP_BODY_UNKNOWN) { + cf_log_err_cs(cs, "Unknown forced response body type '%s'", config->force_to_str); + return -1; + } + + switch (http_body_type_supported[config->force_to]) { + case HTTP_BODY_UNSUPPORTED: + cf_log_err_cs(cs, "Unsupported forced response body type \"%s\", please submit patches", + config->force_to_str); + return -1; + + case HTTP_BODY_INVALID: + cf_log_err_cs(cs, "Invalid HTTP forced response body type. \"%s\" is not a valid web API data " + "markup format", config->force_to_str); + return -1; + + default: + break; + } + } + + return 0; +} + + +static int mod_bootstrap(CONF_SECTION *conf, void *instance) +{ + rlm_rest_t *inst = instance; + + inst->xlat_name = cf_section_name2(conf); + if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf); + + /* + * Register the rest xlat function + */ + xlat_register(inst->xlat_name, rest_xlat, rest_uri_escape, inst); + xlat_register("jsonquote", jsonquote_xlat, NULL, inst); + + return 0; +} + + +/* + * Do any per-module initialization that is separate to each + * configured instance of the module. e.g. set up connections + * to external databases, read configuration files, set up + * dictionary entries, etc. + * + * If configuration information is given in the config section + * that must be referenced in later calls, store a handle to it + * in *instance otherwise put a null pointer there. + */ +static int mod_instantiate(CONF_SECTION *conf, void *instance) +{ + rlm_rest_t *inst = instance; + + /* + * Parse sub-section configs. + */ + if ( + (parse_sub_section(conf, &inst->authorize, section_type_value[MOD_AUTHORIZE].section) < 0) || + (parse_sub_section(conf, &inst->authenticate, section_type_value[MOD_AUTHENTICATE].section) < 0) || + (parse_sub_section(conf, &inst->preacct, section_type_value[MOD_PREACCT].section) < 0) || + (parse_sub_section(conf, &inst->accounting, section_type_value[MOD_ACCOUNTING].section) < 0) || + (parse_sub_section(conf, &inst->pre_proxy, section_type_value[MOD_PRE_PROXY].section) < 0) || + (parse_sub_section(conf, &inst->post_proxy, section_type_value[MOD_POST_PROXY].section) < 0) || + (parse_sub_section(conf, &inst->xlat, "xlat") < 0) || + +#ifdef WITH_COA + (parse_sub_section(conf, &inst->recv_coa, section_type_value[MOD_RECV_COA].section) < 0) || +#endif + +/* @todo add behaviour for checksimul */ +/* (parse_sub_section(conf, &inst->checksimul, MOD_SESSION) < 0) || */ + (parse_sub_section(conf, &inst->post_auth, section_type_value[MOD_POST_AUTH].section) < 0)) + { + return -1; + } + + inst->http_negotiation = fr_str2int(http_negotiation_table, inst->http_negotiation_str, -1); + if (inst->http_negotiation == -1) { + cf_log_err_cs(conf, "Unsupported HTTP version \"%s\".", inst->http_negotiation_str); + return -1; + } + + /* + * Initialise REST libraries. + */ + if (rest_init(inst) < 0) { + return -1; + } + + inst->connect_timeout = ((inst->connect_timeout_tv.tv_usec / 1000) + + (inst->connect_timeout_tv.tv_sec * 1000)); + inst->pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, mod_conn_alive, NULL); + if (!inst->pool) return -1; + + return 0; +} + +/* + * Only free memory we allocated. The strings allocated via + * cf_section_parse() do not need to be freed. + */ +static int mod_detach(void *instance) +{ + rlm_rest_t *inst = instance; + + fr_connection_pool_free(inst->pool); + + /* Free any memory used by libcurl */ + rest_cleanup(); + + return 0; +} + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + * + * If the module needs to temporarily modify it's instantiation + * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE. + * The server will then take care of ensuring that the module + * is single-threaded. + */ +extern module_t rlm_rest; +module_t rlm_rest = { + .magic = RLM_MODULE_INIT, + .name = "rest", + .type = RLM_TYPE_THREAD_SAFE, + .inst_size = sizeof(rlm_rest_t), + .config = module_config, + .bootstrap = mod_bootstrap, + .instantiate = mod_instantiate, + .detach = mod_detach, + .methods = { + [MOD_AUTHENTICATE] = mod_authenticate, + [MOD_AUTHORIZE] = mod_authorize, + [MOD_PREACCT] = mod_preacct, + [MOD_ACCOUNTING] = mod_accounting, + [MOD_POST_AUTH] = mod_post_auth, + [MOD_PRE_PROXY] = mod_pre_proxy, + [MOD_POST_PROXY] = mod_post_proxy, +#ifdef WITH_COA + [MOD_RECV_COA] = mod_recv_coa +#endif + }, +}; |