]> jfr.im git - solanum.git/blob - extensions/extb_combi.c
m_stats: z: remove unnecessary casting and fix format strings
[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 static const char extb_desc[] = "Combination ($&, $|) extban types";
46
47 // #define MOD_DEBUG(s) sendto_realops_snomask(SNO_DEBUG, L_NETWIDE, (s))
48 #define MOD_DEBUG(s)
49 #define RETURN_INVALID { recursion_depth--; return EXTBAN_INVALID; }
50
51 static int _modinit(void);
52 static void _moddeinit(void);
53 static int eb_or(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type);
54 static int eb_and(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type);
55 static int eb_combi(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type, bool is_and);
56 static int recursion_depth = 0;
57
58 DECLARE_MODULE_AV2(extb_extended, _modinit, _moddeinit, NULL, NULL, NULL, NULL, NULL, extb_desc);
59
60 static int
61 _modinit(void)
62 {
63 extban_table['&'] = eb_and;
64 extban_table['|'] = eb_or;
65
66 return 0;
67 }
68
69 static void
70 _moddeinit(void)
71 {
72 extban_table['&'] = NULL;
73 extban_table['|'] = NULL;
74 }
75
76 static int eb_or(const char *data, struct Client *client_p,
77 struct Channel *chptr, long mode_type)
78 {
79 return eb_combi(data, client_p, chptr, mode_type, false);
80 }
81
82 static int eb_and(const char *data, struct Client *client_p,
83 struct Channel *chptr, long mode_type)
84 {
85 return eb_combi(data, client_p, chptr, mode_type, true);
86 }
87
88 static int eb_combi(const char *data, struct Client *client_p,
89 struct Channel *chptr, long mode_type, bool is_and)
90 {
91 const char *p, *banend;
92 bool have_result = false;
93 int allowed_nodes = 11;
94 size_t datalen;
95
96 if (recursion_depth >= 5) {
97 MOD_DEBUG("combo invalid: recursion depth too high");
98 return EXTBAN_INVALID;
99 }
100
101 if (EmptyString(data)) {
102 MOD_DEBUG("combo invalid: empty data");
103 return EXTBAN_INVALID;
104 }
105
106 datalen = strlen(data);
107 if (datalen > BANLEN) {
108 /* I'd be sad if this ever happened, but if it does we
109 * could overflow the buffer used below, so...
110 */
111 MOD_DEBUG("combo invalid: > BANLEN");
112 return EXTBAN_INVALID;
113 }
114 banend = data + datalen;
115
116 if (data[0] == '(') {
117 p = data + 1;
118 banend--;
119 if (*banend != ')') {
120 MOD_DEBUG("combo invalid: starting but no closing paren");
121 return EXTBAN_INVALID;
122 }
123 } else {
124 p = data;
125 }
126
127 /* Empty combibans are invalid. */
128 if (banend == p) {
129 MOD_DEBUG("combo invalid: no data (after removing parens)");
130 return EXTBAN_INVALID;
131 }
132
133 /* Implementation note:
134 * I want it to be impossible to set a syntactically invalid combi-ban.
135 * (mismatched parens).
136 * That is: valid_extban should return false for those.
137 * Ideally we do not parse the entire ban when actually matching it:
138 * we can just short-circuit if we already know the ban is valid.
139 * Unfortunately there is no separate hook or mode_type for validation,
140 * so we always keep parsing even after we have determined a result.
141 */
142
143 recursion_depth++;
144
145 while (--allowed_nodes) {
146 bool invert = false;
147 char *child_data, child_data_buf[BANLEN];
148 ExtbanFunc f;
149
150 if (*p == '~') {
151 invert = true;
152 p++;
153 if (p == banend) {
154 MOD_DEBUG("combo invalid: no data after ~");
155 RETURN_INVALID;
156 }
157 }
158
159 f = extban_table[(unsigned char) *p++];
160 if (!f) {
161 MOD_DEBUG("combo invalid: non-existant child extban");
162 RETURN_INVALID;
163 }
164
165 if (*p == ':') {
166 unsigned int parencount = 0;
167 bool escaped = false, done = false;
168 char *o;
169
170 p++;
171
172 /* Possible optimization: we can skip the actual copy if
173 * we already have_result.
174 */
175 o = child_data = child_data_buf;
176 while (true) {
177 if (p == banend) {
178 if (parencount) {
179 MOD_DEBUG("combo invalid: EOD while in parens");
180 RETURN_INVALID;
181 }
182 break;
183 }
184
185 if (escaped) {
186 if (*p != '(' && *p != ')' && *p != '\\' && *p != ',')
187 *o++ = '\\';
188 *o++ = *p++;
189 escaped = false;
190 } else {
191 switch (*p) {
192 case '\\':
193 escaped = true;
194 break;
195 case '(':
196 parencount++;
197 *o++ = *p;
198 break;
199 case ')':
200 if (!parencount) {
201 MOD_DEBUG("combo invalid: negative parencount");
202 RETURN_INVALID;
203 }
204 parencount--;
205 *o++ = *p;
206 break;
207 case ',':
208 if (parencount)
209 *o++ = *p;
210 else
211 done = true;
212 break;
213 default:
214 *o++ = *p;
215 break;
216 }
217 if (done)
218 break;
219 p++;
220 }
221 }
222 *o = '\0';
223 } else {
224 child_data = NULL;
225 }
226
227 if (!have_result) {
228 int child_result = f(child_data, client_p, chptr, mode_type);
229
230 if (child_result == EXTBAN_INVALID) {
231 MOD_DEBUG("combo invalid: child invalid");
232 RETURN_INVALID;
233 }
234
235 /* Convert child_result to a plain boolean result */
236 if (invert)
237 child_result = child_result == EXTBAN_NOMATCH;
238 else
239 child_result = child_result == EXTBAN_MATCH;
240
241 if (is_and ? !child_result : child_result)
242 have_result = true;
243 }
244
245 if (p == banend)
246 break;
247
248 if (*p++ != ',') {
249 MOD_DEBUG("combo invalid: no ',' after ban");
250 RETURN_INVALID;
251 }
252
253 if (p == banend) {
254 MOD_DEBUG("combo invalid: banend after ','");
255 RETURN_INVALID;
256 }
257 }
258
259 /* at this point, *p should == banend */
260 if (p != banend) {
261 MOD_DEBUG("combo invalid: more child extbans than allowed");
262 RETURN_INVALID;
263 }
264
265 recursion_depth--;
266
267 if (is_and)
268 return have_result ? EXTBAN_NOMATCH : EXTBAN_MATCH;
269 else
270 return have_result ? EXTBAN_MATCH : EXTBAN_NOMATCH;
271 }