]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/command_parse.cpp
6ba4758bde01cf759335a90909b8de22726728f2
[user/henk/code/inspircd.git] / src / command_parse.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/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 "wildcard.h"
16 #include "xline.h"
17 #include "socketengine.h"
18 #include "socket.h"
19 #include "command_parse.h"
20 #include "exitcodes.h"
21
22 /* Directory Searching for Unix-Only */
23 #ifndef WIN32
24 #include <dirent.h>
25 #include <dlfcn.h>
26 #endif
27
28 bool InspIRCd::ULine(const char* server)
29 {
30         if (!server)
31                 return false;
32         if (!*server)
33                 return true;
34
35         return (Config->ulines.find(server) != Config->ulines.end());
36 }
37
38 bool InspIRCd::SilentULine(const char* server)
39 {
40         std::map<irc::string,bool>::iterator n = Config->ulines.find(server);
41         if (n != Config->ulines.end())
42                 return n->second;
43         else return false;
44 }
45
46 int InspIRCd::OperPassCompare(const char* data,const char* input, int tagnumber)
47 {
48         int MOD_RESULT = 0;
49         FOREACH_RESULT_I(this,I_OnOperCompare,OnOperCompare(data, input, tagnumber))
50         if (MOD_RESULT == 1)
51                 return 0;
52         if (MOD_RESULT == -1)
53                 return 1;
54         return strcmp(data,input);
55 }
56
57 std::string InspIRCd::TimeString(time_t curtime)
58 {
59         return std::string(ctime(&curtime),24);
60 }
61
62 /** Refactored by Brain, Jun 2007. Much faster with some clever O(1) array
63  * lookups and pointer maths.
64  */
65 long InspIRCd::Duration(const std::string &str)
66 {
67         unsigned char multiplier = 0;
68         long total = 0;
69         long times = 1;
70         long subtotal = 0;
71
72         /* Iterate each item in the string, looking for number or multiplier */
73         for (std::string::const_reverse_iterator i = str.rbegin(); i != str.rend(); ++i)
74         {
75                 /* Found a number, queue it onto the current number */
76                 if ((*i >= '0') && (*i <= '9'))
77                 {
78                         subtotal = subtotal + ((*i - '0') * times);
79                         times = times * 10;
80                 }
81                 else
82                 {
83                         /* Found something thats not a number, find out how much
84                          * it multiplies the built up number by, multiply the total
85                          * and reset the built up number.
86                          */
87                         if (subtotal)
88                                 total += subtotal * duration_multi[multiplier];
89
90                         /* Next subtotal please */
91                         subtotal = 0;
92                         multiplier = *i;
93                         times = 1;
94                 }
95         }
96         if (multiplier)
97         {
98                 total += subtotal * duration_multi[multiplier];
99                 subtotal = 0;
100         }
101         /* Any trailing values built up are treated as raw seconds */
102         return total + subtotal;
103 }
104
105 /* LoopCall is used to call a command classes handler repeatedly based on the contents of a comma seperated list.
106  * There are two overriden versions of this method, one of which takes two potential lists and the other takes one.
107  * We need a version which takes two potential lists for JOIN, because a JOIN may contain two lists of items at once,
108  * the channel names and their keys as follows:
109  * JOIN #chan1,#chan2,#chan3 key1,,key3
110  * Therefore, we need to deal with both lists concurrently. The first instance of this method does that by creating
111  * two instances of irc::commasepstream and reading them both together until the first runs out of tokens.
112  * The second version is much simpler and just has the one stream to read, and is used in NAMES, WHOIS, PRIVMSG etc.
113  * Both will only parse until they reach ServerInstance->Config->MaxTargets number of targets, to stop abuse via spam.
114  */
115 int CommandParser::LoopCall(userrec* user, command_t* CommandObj, const char** parameters, int pcnt, unsigned int splithere, unsigned int extra)
116 {
117         /* First check if we have more than one item in the list, if we don't we return zero here and the handler
118          * which called us just carries on as it was.
119          */
120         if (!strchr(parameters[splithere],','))
121                 return 0;
122
123         /** Some lame ircds will weed out dupes using some shitty O(n^2) algorithm.
124          * By using std::map (thanks for the idea w00t) we can cut this down a ton.
125          * ...VOOODOOOO!
126          */
127         std::map<irc::string, bool> dupes;
128
129         /* Create two lists, one for channel names, one for keys
130          */
131         irc::commasepstream items1(parameters[splithere]);
132         irc::commasepstream items2(parameters[extra]);
133         std::string extrastuff;
134         std::string item;
135         unsigned int max = 0;
136
137         /* Attempt to iterate these lists and call the command objech
138          * which called us, for every parameter pair until there are
139          * no more left to parse.
140          */
141         while (items1.GetToken(item) && (max++ < ServerInstance->Config->MaxTargets))
142         {
143                 if (dupes.find(item.c_str()) == dupes.end())
144                 {
145                         const char* new_parameters[MAXPARAMETERS];
146
147                         for (int t = 0; (t < pcnt) && (t < MAXPARAMETERS); t++)
148                                 new_parameters[t] = parameters[t];
149
150                         if (!items2.GetToken(extrastuff))
151                                 extrastuff = "";
152
153                         new_parameters[splithere] = item.c_str();
154                         new_parameters[extra] = extrastuff.c_str();
155
156                         CommandObj->Handle(new_parameters,pcnt,user);
157
158                         dupes[item.c_str()] = true;
159                 }
160         }
161         return 1;
162 }
163
164 int CommandParser::LoopCall(userrec* user, command_t* CommandObj, const char** parameters, int pcnt, unsigned int splithere)
165 {
166         /* First check if we have more than one item in the list, if we don't we return zero here and the handler
167          * which called us just carries on as it was.
168          */
169         if (!strchr(parameters[splithere],','))
170                 return 0;
171
172         std::map<irc::string, bool> dupes;
173
174         /* Only one commasepstream here */
175         irc::commasepstream items1(parameters[splithere]);
176         std::string item;
177         unsigned int max = 0;
178
179         /* Parse the commasepstream until there are no tokens remaining.
180          * Each token we parse out, call the command handler that called us
181          * with it
182          */
183         while (items1.GetToken(item) && (max++ < ServerInstance->Config->MaxTargets))
184         {
185                 if (dupes.find(item.c_str()) == dupes.end())
186                 {
187                         const char* new_parameters[MAXPARAMETERS];
188
189                         for (int t = 0; (t < pcnt) && (t < MAXPARAMETERS); t++)
190                                 new_parameters[t] = parameters[t];
191
192                         new_parameters[splithere] = item.c_str();
193
194                         parameters[splithere] = item.c_str();
195
196                         /* Execute the command handler over and over. If someone pulls our user
197                          * record out from under us (e.g. if we /kill a comma sep list, and we're
198                          * in that list ourselves) abort if we're gone.
199                          */
200                         CommandObj->Handle(new_parameters,pcnt,user);
201
202                         dupes[item.c_str()] = true;
203                 }
204         }
205         /* By returning 1 we tell our caller that nothing is to be done,
206          * as all the previous calls handled the data. This makes the parent
207          * return without doing any processing.
208          */
209         return 1;
210 }
211
212 bool CommandParser::IsValidCommand(const std::string &commandname, int pcnt, userrec * user)
213 {
214         command_table::iterator n = cmdlist.find(commandname);
215
216         if (n != cmdlist.end())
217         {
218                 if ((pcnt>=n->second->min_params) && (n->second->source != "<core>"))
219                 {
220                         if ((!n->second->flags_needed) || (user->IsModeSet(n->second->flags_needed)))
221                         {
222                                 if (n->second->flags_needed)
223                                 {
224                                         return ((user->HasPermission(commandname)) || (ServerInstance->ULine(user->server)));
225                                 }
226                                 return true;
227                         }
228                 }
229         }
230         return false;
231 }
232
233 command_t* CommandParser::GetHandler(const std::string &commandname)
234 {
235         command_table::iterator n = cmdlist.find(commandname);
236         if (n != cmdlist.end())
237                 return n->second;
238
239         return NULL;
240 }
241
242 // calls a handler function for a command
243
244 CmdResult CommandParser::CallHandler(const std::string &commandname,const char** parameters, int pcnt, userrec *user)
245 {
246         command_table::iterator n = cmdlist.find(commandname);
247
248         if (n != cmdlist.end())
249         {
250                 if (pcnt >= n->second->min_params)
251                 {
252                         bool bOkay = false;
253
254                         if (IS_LOCAL(user) && n->second->flags_needed)
255                         {
256                                 /* if user is local, and flags are needed .. */
257
258                                 if (user->IsModeSet(n->second->flags_needed))
259                                 {
260                                         /* if user has the flags, and now has the permissions, go ahead */
261                                         if (user->HasPermission(commandname))
262                                                 bOkay = true;
263                                 }
264                         }
265                         else
266                         {
267                                 /* remote or no flags required anyway */
268                                 bOkay = true;
269                         }
270
271                         if (bOkay)
272                         {
273                                 return n->second->Handle(parameters,pcnt,user);
274                         }
275                 }
276         }
277         return CMD_INVALID;
278 }
279
280 void CommandParser::ProcessCommand(userrec *user, std::string &cmd)
281 {
282         const char *command_p[MAXPARAMETERS];
283         int items = 0;
284         irc::tokenstream tokens(cmd);
285         std::string command;
286         tokens.GetToken(command);
287
288         /* A client sent a nick prefix on their command (ick)
289          * rhapsody and some braindead bouncers do this --
290          * the rfc says they shouldnt but also says the ircd should
291          * discard it if they do.
292          */
293         if (*command.c_str() == ':')
294                 tokens.GetToken(command);
295
296         while (tokens.GetToken(para[items]) && (items < MAXPARAMETERS))
297         {
298                 command_p[items] = para[items].c_str();
299                 items++;
300         }
301
302         std::transform(command.begin(), command.end(), command.begin(), ::toupper);
303                 
304         int MOD_RESULT = 0;
305         FOREACH_RESULT(I_OnPreCommand,OnPreCommand(command,command_p,items,user,false,cmd));
306         if (MOD_RESULT == 1) {
307                 return;
308         }
309
310         command_table::iterator cm = cmdlist.find(command);
311         
312         if (cm != cmdlist.end())
313         {
314                 if (user)
315                 {
316                         /* activity resets the ping pending timer */
317                         user->nping = ServerInstance->Time() + user->pingmax;
318                         if (cm->second->flags_needed)
319                         {
320                                 if (!user->IsModeSet(cm->second->flags_needed))
321                                 {
322                                         user->WriteServ("481 %s :Permission Denied - You do not have the required operator privileges",user->nick);
323                                         return;
324                                 }
325                                 if (!user->HasPermission(command))
326                                 {
327                                         user->WriteServ("481 %s :Permission Denied - Oper type %s does not have access to command %s",user->nick,user->oper,command.c_str());
328                                         return;
329                                 }
330                         }
331                         if ((user->registered == REG_ALL) && (!IS_OPER(user)) && (cm->second->IsDisabled()))
332                         {
333                                 /* command is disabled! */
334                                 user->WriteServ("421 %s %s :This command has been disabled.",user->nick,command.c_str());
335                                 ServerInstance->SNO->WriteToSnoMask('d', "%s denied for %s (%s@%s)",
336                                                 command.c_str(), user->nick, user->ident, user->host);
337                                 return;
338                         }
339                         if (items < cm->second->min_params)
340                         {
341                                 user->WriteServ("461 %s %s :Not enough parameters.", user->nick, command.c_str());
342                                 if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (cm->second->syntax.length()))
343                                         user->WriteServ("304 %s :SYNTAX %s %s", user->nick, cm->second->command.c_str(), cm->second->syntax.c_str());
344                                 return;
345                         }
346                         if ((user->registered == REG_ALL) || (cm->second->WorksBeforeReg()))
347                         {
348                                 /* ikky /stats counters */
349                                 cm->second->use_count++;
350                                 cm->second->total_bytes += cmd.length();
351
352                                 int MOD_RESULT = 0;
353                                 FOREACH_RESULT(I_OnPreCommand,OnPreCommand(command,command_p,items,user,true,cmd));
354                                 if (MOD_RESULT == 1)
355                                         return;
356
357                                 /*
358                                  * WARNING: nothing should come after this, as the user may be on a cull list to
359                                  * be nuked next loop iteration. be sensible.
360                                  */
361                                 CmdResult result = cm->second->Handle(command_p,items,user);
362
363                                 FOREACH_MOD(I_OnPostCommand,OnPostCommand(command, command_p, items, user, result,cmd));
364                                 return;
365                         }
366                         else
367                         {
368                                 user->WriteServ("451 %s :You have not registered",command.c_str());
369                                 return;
370                         }
371                 }
372         }
373         else if (user)
374         {
375                 ServerInstance->stats->statsUnknown++;
376                 user->WriteServ("421 %s %s :Unknown command",user->nick,command.c_str());
377         }
378 }
379
380 bool CommandParser::RemoveCommands(const char* source)
381 {
382         command_table::iterator i,safei;
383         for (i = cmdlist.begin(); i != cmdlist.end(); i++)
384         {
385                 safei = i;
386                 safei++;
387                 if (safei != cmdlist.end())
388                 {
389                         RemoveCommand(safei, source);
390                 }
391         }
392         safei = cmdlist.begin();
393         if (safei != cmdlist.end())
394         {
395                 RemoveCommand(safei, source);
396         }
397         return true;
398 }
399
400 void CommandParser::RemoveCommand(command_table::iterator safei, const char* source)
401 {
402         command_t* x = safei->second;
403         if (x->source == std::string(source))
404         {
405                 cmdlist.erase(safei);
406                 delete x;
407         }
408 }
409
410 void CommandParser::ProcessBuffer(std::string &buffer,userrec *user)
411 {
412         std::string::size_type a;
413
414         if (!user)
415                 return;
416
417         while ((a = buffer.rfind("\n")) != std::string::npos)
418                 buffer.erase(a);
419         while ((a = buffer.rfind("\r")) != std::string::npos)
420                 buffer.erase(a);
421
422         if (buffer.length())
423         {
424                 if (!user->muted)
425                 {
426                         ServerInstance->Log(DEBUG,"C[%d] -> :%s %s",user->GetFd(), user->nick, buffer.c_str());
427                         this->ProcessCommand(user,buffer);
428                 }
429         }
430 }
431
432 bool CommandParser::CreateCommand(command_t *f, void* so_handle)
433 {
434         if (so_handle)
435         {
436                 if (RFCCommands.find(f->command) == RFCCommands.end())
437                         RFCCommands[f->command] = so_handle;
438                 else
439                 {
440                         ServerInstance->Log(DEFAULT,"ERK! Somehow, we loaded a cmd_*.so file twice! Only the first instance is being recorded.");
441                         return false;
442                 }
443         }
444
445         /* create the command and push it onto the table */
446         if (cmdlist.find(f->command) == cmdlist.end())
447         {
448                 cmdlist[f->command] = f;
449                 return true;
450         }
451         else return false;
452 }
453
454 CommandParser::CommandParser(InspIRCd* Instance) : ServerInstance(Instance)
455 {
456         para.resize(128);
457 }
458
459 bool CommandParser::FindSym(void** v, void* h)
460 {
461         *v = dlsym(h, "init_command");
462         const char* err = dlerror();
463         if (err && !(*v))
464         {
465                 ServerInstance->Log(SPARSE, "Error loading core command: %s\n", err);
466                 return false;
467         }
468         return true;
469 }
470
471 bool CommandParser::ReloadCommand(const char* cmd, userrec* user)
472 {
473         char filename[MAXBUF];
474         char commandname[MAXBUF];
475         int y = 0;
476
477         for (const char* x = cmd; *x; x++, y++)
478                 commandname[y] = toupper(*x);
479
480         commandname[y] = 0;
481
482         SharedObjectList::iterator command = RFCCommands.find(commandname);
483
484         if (command != RFCCommands.end())
485         {
486                 command_t* cmdptr = cmdlist.find(commandname)->second;
487                 cmdlist.erase(cmdlist.find(commandname));
488
489                 for (char* x = commandname; *x; x++)
490                         *x = tolower(*x);
491
492
493                 delete cmdptr;
494                 dlclose(command->second);
495                 RFCCommands.erase(command);
496
497                 snprintf(filename, MAXBUF, "cmd_%s.so", commandname);
498                 const char* err = this->LoadCommand(filename);
499                 if (err)
500                 {
501                         if (user)
502                                 user->WriteServ("NOTICE %s :*** Error loading 'cmd_%s.so': %s", user->nick, cmd, err);
503                         return false;
504                 }
505
506                 return true;
507         }
508
509         return false;
510 }
511
512 CmdResult cmd_reload::Handle(const char** parameters, int pcnt, userrec *user)
513 {
514         user->WriteServ("NOTICE %s :*** Reloading command '%s'",user->nick, parameters[0]);
515         if (ServerInstance->Parser->ReloadCommand(parameters[0], user))
516         {
517                 user->WriteServ("NOTICE %s :*** Successfully reloaded command '%s'", user->nick, parameters[0]);
518                 ServerInstance->WriteOpers("*** RELOAD: %s reloaded the '%s' command.", user->nick, parameters[0]);
519                 return CMD_SUCCESS;
520         }
521         else
522         {
523                 user->WriteServ("NOTICE %s :*** Could not reload command '%s' -- fix this problem, then /REHASH as soon as possible!", user->nick, parameters[0]);
524                 return CMD_FAILURE;
525         }
526 }
527
528 const char* CommandParser::LoadCommand(const char* name)
529 {
530         char filename[MAXBUF];
531         void* h;
532         command_t* (*cmd_factory_func)(InspIRCd*);
533
534         /* Command already exists? Succeed silently - this is needed for REHASH */
535         if (RFCCommands.find(name) != RFCCommands.end())
536         {
537                 ServerInstance->Log(DEBUG,"Not reloading command %s/%s, it already exists", LIBRARYDIR, name);
538                 return NULL;
539         }
540
541         snprintf(filename, MAXBUF, "%s/%s", LIBRARYDIR, name);
542         h = dlopen(filename, RTLD_NOW | RTLD_GLOBAL);
543
544         if (!h)
545         {
546                 const char* n = dlerror();
547                 ServerInstance->Log(SPARSE, "Error loading core command: %s", n);
548                 return n;
549         }
550
551         if (this->FindSym((void **)&cmd_factory_func, h))
552         {
553                 command_t* newcommand = cmd_factory_func(ServerInstance);
554                 this->CreateCommand(newcommand, h);
555         }
556         return NULL;
557 }
558
559 void CommandParser::SetupCommandTable(userrec* user)
560 {
561         RFCCommands.clear();
562
563         if (!user)
564         {
565                 printf("\nLoading core commands");
566                 fflush(stdout);
567         }
568
569         DIR* library = opendir(LIBRARYDIR);
570         if (library)
571         {
572                 dirent* entry = NULL;
573                 while ((entry = readdir(library)))
574                 {
575                         if (match(entry->d_name, "cmd_*.so"))
576                         {
577                                 if (!user)
578                                 {
579                                         printf(".");
580                                         fflush(stdout);
581                                 }
582                                 const char* err = this->LoadCommand(entry->d_name);
583                                 if (err)
584                                 {
585                                         if (user)
586                                         {
587                                                 user->WriteServ("NOTICE %s :*** Failed to load core command %s: %s", user->nick, entry->d_name, err);
588                                         }
589                                         else
590                                         {
591                                                 printf("Error loading %s: %s", entry->d_name, err);
592                                                 exit(EXIT_STATUS_BADHANDLER);
593                                         }
594                                 }
595                         }
596                 }
597                 closedir(library);
598                 if (!user)
599                         printf("\n");
600         }
601
602         if (cmdlist.find("RELOAD") == cmdlist.end())
603                 this->CreateCommand(new cmd_reload(ServerInstance));
604 }
605
606 int CommandParser::TranslateUIDs(TranslateType to, const std::string &source, std::string &dest)
607 {
608         userrec* user = NULL;
609         std::string item;
610         int translations = 0;
611
612         switch (to)
613         {
614                 case TR_NICK:
615                         /* Translate single nickname */
616                         ServerInstance->Log(DEBUG,"TR_NICK");
617                         user = ServerInstance->FindNick(source);
618                         if (user)
619                         {
620                                 ServerInstance->Log(DEBUG,"Managed UUID");
621                                 dest = user->uuid;
622                                 translations++;
623                         }
624                         else
625                         {
626                                 ServerInstance->Log(DEBUG,"Had to use source.. (%s)", source.c_str());
627                                 dest = source;
628                         }
629                 break;
630                 case TR_NICKLIST:
631                 {
632                         /* Translate comma seperated list of nicknames */
633                         irc::commasepstream items(source);
634                         while (items.GetToken(item))
635                         {
636                                 user = ServerInstance->FindNick(item);
637                                 if (user)
638                                 {
639                                         dest.append(user->uuid);
640                                         translations++;
641                                 }
642                                 else
643                                         dest.append(source);
644                                 dest.append(",");
645                         }
646                         if (!dest.empty())
647                                 dest.erase(dest.end() - 1);
648                 }
649                 break;
650                 case TR_SPACENICKLIST:
651                 {
652                         /* Translate space seperated list of nicknames */
653                         irc::spacesepstream items(source);
654                         while (items.GetToken(item))
655                         {
656                                 user = ServerInstance->FindNick(item);
657                                 if (user)
658                                 {
659                                         dest.append(user->uuid);
660                                         translations++;
661                                 }
662                                 else
663                                         dest.append(source);
664                                 dest.append(" ");
665                         }
666                         if (!dest.empty())
667                                 dest.erase(dest.end() - 1);
668                 }
669                 break;
670                 case TR_END:
671                 case TR_TEXT:
672                 default:
673                         /* Do nothing */
674                         dest = source;
675                 break;
676         }
677
678         return translations;
679 }
680