]>
Commit | Line | Data |
---|---|---|
4ef511eb AC |
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) | |
d63f3f80 | 47 | #define RETURN_INVALID { recursion_depth--; return EXTBAN_INVALID; } |
4ef511eb AC |
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); | |
d63f3f80 | 54 | static int recursion_depth = 0; |
4ef511eb | 55 | |
04f832b7 | 56 | DECLARE_MODULE_AV2(extb_extended, _modinit, _moddeinit, NULL, NULL, NULL, NULL, NULL, NULL); |
4ef511eb AC |
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; | |
a2bc8af8 | 91 | int allowed_nodes = 11; |
4ef511eb AC |
92 | size_t datalen; |
93 | ||
a2bc8af8 | 94 | if (recursion_depth >= 5) { |
d63f3f80 AC |
95 | DEBUG("combo invalid: recursion depth too high"); |
96 | return EXTBAN_INVALID; | |
97 | } | |
98 | ||
4ef511eb AC |
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 | ||
d63f3f80 AC |
141 | recursion_depth++; |
142 | ||
2e548a8a | 143 | while (--allowed_nodes) { |
4ef511eb AC |
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 ~"); | |
d63f3f80 | 153 | RETURN_INVALID; |
4ef511eb AC |
154 | } |
155 | } | |
156 | ||
157 | f = extban_table[(unsigned char) *p++]; | |
158 | if (!f) { | |
159 | DEBUG("combo invalid: non-existant child extban"); | |
d63f3f80 | 160 | RETURN_INVALID; |
4ef511eb AC |
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"); | |
d63f3f80 | 178 | RETURN_INVALID; |
4ef511eb AC |
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"); | |
d63f3f80 | 200 | RETURN_INVALID; |
4ef511eb AC |
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"); | |
d63f3f80 | 230 | RETURN_INVALID; |
4ef511eb AC |
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"); | |
d63f3f80 | 248 | RETURN_INVALID; |
4ef511eb AC |
249 | } |
250 | ||
251 | if (p == banend) { | |
252 | DEBUG("combo invalid: banend after ','"); | |
d63f3f80 | 253 | RETURN_INVALID; |
4ef511eb AC |
254 | } |
255 | } | |
256 | ||
5984986b AC |
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 | ||
d63f3f80 AC |
263 | recursion_depth--; |
264 | ||
4ef511eb AC |
265 | if (is_and) |
266 | return have_result ? EXTBAN_NOMATCH : EXTBAN_MATCH; | |
267 | else | |
268 | return have_result ? EXTBAN_MATCH : EXTBAN_NOMATCH; | |
269 | } |