summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_ruby/rlm_ruby.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_ruby/rlm_ruby.c')
-rw-r--r--src/modules/rlm_ruby/rlm_ruby.c481
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
+ },
+};