1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2018 linuxdaemon <linuxdaemon.irc@gmail.com>
5  *   Copyright (C) 2018 B00mX0r <b00mx0r@aureus.pw>
6  *   Copyright (C) 2017-2019 Sadie Powell <sadie@witchery.services>
7  *   Copyright (C) 2013-2014, 2016 Attila Molnar <attilamolnar@hush.com>
8  *
9  * This file is part of InspIRCd.  InspIRCd is free software: you can
10  * redistribute it and/or modify it under the terms of the GNU General Public
11  * License as published by the Free Software Foundation, version 2.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "inspircd.h"
23 #include "listmode.h"
24 
ListModeBase(Module * Creator,const std::string & Name,char modechar,const std::string & eolstr,unsigned int lnum,unsigned int eolnum,bool autotidy)25 ListModeBase::ListModeBase(Module* Creator, const std::string& Name, char modechar, const std::string& eolstr, unsigned int lnum, unsigned int eolnum, bool autotidy)
26 	: ModeHandler(Creator, Name, modechar, PARAM_ALWAYS, MODETYPE_CHANNEL, MC_LIST)
27 	, listnumeric(lnum)
28 	, endoflistnumeric(eolnum)
29 	, endofliststring(eolstr)
30 	, tidy(autotidy)
31 	, extItem(name + "_mode_list", ExtensionItem::EXT_CHANNEL, Creator)
32 {
33 	list = true;
34 }
35 
DisplayList(User * user,Channel * channel)36 void ListModeBase::DisplayList(User* user, Channel* channel)
37 {
38 	ChanData* cd = extItem.get(channel);
39 	if (cd)
40 	{
41 		for (ModeList::const_iterator it = cd->list.begin(); it != cd->list.end(); ++it)
42 		{
43 			user->WriteNumeric(listnumeric, channel->name, it->mask, it->setter, (unsigned long) it->time);
44 		}
45 	}
46 	user->WriteNumeric(endoflistnumeric, channel->name, endofliststring);
47 }
48 
DisplayEmptyList(User * user,Channel * channel)49 void ListModeBase::DisplayEmptyList(User* user, Channel* channel)
50 {
51 	user->WriteNumeric(endoflistnumeric, channel->name, endofliststring);
52 }
53 
RemoveMode(Channel * channel,Modes::ChangeList & changelist)54 void ListModeBase::RemoveMode(Channel* channel, Modes::ChangeList& changelist)
55 {
56 	ChanData* cd = extItem.get(channel);
57 	if (cd)
58 	{
59 		for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); it++)
60 		{
61 			changelist.push_remove(this, it->mask);
62 		}
63 	}
64 }
65 
DoRehash()66 void ListModeBase::DoRehash()
67 {
68 	ConfigTagList tags = ServerInstance->Config->ConfTags("maxlist");
69 	limitlist newlimits;
70 	bool seen_default = false;
71 	for (ConfigIter i = tags.first; i != tags.second; i++)
72 	{
73 		ConfigTag* c = i->second;
74 
75 		const std::string mname = c->getString("mode");
76 		if (!mname.empty() && !stdalgo::string::equalsci(mname, name) && !(mname.length() == 1 && GetModeChar() == mname[0]))
77 			continue;
78 
79 		ListLimit limit(c->getString("chan", "*", 1), c->getUInt("limit", DEFAULT_LIST_SIZE));
80 
81 		if (limit.mask.empty())
82 			throw ModuleException(InspIRCd::Format("<maxlist:chan> is empty, at %s", c->getTagLocation().c_str()));
83 
84 		if (limit.mask == "*" || limit.mask == "#*")
85 			seen_default = true;
86 
87 		newlimits.push_back(limit);
88 	}
89 
90 	// If no default limit has been specified then insert one.
91 	if (!seen_default)
92 	{
93 		ServerInstance->Logs->Log("MODE", LOG_DEBUG, "No default <maxlist> entry was found for the %s mode; defaulting to %u",
94 			name.c_str(), DEFAULT_LIST_SIZE);
95 		newlimits.push_back(ListLimit("*", DEFAULT_LIST_SIZE));
96 	}
97 
98 	// Most of the time our settings are unchanged, so we can avoid iterating the chanlist
99 	if (chanlimits == newlimits)
100 		return;
101 
102 	chanlimits.swap(newlimits);
103 
104 	const chan_hash& chans = ServerInstance->GetChans();
105 	for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i)
106 	{
107 		ChanData* cd = extItem.get(i->second);
108 		if (cd)
109 			cd->maxitems = -1;
110 	}
111 }
112 
FindLimit(const std::string & channame)113 unsigned int ListModeBase::FindLimit(const std::string& channame)
114 {
115 	for (limitlist::iterator it = chanlimits.begin(); it != chanlimits.end(); ++it)
116 	{
117 		if (InspIRCd::Match(channame, it->mask))
118 		{
119 			// We have a pattern matching the channel
120 			return it->limit;
121 		}
122 	}
123 	return 0;
124 }
125 
GetLimitInternal(const std::string & channame,ChanData * cd)126 unsigned int ListModeBase::GetLimitInternal(const std::string& channame, ChanData* cd)
127 {
128 	if (cd->maxitems < 0)
129 		cd->maxitems = FindLimit(channame);
130 	return cd->maxitems;
131 }
132 
GetLimit(Channel * channel)133 unsigned int ListModeBase::GetLimit(Channel* channel)
134 {
135 	ChanData* cd = extItem.get(channel);
136 	if (!cd) // just find the limit
137 		return FindLimit(channel->name);
138 
139 	return GetLimitInternal(channel->name, cd);
140 }
141 
GetLowerLimit()142 unsigned int ListModeBase::GetLowerLimit()
143 {
144 	if (chanlimits.empty())
145 		return DEFAULT_LIST_SIZE;
146 
147 	unsigned int limit = UINT_MAX;
148 	for (limitlist::iterator iter = chanlimits.begin(); iter != chanlimits.end(); ++iter)
149 	{
150 		if (iter->limit < limit)
151 			limit = iter->limit;
152 	}
153 	return limit;
154 }
155 
OnModeChange(User * source,User *,Channel * channel,std::string & parameter,bool adding)156 ModeAction ListModeBase::OnModeChange(User* source, User*, Channel* channel, std::string &parameter, bool adding)
157 {
158 	// Try and grab the list
159 	ChanData* cd = extItem.get(channel);
160 
161 	if (adding)
162 	{
163 		if (tidy)
164 			ModeParser::CleanMask(parameter);
165 
166 		// If there was no list
167 		if (!cd)
168 		{
169 			// Make one
170 			cd = new ChanData;
171 			extItem.set(channel, cd);
172 		}
173 
174 		// Check if the item already exists in the list
175 		for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); it++)
176 		{
177 			if (parameter == it->mask)
178 			{
179 				/* Give a subclass a chance to error about this */
180 				TellAlreadyOnList(source, channel, parameter);
181 
182 				// it does, deny the change
183 				return MODEACTION_DENY;
184 			}
185 		}
186 
187 		if ((IS_LOCAL(source)) && (cd->list.size() >= GetLimitInternal(channel->name, cd)))
188 		{
189 			/* List is full, give subclass a chance to send a custom message */
190 			TellListTooLong(source, channel, parameter);
191 			return MODEACTION_DENY;
192 		}
193 
194 		/* Ok, it *could* be allowed, now give someone subclassing us
195 		 * a chance to validate the parameter.
196 		 * The param is passed by reference, so they can both modify it
197 		 * and tell us if we allow it or not.
198 		 *
199 		 * eg, the subclass could:
200 		 * 1) allow
201 		 * 2) 'fix' parameter and then allow
202 		 * 3) deny
203 		 */
204 		if (ValidateParam(source, channel, parameter))
205 		{
206 			// And now add the mask onto the list...
207 			cd->list.push_back(ListItem(parameter, source->nick, ServerInstance->Time()));
208 			return MODEACTION_ALLOW;
209 		}
210 		else
211 		{
212 			/* If they deny it they have the job of giving an error message */
213 			return MODEACTION_DENY;
214 		}
215 	}
216 	else
217 	{
218 		// We're taking the mode off
219 		if (cd)
220 		{
221 			for (ModeList::iterator it = cd->list.begin(); it != cd->list.end(); ++it)
222 			{
223 				if (parameter == it->mask)
224 				{
225 					stdalgo::vector::swaperase(cd->list, it);
226 					return MODEACTION_ALLOW;
227 				}
228 			}
229 		}
230 
231 		/* Tried to remove something that wasn't set */
232 		TellNotSet(source, channel, parameter);
233 		return MODEACTION_DENY;
234 	}
235 }
236 
ValidateParam(User *,Channel *,std::string &)237 bool ListModeBase::ValidateParam(User*, Channel*, std::string&)
238 {
239 	return true;
240 }
241 
OnParameterMissing(User *,User *,Channel *)242 void ListModeBase::OnParameterMissing(User*, User*, Channel*)
243 {
244 	// Intentionally left blank.
245 }
246 
TellListTooLong(User * source,Channel * channel,std::string & parameter)247 void ListModeBase::TellListTooLong(User* source, Channel* channel, std::string& parameter)
248 {
249 	source->WriteNumeric(ERR_BANLISTFULL, channel->name, parameter, mode, InspIRCd::Format("Channel %s list is full", name.c_str()));
250 }
251 
TellAlreadyOnList(User * source,Channel * channel,std::string & parameter)252 void ListModeBase::TellAlreadyOnList(User* source, Channel* channel, std::string& parameter)
253 {
254 	source->WriteNumeric(ERR_LISTMODEALREADYSET, channel->name, parameter, mode, InspIRCd::Format("Channel %s list already contains %s", name.c_str(), parameter.c_str()));
255 }
256 
TellNotSet(User * source,Channel * channel,std::string & parameter)257 void ListModeBase::TellNotSet(User* source, Channel* channel, std::string& parameter)
258 {
259 	source->WriteNumeric(ERR_LISTMODENOTSET, channel->name, parameter, mode, InspIRCd::Format("Channel %s list does not contain %s", name.c_str(), parameter.c_str()));
260 }
261