[2987] Applied MaNGOS coding style (see trunk/bcpp.cfg).
[mangos-git.git] / src / mangosd / CliRunnable.cpp
blob80dadab05867f31d2457c62abd9604459339d609
1 /*
2 * Copyright (C) 2005,2006 MaNGOS <http://www.mangosproject.org/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 /// \addtogroup mangosd
20 /// @{
21 /// \file
23 #include "Common.h"
24 #include "Log.h"
25 #include "World.h"
26 #include "ScriptCalls.h"
27 #include "GlobalEvents.h"
28 #include "ObjectMgr.h"
29 #include "WorldSession.h"
30 #include "SystemConfig.h"
31 #include "Config/ConfigEnv.h"
32 #include "Util.h"
34 #ifdef ENABLE_CLI
35 #include "CliRunnable.h"
37 typedef int(* pPrintf)(const char*,...);
38 typedef void(* pCliFunc)(char *,pPrintf);
40 /// Storage structure for commands
41 typedef struct
43 char const * cmd;
44 pCliFunc Func;
45 char const * description;
46 }CliCommand;
48 //func prototypes must be defined
50 void CliHelp(char*,pPrintf);
51 void CliInfo(char*,pPrintf);
52 void CliBan(char*,pPrintf);
53 void CliBanList(char*,pPrintf);
54 void CliRemoveBan(char*,pPrintf);
55 void CliSetGM(char*,pPrintf);
56 void CliListGM(char*,pPrintf);
57 void CliVersion(char*,pPrintf);
58 void CliExit(char*,pPrintf);
59 void CliIdleShutdown(char*,pPrintf zprintf);
60 void CliShutdown(char*,pPrintf zprintf);
61 void CliBroadcast(char*,pPrintf);
62 void CliCreate(char*,pPrintf);
63 void CliDelete(char*,pPrintf);
64 void CliLoadScripts(char*,pPrintf);
65 void CliKick(char*,pPrintf);
66 void CliMotd(char*,pPrintf);
67 void CliCorpses(char*,pPrintf);
68 void CliSetLogLevel(char*,pPrintf);
70 /// Table of known commands
71 const CliCommand Commands[]=
73 {"help", & CliHelp,"Display this help message"},
74 {"broadcast", & CliBroadcast,"Announce in-game message"},
75 {"create", & CliCreate,"Create account"},
76 {"delete", & CliDelete,"Delete account and characters"},
77 {"info", & CliInfo,"Display Server infomation"},
78 {"motd", & CliMotd,"Change or display motd"},
79 {"kick", & CliKick,"Kick user"},
80 {"ban", & CliBan,"Ban account|ip"},
81 {"listbans", & CliBanList,"List bans"},
82 {"unban", & CliRemoveBan,"Remove ban from account|ip"},
83 {"setgm", & CliSetGM,"Edit user privileges"},
84 {"listgm", & CliListGM,"Display user privileges"},
85 {"loadscripts", & CliLoadScripts,"Load script library"},
86 {"setloglevel", & CliSetLogLevel,"Set Log Level"},
87 {"corpses", & CliCorpses,"Manually call corpses erase global even code"},
88 {"version", & CliVersion,"Display server version"},
89 {"idleshutdown", & CliIdleShutdown,"Shutdown server with some delay when not active connections at server"},
90 {"shutdown", & CliShutdown,"Shutdown server with some delay"},
91 {"exit", & CliExit,"Shutdown server NOW"}
93 /// \todo Need some pragma pack? Else explain why in a comment.
94 #define CliTotalCmds sizeof(Commands)/sizeof(CliCommand)
96 /// Reload the scripts and notify the players
97 void CliLoadScripts(char*command,pPrintf zprintf)
99 char const *del=strtok(command," ");
100 if (!del)
101 del="";
102 if(!LoadScriptingModule(del)) // Error report is already done by LoadScriptingModule
103 return;
105 sWorld.SendWorldText("|cffff0000[System Message]:|rScripts reloaded", NULL);
108 /// Delete a user account and all associated characters in this realm
109 /// \todo This function has to be enhanced to respect the login/realm split (delete char, delete account chars in realm, delete account chars in realm then delete account
110 void CliDelete(char*command,pPrintf zprintf)
112 ///- Get the account name from the command line
113 char *account_name=strtok(command," ");
114 if(!account_name)
116 // \r\n is used because this function can also be called from RA
117 zprintf("Syntax is: delete <account>\r\n");
118 return;
121 ///- Escape account name to allow quotes in names
122 std::string safe_account_name=account_name;
123 loginDatabase.escape_string(safe_account_name);
125 ///- Get the account ID from the database
126 Field *fields;
127 // No SQL injection (account_name escaped)
128 QueryResult *result = loginDatabase.PQuery("SELECT `id` FROM `account` WHERE `username` = '%s'",safe_account_name.c_str());
130 if (!result)
132 zprintf("User %s does not exist\r\n",account_name);
133 return;
136 fields = result->Fetch();
137 uint32 account_id = fields[0].GetUInt32();
138 delete result;
140 ///- Circle through characters belonging to this account ID and remove all characters related data (items, quests, ...) from the database
141 // No SQL injection (account_id is db internal)
142 result = sDatabase.PQuery("SELECT `guid` FROM `character` WHERE `account` = '%d'",account_id);
144 if (result)
148 Field *fields = result->Fetch();
150 uint32 guidlo = fields[0].GetUInt32();
152 // kick if player currently
153 if(Player* p = objmgr.GetPlayer(MAKE_GUID(guidlo,HIGHGUID_PLAYER)))
154 p->GetSession()->KickPlayer();
156 WorldSession acc_s(account_id,NULL,0); // some invalid session
157 Player acc_player(&acc_s);
159 acc_player.LoadFromDB(guidlo);
161 acc_player.DeleteFromDB();
163 zprintf("We deleted character: %s from account %s\r\n",acc_player.GetName(),account_name);
165 } while (result->NextRow());
167 delete result;
170 ///- Remove characters and account from the databases
171 sDatabase.BeginTransaction();
173 bool done = sDatabase.PExecute("DELETE FROM `character` WHERE `account` = '%d'",account_id) &&
174 loginDatabase.PExecute("DELETE FROM `account` WHERE `username` = '%s'",safe_account_name.c_str()) &&
175 loginDatabase.PExecute("DELETE FROM `realmcharacters` WHERE `acctid` = '%d'",account_id);
177 sDatabase.CommitTransaction();
179 if (done)
180 zprintf("We deleted account: %s\r\n",account_name);
183 /// Broadcast a message to the World
184 void CliBroadcast(char *text,pPrintf zprintf)
186 std::string str ="|cffff0000[System Message]:|r";
187 str += text;
188 sWorld.SendWorldText(str.c_str(), NULL);
189 zprintf("Broadcasting to the world:%s\r\n",str.c_str());
192 /// Print the list of commands and associated description
193 void CliHelp(char*,pPrintf zprintf)
195 for (unsigned int x=0;x<CliTotalCmds;x++)
196 zprintf("%-13s - %s.\r\n",Commands[x].cmd ,Commands[x].description);
199 /// Exit the realm
200 void CliExit(char*,pPrintf zprintf)
202 zprintf( "Exiting daemon...\r\n" );
203 World::m_stopEvent = true;
206 /// Shutdown the server (with some delay) as soon as no active connections remain on the server
207 void CliIdleShutdown(char* command,pPrintf zprintf)
209 char *args = strtok(command," ");
211 if(!args)
213 zprintf("Syntax is: idleshutdown <seconds|cancel>\r\n");
214 return;
217 if(std::string(args)=="cancel")
219 sWorld.ShutdownCancel();
221 else
224 uint32 time = atoi(args);
226 ///- Prevent interpret wrong arg value as 0 secs shutdown time
227 if(time==0 && (args[0]!='0' || args[1]!='\0') || time < 0)
229 zprintf("Syntax is: idleshutdown <seconds|cancel>\r\n");
230 return;
233 sWorld.ShutdownServ(time,true);
237 /// Shutdown the server with some delay
238 void CliShutdown(char* command,pPrintf zprintf)
240 char *args = strtok(command," ");
242 if(!args)
244 zprintf("Syntax is: shutdown <seconds|cancel>\r\n");
245 return;
248 if(std::string(args)=="cancel")
250 sWorld.ShutdownCancel();
252 else
254 int32 time = atoi(args);
256 ///- Prevent interpret wrong arg value as 0 secs shutdown time
257 if(time==0 && (args[0]!='0' || args[1]!='\0') || time < 0)
259 zprintf("Syntax is: shutdown <seconds|cancel>\r\n");
260 return;
263 sWorld.ShutdownServ(time);
267 /// Display info on users currently in the realm
268 void CliInfo(char*,pPrintf zprintf)
270 ///- Get the list of accounts ID logged to the realm
271 // No SQL injection. RealmID is uint32
272 QueryResult *resultDB = sDatabase.Query("SELECT `name`,`account` FROM `character` WHERE `online` > 0");
274 if (!resultDB)
276 zprintf("NO online users\r\n");
277 return;
280 int linesize = 1+15+2+20+3+15+2+6+3; // see format string
281 char* buf = new char[resultDB->GetRowCount()*linesize+1];
282 char* bufPos = buf;
284 ///- Circle through accounts
287 Field *fieldsDB = resultDB->Fetch();
288 std::string name = fieldsDB[0].GetCppString();
289 uint32 account = fieldsDB[1].GetUInt32();
291 ///- Get the username, last IP and GM level of each account
292 // No SQL injection. account is uint32.
293 QueryResult *resultLogin = loginDatabase.PQuery(
294 "SELECT `username`,`last_ip`,`gmlevel` FROM `account` WHERE `id` = '%u'",account);
296 if(resultLogin)
298 Field *fieldsLogin = resultLogin->Fetch();
299 bufPos+=sprintf(bufPos,"|%15s| %20s | %15s |%6d|\r\n",
300 fieldsLogin[0].GetString(),name.c_str(),fieldsLogin[1].GetString(),fieldsLogin[2].GetUInt32());
302 delete resultLogin;
304 else
305 bufPos += sprintf(bufPos,"|<Error> | %20s |<Error> |<Err> |\r\n",name.c_str());
307 }while(resultDB->NextRow());
309 *bufPos = '\0';
311 ///- Display the list of account/characters online
312 zprintf("Online users: %d\x0d\x0a",resultDB->GetRowCount());
313 zprintf("=================================================================\r\n");
314 zprintf("| Account | Character | IP | GM |\r\n");
315 zprintf("=================================================================\r\n");
316 zprintf("%s",buf);
317 zprintf("=================================================================\r\n");
319 delete resultDB;
320 delete[] buf;
323 /// Display a list of banned accounts and ip addresses
324 void CliBanList(char*,pPrintf zprintf)
326 ///- Get the list of banned accounts and display them
327 Field *fields;
328 QueryResult *result2 = loginDatabase.Query( "SELECT `username` FROM `account` WHERE `banned` > 0" );
329 if(result2)
331 zprintf("Banned Accounts:\r\n");
334 fields = result2->Fetch();
335 zprintf("|%15s|\r\n", fields[0].GetString());
336 }while( result2->NextRow() );
337 delete result2;
340 ///- Get the list of banned IP addresses and display them
341 QueryResult *result3 = loginDatabase.Query( "SELECT `ip` FROM `ip_banned`" );
342 if(result3)
344 zprintf("Banned IPs:\r\n");
347 fields = result3->Fetch();
348 zprintf("|%15s|\r\n", fields[0].GetString());
349 }while( result3->NextRow() );
350 delete result3;
353 if(!result2 && !result3) zprintf("We do not have banned users\r\n");
356 /// Ban an IP address or a user account
357 void CliBan(char*command,pPrintf zprintf)
359 ///- Get the command parameter
360 char *banip = strtok(command," ");
361 if(!banip)
363 zprintf("Syntax is: ban <account|ip>\r\n");
364 return;
367 // Is this IP address or account name?
368 bool is_ip = IsIPAddress(banip);
370 if(sWorld.BanAccount(banip))
372 if(is_ip)
373 zprintf("We banned IP: %s\r\n",banip);
374 else
375 zprintf("We banned account: %s\r\n",banip);
377 else
379 zprintf("Account %s not found and not banned.\r\n",banip);
383 /// Display %MaNGOS version
384 void CliVersion(char*,pPrintf zprintf)
386 //<--maybe better append to info cmd
387 zprintf( "MaNGOS daemon version is %s\r\n", _FULLVERSION );
390 /// Unban an IP adress or a user account
391 void CliRemoveBan(char *command,pPrintf zprintf)
393 ///- Get the command parameter
394 char *banip = strtok(command," ");
395 if(!banip)
396 zprintf("Syntax is: removeban <account|ip>\r\n");
398 sWorld.RemoveBanAccount(banip);
400 ///- If this is an IP address
401 if(IsIPAddress(banip))
402 zprintf("We removed banned IP: %s\r\n",banip);
403 else
404 zprintf("We removed ban from account: %s\r\n",banip);
407 /// Display the list of GMs
408 void CliListGM(char*,pPrintf zprintf)
411 ///- Get the accounts with GM Level >0
412 Field *fields;
414 QueryResult *result = loginDatabase.Query( "SELECT `username`,`gmlevel` FROM `account` WHERE `gmlevel` > 0" );
415 if(result)
418 zprintf("Current gamemasters:\r\n");
419 zprintf("========================\r\n");
420 zprintf("| Account | GM |\r\n");
421 zprintf("========================\r\n");
423 ///- Circle through them. Display username and GM level
426 fields = result->Fetch();
427 zprintf("|%15s|", fields[0].GetString());
428 zprintf("%6s|\r\n",fields[1].GetString());
429 }while( result->NextRow() );
431 zprintf("========================\r\n");
432 delete result;
434 else
436 zprintf("NO gamemasters\r\n");
440 /// Set the GM level of an account
441 void CliSetGM(char *command,pPrintf zprintf)
443 ///- Get the command line arguments
444 char *szAcc = strtok(command," ");
446 if(!szAcc) //wrong syntax 'setgm' without name
448 zprintf("Syntax is: setgm <character> <number (0 - normal, 3 - gamemaster)>\r\n");
449 return;
452 char *szLevel = strtok(NULL," ");
454 if(!szLevel) //wrong syntax 'setgm' without plevel
456 zprintf("Syntax is: setgm <character> <number (0 - normal, 3 - gamemaster)>\r\n");
457 return;
460 //wow it's ok,let's hope it was integer given
461 int lev=atoi(szLevel); //get int anyway (0 if error)
463 ///- Escape the account name to allow quotes in names
464 std::string safe_account_name=szAcc;
465 loginDatabase.escape_string(safe_account_name);
467 ///- Try to find the account, then update the GM level
468 // No SQL injection (account name is escaped)
469 QueryResult *result = loginDatabase.PQuery("SELECT 1 FROM `account` WHERE `username` = '%s'",safe_account_name.c_str());
471 if (result)
473 // No SQL injection (account name is escaped)
474 loginDatabase.PExecute("UPDATE `account` SET `gmlevel` = '%d' WHERE `username` = '%s'",lev,safe_account_name.c_str());
475 zprintf("We added %s gmlevel %d\r\n",szAcc,lev);
477 delete result;
479 else
481 zprintf("No account %s found\r\n",szAcc);
485 /// Create an account
486 void CliCreate(char *command,pPrintf zprintf)
488 //I see no need in this function (why would an admin personally create accounts
489 //instead of using account registration page or accessing db directly?)
490 //but still let it be
492 ///- %Parse the command line arguments
493 char *szAcc = strtok(command, " ");
494 if(!szAcc)
496 zprintf("Syntax is: create <username> <password>\r\n");
497 return;
500 if(strlen(szAcc)>16)
502 zprintf("Account cannot be longer than 16 characters.\r\n");
503 return;
506 char *szPassword = strtok(NULL, " ");
508 if(!szPassword)
510 zprintf("Syntax is: create <username> <password>\r\n");
511 return;
514 ///- Escape the account name to allow quotes in names
515 std::string safe_account_name=szAcc;
516 loginDatabase.escape_string(safe_account_name);
518 ///- Check that the account does not exist yet
519 QueryResult *result1 = loginDatabase.PQuery("SELECT 1 FROM `account` WHERE `username` = '%s'",safe_account_name.c_str());
521 if (result1)
523 zprintf("User %s already exists\r\n",szAcc);
524 delete result1;
525 return;
528 ///- Also escape the password
529 std::string safe_password=szPassword;
530 loginDatabase.escape_string(safe_password);
532 ///- Insert the account in the database (account table)
533 // No SQL injection (escaped account name and password)
534 sDatabase.BeginTransaction();
535 if(loginDatabase.PExecute("INSERT INTO `account` (`username`,`password`,`gmlevel`,`sessionkey`,`email`,`joindate`,`banned`,`last_ip`,`failed_logins`,`locked`) VALUES ('%s','%s','0','','',NOW(),'0','0','0','0')",safe_account_name.c_str(),safe_password.c_str()))
537 ///- Make sure that the realmcharacters table is up-to-date
538 loginDatabase.Execute("INSERT INTO `realmcharacters` (`realmid`, `acctid`, `numchars`) SELECT `realmlist`.`id`, `account`.`id`, 0 FROM `account`, `realmlist` WHERE `account`.`id` NOT IN (SELECT `acctid` FROM `realmcharacters`)");
539 zprintf("User %s with password %s created successfully\r\n",szAcc,szPassword);
541 else
542 zprintf("User %s with password %s NOT created (probably sql file format was updated)\r\n",szAcc,szPassword);
543 sDatabase.CommitTransaction();
546 /// Command parser and dispatcher
547 void ParseCommand( pPrintf zprintf, char* input)
549 unsigned int x;
550 if (!input)
551 return;
553 unsigned int l=strlen(input);
554 char *supposedCommand=NULL,* arguments=(char*)("");
555 if(!l)
556 return;
558 ///- Get the command and the arguments
559 supposedCommand = strtok(input," ");
560 if (l>strlen(supposedCommand))
561 arguments=&input[strlen(supposedCommand)+1];
563 ///- Circle through the command table and invoke the appropriate handler
564 for ( x=0;x<CliTotalCmds;x++)
565 if(!strcmp(Commands[x].cmd,supposedCommand))
567 Commands[x].Func(arguments,zprintf);
568 break;
571 ///- Display an error message if the command is unknown
572 if(x==CliTotalCmds)
573 zprintf("Unknown command: %s\r\n", input);
576 /// Kick a character out of the realm
577 void CliKick(char*command,pPrintf zprintf)
579 char *kickName = strtok(command, " ");
581 if (!kickName)
583 zprintf("Syntax is: kick <charactername>\r\n");
584 return;
587 std::string name = kickName;
588 normalizePlayerName(name);
590 sWorld.KickPlayer(name);
593 /// Display/Define the 'Message of the day' for the realm
594 void CliMotd(char*command,pPrintf zprintf)
597 if (strlen(command) == 0)
599 zprintf("Current Message of the day: \r\n%s\r\n", sWorld.GetMotd());
600 return;
602 else
604 sWorld.SetMotd(command);
605 zprintf("Message of the day changed to:\r\n%s\r\n", command);
609 /// Comment me
610 /// \todo What is CorpsesErase for?
611 void CliCorpses(char*,pPrintf)
613 CorpsesErase();
616 /// Set the level of logging
617 void CliSetLogLevel(char*command,pPrintf zprintf)
619 char *NewLevel = strtok(command, " ");
620 if (!NewLevel)
622 zprintf("Syntax is: setloglevel <loglevel>\r\n");
623 return;
625 sLog.SetLogLevel(NewLevel);
628 /// %Thread start
629 void CliRunnable::run()
631 ///- Init new SQL thread for the world database (one connection call enough)
632 sDatabase.ThreadStart(); // let thread do safe mySQL requests
634 char commandbuf[256];
636 ///- Display the list of available CLI functions then beep
637 sLog.outString("");
638 /// \todo Shoudn't we use here also the sLog singleton?
639 CliHelp(NULL,&printf);
641 if(sConfig.GetIntDefault("BeepAtStart", 1) > 0)
643 printf("\a"); // \a = Alert
646 ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it
647 while (!World::m_stopEvent)
649 printf("mangos>");
650 fflush(stdout);
651 char *command = fgets(commandbuf,sizeof(commandbuf),stdin);
652 if (command != NULL)
654 for(int x=0;command[x];x++)
655 if(command[x]=='\r'||command[x]=='\n')
657 command[x]=0;
658 break;
660 //// \todo Shoudn't we use here also the sLog singleton?
661 ParseCommand(&printf,command);
663 else if (feof(stdin))
665 World::m_stopEvent = true;
669 ///- End the database thread
670 sDatabase.ThreadEnd(); // free mySQL thread resources
672 #endif
673 /// @}