2 // Copyright (c) 1997, 1998 Etienne BERNARD
3 // Copyright (C) 2002,2003,2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 #include <sys/types.h>
30 #include "DCCConnection.H"
31 #include "DCCChatConnection.H"
32 #include "StringTokenizer.H"
33 #include "ServerConnection.H"
35 #include "UserCommands.H"
36 #include "DCCManager.H"
39 unsigned int Bot::MAX_MESSAGES = 2;
40 unsigned int Bot::MAX_NICKLENGTH = 9;
42 #define DEFAULT_NICKNAME "Bobot"
43 #define DEFAULT_USERNAME "bobot"
44 #define DEFAULT_IRCNAME "I'm a bobot++!"
45 #define DEFAULT_COMMANDCHAR '!'
46 #define DEFAULT_USERLISTFILENAME "bot.users"
47 #define DEFAULT_SHITLISTFILENAME "bot.shit"
48 #define DEFAULT_HELPFILENAME "bot.help"
49 #define DEFAULT_SCRIPTLOGFILENAME "script.log"
50 #define DEFAULT_LOGFILENAME "bot.log"
51 #define DEFAULT_LOGDIR getenv ("HOME") + String("/.bobotpp/logs/")
52 #define DEFAULT_INITFILENAME "bot.init"
54 #define DEFAULT_AUTOEXECFILENAME "bot.autoexec"
57 Bot::Bot(String filename, bool debug_on)
58 : nickName(DEFAULT_NICKNAME),
59 wantedNickName(DEFAULT_NICKNAME),
60 userName(DEFAULT_USERNAME),
61 ircName(DEFAULT_IRCNAME),
62 versionString(VERSION_STRING),
65 commandChar(DEFAULT_COMMANDCHAR),
66 userListFileName(DEFAULT_USERLISTFILENAME),
67 shitListFileName(DEFAULT_SHITLISTFILENAME),
68 helpFileName(DEFAULT_HELPFILENAME),
69 initFileName(DEFAULT_INITFILENAME),
74 startTime(time(NULL)),
75 currentTime(startTime),
76 lastNickNameChange(startTime),
77 lastChannelJoin(startTime),
80 receivedUserhostID(0),
81 logFileName(DEFAULT_LOGFILENAME),
82 logs_dir (DEFAULT_LOGDIR),
83 configFileName (filename)
85 ,scriptLogFileName(DEFAULT_SCRIPTLOGFILENAME),
86 autoexecFileName(DEFAULT_AUTOEXECFILENAME)
90 wantedChannels.clear();
91 ignoredUserhosts.clear();
96 init_user_functions ();
98 set_log_dir (logs_dir);
99 set_log_file (logFileName);
102 channelList = new ChannelList();
103 serverList = new ServerList();
105 userList = new UserList(userListFileName);
106 shitList = new ShitList(shitListFileName);
107 todoList = new TodoList();
108 dccConnections = new DCCManager ();
110 // Let's read the alias file
111 std::ifstream initFile(initFileName);
115 // FIXME: these variables are current String instead of
116 // std::string because String>> reads an entire line. This code
117 // needs to be rewritten to use std::string and std::getline (or
118 // better yet, be removed entirely once BotConfig is in place)
119 String temp, alias, command;
122 while (initFile >> temp, temp.length() != 0)
124 StringTokenizer st(temp);
127 temp = Utils::trim_str (temp);
134 if (st.count_tokens (' ') != 2)
136 std::cerr << "Error when reading alias file (" << initFileName
137 << ") line " << line << "...\n";
141 alias = Utils::to_upper (st.next_token());
142 command = Utils::to_upper (st.next_token());
144 // Does the function already exist ?
145 if (!userFunctions[alias])
147 if (userFunction *u = userFunctions[command])
148 userFunctions[alias] =
150 userFunction(u->function,
152 u->needsChannelName);
158 std::srand (std::time (0)); // srand for bot-random
161 botInterp = new BotInterp(this, logs_dir + scriptLogFileName);
162 botInterp->LoadScript(autoexecFileName);
169 while (spyList.size() != 0) {
170 p = (*spyList.begin()).second;
171 spyList.erase(spyList.begin());
174 delete dccConnections;
175 destroy_user_functions ();
178 while (wantedChannels.size() != 0) {
179 w = (*wantedChannels.begin()).second;
180 wantedChannels.erase(wantedChannels.begin());
190 delete serverConnection;
191 logLine("Stopping log.");
196 Bot::logLine(String line)
199 std::time_t current_time = time(0);
201 d = localtime(¤t_time);
202 logFile << "[" << std::setfill('0') << std::setw(2)
203 << d->tm_mday << "/" << std::setfill('0') << std::setw(2)
204 << d->tm_mon + 1 << "/"
205 << d->tm_year + 1900 << " - " << std::setfill('0') << std::setw(2)
206 << d->tm_hour << ":" << std::setfill('0') << std::setw(2)
207 << d->tm_min << ":" << std::setfill('0') << std::setw(2)
216 std::ifstream file(configFileName);
221 logLine(String("I cannot find the file ") + configFileName);
225 while (!file.eof()) {
229 if (temp.length() == 0 || temp[(unsigned int)0] == '#') {
234 StringTokenizer st(temp);
235 String command = Utils::to_upper (Utils::trim_str (st.next_token('=')));
236 String parameters = Utils::trim_str (st.next_token('='));
238 if (command == "NICK" || command == "NICKNAME")
239 nickName = wantedNickName = parameters;
240 else if (command == "USERNAME")
241 userName = parameters;
242 else if (command == "IRCNAME" || command == "REALNAME")
243 ircName = parameters;
244 else if (command == "CMDCHAR" || command == "COMMAND")
245 commandChar = parameters[(unsigned int)0];
246 else if (command == "USERLIST")
247 userListFileName = parameters;
248 else if (command == "SHITLIST")
249 shitListFileName = parameters;
250 else if (command == "CHANNEL") {
251 if (parameters.indexOf(':') == -1) {
252 std::cout << "Warning. The 'channel' syntax has changed."
253 << " Please see the README file for more information."
254 << " I will use compatibility mode, but you're really"
255 << " missing something.\n";
256 StringTokenizer st2(parameters);
257 String name = Utils::to_lower (st2.next_token());
258 String key = st2.next_token();
259 wantedChannels[name] = new wantedChannel("", "", key);
261 StringTokenizer st2(parameters);
262 String name = Utils::to_lower (st2.next_token(':'));
263 String mode = st2.next_token(':');
264 String keep = st2.next_token(':');
265 String key = st2.next_token(':');
266 wantedChannels[name] = new wantedChannel(mode, keep, key);
269 else if (command == "LOGFILE")
271 if (parameters != logFileName)
273 if (parameters[(unsigned int)0] == '/')
276 set_log_file (parameters.subString (1));
279 set_log_file (parameters);
283 else if (command == "SCRIPTLOGFILE")
284 scriptLogFileName = parameters;
285 else if (command == "AUTOEXECFILE")
286 autoexecFileName = parameters;
288 else if (command == "INITFILE")
289 initFileName = parameters;
290 else if (command == "LOCALIP")
291 localIP = parameters;
292 else if (command == "MAXNICKLENGTH")
293 MAX_NICKLENGTH = std::atoi (parameters);
294 else if (command == "SERVER") {
295 if (parameters.indexOf(' ') == -1)
296 serverList->addServer(new Server(parameters));
298 StringTokenizer st2(parameters);
299 String name = st2.next_token();
300 int port = std::atoi(st2.next_token().c_str());
301 serverList->addServer(new Server(name,
307 logLine(String("Syntax error in file ") + configFileName +
308 ", line " + String((long)line));
326 waitForInput(); // This is the main event loop
327 dccConnections->checkStale ();
329 if (!serverConnection->queue->flush())
333 // Run hooks/disconnect
334 this->botInterp->RunHooks
336 serverConnection->server->getHostName (),
338 (Utils::str2scm (serverConnection->server->getHostName ()),
354 struct timeval timer;
356 int sock = serverConnection->getFileDescriptor();
357 int maxSocketNumber = sock;
366 DCC_MAP* dccmap = &dccConnections->dcc_map;
367 for (DCC_MAP::iterator it = dccmap->begin ();
368 it != dccmap->end(); ++it) {
369 int s = it->second->dcc->getFileDescriptor();
375 if (s > maxSocketNumber)
382 switch (select(maxSocketNumber + 1, &rd, NULL, NULL, &timer)) {
383 case 0: /* timeout */
387 default: /* normal */
391 if (FD_ISSET(sock, &rd))
393 if (serverConnection->handleInput())
396 dccConnections->checkInput (rd);
399 if (currentTime < std::time(0)) { // Actions that we do each second
400 currentTime = std::time(0);
401 for (std::map<String, unsigned int, std::less<String> >::iterator
402 it = ignoredUserhosts.begin();
403 it != ignoredUserhosts.end(); ++it)
404 if ((*it).second > 0)
408 while ((line = todoList->getNext()) != "") {
409 serverConnection->queue->sendChannelMode(line);
412 botInterp->RunTimers(currentTime);
414 tm *thisTime = localtime(¤tTime);
415 if (thisTime->tm_sec == 0)
418 std::snprintf(s, 6, "%02d:%02d", thisTime->tm_hour, thisTime->tm_min);
420 botInterp->RunHooks(Hook::TIMER, String(s),
421 scm_list_n (Utils::str2scm (std::string (s)),
428 if (currentTime >= (time_t)(lastNickNameChange + Bot::NICK_CHANGE) &&
429 nickName != wantedNickName) {
430 lastNickNameChange = currentTime;
431 serverConnection->queue->sendNick(wantedNickName);
434 if (currentTime >= (std::time_t)(lastChannelJoin + Bot::CHANNEL_JOIN)) {
435 lastChannelJoin = currentTime;
436 for (std::map<String, wantedChannel *, std::less<String> >::iterator it =
437 wantedChannels.begin(); it != wantedChannels.end();
439 if (channelList->getChannel((*it).first) == 0)
440 serverConnection->queue->sendJoin((*it).first, (*it).second->key);
443 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken
444 + Bot::PING_TIME) && !sentPing)
446 serverConnection->queue->sendPing("Testing connection");
450 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken
458 // We can change server if we will not lose op on a channel
460 Bot::canChangeServer()
465 for (std::map<String, Channel *, std::less<String> >::iterator it =
466 channelList->begin();
467 it != channelList->end(); ++it) {
468 channel = (*it).first;
469 c = channelList->getChannel(channel);
470 if (c->countOp == 1 &&
471 c->count > 1 && this->iAmOp(channel))
483 channelList->clear();
485 if (serverConnection)
486 userList->removeFirst();
488 delete serverConnection;
491 Server * s = serverList->nextServer();
493 std::cout << "No server found. Exiting..." << std::endl;
496 serverConnection = new ServerConnection(this, s, localIP);
497 if (!serverConnection->connect()) {
499 // We sleep 10 seconds, to avoid connection flood
501 delete serverConnection;
512 channelList->clear();
514 userList->removeFirst();
516 delete serverConnection;
519 new ServerConnection(this, serverList->currentServer(), localIP);
521 serverConnection->connect();
525 Bot::connect(int serverNumber)
528 channelList->clear();
530 userList->removeFirst();
532 delete serverConnection;
535 new ServerConnection(this, serverList->get(serverNumber), localIP);
537 serverConnection->connect();
541 Bot::addDCC(Person * from, unsigned long address, int port, int type)
543 DCCConnection *d = 0;
547 d = new DCCChatConnection(this, from->getAddress (),
557 logLine ("DCC Connection failed from " + from->getAddress ());
561 logLine ("DCC CHAT accepted from" + from->getAddress ());
562 dccConnections->addConnection (d);
568 for (std::map<String, Channel *, std::less<String> >::iterator it =
569 channelList->begin();
570 it != channelList->end(); ++it)
571 serverConnection->queue->sendWho((*it).first);
575 Bot::getUserhost(String channel, String nick)
582 c = channelList->getChannel(channel);
584 nick = nick.toLower();
587 if (c && c->hasNick(nick))
588 return c->getUser(nick)->userhost;
590 unsigned long num = sentUserhostID++;
592 serverConnection->queue->sendUserhost(nick);
593 userhostMap[num] = "+";
595 while (userhostMap[num] == "+") {
597 serverConnection->queue->flush();
600 // We have got our answer
601 String res = userhostMap[num];
602 userhostMap.erase(num);
608 Bot::iAmOp(String channel)
610 User * me = channelList->getChannel(channel)->getUser(nickName);
611 return (me->mode & User::OP_MODE);
615 Bot::init_user_functions ()
618 #define uf(f, l, b) new userFunction (f, l, b);
619 userFunctions["ACTION"] = uf (UserCommands::Action, User::USER, true);
620 userFunctions["ADDUSER"] = uf (UserCommands::AddUser, User::FRIEND, false);
621 userFunctions["ADDSERVER"] = uf (UserCommands::AddServer, User::FRIEND,
623 userFunctions["ADDSHIT"] = uf (UserCommands::AddShit, User::FRIEND, false);
624 userFunctions["ALIAS"] = uf (UserCommands::Alias, User::MASTER, false);
625 userFunctions["BAN"] = uf (UserCommands::Ban, User::USER, true);
626 userFunctions["BANLIST"] = uf (UserCommands::BanList, User::USER, true);
627 userFunctions["CHANNELS"] =
628 uf (UserCommands::Channels, User::FRIEND, false);
629 userFunctions["CYCLE"] = uf (UserCommands::Cycle, User::FRIEND, true);
630 userFunctions["DCCLIST"] = uf (UserCommands::DCCList, User::FRIEND, false);
631 userFunctions["DEBAN"] = uf (UserCommands::Deban, User::USER, true);
632 userFunctions["DELSERVER"] = uf (UserCommands::DelServer, User::FRIEND,
634 userFunctions["DELUSER"] = uf (UserCommands::DelUser, User::FRIEND, false);
635 userFunctions["DELSHIT"] = uf (UserCommands::DelShit, User::FRIEND, false);
636 userFunctions["DEOP"] = uf (UserCommands::Deop, User::TRUSTED_USER, true);
637 userFunctions["DIE"] = uf (UserCommands::Die, User::MASTER, false);
638 userFunctions["DO"] = uf (UserCommands::Do, User::MASTER, false);
640 userFunctions["EXECUTE"] = uf (UserCommands::Execute, User::MASTER, false);
642 userFunctions["HELP"] = uf (UserCommands::Help, User::NONE, false);
643 userFunctions["IDENT"] = uf (UserCommands::Ident, User::NONE, true);
644 userFunctions["INVITE"] = uf (UserCommands::Invite, User::USER, true);
645 userFunctions["JOIN"] = uf (UserCommands::Join, User::FRIEND, false);
646 userFunctions["KEEP"] = uf (UserCommands::Keep, User::FRIEND, true);
647 userFunctions["KICK"] = uf (UserCommands::Kick, User::USER, true);
648 userFunctions["KICKBAN"] = uf (UserCommands::KickBan, User::USER, true);
649 userFunctions["LOAD"] = uf (UserCommands::Load, User::FRIEND, false);
651 userFunctions["LOADSCRIPT"] = uf (UserCommands::LoadScript, User::MASTER,
654 userFunctions["LOCK"] = uf (UserCommands::Lock, User::FRIEND, true);
655 userFunctions["MODE"] = uf (UserCommands::Mode, User::FRIEND, true);
656 userFunctions["MSG"] = uf (UserCommands::Msg, User::USER, false);
657 userFunctions["NAMES"] = uf (UserCommands::Names, User::USER, true);
658 userFunctions["NEXTSERVER"] = uf (UserCommands::NextServer, User::FRIEND,
660 userFunctions["NICK"] = uf (UserCommands::Nick, User::FRIEND, false);
661 userFunctions["NSLOOKUP"] = uf (UserCommands::NsLookup, User::USER, false);
662 userFunctions["OP"] = uf (UserCommands::Op, User::TRUSTED_USER, true);
663 userFunctions["PART"] = uf (UserCommands::Part, User::FRIEND, true);
664 userFunctions["PASSWORD"] = uf (UserCommands::Password, User::USER, true);
665 userFunctions["RECONNECT"] =
666 uf (UserCommands::Reconnect, User::FRIEND, false);
667 userFunctions["RSPYMESSAGE"] =
668 uf (UserCommands::RSpyMessage, User::USER, false);
669 userFunctions["SAVE"] = uf (UserCommands::Save, User::FRIEND, false);
670 userFunctions["SAY"] = uf (UserCommands::Say, User::USER, true);
671 userFunctions["SERVER"] = uf (UserCommands::Server, User::FRIEND, false);
672 userFunctions["SERVERLIST"] =
673 uf (UserCommands::ServerList, User::FRIEND, false);
674 userFunctions["SETFLOODRATE"] =
675 uf (UserCommands::SetFloodRate, User::MASTER, false);
676 userFunctions["SETVERSION"] =
677 uf (UserCommands::SetVersion, User::MASTER, false);
678 userFunctions["SHITLIST"] =
679 uf (UserCommands::ShitList, User::FRIEND, false);
680 userFunctions["SPYLIST"] = uf (UserCommands::SpyList, User::USER, false);
681 userFunctions["SPYMESSAGE"] =
682 uf (UserCommands::SpyMessage, User::USER, false);
683 userFunctions["STATS"] = uf (UserCommands::Stats, User::FRIEND, true);
684 userFunctions["TBAN"] = uf (UserCommands::TBan, User::USER, true);
685 userFunctions["TKBAN"] = uf (UserCommands::TKBan, User::USER, true);
686 userFunctions["TOPIC"] = uf (UserCommands::Topic, User::USER, true);
687 userFunctions["UNLOCK"] = uf (UserCommands::Unlock, User::FRIEND, true);
688 userFunctions["USERLIST"] =
689 uf (UserCommands::UserList, User::FRIEND, false);
690 userFunctions["WHO"] = uf (UserCommands::Who, User::NONE, true);
691 userFunctions["WHOIS"] = uf (UserCommands::Whois, User::FRIEND, true);
697 void erase_userf (std::pair<std::string, class userFunction*> it)
704 Bot::destroy_user_functions ()
706 std::for_each (userFunctions.begin (),
707 userFunctions.end (),
709 userFunctions.erase (userFunctions.begin (),
710 userFunctions.end ());
714 Bot::set_log_file (String name)
720 logFile.open(logs_dir + logFileName, std::ios_base::out |
721 std::ios_base::ate | std::ios_base::app);
723 logFile.open(logs_dir + logFileName, ios::out | ios::ate
727 logLine("Starting log.");
731 Bot::set_log_dir (String dir)