unrealircd

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

restrict-commands.c (11926B)

      1 /*
      2  * Restrict specific commands unless certain conditions have been met
      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 	"restrict-commands",
     24 	"1.0.2",
     25 	"Restrict specific commands unless certain conditions have been met",
     26 	"UnrealIRCd Team",
     27 	"unrealircd-6",
     28 };
     29 
     30 typedef struct RestrictedCommand RestrictedCommand;
     31 struct RestrictedCommand {
     32 	RestrictedCommand *prev, *next;
     33 	char *cmd;
     34 	char *conftag;
     35 	SecurityGroup *except;
     36 };
     37 
     38 typedef struct {
     39 	char *conftag;
     40 	char *cmd;
     41 } CmdMap;
     42 
     43 // Forward declarations
     44 const char *find_cmd_byconftag(const char *conftag);
     45 RestrictedCommand *find_restrictions_bycmd(const char *cmd);
     46 RestrictedCommand *find_restrictions_byconftag(const char *conftag);
     47 int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     48 int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
     49 int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
     50 int rcmd_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
     51 int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag);
     52 CMD_OVERRIDE_FUNC(rcmd_override);
     53 
     54 // Globals
     55 static ModuleInfo ModInf;
     56 RestrictedCommand *RestrictedCommandList = NULL;
     57 CmdMap conf_cmdmaps[] = {
     58 	// These are special cases in which we can't override the command, so they are handled through hooks instead
     59 	{ "channel-message", "PRIVMSG" },
     60 	{ "channel-notice", "NOTICE" },
     61 	{ "private-message", "PRIVMSG" },
     62 	{ "private-notice", "NOTICE" },
     63 	{ NULL, NULL, }, // REQUIRED for the loop to properly work
     64 };
     65 
     66 MOD_TEST()
     67 {
     68 	memcpy(&ModInf, modinfo, modinfo->size);
     69 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, rcmd_configtest);
     70 	return MOD_SUCCESS;
     71 }
     72 
     73 MOD_INIT()
     74 {
     75 	MARK_AS_OFFICIAL_MODULE(modinfo);
     76 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, rcmd_configrun);
     77 
     78 	// Due to the nature of PRIVMSG/NOTICE we're gonna need to hook into PRE_* stuff instead of using command overrides
     79 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, -1000000, rcmd_can_send_to_channel);
     80 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, -1000000, rcmd_can_send_to_user);
     81 	return MOD_SUCCESS;
     82 }
     83 
     84 MOD_LOAD()
     85 {
     86 	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR)
     87 	{
     88 		config_error("A critical error occurred when loading module %s: %s", MOD_HEADER.name, ModuleGetErrorStr(modinfo->handle));
     89 		return MOD_FAILED;
     90 	}
     91 	return MOD_SUCCESS;
     92 }
     93 
     94 MOD_UNLOAD()
     95 {
     96 	RestrictedCommand *rcmd, *next;
     97 	for (rcmd = RestrictedCommandList; rcmd; rcmd = next)
     98 	{
     99 		next = rcmd->next;
    100 		safe_free(rcmd->conftag);
    101 		safe_free(rcmd->cmd);
    102 		free_security_group(rcmd->except);
    103 		DelListItem(rcmd, RestrictedCommandList);
    104 		safe_free(rcmd);
    105 	}
    106 	RestrictedCommandList = NULL;
    107 	return MOD_SUCCESS;
    108 }
    109 
    110 const char *find_cmd_byconftag(const char *conftag) {
    111 	CmdMap *cmap;
    112 	for (cmap = conf_cmdmaps; cmap->conftag; cmap++)
    113 	{
    114 		if (!strcmp(cmap->conftag, conftag))
    115 			return cmap->cmd;
    116 	}
    117 	return NULL;
    118 }
    119 
    120 RestrictedCommand *find_restrictions_bycmd(const char *cmd) {
    121 	RestrictedCommand *rcmd;
    122 	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
    123 	{
    124 		if (!strcasecmp(rcmd->cmd, cmd))
    125 			return rcmd;
    126 	}
    127 	return NULL;
    128 }
    129 
    130 RestrictedCommand *find_restrictions_byconftag(const char *conftag) {
    131 	RestrictedCommand *rcmd;
    132 	for (rcmd = RestrictedCommandList; rcmd; rcmd = rcmd->next)
    133 	{
    134 		if (rcmd->conftag && !strcmp(rcmd->conftag, conftag))
    135 			return rcmd;
    136 	}
    137 	return NULL;
    138 }
    139 
    140 int rcmd_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    141 {
    142 	int errors = 0;
    143 	int warn_disable = 0;
    144 	ConfigEntry *cep, *cep2;
    145 
    146 	// We are only interested in set::restrict-commands
    147 	if (type != CONFIG_SET)
    148 		return 0;
    149 
    150 	if (!ce || strcmp(ce->name, "restrict-commands"))
    151 		return 0;
    152 
    153 	for (cep = ce->items; cep; cep = cep->next)
    154 	{
    155 		for (cep2 = cep->items; cep2; cep2 = cep2->next)
    156 		{
    157 			if (!strcmp(cep2->name, "disable"))
    158 			{
    159 				config_warn("%s:%i: set::restrict-commands::%s: the 'disable' option has been removed.",
    160 				            cep2->file->filename, cep2->line_number, cep->name);
    161 				if (!warn_disable)
    162 				{
    163 					config_warn("Simply remove 'disable yes;' from the configuration file and "
    164 				                   "it will have the same effect without it (will disable the command).");
    165 					warn_disable = 1;
    166 				}
    167 				continue;
    168 			}
    169 
    170 			if (!strcmp(cep2->name, "except"))
    171 			{
    172 				test_match_block(cf, cep2, &errors);
    173 				continue;
    174 			}
    175 
    176 			if (!cep2->value)
    177 			{
    178 				config_error("%s:%i: blank set::restrict-commands::%s:%s without value", cep2->file->filename, cep2->line_number, cep->name, cep2->name);
    179 				errors++;
    180 				continue;
    181 			}
    182 
    183 			if (!strcmp(cep2->name, "connect-delay"))
    184 			{
    185 				long v = config_checkval(cep2->value, CFG_TIME);
    186 				if ((v < 1) || (v > 3600))
    187 				{
    188 					config_error("%s:%i: set::restrict-commands::%s::connect-delay should be in range 1-3600", cep2->file->filename, cep2->line_number, cep->name);
    189 					errors++;
    190 				}
    191 				continue;
    192 			}
    193 
    194 			if (!strcmp(cep2->name, "exempt-identified"))
    195 				continue;
    196 
    197 			if (!strcmp(cep2->name, "exempt-webirc"))
    198 				continue;
    199 
    200 			if (!strcmp(cep2->name, "exempt-tls"))
    201 				continue;
    202 
    203 			if (!strcmp(cep2->name, "exempt-reputation-score"))
    204 			{
    205 				int v = atoi(cep2->value);
    206 				if (v <= 0)
    207 				{
    208 					config_error("%s:%i: set::restrict-commands::%s::exempt-reputation-score must be greater than 0", cep2->file->filename, cep2->line_number, cep->name);
    209 					errors++;
    210 				}
    211 				continue;
    212 			}
    213 
    214 			config_error("%s:%i: unknown directive set::restrict-commands::%s::%s", cep2->file->filename, cep2->line_number, cep->name, cep2->name);
    215 			errors++;
    216 		}
    217 	}
    218 
    219 	*errs = errors;
    220 	return errors ? -1 : 1;
    221 }
    222 
    223 int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
    224 {
    225 	ConfigEntry *cep, *cep2;
    226 	const char *cmd, *conftag;
    227 	RestrictedCommand *rcmd;
    228 
    229 	// We are only interested in set::restrict-commands
    230 	if (type != CONFIG_SET)
    231 		return 0;
    232 
    233 	if (!ce || strcmp(ce->name, "restrict-commands"))
    234 		return 0;
    235 
    236 	for (cep = ce->items; cep; cep = cep->next)
    237 	{
    238 		// May need to switch some stuff around for special cases where the config directive doesn't match the actual command
    239 		conftag = NULL;
    240 		if ((cmd = find_cmd_byconftag(cep->name)))
    241 			conftag = cep->name;
    242 		else
    243 			cmd = cep->name;
    244 
    245 		// Try to add override before even allocating the struct so we can bail early
    246 		// Also don't override anything from the conf_cmdmaps[] list because those are handled through hooks instead
    247 		if (!conftag)
    248 		{
    249 			// Let's hope nobody tries to unload the module for PRIVMSG/NOTICE :^)
    250 			if (!CommandExists(cmd))
    251 			{
    252 				config_warn("[restrict-commands] Command '%s' does not exist. Did you mistype? Or is the module providing it not loaded?", cmd);
    253 				continue;
    254 			}
    255 			if (find_restrictions_bycmd(cmd))
    256 			{
    257 				config_warn("[restrict-commands] Multiple set::restrict-commands items for command '%s'. "
    258 				            "Only one config block will be effective.",
    259 				            cmd);
    260 				continue;
    261 			}
    262 			if (!CommandOverrideAdd(ModInf.handle, cmd, 0, rcmd_override))
    263 			{
    264 				config_warn("[restrict-commands] Failed to add override for '%s' (NO RESTRICTIONS APPLY)", cmd);
    265 				continue;
    266 			}
    267 		}
    268 
    269 		rcmd = safe_alloc(sizeof(RestrictedCommand));
    270 		safe_strdup(rcmd->cmd, cmd);
    271 		safe_strdup(rcmd->conftag, conftag);
    272 		rcmd->except = safe_alloc(sizeof(SecurityGroup));
    273 
    274 		for (cep2 = cep->items; cep2; cep2 = cep2->next)
    275 		{
    276 			if (!strcmp(cep2->name, "except"))
    277 			{
    278 				conf_match_block(cf, cep2, &rcmd->except);
    279 				continue;
    280 			}
    281 
    282 			if (!cep2->value)
    283 				continue;
    284 
    285 			if (!strcmp(cep2->name, "connect-delay"))
    286 			{
    287 				rcmd->except->connect_time = config_checkval(cep2->value, CFG_TIME);
    288 				continue;
    289 			}
    290 
    291 			if (!strcmp(cep2->name, "exempt-identified"))
    292 			{
    293 				rcmd->except->identified = config_checkval(cep2->value, CFG_YESNO);
    294 				continue;
    295 			}
    296 			
    297 			if (!strcmp(cep2->name, "exempt-webirc"))
    298 			{
    299 				rcmd->except->webirc = config_checkval(cep2->value, CFG_YESNO);
    300 				continue;
    301 			}
    302 
    303 			if (!strcmp(cep2->name, "exempt-tls"))
    304 			{
    305 				rcmd->except->tls = config_checkval(cep2->value, CFG_YESNO);
    306 				continue;
    307 			}
    308 
    309 			if (!strcmp(cep2->name, "exempt-reputation-score"))
    310 			{
    311 				rcmd->except->reputation_score = atoi(cep2->value);
    312 				continue;
    313 			}
    314 		}
    315 		AddListItem(rcmd, RestrictedCommandList);
    316 	}
    317 
    318 	return 1;
    319 }
    320 
    321 int rcmd_canbypass(Client *client, RestrictedCommand *rcmd)
    322 {
    323 	if (!client || !rcmd)
    324 		return 1;
    325 	if (user_allowed_by_security_group(client, rcmd->except))
    326 		return 1;
    327 	return 0;
    328 }
    329 
    330 int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
    331 {
    332 	if (rcmd_block_message(client, *msg, sendtype, errmsg, "channel", (sendtype == SEND_TYPE_NOTICE ? "channel-notice" : "channel-message")))
    333 		return HOOK_DENY;
    334 
    335 	return HOOK_CONTINUE;
    336 }
    337 
    338 int rcmd_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
    339 {
    340 	// Need a few extra exceptions for user messages only =]
    341 	if ((client == target) || IsULine(target))
    342 		return HOOK_CONTINUE; /* bypass/exempt */
    343 
    344 	if (rcmd_block_message(client, *text, sendtype, errmsg, "user", (sendtype == SEND_TYPE_NOTICE ? "private-notice" : "private-message")))
    345 		return HOOK_DENY;
    346 
    347 	return HOOK_CONTINUE;
    348 }
    349 
    350 int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag)
    351 {
    352 	RestrictedCommand *rcmd;
    353 	static char errbuf[256];
    354 
    355 	// Let's allow non-local users, opers and U:Lines early =]
    356 	if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client))
    357 		return 0;
    358 
    359 	rcmd = find_restrictions_byconftag(conftag);
    360 	if (rcmd && !rcmd_canbypass(client, rcmd))
    361 	{
    362 		int notice = (sendtype == SEND_TYPE_NOTICE ? 1 : 0); // temporary hack FIXME !!!
    363 		if (rcmd->except->connect_time)
    364 		{
    365 			ircsnprintf(errbuf, sizeof(errbuf),
    366 				    "You cannot send %ss to %ss until you've been connected for %ld seconds or more",
    367 				    (notice ? "notice" : "message"), display, rcmd->except->connect_time);
    368 		} else {
    369 			ircsnprintf(errbuf, sizeof(errbuf),
    370 				    "Sending of %ss to %ss been disabled by the network administrators",
    371 				    (notice ? "notice" : "message"), display);
    372 		}
    373 		*errmsg = errbuf;
    374 		return 1;
    375 	}
    376 
    377 	// No restrictions apply, process command as normal =]
    378 	return 0;
    379 }
    380 
    381 CMD_OVERRIDE_FUNC(rcmd_override)
    382 {
    383 	RestrictedCommand *rcmd;
    384 
    385 	if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client))
    386 	{
    387 		CALL_NEXT_COMMAND_OVERRIDE();
    388 		return;
    389 	}
    390 
    391 	rcmd = find_restrictions_bycmd(ovr->command->cmd);
    392 	if (rcmd && !rcmd_canbypass(client, rcmd))
    393 	{
    394 		if (rcmd->except->connect_time)
    395 		{
    396 			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
    397 			               "%s :You must be connected for at least %ld seconds before you can use this command",
    398 			               ovr->command->cmd, rcmd->except->connect_time);
    399 		} else {
    400 			sendnumericfmt(client, ERR_UNKNOWNCOMMAND,
    401 			               "%s :This command is disabled by the network administrator",
    402 			               ovr->command->cmd);
    403 		}
    404 		return;
    405 	}
    406 
    407 	// No restrictions apply, process command as normal =]
    408 	CALL_NEXT_COMMAND_OVERRIDE();
    409 }