// Bot.C -*- C++ -*-
// Copyright (c) 1997, 1998 Etienne BERNARD
// Copyright (C) 2002 Clinton Ebadi
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#include <fstream>
#include <iomanip>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "Bot.H"
#include "DCCConnection.H"
#include "StringTokenizer.H"
#include "ServerConnection.H"
#include "Utils.H"
#define DEFAULT_NICKNAME "Bobot"
#define DEFAULT_USERNAME "bobot"
#define DEFAULT_IRCNAME "I'm a bobot++!"
#define DEFAULT_COMMANDCHAR '!'
#define DEFAULT_USERLISTFILENAME "bot.users"
#define DEFAULT_SHITLISTFILENAME "bot.shit"
#define DEFAULT_HELPFILENAME "bot.help"
#define DEFAULT_SCRIPTLOGFILENAME "script.log"
#define DEFAULT_LOGFILENAME "bot.log"
#define DEFAULT_INITFILENAME "bot.init"
#ifdef USESCRIPTS
#define DEFAULT_AUTOEXECFILENAME "bot.autoexec"
#endif
Bot::Bot(String filename, bool debug_on)
: nickName(DEFAULT_NICKNAME),
wantedNickName(DEFAULT_NICKNAME),
userName(DEFAULT_USERNAME),
ircName(DEFAULT_IRCNAME),
versionString(VERSION_STRING),
userHost(""),
localIP(""),
commandChar(DEFAULT_COMMANDCHAR),
configFileName(filename),
userListFileName(DEFAULT_USERLISTFILENAME),
shitListFileName(DEFAULT_SHITLISTFILENAME),
logFileName(DEFAULT_LOGFILENAME),
helpFileName(DEFAULT_HELPFILENAME),
initFileName(DEFAULT_INITFILENAME),
#ifdef USESCRIPTS
scriptLogFileName(DEFAULT_SCRIPTLOGFILENAME),
autoexecFileName(DEFAULT_AUTOEXECFILENAME),
#endif
connected(false),
debug(debug_on), stop(false), sentPing(false),
startTime(time(NULL)), currentTime(startTime),
lastNickNameChange(startTime), lastChannelJoin(startTime),
serverConnection(0), sentUserhostID(0), receivedUserhostID(0)
{
extern userFunctionsStruct userFunctionsInit[];
#ifdef HAVE_STL_CLEAR
wantedChannels.clear();
ignoredUserhosts.clear();
spyList.clear();
userhostMap.clear();
#endif
for (int i = 0; userFunctionsInit[i].name[0] != '\0'; i++) {
userFunctions.push_back(new
userFunction(String(userFunctionsInit[i].name),
userFunctionsInit[i].function,
userFunctionsInit[i].minLevel,
userFunctionsInit[i].needsChannelName));
}
#if HAVE_IOSBASE
logFile.open(logFileName, std::ios_base::out | std::ios_base::ate
| std::ios_base::app);
#else
logFile.open(logFileName, ios::out | ios::ate
| ios::app);
#endif
logLine("Starting log.");
channelList = new ChannelList();
serverList = new ServerList();
readConfig();
userList = new UserList(userListFileName);
shitList = new ShitList(shitListFileName);
todoList = new TodoList();
// Let's read the alias file
std::ifstream initFile(initFileName);
if (initFile) {
String temp, alias, command;
std::list<userFunction *>::iterator it;
bool found = false;
userFunction *u;
int line = 0;
while (initFile >> temp, temp.length() != 0) {
line++;
StringTokenizer st(temp);
temp = temp.trim();
if (temp[0]=='#') continue;
if (st.countTokens(' ') != 2) {
std::cerr << "Error when reading alias file (" << initFileName
<< ") line " << line << "...\n";
continue;
}
alias = st.nextToken().toUpper();
command = st.nextToken().toUpper();
// Does the function already exist ?
found = false;
for (it = userFunctions.begin(); it != userFunctions.end(); ++it)
if (alias == (*it)->name) {
found = true;
break;
}
if (found) continue;
// Check that the command exists
found = false;
for (it = userFunctions.begin(); it != userFunctions.end(); ++it)
if (command == (*it)->name) {
found = true;
u = *it;
break;
}
if (!found) continue;
userFunctions.push_back (new
userFunction((char *)(const char *)alias,
u->function,
u->minLevel,
u->needsChannelName));
}
}
std::srand (std::time (0)); // srand for bot-random
#ifdef USESCRIPTS
botInterp = new BotInterp(this, scriptLogFileName);
botInterp->LoadScript(autoexecFileName);
#endif
}
Bot::~Bot()
{
// TODO: is it ok to delete iterators!?!
Person *p;
while (spyList.size() != 0) {
p = (*spyList.begin()).second;
spyList.erase(spyList.begin());
delete p;
}
DCCConnection *d;
while (dccConnections.size() != 0) {
d = *dccConnections.begin();
dccConnections.erase(dccConnections.begin());
delete d;
}
userFunction *u;
while (userFunctions.size() != 0) {
u = *userFunctions.begin();
userFunctions.erase(userFunctions.begin());
delete u;
}
wantedChannel *w;
while (wantedChannels.size() != 0) {
w = (*wantedChannels.begin()).second;
wantedChannels.erase(wantedChannels.begin());
delete w;
}
userList->save();
shitList->save();
delete channelList;
delete userList;
delete todoList;
delete serverList;
delete shitList;
delete serverConnection;
logLine("Stopping log.");
logFile.close();
}
void
Bot::logLine(String line)
{
tm *d;
std::time_t current_time = time(0);
d = localtime(¤t_time);
logFile << "[" << std::setfill('0') << std::setw(2)
<< d->tm_mday << "/" << std::setfill('0') << std::setw(2)
<< d->tm_mon + 1 << "/"
<< d->tm_year + 1900 << " - " << std::setfill('0') << std::setw(2)
<< d->tm_hour << ":" << std::setfill('0') << std::setw(2)
<< d->tm_min << ":" << std::setfill('0') << std::setw(2)
<< d->tm_sec << "] "
<< line
<< std::endl;
}
void
Bot::readConfig()
{
std::ifstream file(configFileName);
String temp;
int line = 1;
if (!file) {
logLine(String("I cannot find the file ") + configFileName);
return;
}
while (!file.eof()) {
file >> temp;
if (temp.length() == 0 || temp[0] == '#') {
line++;
continue;
}
StringTokenizer st(temp);
String command = st.nextToken('=').trim().toUpper();
String parameters = st.nextToken('=').trim();
if (command == "NICK" || command == "NICKNAME")
nickName = wantedNickName = parameters;
else if (command == "USERNAME")
userName = parameters;
else if (command == "IRCNAME" || command == "REALNAME")
ircName = parameters;
else if (command == "CMDCHAR" || command == "COMMAND")
commandChar = parameters[0];
else if (command == "USERLIST")
userListFileName = parameters;
else if (command == "SHITLIST")
shitListFileName = parameters;
else if (command == "CHANNEL") {
if (parameters.indexOf(':') == -1) {
std::cout << "Warning. The 'channel' syntax has changed."
<< " Please see the README file for more information."
<< " I will use compatibility mode, but you're really"
<< " missing something.\n";
StringTokenizer st2(parameters);
String name = st2.nextToken().toLower();
String key = st2.nextToken();
wantedChannels[name] = new wantedChannel("", "", key);
} else {
StringTokenizer st2(parameters);
String name = st2.nextToken(':').toLower();
String mode = st2.nextToken(':');
String keep = st2.nextToken(':');
String key = st2.nextToken(':');
wantedChannels[name] = new wantedChannel(mode, keep, key);
}
}
else if (command == "LOGFILE")
logFileName = parameters;
#ifdef USESCRIPTS
else if (command == "SCRIPTLOGFILE")
scriptLogFileName = parameters;
else if (command == "AUTOEXECFILE")
autoexecFileName = parameters;
#endif
else if (command == "INITFILE")
initFileName = parameters;
else if (command == "LOCALIP")
localIP = parameters;
else if (command == "SERVER") {
if (parameters.indexOf(' ') == -1)
serverList->addServer(new Server(parameters));
else {
StringTokenizer st2(parameters);
String name = st2.nextToken();
int port = std::atoi(st2.nextToken());
serverList->addServer(new Server(name,
port,
st2.nextToken()));
}
}
else {
logLine(String("Syntax error in file ") + configFileName +
", line " + String((long)line));
file.close();
std::exit(1);
}
line++;
}
file.close();
}
void
Bot::run()
{
nextServer();
while (!stop) {
waitForInput(); // This is the main event loop
if (!serverConnection->queue->flush())
nextServer();
}
}
void
Bot::waitForInput()
{
#ifdef _HPUX_SOURCE
int rd;
#else
fd_set rd;
#endif
struct timeval timer;
int sock = serverConnection->getFileDescriptor();
int maxSocketNumber = sock;
#ifdef _HPUX_SOURCE
rd = sock;
#else
FD_ZERO(&rd);
FD_SET(sock, &rd);
#endif
for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
it != dccConnections.end(); ++it) {
int s = (*it)->getFileDescriptor();
#ifdef _HPUX_SOURCE
rd |= s;
#else
FD_SET(s, &rd);
#endif
if (s > maxSocketNumber)
maxSocketNumber = s;
}
timer.tv_sec = 1;
timer.tv_usec = 0;
switch (select(maxSocketNumber + 1, &rd, NULL, NULL, &timer)) {
case 0: /* timeout */
break;
case -1: /* error */
break;
default: /* normal */
#ifdef _HPUX_SOURCE
if (rd & sock)
#else
if (FD_ISSET(sock, &rd))
#endif
if (serverConnection->handleInput())
nextServer();
std::list<DCCConnection *>::iterator it = dccConnections.begin();
std::list<DCCConnection *>::iterator it2;
while (it != dccConnections.end()) {
it2 = it;
++it;
#ifdef _HPUX_SOURCE
if (rd & (*it2)->getFileDescriptor()) {
#else
if (FD_ISSET((*it2)->getFileDescriptor(), &rd)) {
#endif
if ((*it2)->handleInput()) {
delete *it2;
dccConnections.erase(it2);
}
}
}
}
if (currentTime < std::time(NULL)) { // Actions that we do each second
currentTime = std::time(NULL);
for (std::map<String, unsigned int, std::less<String> >::iterator
it = ignoredUserhosts.begin();
it != ignoredUserhosts.end(); ++it)
if ((*it).second > 0)
(*it).second--;
String line;
while ((line = todoList->getNext()) != "") {
serverConnection->queue->sendChannelMode(line);
}
#ifdef USESCRIPTS
botInterp->RunTimers(currentTime);
#endif
#ifdef USESCRIPTS
tm *thisTime = localtime(¤tTime);
if (thisTime->tm_sec == 0) {
char s[6];
sprintf(s, "%2d:%2d", thisTime->tm_hour, thisTime->tm_min);
botInterp->RunHooks(Hook::TIMER, String(s),
gh_list(Utils::string2SCM(String(s)), SCM_UNDEFINED));
}
#endif
}
if (currentTime >= (time_t)(lastNickNameChange + Bot::NICK_CHANGE) &&
nickName != wantedNickName) {
lastNickNameChange = currentTime;
serverConnection->queue->sendNick(wantedNickName);
}
if (currentTime >= (std::time_t)(lastChannelJoin + Bot::CHANNEL_JOIN)) {
lastChannelJoin = currentTime;
for (std::map<String, wantedChannel *, std::less<String> >::iterator it =
wantedChannels.begin(); it != wantedChannels.end();
++it)
if (channelList->getChannel((*it).first) == 0)
serverConnection->queue->sendJoin((*it).first, (*it).second->key);
}
std::list<DCCConnection *>::iterator it2;
for (std::list<DCCConnection *>::iterator it = dccConnections.begin();
it != dccConnections.end(); ) {
it2 = it;
++it;
if ((*it2)->autoRemove && currentTime >= (std::time_t)((*it2)->lastSpoken + Bot::DCC_DELAY)) {
delete *it2;
dccConnections.erase(it2);
}
}
if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::PING_TIME) && !sentPing) {
serverConnection->queue->sendPing("Testing connection");
sentPing = true;
}
if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken + Bot::TIMEOUT)) {
sentPing = false;
nextServer();
}
}
// We can change server if we will not lose op on a channel
bool
Bot::canChangeServer()
{
String channel;
Channel *c;
for (std::map<String, Channel *, std::less<String> >::iterator it =
channelList->begin();
it != channelList->end(); ++it) {
channel = (*it).first;
c = channelList->getChannel(channel);
if (c->countOp == 1 &&
c->count > 1 && this->iAmOp(channel))
return false;
}
return true;
}
void
Bot::nextServer()
{
bool cont = false;
if (channelList)
channelList->clear();
if (serverConnection)
userList->removeFirst();
delete serverConnection;
do {
Server * s = serverList->nextServer();
if (!s) {
std::cout << "No server found. Exiting..." << std::endl;
std::exit(1);
}
serverConnection = new ServerConnection(this, s, localIP);
if (!serverConnection->connect()) {
cont = true;
// We sleep 10 seconds, to avoid connection flood
sleep(10);
delete serverConnection;
} else {
cont = false;
}
} while (cont);
}
void
Bot::reconnect()
{
if (channelList)
channelList->clear();
userList->removeFirst();
delete serverConnection;
serverConnection =
new ServerConnection(this, serverList->currentServer(), localIP);
serverConnection->connect();
}
void
Bot::connect(int serverNumber)
{
if (channelList)
channelList->clear();
userList->removeFirst();
delete serverConnection;
serverConnection =
new ServerConnection(this, serverList->get(serverNumber), localIP);
serverConnection->connect();
}
void
Bot::addDCC(Person * from, unsigned long address, int port)
{
DCCConnection * d = new DCCConnection(this, from->getAddress(),
address, port);
if (!d->connect())
return;
dccConnections.push_back(d);
}
void
Bot::rehash()
{
for (std::map<String, Channel *, std::less<String> >::iterator it = channelList->begin();
it != channelList->end(); ++it)
serverConnection->queue->sendWho((*it).first);
}
String
Bot::getUserhost(String channel, String nick)
{
Channel *c;
if (channel == "")
c = 0;
else
c = channelList->getChannel(channel);
nick = nick.toLower();
if (c && c->hasNick(nick))
return c->getUser(nick)->userhost;
unsigned long num = sentUserhostID++;
serverConnection->queue->sendUserhost(nick);
userhostMap[num] = "+";
while (userhostMap[num] == "+") {
waitForInput();
serverConnection->queue->flush();
}
// We have got our answer
String res = userhostMap[num];
userhostMap.erase(num);
return res;
}
bool
Bot::iAmOp(String channel)
{
User * me = channelList->getChannel(channel)->getUser(nickName);
return (me->mode & User::OP_MODE);
}
syntax highlighted by Code2HTML, v. 0.9.1