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