/*
 *   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: ad2f15fff3e2e1a103fca2eefe60a402129f291a $
 * @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: ad2f15fff3e2e1a103fca2eefe60a402129f291a $")

#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
	},
};
