]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_alias.cpp
Update copyright headers.
[user/henk/code/inspircd.git] / src / modules / m_alias.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2018-2019 linuxdaemon <linuxdaemon.irc@gmail.com>
5  *   Copyright (C) 2013, 2015-2019, 2021 Sadie Powell <sadie@witchery.services>
6  *   Copyright (C) 2012-2015, 2018 Attila Molnar <attilamolnar@hush.com>
7  *   Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
8  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
9  *   Copyright (C) 2009 Matt Smith <dz@inspircd.org>
10  *   Copyright (C) 2008-2009 Robin Burchell <robin+git@viroteck.net>
11  *   Copyright (C) 2007-2008 Dennis Friis <peavey@inspircd.org>
12  *   Copyright (C) 2004, 2006-2009 Craig Edwards <brain@inspircd.org>
13  *
14  * This file is part of InspIRCd.  InspIRCd is free software: you can
15  * redistribute it and/or modify it under the terms of the GNU General Public
16  * License as published by the Free Software Foundation, version 2.
17  *
18  * This program is distributed in the hope that it will be useful, but WITHOUT
19  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
21  * details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  */
26
27
28 #include "inspircd.h"
29
30 /** An alias definition
31  */
32 class Alias
33 {
34  public:
35         /** The text of the alias command */
36         std::string AliasedCommand;
37
38         /** Text to replace with */
39         std::string ReplaceFormat;
40
41         /** Nickname required to perform alias */
42         std::string RequiredNick;
43
44         /** Alias requires ulined server */
45         bool ULineOnly;
46
47         /** Requires oper? */
48         bool OperOnly;
49
50         /* whether or not it may be executed via fantasy (default OFF) */
51         bool ChannelCommand;
52
53         /* whether or not it may be executed via /command (default ON) */
54         bool UserCommand;
55
56         /** Format that must be matched for use */
57         std::string format;
58
59         /** Strip color codes before match? */
60         bool StripColor;
61 };
62
63 class ModuleAlias : public Module
64 {
65         std::string fprefix;
66
67         /* We cant use a map, there may be multiple aliases with the same name.
68          * We can, however, use a fancy invention: the multimap. Maps a key to one or more values.
69          *              -- w00t
70          */
71         typedef insp::flat_multimap<std::string, Alias, irc::insensitive_swo> AliasMap;
72
73         AliasMap Aliases;
74
75         /* whether or not +B users are allowed to use fantasy commands */
76         bool AllowBots;
77         UserModeReference botmode;
78
79         // Whether we are actively executing an alias.
80         bool active;
81
82  public:
83         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
84         {
85                 AliasMap newAliases;
86                 ConfigTagList tags = ServerInstance->Config->ConfTags("alias");
87                 for(ConfigIter i = tags.first; i != tags.second; ++i)
88                 {
89                         ConfigTag* tag = i->second;
90                         Alias a;
91                         a.AliasedCommand = tag->getString("text");
92                         if (a.AliasedCommand.empty())
93                                 throw ModuleException("<alias:text> is empty! at " + tag->getTagLocation());
94
95                         tag->readString("replace", a.ReplaceFormat, true);
96                         if (a.ReplaceFormat.empty())
97                                 throw ModuleException("<alias:replace> is empty! at " + tag->getTagLocation());
98
99                         a.RequiredNick = tag->getString("requires");
100                         a.ULineOnly = tag->getBool("uline");
101                         a.ChannelCommand = tag->getBool("channelcommand", false);
102                         a.UserCommand = tag->getBool("usercommand", true);
103                         a.OperOnly = tag->getBool("operonly");
104                         a.format = tag->getString("format");
105                         a.StripColor = tag->getBool("stripcolor");
106
107                         std::transform(a.AliasedCommand.begin(), a.AliasedCommand.end(), a.AliasedCommand.begin(), ::toupper);
108                         newAliases.insert(std::make_pair(a.AliasedCommand, a));
109                 }
110
111                 ConfigTag* fantasy = ServerInstance->Config->ConfValue("fantasy");
112                 AllowBots = fantasy->getBool("allowbots", false);
113                 fprefix = fantasy->getString("prefix", "!", 1, ServerInstance->Config->Limits.MaxLine);
114                 Aliases.swap(newAliases);
115         }
116
117         ModuleAlias()
118                 : botmode(this, "bot")
119                 , active(false)
120         {
121         }
122
123         Version GetVersion() CXX11_OVERRIDE
124         {
125                 return Version("Allows the server administrator to define custom channel commands (e.g. !kick) and server commands (e.g. /OPERSERV).", VF_VENDOR);
126         }
127
128         std::string GetVar(std::string varname, const std::string &original_line)
129         {
130                 irc::spacesepstream ss(original_line);
131                 varname.erase(varname.begin());
132                 int index = *(varname.begin()) - 48;
133                 varname.erase(varname.begin());
134                 bool everything_after = (varname == "-");
135                 std::string word;
136
137                 for (int j = 0; j < index; j++)
138                         ss.GetToken(word);
139
140                 if (everything_after)
141                 {
142                         std::string more;
143                         while (ss.GetToken(more))
144                         {
145                                 word.append(" ");
146                                 word.append(more);
147                         }
148                 }
149
150                 return word;
151         }
152
153         std::string CreateRFCMessage(const std::string& command, CommandBase::Params& parameters)
154         {
155                 std::string message(command);
156                 for (CommandBase::Params::const_iterator iter = parameters.begin(); iter != parameters.end();)
157                 {
158                         const std::string& parameter = *iter++;
159                         message.push_back(' ');
160                         if (iter == parameters.end() && (parameter.empty() || parameter.find(' ') != std::string::npos))
161                                 message.push_back(':');
162                         message.append(parameter);
163                 }
164                 return message;
165         }
166
167         ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
168         {
169                 /* If they're not registered yet, we dont want
170                  * to know.
171                  */
172                 if (user->registered != REG_ALL)
173                         return MOD_RES_PASSTHRU;
174
175                 /* We dont have any commands looking like this? Stop processing. */
176                 std::pair<AliasMap::iterator, AliasMap::iterator> iters = Aliases.equal_range(command);
177                 if (iters.first == iters.second)
178                         return MOD_RES_PASSTHRU;
179
180                 /* The parameters for the command in their original form, with the command stripped off */
181                 std::string original_line = CreateRFCMessage(command, parameters);
182                 std::string compare(original_line, command.length());
183                 while (*(compare.c_str()) == ' ')
184                         compare.erase(compare.begin());
185
186                 for (AliasMap::iterator i = iters.first; i != iters.second; ++i)
187                 {
188                         if (i->second.UserCommand)
189                         {
190                                 if (DoAlias(user, NULL, &(i->second), compare, original_line))
191                                 {
192                                         return MOD_RES_DENY;
193                                 }
194                         }
195                 }
196
197                 // If we made it here, no aliases actually matched.
198                 return MOD_RES_PASSTHRU;
199         }
200
201         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
202         {
203                 // Don't echo anything which is caused by an alias.
204                 if (active)
205                         details.echo = false;
206
207                 return MOD_RES_PASSTHRU;
208         }
209
210         void OnUserPostMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE
211         {
212                 if ((target.type != MessageTarget::TYPE_CHANNEL) || (details.type != MSG_PRIVMSG))
213                 {
214                         return;
215                 }
216
217                 // fcommands are only for local users. Spanningtree will send them back out as their original cmd.
218                 if (!IS_LOCAL(user))
219                 {
220                         return;
221                 }
222
223                 /* Stop here if the user is +B and allowbot is set to no. */
224                 if (!AllowBots && user->IsModeSet(botmode))
225                 {
226                         return;
227                 }
228
229                 Channel *c = target.Get<Channel>();
230                 std::string scommand;
231
232                 // text is like "!moo cows bite me", we want "!moo" first
233                 irc::spacesepstream ss(details.text);
234                 ss.GetToken(scommand);
235
236                 if (scommand.size() <= fprefix.size())
237                 {
238                         return; // wtfbbq
239                 }
240
241                 // we don't want to touch non-fantasy stuff
242                 if (scommand.compare(0, fprefix.size(), fprefix) != 0)
243                 {
244                         return;
245                 }
246
247                 // nor do we give a shit about the prefix
248                 scommand.erase(0, fprefix.size());
249
250                 std::pair<AliasMap::iterator, AliasMap::iterator> iters = Aliases.equal_range(scommand);
251                 if (iters.first == iters.second)
252                         return;
253
254                 /* The parameters for the command in their original form, with the command stripped off */
255                 std::string compare(details.text, scommand.length() + fprefix.size());
256                 while (*(compare.c_str()) == ' ')
257                         compare.erase(compare.begin());
258
259                 for (AliasMap::iterator i = iters.first; i != iters.second; ++i)
260                 {
261                         if (i->second.ChannelCommand)
262                         {
263                                 // We use substr here to remove the fantasy prefix
264                                 if (DoAlias(user, c, &(i->second), compare, details.text.substr(fprefix.size())))
265                                         return;
266                         }
267                 }
268         }
269
270
271         int DoAlias(User *user, Channel *c, Alias *a, const std::string& compare, const std::string& safe)
272         {
273                 std::string stripped(compare);
274                 if (a->StripColor)
275                         InspIRCd::StripColor(stripped);
276
277                 /* Does it match the pattern? */
278                 if (!a->format.empty())
279                 {
280                         if (!InspIRCd::Match(stripped, a->format))
281                                 return 0;
282                 }
283
284                 if ((a->OperOnly) && (!user->IsOper()))
285                         return 0;
286
287                 if (!a->RequiredNick.empty())
288                 {
289                         int numeric = a->ULineOnly ? ERR_NOSUCHSERVICE : ERR_NOSUCHNICK;
290                         User* u = ServerInstance->FindNickOnly(a->RequiredNick);
291                         if (!u)
292                         {
293                                 user->WriteNumeric(numeric, a->RequiredNick, "is currently unavailable. Please try again later.");
294                                 return 1;
295                         }
296
297                         if ((a->ULineOnly) && (!u->server->IsULine()))
298                         {
299                                 ServerInstance->SNO->WriteToSnoMask('a', "NOTICE -- Service "+a->RequiredNick+" required by alias "+a->AliasedCommand+" is not on a U-lined server, possibly underhanded antics detected!");
300                                 user->WriteNumeric(numeric, a->RequiredNick, "is not a network service! Please inform a server operator as soon as possible.");
301                                 return 1;
302                         }
303                 }
304
305                 /* Now, search and replace in a copy of the original_line, replacing $1 through $9 and $1- etc */
306
307                 std::string::size_type crlf = a->ReplaceFormat.find('\n');
308
309                 if (crlf == std::string::npos)
310                 {
311                         DoCommand(a->ReplaceFormat, user, c, safe, a);
312                         return 1;
313                 }
314                 else
315                 {
316                         irc::sepstream commands(a->ReplaceFormat, '\n');
317                         std::string scommand;
318                         while (commands.GetToken(scommand))
319                         {
320                                 DoCommand(scommand, user, c, safe, a);
321                         }
322                         return 1;
323                 }
324         }
325
326         void DoCommand(const std::string& newline, User* user, Channel *chan, const std::string &original_line, Alias* a)
327         {
328                 std::string result;
329                 result.reserve(newline.length());
330                 for (unsigned int i = 0; i < newline.length(); i++)
331                 {
332                         char c = newline[i];
333                         if ((c == '$') && (i + 1 < newline.length()))
334                         {
335                                 if (isdigit(newline[i+1]))
336                                 {
337                                         size_t len = ((i + 2 < newline.length()) && (newline[i+2] == '-')) ? 3 : 2;
338                                         std::string var = newline.substr(i, len);
339                                         result.append(GetVar(var, original_line));
340                                         i += len - 1;
341                                 }
342                                 else if (!newline.compare(i, 5, "$nick", 5))
343                                 {
344                                         result.append(user->nick);
345                                         i += 4;
346                                 }
347                                 else if (!newline.compare(i, 5, "$host", 5))
348                                 {
349                                         result.append(user->GetRealHost());
350                                         i += 4;
351                                 }
352                                 else if (!newline.compare(i, 5, "$chan", 5))
353                                 {
354                                         if (chan)
355                                                 result.append(chan->name);
356                                         i += 4;
357                                 }
358                                 else if (!newline.compare(i, 6, "$ident", 6))
359                                 {
360                                         result.append(user->ident);
361                                         i += 5;
362                                 }
363                                 else if (!newline.compare(i, 6, "$vhost", 6))
364                                 {
365                                         result.append(user->GetDisplayedHost());
366                                         i += 5;
367                                 }
368                                 else if (!newline.compare(i, 12, "$requirement", 12))
369                                 {
370                                         result.append(a->RequiredNick);
371                                         i += 11;
372                                 }
373                                 else
374                                         result.push_back(c);
375                         }
376                         else
377                                 result.push_back(c);
378                 }
379
380                 irc::tokenstream ss(result);
381                 CommandBase::Params pars;
382                 std::string command, token;
383
384                 ss.GetMiddle(command);
385                 while (ss.GetTrailing(token))
386                 {
387                         pars.push_back(token);
388                 }
389
390                 active = true;
391                 ServerInstance->Parser.CallHandler(command, pars, user);
392                 active = false;
393         }
394
395         void Prioritize() CXX11_OVERRIDE
396         {
397                 // Prioritise after spanningtree so that channel aliases show the alias before the effects.
398                 Module* linkmod = ServerInstance->Modules->Find("m_spanningtree.so");
399                 ServerInstance->Modules->SetPriority(this, I_OnUserPostMessage, PRIORITY_AFTER, linkmod);
400         }
401 };
402
403 MODULE_INIT(ModuleAlias)