ec980681f4a6bada51bf867d2962bbc7ccc67683
[clinton/bobotpp.git] / source / Bot.C
1 // Bot.C -*- C++ -*-
2 // Copyright (c) 1997, 1998 Etienne BERNARD
3 // Copyright (C) 2002 Clinton Ebadi
4
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
8 // any later version.
9
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.
14
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.
18
19 #include <fstream>
20 #include <algorithm>
21 #include <iomanip>
22 #include <cstring>
23 #include <cstdlib>
24 #include <cstdio>
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28
29 #include "Bot.H"
30 #include "DCCConnection.H"
31 #include "StringTokenizer.H"
32 #include "ServerConnection.H"
33 #include "Utils.H"
34 #include "UserCommands.H"
35
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"
47 #ifdef USESCRIPTS
48 #define DEFAULT_AUTOEXECFILENAME "bot.autoexec"
49 #endif
50
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),
57 userHost(""),
58 localIP(""),
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),
67 #ifdef USESCRIPTS
68 scriptLogFileName(DEFAULT_SCRIPTLOGFILENAME),
69 autoexecFileName(DEFAULT_AUTOEXECFILENAME),
70 #endif
71 connected(false),
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)
76 {
77 #ifdef HAVE_STL_CLEAR
78 wantedChannels.clear();
79 ignoredUserhosts.clear();
80 spyList.clear();
81 userhostMap.clear();
82 #endif
83
84 init_user_functions ();
85
86 set_log_dir (logs_dir);
87 set_log_file (logFileName);
88
89
90 channelList = new ChannelList();
91 serverList = new ServerList();
92 readConfig();
93 userList = new UserList(userListFileName);
94 shitList = new ShitList(shitListFileName);
95 todoList = new TodoList();
96
97 // Let's read the alias file
98 std::ifstream initFile(initFileName);
99
100 if (initFile) {
101 String temp, alias, command;
102 int line = 0;
103 while (initFile >> temp, temp.length() != 0) {
104 line++;
105 StringTokenizer st(temp);
106 temp = temp.trim();
107 if (temp[0]=='#') continue;
108 if (st.countTokens(' ') != 2) {
109 std::cerr << "Error when reading alias file (" << initFileName
110 << ") line " << line << "...\n";
111 continue;
112 }
113 alias = st.nextToken().toUpper();
114 command = st.nextToken().toUpper();
115
116 // Does the function already exist ?
117 if (!userFunctions[alias])
118 {
119 if (userFunction *u = userFunctions[command])
120 userFunctions[alias] =
121 new
122 userFunction(u->function,
123 u->minLevel,
124 u->needsChannelName);
125 }
126 }
127 }
128
129
130 std::srand (std::time (0)); // srand for bot-random
131 #ifdef USESCRIPTS
132 botInterp = new BotInterp(this, logs_dir + scriptLogFileName);
133 botInterp->LoadScript(autoexecFileName);
134 #endif
135 }
136
137 Bot::~Bot()
138 {
139 // TODO: is it ok to delete iterators!?!
140
141 Person *p;
142 while (spyList.size() != 0) {
143 p = (*spyList.begin()).second;
144 spyList.erase(spyList.begin());
145 delete p;
146 }
147 DCCConnection *d;
148 while (dccConnections.size() != 0) {
149 d = *dccConnections.begin();
150 dccConnections.erase(dccConnections.begin());
151 delete d;
152 }
153
154 destroy_user_functions ();
155
156 wantedChannel *w;
157 while (wantedChannels.size() != 0) {
158 w = (*wantedChannels.begin()).second;
159 wantedChannels.erase(wantedChannels.begin());
160 delete w;
161 }
162 userList->save();
163 shitList->save();
164 delete channelList;
165 delete userList;
166 delete todoList;
167 delete serverList;
168 delete shitList;
169 delete serverConnection;
170 logLine("Stopping log.");
171 logFile.close();
172 }
173
174 void
175 Bot::logLine(String line)
176 {
177 tm *d;
178 std::time_t current_time = time(0);
179
180 d = localtime(&current_time);
181 logFile << "[" << std::setfill('0') << std::setw(2)
182 << d->tm_mday << "/" << std::setfill('0') << std::setw(2)
183 << d->tm_mon + 1 << "/"
184 << d->tm_year + 1900 << " - " << std::setfill('0') << std::setw(2)
185 << d->tm_hour << ":" << std::setfill('0') << std::setw(2)
186 << d->tm_min << ":" << std::setfill('0') << std::setw(2)
187 << d->tm_sec << "] "
188 << line
189 << std::endl;
190 }
191
192 void
193 Bot::readConfig()
194 {
195 std::ifstream file(configFileName);
196 String temp;
197 int line = 1;
198
199 if (!file) {
200 logLine(String("I cannot find the file ") + configFileName);
201 return;
202 }
203
204 while (!file.eof()) {
205
206 file >> temp;
207
208 if (temp.length() == 0 || temp[0] == '#') {
209 line++;
210 continue;
211 }
212
213 StringTokenizer st(temp);
214 String command = st.nextToken('=').trim().toUpper();
215 String parameters = st.nextToken('=').trim();
216
217 if (command == "NICK" || command == "NICKNAME")
218 nickName = wantedNickName = parameters;
219 else if (command == "USERNAME")
220 userName = parameters;
221 else if (command == "IRCNAME" || command == "REALNAME")
222 ircName = parameters;
223 else if (command == "CMDCHAR" || command == "COMMAND")
224 commandChar = parameters[0];
225 else if (command == "USERLIST")
226 userListFileName = parameters;
227 else if (command == "SHITLIST")
228 shitListFileName = parameters;
229 else if (command == "CHANNEL") {
230 if (parameters.indexOf(':') == -1) {
231 std::cout << "Warning. The 'channel' syntax has changed."
232 << " Please see the README file for more information."
233 << " I will use compatibility mode, but you're really"
234 << " missing something.\n";
235 StringTokenizer st2(parameters);
236 String name = st2.nextToken().toLower();
237 String key = st2.nextToken();
238 wantedChannels[name] = new wantedChannel("", "", key);
239 } else {
240 StringTokenizer st2(parameters);
241 String name = st2.nextToken(':').toLower();
242 String mode = st2.nextToken(':');
243 String keep = st2.nextToken(':');
244 String key = st2.nextToken(':');
245 wantedChannels[name] = new wantedChannel(mode, keep, key);
246 }
247 }
248 else if (command == "LOGFILE")
249 {
250 if (parameters != logFileName)
251 {
252 if (parameters[0] == '/')
253 {
254 set_log_dir ("/");
255 set_log_file (parameters.subString (1));
256 }
257 else
258 set_log_file (parameters);
259 }
260 }
261 #ifdef USESCRIPTS
262 else if (command == "SCRIPTLOGFILE")
263 scriptLogFileName = parameters;
264 else if (command == "AUTOEXECFILE")
265 autoexecFileName = parameters;
266 #endif
267 else if (command == "INITFILE")
268 initFileName = parameters;
269 else if (command == "LOCALIP")
270 localIP = parameters;
271 else if (command == "SERVER") {
272 if (parameters.indexOf(' ') == -1)
273 serverList->addServer(new Server(parameters));
274 else {
275 StringTokenizer st2(parameters);
276 String name = st2.nextToken();
277 int port = std::atoi(st2.nextToken());
278 serverList->addServer(new Server(name,
279 port,
280 st2.nextToken()));
281 }
282 }
283 else {
284 logLine(String("Syntax error in file ") + configFileName +
285 ", line " + String((long)line));
286 file.close();
287 std::exit(1);
288 }
289
290 line++;
291 }
292
293 file.close();
294 }
295
296 void
297 Bot::run()
298 {
299 nextServer();
300
301 while (!stop) {
302 waitForInput(); // This is the main event loop
303 if (!serverConnection->queue->flush())
304 nextServer();
305 }
306 }
307
308 void
309 Bot::waitForInput()
310 {
311 #ifdef _HPUX_SOURCE
312 int rd;
313 #else
314 fd_set rd;
315 #endif
316 struct timeval timer;
317
318 int sock = serverConnection->getFileDescriptor();
319 int maxSocketNumber = sock;
320
321 #ifdef _HPUX_SOURCE
322 rd = sock;
323 #else
324 FD_ZERO(&rd);
325 FD_SET(sock, &rd);
326 #endif
327
328 for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
329 it != dccConnections.end(); ++it) {
330 int s = (*it)->getFileDescriptor();
331 #ifdef _HPUX_SOURCE
332 rd |= s;
333 #else
334 FD_SET(s, &rd);
335 #endif
336 if (s > maxSocketNumber)
337 maxSocketNumber = s;
338 }
339
340 timer.tv_sec = 1;
341 timer.tv_usec = 0;
342
343 switch (select(maxSocketNumber + 1, &rd, NULL, NULL, &timer)) {
344 case 0: /* timeout */
345 break;
346 case -1: /* error */
347 break;
348 default: /* normal */
349 #ifdef _HPUX_SOURCE
350 if (rd & sock)
351 #else
352 if (FD_ISSET(sock, &rd))
353 #endif
354 if (serverConnection->handleInput())
355 nextServer();
356
357 std::list<DCCConnection *>::iterator it = dccConnections.begin();
358 std::list<DCCConnection *>::iterator it2;
359
360 while (it != dccConnections.end()) {
361 it2 = it;
362 ++it;
363 #ifdef _HPUX_SOURCE
364 if (rd & (*it2)->getFileDescriptor()) {
365 #else
366 if (FD_ISSET((*it2)->getFileDescriptor(), &rd)) {
367 #endif
368 if ((*it2)->handleInput()) {
369 delete *it2;
370 dccConnections.erase(it2);
371 }
372 }
373 }
374 }
375
376 if (currentTime < std::time(NULL)) { // Actions that we do each second
377 currentTime = std::time(NULL);
378 for (std::map<String, unsigned int, std::less<String> >::iterator
379 it = ignoredUserhosts.begin();
380 it != ignoredUserhosts.end(); ++it)
381 if ((*it).second > 0)
382 (*it).second--;
383
384 String line;
385 while ((line = todoList->getNext()) != "") {
386 serverConnection->queue->sendChannelMode(line);
387 }
388 #ifdef USESCRIPTS
389 botInterp->RunTimers(currentTime);
390 #endif
391
392 #ifdef USESCRIPTS
393 tm *thisTime = localtime(&currentTime);
394 if (thisTime->tm_sec == 0) {
395 char s[6];
396 sprintf(s, "%2d:%2d", thisTime->tm_hour, thisTime->tm_min);
397 botInterp->RunHooks(Hook::TIMER, String(s),
398 gh_list(Utils::string2SCM(String(s)), SCM_UNDEFINED));
399 }
400 #endif
401
402 }
403
404 if (currentTime >= (time_t)(lastNickNameChange + Bot::NICK_CHANGE) &&
405 nickName != wantedNickName) {
406 lastNickNameChange = currentTime;
407 serverConnection->queue->sendNick(wantedNickName);
408 }
409
410 if (currentTime >= (std::time_t)(lastChannelJoin + Bot::CHANNEL_JOIN)) {
411 lastChannelJoin = currentTime;
412 for (std::map<String, wantedChannel *, std::less<String> >::iterator it =
413 wantedChannels.begin(); it != wantedChannels.end();
414 ++it)
415 if (channelList->getChannel((*it).first) == 0)
416 serverConnection->queue->sendJoin((*it).first, (*it).second->key);
417 }
418
419 std::list<DCCConnection *>::iterator it2;
420
421 for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
422 it != dccConnections.end(); ) {
423 it2 = it;
424 ++it;
425 if ((*it2)->autoRemove && currentTime >= (std::time_t)((*it2)->lastSpoken + Bot::DCC_DELAY)) {
426 delete *it2;
427 dccConnections.erase(it2);
428 }
429 }
430
431 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::PING_TIME) && !sentPing) {
432 serverConnection->queue->sendPing("Testing connection");
433 sentPing = true;
434 }
435
436 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::TIMEOUT)) {
437 sentPing = false;
438 nextServer();
439 }
440 }
441
442 // We can change server if we will not lose op on a channel
443 bool
444 Bot::canChangeServer()
445 {
446 String channel;
447 Channel *c;
448
449 for (std::map<String, Channel *, std::less<String> >::iterator it =
450 channelList->begin();
451 it != channelList->end(); ++it) {
452 channel = (*it).first;
453 c = channelList->getChannel(channel);
454 if (c->countOp == 1 &&
455 c->count > 1 && this->iAmOp(channel))
456 return false;
457 }
458 return true;
459 }
460
461 void
462 Bot::nextServer()
463 {
464 bool cont = false;
465
466 if (channelList)
467 channelList->clear();
468
469 if (serverConnection)
470 userList->removeFirst();
471
472 delete serverConnection;
473
474 do {
475 Server * s = serverList->nextServer();
476 if (!s) {
477 std::cout << "No server found. Exiting..." << std::endl;
478 std::exit(1);
479 }
480 serverConnection = new ServerConnection(this, s, localIP);
481 if (!serverConnection->connect()) {
482 cont = true;
483 // We sleep 10 seconds, to avoid connection flood
484 sleep(10);
485 delete serverConnection;
486 } else {
487 cont = false;
488 }
489 } while (cont);
490 }
491
492 void
493 Bot::reconnect()
494 {
495 if (channelList)
496 channelList->clear();
497
498 userList->removeFirst();
499
500 delete serverConnection;
501
502 serverConnection =
503 new ServerConnection(this, serverList->currentServer(), localIP);
504
505 serverConnection->connect();
506 }
507
508 void
509 Bot::connect(int serverNumber)
510 {
511 if (channelList)
512 channelList->clear();
513
514 userList->removeFirst();
515
516 delete serverConnection;
517
518 serverConnection =
519 new ServerConnection(this, serverList->get(serverNumber), localIP);
520
521 serverConnection->connect();
522 }
523
524 void
525 Bot::addDCC(Person * from, unsigned long address, int port)
526 {
527 DCCConnection * d = new DCCConnection(this, from->getAddress(),
528 address, port);
529
530 if (!d->connect())
531 {
532 logLine ("DCC Connection failed from " + from->getAddress ());
533 return;
534 }
535 logLine ("DCC Connection worked!");
536 dccConnections.push_back(d);
537 }
538
539 void
540 Bot::rehash()
541 {
542 for (std::map<String, Channel *, std::less<String> >::iterator it = channelList->begin();
543 it != channelList->end(); ++it)
544 serverConnection->queue->sendWho((*it).first);
545 }
546
547 String
548 Bot::getUserhost(String channel, String nick)
549 {
550 Channel *c;
551
552 if (channel == "")
553 c = 0;
554 else
555 c = channelList->getChannel(channel);
556
557 nick = nick.toLower();
558
559
560 if (c && c->hasNick(nick))
561 return c->getUser(nick)->userhost;
562
563 unsigned long num = sentUserhostID++;
564
565 serverConnection->queue->sendUserhost(nick);
566 userhostMap[num] = "+";
567
568 while (userhostMap[num] == "+") {
569 waitForInput();
570 serverConnection->queue->flush();
571 }
572
573 // We have got our answer
574 String res = userhostMap[num];
575 userhostMap.erase(num);
576
577 return res;
578 }
579
580 bool
581 Bot::iAmOp(String channel)
582 {
583 User * me = channelList->getChannel(channel)->getUser(nickName);
584 return (me->mode & User::OP_MODE);
585 }
586
587 void
588 Bot::init_user_functions ()
589 {
590 // User Functions
591 #define uf(f, l, b) new userFunction (f, l, b);
592 userFunctions["ACTION"] = uf (UserCommands::Action, User::USER, true);
593 userFunctions["ADDUSER"] = uf (UserCommands::AddUser, User::FRIEND, false);
594 userFunctions["ADDSERVER"] = uf (UserCommands::AddServer, User::FRIEND,
595 false);
596 userFunctions["ADDSHIT"] = uf (UserCommands::AddShit, User::FRIEND, false);
597 userFunctions["ALIAS"] = uf (UserCommands::Alias, User::MASTER, false);
598 userFunctions["BAN"] = uf (UserCommands::Ban, User::USER, true);
599 userFunctions["BANLIST"] = uf (UserCommands::BanList, User::USER, true);
600 userFunctions["CHANNELS"] =
601 uf (UserCommands::Channels, User::FRIEND, false);
602 userFunctions["CYCLE"] = uf (UserCommands::Cycle, User::FRIEND, true);
603 userFunctions["DCCLIST"] = uf (UserCommands::DCCList, User::FRIEND, false);
604 userFunctions["DEBAN"] = uf (UserCommands::Deban, User::USER, true);
605 userFunctions["DELSERVER"] = uf (UserCommands::DelServer, User::FRIEND,
606 false);
607 userFunctions["DELUSER"] = uf (UserCommands::DelUser, User::FRIEND, false);
608 userFunctions["DELSHIT"] = uf (UserCommands::DelShit, User::FRIEND, false);
609 userFunctions["DEOP"] = uf (UserCommands::Deop, User::TRUSTED_USER, true);
610 userFunctions["DIE"] = uf (UserCommands::Die, User::MASTER, false);
611 userFunctions["DO"] = uf (UserCommands::Do, User::MASTER, false);
612 #ifdef USESCRIPTS
613 userFunctions["EXECUTE"] = uf (UserCommands::Execute, User::MASTER, false);
614 #endif
615 userFunctions["HELP"] = uf (UserCommands::Help, User::NONE, false);
616 userFunctions["IDENT"] = uf (UserCommands::Ident, User::NONE, true);
617 userFunctions["INVITE"] = uf (UserCommands::Invite, User::USER, true);
618 userFunctions["JOIN"] = uf (UserCommands::Join, User::FRIEND, false);
619 userFunctions["KEEP"] = uf (UserCommands::Keep, User::FRIEND, true);
620 userFunctions["KICK"] = uf (UserCommands::Kick, User::USER, true);
621 userFunctions["KICKBAN"] = uf (UserCommands::KickBan, User::USER, true);
622 userFunctions["LOAD"] = uf (UserCommands::Load, User::FRIEND, false);
623 #ifdef USESCRIPTS
624 userFunctions["LOADSCRIPT"] = uf (UserCommands::LoadScript, User::MASTER,
625 false);
626 #endif
627 userFunctions["LOCK"] = uf (UserCommands::Lock, User::FRIEND, true);
628 userFunctions["MODE"] = uf (UserCommands::Mode, User::FRIEND, true);
629 userFunctions["MSG"] = uf (UserCommands::Msg, User::USER, false);
630 userFunctions["NAMES"] = uf (UserCommands::Names, User::USER, true);
631 userFunctions["NEXTSERVER"] = uf (UserCommands::NextServer, User::FRIEND,
632 false);
633 userFunctions["NICK"] = uf (UserCommands::Nick, User::FRIEND, false);
634 userFunctions["NSLOOKUP"] = uf (UserCommands::NsLookup, User::USER, false);
635 userFunctions["OP"] = uf (UserCommands::Op, User::TRUSTED_USER, true);
636 userFunctions["PART"] = uf (UserCommands::Part, User::FRIEND, true);
637 userFunctions["PASSWORD"] = uf (UserCommands::Password, User::USER, true);
638 userFunctions["RECONNECT"] =
639 uf (UserCommands::Reconnect, User::FRIEND, false);
640 userFunctions["RSPYMESSAGE"] =
641 uf (UserCommands::RSpyMessage, User::USER, false);
642 userFunctions["SAVE"] = uf (UserCommands::Save, User::FRIEND, false);
643 userFunctions["SAY"] = uf (UserCommands::Say, User::USER, true);
644 userFunctions["SERVER"] = uf (UserCommands::Server, User::FRIEND, false);
645 userFunctions["SERVERLIST"] =
646 uf (UserCommands::ServerList, User::FRIEND, false);
647 userFunctions["SETVERSION"] =
648 uf (UserCommands::SetVersion, User::MASTER, false);
649 userFunctions["SHITLIST"] =
650 uf (UserCommands::ShitList, User::FRIEND, false);
651 userFunctions["SPYLIST"] = uf (UserCommands::SpyList, User::USER, false);
652 userFunctions["SPYMESSAGE"] =
653 uf (UserCommands::SpyMessage, User::USER, false);
654 userFunctions["STATS"] = uf (UserCommands::Stats, User::FRIEND, true);
655 userFunctions["TBAN"] = uf (UserCommands::TBan, User::USER, true);
656 userFunctions["TKBAN"] = uf (UserCommands::TKBan, User::USER, true);
657 userFunctions["TOPIC"] = uf (UserCommands::Topic, User::USER, true);
658 userFunctions["UNLOCK"] = uf (UserCommands::Unlock, User::FRIEND, true);
659 userFunctions["USERLIST"] =
660 uf (UserCommands::UserList, User::FRIEND, false);
661 userFunctions["WHO"] = uf (UserCommands::Who, User::NONE, true);
662 userFunctions["WHOIS"] = uf (UserCommands::Whois, User::FRIEND, true);
663 #undef uf
664 }
665
666 namespace
667 {
668 void erase_userf (std::pair<std::string, class userFunction*> it)
669 {
670 delete it.second;
671 }
672 }
673
674 void
675 Bot::destroy_user_functions ()
676 {
677 std::for_each (userFunctions.begin (),
678 userFunctions.end (),
679 erase_userf);
680 userFunctions.erase (userFunctions.begin (),
681 userFunctions.end ());
682 }
683
684 void
685 Bot::set_log_file (String name)
686 {
687 logFileName = name;
688 logFile.close ();
689 logFile.clear ();
690 #if HAVE_IOSBASE
691 logFile.open(logs_dir + logFileName, std::ios_base::out |
692 std::ios_base::ate | std::ios_base::app);
693 #else
694 logFile.open(logs_dir + logFileName, ios::out | ios::ate
695 | ios::app);
696 #endif
697
698 logLine("Starting log.");
699 }
700
701 void
702 Bot::set_log_dir (String dir)
703 {
704 logs_dir = dir;
705 }