/*
 * Copyright (c) 2010 William Pitcock <nenolod@atheme.org>
 * Rights to this code are as documented in doc/LICENSE.
 *
 * Looks for users and performs actions on them.
 */

#include "atheme-compat.h"
#include <limits.h>

DECLARE_MODULE_V1
(
	"contrib/os_trace", false, _modinit, _moddeinit,
	"Copyright (c) 2010 William Pitcock <nenolod@atheme.org>",
	"Atheme Development Group <http://www.atheme.org>"
);

static char *reason_extract(char **args);
static void os_cmd_trace(sourceinfo_t *si, int parc, char *parv[]);

command_t os_trace = { "TRACE", N_("Looks for users and performs actions on them."), PRIV_USER_AUSPEX, 2, os_cmd_trace, { .path = "contrib/trace" } };

typedef struct {
	void /* trace_query_domain_t */ *(*prepare)(char **args);
	bool (*exec)(user_t *u, void /* trace_query_domain_t */ *q);
	void (*cleanup)(void /* trace_query_domain_t */ *q);
} trace_query_constructor_t;

typedef struct {
	trace_query_constructor_t *cons;
	mowgli_node_t node;
} trace_query_domain_t;

static int read_comparison_operator(char** string, int default_comparison)
{
	if (**string == '<')
	{
		if (*((*string)+1) == '=')
		{
			*string += 2;
			return 2;
		}
		else
		{
			*string += 1;
			return 1;
		}
	}
	else if (**string == '>')
	{
		if (*((*string)+1) == '=')
		{
			*string += 2;
			return 4;
		}
		else
		{
			*string += 1;
			return 3;
		}
	}
	else if (**string == '=')
	{
		*string += 1;
		return 0;
	}
	else
		return default_comparison;
}

char *reason_extract(char **args)
{
	char *start = *args;
	bool quotes = false;

	while (**args == ' ')
	{
		(*args)++;
	}
	if (**args == '"')
	{
		start = ++(*args);
		quotes = true;
	}
	else
		start = *args;

	while (**args)
	{
		if (quotes && **args == '"')
		{
			quotes = false;
			**args = 0;
			(*args)++;
			break;
		}
		else if (!quotes && **args == ' ')
		{
			**args = 0;
			(*args)++;
			break;
		}
		
		(*args)++;
	}

	if (!(**args))
		*args = NULL;

	if (start == *args)
		return NULL;  /* No reason found. */
	if (quotes)
		return NULL;  /* Unclosed quotes. */

	return start;
}

typedef struct {
	trace_query_domain_t domain;
	atheme_regex_t *regex;
	char *pattern;
	int flags;
} trace_query_regexp_domain_t;

static void *trace_regexp_prepare(char **args)
{
	trace_query_regexp_domain_t *domain;

	return_val_if_fail(args != NULL, NULL);
	return_val_if_fail(*args != NULL, NULL);

	domain = scalloc(sizeof(trace_query_regexp_domain_t), 1);
	domain->pattern = regex_extract(*args, &(*args), &domain->flags);
	domain->regex = regex_create(domain->pattern, domain->flags);
	
	return domain;
}

static bool trace_regexp_exec(user_t *u, void *q)
{
	char usermask[512];
	trace_query_regexp_domain_t *domain = (trace_query_regexp_domain_t *) q;

	return_val_if_fail(domain != NULL, false);
	return_val_if_fail(u != NULL, false);

	if (domain->regex == NULL)
		return false;

	snprintf(usermask, 512, "%s!%s@%s %s", u->nick, u->user, u->host, u->gecos);

	return regex_match(domain->regex, usermask);
}

static void trace_regexp_cleanup(void *q)
{
	trace_query_regexp_domain_t *domain = (trace_query_regexp_domain_t *) q;

	return_if_fail(domain != NULL);

	if (domain->regex != NULL)
		regex_destroy(domain->regex);

	free(domain);
}

trace_query_constructor_t trace_regexp = { trace_regexp_prepare, trace_regexp_exec, trace_regexp_cleanup };

typedef struct {
	trace_query_domain_t domain;
	server_t *server;
} trace_query_server_domain_t;

