#include "module.h" #define AUTHOR "aquanight" #define VERSION "1.0.0" /* OPTIONAL Configuration Section */ /* Enable debugging output (and there's bound to be lots of it - most people will just leave this off. */ /* #define ENABLE_DEBUG */ /* Force logging of TRACE KILL, TRACE AKILL/GLINE, and TRACE GAG/IGNORE. Set to 1 if you want it or 0 if not. (DO NOT UNDEFINE PLEASE.) */ #define FORCE_LOG 1 /* End of configuration section. */ static Command* cTrace = 0; static Command* cTraceCrit = 0; static Command* cTraceAct = 0; static int my_os_trace(User* u); static void my_os_trace_help(User* u); static int my_os_trace_help_full(User* u); static int my_os_trace_help_criteria(User* u); static int my_os_trace_help_action(User* u); int AnopeInit(int argc, char** argv) { alog("Loading os_trace module..."); cTrace = createCommand("TRACE", my_os_trace, is_services_oper, -1, -1, -1, -1, -1); cTraceCrit = createCommand("TRACE CRITERIA", NULL, NULL, -1, -1, -1, -1, -1); cTraceAct = createCommand("TRACE ACTION", NULL, NULL, -1, -1, -1, -1, -1); alog("[os_trace] Adding command: /msg OperServ TRACE [Status: %d]", moduleAddCommand(OPERSERV, cTrace, MOD_HEAD)); alog("[os_trace] Adding help entries..."); moduleAddCommand(OPERSERV, cTraceCrit, MOD_HEAD); moduleAddCommand(OPERSERV, cTraceAct, MOD_HEAD); moduleAddHelp(cTrace, my_os_trace_help_full); moduleAddHelp(cTraceCrit, my_os_trace_help_criteria); moduleAddHelp(cTraceAct, my_os_trace_help_action); moduleSetOperHelp(my_os_trace_help); moduleAddAuthor(AUTHOR); moduleAddVersion(VERSION); alog("[os_trace] Module loaded."); return MOD_CONT; } void AnopeFini(void) { alog("[os_trace] Unloading..."); alog("[os_trace] Removing command /msg OperServ TRACE"); moduleDelCommand(OPERSERV, "TRACE"); destroyCommand(cTrace); alog("[os_trace] Module unloaded."); } /* Parameter Hard limit */ #define CRIT_MAXPARAM 15 typedef struct _DomainInfo { char* domain; int count; struct _DomainInfo* next; } DomainInfo; typedef struct _ActionParams { int abuse_opers; int limit; char* reason; int depth; int log; int current; int duration; DomainInfo* domains; } ActionParams; /* Trace action table... */ /* Called after all criteria have been parsed, before match testing begins: * source : User who used /operserv TRACE * argv : Criteria arguments as: KEYWORD params. (Always use stricmp for KEYWORD.) * (Statically allocated with CRIT_MAXPARAM elements. Empty elements are NULL. Use * sstrdup when assigning new strings to empty elements. Free any modified or removed * elements. This is useful to allow: * a) Sanitizing the action parameters, which can include: * - Removing unneeded parameters (such as REASON with PRINT). * - Converting parameters to valid syntax. * - Adding missing parameters with default values. * b) Adding parameters for internal use. (For example, COUNT.) * Changes will propogate to ActionProc. */ typedef void (*ActionInit)(User* source, ActionParams* params); /* Called when a match is found. source and argv are the same as in ActionInit. * who is the user who matched. Changes to a parameter will propogate to later calls. */ typedef void (*ActionProc)(User* source, User* who, ActionParams* params); /* Called after all matches have been processed. Changes to argv will not propogate as there * will be nothing to propogate to. There is no need to free() anything, non-NULL parameters * will be free'd automatically (thus it's important all parameters are sstrdup'd or malloc'd. */ typedef void (*ActionDone)(User* source, ActionParams* params); typedef struct _TraceAction { char* keyword; ActionInit init; ActionProc proc; ActionDone done; int (*allowed)(User* u); char* description; } TraceAction; static void act_print_init(User* source, ActionParams* params); static void act_print_doit(User* source, User* who, ActionParams* params); static void act_print_term(User* source, ActionParams* params); static void act_count_init(User* source, ActionParams* params); static void act_count_doit(User* source, User* who, ActionParams* params); static void act_count_term(User* source, ActionParams* params); static void act_kill_init(User* source, ActionParams* params); static void act_kill_doit(User* source, User* who, ActionParams* params); static void act_kill_term(User* source, ActionParams* params); static void act_akill_init(User* source, ActionParams* params); static void act_akill_doit(User* source, User* who, ActionParams* params); static void act_akill_term(User* source, ActionParams* params); static void act_gag_init(User* source, ActionParams* params); static void act_gag_doit(User* source, User* who, ActionParams* params); static void act_gag_term(User* source, ActionParams* params); static void act_domains_init(User* source, ActionParams* params); static void act_domains_doit(User* source, User* who, ActionParams* params); static void act_domains_term(User* source, ActionParams* params); static TraceAction actions[] = { { "PRINT", act_print_init, act_print_doit, act_print_term, is_services_oper, "Display hostmask of matching clients." }, { "COUNT", act_count_init, act_count_doit, act_count_term, is_services_oper, "Display only the number of matching clients." }, { "KILL", act_kill_init, act_kill_doit, act_kill_term, is_services_oper, "Kill matching clients." }, { "AKILL", act_akill_init, act_akill_doit, act_akill_term, is_services_oper, "Issue an AKILL for the client's hostname." }, { "GLINE", act_akill_init, act_akill_doit, act_akill_term, is_services_oper, "Alias for AKILL." }, { "IGNORE", act_gag_init, act_gag_doit, act_gag_term, is_services_admin, "Set a services ignore for the client." }, { "GAG", act_gag_init, act_gag_doit, act_gag_term, is_services_admin, "Alias of IGNORE." }, { "DOMAINS", act_domains_init, act_domains_doit, act_domains_term, is_services_oper, "Print all domains used on the network." } }; static int crit_mask(User* source, User* u, char* argv[], ActionParams* params); static int crit_nick(User* source, User* u, char* argv[], ActionParams* params); static int crit_ident(User* source, User* u, char* argv[], ActionParams* params); static int crit_host(User* source, User* u, char* argv[], ActionParams* params); static int crit_info(User* source, User* u, char* argv[], ActionParams* params); static int crit_server(User* source, User* u, char* argv[], ActionParams* params); /* static int crit_ip(User* source, User* u, char* argv[], ActionParams* params); */ static int crit_vhost(User* source, User* u, char* argv[], ActionParams* params); static int crit_identify(User* source, User* u, char* argv[], ActionParams* params); static int crit_channel(User* source, User* u, char* argv[], ActionParams* params); static int crit_numchannels(User* source, User* u, char* argv[], ActionParams* params); static int crit_nickage(User* source, User* u, char* argv[], ActionParams* params); static int crit_access(User* source, User* u, char* argv[], ActionParams* params); static int crit_clones(User* source, User* u, char* argv[], ActionParams* params); static int crit_infospace(User* source, User* u, char* argv[], ActionParams* params); /* static int crit_usermode(User* source, User* u, char* argv[], ActionParams* params); */ static int crit_chanaccess(User* source, User* u, char* argv[], ActionParams* params); static int crit_abuse(User* source, User* u, char* argv[], ActionParams* params); static int crit_limit(User* source, User* u, char* argv[], ActionParams* params); static int crit_reason(User* source, User* u, char* argv[], ActionParams* params); static int crit_duration(User* source, User* u, char* argv[], ActionParams* params); static int crit_depth(User* source, User* u, char* argv[], ActionParams* params); static int crit_log(User* source, User* u, char* argv[], ActionParams* params); /* When u is NULL the CriteriaProc should simply make sure the parameters are valid and return 1 if so, or 0 if not. */ typedef int (*CriteriaProc)(User* source, User* u, char* argv[], ActionParams* params); typedef struct _TraceCriteria { char* keyword; int numargs; /* -1 if we want a parameter w/ spaces. */ CriteriaProc proc; char* description; char* extrahelp; /* Extra help appended at the end of HELP TRACE CRITERIA. \n's ok. */ } TraceCriteria; static TraceCriteria crit[] = { { "MASK", 1, crit_mask, "n!u@h - Match against a full nick!user@hostname hostmask.", NULL }, { "NICK", 1, crit_nick, "nick - Match against a nickname.", NULL }, { "IDENT", 1, crit_ident, "ident - Match against a username/ident.", NULL }, { "HOST", 1, crit_host, "hostname - Match against a hostname.", NULL }, { "INFO", 1, crit_info, "mask - Match against the realname field (one word only).", NULL }, { "SERVER", 1, crit_server, "server - Match against a servername.", NULL }, /* { "IP", 1, crit_ip, "1.2.3.4 - Match against numeric IP (if possible).", NULL }, */ /* CURSED ANOPE DOESN'T STORE THE IP :/ */ { "VHOST", 1, crit_vhost, "vhost - Match against vhost or cloaked host.", NULL }, { "IDENTIFY", 1, crit_identify, "yes/recognized/no - Match users who are identified, recognized (but not identified), or not recognized.", "The difference between identified and recognized is simple: \"identified\"\n" "status occurs after identifying, while \"recognized\" occurs if a user\n" "matches one of his ACCESS LIST entries but has not identified. Recognized\n" "status is only possible if SET SECURE is OFF and the user has one or more\n" "ACCESS LIST entries." }, { "CHANNEL", 1, crit_channel, "#channel Match users in channel.", "For CHANNEL, prefixes may be used to select channel members with a given\n" "status level. Use the actual mode character (eg, o for ops, v for voice,\n" "etc) rather than the NAMES prefixes. For example: o#foo will select ops\n" "from #foo. v#foo will select voices (but not ops) in #foo. ov#foo will\n" "select both ops and voices. -#foo will select users without any status.\n" "v-#foo will select voices and normals. @ and + may be used for ops and voices." }, { "NUMCHANNELS", 1, crit_numchannels, "n Number of channels the user is in. Comparison operators allowed.", NULL }, { "NICKAGE", 1, crit_nickage, "cmp Time client has held the nick. Comparison operators allowed.", NULL }, { "ACCESS", 1, crit_access, "{n|i|o|a|r} Client has \002n\002ormal, \002i\002rcop, or services \002o\002per, \002a\002dmin, or \002r\002oot access.", "For ACCESS, multiple types may be given and are matched in an OR fashion.\n" "For example, \002oar\002 would match anyone with any level of services access,\n" "while ni will match anyone that does not." }, { "CLONES", 1, crit_clones, "num Ignore client with fewer than num clones (connections from the same host).", NULL }, { "INFO_SPACE", 1, crit_infospace, "yes/no If yes, match if a client's realname begins with a space character.", NULL }, /* { "USERMODE", 1, crit_usermode, "[+plsmodes][-mnsmodes] Match if user has all of plsmodes and none of mnsmodes usermodes.", NULL }, */ { "CHANACCESS", 2, crit_chanaccess, "#chan access User has named privilege in #chan.", "For CHANACCESS, access is selected from the privilege keywords as listed in\n" "\002/msg ChanServ HELP LEVELS DESC\002. This is compatible with xOP. For example,\n" "AUTOOP will match users in the AOP or SOP list in channels using xOP. AUTOstatus\n" "levels will match even if the user does not normally get that status For\n" "example, while AUTOHALFOP will override AUTOVOICE, users with both will still\n" "match for both." }, { "ABUSE", 1, crit_abuse, "OPERS Allow actions KILL, GLINE/AKILL, and GAG/IGNORE to operate on IRC operators.\n", "For ABUSE OPERS, KILL, GLINE/AKILL, and GAG/IGNORE will NEVER affect an IRCop of a higher level\n" "of access than you. Service opers, admins, and roots that are not opered are treated as normal\n" "users (if NSStrictPrivileges is in effect), otherwise they are considered at their respective\n" "levels and will not be effected (regardless of the presence or absence of ABUSE OPERS." }, { "LIMIT", 1, crit_limit, "num Stop processing after num matches.", "LIMIT can be used to protect from inadverently using KILL, AKILL/GLINE, or GAG/IGNORE with a\n" "poorly formed criteria that could otherwise result in affecting a large quantity of innocent\n" "users. It can also be used with PRINT to limit the number of matches returned. It does not\n" "affect COUNT. Note that LIMIT will limit the number of akills placed. It will not limit the\n" "number of users affected by an akill." }, { "REASON", -1, crit_reason, "reason (Must be last.) Specifies the reason for KILLs and GLINEs/AKILLs.", NULL }, { "DURATION", 0, crit_duration, "Sets the duration for GLINES/AKILLS. See HELP AKILL for more information.", NULL }, { "DEPTH", 1, crit_depth, "parts How many parts is considered part of the domain for the \002DOMAINS\002 action.", NULL }, { "LOG", 0, crit_log, "Log each match and what action will be taken (for KILL, AKILL/GLINE, and GAG/IGNORE).", #if FORCE_LOG == 1 "This network has chosen to force logging of all TRACE actions where LOG would be appropriate, so its\n" "presence or absence will have no effect - actions will be logged no matter what." #else NULL #endif } }; typedef struct _CompiledCriteria { TraceCriteria* crit; char* argv[CRIT_MAXPARAM]; } CompiledCriteria; #define MAX_CRIT 16 #ifdef ENABLE_DEBUG #define DEBUG(x) alog x #else #define DEBUG(x) (void)(x) #endif static int my_os_trace(User* u) { char* tok = NULL; char* buf = NULL; CompiledCriteria cmpcrit[MAX_CRIT]; int i = 0, numtok = 0, nCrit = 0, nArg = 0, j = 0; User* target = NULL, *nexttarget = NULL; ActionParams params = {0, 0, NULL, 0, 0, 0}; TraceAction* act = NULL; DEBUG(("Initializing...")); for (nCrit = 0; nCrit < MAX_CRIT; nCrit++) { cmpcrit[nCrit].crit = 0; for (nArg = 0; nArg < CRIT_MAXPARAM; nArg++) cmpcrit[nCrit].argv[nArg] = NULL; } nCrit = 0; nArg = 0; buf = moduleGetLastBuffer(); numtok = myNumToken(buf, ' '); if (numtok < 2) { notice_user(s_OperServ, u, "Syntax: \002TRACE \037action\037 \037criteria\037"); notice_user(s_OperServ, u, "\002/msg %s HELP TRACE for more information\002", s_OperServ); return MOD_STOP; } tok = myStrGetToken(buf, ' ', 0); DEBUG(("Getting action for token %s", tok)); for (i = 0; i < (sizeof(actions) / sizeof(actions[0])); i++) { if (!stricmp(actions[i].keyword, tok)) { DEBUG(("Match found: keyword == %s , init == %p , proc == %p , done == %p", actions[i].keyword, (void*)actions[i].init, (void*)actions[i].proc, (void*)actions[i].done)); act = &actions[i]; } } if (act == NULL) { notice_user(s_OperServ, u, "Unknown action \002%s\002. Type \002/msg %s HELP TRACE ACTIONS\002 for help.", tok, s_OperServ); free(tok); return MOD_STOP; } if (!act->allowed(u)) { notice_lang(s_OperServ, u, PERMISSION_DENIED); return MOD_STOP; } free(tok); DEBUG(("Entering query compile loop. numtok = %d", numtok)); /* Compile the query... */ for (i = 1; i < numtok; i++) { DEBUG(("Top of query compile loop. i = %d, numtok = %d", i, numtok)); tok = myStrGetToken(buf, ' ', i); DEBUG(("Finding matching criteria entry for %s", tok)); for (j = 0; j < (sizeof(crit) / sizeof(crit[0])); j++) { if (!stricmp(tok, crit[j].keyword)) { DEBUG(("Match found: keyword == %s, numargs == %d, proc == %p", crit[j].keyword, crit[j].numargs, (void*)crit[j].proc)); cmpcrit[nCrit].crit = &crit[j]; nArg = 0; if (crit[j].numargs < 0) { DEBUG(("Eating remaining parameters for this criteria.")); cmpcrit[j].argv[0] = myStrGetTokenRemainder(buf, ' ', i + 1); i = numtok; /* Exit when we get to the loop end. */ } else { DEBUG(("Eating %d parameters for this criteria.", crit[j].numargs)); for (nArg = 0; nArg < crit[j].numargs; nArg++) { cmpcrit[nCrit].argv[nArg] = myStrGetToken(buf, ' ', ++i); DEBUG(("Parameter %d : %s", nArg, cmpcrit[nCrit].argv[nArg])); } } /* Validate the parameters. */ switch (cmpcrit[nCrit].crit->proc(u, NULL, cmpcrit[nCrit].argv, ¶ms)) { case 0: /* Parameters invalid. */ DEBUG(("Criteria handler reported invalid parameters!")); notice_user(s_OperServ, u, "Invalid parameter for criteria \002%s\002. Type \002/msg %s HELP TRACE CRITERIA\002 for help.", tok, s_OperServ); free(tok); for(nCrit = 0; nCrit < MAX_CRIT; nCrit++) { for (nArg = 0; nArg < CRIT_MAXPARAM; nArg++) { if (cmpcrit[nCrit].argv[nArg]) { free(cmpcrit[nCrit].argv[nArg]); } } } return MOD_STOP; case -1: /* Parameters ok, but don't include this query. */ DEBUG(("Criteria handler requesting drop from query.")); for (nArg = 0; nArg < CRIT_MAXPARAM; nArg++) { if (cmpcrit[nCrit].argv[nArg]) { free(cmpcrit[nCrit].argv[nArg]); cmpcrit[nCrit].argv[nArg] = NULL; } } cmpcrit[nCrit].crit = NULL; break; case 1: /* Parameters ok, include in query. */ DEBUG(("Criteria handler verifies parameter(s) OK.")); nCrit++; } break; } } free(tok); } /* Ok, now cmpcrit is filled with all the criteria to be processed and the arguments to give to them. */ /* Let it begin. */ DEBUG(("Initializing action handler")); act->init(u, ¶ms); for (target = firstuser(), nexttarget = nextuser(); target; target = nexttarget, nexttarget = nextuser()) { DEBUG(("Top of user loop. (User = %s)", target->nick)); j = 1; /* Default to true. */ for (nCrit = 0; nCrit < MAX_CRIT; nCrit++) { DEBUG(("Top of criteria check loop... (%d of %d)", nCrit, MAX_CRIT)); if (cmpcrit[nCrit].crit) { if (j) DEBUG(("Running criteria check %s", cmpcrit[nCrit].crit->keyword)); j = j && cmpcrit[nCrit].crit->proc(u, target, cmpcrit[nCrit].argv, ¶ms); if (!j) { DEBUG(("User does not match - moving on.")); break; } } } if (j) { DEBUG(("User matched! Running action handler.")); act->proc(u, target, ¶ms); } } DEBUG(("When all is said and done... CLEANUP!")); act->done(u, ¶ms); return MOD_CONT; } static void my_os_trace_help(User* u) { if (!is_services_oper(u)) return; notice_user(s_OperServ, u, " TRACE Execute an action for each user matching a given criteria."); } static int my_os_trace_help_full(User* u) { if (!is_services_oper(u)) return MOD_CONT; notice_user(s_OperServ, u, "Syntax: \002TRACE \037action\037 \037criteria\037"); notice_user(s_OperServ, u, "Executes the specified \037action\037 for each user matching the given"); notice_user(s_OperServ, u, "\037criteria\037. \037action\037 is a single keyword that indicates what"); notice_user(s_OperServ, u, "action will be taken. Type \002/msg %s HELP TRACE ACTION\002 for help", s_OperServ); notice_user(s_OperServ, u, "with actions."); notice_user(s_OperServ, u, " "); notice_user(s_OperServ, u, "The \037criteria\037 parameter is more complex. It consists"); notice_user(s_OperServ, u, "of a series of keywords naming the test to be performed, each follwed by"); notice_user(s_OperServ, u, "zero or more parameters. Type \002/msg %s HELP TRACE CRITERIA\002 for", s_OperServ); notice_user(s_OperServ, u, "help with criteria."); return MOD_CONT; } static int my_os_trace_help_criteria(User* u) { int i = 0, j = 0, numlin; char* tmp; if (!is_services_oper(u)) return MOD_CONT; notice_user(s_OperServ, u, "Available criteria:"); for (i = 0; i < (sizeof(crit) / sizeof(crit[0])); i++) { notice_user(s_OperServ, u, "%s %s", crit[i].keyword, crit[i].description); } /* Again for the "extra info". */ for (i = 0; i < (sizeof(crit) / sizeof(crit[0])); i++) { if (crit[i].extrahelp) { numlin = myNumToken(crit[i].extrahelp, '\n'); for (j = 0; j < numlin; j++) { tmp = myStrGetToken(crit[i].extrahelp, '\n', j); notice_user(s_OperServ, u, "%s", tmp); free(tmp); } } } return MOD_CONT; } static int my_os_trace_help_action(User* u) { int i = 0; if (!is_services_oper(u)) return MOD_CONT; notice_user(s_OperServ, u , "Available actions:"); for (i = 0; i < (sizeof(actions) / sizeof(actions[0])); i++) { notice_user(s_OperServ, u, "%s %s", actions[i].keyword, actions[i].description); } return MOD_CONT; } /* Friendly reminder of what this is: * typedef struct _DomainInfo { * char* domain; * int count; * struct _DomainInfo* next; * } DomainInfo; * * typedef struct _ActionParams { * int abuse_opers; * int limit; * char* reason; * int depth; * int log; * int current; * int duration; * DomainInfo* domains; * } ActionParams; */ /* Just a sanity check. Some people insist on causing trouble sometimes. */ #ifndef FORCE_LOG #define FORCE_LOG 0 #endif static void act_print_init(User* source, ActionParams* params) { params->current = 0; params->abuse_opers = 0; /* We don't need this. */ if (params->reason) free(params->reason); /* Don't need this either. */ params->reason = NULL; params->depth = 0; params->log = 0; /* params->limit used */ params->duration = 0; } static void act_print_doit(User* source, User* who, ActionParams* params) { params->current++; if (params->limit > 0 && params->current > params->limit) { return; } if (ircd->vhost && who->vhost && stricmp(who->vhost, who->host)) { if (ircd->vident && who->vident && stricmp(who->vident, who->username)) notice_user(s_OperServ, source, "Match: %s!%s@%s => %s@%s (%s) on %s ", who->nick, who->username, who->host, who->vident, who->vhost, who->realname, who->server->name); else notice_user(s_OperServ, source, "Match: %s!%s@%s => %s (%s) on %s ", who->nick, who->username, who->host, who->vhost, who->realname, who->server->name); } else { if (ircd->vident && who->vident && stricmp(who->vident, who->username)) notice_user(s_OperServ, source, "Match: %s!%s@%s => %s@%s (%s) on %s ", who->nick, who->username, who->host, who->vident, who->host, who->realname, who->server->name); else notice_user(s_OperServ, source, "Match: %s!%s@%s (%s) on %s ", who->nick, who->username, who->host, who->realname, who->server->name); } } static void act_print_term(User* source, ActionParams* params) { if (params->limit > 0 && params->current > params->limit) notice_user(s_OperServ, source, "Found %d match%s, truncated list to %d (use larger LIMIT to see the remaining matches).", params->current, (params->current != 1 ? "es" : ""), params->limit); else notice_user(s_OperServ, source, "Found %d match%s.", params->current, (params->current != 1 ? "es" : "")); } static void act_count_init(User* source, ActionParams* params) { params->current = 0; params->abuse_opers = 0; /* We don't need this. */ if (params->reason) free(params->reason); /* Don't need this either. */ params->reason = NULL; params->depth = 0; params->log = 0; params->limit = 0; params->duration = 0; } static void act_count_doit(User* source, User* who, ActionParams* params) { params->current++; } static void act_count_term(User* source, ActionParams* params) { notice_user(s_OperServ, source, "Found %d match%s.", params->current, (params->current != 1 ? "es" : "")); } static void act_kill_init(User* source, ActionParams* params) { char buf[BUFSIZE]; params->current = 0; /* params->abuse_opers used */ /* params->reason used */ params->depth = 0; params->log = params->log || FORCE_LOG; /* params->limit used */ params->duration = 0; /* Assign a default reason. */ if (!params->reason) { snprintf(buf, BUFSIZE - 1, "Requested by %s", source->nick); params->reason = sstrdup(buf); } if (!(*params->reason)) { free(params->reason); snprintf(buf, BUFSIZE - 1, "Requested by %s", source->nick); params->reason = sstrdup(buf); } } static void act_kill_doit(User* source, User* who, ActionParams* params) { if (source == who) return; if (is_services_root(who) && !is_services_root(source)) return; if (is_services_admin(who) && !is_services_admin(source)) return; if (is_services_oper(who) && !is_services_oper(source)) return; if (is_oper(who) && !params->abuse_opers) return; params->current++; if (params->limit > 0 && params->current > params->limit) { return; } if (params->log) alog("TRACE KILL %s by %s REASON %s", who->nick, source->nick, params->reason); kill_user(s_OperServ, who->nick, params->reason); } static void act_kill_term(User* source, ActionParams* params) { if (params->reason) free(params->reason); if (params->limit > 0 && params->current > params->limit) notice_user(s_OperServ, source, "Found %d match%s, only killed %d.", params->current, (params->current != 1 ? "es" : ""), params->limit); else notice_user(s_OperServ, source, "Found %d match%s.", params->current, (params->current != 1 ? "es" : "")); } static void act_akill_init(User* source, ActionParams* params) { char buf[BUFSIZE]; params->current = 0; /* params->abuse_opers used */ /* params->reason used */ params->depth = 0; params->log = params->log || FORCE_LOG; /* params->limit used */ /* params->duration used */ /* Assign a default reason. */ if (!params->reason) { snprintf(buf, BUFSIZE - 1, "Requested by %s", source->nick); params->reason = sstrdup(buf); } if (!(*params->reason)) { free(params->reason); snprintf(buf, BUFSIZE - 1, "Requested by %s", source->nick); params->reason = sstrdup(buf); } } static void act_akill_doit(User* source, User* who, ActionParams* params) { char buf[BUFSIZE]; if (source == who) return; if (is_services_root(who) && !is_services_root(source)) return; if (is_services_admin(who) && !is_services_admin(source)) return; if (is_services_oper(who) && !is_services_oper(source)) return; if (is_oper(who) && !params->abuse_opers) return; if (params->log) alog("TRACE AKILL %s by %s DURATION %d REASON %s", who->nick, source->nick, params->duration, params->reason); if (params->limit > 0 && params->current > params->limit) { return; } params->current++; snprintf(buf, BUFSIZE - 1, "*@%s", who->host); kill_user(s_OperServ, who->nick, params->reason); /* Thwak him so that the akill gets enforced no matter what. */ add_akill(source, buf, source->nick, (params->duration ? time(NULL) + params->duration : 0), params->reason); } static void act_akill_term(User* source, ActionParams* params) { if (params->reason) free(params->reason); if (params->limit > 0 && params->current > params->limit) notice_user(s_OperServ, source, "Found %d match%s, only akilled %d.", params->current, (params->current != 1 ? "es" : ""), params->limit); else notice_user(s_OperServ, source, "Found %d match%s.", params->current, (params->current != 1 ? "es" : "")); } static void act_gag_init(User* source, ActionParams* params) { params->current = 0; /* params->abuse_opers used */ if (params->reason) free(params->reason); params->reason = NULL; params->depth = 0; params->log = params->log || FORCE_LOG; /* params->limit used */ /* params->duration used */ if (params->abuse_opers) notice_user(s_OperServ, source, "Warning - using ABUSE OPERS with GAG or IGNORE will be ineffective at first."); } static void act_gag_doit(User* source, User* who, ActionParams* params) { if (source == who) return; if (is_services_root(who) && !is_services_root(source)) return; if (is_services_admin(who) && !is_services_admin(source)) return; if (is_services_oper(who) && !is_services_oper(source)) return; if (is_oper(who) && !params->abuse_opers) return; if (params->log) alog("TRACE IGNORE %s by %s DURATION %d", who->nick, source->nick, params->duration); if (params->limit > 0 && params->current > params->limit) { return; } params->current++; add_ignore(who->nick, params->duration); } static void act_gag_term(User* source, ActionParams* params) { if (params->limit > 0 && params->current > params->limit) notice_user(s_OperServ, source, "Found %d match%s, only ignored %d.", params->current, (params->current != 1 ? "es" : ""), params->limit); else notice_user(s_OperServ, source, "Found %d match%s.", params->current, (params->current != 1 ? "es" : "")); } static void act_domains_init(User* source, ActionParams* params) { params->current = 0; params->abuse_opers = 0; if (params->reason) free(params->reason); params->reason = NULL; /* params->depth used */ params->log = 0; params->limit = 0; params->duration = 0; params->domains = NULL; } static void act_domains_doit(User* source, User* who, ActionParams* params) { int numparts = 0; char* tmp; DomainInfo* d; numparts = myNumToken(who->host, '.'); if (numparts <= 1) tmp = sstrdup(who->host); if (numparts <= params->depth) tmp = myStrGetTokenRemainder(who->host, '.', 1); else tmp = myStrGetTokenRemainder(who->host, '.', numparts - params->depth); for (d = params->domains; d; d = d->next) { if (!stricmp(d->domain, tmp)) { d->count++; free(tmp); return; } } /* Not found, create one. */ d = smalloc(sizeof(DomainInfo)); /* return valid pointer OR ELSE */ d->domain = tmp; d->count = 1; d->next = params->domains; params->domains = d; } static void act_domains_term(User* source, ActionParams* params) { DomainInfo* d; d = params->domains; while (d) { notice_user(s_OperServ, source, "Domain %s contains %d user%s.", d->domain, d->count, (d->count != 1 ? "s" : "")); free(d->domain); params->domains = d->next; free(d); d = params->domains; } } static int crit_mask(User* source, User* u, char* argv[], ActionParams* params) { char buf[BUFSIZE]; if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!u) return 1; snprintf(buf, BUFSIZE - 1, "%s!%s@%s", u->nick, u->username, u->host); if (match_wild_nocase(argv[0], buf)) return 1; if (ircd->vhost && u->vhost && stricmp(u->vhost, u->host)) { snprintf(buf, BUFSIZE - 1, "%s!%s@%s", u->nick, u->username, u->vhost); if (match_wild_nocase(argv[0], buf)) return 1; } if (ircd->vident && u->vident && stricmp(u->vident, u->username)) { snprintf(buf, BUFSIZE - 1, "%s!%s@%s", u->nick, u->vident, u->host); if (match_wild_nocase(argv[0], buf)) return 1; if (ircd->vhost && u->vhost && stricmp(u->vhost, u->host)) { snprintf(buf, BUFSIZE - 1, "%s!%s@%s", u->nick, u->vident, u->vhost); if (match_wild_nocase(argv[0], buf)) return 1; } } return 0; } static int crit_nick(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!u) return 1; return match_wild_nocase(argv[0], u->nick); } static int crit_ident(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!u) return 1; return match_wild_nocase(argv[0], u->username); } static int crit_host(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!u) return 1; return match_wild_nocase(argv[0], u->host); } static int crit_info(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!u) return 1; return match_wild_nocase(argv[0], u->realname); } static int crit_server(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!u) return 1; return match_wild_nocase(argv[0], u->server->name); } /* static int crit_ip(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } / * TODO : CIDR SUPPORT. * / (Intentional space.) if (!ircd->nickip) { notice_user(s_OperServ, source, "The ircd software used by this network does not provide the information required for the IP criteria."); return 0; } if (!u) return 1; return match_wild_nocase(argv[0], u->ip); } */ static int crit_vhost(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!ircd->vhost) { notice_user(s_OperServ, source, "The ircd software used by this network does not support vhosts."); return 0; } if (!u) return 1; return match_wild_nocase(argv[0], u->vhost); } static int crit_identify(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } if (!stricmp(argv[0], "yes")) { if (!u) return 1; else return nick_identified(u); } else if (!stricmp(argv[0], "recognized")) { if (!u) return 1; else return nick_recognized(u) && !nick_identified(u); } else if (!stricmp(argv[0], "no")) { if (!u) return 1; else return !nick_recognized(u); } else return 0; } static int crit_channel(User* source, User* u, char* argv[], ActionParams* params) { Channel* c; char* tmp; int wantnormals = 0; int wantstatus = 0; int status = 0; if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } for (tmp = argv[0]; *tmp && (cumodes[(int)*tmp].status != 0 || *tmp == '-' || *tmp == '@' || *tmp == '+'); tmp++) { if (*tmp == '-') wantnormals = 1; else if (*tmp == '@') wantstatus |= CUS_OP; else if (*tmp == '+') wantstatus |= CUS_VOICE; else wantstatus |= cumodes[(int)*tmp].status; } if (!*tmp) { notice_user(s_OperServ, source, "Invalid channel specification."); return 0; } if (!u) return 1; if (!(c = findchan(tmp))) return 0; if (!is_on_chan(c, u)) return 0; if (!wantnormals && !wantstatus) return 1; /* No prefixes = Return everyone. */ status = chan_get_user_status(c, u); if (!status && wantnormals) return 1; /* - prefix - return normals. */ if (status & wantstatus) return 1; /* status mode chars, return people with any those statuses. */ /* Hm. Oh well. */ return 0; } static int crit_numchannels(User* source, User* u, char* argv[], ActionParams* params) { int cmpop; unsigned long l, chancount = 0; struct u_chanlist* ch = NULL; char* tmp, *tmp2; if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } tmp = argv[0]; switch (*tmp) { case '<': cmpop = -1; tmp++; break; case '>': cmpop = 1; tmp++; break; case '=': cmpop = 3; tmp++; break; default: cmpop = 0; break; } switch (*tmp) { case '=': if (cmpop == 1) cmpop = 2; else if (cmpop == -1) cmpop = -2; else if (cmpop == 3) cmpop = 0; else { notice_user(s_OperServ, source, "Invalid operation specification."); return 0; } tmp++; break; case '>': if (cmpop == 3) cmpop = 2; else { notice_user(s_OperServ, source, "Invalid operation specification."); return 0; } tmp++; break; case '<': if (cmpop == 3) cmpop = -2; else { notice_user(s_OperServ, source, "Invalid operation specification."); return 0; } tmp++; break; default: if (cmpop == 3) cmpop = 0; } l = strtoul(tmp, &tmp2, 10); if (*tmp2) { notice_user(s_OperServ, source, "Number parameter for comparison is invalid."); return 0; } if (!u) return 1; for (ch = u->chans; ch; ch = ch->next) { chancount++; } switch (cmpop) { case 0: return chancount == l; case 1: return chancount > l; case 2: return chancount >= l; case -1: return chancount < l; case -2: return chancount <= l; default: /* ugh */ return 0; } } static int crit_nickage(User* source, User* u, char* argv[], ActionParams* params) { int cmpop; unsigned long l, age; char* tmp, *tmp2; if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } tmp = argv[0]; switch (*tmp) { case '<': cmpop = -1; tmp++; break; case '>': cmpop = 1; tmp++; break; case '=': cmpop = 3; tmp++; break; default: cmpop = 0; break; } switch (*tmp) { case '=': if (cmpop == 1) cmpop = 2; else if (cmpop == -1) cmpop = -2; else if (cmpop == 3) cmpop = 0; else { notice_user(s_OperServ, source, "Invalid operation specification."); return 0; } tmp++; break; case '>': if (cmpop == 3) cmpop = 2; else { notice_user(s_OperServ, source, "Invalid operation specification."); return 0; } tmp++; break; case '<': if (cmpop == 3) cmpop = -2; else { notice_user(s_OperServ, source, "Invalid operation specification."); return 0; } tmp++; break; default: if (cmpop == 3) cmpop = 0; } l = strtoul(tmp, &tmp2, 10); if (*tmp2) { notice_user(s_OperServ, source, "Number parameter for comparison is invalid."); return 0; } if (!u) return 1; age = time(NULL) - u->timestamp; switch (cmpop) { case 0: return age == l; case 1: return age > l; case 2: return age >= l; case -1: return age < l; case -2: return age <= l; default: /* ugh */ return 0; } } static int crit_access(User* source, User* u, char* argv[], ActionParams* params) { int wantnormals = 0, wantopers = 0, wantcsops = 0, wantadmins = 0, wantroots = 0; char* c; if (!argv[0]) { notice_user(s_OperServ, source, "Missing required access specification."); return 0; } for (c = argv[0]; *c; c++) { if (*c == 'n' || *c == 'N') wantnormals = 1; else if (*c == 'i' || *c == 'I') wantopers = 1; else if (*c == 'o' || *c == 'O') wantcsops = 1; else if (*c == 'a' || *c == 'A') wantadmins = 1; else if (*c == 'r' || *c == 'R') wantroots = 1; else { notice_user(s_OperServ, source, "Invalid access character %c", *c); return 0; } } if (!u) return 1; if (is_services_root(u)) return wantroots; if (is_services_admin(u)) return wantadmins; if (is_services_oper(u)) return wantcsops; if (is_oper(u)) return wantopers; return wantnormals; } static int crit_clones(User* source, User* u, char* argv[], ActionParams* params) { unsigned long l; char* tmp; Session* session; if (!LimitSessions) { notice_user(s_OperServ, source, "Session limiting is currently disabled. The CLONES criteria requires session limiting to function."); return 0; } if (!argv[0]) { notice_user(s_OperServ, source, "Missing required number argument."); return 0; } l = strtoul(argv[0], &tmp, 10); if (*tmp) { notice_user(s_OperServ, source, "Invalid number."); return 0; } if (!u) return 1; session = findsession(u->host); if (!session) return 0; return session->count >= l; } static int crit_infospace(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } else if (stricmp(argv[0], "YES") && stricmp(argv[0], "NO")) { notice_user(s_OperServ, source, "Expected yes or no argument."); return 0; } if (!u) return 1; /* This does look weird. Remember that ^ is XOR: * When realname is valid and begins witha space, the first half is true. * When argv[0] is "NO" the second half is true. * So when the realname begins with a space and the parameter is NO, it's false (we DON'T want a space, but we have one). * When the realname begins with a space and the parameter is YES, it's true (we WANT a space, and have one). * When the realname begins with nonspace and the parameter is YES, it's false (we WANT a space but don't have one). * When the realname begins with nonspace and the parameter is NO, it's ture (we DON'T want a space, and don't have one). */ return (u->realname && *(u->realname) == ' ') ^ !stricmp(argv[0], "NO"); } /* static int crit_usermode(User* source, User* u, char* argv[], ActionParams* params) { char* c = NULL; int want = 1; / * 1 = We want. 0 = We don't want. * / / * Validate. We want one argument which is formatted like so: * argv[1] = * modespec = [+][-] * musthave = * canthave = * modechars= * flag = (As defined by the IRCd.) * / if (!argv[0]) { notice_user(s_OperServ, source, "Missing required usermode parameter."); return 0; } for (c = argv[0]; *c; c++) { if (*c != '-' && *c != '+' && umodes[*c] == 0) { / * Ick. * / notice_user(s_OperServ, source, "Usermode \002%c\002 is unknown.", *c); return 0; } } if (!u) return 1; for (c = argv[0]; *c; c++) { if (*c == '+') want = 1; else if (*c == '-') want = 0; else { if (!((want ? 1 : 0) ^ (u->mode & umodes[*c] ? 0 : 1))) return 0; } } return 1; } */ static int crit_chanaccess(User* source, User* u, char* argv[], ActionParams* params) { ChannelInfo* ci; int i = 0, lvl = -1; /* Validate. We require two arguments. One must be a registered channel, the other an access keyword. */ if (!argv[0] || !argv[1]) { notice_user(s_OperServ, source, "Insufficient arguments."); return 0; } if (!(ci = cs_findchan(argv[0]))) { /* Don't even THINK about asking me to make ChanServ say this - Services should ONLY speak when * spoken to. */ notice_lang(s_OperServ, source, CHAN_X_NOT_REGISTERED, argv[0]); return 0; } /* Hack for additional FOUNDER keyword. */ if (!stricmp(argv[1], "FOUNDER")) { if (!u) return 1; else return is_founder(u, ci); } for (i = 0; levelinfo[i].what >= 0; i++) { if (!stricmp(levelinfo[i].name, argv[1])) { lvl = levelinfo[i].what; break; } } if (lvl < 0) { /* Yes. OperServ telling them to bug ChanServ for help on this one. Don't even THINK about asking me * to make ChanServ say this - Services should ONLY speak when spoken to. */ notice_lang(s_OperServ, source, CHAN_LEVELS_UNKNOWN, argv[1], s_ChanServ); return 0; } if (!u) return 1; /* Validations OK! */ else { /* Show time, run the criteria check! */ return check_access(u, ci, lvl); } } static int crit_abuse(User* source, User* u, char* argv[], ActionParams* params) { if (argv[0]) { if (!stricmp(argv[0], "OPERS")) { params->abuse_opers = 1; return -1; } else { notice_user(s_OperServ, source, "Invalid keyword (expected \002OPERS\002)."); return 0; } } else { notice_user(s_OperServ, source, "Missing required \002OPERS\002 keyword."); return 0; } } static int crit_limit(User* source, User* u, char* argv[], ActionParams* params) { char* tmp; unsigned long l; if (argv[0]) { l = strtoul(argv[0], &tmp, 10); if (*tmp) { notice_user(s_OperServ, source, "Invalid limit (not a number): %s", argv[0]); return 0; } } else { notice_user(s_OperServ, source, "Missing required limit argument."); return 0; } params->limit = l; return -1; } static int crit_reason(User* source, User* u, char* argv[], ActionParams* params) { if (argv[0]) { params->reason = sstrdup(argv[0]); /* MUST FREE THIS IN THE ACTION DONE HANDLER! */ return -1; } else { notice_user(s_OperServ, source, "Zero-length reason not permitted."); return 0; } } static int crit_duration(User* source, User* u, char* argv[], ActionParams* params) { if (!argv[0]) { notice_user(s_OperServ, source, "Missing required parameter."); return 0; } params->duration = dotime(argv[0]); if (params->duration > 0 && params->duration < 60) { notice_lang(s_OperServ, source, BAD_EXPIRY_TIME); return 0; } return -1; } static int crit_depth(User* source, User* u, char* argv[], ActionParams* params) { char* tmp; unsigned long l; if (argv[0]) { l = strtoul(argv[0], &tmp, 10); if (*tmp) { notice_user(s_OperServ, source, "Invalid depth (not a number): %s", argv[0]); return 0; } } else { notice_user(s_OperServ, source, "Missing required depth argument."); return 0; } if (l <= 0) { notice_user(s_OperServ, source, "Invalid depth (must be greater than 0)."); return 0; } params->depth = l; return -1; } static int crit_log(User* source, User* u, char* argv[], ActionParams* params) { params->log = 1; return -1; }