1 /* 2 * InspIRCd -- Internet Relay Chat Daemon 3 * 4 * Copyright (C) 2013, 2017-2019, 2021 Sadie Powell <sadie@witchery.services> 5 * Copyright (C) 2013 Adam <Adam@anope.org> 6 * Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com> 7 * Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be> 8 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> 9 * Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org> 10 * Copyright (C) 2009 John Brooks <special@inspircd.org> 11 * Copyright (C) 2008-2009 Robin Burchell <robin+git@viroteck.net> 12 * Copyright (C) 2008, 2010 Craig Edwards <brain@inspircd.org> 13 * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> 14 * 15 * This file is part of InspIRCd. InspIRCd is free software: you can 16 * redistribute it and/or modify it under the terms of the GNU General Public 17 * License as published by the Free Software Foundation, version 2. 18 * 19 * This program is distributed in the hope that it will be useful, but WITHOUT 20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 21 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 22 * details. 23 * 24 * You should have received a copy of the GNU General Public License 25 * along with this program. If not, see <http://www.gnu.org/licenses/>. 26 */ 27 28 29 #include "inspircd.h" 30 #include "modules/callerid.h" 31 #include "modules/ctctags.h" 32 33 enum 34 { 35 RPL_ACCEPTLIST = 281, 36 RPL_ENDOFACCEPT = 282, 37 ERR_ACCEPTFULL = 456, 38 ERR_ACCEPTEXIST = 457, 39 ERR_ACCEPTNOT = 458, 40 ERR_TARGUMODEG = 716, 41 RPL_TARGNOTIFY = 717, 42 RPL_UMODEGMSG = 718 43 }; 44 45 class callerid_data 46 { 47 public: 48 typedef insp::flat_set<User*> UserSet; 49 typedef std::vector<callerid_data*> CallerIdDataSet; 50 51 time_t lastnotify; 52 53 /** Users I accept messages from 54 */ 55 UserSet accepting; 56 57 /** Users who list me as accepted 58 */ 59 CallerIdDataSet wholistsme; 60 callerid_data()61 callerid_data() : lastnotify(0) { } 62 ToString(bool human) const63 std::string ToString(bool human) const 64 { 65 std::ostringstream oss; 66 oss << lastnotify; 67 for (UserSet::const_iterator i = accepting.begin(); i != accepting.end(); ++i) 68 { 69 User* u = *i; 70 if (human) 71 oss << ' ' << u->nick; 72 else 73 oss << ',' << u->uuid; 74 } 75 return oss.str(); 76 } 77 }; 78 79 struct CallerIDExtInfo : public ExtensionItem 80 { CallerIDExtInfoCallerIDExtInfo81 CallerIDExtInfo(Module* parent) 82 : ExtensionItem("callerid_data", ExtensionItem::EXT_USER, parent) 83 { 84 } 85 ToHumanCallerIDExtInfo86 std::string ToHuman(const Extensible* container, void* item) const CXX11_OVERRIDE 87 { 88 callerid_data* dat = static_cast<callerid_data*>(item); 89 return dat->ToString(true); 90 } 91 ToInternalCallerIDExtInfo92 std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE 93 { 94 callerid_data* dat = static_cast<callerid_data*>(item); 95 return dat->ToString(false); 96 } 97 FromInternalCallerIDExtInfo98 void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE 99 { 100 void* old = get_raw(container); 101 if (old) 102 this->free(NULL, old); 103 callerid_data* dat = new callerid_data; 104 set_raw(container, dat); 105 106 irc::commasepstream s(value); 107 std::string tok; 108 if (s.GetToken(tok)) 109 dat->lastnotify = ConvToNum<time_t>(tok); 110 111 while (s.GetToken(tok)) 112 { 113 User *u = ServerInstance->FindNick(tok); 114 if ((u) && (u->registered == REG_ALL) && (!u->quitting)) 115 { 116 if (dat->accepting.insert(u).second) 117 { 118 callerid_data* other = this->get(u, true); 119 other->wholistsme.push_back(dat); 120 } 121 } 122 } 123 } 124 getCallerIDExtInfo125 callerid_data* get(User* user, bool create) 126 { 127 callerid_data* dat = static_cast<callerid_data*>(get_raw(user)); 128 if (create && !dat) 129 { 130 dat = new callerid_data; 131 set_raw(user, dat); 132 } 133 return dat; 134 } 135 freeCallerIDExtInfo136 void free(Extensible* container, void* item) CXX11_OVERRIDE 137 { 138 callerid_data* dat = static_cast<callerid_data*>(item); 139 140 // We need to walk the list of users on our accept list, and remove ourselves from their wholistsme. 141 for (callerid_data::UserSet::iterator it = dat->accepting.begin(); it != dat->accepting.end(); ++it) 142 { 143 callerid_data* target = this->get(*it, false); 144 if (!target) 145 { 146 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (1)"); 147 continue; // shouldn't happen, but oh well. 148 } 149 150 if (!stdalgo::vector::swaperase(target->wholistsme, dat)) 151 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (2)"); 152 } 153 delete dat; 154 } 155 }; 156 157 class CommandAccept : public Command 158 { 159 /** Pair: first is the target, second is true to add, false to remove 160 */ 161 typedef std::pair<User*, bool> ACCEPTAction; 162 GetTargetAndAction(std::string & tok,User * cmdfrom=NULL)163 static ACCEPTAction GetTargetAndAction(std::string& tok, User* cmdfrom = NULL) 164 { 165 bool remove = (tok[0] == '-'); 166 if ((remove) || (tok[0] == '+')) 167 tok.erase(tok.begin()); 168 169 User* target; 170 if (!cmdfrom || !IS_LOCAL(cmdfrom)) 171 target = ServerInstance->FindNick(tok); 172 else 173 target = ServerInstance->FindNickOnly(tok); 174 175 if ((!target) || (target->registered != REG_ALL) || (target->quitting)) 176 target = NULL; 177 178 return std::make_pair(target, !remove); 179 } 180 181 public: 182 CallerIDExtInfo extInfo; 183 unsigned int maxaccepts; CommandAccept(Module * Creator)184 CommandAccept(Module* Creator) : Command(Creator, "ACCEPT", 1), 185 extInfo(Creator) 186 { 187 allow_empty_last_param = false; 188 syntax = "*|(+|-)<nick>[,(+|-)<nick>]+"; 189 TRANSLATE1(TR_CUSTOM); 190 } 191 EncodeParameter(std::string & parameter,unsigned int index)192 void EncodeParameter(std::string& parameter, unsigned int index) CXX11_OVERRIDE 193 { 194 // Send lists as-is (part of 2.0 compat) 195 if (parameter.find(',') != std::string::npos) 196 return; 197 198 // Convert a (+|-)<nick> into a [-]<uuid> 199 ACCEPTAction action = GetTargetAndAction(parameter); 200 if (!action.first) 201 return; 202 203 parameter = (action.second ? "" : "-") + action.first->uuid; 204 } 205 206 /** Will take any number of nicks (up to MaxTargets), which can be separated by commas. 207 * - in front of any nick removes, and an * lists. This effectively means you can do: 208 * /accept nick1,nick2,nick3,* 209 * to add 3 nicks and then show your list 210 */ Handle(User * user,const Params & parameters)211 CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE 212 { 213 if (CommandParser::LoopCall(user, this, parameters, 0)) 214 return CMD_SUCCESS; 215 216 /* Even if callerid mode is not set, we let them manage their ACCEPT list so that if they go +g they can 217 * have a list already setup. */ 218 219 if (parameters[0] == "*") 220 { 221 ListAccept(user); 222 return CMD_SUCCESS; 223 } 224 225 std::string tok = parameters[0]; 226 ACCEPTAction action = GetTargetAndAction(tok, user); 227 if (!action.first) 228 { 229 user->WriteNumeric(Numerics::NoSuchNick(tok)); 230 return CMD_FAILURE; 231 } 232 233 if ((!IS_LOCAL(user)) && (!IS_LOCAL(action.first))) 234 // Neither source nor target is local, forward the command to the server of target 235 return CMD_SUCCESS; 236 237 // The second item in the pair is true if the first char is a '+' (or nothing), false if it's a '-' 238 if (action.second) 239 return (AddAccept(user, action.first) ? CMD_SUCCESS : CMD_FAILURE); 240 else 241 return (RemoveAccept(user, action.first) ? CMD_SUCCESS : CMD_FAILURE); 242 } 243 GetRouting(User * user,const Params & parameters)244 RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE 245 { 246 // There is a list in parameters[0] in two cases: 247 // Either when the source is remote, this happens because 2.0 servers send comma separated uuid lists, 248 // we don't split those but broadcast them, as before. 249 // 250 // Or if the source is local then LoopCall() runs OnPostCommand() after each entry in the list, 251 // meaning the linking module has sent an ACCEPT already for each entry in the list to the 252 // appropriate server and the ACCEPT with the list of nicks (this) doesn't need to be sent anywhere. 253 if ((!IS_LOCAL(user)) && (parameters[0].find(',') != std::string::npos)) 254 return ROUTE_BROADCAST; 255 256 // Find the target 257 std::string targetstring = parameters[0]; 258 ACCEPTAction action = GetTargetAndAction(targetstring, user); 259 if (!action.first) 260 // Target is a "*" or source is local and the target is a list of nicks 261 return ROUTE_LOCALONLY; 262 263 // Route to the server of the target 264 return ROUTE_UNICAST(action.first->server); 265 } 266 ListAccept(User * user)267 void ListAccept(User* user) 268 { 269 callerid_data* dat = extInfo.get(user, false); 270 if (dat) 271 { 272 for (callerid_data::UserSet::iterator i = dat->accepting.begin(); i != dat->accepting.end(); ++i) 273 user->WriteNumeric(RPL_ACCEPTLIST, (*i)->nick); 274 } 275 user->WriteNumeric(RPL_ENDOFACCEPT, "End of ACCEPT list"); 276 } 277 AddAccept(User * user,User * whotoadd)278 bool AddAccept(User* user, User* whotoadd) 279 { 280 // Add this user to my accept list first, so look me up.. 281 callerid_data* dat = extInfo.get(user, true); 282 if (dat->accepting.size() >= maxaccepts) 283 { 284 user->WriteNumeric(ERR_ACCEPTFULL, InspIRCd::Format("Accept list is full (limit is %d)", maxaccepts)); 285 return false; 286 } 287 if (!dat->accepting.insert(whotoadd).second) 288 { 289 user->WriteNumeric(ERR_ACCEPTEXIST, whotoadd->nick, "is already on your accept list"); 290 return false; 291 } 292 293 // Now, look them up, and add me to their list 294 callerid_data* target = extInfo.get(whotoadd, true); 295 target->wholistsme.push_back(dat); 296 297 user->WriteNotice(whotoadd->nick + " is now on your accept list"); 298 return true; 299 } 300 RemoveAccept(User * user,User * whotoremove)301 bool RemoveAccept(User* user, User* whotoremove) 302 { 303 // Remove them from my list, so look up my list.. 304 callerid_data* dat = extInfo.get(user, false); 305 if (!dat) 306 { 307 user->WriteNumeric(ERR_ACCEPTNOT, whotoremove->nick, "is not on your accept list"); 308 return false; 309 } 310 if (!dat->accepting.erase(whotoremove)) 311 { 312 user->WriteNumeric(ERR_ACCEPTNOT, whotoremove->nick, "is not on your accept list"); 313 return false; 314 } 315 316 // Look up their list to remove me. 317 callerid_data *dat2 = extInfo.get(whotoremove, false); 318 if (!dat2) 319 { 320 // How the fuck is this possible. 321 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (3)"); 322 return false; 323 } 324 325 if (!stdalgo::vector::swaperase(dat2->wholistsme, dat)) 326 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (4)"); 327 328 329 user->WriteNotice(whotoremove->nick + " is no longer on your accept list"); 330 return true; 331 } 332 }; 333 334 class CallerIDAPIImpl : public CallerID::APIBase 335 { 336 private: 337 CallerIDExtInfo& ext; 338 339 public: CallerIDAPIImpl(Module * Creator,CallerIDExtInfo & Ext)340 CallerIDAPIImpl(Module* Creator, CallerIDExtInfo& Ext) 341 : CallerID::APIBase(Creator) 342 , ext(Ext) 343 { 344 } 345 IsOnAcceptList(User * source,User * target)346 bool IsOnAcceptList(User* source, User* target) CXX11_OVERRIDE 347 { 348 callerid_data* dat = ext.get(target, true); 349 return dat->accepting.count(source); 350 } 351 }; 352 353 354 class ModuleCallerID 355 : public Module 356 , public CTCTags::EventListener 357 { 358 CommandAccept cmd; 359 CallerIDAPIImpl api; 360 SimpleUserModeHandler myumode; 361 362 // Configuration variables: 363 bool tracknick; // Allow ACCEPT entries to update with nick changes. 364 unsigned int notify_cooldown; // Seconds between notifications. 365 366 /** Removes a user from all accept lists 367 * @param who The user to remove from accepts 368 */ RemoveFromAllAccepts(User * who)369 void RemoveFromAllAccepts(User* who) 370 { 371 // First, find the list of people who have me on accept 372 callerid_data *userdata = cmd.extInfo.get(who, false); 373 if (!userdata) 374 return; 375 376 // Iterate over the list of people who accept me, and remove all entries 377 for (callerid_data::CallerIdDataSet::iterator it = userdata->wholistsme.begin(); it != userdata->wholistsme.end(); ++it) 378 { 379 callerid_data *dat = *(it); 380 381 // Find me on their callerid list 382 if (!dat->accepting.erase(who)) 383 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "ERROR: Inconsistency detected in callerid state, please report (5)"); 384 } 385 386 userdata->wholistsme.clear(); 387 } 388 389 public: ModuleCallerID()390 ModuleCallerID() 391 : CTCTags::EventListener(this) 392 , cmd(this) 393 , api(this, cmd.extInfo) 394 , myumode(this, "callerid", 'g') 395 { 396 } 397 GetVersion()398 Version GetVersion() CXX11_OVERRIDE 399 { 400 return Version("Provides user mode g (callerid) which allows users to require that other users are on their whitelist before messaging them.", VF_COMMON | VF_VENDOR); 401 } 402 On005Numeric(std::map<std::string,std::string> & tokens)403 void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE 404 { 405 tokens["ACCEPT"] = ConvToStr(cmd.maxaccepts); 406 tokens["CALLERID"] = ConvToStr(myumode.GetModeChar()); 407 } 408 HandleMessage(User * user,const MessageTarget & target)409 ModResult HandleMessage(User* user, const MessageTarget& target) 410 { 411 if (!IS_LOCAL(user) || target.type != MessageTarget::TYPE_USER) 412 return MOD_RES_PASSTHRU; 413 414 User* dest = target.Get<User>(); 415 if (!dest->IsModeSet(myumode) || (user == dest)) 416 return MOD_RES_PASSTHRU; 417 418 if (user->HasPrivPermission("users/ignore-callerid")) 419 return MOD_RES_PASSTHRU; 420 421 callerid_data* dat = cmd.extInfo.get(dest, true); 422 if (!dat->accepting.count(user)) 423 { 424 time_t now = ServerInstance->Time(); 425 /* +g and *not* accepted */ 426 user->WriteNumeric(ERR_TARGUMODEG, dest->nick, "is in +g mode (server-side ignore)."); 427 if (now > (dat->lastnotify + (time_t)notify_cooldown)) 428 { 429 user->WriteNumeric(RPL_TARGNOTIFY, dest->nick, "has been informed that you messaged them."); 430 dest->WriteRemoteNumeric(RPL_UMODEGMSG, user->nick, InspIRCd::Format("%s@%s", user->ident.c_str(), user->GetDisplayedHost().c_str()), InspIRCd::Format("is messaging you, and you have user mode +g set. Use /ACCEPT +%s to allow.", 431 user->nick.c_str())); 432 dat->lastnotify = now; 433 } 434 return MOD_RES_DENY; 435 } 436 return MOD_RES_PASSTHRU; 437 } 438 OnUserPreMessage(User * user,const MessageTarget & target,MessageDetails & details)439 ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE 440 { 441 return HandleMessage(user, target); 442 } 443 OnUserPreTagMessage(User * user,const MessageTarget & target,CTCTags::TagMessageDetails & details)444 ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) CXX11_OVERRIDE 445 { 446 return HandleMessage(user, target); 447 } 448 OnUserPostNick(User * user,const std::string & oldnick)449 void OnUserPostNick(User* user, const std::string& oldnick) CXX11_OVERRIDE 450 { 451 if (!tracknick) 452 RemoveFromAllAccepts(user); 453 } 454 OnUserQuit(User * user,const std::string & message,const std::string & oper_message)455 void OnUserQuit(User* user, const std::string& message, const std::string& oper_message) CXX11_OVERRIDE 456 { 457 RemoveFromAllAccepts(user); 458 } 459 ReadConfig(ConfigStatus & status)460 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE 461 { 462 ConfigTag* tag = ServerInstance->Config->ConfValue("callerid"); 463 cmd.maxaccepts = tag->getUInt("maxaccepts", 30); 464 tracknick = tag->getBool("tracknick"); 465 notify_cooldown = tag->getDuration("cooldown", 60); 466 } 467 Prioritize()468 void Prioritize() CXX11_OVERRIDE 469 { 470 // Want to be after modules like silence or services_account 471 ServerInstance->Modules->SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST); 472 } 473 }; 474 475 MODULE_INIT(ModuleCallerID) 476