]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/command_parse.cpp
Move all local-only fields to LocalUser
[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)->Penalty += cm != cmdlist.end() ? cm->second->Penalty : 2;
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",user->nick.c_str(),irc::Spacify(user->oper.c_str()),command.c_str());
344                         return do_more;
345                 }
346         }
347         if ((user->registered == REG_ALL) && (!IS_OPER(user)) && (cm->second->IsDisabled()))
348         {
349                 /* command is disabled! */
350                 if (ServerInstance->Config->DisabledDontExist)
351                 {
352                         user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str());
353                 }
354                 else
355                 {
356                         user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :This command has been disabled.",
357                                                                                 user->nick.c_str(), command.c_str());
358                 }
359
360                 ServerInstance->SNO->WriteToSnoMask('t', "%s denied for %s (%s@%s)",
361                                 command.c_str(), user->nick.c_str(), user->ident.c_str(), user->host.c_str());
362                 return do_more;
363         }
364         if (command_p.size() < cm->second->min_params)
365         {
366                 user->WriteNumeric(ERR_NEEDMOREPARAMS, "%s %s :Not enough parameters.", user->nick.c_str(), command.c_str());
367                 if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (cm->second->syntax.length()))
368                         user->WriteNumeric(RPL_SYNTAX, "%s :SYNTAX %s %s", user->nick.c_str(), cm->second->command.c_str(), cm->second->syntax.c_str());
369                 return do_more;
370         }
371         if ((user->registered != REG_ALL) && (!cm->second->WorksBeforeReg()))
372         {
373                 user->WriteNumeric(ERR_NOTREGISTERED, "%s :You have not registered",command.c_str());
374                 return do_more;
375         }
376         else
377         {
378                 /* passed all checks.. first, do the (ugly) stats counters. */
379                 cm->second->use_count++;
380                 cm->second->total_bytes += cmd.length();
381
382                 /* module calls too */
383                 FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, command_p, user, true, cmd));
384                 if (MOD_RESULT == MOD_RES_DENY)
385                         return do_more;
386
387                 /*
388                  * WARNING: be careful, the user may be deleted soon
389                  */
390                 CmdResult result = cm->second->Handle(command_p, user);
391
392                 FOREACH_MOD(I_OnPostCommand,OnPostCommand(command, command_p, user, result,cmd));
393                 return do_more;
394         }
395 }
396
397 void CommandParser::RemoveCommand(Command* x)
398 {
399         Commandtable::iterator n = cmdlist.find(x->command);
400         if (n != cmdlist.end() && n->second == x)
401                 cmdlist.erase(n);
402 }
403
404 Command::~Command()
405 {
406         ServerInstance->Parser->RemoveCommand(this);
407 }
408
409 bool CommandParser::ProcessBuffer(std::string &buffer,User *user)
410 {
411         if (!user || buffer.empty())
412                 return true;
413
414         ServerInstance->Logs->Log("USERINPUT", DEBUG, "C[%d] I :%s %s", 
415                 user->GetFd(), user->nick.c_str(), buffer.c_str());
416         return ProcessCommand(user,buffer);
417 }
418
419 bool CommandParser::AddCommand(Command *f)
420 {
421         /* create the command and push it onto the table */
422         if (cmdlist.find(f->command) == cmdlist.end())
423         {
424                 cmdlist[f->command] = f;
425                 return true;
426         }
427         return false;
428 }
429
430 CommandParser::CommandParser() 
431 {
432         para.resize(128);
433 }
434
435 int CommandParser::TranslateUIDs(const std::vector<TranslateType> to, const std::vector<std::string> &source, std::string &dest, bool prefix_final, Command* custom_translator)
436 {
437         std::vector<TranslateType>::const_iterator types = to.begin();
438         User* user = NULL;
439         unsigned int i;
440         int translations = 0;
441         dest.clear();
442
443         for(i=0; i < source.size(); i++)
444         {
445                 TranslateType t;
446                 std::string item = source[i];
447
448                 if (types == to.end())
449                         t = TR_TEXT;
450                 else
451                 {
452                         t = *types;
453                         types++;
454                 }
455
456                 if (prefix_final && i == source.size() - 1)
457                         dest.append(":");
458
459                 switch (t)
460                 {
461                         case TR_NICK:
462                                 /* Translate single nickname */
463                                 user = ServerInstance->FindNick(item);
464                                 if (user)
465                                 {
466                                         dest.append(user->uuid);
467                                         translations++;
468                                 }
469                                 else
470                                         dest.append(item);
471                         break;
472                         case TR_CUSTOM:
473                                 if (custom_translator)
474                                         custom_translator->EncodeParameter(item, i);
475                                 dest.append(item);
476                         break;
477                         case TR_END:
478                         case TR_TEXT:
479                         default:
480                                 /* Do nothing */
481                                 dest.append(item);
482                         break;
483                 }
484                 if (i != source.size() - 1)
485                         dest.append(" ");
486         }
487
488         return translations;
489 }
490
491 int CommandParser::TranslateUIDs(TranslateType to, const std::string &source, std::string &dest)
492 {
493         User* user = NULL;
494         std::string item;
495         int translations = 0;
496         dest.clear();
497
498         switch (to)
499         {
500                 case TR_NICK:
501                         /* Translate single nickname */
502                         user = ServerInstance->FindNick(source);
503                         if (user)
504                         {
505                                 dest = user->uuid;
506                                 translations++;
507                         }
508                         else
509                                 dest = source;
510                 break;
511                 case TR_END:
512                 case TR_TEXT:
513                 default:
514                         /* Do nothing */
515                         dest = source;
516                 break;
517         }
518
519         return translations;
520 }