static void *trace_server_prepare(char **args)
{
	char *server;
	trace_query_server_domain_t *domain;

	return_val_if_fail(args != NULL, NULL);
	return_val_if_fail(*args != NULL, NULL);

	/* split out the next space */
	server = strtok(*args, " ");

	domain = scalloc(sizeof(trace_query_server_domain_t), 1);
	domain->server = server_find(server);

	/* advance *args to next token */
	*args = strtok(NULL, "");

	return domain;
}

static bool trace_server_exec(user_t *u, void *q)
{
	trace_query_server_domain_t *domain = (trace_query_server_domain_t *) q;

	return_val_if_fail(domain != NULL, false);
	return_val_if_fail(u != NULL, false);

	return (domain->server == u->server);
}

static void trace_server_cleanup(void *q)
{
	trace_query_server_domain_t *domain = (trace_query_server_domain_t *) q;

	return_if_fail(domain != NULL);

	free(domain);
}

trace_query_constructor_t trace_server = { trace_server_prepare, trace_server_exec, trace_server_cleanup };

typedef struct {
	trace_query_domain_t domain;
	char *pattern;
} trace_query_glob_domain_t;

static void *trace_glob_prepare(char **args)
{
	char *pattern;
	trace_query_glob_domain_t *domain;
	
	return_val_if_fail(args != NULL, NULL);
	return_val_if_fail(*args != NULL, NULL);

	/* split out the next space */
	pattern = strtok(*args, " ");

	domain = scalloc(sizeof(trace_query_glob_domain_t), 1);
	domain->pattern = sstrdup(pattern);

	*args = strtok(NULL, "");
	
	return domain;
}

static bool trace_glob_exec(user_t *u, void *q)
{
	char usermask[512];
	trace_query_glob_domain_t *domain = (trace_query_glob_domain_t *) q;

	return_val_if_fail(domain != NULL, false);
	return_val_if_fail(u != NULL, false);

	if (domain->pattern == NULL)
		return false;

	snprintf(usermask, 512, "%s!%s@%s", u->nick, u->user, u->host);

	return !match(domain->pattern, usermask);
}

static void trace_glob_cleanup(void *q)
{
	trace_query_glob_domain_t *domain = (trace_query_glob_domain_t *) q;

	return_if_fail(domain != NULL);

	free(domain);
}

trace_query_constructor_t trace_glob = { trace_glob_prepare, trace_glob_exec, trace_glob_cleanup };

typedef struct {
	trace_query_domain_t domain;
	channel_t *channel;
} trace_query_channel_domain_t;

static void *trace_channel_prepare(char **args)
{
	char *channel;
	trace_query_channel_domain_t *domain;

	return_val_if_fail(args != NULL, NULL);
	return_val_if_fail(*args != NULL, NULL);

	/* split out the next space */
	channel = strtok(*args, " ");

	domain = scalloc(sizeof(trace_query_channel_domain_t), 1);
	domain->channel = channel_find(channel);

	/* advance *args to next token */
	*args = strtok(NULL, "");

	return domain;
}

static bool trace_channel_exec(user_t *u, void *q)
{
	trace_query_channel_domain_t *domain = (trace_query_channel_domain_t *) q;

	return_val_if_fail(domain != NULL, false);
	return_val_if_fail(u != NULL, false);

	return (chanuser_find(domain->channel, u) != NULL);
}

static void trace_channel_cleanup(void *q)
{
	trace_query_channel_domain_t *domain = (trace_query_channel_domain_t *) q;

	return_if_fail(domain != NULL);

	free(domain);
}

trace_query_constructor_t trace_channel = { trace_channel_prepare, trace_channel_exec, trace_channel_cleanup };

typedef struct {
	trace_query_domain_t domain;
	int nickage;
	int comparison;
} trace_query_nickage_domain_t;

static void *trace_nickage_prepare(char **args)
{
	char *nickage_string;
	trace_query_nickage_domain_t *domain;

	return_val_if_fail(args != NULL, NULL);
	return_val_if_fail(*args != NULL, NULL);

	/* split out the next space */
	nickage_string = strtok(*args, " ");

	domain = scalloc(sizeof(trace_query_nickage_domain_t), 1);
	domain->comparison = read_comparison_operator(&nickage_string, 2);
	domain->nickage = atoi(nickage_string);

	/* advance *args to next token */
	*args = strtok(NULL, "");

	return domain;
}

