2 // Copyright (c) 1997, 1998 Etienne BERNARD
3 // Copyright (C) 2002 Clinton Ebadi
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
26 #include <sys/types.h>
30 #include "DCCConnection.H"
31 #include "StringTokenizer.H"
32 #include "ServerConnection.H"
34 #include "UserCommands.H"
36 #define DEFAULT_NICKNAME "Bobot"
37 #define DEFAULT_USERNAME "bobot"
38 #define DEFAULT_IRCNAME "I'm a bobot++!"
39 #define DEFAULT_COMMANDCHAR '!'
40 #define DEFAULT_USERLISTFILENAME "bot.users"
41 #define DEFAULT_SHITLISTFILENAME "bot.shit"
42 #define DEFAULT_HELPFILENAME "bot.help"
43 #define DEFAULT_SCRIPTLOGFILENAME "script.log"
44 #define DEFAULT_LOGFILENAME "bot.log"
45 #define DEFAULT_LOGDIR getenv ("HOME") + String("/.bobotpp/logs/")
46 #define DEFAULT_INITFILENAME "bot.init"
48 #define DEFAULT_AUTOEXECFILENAME "bot.autoexec"
51 Bot::Bot(String filename, bool debug_on)
52 : nickName(DEFAULT_NICKNAME),
53 wantedNickName(DEFAULT_NICKNAME),
54 userName(DEFAULT_USERNAME),
55 ircName(DEFAULT_IRCNAME),
56 versionString(VERSION_STRING),
59 commandChar(DEFAULT_COMMANDCHAR),
60 configFileName(filename),
61 userListFileName(DEFAULT_USERLISTFILENAME),
62 shitListFileName(DEFAULT_SHITLISTFILENAME),
63 logFileName(DEFAULT_LOGFILENAME),
64 logs_dir (DEFAULT_LOGDIR),
65 helpFileName(DEFAULT_HELPFILENAME),
66 initFileName(DEFAULT_INITFILENAME),
68 scriptLogFileName(DEFAULT_SCRIPTLOGFILENAME),
69 autoexecFileName(DEFAULT_AUTOEXECFILENAME),
72 debug(debug_on), stop(false), sentPing(false),
73 startTime(time(NULL)), currentTime(startTime),
74 lastNickNameChange(startTime), lastChannelJoin(startTime),
75 serverConnection(0), sentUserhostID(0), receivedUserhostID(0)
78 wantedChannels.clear();
79 ignoredUserhosts.clear();
84 init_user_functions ();
86 set_log_dir (logs_dir);
87 set_log_file (logFileName);
90 channelList = new ChannelList();
91 serverList = new ServerList();
93 userList = new UserList(userListFileName);
94 shitList = new ShitList(shitListFileName);
95 todoList = new TodoList();
97 // Let's read the alias file
98 std::ifstream initFile(initFileName);
101 String temp, alias, command;
103 while (initFile >> temp, temp.length() != 0) {
105 StringTokenizer st(temp);
107 if (temp[0]=='#') continue;
108 if (st.countTokens(' ') != 2) {
109 std::cerr << "Error when reading alias file (" << initFileName
110 << ") line " << line << "...\n";
113 alias = st.nextToken().toUpper();
114 command = st.nextToken().toUpper();
116 // Does the function already exist ?
117 if (!userFunctions[alias])
119 if (userFunction *u = userFunctions[command])
120 userFunctions[alias] =
122 userFunction(u->function,
124 u->needsChannelName);
130 std::srand (std::time (0)); // srand for bot-random
132 botInterp = new BotInterp(this, logs_dir + scriptLogFileName);
133 botInterp->LoadScript(autoexecFileName);
140 while (spyList.size() != 0) {
141 p = (*spyList.begin()).second;
142 spyList.erase(spyList.begin());
146 while (dccConnections.size() != 0) {
147 d = *dccConnections.begin();
148 dccConnections.erase(dccConnections.begin());
152 destroy_user_functions ();
155 while (wantedChannels.size() != 0) {
156 w = (*wantedChannels.begin()).second;
157 wantedChannels.erase(wantedChannels.begin());
167 delete serverConnection;
168 logLine("Stopping log.");
173 Bot::logLine(String line)
176 std::time_t current_time = time(0);
178 d = localtime(¤t_time);
179 logFile << "[" << std::setfill('0') << std::setw(2)
180 << d->tm_mday << "/" << std::setfill('0') << std::setw(2)
181 << d->tm_mon + 1 << "/"
182 << d->tm_year + 1900 << " - " << std::setfill('0') << std::setw(2)
183 << d->tm_hour << ":" << std::setfill('0') << std::setw(2)
184 << d->tm_min << ":" << std::setfill('0') << std::setw(2)
193 std::ifstream file(configFileName);
198 logLine(String("I cannot find the file ") + configFileName);
202 while (!file.eof()) {
206 if (temp.length() == 0 || temp[0] == '#') {
211 StringTokenizer st(temp);
212 String command = st.nextToken('=').trim().toUpper();
213 String parameters = st.nextToken('=').trim();
215 if (command == "NICK" || command == "NICKNAME")
216 nickName = wantedNickName = parameters;
217 else if (command == "USERNAME")
218 userName = parameters;
219 else if (command == "IRCNAME" || command == "REALNAME")
220 ircName = parameters;
221 else if (command == "CMDCHAR" || command == "COMMAND")
222 commandChar = parameters[0];
223 else if (command == "USERLIST")
224 userListFileName = parameters;
225 else if (command == "SHITLIST")
226 shitListFileName = parameters;
227 else if (command == "CHANNEL") {
228 if (parameters.indexOf(':') == -1) {
229 std::cout << "Warning. The 'channel' syntax has changed."
230 << " Please see the README file for more information."
231 << " I will use compatibility mode, but you're really"
232 << " missing something.\n";
233 StringTokenizer st2(parameters);
234 String name = st2.nextToken().toLower();
235 String key = st2.nextToken();
236 wantedChannels[name] = new wantedChannel("", "", key);
238 StringTokenizer st2(parameters);
239 String name = st2.nextToken(':').toLower();
240 String mode = st2.nextToken(':');
241 String keep = st2.nextToken(':');
242 String key = st2.nextToken(':');
243 wantedChannels[name] = new wantedChannel(mode, keep, key);
246 else if (command == "LOGFILE")
248 if (parameters != logFileName)
250 if (parameters[0] == '/')
253 set_log_file (parameters.subString (1));
256 set_log_file (parameters);
260 else if (command == "SCRIPTLOGFILE")
261 scriptLogFileName = parameters;
262 else if (command == "AUTOEXECFILE")
263 autoexecFileName = parameters;
265 else if (command == "INITFILE")
266 initFileName = parameters;
267 else if (command == "LOCALIP")
268 localIP = parameters;
269 else if (command == "SERVER") {
270 if (parameters.indexOf(' ') == -1)
271 serverList->addServer(new Server(parameters));
273 StringTokenizer st2(parameters);
274 String name = st2.nextToken();
275 int port = std::atoi(st2.nextToken());
276 serverList->addServer(new Server(name,
282 logLine(String("Syntax error in file ") + configFileName +
283 ", line " + String((long)line));
300 waitForInput(); // This is the main event loop
301 if (!serverConnection->queue->flush())
314 struct timeval timer;
316 int sock = serverConnection->getFileDescriptor();
317 int maxSocketNumber = sock;
326 for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
327 it != dccConnections.end(); ++it) {
328 int s = (*it)->getFileDescriptor();
334 if (s > maxSocketNumber)
341 switch (select(maxSocketNumber + 1, &rd, NULL, NULL, &timer)) {
342 case 0: /* timeout */
346 default: /* normal */
350 if (FD_ISSET(sock, &rd))
352 if (serverConnection->handleInput())
355 std::list<DCCConnection *>::iterator it = dccConnections.begin();
356 std::list<DCCConnection *>::iterator it2;
358 while (it != dccConnections.end()) {
362 if (rd & (*it2)->getFileDescriptor()) {
364 if (FD_ISSET((*it2)->getFileDescriptor(), &rd)) {
366 if ((*it2)->handleInput()) {
368 dccConnections.erase(it2);
374 if (currentTime < std::time(NULL)) { // Actions that we do each second
375 currentTime = std::time(NULL);
376 for (std::map<String, unsigned int, std::less<String> >::iterator
377 it = ignoredUserhosts.begin();
378 it != ignoredUserhosts.end(); ++it)
379 if ((*it).second > 0)
383 while ((line = todoList->getNext()) != "") {
384 serverConnection->queue->sendChannelMode(line);
387 botInterp->RunTimers(currentTime);
391 tm *thisTime = localtime(¤tTime);
392 if (thisTime->tm_sec == 0) {
394 sprintf(s, "%2d:%2d", thisTime->tm_hour, thisTime->tm_min);
395 botInterp->RunHooks(Hook::TIMER, String(s),
396 gh_list(Utils::string2SCM(String(s)), SCM_UNDEFINED));
402 if (currentTime >= (time_t)(lastNickNameChange + Bot::NICK_CHANGE) &&
403 nickName != wantedNickName) {
404 lastNickNameChange = currentTime;
405 serverConnection->queue->sendNick(wantedNickName);
408 if (currentTime >= (std::time_t)(lastChannelJoin + Bot::CHANNEL_JOIN)) {
409 lastChannelJoin = currentTime;
410 for (std::map<String, wantedChannel *, std::less<String> >::iterator it =
411 wantedChannels.begin(); it != wantedChannels.end();
413 if (channelList->getChannel((*it).first) == 0)
414 serverConnection->queue->sendJoin((*it).first, (*it).second->key);
417 std::list<DCCConnection *>::iterator it2;
419 for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
420 it != dccConnections.end(); ) {
423 if ((*it2)->autoRemove && currentTime >= (std::time_t)((*it2)->lastSpoken + Bot::DCC_DELAY)) {
425 dccConnections.erase(it2);
429 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::PING_TIME) && !sentPing) {
430 serverConnection->queue->sendPing("Testing connection");
434 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::TIMEOUT)) {
440 // We can change server if we will not lose op on a channel
442 Bot::canChangeServer()
447 for (std::map<String, Channel *, std::less<String> >::iterator it =
448 channelList->begin();
449 it != channelList->end(); ++it) {
450 channel = (*it).first;
451 c = channelList->getChannel(channel);
452 if (c->countOp == 1 &&
453 c->count > 1 && this->iAmOp(channel))
465 channelList->clear();
467 if (serverConnection)
468 userList->removeFirst();
470 delete serverConnection;
473 Server * s = serverList->nextServer();
475 std::cout << "No server found. Exiting..." << std::endl;
478 serverConnection = new ServerConnection(this, s, localIP);
479 if (!serverConnection->connect()) {
481 // We sleep 10 seconds, to avoid connection flood
483 delete serverConnection;
494 channelList->clear();
496 userList->removeFirst();
498 delete serverConnection;
501 new ServerConnection(this, serverList->currentServer(), localIP);
503 serverConnection->connect();
507 Bot::connect(int serverNumber)
510 channelList->clear();
512 userList->removeFirst();
514 delete serverConnection;
517 new ServerConnection(this, serverList->get(serverNumber), localIP);
519 serverConnection->connect();
523 Bot::addDCC(Person * from, unsigned long address, int port)
525 DCCConnection * d = new DCCConnection(this, from->getAddress (),
530 logLine ("DCC Connection failed from " + from->getAddress ());
533 logLine ("DCC CHAT accepted from" + from->getAddress ());
534 dccConnections.push_back(d);
540 for (std::map<String, Channel *, std::less<String> >::iterator it =
541 channelList->begin();
542 it != channelList->end(); ++it)
543 serverConnection->queue->sendWho((*it).first);
547 Bot::getUserhost(String channel, String nick)
554 c = channelList->getChannel(channel);
556 nick = nick.toLower();
559 if (c && c->hasNick(nick))
560 return c->getUser(nick)->userhost;
562 unsigned long num = sentUserhostID++;
564 serverConnection->queue->sendUserhost(nick);
565 userhostMap[num] = "+";
567 while (userhostMap[num] == "+") {
569 serverConnection->queue->flush();
572 // We have got our answer
573 String res = userhostMap[num];
574 userhostMap.erase(num);
580 Bot::iAmOp(String channel)
582 User * me = channelList->getChannel(channel)->getUser(nickName);
583 return (me->mode & User::OP_MODE);
587 Bot::init_user_functions ()
590 #define uf(f, l, b) new userFunction (f, l, b);
591 userFunctions["ACTION"] = uf (UserCommands::Action, User::USER, true);
592 userFunctions["ADDUSER"] = uf (UserCommands::AddUser, User::FRIEND, false);
593 userFunctions["ADDSERVER"] = uf (UserCommands::AddServer, User::FRIEND,
595 userFunctions["ADDSHIT"] = uf (UserCommands::AddShit, User::FRIEND, false);
596 userFunctions["ALIAS"] = uf (UserCommands::Alias, User::MASTER, false);
597 userFunctions["BAN"] = uf (UserCommands::Ban, User::USER, true);
598 userFunctions["BANLIST"] = uf (UserCommands::BanList, User::USER, true);
599 userFunctions["CHANNELS"] =
600 uf (UserCommands::Channels, User::FRIEND, false);
601 userFunctions["CYCLE"] = uf (UserCommands::Cycle, User::FRIEND, true);
602 userFunctions["DCCLIST"] = uf (UserCommands::DCCList, User::FRIEND, false);
603 userFunctions["DEBAN"] = uf (UserCommands::Deban, User::USER, true);
604 userFunctions["DELSERVER"] = uf (UserCommands::DelServer, User::FRIEND,
606 userFunctions["DELUSER"] = uf (UserCommands::DelUser, User::FRIEND, false);
607 userFunctions["DELSHIT"] = uf (UserCommands::DelShit, User::FRIEND, false);
608 userFunctions["DEOP"] = uf (UserCommands::Deop, User::TRUSTED_USER, true);
609 userFunctions["DIE"] = uf (UserCommands::Die, User::MASTER, false);
610 userFunctions["DO"] = uf (UserCommands::Do, User::MASTER, false);
612 userFunctions["EXECUTE"] = uf (UserCommands::Execute, User::MASTER, false);
614 userFunctions["HELP"] = uf (UserCommands::Help, User::NONE, false);
615 userFunctions["IDENT"] = uf (UserCommands::Ident, User::NONE, true);
616 userFunctions["INVITE"] = uf (UserCommands::Invite, User::USER, true);
617 userFunctions["JOIN"] = uf (UserCommands::Join, User::FRIEND, false);
618 userFunctions["KEEP"] = uf (UserCommands::Keep, User::FRIEND, true);
619 userFunctions["KICK"] = uf (UserCommands::Kick, User::USER, true);
620 userFunctions["KICKBAN"] = uf (UserCommands::KickBan, User::USER, true);
621 userFunctions["LOAD"] = uf (UserCommands::Load, User::FRIEND, false);
623 userFunctions["LOADSCRIPT"] = uf (UserCommands::LoadScript, User::MASTER,
626 userFunctions["LOCK"] = uf (UserCommands::Lock, User::FRIEND, true);
627 userFunctions["MODE"] = uf (UserCommands::Mode, User::FRIEND, true);
628 userFunctions["MSG"] = uf (UserCommands::Msg, User::USER, false);
629 userFunctions["NAMES"] = uf (UserCommands::Names, User::USER, true);
630 userFunctions["NEXTSERVER"] = uf (UserCommands::NextServer, User::FRIEND,
632 userFunctions["NICK"] = uf (UserCommands::Nick, User::FRIEND, false);
633 userFunctions["NSLOOKUP"] = uf (UserCommands::NsLookup, User::USER, false);
634 userFunctions["OP"] = uf (UserCommands::Op, User::TRUSTED_USER, true);
635 userFunctions["PART"] = uf (UserCommands::Part, User::FRIEND, true);
636 userFunctions["PASSWORD"] = uf (UserCommands::Password, User::USER, true);
637 userFunctions["RECONNECT"] =
638 uf (UserCommands::Reconnect, User::FRIEND, false);
639 userFunctions["RSPYMESSAGE"] =
640 uf (UserCommands::RSpyMessage, User::USER, false);
641 userFunctions["SAVE"] = uf (UserCommands::Save, User::FRIEND, false);
642 userFunctions["SAY"] = uf (UserCommands::Say, User::USER, true);
643 userFunctions["SERVER"] = uf (UserCommands::Server, User::FRIEND, false);
644 userFunctions["SERVERLIST"] =
645 uf (UserCommands::ServerList, User::FRIEND, false);
646 userFunctions["SETVERSION"] =
647 uf (UserCommands::SetVersion, User::MASTER, false);
648 userFunctions["SHITLIST"] =
649 uf (UserCommands::ShitList, User::FRIEND, false);
650 userFunctions["SPYLIST"] = uf (UserCommands::SpyList, User::USER, false);
651 userFunctions["SPYMESSAGE"] =
652 uf (UserCommands::SpyMessage, User::USER, false);
653 userFunctions["STATS"] = uf (UserCommands::Stats, User::FRIEND, true);
654 userFunctions["TBAN"] = uf (UserCommands::TBan, User::USER, true);
655 userFunctions["TKBAN"] = uf (UserCommands::TKBan, User::USER, true);
656 userFunctions["TOPIC"] = uf (UserCommands::Topic, User::USER, true);
657 userFunctions["UNLOCK"] = uf (UserCommands::Unlock, User::FRIEND, true);
658 userFunctions["USERLIST"] =
659 uf (UserCommands::UserList, User::FRIEND, false);
660 userFunctions["WHO"] = uf (UserCommands::Who, User::NONE, true);
661 userFunctions["WHOIS"] = uf (UserCommands::Whois, User::FRIEND, true);
667 void erase_userf (std::pair<std::string, class userFunction*> it)
674 Bot::destroy_user_functions ()
676 std::for_each (userFunctions.begin (),
677 userFunctions.end (),
679 userFunctions.erase (userFunctions.begin (),
680 userFunctions.end ());
684 Bot::set_log_file (String name)
690 logFile.open(logs_dir + logFileName, std::ios_base::out |
691 std::ios_base::ate | std::ios_base::app);
693 logFile.open(logs_dir + logFileName, ios::out | ios::ate
697 logLine("Starting log.");
701 Bot::set_log_dir (String dir)