anope

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

ns_group.cpp (12710B)

      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 #include "modules/ns_cert.h"
     14 
     15 class NSGroupRequest : public IdentifyRequest
     16 {
     17 	CommandSource source;
     18 	Command *cmd;
     19 	Anope::string nick;
     20 	Reference<NickAlias> target;
     21 
     22  public:
     23 	NSGroupRequest(Module *o, CommandSource &src, Command *c, const Anope::string &n, NickAlias *targ, const Anope::string &pass) : IdentifyRequest(o, targ->nc->display, pass), source(src), cmd(c), nick(n), target(targ) { }
     24 
     25 	void OnSuccess() anope_override
     26 	{
     27 		User *u = source.GetUser();
     28 
     29 		/* user changed nick? */
     30 		if (u != NULL && u->nick != nick)
     31 			return;
     32 
     33 		if (!target || !target->nc)
     34 			return;
     35 
     36 		NickAlias *na = NickAlias::Find(nick);
     37 		/* If the nick is already registered, drop it. */
     38 		if (na)
     39 		{
     40 			delete na;
     41 		}
     42 
     43 		na = new NickAlias(nick, target->nc);
     44 		na->time_registered = na->last_seen = Anope::CurTime;
     45 
     46 		if (u != NULL)
     47 		{
     48 			na->last_usermask = u->GetIdent() + "@" + u->GetDisplayedHost();
     49 			na->last_realname = u->realname;
     50 		}
     51 		else
     52 		{
     53 			na->last_realname = source.GetNick();
     54 		}
     55 
     56 		if (u != NULL)
     57 		{
     58 			IRCD->SendLogin(u, na); // protocol modules prevent this on unconfirmed accounts
     59 			u->Login(target->nc);
     60 			FOREACH_MOD(OnNickGroup, (u, target));
     61 		}
     62 
     63 		Log(LOG_COMMAND, source, cmd) << "to make " << nick << " join group of " << target->nick << " (" << target->nc->display << ") (email: " << (!target->nc->email.empty() ? target->nc->email : "none") << ")";
     64 		source.Reply(_("You are now in the group of \002%s\002."), target->nick.c_str());
     65 
     66 		if (u)
     67 			u->lastnickreg = Anope::CurTime;
     68 	}
     69 
     70 	void OnFail() anope_override
     71 	{
     72 		User *u = source.GetUser();
     73 
     74 		Log(LOG_COMMAND, source, cmd) << "and failed to group to " << target->nick;
     75 		if (NickAlias::Find(GetAccount()) != NULL)
     76 		{
     77 			source.Reply(PASSWORD_INCORRECT);
     78 			if (u)
     79 				u->BadPassword();
     80 		}
     81 		else
     82 			source.Reply(NICK_X_NOT_REGISTERED, GetAccount().c_str());
     83 	}
     84 };
     85 
     86 class CommandNSGroup : public Command
     87 {
     88  public:
     89 	CommandNSGroup(Module *creator) : Command(creator, "nickserv/group", 0, 2)
     90 	{
     91 		this->SetDesc(_("Join a group"));
     92 		this->SetSyntax(_("\037[target]\037 \037[password]\037"));
     93 		this->AllowUnregistered(true);
     94 	}
     95 
     96 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
     97 	{
     98 		User *user = source.GetUser();
     99 
    100 		Anope::string nick;
    101 		if (params.empty())
    102 		{
    103 			NickCore* core = source.GetAccount();
    104 			if (core)
    105 				nick = core->display;
    106 		}
    107 		else
    108 			nick = params[0];
    109 
    110 		if (nick.empty())
    111 		{
    112 			this->SendSyntax(source);
    113 			return;
    114 		}
    115 
    116 		const Anope::string &pass = params.size() > 1 ? params[1] : "";
    117 
    118 		if (Anope::ReadOnly)
    119 		{
    120 			source.Reply(_("Sorry, nickname grouping is temporarily disabled."));
    121 			return;
    122 		}
    123 
    124 		if (!IRCD->IsNickValid(source.GetNick()))
    125 		{
    126 			source.Reply(NICK_CANNOT_BE_REGISTERED, source.GetNick().c_str());
    127 			return;
    128 		}
    129 
    130 		if (Config->GetModule("nickserv")->Get<bool>("restrictopernicks"))
    131 			for (unsigned i = 0; i < Oper::opers.size(); ++i)
    132 			{
    133 				Oper *o = Oper::opers[i];
    134 
    135 				if (user != NULL && !user->HasMode("OPER") && user->nick.find_ci(o->name) != Anope::string::npos)
    136 				{
    137 					source.Reply(NICK_CANNOT_BE_REGISTERED, user->nick.c_str());
    138 					return;
    139 				}
    140 			}
    141 
    142 		NickAlias *target, *na = NickAlias::Find(source.GetNick());
    143 		const Anope::string &guestnick = Config->GetModule("nickserv")->Get<const Anope::string>("guestnickprefix", "Guest");
    144 		time_t reg_delay = Config->GetModule("nickserv")->Get<time_t>("regdelay");
    145 		unsigned maxaliases = Config->GetModule(this->owner)->Get<unsigned>("maxaliases");
    146 		if (!(target = NickAlias::Find(nick)))
    147 			source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
    148 		else if (user && Anope::CurTime < user->lastnickreg + reg_delay)
    149 			source.Reply(_("Please wait %d seconds before using the GROUP command again."), (reg_delay + user->lastnickreg) - Anope::CurTime);
    150 		else if (target->nc->HasExt("NS_SUSPENDED"))
    151 		{
    152 			Log(LOG_COMMAND, source, this) << "and tried to group to SUSPENDED nick " << target->nick;
    153 			source.Reply(NICK_X_SUSPENDED, target->nick.c_str());
    154 		}
    155 		else if (na && Config->GetModule(this->owner)->Get<bool>("nogroupchange"))
    156 			source.Reply(_("Your nick is already registered."));
    157 		else if (na && *target->nc == *na->nc)
    158 			source.Reply(_("You are already a member of the group of \002%s\002."), target->nick.c_str());
    159 		else if (na && na->nc != source.GetAccount())
    160 			source.Reply(NICK_IDENTIFY_REQUIRED);
    161 		else if (maxaliases && target->nc->aliases->size() >= maxaliases && !target->nc->IsServicesOper())
    162 			source.Reply(_("There are too many nicks in your group."));
    163 		else if (source.GetNick().length() <= guestnick.length() + 7 &&
    164 			source.GetNick().length() >= guestnick.length() + 1 &&
    165 			!source.GetNick().find_ci(guestnick) && !source.GetNick().substr(guestnick.length()).find_first_not_of("1234567890"))
    166 		{
    167 			source.Reply(NICK_CANNOT_BE_REGISTERED, source.GetNick().c_str());
    168 		}
    169 		else
    170 		{
    171 			bool ok = false;
    172 			if (!na && source.GetAccount() == target->nc)
    173 				ok = true;
    174 
    175 			NSCertList *cl = target->nc->GetExt<NSCertList>("certificates");
    176 			if (user != NULL && !user->fingerprint.empty() && cl && cl->FindCert(user->fingerprint))
    177 				ok = true;
    178 
    179 			if (ok == false && !pass.empty())
    180 			{
    181 				NSGroupRequest *req = new NSGroupRequest(owner, source, this, source.GetNick(), target, pass);
    182 				FOREACH_MOD(OnCheckAuthentication, (source.GetUser(), req));
    183 				req->Dispatch();
    184 			}
    185 			else
    186 			{
    187 				NSGroupRequest req(owner, source, this, source.GetNick(), target, pass);
    188 
    189 				if (ok)
    190 					req.OnSuccess();
    191 				else
    192 					req.OnFail();
    193 			}
    194 		}
    195 	}
    196 
    197 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    198 	{
    199 		this->SendSyntax(source);
    200 		source.Reply(" ");
    201 		source.Reply(_("This command makes your nickname join the \037target\037 nickname's\n"
    202 				"group. \037password\037 is the password of the target nickname.\n"
    203 				" \n"
    204 				"Joining a group will allow you to share your configuration,\n"
    205 				"memos, and channel privileges with all the nicknames in the\n"
    206 				"group, and much more!\n"
    207 				" \n"
    208 				"A group exists as long as it is useful. This means that even\n"
    209 				"if a nick of the group is dropped, you won't lose the\n"
    210 				"shared things described above, as long as there is at\n"
    211 				"least one nick remaining in the group.\n"
    212 				" \n"
    213 				"You may be able to use this command even if you have not registered\n"
    214 				"your nick yet. If your nick is already registered, you'll\n"
    215 				"need to identify yourself before using this command.\n"
    216 				" \n"
    217 				"It is recommended to use this command with a non-registered\n"
    218 				"nick because it will be registered automatically when\n"
    219 				"using this command. You may use it with a registered nick (to\n"
    220 				"change your group) only if your network administrators allowed\n"
    221 				"it.\n"
    222 				" \n"
    223 				"You can only be in one group at a time. Group merging is\n"
    224 				"not possible.\n"
    225 				" \n"
    226 				"\037Note\037: all the nicknames of a group have the same password."));
    227 		return true;
    228 	}
    229 };
    230 
    231 class CommandNSUngroup : public Command
    232 {
    233  public:
    234 	CommandNSUngroup(Module *creator) : Command(creator, "nickserv/ungroup", 0, 1)
    235 	{
    236 		this->SetDesc(_("Remove a nick from a group"));
    237 		this->SetSyntax(_("[\037nick\037]"));
    238 	}
    239 
    240 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    241 	{
    242 		Anope::string nick = !params.empty() ? params[0] : "";
    243 		NickAlias *na = NickAlias::Find(!nick.empty() ? nick : source.GetNick());
    244 
    245 		if (source.GetAccount()->aliases->size() == 1)
    246 			source.Reply(_("Your nick is not grouped to anything, you can't ungroup it."));
    247 		else if (!na)
    248 			source.Reply(NICK_X_NOT_REGISTERED, !nick.empty() ? nick.c_str() : source.GetNick().c_str());
    249 		else if (na->nc != source.GetAccount())
    250 			source.Reply(_("Nick %s is not in your group."), na->nick.c_str());
    251 		else
    252 		{
    253 			NickCore *oldcore = na->nc;
    254 
    255 			std::vector<NickAlias *>::iterator it = std::find(oldcore->aliases->begin(), oldcore->aliases->end(), na);
    256 			if (it != oldcore->aliases->end())
    257 				oldcore->aliases->erase(it);
    258 
    259 			if (na->nick.equals_ci(oldcore->display))
    260 				oldcore->SetDisplay(oldcore->aliases->front());
    261 
    262 			NickCore *nc = new NickCore(na->nick);
    263 			na->nc = nc;
    264 			nc->aliases->push_back(na);
    265 
    266 			nc->pass = oldcore->pass;
    267 			if (!oldcore->email.empty())
    268 				nc->email = oldcore->email;
    269 			nc->language = oldcore->language;
    270 
    271 			Log(LOG_COMMAND, source, this) << "to make " << na->nick << " leave group of " << oldcore->display << " (email: " << (!oldcore->email.empty() ? oldcore->email : "none") << ")";
    272 			source.Reply(_("Nick %s has been ungrouped from %s."), na->nick.c_str(), oldcore->display.c_str());
    273 
    274 			User *user = User::Find(na->nick, true);
    275 			if (user)
    276 				/* The user on the nick who was ungrouped may be identified to the old group, set -r */
    277 				user->RemoveMode(source.service, "REGISTERED");
    278 		}
    279 	}
    280 
    281 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    282 	{
    283 		this->SendSyntax(source);
    284 		source.Reply(" ");
    285 		source.Reply(_("This command ungroups your nick, or if given, the specified nick,\n"
    286 				"from the group it is in. The ungrouped nick keeps its registration\n"
    287 				"time, password, email, greet, language, and url. Everything else\n"
    288 				"is reset. You may not ungroup yourself if there is only one nick in\n"
    289 				"your group."));
    290 		return true;
    291 	}
    292 };
    293 
    294 class CommandNSGList : public Command
    295 {
    296  public:
    297 	CommandNSGList(Module *creator) : Command(creator, "nickserv/glist", 0, 1)
    298 	{
    299 		this->SetDesc(_("Lists all nicknames in your group"));
    300 	}
    301 
    302 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
    303 	{
    304 		const Anope::string &nick = !params.empty() ? params[0] : "";
    305 		const NickCore *nc;
    306 
    307 		if (!nick.empty())
    308 		{
    309 			const NickAlias *na = NickAlias::Find(nick);
    310 			if (!na)
    311 			{
    312 				source.Reply(NICK_X_NOT_REGISTERED, nick.c_str());
    313 				return;
    314 			}
    315 			else if (na->nc != source.GetAccount() && !source.IsServicesOper())
    316 			{
    317 				source.Reply(ACCESS_DENIED);
    318 				return;
    319 			}
    320 
    321 			nc = na->nc;
    322 		}
    323 		else
    324 			nc = source.GetAccount();
    325 
    326 		ListFormatter list(source.GetAccount());
    327 		list.AddColumn(_("Nick")).AddColumn(_("Expires"));
    328 		time_t nickserv_expire = Config->GetModule("nickserv")->Get<time_t>("expire", "21d"),
    329 		       unconfirmed_expire = Config->GetModule("ns_register")->Get<time_t>("unconfirmedexpire", "1d");
    330 		for (unsigned i = 0; i < nc->aliases->size(); ++i)
    331 		{
    332 			const NickAlias *na2 = nc->aliases->at(i);
    333 
    334 			Anope::string expires;
    335 			if (na2->HasExt("NS_NO_EXPIRE"))
    336 				expires = NO_EXPIRE;
    337 			else if (!nickserv_expire || Anope::NoExpire)
    338 				;
    339 			else if (na2->nc->HasExt("UNCONFIRMED") && unconfirmed_expire)
    340 				expires = Anope::strftime(na2->time_registered + unconfirmed_expire, source.GetAccount());
    341 			else
    342 				expires = Anope::strftime(na2->last_seen + nickserv_expire, source.GetAccount());
    343 
    344 			ListFormatter::ListEntry entry;
    345 			entry["Nick"] = na2->nick;
    346 			entry["Expires"] = expires;
    347 			list.AddEntry(entry);
    348 		}
    349 
    350 		source.Reply(!nick.empty() ? _("List of nicknames in the group of \002%s\002:") : _("List of nicknames in your group:"), nc->display.c_str());
    351 		std::vector<Anope::string> replies;
    352 		list.Process(replies);
    353 
    354 		for (unsigned i = 0; i < replies.size(); ++i)
    355 			source.Reply(replies[i]);
    356 
    357 		source.Reply(_("%d nickname(s) in the group."), nc->aliases->size());
    358 	}
    359 
    360 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
    361 	{
    362 		if (source.IsServicesOper())
    363 			source.Reply(_("Syntax: \002%s [\037nickname\037]\002\n"
    364 					" \n"
    365 					"Without a parameter, lists all nicknames that are in\n"
    366 					"your group.\n"
    367 					" \n"
    368 					"With a parameter, lists all nicknames that are in the\n"
    369 					"group of the given nick.\n"
    370 					"Specifying a nick is limited to \002Services Operators\002."),
    371 					source.command.c_str());
    372 		else
    373 			source.Reply(_("Syntax: \002%s\002\n"
    374 					" \n"
    375 					"Lists all nicks in your group."), source.command.c_str());
    376 
    377 		return true;
    378 	}
    379 };
    380 
    381 class NSGroup : public Module
    382 {
    383 	CommandNSGroup commandnsgroup;
    384 	CommandNSUngroup commandnsungroup;
    385 	CommandNSGList commandnsglist;
    386 
    387  public:
    388 	NSGroup(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
    389 		commandnsgroup(this), commandnsungroup(this), commandnsglist(this)
    390 	{
    391 		if (Config->GetModule("nickserv")->Get<bool>("nonicknameownership"))
    392 			throw ModuleException(modname + " can not be used with options:nonicknameownership enabled");
    393 	}
    394 };
    395 
    396 MODULE_INIT(NSGroup)