static bool trace_nickage_exec(user_t *u, void *q)
{
	trace_query_nickage_domain_t *domain = (trace_query_nickage_domain_t *) q;

	return_val_if_fail(domain != NULL, false);
	return_val_if_fail(u != NULL, false);

	int nickage = CURRTIME - u->ts;
	if (domain->comparison == 1)
		return (nickage < domain->nickage);
	else if (domain->comparison == 2)
		return (nickage <= domain->nickage);
	else if (domain->comparison == 3)
		return (nickage > domain->nickage);
	else if (domain->comparison == 4)
		return (nickage >= domain->nickage);
	else
		return (nickage == domain->nickage);
}

static void trace_nickage_cleanup(void *q)
{
	trace_query_nickage_domain_t *domain = (trace_query_nickage_domain_t *) q;

	return_if_fail(domain != NULL);

	free(domain);
}

trace_query_constructor_t trace_nickage = { trace_nickage_prepare, trace_nickage_exec, trace_nickage_cleanup };

typedef struct {
	trace_query_domain_t domain;
	int numchan;
	int comparison;
} trace_query_numchan_domain_t;

static void *trace_numchan_prepare(char **args)
{
	char *numchan_string;
	trace_query_numchan_domain_t *domain;

	return_val_if_fail(args != NULL, NULL);
	return_val_if_fail(*args != NULL, NULL);

	/* split out the next space */
	numchan_string = strtok(*args, " ");

	domain = scalloc(sizeof(trace_query_numchan_domain_t), 1);
	domain->comparison = read_comparison_operator(&numchan_string, 0);
	domain->numchan = atoi(numchan_string);

	/* advance *args to next token */
	*args = strtok(NULL, "");

	return domain;
}

static bool trace_numchan_exec(user_t *u, void *q)
{
	trace_query_numchan_domain_t *domain = (trace_query_numchan_domain_t *) q;

	return_val_if_fail(domain != NULL, false);
	return_val_if_fail(u != NULL, false);

	int numchan = u->channels.count;
	if (domain->comparison == 1)
		return (numchan < domain->numchan);
	else if (domain->comparison == 2)
		return (numchan <= domain->numchan);
	else if (domain->comparison == 3)
		return (numchan > domain->numchan);
	else if (domain->comparison == 4)
		return (numchan >= domain->numchan);
	else
		return (numchan == domain->numchan);
}

static void trace_numchan_cleanup(void *q)
{
	trace_query_numchan_domain_t *domain = (trace_query_numchan_domain_t *) q;

	return_if_fail(domain != NULL);

	free(domain);
}

trace_query_constructor_t trace_numchan = { trace_numchan_prepare, trace_numchan_exec, trace_numchan_cleanup };

typedef struct {
	trace_query_domain_t domain;
	bool identified;
} trace_query_identified_domain_t;

static void *trace_identified_prepare(char **args)
{
	char *yesno;
	bool identified;
	trace_query_identified_domain_t *domain;

	return_val_if_fail(args != NULL, NULL);
	return_val_if_fail(*args != NULL, NULL);

	yesno = strtok(*args, " ");

	if (!strcasecmp(yesno, "yes"))
		identified = true;
	else if (!strcasecmp(yesno, "no"))
		identified = false;
	else
		return NULL;

	domain = scalloc(sizeof(trace_query_identified_domain_t), 1);
	domain->identified = identified;

	/* advance *args to next token */
	*args = strtok(NULL, "");

	return domain;
}

static bool trace_identified_exec(user_t *u, void *q)
{
	trace_query_identified_domain_t *domain = (trace_query_identified_domain_t *) q;

	return_val_if_fail(domain != NULL, false);
	return_val_if_fail(u != NULL, false);

	return (domain->identified == (u->myuser != NULL));
}

static void trace_identified_cleanup(void *q)
{
	trace_query_identified_domain_t *domain = (trace_query_identified_domain_t *) q;

	return_if_fail(domain != NULL);

	free(domain);
}

trace_query_constructor_t trace_identified = { trace_identified_prepare, trace_identified_exec, trace_identified_cleanup };

/****************************************************************************************************/

typedef struct {
	sourceinfo_t *si;
	bool matched;
} trace_action_t;

typedef struct {
	trace_action_t *(*prepare)(sourceinfo_t *si, char **args);
	void (*exec)(user_t *u, trace_action_t *a);
	void (*cleanup)(trace_action_t *a, bool succeeded);
} trace_action_constructor_t;

