[project @ 2002-07-12 03:27:05 by unknown_lamer]
[clinton/bobotpp.git] / source / Bot.C
CommitLineData
cb21075d 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 <iomanip>
21#include <cstring>
22#include <cstdlib>
23#include <cstdio>
24#include <sys/time.h>
25#include <sys/types.h>
26#include <unistd.h>
27
28#include "Bot.H"
29#include "DCCConnection.H"
30#include "StringTokenizer.H"
31#include "ServerConnection.H"
32#include "Utils.H"
33
34#define DEFAULT_NICKNAME "Bobot"
35#define DEFAULT_USERNAME "bobot"
36#define DEFAULT_IRCNAME "I'm a bobot++!"
37#define DEFAULT_COMMANDCHAR '!'
38#define DEFAULT_USERLISTFILENAME "bot.users"
39#define DEFAULT_SHITLISTFILENAME "bot.shit"
40#define DEFAULT_HELPFILENAME "bot.help"
41#define DEFAULT_SCRIPTLOGFILENAME "script.log"
42#define DEFAULT_LOGFILENAME "bot.log"
439869bf 43#define DEFAULT_LOGDIR getenv ("HOME") + String("/.bobotpp/logs/")
cb21075d 44#define DEFAULT_INITFILENAME "bot.init"
45#ifdef USESCRIPTS
46#define DEFAULT_AUTOEXECFILENAME "bot.autoexec"
47#endif
48
49Bot::Bot(String filename, bool debug_on)
50 : nickName(DEFAULT_NICKNAME),
51 wantedNickName(DEFAULT_NICKNAME),
52 userName(DEFAULT_USERNAME),
53 ircName(DEFAULT_IRCNAME),
54 versionString(VERSION_STRING),
55 userHost(""),
56 localIP(""),
57 commandChar(DEFAULT_COMMANDCHAR),
58 configFileName(filename),
59 userListFileName(DEFAULT_USERLISTFILENAME),
60 shitListFileName(DEFAULT_SHITLISTFILENAME),
61 logFileName(DEFAULT_LOGFILENAME),
439869bf 62 logs_dir (DEFAULT_LOGDIR),
cb21075d 63 helpFileName(DEFAULT_HELPFILENAME),
64 initFileName(DEFAULT_INITFILENAME),
65#ifdef USESCRIPTS
66 scriptLogFileName(DEFAULT_SCRIPTLOGFILENAME),
67 autoexecFileName(DEFAULT_AUTOEXECFILENAME),
68#endif
69 connected(false),
70 debug(debug_on), stop(false), sentPing(false),
71 startTime(time(NULL)), currentTime(startTime),
72 lastNickNameChange(startTime), lastChannelJoin(startTime),
73 serverConnection(0), sentUserhostID(0), receivedUserhostID(0)
74{
75 extern userFunctionsStruct userFunctionsInit[];
76
77#ifdef HAVE_STL_CLEAR
78 wantedChannels.clear();
79 ignoredUserhosts.clear();
80 spyList.clear();
81 userhostMap.clear();
82#endif
83
84 for (int i = 0; userFunctionsInit[i].name[0] != '\0'; i++) {
85 userFunctions.push_back(new
86 userFunction(String(userFunctionsInit[i].name),
87 userFunctionsInit[i].function,
88 userFunctionsInit[i].minLevel,
89 userFunctionsInit[i].needsChannelName));
90 }
91
92#if HAVE_IOSBASE
439869bf 93 logFile.open(logs_dir + logFileName, std::ios_base::out |
94 std::ios_base::ate | std::ios_base::app);
cb21075d 95#else
439869bf 96 logFile.open(logs_dir + logFileName, ios::out | ios::ate
cb21075d 97 | ios::app);
98#endif
99 logLine("Starting log.");
100 channelList = new ChannelList();
101 serverList = new ServerList();
102 readConfig();
103 userList = new UserList(userListFileName);
104 shitList = new ShitList(shitListFileName);
105 todoList = new TodoList();
106
107 // Let's read the alias file
108 std::ifstream initFile(initFileName);
109
110 if (initFile) {
111 String temp, alias, command;
112 std::list<userFunction *>::iterator it;
113 bool found = false;
114 userFunction *u;
115 int line = 0;
116 while (initFile >> temp, temp.length() != 0) {
117 line++;
118 StringTokenizer st(temp);
119 temp = temp.trim();
120 if (temp[0]=='#') continue;
121 if (st.countTokens(' ') != 2) {
122 std::cerr << "Error when reading alias file (" << initFileName
123 << ") line " << line << "...\n";
124 continue;
125 }
126 alias = st.nextToken().toUpper();
127 command = st.nextToken().toUpper();
128
129 // Does the function already exist ?
130 found = false;
131 for (it = userFunctions.begin(); it != userFunctions.end(); ++it)
132 if (alias == (*it)->name) {
133 found = true;
134 break;
135 }
136 if (found) continue;
137
138 // Check that the command exists
139 found = false;
140 for (it = userFunctions.begin(); it != userFunctions.end(); ++it)
141 if (command == (*it)->name) {
142 found = true;
143 u = *it;
144 break;
145 }
146 if (!found) continue;
147
148 userFunctions.push_back (new
149 userFunction((char *)(const char *)alias,
150 u->function,
151 u->minLevel,
152 u->needsChannelName));
153 }
154 }
155
156 std::srand (std::time (0)); // srand for bot-random
157#ifdef USESCRIPTS
439869bf 158 botInterp = new BotInterp(this, logs_dir + scriptLogFileName);
cb21075d 159 botInterp->LoadScript(autoexecFileName);
160#endif
161}
162
163Bot::~Bot()
164{
165 // TODO: is it ok to delete iterators!?!
166
167 Person *p;
168 while (spyList.size() != 0) {
169 p = (*spyList.begin()).second;
170 spyList.erase(spyList.begin());
171 delete p;
172 }
173 DCCConnection *d;
174 while (dccConnections.size() != 0) {
175 d = *dccConnections.begin();
176 dccConnections.erase(dccConnections.begin());
177 delete d;
178 }
179 userFunction *u;
180 while (userFunctions.size() != 0) {
181 u = *userFunctions.begin();
182 userFunctions.erase(userFunctions.begin());
183 delete u;
184 }
185 wantedChannel *w;
186 while (wantedChannels.size() != 0) {
187 w = (*wantedChannels.begin()).second;
188 wantedChannels.erase(wantedChannels.begin());
189 delete w;
190 }
191 userList->save();
192 shitList->save();
193 delete channelList;
194 delete userList;
195 delete todoList;
196 delete serverList;
197 delete shitList;
198 delete serverConnection;
199 logLine("Stopping log.");
200 logFile.close();
201}
202
203void
204Bot::logLine(String line)
205{
206 tm *d;
207 std::time_t current_time = time(0);
208
209 d = localtime(&current_time);
210 logFile << "[" << std::setfill('0') << std::setw(2)
211 << d->tm_mday << "/" << std::setfill('0') << std::setw(2)
212 << d->tm_mon + 1 << "/"
213 << d->tm_year + 1900 << " - " << std::setfill('0') << std::setw(2)
214 << d->tm_hour << ":" << std::setfill('0') << std::setw(2)
215 << d->tm_min << ":" << std::setfill('0') << std::setw(2)
216 << d->tm_sec << "] "
217 << line
218 << std::endl;
219}
220
221void
222Bot::readConfig()
223{
224 std::ifstream file(configFileName);
225 String temp;
226 int line = 1;
227
228 if (!file) {
229 logLine(String("I cannot find the file ") + configFileName);
230 return;
231 }
232
233 while (!file.eof()) {
234
235 file >> temp;
236
237 if (temp.length() == 0 || temp[0] == '#') {
238 line++;
239 continue;
240 }
241
242 StringTokenizer st(temp);
243 String command = st.nextToken('=').trim().toUpper();
244 String parameters = st.nextToken('=').trim();
245
246 if (command == "NICK" || command == "NICKNAME")
247 nickName = wantedNickName = parameters;
248 else if (command == "USERNAME")
249 userName = parameters;
250 else if (command == "IRCNAME" || command == "REALNAME")
251 ircName = parameters;
252 else if (command == "CMDCHAR" || command == "COMMAND")
253 commandChar = parameters[0];
254 else if (command == "USERLIST")
255 userListFileName = parameters;
256 else if (command == "SHITLIST")
257 shitListFileName = parameters;
258 else if (command == "CHANNEL") {
259 if (parameters.indexOf(':') == -1) {
260 std::cout << "Warning. The 'channel' syntax has changed."
261 << " Please see the README file for more information."
262 << " I will use compatibility mode, but you're really"
263 << " missing something.\n";
264 StringTokenizer st2(parameters);
265 String name = st2.nextToken().toLower();
266 String key = st2.nextToken();
267 wantedChannels[name] = new wantedChannel("", "", key);
268 } else {
269 StringTokenizer st2(parameters);
270 String name = st2.nextToken(':').toLower();
271 String mode = st2.nextToken(':');
272 String keep = st2.nextToken(':');
273 String key = st2.nextToken(':');
274 wantedChannels[name] = new wantedChannel(mode, keep, key);
275 }
276 }
277 else if (command == "LOGFILE")
439869bf 278 logFileName = parameters;
cb21075d 279#ifdef USESCRIPTS
280 else if (command == "SCRIPTLOGFILE")
281 scriptLogFileName = parameters;
282 else if (command == "AUTOEXECFILE")
283 autoexecFileName = parameters;
284#endif
285 else if (command == "INITFILE")
286 initFileName = parameters;
287 else if (command == "LOCALIP")
288 localIP = parameters;
289 else if (command == "SERVER") {
290 if (parameters.indexOf(' ') == -1)
291 serverList->addServer(new Server(parameters));
292 else {
293 StringTokenizer st2(parameters);
294 String name = st2.nextToken();
295 int port = std::atoi(st2.nextToken());
296 serverList->addServer(new Server(name,
297 port,
298 st2.nextToken()));
299 }
300 }
301 else {
302 logLine(String("Syntax error in file ") + configFileName +
303 ", line " + String((long)line));
304 file.close();
305 std::exit(1);
306 }
307
308 line++;
309 }
310
311 file.close();
312}
313
314void
315Bot::run()
316{
317 nextServer();
318
319 while (!stop) {
320 waitForInput(); // This is the main event loop
321 if (!serverConnection->queue->flush())
322 nextServer();
323 }
324}
325
326void
327Bot::waitForInput()
328{
329#ifdef _HPUX_SOURCE
330 int rd;
331#else
332 fd_set rd;
333#endif
334 struct timeval timer;
335
336 int sock = serverConnection->getFileDescriptor();
337 int maxSocketNumber = sock;
338
339#ifdef _HPUX_SOURCE
340 rd = sock;
341#else
342 FD_ZERO(&rd);
343 FD_SET(sock, &rd);
344#endif
345
346 for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
347 it != dccConnections.end(); ++it) {
348 int s = (*it)->getFileDescriptor();
349#ifdef _HPUX_SOURCE
350 rd |= s;
351#else
352 FD_SET(s, &rd);
353#endif
354 if (s > maxSocketNumber)
355 maxSocketNumber = s;
356 }
357
358 timer.tv_sec = 1;
359 timer.tv_usec = 0;
360
361 switch (select(maxSocketNumber + 1, &rd, NULL, NULL, &timer)) {
362 case 0: /* timeout */
363 break;
364 case -1: /* error */
365 break;
366 default: /* normal */
367#ifdef _HPUX_SOURCE
368 if (rd & sock)
369#else
370 if (FD_ISSET(sock, &rd))
371#endif
372 if (serverConnection->handleInput())
373 nextServer();
374
375 std::list<DCCConnection *>::iterator it = dccConnections.begin();
376 std::list<DCCConnection *>::iterator it2;
377
378 while (it != dccConnections.end()) {
379 it2 = it;
380 ++it;
381#ifdef _HPUX_SOURCE
382 if (rd & (*it2)->getFileDescriptor()) {
383#else
384 if (FD_ISSET((*it2)->getFileDescriptor(), &rd)) {
385#endif
386 if ((*it2)->handleInput()) {
387 delete *it2;
388 dccConnections.erase(it2);
389 }
390 }
391 }
392 }
393
394 if (currentTime < std::time(NULL)) { // Actions that we do each second
395 currentTime = std::time(NULL);
396 for (std::map<String, unsigned int, std::less<String> >::iterator
397 it = ignoredUserhosts.begin();
398 it != ignoredUserhosts.end(); ++it)
399 if ((*it).second > 0)
400 (*it).second--;
401
402 String line;
403 while ((line = todoList->getNext()) != "") {
404 serverConnection->queue->sendChannelMode(line);
405 }
406#ifdef USESCRIPTS
407 botInterp->RunTimers(currentTime);
408#endif
409
410#ifdef USESCRIPTS
411 tm *thisTime = localtime(&currentTime);
412 if (thisTime->tm_sec == 0) {
413 char s[6];
414 sprintf(s, "%2d:%2d", thisTime->tm_hour, thisTime->tm_min);
415 botInterp->RunHooks(Hook::TIMER, String(s),
416 gh_list(Utils::string2SCM(String(s)), SCM_UNDEFINED));
417 }
418#endif
419
420 }
421
422 if (currentTime >= (time_t)(lastNickNameChange + Bot::NICK_CHANGE) &&
423 nickName != wantedNickName) {
424 lastNickNameChange = currentTime;
425 serverConnection->queue->sendNick(wantedNickName);
426 }
427
428 if (currentTime >= (std::time_t)(lastChannelJoin + Bot::CHANNEL_JOIN)) {
429 lastChannelJoin = currentTime;
430 for (std::map<String, wantedChannel *, std::less<String> >::iterator it =
431 wantedChannels.begin(); it != wantedChannels.end();
432 ++it)
433 if (channelList->getChannel((*it).first) == 0)
434 serverConnection->queue->sendJoin((*it).first, (*it).second->key);
435 }
436
437 std::list<DCCConnection *>::iterator it2;
438
439 for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
440 it != dccConnections.end(); ) {
441 it2 = it;
442 ++it;
443 if ((*it2)->autoRemove && currentTime >= (std::time_t)((*it2)->lastSpoken + Bot::DCC_DELAY)) {
444 delete *it2;
445 dccConnections.erase(it2);
446 }
447 }
448
449 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::PING_TIME) && !sentPing) {
450 serverConnection->queue->sendPing("Testing connection");
451 sentPing = true;
452 }
453
454 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::TIMEOUT)) {
455 sentPing = false;
456 nextServer();
457 }
458}
459
460// We can change server if we will not lose op on a channel
461bool
462Bot::canChangeServer()
463{
464 String channel;
465 Channel *c;
466
467 for (std::map<String, Channel *, std::less<String> >::iterator it =
468 channelList->begin();
469 it != channelList->end(); ++it) {
470 channel = (*it).first;
471 c = channelList->getChannel(channel);
472 if (c->countOp == 1 &&
473 c->count > 1 && this->iAmOp(channel))
474 return false;
475 }
476 return true;
477}
478
479void
480Bot::nextServer()
481{
482 bool cont = false;
483
484 if (channelList)
485 channelList->clear();
486
487 if (serverConnection)
488 userList->removeFirst();
489
490 delete serverConnection;
491
492 do {
493 Server * s = serverList->nextServer();
494 if (!s) {
495 std::cout << "No server found. Exiting..." << std::endl;
496 std::exit(1);
497 }
498 serverConnection = new ServerConnection(this, s, localIP);
499 if (!serverConnection->connect()) {
500 cont = true;
501 // We sleep 10 seconds, to avoid connection flood
502 sleep(10);
503 delete serverConnection;
504 } else {
505 cont = false;
506 }
507 } while (cont);
508}
509
510void
511Bot::reconnect()
512{
513 if (channelList)
514 channelList->clear();
515
516 userList->removeFirst();
517
518 delete serverConnection;
519
520 serverConnection =
521 new ServerConnection(this, serverList->currentServer(), localIP);
522
523 serverConnection->connect();
524}
525
526void
527Bot::connect(int serverNumber)
528{
529 if (channelList)
530 channelList->clear();
531
532 userList->removeFirst();
533
534 delete serverConnection;
535
536 serverConnection =
537 new ServerConnection(this, serverList->get(serverNumber), localIP);
538
539 serverConnection->connect();
540}
541
542void
543Bot::addDCC(Person * from, unsigned long address, int port)
544{
545 DCCConnection * d = new DCCConnection(this, from->getAddress(),
546 address, port);
547
548 if (!d->connect())
549 return;
550
551 dccConnections.push_back(d);
552}
553
554void
555Bot::rehash()
556{
557 for (std::map<String, Channel *, std::less<String> >::iterator it = channelList->begin();
558 it != channelList->end(); ++it)
559 serverConnection->queue->sendWho((*it).first);
560}
561
562String
563Bot::getUserhost(String channel, String nick)
564{
565 Channel *c;
566
567 if (channel == "")
568 c = 0;
569 else
570 c = channelList->getChannel(channel);
571
572 nick = nick.toLower();
573
574
575 if (c && c->hasNick(nick))
576 return c->getUser(nick)->userhost;
577
578 unsigned long num = sentUserhostID++;
579
580 serverConnection->queue->sendUserhost(nick);
581 userhostMap[num] = "+";
582
583 while (userhostMap[num] == "+") {
584 waitForInput();
585 serverConnection->queue->flush();
586 }
587
588 // We have got our answer
589 String res = userhostMap[num];
590 userhostMap.erase(num);
591
592 return res;
593}
594
595bool
596Bot::iAmOp(String channel)
597{
598 User * me = channelList->getChannel(channel)->getUser(nickName);
599 return (me->mode & User::OP_MODE);
600}