1 /* sockcheck.c - insecure proxy checking
2 * Copyright 2000-2004 srvx Development Team
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version. Important limitations are
8 * listed in the COPYING file that accompanies this software.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, email srvx-maintainers@srvx.net.
25 #ifdef HAVE_SYS_SOCKET_H
26 #include <sys/socket.h>
28 #ifdef HAVE_NETINET_IN_H
29 #include <netinet/in.h>
31 #ifdef HAVE_ARPA_INET_H
32 #include <arpa/inet.h>
35 /* TODO, 1.3 or later: allow rules like "27374:" "reject:Subseven detected";
36 * (For convenience; right now it assumes that there will be a state
37 * rather than an immediate response upon connection.)
40 #if !defined(SOCKCHECK_DEBUG)
41 #define SOCKCHECK_DEBUG 0
43 #define SOCKCHECK_TEST_DB "sockcheck.conf"
45 enum sockcheck_decision {
52 enum sockcheck_decision decision;
57 } *sockcheck_cache_info;
59 DECLARE_LIST(sci_list, sockcheck_cache_info);
60 DEFINE_LIST(sci_list, sockcheck_cache_info)
62 /* Here's the list of hosts that need to be started on.
64 static struct sci_list pending_sci_list;
66 /* Map of previously checked IPs state (if we've accepted the address yet).
67 * The data for each entry is a pointer to a sockcheck_cache_info.
69 static dict_t checked_ip_dict;
71 /* Each sockcheck template is formed as a Mealy state machine (that is,
72 * the output on a state transition is a function of both the current
73 * state and the input). Mealy state machines require fewer states to
74 * match the same input than Moore machines (where the output is only
75 * a function of the current state).
77 * A state is characterized by sending some data (possibly nothing),
78 * waiting a certain amount of time to receive one of zero or more
79 * responses, and a decision (accept, reject, continue to another
80 * state) based on the received response.
83 struct sockcheck_response {
85 struct sockcheck_state *next;
88 DECLARE_LIST(response_list, struct sockcheck_response *);
89 DEFINE_LIST(response_list, struct sockcheck_response *)
90 static unsigned int max_responses;
92 struct sockcheck_state {
94 unsigned short timeout;
96 enum sockcheck_decision type;
98 struct response_list responses;
101 struct sockcheck_list {
102 unsigned int size, used, refs;
103 struct sockcheck_state **list;
109 static struct sockcheck_list *tests;
111 /* Stuff to track client state, one instance per open connection. */
112 struct sockcheck_client {
114 struct sockcheck_list *tests;
115 sockcheck_cache_info addr;
116 unsigned int client_index;
117 unsigned int test_index;
118 unsigned short test_rep;
119 struct sockcheck_state *state;
120 unsigned int read_size, read_used, read_pos;
122 const char **resp_state;
126 unsigned int max_clients;
127 unsigned int max_read;
128 unsigned int gline_duration;
129 unsigned int max_cache_age;
130 struct sockaddr_in *local_addr;
134 static unsigned int sockcheck_num_clients;
135 static struct sockcheck_client **client_list;
136 static unsigned int proxies_detected, checked_ip_count;
137 static struct module *sockcheck_module;
138 static struct log_type *PC_LOG;
139 const char *sockcheck_module_deps[] = { NULL };
141 static const struct message_entry msgtab[] = {
142 { "PCMSG_PROXY_DEFINITION_FAILED", "Proxy definition failed: %s" },
143 { "PCMSG_PROXY_DEFINITION_SUCCEEDED", "New proxy type defined." },
144 { "PCMSG_UNSCANNABLE_IP", "%s has a spoofed, hidden or localnet IP." },
145 { "PCMSG_ADDRESS_QUEUED", "$b%s$b is now queued to be proxy-checked." },
146 { "PCMSG_ADDRESS_UNRESOLVED", "Unable to resolve $b%s$b to an IP address." },
147 { "PCMSG_CHECKING_ADDRESS", "$b%s$b is currently being checked; unable to clear it." },
148 { "PCMSG_NOT_REMOVED_FROM_CACHE", "$b%s$b was not cached and therefore was not cleared." },
149 { "PCMSG_REMOVED_FROM_CACHE", "$b%s$b was cleared from the cached hosts list." },
150 { "PCMSG_DISABLED", "Proxy scanning is $bdisabled$b." },
151 { "PCMSG_NOT_CACHED", "No proxycheck records exist for IP %s." },
152 { "PCMSG_STATUS_CHECKING", "IP %s proxycheck state: last touched %s ago, still checking" },
153 { "PCMSG_STATUS_ACCEPTED", "IP %s proxycheck state: last touched %s ago, acepted" },
154 { "PCMSG_STATUS_REJECTED", "IP %s proxycheck state: last touched %s ago, rejected: %s" },
155 { "PCMSG_STATUS_UNKNOWN", "IP %s proxycheck state: last touched %s ago, invalid status" },
156 { "PCMSG_STATISTICS", "Since booting, I have checked %d clients for illicit proxies, and detected %d proxy hosts.\nI am currently checking %d clients (out of %d max) and have a backlog of %d more to start on.\nI currently have %d hosts cached.\nI know how to detect %d kinds of proxies." },
160 static struct sockcheck_list *
161 sockcheck_list_alloc(unsigned int size)
163 struct sockcheck_list *list = malloc(sizeof(*list));
167 list->list = malloc(list->size*sizeof(list->list[0]));
172 sockcheck_list_append(struct sockcheck_list *list, struct sockcheck_state *new_item)
174 if (list->used == list->size) {
176 list->list = realloc(list->list, list->size*sizeof(list->list[0]));
178 list->list[list->used++] = new_item;
181 static struct sockcheck_list *
182 sockcheck_list_clone(struct sockcheck_list *old_list)
184 struct sockcheck_list *new_list = malloc(sizeof(*new_list));
185 new_list->used = old_list->used;
187 new_list->size = old_list->size;
188 new_list->list = malloc(new_list->size*sizeof(new_list->list[0]));
189 memcpy(new_list->list, old_list->list, new_list->used*sizeof(new_list->list[0]));
194 sockcheck_list_unref(struct sockcheck_list *list)
196 if (!list || --list->refs > 0) return;
202 sockcheck_issue_gline(sockcheck_cache_info sci)
204 char *target = alloca(3+strlen(sci->hostname));
205 strcpy(target, "*@");
206 strcpy(target+2, sci->hostname);
207 log_module(PC_LOG, LOG_INFO, "Issuing gline for client at IP %s hostname %s: %s", inet_ntoa(sci->addr), sci->hostname, sci->reason);
208 gline_add("ProxyCheck", target, sockcheck_conf.gline_duration, sci->reason, now, 1);
211 static struct sockcheck_client *
212 sockcheck_alloc_client(sockcheck_cache_info sci)
214 struct sockcheck_client *client;
215 client = calloc(1, sizeof(*client));
216 client->tests = tests;
217 client->tests->refs++;
219 client->read_size = sockcheck_conf.max_read;
220 client->read = malloc(client->read_size);
221 client->resp_state = malloc(max_responses * sizeof(client->resp_state[0]));
226 sockcheck_free_client(struct sockcheck_client *client)
228 if (SOCKCHECK_DEBUG) {
229 log_module(PC_LOG, LOG_INFO, "Goodbye %s (%p)! I set you free!", client->addr->hostname, client);
231 if (client->fd) ioset_close(client->fd->fd, 1);
232 sockcheck_list_unref(client->tests);
234 free(client->resp_state);
238 static void sockcheck_start_client(unsigned int idx);
239 static void sockcheck_begin_test(struct sockcheck_client *client);
240 static void sockcheck_advance(struct sockcheck_client *client, unsigned int next_state);
243 sockcheck_timeout_client(void *data)
245 struct sockcheck_client *client = data;
246 if (SOCKCHECK_DEBUG) {
247 log_module(PC_LOG, LOG_INFO, "Client %s timed out.", client->addr->hostname);
249 sockcheck_advance(client, client->state->responses.used-1);
253 sockcheck_print_client(const struct sockcheck_client *client)
255 static const char *decs[] = {"CHECKING", "ACCEPT", "REJECT"};
256 log_module(PC_LOG, LOG_INFO, "client %p: { addr = %p { decision = %s; last_touched = "FMT_TIME_T"; reason = %s; hostname = \"%s\" }; "
257 "test_index = %d; state = %p { port = %d; type = %s; template = \"%s\"; ... }; "
258 "fd = %p(%d); read = %p; read_size = %d; read_used = %d; read_pos = %d; }",
259 client, client->addr, decs[client->addr->decision], client->addr->last_touched, client->addr->reason, client->addr->hostname,
260 client->test_index, client->state,
261 (client->state ? client->state->port : 0),
262 (client->state ? decs[client->state->type] : "N/A"),
263 (client->state ? client->state->template : "N/A"),
264 client->fd, (client->fd ? client->fd->fd : 0),
265 client->read, client->read_size, client->read_used, client->read_pos);
268 static char hexvals[256] = {
269 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */
270 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16 */
271 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32 */
272 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 48 */
273 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 64 */
274 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 */
275 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 96 */
276 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 112 */
280 expand_var(const struct sockcheck_client *client, char var, char **p_expansion, unsigned int *p_exp_length)
282 extern struct cManagerNode cManager;
283 const char *expansion;
284 unsigned int exp_length;
292 /* expand variable */
295 expansion = client->addr->hostname;
296 exp_length = strlen(expansion);
299 exp4 = client->addr->addr.s_addr;
300 exp_length = sizeof(exp4);
301 expansion = (char*)&exp4;
304 exp2 = htons(client->state->port);
305 exp_length = sizeof(exp2);
306 expansion = (char*)&exp2;
309 expansion = cManager.uplink->host;
310 exp_length = strlen(expansion);
313 log_module(PC_LOG, LOG_WARNING, "Request to expand unknown sockcheck variable $%c, using empty expansion.", var);
318 *p_expansion = malloc(exp_length);
319 memcpy(*p_expansion, expansion, exp_length);
322 *p_exp_length = exp_length;
327 sockcheck_check_template(const char *template, int is_input)
330 if (is_input && !strcmp(template, "other")) return 1;
331 for (nn=0; template[nn]; nn += 2) {
332 switch (template[nn]) {
334 if (!template[nn+1]) {
335 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s had = at end of template; needs a second character.", template);
340 switch (template[nn+1]) {
341 case 'c': case 'i': case 'p': case 'u': break;
343 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s refers to unknown variable %c (pos %d).", template, template[nn+1], nn);
349 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s: . is only valid in input templates.", template);
352 if (template[nn+1] != '.') {
353 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s expects .. to come in twos (pos %d).", template, nn);
357 case '0': case '1': case '2': case '3': case '4':
358 case '5': case '6': case '7': case '8': case '9':
359 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
360 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
361 if (!hexvals[(unsigned char)template[nn+1]] && (template[nn+1] != '0')) {
362 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s expects hex characters to come in twos (pos %d).", template, nn);
367 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s: unrecognized character '%c' (pos %d).", template, template[nn], nn);
375 sockcheck_elaborate_state(struct sockcheck_client *client)
377 const char *template;
380 for (template = client->state->template, nn = 0; template[nn]; nn += 2) {
381 switch (template[nn]) {
382 case '=': ioset_write(client->fd, template+nn+1, 1); break;
383 case '0': case '1': case '2': case '3': case '4':
384 case '5': case '6': case '7': case '8': case '9':
385 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
386 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': {
387 char ch = hexvals[(unsigned char)template[nn]] << 4
388 | hexvals[(unsigned char)template[nn+1]];
389 ioset_write(client->fd, &ch, 1);
394 unsigned int exp_length;
395 expand_var(client, template[nn+1], &expansion, &exp_length);
396 ioset_write(client->fd, expansion, exp_length);
402 for (nn=0; nn<client->state->responses.used; nn++) {
403 /* Set their resp_state to the start of the response. */
404 client->resp_state[nn] = client->state->responses.list[nn]->template;
405 /* If it doesn't require reading, take it now. */
406 if (client->resp_state[nn] && !*client->resp_state[nn]) {
407 if (SOCKCHECK_DEBUG) {
408 log_module(PC_LOG, LOG_INFO, "Skipping straight to easy option %d for %p.", nn, client);
410 sockcheck_advance(client, nn);
414 timeq_add(now + client->state->timeout, sockcheck_timeout_client, client);
415 if (SOCKCHECK_DEBUG) {
416 log_module(PC_LOG, LOG_INFO, "Elaborated state for %s:", client->addr->hostname);
417 sockcheck_print_client(client);
422 sockcheck_decide(struct sockcheck_client *client, enum sockcheck_decision decision)
427 client->addr->decision = decision;
428 client->addr->last_touched = now;
432 if (SOCKCHECK_DEBUG) {
433 log_module(PC_LOG, LOG_INFO, "Proxy check passed for client at IP %s hostname %s.", inet_ntoa(client->addr->addr), client->addr->hostname);
437 client->addr->reason = client->state->template;
439 sockcheck_issue_gline(client->addr);
440 if (SOCKCHECK_DEBUG) {
441 log_module(PC_LOG, LOG_INFO, "Proxy check rejects client at IP %s hostname %s (%s)", inet_ntoa(client->addr->addr), client->addr->hostname, client->addr->reason);
443 /* Don't compare test_index != 0 directly, because somebody
444 * else may have reordered the tests already. */
445 if (client->tests->list[client->test_index] != tests->list[0]) {
446 struct sockcheck_list *new_tests = sockcheck_list_clone(tests);
447 struct sockcheck_state *new_first = client->tests->list[client->test_index];
448 for (n=0; (n<tests->used) && (tests->list[n] != new_first); n++) ;
449 for (; n>0; n--) new_tests->list[n] = new_tests->list[n-1];
450 new_tests->list[0] = new_first;
451 sockcheck_list_unref(tests);
456 log_module(PC_LOG, LOG_ERROR, "BUG: sockcheck_decide(\"%s\", %d): unrecognized decision.", client->addr->hostname, decision);
458 n = client->client_index;
459 sockcheck_free_client(client);
460 if ((--sockcheck_num_clients < sockcheck_conf.max_clients)
461 && (pending_sci_list.used > 0)) {
462 sockcheck_start_client(n);
469 sockcheck_advance(struct sockcheck_client *client, unsigned int next_state)
471 struct sockcheck_state *ns;
473 timeq_del(0, sockcheck_timeout_client, client, TIMEQ_IGNORE_WHEN);
474 if (SOCKCHECK_DEBUG) {
477 static const char *hexmap = "0123456789ABCDEF";
478 log_module(PC_LOG, LOG_INFO, "sockcheck_advance(%s) following response %d (type %d) of %d.", client->addr->hostname, next_state, client->state->responses.list[next_state]->next->type, client->state->responses.used);
479 for (n=0; n<client->read_used; n++) {
480 for (m=0; (m<(sizeof(buffer)-1)>>1) && ((n+m) < client->read_used); m++) {
481 buffer[m << 1] = hexmap[client->read[n+m] >> 4];
482 buffer[m << 1 | 1] = hexmap[client->read[n+m] & 15];
485 log_module(PC_LOG, LOG_INFO, " .. read data: %s", buffer);
488 sockcheck_print_client(client);
491 ns = client->state = client->state->responses.list[next_state]->next;
494 sockcheck_elaborate_state(client);
497 sockcheck_decide(client, REJECT);
500 if (++client->test_rep < client->tests->list[client->test_index]->reps) {
501 sockcheck_begin_test(client);
502 } else if (++client->test_index < client->tests->used) {
503 client->test_rep = 0;
504 sockcheck_begin_test(client);
506 sockcheck_decide(client, ACCEPT);
510 log_module(PC_LOG, LOG_ERROR, "BUG: unknown next-state type %d (after %p).", ns->type, client->state);
516 sockcheck_readable(struct io_fd *fd)
518 /* read what we can from the fd */
519 struct sockcheck_client *client = fd->data;
523 res = read(fd->fd, client->read + client->read_used, client->read_size - client->read_used);
525 switch (res = errno) {
527 log_module(PC_LOG, LOG_ERROR, "BUG: sockcheck_readable(%d/%s): read() returned errno %d (%s)", fd->fd, client->addr->hostname, errno, strerror(errno));
531 sockcheck_advance(client, client->state->responses.used - 1);
534 } else if (res == 0) {
535 sockcheck_advance(client, client->state->responses.used - 1);
538 client->read_used += res;
540 if (SOCKCHECK_DEBUG) {
543 static const char *hexmap = "0123456789ABCDEF";
544 for (n=0; n<client->read_used; n++) {
545 for (m=0; (m<(sizeof(buffer)-1)>>1) && ((n+m) < client->read_used); m++) {
546 buffer[m << 1] = hexmap[client->read[n+m] >> 4];
547 buffer[m << 1 | 1] = hexmap[client->read[n+m] & 15];
550 log_module(PC_LOG, LOG_INFO, "read %d bytes data: %s", client->read_used, buffer);
555 /* See if what's been read matches any of the expected responses */
556 while (client->read_pos < client->read_used) {
557 unsigned int last_pos = client->read_pos;
559 const char *resp_state;
561 for (nn=0; nn<(client->state->responses.used-1); nn++) {
563 unsigned int exp_length = 1, free_exp = 0;
564 /* compare against possible target */
565 resp_state = client->resp_state[nn];
566 if (resp_state == NULL) continue;
567 switch (*resp_state) {
569 bleh = resp_state[1];
573 /* any character passes */
577 case '0': case '1': case '2': case '3': case '4':
578 case '5': case '6': case '7': case '8': case '9':
579 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
580 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
581 bleh = hexvals[(unsigned char)resp_state[0]] << 4
582 | hexvals[(unsigned char)resp_state[1]];
586 expand_var(client, resp_state[1], &expected, &exp_length);
590 if (client->read_pos+exp_length <= client->read_used) {
591 if (exp_length && memcmp(client->read+client->read_pos, expected, exp_length)) {
594 client->read_pos += exp_length;
597 /* can't check the variable yet, so come back later */
600 if (free_exp) free(expected);
602 client->resp_state[nn] = resp_state = resp_state + 2;
604 sockcheck_advance(client, nn);
608 client->resp_state[nn] = NULL;
611 if (last_pos == client->read_pos) break;
614 /* nothing seemed to match. what now? */
615 if (client->read_used >= client->read_size) {
616 /* we got more data than we expected to get .. don't read any more */
617 if (SOCKCHECK_DEBUG) {
618 log_module(PC_LOG, LOG_INFO, "Buffer filled (unmatched) for client %s", client->addr->hostname);
620 sockcheck_advance(client, client->state->responses.used-1);
626 sockcheck_connected(struct io_fd *fd, int rc)
628 struct sockcheck_client *client = fd->data;
632 log_module(PC_LOG, LOG_ERROR, "BUG: connect() got error %d (%s) for client at %s.", rc, strerror(rc), client->addr->hostname);
636 if (SOCKCHECK_DEBUG) {
637 log_module(PC_LOG, LOG_INFO, "Client %s gave us errno %d (%s)", client->addr->hostname, rc, strerror(rc));
639 sockcheck_advance(client, client->state->responses.used-1);
644 if (SOCKCHECK_DEBUG) {
645 log_module(PC_LOG, LOG_INFO, "Connected: to %s port %d.", client->addr->hostname, client->state->port);
647 sockcheck_elaborate_state(client);
651 sockcheck_begin_test(struct sockcheck_client *client)
656 ioset_close(client->fd->fd, 1);
660 client->state = client->tests->list[client->test_index];
661 client->read_pos = 0;
662 client->read_used = 0;
663 client->fd = io_fd = ioset_connect((struct sockaddr*)sockcheck_conf.local_addr, sizeof(struct sockaddr), client->addr->hostname, client->state->port, 0, client, sockcheck_connected);
665 client->test_index++;
668 io_fd->readable_cb = sockcheck_readable;
669 timeq_add(now + client->state->timeout, sockcheck_timeout_client, client);
670 if (SOCKCHECK_DEBUG) {
671 log_module(PC_LOG, LOG_INFO, "Starting proxy check on %s:%d (test %d) with fd %d (%p).", client->addr->hostname, client->state->port, client->test_index, io_fd->fd, io_fd);
674 } while (client->test_index < client->tests->used);
675 /* Ran out of tests to run; accept this client. */
676 sockcheck_decide(client, ACCEPT);
680 sockcheck_start_client(unsigned int idx)
682 sockcheck_cache_info sci;
683 struct sockcheck_client *client;
685 if (pending_sci_list.used == 0) return;
686 if (!(sci = pending_sci_list.list[0])) {
687 log_module(PC_LOG, LOG_ERROR, "BUG: sockcheck_start_client(%d) found null pointer in pending_sci_list.", idx);
690 memmove(pending_sci_list.list, pending_sci_list.list+1,
691 (--pending_sci_list.used)*sizeof(pending_sci_list.list[0]));
692 sockcheck_num_clients++;
694 client = client_list[idx] = sockcheck_alloc_client(sci);
695 log_module(PC_LOG, LOG_INFO, "Proxy-checking client at %s (%s) as client %d (%p) of %d.", inet_ntoa(sci->addr), sci->hostname, idx, client, sockcheck_num_clients);
696 client->test_rep = 0;
697 client->client_index = idx;
698 sockcheck_begin_test(client);
702 sockcheck_queue_address(struct in_addr addr)
704 sockcheck_cache_info sci;
705 char *ipstr=inet_ntoa(addr);
707 sci = dict_find(checked_ip_dict, ipstr, NULL);
709 switch (sci->decision) {
711 /* We are already checking this host. */
714 if ((sci->last_touched + sockcheck_conf.max_cache_age) >= (unsigned)now) return;
717 if ((sci->last_touched + sockcheck_conf.gline_duration) >= (unsigned)now) {
718 sockcheck_issue_gline(sci);
723 dict_remove(checked_ip_dict, sci->hostname);
725 sci = calloc(1, sizeof(*sci));
726 sci->decision = CHECKING;
727 sci->last_touched = now;
730 strncpy(sci->hostname, ipstr, sizeof(sci->hostname));
731 dict_insert(checked_ip_dict, sci->hostname, sci);
732 sci_list_append(&pending_sci_list, sci);
733 if (sockcheck_num_clients < sockcheck_conf.max_clients)
734 sockcheck_start_client(sockcheck_num_clients);
738 sockcheck_uncache_host(const char *name)
740 sockcheck_cache_info sci;
741 if ((sci = dict_find(checked_ip_dict, name, NULL))
742 && (sci->decision == CHECKING)) {
745 return dict_remove(checked_ip_dict, name);
749 sockcheck_create_response(const char *key, void *data, void *extra)
751 const char *str, *end;
752 struct record_data *rd = data;
753 struct sockcheck_state *parent = extra;
754 struct sockcheck_response *resp;
758 /* allocate memory and tack it onto parent->responses */
759 resp = malloc(sizeof(*resp));
760 for (end = key; *end != ':' && *end != 0; end += 2 && end) ;
761 templ = malloc(end - key + 1);
762 memcpy(templ, key, end - key);
763 templ[end - key] = 0;
764 resp->template = templ;
765 if (!sockcheck_check_template(resp->template, 1)) _exit(1);
766 resp->next = malloc(sizeof(*resp->next));
767 resp->next->port = parent->port;
768 response_list_append(&parent->responses, resp);
769 /* now figure out how to create resp->next */
770 if ((str = GET_RECORD_QSTRING(rd))) {
771 if (!ircncasecmp(str, "reject", 6)) {
772 resp->next->type = REJECT;
773 } else if (!ircncasecmp(str, "accept", 6)) {
774 resp->next->type = ACCEPT;
776 log_module(PC_LOG, LOG_ERROR, "Error: unknown sockcheck decision `%s', defaulting to accept.", str);
777 resp->next->type = ACCEPT;
780 resp->next->template = strdup(str+7);
782 resp->next->template = strdup("No explanation given");
784 } else if ((resps = GET_RECORD_OBJECT(rd))) {
785 resp->next->type = CHECKING;
786 response_list_init(&resp->next->responses);
788 resp->next->template = strdup(end+1);
789 if (!sockcheck_check_template(resp->next->template, 0)) _exit(1);
791 resp->next->template = strdup("");
793 dict_foreach(resps, sockcheck_create_response, resp->next);
798 /* key: PORT:send-pattern, as in keys of sockcheck.conf.example
799 * data: recdb record_data containing response
800 * extra: struct sockcheck_list* to append test to
803 sockcheck_create_test(const char *key, void *data, void *extra)
806 struct record_data *rd;
808 struct sockcheck_state *new_test;
812 new_test = malloc(sizeof(*new_test));
813 new_test->template = NULL;
815 new_test->port = strtoul(key, &end, 0);
816 new_test->timeout = 5;
817 new_test->type = CHECKING;
818 response_list_init(&new_test->responses);
819 if (!(object = GET_RECORD_OBJECT(rd))) {
820 log_module(PC_LOG, LOG_ERROR, "Error: misformed sockcheck test `%s', skipping it.", key);
826 case '@': new_test->timeout = strtoul(end+1, &end, 0); break;
827 case '*': new_test->reps = strtoul(end+1, &end, 0); break;
829 new_test->template = strdup(end+1);
831 if (!sockcheck_check_template(new_test->template, 0)) _exit(1);
834 log_module(PC_LOG, LOG_ERROR, "Error: misformed sockcheck test `%s', skipping it.", key);
839 if (!new_test->template) {
840 log_module(PC_LOG, LOG_ERROR, "Error: misformed sockcheck test `%s', skipping it.", key);
844 dict_foreach(object, sockcheck_create_response, new_test);
845 /* If none of the responses have template "other", create a
846 * default response that goes to accept. */
847 for (n=0; n<new_test->responses.used; n++) {
848 if (!strcmp(new_test->responses.list[n]->template, "other")) break;
850 if (n == new_test->responses.used) {
851 rd = alloc_record_data_qstring("accept");
852 sockcheck_create_response("other", rd, new_test);
853 free_record_data(rd);
854 } else if (n != (new_test->responses.used - 1)) {
855 struct sockcheck_response *tmp;
856 /* switch the response for "other" to the end */
857 tmp = new_test->responses.list[new_test->responses.used - 1];
858 new_test->responses.list[new_test->responses.used - 1] = new_test->responses.list[n];
859 new_test->responses.list[n] = tmp;
861 if (new_test->responses.used > max_responses) {
862 max_responses = new_test->responses.used;
864 sockcheck_list_append(extra, new_test);
869 sockcheck_read_tests(void)
872 struct sockcheck_list *new_tests;
873 test_db = parse_database(SOCKCHECK_TEST_DB);
876 if (dict_size(test_db) > 0) {
877 new_tests = sockcheck_list_alloc(dict_size(test_db));
878 dict_foreach(test_db, sockcheck_create_test, new_tests);
879 if (tests) sockcheck_list_unref(tests);
882 log_module(PC_LOG, LOG_ERROR, "%s was empty - disabling sockcheck.", SOCKCHECK_TEST_DB);
884 free_database(test_db);
888 sockcheck_free_state(struct sockcheck_state *state)
891 if (state->type == CHECKING) {
892 for (n=0; n<state->responses.used; n++) {
893 free((char*)state->responses.list[n]->template);
894 sockcheck_free_state(state->responses.list[n]->next);
895 free(state->responses.list[n]);
897 response_list_clean(&state->responses);
899 free((char*)state->template);
904 sockcheck_add_test(const char *desc)
906 struct sockcheck_list *new_tests;
909 struct record_data *rd;
911 if ((reason = parse_record(desc, &name, &rd)))
913 new_tests = sockcheck_list_clone(tests);
914 if (sockcheck_create_test(name, rd, new_tests)) {
915 sockcheck_list_unref(new_tests);
916 return "Sockcheck test parse error";
918 sockcheck_list_unref(tests);
924 sockcheck_shutdown(void)
929 for (n=0; n<sockcheck_conf.max_clients; n++) {
931 sockcheck_free_client(client_list[n]);
935 sockcheck_num_clients = 0;
936 dict_delete(checked_ip_dict);
937 sci_list_clean(&pending_sci_list);
939 for (n=0; n<tests->used; n++)
940 sockcheck_free_state(tests->list[n]);
941 sockcheck_list_unref(tests);
942 if (sockcheck_conf.local_addr) {
943 free(sockcheck_conf.local_addr);
944 sockcheck_conf.local_addr_len = 0;
949 sockcheck_clean_cache(UNUSED_ARG(void *data))
952 dict_iterator_t it, next;
953 sockcheck_cache_info sci;
957 if (SOCKCHECK_DEBUG) {
958 struct string_buffer sb;
959 string_buffer_init(&sb);
960 /* Remember which clients we're still checking; we're not allowed to remove them. */
961 for (curr_clients = dict_new(), nn=0; nn < sockcheck_conf.max_clients; nn++) {
962 if (!client_list[nn])
964 dict_insert(curr_clients, client_list[nn]->addr->hostname, client_list[nn]);
965 string_buffer_append(&sb, ' ');
966 string_buffer_append_string(&sb, client_list[nn]->addr->hostname);
968 string_buffer_append(&sb, '\0');
969 log_module(PC_LOG, LOG_INFO, "Cleaning sockcheck cache at "FMT_TIME_T"; current clients: %s.", now, sb.list);
970 string_buffer_clean(&sb);
972 for (curr_clients = dict_new(), nn=0; nn < sockcheck_conf.max_clients; nn++) {
973 if (!client_list[nn])
975 dict_insert(curr_clients, client_list[nn]->addr->hostname, client_list[nn]);
979 for (it=dict_first(checked_ip_dict); it; it=next) {
980 next = iter_next(it);
982 max_age = (sci->decision == REJECT) ? sockcheck_conf.gline_duration : sockcheck_conf.max_cache_age;
983 if (((sci->last_touched + max_age) < now)
984 && !dict_find(curr_clients, sci->hostname, NULL)) {
985 if (SOCKCHECK_DEBUG) {
986 log_module(PC_LOG, LOG_INFO, " .. nuking %s (last touched "FMT_TIME_T").", sci->hostname, sci->last_touched);
988 dict_remove(checked_ip_dict, sci->hostname);
991 dict_delete(curr_clients);
992 timeq_add(now+sockcheck_conf.max_cache_age, sockcheck_clean_cache, 0);
995 static MODCMD_FUNC(cmd_defproxy)
999 if ((reason = sockcheck_add_test(unsplit_string(argv+1, argc-1, NULL)))) {
1000 reply("PCMSG_PROXY_DEFINITION_FAILED", reason);
1003 reply("PCMSG_PROXY_DEFINITION_SUCCEEDED");
1007 static MODCMD_FUNC(cmd_hostscan)
1011 struct in_addr ipaddr;
1014 for (n=1; n<argc; n++) {
1015 struct userNode *un = GetUserH(argv[n]);
1018 if ((un->ip.s_addr == 0) || (ntohl(un->ip.s_addr) == INADDR_LOOPBACK)) {
1019 reply("PCMSG_UNSCANNABLE_IP", un->nick);
1021 strcpy(hnamebuf, inet_ntoa(un->ip));
1022 sockcheck_queue_address(un->ip);
1023 reply("PCMSG_ADDRESS_QUEUED", hnamebuf);
1026 char *scanhost = argv[n];
1027 if (getipbyname(scanhost, &addr)) {
1028 ipaddr.s_addr = htonl(addr);
1029 sockcheck_queue_address(ipaddr);
1030 reply("PCMSG_ADDRESS_QUEUED", scanhost);
1032 reply("PCMSG_ADDRESS_UNRESOLVED", scanhost);
1039 static MODCMD_FUNC(cmd_clearhost)
1044 for (n=1; n<argc; n++) {
1045 struct userNode *un = GetUserH(argv[n]);
1046 const char *scanhost;
1049 strcpy(hnamebuf, inet_ntoa(un->ip));
1050 scanhost = hnamebuf;
1054 switch (sockcheck_uncache_host(scanhost)) {
1056 reply("PCMSG_CHECKING_ADDRESS", scanhost);
1059 reply("PCMSG_NOT_REMOVED_FROM_CACHE", scanhost);
1062 reply("PCMSG_REMOVED_FROM_CACHE", scanhost);
1069 static MODCMD_FUNC(cmd_stats_proxycheck)
1072 const char *hostname = argv[1];
1073 char elapse_buf[INTERVALLEN];
1076 sockcheck_cache_info sci = dict_find(checked_ip_dict, hostname, NULL);
1078 reply("PCMSG_NOT_CACHED", hostname);
1081 intervalString(elapse_buf, now - sci->last_touched);
1082 switch (sci->decision) {
1083 case CHECKING: msg = "PCMSG_STATUS_CHECKING"; break;
1084 case ACCEPT: msg = "PCMSG_STATUS_ACCEPTED"; break;
1085 case REJECT: msg = "PCMSG_STATUS_REJECTED"; break;
1086 default: msg = "PCMSG_STATUS_UNKNOWN"; break;
1088 reply(msg, sci->hostname, elapse_buf, sci->reason);
1091 reply("PCMSG_STATISTICS", checked_ip_count, proxies_detected, sockcheck_num_clients, sockcheck_conf.max_clients, pending_sci_list.used, dict_size(checked_ip_dict), (tests ? tests->used : 0));
1097 sockcheck_new_user(struct userNode *user) {
1098 /* If they have a bum IP, or are bursting in, don't proxy-check or G-line them. */
1100 && (ntohl(user->ip.s_addr) != INADDR_LOOPBACK)
1101 && !user->uplink->burst)
1102 sockcheck_queue_address(user->ip);
1107 _sockcheck_init(void)
1109 checked_ip_dict = dict_new();
1110 dict_set_free_data(checked_ip_dict, free);
1111 sci_list_init(&pending_sci_list);
1112 sockcheck_num_clients = 0;
1113 sockcheck_read_tests();
1114 timeq_del(0, sockcheck_clean_cache, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
1115 client_list = calloc(sockcheck_conf.max_clients, sizeof(client_list[0]));
1116 timeq_add(now+sockcheck_conf.max_cache_age, sockcheck_clean_cache, 0);
1120 sockcheck_read_conf(void)
1125 /* set the defaults here in case the entire record is missing */
1126 sockcheck_conf.max_clients = 32;
1127 sockcheck_conf.max_read = 1024;
1128 sockcheck_conf.gline_duration = 3600;
1129 sockcheck_conf.max_cache_age = 60;
1130 if (sockcheck_conf.local_addr) {
1131 free(sockcheck_conf.local_addr);
1132 sockcheck_conf.local_addr = NULL;
1134 /* now try to read from the conf database */
1135 if ((my_node = conf_get_data("modules/sockcheck", RECDB_OBJECT))) {
1136 str = database_get_data(my_node, "max_sockets", RECDB_QSTRING);
1137 if (str) sockcheck_conf.max_clients = strtoul(str, NULL, 0);
1138 str = database_get_data(my_node, "max_clients", RECDB_QSTRING);
1139 if (str) sockcheck_conf.max_clients = strtoul(str, NULL, 0);
1140 str = database_get_data(my_node, "max_read", RECDB_QSTRING);
1141 if (str) sockcheck_conf.max_read = strtoul(str, NULL, 0);
1142 str = database_get_data(my_node, "max_cache_age", RECDB_QSTRING);
1143 if (str) sockcheck_conf.max_cache_age = ParseInterval(str);
1144 str = database_get_data(my_node, "gline_duration", RECDB_QSTRING);
1145 if (str) sockcheck_conf.gline_duration = ParseInterval(str);
1146 str = database_get_data(my_node, "address", RECDB_QSTRING);
1148 struct sockaddr_in *sin;
1151 sockcheck_conf.local_addr_len = sizeof(*sin);
1152 if (getipbyname(str, &addr)) {
1153 sin = malloc(sockcheck_conf.local_addr_len);
1154 sin->sin_family = AF_INET;
1156 sin->sin_addr.s_addr = addr;
1160 memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
1161 sockcheck_conf.local_addr = sin;
1163 log_module(PC_LOG, LOG_ERROR, "Error: Unable to get host named `%s', not checking a specific address.", str);
1164 sockcheck_conf.local_addr = NULL;
1171 sockcheck_init(void)
1173 PC_LOG = log_register_type("ProxyCheck", "file:proxycheck.log");
1174 conf_register_reload(sockcheck_read_conf);
1175 reg_exit_func(sockcheck_shutdown);
1177 message_register_table(msgtab);
1179 sockcheck_module = module_register("ProxyCheck", PC_LOG, "mod-sockcheck.help", NULL);
1180 modcmd_register(sockcheck_module, "defproxy", cmd_defproxy, 2, 0, "level", "999", NULL);
1181 modcmd_register(sockcheck_module, "hostscan", cmd_hostscan, 2, 0, "level", "650", NULL);
1182 modcmd_register(sockcheck_module, "clearhost", cmd_clearhost, 2, 0, "level", "650", NULL);
1183 modcmd_register(sockcheck_module, "stats proxycheck", cmd_stats_proxycheck, 0, 0, NULL);
1184 reg_new_user_func(sockcheck_new_user);
1189 sockcheck_finalize(void)