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) 2014, 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 "xline.h" 24 25 class CommandClearChan : public Command 26 { 27 public: 28 Channel* activechan; 29 CommandClearChan(Module * Creator)30 CommandClearChan(Module* Creator) 31 : Command(Creator, "CLEARCHAN", 1, 3) 32 { 33 syntax = "<channel> [KILL|KICK|G|Z] [:<reason>]"; 34 flags_needed = 'o'; 35 36 // Stop the linking mod from forwarding ENCAP'd CLEARCHAN commands, see below why 37 force_manual_route = true; 38 } 39 Handle(User * user,const Params & parameters)40 CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE 41 { 42 Channel* chan = activechan = ServerInstance->FindChan(parameters[0]); 43 if (!chan) 44 { 45 user->WriteNotice("The channel " + parameters[0] + " does not exist."); 46 return CMD_FAILURE; 47 } 48 49 // See what method the oper wants to use, default to KILL 50 std::string method("KILL"); 51 if (parameters.size() > 1) 52 { 53 method = parameters[1]; 54 std::transform(method.begin(), method.end(), method.begin(), ::toupper); 55 } 56 57 XLineFactory* xlf = NULL; 58 bool kick = (method == "KICK"); 59 if ((!kick) && (method != "KILL")) 60 { 61 if ((method != "Z") && (method != "G")) 62 { 63 user->WriteNotice("Invalid method for clearing " + chan->name); 64 return CMD_FAILURE; 65 } 66 67 xlf = ServerInstance->XLines->GetFactory(method); 68 if (!xlf) 69 return CMD_FAILURE; 70 } 71 72 const std::string reason = parameters.size() > 2 ? parameters.back() : "Clearing " + chan->name; 73 74 if (!user->server->IsSilentULine()) 75 ServerInstance->SNO->WriteToSnoMask((IS_LOCAL(user) ? 'a' : 'A'), user->nick + " has cleared \002" + chan->name + "\002 (" + method + "): " + reason); 76 77 user->WriteNotice("Clearing \002" + chan->name + "\002 (" + method + "): " + reason); 78 79 { 80 // Route this command manually so it is sent before the QUITs we are about to generate. 81 // The idea is that by the time our QUITs reach the next hop, it has already removed all their 82 // clients from the channel, meaning victims on other servers won't see the victims on this 83 // server quitting. 84 CommandBase::Params eparams; 85 eparams.push_back(chan->name); 86 eparams.push_back(method); 87 eparams.push_back(":"); 88 eparams.back().append(reason); 89 ServerInstance->PI->BroadcastEncap(this->name, eparams, user, user); 90 } 91 92 // Attach to the appropriate hook so we're able to hide the QUIT/KICK messages 93 Implementation hook = (kick ? I_OnUserKick : I_OnBuildNeighborList); 94 ServerInstance->Modules->Attach(hook, creator); 95 96 std::string mask; 97 // Now remove all local non-opers from the channel 98 Channel::MemberMap& users = chan->userlist; 99 for (Channel::MemberMap::iterator i = users.begin(); i != users.end(); ) 100 { 101 User* curr = i->first; 102 const Channel::MemberMap::iterator currit = i; 103 ++i; 104 105 if (!IS_LOCAL(curr) || curr->IsOper()) 106 continue; 107 108 // If kicking users, remove them and skip the QuitUser() 109 if (kick) 110 { 111 chan->KickUser(ServerInstance->FakeClient, currit, reason); 112 continue; 113 } 114 115 // If we are banning users then create the XLine and add it 116 if (xlf) 117 { 118 XLine* xline; 119 try 120 { 121 mask = ((method[0] == 'Z') ? curr->GetIPString() : "*@" + curr->GetRealHost()); 122 xline = xlf->Generate(ServerInstance->Time(), 60*60, user->nick, reason, mask); 123 } 124 catch (ModuleException&) 125 { 126 // Nothing, move on to the next user 127 continue; 128 } 129 130 if (!ServerInstance->XLines->AddLine(xline, user)) 131 delete xline; 132 } 133 134 ServerInstance->Users->QuitUser(curr, reason); 135 } 136 137 ServerInstance->Modules->Detach(hook, creator); 138 if (xlf) 139 ServerInstance->XLines->ApplyLines(); 140 141 return CMD_SUCCESS; 142 } 143 }; 144 145 class ModuleClearChan : public Module 146 { 147 CommandClearChan cmd; 148 149 public: ModuleClearChan()150 ModuleClearChan() 151 : cmd(this) 152 { 153 } 154 init()155 void init() CXX11_OVERRIDE 156 { 157 // Only attached while we are working; don't react to events otherwise 158 ServerInstance->Modules->DetachAll(this); 159 } 160 OnBuildNeighborList(User * source,IncludeChanList & include,std::map<User *,bool> & exception)161 void OnBuildNeighborList(User* source, IncludeChanList& include, std::map<User*, bool>& exception) CXX11_OVERRIDE 162 { 163 bool found = false; 164 for (IncludeChanList::iterator i = include.begin(); i != include.end(); ++i) 165 { 166 if ((*i)->chan == cmd.activechan) 167 { 168 // Don't show the QUIT to anyone in the channel by default 169 include.erase(i); 170 found = true; 171 break; 172 } 173 } 174 175 const Channel::MemberMap& users = cmd.activechan->GetUsers(); 176 for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) 177 { 178 LocalUser* curr = IS_LOCAL(i->first); 179 if (!curr) 180 continue; 181 182 if (curr->IsOper()) 183 { 184 // If another module has removed the channel we're working on from the list of channels 185 // to consider for sending the QUIT to then don't add exceptions for opers, because the 186 // module before us doesn't want them to see it or added the exceptions already. 187 // If there is a value for this oper in excepts already, this won't overwrite it. 188 if (found) 189 exception.insert(std::make_pair(curr, true)); 190 continue; 191 } 192 else if (!include.empty() && curr->chans.size() > 1) 193 { 194 // This is a victim and potentially has another common channel with the user quitting, 195 // add a negative exception overwriting the previous value, if any. 196 exception[curr] = false; 197 } 198 } 199 } 200 OnUserKick(User * source,Membership * memb,const std::string & reason,CUList & excepts)201 void OnUserKick(User* source, Membership* memb, const std::string& reason, CUList& excepts) CXX11_OVERRIDE 202 { 203 // Hide the KICK from all non-opers 204 User* leaving = memb->user; 205 const Channel::MemberMap& users = memb->chan->GetUsers(); 206 for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) 207 { 208 User* curr = i->first; 209 if ((IS_LOCAL(curr)) && (!curr->IsOper()) && (curr != leaving)) 210 excepts.insert(curr); 211 } 212 } 213 GetVersion()214 Version GetVersion() CXX11_OVERRIDE 215 { 216 return Version("Adds the /CLEARCHAN command which allows server operators to mass-punish the members of a channel.", VF_VENDOR|VF_OPTCOMMON); 217 } 218 }; 219 220 MODULE_INIT(ModuleClearChan) 221