emehta-contrib/os_trace.c

930 lines
21 KiB
C

/*
* 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(domain->channel != 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
*/