1 /* BotServ core functions
2  *
3  * (C) 2003-2020 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 class CommandBSBot : public Command
15 {
16  private:
DoAdd(CommandSource & source,const std::vector<Anope::string> & params)17 	void DoAdd(CommandSource &source, const std::vector<Anope::string> &params)
18 	{
19 		const Anope::string &nick = params[1];
20 		const Anope::string &user = params[2];
21 		const Anope::string &host = params[3];
22 		const Anope::string &real = params[4];
23 
24 		if (BotInfo::Find(nick, true))
25 		{
26 			source.Reply(_("Bot \002%s\002 already exists."), nick.c_str());
27 			return;
28 		}
29 
30 		Configuration::Block *networkinfo = Config->GetBlock("networkinfo");
31 
32 		if (nick.length() > networkinfo->Get<unsigned>("nicklen"))
33 		{
34 			source.Reply(_("Bot nicks may only be %d characters long."), networkinfo->Get<unsigned>("nicklen"));
35 			return;
36 		}
37 
38 		if (user.length() > networkinfo->Get<unsigned>("userlen"))
39 		{
40 			source.Reply(_("Bot idents may only be %d characters long."), networkinfo->Get<unsigned>("userlen"));
41 			return;
42 		}
43 
44 		if (host.length() > networkinfo->Get<unsigned>("hostlen"))
45 		{
46 			source.Reply(_("Bot hosts may only be %d characters long."), networkinfo->Get<unsigned>("hostlen"));
47 			return;
48 		}
49 
50 		if (!IRCD->IsNickValid(nick))
51 		{
52 			source.Reply(_("Bot nicks may only contain valid nick characters."));
53 			return;
54 		}
55 
56 		if (!IRCD->IsIdentValid(user))
57 		{
58 			source.Reply(_("Bot idents may only contain valid ident characters."));
59 			return;
60 		}
61 
62 		if (!IRCD->IsHostValid(host))
63 		{
64 			source.Reply(_("Bot hosts may only contain valid host characters."));
65 			return;
66 		}
67 
68 		/* We check whether the nick is registered, and inform the user
69 		* if so. You need to drop the nick manually before you can use
70 		* it as a bot nick from now on -GD
71 		*/
72 		if (NickAlias::Find(nick))
73 		{
74 			source.Reply(NICK_ALREADY_REGISTERED, nick.c_str());
75 			return;
76 		}
77 
78 		User *u = User::Find(nick, true);
79 		if (u)
80 		{
81 			source.Reply(_("Nick \002%s\002 is currently in use."), u->nick.c_str());
82 			return;
83 		}
84 
85 		BotInfo *bi = new BotInfo(nick, user, host, real);
86 
87 		Log(LOG_ADMIN, source, this) << "ADD " << bi->GetMask() << " " << bi->realname;
88 
89 		source.Reply(_("%s!%s@%s (%s) added to the bot list."), bi->nick.c_str(), bi->GetIdent().c_str(), bi->host.c_str(), bi->realname.c_str());
90 
91 		FOREACH_MOD(OnBotCreate, (bi));
92 		return;
93 	}
94 
DoChange(CommandSource & source,const std::vector<Anope::string> & params)95 	void DoChange(CommandSource &source, const std::vector<Anope::string> &params)
96 	{
97 		const Anope::string &oldnick = params[1];
98 		const Anope::string &nick = params.size() > 2 ? params[2] : "";
99 		const Anope::string &user = params.size() > 3 ? params[3] : "";
100 		const Anope::string &host = params.size() > 4 ? params[4] : "";
101 		const Anope::string &real = params.size() > 5 ? params[5] : "";
102 
103 		if (oldnick.empty() || nick.empty())
104 		{
105 			this->OnSyntaxError(source, "CHANGE");
106 			return;
107 		}
108 
109 		BotInfo *bi = BotInfo::Find(oldnick, true);
110 		if (!bi)
111 		{
112 			source.Reply(BOT_DOES_NOT_EXIST, oldnick.c_str());
113 			return;
114 		}
115 
116 		if (bi->conf)
117 		{
118 			source.Reply(_("Bot %s is not changeable."), bi->nick.c_str());
119 			return;
120 		}
121 
122 		Configuration::Block *networkinfo = Config->GetBlock("networkinfo");
123 
124 		if (nick.length() > networkinfo->Get<unsigned>("nicklen"))
125 		{
126 			source.Reply(_("Bot nicks may only be %d characters long."), networkinfo->Get<unsigned>("nicklen"));
127 			return;
128 		}
129 
130 		if (user.length() > networkinfo->Get<unsigned>("userlen"))
131 		{
132 			source.Reply(_("Bot idents may only be %d characters long."), networkinfo->Get<unsigned>("userlen"));
133 			return;
134 		}
135 
136 		if (host.length() > networkinfo->Get<unsigned>("hostlen"))
137 		{
138 			source.Reply(_("Bot hosts may only be %d characters long."), networkinfo->Get<unsigned>("hostlen"));
139 			return;
140 		}
141 
142 		/* Checks whether there *are* changes.
143 		* Case sensitive because we may want to change just the case.
144 		* And we must finally check that the nick is not already
145 		* taken by another bot.
146 		*/
147 		if (nick.equals_cs(bi->nick) && (!user.empty() ? user.equals_cs(bi->GetIdent()) : 1) && (!host.empty() ? host.equals_cs(bi->host) : 1) && (!real.empty() ? real.equals_cs(bi->realname) : 1))
148 		{
149 			source.Reply(_("The old information is the same as the new information specified."));
150 			return;
151 		}
152 
153 		if (!IRCD->IsNickValid(nick))
154 		{
155 			source.Reply(_("Bot nicks may only contain valid nick characters."));
156 			return;
157 		}
158 
159 		if (!user.empty() && !IRCD->IsIdentValid(user))
160 		{
161 			source.Reply(_("Bot idents may only contain valid ident characters."));
162 			return;
163 		}
164 
165 		if (!host.empty() && !IRCD->IsHostValid(host))
166 		{
167 			source.Reply(_("Bot hosts may only contain valid host characters."));
168 			return;
169 		}
170 
171 		if (!nick.equals_ci(bi->nick))
172 		{
173 			if (BotInfo::Find(nick, true))
174 			{
175 				source.Reply(_("Bot \002%s\002 already exists."), nick.c_str());
176 				return;
177 			}
178 
179 			if (User::Find(nick, true))
180 			{
181 				source.Reply(_("Nick \002%s\002 is currently in use."), nick.c_str());
182 				return;
183 			}
184 		}
185 
186 		if (!nick.equals_ci(bi->nick))
187 		{
188 			/* We check whether the nick is registered, and inform the user
189 			* if so. You need to drop the nick manually before you can use
190 			* it as a bot nick from now on -GD
191 			*/
192 			if (NickAlias::Find(nick))
193 			{
194 				source.Reply(NICK_ALREADY_REGISTERED, nick.c_str());
195 				return;
196 			}
197 
198 			/* The new nick is really different, so we remove the Q line for the old nick. */
199 			XLine x_del(bi->nick);
200 			IRCD->SendSQLineDel(&x_del);
201 
202 			/* Add a Q line for the new nick */
203 			XLine x(nick, "Reserved for services");
204 			IRCD->SendSQLine(NULL, &x);
205 		}
206 
207 		if (!user.empty())
208 		{
209 			IRCD->SendQuit(bi, "Quit: Be right back");
210 			bi->introduced = false;
211 		}
212 		else
213 			IRCD->SendNickChange(bi, nick);
214 
215 		if (!nick.equals_cs(bi->nick))
216 			bi->SetNewNick(nick);
217 
218 		if (!user.empty() && !user.equals_cs(bi->GetIdent()))
219 			bi->SetIdent(user);
220 		if (!host.empty() && !host.equals_cs(bi->host))
221 			bi->host = host;
222 		if (!real.empty() && !real.equals_cs(bi->realname))
223 			bi->realname = real;
224 
225 		if (!user.empty())
226 			bi->OnKill();
227 
228 		source.Reply(_("Bot \002%s\002 has been changed to %s!%s@%s (%s)."), oldnick.c_str(), bi->nick.c_str(), bi->GetIdent().c_str(), bi->host.c_str(), bi->realname.c_str());
229 		Log(LOG_ADMIN, source, this) << "CHANGE " << oldnick << " to " << bi->GetMask() << " " << bi->realname;
230 
231 		FOREACH_MOD(OnBotChange, (bi));
232 		return;
233 	}
234 
DoDel(CommandSource & source,const std::vector<Anope::string> & params)235 	void DoDel(CommandSource &source, const std::vector<Anope::string> &params)
236 	{
237 		const Anope::string &nick = params[1];
238 
239 		if (nick.empty())
240 		{
241 			this->OnSyntaxError(source, "DEL");
242 			return;
243 		}
244 
245 		BotInfo *bi = BotInfo::Find(nick, true);
246 		if (!bi)
247 		{
248 			source.Reply(BOT_DOES_NOT_EXIST, nick.c_str());
249 			return;
250 		}
251 
252 		if (bi->conf)
253 		{
254 			source.Reply(_("Bot %s is not deletable."), bi->nick.c_str());
255 			return;
256 		}
257 
258 		FOREACH_MOD(OnBotDelete, (bi));
259 
260 		Log(LOG_ADMIN, source, this) << "DEL " << bi->nick;
261 
262 		source.Reply(_("Bot \002%s\002 has been deleted."), nick.c_str());
263 		delete bi;
264 		return;
265 	}
266  public:
CommandBSBot(Module * creator)267 	CommandBSBot(Module *creator) : Command(creator, "botserv/bot", 1, 6)
268 	{
269 		this->SetDesc(_("Maintains network bot list"));
270 		this->SetSyntax(_("\002ADD \037nick\037 \037user\037 \037host\037 \037real\037\002"));
271 		this->SetSyntax(_("\002CHANGE \037oldnick\037 \037newnick\037 [\037user\037 [\037host\037 [\037real\037]]]\002"));
272 		this->SetSyntax(_("\002DEL \037nick\037\002"));
273 	}
274 
Execute(CommandSource & source,const std::vector<Anope::string> & params)275 	void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
276 	{
277 		const Anope::string &cmd = params[0];
278 
279 		if (Anope::ReadOnly)
280 		{
281 			source.Reply(_("Sorry, bot modification is temporarily disabled."));
282 			return;
283 		}
284 
285 		if (cmd.equals_ci("ADD"))
286 		{
287 			// ADD nick user host real - 5
288 			if (!source.HasCommand("botserv/bot/add"))
289 			{
290 				source.Reply(ACCESS_DENIED);
291 				return;
292 			}
293 
294 			if (params.size() < 5)
295 			{
296 				this->OnSyntaxError(source, "ADD");
297 				return;
298 			}
299 
300 			std::vector<Anope::string> tempparams = params;
301 			// ADD takes less params than CHANGE, so we need to take 6 if given and append it with a space to 5.
302 			if (tempparams.size() >= 6)
303 				tempparams[4] = tempparams[4] + " " + tempparams[5];
304 
305 			return this->DoAdd(source, tempparams);
306 		}
307 		else if (cmd.equals_ci("CHANGE"))
308 		{
309 			// CHANGE oldn newn user host real - 6
310 			// but only oldn and newn are required
311 			if (!source.HasCommand("botserv/bot/change"))
312 			{
313 				source.Reply(ACCESS_DENIED);
314 				return;
315 			}
316 
317 			if (params.size() < 3)
318 			{
319 				this->OnSyntaxError(source, "CHANGE");
320 				return;
321 			}
322 
323 			return this->DoChange(source, params);
324 		}
325 		else if (cmd.equals_ci("DEL"))
326 		{
327 			// DEL nick
328 			if (!source.HasCommand("botserv/bot/del"))
329 			{
330 				source.Reply(ACCESS_DENIED);
331 				return;
332 			}
333 
334 			if (params.size() < 1)
335 			{
336 				this->OnSyntaxError(source, "DEL");
337 				return;
338 			}
339 
340 			return this->DoDel(source, params);
341 		}
342 		else
343 			this->OnSyntaxError(source, "");
344 
345 		return;
346 	}
347 
OnHelp(CommandSource & source,const Anope::string & subcommand)348 	bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
349 	{
350 		this->SendSyntax(source);
351 		source.Reply(" ");
352 		source.Reply(_("Allows Services Operators to create, modify, and delete\n"
353 				"bots that users will be able to use on their own\n"
354 				"channels.\n"
355 				" \n"
356 				"\002BOT ADD\002 adds a bot with the given nickname, username,\n"
357 				"hostname and realname. Since no integrity checks are done\n"
358 				"for these settings, be really careful.\n"
359 				" \n"
360 				"\002BOT CHANGE\002 allows you to change the nickname, username, hostname\n"
361 				"or realname of a bot without deleting it (and\n"
362 				"all the data associated with it).\n"
363 				" \n"
364 				"\002BOT DEL\002 removes the given bot from the bot list.\n"
365 				" \n"
366 				"\002Note\002: You cannot create a bot with a nick that is\n"
367 				"currently registered. If an unregistered user is currently\n"
368 				"using the nick, they will be killed."));
369 		return true;
370 	}
371 };
372 
373 class BSBot : public Module
374 {
375 	CommandBSBot commandbsbot;
376 
377  public:
BSBot(const Anope::string & modname,const Anope::string & creator)378 	BSBot(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, VENDOR),
379 		commandbsbot(this)
380 	{
381 
382 	}
383 };
384 
385 MODULE_INIT(BSBot)
386