os_sxline.cpp

Go to the documentation of this file.
00001 /* OperServ core functions
00002  *
00003  * (C) 2003-2013 Anope Team
00004  * Contact us at team@anope.org
00005  *
00006  * Please read COPYING and README for further details.
00007  *
00008  * Based on the original code of Epona by Lara.
00009  * Based on the original code of Services by Andy Church.
00010  */
00011 
00012 /*************************************************************************/
00013 
00014 #include "module.h"
00015 
00016 class SXLineDelCallback : public NumberList
00017 {
00018         XLineManager *xlm;
00019         Command *command;
00020         CommandSource &source;
00021         unsigned deleted;
00022  public:
00023         SXLineDelCallback(XLineManager *x, Command *c, CommandSource &_source, const Anope::string &numlist) : NumberList(numlist, true), xlm(x), command(c), source(_source), deleted(0)
00024         {
00025         }
00026 
00027         ~SXLineDelCallback()
00028         {
00029                 if (!deleted)
00030                         source.Reply(_("No matching entries on the %s list."), this->command->name.c_str());
00031                 else if (deleted == 1)
00032                         source.Reply(_("Deleted 1 entry from the %s list."), this->command->name.c_str());
00033                 else
00034                         source.Reply(_("Deleted %d entries from the %s list."), deleted, this->command->name.c_str());
00035         }
00036 
00037         void HandleNumber(unsigned number) anope_override
00038         {
00039                 if (!number)
00040                         return;
00041 
00042                 XLine *x = this->xlm->GetEntry(number - 1);
00043 
00044                 if (!x)
00045                         return;
00046 
00047                 ++deleted;
00048                 DoDel(this->xlm, source, x);
00049         }
00050 
00051         static void DoDel(XLineManager *xlm, CommandSource &source, XLine *x)
00052         {
00053                 xlm->DelXLine(x);
00054         }
00055 };
00056 
00057 class CommandOSSXLineBase : public Command
00058 {
00059  private:
00060         virtual XLineManager* xlm() = 0;
00061 
00062         virtual void OnAdd(CommandSource &source, const std::vector<Anope::string> &params) = 0;
00063 
00064         void OnDel(CommandSource &source, const std::vector<Anope::string> &params)
00065         {
00066 
00067                 if (!this->xlm() || this->xlm()->GetList().empty())
00068                 {
00069                         source.Reply(_("%s list is empty."), source.command.c_str());
00070                         return;
00071                 }
00072 
00073                 const Anope::string &mask = params.size() > 1 ? params[1] : "";
00074 
00075                 if (mask.empty())
00076                 {
00077                         this->OnSyntaxError(source, "DEL");
00078                         return;
00079                 }
00080 
00081                 if (isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
00082                 {
00083                         SXLineDelCallback list(this->xlm(), this, source, mask);
00084                         list.Process();
00085                 }
00086                 else
00087                 {
00088                         XLine *x = this->xlm()->HasEntry(mask);
00089 
00090                         if (!x)
00091                         {
00092                                 source.Reply(_("\002%s\002 not found on the %s list."), mask.c_str(), source.command.c_str());
00093                                 return;
00094                         }
00095 
00096                         FOREACH_MOD(I_OnDelXLine, OnDelXLine(source, x, this->xlm()));
00097 
00098                         SXLineDelCallback::DoDel(this->xlm(), source, x);
00099                         source.Reply(_("\002%s\002 deleted from the %s list."), mask.c_str(), source.command.c_str());
00100                 }
00101 
00102                 if (Anope::ReadOnly)
00103                         source.Reply(READ_ONLY_MODE);
00104 
00105                 return;
00106         }
00107 
00108         void ProcessList(CommandSource &source, const std::vector<Anope::string> &params, ListFormatter &list)
00109         {
00110                 if (!this->xlm() || this->xlm()->GetList().empty())
00111                 {
00112                         source.Reply(_("%s list is empty."), source.command.c_str());
00113                         return;
00114                 }
00115 
00116                 const Anope::string &mask = params.size() > 1 ? params[1] : "";
00117 
00118                 if (!mask.empty() && isdigit(mask[0]) && mask.find_first_not_of("1234567890,-") == Anope::string::npos)
00119                 {
00120                         class SXLineListCallback : public NumberList
00121                         {
00122                                 XLineManager *xlm;
00123                                 ListFormatter &list;
00124                          public:
00125                                 SXLineListCallback(XLineManager *x, ListFormatter &_list, const Anope::string &numlist) : NumberList(numlist, false), xlm(x), list(_list)
00126                                 {
00127                                 }
00128 
00129                                 void HandleNumber(unsigned number) anope_override
00130                                 {
00131                                         if (!number)
00132                                                 return;
00133 
00134                                         const XLine *x = this->xlm->GetEntry(number - 1);
00135 
00136                                         if (!x)
00137                                                 return;
00138 
00139                                         ListFormatter::ListEntry entry;
00140                                         entry["Number"] = stringify(number);
00141                                         entry["Mask"] = x->mask;
00142                                         entry["By"] = x->by;
00143                                         entry["Created"] = Anope::strftime(x->created, NULL, true);
00144                                         entry["Expires"] = Anope::Expires(x->expires);
00145                                         entry["Reason"] = x->reason;
00146                                         list.AddEntry(entry);
00147                                 }
00148                         }
00149                         sl_list(this->xlm(), list, mask);
00150                         sl_list.Process();
00151                 }
00152                 else
00153                 {
00154                         for (unsigned i = 0, end = this->xlm()->GetCount(); i < end; ++i)
00155                         {
00156                                 const XLine *x = this->xlm()->GetEntry(i);
00157 
00158                                 if (mask.empty() || mask.equals_ci(x->mask) || mask == x->id || Anope::Match(x->mask, mask, false, true))
00159                                 {
00160                                         ListFormatter::ListEntry entry;
00161                                         entry["Number"] = stringify(i + 1);
00162                                         entry["Mask"] = x->mask;
00163                                         entry["By"] = x->by;
00164                                         entry["Created"] = Anope::strftime(x->created, NULL, true);
00165                                         entry["Expires"] = Anope::Expires(x->expires, source.nc);
00166                                         entry["Reason"] = x->reason;
00167                                         list.AddEntry(entry);
00168                                 }
00169                         }
00170                 }
00171 
00172                 if (list.IsEmpty())
00173                         source.Reply(_("No matching entries on the %s list."), source.command.c_str());
00174                 else
00175                 {
00176                         source.Reply(_("Current %s list:"), source.command.c_str());
00177 
00178                         std::vector<Anope::string> replies;
00179                         list.Process(replies);
00180 
00181                         for (unsigned i = 0; i < replies.size(); ++i)
00182                                 source.Reply(replies[i]);
00183                 }
00184         }
00185 
00186         void OnList(CommandSource &source, const std::vector<Anope::string> &params)
00187         {
00188                 ListFormatter list;
00189                 list.AddColumn("Number").AddColumn("Mask").AddColumn("Reason");
00190 
00191                 this->ProcessList(source, params, list);
00192         }
00193 
00194         void OnView(CommandSource &source, const std::vector<Anope::string> &params)
00195         {
00196                 ListFormatter list;
00197                 list.AddColumn("Number").AddColumn("Mask").AddColumn("By").AddColumn("Created").AddColumn("Expires").AddColumn("Reason");
00198                 this->ProcessList(source, params, list);
00199         }
00200 
00201         void OnClear(CommandSource &source)
00202         {
00203                 FOREACH_MOD(I_OnDelXLine, OnDelXLine(source, NULL, this->xlm()));
00204 
00205                 for (unsigned i = this->xlm()->GetCount(); i > 0; --i)
00206                 {
00207                         XLine *x = this->xlm()->GetEntry(i - 1);
00208                         this->xlm()->DelXLine(x);
00209                 }
00210 
00211                 source.Reply(_("The %s list has been cleared."), source.command.c_str());
00212 
00213                 return;
00214         }
00215  public:
00216         CommandOSSXLineBase(Module *creator, const Anope::string &cmd) : Command(creator, cmd, 1, 4)
00217         {
00218                 this->SetDesc(Anope::printf(_("Manipulate the %s list"), cmd.c_str()));
00219         }
00220 
00221         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00222         {
00223                 const Anope::string &cmd = params[0];
00224 
00225                 if (cmd.equals_ci("ADD"))
00226                         return this->OnAdd(source, params);
00227                 else if (cmd.equals_ci("DEL"))
00228                         return this->OnDel(source, params);
00229                 else if (cmd.equals_ci("LIST"))
00230                         return this->OnList(source, params);
00231                 else if (cmd.equals_ci("VIEW"))
00232                         return this->OnView(source, params);
00233                 else if (cmd.equals_ci("CLEAR"))
00234                         return this->OnClear(source);
00235                 else
00236                         this->OnSyntaxError(source, "");
00237 
00238                 return;
00239         }
00240 
00241         virtual bool OnHelp(CommandSource &source, const Anope::string &subcommand) = 0;
00242 };
00243 
00244 class CommandOSSNLine : public CommandOSSXLineBase
00245 {
00246         XLineManager *xlm()
00247         {
00248                 return this->snlines;
00249         }
00250 
00251         void OnAdd(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00252         {
00253                 if (!this->xlm() || !IRCD->CanSNLine)
00254                 {
00255                         source.Reply(_("Your IRCd does not support SNLINE."));
00256                         return;
00257                 }
00258 
00259                 unsigned last_param = 2;
00260                 Anope::string param, expiry;
00261                 time_t expires;
00262 
00263                 param = params.size() > 1 ? params[1] : "";
00264                 if (!param.empty() && param[0] == '+')
00265                 {
00266                         expiry = param;
00267                         param = params.size() > 2 ? params[2] : "";
00268                         last_param = 3;
00269                 }
00270 
00271                 expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->SNLineExpiry;
00272                 /* If the expiry given does not contain a final letter, it's in days,
00273                  * said the doc. Ah well.
00274                  */
00275                 if (!expiry.empty() && isdigit(expiry[expiry.length() - 1]))
00276                         expires *= 86400;
00277                 /* Do not allow less than a minute expiry time */
00278                 if (expires && expires < 60)
00279                 {
00280                         source.Reply(BAD_EXPIRY_TIME);
00281                         return;
00282                 }
00283                 else if (expires > 0)
00284                         expires += Anope::CurTime;
00285 
00286                 if (param.empty())
00287                 {
00288                         this->OnSyntaxError(source, "ADD");
00289                         return;
00290                 }
00291 
00292                 Anope::string rest = param;
00293                 if (params.size() > last_param)
00294                         rest += " " + params[last_param];
00295 
00296                 if (rest.find(':') == Anope::string::npos)
00297                 {
00298                         this->OnSyntaxError(source, "ADD");
00299                         return;
00300                 }
00301 
00302                 sepstream sep(rest, ':');
00303                 Anope::string mask;
00304                 sep.GetToken(mask);
00305                 Anope::string reason = sep.GetRemaining();
00306 
00307                 if (mask.empty() || reason.empty())
00308                 {
00309                         this->OnSyntaxError(source, "ADD");
00310                         return;
00311                 }
00312 
00313                 if (mask[0] == '/' && mask[mask.length() - 1] == '/')
00314                 {
00315                         if (Config->RegexEngine.empty())
00316                         {
00317                                 source.Reply(_("Regex is enabled."));
00318                                 return;
00319                         }
00320 
00321                         ServiceReference<RegexProvider> provider("Regex", Config->RegexEngine);
00322                         if (!provider)
00323                         {
00324                                 source.Reply(_("Unable to find regex engine %s."), Config->RegexEngine.c_str());
00325                                 return;
00326                         }
00327 
00328                         try
00329                         {
00330                                 Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
00331                                 delete provider->Compile(stripped_mask);
00332                         }
00333                         catch (const RegexException &ex)
00334                         {
00335                                 source.Reply("%s", ex.GetReason().c_str());
00336                                 return;
00337                         }
00338                 }
00339 
00340                 if (!this->xlm()->CanAdd(source, mask, expires, reason))
00341                         return;
00342                 else if (mask.find_first_not_of("/.*?") == Anope::string::npos)
00343                 {
00344                         source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
00345                         return;
00346                 }
00347 
00348                 /* Clean up the last character of the mask if it is a space
00349                  * See bug #761
00350                  */
00351                 unsigned masklen = mask.length();
00352                 if (mask[masklen - 1] == ' ')
00353                         mask.erase(masklen - 1);
00354 
00355                 XLine *x = new XLine(mask, source.GetNick(), expires, reason);
00356                 if (Config->AkillIds)
00357                         x->id = XLineManager::GenerateUID();
00358 
00359                 unsigned int affected = 0;
00360                 for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
00361                         if (this->xlm()->Check(it->second, x))
00362                                 ++affected;
00363                 float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
00364 
00365                 if (percent > 95)
00366                 {
00367                         source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
00368                         Log(LOG_ADMIN, source, this) << "tried to " << source.command << " " << percent << "% of the network (" << affected << " users)";
00369                         delete x;
00370                         return;
00371                 }
00372 
00373                 EventReturn MOD_RESULT;
00374                 FOREACH_RESULT(I_OnAddXLine, OnAddXLine(source, x, this->xlm()));
00375                 if (MOD_RESULT == EVENT_STOP)
00376                 {
00377                         delete x;
00378                         return;
00379                 }
00380 
00381                 this->xlm()->AddXLine(x);
00382 
00383                 if (Config->KillonSNline)
00384                 {
00385                         this->xlm()->Send(source.GetUser(), x);
00386 
00387                         Anope::string rreason = "G-Lined: " + reason;
00388 
00389                         for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
00390                         {
00391                                 User *user = it->second;
00392                                 ++it;
00393 
00394                                 if (!user->HasMode("OPER") && user->server != Me && Anope::Match(user->realname, x->mask, false, true))
00395                                         user->Kill(Config->ServerName, rreason);
00396                         }
00397                 }
00398 
00399                 source.Reply(_("\002%s\002 added to the %s list."), mask.c_str(), source.command.c_str());
00400                 Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
00401                 if (Anope::ReadOnly)
00402                         source.Reply(READ_ONLY_MODE);
00403         }
00404 
00405         ServiceReference<XLineManager> snlines;
00406  public:
00407         CommandOSSNLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/snline"), snlines("XLineManager", "xlinemanager/snline")
00408         {
00409                 this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037:\037reason\037"));
00410                 this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}"));
00411                 this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]"));
00412                 this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]"));
00413                 this->SetSyntax(_("CLEAR"));
00414         }
00415 
00416         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00417         {
00418                 this->SendSyntax(source);
00419                 source.Reply(" ");
00420                 source.Reply(_("Allows Services Operators to manipulate the SNLINE list.  If\n"
00421                                 "a user with a realname matching an SNLINE mask attempts to\n"
00422                                 "connect, Services will not allow it to pursue his IRC\n"
00423                                 "session."));
00424                 source.Reply(_(" \n"
00425                                 "\002SNLINE ADD\002 adds the given realname mask to the SNLINE\n"
00426                                 "list for the given reason (which \002must\002 be given).\n"
00427                                 "\037expiry\037 is specified as an integer followed by one of \037d\037\n"
00428                                 "(days), \037h\037 (hours), or \037m\037 (minutes).  Combinations (such as\n"
00429                                 "\0371h30m\037) are not permitted.  If a unit specifier is not\n"
00430                                 "included, the default is days (so \037+30\037 by itself means 30\n"
00431                                 "days).  To add an SNLINE which does not expire, use \037+0\037.  If the\n"
00432                                 "realname mask to be added starts with a \037+\037, an expiry time must\n"
00433                                 "be given, even if it is the same as the default.  The\n"
00434                                 "current SNLINE default expiry time can be found with the\n"
00435                                 "\002STATS AKILL\002 command.\n"
00436                                 "Note: because the realname mask may contain spaces, the\n"
00437                                 "separator between it and the reason is a colon."));
00438                 if (!Config->RegexEngine.empty())
00439                 {
00440                         source.Reply(" ");
00441                         source.Reply(_("Regex matches are also supported using the %s engine.\n"
00442                                         "Enclose your mask in // if this is desired."), Config->RegexEngine.c_str());
00443                 }
00444                 source.Reply(_(" \n"
00445                                 "The \002SNLINE DEL\002 command removes the given mask from the\n"
00446                                 "SNLINE list if it is present.  If a list of entry numbers is\n"
00447                                 "given, those entries are deleted.  (See the example for LIST\n"
00448                                 "below.)\n"
00449                                 " \n"
00450                                 "The \002SNLINE LIST\002 command displays the SNLINE list.\n"
00451                                 "If a wildcard mask is given, only those entries matching the\n"
00452                                 "mask are displayed.  If a list of entry numbers is given,\n"
00453                                 "only those entries are shown; for example:\n"
00454                                 "   \002SNLINE LIST 2-5,7-9\002\n"
00455                                 "      Lists SNLINE entries numbered 2 through 5 and 7\n"
00456                                 "      through 9.\n"
00457                                 " \n"
00458                                 "\002SNLINE VIEW\002 is a more verbose version of \002SNLINE LIST\002, and\n"
00459                                 "will show who added an SNLINE, the date it was added, and when\n"
00460                                 "it expires, as well as the realname mask and reason.\n"
00461                                 " \n"
00462                                 "\002SNLINE CLEAR\002 clears all entries of the SNLINE list."));
00463                 return true;
00464         }
00465 };
00466 
00467 class CommandOSSQLine : public CommandOSSXLineBase
00468 {
00469         XLineManager *xlm()
00470         {
00471                 return this->sqlines;
00472         }
00473 
00474         void OnAdd(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00475         {
00476                 if (!this->xlm() || !IRCD->CanSQLine)
00477                 {
00478                         source.Reply(_("Your IRCd does not support SQLINE."));
00479                         return;
00480                 }
00481 
00482                 unsigned last_param = 2;
00483                 Anope::string expiry, mask;
00484                 time_t expires;
00485 
00486                 mask = params.size() > 1 ? params[1] : "";
00487                 if (!mask.empty() && mask[0] == '+')
00488                 {
00489                         expiry = mask;
00490                         mask = params.size() > 2 ? params[2] : "";
00491                         last_param = 3;
00492                 }
00493 
00494                 expires = !expiry.empty() ? Anope::DoTime(expiry) : Config->SQLineExpiry;
00495                 /* If the expiry given does not contain a final letter, it's in days,
00496                  * said the doc. Ah well.
00497                  */
00498                 if (!expiry.empty() && isdigit(expiry[expiry.length() - 1]))
00499                         expires *= 86400;
00500                 /* Do not allow less than a minute expiry time */
00501                 if (expires && expires < 60)
00502                 {
00503                         source.Reply(BAD_EXPIRY_TIME);
00504                         return;
00505                 }
00506                 else if (expires > 0)
00507                         expires += Anope::CurTime;
00508 
00509                 if (params.size() <= last_param)
00510                 {
00511                         this->OnSyntaxError(source, "ADD");
00512                         return;
00513                 }
00514 
00515                 Anope::string reason = params[last_param];
00516                 if (last_param == 2 && params.size() > 3)
00517                         reason += " " + params[3];
00518 
00519                 if (mask.empty() || reason.empty())
00520                 {
00521                         this->OnSyntaxError(source, "ADD");
00522                         return;
00523                 }
00524 
00525                 if (mask[0] == '/' && mask[mask.length() - 1] == '/')
00526                 {
00527                         if (Config->RegexEngine.empty())
00528                         {
00529                                 source.Reply(_("Regex is enabled."));
00530                                 return;
00531                         }
00532 
00533                         ServiceReference<RegexProvider> provider("Regex", Config->RegexEngine);
00534                         if (!provider)
00535                         {
00536                                 source.Reply(_("Unable to find regex engine %s."), Config->RegexEngine.c_str());
00537                                 return;
00538                         }
00539 
00540                         try
00541                         {
00542                                 Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
00543                                 delete provider->Compile(stripped_mask);
00544                         }
00545                         catch (const RegexException &ex)
00546                         {
00547                                 source.Reply("%s", ex.GetReason().c_str());
00548                                 return;
00549                         }
00550                 }
00551 
00552                 if (!this->sqlines->CanAdd(source, mask, expires, reason))
00553                         return;
00554                 else if (mask.find_first_not_of("./?*") == Anope::string::npos)
00555                 {
00556                         source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
00557                         return;
00558                 }
00559 
00560                 XLine *x = new XLine(mask, source.GetNick(), expires, reason);
00561                 if (Config->AkillIds)
00562                         x->id = XLineManager::GenerateUID();
00563 
00564                 unsigned int affected = 0;
00565                 for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
00566                         if (this->xlm()->Check(it->second, x))
00567                                 ++affected;
00568                 float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
00569 
00570                 if (percent > 95)
00571                 {
00572                         source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
00573                         Log(LOG_ADMIN, source, this) << "tried to SQLine " << percent << "% of the network (" << affected << " users)";
00574                         delete x;
00575                         return;
00576                 }
00577 
00578                 EventReturn MOD_RESULT;
00579                 FOREACH_RESULT(I_OnAddXLine, OnAddXLine(source, x, this->xlm()));
00580                 if (MOD_RESULT == EVENT_STOP)
00581                 {
00582                         delete x;
00583                         return;
00584                 }
00585 
00586                 this->xlm()->AddXLine(x);
00587                 if (Config->KillonSQline)
00588                 {
00589                         Anope::string rreason = "Q-Lined: " + reason;
00590 
00591                         if (mask[0] == '#')
00592                         {
00593                                 for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit)
00594                                 {
00595                                         Channel *c = cit->second;
00596 
00597                                         if (!Anope::Match(c->name, mask, false, true))
00598                                                 continue;
00599 
00600                                         std::vector<User *> users;
00601                                         for (Channel::ChanUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; ++it)
00602                                         {
00603                                                 ChanUserContainer *uc = *it;
00604                                                 User *user = uc->user;
00605 
00606                                                 if (!user->HasMode("OPER") && user->server != Me)
00607                                                         users.push_back(user);
00608                                         }
00609 
00610                                         for (unsigned i = 0; i < users.size(); ++i)
00611                                                 c->Kick(NULL, users[i], "%s", reason.c_str());
00612                                 }
00613                         }
00614                         else
00615                         {
00616                                 for (user_map::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
00617                                 {
00618                                         User *user = it->second;
00619                                         ++it;
00620 
00621                                         if (!user->HasMode("OPER") && user->server != Me && Anope::Match(user->nick, x->mask, false, true))
00622                                                 user->Kill(Config->ServerName, rreason);
00623                                 }
00624                         }
00625 
00626                         this->xlm()->Send(source.GetUser(), x);
00627                 }
00628 
00629                 source.Reply(_("\002%s\002 added to the SQLINE list."), mask.c_str());
00630                 Log(LOG_ADMIN, source, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? Anope::Duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
00631 
00632                 if (Anope::ReadOnly)
00633                         source.Reply(READ_ONLY_MODE);
00634         }
00635 
00636         ServiceReference<XLineManager> sqlines;
00637  public:
00638         CommandOSSQLine(Module *creator) : CommandOSSXLineBase(creator, "operserv/sqline"), sqlines("XLineManager", "xlinemanager/sqline")
00639         {
00640                 this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037"));
00641                 this->SetSyntax(_("DEL {\037mask\037 | \037entry-num\037 | \037list\037 | \037id\037}"));
00642                 this->SetSyntax(_("LIST [\037mask\037 | \037list\037 | \037id\037]"));
00643                 this->SetSyntax(_("VIEW [\037mask\037 | \037list\037 | \037id\037]"));
00644                 this->SetSyntax(_("CLEAR"));
00645         }
00646 
00647         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00648         {
00649                 this->SendSyntax(source);
00650                 source.Reply(" ");
00651                 source.Reply(_("Allows Services Operators to manipulate the SQLINE list.  If\n"
00652                                 "a user with a nick matching an SQLINE mask attempts to\n"
00653                                 "connect, Services will not allow it to pursue his IRC\n"
00654                                 "session.\n"
00655                                 "If the first character of the mask is #, services will\n"
00656                                 "prevent the use of matching channels."));
00657                 source.Reply(_(" \n"
00658                                 "\002SQLINE ADD\002 adds the given (nick's) mask to the SQLINE\n"
00659                                 "list for the given reason (which \002must\002 be given).\n"
00660                                 "\037expiry\037 is specified as an integer followed by one of \037d\037\n"
00661                                 "(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as\n"
00662                                 "\0371h30m\037) are not permitted. If a unit specifier is not\n"
00663                                 "included, the default is days (so \037+30\037 by itself means 30\n"
00664                                 "days). To add an SQLINE which does not expire, use \037+0\037.\n"
00665                                 "If the mask to be added starts with a \037+\037, an expiry time\n"
00666                                 "must be given, even if it is the same as the default. The\n"
00667                                 "current SQLINE default expiry time can be found with the\n"
00668                                 "\002STATS AKILL\002 command."));
00669                 if (!Config->RegexEngine.empty())
00670                 {
00671                         source.Reply(" ");
00672                         source.Reply(_("Regex matches are also supported using the %s engine.\n"
00673                                         "Enclose your mask in // if this is desired."), Config->RegexEngine.c_str());
00674                 }
00675                 source.Reply(_(" \n"
00676                                 "The \002SQLINE DEL\002 command removes the given mask from the\n"
00677                                 "SQLINE list if it is present. If a list of entry numbers is\n"
00678                                 "given, those entries are deleted. (See the example for LIST\n"
00679                                 "below.)\n"
00680                                 " \n"
00681                                 "The \002SQLINE LIST\002 command displays the SQLINE list.\n"
00682                                 "If a wildcard mask is given, only those entries matching the\n"
00683                                 "mask are displayed. If a list of entry numbers is given,\n"
00684                                 "only those entries are shown; for example:\n"
00685                                 "   \002SQLINE LIST 2-5,7-9\002\n"
00686                                 "      Lists SQLINE entries numbered 2 through 5 and 7\n"
00687                                 "      through 9.\n"
00688                                 " \n"
00689                                 "\002SQLINE VIEW\002 is a more verbose version of \002SQLINE LIST\002, and\n"
00690                                 "will show who added an SQLINE, the date it was added, and when\n"
00691                                 "it expires, as well as the mask and reason.\n"
00692                                 " \n"
00693                                 "\002SQLINE CLEAR\002 clears all entries of the SQLINE list."));
00694                 return true;
00695         }
00696 };
00697 
00698 class OSSXLine : public Module
00699 {
00700         CommandOSSNLine commandossnline;
00701         CommandOSSQLine commandossqline;
00702 
00703  public:
00704         OSSXLine(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE),
00705                 commandossnline(this), commandossqline(this)
00706         {
00707                 this->SetAuthor("Anope");
00708         }
00709 };
00710 
00711 MODULE_INIT(OSSXLine)