rendered paste body#include "ircbot.h"#include <fcntl.h>// Staff channelsstatic const ci::string StaffChannel = "#chanstat.staff";static const ci::string BotChannel = "#chanstat.bots";#define INVITETHROTTLE 4#define COMMANDTHROTTLE 6std::string LogDir;std::string NSLine;std::string NSNLine;std::string NSPW;std::string AppendLink;unsigned MaxChannels;std::string AudMode;unsigned Raw001Pos;bool PersonalBot = false;const ci::string WhiteList[] = { BotChannel, "#chanstat", ""};const ci::string DontLog[] = { BotChannel, StaffChannel, ""};static int lasthour;/* Time this module was loaded */static time_t loadtime;/* if this is true, a netsplit recently happend * if there is a netsplit the bot will not part * channels for being empty */static bool netsplit = false;/* the time the netsplit was first detected */static time_t netsplit_time = 0;/* this is the nick the irc server (or, bnc rather) tells * the bot he is from the 001 raw upon connecting * if this is not the same as the config nick for some reason * (the correct nick was already online) then this can cause * issues when the bot rejoins channels, because it thinks that * the chanstat who joined is not it, and will part because there * is "another bot" in the channel * * basically what well do if this happens is check to make sure * the "other bot" joining isnt this value, and possibly send a /nick * to the ircd (but then it wouldnt be identified.. and the nick is * probably in use anyway) */std::string ircnick;/* If this is set to the channel name the bot joins, show greet. * I suspose two very rapid joins (getting the second join request * before the first JOIN was confirmed) would cause the first * channel not to show a message.. I don't think this will be * an issue though. */static ci::string greetchannel;/* This is the nick that invited chanstat, passed to the non-hub bots by !dojoin * the same thing applies here as stated above^ * This is used to inform the user of any reason it cant join +bkl etc * Once this is done, the value is cleared (so if they ping and reconnect * we dont end up spamming the crap out of some guy). This is also cleared after * the greet is showed (as this will probably be used in that greet message anyway */static std::string invitedby;class CSChannel;struct Ignore;struct Blacklist;struct Throttle;struct HostAllow;static void SaveDatabase();CSChannel *getcs(const ci::string &channel, bool requestnames = true);static void SwapLogs();/* List of channels */static std::map<ci::string, CSChannel *> Channels;/* botname, channel count */static std::map<ci::string, int> BotsChanCount;/* Ignored hosts */// XXX this should really be in a std::list ..static std::map<ci::string, Ignore *> IgnoredHosts;/* Blacklisted channels */static std::map<ci::string, Blacklist *> Blacklists;/* Map of hosts and duration times for temporary ignores */static std::map<ci::string, Throttle *> Throttles;/* List of people allowed to use this bot */static std::vector<HostAllow *> HostAllows;class CSChannel{ friend CSChannel *getcs(const ci::string &, bool); friend void SwapLogs(); private: std::ofstream log; bool informed_of_error; public: ci::string name; /* users in the channel */ std::vector<ci::string> users; CSChannel(const ci::string &ciname) { informed_of_error = false; std::string tname = assign(ciname); for (unsigned i = 0; i < tname.size(); ++i) tname[i] = std::tolower(tname[i]); this->name = assign(tname); std::string channelname = LogDir + std::string(this->name.c_str()) + ".log"; log.open(channelname.c_str(), std::ios_base::app); if (!log.is_open()) { IRCdProto->Privmsg(StaffChannel, "!!!ERROR!!! Unable to open logfile %s", channelname.c_str()); } } ~CSChannel() { if (log.is_open()) log.close(); users.clear(); std::map<ci::string, CSChannel *>::iterator it = Channels.find(name); if (it != Channels.end()) { Channels.erase(it); } } void AddUser(std::string &nick) { AddUser(nick.c_str()); } void AddUser(const ci::string &nick) { ci::string tnick = nick; AddUser(tnick); } void AddUser(ci::string &nick) { std::vector<ci::string>::iterator it = std::find(this->users.begin(), this->users.end(), nick); // XXX Can this happen anymore? Need to check this ... if (it != this->users.end()) return; this->users.push_back(nick); } void DelUser(const std::string &nick) { const ci::string cinick = assign(nick); DelUser(cinick); } void DelUser(const ci::string &nick) { std::vector<ci::string>::iterator it = std::find(this->users.begin(), this->users.end(), nick); if (it != this->users.end()) { DelUser(it); } } void DelUser(const std::vector<ci::string>::iterator &it) { if (it != this->users.end()) { this->users.erase(it); } time_t t = time(NULL); // If we're in a netsplit check to see if netsplit mode should be over if (netsplit) { if (netsplit_time + 300 <= t) { //netsplit is over netsplit = false; if (Config->Nick == "ChanStat") { IRCdProto->Privmsg(StaffChannel, "(\00303NETSPLIT\003) Netsplit mode is over"); } } } if (!this->users.empty() && this->users.size() <= 1) { // Don't part channels during netsplits if (netsplit) { IRCdProto->Privmsg(StaffChannel, "(\00307WARNING\003) %s may be empty, but didn't part because of recent netsplit", this->name.c_str()); return; } if (loadtime < t - 60) { IRCdProto->Part(this->name, "ChanStat has parted %s because it did not meet the minimum user requirement.", this->name.c_str()); IRCdProto->Privmsg(StaffChannel, "(\00307PARTS\003) Parted %s because it did not meet the user requirement", this->name.c_str()); } else { IRCdProto->Privmsg(StaffChannel, "(\00307WARNING\003) %s may be empty, but didn't part because of a recent reload", this->name.c_str()); } } } void Write(const char *message, ...) { if (!message) { IRCdProto->Privmsg(StaffChannel, "!!!WARNING!!! Was told to write a nonexistant message to logfile for %s", this->name.c_str()); return; } char messagebuf[512]; memset(&messagebuf, 0, sizeof(messagebuf)); va_list vi; va_start(vi, message); vsnprintf(messagebuf, sizeof(messagebuf) - 1, message, vi); va_end(vi); std::string sbuf = messagebuf; Write(sbuf); } void Write(const std::string &message) { if (message.empty()) { IRCdProto->Privmsg(StaffChannel, "!!!WARNING!!! Was told to write a nonexistant message to logfile for %s", this->name.c_str()); return; } if (!this->log.is_open()) { if (!informed_of_error) { informed_of_error = true; IRCdProto->Privmsg(StaffChannel, "!!!ERROR!!! FD for %s IS NOT OPEN; CAN'T LOG. THIS IS NOT A GOOD THING.", this->name.c_str()); } return; } this->log << message; this->log << std::endl; }};struct Blacklist{ std::string nick; ci::string reason; time_t date; time_t expires;};struct Ignore{ std::string nick; ci::string reason; time_t date; time_t expires;};struct Throttle{ time_t expirytime; int count; time_t lastused; bool reached;};struct HostAllow{ std::string host; std::string creator;};CSChannel *findcs(const ci::string &channel);/** Get the total channel count for this bot, this checks our whiteliste channels */static unsigned GetTotalChannelCount(){ unsigned Extra = 0; for (unsigned i = 0; i < sizeof(WhiteList); ++i) { if (WhiteList[i].empty()) break; /* Personal bots use real channel count */ if (!findcs(WhiteList[i]) && !PersonalBot) ++Extra; } return Channels.size() + Extra;}/* buf is a nick!ident@host format, put nick in nick and ident@host in host */void GetNickHost(std::string buf, std::string &nick, std::string &host){ std::stringstream ss(buf); if (buf.find('!') == std::string::npos) { nick = buf; host.clear(); } else { std::getline(ss, nick, '!'); std::getline(ss, host, ' '); }}static void AddCounter(const std::string &uhost, int limit){ Throttle *t; time_t ct = time(NULL); std::map<ci::string, Throttle *>::iterator it = Throttles.find(assign(uhost)); if (it != Throttles.end()) { t = it->second; t->count++; if (t->count >= limit) { IRCdProto->Privmsg(BotChannel, "(4THROTTLE) Added ignore for %s for 10 minutes due to spam", uhost.c_str()); t->reached = true; t->expirytime = ct + 600; } t->lastused = time(NULL); } else { t = new Throttle; t->count = 1; t->lastused = ct; t->reached = false; t->expirytime = ct + 600; Throttles.insert(std::make_pair(assign(uhost), t)); }}static int match_wild(const char *pattern, const char *str, int docase = 0){ char c; const char *s; if (!str || !*str || !pattern || !*pattern) return 0; /* This WILL eventually terminate: either by *pattern == 0, or by a * trailing '*'. */ for (;;) { switch (c = *pattern++) { case 0: if (!*str) return 1; return 0; case '?': if (!*str) return 0; str++; break; case '%': { if ((*str < 48) || (*str > 57)) return 0; str++; break; } case '\\': { if (!(*str && *pattern)) return 0; if (*str != *pattern) { return 0; } pattern++; str++; break; } case '*': if (!*pattern) return 1; /* trailing '*' matches everything else */ s = str; while (*s) { if ((docase ? (*s == *pattern) : (tolower(*s) == tolower(*pattern))) && match_wild(pattern, s, docase)) return 1; s++; } break; default: if (docase ? (*str++ != c) : (tolower(*str++) != tolower(c))) return 0; break; } /* switch */ }}bool IsWhiteList(const ci::string &chan){ if (chan.empty()) return false; for (unsigned i = 0; i < sizeof(WhiteList); ++i) { if (WhiteList[i].empty()) break; if (chan == WhiteList[i]) return true; } return false;}/** Check if a channel should be logged * @return true to NOT be logged, false to be logged */bool IsDontLog(const ci::string &chan){ if (chan.empty()) return false; for (unsigned i = 0; i < sizeof(DontLog); ++i) { if (DontLog[i].empty()) break; if (chan == DontLog[i]) return true; } /* ONLY the hub will log whitelisted channels */ if (IsWhiteList(chan) && Config->Nick != "ChanStat") return true; return false;}bool IsIgnored(const std::string &host){ std::map<ci::string, Ignore *>::iterator it, it2; std::map<ci::string, Throttle *>::iterator tit, tit2; time_t ct = time(NULL); for (tit = Throttles.begin(); tit != Throttles.end(); tit = tit2) { tit2 = tit; ++tit2; Throttle *t = tit->second; if ((ct - t->lastused) >= 600 || (ct >= t->expirytime)) { if (t->reached && Config->Nick == "ChanStat") IRCdProto->Privmsg(BotChannel, "(\00303THROTTLE\003) Expiring throttle for %s", tit->first.c_str()); delete t; Throttles.erase(tit); } else if (t->reached && match_wild(const_cast<char *>(tit->first.c_str()), const_cast<char *>(host.c_str()))) return true; } for (it = IgnoredHosts.begin(); it != IgnoredHosts.end(); it = it2) { it2 = it; ++it2; Ignore *i = it->second; if (i->expires > 0 && i->expires <= ct) { if (Config->Nick == "ChanStat") IRCdProto->Privmsg(StaffChannel, "(\00303IGNORE\003) Expiring ignore for %s", it->first.c_str()); delete i; IgnoredHosts.erase(it); } else if (match_wild(const_cast<char *>(it->first.c_str()), const_cast<char *>(host.c_str()))) return true; } return false;}bool IsBlacklisted(const std::string &chan){ if (chan.empty()) return false; std::map<ci::string, Blacklist *>::iterator it = Blacklists.find(assign(chan)); time_t t = time(NULL); if (it != Blacklists.end()) { Blacklist *b = it->second; if (b->expires > 0 && b->expires <= t) { if (Config->Nick == "ChanStat") IRCdProto->Privmsg(StaffChannel, "(\00303BLACKLIST\003) Expiring blacklist for channel %s", chan.c_str()); delete it->second; Blacklists.erase(it); return false; } return true; } return false;}bool CanUsePersonalCommands(const std::string &host){ if (!PersonalBot) { IRCdProto->Privmsg(StaffChannel, "!!!WARNING!!! CanUsePersonalCommands() was called on a nonpersonal bot?"); return false; } for (std::vector<HostAllow *>::iterator it = HostAllows.begin(); it != HostAllows.end(); ++it) { HostAllow *h = *it; if (match_wild(const_cast<char *>(h->host.c_str()), const_cast<char *>(host.c_str()))) { IRCdProto->Privmsg(BotChannel, "(\00312CUSTOMBOT\003) Granted access to %s by matching %s", host.c_str(), h->host.c_str()); return true; } } IRCdProto->Privmsg(BotChannel, "(\00312CUSTOMBOT\003) Denied access to %s", host.c_str()); return false;}/* write to all files */void writeToAll(const std::string &buf){ std::map<ci::string, CSChannel *>::iterator it; CSChannel *c; for (it = Channels.begin(); it != Channels.end(); ++it) { c = it->second; c->Write(buf); }}void HumanReadableTime(time_t t, char *buf){ time_t ctime = time(NULL) - t; int weeks, days, hours, mins; weeks = days = hours = mins = 0; while (ctime > 604800) { weeks++; ctime -= 604800; } while (ctime > 86400) { days++; ctime -= 86400; } while (ctime > 3600) { hours++; ctime -= 3600; } while (ctime > 60) { mins++; ctime -= 60; } snprintf(buf, 100, "%i weeks %i days %i hours %i mins", weeks, days, hours, mins);}int file_exists(const char *filename){ struct stat buf; int i = stat(filename, &buf); if (i == 0) return 1; return 0;}/* Log file swapping system * #chan.log.13 -> #chan.log.14 * #chan.log.12 -> #chan.log.13 * .. * #chan.log2 -> #chan.log.3 * #chan.log -> #chan.log.2 * Delete #chan.log.14 * New file #chan.log is logged to. */static void SwapLogs(){ int i; char buf[10]; std::map<ci::string, CSChannel *>::iterator it; ci::string channels; std::string fname, fname2; CSChannel *c; for (it = Channels.begin(); it != Channels.end(); ++it) { channels = it->first; c = it->second; fname = LogDir + std::string(c->name.c_str()) + ".log"; if (!file_exists(fname.c_str())) continue; if (c->log.is_open()) c->log.close(); for (i = 13; i > 0; --i) { memset(&buf, 0, sizeof(buf)); sprintf(buf, "%d", i); fname = LogDir + std::string(c->name.c_str()) + ".log." + std::string(buf); if (file_exists(fname.c_str())) { ++i; memset(&buf, 0, sizeof(buf)); sprintf(buf, "%d", i); fname2 = LogDir + std::string(c->name.c_str()) + ".log." + std::string(buf); --i; rename(fname.c_str(), fname2.c_str()); } } //NULL->1 fname = LogDir + std::string(c->name.c_str()) + ".log.1"; fname2 = LogDir + std::string(c->name.c_str()) + ".log"; rename(fname2.c_str(), fname.c_str()); //destroy 14 fname = LogDir + std::string(c->name.c_str()) + ".log.14"; if (file_exists(fname.c_str())) unlink(fname.c_str()); fname = LogDir + std::string(c->name.c_str()) + ".log"; c->log.open(fname.c_str(), std::ios_base::app); }}void getTimestamp(char *hourbuf, char *minbuf){ time_t t; tm curtime; int hour, min; char msgbuf[100]; const char *dayofweek, *month; time(&t); curtime = *(localtime(&t)); hour = curtime.tm_hour; min = curtime.tm_min; if (min <= 9) snprintf(minbuf, 4, "0%i", min); else snprintf(minbuf, 4, "%i", min); if (hour <= 9) snprintf(hourbuf, 4, "0%i", hour); else snprintf(hourbuf, 4, "%i", hour); if (lasthour > hour) { switch (curtime.tm_wday) { case 0: dayofweek = "Sun"; break; case 1: dayofweek = "Mon"; break; case 2: dayofweek = "Tue"; break; case 3: dayofweek = "Wed"; break; case 4: dayofweek = "Thu"; break; case 5: dayofweek = "Fri"; break; case 6: dayofweek = "Sat"; break; default: dayofweek = "XXX"; break; } switch (curtime.tm_mon) { case 0: month = "Jan"; break; case 1: month = "Feb"; break; case 2: month = "Mar"; break; case 3: month = "Apr"; break; case 4: month = "May"; break; case 5: month = "Jun"; break; case 6: month = "Jul"; break; case 7: month = "Aug"; break; case 8: month = "Sep"; break; case 9: month = "Oct"; break; case 10: month = "Nov"; break; case 11: month = "Dec"; break; default: month = "XXX"; break; } SwapLogs(); if (curtime.tm_mday > 9) snprintf(msgbuf, sizeof(msgbuf), "[00:00] --- %s %s %i %i", dayofweek, month, curtime.tm_mday, 1900 + curtime.tm_year); else snprintf(msgbuf, sizeof(msgbuf), "[00:00] --- %s %s %i %i", dayofweek, month, curtime.tm_mday, 1900 + curtime.tm_year); writeToAll(msgbuf); } lasthour = hour;}/* Remove a user from every channel they are in (quit) */void removeUser(const std::string &nick, const std::string &uhost){ removeUser(nick.c_str(), uhost.c_str());}void removeUser(const ci::string &nick, const ci::string &uhost){ char hour[4], min[4]; getTimestamp(hour, min); for (std::map<ci::string, CSChannel *>::iterator it = Channels.begin(); it != Channels.end(); ++it) { ci::string chname = it->first; CSChannel *c = getcs(chname); if (!c) { IRCdProto->Privmsg(StaffChannel, "WARNING: removeUser() getcs returned NULL for %s saved in the Channels map?", chname.c_str()); continue; } std::vector<ci::string>::iterator user = std::find(c->users.begin(), c->users.end(), nick); if (user != c->users.end()) { c->Write("[%s:%s] %s (%s) left irc: Quit:", hour, min, nick.c_str(), uhost.c_str()); c->DelUser(user); } }}/* Rename a user in every channel they are in (nick) */void renameUser(const ci::string &oldnick, const ci::string &newnick){ char hour[4], min[4]; getTimestamp(hour, min); for (std::map<ci::string, CSChannel *>::iterator it = Channels.begin(); it != Channels.end(); ++it) { /* We use getcs to reinitialize logfile, if needed */ ci::string chname = it->first; CSChannel *c = getcs(chname); if (!c) { IRCdProto->Privmsg(StaffChannel, "WARNING: renameUser() getcs() returned NULL for %s saved in the Channels map?", chname.c_str()); continue; } /* Get old user.. */ std::vector<ci::string>::iterator user = std::find(c->users.begin(), c->users.end(), oldnick); if (user != c->users.end()) { c->Write("[%s:%s] Nick change: %s -> %s", hour, min, oldnick.c_str(), newnick.c_str()); c->users.erase(user); c->AddUser(newnick); } }}CSChannel *findcs(const ci::string &channel){ std::map<ci::string, CSChannel *>::iterator it = Channels.find(channel); if (it != Channels.end()) { return it->second; } return NULL;}CSChannel *findcs(const std::string &channel){ ci::string chan = assign(channel); return findcs(chan);}CSChannel *getcs(const ci::string &channel, bool requestnames){ CSChannel *cs; bool empty_chanlist = Channels.empty(); if (channel.empty()) return NULL; else if (channel[0] != '#') return NULL; /* Don't log this channel */ else if (IsDontLog(channel)) return NULL; else if (IRCSocket && empty_chanlist) //whois to fill up internal channel list, if internal channel list is EMPTY IRCSocket->Write("WHOIS %s", Config->Nick.c_str()); std::map<ci::string, CSChannel *>::iterator it = Channels.find(channel); if (it != Channels.end()) { cs = it->second; if (!cs->log.is_open()) { IRCdProto->Privmsg(StaffChannel, "WARNING: getcs() was about to return a channel with a closed log file: %s", cs->name.c_str()); return NULL; } } else { cs = new CSChannel(channel); if (!cs->log.is_open()) { delete cs; IRCdProto->Privmsg(StaffChannel, "FATAL: error, unable to open log file for %s", channel.c_str()); IRCSocket->Write("QUIT"); End = true; return NULL; } Channels.insert(std::make_pair(channel, cs)); } /* Request names is always set unless we're joining a channel, dont request names here as ircd will send us */ /* It's also not set when we're requesting getcs in do_names - Adam */ /* Additionally, don't request names if Channels is empty - This is becuase the WHOIS sent above gets recieved and calls getcs() * which will come back to this code to request NAMES * * We check for empty() at the beginning of this function as we don't know if this function added a channel * Using .size() == 1 is potentially unsafe if we were in fact in one channel, we would be requesting /names every time * something happend in an empty (or, thought to be) channel. - Adam */ if (IRCSocket && cs->users.empty() && requestnames && !empty_chanlist) IRCSocket->Write("NAMES %s", channel.c_str()); return cs;}class ChanStatNotice : public Command{ public: ChanStatNotice() : Command("!CHANSTATS", 0, 0) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { ci::string chan = target; chan.erase(chan.begin()); if (IsWhiteList(target) && Config->Nick != "ChanStat") return; if (IsIgnored(nick + "!" + uhost)) { IRCdProto->Privmsg(BotChannel, "(04IGNORES) Ignored !chanstats from %s (%s) on %s", nick.c_str(), uhost.c_str(), target.c_str()); return; } AddCounter(nick + "!" + uhost, COMMANDTHROTTLE); IRCdProto->Privmsg(BotChannel, "(7COMMANDS) %s (%s) used %s in %s", nick.c_str(), uhost.c_str(), this->GetName().c_str(), target.c_str()); IRCdProto->Notice(nick, "(12ChanStats): Channel Statistics for 12%s can be found by going to 12http://chanstat.info/search.php?channel=%s%s", target.c_str(), chan.c_str(), AppendLink.c_str()); }};class ChanStatMessage : public Command{ public: ChanStatMessage() : Command("@CHANSTATS", 0, 0) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { ci::string chan = target; chan.erase(chan.begin()); if (IsWhiteList(target) && Config->Nick != "ChanStat") return; if (IsIgnored(nick + "!" + uhost)) { IRCdProto->Privmsg(BotChannel, "(04IGNORES) Ignored @chanstats from %s (%s) on %s", nick.c_str(), uhost.c_str(), target.c_str()); return; } AddCounter(nick + "!" + uhost, COMMANDTHROTTLE); IRCdProto->Privmsg(BotChannel, "(7COMMANDS) %s (%s) used %s in %s", nick.c_str(), uhost.c_str(), this->GetName().c_str(), target.c_str()); IRCdProto->Privmsg(target.c_str(), "(12ChanStats): Channel Statistics for 12%s can be found by going to 12http://chanstat.info/search.php?channel=%s%s", target.c_str(), chan.c_str(), AppendLink.c_str()); }};class ChanStatMapNotice : public Command{ public: ChanStatMapNotice() : Command("!MAP", 0, 0) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (IsWhiteList(target) && Config->Nick != "ChanStat") return; if (IsIgnored(nick + "!" + uhost)) { IRCdProto->Privmsg(BotChannel, "(04IGNORES) Ignored !map from %s (%s) on %s", nick.c_str(), uhost.c_str(), target.c_str()); return; } AddCounter(nick + "!" + uhost, COMMANDTHROTTLE); ci::string chan = target; chan.erase(chan.begin()); IRCdProto->Notice(nick, "(12Map): Relationship map for 12%s can be found by going to 12http://chanstat.info/map.php?channel=%s%s", target.c_str(), chan.c_str(), AppendLink.c_str()); IRCdProto->Privmsg(BotChannel, "(7COMMANDS) %s (%s) used %s in %s", nick.c_str(), uhost.c_str(), this->GetName().c_str(), target.c_str()); }};class ChanStatMapMessage : public Command{ public: ChanStatMapMessage() : Command("@MAP", 0, 0) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (IsWhiteList(target) && Config->Nick != "ChanStat") return; if (IsIgnored(nick + "!" + uhost)) { IRCdProto->Privmsg(BotChannel, "(04IGNORES) Ignored @map from %s (%s) on %s", nick.c_str(), uhost.c_str(), target.c_str()); return; } AddCounter(nick + "!" + uhost, COMMANDTHROTTLE); ci::string chan = target; chan.erase(chan.begin()); IRCdProto->Privmsg(assign(target), "(12Map): Relationship map for 12%s can be found by going to 12http://chanstat.info/map.php?channel=%s%s", target.c_str(), chan.c_str(), AppendLink.c_str()); IRCdProto->Privmsg(BotChannel, "(7COMMANDS) %s (%s) used %s in %s", nick.c_str(), uhost.c_str(), this->GetName().c_str(), target.c_str()); }};class ChanStatChannel : public Command{ public: ChanStatChannel() : Command("!CHANNELS", 0, 1) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel && !PersonalBot) { if (params.empty() || params[0] == Config->Nick) IRCdProto->Privmsg(target.c_str(), "!mychannels %i", GetTotalChannelCount()); } }};class ChanStatDoJoin : public Command{ public: ChanStatDoJoin() : Command("!DOJOIN", 3, 3) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (Config->Nick == params[0] && target == BotChannel) { greetchannel = params[1]; invitedby = assign(params[2]); IRCdProto->Join(params[1]); } }};class ChanStatMyChannels : public Command{ public: ChanStatMyChannels() : Command("!MYCHANNELS", 1, 1) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { ci::string cinick = assign(nick); if (target == BotChannel && Config->Nick == "ChanStat") { std::map<ci::string, int>::iterator it = BotsChanCount.find(cinick); if (it != BotsChanCount.end()) { BotsChanCount.erase(it); } BotsChanCount.insert(std::make_pair(cinick, atoi(params[0].c_str()))); } }};class ChanStatPart : public Command{ public: ChanStatPart() : Command("!PART", 1, 1) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel) { if (Config->Nick == "ChanStat") { IRCdProto->Privmsg(StaffChannel, "(04PARTS) Parted channel %s", params[0].c_str()); } IRCdProto->Part(params[0], "This bot has been parted by a ChanStat staff member, please join #ChanStat for more information"); } else if (PersonalBot && CanUsePersonalCommands(nick + "!" + uhost)) { IRCdProto->Privmsg(BotChannel, "(\00312CUSTOMBOT\003) %s!%s used PART in %s to part me from %s", nick.c_str(), uhost.c_str(), target.c_str(), params[0].c_str()); IRCdProto->Part(params[0], "This bot has been parted by %s; contact this person for more information", nick.c_str()); } }};class ChanStatBlacklist : public Command{ public: ChanStatBlacklist() : Command("!BLACKLIST", 1, 4) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { Blacklist *b; std::map<ci::string, Blacklist *>::iterator it, it2; int i; char datebuf[100], expirebuf[100]; time_t t = time(NULL); if (target == BotChannel) { if (Config->Nick == "ChanStat") { if (params.size() < 2 && params[0] != "LIST") return; if (params[0] == "ADD") { b = new Blacklist; b->nick = nick; b->date = t; b->expires = t + 604800; // Expires in one week by default if (params.size() > 2) { if (params[2][0] == '+') { ci::string expiry = params[2]; expiry.erase(expiry.begin()); if (!expiry.empty() && expiry.size() > 1) { unsigned multiplier = 1; if (expiry[0] == 'd') { multiplier = 86400; } else if (expiry[0] == 'h') { multiplier = 3600; } else if (expiry[0] == 'm') { multiplier = 60; } if (multiplier != 1) expiry.erase(expiry.begin()); long num = atol(expiry.c_str()); if (num > 0) { b->expires = (t + (num * multiplier)); } } else if (!expiry.empty()) { if (expiry[0] == '0') { b->expires = 0; } } } else b->reason = params[2]; if (params.size() > 3) { if (!b->reason.empty()) b->reason += " "; b->reason += params[3]; } } else b->reason = "Not specified"; Blacklists.insert(std::make_pair(params[1], b)); IRCdProto->Part(params[1], "Your channel has been blacklisted. Join #ChanStat for more information."); IRCdProto->Privmsg(BotChannel, "!blacklistpart %s", params[1].c_str()); if (b->expires) HumanReadableTime(t - (b->expires - t), expirebuf); else snprintf(expirebuf, sizeof(expirebuf), "never"); IRCdProto->Privmsg(StaffChannel, "(\00304BLACKLIST\003) Blacklisted channel %s, expires in %s", params[1].c_str(), expirebuf); } else if (params[0] == "DEL") { IRCdProto->Privmsg(StaffChannel, "(04BLACKLIST) Unblacklisted channel %s", params[1].c_str()); it = Blacklists.find(params[1]); if (it != Blacklists.end()) { Blacklists.erase(it); } } else if (!stricmp(params[0].c_str(), "LIST")) { IRCdProto->Privmsg(BotChannel, "Blacklisted channels:"); for (it = Blacklists.begin(), i = 1; it != Blacklists.end(); it = it2, i++) { it2 = it; ++it2; b = it->second; HumanReadableTime(b->date, datebuf); if (b->expires > 0 && b->expires <= t) { if (Config->Nick == "ChanStat") IRCdProto->Privmsg(StaffChannel, "(\00303BLACKLIST\003) Expiring blacklist for channel %s", it->first.c_str()); delete it->second; Blacklists.erase(it); --i; continue; } if (b->expires) HumanReadableTime(t - (b->expires - t), expirebuf); else snprintf(expirebuf, sizeof(expirebuf), "never"); IRCdProto->Privmsg(BotChannel, "(\00307BLACKLIST\003) %i. [\002Channel\002]: %s [\002By\002]: %s [\002Set\002]: %s ago [\002Expires\002]: %s [\002Reason\002]: %s", i, it->first.c_str(), b->nick.c_str(), datebuf, expirebuf, b->reason.c_str()); } } } } }};class ChanStatIgnore : public Command{ public: ChanStatIgnore() : Command("!IGNORE", 1, 4) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { Ignore *i; int j; std::map<ci::string, Ignore *>::iterator it, it2; char datebuf[100], expirebuf[100]; time_t t = time(NULL); if (target == BotChannel) { if (params.size() < 2 && params[0] != "LIST") return; if (params[0] == "ADD") { i = new Ignore; i->nick = nick; i->date = t; i->expires = t + 604800; if (params.size() > 2) { if (params[2][0] == '+') { ci::string expiry = params[2]; expiry.erase(expiry.begin()); if (!expiry.empty() && expiry.size() > 1) { unsigned multiplier = 1; if (expiry[0] == 'd') { multiplier = 86400; } else if (expiry[0] == 'h') { multiplier = 3600; } else if (expiry[0] == 'm') { multiplier = 60; } if (multiplier != 1) expiry.erase(expiry.begin()); long num = atol(expiry.c_str()); if (num > 0) { i->expires = (t + (num * multiplier)); } } else if (!expiry.empty()) { if (expiry[0] == '0') { i->expires = 0; } } } else i->reason = params[2]; if (params.size() > 3) { if (!i->reason.empty()) i->reason += " "; i->reason += params[3]; } } else { i->reason = "Not specified"; } IgnoredHosts.insert(std::make_pair(params[1], i)); if (i->expires) HumanReadableTime(t - (i->expires - t), expirebuf); else snprintf(expirebuf, sizeof(expirebuf), "never"); if (Config->Nick == "ChanStat") IRCdProto->Privmsg(StaffChannel, "(\00304IGNORE\003) Ignored host %s, expires in %s", params[1].c_str(), expirebuf); } else if (params[0] == "DEL") { if (Config->Nick == "ChanStat") IRCdProto->Privmsg(StaffChannel, "(04IGNORE) Unignored host %s", params[1].c_str()); it = IgnoredHosts.find(params[1]); if (it != IgnoredHosts.end()) { IgnoredHosts.erase(it); } } else if (params[0] == "LIST") { if (Config->Nick == "ChanStat") { IRCdProto->Privmsg(BotChannel, "Ignored hosts:"); for (it = IgnoredHosts.begin(), j = 1; it != IgnoredHosts.end(); it = it2, j++) { it2 = it; ++it2; i = it->second; if (i->expires > 0 && i->expires <= t) { if (Config->Nick == "ChanStat") IRCdProto->Privmsg(StaffChannel, "(\00303IGNORE\003) Expiring ignore for %s", it->first.c_str()); delete it->second; IgnoredHosts.erase(it); --i; continue; } HumanReadableTime(i->date, datebuf); if (i->expires) HumanReadableTime(t - (i->expires - t), expirebuf); else snprintf(expirebuf, sizeof(expirebuf), "never"); IRCdProto->Privmsg(BotChannel, "(\00307IGNORES\003) %i. [\002Host\002]: %s [\002By\002]: %s [\002Set\002]: %s ago [\002Expires\002]: %s [\002Reason\002]: %s", j, it->first.c_str(), i->nick.c_str(), datebuf, expirebuf, i->reason.c_str()); } } } } }};class ChanStatBotinfo : public Command{ public: ChanStatBotinfo() : Command("!BOTINFO", 1, 1) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel && !params.empty()) { if (params[0] == Config->Nick || params[0] == "ALL") { ci::string channels; for (std::map<ci::string, CSChannel *>::iterator it = Channels.begin(); it != Channels.end(); ++it) { if (channels.empty()) channels = it->first; else channels += " " + it->first; } IRCdProto->Privmsg(target.c_str(), "(7BOTINFO) Currently on %d channels: %s", GetTotalChannelCount(), channels.c_str()); } } }};class ChanStatChancount : public Command{ public: ChanStatChancount() : Command("!CHANCOUNT", 0, 0) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel) { IRCdProto->Privmsg(target.c_str(), "(7CHANCOUNT) Currently on %d channels", GetTotalChannelCount()); } }};class ChanStatBLPart : public Command{ public: ChanStatBLPart() : Command("!BLACKLISTPART", 1, 1) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel) { IRCdProto->Part(assign(params[0]), "Your channel has been blacklisted. Join #ChanStat for more information"); } }};class ChanStatAMSG : public Command{ public: ChanStatAMSG() : Command("!AMSG", 1, 1) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel) { if (nick != "Adam" && nick != "Matt-") return; if (Config->Nick == "ChanStat") IRCdProto->Privmsg("#chanstat", "(7 GLOBAL ) %s", params[0].c_str()); for (std::map<ci::string, CSChannel *>::iterator it = Channels.begin(); it != Channels.end(); ++it) { ci::string chname = it->first; if (IsWhiteList(chname) && Config->Nick == "ChanStat") // These shouldn't be tracked anyway continue; IRCdProto->Privmsg(chname.c_str(), "(7 GLOBAL ) %s", params[0].c_str()); } } }};class ChanStatNick : public Command{ public: ChanStatNick() : Command("!NICK", 0, 0) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel) { IRCSocket->Write("NICK %s", Config->Nick.c_str()); } }};class ChanStatID : public Command{ public: ChanStatID() : Command("!ID", 0, 0) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel) { IRCdProto->Privmsg("NickServ", "IDENTIFY %s", NSPW.c_str()); } }};class ChanStatQuit : public Command{ public: ChanStatQuit() : Command("!QUIT", 1, 2) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { if (target == BotChannel) { if ((nick == "Adam" || nick == "Matt-") && params[0] == "ALL") { SaveDatabase(); IRCSocket->Write("QUIT :%s", params.size() > 1 ? params[1].c_str() : ""); End = true; } else if (params[0] == Config->Nick) { SaveDatabase(); if (PersonalBot) IRCSocket->Write("QUIT :This bot has been removed by a staff member, for more information join #ChanStat"); else IRCSocket->Write("QUIT :%s", params.size() > 1 ? params[1].c_str() : ""); End = true; } } }};class ChanStatHostManage : public Command{ public: ChanStatHostManage() : Command("!HOSTS", 1, 3) { } void Execute(const std::string &nick, const std::string &uhost, const ci::string &target, const std::vector<ci::string> ¶ms) { std::vector<ci::string> realparams = params; if (target == BotChannel) { realparams.erase(realparams.begin()); if (params[0] != Config->Nick && params[0] != "ALL") return; if (realparams.empty()) return; } else if (!CanUsePersonalCommands(nick + "!" + uhost)) { IRCdProto->Notice(nick, "Access denied."); return; } if (realparams[0] == "ADD" && realparams.size() > 1) { if (HostAllows.size() >= 10 && target != BotChannel) { IRCdProto->Notice(nick, "Sorry, your allowed hosts list is full."); IRCdProto->Privmsg(BotChannel, "(\00312CUSTOMBOT\003) Denied adding host %s from %s in %s because the allowed host list is full.", realparams[1].c_str(), nick.c_str(), target.c_str()); } else { HostAllow *h = new HostAllow; h->host = assign(realparams[1]); h->creator = nick; HostAllows.push_back(h); IRCdProto->Notice(nick, "Host %s added to the allowed hosts list.", h->host.c_str()); IRCdProto->Privmsg(BotChannel, "(\00312CUSTOMBOT\003) %s added host %s to allowed hosts list in %s", nick.c_str(), realparams[1].c_str(), target.c_str()); } } else if (realparams[0] == "DEL" && realparams.size() > 1) { int value = atoi(realparams[1].c_str()); if (value > 0) { unsigned valueu = value; if (valueu <= HostAllows.size()) { IRCdProto->Notice(nick, "Host %s removed from the allowed hosts list.", HostAllows[valueu - 1]->host.c_str()); IRCdProto->Privmsg(BotChannel, "(\00312CUSTOMBOT\003) %s removed host %s from the allowed hosts list in %s", nick.c_str(), HostAllows[valueu - 1]->host.c_str(), target.c_str()); delete HostAllows[valueu - 1]; HostAllows.erase(HostAllows.begin() + valueu - 1); } else { IRCdProto->Notice(nick, "Unabled to find host %i on allowed hosts list.", value); } } else { bool deleted = false; for (std::vector<HostAllow *>::iterator it = HostAllows.begin(); it != HostAllows.end(); ++it) { if (realparams[1] == (*it)->host) { IRCdProto->Notice(nick, "Host %s removed from the allowed hosts list.", (*it)->host.c_str()); IRCdProto->Privmsg(BotChannel, "(\00312CUSTOMBOT\003) %s removed host %s from the allowed hosts list in %s", nick.c_str(), (*it)->host.c_str(), target.c_str()); delete *it; HostAllows.erase(it); deleted = true; break; } } if (!deleted) { IRCdProto->Notice(nick, "Unabled to find %s on allowed hosts list.", realparams[1].c_str()); } } } else if (realparams[0] == "LIST") { if (HostAllows.empty()) { IRCdProto->Notice(nick, "Allowed hosts list is empty."); } else { unsigned num = 0; for (std::vector<HostAllow *>::iterator it = HostAllows.begin(); it != HostAllows.end(); ++it) { HostAllow *h = *it; IRCdProto->Notice(nick, "%d. Host: %s Added by: %s", ++num, h->host.c_str(), h->creator.c_str()); } } } else { IRCdProto->Notice(nick, "Usage: !HOSTS (ADD|DEL|LIST) [nick@host/number]"); } }};EventReturn do_privmsg(const std::vector<std::string> ¶ms){ if (params.size() < 3) return EVENT_CONTINUE; ci::string chname = assign(params[2]); char mins[4], hours[4]; getTimestamp(hours, mins); std::string nick, uhost; GetNickHost(params[0], nick, uhost); if (nick.empty()) return EVENT_CONTINUE; if (params.size() > 2 && !params[2].empty() && params[2][0] != '#') { return EVENT_CONTINUE; } CSChannel *cs = getcs(chname); if (!cs) return EVENT_CONTINUE; if (params.size() > 3 && !params[3].empty()) { if ((params[3][0] == '\1') && (!strncmp(params[3].c_str(), "\1ACTION ", 8))) { std::string temp = params[3]; temp.erase(temp.length() - 1); char *msgbuf = const_cast<char *>(temp.c_str()); //XXX ew? msgbuf += 8; cs->Write("[%s:%s] Action: %s %s", hours, mins, nick.c_str(), msgbuf); } else cs->Write("[%s:%s] <%s> %s", hours, mins, nick.c_str(), params[3].c_str()); } return EVENT_CONTINUE;}EventReturn do_mode(const std::vector<std::string> ¶ms){ ci::string chname = assign(params[2]); char hour[4], min[4]; getTimestamp(hour, min); CSChannel *c = getcs(chname); if (!c) return EVENT_CONTINUE; std::string modes; for (unsigned j = 3; j < params.size(); ++j) { if (modes.empty()) modes = params[j]; else modes += " " + params[j]; } c->Write("[%s:%s] %s: mode change '%s' by %s", hour, min, params[2].c_str(), modes.c_str(), params[0].c_str()); return EVENT_CONTINUE;}EventReturn do_join(const std::vector<std::string> ¶ms){ std::string nick; std::string host; ci::string chname = assign(params[2]), cinick; GetNickHost(params[0], nick, host); /* A chanstat joined the bot channel, request its channels but ONLY not during a netsplit, otherwise the hub ends up floooding out * the other bots */ if (!netsplit && Config->Nick == "ChanStat" && chname == BotChannel && nick.find("ChanStat") != std::string::npos) { IRCdProto->Privmsg(BotChannel, "!channels %s", nick.c_str()); } bool mejoining = (nick == Config->Nick ? false : true); CSChannel *cs = getcs(chname, mejoining); if (!cs) return EVENT_CONTINUE; cinick = assign(nick); if (!nick.empty() && !host.empty()) { /* If another chanstat joins the channel, we part * they must have "ChanStat@" in their ident, and have * ChanStat.info chanstat.info or 2001:470:1f0f:438: in their host */ if (nick != Config->Nick && host.find("ChanStat@") != std::string::npos && (host.find("ChanStat.info") != std::string::npos || host.find("chanstat.info") != std::string::npos || host.find("2001:470:1f0f:438:") != std::string::npos)) { if (!ircnick.empty() && ircnick != Config->Nick) { /* This means that the bot has disconnected sometime and reconnected back on * a different nick. It would have parted here, resulting in the bot losing * all of the channels it was in (fake detection of another ChanStat) * A /NICK command was sent earlier in do_mynick, and may have not * been successful or we may have not got the responce yet.. Regardless * we should not part here */ IRCdProto->Privmsg(StaffChannel, "(04WARNING) %s joined %s, I should be %s but I have detected myself to be %s. Not parting.", nick.c_str(), params[2].c_str(), Config->Nick.c_str(), ircnick.c_str()); } /* Don't part the channel if this channel is whitelisted. */ else if (!IsWhiteList(chname)) { IRCdProto->Part(params[2], "Another ChanStat was detected in your channel"); IRCdProto->Privmsg(StaffChannel, "(3PARTS) Parted %s because another bot was found in the channel (%s)", params[2].c_str(), nick.c_str()); } return EVENT_CONTINUE; } if (Config->Nick == nick) { /* The bot has joined the channel * Send a MODE #channel to the ircd so we can * check for retards doing stuff such as unreals +u mode * which would break the bot */ IRCSocket->Write("MODE %s", chname.c_str()); } } char hour[4], min[4]; getTimestamp(hour, min); cs->Write("[%s:%s] %s (%s) joined %s.", hour, min, nick.c_str(), host.c_str(), params[2].c_str()); cs->AddUser(cinick); if (!greetchannel.empty() && greetchannel == params[2]) { // Matt is a retard who doesn't want the nick in the greet msg IRCdProto->Privmsg(params[2], "12Thank you for inviting ChanStat, an IRC channel statistics bot. For immediate assistance go to14 http://chanstat.info12. For news, updates, and extra features visit our forums at14 http://forum.chanstat.info12. If you have any further questions, join14 #ChanStat12."); greetchannel.clear(); IRCdProto->Privmsg(StaffChannel, "(3JOINS) Just joined %s", params[2].c_str()); if (!PersonalBot) IRCdProto->Privmsg(BotChannel, "!mychannels %i", GetTotalChannelCount()); } invitedby.clear(); return EVENT_CONTINUE;}EventReturn do_part(const std::vector<std::string> ¶ms){ ci::string chname = assign(params[2]); std::string nick, host; GetNickHost(params[0], nick, host); if (chname == BotChannel && Config->Nick == "ChanStat" && nick.find("ChanStat") != std::string::npos) { std::map<ci::string, int>::iterator it = BotsChanCount.find(assign(nick)); if (it != BotsChanCount.end()) { BotsChanCount.erase(it); } } CSChannel *cs = getcs(chname); if (!cs) return EVENT_CONTINUE; if (nick.empty()) return EVENT_CONTINUE; char hour[4], min[4]; getTimestamp(hour, min); cs->Write("[%s:%s] %s (%s) left %s.", hour, min, nick.c_str(), host.c_str(), params[2].c_str()); if (nick == Config->Nick) { // We do -1 here because this channel isnt deleted yet if (!PersonalBot) IRCdProto->Privmsg(BotChannel, "!mychannels %d", GetTotalChannelCount() - 1); delete cs; } else { cs->DelUser(nick); } return EVENT_CONTINUE;}EventReturn do_nick(const std::vector<std::string> ¶ms){ std::string nick, uhost; ci::string chname = assign(params[2]); GetNickHost(params[0], nick, uhost); if (nick.empty()) return EVENT_CONTINUE; /* This is the bot being renamed, from the nick it detected itself to be at * the 001 raw to the nick in the Config */ if (!ircnick.empty() && nick == ircnick && params[2] == Config->Nick) { // Clear the user that the 001 said to make everything hopefully work again ircnick.clear(); IRCdProto->Privmsg(StaffChannel, "(7NICK) I have detected changing back to %s (I should be %s)", params[2].c_str(), Config->Nick.c_str()); IRCSocket->Write("WHOIS %s", Config->Nick.c_str()); } renameUser(assign(nick), assign(params[2])); return EVENT_CONTINUE;}EventReturn do_quit(const std::vector<std::string> ¶ms){ std::string nick, uhost; GetNickHost(params[0], nick, uhost); // This is a netsplit if (match_wild(NSLine.c_str(), params[2].c_str()) && params[2].find(NSNLine) == std::string::npos) { // Reset the netsplit time netsplit_time = time(NULL); // Isn't already a netsplit if (!netsplit) { // netsplit mode on netsplit = true; if (Config->Nick == "ChanStat") { IRCdProto->Privmsg(StaffChannel, "(04NETSPLIT) ChanStat has detected a netsplit, now in netsplit mode"); } } } /* if this was a bot that quit, remove it */ /* However, dont if there is a netsplit */ if (!netsplit && Config->Nick == "ChanStat" && nick.find("ChanStat") != std::string::npos) { std::map<ci::string, int>::iterator it = BotsChanCount.find(assign(nick)); if (it != BotsChanCount.end()) { BotsChanCount.erase(it); } } removeUser(assign(nick), assign(uhost)); return EVENT_CONTINUE;}EventReturn do_names(const std::vector<std::string> ¶ms){ if (params.size() <= 5) return EVENT_CONTINUE; std::stringstream ss(params[5]); ci::string cname = assign(params[4]); /* Don't rerequest names here, as we already have it */ CSChannel *c = getcs(cname, false); if (!c) return EVENT_CONTINUE; std::string buf; while (std::getline(ss, buf, ' ')) { while (!buf.empty() && (buf[0] == '~' || buf[0] == '&' || buf[0] == '@' || buf[0] == '%' || buf[0] == '+')) buf.erase(buf.begin()); if (!buf.empty()) c->AddUser(ci::string(buf.c_str())); } /* Im the only one in the channel? */ if (!c->users.empty() && c->users.size() == 1 && c->users[0] == Config->Nick) { IRCdProto->Part(assign(cname)); IRCdProto->Privmsg(StaffChannel, "(\00307PARTS\003) Just parted %s because it was empty", cname.c_str()); } return EVENT_CONTINUE;}EventReturn do_kick(const std::vector<std::string> ¶ms){ if (params[3] == Config->Nick && params.size() > 4 && !params[4].empty() && params[4] == "Fake direction") { /* This is to fix a SwiftIRC bug * There is a problem with their IRCd when a server splits * and reconnects, it will sometimes mass kick everyone for * "Fake direction". Unfortuantly, the administration of * SwiftIRC are incapable or unwilling to put forth the effort * to fix this, so we must rejoin and halt. * EDIT: This is supposidly fixed now.. will leave it here just incase for now. */ /* Pretend were in netsplit mode (or restart the netsplit mode counter) * to prevent parting for no users */ netsplit = true; netsplit_time = time(NULL); IRCdProto->Join(params[2]); return EVENT_CONTINUE; } char hour[4], min[4]; getTimestamp(hour, min); ci::string chname = assign(params[2]); /* Remove this bot from the internal list */ if (Config->Nick == "ChanStat" && chname == BotChannel && params[3].find("ChanStat") != std::string::npos) { std::map<ci::string, int>::iterator it = BotsChanCount.find(assign(params[3])); if (it != BotsChanCount.end()) { BotsChanCount.erase(it); } } CSChannel *cs = getcs(chname); if (!cs) return EVENT_CONTINUE; std::string nick, uhost; GetNickHost(params[0], nick, uhost); cs->Write("[%s:%s] %s kicked from %s by %s: %s", hour, min, params[3].c_str(), params[2].c_str(), nick.c_str(), params[4].c_str()); if (Config->Nick == params[3]) { IRCdProto->Privmsg(StaffChannel, "(03KICK) Kicked from %s by %s (%s)", params[2].c_str(), nick.c_str(), uhost.c_str()); // We do -1 here because this channel isn't deleted yet if (!PersonalBot) IRCdProto->Privmsg(BotChannel, "!mychannels %d", GetTotalChannelCount() - 1); delete cs; } else { cs->DelUser(params[3]); } return EVENT_CONTINUE;}EventReturn do_invite(const std::vector<std::string> ¶ms){ std::string nick, host; ci::string chname = assign(params[3]); char hour[4], min[4]; GetNickHost(params[0], nick, host); getTimestamp(hour, min); char *channel = const_cast<char *>(params[3].c_str()); if (IsWhiteList(chname)) { IRCdProto->Privmsg(StaffChannel, "(03JOINS) Joined whitelisted channel %s because I was invited by %s (%s)", params[3].c_str(), nick.c_str(), params[0].c_str()); IRCdProto->Join(params[3]); return EVENT_CONTINUE; } else if (IsIgnored(params[0])) { IRCdProto->Privmsg(BotChannel, "(04IGNORES) Ignored invite to %s from ignored host %s", params[3].c_str(), params[0].c_str()); return EVENT_CONTINUE; } else if (Config->Nick != "ChanStat" && !PersonalBot) { IRCdProto->Privmsg(BotChannel, "(04INVITES) Denied invite to %s from %s because I'm not the hub", params[3].c_str(), params[0].c_str()); IRCdProto->Notice(nick, "You must invite the hub bot, /invite ChanStat %s", params[3].c_str()); return EVENT_CONTINUE; } else if (IsBlacklisted(params[3])) { IRCdProto->Privmsg(BotChannel, "(04BLACKLIST) Denied invite from %s because %s is blacklisted", params[0].c_str(), params[3].c_str()); IRCdProto->Notice(nick, "Your channel is blacklisted. Join #ChanStat for more information"); return EVENT_CONTINUE; } else if (strchr(channel, '/')) { IRCdProto->Notice(nick, "Sorry, channel names may not have a '/' in them."); IRCdProto->Privmsg(StaffChannel, "(04INVITES) Denied invite from %s because of invalid channel name %s", params[0].c_str(), channel); return EVENT_CONTINUE; } else if (PersonalBot) { if (!CanUsePersonalCommands(params[0])) { IRCdProto->Notice(nick, "This is a private bot. To invite ChanStat to your channel use /invite ChanStat %s", params[3].c_str()); IRCdProto->Privmsg(BotChannel, "(\00304INVITES\003) Denied invite from %s to %s because they aren't on the allowed hosts list.", params[0].c_str(), params[3].c_str()); return EVENT_STOP; } } IRCdProto->Privmsg(StaffChannel, "(03INVITES) %s invited me to %s", params[0].c_str(), params[3].c_str()); AddCounter(params[0], INVITETHROTTLE); unsigned numchan, minnumchan = MaxChannels; ci::string botnick, minbotnick = "ChanStat-01"; if (!PersonalBot) { IRCdProto->Notice(nick, "Please wait while I search for an open bot..."); for (std::map<ci::string, int>::iterator it = BotsChanCount.begin(); it != BotsChanCount.end(); ++it) { botnick = it->first; numchan = it->second; if (numchan < minnumchan) { minbotnick = botnick; minnumchan = numchan; } } } if (GetTotalChannelCount() < minnumchan) { IRCdProto->Join(params[3]); greetchannel = assign(params[3]); invitedby = nick; return EVENT_CONTINUE; } else if (PersonalBot) { IRCdProto->Privmsg(StaffChannel, "(\00304INVITES\003) Denied invite from %s to %s because I am full", params[0].c_str(), params[3].c_str()); IRCdProto->Notice(nick, "This bot is currently full, for assistance join #ChanStat."); return EVENT_CONTINUE; } if (minnumchan < MaxChannels) IRCdProto->Privmsg(BotChannel, "!dojoin %s %s %s", minbotnick.c_str(), params[3].c_str(), nick.c_str()); else { if (BotsChanCount.empty()) { IRCdProto->Privmsg(StaffChannel, "(\00304INVITES\003) Denied invite from %s to %s because we were busy", params[0].c_str(), params[3].c_str()); IRCdProto->Notice(nick, "ChanStat is currently busy, please try again later."); IRCdProto->Privmsg(BotChannel, "!channels"); } else { IRCdProto->Privmsg(StaffChannel, "(\00304INVITES\003) Denied invite from %s to %s because we are full", params[0].c_str(), params[3].c_str()); IRCdProto->Notice(nick, "ChanStat is currently full, for assistance join #ChanStat."); } } return EVENT_CONTINUE;}EventReturn do_chmodeL(const std::vector<std::string> ¶ms){ IRCdProto->Privmsg(StaffChannel, "(\00307JOINS\003) I was linked from %s to %s", params[4].c_str(), params[18].c_str()); CSChannel *cs = findcs(params[4]); if (cs) { delete cs; IRCdProto->Privmsg(StaffChannel, "(\00303REMOVED\003) Removed %s from internal tracking", params[4].c_str()); } return EVENT_CONTINUE;}EventReturn do_swiftirc_faillink(const std::vector<std::string> ¶ms){ IRCdProto->Privmsg(StaffChannel, "(\00304JOINS\003) %s", params[3].c_str()); std::vector<std::string> splitbuf; std::string msg = params[3]; SplitBuffer(msg, splitbuf); CSChannel *cs; if (!splitbuf.empty() && (cs = findcs(splitbuf[0]))) { delete cs; IRCdProto->Privmsg(StaffChannel, "(\00303REMOVED\003) Removed %s from internal tracking", splitbuf[0].c_str()); } return EVENT_CONTINUE;}EventReturn do_failjoin(const std::vector<std::string> ¶ms){ IRCdProto->Privmsg(StaffChannel, "(4JOINS) Could not join %s (%s)", params[3].c_str(), params[4].c_str()); if (!invitedby.empty()) IRCdProto->Notice(invitedby, "(4ERROR) Unable to join \2%s\2 (%s)", params[3].c_str(), params[4].c_str()); invitedby.clear(); CSChannel *cs = findcs(params[3]); if (cs) { IRCdProto->Privmsg(StaffChannel, "(\00303REMOVED\003) Removed %s from internal tracking", cs->name.c_str()); delete cs; } return EVENT_CONTINUE;}EventReturn do_failjoin2(const std::vector<std::string> ¶ms){ IRCdProto->Privmsg(StaffChannel, "(4JOINS) Could not join, (%s)", params[3].c_str()); if (!invitedby.empty()) IRCdProto->Notice(invitedby, "(4ERROR) Unable to join (%s)", params[3].c_str()); invitedby.clear(); CSChannel *cs = findcs(params[3]); if (cs) { IRCdProto->Privmsg(StaffChannel, "(\00303REMOVED\003) Removed %s from internal tracking", cs->name.c_str()); delete cs; } return EVENT_CONTINUE;}EventReturn do_whois(const std::vector<std::string> ¶ms){ std::string buf; std::stringstream ss(params[4]); int i = 0; ci::string cibuf; while (std::getline(ss, buf, ' ')) { if (!buf.empty() && (buf[0] == '!' || buf[0] == '~' || buf[0] == '&' || buf[0] == '@' || buf[0] == '%' || buf[0] == '+')) buf.erase(buf.begin()); if (!buf.empty()) { cibuf = assign(buf); getcs(cibuf); ++i; } } IRCdProto->Privmsg(BotChannel, "(07Loading) Successfully found %i channels", i); return EVENT_CONTINUE;}EventReturn do_getmynick(const std::vector<std::string> ¶ms){ std::vector<std::string> tok; std::string nick, host, buf; if (params.size() <= 3) { IRCdProto->Privmsg(StaffChannel, "!!!WARNING!!! got invalid do_getmynick"); return EVENT_CONTINUE; } buf = params[3]; SplitBuffer(buf, tok); if (tok.size() <= Raw001Pos) { IRCdProto->Privmsg(StaffChannel, "!!!WARNING!!! RAW001POSITION is out of range (%d <= %d)", tok.size(), Raw001Pos); // RAW001POSITION doens't exist.. too big return EVENT_CONTINUE; } GetNickHost(tok[Raw001Pos], nick, host); ircnick = nick; if (!ircnick.empty() && Config->Nick != ircnick.c_str()) { IRCdProto->Privmsg(StaffChannel, "(04WARNING) I have detected that I am %s, I should be %s", ircnick.c_str(), Config->Nick.c_str()); // Attempt to nick back to the proper nick, if this works it will catch it in do_nick IRCSocket->Write("NICK %s", Config->Nick.c_str()); } else { ircnick.clear(); } return EVENT_CONTINUE;}EventReturn do_topic(const std::vector<std::string> ¶ms){ CSChannel *cs; char hour[4], min[4]; ci::string chname = assign(params[2]); if (!(cs = getcs(chname))) return EVENT_CONTINUE; getTimestamp(hour, min); cs->Write("[%s:%s] Topic changed on %s by %s: %s", hour, min, params[2].c_str(), params[0].c_str(), params[3].c_str()); return EVENT_CONTINUE;}EventReturn do_324mode(const std::vector<std::string> ¶ms){ if (!AudMode.empty() && params.size() > 4 && params[4].find(AudMode) != std::string::npos) { /* Retarded channel, they are +u */ IRCdProto->Privmsg(StaffChannel, "(07WARNING) Channel %s has been detected as +u", params[3].c_str()); if (Config->Nick == "ChanStat") { Blacklist *b = new Blacklist; b->nick = Config->Nick; b->reason = "Automated blacklist for channel being +u"; time_t t = time(NULL); b->date = t; b->expires = t + 604800; //+u channels expire in a week Blacklists.insert(std::make_pair(assign(params[3]), b)); IRCdProto->Privmsg(StaffChannel, "(04BLACKLIST) Automatically blacklisted channel %s for +u", params[3].c_str()); IRCdProto->Part(params[3]); } else { IRCdProto->Privmsg(BotChannel, "!blacklist add %s +d7 Automated blacklist for +u", params[3].c_str()); } } return EVENT_CONTINUE;}static void LoadDatabase(){ FILE *fd; char filebuf[200]; std::vector<std::string> buf; Blacklist *b; Ignore *i; std::string bbuf; std::string dbname = "./data/" + Config->Nick + ".db"; if (!(fd = fopen(dbname.c_str(), "r"))) return; while (!feof(fd)) { memset(&filebuf, '\0', sizeof(filebuf)); fgets(filebuf, sizeof(filebuf), fd); if (!filebuf || !*filebuf) continue; bbuf = filebuf; while (bbuf[bbuf.length() - 1] == 10) bbuf.erase(bbuf.length() - 1); SplitBuffer(bbuf, buf); if (buf.empty()) continue; if (buf[0] == "channel") { ci::string chname = assign(buf[1]); getcs(chname); } else if (buf[0] == "blacklist" && Config->Nick == "ChanStat") { b = new Blacklist; b->nick = buf[2]; b->date = atol(buf[3].c_str()); b->expires = atol(buf[4].c_str()); b->reason = assign(buf[5]); Blacklists.insert(std::make_pair(assign(buf[1]), b)); } else if (buf[0] == "ignore") { i = new Ignore; i->nick = buf[2]; i->date = atol(buf[3].c_str()); i->expires = atol(buf[4].c_str()); i->reason = assign(buf[5]); IgnoredHosts.insert(std::make_pair(assign(buf[1]), i)); } else if (buf[0] == "hostallow") { HostAllow *h = new HostAllow; h->host = buf[1]; h->creator = buf[2]; HostAllows.push_back(h); } } fclose(fd);}static void SaveDatabase(){ FILE *fd; char filebuf[200]; std::string dbname = "./data/" + Config->Nick + ".db"; if (!(fd = fopen(dbname.c_str(), "w"))) return; for (std::map<ci::string, CSChannel *>::iterator cit = Channels.begin(); cit != Channels.end(); ++cit) { CSChannel *c = cit->second; snprintf(filebuf, sizeof(filebuf), "channel %s\n", c->name.c_str()); fputs(filebuf, fd); /* We have no use for this, it is for the uptime.php page */ if (!c->users.empty()) { snprintf(filebuf, sizeof(filebuf), "users %s %d\n", c->name.c_str(), c->users.size()); fputs(filebuf, fd); } } for (std::map<ci::string, Ignore *>::iterator iit = IgnoredHosts.begin(); iit != IgnoredHosts.end(); ++iit) { Ignore *i = iit->second; snprintf(filebuf, sizeof(filebuf), "ignore %s %s %ld %ld :%s\n", iit->first.c_str(), i->nick.c_str(), i->date, i->expires, i->reason.c_str()); fputs(filebuf, fd); } for (std::map<ci::string, Blacklist *>::iterator bit = Blacklists.begin(); bit != Blacklists.end(); ++bit) { Blacklist *b = bit->second; snprintf(filebuf, sizeof(filebuf), "blacklist %s %s %ld %ld :%s\n", bit->first.c_str(), b->nick.c_str(), b->date, b->expires, b->reason.c_str()); fputs(filebuf, fd); } for (std::vector<HostAllow *>::iterator hostallow = HostAllows.begin(); hostallow != HostAllows.end(); ++hostallow) { HostAllow *h = *hostallow; snprintf(filebuf, sizeof(filebuf), "hostallow %s %s\n", h->host.c_str(), h->creator.c_str()); fputs(filebuf, fd); } fclose(fd);}class DatabaseTimer : public Timer{ public: DatabaseTimer() : Timer(300, time(NULL), true) { } void Tick(time_t) { SaveDatabase(); /* We call getTimestamp here to cycle the logs if necessary (aka, this is a * VERY idle bot...) */ char hour[4], min[4]; getTimestamp(hour, min); }} _DatabaseTimer;class ChanStat : public Module{ public: ChanStat() { OnConfigRead(); this->AddCommand(new ChanStatNotice()); this->AddCommand(new ChanStatMessage()); this->AddCommand(new ChanStatMapNotice()); this->AddCommand(new ChanStatMapMessage()); this->AddCommand(new ChanStatChannel()); this->AddCommand(new ChanStatMyChannels()); this->AddCommand(new ChanStatDoJoin()); this->AddCommand(new ChanStatPart()); this->AddCommand(new ChanStatBlacklist()); this->AddCommand(new ChanStatIgnore()); this->AddCommand(new ChanStatBotinfo()); this->AddCommand(new ChanStatChancount()); this->AddCommand(new ChanStatBLPart()); this->AddCommand(new ChanStatAMSG()); this->AddCommand(new ChanStatNick()); this->AddCommand(new ChanStatID()); this->AddCommand(new ChanStatQuit()); if (PersonalBot) { this->AddCommand(new ChanStatHostManage()); } this->AddMessage("PRIVMSG", do_privmsg); this->AddMessage("MODE", do_mode); this->AddMessage("JOIN", do_join); this->AddMessage("PART", do_part); this->AddMessage("NICK", do_nick); this->AddMessage("QUIT", do_quit); this->AddMessage("353", do_names); this->AddMessage("KICK", do_kick); this->AddMessage("INVITE", do_invite); this->AddMessage("TOPIC", do_topic); this->AddMessage("470", do_chmodeL); this->AddMessage("538", do_swiftirc_faillink); this->AddMessage("474", do_failjoin); this->AddMessage("475", do_failjoin); this->AddMessage("473", do_failjoin); this->AddMessage("471", do_failjoin); this->AddMessage("489", do_failjoin); this->AddMessage("519", do_failjoin2); this->AddMessage("520", do_failjoin2); this->AddMessage("319", do_whois); this->AddMessage("001", do_getmynick); this->AddMessage("324", do_324mode); Event e[] = { E_OnConnect, E_OnConfigRead }; this->AddEvent(e, 2); LoadDatabase(); loadtime = time(NULL); } ~ChanStat() { SaveDatabase(); for (std::map<ci::string, CSChannel *>::iterator it = Channels.begin(); it != Channels.end(); ) { CSChannel *cs = it->second; ++it; delete cs; } Channels.clear(); BotsChanCount.clear(); for (std::map<ci::string, Blacklist *>::iterator blklist = Blacklists.begin(); blklist != Blacklists.end(); ++blklist) { delete blklist->second; } Blacklists.clear(); for (std::map<ci::string, Throttle *>::iterator throttle = Throttles.begin(); throttle != Throttles.end(); ++throttle) { delete throttle->second; } Throttles.clear(); for (std::map<ci::string, Ignore *>::iterator ignore = IgnoredHosts.begin(); ignore != IgnoredHosts.end(); ++ignore) { delete ignore->second; } IgnoredHosts.clear(); for (std::vector<HostAllow *>::iterator hostallow = HostAllows.begin(); hostallow != HostAllows.end(); ++hostallow) { delete *hostallow; } HostAllows.clear(); } void OnConnect(std::string &Server, std::string &BindIP, std::string &Pass, int Port, bool) { IRCdProto->Privmsg("NickServ", "IDENTIFY %s", NSPW.c_str()); if (!ircnick.empty()) IRCSocket->Write("MODE %s -x+pB", ircnick.c_str()); else IRCSocket->Write("MODE %s -x+pB", Config->Nick.c_str()); for (unsigned i = 0; i < sizeof(WhiteList); ++i) { if (WhiteList[i].empty()) break; /* Personal bots do not join #chanstat */ if (PersonalBot && WhiteList[i] == "#chanstat") continue; IRCdProto->Join(WhiteList[i]); } for (std::map<ci::string, CSChannel *>::iterator it = Channels.begin(); it != Channels.end(); ++it) { IRCdProto->Join(it->second->name); } } void OnConfigRead() { ConfigReader Values[] = { {"LogDir", &LogDir, DT_STRING}, {"NetsplitLine", &NSLine, DT_STRING}, {"NetsplitNotLine", &NSNLine, DT_STRING}, {"NickServPassword", &NSPW, DT_STRING}, {"AppendLink", &AppendLink, DT_STRING}, {"MaxChannels", &MaxChannels, DT_UNSIGNED}, {"AuditoriumMode", &AudMode, DT_STRING}, {"Raw001Position", &Raw001Pos, DT_UNSIGNED}, {"PersonalBot", &PersonalBot, DT_BOOL} }; ReadConfig(Values, 9); }};MODULE_INIT(ChanStat)