]> jfr.im git - solanum.git/blob - extensions/extb_combi.c
extb_combi: relax recursion and complexity limits now that bancache for unjoined...
[solanum.git] / extensions / extb_combi.c
1 /*
2 * Extban that combines other extbans.
3 *
4 * Basic example:
5 * $&:~a,m:*!*@gateway/web/cgi-irc*
6 * Which means: match unidentified webchat users.
7 * ("m" is another new extban type, which just does a normal match).
8 *
9 * More complicated example:
10 * $&:~a,|:(m:*!*@gateway/web/foo,m:*!*@gateway/web/bar)
11 * Which means: unidentified and using the foo or bar gateway.
12 *
13 * Rules:
14 *
15 * - Optional pair of parens around data.
16 *
17 * - component bans are separated by commas, but commas between
18 * matching pairs of parens are skipped.
19 *
20 * - Unbalanced parens are an error.
21 *
22 * - Parens, commas and backslashes can be escaped by backslashes.
23 *
24 * - A backslash before any character other than a paren or backslash
25 * is just a backslash (backslash and character are both used).
26 *
27 * - Non-existant extbans are invalid.
28 * This is primarily for consistency with non-combined bans:
29 * the ircd does not let you set +b $f unless the 'f' extban is loaded,
30 * so setting $&:f should be impossible too.
31 *
32 * Issues:
33 * - Backslashes double inside nested bans.
34 * Hopefully acceptable because they should be rare.
35 *
36 * - Is performance good enough?
37 * I suspect it is, but have done no load testing.
38 */
39
40 #include "stdinc.h"
41 #include "modules.h"
42 #include "client.h"
43 #include "ircd.h"
44
45 // #define DEBUG(s) sendto_realops_snomask(SNO_DEBUG, L_NETWIDE, (s))
46 #define DEBUG(s)
47 #define RETURN_INVALID { recursion_depth--; return EXTBAN_INVALID; }
48
49 static int _modinit(void);
50 static void _moddeinit(void);
51 static int eb_or(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type);
52 static int eb_and(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type);
53 static int eb_combi(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type, int is_and);
54 static int recursion_depth = 0;
55
56 DECLARE_MODULE_AV1(extb_extended, _modinit, _moddeinit, NULL, NULL, NULL, "$Revision: 1 $");
57
58 static int
59 _modinit(void)
60 {
61 extban_table['&'] = eb_and;
62 extban_table['|'] = eb_or;
63
64 return 0;
65 }
66
67 static void
68 _moddeinit(void)
69 {
70 extban_table['&'] = NULL;
71 extban_table['|'] = NULL;
72 }
73
74 static int eb_or(const char *data, struct Client *client_p,
75 struct Channel *chptr, long mode_type)
76 {
77 return eb_combi(data, client_p, chptr, mode_type, FALSE);
78 }
79
80 static int eb_and(const char *data, struct Client *client_p,
81 struct Channel *chptr, long mode_type)
82 {
83 return eb_combi(data, client_p, chptr, mode_type, TRUE);
84 }
85
86 static int eb_combi(const char *data, struct Client *client_p,
87 struct Channel *chptr, long mode_type, int is_and)
88 {
89 const char *p, *banend;
90 int have_result = FALSE;
91 int allowed_nodes = 11;
92 size_t datalen;
93
94 if (recursion_depth >= 5) {
95 DEBUG("combo invalid: recursion depth too high");
96 return EXTBAN_INVALID;
97 }
98
99 if (EmptyString(data)) {
100 DEBUG("combo invalid: empty data");
101 return EXTBAN_INVALID;
102 }
103
104 datalen = strlen(data);
105 if (datalen > BANLEN) {
106 /* I'd be sad if this ever happened, but if it does we
107 * could overflow the buffer used below, so...
108 */
109 DEBUG("combo invalid: > BANLEN");
110 return EXTBAN_INVALID;
111 }
112 banend = data + datalen;
113
114 if (data[0] == '(') {
115 p = data + 1;
116 banend--;
117 if (*banend != ')') {
118 DEBUG("combo invalid: starting but no closing paren");
119 return EXTBAN_INVALID;
120 }
121 } else {
122 p = data;
123 }
124
125 /* Empty combibans are invalid. */
126 if (banend == p) {
127 DEBUG("combo invalid: no data (after removing parens)");
128 return EXTBAN_INVALID;
129 }
130
131 /* Implementation note:
132 * I want it to be impossible to set a syntactically invalid combi-ban.
133 * (mismatched parens).
134 * That is: valid_extban should return false for those.
135 * Ideally we do not parse the entire ban when actually matching it:
136 * we can just short-circuit if we already know the ban is valid.
137 * Unfortunately there is no separate hook or mode_type for validation,
138 * so we always keep parsing even after we have determined a result.
139 */
140
141 recursion_depth++;
142
143 while (--allowed_nodes) {
144 int invert = FALSE;
145 char *child_data, child_data_buf[BANLEN];
146 ExtbanFunc f;
147
148 if (*p == '~') {
149 invert = TRUE;
150 p++;
151 if (p == banend) {
152 DEBUG("combo invalid: no data after ~");
153 RETURN_INVALID;
154 }
155 }
156
157 f = extban_table[(unsigned char) *p++];
158 if (!f) {
159 DEBUG("combo invalid: non-existant child extban");
160 RETURN_INVALID;
161 }
162
163 if (*p == ':') {
164 unsigned int parencount = 0;
165 int escaped = FALSE, done = FALSE;
166 char *o;
167
168 p++;
169
170 /* Possible optimization: we can skip the actual copy if
171 * we already have_result.
172 */
173 o = child_data = child_data_buf;
174 while (TRUE) {
175 if (p == banend) {
176 if (parencount) {
177 DEBUG("combo invalid: EOD while in parens");
178 RETURN_INVALID;
179 }
180 break;
181 }
182
183 if (escaped) {
184 if (*p != '(' && *p != ')' && *p != '\\' && *p != ',')
185 *o++ = '\\';
186 *o++ = *p++;
187 escaped = FALSE;
188 } else {
189 switch (*p) {
190 case '\\':
191 escaped = TRUE;
192 break;
193 case '(':
194 parencount++;
195 *o++ = *p;
196 break;
197 case ')':
198 if (!parencount) {
199 DEBUG("combo invalid: negative parencount");
200 RETURN_INVALID;
201 }
202 parencount--;
203 *o++ = *p;
204 break;
205 case ',':
206 if (parencount)
207 *o++ = *p;
208 else
209 done = TRUE;
210 break;
211 default:
212 *o++ = *p;
213 break;
214 }
215 if (done)
216 break;
217 p++;
218 }
219 }
220 *o = '\0';
221 } else {
222 child_data = NULL;
223 }
224
225 if (!have_result) {
226 int child_result = f(child_data, client_p, chptr, mode_type);
227
228 if (child_result == EXTBAN_INVALID) {
229 DEBUG("combo invalid: child invalid");
230 RETURN_INVALID;
231 }
232
233 /* Convert child_result to a plain boolean result */
234 if (invert)
235 child_result = child_result == EXTBAN_NOMATCH;
236 else
237 child_result = child_result == EXTBAN_MATCH;
238
239 if (is_and ? !child_result : child_result)
240 have_result = TRUE;
241 }
242
243 if (p == banend)
244 break;
245
246 if (*p++ != ',') {
247 DEBUG("combo invalid: no ',' after ban");
248 RETURN_INVALID;
249 }
250
251 if (p == banend) {
252 DEBUG("combo invalid: banend after ','");
253 RETURN_INVALID;
254 }
255 }
256
257 /* at this point, *p should == banend */
258 if (p != banend) {
259 DEBUG("combo invalid: more child extbans than allowed");
260 RETURN_INVALID;
261 }
262
263 recursion_depth--;
264
265 if (is_and)
266 return have_result ? EXTBAN_NOMATCH : EXTBAN_MATCH;
267 else
268 return have_result ? EXTBAN_MATCH : EXTBAN_NOMATCH;
269 }