anope- supernets anope source code & configuration |
git clone git://git.acid.vegas/anope.git |
Log | Files | Refs | Archive | README |
os_akill.cpp (13532B)
1 /* OperServ core functions 2 * 3 * (C) 2003-2022 Anope Team 4 * Contact us at team@anope.org 5 * 6 * Please read COPYING and README for further details. 7 * 8 * Based on the original code of Epona by Lara. 9 * Based on the original code of Services by Andy Church. 10 */ 11 12 #include "module.h" 13 14 static ServiceReference<XLineManager> akills("XLineManager", "xlinemanager/sgline"); 15 16 class AkillDelCallback : public NumberList 17 { 18 CommandSource &source; 19 unsigned deleted; 20 Command *cmd; 21 public: 22 AkillDelCallback(CommandSource &_source, const Anope::string &numlist, Command *c) : NumberList(numlist, true), source(_source), deleted(0), cmd(c) 23 { 24 } 25 26 ~AkillDelCallback() 27 { 28 if (!deleted) 29 source.Reply(_("No matching entries on the AKILL list.")); 30 else if (deleted == 1) 31 source.Reply(_("Deleted 1 entry from the AKILL list.")); 32 else 33 source.Reply(_("Deleted %d entries from the AKILL list."), deleted); 34 } 35 36 void HandleNumber(unsigned number) anope_override 37 { 38 if (!number) 39 return; 40 41 XLine *x = akills->GetEntry(number - 1); 42 43 if (!x) 44 return; 45 46 Log(LOG_ADMIN, source, cmd) << "to remove " << x->mask << " from the list"; 47 48 ++deleted; 49 DoDel(source, x); 50 } 51 52 static void DoDel(CommandSource &source, XLine *x) 53 { 54 akills->DelXLine(x); 55 } 56 }; 57 58 class CommandOSAKill : public Command 59 { 60 private: 61 void DoAdd(CommandSource &source, const std::vector<Anope::string> ¶ms) 62 { 63 Anope::string expiry, mask; 64 65 if (params.size() < 2) 66 { 67 this->OnSyntaxError(source, "ADD"); 68 return; 69 } 70 71 spacesepstream sep(params[1]); 72 sep.GetToken(mask); 73 74 if (mask[0] == '+') 75 { 76 expiry = mask; 77 sep.GetToken(mask); 78 } 79 80 time_t expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->GetModule("operserv")->Get<time_t>("autokillexpiry", "30d"); 81 /* If the expiry given does not contain a final letter, it's in days, 82 * said the doc. Ah well. 83 */ 84 if (!expiry.empty() && isdigit(expiry[expiry.length() - 1])) 85 expires *= 86400; 86 /* Do not allow less than a minute expiry time */ 87 if (expires && expires < 60) 88 { 89 source.Reply(BAD_EXPIRY_TIME); 90 return; 91 } 92 else if (expires > 0) 93 expires += Anope::CurTime; 94 95 if (sep.StreamEnd()) 96 { 97 this->OnSyntaxError(source, "ADD"); 98 return; 99 } 100 101 Anope::string reason; 102 if (mask.find('#') != Anope::string::npos) 103 { 104 Anope::string remaining = sep.GetRemaining(); 105 106 size_t co = remaining[0] == ':' ? 0 : remaining.rfind(" :"); 107 if (co == Anope::string::npos) 108 { 109 this->OnSyntaxError(source, "ADD"); 110 return; 111 } 112 113 if (co != 0) 114 ++co; 115 116 reason = remaining.substr(co + 1); 117 mask += " " + remaining.substr(0, co); 118 mask.trim(); 119 } 120 else 121 reason = sep.GetRemaining(); 122 123 if (mask[0] == '/' && mask[mask.length() - 1] == '/') 124 { 125 const Anope::string ®exengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine"); 126 127 if (regexengine.empty()) 128 { 129 source.Reply(_("Regex is disabled.")); 130 return; 131 } 132 133 ServiceReference<RegexProvider> provider("Regex", regexengine); 134 if (!provider) 135 { 136 source.Reply(_("Unable to find regex engine %s."), regexengine.c_str()); 137 return; 138 } 139 140 try 141 { 142 Anope::string stripped_mask = mask.substr(1, mask.length() - 2); 143 delete provider->Compile(stripped_mask); 144 } 145 catch (const RegexException &ex) 146 { 147 source.Reply("%s", ex.GetReason().c_str()); 148 return; 149 } 150 } 151 152 User *targ = User::Find(mask, true); 153 if (targ) 154 mask = "*@" + targ->host; 155 156 if (Config->GetModule("operserv")->Get<bool>("addakiller", "yes") && !source.GetNick().empty()) 157 reason = "[" + source.GetNick() + "] " + reason; 158 159 if (mask.find_first_not_of("/~@.*?") == Anope::string::npos) 160 { 161 source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); 162 return; 163 } 164 else if (mask.find('@') == Anope::string::npos) 165 { 166 source.Reply(BAD_USERHOST_MASK); 167 return; 168 } 169 170 XLine *x = new XLine(mask, source.GetNick(), expires, reason); 171 if (Config->GetModule("operserv")->Get<bool>("akillids")) 172 x->id = XLineManager::GenerateUID(); 173 174 unsigned int affected = 0; 175 for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it) 176 if (akills->Check(it->second, x)) 177 ++affected; 178 float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0; 179 180 if (percent > 95) 181 { 182 source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str()); 183 Log(LOG_ADMIN, source, this) << "tried to akill " << percent << "% of the network (" << affected << " users)"; 184 delete x; 185 return; 186 } 187 188 if (!akills->CanAdd(source, mask, expires, reason)) 189 return; 190 191 EventReturn MOD_RESULT; 192 FOREACH_RESULT(OnAddXLine, MOD_RESULT, (source, x, akills)); 193 if (MOD_RESULT == EVENT_STOP) 194 { 195 delete x; 196 return; 197 } 198 199 akills->AddXLine(x); 200 if (Config->GetModule("operserv")->Get<bool>("akillonadd")) 201 akills->Send(NULL, x); 202 203 source.Reply(_("\002%s\002 added to the AKILL list."), mask.c_str()); 204 205 Log(LOG_ADMIN, source, this) << "on " << mask << " (" << x->reason << "), expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]"; 206 if (Anope::ReadOnly) 207 source.Reply(READ_ONLY_MODE); 208 } 209 210 void DoDel(CommandSource &source, const std::vector<Anope::string> ¶ms) 211 { 212 const Anope::string &mask = params.size() > 1 ? params[1] : ""; 213 214 if (mask.empty()) 215 { 216 this->OnSyntaxError(source, "DEL"); 217 return; 218 } 219 220 if (akills->GetList().empty()) 221 { 222 source.Reply(_("AKILL list is empty.")); 223 return; 224 } 225 226 if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) 227 { 228 AkillDelCallback list(source, mask, this); 229 list.Process(); 230 } 231 else 232 { 233 XLine *x = akills->HasEntry(mask); 234 235 if (!x) 236 { 237 source.Reply(_("\002%s\002 not found on the AKILL list."), mask.c_str()); 238 return; 239 } 240 241 do 242 { 243 FOREACH_MOD(OnDelXLine, (source, x, akills)); 244 245 Log(LOG_ADMIN, source, this) << "to remove " << x->mask << " from the list"; 246 source.Reply(_("\002%s\002 deleted from the AKILL list."), x->mask.c_str()); 247 AkillDelCallback::DoDel(source, x); 248 } 249 while ((x = akills->HasEntry(mask))); 250 251 } 252 253 if (Anope::ReadOnly) 254 source.Reply(READ_ONLY_MODE); 255 256 return; 257 } 258 259 void ProcessList(CommandSource &source, const std::vector<Anope::string> ¶ms, ListFormatter &list) 260 { 261 const Anope::string &mask = params.size() > 1 ? params[1] : ""; 262 263 if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos) 264 { 265 class ListCallback : public NumberList 266 { 267 CommandSource &source; 268 ListFormatter &list; 269 public: 270 ListCallback(CommandSource &_source, ListFormatter &_list, const Anope::string &numstr) : NumberList(numstr, false), source(_source), list(_list) 271 { 272 } 273 274 void HandleNumber(unsigned number) anope_override 275 { 276 if (!number) 277 return; 278 279 const XLine *x = akills->GetEntry(number - 1); 280 281 if (!x) 282 return; 283 284 ListFormatter::ListEntry entry; 285 entry["Number"] = stringify(number); 286 entry["Mask"] = x->mask; 287 entry["Creator"] = x->by; 288 entry["Created"] = Anope::strftime(x->created, NULL, true); 289 entry["Expires"] = Anope::Expires(x->expires, source.nc); 290 entry["ID"] = x->id; 291 entry["Reason"] = x->reason; 292 this->list.AddEntry(entry); 293 } 294 } 295 nl_list(source, list, mask); 296 nl_list.Process(); 297 } 298 else 299 { 300 for (unsigned i = 0, end = akills->GetCount(); i < end; ++i) 301 { 302 const XLine *x = akills->GetEntry(i); 303 304 if (mask.empty() || mask.equals_ci(x->mask) || mask == x->id || Anope::Match(x->mask, mask, false, true)) 305 { 306 ListFormatter::ListEntry entry; 307 entry["Number"] = stringify(i + 1); 308 entry["Mask"] = x->mask; 309 entry["Creator"] = x->by; 310 entry["Created"] = Anope::strftime(x->created, NULL, true); 311 entry["Expires"] = Anope::Expires(x->expires, source.nc); 312 entry["ID"] = x->id; 313 entry["Reason"] = x->reason; 314 list.AddEntry(entry); 315 } 316 } 317 } 318 319 if (list.IsEmpty()) 320 source.Reply(_("No matching entries on the AKILL list.")); 321 else 322 { 323 source.Reply(_("Current AKILL list:")); 324 325 std::vector<Anope::string> replies; 326 list.Process(replies); 327 328 for (unsigned i = 0; i < replies.size(); ++i) 329 source.Reply(replies[i]); 330 331 source.Reply(_("End of AKILL list.")); 332 } 333 } 334 335 void DoList(CommandSource &source, const std::vector<Anope::string> ¶ms) 336 { 337 if (akills->GetList().empty()) 338 { 339 source.Reply(_("AKILL list is empty.")); 340 return; 341 } 342 343 ListFormatter list(source.GetAccount()); 344 list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Reason")); 345 346 this->ProcessList(source, params, list); 347 } 348 349 void DoView(CommandSource &source, const std::vector<Anope::string> ¶ms) 350 { 351 if (akills->GetList().empty()) 352 { 353 source.Reply(_("AKILL list is empty.")); 354 return; 355 } 356 357 ListFormatter list(source.GetAccount()); 358 list.AddColumn(_("Number")).AddColumn(_("Mask")).AddColumn(_("Creator")).AddColumn(_("Created")).AddColumn(_("Expires")); 359 if (Config->GetModule("operserv")->Get<bool>("akillids")) 360 list.AddColumn(_("ID")); 361 list.AddColumn(_("Reason")); 362 363 this->ProcessList(source, params, list); 364 } 365 366 void DoClear(CommandSource &source) 367 { 368 369 for (unsigned i = akills->GetCount(); i > 0; --i) 370 { 371 XLine *x = akills->GetEntry(i - 1); 372 FOREACH_MOD(OnDelXLine, (source, x, akills)); 373 akills->DelXLine(x); 374 } 375 376 Log(LOG_ADMIN, source, this) << "to CLEAR the list"; 377 source.Reply(_("The AKILL list has been cleared.")); 378 379 if (Anope::ReadOnly) 380 source.Reply(READ_ONLY_MODE); 381 } 382 public: 383 CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 2) 384 { 385 this->SetDesc(_("Manipulate the AKILL list")); 386 this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037")); 387 this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}")); 388 this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]")); 389 this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]")); 390 this->SetSyntax("CLEAR"); 391 } 392 393 void Execute(CommandSource &source, const std::vector<Anope::string> ¶ms) anope_override 394 { 395 const Anope::string &cmd = params[0]; 396 397 if (!akills) 398 return; 399 400 if (cmd.equals_ci("ADD")) 401 return this->DoAdd(source, params); 402 else if (cmd.equals_ci("DEL")) 403 return this->DoDel(source, params); 404 else if (cmd.equals_ci("LIST")) 405 return this->DoList(source, params); 406 else if (cmd.equals_ci("VIEW")) 407 return this->DoView(source, params); 408 else if (cmd.equals_ci("CLEAR")) 409 return this->DoClear(source); 410 else 411 this->OnSyntaxError(source, ""); 412 413 return; 414 } 415 416 bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override 417 { 418 this->SendSyntax(source); 419 source.Reply(" "); 420 source.Reply(_("Allows Services Operators to manipulate the AKILL list. If\n" 421 "a user matching an AKILL mask attempts to connect, Services\n" 422 "will issue a KILL for that user and, on supported server\n" 423 "types, will instruct all servers to add a ban for the mask\n" 424 "which the user matched.\n" 425 " \n" 426 "\002AKILL ADD\002 adds the given mask to the AKILL\n" 427 "list for the given reason, which \002must\002 be given.\n" 428 "Mask should be in the format of nick!user@host#real name,\n" 429 "though all that is required is user@host. If a real name is specified,\n" 430 "the reason must be prepended with a :.\n" 431 "\037expiry\037 is specified as an integer followed by one of \037d\037\n" 432 "(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as\n" 433 "\0371h30m\037) are not permitted. If a unit specifier is not\n" 434 "included, the default is days (so \037+30\037 by itself means 30\n" 435 "days). To add an AKILL which does not expire, use \037+0\037. If the\n" 436 "usermask to be added starts with a \037+\037, an expiry time must\n" 437 "be given, even if it is the same as the default. The\n" 438 "current AKILL default expiry time can be found with the\n" 439 "\002STATS AKILL\002 command.")); 440 const Anope::string ®exengine = Config->GetBlock("options")->Get<const Anope::string>("regexengine"); 441 if (!regexengine.empty()) 442 { 443 source.Reply(" "); 444 source.Reply(_("Regex matches are also supported using the %s engine.\n" 445 "Enclose your mask in // if this is desired."), regexengine.c_str()); 446 } 447 source.Reply(_( 448 " \n" 449 "The \002AKILL DEL\002 command removes the given mask from the\n" 450 "AKILL list if it is present. If a list of entry numbers is\n" 451 "given, those entries are deleted. (See the example for LIST\n" 452 "below.)\n" 453 " \n" 454 "The \002AKILL LIST\002 command displays the AKILL list.\n" 455 "If a wildcard mask is given, only those entries matching the\n" 456 "mask are displayed. If a list of entry numbers is given,\n" 457 "only those entries are shown; for example:\n" 458 " \002AKILL LIST 2-5,7-9\002\n" 459 " Lists AKILL entries numbered 2 through 5 and 7\n" 460 " through 9.\n" 461 " \n" 462 "\002AKILL VIEW\002 is a more verbose version of \002AKILL LIST\002, and\n" 463 "will show who added an AKILL, the date it was added, and when\n" 464 "it expires, as well as the user@host/ip mask and reason.\n" 465 " \n" 466 "\002AKILL CLEAR\002 clears all entries of the AKILL list.")); 467 return true; 468 } 469 }; 470 471 class OSAKill : public Module 472 { 473 CommandOSAKill commandosakill; 474 475 public: 476 OSAKill(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR), 477 commandosakill(this) 478 { 479 480 } 481 }; 482 483 MODULE_INIT(OSAKill)