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
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"
35 #include "CliRunnable.h"
37 typedef int(* pPrintf
)(const char*,...);
38 typedef void(* pCliFunc
)(char *,pPrintf
);
40 /// Storage structure for commands
45 char const * description
;
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
," ");
102 if(!LoadScriptingModule(del
)) // Error report is already done by LoadScriptingModule
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
," ");
116 // \r\n is used because this function can also be called from RA
117 zprintf("Syntax is: delete <account>\r\n");
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
127 // No SQL injection (account_name escaped)
128 QueryResult
*result
= loginDatabase
.PQuery("SELECT `id` FROM `account` WHERE `username` = '%s'",safe_account_name
.c_str());
132 zprintf("User %s does not exist\r\n",account_name
);
136 fields
= result
->Fetch();
137 uint32 account_id
= fields
[0].GetUInt32();
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
);
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());
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();
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";
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
);
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
," ");
213 zprintf("Syntax is: idleshutdown <seconds|cancel>\r\n");
217 if(std::string(args
)=="cancel")
219 sWorld
.ShutdownCancel();
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");
233 sWorld
.ShutdownServ(time
,true);
237 /// Shutdown the server with some delay
238 void CliShutdown(char* command
,pPrintf zprintf
)
240 char *args
= strtok(command
," ");
244 zprintf("Syntax is: shutdown <seconds|cancel>\r\n");
248 if(std::string(args
)=="cancel")
250 sWorld
.ShutdownCancel();
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");
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");
276 zprintf("NO online users\r\n");
280 int linesize
= 1+15+2+20+3+15+2+6+3; // see format string
281 char* buf
= new char[resultDB
->GetRowCount()*linesize
+1];
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
);
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());
305 bufPos
+= sprintf(bufPos
,"|<Error> | %20s |<Error> |<Err> |\r\n",name
.c_str());
307 }while(resultDB
->NextRow());
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");
317 zprintf("=================================================================\r\n");
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
328 QueryResult
*result2
= loginDatabase
.Query( "SELECT `username` FROM `account` WHERE `banned` > 0" );
331 zprintf("Banned Accounts:\r\n");
334 fields
= result2
->Fetch();
335 zprintf("|%15s|\r\n", fields
[0].GetString());
336 }while( result2
->NextRow() );
340 ///- Get the list of banned IP addresses and display them
341 QueryResult
*result3
= loginDatabase
.Query( "SELECT `ip` FROM `ip_banned`" );
344 zprintf("Banned IPs:\r\n");
347 fields
= result3
->Fetch();
348 zprintf("|%15s|\r\n", fields
[0].GetString());
349 }while( result3
->NextRow() );
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
," ");
363 zprintf("Syntax is: ban <account|ip>\r\n");
367 // Is this IP address or account name?
368 bool is_ip
= IsIPAddress(banip
);
370 if(sWorld
.BanAccount(banip
))
373 zprintf("We banned IP: %s\r\n",banip
);
375 zprintf("We banned account: %s\r\n",banip
);
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
," ");
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
);
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
414 QueryResult
*result
= loginDatabase
.Query( "SELECT `username`,`gmlevel` FROM `account` WHERE `gmlevel` > 0" );
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");
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");
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");
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());
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
);
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
, " ");
496 zprintf("Syntax is: create <username> <password>\r\n");
502 zprintf("Account cannot be longer than 16 characters.\r\n");
506 char *szPassword
= strtok(NULL
, " ");
510 zprintf("Syntax is: create <username> <password>\r\n");
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());
523 zprintf("User %s already exists\r\n",szAcc
);
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
);
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
)
553 unsigned int l
=strlen(input
);
554 char *supposedCommand
=NULL
,* arguments
=(char*)("");
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
);
571 ///- Display an error message if the command is unknown
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
, " ");
583 zprintf("Syntax is: kick <charactername>\r\n");
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());
604 sWorld
.SetMotd(command
);
605 zprintf("Message of the day changed to:\r\n%s\r\n", command
);
610 /// \todo What is CorpsesErase for?
611 void CliCorpses(char*,pPrintf
)
616 /// Set the level of logging
617 void CliSetLogLevel(char*command
,pPrintf zprintf
)
619 char *NewLevel
= strtok(command
, " ");
622 zprintf("Syntax is: setloglevel <loglevel>\r\n");
625 sLog
.SetLogLevel(NewLevel
);
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
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
)
651 char *command
= fgets(commandbuf
,sizeof(commandbuf
),stdin
);
654 for(int x
=0;command
[x
];x
++)
655 if(command
[x
]=='\r'||command
[x
]=='\n')
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