]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/command_parse.cpp
86f801d3e3c3e4023d7f25d2f66235679ede9069
[user/henk/code/inspircd.git] / src / command_parse.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *          the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include "xline.h"
16 #include "socketengine.h"
17 #include "socket.h"
18 #include "command_parse.h"
19 #include "exitcodes.h"
20
21 /* Directory Searching for Unix-Only */
22 #ifndef WIN32
23 #include <dirent.h>
24 #include <dlfcn.h>
25 #endif
26
27 int InspIRCd::PassCompare(Extensible* ex, const std::string &data, const std::string &input, const std::string &hashtype)
28 {
29         ModResult res;
30         FIRST_MOD_RESULT(OnPassCompare, res, (ex, data, input, hashtype));
31
32         /* Module matched */
33         if (res == MOD_RES_ALLOW)
34                 return 0;
35
36         /* Module explicitly didnt match */
37         if (res == MOD_RES_DENY)
38                 return 1;
39
40         /* We dont handle any hash types except for plaintext - Thanks tra26 */
41         if (hashtype != "" && hashtype != "plaintext")
42                 /* See below. 1 because they dont match */
43                 return 1;
44
45         return (data != input); // this seems back to front, but returns 0 if they *match*, 1 else
46 }
47
48 /* LoopCall is used to call a command classes handler repeatedly based on the contents of a comma seperated list.
49  * There are two overriden versions of this method, one of which takes two potential lists and the other takes one.
50  * We need a version which takes two potential lists for JOIN, because a JOIN may contain two lists of items at once,
51  * the channel names and their keys as follows:
52  * JOIN #chan1,#chan2,#chan3 key1,,key3
53  * Therefore, we need to deal with both lists concurrently. The first instance of this method does that by creating
54  * two instances of irc::commasepstream and reading them both together until the first runs out of tokens.
55  * The second version is much simpler and just has the one stream to read, and is used in NAMES, WHOIS, PRIVMSG etc.
56  * Both will only parse until they reach ServerInstance->Config->MaxTargets number of targets, to stop abuse via spam.
57  */
58 int CommandParser::LoopCall(User* user, Command* CommandObj, const std::vector<std::string>& parameters, unsigned int splithere, unsigned int extra)
59 {
60         if (splithere >= parameters.size())
61                 return 0;
62
63         /* First check if we have more than one item in the list, if we don't we return zero here and the handler
64          * which called us just carries on as it was.
65          */
66         if (parameters[splithere].find(',') == std::string::npos)
67                 return 0;
68
69         /** Some lame ircds will weed out dupes using some shitty O(n^2) algorithm.
70          * By using std::set (thanks for the idea w00t) we can cut this down a ton.
71          * ...VOOODOOOO!
72          */
73         std::set<irc::string> dupes;
74
75         /* Create two lists, one for channel names, one for keys
76          */
77         irc::commasepstream items1(parameters[splithere]);
78         irc::commasepstream items2(parameters[extra]);
79         std::string extrastuff;
80         std::string item;
81         unsigned int max = 0;
82
83         /* Attempt to iterate these lists and call the command objech
84          * which called us, for every parameter pair until there are
85          * no more left to parse.
86          */
87         while (items1.GetToken(item) && (max++ < ServerInstance->Config->MaxTargets))
88         {
89                 if (dupes.find(item.c_str()) == dupes.end())
90                 {
91                         std::vector<std::string> new_parameters;
92
93                         for (unsigned int t = 0; (t < parameters.size()) && (t < MAXPARAMETERS); t++)
94                                 new_parameters.push_back(parameters[t]);
95
96                         if (!items2.GetToken(extrastuff))
97                                 extrastuff = "";
98
99                         new_parameters[splithere] = item.c_str();
100                         new_parameters[extra] = extrastuff.c_str();
101
102                         CommandObj->Handle(new_parameters, user);
103
104                         dupes.insert(item.c_str());
105                 }
106         }
107         return 1;
108 }
109
110 int CommandParser::LoopCall(User* user, Command* CommandObj, const std::vector<std::string>& parameters, unsigned int splithere)
111 {
112         if (splithere >= parameters.size())
113                 return 0;
114
115         /* First check if we have more than one item in the list, if we don't we return zero here and the handler
116          * which called us just carries on as it was.
117          */
118         if (parameters[splithere].find(',') == std::string::npos)
119                 return 0;
120
121         std::set<irc::string> dupes;
122
123         /* Only one commasepstream here */
124         irc::commasepstream items1(parameters[splithere]);
125         std::string item;
126         unsigned int max = 0;
127
128         /* Parse the commasepstream until there are no tokens remaining.
129          * Each token we parse out, call the command handler that called us
130          * with it
131          */
132         while (items1.GetToken(item) && (max++ < ServerInstance->Config->MaxTargets))
133         {
134                 if (dupes.find(item.c_str()) == dupes.end())
135                 {
136                         std::vector<std::string> new_parameters;
137
138                         for (unsigned int t = 0; (t < parameters.size()) && (t < MAXPARAMETERS); t++)
139                                 new_parameters.push_back(parameters[t]);
140
141                         new_parameters[splithere] = item.c_str();
142
143                         /* Execute the command handler. */
144                         CommandObj->Handle(new_parameters, user);
145
146                         dupes.insert(item.c_str());
147                 }
148         }
149         /* By returning 1 we tell our caller that nothing is to be done,
150          * as all the previous calls handled the data. This makes the parent
151          * return without doing any processing.
152          */
153         return 1;
154 }
155
156 bool CommandParser::IsValidCommand(const std::string &commandname, unsigned int pcnt, User * user)
157 {
158         Commandtable::iterator n = cmdlist.find(commandname);
159
160         if (n != cmdlist.end())
161         {
162                 if ((pcnt >= n->second->min_params))
163                 {
164                         if (IS_LOCAL(user) && n->second->flags_needed)
165                         {
166                                 if (user->IsModeSet(n->second->flags_needed))
167                                 {
168                                         return (user->HasPermission(commandname));
169                                 }
170                         }
171                         else
172                         {
173                                 return true;
174                         }
175                 }
176         }
177         return false;
178 }
179
180 Command* CommandParser::GetHandler(const std::string &commandname)
181 {
182         Commandtable::iterator n = cmdlist.find(commandname);
183         if (n != cmdlist.end())
184                 return n->second;
185
186         return NULL;
187 }
188
189 // calls a handler function for a command
190
191 CmdResult CommandParser::CallHandler(const std::string &commandname, const std::vector<std::string>& parameters, User *user)
192 {
193         Commandtable::iterator n = cmdlist.find(commandname);
194
195         if (n != cmdlist.end())
196         {
197                 if (parameters.size() >= n->second->min_params)
198                 {
199                         bool bOkay = false;
200
201                         if (IS_LOCAL(user) && n->second->flags_needed)
202                         {
203                                 /* if user is local, and flags are needed .. */
204
205                                 if (user->IsModeSet(n->second->flags_needed))
206                                 {
207                                         /* if user has the flags, and now has the permissions, go ahead */
208                                         if (user->HasPermission(commandname))
209                                                 bOkay = true;
210                                 }
211                         }
212                         else
213                         {
214                                 /* remote or no flags required anyway */
215                                 bOkay = true;
216                         }
217
218                         if (bOkay)
219                         {
220                                 return n->second->Handle(parameters,user);
221                         }
222                 }
223         }
224         return CMD_INVALID;
225 }
226
227 bool CommandParser::ProcessCommand(User *user, std::string &cmd)
228 {
229         std::vector<std::string> command_p;
230         irc::tokenstream tokens(cmd);
231         std::string command, token;
232         tokens.GetToken(command);
233
234         /* A client sent a nick prefix on their command (ick)
235          * rhapsody and some braindead bouncers do this --
236          * the rfc says they shouldnt but also says the ircd should
237          * discard it if they do.
238          */
239         if (command[0] == ':')
240                 tokens.GetToken(command);
241
242         while (tokens.GetToken(token) && (command_p.size() <= MAXPARAMETERS))
243                 command_p.push_back(token);
244
245         std::transform(command.begin(), command.end(), command.begin(), ::toupper);
246
247         /* find the command, check it exists */
248         Commandtable::iterator cm = cmdlist.find(command);
249
250         /* Modify the user's penalty regardless of whether or not the command exists */
251         bool do_more = true;
252         if (IS_LOCAL(user) && !user->HasPrivPermission("users/flood/no-throttle"))
253         {
254                 // If it *doesn't* exist, give it a slightly heftier penalty than normal to deter flooding us crap
255                 IS_LOCAL(user)->CommandFloodPenalty += cm != cmdlist.end() ? cm->second->Penalty * 1000 : 2000;
256         }
257
258
259         if (cm == cmdlist.end())
260         {
261                 ModResult MOD_RESULT;
262                 FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false, cmd));
263                 if (MOD_RESULT == MOD_RES_DENY)
264                         return true;
265
266                 /*
267                  * This double lookup is in case a module (abbreviation) wishes to change a command.
268                  * Sure, the double lookup is a bit painful, but bear in mind this only happens for unknowns anyway.
269                  *
270                  * Thanks dz for making me actually understand why this is necessary!
271                  * -- w00t
272                  */
273                 cm = cmdlist.find(command);
274                 if (cm == cmdlist.end())
275                 {
276                         if (user->registered == REG_ALL)
277                                 user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str());
278                         ServerInstance->stats->statsUnknown++;
279                         return true;
280                 }
281         }
282
283         if (cm->second->max_params && command_p.size() > cm->second->max_params)
284         {
285                 /*
286                  * command_p input (assuming max_params 1):
287                  *      this
288                  *      is
289                  *      a
290                  *      test
291                  */
292                 std::string lparam = "";
293
294                 /*
295                  * The '-1' here is a clever trick, we'll go backwards throwing everything into a temporary param
296                  * and then just toss that into the array.
297                  * -- w00t
298                  */
299                 while (command_p.size() > (cm->second->max_params - 1))
300                 {
301                         // BE CAREFUL: .end() returns past the end of the vector, hence decrement.
302                         std::vector<std::string>::iterator it = --command_p.end();
303
304                         lparam.insert(0, " " + *(it));
305                         command_p.erase(it); // remove last element
306                 }
307
308                 /* we now have (each iteration):
309                  *      ' test'
310                  *      ' a test'
311                  *      ' is a test' <-- final string
312                  * ...now remove the ' ' at the start...
313                  */
314                 lparam.erase(lparam.begin());
315
316                 /* param is now 'is a test', which is exactly what we wanted! */
317                 command_p.push_back(lparam);
318         }
319
320         /*
321          * We call OnPreCommand here seperately if the command exists, so the magic above can
322          * truncate to max_params if necessary. -- w00t
323          */
324         ModResult MOD_RESULT;
325         FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, false, cmd));
326         if (MOD_RESULT == MOD_RES_DENY)
327                 return true;
328
329         /* activity resets the ping pending timer */
330         LocalUser* luser = IS_LOCAL(user);
331         if (luser)
332                 luser->nping = ServerInstance->Time() + luser->MyClass->GetPingTime();
333
334         if (cm->second->flags_needed)
335         {
336                 if (!user->IsModeSet(cm->second->flags_needed))
337                 {
338                         user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - You do not have the required operator privileges",user->nick.c_str());
339                         return do_more;
340                 }
341                 if (!user->HasPermission(command))
342                 {
343                         user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to command %s",
344                                 user->nick.c_str(), user->oper->NameStr(), command.c_str());
345                         return do_more;
346                 }
347         }
348         if ((user->registered == REG_ALL) && (!IS_OPER(user)) && (cm->second->IsDisabled()))
349         {
350                 /* command is disabled! */
351                 if (ServerInstance->Config->DisabledDontExist)
352                 {
353                         user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str());
354                 }
355                 else
356                 {
357                         user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :This command has been disabled.",
358                                                                                 user->nick.c_str(), command.c_str());
359                 }
360
361                 ServerInstance->SNO->WriteToSnoMask('t', "%s denied for %s (%s@%s)",
362                                 command.c_str(), user->nick.c_str(), user->ident.c_str(), user->host.c_str());
363                 return do_more;
364         }
365         if (command_p.size() < cm->second->min_params)
366         {
367                 user->WriteNumeric(ERR_NEEDMOREPARAMS, "%s %s :Not enough parameters.", user->nick.c_str(), command.c_str());
368                 if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (cm->second->syntax.length()))
369                         user->WriteNumeric(RPL_SYNTAX, "%s :SYNTAX %s %s", user->nick.c_str(), cm->second->command.c_str(), cm->second->syntax.c_str());
370                 return do_more;
371         }
372         if ((user->registered != REG_ALL) && (!cm->second->WorksBeforeReg()))
373         {
374                 user->WriteNumeric(ERR_NOTREGISTERED, "%s :You have not registered",command.c_str());
375                 return do_more;
376         }
377         else
378         {
379                 /* passed all checks.. first, do the (ugly) stats counters. */
380                 cm->second->use_count++;
381                 cm->second->total_bytes += cmd.length();
382
383                 /* module calls too */
384                 FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, true, cmd));
385                 if (MOD_RESULT == MOD_RES_DENY)
386                         return do_more;
387
388                 /*
389                  * WARNING: be careful, the user may be deleted soon
390                  */
391                 CmdResult result = cm->second->Handle(command_p, user);
392
393                 FOREACH_MOD(I_OnPostCommand,OnPostCommand(command, command_p, user, result,cmd));
394                 return do_more;
395         }
396 }
397
398 void CommandParser::RemoveCommand(Command* x)
399 {
400         Commandtable::iterator n = cmdlist.find(x->command);
401         if (n != cmdlist.end() && n->second == x)
402                 cmdlist.erase(n);
403 }
404
405 Command::~Command()
406 {
407         ServerInstance->Parser->RemoveCommand(this);
408 }
409
410 bool CommandParser::ProcessBuffer(std::string &buffer,User *user)
411 {
412         if (!user || buffer.empty())
413                 return true;
414
415         ServerInstance->Logs->Log("USERINPUT", DEBUG, "C[%s] I :%s %s",
416                 user->uuid.c_str(), user->nick.c_str(), buffer.c_str());
417         return ProcessCommand(user,buffer);
418 }
419
420 bool CommandParser::AddCommand(Command *f)
421 {
422         /* create the command and push it onto the table */
423         if (cmdlist.find(f->command) == cmdlist.end())
424         {
425                 cmdlist[f->command] = f;
426                 return true;
427         }
428         return false;
429 }
430
431 CommandParser::CommandParser()
432 {
433         para.resize(128);
434 }
435
436 int CommandParser::TranslateUIDs(const std::vector<TranslateType> to, const std::vector<std::string> &source, std::string &dest, bool prefix_final, Command* custom_translator)
437 {
438         std::vector<TranslateType>::const_iterator types = to.begin();
439         User* user = NULL;
440         unsigned int i;
441         int translations = 0;
442         dest.clear();
443
444         for(i=0; i < source.size(); i++)
445         {
446                 TranslateType t;
447                 std::string item = source[i];
448
449                 if (types == to.end())
450                         t = TR_TEXT;
451                 else
452                 {
453                         t = *types;
454                         types++;
455                 }
456
457                 if (prefix_final && i == source.size() - 1)
458                         dest.append(":");
459
460                 switch (t)
461                 {
462                         case TR_NICK:
463                                 /* Translate single nickname */
464                                 user = ServerInstance->FindNick(item);
465                                 if (user)
466                                 {
467                                         dest.append(user->uuid);
468                                         translations++;
469                                 }
470                                 else
471                                         dest.append(item);
472                         break;
473                         case TR_CUSTOM:
474                                 if (custom_translator)
475                                         custom_translator->EncodeParameter(item, i);
476                                 dest.append(item);
477                         break;
478                         case TR_END:
479                         case TR_TEXT:
480                         default:
481                                 /* Do nothing */
482                                 dest.append(item);
483                         break;
484                 }
485                 if (i != source.size() - 1)
486                         dest.append(" ");
487         }
488
489         return translations;
490 }
491
492 int CommandParser::TranslateUIDs(TranslateType to, const std::string &source, std::string &dest)
493 {
494         User* user = NULL;
495         std::string item;
496         int translations = 0;
497         dest.clear();
498
499         switch (to)
500         {
501                 case TR_NICK:
502                         /* Translate single nickname */
503                         user = ServerInstance->FindNick(source);
504                         if (user)
505                         {
506                                 dest = user->uuid;
507                                 translations++;
508                         }
509                         else
510                                 dest = source;
511                 break;
512                 case TR_END:
513                 case TR_TEXT:
514                 default:
515                         /* Do nothing */
516                         dest = source;
517                 break;
518         }
519
520         return translations;
521 }