1 /* 2 * InspIRCd -- Internet Relay Chat Daemon 3 * 4 * Copyright (C) 2019 Robby <robby@chatbelgie.be> 5 * Copyright (C) 2017-2018 Sadie Powell <sadie@witchery.services> 6 * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> 7 * 8 * This file is part of InspIRCd. InspIRCd is free software: you can 9 * redistribute it and/or modify it under the terms of the GNU General Public 10 * License as published by the Free Software Foundation, version 2. 11 * 12 * This program is distributed in the hope that it will be useful, but WITHOUT 13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 15 * details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21 22 #include "inspircd.h" 23 #include "modules/away.h" 24 25 #define INSPIRCD_MONITOR_MANAGER_ONLY 26 #include "m_monitor.cpp" 27 28 enum 29 { 30 RPL_GONEAWAY = 598, 31 RPL_NOTAWAY = 599, 32 RPL_LOGON = 600, 33 RPL_LOGOFF = 601, 34 RPL_WATCHOFF = 602, 35 RPL_WATCHSTAT = 603, 36 RPL_NOWON = 604, 37 RPL_NOWOFF = 605, 38 RPL_WATCHLIST = 606, 39 RPL_ENDOFWATCHLIST = 607, 40 // RPL_CLEARWATCH = 608, // unused 41 RPL_NOWISAWAY = 609, 42 ERR_TOOMANYWATCH = 512, 43 ERR_INVALIDWATCHNICK = 942 44 }; 45 46 class CommandWatch : public SplitCommand 47 { 48 // Additional penalty for /WATCH commands that request a list from the server 49 static const unsigned int ListPenalty = 4000; 50 51 IRCv3::Monitor::Manager& manager; 52 SendOnlineOffline(LocalUser * user,const std::string & nick,bool show_offline=true)53 static void SendOnlineOffline(LocalUser* user, const std::string& nick, bool show_offline = true) 54 { 55 User* target = IRCv3::Monitor::Manager::FindNick(nick); 56 if (target) 57 { 58 // The away state should only be sent if the client requests away notifications for a nick but 2.0 always sends them so we do that too 59 if (target->IsAway()) 60 user->WriteNumeric(RPL_NOWISAWAY, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->awaytime, "is away"); 61 else 62 user->WriteNumeric(RPL_NOWON, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->age, "is online"); 63 } 64 else if (show_offline) 65 user->WriteNumeric(RPL_NOWOFF, nick, "*", "*", "0", "is offline"); 66 } 67 HandlePlus(LocalUser * user,const std::string & nick)68 void HandlePlus(LocalUser* user, const std::string& nick) 69 { 70 IRCv3::Monitor::Manager::WatchResult result = manager.Watch(user, nick, maxwatch); 71 if (result == IRCv3::Monitor::Manager::WR_TOOMANY) 72 { 73 // List is full, send error numeric 74 user->WriteNumeric(ERR_TOOMANYWATCH, nick, "Too many WATCH entries"); 75 return; 76 } 77 else if (result == IRCv3::Monitor::Manager::WR_INVALIDNICK) 78 { 79 user->WriteNumeric(ERR_INVALIDWATCHNICK, nick, "Invalid nickname"); 80 return; 81 } 82 else if (result != IRCv3::Monitor::Manager::WR_OK) 83 return; 84 85 SendOnlineOffline(user, nick); 86 } 87 HandleMinus(LocalUser * user,const std::string & nick)88 void HandleMinus(LocalUser* user, const std::string& nick) 89 { 90 if (!manager.Unwatch(user, nick)) 91 return; 92 93 User* target = IRCv3::Monitor::Manager::FindNick(nick); 94 if (target) 95 user->WriteNumeric(RPL_WATCHOFF, target->nick, target->ident, target->GetDisplayedHost(), (unsigned long)target->age, "stopped watching"); 96 else 97 user->WriteNumeric(RPL_WATCHOFF, nick, "*", "*", "0", "stopped watching"); 98 } 99 HandleList(LocalUser * user,bool show_offline)100 void HandleList(LocalUser* user, bool show_offline) 101 { 102 user->CommandFloodPenalty += ListPenalty; 103 const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user); 104 for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i) 105 { 106 const IRCv3::Monitor::Entry* entry = *i; 107 SendOnlineOffline(user, entry->GetNick(), show_offline); 108 } 109 user->WriteNumeric(RPL_ENDOFWATCHLIST, "End of WATCH list"); 110 } 111 HandleStats(LocalUser * user)112 void HandleStats(LocalUser* user) 113 { 114 user->CommandFloodPenalty += ListPenalty; 115 116 // Do not show how many clients are watching this nick, it's pointless 117 const IRCv3::Monitor::WatchedList& list = manager.GetWatched(user); 118 user->WriteNumeric(RPL_WATCHSTAT, InspIRCd::Format("You have %lu and are on 0 WATCH entries", (unsigned long)list.size())); 119 120 Numeric::Builder<' '> out(user, RPL_WATCHLIST); 121 for (IRCv3::Monitor::WatchedList::const_iterator i = list.begin(); i != list.end(); ++i) 122 { 123 const IRCv3::Monitor::Entry* entry = *i; 124 out.Add(entry->GetNick()); 125 } 126 out.Flush(); 127 user->WriteNumeric(RPL_ENDOFWATCHLIST, "End of WATCH S"); 128 } 129 130 public: 131 unsigned int maxwatch; 132 CommandWatch(Module * mod,IRCv3::Monitor::Manager & managerref)133 CommandWatch(Module* mod, IRCv3::Monitor::Manager& managerref) 134 : SplitCommand(mod, "WATCH") 135 , manager(managerref) 136 { 137 allow_empty_last_param = false; 138 syntax = "C|L|l|S|(+|-)<nick> [(+|-)<nick>]+"; 139 } 140 HandleLocal(LocalUser * user,const Params & parameters)141 CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE 142 { 143 if (parameters.empty()) 144 { 145 HandleList(user, false); 146 return CMD_SUCCESS; 147 } 148 149 bool watch_l_done = false; 150 bool watch_s_done = false; 151 152 for (std::vector<std::string>::const_iterator i = parameters.begin(); i != parameters.end(); ++i) 153 { 154 const std::string& token = *i; 155 char subcmd = toupper(token[0]); 156 if (subcmd == '+') 157 { 158 HandlePlus(user, token.substr(1)); 159 } 160 else if (subcmd == '-') 161 { 162 HandleMinus(user, token.substr(1)); 163 } 164 else if (subcmd == 'C') 165 { 166 manager.UnwatchAll(user); 167 } 168 else if ((subcmd == 'L') && (!watch_l_done)) 169 { 170 watch_l_done = true; 171 // WATCH L requests a full list with online and offline nicks 172 // WATCH l requests a list with only online nicks 173 HandleList(user, (token[0] == 'L')); 174 } 175 else if ((subcmd == 'S') && (!watch_s_done)) 176 { 177 watch_s_done = true; 178 HandleStats(user); 179 } 180 } 181 return CMD_SUCCESS; 182 } 183 }; 184 185 class ModuleWatch 186 : public Module 187 , public Away::EventListener 188 { 189 IRCv3::Monitor::Manager manager; 190 CommandWatch cmd; 191 SendAlert(User * user,const std::string & nick,unsigned int numeric,const char * numerictext,time_t shownts)192 void SendAlert(User* user, const std::string& nick, unsigned int numeric, const char* numerictext, time_t shownts) 193 { 194 const IRCv3::Monitor::WatcherList* list = manager.GetWatcherList(nick); 195 if (!list) 196 return; 197 198 Numeric::Numeric num(numeric); 199 num.push(nick).push(user->ident).push(user->GetDisplayedHost()).push(ConvToStr(shownts)).push(numerictext); 200 for (IRCv3::Monitor::WatcherList::const_iterator i = list->begin(); i != list->end(); ++i) 201 { 202 LocalUser* curr = *i; 203 curr->WriteNumeric(num); 204 } 205 } 206 Online(User * user)207 void Online(User* user) 208 { 209 SendAlert(user, user->nick, RPL_LOGON, "arrived online", user->age); 210 } 211 Offline(User * user,const std::string & nick)212 void Offline(User* user, const std::string& nick) 213 { 214 SendAlert(user, nick, RPL_LOGOFF, "went offline", user->age); 215 } 216 217 public: ModuleWatch()218 ModuleWatch() 219 : Away::EventListener(this) 220 , manager(this, "watch") 221 , cmd(this, manager) 222 { 223 } 224 ReadConfig(ConfigStatus & status)225 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE 226 { 227 ConfigTag* tag = ServerInstance->Config->ConfValue("watch"); 228 cmd.maxwatch = tag->getUInt("maxwatch", 30, 1); 229 } 230 OnPostConnect(User * user)231 void OnPostConnect(User* user) CXX11_OVERRIDE 232 { 233 Online(user); 234 } 235 OnUserPostNick(User * user,const std::string & oldnick)236 void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE 237 { 238 // Detect and ignore nickname case change 239 if (ServerInstance->FindNickOnly(oldnick) == user) 240 return; 241 242 Offline(user, oldnick); 243 Online(user); 244 } 245 OnUserQuit(User * user,const std::string & message,const std::string & oper_message)246 void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE 247 { 248 LocalUser* localuser = IS_LOCAL(user); 249 if (localuser) 250 manager.UnwatchAll(localuser); 251 Offline(user, user->nick); 252 } 253 OnUserAway(User * user)254 void OnUserAway(User* user) CXX11_OVERRIDE 255 { 256 SendAlert(user, user->nick, RPL_GONEAWAY, user->awaymsg.c_str(), user->awaytime); 257 } 258 OnUserBack(User * user)259 void OnUserBack(User* user) CXX11_OVERRIDE 260 { 261 SendAlert(user, user->nick, RPL_NOTAWAY, "is no longer away", ServerInstance->Time()); 262 } 263 On005Numeric(std::map<std::string,std::string> & tokens)264 void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE 265 { 266 tokens["WATCH"] = ConvToStr(cmd.maxwatch); 267 } 268 GetVersion()269 Version GetVersion() CXX11_OVERRIDE 270 { 271 return Version("Adds the /WATCH command which allows users to find out when their friends are connected to the server.", VF_VENDOR); 272 } 273 }; 274 275 MODULE_INIT(ModuleWatch) 276