mirror of
https://github.com/anope/anope.git
synced 2026-06-25 12:36:38 +02:00
877 lines
20 KiB
C++
877 lines
20 KiB
C++
// Anope IRC Services <https://www.anope.org/>
|
|
//
|
|
// Copyright (C) 2003-2025 Anope Contributors
|
|
//
|
|
// Anope is free software. You can use, modify, and/or distribute it under the
|
|
// terms of version 2 of the GNU General Public License. See docs/LICENSE.txt
|
|
// for the complete terms of this license and docs/AUTHORS.txt for a list of
|
|
// contributors.
|
|
//
|
|
// Based on the original code of Epona by Lara
|
|
// Based on the original code of Services by Andy Church
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
#include "services.h"
|
|
#include "modules.h"
|
|
#include "users.h"
|
|
#include "account.h"
|
|
#include "protocol.h"
|
|
#include "servers.h"
|
|
#include "channels.h"
|
|
#include "bots.h"
|
|
#include "config.h"
|
|
#include "opertype.h"
|
|
#include "language.h"
|
|
#include "sockets.h"
|
|
#include "uplink.h"
|
|
|
|
user_map UserListByNick, UserListByUID;
|
|
|
|
size_t OperCount = 0;
|
|
size_t MaxUserCount = 0;
|
|
time_t MaxUserTime = 0;
|
|
|
|
std::list<User *> User::quitting_users;
|
|
|
|
User::User(const Anope::string &snick, const Anope::string &sident, const Anope::string &shost, const Anope::string &svhost, const Anope::string &uip, Server *sserver, const Anope::string &srealname, time_t ts, const Anope::string &smodes, const std::vector<Anope::string> &smodeparams, const Anope::string &suid, NickCore *account)
|
|
: ip(uip)
|
|
{
|
|
if (snick.empty() || sident.empty() || shost.empty())
|
|
throw CoreException("Bad args passed to User::User");
|
|
|
|
/* we used to do this by calloc, no more. */
|
|
quit = false;
|
|
server = NULL;
|
|
invalid_pw_count = invalid_pw_time = lastmemosend = lastnickreg = lastmail = 0;
|
|
|
|
this->nick = snick;
|
|
this->ident = sident;
|
|
this->host = shost;
|
|
this->vhost = svhost;
|
|
this->chost = svhost;
|
|
this->server = sserver;
|
|
this->realname = srealname;
|
|
this->timestamp = this->signon = ts;
|
|
this->SetModesInternal(sserver, smodes, smodeparams);
|
|
this->uid = suid;
|
|
this->super_admin = false;
|
|
this->nc = NULL;
|
|
|
|
size_t old = UserListByNick.size();
|
|
UserListByNick[snick] = this;
|
|
if (!suid.empty())
|
|
UserListByUID[suid] = this;
|
|
if (old == UserListByNick.size())
|
|
Log(LOG_DEBUG) << "Duplicate user " << snick << " in user table?";
|
|
|
|
this->Login(account);
|
|
this->UpdateHost();
|
|
|
|
if (sserver) // Our bots are introduced on startup with no server
|
|
{
|
|
++sserver->users;
|
|
if (server->IsSynced())
|
|
Log(this, "connect") << (!vhost.empty() && vhost != host ? "(" + vhost + ") " : "") << "(" << srealname << ") " << (!uip.empty() && uip != host ? "[" + uip + "] " : "") << "connected to the network (" << sserver->GetName() << ")";
|
|
}
|
|
|
|
if (UserListByNick.size() > MaxUserCount)
|
|
{
|
|
MaxUserCount = UserListByNick.size();
|
|
MaxUserTime = Anope::CurTime;
|
|
if (sserver && sserver->IsSynced())
|
|
Log(this, "maxusers") << "connected - new maximum user count: " << UserListByNick.size();
|
|
}
|
|
|
|
bool exempt = false;
|
|
if (server && server->IsULined())
|
|
exempt = true;
|
|
FOREACH_MOD(OnUserConnect, (this, exempt));
|
|
}
|
|
|
|
static void CollideKill(User *target, const Anope::string &reason)
|
|
{
|
|
if (target->server != Me)
|
|
target->Kill(Me, reason);
|
|
else
|
|
{
|
|
// Be sure my user is really dead
|
|
IRCD->SendQuit(target, reason);
|
|
|
|
// Reintroduce my client
|
|
if (BotInfo *bi = dynamic_cast<BotInfo *>(target))
|
|
bi->OnKill();
|
|
else
|
|
target->Quit(reason);
|
|
}
|
|
}
|
|
|
|
static void Collide(User *u, const Anope::string &id, const Anope::string &type)
|
|
{
|
|
// Kill incoming user
|
|
IRCD->SendKill(Me, id, type);
|
|
// Quit colliding user
|
|
CollideKill(u, type);
|
|
}
|
|
|
|
User *User::OnIntroduce(const Anope::string &snick, const Anope::string &sident, const Anope::string &shost, const Anope::string &svhost, const Anope::string &sip, Server *sserver, const Anope::string &srealname, time_t ts, const Anope::string &smodes, const Anope::string &suid, NickCore *nc, const std::vector<Anope::string> &smodeparams)
|
|
{
|
|
// How IRCds handle collisions varies a lot, for safety well just always kill both sides
|
|
// With properly set qlines, this can almost never happen anyway
|
|
|
|
User *u = User::Find(snick, true);
|
|
if (u)
|
|
{
|
|
Collide(u, !suid.empty() ? suid : snick, "Nick collision");
|
|
return NULL;
|
|
}
|
|
|
|
if (!suid.empty())
|
|
{
|
|
u = User::Find(suid);
|
|
if (u)
|
|
{
|
|
Collide(u, suid, "ID collision");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return new User(snick, sident, shost, svhost, sip, sserver, srealname, ts, smodes, smodeparams, suid, nc);
|
|
}
|
|
|
|
void User::ChangeNick(const Anope::string &newnick, time_t ts)
|
|
{
|
|
/* Sanity check to make sure we don't segfault */
|
|
if (newnick.empty())
|
|
throw CoreException("User::ChangeNick() got a bad argument");
|
|
|
|
this->super_admin = false;
|
|
Log(this, "nick") << "(" << this->realname << ") changed nick to " << newnick;
|
|
|
|
Anope::string old = this->nick;
|
|
this->timestamp = ts;
|
|
|
|
if (this->nick.equals_ci(newnick))
|
|
this->nick = newnick;
|
|
else
|
|
{
|
|
NickAlias *old_na = NickAlias::Find(this->nick);
|
|
if (old_na && this->IsIdentified(true))
|
|
old_na->last_seen = Anope::CurTime;
|
|
|
|
UserListByNick.erase(this->nick);
|
|
|
|
this->nick = newnick;
|
|
|
|
User *&other = UserListByNick[this->nick];
|
|
if (other)
|
|
{
|
|
CollideKill(this, "Nick collision");
|
|
CollideKill(other, "Nick collision");
|
|
return;
|
|
}
|
|
other = this;
|
|
|
|
NickAlias *na = NickAlias::Find(this->nick);
|
|
if (na && na->nc == this->Account())
|
|
{
|
|
na->last_seen = Anope::CurTime;
|
|
this->UpdateHost();
|
|
}
|
|
}
|
|
|
|
FOREACH_MOD(OnUserNickChange, (this, old));
|
|
}
|
|
|
|
void User::SetDisplayedHost(const Anope::string &shost)
|
|
{
|
|
if (shost.empty())
|
|
throw CoreException("empty host? in MY services? it seems it's more likely than I thought.");
|
|
|
|
this->vhost = shost;
|
|
|
|
Log(this, "host") << "changed vhost to " << shost;
|
|
|
|
FOREACH_MOD(OnSetDisplayedHost, (this));
|
|
|
|
this->UpdateHost();
|
|
}
|
|
|
|
const Anope::string &User::GetDisplayedHost() const
|
|
{
|
|
if (!this->vhost.empty())
|
|
return this->vhost;
|
|
else if (this->HasMode("CLOAK") && !this->GetCloakedHost().empty())
|
|
return this->GetCloakedHost();
|
|
else
|
|
return this->host;
|
|
}
|
|
|
|
void User::SetCloakedHost(const Anope::string &newhost)
|
|
{
|
|
if (newhost.empty())
|
|
throw CoreException("empty host in User::SetCloakedHost");
|
|
|
|
chost = newhost;
|
|
|
|
Log(this, "host") << "changed cloaked host to " << newhost;
|
|
|
|
this->UpdateHost();
|
|
}
|
|
|
|
const Anope::string &User::GetCloakedHost() const
|
|
{
|
|
return chost;
|
|
}
|
|
|
|
const Anope::string &User::GetUID() const
|
|
{
|
|
if (!this->uid.empty() && IRCD->RequiresID)
|
|
return this->uid;
|
|
else
|
|
return this->nick;
|
|
}
|
|
|
|
void User::SetVIdent(const Anope::string &sident)
|
|
{
|
|
this->vident = sident;
|
|
|
|
Log(this, "ident") << "changed vident to " << sident;
|
|
|
|
this->UpdateHost();
|
|
}
|
|
|
|
const Anope::string &User::GetVIdent() const
|
|
{
|
|
if (!this->vident.empty())
|
|
return this->vident;
|
|
else
|
|
return this->ident;
|
|
}
|
|
|
|
void User::SetIdent(const Anope::string &sident)
|
|
{
|
|
this->ident = sident;
|
|
|
|
Log(this, "ident") << "changed real ident to " << sident;
|
|
|
|
this->UpdateHost();
|
|
}
|
|
|
|
const Anope::string &User::GetIdent() const
|
|
{
|
|
return this->ident;
|
|
}
|
|
|
|
Anope::string User::GetMask() const
|
|
{
|
|
return this->nick + "!" + this->ident + "@" + this->host;
|
|
}
|
|
|
|
Anope::string User::GetDisplayedMask() const
|
|
{
|
|
return this->nick + "!" + this->GetVIdent() + "@" + this->GetDisplayedHost();
|
|
}
|
|
|
|
void User::SetRealname(const Anope::string &srealname)
|
|
{
|
|
if (srealname.empty())
|
|
throw CoreException("realname empty in SetRealname");
|
|
|
|
this->realname = srealname;
|
|
Log(this, "realname") << "changed realname to " << srealname;
|
|
}
|
|
|
|
User::~User()
|
|
{
|
|
UnsetExtensibles();
|
|
|
|
if (this->server != NULL)
|
|
{
|
|
if (this->server->IsSynced())
|
|
Log(this, "disconnect") << "(" << this->realname << ") disconnected from the network (" << this->server->GetName() << ")";
|
|
--this->server->users;
|
|
}
|
|
|
|
FOREACH_MOD(OnPreUserLogoff, (this));
|
|
|
|
ModeManager::StackerDel(this);
|
|
this->Logout();
|
|
|
|
if (this->HasMode("OPER"))
|
|
--OperCount;
|
|
|
|
while (!this->chans.empty())
|
|
this->chans.begin()->second->chan->DeleteUser(this);
|
|
|
|
UserListByNick.erase(this->nick);
|
|
if (!this->uid.empty())
|
|
UserListByUID.erase(this->uid);
|
|
|
|
FOREACH_MOD(OnPostUserLogoff, (this));
|
|
}
|
|
|
|
void User::SendMessage(BotInfo *source, const char *fmt, ...)
|
|
{
|
|
const char *translated_message = Language::Translate(this, fmt);
|
|
|
|
Anope::string buf;
|
|
ANOPE_FORMAT(fmt, translated_message, buf);
|
|
this->SendMessage(source, buf);
|
|
}
|
|
|
|
void User::SendMessage(BotInfo *source, int count, const char *singular, const char *plural, ...)
|
|
{
|
|
const char *translated_message = Language::Translate(this, count, singular, plural);
|
|
|
|
Anope::string buf;
|
|
ANOPE_FORMAT(plural, translated_message, buf);
|
|
this->SendMessage(source, buf);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
void SendMessageInternal(BotInfo *source, User *target, const Anope::string &msg, const Anope::map<Anope::string> &tags)
|
|
{
|
|
LineWrapper lw(Language::Translate(target, msg.c_str()));
|
|
for (Anope::string line; lw.GetLine(line); )
|
|
{
|
|
if (target->ShouldPrivmsg())
|
|
IRCD->SendPrivmsg(source, target->GetUID(), line, tags);
|
|
else
|
|
IRCD->SendNotice(source, target->GetUID(), line, tags);
|
|
}
|
|
}
|
|
}
|
|
|
|
void User::SendMessage(BotInfo *source, const Anope::string &msg)
|
|
{
|
|
SendMessageInternal(source, this, msg, {});
|
|
}
|
|
|
|
void User::SendMessage(CommandSource &source, const Anope::string &msg)
|
|
{
|
|
Anope::map<Anope::string> tags;
|
|
if (!source.msgid.empty())
|
|
tags["+draft/reply"] = source.msgid;
|
|
|
|
SendMessageInternal(*source.service, this, msg, tags);
|
|
}
|
|
|
|
void User::Identify(NickAlias *na)
|
|
{
|
|
if (this->nick.equals_ci(na->nick))
|
|
na->UpdateSeen(this);
|
|
|
|
IRCD->SendLogin(this, na);
|
|
|
|
this->Login(na->nc);
|
|
|
|
FOREACH_MOD(OnNickIdentify, (this));
|
|
|
|
if (this->IsServicesOper())
|
|
{
|
|
if (!this->nc->o->ot->modes.empty())
|
|
{
|
|
auto *um = ModeManager::FindUserModeByName("OPER");
|
|
if (um && !this->HasMode("OPER") && this->nc->o->ot->modes.find(um->mchar) != Anope::string::npos)
|
|
IRCD->SendOper(this);
|
|
|
|
this->SetModes(NULL, this->nc->o->ot->modes);
|
|
this->SendMessage(NULL, _("Changing your usermodes to \002%s\002"), this->nc->o->ot->modes.c_str());
|
|
}
|
|
if (IRCD->CanSetVHost && !this->nc->o->vhost.empty())
|
|
{
|
|
this->SendMessage(NULL, _("Changing your vhost to \002%s\002"), this->nc->o->vhost.c_str());
|
|
this->SetDisplayedHost(this->nc->o->vhost);
|
|
IRCD->SendVHost(this, "", this->nc->o->vhost);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void User::Login(NickCore *core)
|
|
{
|
|
if (!core || core == this->nc)
|
|
return;
|
|
|
|
this->Logout();
|
|
this->nc = core;
|
|
core->users.push_back(this);
|
|
|
|
this->UpdateHost();
|
|
|
|
if (this->server->IsSynced())
|
|
Log(this, "account") << "is now identified as " << this->nc->display;
|
|
|
|
FOREACH_MOD(OnUserLogin, (this));
|
|
}
|
|
|
|
void User::Logout()
|
|
{
|
|
if (!this->nc)
|
|
return;
|
|
|
|
Log(this, "account") << "is no longer identified as " << this->nc->display;
|
|
|
|
std::list<User *>::iterator it = std::find(this->nc->users.begin(), this->nc->users.end(), this);
|
|
if (it != this->nc->users.end())
|
|
this->nc->users.erase(it);
|
|
|
|
this->nc = NULL;
|
|
}
|
|
|
|
NickCore *User::Account() const
|
|
{
|
|
return this->nc;
|
|
}
|
|
|
|
NickAlias *User::AccountNick() const
|
|
{
|
|
return this->nc ? this->nc->na : nullptr;
|
|
}
|
|
|
|
bool User::IsIdentified(bool check_nick) const
|
|
{
|
|
if (check_nick && this->nc)
|
|
{
|
|
NickAlias *na = NickAlias::Find(this->nick);
|
|
return na && *na->nc == *this->nc;
|
|
}
|
|
|
|
return this->nc;
|
|
}
|
|
|
|
bool User::IsSecurelyConnected() const
|
|
{
|
|
return HasMode("SSL") || HasExt("ssl");
|
|
}
|
|
|
|
bool User::IsServicesOper()
|
|
{
|
|
if (!this->nc || !this->nc->IsServicesOper())
|
|
return false; // Account isn't a services oper.
|
|
|
|
auto *oper = this->nc->o;
|
|
if (oper->require_oper && !this->HasMode("OPER"))
|
|
return false; // User isn't an ircd oper.
|
|
|
|
if (!oper->certfp.empty())
|
|
{
|
|
bool match = false;
|
|
for (const auto &certfp : oper->certfp)
|
|
{
|
|
if (this->fingerprint == certfp)
|
|
{
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!match)
|
|
return false; // Wrong TLS fingerprint.
|
|
}
|
|
|
|
if (!oper->hosts.empty())
|
|
{
|
|
bool match = false;
|
|
Anope::string match_host = this->GetIdent() + "@" + this->host;
|
|
Anope::string match_ip = this->GetIdent() + "@" + this->ip.addr();
|
|
for (const auto &userhost : oper->hosts)
|
|
{
|
|
if (Anope::Match(match_host, userhost) || Anope::Match(match_ip, userhost))
|
|
{
|
|
match = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!match)
|
|
return false; // Wrong user@host/ip.
|
|
}
|
|
|
|
EventReturn MOD_RESULT;
|
|
FOREACH_RESULT(IsServicesOper, MOD_RESULT, (this));
|
|
return MOD_RESULT != EVENT_STOP;
|
|
}
|
|
|
|
bool User::HasCommand(const Anope::string &command)
|
|
{
|
|
if (this->IsServicesOper())
|
|
return this->nc->o->ot->HasCommand(command);
|
|
return false;
|
|
}
|
|
|
|
bool User::HasPriv(const Anope::string &priv)
|
|
{
|
|
if (this->IsServicesOper())
|
|
return this->nc->o->ot->HasPriv(priv);
|
|
return false;
|
|
}
|
|
|
|
void User::UpdateHost()
|
|
{
|
|
if (this->host.empty())
|
|
return;
|
|
|
|
NickAlias *na = NickAlias::Find(this->nick);
|
|
if (na && this->IsIdentified(true))
|
|
na->UpdateSeen(this);
|
|
}
|
|
|
|
void User::SetAway(const Anope::string &msg, time_t ts)
|
|
{
|
|
FOREACH_MOD(OnUserAway, (this, msg));
|
|
if (msg.empty())
|
|
{
|
|
this->awaymsg.clear();
|
|
this->awaytime = 0;
|
|
Log(this, "away") << "is no longer away";
|
|
}
|
|
else
|
|
{
|
|
this->awaymsg = msg;
|
|
this->awaytime = ts ? ts : Anope::CurTime;
|
|
Log(this, "away") << "is now away: " << msg;
|
|
}
|
|
}
|
|
|
|
|
|
bool User::HasMode(const Anope::string &mname) const
|
|
{
|
|
return this->modes.count(mname);
|
|
}
|
|
|
|
void User::SetModeInternal(const MessageSource &source, UserMode *um, const ModeData &data)
|
|
{
|
|
if (!um)
|
|
return;
|
|
|
|
this->modes[um->name] = data;
|
|
|
|
if (um->name == "OPER")
|
|
{
|
|
++OperCount;
|
|
|
|
if (this->IsServicesOper())
|
|
{
|
|
if (!this->nc->o->ot->modes.empty())
|
|
{
|
|
auto *oper = ModeManager::FindUserModeByName("OPER");
|
|
if (oper && !this->HasMode("OPER") && this->nc->o->ot->modes.find(oper->mchar) != Anope::string::npos)
|
|
IRCD->SendOper(this);
|
|
|
|
this->SetModes(NULL, this->nc->o->ot->modes);
|
|
this->SendMessage(NULL, _("Changing your usermodes to \002%s\002"), this->nc->o->ot->modes.c_str());
|
|
}
|
|
if (IRCD->CanSetVHost && !this->nc->o->vhost.empty())
|
|
{
|
|
this->SendMessage(NULL, _("Changing your vhost to \002%s\002"), this->nc->o->vhost.c_str());
|
|
this->SetDisplayedHost(this->nc->o->vhost);
|
|
IRCD->SendVHost(this, "", this->nc->o->vhost);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (um->name == "CLOAK" || um->name == "VHOST")
|
|
this->UpdateHost();
|
|
|
|
FOREACH_MOD(OnUserModeSet, (source, this, um->name));
|
|
}
|
|
|
|
void User::RemoveModeInternal(const MessageSource &source, UserMode *um)
|
|
{
|
|
if (!um)
|
|
return;
|
|
|
|
this->modes.erase(um->name);
|
|
|
|
if (um->name == "OPER")
|
|
{
|
|
--OperCount;
|
|
|
|
// Don't let people de-oper and remain a SuperAdmin
|
|
this->super_admin = false;
|
|
}
|
|
|
|
if (um->name == "CLOAK" || um->name == "VHOST")
|
|
{
|
|
this->vhost.clear();
|
|
this->UpdateHost();
|
|
}
|
|
|
|
FOREACH_MOD(OnUserModeUnset, (source, this, um->name));
|
|
}
|
|
|
|
void User::SetMode(BotInfo *bi, UserMode *um, const ModeData &data)
|
|
{
|
|
if (!um || HasMode(um->name))
|
|
return;
|
|
|
|
ModeManager::StackerAdd(bi, this, um, true, data);
|
|
SetModeInternal(bi, um, data);
|
|
}
|
|
|
|
void User::SetMode(BotInfo *bi, const Anope::string &uname, const ModeData &data)
|
|
{
|
|
SetMode(bi, ModeManager::FindUserModeByName(uname), data);
|
|
}
|
|
|
|
void User::RemoveMode(BotInfo *bi, UserMode *um, const Anope::string ¶m)
|
|
{
|
|
if (!um || !HasMode(um->name))
|
|
return;
|
|
|
|
ModeManager::StackerAdd(bi, this, um, false, param);
|
|
RemoveModeInternal(bi, um);
|
|
}
|
|
|
|
void User::RemoveMode(BotInfo *bi, const Anope::string &name, const Anope::string ¶m)
|
|
{
|
|
RemoveMode(bi, ModeManager::FindUserModeByName(name), param);
|
|
}
|
|
|
|
void User::SetModes(BotInfo *bi, const char *umodes, ...)
|
|
{
|
|
Anope::string buf;
|
|
ANOPE_FORMAT(umodes, umodes, buf);
|
|
SetModes(bi, buf);
|
|
}
|
|
|
|
void User::SetModes(BotInfo *bi, const Anope::string &umodes)
|
|
{
|
|
Anope::string modebuf, sbuf;
|
|
int add = -1;
|
|
spacesepstream sep(umodes);
|
|
sep.GetToken(modebuf);
|
|
for (auto mode : modebuf)
|
|
{
|
|
UserMode *um;
|
|
|
|
switch (mode)
|
|
{
|
|
case '+':
|
|
add = 1;
|
|
continue;
|
|
case '-':
|
|
add = 0;
|
|
continue;
|
|
default:
|
|
if (add == -1)
|
|
continue;
|
|
um = ModeManager::FindUserModeByChar(mode);
|
|
if (!um)
|
|
continue;
|
|
}
|
|
|
|
if (add)
|
|
{
|
|
if (um->type == MODE_PARAM && sep.GetToken(sbuf))
|
|
this->SetMode(bi, um, sbuf);
|
|
else
|
|
this->SetMode(bi, um);
|
|
}
|
|
else
|
|
this->RemoveMode(bi, um);
|
|
}
|
|
}
|
|
|
|
void User::SetModesInternal(const MessageSource &source, const Anope::string &umodes, const std::vector<Anope::string> &umodeparams)
|
|
{
|
|
if (this->server && this->server->IsSynced() && Anope::string(umodes) != "+")
|
|
Log(this, "mode") << "changes modes to " << umodes;
|
|
|
|
int add = -1;
|
|
auto paramit = umodeparams.begin();
|
|
for (const auto mode : umodes)
|
|
{
|
|
UserMode *um;
|
|
|
|
switch (mode)
|
|
{
|
|
case '+':
|
|
add = 1;
|
|
continue;
|
|
case '-':
|
|
add = 0;
|
|
continue;
|
|
default:
|
|
if (add == -1)
|
|
continue;
|
|
um = ModeManager::FindUserModeByChar(mode);
|
|
if (!um)
|
|
continue;
|
|
}
|
|
|
|
if (add)
|
|
{
|
|
Anope::string sbuf;
|
|
if (um->type == MODE_PARAM && paramit != umodeparams.end())
|
|
this->SetModeInternal(source, um, *paramit++);
|
|
else
|
|
this->SetModeInternal(source, um);
|
|
}
|
|
else
|
|
this->RemoveModeInternal(source, um);
|
|
}
|
|
}
|
|
|
|
Anope::string User::GetModes() const
|
|
{
|
|
Anope::string m, params;
|
|
|
|
for (const auto &[mode, data] : this->modes)
|
|
{
|
|
UserMode *um = ModeManager::FindUserModeByName(mode);
|
|
if (um == NULL)
|
|
continue;
|
|
|
|
m += um->mchar;
|
|
|
|
if (!data.value.empty())
|
|
params += " " + data.value;
|
|
}
|
|
|
|
return m + params;
|
|
}
|
|
|
|
const User::ModeList &User::GetModeList() const
|
|
{
|
|
return modes;
|
|
}
|
|
|
|
ChanUserContainer *User::FindChannel(Channel *c) const
|
|
{
|
|
User::ChanUserList::const_iterator it = this->chans.find(c);
|
|
if (it != this->chans.end())
|
|
return it->second;
|
|
return NULL;
|
|
}
|
|
|
|
bool User::IsProtected()
|
|
{
|
|
return this->HasMode("PROTECTED") || this->HasMode("GOD") || this->HasPriv("protected") || (this->server && this->server->IsULined());
|
|
}
|
|
|
|
void User::Kill(const MessageSource &source, const Anope::string &reason)
|
|
{
|
|
Anope::string real_reason = source.GetName() + " (" + reason + ")";
|
|
|
|
IRCD->SendSVSKill(source, this, real_reason);
|
|
}
|
|
|
|
void User::KillInternal(const MessageSource &source, const Anope::string &reason)
|
|
{
|
|
if (this->quit)
|
|
{
|
|
Log(LOG_DEBUG) << "Duplicate quit for " << this->nick;
|
|
return;
|
|
}
|
|
|
|
Log(this, "killed") << "was killed by " << source.GetName() << " (Reason: " << reason << ")";
|
|
|
|
this->Quit(reason);
|
|
}
|
|
|
|
void User::Quit(const Anope::string &reason)
|
|
{
|
|
if (this->quit)
|
|
{
|
|
Log(LOG_DEBUG) << "Duplicate quit for " << this->nick;
|
|
return;
|
|
}
|
|
|
|
FOREACH_MOD(OnUserQuit, (this, reason));
|
|
|
|
this->quit = true;
|
|
quitting_users.push_back(this);
|
|
}
|
|
|
|
bool User::Quitting() const
|
|
{
|
|
return this->quit;
|
|
}
|
|
|
|
Anope::string User::Mask() const
|
|
{
|
|
Anope::string mask;
|
|
const Anope::string &mident = this->GetIdent();
|
|
const Anope::string &mhost = this->GetDisplayedHost();
|
|
|
|
if (mident[0] == '~')
|
|
mask = "*" + mident + "@";
|
|
else
|
|
mask = mident + "@";
|
|
|
|
sockaddrs addr(mhost);
|
|
if (addr.valid() && addr.sa.sa_family == AF_INET)
|
|
{
|
|
size_t dot = mhost.rfind('.');
|
|
mask += mhost.substr(0, dot) + (dot == Anope::string::npos ? "" : ".*");
|
|
}
|
|
else
|
|
{
|
|
size_t dot = mhost.find('.');
|
|
if (dot != Anope::string::npos && mhost.find('.', dot + 1) != Anope::string::npos)
|
|
mask += "*" + mhost.substr(dot);
|
|
else
|
|
mask += mhost;
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
bool User::BadPassword()
|
|
{
|
|
const auto badpasslimit = Config->GetBlock("options").Get<unsigned>("badpasslimit");
|
|
if (!badpasslimit)
|
|
return false;
|
|
|
|
const auto badpasstimeout = Config->GetBlock("options").Get<time_t>("badpasstimeout");
|
|
if (badpasstimeout > 0 && this->invalid_pw_time > 0 && this->invalid_pw_time < Anope::CurTime - badpasstimeout)
|
|
this->invalid_pw_count = 0;
|
|
|
|
++this->invalid_pw_count;
|
|
this->invalid_pw_time = Anope::CurTime;
|
|
if (this->invalid_pw_count >= badpasslimit)
|
|
{
|
|
this->Kill(Me, "Too many invalid passwords");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool User::ShouldPrivmsg() const
|
|
{
|
|
// Send a PRIVMSG instead of a NOTICE if:
|
|
// 1. The user is not registered and msg is in nickserv:defaults.
|
|
// 2. The user is registered and has set /ns set message on.
|
|
static ExtensibleRef<bool> msg("MSG");
|
|
return (!nc && Config->DefPrivmsg) || (nc && msg && msg->HasExt(nc));
|
|
}
|
|
|
|
User *User::Find(const Anope::string &name, bool nick_only)
|
|
{
|
|
if (!nick_only && IRCD && IRCD->RequiresID)
|
|
{
|
|
user_map::iterator it = UserListByUID.find(name);
|
|
if (it != UserListByUID.end())
|
|
return it->second;
|
|
|
|
if (IRCD->AmbiguousID)
|
|
return NULL;
|
|
}
|
|
|
|
user_map::iterator it = UserListByNick.find(name);
|
|
if (it != UserListByNick.end())
|
|
return it->second;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void User::QuitUsers()
|
|
{
|
|
for (const auto *quitting_user : quitting_users)
|
|
delete quitting_user;
|
|
quitting_users.clear();
|
|
}
|