]>
Commit | Line | Data |
---|---|---|
add80afd EM |
1 | /* |
2 | * charybdis: A slightly useful ircd. | |
3 | * blacklist.c: Manages DNS blacklist entries and lookups | |
4 | * | |
5 | * Copyright (C) 2006-2011 charybdis development team | |
6 | * | |
7 | * Redistribution and use in source and binary forms, with or without | |
8 | * modification, are permitted provided that the following conditions are | |
9 | * met: | |
10 | * | |
11 | * 1. Redistributions of source code must retain the above copyright notice, | |
12 | * this list of conditions and the following disclaimer. | |
13 | * | |
14 | * 2. Redistributions in binary form must reproduce the above copyright | |
15 | * notice, this list of conditions and the following disclaimer in the | |
16 | * documentation and/or other materials provided with the distribution. | |
17 | * | |
18 | * 3. The name of the author may not be used to endorse or promote products | |
19 | * derived from this software without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
22 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
23 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
24 | * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, | |
25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
26 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
29 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING | |
30 | * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
31 | * POSSIBILITY OF SUCH DAMAGE. | |
32 | */ | |
33 | ||
34 | /* Originally written for charybdis circa 2006 (by nenolod?). | |
35 | * Tweaked for authd. Some functions and structs renamed. Public/private | |
36 | * interfaces have been shifted around. Some code has been cleaned up too. | |
37 | * -- Elizafox 24 March 2016 | |
38 | */ | |
39 | ||
40 | #include "authd.h" | |
41 | #include "provider.h" | |
42 | #include "stdinc.h" | |
43 | #include "dns.h" | |
44 | ||
45 | typedef enum filter_t | |
46 | { | |
47 | FILTER_ALL = 1, | |
48 | FILTER_LAST = 2, | |
49 | } filter_t; | |
50 | ||
51 | /* Blacklist accepted IP types */ | |
52 | #define IPTYPE_IPV4 1 | |
53 | #define IPTYPE_IPV6 2 | |
54 | ||
55 | /* A configured DNSBL */ | |
56 | struct blacklist | |
57 | { | |
58 | char host[IRCD_RES_HOSTLEN + 1]; | |
59 | char reason[BUFSIZE]; /* Reason template (ircd fills in the blanks) */ | |
60 | unsigned char iptype; /* IP types supported */ | |
61 | rb_dlink_list filters; /* Filters for queries */ | |
62 | ||
63 | bool delete; /* If true delete when no clients */ | |
64 | int refcount; /* When 0 and delete is set, remove this blacklist */ | |
65 | ||
66 | time_t lastwarning; /* Last warning about garbage replies sent */ | |
67 | }; | |
68 | ||
69 | /* A lookup in progress for a particular DNSBL for a particular client */ | |
70 | struct blacklist_lookup | |
71 | { | |
72 | struct blacklist *bl; /* Blacklist we're checking */ | |
73 | struct auth_client *auth; /* Client */ | |
74 | struct dns_query *query; /* DNS query pointer */ | |
75 | ||
76 | rb_dlink_node node; | |
77 | }; | |
78 | ||
79 | /* A blacklist filter */ | |
80 | struct blacklist_filter | |
81 | { | |
82 | filter_t type; /* Type of filter */ | |
83 | char filterstr[HOSTIPLEN]; /* The filter itself */ | |
84 | ||
85 | rb_dlink_node node; | |
86 | }; | |
87 | ||
88 | /* Blacklist user data attached to auth_client instance */ | |
89 | struct blacklist_user | |
90 | { | |
91 | rb_dlink_list queries; /* Blacklist queries in flight */ | |
92 | time_t timeout; /* When this times out */ | |
93 | }; | |
94 | ||
95 | /* public interfaces */ | |
96 | static bool blacklists_init(void); | |
97 | static void blacklists_destroy(void); | |
98 | ||
99 | static bool blacklists_start(struct auth_client *); | |
100 | static void blacklists_cancel(struct auth_client *); | |
101 | ||
102 | /* private interfaces */ | |
103 | static void unref_blacklist(struct blacklist *); | |
104 | static struct blacklist *new_blacklist(char *, char *, bool, bool, rb_dlink_list *); | |
105 | static struct blacklist *find_blacklist(char *); | |
106 | static bool blacklist_check_reply(struct blacklist_lookup *, const char *); | |
107 | static void blacklist_dns_callback(const char *, bool, query_type, void *); | |
108 | static void initiate_blacklist_dnsquery(struct blacklist *, struct auth_client *); | |
109 | static void timeout_blacklist_queries_event(void *); | |
110 | ||
111 | /* Variables */ | |
112 | static rb_dlink_list blacklist_list = { NULL, NULL, 0 }; | |
113 | static struct ev_entry *timeout_ev; | |
114 | static EVH timeout_blacklists; | |
115 | static int blacklist_timeout = 15; | |
116 | ||
117 | /* private interfaces */ | |
118 | ||
119 | static void | |
120 | unref_blacklist(struct blacklist *bl) | |
121 | { | |
122 | rb_dlink_node *ptr, *nptr; | |
123 | ||
124 | bl->refcount--; | |
125 | if (bl->delete && bl->refcount <= 0) | |
126 | { | |
127 | RB_DLINK_FOREACH_SAFE(ptr, nptr, bl->filters.head) | |
128 | { | |
129 | rb_dlinkDelete(ptr, &bl->filters); | |
130 | rb_free(ptr); | |
131 | } | |
132 | ||
133 | rb_dlinkFindDestroy(bl, &blacklist_list); | |
134 | rb_free(bl); | |
135 | } | |
136 | } | |
137 | ||
138 | static struct blacklist * | |
139 | new_blacklist(char *name, char *reason, bool ipv4, bool ipv6, rb_dlink_list *filters) | |
140 | { | |
141 | struct blacklist *bl; | |
142 | ||
143 | if (name == NULL || reason == NULL || !(ipv4 || ipv6)) | |
144 | return NULL; | |
145 | ||
146 | if((bl = find_blacklist(name)) == NULL) | |
147 | { | |
148 | bl = rb_malloc(sizeof(struct blacklist)); | |
149 | rb_dlinkAddAlloc(bl, &blacklist_list); | |
150 | } | |
151 | else | |
152 | bl->delete = false; | |
153 | ||
154 | rb_strlcpy(bl->host, name, IRCD_RES_HOSTLEN + 1); | |
155 | rb_strlcpy(bl->reason, reason, BUFSIZE); | |
156 | if(ipv4) | |
157 | bl->iptype |= IPTYPE_IPV4; | |
158 | if(ipv6) | |
159 | bl->iptype |= IPTYPE_IPV6; | |
160 | ||
161 | rb_dlinkMoveList(filters, &bl->filters); | |
162 | ||
163 | bl->lastwarning = 0; | |
164 | ||
165 | return bl; | |
166 | } | |
167 | ||
168 | static struct blacklist * | |
169 | find_blacklist(char *name) | |
170 | { | |
171 | rb_dlink_node *ptr; | |
172 | ||
173 | RB_DLINK_FOREACH(ptr, blacklist_list.head) | |
174 | { | |
175 | struct blacklist *bl = (struct blacklist *)ptr->data; | |
176 | ||
177 | if (!strcasecmp(bl->host, name)) | |
178 | return bl; | |
179 | } | |
180 | ||
181 | return NULL; | |
182 | } | |
183 | ||
184 | static inline bool | |
185 | blacklist_check_reply(struct blacklist_lookup *bllookup, const char *ipaddr) | |
186 | { | |
187 | struct blacklist *bl = bllookup->bl; | |
188 | const char *lastoctet; | |
189 | rb_dlink_node *ptr; | |
190 | ||
191 | /* No filters and entry found - thus positive match */ | |
192 | if (!rb_dlink_list_length(&bl->filters)) | |
193 | return true; | |
194 | ||
195 | /* Below will prolly have to change if IPv6 address replies are sent back */ | |
196 | if ((lastoctet = strrchr(ipaddr, '.')) == NULL || *(++lastoctet) == '\0') | |
197 | goto blwarn; | |
198 | ||
199 | RB_DLINK_FOREACH(ptr, bl->filters.head) | |
200 | { | |
201 | struct blacklist_filter *filter = ptr->data; | |
202 | const char *cmpstr; | |
203 | ||
204 | if (filter->type == FILTER_ALL) | |
205 | cmpstr = ipaddr; | |
206 | else if (filter->type == FILTER_LAST) | |
207 | cmpstr = lastoctet; | |
208 | else | |
209 | { | |
210 | warn_opers(L_CRIT, "BUG: Unknown blacklist filter type on blacklist %s: %d", | |
211 | bl->host, filter->type); | |
212 | continue; | |
213 | } | |
214 | ||
215 | if (strcmp(cmpstr, filter->filterstr) == 0) | |
216 | /* Match! */ | |
217 | return true; | |
218 | } | |
219 | ||
220 | return false; | |
221 | blwarn: | |
222 | if (bl->lastwarning + 3600 < rb_current_time()) | |
223 | { | |
224 | warn_opers(L_WARN, "Garbage/undecipherable reply received from blacklist %s (reply %s)", | |
225 | bl->host, ipaddr); | |
226 | bl->lastwarning = rb_current_time(); | |
227 | } | |
228 | return false; | |
229 | } | |
230 | ||
231 | static void | |
232 | blacklist_dns_callback(const char *result, bool status, query_type type, void *data) | |
233 | { | |
234 | struct blacklist_lookup *bllookup = (struct blacklist_lookup *)data; | |
235 | struct blacklist_user *bluser; | |
236 | struct auth_client *auth; | |
237 | ||
238 | if (bllookup == NULL || bllookup->auth == NULL) | |
239 | return; | |
240 | ||
241 | auth = bllookup->auth; | |
242 | bluser = auth->data[PROVIDER_BLACKLIST]; | |
243 | if(bluser == NULL) | |
244 | return; | |
245 | ||
246 | if (result != NULL && status && blacklist_check_reply(bllookup, result)) | |
247 | { | |
248 | /* Match found, so proceed no further */ | |
249 | blacklists_cancel(auth); | |
250 | reject_client(auth, PROVIDER_BLACKLIST, bllookup->bl->reason); | |
251 | return; | |
252 | } | |
253 | ||
254 | unref_blacklist(bllookup->bl); | |
255 | rb_dlinkDelete(&bllookup->node, &bluser->queries); | |
256 | rb_free(bllookup); | |
257 | ||
258 | if(!rb_dlink_list_length(&bluser->queries)) | |
259 | { | |
260 | /* Done here */ | |
261 | provider_done(auth, PROVIDER_BLACKLIST); | |
262 | rb_free(bluser); | |
263 | auth->data[PROVIDER_BLACKLIST] = NULL; | |
264 | } | |
265 | } | |
266 | ||
267 | static void | |
268 | initiate_blacklist_dnsquery(struct blacklist *bl, struct auth_client *auth) | |
269 | { | |
270 | struct blacklist_lookup *bllookup = rb_malloc(sizeof(struct blacklist_lookup)); | |
271 | struct blacklist_user *bluser = auth->data[PROVIDER_BLACKLIST]; | |
272 | char buf[IRCD_RES_HOSTLEN + 1]; | |
273 | int aftype; | |
274 | ||
275 | bllookup->bl = bl; | |
276 | bllookup->auth = auth; | |
277 | ||
278 | aftype = GET_SS_FAMILY(&auth->c_addr); | |
279 | if((aftype == AF_INET && (bl->iptype & IPTYPE_IPV4) == 0) || | |
280 | (aftype == AF_INET6 && (bl->iptype & IPTYPE_IPV6) == 0)) | |
281 | /* Incorrect blacklist type for this IP... */ | |
282 | return; | |
283 | ||
284 | build_rdns(buf, sizeof(buf), &auth->c_addr, bl->host); | |
285 | ||
286 | bllookup->query = lookup_ip(buf, AF_INET, blacklist_dns_callback, bllookup); | |
287 | ||
288 | rb_dlinkAdd(bllookup, &bllookup->node, &bluser->queries); | |
289 | bl->refcount++; | |
290 | } | |
291 | ||
292 | /* Timeout outstanding queries */ | |
293 | static void | |
294 | timeout_blacklist_queries_event(void *notused) | |
295 | { | |
296 | struct auth_client *auth; | |
297 | rb_dictionary_iter iter; | |
298 | ||
299 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) | |
300 | { | |
301 | struct blacklist_user *bluser = auth->data[PROVIDER_BLACKLIST]; | |
302 | ||
303 | if(bluser != NULL && bluser->timeout < rb_current_time()) | |
304 | { | |
305 | blacklists_cancel(auth); | |
306 | provider_done(auth, PROVIDER_BLACKLIST); | |
307 | } | |
308 | } | |
309 | } | |
310 | ||
311 | /* public interfaces */ | |
312 | static bool | |
313 | blacklists_start(struct auth_client *auth) | |
314 | { | |
315 | struct blacklist_user *bluser; | |
316 | rb_dlink_node *nptr; | |
317 | ||
318 | if(auth->data[PROVIDER_BLACKLIST] != NULL) | |
319 | return true; | |
320 | ||
321 | if(!rb_dlink_list_length(&blacklist_list)) | |
322 | /* Nothing to do... */ | |
323 | return true; | |
324 | ||
325 | bluser = auth->data[PROVIDER_BLACKLIST] = rb_malloc(sizeof(struct blacklist_user)); | |
326 | ||
327 | RB_DLINK_FOREACH(nptr, blacklist_list.head) | |
328 | { | |
329 | struct blacklist *bl = (struct blacklist *) nptr->data; | |
330 | ||
331 | if (!bl->delete) | |
332 | initiate_blacklist_dnsquery(bl, auth); | |
333 | } | |
334 | ||
335 | bluser->timeout = rb_current_time() + blacklist_timeout; | |
336 | ||
337 | set_provider(auth, PROVIDER_BLACKLIST); | |
338 | return true; | |
339 | } | |
340 | ||
341 | static void | |
342 | blacklists_cancel(struct auth_client *auth) | |
343 | { | |
344 | rb_dlink_node *ptr, *nptr; | |
345 | struct blacklist_user *bluser = auth->data[PROVIDER_BLACKLIST]; | |
346 | ||
347 | if(bluser == NULL) | |
348 | return; | |
349 | ||
350 | RB_DLINK_FOREACH_SAFE(ptr, nptr, bluser->queries.head) | |
351 | { | |
352 | struct blacklist_lookup *bllookup = ptr->data; | |
353 | rb_dlinkDelete(&bllookup->node, &bluser->queries); | |
354 | unref_blacklist(bllookup->bl); | |
355 | cancel_query(bllookup->query); | |
356 | rb_free(bllookup); | |
357 | } | |
358 | ||
359 | rb_free(bluser); | |
360 | auth->data[PROVIDER_BLACKLIST] = NULL; | |
361 | } | |
362 | ||
363 | static bool | |
364 | blacklists_init(void) | |
365 | { | |
366 | timeout_ev = rb_event_addish("timeout_blacklist_queries_event", timeout_blacklist_queries_event, NULL, 1); | |
367 | return (timeout_ev != NULL); | |
368 | } | |
369 | ||
370 | static void | |
371 | blacklists_destroy(void) | |
372 | { | |
373 | rb_dlink_node *ptr, *nptr; | |
374 | rb_dictionary_iter iter; | |
375 | struct blacklist *bl; | |
376 | struct auth_client *auth; | |
377 | ||
378 | RB_DICTIONARY_FOREACH(auth, &iter, auth_clients) | |
379 | { | |
380 | blacklists_cancel(auth); | |
381 | } | |
382 | ||
383 | RB_DLINK_FOREACH_SAFE(ptr, nptr, blacklist_list.head) | |
384 | { | |
385 | bl = ptr->data; | |
386 | if (bl->refcount > 0) | |
387 | bl->delete = true; | |
388 | else | |
389 | { | |
390 | rb_free(ptr->data); | |
391 | rb_dlinkDestroy(ptr, &blacklist_list); | |
392 | } | |
393 | } | |
394 | } | |
395 | ||
396 | struct auth_provider blacklist_provider = | |
397 | { | |
398 | .id = PROVIDER_BLACKLIST, | |
399 | .init = blacklists_init, | |
400 | .destroy = blacklists_destroy, | |
401 | .start = blacklists_start, | |
402 | .cancel = blacklists_cancel, | |
403 | .completed = NULL, | |
404 | }; |