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