00001
00002
00003
00004
00005
00006
00007
00008
00009
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;
00033 Anope::string channel;
00034 Anope::string message;
00035 time_t last;
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
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> ¶ms) 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> ¶ms) 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)