static void trace_action_init(trace_action_t *a, sourceinfo_t *si);

/* initializes common fields for trace action object. */
void trace_action_init(trace_action_t *a, sourceinfo_t *si)
{
	return_if_fail(a != NULL);
	return_if_fail(si != NULL);

	a->si = si;
	a->matched = false;
}

static trace_action_t *trace_print_prepare(sourceinfo_t *si, char **args)
{
	trace_action_t *a;

	return_val_if_fail(si != NULL, NULL);

	a = scalloc(sizeof(trace_action_t), 1);
	trace_action_init(a, si);

	return a;
}

static void trace_print_exec(user_t *u, trace_action_t *a)
{
	return_if_fail(u != NULL);
	return_if_fail(a != NULL);
	if(is_internal_client(u))
		return;

	a->matched = true;
	command_success_nodata(a->si, _("\2Match:\2  %s!%s@%s %s {%s}"), u->nick, u->user, u->host, u->gecos, u->server->name);
}

static void trace_print_cleanup(trace_action_t *a, bool succeeded)
{
	return_if_fail(a != NULL);

	if (!a->matched && succeeded)
		command_success_nodata(a->si, _("No matches."));
	free(a);
}

trace_action_constructor_t trace_print = { trace_print_prepare, trace_print_exec, trace_print_cleanup };

typedef struct {
	trace_action_t base;
	char *reason;
} trace_action_kill_t;

static trace_action_t *trace_kill_prepare(sourceinfo_t *si, char **args)
{
	trace_action_kill_t *a;
	char *reason;

	return_val_if_fail(si != NULL, NULL);
	return_val_if_fail(args != NULL, NULL);
	if (*args == NULL)
		return NULL;

	reason = reason_extract(args);
	if (reason == NULL)
		return NULL;

	a = scalloc(sizeof(trace_action_kill_t), 1);
	trace_action_init(&a->base, si);
	a->reason = reason;

	return (trace_action_t*) a;
}

static void trace_kill_exec(user_t *u, trace_action_t *act)
{
	service_t *svs;
	trace_action_kill_t *a = (trace_action_kill_t *) act;

	return_if_fail(u != NULL);
	return_if_fail(a != NULL);
	if (is_internal_client(u))
		return;
	if (is_ircop(u))
		return;
	if (u->myuser && is_soper(u->myuser))
		return;
	if ((svs = service_find("operserv")) != NULL)
		return;

	act->matched = true;
	kill_user(svs->me, u, "%s", a->reason);
	command_success_nodata(act->si, _("\2%s\2 has been killed."), u->nick);
}

static void trace_kill_cleanup(trace_action_t *a, bool succeeded)
{
	return_if_fail(a != NULL);

	if (!a->matched && succeeded)
		command_success_nodata(a->si, _("No matches."));
	free(a);
}

trace_action_constructor_t trace_kill = { trace_kill_prepare, trace_kill_exec, trace_kill_cleanup };

typedef struct {
	trace_action_t base;
	long duration;
	char *reason;
} trace_action_akill_t;

static trace_action_t *trace_akill_prepare(sourceinfo_t *si, char **args)
{
	trace_action_akill_t *a;
	char *s, *reason;
	long duration = config_options.kline_time;
	char token;

	return_val_if_fail(si != NULL, NULL);
	return_val_if_fail(args != NULL, NULL);
	if (*args == NULL)
		return NULL;

	while (**args == ' ')
		(*args)++;

	/* Extract a token, but only if there's one to remove.
	 * Otherwise, this would clip a word off the reason. */
	token = 0;
	s = *args; 
	if (!strncasecmp(s, "!T", 2) || !strncasecmp(s, "!P", 2))
	{
		if (s[2] == ' ')
		{
			token = tolower(s[1]);
			s[2] = '\0';
			*args += 3;
		}
		else if (s[2] == '\0')
		{
			token = tolower(s[1]);
			*args += 2;
		}
	}

	if (token == 't')
	{
		s = strtok(*args, " ");
		*args = strtok(NULL, "");
		if (*args == NULL)
			return NULL;

		duration = (atol(s) * 60);
		while (isdigit(*s))
		s++;
		if (*s == 'h' || *s == 'H')
			duration *= 60;
		else if (*s == 'd' || *s == 'D')
			duration *= 1440;
		else if (*s == 'w' || *s == 'W')
			duration *= 10080;
		else if (*s == '\0')
			;
		else
			duration = 0;
		
		if (duration == 0)
			return NULL;
	}
	else if (token == 'p')
		duration = 0;
	
	reason = reason_extract(args);
	if (reason == NULL)
		return NULL;
	
	a = scalloc(sizeof(trace_action_akill_t), 1);
	trace_action_init(&a->base, si);
	a->duration = duration;
	a->reason = reason;


	return (trace_action_t*) a;
}

