anope

- supernets anope source code & configuration
git clone git://git.acid.vegas/anope.git
Log | Files | Refs | Archive | README

ns_ajoin.cpp (10542B)

      1 /* NickServ core functions
      2  *
      3  * (C) 2003-2022 Anope Team
      4  * Contact us at team@anope.org
      5  *
      6  * Please read COPYING and README for further details.
      7  *
      8  * Based on the original code of Epona by Lara.
      9  * Based on the original code of Services by Andy Church.
     10  */
     11 
     12 #include "module.h"
     13 
     14 struct AJoinEntry;
     15 
     16 struct AJoinList : Serialize::Checker<std::vector<AJoinEntry *> >
     17 {
     18 	AJoinList(Extensible *) : Serialize::Checker<std::vector<AJoinEntry *> >("AJoinEntry") { }
     19 	~AJoinList();
     20 };
     21 
     22 struct AJoinEntry : Serializable
     23 {
     24 	Serialize::Reference<NickCore> owner;
     25 	Anope::string channel;
     26 	Anope::string key;
     27 
     28 	AJoinEntry(Extensible *) : Serializable("AJoinEntry") { }
     29 
     30 	~AJoinEntry()
     31 	{
     32 		AJoinList *channels = owner->GetExt<AJoinList>("ajoinlist");
     33 		if (channels)
     34 		{
     35 			std::vector<AJoinEntry *>::iterator it = std::find((*channels)->begin(), (*channels)->end(), this);
     36 			if (it != (*channels)->end())
     37 				(*channels)->erase(it);
     38 		}
     39 	}
     40 
     41 	void Serialize(Serialize::Data &sd) const anope_override
     42 	{
     43 		if (!this->owner)
     44 			return;
     45 
     46 		sd["owner"] << this->owner->display;
     47 		sd["channel"] << this->channel;
     48 		sd["key"] << this->key;
     49 	}
     50 
     51 	static Serializable* Unserialize(Serializable *obj, Serialize::Data &sd)
     52 	{
     53 		Anope::string sowner;
     54 
     55 		sd["owner"] >> sowner;
     56 
     57 		NickCore *nc = NickCore::Find(sowner);
     58 		if (nc == NULL)
     59 			return NULL;
     60 
     61 		AJoinEntry *aj;
     62 		if (obj)
     63 			aj = anope_dynamic_static_cast<AJoinEntry *>(obj);
     64 		else
     65 		{
     66 			aj = new AJoinEntry(nc);
     67 			aj->owner = nc;
     68 		}
     69 
     70 		sd["channel"] >> aj->channel;
     71 		sd["key"] >> aj->key;
     72 
     73 		if (!obj)
     74 		{
     75 			AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
     76 			(*channels)->push_back(aj);
     77 		}
     78 
     79 		return aj;
     80 	}
     81 };
     82 
     83 AJoinList::~AJoinList()
     84 {
     85 	for (unsigned i = 0; i < (*this)->size(); ++i)
     86 		delete (*this)->at(i);
     87 }
     88 
     89 class CommandNSAJoin : public Command
     90 {
     91 	void DoList(CommandSource &source, NickCore *nc)
     92 	{
     93 		AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
     94 
     95 		if ((*channels)->empty())
     96 			source.Reply(_("%s's auto join list is empty."), nc->display.c_str());
     97 		else
     98 		{
     99 			ListFormatter list(source.GetAccount());
    100 			list.AddColumn(_("Number")).AddColumn(_("Channel")).AddColumn(_("Key"));
    101 			for (unsigned i = 0; i < (*channels)->size(); ++i)
    102 			{
    103 				AJoinEntry *aj = (*channels)->at(i);
    104 				ListFormatter::ListEntry entry;
    105 				entry["Number"] = stringify(i + 1);
    106 				entry["Channel"] = aj->channel;
    107 				entry["Key"] = aj->key;
    108 				list.AddEntry(entry);
    109 			}
    110 
    111 			source.Reply(_("%s's auto join list:"), nc->display.c_str());
    112 
    113 			std::vector<Anope::string> replies;
    114 			list.Process(replies);
    115 
    116 			for (unsigned i = 0; i < replies.size(); ++i)
    117 				source.Reply(replies[i]);
    118 		}
    119 	}
    120 
    121 	void DoAdd(CommandSource &source, NickCore *nc, const Anope::string &chans, const Anope::string &keys)
    122 	{
    123 		AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
    124 
    125 		Anope::string addedchans;
    126 		Anope::string alreadyadded;
    127 		Anope::string invalidkey;
    128 		commasepstream ksep(keys, true);
    129 		commasepstream csep(chans);
    130 		for (Anope::string chan, key; csep.GetToken(chan);)
    131 		{
    132 			ksep.GetToken(key);
    133 
    134 			unsigned i = 0;
    135 			for (; i < (*channels)->size(); ++i)
    136 				if ((*channels)->at(i)->channel.equals_ci(chan))
    137 					break;
    138 
    139 			if ((*channels)->size() >= Config->GetModule(this->owner)->Get<unsigned>("ajoinmax"))
    140 			{
    141 				source.Reply(_("Sorry, the maximum of %d auto join entries has been reached."), Config->GetModule(this->owner)->Get<unsigned>("ajoinmax"));
    142 				return;
    143 			}
    144 			else if (i != (*channels)->size())
    145 				alreadyadded += chan + ", ";
    146 			else if (IRCD->IsChannelValid(chan) == false)
    147 				source.Reply(CHAN_X_INVALID, chan.c_str());
    148 			else
    149 			{
    150 				Channel *c = Channel::Find(chan);
    151 				Anope::string k;
    152 				if (c && c->GetParam("KEY", k) && key != k)
    153 				{
    154 					invalidkey += chan + ", ";
    155 					continue;
    156 				}
    157 
    158 				AJoinEntry *entry = new AJoinEntry(nc);
    159 				entry->owner = nc;
    160 				entry->channel = chan;
    161 				entry->key = key;
    162 				(*channels)->push_back(entry);
    163 				addedchans += chan + ", ";
    164 			}
    165 		}
    166 
    167 		if (!alreadyadded.empty())
    168 		{
    169 			alreadyadded = alreadyadded.substr(0, alreadyadded.length() - 2);
    170 			source.Reply(_("%s is already on %s's auto join list."), alreadyadded.c_str(), nc->display.c_str());
    171 		}
    172 
    173 		if (!invalidkey.empty())
    174 		{
    175 			invalidkey = invalidkey.substr(0, invalidkey.length() - 2);
    176 			source.Reply(_("%s had an invalid key specified, and was thus ignored."), invalidkey.c_str());
    177 		}
    178 
    179 		if (addedchans.empty())
    180 			return;
    181 
    182 		addedchans = addedchans.substr(0, addedchans.length() - 2);
    183 		Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to ADD channel " << addedchans << " to " << nc->display;
    184 		source.Reply(_("%s added to %s's auto join list."), addedchans.c_str(), nc->display.c_str());
    185 	}
    186 
    187 	void DoDel(CommandSource &source, NickCore *nc, const Anope::string &chans)
    188 	{
    189 		AJoinList *channels = nc->Require<AJoinList>("ajoinlist");
    190 		Anope::string delchans;
    191 		Anope::string notfoundchans;
    192 		commasepstream sep(chans);
    193 
    194 		for (Anope::string chan; sep.GetToken(chan);)
    195 		{
    196 			unsigned i = 0;
    197 			for (; i < (*channels)->size(); ++i)
    198 				if ((*channels)->at(i)->channel.equals_ci(chan))
    199 					break;
    200 
    201 			if (i == (*channels)->size())
    202 				notfoundchans += chan + ", ";
    203 			else
    204 			{
    205 				delete (*channels)->at(i);
    206 				delchans += chan + ", ";
    207 			}
    208 		}
    209 
    210 		if (!notfoundchans.empty())
    211 		{
    212 			notfoundchans = notfoundchans.substr(0, notfoundchans.length() - 2);
    213 			source.Reply(_("%s was not found on %s's auto join list."), notfoundchans.c_str(), nc->display.c_str());
    214 		}
    215 
    216 		if (delchans.empty())
    217 			return;
    218 
    219 		delchans = delchans.substr(0, delchans.length() - 2);
    220 		Log(nc == source.GetAccount() ? LOG_COMMAND : LOG_ADMIN, source, this) << "to DELETE channel " << delchans << " from " << nc->display;
    221 		source.Reply(_("%s was removed from %s's auto join list."), delchans.c_str(), nc->display.c_str());
    222 
    223 		if ((*channels)->empty())
    224 			nc->Shrink<AJoinList>("ajoinlist");
    225 	}
    226 
    227  public:
    228 	CommandNSAJoin(Module *creator) : Command(creator, "nickserv/ajoin", 1, 4)
    229 	{
    230 		this->SetDesc(_("Manage your auto join list"));
    231 		this->SetSyntax(_("ADD [\037nickname\037] \037channel\037 [\037key\037]"));
    232 		this->SetSyntax(_("DEL [\037nickname\037] \037channel\037"));
    233 		this->SetSyntax(_("LIST [\037nickname\037]"));
    234 	}
    235 
    236 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    237 	{
    238 		const Anope::string &cmd = params[0];
    239 		Anope::string nick, param, param2;
    240 
    241 		if (cmd.equals_ci("LIST"))
    242 			nick = params.size() > 1 ? params[1] : "";
    243 		else
    244 			nick = (params.size() > 2 && IRCD->IsChannelValid(params[2])) ? params[1] : "";
    245 
    246 		NickCore *nc;
    247 		if (!nick.empty())
    248 		{
    249 			const NickAlias *na = NickAlias::Find(nick);
    250 			if (na == NULL)
    251 			{
    252 				source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
    253 				return;
    254 			}
    255 			else if (na->nc != source.GetAccount() && !source.HasCommand("nickserv/ajoin"))
    256 			{
    257 				source.Reply(ACCESS_DENIED);
    258 				return;
    259 			}
    260 
    261 			nc = na->nc;
    262 			param = params.size() > 2 ? params[2] : "";
    263 			param2 = params.size() > 3 ? params[3] : "";
    264 		}
    265 		else
    266 		{
    267 			nc = source.nc;
    268 			param = params.size() > 1 ? params[1] : "";
    269 			param2 = params.size() > 2 ? params[2] : "";
    270 		}
    271 
    272 		if (cmd.equals_ci("LIST"))
    273 			return this->DoList(source, nc);
    274 		else if (nc->HasExt("NS_SUSPENDED"))
    275 			source.Reply(NICK_X_SUSPENDED, nc->display.c_str());
    276 		else if (param.empty())
    277 			this->OnSyntaxError(source, "");
    278 		else if (Anope::ReadOnly)
    279 			source.Reply(READ_ONLY_MODE);
    280 		else if (cmd.equals_ci("ADD"))
    281 			return this->DoAdd(source, nc, param, param2);
    282 		else if (cmd.equals_ci("DEL"))
    283 			return this->DoDel(source, nc, param);
    284 		else
    285 			this->OnSyntaxError(source, "");
    286 	}
    287 
    288 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    289 	{
    290 		this->SendSyntax(source);
    291 		source.Reply(" ");
    292 		source.Reply(_("This command manages your auto join list. When you identify\n"
    293 				"you will automatically join the channels on your auto join list.\n"
    294 				"Services Operators may provide a nick to modify other users'\n"
    295 				"auto join lists."));
    296 		return true;
    297 	}
    298 };
    299 
    300 class NSAJoin : public Module
    301 {
    302 	CommandNSAJoin commandnsajoin;
    303 	ExtensibleItem<AJoinList> ajoinlist;
    304 	Serialize::Type ajoinentry_type;
    305 
    306  public:
    307 	NSAJoin(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    308 		commandnsajoin(this), ajoinlist(this, "ajoinlist"),
    309 		ajoinentry_type("AJoinEntry", AJoinEntry::Unserialize)
    310 	{
    311 
    312 		if (!IRCD || !IRCD->CanSVSJoin)
    313 			throw ModuleException("Your IRCd does not support SVSJOIN");
    314 
    315 	}
    316 
    317 	void OnUserLogin(User *u) anope_override
    318 	{
    319 		BotInfo *NickServ = Config->GetClient("NickServ");
    320 		if (!NickServ)
    321 			return;
    322 
    323 		AJoinList *channels = u->Account()->GetExt<AJoinList>("ajoinlist");
    324 		if (channels == NULL)
    325 			return;
    326 
    327 		/* Set +r now, so we can ajoin users into +R channels */
    328 		ModeManager::ProcessModes();
    329 
    330 		for (unsigned i = 0; i < (*channels)->size(); ++i)
    331 		{
    332 			AJoinEntry *entry = (*channels)->at(i);
    333 			Channel *c = Channel::Find(entry->channel);
    334 			ChannelInfo *ci;
    335 
    336 			if (c)
    337 				ci = c->ci;
    338 			else
    339 				ci = ChannelInfo::Find(entry->channel);
    340 
    341 			bool need_invite = false;
    342 			Anope::string key = entry->key;
    343 			AccessGroup u_access;
    344 
    345 			if (ci != NULL)
    346 			{
    347 				if (ci->HasExt("CS_SUSPENDED"))
    348 					continue;
    349 				u_access = ci->AccessFor(u);
    350 			}
    351 			if (c != NULL)
    352 			{
    353 				if (c->FindUser(u) != NULL)
    354 					continue;
    355 				else if (c->HasMode("OPERONLY") && !u->HasMode("OPER"))
    356 					continue;
    357 				else if (c->HasMode("ADMINONLY") && !u->HasMode("ADMIN"))
    358 					continue;
    359 				else if (c->HasMode("SSL") && !(u->HasMode("SSL") || u->HasExt("ssl")))
    360 					continue;
    361 				else if (c->MatchesList(u, "BAN") == true && c->MatchesList(u, "EXCEPT") == false)
    362 					need_invite = true;
    363 				else if (c->HasMode("INVITE") && c->MatchesList(u, "INVITEOVERRIDE") == false)
    364 					need_invite = true;
    365 
    366 				if (c->HasMode("KEY"))
    367 				{
    368 					Anope::string k;
    369 					if (c->GetParam("KEY", k))
    370 					{
    371 						if (u_access.HasPriv("GETKEY"))
    372 							key = k;
    373 						else if (key != k)
    374 							need_invite = true;
    375 					}
    376 				}
    377 				if (c->HasMode("LIMIT"))
    378 				{
    379 					Anope::string l;
    380 					if (c->GetParam("LIMIT", l))
    381 					{
    382 						try
    383 						{
    384 							unsigned limit = convertTo<unsigned>(l);
    385 							if (c->users.size() >= limit)
    386 								need_invite = true;
    387 						}
    388 						catch (const ConvertException &) { }
    389 					}
    390 				}
    391 			}
    392 
    393 			if (need_invite && c != NULL)
    394 			{
    395 				if (!u_access.HasPriv("INVITE"))
    396 					continue;
    397 				IRCD->SendInvite(NickServ, c, u);
    398 			}
    399 
    400 			IRCD->SendSVSJoin(NickServ, u, entry->channel, key);
    401 		}
    402 	}
    403 };
    404 
    405 MODULE_INIT(NSAJoin)