1 /* mod-sockcheck.c - insecure proxy checking
2 * Copyright 2000-2004 srvx Development Team
4 * This file is part of x3.
6 * x3 is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
27 #ifdef HAVE_SYS_SOCKET_H
28 #include <sys/socket.h>
30 #ifdef HAVE_NETINET_IN_H
31 #include <netinet/in.h>
33 #ifdef HAVE_ARPA_INET_H
34 #include <arpa/inet.h>
37 /* TODO, 1.3 or later: allow rules like "27374:" "reject:Subseven detected";
38 * (For convenience; right now it assumes that there will be a state
39 * rather than an immediate response upon connection.)
42 #if !defined(SOCKCHECK_DEBUG)
43 #define SOCKCHECK_DEBUG 0
45 #define SOCKCHECK_TEST_DB "sockcheck.conf"
47 enum sockcheck_decision
{
57 enum sockcheck_decision decision
;
58 char hostname
[IRC_NTOP_MAX_SIZE
]; /* acts as key for checked_ip_dict */
59 } *sockcheck_cache_info
;
61 DECLARE_LIST(sci_list
, sockcheck_cache_info
);
62 DEFINE_LIST(sci_list
, sockcheck_cache_info
)
64 /* Here's the list of hosts that need to be started on.
66 static struct sci_list pending_sci_list
;
68 /* Map of previously checked IPs state (if we've accepted the address yet).
69 * The data for each entry is a pointer to a sockcheck_cache_info.
71 static dict_t checked_ip_dict
;
73 /* Each sockcheck template is formed as a Mealy state machine (that is,
74 * the output on a state transition is a function of both the current
75 * state and the input). Mealy state machines require fewer states to
76 * match the same input than Moore machines (where the output is only
77 * a function of the current state).
79 * A state is characterized by sending some data (possibly nothing),
80 * waiting a certain amount of time to receive one of zero or more
81 * responses, and a decision (accept, reject, continue to another
82 * state) based on the received response.
85 struct sockcheck_response
{
87 struct sockcheck_state
*next
;
90 DECLARE_LIST(response_list
, struct sockcheck_response
*);
91 DEFINE_LIST(response_list
, struct sockcheck_response
*)
92 static unsigned int max_responses
;
94 struct sockcheck_state
{
96 unsigned short timeout
;
98 enum sockcheck_decision type
;
100 struct response_list responses
;
103 struct sockcheck_list
{
104 unsigned int size
, used
, refs
;
105 struct sockcheck_state
**list
;
111 static struct sockcheck_list
*tests
;
113 /* Stuff to track client state, one instance per open connection. */
114 struct sockcheck_client
{
116 struct sockcheck_list
*tests
;
117 sockcheck_cache_info addr
;
118 unsigned int client_index
;
119 unsigned int test_index
;
120 unsigned short test_rep
;
121 struct sockcheck_state
*state
;
122 unsigned int read_size
, read_used
, read_pos
;
124 const char **resp_state
;
128 unsigned int max_clients
;
129 unsigned int max_read
;
130 unsigned int gline_duration
;
131 unsigned int max_cache_age
;
132 struct sockaddr
*local_addr
;
136 static unsigned int sockcheck_num_clients
;
137 static struct sockcheck_client
**client_list
;
138 static unsigned int proxies_detected
, checked_ip_count
;
139 static struct module *sockcheck_module
;
140 static struct log_type
*PC_LOG
;
141 const char *sockcheck_module_deps
[] = { NULL
};
143 static const struct message_entry msgtab
[] = {
144 { "PCMSG_PROXY_DEFINITION_FAILED", "Proxy definition failed: %s" },
145 { "PCMSG_PROXY_DEFINITION_SUCCEEDED", "New proxy type defined." },
146 { "PCMSG_UNSCANNABLE_IP", "%s has a spoofed, hidden or localnet IP." },
147 { "PCMSG_ADDRESS_QUEUED", "$b%s$b is now queued to be proxy-checked." },
148 { "PCMSG_ADDRESS_UNRESOLVED", "Unable to resolve $b%s$b to an IP address." },
149 { "PCMSG_CHECKING_ADDRESS", "$b%s$b is currently being checked; unable to clear it." },
150 { "PCMSG_NOT_REMOVED_FROM_CACHE", "$b%s$b was not cached and therefore was not cleared." },
151 { "PCMSG_REMOVED_FROM_CACHE", "$b%s$b was cleared from the cached hosts list." },
152 { "PCMSG_DISABLED", "Proxy scanning is $bdisabled$b." },
153 { "PCMSG_NOT_CACHED", "No proxycheck records exist for IP %s." },
154 { "PCMSG_STATUS_CHECKING", "IP %s proxycheck state: last touched %s ago, still checking" },
155 { "PCMSG_STATUS_ACCEPTED", "IP %s proxycheck state: last touched %s ago, accepted" },
156 { "PCMSG_STATUS_REJECTED", "IP %s proxycheck state: last touched %s ago, rejected: %s" },
157 { "PCMSG_STATUS_UNKNOWN", "IP %s proxycheck state: last touched %s ago, invalid status" },
158 { "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." },
162 static struct sockcheck_list
*
163 sockcheck_list_alloc(unsigned int size
)
165 struct sockcheck_list
*list
= malloc(sizeof(*list
));
169 list
->list
= malloc(list
->size
*sizeof(list
->list
[0]));
174 sockcheck_list_append(struct sockcheck_list
*list
, struct sockcheck_state
*new_item
)
176 if (list
->used
== list
->size
) {
178 list
->list
= realloc(list
->list
, list
->size
*sizeof(list
->list
[0]));
180 list
->list
[list
->used
++] = new_item
;
183 static struct sockcheck_list
*
184 sockcheck_list_clone(struct sockcheck_list
*old_list
)
186 struct sockcheck_list
*new_list
= malloc(sizeof(*new_list
));
187 new_list
->used
= old_list
->used
;
189 new_list
->size
= old_list
->size
;
190 new_list
->list
= malloc(new_list
->size
*sizeof(new_list
->list
[0]));
191 memcpy(new_list
->list
, old_list
->list
, new_list
->used
*sizeof(new_list
->list
[0]));
196 sockcheck_list_unref(struct sockcheck_list
*list
)
198 if (!list
|| --list
->refs
> 0) return;
204 sockcheck_issue_gline(sockcheck_cache_info sci
)
206 char addr
[IRC_NTOP_MAX_SIZE
+ 2] = {'*', '@', '\0'};
207 irc_ntop(addr
+ 2, sizeof(addr
) - 2, &sci
->addr
);
208 log_module(PC_LOG
, LOG_INFO
, "Issuing gline for client at %s: %s", addr
+ 2, sci
->reason
);
209 gline_add("ProxyCheck", addr
, sockcheck_conf
.gline_duration
, sci
->reason
, now
, 1, 1);
213 static struct sockcheck_client
*
214 sockcheck_alloc_client(sockcheck_cache_info sci
)
216 struct sockcheck_client
*client
;
217 client
= calloc(1, sizeof(*client
));
218 client
->tests
= tests
;
219 client
->tests
->refs
++;
221 client
->read_size
= sockcheck_conf
.max_read
;
222 client
->read
= malloc(client
->read_size
);
223 client
->resp_state
= malloc(max_responses
* sizeof(client
->resp_state
[0]));
228 sockcheck_free_client(struct sockcheck_client
*client
)
230 if (SOCKCHECK_DEBUG
) {
231 log_module(PC_LOG
, LOG_INFO
, "Goodbye %s (%p)! I set you free!", client
->addr
->hostname
, (void*)client
);
234 ioset_close(client
->fd
, 1);
236 sockcheck_list_unref(client
->tests
);
238 free(client
->resp_state
);
242 static void sockcheck_start_client(unsigned int idx
);
243 static void sockcheck_begin_test(struct sockcheck_client
*client
);
244 static void sockcheck_advance(struct sockcheck_client
*client
, unsigned int next_state
);
247 sockcheck_timeout_client(void *data
)
249 struct sockcheck_client
*client
= data
;
250 if (SOCKCHECK_DEBUG
) {
251 log_module(PC_LOG
, LOG_INFO
, "Client %s timed out.", client
->addr
->hostname
);
254 sockcheck_advance(client
, client
->state
->responses
.used
-1);
258 sockcheck_print_client(const struct sockcheck_client
*client
)
260 static const char *decs
[] = {"CHECKING", "ACCEPT", "REJECT"};
261 log_module(PC_LOG
, LOG_INFO
, "client %p: { addr = %p { decision = %s; last_touched = "FMT_TIME_T
"; reason = %s; hostname = \"%s\" }; "
262 "test_index = %d; state = %p { port = %d; type = %s; template = \"%s\"; ... }; "
263 "fd = %p(%d); read = %p; read_size = %d; read_used = %d; read_pos = %d; }",
264 (void*)client
, (void*)client
->addr
, decs
[client
->addr
->decision
], client
->addr
->last_touched
,
265 client
->addr
->reason
, client
->addr
->hostname
,
266 client
->test_index
, (void*)client
->state
,
267 (client
->state
? client
->state
->port
: 0),
268 (client
->state
? decs
[client
->state
->type
] : "N/A"),
269 (client
->state
? client
->state
->template : "N/A"),
270 (void*)client
->fd
, (client
->fd
? client
->fd
->fd
: 0),
271 (void*)client
->read
, client
->read_size
, client
->read_used
, client
->read_pos
);
274 static char hexvals
[256] = {
275 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */
276 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16 */
277 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32 */
278 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 48 */
279 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 64 */
280 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 */
281 0,10,11,12,13,14,15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 96 */
282 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 112 */
286 expand_var(const struct sockcheck_client
*client
, char var
, char **p_expansion
, unsigned int *p_exp_length
)
288 extern struct cManagerNode cManager
;
289 const char *expansion
;
290 unsigned int exp_length
;
294 /* expand variable */
297 expansion
= client
->addr
->hostname
;
298 exp_length
= strlen(expansion
);
301 exp4
= client
->addr
->addr
.in6_32
[3];
302 exp_length
= sizeof(exp4
);
303 expansion
= (char*)&exp4
;
306 exp2
= htons(client
->state
->port
);
307 exp_length
= sizeof(exp2
);
308 expansion
= (char*)&exp2
;
311 expansion
= cManager
.uplink
->host
;
312 exp_length
= strlen(expansion
);
315 log_module(PC_LOG
, LOG_WARNING
, "Request to expand unknown sockcheck variable $%c, using empty expansion.", var
);
320 *p_expansion
= malloc(exp_length
);
321 memcpy(*p_expansion
, expansion
, exp_length
);
324 *p_exp_length
= exp_length
;
329 sockcheck_check_template(const char *template, int is_input
)
332 if (is_input
&& !strcmp(template, "other")) return 1;
333 for (nn
=0; template[nn
]; nn
+= 2) {
334 switch (template[nn
]) {
336 if (!template[nn
+1]) {
337 log_module(MAIN_LOG
, LOG_ERROR
, "ProxyCheck template %s had = at end of template; needs a second character.", template);
342 switch (template[nn
+1]) {
343 case 'c': case 'i': case 'p': case 'u': break;
345 log_module(MAIN_LOG
, LOG_ERROR
, "ProxyCheck template %s refers to unknown variable %c (pos %d).", template, template[nn
+1], nn
);
351 log_module(MAIN_LOG
, LOG_ERROR
, "ProxyCheck template %s: . is only valid in input templates.", template);
354 if (template[nn
+1] != '.') {
355 log_module(MAIN_LOG
, LOG_ERROR
, "ProxyCheck template %s expects .. to come in twos (pos %d).", template, nn
);
359 case '0': case '1': case '2': case '3': case '4':
360 case '5': case '6': case '7': case '8': case '9':
361 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
362 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
363 if (!hexvals
[(unsigned char)template[nn
+1]] && (template[nn
+1] != '0')) {
364 log_module(MAIN_LOG
, LOG_ERROR
, "ProxyCheck template %s expects hex characters to come in twos (pos %d).", template, nn
);
369 log_module(MAIN_LOG
, LOG_ERROR
, "ProxyCheck template %s: unrecognized character '%c' (pos %d).", template, template[nn
], nn
);
377 sockcheck_elaborate_state(struct sockcheck_client
*client
)
379 const char *template;
382 for (template = client
->state
->template, nn
= 0; template[nn
]; nn
+= 2) {
383 switch (template[nn
]) {
384 case '=': ioset_write(client
->fd
, template+nn
+1, 1); break;
385 case '0': case '1': case '2': case '3': case '4':
386 case '5': case '6': case '7': case '8': case '9':
387 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
388 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': {
389 char ch
= hexvals
[(unsigned char)template[nn
]] << 4
390 | hexvals
[(unsigned char)template[nn
+1]];
391 ioset_write(client
->fd
, &ch
, 1);
396 unsigned int exp_length
;
397 expand_var(client
, template[nn
+1], &expansion
, &exp_length
);
398 ioset_write(client
->fd
, expansion
, exp_length
);
404 for (nn
=0; nn
<client
->state
->responses
.used
; nn
++) {
405 /* Set their resp_state to the start of the response. */
406 client
->resp_state
[nn
] = client
->state
->responses
.list
[nn
]->template;
407 /* If it doesn't require reading, take it now. */
408 if (client
->resp_state
[nn
] && !*client
->resp_state
[nn
]) {
409 if (SOCKCHECK_DEBUG
) {
410 log_module(PC_LOG
, LOG_INFO
, "Skipping straight to easy option %d for %p.", nn
, (void*)client
);
412 sockcheck_advance(client
, nn
);
416 timeq_add(now
+ client
->state
->timeout
, sockcheck_timeout_client
, client
);
417 if (SOCKCHECK_DEBUG
) {
418 log_module(PC_LOG
, LOG_INFO
, "Elaborated state for %s:", client
->addr
->hostname
);
419 sockcheck_print_client(client
);
424 sockcheck_decide(struct sockcheck_client
*client
, enum sockcheck_decision decision
)
429 client
->addr
->decision
= decision
;
430 client
->addr
->last_touched
= now
;
434 if (SOCKCHECK_DEBUG
) {
435 log_module(PC_LOG
, LOG_INFO
, "Proxy check passed for client at %s.", client
->addr
->hostname
);
439 client
->addr
->reason
= client
->state
->template;
441 sockcheck_issue_gline(client
->addr
);
442 if (SOCKCHECK_DEBUG
) {
443 log_module(PC_LOG
, LOG_INFO
, "Proxy check rejects client at %s (%s)", client
->addr
->hostname
, client
->addr
->reason
);
445 /* Don't compare test_index != 0 directly, because somebody
446 * else may have reordered the tests already. */
447 if (client
->tests
->list
[client
->test_index
] != tests
->list
[0]) {
448 struct sockcheck_list
*new_tests
= sockcheck_list_clone(tests
);
449 struct sockcheck_state
*new_first
= client
->tests
->list
[client
->test_index
];
450 for (n
=0; (n
<tests
->used
) && (tests
->list
[n
] != new_first
); n
++) ;
451 for (; n
>0; n
--) new_tests
->list
[n
] = new_tests
->list
[n
-1];
452 new_tests
->list
[0] = new_first
;
453 sockcheck_list_unref(tests
);
458 log_module(PC_LOG
, LOG_ERROR
, "BUG: sockcheck_decide(\"%s\", %d): unrecognized decision.", client
->addr
->hostname
, decision
);
460 n
= client
->client_index
;
461 sockcheck_free_client(client
);
462 if ((--sockcheck_num_clients
< sockcheck_conf
.max_clients
)
463 && (pending_sci_list
.used
> 0)) {
464 sockcheck_start_client(n
);
471 sockcheck_advance(struct sockcheck_client
*client
, unsigned int next_state
)
473 struct sockcheck_state
*ns
;
476 timeq_del(0, sockcheck_timeout_client
, client
, TIMEQ_IGNORE_WHEN
);
477 if (SOCKCHECK_DEBUG
) {
480 static const char *hexmap
= "0123456789ABCDEF";
481 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
);
482 for (n
=0; n
<client
->read_used
; n
++) {
483 for (m
=0; (m
<(sizeof(buffer
)-1)>>1) && ((n
+m
) < client
->read_used
); m
++) {
484 buffer
[m
<< 1] = hexmap
[client
->read
[n
+m
] >> 4];
485 buffer
[m
<< 1 | 1] = hexmap
[client
->read
[n
+m
] & 15];
488 log_module(PC_LOG
, LOG_INFO
, " .. read data: %s", buffer
);
491 sockcheck_print_client(client
);
494 ns
= client
->state
= client
->state
->responses
.list
[next_state
]->next
;
497 sockcheck_elaborate_state(client
);
500 sockcheck_decide(client
, REJECT
);
503 if (++client
->test_rep
< client
->tests
->list
[client
->test_index
]->reps
) {
504 sockcheck_begin_test(client
);
505 } else if (++client
->test_index
< client
->tests
->used
) {
506 client
->test_rep
= 0;
507 sockcheck_begin_test(client
);
509 sockcheck_decide(client
, ACCEPT
);
513 log_module(PC_LOG
, LOG_ERROR
, "BUG: unknown next-state type %d (after %p).", ns
->type
, (void*)client
->state
);
519 sockcheck_readable(struct io_fd
*fd
)
521 /* read what we can from the fd */
522 struct sockcheck_client
*client
= fd
->data
;
527 res
= read(fd
->fd
, client
->read
+ client
->read_used
, client
->read_size
- client
->read_used
);
529 switch (res
= errno
) {
531 log_module(PC_LOG
, LOG_ERROR
, "BUG: sockcheck_readable(%d/%s): read() returned errno %d (%s)", fd
->fd
, client
->addr
->hostname
, errno
, strerror(errno
));
535 sockcheck_advance(client
, client
->state
->responses
.used
- 1);
538 } else if (res
== 0) {
539 sockcheck_advance(client
, client
->state
->responses
.used
- 1);
542 client
->read_used
+= res
;
544 if (SOCKCHECK_DEBUG
) {
547 static const char *hexmap
= "0123456789ABCDEF";
548 for (n
=0; n
<client
->read_used
; n
++) {
549 for (m
=0; (m
<(sizeof(buffer
)-1)>>1) && ((n
+m
) < client
->read_used
); m
++) {
550 buffer
[m
<< 1] = hexmap
[client
->read
[n
+m
] >> 4];
551 buffer
[m
<< 1 | 1] = hexmap
[client
->read
[n
+m
] & 15];
554 log_module(PC_LOG
, LOG_INFO
, "read %d bytes data: %s", client
->read_used
, buffer
);
559 /* See if what's been read matches any of the expected responses */
560 while (client
->read_pos
< client
->read_used
) {
561 unsigned int last_pos
= client
->read_pos
;
563 const char *resp_state
;
565 for (nn
=0; nn
<(client
->state
->responses
.used
-1); nn
++) {
567 unsigned int exp_length
= 1, free_exp
= 0;
568 /* compare against possible target */
569 resp_state
= client
->resp_state
[nn
];
570 if (resp_state
== NULL
) continue;
571 switch (*resp_state
) {
573 bleh
= resp_state
[1];
577 /* any character passes */
581 case '0': case '1': case '2': case '3': case '4':
582 case '5': case '6': case '7': case '8': case '9':
583 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
584 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
585 bleh
= hexvals
[(unsigned char)resp_state
[0]] << 4
586 | hexvals
[(unsigned char)resp_state
[1]];
590 expand_var(client
, resp_state
[1], &expected
, &exp_length
);
594 if (client
->read_pos
+exp_length
<= client
->read_used
) {
595 if (exp_length
&& memcmp(client
->read
+client
->read_pos
, expected
, exp_length
)) {
598 client
->read_pos
+= exp_length
;
601 /* can't check the variable yet, so come back later */
604 if (free_exp
) free(expected
);
606 client
->resp_state
[nn
] = resp_state
= resp_state
+ 2;
608 sockcheck_advance(client
, nn
);
612 client
->resp_state
[nn
] = NULL
;
615 if (last_pos
== client
->read_pos
) break;
618 /* nothing seemed to match. what now? */
619 if (client
->read_used
>= client
->read_size
) {
620 /* we got more data than we expected to get .. don't read any more */
621 if (SOCKCHECK_DEBUG
) {
622 log_module(PC_LOG
, LOG_INFO
, "Buffer filled (unmatched) for client %s", client
->addr
->hostname
);
624 sockcheck_advance(client
, client
->state
->responses
.used
-1);
630 sockcheck_connected(struct io_fd
*fd
, int rc
)
632 struct sockcheck_client
*client
= fd
->data
;
637 log_module(PC_LOG
, LOG_ERROR
, "BUG: connect() got error %d (%s) for client at %s.", rc
, strerror(rc
), client
->addr
->hostname
);
641 if (SOCKCHECK_DEBUG
) {
642 log_module(PC_LOG
, LOG_INFO
, "Client %s gave us errno %d (%s)", client
->addr
->hostname
, rc
, strerror(rc
));
644 sockcheck_advance(client
, client
->state
->responses
.used
-1);
648 if (SOCKCHECK_DEBUG
) {
649 log_module(PC_LOG
, LOG_INFO
, "Connected: to %s port %d.", client
->addr
->hostname
, client
->state
->port
);
651 sockcheck_elaborate_state(client
);
655 sockcheck_begin_test(struct sockcheck_client
*client
)
660 ioset_close(client
->fd
, 1);
663 client
->state
= client
->tests
->list
[client
->test_index
];
664 client
->read_pos
= 0;
665 client
->read_used
= 0;
666 io_fd
= ioset_connect(sockcheck_conf
.local_addr
, sockcheck_conf
.local_addr_len
, client
->addr
->hostname
, client
->state
->port
, 0, client
, sockcheck_connected
);
669 client
->test_index
++;
672 io_fd
->readable_cb
= sockcheck_readable
;
673 timeq_add(now
+ client
->state
->timeout
, sockcheck_timeout_client
, client
);
674 if (SOCKCHECK_DEBUG
) {
675 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
, (void*)io_fd
);
678 } while (client
->test_index
< client
->tests
->used
);
679 /* Ran out of tests to run; accept this client. */
680 sockcheck_decide(client
, ACCEPT
);
684 sockcheck_start_client(unsigned int idx
)
686 sockcheck_cache_info sci
;
687 struct sockcheck_client
*client
;
689 if (pending_sci_list
.used
== 0) return;
690 if (!(sci
= pending_sci_list
.list
[0])) {
691 log_module(PC_LOG
, LOG_ERROR
, "BUG: sockcheck_start_client(%d) found null pointer in pending_sci_list.", idx
);
694 memmove(pending_sci_list
.list
, pending_sci_list
.list
+1,
695 (--pending_sci_list
.used
)*sizeof(pending_sci_list
.list
[0]));
696 sockcheck_num_clients
++;
698 client
= client_list
[idx
] = sockcheck_alloc_client(sci
);
699 log_module(PC_LOG
, LOG_INFO
, "Proxy-checking client at %s as client %d (%p) of %d.", sci
->hostname
, idx
, (void*)client
, sockcheck_num_clients
);
700 client
->test_rep
= 0;
701 client
->client_index
= idx
;
702 sockcheck_begin_test(client
);
706 sockcheck_queue_address(irc_in_addr_t addr
)
708 sockcheck_cache_info sci
;
709 const char *ipstr
= irc_ntoa(&addr
);
711 sci
= dict_find(checked_ip_dict
, ipstr
, NULL
);
714 switch (sci
->decision
) {
716 /* We are already checking this host. */
719 if ((sci
->last_touched
+ sockcheck_conf
.max_cache_age
) >= (unsigned)now
) return;
722 if ((sci
->last_touched
+ sockcheck_conf
.gline_duration
) >= (unsigned)now
) {
723 sockcheck_issue_gline(sci
);
728 dict_remove(checked_ip_dict
, sci
->hostname
);
730 sci
= calloc(1, sizeof(*sci
));
731 sci
->decision
= CHECKING
;
732 sci
->last_touched
= now
;
735 strncpy(sci
->hostname
, ipstr
, sizeof(sci
->hostname
));
736 dict_insert(checked_ip_dict
, sci
->hostname
, sci
);
737 sci_list_append(&pending_sci_list
, sci
);
738 if (sockcheck_num_clients
< sockcheck_conf
.max_clients
)
739 sockcheck_start_client(sockcheck_num_clients
);
743 sockcheck_uncache_host(const char *name
)
745 sockcheck_cache_info sci
;
746 if ((sci
= dict_find(checked_ip_dict
, name
, NULL
))
747 && (sci
->decision
== CHECKING
)) {
750 return dict_remove(checked_ip_dict
, name
);
754 sockcheck_create_response(const char *key
, void *data
, void *extra
)
756 const char *str
, *end
;
757 struct record_data
*rd
= data
;
758 struct sockcheck_state
*parent
= extra
;
759 struct sockcheck_response
*resp
;
763 /* allocate memory and tack it onto parent->responses */
764 resp
= malloc(sizeof(*resp
));
765 for (end
= key
; *end
!= ':' && *end
!= 0; end
+= 2 && end
) ;
766 templ
= malloc(end
- key
+ 1);
767 memcpy(templ
, key
, end
- key
);
768 templ
[end
- key
] = 0;
769 resp
->template = templ
;
770 if (!sockcheck_check_template(resp
->template, 1)) _exit(1);
771 resp
->next
= malloc(sizeof(*resp
->next
));
772 resp
->next
->port
= parent
->port
;
773 response_list_append(&parent
->responses
, resp
);
774 /* now figure out how to create resp->next */
775 if ((str
= GET_RECORD_QSTRING(rd
))) {
776 if (!ircncasecmp(str
, "reject", 6)) {
777 resp
->next
->type
= REJECT
;
778 } else if (!ircncasecmp(str
, "accept", 6)) {
779 resp
->next
->type
= ACCEPT
;
781 log_module(PC_LOG
, LOG_ERROR
, "Error: unknown sockcheck decision `%s', defaulting to accept.", str
);
782 resp
->next
->type
= ACCEPT
;
785 resp
->next
->template = strdup(str
+7);
787 resp
->next
->template = strdup("No explanation given");
789 } else if ((resps
= GET_RECORD_OBJECT(rd
))) {
790 resp
->next
->type
= CHECKING
;
791 response_list_init(&resp
->next
->responses
);
793 resp
->next
->template = strdup(end
+1);
794 if (!sockcheck_check_template(resp
->next
->template, 0)) _exit(1);
796 resp
->next
->template = strdup("");
798 dict_foreach(resps
, sockcheck_create_response
, resp
->next
);
803 /* key: PORT:send-pattern, as in keys of sockcheck.conf.example
804 * data: recdb record_data containing response
805 * extra: struct sockcheck_list* to append test to
808 sockcheck_create_test(const char *key
, void *data
, void *extra
)
811 struct record_data
*rd
;
813 struct sockcheck_state
*new_test
;
817 new_test
= malloc(sizeof(*new_test
));
818 new_test
->template = NULL
;
820 new_test
->port
= strtoul(key
, &end
, 0);
821 new_test
->timeout
= 5;
822 new_test
->type
= CHECKING
;
823 response_list_init(&new_test
->responses
);
824 if (!(object
= GET_RECORD_OBJECT(rd
))) {
825 log_module(PC_LOG
, LOG_ERROR
, "Error: misformed sockcheck test `%s', skipping it.", key
);
831 case '@': new_test
->timeout
= strtoul(end
+1, &end
, 0); break;
832 case '*': new_test
->reps
= strtoul(end
+1, &end
, 0); break;
834 new_test
->template = strdup(end
+1);
836 if (!sockcheck_check_template(new_test
->template, 0)) _exit(1);
839 log_module(PC_LOG
, LOG_ERROR
, "Error: misformed sockcheck test `%s', skipping it.", key
);
844 if (!new_test
->template) {
845 log_module(PC_LOG
, LOG_ERROR
, "Error: misformed sockcheck test `%s', skipping it.", key
);
849 dict_foreach(object
, sockcheck_create_response
, new_test
);
850 /* If none of the responses have template "other", create a
851 * default response that goes to accept. */
852 for (n
=0; n
<new_test
->responses
.used
; n
++) {
853 if (!strcmp(new_test
->responses
.list
[n
]->template, "other")) break;
855 if (n
== new_test
->responses
.used
) {
856 rd
= alloc_record_data_qstring("accept");
857 sockcheck_create_response("other", rd
, new_test
);
858 free_record_data(rd
);
859 } else if (n
!= (new_test
->responses
.used
- 1)) {
860 struct sockcheck_response
*tmp
;
861 /* switch the response for "other" to the end */
862 tmp
= new_test
->responses
.list
[new_test
->responses
.used
- 1];
863 new_test
->responses
.list
[new_test
->responses
.used
- 1] = new_test
->responses
.list
[n
];
864 new_test
->responses
.list
[n
] = tmp
;
866 if (new_test
->responses
.used
> max_responses
) {
867 max_responses
= new_test
->responses
.used
;
869 sockcheck_list_append(extra
, new_test
);
874 sockcheck_read_tests(void)
877 struct sockcheck_list
*new_tests
;
878 test_db
= parse_database(SOCKCHECK_TEST_DB
);
881 if (dict_size(test_db
) > 0) {
882 new_tests
= sockcheck_list_alloc(dict_size(test_db
));
883 dict_foreach(test_db
, sockcheck_create_test
, new_tests
);
884 if (tests
) sockcheck_list_unref(tests
);
887 log_module(PC_LOG
, LOG_ERROR
, "%s was empty - disabling sockcheck.", SOCKCHECK_TEST_DB
);
889 free_database(test_db
);
893 sockcheck_free_state(struct sockcheck_state
*state
)
896 if (state
->type
== CHECKING
) {
897 for (n
=0; n
<state
->responses
.used
; n
++) {
898 free((char*)state
->responses
.list
[n
]->template);
899 sockcheck_free_state(state
->responses
.list
[n
]->next
);
900 free(state
->responses
.list
[n
]);
902 response_list_clean(&state
->responses
);
904 free((char*)state
->template);
909 sockcheck_add_test(const char *desc
)
911 struct sockcheck_list
*new_tests
;
914 struct record_data
*rd
;
916 if ((reason
= parse_record(desc
, &name
, &rd
)))
918 new_tests
= sockcheck_list_clone(tests
);
919 if (sockcheck_create_test(name
, rd
, new_tests
)) {
920 sockcheck_list_unref(new_tests
);
921 return "Sockcheck test parse error";
923 sockcheck_list_unref(tests
);
929 sockcheck_shutdown(UNUSED_ARG(void *extra
))
934 for (n
=0; n
<sockcheck_conf
.max_clients
; n
++) {
936 sockcheck_free_client(client_list
[n
]);
940 sockcheck_num_clients
= 0;
941 dict_delete(checked_ip_dict
);
942 sci_list_clean(&pending_sci_list
);
944 for (n
=0; n
<tests
->used
; n
++)
945 sockcheck_free_state(tests
->list
[n
]);
946 sockcheck_list_unref(tests
);
947 if (sockcheck_conf
.local_addr
) {
948 free(sockcheck_conf
.local_addr
);
949 sockcheck_conf
.local_addr_len
= 0;
954 sockcheck_clean_cache(UNUSED_ARG(void *data
))
957 dict_iterator_t it
, next
;
958 sockcheck_cache_info sci
;
962 if (SOCKCHECK_DEBUG
) {
963 struct string_buffer sb
;
964 string_buffer_init(&sb
);
965 /* Remember which clients we're still checking; we're not allowed to remove them. */
966 for (curr_clients
= dict_new(), nn
=0; nn
< sockcheck_conf
.max_clients
; nn
++) {
967 if (!client_list
[nn
])
969 dict_insert(curr_clients
, client_list
[nn
]->addr
->hostname
, client_list
[nn
]);
970 string_buffer_append(&sb
, ' ');
971 string_buffer_append_string(&sb
, client_list
[nn
]->addr
->hostname
);
973 string_buffer_append(&sb
, '\0');
974 log_module(PC_LOG
, LOG_INFO
, "Cleaning sockcheck cache at "FMT_TIME_T
"; current clients: %s.", now
, sb
.list
);
975 string_buffer_clean(&sb
);
977 for (curr_clients
= dict_new(), nn
=0; nn
< sockcheck_conf
.max_clients
; nn
++) {
978 if (!client_list
[nn
])
980 dict_insert(curr_clients
, client_list
[nn
]->addr
->hostname
, client_list
[nn
]);
984 for (it
=dict_first(checked_ip_dict
); it
; it
=next
) {
985 next
= iter_next(it
);
987 max_age
= (sci
->decision
== REJECT
) ? sockcheck_conf
.gline_duration
: sockcheck_conf
.max_cache_age
;
988 if (((sci
->last_touched
+ max_age
) < now
)
989 && !dict_find(curr_clients
, sci
->hostname
, NULL
)) {
990 if (SOCKCHECK_DEBUG
) {
991 log_module(PC_LOG
, LOG_INFO
, " .. nuking %s (last touched "FMT_TIME_T
").", sci
->hostname
, sci
->last_touched
);
993 dict_remove(checked_ip_dict
, sci
->hostname
);
996 dict_delete(curr_clients
);
997 timeq_add(now
+sockcheck_conf
.max_cache_age
, sockcheck_clean_cache
, 0);
1000 static MODCMD_FUNC(cmd_defproxy
)
1004 if ((reason
= sockcheck_add_test(unsplit_string(argv
+1, argc
-1, NULL
)))) {
1005 reply("PCMSG_PROXY_DEFINITION_FAILED", reason
);
1008 reply("PCMSG_PROXY_DEFINITION_SUCCEEDED");
1012 static MODCMD_FUNC(cmd_hostscan
)
1015 irc_in_addr_t ipaddr
;
1016 char hnamebuf
[IRC_NTOP_MAX_SIZE
];
1018 for (n
=1; n
<argc
; n
++) {
1019 struct userNode
*un
= GetUserH(argv
[n
]);
1022 if (!irc_in_addr_is_valid(un
->ip
)
1023 || irc_in_addr_is_loopback(un
->ip
)) {
1024 reply("PCMSG_UNSCANNABLE_IP", un
->nick
);
1026 irc_ntop(hnamebuf
, sizeof(hnamebuf
), &un
->ip
);
1027 sockcheck_queue_address(un
->ip
);
1028 reply("PCMSG_ADDRESS_QUEUED", hnamebuf
);
1031 char *scanhost
= argv
[n
];
1032 if (irc_pton(&ipaddr
, NULL
, scanhost
)) {
1033 sockcheck_queue_address(ipaddr
);
1034 reply("PCMSG_ADDRESS_QUEUED", scanhost
);
1036 reply("PCMSG_ADDRESS_UNRESOLVED", scanhost
);
1043 static MODCMD_FUNC(cmd_clearhost
)
1046 char hnamebuf
[IRC_NTOP_MAX_SIZE
];
1048 for (n
=1; n
<argc
; n
++) {
1049 struct userNode
*un
= GetUserH(argv
[n
]);
1050 const char *scanhost
;
1053 irc_ntop(hnamebuf
, sizeof(hnamebuf
), &un
->ip
);
1054 scanhost
= hnamebuf
;
1058 switch (sockcheck_uncache_host(scanhost
)) {
1060 reply("PCMSG_CHECKING_ADDRESS", scanhost
);
1063 reply("PCMSG_NOT_REMOVED_FROM_CACHE", scanhost
);
1066 reply("PCMSG_REMOVED_FROM_CACHE", scanhost
);
1073 static MODCMD_FUNC(cmd_stats_proxycheck
)
1076 const char *hostname
= argv
[1];
1077 char elapse_buf
[INTERVALLEN
];
1080 sockcheck_cache_info sci
= dict_find(checked_ip_dict
, hostname
, NULL
);
1082 reply("PCMSG_NOT_CACHED", hostname
);
1085 intervalString(elapse_buf
, now
- sci
->last_touched
, user
->handle_info
);
1086 switch (sci
->decision
) {
1087 case CHECKING
: msg
= "PCMSG_STATUS_CHECKING"; break;
1088 case ACCEPT
: msg
= "PCMSG_STATUS_ACCEPTED"; break;
1089 case REJECT
: msg
= "PCMSG_STATUS_REJECTED"; break;
1090 default: msg
= "PCMSG_STATUS_UNKNOWN"; break;
1092 reply(msg
, sci
->hostname
, elapse_buf
, sci
->reason
);
1095 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));
1101 sockcheck_new_user(struct userNode
*user
, UNUSED_ARG(void *extra
)) {
1102 /* If they have a bum IP, or are bursting in, don't proxy-check or G-line them. */
1103 if (irc_in_addr_is_valid(user
->ip
)
1104 && !irc_in_addr_is_loopback(user
->ip
)
1105 && !user
->uplink
->burst
)
1106 sockcheck_queue_address(user
->ip
);
1111 _sockcheck_init(void)
1113 checked_ip_dict
= dict_new();
1114 dict_set_free_data(checked_ip_dict
, free
);
1115 sci_list_init(&pending_sci_list
);
1116 sockcheck_num_clients
= 0;
1117 sockcheck_read_tests();
1118 timeq_del(0, sockcheck_clean_cache
, 0, TIMEQ_IGNORE_WHEN
|TIMEQ_IGNORE_DATA
);
1119 client_list
= calloc(sockcheck_conf
.max_clients
, sizeof(client_list
[0]));
1120 timeq_add(now
+sockcheck_conf
.max_cache_age
, sockcheck_clean_cache
, 0);
1124 sockcheck_read_conf(void)
1127 struct addrinfo
*ai
;
1130 /* set the defaults here in case the entire record is missing */
1131 sockcheck_conf
.max_clients
= 32;
1132 sockcheck_conf
.max_read
= 1024;
1133 sockcheck_conf
.gline_duration
= 3600;
1134 sockcheck_conf
.max_cache_age
= 60;
1135 if (sockcheck_conf
.local_addr
) {
1136 free(sockcheck_conf
.local_addr
);
1137 sockcheck_conf
.local_addr
= NULL
;
1139 /* now try to read from the conf database */
1140 if ((my_node
= conf_get_data("modules/sockcheck", RECDB_OBJECT
))) {
1141 str
= database_get_data(my_node
, "max_sockets", RECDB_QSTRING
);
1142 if (str
) sockcheck_conf
.max_clients
= strtoul(str
, NULL
, 0);
1143 str
= database_get_data(my_node
, "max_clients", RECDB_QSTRING
);
1144 if (str
) sockcheck_conf
.max_clients
= strtoul(str
, NULL
, 0);
1145 str
= database_get_data(my_node
, "max_read", RECDB_QSTRING
);
1146 if (str
) sockcheck_conf
.max_read
= strtoul(str
, NULL
, 0);
1147 str
= database_get_data(my_node
, "max_cache_age", RECDB_QSTRING
);
1148 if (str
) sockcheck_conf
.max_cache_age
= ParseInterval(str
);
1149 str
= database_get_data(my_node
, "gline_duration", RECDB_QSTRING
);
1150 if (str
) sockcheck_conf
.gline_duration
= ParseInterval(str
);
1151 str
= database_get_data(my_node
, "address", RECDB_QSTRING
);
1152 str
= database_get_data(my_node
, "bind_address", RECDB_QSTRING
);
1153 if (!str
) str
= database_get_data(my_node
, "address", RECDB_QSTRING
);
1154 if (!getaddrinfo(str
, NULL
, NULL
, &ai
)) {
1155 sockcheck_conf
.local_addr_len
= ai
->ai_addrlen
;
1156 sockcheck_conf
.local_addr
= calloc(1, ai
->ai_addrlen
);
1157 memcpy(sockcheck_conf
.local_addr
, ai
->ai_addr
, ai
->ai_addrlen
);
1160 sockcheck_conf
.local_addr_len
= 0;
1161 sockcheck_conf
.local_addr
= NULL
;
1163 log_module(PC_LOG
, LOG_ERROR
, "Error: Unable to get host named `%s', not checking from a specific address.", str
);
1169 sockcheck_init(void)
1171 PC_LOG
= log_register_type("ProxyCheck", "file:proxycheck.log");
1172 conf_register_reload(sockcheck_read_conf
);
1173 reg_exit_func(sockcheck_shutdown
, NULL
);
1175 message_register_table(msgtab
);
1177 sockcheck_module
= module_register("ProxyCheck", PC_LOG
, "mod-sockcheck.help", NULL
);
1178 modcmd_register(sockcheck_module
, "defproxy", cmd_defproxy
, 2, 0, "level", "999", NULL
);
1179 modcmd_register(sockcheck_module
, "hostscan", cmd_hostscan
, 2, 0, "level", "650", NULL
);
1180 modcmd_register(sockcheck_module
, "clearhost", cmd_clearhost
, 2, 0, "level", "650", NULL
);
1181 modcmd_register(sockcheck_module
, "stats proxycheck", cmd_stats_proxycheck
, 0, 0, NULL
);
1182 reg_new_user_func(sockcheck_new_user
, NULL
);
1187 sockcheck_finalize(void)