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