static void trace_akill_exec(user_t *u, trace_action_t *act)
{
	const char *kuser, *khost;
	trace_action_akill_t *a = (trace_action_akill_t *) act;

	return_if_fail(u != NULL);
	return_if_fail(a != NULL);
	if (is_internal_client(u))
		return;
	if (is_ircop(u))
		return;
	if (u->myuser && is_soper(u->myuser))
		return;

	kuser = "*";
	khost = u->host;

	if (!match(khost, "127.0.0.1") || !match_ips(khost, "127.0.0.1"))
		return;
	if (me.vhost != NULL && (!match(khost, me.vhost) || !match_ips(khost, me.vhost)))
		return;
	if (kline_find(kuser, khost))
		return;

	act->matched = true;
	kline_add(kuser, khost, a->reason, a->duration, get_storage_oper_name(act->si));
	command_success_nodata(act->si, _("\2%s\2 has been akilled."), u->nick);
}

static void trace_akill_cleanup(trace_action_t *a, bool succeeded)
{
	return_if_fail(a != NULL);

	if (!a->matched && succeeded)
		command_success_nodata(a->si, _("No matches."));
	free(a);
}

trace_action_constructor_t trace_akill = { trace_akill_prepare, trace_akill_exec, trace_akill_cleanup };

typedef struct {
	trace_action_t base;
	int matches;
} trace_action_count_t;

static trace_action_t *trace_count_prepare(sourceinfo_t *si, char **args)
{
	trace_action_count_t *a;

	return_val_if_fail(si != NULL, NULL);

	a = scalloc(sizeof(trace_action_count_t), 1);
	trace_action_init(&a->base, si);

	return (trace_action_t *) a;
}

static void trace_count_exec(user_t *u, trace_action_t *act)
{
	trace_action_count_t *a = (trace_action_count_t *) act;

	return_if_fail(u != NULL);
	return_if_fail(a != NULL);
	if (is_internal_client(u))
		return;

	act->matched = true;
	a->matches++;
}

static void trace_count_cleanup(trace_action_t *act, bool succeeded)
{
	trace_action_count_t *a = (trace_action_count_t *) act;

	return_if_fail(a != NULL);

	if (succeeded)
		command_success_nodata(act->si, _("\2%d\2 matches"), a->matches);

	free(a);
}

trace_action_constructor_t trace_count = { trace_count_prepare, trace_count_exec, trace_count_cleanup };

/*
 * Add-on interface.
 *
 * This allows third-party module writers to extend the trace API.
 * Just copy the prototypes out of trace.c, and add the trace_cmdtree
 * symbol to your module with MODULE_USE_SYMBOL().
 *
 * Then add your criteria to the tree with mowgli_patricia_add().
 */
mowgli_patricia_t *trace_cmdtree = NULL;
mowgli_patricia_t *trace_acttree = NULL;

void _modinit(module_t *m)
{
	service_named_bind_command("operserv", &os_trace);

	trace_cmdtree = mowgli_patricia_create(strcasecanon);
	mowgli_patricia_add(trace_cmdtree, "REGEXP", &trace_regexp);
	mowgli_patricia_add(trace_cmdtree, "SERVER", &trace_server);
	mowgli_patricia_add(trace_cmdtree, "GLOB", &trace_glob);
	mowgli_patricia_add(trace_cmdtree, "CHANNEL", &trace_channel);
	mowgli_patricia_add(trace_cmdtree, "NICKAGE", &trace_nickage);
	mowgli_patricia_add(trace_cmdtree, "NUMCHAN", &trace_numchan);
	mowgli_patricia_add(trace_cmdtree, "IDENTIFIED", &trace_identified);

	trace_acttree = mowgli_patricia_create(strcasecanon);
	mowgli_patricia_add(trace_acttree, "PRINT", &trace_print);
	mowgli_patricia_add(trace_acttree, "KILL", &trace_kill);
	mowgli_patricia_add(trace_acttree, "AKILL", &trace_akill);
	mowgli_patricia_add(trace_acttree, "COUNT", &trace_count);
}

