The InspIRCd Project
Home | Developers | Wiki | Forums | Bug Tracker | SVN | Download
Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members

command_parse.cpp

Go to the documentation of this file.
00001 /*       +------------------------------------+
00002  *       | Inspire Internet Relay Chat Daemon |
00003  *       +------------------------------------+
00004  *
00005  *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
00006  * See: http://www.inspircd.org/wiki/index.php/Credits
00007  *
00008  * This program is free but copyrighted software; see
00009  *          the file COPYING for details.
00010  *
00011  * ---------------------------------------------------
00012  */
00013 
00014 /* $Core */
00015 
00016 #include "inspircd.h"
00017 #include "xline.h"
00018 #include "socketengine.h"
00019 #include "socket.h"
00020 #include "command_parse.h"
00021 #include "exitcodes.h"
00022 
00023 /* Directory Searching for Unix-Only */
00024 #ifndef WIN32
00025 #include <dirent.h>
00026 #include <dlfcn.h>
00027 #endif
00028 
00029 int InspIRCd::PassCompare(Extensible* ex, const std::string &data, const std::string &input, const std::string &hashtype)
00030 {
00031         int MOD_RESULT = 0;
00032         FOREACH_RESULT_I(this,I_OnPassCompare,OnPassCompare(ex, data, input, hashtype))
00033         if (MOD_RESULT == 1)
00034                 return 0;
00035         if (MOD_RESULT == -1)
00036                 return 1;
00037         return data != input; // this seems back to front, but returns 0 if they *match*, 1 else
00038 }
00039 
00040 /* LoopCall is used to call a command classes handler repeatedly based on the contents of a comma seperated list.
00041  * There are two overriden versions of this method, one of which takes two potential lists and the other takes one.
00042  * We need a version which takes two potential lists for JOIN, because a JOIN may contain two lists of items at once,
00043  * the channel names and their keys as follows:
00044  * JOIN #chan1,#chan2,#chan3 key1,,key3
00045  * Therefore, we need to deal with both lists concurrently. The first instance of this method does that by creating
00046  * two instances of irc::commasepstream and reading them both together until the first runs out of tokens.
00047  * The second version is much simpler and just has the one stream to read, and is used in NAMES, WHOIS, PRIVMSG etc.
00048  * Both will only parse until they reach ServerInstance->Config->MaxTargets number of targets, to stop abuse via spam.
00049  */
00050 int CommandParser::LoopCall(User* user, Command* CommandObj, const std::vector<std::string>& parameters, unsigned int splithere, unsigned int extra)
00051 {
00052         if (splithere >= parameters.size())
00053                 return 0;
00054 
00055         /* First check if we have more than one item in the list, if we don't we return zero here and the handler
00056          * which called us just carries on as it was.
00057          */
00058         if (parameters[splithere].find(',') == std::string::npos)
00059                 return 0;
00060 
00065         std::map<irc::string, bool> dupes;
00066 
00067         /* Create two lists, one for channel names, one for keys
00068          */
00069         irc::commasepstream items1(parameters[splithere]);
00070         irc::commasepstream items2(parameters[extra]);
00071         std::string extrastuff;
00072         std::string item;
00073         unsigned int max = 0;
00074 
00075         /* Attempt to iterate these lists and call the command objech
00076          * which called us, for every parameter pair until there are
00077          * no more left to parse.
00078          */
00079         while (items1.GetToken(item) && (max++ < ServerInstance->Config->MaxTargets))
00080         {
00081                 if (dupes.find(item.c_str()) == dupes.end())
00082                 {
00083                         std::vector<std::string> new_parameters;
00084 
00085                         for (unsigned int t = 0; (t < parameters.size()) && (t < MAXPARAMETERS); t++)
00086                                 new_parameters.push_back(parameters[t]);
00087 
00088                         if (!items2.GetToken(extrastuff))
00089                                 extrastuff = "";
00090 
00091                         new_parameters[splithere] = item.c_str();
00092                         new_parameters[extra] = extrastuff.c_str();
00093 
00094                         CommandObj->Handle(new_parameters, user);
00095 
00096                         dupes[item.c_str()] = true;
00097                 }
00098         }
00099         return 1;
00100 }
00101 
00102 int CommandParser::LoopCall(User* user, Command* CommandObj, const std::vector<std::string>& parameters, unsigned int splithere)
00103 {
00104         if (splithere >= parameters.size())
00105                 return 0;
00106 
00107         /* First check if we have more than one item in the list, if we don't we return zero here and the handler
00108          * which called us just carries on as it was.
00109          */
00110         if (parameters[splithere].find(',') == std::string::npos)
00111                 return 0;
00112 
00113         std::map<irc::string, bool> dupes;
00114 
00115         /* Only one commasepstream here */
00116         irc::commasepstream items1(parameters[splithere]);
00117         std::string item;
00118         unsigned int max = 0;
00119 
00120         /* Parse the commasepstream until there are no tokens remaining.
00121          * Each token we parse out, call the command handler that called us
00122          * with it
00123          */
00124         while (items1.GetToken(item) && (max++ < ServerInstance->Config->MaxTargets))
00125         {
00126                 if (dupes.find(item.c_str()) == dupes.end())
00127                 {
00128                         std::vector<std::string> new_parameters;
00129 
00130                         for (unsigned int t = 0; (t < parameters.size()) && (t < MAXPARAMETERS); t++)
00131                                 new_parameters.push_back(parameters[t]);
00132 
00133                         new_parameters[splithere] = item.c_str();
00134 
00135                         /* Execute the command handler. */
00136                         CommandObj->Handle(new_parameters, user);
00137 
00138                         dupes[item.c_str()] = true;
00139                 }
00140         }
00141         /* By returning 1 we tell our caller that nothing is to be done,
00142          * as all the previous calls handled the data. This makes the parent
00143          * return without doing any processing.
00144          */
00145         return 1;
00146 }
00147 
00148 bool CommandParser::IsValidCommand(const std::string &commandname, unsigned int pcnt, User * user)
00149 {
00150         Commandtable::iterator n = cmdlist.find(commandname);
00151 
00152         if (n != cmdlist.end())
00153         {
00154                 if ((pcnt >= n->second->min_params) && (n->second->source != "<core>"))
00155                 {
00156                         if (IS_LOCAL(user) && n->second->flags_needed)
00157                         {
00158                                 if (user->IsModeSet(n->second->flags_needed))
00159                                 {
00160                                         return (user->HasPermission(commandname));
00161                                 }
00162                         }
00163                         else
00164                         {
00165                                 return true;
00166                         }
00167                 }
00168         }
00169         return false;
00170 }
00171 
00172 Command* CommandParser::GetHandler(const std::string &commandname)
00173 {
00174         Commandtable::iterator n = cmdlist.find(commandname);
00175         if (n != cmdlist.end())
00176                 return n->second;
00177 
00178         return NULL;
00179 }
00180 
00181 // calls a handler function for a command
00182 
00183 CmdResult CommandParser::CallHandler(const std::string &commandname, const std::vector<std::string>& parameters, User *user)
00184 {
00185         Commandtable::iterator n = cmdlist.find(commandname);
00186 
00187         if (n != cmdlist.end())
00188         {
00189                 if (parameters.size() >= n->second->min_params)
00190                 {
00191                         bool bOkay = false;
00192 
00193                         if (IS_LOCAL(user) && n->second->flags_needed)
00194                         {
00195                                 /* if user is local, and flags are needed .. */
00196 
00197                                 if (user->IsModeSet(n->second->flags_needed))
00198                                 {
00199                                         /* if user has the flags, and now has the permissions, go ahead */
00200                                         if (user->HasPermission(commandname))
00201                                                 bOkay = true;
00202                                 }
00203                         }
00204                         else
00205                         {
00206                                 /* remote or no flags required anyway */
00207                                 bOkay = true;
00208                         }
00209 
00210                         if (bOkay)
00211                         {
00212                                 return n->second->Handle(parameters,user);
00213                         }
00214                 }
00215         }
00216         return CMD_INVALID;
00217 }
00218 
00219 void CommandParser::DoLines(User* current, bool one_only)
00220 {
00221         // while there are complete lines to process...
00222         unsigned int floodlines = 0;
00223 
00224         while (current->BufferIsReady())
00225         {
00226                 if (current->MyClass)
00227                 {
00228                         if (ServerInstance->Time() > current->reset_due)
00229                         {
00230                                 current->reset_due = ServerInstance->Time() + current->MyClass->GetThreshold();
00231                                 current->lines_in = 0;
00232                         }
00233 
00234                         if (++current->lines_in > current->MyClass->GetFlood() && current->MyClass->GetFlood())
00235                         {
00236                                 ServerInstance->FloodQuitUser(current);
00237                                 return;
00238                         }
00239 
00240                         if ((++floodlines > current->MyClass->GetFlood()) && (current->MyClass->GetFlood() != 0))
00241                         {
00242                                 ServerInstance->FloodQuitUser(current);
00243                                 return;
00244                         }
00245                 }
00246 
00247                 // use GetBuffer to copy single lines into the sanitized string
00248                 std::string single_line = current->GetBuffer();
00249                 current->bytes_in += single_line.length();
00250                 current->cmds_in++;
00251                 if (single_line.length() > MAXBUF - 2)  // MAXBUF is 514 to allow for neccessary line terminators
00252                         single_line.resize(MAXBUF - 2); // So to trim to 512 here, we use MAXBUF - 2
00253 
00254                 // ProcessBuffer returns false if the user has gone over penalty
00255                 if (!ServerInstance->Parser->ProcessBuffer(single_line, current) || one_only)
00256                         break;
00257         }
00258 }
00259 
00260 bool CommandParser::ProcessCommand(User *user, std::string &cmd)
00261 {
00262         std::vector<std::string> command_p;
00263         irc::tokenstream tokens(cmd);
00264         std::string command, token;
00265         tokens.GetToken(command);
00266 
00267         /* A client sent a nick prefix on their command (ick)
00268          * rhapsody and some braindead bouncers do this --
00269          * the rfc says they shouldnt but also says the ircd should
00270          * discard it if they do.
00271          */
00272         if (command[0] == ':')
00273                 tokens.GetToken(command);
00274 
00275         while (tokens.GetToken(token) && (command_p.size() <= MAXPARAMETERS))
00276                 command_p.push_back(token);
00277 
00278         std::transform(command.begin(), command.end(), command.begin(), ::toupper);
00279                 
00280         int MOD_RESULT = 0;
00281         FOREACH_RESULT(I_OnPreCommand,OnPreCommand(command, command_p, user, false, cmd));
00282         if (MOD_RESULT == 1) {
00283                 return true;
00284         }
00285 
00286         /* find the command, check it exists */
00287         Commandtable::iterator cm = cmdlist.find(command);
00288         
00289         if (cm == cmdlist.end())
00290         {
00291                 if (user->registered == REG_ALL)
00292                 {
00293                         user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str());
00294                 }
00295                 ServerInstance->stats->statsUnknown++;
00296                 return true;
00297         }
00298 
00299         /* Modify the user's penalty */
00300         bool do_more = true;
00301         if (!user->ExemptFromPenalty)
00302         {
00303                 user->IncreasePenalty(cm->second->Penalty);
00304                 do_more = (user->Penalty < 10);
00305                 if (!do_more)
00306                         user->OverPenalty = true;
00307         }
00308 
00309         /* activity resets the ping pending timer */
00310         if (user->MyClass)
00311                 user->nping = ServerInstance->Time() + user->MyClass->GetPingTime();
00312 
00313         if (cm->second->flags_needed)
00314         {
00315                 if (!user->IsModeSet(cm->second->flags_needed))
00316                 {
00317                         user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - You do not have the required operator privileges",user->nick.c_str());
00318                         return do_more;
00319                 }
00320                 if (!user->HasPermission(command))
00321                 {
00322                         user->WriteNumeric(ERR_NOPRIVILEGES, "%s :Permission Denied - Oper type %s does not have access to command %s",user->nick.c_str(),user->oper.c_str(),command.c_str());
00323                         return do_more;
00324                 }
00325         }
00326         if ((user->registered == REG_ALL) && (!IS_OPER(user)) && (cm->second->IsDisabled()))
00327         {
00328                 /* command is disabled! */
00329                 if (ServerInstance->Config->DisabledDontExist)
00330                 {
00331                         user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :Unknown command",user->nick.c_str(),command.c_str());
00332                 }
00333                 else
00334                 {
00335                         user->WriteNumeric(ERR_UNKNOWNCOMMAND, "%s %s :This command has been disabled.",
00336                                                                                 user->nick.c_str(), command.c_str());
00337                 }
00338 
00339                 ServerInstance->SNO->WriteToSnoMask('d', "%s denied for %s (%s@%s)",
00340                                 command.c_str(), user->nick.c_str(), user->ident.c_str(), user->host.c_str());
00341                 return do_more;
00342         }
00343         if (command_p.size() < cm->second->min_params)
00344         {
00345                 user->WriteNumeric(ERR_NEEDMOREPARAMS, "%s %s :Not enough parameters.", user->nick.c_str(), command.c_str());
00346                 if ((ServerInstance->Config->SyntaxHints) && (user->registered == REG_ALL) && (cm->second->syntax.length()))
00347                         user->WriteNumeric(RPL_SYNTAX, "%s :SYNTAX %s %s", user->nick.c_str(), cm->second->command.c_str(), cm->second->syntax.c_str());
00348                 return do_more;
00349         }
00350         if ((user->registered != REG_ALL) && (!cm->second->WorksBeforeReg()))
00351         {
00352                 user->WriteNumeric(ERR_NOTREGISTERED, "%s :You have not registered",command.c_str());
00353                 return do_more;
00354         }
00355         else
00356         {
00357                 /* passed all checks.. first, do the (ugly) stats counters. */
00358                 cm->second->use_count++;
00359                 cm->second->total_bytes += cmd.length();
00360 
00361                 /* module calls too */
00362                 MOD_RESULT = 0;
00363                 FOREACH_RESULT(I_OnPreCommand,OnPreCommand(command, command_p, user, true, cmd));
00364                 if (MOD_RESULT == 1)
00365                         return do_more;
00366 
00367                 /*
00368                  * WARNING: be careful, the user may be deleted soon
00369                  */
00370                 CmdResult result = cm->second->Handle(command_p, user);
00371 
00372                 FOREACH_MOD(I_OnPostCommand,OnPostCommand(command, command_p, user, result,cmd));
00373                 return do_more;
00374         }
00375 }
00376 
00377 void CommandParser::RemoveCommands(const char* source)
00378 {
00379         Commandtable::iterator i,safei;
00380         for (i = cmdlist.begin(); i != cmdlist.end();)
00381         {
00382                 safei = i;
00383                 i++;
00384                 RemoveCommand(safei, source);
00385         }
00386 }
00387 
00388 void CommandParser::RemoveCommand(Commandtable::iterator safei, const char* source)
00389 {
00390         Command* x = safei->second;
00391         if (x->source == std::string(source))
00392         {
00393                 cmdlist.erase(safei);
00394                 delete x;
00395         }
00396 }
00397 
00398 bool CommandParser::ProcessBuffer(std::string &buffer,User *user)
00399 {
00400         std::string::size_type a;
00401 
00402         if (!user)
00403                 return true;
00404 
00405         while ((a = buffer.rfind("\n")) != std::string::npos)
00406                 buffer.erase(a);
00407         while ((a = buffer.rfind("\r")) != std::string::npos)
00408                 buffer.erase(a);
00409 
00410         if (buffer.length())
00411         {
00412                 ServerInstance->Logs->Log("USERINPUT", DEBUG,"C[%d] I :%s %s",user->GetFd(), user->nick.c_str(), buffer.c_str());
00413                 return this->ProcessCommand(user,buffer);
00414         }
00415 
00416         return true;
00417 }
00418 
00419 bool CommandParser::CreateCommand(Command *f, void* so_handle)
00420 {
00421         if (so_handle)
00422         {
00423                 if (RFCCommands.find(f->command) == RFCCommands.end())
00424                         RFCCommands[f->command] = so_handle;
00425                 else
00426                 {
00427                         ServerInstance->Logs->Log("COMMAND",DEFAULT,"ERK! Somehow, we loaded a cmd_*.so file twice! Only the first instance is being recorded.");
00428                         return false;
00429                 }
00430         }
00431 
00432         /* create the command and push it onto the table */
00433         if (cmdlist.find(f->command) == cmdlist.end())
00434         {
00435                 cmdlist[f->command] = f;
00436                 return true;
00437         }
00438         else return false;
00439 }
00440 
00441 CommandParser::CommandParser(InspIRCd* Instance) : ServerInstance(Instance)
00442 {
00443         para.resize(128);
00444 }
00445 
00446 bool CommandParser::FindSym(void** v, void* h, const std::string &name)
00447 {
00448         *v = dlsym(h, "init_command");
00449         const char* err = dlerror();
00450         if (err && !(*v))
00451         {
00452                 ServerInstance->Logs->Log("COMMAND",SPARSE, "Error loading core command %s: %s\n", name.c_str(), err);
00453                 return false;
00454         }
00455         return true;
00456 }
00457 
00458 bool CommandParser::ReloadCommand(std::string cmd, User* user)
00459 {
00460         char filename[MAXBUF];
00461         std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
00462 
00463         SharedObjectList::iterator command = RFCCommands.find(cmd);
00464 
00465         if (command != RFCCommands.end())
00466         {
00467                 Command* cmdptr = cmdlist.find(cmd)->second;
00468                 cmdlist.erase(cmdlist.find(cmd));
00469 
00470                 RFCCommands.erase(cmd);
00471                 std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower);
00472                 delete cmdptr;
00473                 dlclose(command->second);
00474 
00475                 snprintf(filename, MAXBUF, "cmd_%s.so", cmd.c_str());
00476                 const char* err = this->LoadCommand(filename);
00477                 if (err)
00478                 {
00479                         if (user)
00480                                 user->WriteServ("NOTICE %s :*** Error loading 'cmd_%s.so': %s", user->nick.c_str(), cmd.c_str(), err);
00481                         return false;
00482                 }
00483 
00484                 return true;
00485         }
00486 
00487         return false;
00488 }
00489 
00490 CmdResult cmd_reload::Handle(const std::vector<std::string>& parameters, User *user)
00491 {
00492         if (parameters.size() < 1)
00493                 return CMD_FAILURE;
00494 
00495         user->WriteServ("NOTICE %s :*** Reloading command '%s'",user->nick.c_str(), parameters[0].c_str());
00496         if (ServerInstance->Parser->ReloadCommand(parameters[0], user))
00497         {
00498                 user->WriteServ("NOTICE %s :*** Successfully reloaded command '%s'", user->nick.c_str(), parameters[0].c_str());
00499                 ServerInstance->SNO->WriteToSnoMask('A', "RELOAD: %s reloaded the '%s' command.", user->nick.c_str(), parameters[0].c_str());
00500                 return CMD_SUCCESS;
00501         }
00502         else
00503         {
00504                 user->WriteServ("NOTICE %s :*** Could not reload command '%s' -- fix this problem, then /REHASH as soon as possible!", user->nick.c_str(), parameters[0].c_str());
00505                 return CMD_FAILURE;
00506         }
00507 }
00508 
00509 const char* CommandParser::LoadCommand(const char* name)
00510 {
00511         char filename[MAXBUF];
00512         void* h;
00513         Command* (*cmd_factory_func)(InspIRCd*);
00514 
00515         /* Command already exists? Succeed silently - this is needed for REHASH */
00516         if (RFCCommands.find(name) != RFCCommands.end())
00517         {
00518                 ServerInstance->Logs->Log("COMMAND",DEBUG,"Not reloading command %s/%s, it already exists", LIBRARYDIR, name);
00519                 return NULL;
00520         }
00521 
00522         snprintf(filename, MAXBUF, "%s/%s", LIBRARYDIR, name);
00523         h = dlopen(filename, RTLD_NOW | RTLD_GLOBAL);
00524 
00525         if (!h)
00526         {
00527                 const char* n = dlerror();
00528                 ServerInstance->Logs->Log("COMMAND",SPARSE, "Error loading core command %s: %s", name, n);
00529                 return n;
00530         }
00531 
00532         if (this->FindSym((void **)&cmd_factory_func, h, name))
00533         {
00534                 Command* newcommand = cmd_factory_func(ServerInstance);
00535                 this->CreateCommand(newcommand, h);
00536         }
00537         return NULL;
00538 }
00539 
00540 void CommandParser::SetupCommandTable(User* user)
00541 {
00542         RFCCommands.clear();
00543 
00544         if (!user)
00545         {
00546                 printf("\nLoading core commands");
00547                 fflush(stdout);
00548         }
00549 
00550         DIR* library = opendir(LIBRARYDIR);
00551         if (library)
00552         {
00553                 dirent* entry = NULL;
00554                 while (0 != (entry = readdir(library)))
00555                 {
00556                         if (InspIRCd::Match(entry->d_name, "cmd_*.so"))
00557                         {
00558                                 if (!user)
00559                                 {
00560                                         printf(".");
00561                                         fflush(stdout);
00562                                 }
00563                                 const char* err = this->LoadCommand(entry->d_name);
00564                                 if (err)
00565                                 {
00566                                         if (user)
00567                                         {
00568                                                 user->WriteServ("NOTICE %s :*** Failed to load core command %s: %s", user->nick.c_str(), entry->d_name, err);
00569                                         }
00570                                         else
00571                                         {
00572                                                 printf("Error loading %s: %s", entry->d_name, err);
00573                                                 exit(EXIT_STATUS_BADHANDLER);
00574                                         }
00575                                 }
00576                         }
00577                 }
00578                 closedir(library);
00579                 if (!user)
00580                         printf("\n");
00581         }
00582 
00583         if (cmdlist.find("RELOAD") == cmdlist.end())
00584                 this->CreateCommand(new cmd_reload(ServerInstance));
00585 }
00586 
00587 int CommandParser::TranslateUIDs(TranslateType to, const std::string &source, std::string &dest)
00588 {
00589         User* user = NULL;
00590         std::string item;
00591         int translations = 0;
00592         dest.clear();
00593 
00594         switch (to)
00595         {
00596                 case TR_NICK:
00597                         /* Translate single nickname */
00598                         user = ServerInstance->FindNick(source);
00599                         if (user)
00600                         {
00601                                 dest = user->uuid;
00602                                 translations++;
00603                         }
00604                         else
00605                                 dest = source;
00606                 break;
00607                 case TR_NICKLIST:
00608                 {
00609                         /* Translate comma seperated list of nicknames */
00610                         irc::commasepstream items(source);
00611                         while (items.GetToken(item))
00612                         {
00613                                 user = ServerInstance->FindNick(item);
00614                                 if (user)
00615                                 {
00616                                         dest.append(user->uuid);
00617                                         translations++;
00618                                 }
00619                                 else
00620                                         dest.append(item);
00621                                 dest.append(",");
00622                         }
00623                         if (!dest.empty())
00624                                 dest.erase(dest.end() - 1);
00625                 }
00626                 break;
00627                 case TR_SPACENICKLIST:
00628                 {
00629                         /* Translate space seperated list of nicknames */
00630                         irc::spacesepstream items(source);
00631                         while (items.GetToken(item))
00632                         {
00633                                 user = ServerInstance->FindNick(item);
00634                                 if (user)
00635                                 {
00636                                         dest.append(user->uuid);
00637                                         translations++;
00638                                 }
00639                                 else
00640                                         dest.append(item);
00641                                 dest.append(" ");
00642                         }
00643                         if (!dest.empty())
00644                                 dest.erase(dest.end() - 1);
00645                 }
00646                 break;
00647                 case TR_END:
00648                 case TR_TEXT:
00649                 default:
00650                         /* Do nothing */
00651                         dest = source;
00652                 break;
00653         }
00654 
00655         return translations;
00656 }
00657 
00658