cs_seen.cpp

Go to the documentation of this file.
00001 /* cs_seen: provides a seen command by tracking all users
00002  *
00003  * (C) 2003-2013 Anope Team
00004  * Contact us at team@anope.org
00005  *
00006  * Please read COPYING and README for further details.
00007  *
00008  * Based on the original code of Epona by Lara.
00009  * Based on the original code of Services by Andy Church.
00010  */
00011 
00012 /*************************************************************************/
00013 
00014 
00015 #include "module.h"
00016 
00017 enum TypeInfo
00018 {
00019         NEW, NICK_TO, NICK_FROM, JOIN, PART, QUIT, KICK
00020 };
00021 
00022 struct SeenInfo;
00023 static SeenInfo *FindInfo(const Anope::string &nick);
00024 typedef Anope::hash_map<SeenInfo *> database_map;
00025 database_map database;
00026 
00027 struct SeenInfo : Serializable
00028 {
00029         Anope::string nick;
00030         Anope::string vhost;
00031         TypeInfo type;
00032         Anope::string nick2;    // for nickchanges and kicks
00033         Anope::string channel;  // for join/part/kick
00034         Anope::string message;  // for part/kick/quit
00035         time_t last;            // the time when the user was last seen
00036 
00037         SeenInfo() : Serializable("SeenInfo")
00038         {
00039         }
00040 
00041         void Serialize(Serialize::Data &data) const anope_override
00042         {
00043                 data["nick"] << nick;
00044                 data["vhost"] << vhost;
00045                 data["type"] << type;
00046                 data["nick2"] << nick2;
00047                 data["channel"] << channel;
00048                 data["message"] << message;
00049                 data.SetType("last", Serialize::Data::DT_INT); data["last"] << last;
00050         }
00051 
00052         static Serializable* Unserialize(Serializable *obj, Serialize::Data &data)
00053         {
00054                 Anope::string snick;
00055                 
00056                 data["nick"] >> snick;
00057 
00058                 SeenInfo *s;
00059                 if (obj)
00060                         s = anope_dynamic_static_cast<SeenInfo *>(obj);
00061                 else
00062                 {
00063                         /* ignore duplicate entries in the db, created by an old bug */
00064                         s = FindInfo(snick);
00065                         if (!s)
00066                                 s = new SeenInfo();
00067                 }
00068 
00069                 data["nick"] >> s->nick;
00070                 data["vhost"] >> s->vhost;
00071                 unsigned int n;
00072                 data["type"] >> n;
00073                 s->type = static_cast<TypeInfo>(n);
00074                 data["nick2"] >> s->nick2;
00075                 data["channel"] >> s->channel;
00076                 data["message"] >> s->message;
00077                 data["last"] >> s->last;
00078 
00079                 if (!obj)
00080                         database[s->nick] = s;
00081                 return s;
00082         }
00083 };
00084 
00085 static time_t purgetime;
00086 static time_t expiretimeout;
00087 
00088 static SeenInfo *FindInfo(const Anope::string &nick)
00089 {
00090         database_map::iterator iter = database.find(nick);
00091         if (iter != database.end())
00092                 return iter->second;
00093         return NULL;
00094 }
00095 
00096 static bool ShouldHide(const Anope::string &channel, User *u)
00097 {
00098         Channel *targetchan = Channel::Find(channel);
00099         const ChannelInfo *targetchan_ci = targetchan ? *targetchan->ci : ChannelInfo::Find(channel);
00100 
00101         if (targetchan && targetchan->HasMode("SECRET"))
00102                 return true;
00103         else if (targetchan_ci && targetchan_ci->HasExt("PRIVATE"))
00104                 return true;
00105         else if (u && u->HasMode("PRIV"))
00106                 return true;
00107         return false;
00108 }
00109 
00110 class CommandOSSeen : public Command
00111 {
00112  public:
00113         CommandOSSeen(Module *creator) : Command(creator, "operserv/seen", 1, 2)
00114         {
00115                 this->SetDesc(_("Statistics and maintenance for seen data"));
00116                 this->SetSyntax(_("STATS"));
00117                 this->SetSyntax(_("CLEAR \037time\037"));
00118         }
00119 
00120         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00121         {
00122                 if (params[0].equals_ci("STATS"))
00123                 {
00124                         size_t mem_counter;
00125                         mem_counter = sizeof(database_map);
00126                         for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end; ++it)
00127                         {
00128                                 mem_counter += (5 * sizeof(Anope::string)) + sizeof(TypeInfo) + sizeof(time_t);
00129                                 mem_counter += it->first.capacity();
00130                                 mem_counter += it->second->vhost.capacity();
00131                                 mem_counter += it->second->nick2.capacity();
00132                                 mem_counter += it->second->channel.capacity();
00133                                 mem_counter += it->second->message.capacity();
00134                         }
00135                         source.Reply(_("%lu nicks are stored in the database, using %.2Lf kB of memory."), database.size(), static_cast<long double>(mem_counter) / 1024);
00136                 }
00137                 else if (params[0].equals_ci("CLEAR"))
00138                 {
00139                         time_t time = 0;
00140                         if ((params.size() < 2) || (0 >= (time = Anope::DoTime(params[1]))))
00141                         {
00142                                 this->OnSyntaxError(source, params[0]);
00143                                 return;
00144                         }
00145                         time = Anope::CurTime - time;
00146                         database_map::iterator buf;
00147                         size_t counter = 0;
00148                         for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;)
00149                         {
00150                                 buf = it;
00151                                 ++it;
00152                                 if (time < buf->second->last)
00153                                 {
00154                                         Log(LOG_DEBUG) << buf->first << " was last seen " << Anope::strftime(buf->second->last) << ", deleting entry";
00155                                         delete buf->second;
00156                                         database.erase(buf);
00157                                         counter++;
00158                                 }
00159                         }
00160                         Log(LOG_ADMIN, source, this) << "CLEAR and removed " << counter << " nicks that were added after " << Anope::strftime(time, NULL, true);
00161                         source.Reply(_("Database cleared, removed %lu nicks that were added after %s."), counter, Anope::strftime(time, source.nc, true).c_str());
00162                 }
00163                 else
00164                         this->SendSyntax(source);
00165         }
00166 
00167         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00168         {
00169                 this->SendSyntax(source);
00170                 source.Reply(" ");
00171                 source.Reply(_("The \002STATS\002 command prints out statistics about stored nicks and memory usage."));
00172                 source.Reply(_("The \002CLEAR\002 command lets you clean the database by removing all entries from the\n"
00173                                 "entries from the database that were added within \037time\037.\n"
00174                                 " \n"
00175                                 "Example:\n"
00176                                 " %s CLEAR 30m\n"
00177                                 " Will remove all entries that were added within the last 30 minutes."), source.command.c_str());
00178                 return true;
00179         }
00180 };
00181 
00182 class CommandSeen : public Command
00183 {
00184  public:
00185         CommandSeen(Module *creator) : Command(creator, "chanserv/seen", 1, 2)
00186         {
00187                 this->SetDesc(_("Tells you about the last time a user was seen"));
00188                 this->SetSyntax(_("\037nick\037"));
00189         }
00190 
00191         void Execute(CommandSource &source, const std::vector<Anope::string> &params) anope_override
00192         {
00193                 const Anope::string &target = params[0];
00194 
00195                 if (target.length() > Config->NickLen)
00196                 {
00197                         source.Reply(_("Nick too long, max length is %u characters."), Config->NickLen);
00198                         return;
00199                 }
00200 
00201                 if (BotInfo::Find(target, true) != NULL)
00202                 {
00203                         source.Reply(_("%s is a client on services."), target.c_str());
00204                         return;
00205                 }
00206 
00207                 if (target.equals_ci(source.GetNick()))
00208                 {
00209                         source.Reply(_("You might see yourself in the mirror, %s."), source.GetNick().c_str());
00210                         return;
00211                 }
00212 
00213                 SeenInfo *info = FindInfo(target);
00214                 if (!info)
00215                 {
00216                         source.Reply(_("Sorry, I have not seen %s."), target.c_str());
00217                         return;
00218                 }
00219 
00220                 User *u2 = User::Find(target, true);
00221                 Anope::string onlinestatus;
00222                 if (u2)
00223                         onlinestatus = ".";
00224                 else
00225                         onlinestatus = Anope::printf(_(" but %s mysteriously dematerialized."), target.c_str());
00226 
00227                 Anope::string timebuf = Anope::Duration(Anope::CurTime - info->last, source.nc);
00228                 Anope::string timebuf2 = Anope::strftime(info->last, source.nc, true);
00229 
00230                 if (info->type == NEW)
00231                 {
00232                         source.Reply(_("%s (%s) was last seen connecting %s ago (%s)%s"),
00233                                 target.c_str(), info->vhost.c_str(), timebuf.c_str(), timebuf2.c_str(), onlinestatus.c_str());
00234                 }
00235                 else if (info->type == NICK_TO)
00236                 {
00237                         u2 = User::Find(info->nick2, true);
00238                         if (u2)
00239                                 onlinestatus = Anope::printf( _(". %s is still online."), u2->nick.c_str());
00240                         else
00241                                 onlinestatus = Anope::printf(_(", but %s mysteriously dematerialized."), info->nick2.c_str());
00242 
00243                         source.Reply(_("%s (%s) was last seen changing nick to %s %s ago%s"),
00244                                 target.c_str(), info->vhost.c_str(), info->nick2.c_str(), timebuf.c_str(), onlinestatus.c_str());
00245                 }
00246                 else if (info->type == NICK_FROM)
00247                 {
00248                         source.Reply(_("%s (%s) was last seen changing nick from %s to %s %s ago%s"),
00249                                 target.c_str(), info->vhost.c_str(), info->nick2.c_str(), target.c_str(), timebuf.c_str(), onlinestatus.c_str());
00250                 }
00251                 else if (info->type == JOIN)
00252                 {
00253                         if (ShouldHide(info->channel, u2))
00254                                 source.Reply(_("%s (%s) was last seen joining a secret channel %s ago%s"),
00255                                         target.c_str(), info->vhost.c_str(), timebuf.c_str(), onlinestatus.c_str());
00256                         else
00257                                 source.Reply(_("%s (%s) was last seen joining %s %s ago%s"),
00258                                         target.c_str(), info->vhost.c_str(), info->channel.c_str(), timebuf.c_str(), onlinestatus.c_str());
00259                 }
00260                 else if (info->type == PART)
00261                 {
00262                         if (ShouldHide(info->channel, u2))
00263                                 source.Reply(_("%s (%s) was last seen parting a secret channel %s ago%s"),
00264                                         target.c_str(), info->vhost.c_str(), timebuf.c_str(), onlinestatus.c_str());
00265                         else
00266                                 source.Reply(_("%s (%s) was last seen parting %s %s ago%s"),
00267                                         target.c_str(), info->vhost.c_str(), info->channel.c_str(), timebuf.c_str(), onlinestatus.c_str());
00268                 }
00269                 else if (info->type == QUIT)
00270                 {
00271                         source.Reply(_("%s (%s) was last seen quitting (%s) %s ago (%s)."),
00272                                         target.c_str(), info->vhost.c_str(), info->message.c_str(), timebuf.c_str(), timebuf2.c_str());
00273                 }
00274                 else if (info->type == KICK)
00275                 {
00276                         if (ShouldHide(info->channel, u2))
00277                                 source.Reply(_("%s (%s) was kicked from a secret channel %s ago%s"),
00278                                         target.c_str(), info->vhost.c_str(), timebuf.c_str(), onlinestatus.c_str());
00279                         else
00280                                 source.Reply(_("%s (%s) was kicked from %s (\"%s\") %s ago%s"),
00281                                         target.c_str(), info->vhost.c_str(), info->channel.c_str(), info->message.c_str(), timebuf.c_str(), onlinestatus.c_str());
00282                 }
00283         }
00284 
00285         bool OnHelp(CommandSource &source, const Anope::string &subcommand) anope_override
00286         {
00287                 this->SendSyntax(source);
00288                 source.Reply(" ");
00289                 source.Reply(_("Checks for the last time \037nick\037 was seen joining, leaving,\n"
00290                                 "or changing nick on the network and tells you when and, depending\n"
00291                                 "on channel or user settings, where it was."));
00292                 return true;
00293         }
00294 };
00295 
00296 class DataBasePurger : public CallBack
00297 {
00298  public:
00299         DataBasePurger(Module *owner) : CallBack(owner, 300, Anope::CurTime, true) { }
00300 
00301         void Tick(time_t) anope_override
00302         {
00303                 size_t previous_size = database.size();
00304                 for (database_map::iterator it = database.begin(), it_end = database.end(); it != it_end;)
00305                 {
00306                         database_map::iterator cur = it;
00307                         ++it;
00308 
00309                         if ((Anope::CurTime - cur->second->last) > purgetime)
00310                         {
00311                                 Log(LOG_DEBUG) << cur->first << " was last seen " << Anope::strftime(cur->second->last) << ", purging entries";
00312                                 delete cur->second;
00313                                 database.erase(cur);
00314                         }
00315                 }
00316                 Log(LOG_DEBUG) << "cs_seen: Purged database, checked " << previous_size << " nicks and removed " << (previous_size - database.size()) << " old entries.";
00317         }
00318 };
00319 
00320 class CSSeen : public Module
00321 {
00322         Serialize::Type seeninfo_type;
00323         CommandSeen commandseen;
00324         CommandOSSeen commandosseen;
00325         DataBasePurger purger;
00326  public:
00327         CSSeen(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, CORE), seeninfo_type("SeenInfo", SeenInfo::Unserialize), commandseen(this), commandosseen(this), purger(this)
00328         {
00329                 this->SetAuthor("Anope");
00330 
00331                 Implementation eventlist[] =  { I_OnReload,
00332                                                 I_OnUserConnect,
00333                                                 I_OnUserNickChange,
00334                                                 I_OnUserQuit,
00335                                                 I_OnJoinChannel,
00336                                                 I_OnPartChannel,
00337                                                 I_OnUserKicked };
00338                 ModuleManager::Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
00339 
00340                 OnReload();
00341         }
00342 
00343         void OnReload() anope_override
00344         {
00345                 ConfigReader config;
00346                 purgetime = Anope::DoTime(config.ReadValue("cs_seen", "purgetime", "30d", 0));
00347                 expiretimeout = Anope::DoTime(config.ReadValue("cs_seen", "expiretimeout", "1d", 0));
00348 
00349                 if (purger.GetSecs() != expiretimeout)
00350                         purger.SetSecs(expiretimeout);
00351         }
00352 
00353         void OnUserConnect(User *u, bool &exempt) anope_override
00354         {
00355                 if (!u->Quitting())
00356                         UpdateUser(u, NEW, u->nick, "", "", "");
00357         }
00358 
00359         void OnUserNickChange(User *u, const Anope::string &oldnick) anope_override
00360         {
00361                 UpdateUser(u, NICK_TO, oldnick, u->nick, "", "");
00362                 UpdateUser(u, NICK_FROM, u->nick, oldnick, "", "");
00363         }
00364 
00365         void OnUserQuit(User *u, const Anope::string &msg) anope_override
00366         {
00367                 UpdateUser(u, QUIT, u->nick, "", "", msg);
00368         }
00369 
00370         void OnJoinChannel(User *u, Channel *c) anope_override
00371         {
00372                 UpdateUser(u, JOIN, u->nick, "", c->name, "");
00373         }
00374 
00375         void OnPartChannel(User *u, Channel *c, const Anope::string &channel, const Anope::string &msg) anope_override
00376         {
00377                 UpdateUser(u, PART, u->nick, "", channel, msg);
00378         }
00379 
00380         void OnUserKicked(Channel *c, User *target, MessageSource &source, const Anope::string &msg) anope_override
00381         {
00382                 UpdateUser(target, KICK, target->nick, source.GetSource(), c->name, msg);
00383         }
00384 
00385  private:
00386         void UpdateUser(const User *u, const TypeInfo Type, const Anope::string &nick, const Anope::string &nick2, const Anope::string &channel, const Anope::string &message)
00387         {
00388                 if (!u->server->IsSynced())
00389                         return;
00390 
00391                 SeenInfo *info = FindInfo(nick);
00392                 if (!info)
00393                 {
00394                         info = new SeenInfo;
00395                         database.insert(std::pair<Anope::string, SeenInfo *>(nick, info));
00396                 }
00397                 info->nick = nick;
00398                 info->vhost = u->GetVIdent() + "@" + u->GetDisplayedHost();
00399                 info->type = Type;
00400                 info->last = Anope::CurTime;
00401                 info->nick2 = nick2;
00402                 info->channel = channel;
00403                 info->message = message;
00404         }
00405 };
00406 
00407 MODULE_INIT(CSSeen)