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