]>
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 | ||
eeabf33a EM |
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) | |
d63f3f80 | 49 | #define RETURN_INVALID { recursion_depth--; return EXTBAN_INVALID; } |
4ef511eb AC |
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, int is_and); | |
d63f3f80 | 56 | static int recursion_depth = 0; |
4ef511eb | 57 | |
c81afd15 | 58 | DECLARE_MODULE_AV2(extb_extended, _modinit, _moddeinit, NULL, NULL, NULL, NULL, NULL, extb_desc); |
4ef511eb AC |
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, int is_and) | |
90 | { | |
91 | const char *p, *banend; | |
92 | int have_result = FALSE; | |
a2bc8af8 | 93 | int allowed_nodes = 11; |
4ef511eb AC |
94 | size_t datalen; |
95 | ||
a2bc8af8 | 96 | if (recursion_depth >= 5) { |
eeabf33a | 97 | MOD_DEBUG("combo invalid: recursion depth too high"); |
d63f3f80 AC |
98 | return EXTBAN_INVALID; |
99 | } | |
100 | ||
4ef511eb | 101 | if (EmptyString(data)) { |
eeabf33a | 102 | MOD_DEBUG("combo invalid: empty data"); |
4ef511eb AC |
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 | */ | |
eeabf33a | 111 | MOD_DEBUG("combo invalid: > BANLEN"); |
4ef511eb AC |
112 | return EXTBAN_INVALID; |
113 | } | |
114 | banend = data + datalen; | |
115 | ||
116 | if (data[0] == '(') { | |
117 | p = data + 1; | |
118 | banend--; | |
119 | if (*banend != ')') { | |
eeabf33a | 120 | MOD_DEBUG("combo invalid: starting but no closing paren"); |
4ef511eb AC |
121 | return EXTBAN_INVALID; |
122 | } | |
123 | } else { | |
124 | p = data; | |
125 | } | |
126 | ||
127 | /* Empty combibans are invalid. */ | |
128 | if (banend == p) { | |
eeabf33a | 129 | MOD_DEBUG("combo invalid: no data (after removing parens)"); |
4ef511eb AC |
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 | ||
d63f3f80 AC |
143 | recursion_depth++; |
144 | ||
2e548a8a | 145 | while (--allowed_nodes) { |
4ef511eb AC |
146 | int 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) { | |
eeabf33a | 154 | MOD_DEBUG("combo invalid: no data after ~"); |
d63f3f80 | 155 | RETURN_INVALID; |
4ef511eb AC |
156 | } |
157 | } | |
158 | ||
159 | f = extban_table[(unsigned char) *p++]; | |
160 | if (!f) { | |
eeabf33a | 161 | MOD_DEBUG("combo invalid: non-existant child extban"); |
d63f3f80 | 162 | RETURN_INVALID; |
4ef511eb AC |
163 | } |
164 | ||
165 | if (*p == ':') { | |
166 | unsigned int parencount = 0; | |
167 | int 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) { | |
eeabf33a | 179 | MOD_DEBUG("combo invalid: EOD while in parens"); |
d63f3f80 | 180 | RETURN_INVALID; |
4ef511eb AC |
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) { | |
eeabf33a | 201 | MOD_DEBUG("combo invalid: negative parencount"); |
d63f3f80 | 202 | RETURN_INVALID; |
4ef511eb AC |
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) { | |
eeabf33a | 231 | MOD_DEBUG("combo invalid: child invalid"); |
d63f3f80 | 232 | RETURN_INVALID; |
4ef511eb AC |
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++ != ',') { | |
eeabf33a | 249 | MOD_DEBUG("combo invalid: no ',' after ban"); |
d63f3f80 | 250 | RETURN_INVALID; |
4ef511eb AC |
251 | } |
252 | ||
253 | if (p == banend) { | |
eeabf33a | 254 | MOD_DEBUG("combo invalid: banend after ','"); |
d63f3f80 | 255 | RETURN_INVALID; |
4ef511eb AC |
256 | } |
257 | } | |
258 | ||
5984986b AC |
259 | /* at this point, *p should == banend */ |
260 | if (p != banend) { | |
eeabf33a | 261 | MOD_DEBUG("combo invalid: more child extbans than allowed"); |
5984986b AC |
262 | RETURN_INVALID; |
263 | } | |
264 | ||
d63f3f80 AC |
265 | recursion_depth--; |
266 | ||
4ef511eb AC |
267 | if (is_and) |
268 | return have_result ? EXTBAN_NOMATCH : EXTBAN_MATCH; | |
269 | else | |
270 | return have_result ? EXTBAN_MATCH : EXTBAN_NOMATCH; | |
271 | } |