void _moddeinit(module_unload_intent_t intent)
{
	mowgli_patricia_destroy(trace_cmdtree, NULL, NULL);

	service_named_unbind_command("operserv", &os_trace);
}

#define MAXMATCHES_DEF 1000

static bool os_cmd_trace_run(sourceinfo_t *si, trace_action_constructor_t *actcons, trace_action_t* act, mowgli_list_t *crit, char *args);

static void os_cmd_trace(sourceinfo_t *si, int parc, char *parv[])
{
	mowgli_list_t crit = { NULL, NULL, 0 };
	trace_action_constructor_t *actcons;
	trace_action_t* act;
	char *args = parv[1];
	mowgli_node_t *n, *tn;
	char *params;
	bool succeeded;

	if (!parv[0])
	{
		command_fail(si, fault_badparams, STR_INVALID_PARAMS, "TRACE");
		command_fail(si, fault_badparams, _("Syntax: TRACE <action> <params>"));
		return;
	}

	actcons = mowgli_patricia_retrieve(trace_acttree, parv[0]);
	if (actcons == NULL)
	{
		command_fail(si, fault_badparams, STR_INVALID_PARAMS, "TRACE");
		command_fail(si, fault_badparams, _("Syntax: TRACE <action> <params>"));
		return;
	}

	act = actcons->prepare(si, &args);
	if (act == NULL)
	{
		command_fail(si, fault_nosuch_target, _("Action compilation failed."));
		return;
	}

	params = sstrdup(args);
	succeeded = os_cmd_trace_run(si, actcons, act, &crit, args);

	MOWGLI_ITER_FOREACH_SAFE(n, tn, crit.head)
	{
		trace_query_domain_t *q = (trace_query_domain_t *) n->data;
		q->cons->cleanup(q);		
	}
	actcons->cleanup(act, succeeded);

	if (succeeded)
		logcommand(si, CMDLOG_ADMIN, "TRACE: \2%s\2 \2%s\2", parv[0], params);

	free(params);
}

static bool os_cmd_trace_run(sourceinfo_t *si, trace_action_constructor_t *actcons, trace_action_t* act, mowgli_list_t *crit, char *args)
{
	user_t *u;
	mowgli_patricia_iteration_state_t state;
	mowgli_node_t *n;

	if (args == NULL)
	{
		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "TRACE");
		command_fail(si, fault_needmoreparams, _("Syntax: TRACE <action> <params>"));
		return false;
	}

	while (true)
	{
		trace_query_constructor_t *cons;
		trace_query_domain_t *q;
		char *cmd = strtok(args, " ");

		if (cmd == NULL)
			break;

		cons = mowgli_patricia_retrieve(trace_cmdtree, cmd);
		if (cons == NULL)
		{
			command_fail(si, fault_nosuch_target, _("Invalid criteria specified."));
			return false;
		}

		args = strtok(NULL, "");
		if (args == NULL)
		{
			command_fail(si, fault_nosuch_target, _("Invalid criteria specified."));
			return false;
		}

		q = cons->prepare(&args);
		slog(LG_DEBUG, "operserv/trace: adding criteria %p(%s) to list [remain: %s]", q, cmd, args);
		if (q == NULL)
		{
			command_fail(si, fault_nosuch_target, _("Invalid criteria specified."));
			return false;
		}
		slog(LG_DEBUG, "operserv/trace: new args position [%s]", args);

		q->cons = cons;
		mowgli_node_add(q, &q->node, crit);
	}

	MOWGLI_PATRICIA_FOREACH(u, &state, userlist)
	{
		bool doit = true;

		MOWGLI_ITER_FOREACH(n, crit->head)
		{
			trace_query_domain_t *q = (trace_query_domain_t *) n->data;

			if (!q->cons->exec(u, q))
			{
				doit = false;
				break;
			}
		}

		if (doit)
			actcons->exec(u, act);
	}

	return true;
}

/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
 * vim:ts=8
 * vim:sw=8
 * vim:noexpandtab
 */
