1
0
mirror of https://github.com/anope/anope.git synced 2026-07-10 18:03:14 +02:00

Added regex support for many commands, such as akill, sqline, snline,

all of the */list commands, etc.

Also extended the ability of akill to match a full nick!user@host and
real name of users.
This commit is contained in:
Adam
2012-02-22 18:12:02 -05:00
parent 81e50dd1f4
commit 3850b073dd
35 changed files with 1203 additions and 424 deletions
+7
View File
@@ -498,6 +498,13 @@ options
* If set, Services do not allow ownership of nick names, only ownership of accounts.
*/
nonicknameownership = no
/* The regex engine to use, as provided by the regex modules.
* Leave commented to disable regex matching.
*
* Note for this to work the regex module providing the regex engine must be loaded.
*/
regexengine = "regex/pcre"
}
/*
+22
View File
@@ -310,6 +310,28 @@ sqlite
database = "anope.db"
}
/*
* m_regex_pcre
*
* Provides the regex engine regex/pcre, which uses the Perl Compatible Regular Expressions library.
*/
module { name = "m_regex_pcre" }
/*
* m_regex_posix
*
* Provides the regex engine regex/posix, which uses the POSIX compliant regular expressions.
* This is likely the only regex module you will not need extra libraries for.
*/
#module { name = "m_regex_posix" }
/*
* m_regex_tre
*
* Provides the regex engine regex/tre, which uses the TRE regex library.
*/
#module { name = "m_regex_tre" }
/*
* m_rewrite
*
+2
View File
@@ -1,5 +1,7 @@
Anope Version 1.9.7
--------------------
A Added ability for using regex as patterns for various commands
A Extended ability of operserv/akill to also match against nick and realname
Anope Version 1.9.6
--------------------
+1
View File
@@ -1,6 +1,7 @@
Anope Version 1.9.7
-------------------
operserv:notifications removed in favor of log{} blocks
options:regexengine added
Anope Version 1.9.6
-------------------
+2 -1
View File
@@ -346,8 +346,9 @@ namespace Anope
* @param str The string to check against the pattern (e.g. foobar)
* @param mask The pattern to check (e.g. foo*bar)
* @param case_sensitive Whether or not the match is case sensitive, default false.
* @param use_regex Whether or not to try regex. case_sensitive is not used in regex.
*/
extern CoreExport bool Match(const string &str, const string &mask, bool case_sensitive = false);
extern CoreExport bool Match(const string &str, const string &mask, bool case_sensitive = false, bool use_regex = false);
/** Find a message in the message table
* @param name The name of the message were looking for
+2
View File
@@ -456,6 +456,8 @@ class CoreExport ServerConfig
bool HidePrivilegedCommands;
/* If set, nicks cant be owned/everything is entirely account based */
bool NoNicknameOwnership;
/* Regex engine to use */
Anope::string RegexEngine;
/* A vector of our logfile options */
std::vector<LogInfo *> LogInfos;
+1
View File
@@ -35,6 +35,7 @@ class Module;
class NickAlias;
class NickCore;
class OperType;
class Regex;
class Server;
class ServerConfig;
class Socket;
+1
View File
@@ -36,6 +36,7 @@
#include "oper.h"
#include "opertype.h"
#include "protocol.h"
#include "regexpr.h"
#include "regchannel.h"
#include "serialize.h"
#include "servers.h"
+26 -12
View File
@@ -14,8 +14,10 @@
class CoreExport XLine : public Serializable
{
void InitRegex();
public:
Anope::string Mask;
Regex *regex;
Anope::string By;
time_t Created;
time_t Expires;
@@ -25,11 +27,18 @@ class CoreExport XLine : public Serializable
XLine(const Anope::string &mask, const Anope::string &reason = "", const Anope::string &uid = "");
XLine(const Anope::string &mask, const Anope::string &by, const time_t expires, const Anope::string &reason, const Anope::string &uid);
XLine(const Anope::string &mask, const Anope::string &by, const time_t expires, const Anope::string &reason, const Anope::string &uid = "");
~XLine();
Anope::string GetNick() const;
Anope::string GetUser() const;
Anope::string GetHost() const;
Anope::string GetReal() const;
Anope::string GetReason() const;
bool HasNickOrReal() const;
bool IsRegex() const;
Anope::string serialize_name() const;
serialized_data serialize();
@@ -41,7 +50,8 @@ class CoreExport XLineManager : public Service
char type;
/* List of XLines in this XLineManager */
std::vector<XLine *> XLines;
static std::map<Anope::string, XLine *, ci::less> XLinesByUID;
/* Akills can have the same IDs, sometimes */
static std::multimap<Anope::string, XLine *, ci::less> XLinesByUID;
public:
/* List of XLine managers we check users against in XLineManager::CheckAll */
static std::list<XLineManager *> XLineManagers;
@@ -63,7 +73,7 @@ class CoreExport XLineManager : public Service
* Wparam u The user
* @return A pair of the XLineManager the user was found in and the XLine they matched, both may be NULL for no match
*/
static std::pair<XLineManager *, XLine *> CheckAll(User *u);
static void CheckAll(User *u);
/** Generate a unique ID for this XLine
* @return A unique ID
@@ -116,15 +126,13 @@ class CoreExport XLineManager : public Service
void Clear();
/** Checks if a mask can/should be added to the XLineManager
* @param source The source adding the mask.
* @param mask The mask
* @param expires When the mask would expire
* @return A pair of int and XLine*.
* 1 - Mask already exists
* 2 - Mask already exists, but the expiry time was changed
* 3 - Mask is already covered by another mask
* In each case the XLine it matches/is covered by is returned in XLine*
* @param reason the reason
* @return true if the mask can be added
*/
std::pair<int, XLine *> CanAdd(const Anope::string &mask, time_t expires);
bool CanAdd(CommandSource &source, const Anope::string &mask, time_t expires, const Anope::string &reason);
/** Checks if this list has an entry
* @param mask The mask
@@ -134,15 +142,21 @@ class CoreExport XLineManager : public Service
/** Check a user against all of the xlines in this XLineManager
* @param u The user
* @return The xline the user marches, if any. Also calls OnMatch()
* @return The xline the user marches, if any.
*/
virtual XLine *Check(User *u);
XLine *CheckAllXLines(User *u);
/** Check a user against an xline
* @param u The user
* @param x The xline
*/
virtual bool Check(User *u, XLine *x) = 0;
/** Called when a user matches a xline in this XLineManager
* @param u The user
* @param x The XLine they match
*/
virtual void OnMatch(User *u, XLine *x);
virtual void OnMatch(User *u, XLine *x) = 0;
/** Called when an XLine expires
* @param x The xline
+1 -1
View File
@@ -67,7 +67,7 @@ class CoreExport IRCDProto
virtual void SendSVSNOOP(const Server *, bool) { }
virtual void SendTopic(BotInfo *, Channel *);
virtual void SendVhostDel(User *) { }
virtual void SendAkill(User *, const XLine *) = 0;
virtual void SendAkill(User *, XLine *) = 0;
virtual void SendAkillDel(const XLine *) = 0;
virtual void SendSVSKill(const BotInfo *source, const User *user, const char *fmt, ...);
virtual void SendMode(const BotInfo *bi, const Channel *dest, const char *fmt, ...);
+47
View File
@@ -0,0 +1,47 @@
/*
*
* (C) 2003-2012 Anope Team
* Contact us at team@anope.org
*
* Please read COPYING and README for further details.
*
* Based on the original code of Epona by Lara.
* Based on the original code of Services by Andy Church.
*
*/
#ifndef REGEXPR_H
#define REGEXPR_H
#include "services.h"
#include "anope.h"
#include "service.h"
class RegexException : public CoreException
{
public:
RegexException(const Anope::string &reason = "") : CoreException(reason) { }
virtual ~RegexException() throw() { }
};
class CoreExport Regex
{
Anope::string expression;
protected:
Regex(const Anope::string &expr) : expression(expr) { }
public:
virtual ~Regex() { }
const Anope::string &GetExpression() { return expression; }
virtual bool Matches(const Anope::string &str) = 0;
};
class CoreExport RegexProvider : public Service
{
public:
RegexProvider(Module *o, const Anope::string &n) : Service(o, "Regex", n) { }
virtual Regex *Compile(const Anope::string &) = 0;
};
#endif // REGEXPR_H
+5 -1
View File
@@ -87,7 +87,7 @@ class CommandCSList : public Command
else if (channoexpire && !ci->HasFlag(CI_NO_EXPIRE))
continue;
if (pattern.equals_ci(ci->name) || ci->name.equals_ci(spattern) || Anope::Match(ci->name, pattern) || Anope::Match(ci->name, spattern))
if (pattern.equals_ci(ci->name) || ci->name.equals_ci(spattern) || Anope::Match(ci->name, pattern, false, true) || Anope::Match(ci->name, spattern, false, true))
{
if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nchans <= Config->CSListMax)
{
@@ -125,6 +125,10 @@ class CommandCSList : public Command
"(Channels with the \002PRIVATE\002 option set are not listed.)\n"
"Note that a preceding '#' specifies a range, channel names\n"
"are to be written without '#'."));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your pattern in // if this desired.", Config->RegexEngine.c_str());
return true;
}
};
+5 -1
View File
@@ -93,7 +93,7 @@ class CommandNSList : public Command
* Instead we build a nice nick!user@host buffer to compare.
* The output is then generated separately. -TheShadow */
Anope::string buf = Anope::printf("%s!%s", na->nick.c_str(), !na->last_usermask.empty() ? na->last_usermask.c_str() : "*@*");
if (na->nick.equals_ci(pattern) || Anope::Match(buf, pattern))
if (na->nick.equals_ci(pattern) || Anope::Match(buf, pattern, false, true))
{
if (((count + 1 >= from && count + 1 <= to) || (!from && !to)) && ++nnicks <= Config->NSListMax)
{
@@ -156,6 +156,10 @@ class CommandNSList : public Command
" \n"
" \002LIST * NOEXPIRE\002\n"
" Lists all registered nicks which have been set to not expire.\n"));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your pattern in // if this desired.", Config->RegexEngine.c_str());
return true;
}
+128 -87
View File
@@ -60,19 +60,24 @@ class CommandOSAKill : public Command
void DoAdd(CommandSource &source, const std::vector<Anope::string> &params)
{
User *u = source.u;
unsigned last_param = 2;
Anope::string expiry, mask;
time_t expires;
mask = params.size() > 1 ? params[1] : "";
if (!mask.empty() && mask[0] == '+')
if (params.size() < 2)
{
expiry = mask;
mask = params.size() > 2 ? params[2] : "";
last_param = 3;
this->OnSyntaxError(source, "ADD");
return;
}
expires = !expiry.empty() ? dotime(expiry) : Config->AutokillExpiry;
spacesepstream sep(params[1]);
sep.GetToken(mask);
if (mask[0] == '+')
{
expiry = mask;
sep.GetToken(mask);
}
time_t expires = !expiry.empty() ? dotime(expiry) : Config->AutokillExpiry;
/* If the expiry given does not contain a final letter, it's in days,
* said the doc. Ah well.
*/
@@ -87,84 +92,108 @@ class CommandOSAKill : public Command
else if (expires > 0)
expires += Anope::CurTime;
if (params.size() <= last_param)
if (sep.StreamEnd())
{
this->OnSyntaxError(source, "ADD");
return;
}
Anope::string reason = params[last_param];
if (last_param == 2 && params.size() > 3)
reason += " " + params[3];
if (!mask.empty() && !reason.empty())
Anope::string reason;
if (mask.find('#') != Anope::string::npos)
{
User *targ = NULL;
std::pair<int, XLine *> canAdd = akills->CanAdd(mask, expires);
if (mask.find('!') != Anope::string::npos)
source.Reply(_("\002Reminder\002: AKILL masks cannot contain nicknames; make sure you have \002not\002 included a nick portion in your mask."));
else if (mask.find('@') == Anope::string::npos && !(targ = finduser(mask)))
source.Reply(BAD_USERHOST_MASK);
else if (mask.find_first_not_of("~@.*?") == Anope::string::npos)
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
else if (canAdd.first == 1)
source.Reply(_("\002%s\002 already exists on the AKILL list."), canAdd.second->Mask.c_str());
else if (canAdd.first == 2)
source.Reply(_("Expiry time of \002%s\002 changed."), canAdd.second->Mask.c_str());
else if (canAdd.first == 3)
source.Reply(_("\002%s\002 is already covered by %s."), mask.c_str(), canAdd.second->Mask.c_str());
else
Anope::string remaining = sep.GetRemaining();
size_t co = remaining[0] == ':' ? 0 : remaining.rfind(" :");
if (co == Anope::string::npos)
{
if (targ)
mask = "*@" + targ->host;
unsigned int affected = 0;
for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (Anope::Match(it->second->GetIdent() + "@" + it->second->host, mask))
++affected;
float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
if (percent > 95)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
Log(LOG_ADMIN, u, this) << "tried to akill " << percent << "% of the network (" << affected << " users)";
return;
}
if (Config->AddAkiller)
reason = "[" + u->nick + "] " + reason;
Anope::string id;
if (Config->AkillIds)
{
id = XLineManager::GenerateUID();
reason = reason + " (ID: " + id + ")";
}
XLine *x = new XLine(mask, u->nick, expires, reason, id);
EventReturn MOD_RESULT;
FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, akills));
if (MOD_RESULT == EVENT_STOP)
{
delete x;
return;
}
akills->AddXLine(x);
if (Config->AkillOnAdd)
akills->Send(NULL, x);
source.Reply(_("\002%s\002 added to the AKILL list."), mask.c_str());
Log(LOG_ADMIN, u, this) << "on " << mask << " (" << x->Reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
if (readonly)
source.Reply(READ_ONLY_MODE);
this->OnSyntaxError(source, "ADD");
return;
}
if (co != 0)
++co;
reason = remaining.substr(co + 1);
mask += " " + remaining.substr(0, co);
mask.trim();
}
else
this->OnSyntaxError(source, "ADD");
reason = sep.GetRemaining();
return;
if (mask[0] == '/' && mask[mask.length() - 1] == '/')
{
if (Config->RegexEngine.empty())
{
source.Reply(_("Regex is enabled."));
return;
}
service_reference<RegexProvider> provider("Regex", Config->RegexEngine);
if (!provider)
{
source.Reply(_("Unable to find regex engine %s"), Config->RegexEngine.c_str());
return;
}
try
{
Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
delete provider->Compile(stripped_mask);
}
catch (const RegexException &ex)
{
source.Reply("%s", ex.GetReason().c_str());
return;
}
}
User *targ = finduser(mask);
if (targ)
mask = "*@" + targ->host;
if (!akills->CanAdd(source, mask, expires, reason))
return;
else if (mask.find_first_not_of("/~@.*?") == Anope::string::npos)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
return;
}
XLine *x = new XLine(mask, u->nick, expires, reason);
if (Config->AkillIds)
x->UID = XLineManager::GenerateUID();
unsigned int affected = 0;
for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (akills->Check(it->second, x))
++affected;
float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
if (percent > 95)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
Log(LOG_ADMIN, u, this) << "tried to akill " << percent << "% of the network (" << affected << " users)";
delete x;
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, akills));
if (MOD_RESULT == EVENT_STOP)
{
delete x;
return;
}
akills->AddXLine(x);
if (Config->AkillOnAdd)
akills->Send(NULL, x);
source.Reply(_("\002%s\002 added to the AKILL list."), mask.c_str());
Log(LOG_ADMIN, u, this) << "on " << mask << " (" << x->Reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
if (readonly)
source.Reply(READ_ONLY_MODE);
}
void DoDel(CommandSource &source, const std::vector<Anope::string> &params)
@@ -199,10 +228,14 @@ class CommandOSAKill : public Command
return;
}
FOREACH_MOD(I_OnDelXLine, OnDelXLine(u, x, akills));
do
{
FOREACH_MOD(I_OnDelXLine, OnDelXLine(u, x, akills));
source.Reply(_("\002%s\002 deleted from the AKILL list."), x->Mask.c_str());
AkillDelCallback::DoDel(source, x);
source.Reply(_("\002%s\002 deleted from the AKILL list."), x->Mask.c_str());
AkillDelCallback::DoDel(source, x);
}
while ((x = akills->HasEntry(mask)));
}
@@ -240,7 +273,7 @@ class CommandOSAKill : public Command
entry["Number"] = stringify(number);
entry["Mask"] = x->Mask;
entry["Creator"] = x->By;
entry["Created"] = do_strftime(x->Created);
entry["Created"] = do_strftime(x->Created, NULL, true);
entry["Expires"] = expire_left(NULL, x->Expires);
entry["Reason"] = x->Reason;
this->list.addEntry(entry);
@@ -255,13 +288,13 @@ class CommandOSAKill : public Command
{
XLine *x = akills->GetEntry(i);
if (mask.empty() || mask.equals_ci(x->Mask) || mask == x->UID || Anope::Match(x->Mask, mask))
if (mask.empty() || mask.equals_ci(x->Mask) || mask == x->UID || Anope::Match(x->Mask, mask, false, true))
{
ListFormatter::ListEntry entry;
entry["Number"] = stringify(i + 1);
entry["Mask"] = x->Mask;
entry["Creator"] = x->By;
entry["Created"] = do_strftime(x->Created);
entry["Created"] = do_strftime(x->Created, NULL, true);
entry["Expires"] = expire_left(source.u->Account(), x->Expires);
entry["Reason"] = x->Reason;
list.addEntry(entry);
@@ -327,7 +360,7 @@ class CommandOSAKill : public Command
source.Reply(_("The AKILL list has been cleared."));
}
public:
CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 4)
CommandOSAKill(Module *creator) : Command(creator, "operserv/akill", 1, 2)
{
this->SetDesc(_("Manipulate the AKILL list"));
this->SetSyntax(_("ADD [+\037expiry\037] \037mask\037 \037reason\037"));
@@ -367,11 +400,14 @@ class CommandOSAKill : public Command
source.Reply(_("Allows Services operators to manipulate the AKILL list. If\n"
"a user matching an AKILL mask attempts to connect, Services\n"
"will issue a KILL for that user and, on supported server\n"
"types, will instruct all servers to add a ban (K-line) for\n"
"the mask which the user matched.\n"
"types, will instruct all servers to add a ban for the mask\n"
"which the user matched.\n"
" \n"
"\002AKILL ADD\002 adds the given nick or user@host/ip mask to the AKILL\n"
"list for the given reason (which \002must\002 be given).\n"
"\002AKILL ADD\002 adds the given mask to the AKILL\n"
"list for the given reason, which \002must\002 be given.\n"
"Mask should be in the format of nick!user@host#real name,\n"
"though all that is required is user@host. If a real name is specified,\n"
"the reason must be prepended with a :.\n"
"\037expiry\037 is specified as an integer followed by one of \037d\037 \n"
"(days), \037h\037 (hours), or \037m\037 (minutes). Combinations (such as \n"
"\0371h30m\037) are not permitted. If a unit specifier is not \n"
@@ -380,7 +416,12 @@ class CommandOSAKill : public Command
"usermask to be added starts with a \037+\037, an expiry time must\n"
"be given, even if it is the same as the default. The\n"
"current AKILL default expiry time can be found with the\n"
"\002STATS AKILL\002 command.\n"
"\002STATS AKILL\002 command.\n"));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your mask in // if this desired.", Config->RegexEngine.c_str());
source.Reply(_(
" \n"
"The \002AKILL DEL\002 command removes the given mask from the\n"
"AKILL list if it is present. If a list of entry numbers is \n"
+1 -1
View File
@@ -82,7 +82,7 @@ class CommandOSChanKill : public Command
XLine *x = new XLine("*@" + uc->user->host, u->nick, expires, realreason, XLineManager::GenerateUID());
akills->AddXLine(x);
akills->Check(uc->user);
akills->OnMatch(uc->user, x);
}
Log(LOG_ADMIN, u, this) << "on " << c->name << " (" << realreason << ")";
+12 -3
View File
@@ -41,7 +41,7 @@ class MyForbidService : public ForbidService
{
ForbidData *d = this->forbidData[i - 1];
if ((ftype == FT_NONE || ftype == d->type) && Anope::Match(mask, d->mask))
if ((ftype == FT_NONE || ftype == d->type) && Anope::Match(mask, d->mask, false, true))
return d;
}
return NULL;
@@ -217,6 +217,10 @@ class CommandOSForbid : public Command
source.Reply(" ");
source.Reply(_("Forbid allows you to forbid usage of certain nicknames, channels,\n"
"and email addresses. Wildcards are accepted for all entries."));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your pattern in // if this desired.", Config->RegexEngine.c_str());
return true;
}
};
@@ -273,8 +277,13 @@ class OSForbid : public Module
BotInfo *bi = findbot(Config->OperServ);
ForbidData *d = this->forbidService.FindForbid(c->name, FT_CHAN);
if (bi != NULL && d != NULL)
{
if (!c->HasFlag(CH_INHABIT))
{
if (ircd->chansqline)
{
XLine x(c->name, bi->nick, Anope::CurTime + Config->CSInhabit, d->reason);
ircdproto->SendSQLine(NULL, &x);
}
else if (!c->HasFlag(CH_INHABIT))
{
/* Join ChanServ and set a timer for this channel to part ChanServ later */
c->Hold();
+5 -1
View File
@@ -119,7 +119,7 @@ class OSIgnoreService : public IgnoreService
tmp = mask + "!*@*";
for (; ign != ign_end; ++ign)
if (Anope::Match(tmp, ign->mask))
if (Anope::Match(tmp, ign->mask, false, true))
break;
}
@@ -297,6 +297,10 @@ class CommandOSIgnore : public Command
"Wildcards are permitted.\n"
" \n"
"Ignores will not be enforced on IRC Operators."));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your pattern in // if this desired.", Config->RegexEngine.c_str());
return true;
}
};
+9 -1
View File
@@ -67,7 +67,7 @@ class CommandOSChanList : public Command
{
Channel *c = cit->second;
if (!pattern.empty() && !Anope::Match(c->name, pattern))
if (!pattern.empty() && !Anope::Match(c->name, pattern, false, true))
continue;
if (!Modes.empty())
for (std::list<ChannelModeName>::iterator it = Modes.begin(), it_end = Modes.end(); it != it_end; ++it)
@@ -102,6 +102,10 @@ class CommandOSChanList : public Command
"is given, lists only the channels the user using it is on. If SECRET is\n"
"specified, lists only channels matching \002pattern\002 that have the +s or\n"
"+p mode."));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your pattern in // if this desired.", Config->RegexEngine.c_str());
return true;
}
};
@@ -194,6 +198,10 @@ class CommandOSUserList : public Command
"the format nick!user@host). If \002channel\002 is given, lists only users\n"
"that are on the given channel. If INVISIBLE is specified, only users\n"
"with the +i flag will be listed."));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your pattern in // if this desired.", Config->RegexEngine.c_str());
return true;
}
};
+213 -163
View File
@@ -141,7 +141,7 @@ class CommandOSSXLineBase : public Command
entry["Number"] = stringify(Number);
entry["Mask"] = x->Mask;
entry["By"] = x->By;
entry["Created"] = do_strftime(x->Created);
entry["Created"] = do_strftime(x->Created, NULL, true);
entry["Expires"] = expire_left(NULL, x->Expires);
entry["Reason"] = x->Reason;
list.addEntry(entry);
@@ -156,13 +156,13 @@ class CommandOSSXLineBase : public Command
{
XLine *x = this->xlm()->GetEntry(i);
if (mask.empty() || mask.equals_ci(x->Mask) || mask == x->UID || Anope::Match(x->Mask, mask))
if (mask.empty() || mask.equals_ci(x->Mask) || mask == x->UID || Anope::Match(x->Mask, mask, false, true))
{
ListFormatter::ListEntry entry;
entry["Number"] = stringify(i + 1);
entry["Mask"] = x->Mask;
entry["By"] = x->By;
entry["Created"] = do_strftime(x->Created);
entry["Created"] = do_strftime(x->Created, NULL, true);
entry["Expires"] = expire_left(source.u->Account(), x->Expires);
entry["Reason"] = x->Reason;
list.addEntry(entry);
@@ -307,85 +307,105 @@ class CommandOSSNLine : public CommandOSSXLineBase
sep.GetToken(mask);
Anope::string reason = sep.GetRemaining();
if (!mask.empty() && !reason.empty())
if (mask.empty() || reason.empty())
{
std::pair<int, XLine *> canAdd = this->xlm()->CanAdd(mask, expires);
if (mask.find_first_not_of("*?") == Anope::string::npos)
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
else if (canAdd.first == 1)
source.Reply(_("\002%s\002 already exists on the %s list."), canAdd.second->Mask.c_str(), source.command.c_str());
else if (canAdd.first == 2)
source.Reply(_("Expiry time of \002%s\002 changed."), canAdd.second->Mask.c_str());
else if (canAdd.first == 3)
source.Reply(_("\002%s\002 is already covered by %s."), mask.c_str(), canAdd.second->Mask.c_str());
else
this->OnSyntaxError(source, "ADD");
return;
}
if (mask[0] == '/' && mask[mask.length() - 1] == '/')
{
if (Config->RegexEngine.empty())
{
/* Clean up the last character of the mask if it is a space
* See bug #761
*/
unsigned masklen = mask.length();
if (mask[masklen - 1] == ' ')
mask.erase(masklen - 1);
unsigned int affected = 0;
for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (Anope::Match(it->second->realname, mask))
++affected;
float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
if (percent > 95)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
Log(LOG_ADMIN, u, this) << "tried to " << source.command << " " << percent << "% of the network (" << affected << " users)";
return;
}
if (Config->AddAkiller)
reason = "[" + u->nick + "] " + reason;
Anope::string id;
if (Config->AkillIds)
{
id = XLineManager::GenerateUID();
reason = reason + " (ID: " + id + ")";
}
XLine *x = new XLine(mask, u->nick, expires, reason, id);
EventReturn MOD_RESULT;
FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, this->xlm()));
if (MOD_RESULT == EVENT_STOP)
{
delete x;
return;
}
this->xlm()->AddXLine(x);
if (Config->KillonSNline && !ircd->sglineenforce)
{
Anope::string rreason = "G-Lined: " + reason;
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end();)
{
User *user = it->second;
++it;
if (!user->HasMode(UMODE_OPER) && user->server != Me && Anope::Match(user->realname, x->Mask))
user->Kill(Config->ServerName, rreason);
}
}
source.Reply(_("\002%s\002 added to the %s list."), mask.c_str(), source.command.c_str());
Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
if (readonly)
source.Reply(READ_ONLY_MODE);
source.Reply(_("Regex is enabled."));
return;
}
}
else
this->OnSyntaxError(source, "ADD");
service_reference<RegexProvider> provider("Regex", Config->RegexEngine);
if (!provider)
{
source.Reply(_("Unable to find regex engine %s"), Config->RegexEngine.c_str());
return;
}
return;
try
{
Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
delete provider->Compile(stripped_mask);
}
catch (const RegexException &ex)
{
source.Reply("%s", ex.GetReason().c_str());
return;
}
}
if (!this->xlm()->CanAdd(source, mask, expires, reason))
return;
else if (mask.find_first_not_of("/.*?") == Anope::string::npos)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
return;
}
/* Clean up the last character of the mask if it is a space
* See bug #761
*/
unsigned masklen = mask.length();
if (mask[masklen - 1] == ' ')
mask.erase(masklen - 1);
XLine *x = new XLine(mask, u->nick, expires, reason);
if (Config->AkillIds)
x->UID = XLineManager::GenerateUID();
unsigned int affected = 0;
for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (this->xlm()->Check(it->second, x))
++affected;
float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
if (percent > 95)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
Log(LOG_ADMIN, u, this) << "tried to " << source.command << " " << percent << "% of the network (" << affected << " users)";
delete x;
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, this->xlm()));
if (MOD_RESULT == EVENT_STOP)
{
delete x;
return;
}
this->xlm()->AddXLine(x);
if (Config->KillonSNline)
{
this->xlm()->Send(u, x);
if (!ircd->sglineenforce)
{
Anope::string rreason = "G-Lined: " + reason;
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end();)
{
User *user = it->second;
++it;
if (!user->HasMode(UMODE_OPER) && user->server != Me && Anope::Match(user->realname, x->Mask, false, true))
user->Kill(Config->ServerName, rreason);
}
}
}
source.Reply(_("\002%s\002 added to the %s list."), mask.c_str(), source.command.c_str());
Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
if (readonly)
source.Reply(READ_ONLY_MODE);
}
service_reference<XLineManager> snlines;
@@ -421,6 +441,10 @@ class CommandOSSNLine : public CommandOSSXLineBase
"\002STATS AKILL\002 command.\n"
"Note: because the realname mask may contain spaces, the\n"
"separator between it and the reason is a colon.\n"));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your mask in // if this desired.", Config->RegexEngine.c_str());
source.Reply(_(" \n"
"The \002SNLINE DEL\002 command removes the given mask from the\n"
"SNLINE list if it is present. If a list of entry numbers is \n"
@@ -496,95 +520,118 @@ class CommandOSSQLine : public CommandOSSXLineBase
Anope::string reason = params[last_param];
if (last_param == 2 && params.size() > 3)
reason += " " + params[3];
if (!mask.empty() && !reason.empty())
if (mask.empty() || reason.empty())
{
std::pair<int, XLine *> canAdd = this->sqlines->CanAdd(mask, expires);
if (mask.find_first_not_of("*") == Anope::string::npos)
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
else if (canAdd.first == 1)
source.Reply(_("\002%s\002 already exists on the SQLINE list."), canAdd.second->Mask.c_str());
else if (canAdd.first == 2)
source.Reply(_("Expiry time of \002%s\002 changed."), canAdd.second->Mask.c_str());
else if (canAdd.first == 3)
source.Reply(_("\002%s\002 is already covered by %s."), mask.c_str(), canAdd.second->Mask.c_str());
else
this->OnSyntaxError(source, "ADD");
return;
}
if (mask[0] == '/' && mask[mask.length() - 1] == '/')
{
if (Config->RegexEngine.empty())
{
unsigned int affected = 0;
for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (Anope::Match(it->second->nick, mask))
++affected;
float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
if (percent > 95)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
Log(LOG_ADMIN, u, this) << "tried to SQLine " << percent << "% of the network (" << affected << " users)";
return;
}
Anope::string id = XLineManager::GenerateUID();
reason = reason + " (ID: " + id + ")";
XLine *x = new XLine(mask, u->nick, expires, reason, id);
EventReturn MOD_RESULT;
FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, this->xlm()));
if (MOD_RESULT == EVENT_STOP)
{
delete x;
return;
}
this->xlm()->AddXLine(x);
if (Config->KillonSQline)
{
Anope::string rreason = "Q-Lined: " + reason;
if (mask[0] == '#')
{
for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit)
{
Channel *c = cit->second;
if (!Anope::Match(c->name, mask))
continue;
for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; )
{
UserContainer *uc = *it;
++it;
if (uc->user->HasMode(UMODE_OPER) || uc->user->server == Me)
continue;
c->Kick(NULL, uc->user, "%s", reason.c_str());
}
}
}
else
{
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end();)
{
User *user = it->second;
++it;
if (!user->HasMode(UMODE_OPER) && user->server != Me && Anope::Match(user->nick, x->Mask))
user->Kill(Config->ServerName, rreason);
}
}
}
this->xlm()->Send(NULL, x);
source.Reply(_("\002%s\002 added to the SQLINE list."), mask.c_str());
Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
if (readonly)
source.Reply(READ_ONLY_MODE);
source.Reply(_("Regex is enabled."));
return;
}
}
else
this->OnSyntaxError(source, "ADD");
service_reference<RegexProvider> provider("Regex", Config->RegexEngine);
if (!provider)
{
source.Reply(_("Unable to find regex engine %s"), Config->RegexEngine.c_str());
return;
}
return;
try
{
Anope::string stripped_mask = mask.substr(1, mask.length() - 2);
delete provider->Compile(stripped_mask);
}
catch (const RegexException &ex)
{
source.Reply("%s", ex.GetReason().c_str());
return;
}
}
if (!this->sqlines->CanAdd(source, mask, expires, reason))
return;
else if (mask.find_first_not_of("./?*") == Anope::string::npos)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
return;
}
XLine *x = new XLine(mask, u->nick, expires, reason);
if (Config->AkillIds)
x->UID = XLineManager::GenerateUID();
unsigned int affected = 0;
for (Anope::insensitive_map<User *>::iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (this->xlm()->Check(it->second, x))
++affected;
float percent = static_cast<float>(affected) / static_cast<float>(UserListByNick.size()) * 100.0;
if (percent > 95)
{
source.Reply(USERHOST_MASK_TOO_WIDE, mask.c_str());
Log(LOG_ADMIN, u, this) << "tried to SQLine " << percent << "% of the network (" << affected << " users)";
delete x;
return;
}
EventReturn MOD_RESULT;
FOREACH_RESULT(I_OnAddXLine, OnAddXLine(u, x, this->xlm()));
if (MOD_RESULT == EVENT_STOP)
{
delete x;
return;
}
this->xlm()->AddXLine(x);
if (Config->KillonSQline)
{
Anope::string rreason = "Q-Lined: " + reason;
if (mask[0] == '#')
{
for (channel_map::const_iterator cit = ChannelList.begin(), cit_end = ChannelList.end(); cit != cit_end; ++cit)
{
Channel *c = cit->second;
if (!Anope::Match(c->name, mask, false, true))
continue;
for (CUserList::iterator it = c->users.begin(), it_end = c->users.end(); it != it_end; )
{
UserContainer *uc = *it;
++it;
if (uc->user->HasMode(UMODE_OPER) || uc->user->server == Me)
continue;
c->Kick(NULL, uc->user, "%s", reason.c_str());
}
}
}
else
{
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end();)
{
User *user = it->second;
++it;
if (!user->HasMode(UMODE_OPER) && user->server != Me && Anope::Match(user->nick, x->Mask, false, true))
user->Kill(Config->ServerName, rreason);
}
}
this->xlm()->Send(u, x);
}
source.Reply(_("\002%s\002 added to the SQLINE list."), mask.c_str());
Log(LOG_ADMIN, u, this) << "on " << mask << " (" << reason << ") expires in " << (expires ? duration(expires - Anope::CurTime) : "never") << " [affects " << affected << " user(s) (" << percent << "%)]";
if (readonly)
source.Reply(READ_ONLY_MODE);
}
service_reference<XLineManager> sqlines;
@@ -607,8 +654,7 @@ class CommandOSSQLine : public CommandOSSXLineBase
"connect, Services will not allow it to pursue his IRC\n"
"session.\n"
"If the first character of the mask is #, services will \n"
"prevent the use of matching channels (on IRCds that \n"
"support it).\n"));
"prevent the use of matching channels."));
source.Reply(_(" \n"
"\002SQLINE ADD\002 adds the given (nick's) mask to the SQLINE\n"
"list for the given reason (which \002must\002 be given).\n"
@@ -621,6 +667,10 @@ class CommandOSSQLine : public CommandOSSXLineBase
"must be given, even if it is the same as the default. The\n"
"current SQLINE default expiry time can be found with the\n"
"\002STATS AKILL\002 command.\n"));
if (!Config->RegexEngine.empty())
source.Reply(" \n"
"Regex matches are also supported using the %s engine.\n"
"Enclose your mask in // if this desired.", Config->RegexEngine.c_str());
source.Reply(_(" \n"
"The \002SQLINE DEL\002 command removes the given mask from the\n"
"SQLINE list if it is present. If a list of entry numbers is \n"
+55
View File
@@ -0,0 +1,55 @@
/* RequiredLibraries: pcre */
#include "module.h"
#include <pcre.h>
class PCRERegex : public Regex
{
pcre *regex;
public:
PCRERegex(const Anope::string &expr) : Regex(expr)
{
const char *error;
int erroffset;
this->regex = pcre_compile(expr.c_str(), PCRE_CASELESS, &error, &erroffset, NULL);
if (!this->regex)
throw RegexException("Error in regex " + expr + " at offset " + stringify(erroffset) + ": " + error);
}
~PCRERegex()
{
pcre_free(this->regex);
}
bool Matches(const Anope::string &str)
{
return pcre_exec(this->regex, NULL, str.c_str(), str.length(), 0, 0, NULL, 0) > -1;
}
};
class PCRERegexProvider : public RegexProvider
{
public:
PCRERegexProvider(Module *creator) : RegexProvider(creator, "regex/pcre") { }
Regex *Compile(const Anope::string &expression) anope_override
{
return new PCRERegex(expression);
}
};
class ModuleRegexPCRE : public Module
{
PCRERegexProvider pcre_regex_provider;
public:
ModuleRegexPCRE(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED),
pcre_regex_provider(this)
{
this->SetAuthor("Anope");
this->SetPermanent(true);
}
};
MODULE_INIT(ModuleRegexPCRE)
+57
View File
@@ -0,0 +1,57 @@
#include "module.h"
#include <sys/types.h>
#include <regex.h>
class POSIXRegex : public Regex
{
regex_t regbuf;
public:
POSIXRegex(const Anope::string &expr) : Regex(expr)
{
int err = regcomp(&this->regbuf, expr.c_str(), REG_EXTENDED | REG_NOSUB);
if (err)
{
char buf[BUFSIZE];
regerror(err, &this->regbuf, buf, sizeof(buf));
regfree(&this->regbuf);
throw RegexException("Error in regex " + expr + ": " + buf);
}
}
~POSIXRegex()
{
regfree(&this->regbuf);
}
bool Matches(const Anope::string &str)
{
return regexec(&this->regbuf, str.c_str(), 0, NULL, 0) == 0;
}
};
class POSIXRegexProvider : public RegexProvider
{
public:
POSIXRegexProvider(Module *creator) : RegexProvider(creator, "regex/posix") { }
Regex *Compile(const Anope::string &expression) anope_override
{
return new POSIXRegex(expression);
}
};
class ModuleRegexPOSIX : public Module
{
POSIXRegexProvider posix_regex_provider;
public:
ModuleRegexPOSIX(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED),
posix_regex_provider(this)
{
this->SetAuthor("Anope");
this->SetPermanent(true);
}
};
MODULE_INIT(ModuleRegexPOSIX)
+58
View File
@@ -0,0 +1,58 @@
/* RequiredLibraries: tre */
#include "module.h"
#include <tre/regex.h>
class TRERegex : public Regex
{
regex_t regbuf;
public:
TRERegex(const Anope::string &expr) : Regex(expr)
{
int err = regcomp(&this->regbuf, expr.c_str(), REG_EXTENDED | REG_NOSUB);
if (err)
{
char buf[BUFSIZE];
regerror(err, &this->regbuf, buf, sizeof(buf));
regfree(&this->regbuf);
throw RegexException("Error in regex " + expr + ": " + buf);
}
}
~TRERegex()
{
regfree(&this->regbuf);
}
bool Matches(const Anope::string &str)
{
return regexec(&this->regbuf, str.c_str(), 0, NULL, 0) == 0;
}
};
class TRERegexProvider : public RegexProvider
{
public:
TRERegexProvider(Module *creator) : RegexProvider(creator, "regex/tre") { }
Regex *Compile(const Anope::string &expression) anope_override
{
return new TRERegex(expression);
}
};
class ModuleRegexTRE : public Module
{
TRERegexProvider tre_regex_provider;
public:
ModuleRegexTRE(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED),
tre_regex_provider(this)
{
this->SetAuthor("Anope");
this->SetPermanent(true);
}
};
MODULE_INIT(ModuleRegexTRE)
+56 -6
View File
@@ -77,7 +77,7 @@ class BahamutIRCdProto : public IRCDProto
/* SQLINE */
void SendSQLine(User *, const XLine *x) anope_override
{
UplinkSocket::Message() << "SQLINE " << x->Mask << " :" << x->Reason;
UplinkSocket::Message() << "SQLINE " << x->Mask << " :" << x->GetReason();
}
/* UNSLINE */
@@ -103,9 +103,9 @@ class BahamutIRCdProto : public IRCDProto
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
/* this will likely fail so its only here for legacy */
UplinkSocket::Message() << "SZLINE " << x->GetHost() << " :" << x->Reason;
UplinkSocket::Message() << "SZLINE " << x->GetHost() << " :" << x->GetReason();
/* this is how we are supposed to deal with it */
UplinkSocket::Message() << "AKILL " << x->GetHost() << " * " << timeleft << " " << x->By << " " << Anope::CurTime << " :" << x->Reason;
UplinkSocket::Message() << "AKILL " << x->GetHost() << " * " << timeleft << " " << x->By << " " << Anope::CurTime << " :" << x->GetReason();
}
/* SVSNOOP */
@@ -117,12 +117,27 @@ class BahamutIRCdProto : public IRCDProto
/* SGLINE */
void SendSGLine(User *, const XLine *x) anope_override
{
UplinkSocket::Message() << "SGLINE " << x->Mask.length() << " :" << x->Mask << ":" << x->Reason;
UplinkSocket::Message() << "SGLINE " << x->Mask.length() << " :" << x->Mask << ":" << x->GetReason();
}
/* RAKILL */
void SendAkillDel(const XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
return;
/* ZLine if we can instead */
try
{
if (x->GetUser() == "*")
{
sockaddrs(x->GetHost());
ircdproto->SendSZLineDel(x);
return;
}
}
catch (const SocketException &) { }
UplinkSocket::Message() << "RAKILL " << x->GetHost() << " " << x->GetUser();
}
@@ -160,13 +175,48 @@ class BahamutIRCdProto : public IRCDProto
}
}
void SendAkill(User *, const XLine *x) anope_override
void SendAkill(User *u, XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
{
if (!u)
{
/* No user (this akill was just added), and contains nick and/or realname. Find users that match and ban them */
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (x->manager->Check(it->second, x))
this->SendAkill(it->second, x);
return;
}
XLine *old = x;
if (old->manager->HasEntry("*@" + u->host))
return;
/* We can't akill x as it has a nick and/or realname included, so create a new akill for *@host */
x = new XLine("*@" + u->host, old->By, old->Expires, old->Reason, old->UID);
old->manager->AddXLine(x);
Log(findbot(Config->OperServ), "akill") << "AKILL: Added an akill for " << x->Mask << " because " << u->GetMask() << "#" << u->realname << " matches " << old->Mask;
}
/* ZLine if we can instead */
try
{
if (x->GetUser() == "*")
{
sockaddrs(x->GetHost());
ircdproto->SendSZLine(u, x);
return;
}
}
catch (const SocketException &) { }
// Calculate the time left before this would expire, capping it at 2 days
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800)
timeleft = 172800;
UplinkSocket::Message() << "AKILL " << x->GetHost() << " " << x->GetUser() << " " << timeleft << " " << x->By << " " << Anope::CurTime << " :" << x->Reason;
UplinkSocket::Message() << "AKILL " << x->GetHost() << " " << x->GetUser() << " " << timeleft << " " << x->By << " " << Anope::CurTime << " :" << x->GetReason();
}
/*
+63 -2
View File
@@ -51,7 +51,22 @@ class InspIRCdTS6Proto : public IRCDProto
void SendAkillDel(const XLine *x) anope_override
{
UplinkSocket::Message(findbot(Config->OperServ)) << "GLINE " << x->Mask;
BotInfo *bi = findbot(Config->OperServ);
/* InspIRCd may support regex bans */
if (x->IsRegex() && has_rlinemod)
{
Anope::string mask = x->Mask;
size_t h = x->Mask.find('#');
if (h != Anope::string::npos)
mask = mask.replace(h, 1, ' ');
UplinkSocket::Message(bi) << "RLINE " << mask;
return;
}
else if (x->IsRegex() || x->HasNickOrReal())
return;
UplinkSocket::Message(bi) << "GLINE " << x->Mask;
}
void SendTopic(BotInfo *whosets, Channel *c) anope_override
@@ -70,13 +85,59 @@ class InspIRCdTS6Proto : public IRCDProto
this->SendChgIdentInternal(u->nick, u->GetIdent());
}
void SendAkill(User *, const XLine *x) anope_override
void SendAkill(User *u, XLine *x) anope_override
{
// Calculate the time left before this would expire, capping it at 2 days
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
BotInfo *bi = findbot(Config->OperServ);
/* InspIRCd may support regex bans, if they do we can send this and forget about it */
if (x->IsRegex() && has_rlinemod)
{
Anope::string mask = x->Mask;
size_t h = x->Mask.find('#');
if (h != Anope::string::npos)
mask = mask.replace(h, 1, ' ');
UplinkSocket::Message(bi) << "RLINE " << mask << " " << timeleft << " :" << x->Reason;
return;
}
else if (x->IsRegex() || x->HasNickOrReal())
{
if (!u)
{
/* No user (this akill was just added), and contains nick and/or realname. Find users that match and ban them */
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (x->manager->Check(it->second, x))
this->SendAkill(it->second, x);
return;
}
XLine *old = x;
if (old->manager->HasEntry("*@" + u->host))
return;
/* We can't akill x as it has a nick and/or realname included, so create a new akill for *@host */
x = new XLine("*@" + u->host, old->By, old->Expires, old->Reason, old->UID);
old->manager->AddXLine(x);
Log(bi, "akill") << "AKILL: Added an akill for " << x->Mask << " because " << u->GetMask() << "#" << u->realname << " matches " << old->Mask;
}
/* ZLine if we can instead */
try
{
if (x->GetUser() == "*")
{
sockaddrs(x->GetHost());
ircdproto->SendSZLine(u, x);
return;
}
}
catch (const SocketException &) { }
UplinkSocket::Message(bi) << "ADDLINE G " << x->GetUser() << "@" << x->GetHost() << " " << x->By << " " << Anope::CurTime << " " << timeleft << " :" << x->Reason;
}
+54 -4
View File
@@ -81,6 +81,21 @@ class InspIRCdProto : public IRCDProto
{
void SendAkillDel(const XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
return;
/* ZLine if we can instead */
try
{
if (x->GetUser() == "*")
{
sockaddrs(x->GetHost());
ircdproto->SendSZLineDel(x);
return;
}
}
catch (const SocketException &) { }
UplinkSocket::Message(findbot(Config->OperServ)) << "GLINE " << x->Mask;
}
@@ -100,13 +115,48 @@ class InspIRCdProto : public IRCDProto
inspircd_cmd_chgident(u->nick, u->GetIdent());
}
void SendAkill(User *, const XLine *x) anope_override
void SendAkill(User *u, XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
{
if (!u)
{
/* No user (this akill was just added), and contains nick and/or realname. Find users that match and ban them */
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (x->manager->Check(it->second, x))
this->SendAkill(it->second, x);
return;
}
XLine *old = x;
if (old->manager->HasEntry("*@" + u->host))
return;
/* We can't akill x as it has a nick and/or realname included, so create a new akill for *@host */
x = new XLine("*@" + u->host, old->By, old->Expires, old->Reason, old->UID);
old->manager->AddXLine(x);
Log(findbot(Config->OperServ), "akill") << "AKILL: Added an akill for " << x->Mask << " because " << u->GetMask() << "#" << u->realname << " matches " << old->Mask;
}
/* ZLine if we can instead */
try
{
if (x->GetUser() == "*")
{
sockaddrs(x->GetHost());
ircdproto->SendSZLine(u, x);
return;
}
}
catch (const SocketException &) { }
// Calculate the time left before this would expire, capping it at 2 days
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
UplinkSocket::Message(Me) << "ADDLINE G " << x->Mask << " " << x->By << " " << Anope::CurTime << " " << timeleft << " :" << x->Reason;
UplinkSocket::Message(Me) << "ADDLINE G " << x->Mask << " " << x->By << " " << Anope::CurTime << " " << timeleft << " :" << x->GetReason();
}
void SendSVSKillInternal(const BotInfo *source, const User *user, const Anope::string &buf) anope_override
@@ -194,7 +244,7 @@ class InspIRCdProto : public IRCDProto
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
UplinkSocket::Message(Me) << "ADDLINE Q " << x->Mask << " " << x->By << " " << Anope::CurTime << " " << timeleft << " :" << x->Reason;
UplinkSocket::Message(Me) << "ADDLINE Q " << x->Mask << " " << x->By << " " << Anope::CurTime << " " << timeleft << " :" << x->GetReason();
}
/* Functions that use serval cmd functions */
@@ -254,7 +304,7 @@ class InspIRCdProto : public IRCDProto
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
UplinkSocket::Message(Me) << "ADDLINE Z " << x->GetHost() << " " << x->By << " " << Anope::CurTime << " " << timeleft << " :" << x->Reason;
UplinkSocket::Message(Me) << "ADDLINE Z " << x->GetHost() << " " << x->By << " " << Anope::CurTime << " " << timeleft << " :" << x->GetReason();
}
void SendSVSJoin(const BotInfo *source, const Anope::string &nick, const Anope::string &chan, const Anope::string &) anope_override
+3
View File
@@ -17,6 +17,7 @@
static bool has_globopsmod = false;
static bool has_chghostmod = false;
static bool has_chgidentmod = false;
static bool has_rlinemod = false;
#include "inspircd-ts6.h"
IRCDVar myIrcd = {
@@ -369,6 +370,8 @@ class Inspircd12IRCdMessage : public InspircdIRCdMessage
has_hidechansmod = true;
if (params[1].find("m_servprotect.so") != Anope::string::npos)
ircd->pseudoclient_mode = "+Ik";
if (params[1].find("m_rline.so") != Anope::string::npos)
has_rlinemod = true;
}
else if (params[0].equals_cs("CAPABILITIES"))
{
+9
View File
@@ -17,6 +17,7 @@
static bool has_chghostmod = false;
static bool has_chgidentmod = false;
static bool has_globopsmod = true; // Not a typo
static bool has_rlinemod = false;
#include "inspircd-ts6.h"
IRCDVar myIrcd = {
@@ -556,8 +557,16 @@ class Inspircd20IRCdMessage : public InspircdIRCdMessage
Anope::string module;
while (ssep.GetToken(module))
{
if (module.equals_cs("m_svshold.so"))
has_svsholdmod = true;
else if (module.find("m_rline.so") == 0)
{
has_rlinemod = true;
if (!Config->RegexEngine.empty() && Config->RegexEngine != module.substr(11))
Log() << "Warning: InspIRCd is using regex engine " << module.substr(11) << ", but we have " << Config->RegexEngine << ". This may cause inconsistencies.";
}
}
}
else if (params[0].equals_cs("MODSUPPORT"))
{
+32 -5
View File
@@ -47,7 +47,7 @@ class PlexusProto : public IRCDProto
void SendSQLine(User *, const XLine *x) anope_override
{
UplinkSocket::Message(Me) << "RESV * " << x->Mask << " :" << x->Reason;
UplinkSocket::Message(Me) << "RESV * " << x->Mask << " :" << x->GetReason();
}
void SendSGLineDel(const XLine *x) anope_override
@@ -59,11 +59,14 @@ class PlexusProto : public IRCDProto
void SendSGLine(User *, const XLine *x) anope_override
{
BotInfo *bi = findbot(Config->OperServ);
UplinkSocket::Message(bi) << "XLINE * " << x->Mask << " 0 :" << x->Reason;
UplinkSocket::Message(bi) << "XLINE * " << x->Mask << " 0 :" << x->GetReason();
}
void SendAkillDel(const XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
return;
BotInfo *bi = findbot(Config->OperServ);
UplinkSocket::Message(bi) << "UNKLINE * " << x->GetUser() << " " << x->GetHost();
}
@@ -94,14 +97,38 @@ class PlexusProto : public IRCDProto
}
}
void SendAkill(User *, const XLine *x) anope_override
void SendAkill(User *u, XLine *x) anope_override
{
BotInfo *bi = findbot(Config->OperServ);
if (x->IsRegex() || x->HasNickOrReal())
{
if (!u)
{
/* No user (this akill was just added), and contains nick and/or realname. Find users that match and ban them */
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (x->manager->Check(it->second, x))
this->SendAkill(it->second, x);
return;
}
XLine *old = x;
if (old->manager->HasEntry("*@" + u->host))
return;
/* We can't akill x as it has a nick and/or realname included, so create a new akill for *@host */
x = new XLine("*@" + u->host, old->By, old->Expires, old->Reason, old->UID);
old->manager->AddXLine(x);
Log(bi, "akill") << "AKILL: Added an akill for " << x->Mask << " because " << u->GetMask() << "#" << u->realname << " matches " << old->Mask;
}
// Calculate the time left before this would expire, capping it at 2 days
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
BotInfo *bi = findbot(Config->OperServ);
UplinkSocket::Message(bi) << "KLINE * " << timeleft << " " << x->GetUser() << " " << x->GetHost() << " :" << x->Reason;
UplinkSocket::Message(bi) << "KLINE * " << timeleft << " " << x->GetUser() << " " << x->GetHost() << " :" << x->GetReason();
}
void SendServer(const Server *server) anope_override
+32 -5
View File
@@ -47,7 +47,7 @@ class RatboxProto : public IRCDProto
void SendSQLine(User *, const XLine *x) anope_override
{
UplinkSocket::Message(Me) << "RESV * " << x->Mask << " :" << x->Reason;
UplinkSocket::Message(Me) << "RESV * " << x->Mask << " :" << x->GetReason();
}
void SendSGLineDel(const XLine *x) anope_override
@@ -59,11 +59,14 @@ class RatboxProto : public IRCDProto
void SendSGLine(User *, const XLine *x) anope_override
{
BotInfo *bi = findbot(Config->OperServ);
UplinkSocket::Message(bi) << "XLINE * " << x->Mask << " 0 :" << x->Reason;
UplinkSocket::Message(bi) << "XLINE * " << x->Mask << " 0 :" << x->GetReason();
}
void SendAkillDel(const XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
return;
BotInfo *bi = findbot(Config->OperServ);
UplinkSocket::Message(bi) << "UNKLINE * " << x->GetUser() << " " << x->GetHost();
}
@@ -89,14 +92,38 @@ class RatboxProto : public IRCDProto
}
}
void SendAkill(User *, const XLine *x) anope_override
void SendAkill(User *u, XLine *x) anope_override
{
BotInfo *bi = findbot(Config->OperServ);
if (x->IsRegex() || x->HasNickOrReal())
{
if (!u)
{
/* No user (this akill was just added), and contains nick and/or realname. Find users that match and ban them */
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (x->manager->Check(it->second, x))
this->SendAkill(it->second, x);
return;
}
XLine *old = x;
if (old->manager->HasEntry("*@" + u->host))
return;
/* We can't akill x as it has a nick and/or realname included, so create a new akill for *@host */
x = new XLine("*@" + u->host, old->By, old->Expires, old->Reason, old->UID);
old->manager->AddXLine(x);
Log(bi, "akill") << "AKILL: Added an akill for " << x->Mask << " because " << u->GetMask() << "#" << u->realname << " matches " << old->Mask;
}
// Calculate the time left before this would expire, capping it at 2 days
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
BotInfo *bi = findbot(Config->OperServ);
UplinkSocket::Message(bi) << "KLINE * " << timeleft << " " << x->GetUser() << " " << x->GetHost() << " :" << x->Reason;
UplinkSocket::Message(bi) << "KLINE * " << timeleft << " " << x->GetUser() << " " << x->GetHost() << " :" << x->GetReason();
}
/* SERVER name hop descript */
+55 -5
View File
@@ -48,6 +48,21 @@ class UnrealIRCdProto : public IRCDProto
void SendAkillDel(const XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
return;
/* ZLine if we can instead */
try
{
if (x->GetUser() == "*")
{
sockaddrs(x->GetHost());
ircdproto->SendSZLineDel(x);
return;
}
}
catch (const SocketException &) { }
UplinkSocket::Message() << "BD - G " << x->GetUser() << " " << x->GetHost() << " " << Config->OperServ;
}
@@ -65,13 +80,48 @@ class UnrealIRCdProto : public IRCDProto
u->SetMode(bi, UMODE_CLOAK);
}
void SendAkill(User *, const XLine *x) anope_override
void SendAkill(User *u, XLine *x) anope_override
{
if (x->IsRegex() || x->HasNickOrReal())
{
if (!u)
{
/* No user (this akill was just added), and contains nick and/or realname. Find users that match and ban them */
for (Anope::insensitive_map<User *>::const_iterator it = UserListByNick.begin(); it != UserListByNick.end(); ++it)
if (x->manager->Check(it->second, x))
this->SendAkill(it->second, x);
return;
}
XLine *old = x;
if (old->manager->HasEntry("*@" + u->host))
return;
/* We can't akill x as it has a nick and/or realname included, so create a new akill for *@host */
x = new XLine("*@" + u->host, old->By, old->Expires, old->Reason, old->UID);
old->manager->AddXLine(x);
Log(findbot(Config->OperServ), "akill") << "AKILL: Added an akill for " << x->Mask << " because " << u->GetMask() << "#" << u->realname << " matches " << old->Mask;
}
/* ZLine if we can instead */
try
{
if (x->GetUser() == "*")
{
sockaddrs(x->GetHost());
ircdproto->SendSZLine(u, x);
return;
}
}
catch (const SocketException &) { }
// Calculate the time left before this would expire, capping it at 2 days
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
UplinkSocket::Message() << "BD + G " << x->GetUser() << " " << x->GetHost() << " " << x->By << " " << Anope::CurTime + timeleft << " " << x->Created << " :" << x->Reason;
UplinkSocket::Message() << "BD + G " << x->GetUser() << " " << x->GetHost() << " " << x->By << " " << Anope::CurTime + timeleft << " " << x->Created << " :" << x->GetReason();
}
void SendSVSKillInternal(const BotInfo *source, const User *user, const Anope::string &buf) anope_override
@@ -149,7 +199,7 @@ class UnrealIRCdProto : public IRCDProto
*/
void SendSQLine(User *, const XLine *x) anope_override
{
UplinkSocket::Message() << "c " << x->Mask << " :" << x->Reason;
UplinkSocket::Message() << "c " << x->Mask << " :" << x->GetReason();
}
/*
@@ -237,7 +287,7 @@ class UnrealIRCdProto : public IRCDProto
time_t timeleft = x->Expires - Anope::CurTime;
if (timeleft > 172800 || !x->Expires)
timeleft = 172800;
UplinkSocket::Message() << "BD + Z * " << x->GetHost() << " " << x->By << " " << Anope::CurTime + timeleft << " " << x->Created << " :" << x->Reason;
UplinkSocket::Message() << "BD + Z * " << x->GetHost() << " " << x->By << " " << Anope::CurTime + timeleft << " " << x->Created << " :" << x->GetReason();
}
/* SGLINE */
@@ -246,7 +296,7 @@ class UnrealIRCdProto : public IRCDProto
*/
void SendSGLine(User *, const XLine *x) anope_override
{
Anope::string edited_reason = x->Reason;
Anope::string edited_reason = x->GetReason();
edited_reason = edited_reason.replace_all_cs(" ", "_");
UplinkSocket::Message() << "BR + " << edited_reason << " :" << x->Mask;
}
+59 -42
View File
@@ -22,9 +22,9 @@ class SGLineManager : public XLineManager
void OnMatch(User *u, XLine *x) anope_override
{
this->Send(u, x);
if (u)
u->Kill(Config->OperServ, x->Reason);
this->Send(u, x);
}
void OnExpire(XLine *x) anope_override
@@ -34,19 +34,7 @@ class SGLineManager : public XLineManager
void Send(User *u, XLine *x) anope_override
{
try
{
if (!ircd->szline)
throw SocketException("SZLine is not supported");
else if (x->GetUser() != "*")
throw SocketException("Can not ZLine a username");
sockaddrs(x->GetHost());
ircdproto->SendSZLine(u, x);
}
catch (const SocketException &)
{
ircdproto->SendAkill(u, x);
}
ircdproto->SendAkill(u, x);
}
void SendDel(XLine *x) anope_override
@@ -65,6 +53,44 @@ class SGLineManager : public XLineManager
ircdproto->SendAkillDel(x);
}
}
bool Check(User *u, XLine *x) anope_override
{
if (x->regex)
{
Anope::string uh = u->GetIdent() + "@" + u->host, nuhr = u->nick + "!" + uh + "#" + u->realname;
if (x->regex->Matches(uh) || x->regex->Matches(nuhr))
return true;
return false;
}
if (!x->GetNick().empty() && !Anope::Match(u->nick, x->GetNick()))
return false;
if (!x->GetUser().empty() && !Anope::Match(u->GetIdent(), x->GetUser()))
return false;
if (!x->GetReal().empty() && !Anope::Match(u->realname, x->GetReal()))
return false;
if (!x->GetHost().empty())
{
try
{
cidr cidr_ip(x->GetHost());
sockaddrs ip(u->ip);
if (cidr_ip.match(ip))
return true;
}
catch (const SocketException &) { }
}
if (x->GetHost().empty() || Anope::Match(u->host, x->GetHost()))
return true;
return false;
}
};
class SQLineManager : public XLineManager
@@ -74,13 +100,13 @@ class SQLineManager : public XLineManager
void OnMatch(User *u, XLine *x) anope_override
{
this->Send(u, x);
if (u)
{
Anope::string reason = "Q-Lined: " + x->Reason;
u->Kill(Config->OperServ, reason);
}
this->Send(u, x);
}
void OnExpire(XLine *x) anope_override
@@ -98,12 +124,18 @@ class SQLineManager : public XLineManager
ircdproto->SendSQLineDel(x);
}
bool Check(User *u, XLine *x) anope_override
{
if (x->regex)
return x->regex->Matches(u->nick);
return Anope::Match(u->nick, x->Mask);
}
bool CheckChannel(Channel *c)
{
if (ircd->chansqline)
for (std::vector<XLine *>::const_iterator it = this->GetList().begin(), it_end = this->GetList().end(); it != it_end; ++it)
if (Anope::Match(c->name, (*it)->Mask))
return true;
for (std::vector<XLine *>::const_iterator it = this->GetList().begin(), it_end = this->GetList().end(); it != it_end; ++it)
if (Anope::Match(c->name, (*it)->Mask, false, true))
return true;
return false;
}
};
@@ -115,12 +147,13 @@ class SNLineManager : public XLineManager
void OnMatch(User *u, XLine *x) anope_override
{
this->Send(u, x);
if (u)
{
Anope::string reason = "G-Lined: " + x->Reason;
u->Kill(Config->OperServ, reason);
}
this->Send(u, x);
}
void OnExpire(XLine *x) anope_override
@@ -138,27 +171,11 @@ class SNLineManager : public XLineManager
ircdproto->SendSGLineDel(x);
}
XLine *Check(User *u) anope_override
bool Check(User *u, XLine *x) anope_override
{
for (unsigned i = this->GetList().size(); i > 0; --i)
{
XLine *x = this->GetList()[i - 1];
if (x->Expires && x->Expires < Anope::CurTime)
{
this->OnExpire(x);
this->DelXLine(x);
continue;
}
if (Anope::Match(u->realname, x->Mask))
{
this->OnMatch(u, x);
return x;
}
}
return NULL;
if (x->regex)
return x->regex->Matches(u->realname);
return Anope::Match(u->realname, x->Mask, false, true);
}
};
@@ -237,7 +254,7 @@ class OperServCore : public Module
void OnUserNickChange(User *u, const Anope::string &oldnick) anope_override
{
if (ircd->sqline && !u->HasMode(UMODE_OPER))
this->sqlines.Check(u);
this->sqlines.CheckAllXLines(u);
}
EventReturn OnCheckKick(User *u, ChannelInfo *ci, bool &kick) anope_override
+1 -1
View File
@@ -11,7 +11,6 @@
#include "services.h"
#include "config.h"
#include "module.h"
#include "extern.h"
#include "bots.h"
#include "access.h"
@@ -1157,6 +1156,7 @@ ConfigItems::ConfigItems(ServerConfig *conf)
{"options", "retrywait", "60", new ValueContainerInt(&conf->RetryWait), DT_INTEGER, ValidateNotZero},
{"options", "hideprivilegedcommands", "no", new ValueContainerBool(&conf->HidePrivilegedCommands), DT_BOOLEAN, NoValidation},
{"options", "nonicknameownership", "no", new ValueContainerBool(&conf->NoNicknameOwnership), DT_BOOLEAN | DT_NORELOAD, NoValidation},
{"options", "regexengine", "", new ValueContainerString(&conf->RegexEngine), DT_STRING, NoValidation},
{"nickserv", "name", "", new ValueContainerString(&conf->NickServ), DT_STRING, NoValidation},
{"nickserv", "emailregistration", "no", new ValueContainerBool(&conf->NSEmailReg), DT_BOOLEAN, NoValidation},
{"nickserv", "forceemail", "no", new ValueContainerBool(&conf->NSForceEmail), DT_BOOLEAN, ValidateEmailReg},
+38 -1
View File
@@ -18,6 +18,7 @@
#include "config.h"
#include "bots.h"
#include "language.h"
#include "regexpr.h"
#include <errno.h>
#include <sys/types.h>
@@ -570,10 +571,46 @@ std::vector<Anope::string> BuildStringVector(const Anope::string &src, char deli
/*************************************************************************/
bool Anope::Match(const Anope::string &str, const Anope::string &mask, bool case_sensitive)
bool Anope::Match(const Anope::string &str, const Anope::string &mask, bool case_sensitive, bool use_regex)
{
size_t s = 0, m = 0, str_len = str.length(), mask_len = mask.length();
if (use_regex && mask_len >= 2 && mask[0] == '/' && mask[mask.length() - 1] == '/')
{
Anope::string stripped_mask = mask.substr(1, mask_len - 2);
// This is often called with the same mask multiple times in a row, so cache it
static Regex *r = NULL;
if (r == NULL || r->GetExpression() != stripped_mask)
{
service_reference<RegexProvider> provider("Regex", Config->RegexEngine);
if (provider)
{
try
{
delete r;
r = NULL;
// This may throw
r = provider->Compile(stripped_mask);
}
catch (const RegexException &ex)
{
Log(LOG_DEBUG) << ex.GetReason();
}
}
else
{
delete r;
r = NULL;
}
}
if (r != NULL && r->Matches(str))
return true;
// Fall through to non regex match
}
while (s < str_len && m < mask_len && mask[m] != '*')
{
char string = str[s], wild = mask[m];
+1 -1
View File
@@ -462,7 +462,7 @@ void ModuleManager::UnloadAll()
{
std::vector<Anope::string> modules[MT_END];
for (std::list<Module *>::iterator it = Modules.begin(), it_end = Modules.end(); it != it_end; ++it)
if ((*it)->type != PROTOCOL)
if ((*it)->type != PROTOCOL && !(*it)->GetPermanent())
modules[(*it)->type].push_back((*it)->name);
for (size_t i = MT_BEGIN + 1; i != MT_END; ++i)
+140 -80
View File
@@ -16,19 +16,54 @@
#include "users.h"
#include "extern.h"
#include "sockets.h"
#include "regexpr.h"
#include "config.h"
#include "commands.h"
/* List of XLine managers we check users against in XLineManager::CheckAll */
std::list<XLineManager *> XLineManager::XLineManagers;
std::map<Anope::string, XLine *, ci::less> XLineManager::XLinesByUID;
std::multimap<Anope::string, XLine *, ci::less> XLineManager::XLinesByUID;
void XLine::InitRegex()
{
if (!Config->RegexEngine.empty() && this->Mask.length() >= 2 && this->Mask[0] == '/' && this->Mask[this->Mask.length() - 1] == '/')
{
Anope::string stripped_mask = this->Mask.substr(1, this->Mask.length() - 2);
service_reference<RegexProvider> provider("Regex", Config->RegexEngine);
if (provider)
{
try
{
this->regex = provider->Compile(stripped_mask);
}
catch (const RegexException &ex)
{
Log(LOG_DEBUG) << ex.GetReason();
}
}
}
}
XLine::XLine(const Anope::string &mask, const Anope::string &reason, const Anope::string &uid) : Mask(mask), Created(0), Expires(0), Reason(reason), UID(uid)
{
regex = NULL;
manager = NULL;
this->InitRegex();
}
XLine::XLine(const Anope::string &mask, const Anope::string &by, const time_t expires, const Anope::string &reason, const Anope::string &uid) : Mask(mask), By(by), Created(Anope::CurTime), Expires(expires), Reason(reason), UID(uid)
{
regex = NULL;
manager = NULL;
this->InitRegex();
}
XLine::~XLine()
{
delete regex;
}
Anope::string XLine::GetNick() const
@@ -47,7 +82,7 @@ Anope::string XLine::GetUser() const
if (host_t != Anope::string::npos)
{
if (user_t != Anope::string::npos)
if (user_t != Anope::string::npos && host_t > user_t)
return this->Mask.substr(user_t + 1, host_t - user_t - 1);
else
return this->Mask.substr(0, host_t);
@@ -58,12 +93,49 @@ Anope::string XLine::GetUser() const
Anope::string XLine::GetHost() const
{
size_t host_t = this->Mask.find('@');
size_t host_t = this->Mask.find('@'), real_t = this->Mask.find('#');
if (host_t == Anope::string::npos)
return this->Mask;
if (host_t != Anope::string::npos)
{
if (real_t != Anope::string::npos && real_t > host_t)
return this->Mask.substr(host_t + 1, real_t - host_t - 1);
else
return this->Mask.substr(host_t + 1);
}
else
return this->Mask.substr(host_t + 1);
return "";
}
Anope::string XLine::GetReal() const
{
size_t real_t = this->Mask.find('#');
if (real_t != Anope::string::npos)
return this->Mask.substr(real_t + 1);
else
return "";
}
Anope::string XLine::GetReason() const
{
Anope::string r = this->Reason;
if (Config->AddAkiller && !this->By.empty())
r = "[" + this->By + "] " + r;
if (!this->UID.empty())
r += " (ID: " + this->UID + ")";
return r;
}
bool XLine::HasNickOrReal() const
{
bool r = this->GetNick().find_first_not_of("?*") != Anope::string::npos;
r = r || this->GetReal().find_first_not_of("?*") != Anope::string::npos;
return r;
}
bool XLine::IsRegex() const
{
return !this->Mask.empty() && this->Mask[0] == '/' && this->Mask[this->Mask.length() - 1] == '/';
}
Anope::string XLine::serialize_name() const
@@ -128,40 +200,43 @@ void XLineManager::UnregisterXLineManager(XLineManager *xlm)
* @param u The user
* @return A pair of the XLineManager the user was found in and the XLine they matched, both may be NULL for no match
*/
std::pair<XLineManager *, XLine *> XLineManager::CheckAll(User *u)
void XLineManager::CheckAll(User *u)
{
std::pair<XLineManager *, XLine *> ret;
ret.first = NULL;
ret.second = NULL;
for (std::list<XLineManager *>::iterator it = XLineManagers.begin(), it_end = XLineManagers.end(); it != it_end; ++it)
{
XLineManager *xlm = *it;
XLine *x = xlm->Check(u);
XLine *x = xlm->CheckAllXLines(u);
if (x)
{
ret.first = xlm;
ret.second = x;
xlm->OnMatch(u, x);
break;
}
}
return ret;
}
Anope::string XLineManager::GenerateUID()
{
Anope::string id;
for (int i = 0; i < 10; ++i)
int count = 0;
while (id.empty() || XLinesByUID.count(id) > 0)
{
char c;
do
c = (random() % 75) + 48;
while (!isupper(c) && !isdigit(c));
id += c;
if (++count > 10)
{
id.clear();
Log(LOG_DEBUG) << "Unable to generate XLine UID";
break;
}
for (int i = 0; i < 10; ++i)
{
char c;
do
c = (random() % 75) + 48;
while (!isupper(c) && !isdigit(c));
id += c;
}
}
return id;
@@ -212,7 +287,7 @@ void XLineManager::AddXLine(XLine *x)
{
x->manager = this;
if (!x->UID.empty())
XLinesByUID[x->UID] = x;
XLinesByUID.insert(std::make_pair(x->UID, x));
this->XLines.push_back(x);
}
@@ -225,7 +300,15 @@ bool XLineManager::DelXLine(XLine *x)
std::vector<XLine *>::iterator it = std::find(this->XLines.begin(), this->XLines.end(), x);
if (!x->UID.empty())
XLinesByUID.erase(x->UID);
{
std::multimap<Anope::string, XLine *, ci::less>::iterator it2 = XLinesByUID.find(x->UID), it3 = XLinesByUID.upper_bound(x->UID);
for (; it2 != XLinesByUID.end() && it2 != it3; ++it2)
if (it2->second == x)
{
XLinesByUID.erase(it2);
break;
}
}
if (it != this->XLines.end())
{
@@ -266,53 +349,57 @@ void XLineManager::Clear()
}
/** Checks if a mask can/should be added to the XLineManager
* @param source The source adding the mask.
* @param mask The mask
* @param expires When the mask would expire
* @return A pair of int and XLine*.
* 1 - Mask already exists
* 2 - Mask already exists, but the expiry time was changed
* 3 - Mask is already covered by another mask
* In each case the XLine it matches/is covered by is returned in XLine*
* @param reason the reason
* @return true if the mask can be added
*/
std::pair<int, XLine *> XLineManager::CanAdd(const Anope::string &mask, time_t expires)
bool XLineManager::CanAdd(CommandSource &source, const Anope::string &mask, time_t expires, const Anope::string &reason)
{
std::pair<int, XLine *> ret;
ret.first = 0;
ret.second = NULL;
for (unsigned i = this->GetCount(); i > 0; --i)
{
XLine *x = this->GetEntry(i - 1);
ret.second = x;
if (x->Mask.equals_ci(mask))
{
if (!x->Expires || x->Expires >= expires)
{
ret.first = 1;
break;
if (x->Reason != reason)
{
x->Reason = reason;
source.Reply(_("Reason for %s updated."), x->Mask.c_str());
}
else
source.Reply(_("%s already exists."), mask.c_str());
}
else
{
x->Expires = expires;
ret.first = 2;
break;
if (x->Reason != reason)
{
x->Reason = reason;
source.Reply(_("Expiry and reason updated for %s."), x->Mask.c_str());
}
else
source.Reply(_("Expiry for %s updated."), x->Mask.c_str());
}
return false;
}
else if (Anope::Match(mask, x->Mask) && (!x->Expires || x->Expires >= expires))
{
ret.first = 3;
break;
source.Reply(_("%s is already covered by %s."), mask.c_str(), x->Mask.c_str());
return false;
}
else if (Anope::Match(x->Mask, mask) && (!expires || x->Expires <= expires))
{
source.Reply(_("Removing %s because %s covers it."), x->Mask.c_str(), mask.c_str());
this->DelXLine(x);
}
}
return ret;
return true;
}
/** Checks if this list has an entry
@@ -322,8 +409,10 @@ std::pair<int, XLine *> XLineManager::CanAdd(const Anope::string &mask, time_t e
XLine *XLineManager::HasEntry(const Anope::string &mask)
{
std::map<Anope::string, XLine *, ci::less>::iterator it = XLinesByUID.find(mask);
if (it != XLinesByUID.end() && (it->second->manager == NULL || it->second->manager == this))
return it->second;
if (it != XLinesByUID.end())
for (std::map<Anope::string, XLine *, ci::less>::iterator it2 = XLinesByUID.upper_bound(mask); it != it2; ++it)
if (it->second->manager == NULL || it->second->manager == this)
return it->second;
for (unsigned i = 0, end = this->XLines.size(); i < end; ++i)
{
XLine *x = this->XLines[i];
@@ -337,9 +426,9 @@ XLine *XLineManager::HasEntry(const Anope::string &mask)
/** Check a user against all of the xlines in this XLineManager
* @param u The user
* @return The xline the user marches, if any. Also calls OnMatch()
* @return The xline the user marches, if any.
*/
XLine *XLineManager::Check(User *u)
XLine *XLineManager::CheckAllXLines(User *u)
{
for (unsigned i = this->XLines.size(); i > 0; --i)
{
@@ -352,30 +441,9 @@ XLine *XLineManager::Check(User *u)
continue;
}
if (!x->GetNick().empty() && !Anope::Match(u->nick, x->GetNick()))
continue;
if (!x->GetUser().empty() && !Anope::Match(u->GetIdent(), x->GetUser()))
continue;
if (!x->GetHost().empty())
if (this->Check(u, x))
{
try
{
cidr cidr_ip(x->GetHost());
sockaddrs ip(u->ip);
if (cidr_ip.match(ip))
{
OnMatch(u, x);
return x;
}
}
catch (const SocketException &) { }
}
if (x->GetHost().empty() || (Anope::Match(u->host, x->GetHost()) || (!u->chost.empty() && Anope::Match(u->chost, x->GetHost())) || (!u->vhost.empty() && Anope::Match(u->vhost, x->GetHost()))))
{
OnMatch(u, x);
this->OnMatch(u, x);
return x;
}
}
@@ -383,14 +451,6 @@ XLine *XLineManager::Check(User *u)
return NULL;
}
/** Called when a user matches a xline in this XLineManager
* @param u The user
* @param x The XLine they match
*/
void XLineManager::OnMatch(User *u, XLine *x)
{
}
/** Called when an XLine expires
* @param x The xline
*/