diff options
Diffstat (limited to 'src/modules/rlm_ruby/rlm_ruby.c')
-rw-r--r-- | src/modules/rlm_ruby/rlm_ruby.c | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/src/modules/rlm_ruby/rlm_ruby.c b/src/modules/rlm_ruby/rlm_ruby.c new file mode 100644 index 0000000..ad2f15f --- /dev/null +++ b/src/modules/rlm_ruby/rlm_ruby.c @@ -0,0 +1,481 @@ +/* + * 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_ruby.c + * @brief Translates requests between the server an a ruby interpreter. + * + * @note Maintainers note + * @note Please don't use this module, Matz ruby was never designed for embedding. + * @note This module leaks memory, and the ruby code installs signal handlers + * @note which interfere with normal operation of the server. It's all bad... + * @note mruby shows some promise, feel free to rewrite the module to use that. + * @note https://github.com/mruby/mruby + * + * @copyright 2008 Andriy Dmytrenko aka Antti, BuzhNET + */ + + +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> + +/* + * Undefine any HAVE_* flags which may conflict + * ruby.h *REALLY* shouldn't #include its config.h file, + * but it does *sigh*. + */ +#undef HAVE_CRYPT + +#ifdef __clang__ +DIAG_OFF(disabled-macro-expansion) +#endif +#include <ruby.h> + +/* + * Define a structure for our module configuration. + * + * These variables do not need to be in a structure, but it's + * a lot cleaner to do so, and a pointer to the structure can + * be used as the instance handle. + */ +typedef struct rlm_ruby_t { +#define RLM_RUBY_STRUCT(foo) unsigned long func_##foo + + RLM_RUBY_STRUCT(instantiate); + RLM_RUBY_STRUCT(authorize); + RLM_RUBY_STRUCT(authenticate); + RLM_RUBY_STRUCT(preacct); + RLM_RUBY_STRUCT(accounting); + RLM_RUBY_STRUCT(checksimul); + RLM_RUBY_STRUCT(pre_proxy); + RLM_RUBY_STRUCT(post_proxy); + RLM_RUBY_STRUCT(post_auth); +#ifdef WITH_COA + RLM_RUBY_STRUCT(recv_coa); + RLM_RUBY_STRUCT(send_coa); +#endif + RLM_RUBY_STRUCT(detach); + + char const *filename; + char const *module_name; + VALUE module; + +} rlm_ruby_t; + +/* + * 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 module_config[] = { + { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_REQUIRED, struct rlm_ruby_t, filename), NULL }, + { "module", FR_CONF_OFFSET(PW_TYPE_STRING, struct rlm_ruby_t, module_name), "Radiusd" }, + CONF_PARSER_TERMINATOR +}; + + +/* + * radiusd Ruby functions + */ + +/* radlog wrapper */ + +static VALUE radlog_rb(UNUSED VALUE self, VALUE msg_type, VALUE rb_msg) { + int status; + char *msg; + status = FIX2INT(msg_type); + msg = StringValuePtr(rb_msg); + radlog(status, "%s", msg); + return Qnil; +} + +/* Tuple to value pair conversion */ + +static void add_vp_tuple(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vpp, VALUE rb_value, + char const *function_name) { + int i; + long outertuplesize; + VALUE_PAIR *vp; + + /* If the Ruby function gave us nil for the tuple, then just return. */ + if (NIL_P(rb_value)) { + return; + } + + if (TYPE(rb_value) != T_ARRAY) { + REDEBUG("add_vp_tuple, %s: non-array passed", function_name); + return; + } + + /* Get the array size. */ + outertuplesize = RARRAY_LEN(rb_value); + + for (i = 0; i < outertuplesize; i++) { + VALUE pTupleElement = rb_ary_entry(rb_value, i); + + if ((pTupleElement != 0) && + (TYPE(pTupleElement) == T_ARRAY)) { + + /* Check if it's a pair */ + long tuplesize; + + if ((tuplesize = RARRAY_LEN(pTupleElement)) != 2) { + REDEBUG("%s: tuple element %i is a tuple " + " of size %li. must be 2\n", function_name, + i, tuplesize); + } else { + VALUE pString1, pString2; + + pString1 = rb_ary_entry(pTupleElement, 0); + pString2 = rb_ary_entry(pTupleElement, 1); + + if ((TYPE(pString1) == T_STRING) && + (TYPE(pString2) == T_STRING)) { + + + char const *s1, *s2; + + /* fr_pair_make() will convert and find any + * errors in the pair. + */ + + s1 = StringValuePtr(pString1); + s2 = StringValuePtr(pString2); + + if ((s1 != NULL) && (s2 != NULL)) { + DEBUG("%s: %s = %s ", + function_name, s1, s2); + + /* xxx Might need to support other T_OP */ + vp = fr_pair_make(ctx, vpp, s1, s2, T_OP_EQ); + if (vp != NULL) { + DEBUG("%s: s1, s2 OK", function_name); + } else { + DEBUG("%s: s1, s2 FAILED", function_name); + } + } else { + REDEBUG("%s: string conv failed", function_name); + } + + } else { + REDEBUG("%s: tuple element %d must be " + "(string, string)", function_name, i); + } + } + } else { + REDEBUG("%s: tuple element %d is not a tuple\n", + function_name, i); + } + } + +} + +/* This is the core Ruby function that the others wrap around. + * Pass the value-pair print strings in a tuple. + * xxx We're not checking the errors. If we have errors, what do we do? + */ + +#define BUF_SIZE 1024 +static rlm_rcode_t CC_HINT(nonnull (4)) do_ruby(REQUEST *request, unsigned long func, + VALUE module, char const *function_name) +{ + rlm_rcode_t rcode = RLM_MODULE_OK; + vp_cursor_t cursor; + + char buf[BUF_SIZE]; /* same size as vp_print buffer */ + + VALUE_PAIR *vp; + VALUE rb_request, rb_result, rb_reply_items, rb_config, rbString1, rbString2; + + int n_tuple; + DEBUG("Calling ruby function %s which has id: %lu\n", function_name, func); + + /* Return with "OK, continue" if the function is not defined. + * TODO: Should check with rb_respond_to each time, just because ruby can define function dynamicly? + */ + if (func == 0) { + return rcode; + } + + n_tuple = 0; + if (request) { + for (vp = fr_cursor_init(&cursor, &request->packet->vps); + vp; + vp = fr_cursor_next(&cursor)) { + n_tuple++; + } + } + + /* + Creating ruby array, that contains arrays of [name,value] + Maybe we should use hash instead? Can this names repeat? + */ + rb_request = rb_ary_new2(n_tuple); + + if (request) { + for (vp = fr_cursor_init(&cursor, &request->packet->vps); + vp; + vp = fr_cursor_next(&cursor)) { + VALUE tmp = rb_ary_new2(2); + + /* The name. logic from vp_prints, lib/print.c */ + if (vp->da->flags.has_tag) { + snprintf(buf, BUF_SIZE, "%s:%d", vp->da->name, vp->tag); + } else { + strlcpy(buf, vp->da->name, sizeof(buf)); + } + rbString1 = rb_str_new2(buf); + vp_prints_value(buf, sizeof (buf), vp, '"'); + rbString2 = rb_str_new2(buf); + + rb_ary_push(tmp, rbString1); + rb_ary_push(tmp, rbString2); + rb_ary_push(rb_request, tmp); + } + } + + /* Calling corresponding ruby function, passing request and catching result */ + rb_result = rb_funcall(module, func, 1, rb_request); + + /* + * Checking result, it can be array of type [result, + * [array of reply pairs],[array of config pairs]], + * It can also be just a fixnum, which is a result itself. + */ + if (TYPE(rb_result) == T_ARRAY) { + if (!FIXNUM_P(rb_ary_entry(rb_result, 0))) { + ERROR("First element of an array was not a FIXNUM (Which has to be a return_value)"); + + rcode = RLM_MODULE_FAIL; + goto finish; + } + + rcode = FIX2INT(rb_ary_entry(rb_result, 0)); + + /* + * Only process the results if we were passed a request. + */ + if (request) { + rb_reply_items = rb_ary_entry(rb_result, 1); + rb_config = rb_ary_entry(rb_result, 2); + + add_vp_tuple(request->reply, request, &request->reply->vps, + rb_reply_items, function_name); + add_vp_tuple(request, request, &request->config, + rb_config, function_name); + } + } else if (FIXNUM_P(rb_result)) { + rcode = FIX2INT(rb_result); + } + +finish: + return rcode; +} + +static struct varlookup { + char const* name; + int value; +} constants[] = { + { "L_DBG", L_DBG}, + { "L_AUTH", L_AUTH}, + { "L_INFO", L_INFO}, + { "L_ERR", L_ERR}, + { "L_PROXY", L_PROXY}, + { "RLM_MODULE_REJECT", RLM_MODULE_REJECT}, + { "RLM_MODULE_FAIL", RLM_MODULE_FAIL}, + { "RLM_MODULE_OK", RLM_MODULE_OK}, + { "RLM_MODULE_HANDLED", RLM_MODULE_HANDLED}, + { "RLM_MODULE_INVALID", RLM_MODULE_INVALID}, + { "RLM_MODULE_USERLOCK", RLM_MODULE_USERLOCK}, + { "RLM_MODULE_NOTFOUND", RLM_MODULE_NOTFOUND}, + { "RLM_MODULE_NOOP", RLM_MODULE_NOOP}, + { "RLM_MODULE_UPDATED", RLM_MODULE_UPDATED}, + { "RLM_MODULE_NUMCODES", RLM_MODULE_NUMCODES}, + { NULL, 0}, +}; + +/* + * Import a user module and load a function from it + */ +static int load_function(char const *f_name, unsigned long *func, VALUE module) { + if (!f_name) { + *func = 0; + } else { + *func = rb_intern(f_name); + /* rb_intern returns a symbol of a function, not a function itself + it can be aplied to any recipient, + so we should check it for our module recipient + */ + if (!rb_respond_to(module, *func)) + *func = 0; + } + DEBUG("load_function %s, result: %lu", f_name, *func); + 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(UNUSED CONF_SECTION *conf, void *instance) +{ + rlm_ruby_t *inst = instance; + VALUE module; + + int idx; + int status; + + /* + * Initialize Ruby interpreter. Fatal error if this fails. + */ + ruby_init(); + ruby_init_loadpath(); + ruby_script("radiusd"); + + /* disabling GC, it will eat your memory, but at least it will be stable. */ + rb_gc_disable(); + + /* + * Setup our 'radiusd' module. + */ + module = inst->module = rb_define_module(inst->module_name); + if (!module) { + ERROR("Ruby rb_define_module failed"); + + return -1; + } + + /* + * Load constants into module + */ + for (idx = 0; constants[idx].name; idx++) { + rb_define_const(module, constants[idx].name, INT2NUM(constants[idx].value)); + } + + /* + * Expose some FreeRADIUS API functions as ruby functions + */ + rb_define_module_function(module, "radlog", radlog_rb, 2); + + DEBUG("Loading file %s...", inst->filename); + rb_load_protect(rb_str_new2(inst->filename), 0, &status); + if (status) { + ERROR("Error loading file %s status: %d", inst->filename, status); + + return -1; + } + DEBUG("Loaded file %s", inst->filename); + + /* + * Import user modules. + */ +#define RLM_RUBY_LOAD(foo) if (load_function(#foo, &inst->func_##foo, inst->module)==-1) { \ + return -1; \ + } + + RLM_RUBY_LOAD(instantiate); + RLM_RUBY_LOAD(authenticate); + RLM_RUBY_LOAD(authorize); + RLM_RUBY_LOAD(preacct); + RLM_RUBY_LOAD(accounting); + RLM_RUBY_LOAD(checksimul); + RLM_RUBY_LOAD(pre_proxy); + RLM_RUBY_LOAD(post_proxy); + RLM_RUBY_LOAD(post_auth); +#ifdef WITH_COA + RLM_RUBY_LOAD(recv_coa); + RLM_RUBY_LOAD(send_coa); +#endif + RLM_RUBY_LOAD(detach); + + /* Call the instantiate function. No request. Use the return value. */ + return do_ruby(NULL, inst->func_instantiate, inst->module, "instantiate"); +} + +#define RLM_RUBY_FUNC(foo) static rlm_rcode_t CC_HINT(nonnull) mod_##foo(void *instance, REQUEST *request) \ + { \ + return do_ruby(request, \ + ((struct rlm_ruby_t *)instance)->func_##foo,((struct rlm_ruby_t *)instance)->module, \ + #foo); \ + } + +RLM_RUBY_FUNC(authorize) +RLM_RUBY_FUNC(authenticate) +RLM_RUBY_FUNC(preacct) +RLM_RUBY_FUNC(accounting) +RLM_RUBY_FUNC(checksimul) +RLM_RUBY_FUNC(pre_proxy) +RLM_RUBY_FUNC(post_proxy) +RLM_RUBY_FUNC(post_auth) +#ifdef WITH_COA +RLM_RUBY_FUNC(recv_coa) +RLM_RUBY_FUNC(send_coa) +#endif + +static int mod_detach(UNUSED void *instance) +{ + ruby_finalize(); + ruby_cleanup(0); + + 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_ruby; +module_t rlm_ruby = { + .magic = RLM_MODULE_INIT, + .name = "ruby", + .type = RLM_TYPE_THREAD_UNSAFE, /* type, ok, let's be honest, MRI is not yet treadsafe */ + .inst_size = sizeof(rlm_ruby_t), + .config = module_config, + .instantiate = mod_instantiate, + .detach = mod_detach, + .methods = { + [MOD_AUTHENTICATE] = mod_authenticate, + [MOD_AUTHORIZE] = mod_authorize, + [MOD_PREACCT] = mod_preacct, + [MOD_ACCOUNTING] = mod_accounting, + [MOD_SESSION] = mod_checksimul, + [MOD_PRE_PROXY] = mod_pre_proxy, + [MOD_POST_PROXY] = mod_post_proxy, + [MOD_POST_AUTH] = mod_post_auth, +#ifdef WITH_COA + [MOD_RECV_COA] = mod_recv_coa, + [MOD_SEND_COA] = mod_send_coa +#endif + }, +}; |