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 ¶meter, 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