unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

rmtkl.c (8617B)

      1 /*
      2  * Easily remove *-Lines in bulk
      3  * (C) Copyright 2019 Gottem and the UnrealIRCd team
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 1, or (at your option)
      8  * any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program; if not, write to the Free Software
     17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     18  */
     19 
     20 #include "unrealircd.h"
     21 
     22 ModuleHeader MOD_HEADER = {
     23 	"rmtkl",
     24 	"1.4",
     25 	"Adds /rmtkl command to easily remove *-Lines in bulk",
     26 	"Gottem and the UnrealIRCd Team",
     27 	"unrealircd-6",
     28 };
     29 
     30 #define IsParam(x) (parc > (x) && !BadPtr(parv[(x)]))
     31 #define IsNotParam(x) (parc <= (x) || BadPtr(parv[(x)]))
     32 
     33 typedef struct {
     34 	int type;
     35 	char flag;
     36 	char *txt;
     37 	char *operpriv;
     38 } TKLType;
     39 
     40 static void dump_str(Client *client, const char **buf);
     41 static TKLType *find_TKLType_by_flag(char flag);
     42 void rmtkl_check_options(const char *param, int *skipperm, int *silent);
     43 int rmtkl_tryremove(Client *client, TKLType *tkltype, TKL *tkl, const char *uhmask, const char *commentmask, int skipperm, int silent);
     44 CMD_FUNC(rmtkl);
     45 
     46 TKLType tkl_types[] = {
     47 	{ TKL_KILL, 'k', "K-Line", "server-ban:kline:remove" },
     48 	{ TKL_ZAP, 'z',	"Z-Line", "server-ban:zline:local:remove" },
     49 	{ TKL_KILL | TKL_GLOBAL, 'G', "G-Line", "server-ban:gline:remove" },
     50 	{ TKL_ZAP | TKL_GLOBAL, 'Z', "Global Z-Line", "server-ban:zline:global:remove" },
     51 	{ TKL_SHUN | TKL_GLOBAL, 's', "Shun", "server-ban:shun:remove" },
     52 //	{ TKL_SPAMF | TKL_GLOBAL, 'F', "Global Spamfilter", "server-ban:spamfilter:remove" }, TODO: re-add spamfilter support
     53 	{ 0, 0, "Unknown *-Line", 0 },
     54 };
     55 
     56 static const char *rmtkl_help[] = {
     57 	"*** \002Help on /rmtkl\002 *** ",
     58 	"Removes all TKLs matching the given conditions from the local server, or the entire",
     59 	"network if it's a global-type ban.",
     60 	"Syntax:",
     61 	"    \002/rmtkl\002 \037user@host\037 \037type\037 [\037comment\037] [\037-skipperm\037] [\037-silent\037]",
     62 	"The \037user@host\037 field is a wildcard mask to match the target of a ban.",
     63 	"The \037type\037 field may contain any number of the following characters:",
     64 	"    k, z, G, Z, s, F and *",
     65 	"    These correspond to (local) K-Line, (local) Z-Line, G-Line, Global Z-Line, (global) Shun and (global) Spamfilter",
     66 	"    (asterisk includes every type besides F)",
     67 	"The \037comment\037 field is also a wildcard mask to match the reason text of a ban. If specified, it must always",
     68 	"come \037before\037 the options starting with \002-\002.",
     69 	"Examples:",
     70 	"    - \002/rmtkl * *\002",
     71 	"        [remove \037all\037 supported TKLs except spamfilters]",
     72 	"    - \002/rmtkl *@*.mx GZ\002 * -skipperm",
     73 	"        [remove all Mexican G/Z-Lines while skipping over permanent ones]",
     74 /*	"    - \002/rmtkl * * *Zombie*\002",
     75 	"        [remove all non-spamfilter bans having \037Zombie\037 in the reason field]", TODO: re-add spamfilter support  */
     76 	"*** \002End of help\002 ***",
     77 	NULL
     78 };
     79 
     80 MOD_INIT()
     81 {
     82 	MARK_AS_OFFICIAL_MODULE(modinfo);
     83 	if (CommandExists("RMTKL"))
     84 	{
     85 		config_error("Command RMTKL already exists");
     86 		return MOD_FAILED;
     87 	}
     88 	CommandAdd(modinfo->handle, "RMTKL", rmtkl, 5, CMD_USER);
     89 	return MOD_SUCCESS;
     90 }
     91 
     92 MOD_LOAD()
     93 {
     94 	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
     95 	{
     96 		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
     97 		return MOD_FAILED;
     98 	}
     99 	return MOD_SUCCESS;
    100 }
    101 
    102 MOD_UNLOAD()
    103 {
    104 	return MOD_SUCCESS;
    105 }
    106 
    107 static void dump_str(Client *client, const char **buf)
    108 {
    109 	if (!MyUser(client))
    110 		return;
    111 
    112 	// Using sendto_one() instead of sendnumericfmt() because the latter strips indentation and stuff ;]
    113 	for (; *buf != NULL; buf++)
    114 		sendto_one(client, NULL, ":%s %03d %s :%s", me.name, RPL_TEXT, client->name, *buf);
    115 
    116 	// Let user take 8 seconds to read it
    117 	add_fake_lag(client, 8000);
    118 }
    119 
    120 static TKLType *find_TKLType_by_flag(char flag)
    121 {
    122 	TKLType *t;
    123 	for (t = tkl_types; t->type; t++)
    124 		if (t->flag == flag)
    125 			break;
    126 	return t;
    127 }
    128 
    129 void rmtkl_check_options(const char *param, int *skipperm, int *silent) {
    130 	if (!strcasecmp("-skipperm", param))
    131 		*skipperm = 1;
    132 	if (!strcasecmp("-silent", param))
    133 		*silent = 1;
    134 }
    135 
    136 int rmtkl_tryremove(Client *client, TKLType *tkltype, TKL *tkl, const char *uhmask, const char *commentmask, int skipperm, int silent)
    137 {
    138 	if (tkl->type != tkltype->type)
    139 		return 0;
    140 
    141 	// Let's not touch Q-Lines
    142 	if (tkl->type & TKL_NAME)
    143 		return 0;
    144 
    145 	/* Don't touch TKL's that were added through config */
    146 	if (tkl->flags & TKL_FLAG_CONFIG)
    147 		return 0;
    148 
    149 	if (TKLIsSpamfilter(tkl))
    150 	{
    151 #if 0
    152 //FIXME: re-add spamfilter support
    153 		// Is a spamfilter added through IRC, we can remove this if the "user" mask matches the reason
    154 		if (!match_simple(uhmask, tkl->reason))
    155 			return 0;
    156 #endif
    157 	} else
    158 	if (TKLIsServerBan(tkl))
    159 	{
    160 		if (!match_simple(uhmask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
    161 			return 0;
    162 
    163 		if (commentmask && !match_simple(commentmask, tkl->ptr.serverban->reason))
    164 			return 0;
    165 	} else
    166 		return 0;
    167 
    168 	if (skipperm && tkl->expire_at == 0)
    169 		return 0;
    170 
    171 	if (!silent)
    172 		sendnotice_tkl_del(client->name, tkl);
    173 
    174 	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
    175 
    176 	if (tkl->type & TKL_SHUN)
    177 		tkl_check_local_remove_shun(tkl);
    178 	tkl_del_line(tkl);
    179 	return 1;
    180 }
    181 
    182 CMD_FUNC(rmtkl)
    183 {
    184 	TKL *tkl, *next;
    185 	TKLType *tkltype;
    186 	const char *types, *uhmask, *commentmask, *p;
    187 	char tklchar;
    188 	int tklindex, tklindex2, skipperm, silent;
    189 	unsigned int count;
    190 	char broadcast[BUFSIZE];
    191 
    192 	if (!IsULine(client) && !IsOper(client))
    193 	{
    194 		sendnumeric(client, ERR_NOPRIVILEGES);
    195 		return;
    196 	}
    197 
    198 	if (IsNotParam(1))
    199 	{
    200 		dump_str(client, rmtkl_help);
    201 		return;
    202 	}
    203 
    204 	if (IsNotParam(2))
    205 	{
    206 		sendnotice(client, "Not enough parameters. Type /RMTKL for help.");
    207 		return;
    208 	}
    209 
    210 	uhmask = parv[1];
    211 	types = parv[2];
    212 	commentmask = NULL;
    213 	skipperm = 0;
    214 	silent = 0;
    215 	count = 0;
    216 	snprintf(broadcast, sizeof(broadcast), ":%s RMTKL %s %s", client->name, types, uhmask);
    217 
    218 	// Check for optionals
    219 	if (IsParam(3))
    220 	{
    221 		// Comment mask, if specified, always goes third
    222 		if (*parv[3] != '-')
    223 			commentmask = parv[3];
    224 		else
    225 			rmtkl_check_options(parv[3], &skipperm, &silent);
    226 		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[3]);
    227 	}
    228 	if (IsParam(4))
    229 	{
    230 		rmtkl_check_options(parv[4], &skipperm, &silent);
    231 		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[4]);
    232 	}
    233 	if (IsParam(5))
    234 	{
    235 		rmtkl_check_options(parv[5], &skipperm, &silent);
    236 		ircsnprintf(broadcast, sizeof(broadcast), "%s %s", broadcast, parv[5]);
    237 	}
    238 
    239 	// Wildcard resolves to everything but 'F', since spamfilters are a bit special
    240 	if (strchr(types, '*'))
    241 		types = "kzGZs";
    242 
    243 	// Make sure the oper actually has the privileges to remove the *-Lines he wants
    244 	if (!IsULine(client))
    245 	{
    246 		for (p = types; *p; p++)
    247 		{
    248 			tkltype = find_TKLType_by_flag(*p);
    249 			if (!tkltype->type)
    250 				continue;
    251 
    252 			if (!ValidatePermissionsForPath(tkltype->operpriv, client, NULL, NULL, NULL))
    253 			{
    254 				sendnumeric(client, ERR_NOPRIVILEGES);
    255 				return;
    256 			}
    257 		}
    258 	}
    259 
    260 	// Broadcast the command to other servers *before* we proceed with removal
    261 	sendto_server(NULL, 0, 0, NULL, "%s", broadcast);
    262 
    263 	// Loop over all supported types
    264 	for (tkltype = tkl_types; tkltype->type; tkltype++) {
    265 		if (!strchr(types, tkltype->flag))
    266 			continue;
    267 
    268 		// Loop over all TKL entries, first try the ones in the hash table
    269 		tklchar = tkl_typetochar(tkltype->type);
    270 		tklindex = tkl_ip_hash_type(tklchar);
    271 		if (tklindex >= 0)
    272 		{
    273 			for (tklindex2 = 0; tklindex2 < TKLIPHASHLEN2; tklindex2++)
    274 			{
    275 				for (tkl = tklines_ip_hash[tklindex][tklindex2]; tkl; tkl = next)
    276 				{
    277 					next = tkl->next;
    278 					count += rmtkl_tryremove(client, tkltype, tkl, uhmask, commentmask, skipperm, silent);
    279 				}
    280 			}
    281 		}
    282 
    283 		// Then the regular *-Lines (not an else because certain TKLs might have a hash as well as a plain linked list)
    284 		tklindex = tkl_hash(tklchar);
    285 		for (tkl = tklines[tklindex]; tkl; tkl = next)
    286 		{
    287 			next = tkl->next;
    288 			count += rmtkl_tryremove(client, tkltype, tkl, uhmask, commentmask, skipperm, silent);
    289 		}
    290 	}
    291 
    292 	unreal_log(ULOG_INFO, "tkl", "RMTKL_COMMAND", client,
    293 	           "[rmtkl] $client removed $tkl_removed_count TKLine(s) using /RMTKL",
    294 	           log_data_integer("tkl_removed_count", count));
    295 }