]> jfr.im git - solanum.git/blame - extensions/extb_combi.c
import marienz's extb_combi module (ref #74)
[solanum.git] / extensions / extb_combi.c
CommitLineData
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
48static int _modinit(void);
49static void _moddeinit(void);
50static int eb_or(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type);
51static int eb_and(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type);
52static int eb_combi(const char *data, struct Client *client_p, struct Channel *chptr, long mode_type, int is_and);
53
54
55DECLARE_MODULE_AV1(extb_extended, _modinit, _moddeinit, NULL, NULL, NULL, "$Revision: 1 $");
56
57static int
58_modinit(void)
59{
60 extban_table['&'] = eb_and;
61 extban_table['|'] = eb_or;
62
63 return 0;
64}
65
66static void
67_moddeinit(void)
68{
69 extban_table['&'] = NULL;
70 extban_table['|'] = NULL;
71}
72
73static 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
79static 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
85static 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}