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