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 }