Initial import (again)
[srvx.git] / src / mod-sockcheck.c
1 /* sockcheck.c - insecure proxy checking
2  * Copyright 2000-2004 srvx Development Team
3  *
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.
9  *
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.
14  *
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.
17  */
18
19 #include "conf.h"
20 #include "gline.h"
21 #include "ioset.h"
22 #include "modcmd.h"
23 #include "timeq.h"
24
25 #ifdef HAVE_SYS_SOCKET_H
26 #include <sys/socket.h>
27 #endif
28 #ifdef HAVE_NETINET_IN_H
29 #include <netinet/in.h>
30 #endif
31 #ifdef HAVE_ARPA_INET_H
32 #include <arpa/inet.h>
33 #endif
34
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.)
38  */
39
40 #if !defined(SOCKCHECK_DEBUG)
41 #define SOCKCHECK_DEBUG 0
42 #endif
43 #define SOCKCHECK_TEST_DB "sockcheck.conf"
44
45 enum sockcheck_decision {
46     CHECKING,
47     ACCEPT,
48     REJECT
49 };
50
51 typedef struct {
52     enum sockcheck_decision decision;
53     time_t last_touched;
54     const char *reason;
55     struct in_addr addr;
56     char hostname[16];
57 } *sockcheck_cache_info;
58
59 DECLARE_LIST(sci_list, sockcheck_cache_info);
60 DEFINE_LIST(sci_list, sockcheck_cache_info)
61
62 /* Here's the list of hosts that need to be started on.
63  */
64 static struct sci_list pending_sci_list;
65
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.
68  */
69 static dict_t checked_ip_dict;
70
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).
76  * 
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.
81  */
82
83 struct sockcheck_response {
84     const char *template;
85     struct sockcheck_state *next;
86 };
87
88 DECLARE_LIST(response_list, struct sockcheck_response *);
89 DEFINE_LIST(response_list, struct sockcheck_response *)
90 static unsigned int max_responses;
91
92 struct sockcheck_state {
93     unsigned short port;
94     unsigned short timeout;
95     unsigned short reps;
96     enum sockcheck_decision type;
97     const char *template;
98     struct response_list responses;
99 };
100
101 struct sockcheck_list {
102     unsigned int size, used, refs;
103     struct sockcheck_state **list;
104 };
105
106 /*
107  * List of tests.
108  */
109 static struct sockcheck_list *tests;
110
111 /* Stuff to track client state, one instance per open connection. */
112 struct sockcheck_client {
113     struct io_fd *fd;
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;
121     char *read;
122     const char **resp_state;
123 };
124
125 static struct {
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;
131     int local_addr_len;
132 } sockcheck_conf;
133
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 };
140
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." },
157     { NULL, NULL }
158 };
159
160 static struct sockcheck_list *
161 sockcheck_list_alloc(unsigned int size)
162 {
163     struct sockcheck_list *list = malloc(sizeof(*list));
164     list->used = 0;
165     list->refs = 1;
166     list->size = size;
167     list->list = malloc(list->size*sizeof(list->list[0]));
168     return list;
169 }
170
171 static void
172 sockcheck_list_append(struct sockcheck_list *list, struct sockcheck_state *new_item)
173 {
174     if (list->used == list->size) {
175         list->size <<= 1;
176         list->list = realloc(list->list, list->size*sizeof(list->list[0]));
177     }
178     list->list[list->used++] = new_item;
179 }
180
181 static struct sockcheck_list *
182 sockcheck_list_clone(struct sockcheck_list *old_list)
183 {
184     struct sockcheck_list *new_list = malloc(sizeof(*new_list));
185     new_list->used = old_list->used;
186     new_list->refs = 1;
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]));
190     return new_list;
191 }
192
193 static void
194 sockcheck_list_unref(struct sockcheck_list *list)
195 {
196     if (!list || --list->refs > 0) return;
197     free(list->list);
198     free(list);
199 }
200
201 static void
202 sockcheck_issue_gline(sockcheck_cache_info sci)
203 {
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);
209 }
210
211 static struct sockcheck_client *
212 sockcheck_alloc_client(sockcheck_cache_info sci)
213 {
214     struct sockcheck_client *client;
215     client = calloc(1, sizeof(*client));
216     client->tests = tests;
217     client->tests->refs++;
218     client->addr = sci;
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]));
222     return client;
223 }
224
225 static void
226 sockcheck_free_client(struct sockcheck_client *client)
227 {
228     if (SOCKCHECK_DEBUG) {
229         log_module(PC_LOG, LOG_INFO, "Goodbye %s (%p)!  I set you free!", client->addr->hostname, client);
230     }
231     if (client->fd) ioset_close(client->fd->fd, 1);
232     sockcheck_list_unref(client->tests);
233     free(client->read);
234     free(client->resp_state);
235     free(client);
236 }
237
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);
241
242 static void
243 sockcheck_timeout_client(void *data)
244 {
245     struct sockcheck_client *client = data;
246     if (SOCKCHECK_DEBUG) {
247         log_module(PC_LOG, LOG_INFO, "Client %s timed out.", client->addr->hostname);
248     }
249     sockcheck_advance(client, client->state->responses.used-1);
250 }
251
252 static void
253 sockcheck_print_client(const struct sockcheck_client *client)
254 {
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);
266 }
267
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 */
277 };
278
279 static void
280 expand_var(const struct sockcheck_client *client, char var, char **p_expansion, unsigned int *p_exp_length)
281 {
282     extern struct cManagerNode cManager;
283     const char *expansion;
284     unsigned int exp_length;
285 #ifndef __SOLARIS__
286     u_int32_t exp4;
287     u_int16_t exp2;
288 #else
289     uint32_t exp4;
290     uint16_t exp2;
291 #endif
292     /* expand variable */
293     switch (var) {
294     case 'c':
295         expansion = client->addr->hostname;
296         exp_length = strlen(expansion);
297         break;
298     case 'i':
299         exp4 = client->addr->addr.s_addr;
300         exp_length = sizeof(exp4);
301         expansion = (char*)&exp4;
302         break;
303     case 'p':
304         exp2 = htons(client->state->port);
305         exp_length = sizeof(exp2);
306         expansion = (char*)&exp2;
307         break;
308     case 'u':
309         expansion = cManager.uplink->host;
310         exp_length = strlen(expansion);
311         break;
312     default:
313         log_module(PC_LOG, LOG_WARNING, "Request to expand unknown sockcheck variable $%c, using empty expansion.", var);
314         expansion = "";
315         exp_length = 0;
316     }
317     if (p_expansion) {
318         *p_expansion = malloc(exp_length);
319         memcpy(*p_expansion, expansion, exp_length);
320     }
321     if (p_exp_length) {
322         *p_exp_length = exp_length;
323     }
324 }
325
326 static int
327 sockcheck_check_template(const char *template, int is_input)
328 {
329     unsigned int nn;
330     if (is_input && !strcmp(template, "other")) return 1;
331     for (nn=0; template[nn]; nn += 2) {
332         switch (template[nn]) {
333         case '=':
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);
336                 return 0;
337             }
338             break;
339         case '$':
340             switch (template[nn+1]) {
341             case 'c': case 'i': case 'p': case 'u': break;
342             default:
343                 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s refers to unknown variable %c (pos %d).", template, template[nn+1], nn);
344                 return 0;
345             }
346             break;
347         case '.':
348             if (!is_input) {
349                 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s: . is only valid in input templates.", template);
350                 return 0;
351             }
352             if (template[nn+1] != '.') {
353                 log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s expects .. to come in twos (pos %d).", template, nn);
354                 return 0;
355             }
356             break;
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);
363                 return 0;
364             }
365             break;
366         default:
367             log_module(MAIN_LOG, LOG_ERROR, "ProxyCheck template %s: unrecognized character '%c' (pos %d).", template, template[nn], nn);
368             return 0;
369         }
370     }
371     return 1;
372 }
373
374 static void
375 sockcheck_elaborate_state(struct sockcheck_client *client)
376 {
377     const char *template;
378     unsigned int nn;
379
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);
390             break;
391         }
392         case '$': {
393             char *expansion;
394             unsigned int exp_length;
395             expand_var(client, template[nn+1], &expansion, &exp_length);
396             ioset_write(client->fd, expansion, exp_length);
397             free(expansion);
398             break;
399         }
400         }
401     }
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);
409             }
410             sockcheck_advance(client, nn);
411             return;
412         }
413     }
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);
418     }
419 }
420
421 static void
422 sockcheck_decide(struct sockcheck_client *client, enum sockcheck_decision decision)
423 {
424     unsigned int n;
425
426     checked_ip_count++;
427     client->addr->decision = decision;
428     client->addr->last_touched = now;
429     switch (decision) {
430     case ACCEPT:
431         /* do nothing */
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);
434         }
435         break;
436     case REJECT:
437         client->addr->reason = client->state->template;
438         proxies_detected++;
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);
442         }
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);
452             tests = new_tests;
453         }
454         break;
455     default:
456         log_module(PC_LOG, LOG_ERROR, "BUG: sockcheck_decide(\"%s\", %d): unrecognized decision.", client->addr->hostname, decision);
457     }
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);
463     } else {
464         client_list[n] = 0;
465     }
466 }
467
468 static void
469 sockcheck_advance(struct sockcheck_client *client, unsigned int next_state)
470 {
471     struct sockcheck_state *ns;
472
473     timeq_del(0, sockcheck_timeout_client, client, TIMEQ_IGNORE_WHEN);
474     if (SOCKCHECK_DEBUG) {
475         unsigned int n, m;
476         char buffer[201];
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];
483             }
484             buffer[m<<1] = 0;
485             log_module(PC_LOG, LOG_INFO, " .. read data: %s", buffer);
486             n += m;
487         }
488         sockcheck_print_client(client);
489     }
490
491     ns = client->state = client->state->responses.list[next_state]->next;
492     switch (ns->type) {
493     case CHECKING:
494         sockcheck_elaborate_state(client);
495         break;
496     case REJECT:
497         sockcheck_decide(client, REJECT);
498         break;
499     case ACCEPT:
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);
505         } else {
506             sockcheck_decide(client, ACCEPT);
507         }
508         break;
509     default:
510         log_module(PC_LOG, LOG_ERROR, "BUG: unknown next-state type %d (after %p).", ns->type, client->state);
511         break;
512     }
513 }
514
515 static void
516 sockcheck_readable(struct io_fd *fd)
517 {
518     /* read what we can from the fd */
519     struct sockcheck_client *client = fd->data;
520     unsigned int nn;
521     int res;
522
523     res = read(fd->fd, client->read + client->read_used, client->read_size - client->read_used);
524     if (res < 0) {
525         switch (res = errno) {
526         default:
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));
528         case EAGAIN:
529             return;
530         case ECONNRESET:
531             sockcheck_advance(client, client->state->responses.used - 1);
532             return;
533         }
534     } else if (res == 0) {
535         sockcheck_advance(client, client->state->responses.used - 1);
536         return;
537     } else {
538         client->read_used += res;
539     }
540     if (SOCKCHECK_DEBUG) {
541         unsigned int n, m;
542         char buffer[201];
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];
548             }
549             buffer[m<<1] = 0;
550             log_module(PC_LOG, LOG_INFO, "read %d bytes data: %s", client->read_used, buffer);
551             n += m;
552         }
553     }
554
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;
558         char bleh;
559         const char *resp_state;
560
561         for (nn=0; nn<(client->state->responses.used-1); nn++) {
562             char *expected;
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) {
568             case '=': 
569                 bleh = resp_state[1];
570                 expected = &bleh;
571                 break;
572             case '.':
573                 /* any character passes */
574                 client->read_pos++;
575                 exp_length = 0;
576                 break;
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]];
583                 expected = &bleh;
584                 break;
585             case '$':
586                 expand_var(client, resp_state[1], &expected, &exp_length);
587                 free_exp = 1;
588                 break;
589             }
590             if (client->read_pos+exp_length <= client->read_used) {
591                 if (exp_length && memcmp(client->read+client->read_pos, expected, exp_length)) {
592                     resp_state = NULL;
593                 } else {
594                     client->read_pos += exp_length;
595                 }
596             } else {
597                 /* can't check the variable yet, so come back later */
598                 resp_state -= 2;
599             }
600             if (free_exp) free(expected);
601             if (resp_state) {
602                 client->resp_state[nn] = resp_state = resp_state + 2;
603                 if (!*resp_state) {
604                     sockcheck_advance(client, nn);
605                     return;
606                 }
607             } else {
608                 client->resp_state[nn] = NULL;
609             }
610         }
611         if (last_pos == client->read_pos) break;
612     }
613
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);
619         }
620         sockcheck_advance(client, client->state->responses.used-1);
621         return;
622     }
623 }
624
625 static void
626 sockcheck_connected(struct io_fd *fd, int rc)
627 {
628     struct sockcheck_client *client = fd->data;
629     client->fd = fd;
630     switch (rc) {
631     default:
632         log_module(PC_LOG, LOG_ERROR, "BUG: connect() got error %d (%s) for client at %s.", rc, strerror(rc), client->addr->hostname);
633     case EHOSTUNREACH:
634     case ECONNREFUSED:
635     case ETIMEDOUT:
636         if (SOCKCHECK_DEBUG) {
637             log_module(PC_LOG, LOG_INFO, "Client %s gave us errno %d (%s)", client->addr->hostname, rc, strerror(rc));
638         }
639         sockcheck_advance(client, client->state->responses.used-1);
640         return;
641     case 0: break;
642     }
643     fd->wants_reads = 1;
644     if (SOCKCHECK_DEBUG) {
645         log_module(PC_LOG, LOG_INFO, "Connected: to %s port %d.", client->addr->hostname, client->state->port);
646     }
647     sockcheck_elaborate_state(client);
648 }
649
650 static void
651 sockcheck_begin_test(struct sockcheck_client *client)
652 {
653     struct io_fd *io_fd;
654
655     if (client->fd) {
656         ioset_close(client->fd->fd, 1);
657         client->fd = NULL;
658     }
659     do {
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);
664         if (!io_fd) {
665             client->test_index++;
666             continue;
667         }
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);
672         }
673         return;
674     } while (client->test_index < client->tests->used);
675     /* Ran out of tests to run; accept this client. */
676     sockcheck_decide(client, ACCEPT);
677 }
678
679 static void
680 sockcheck_start_client(unsigned int idx)
681 {
682     sockcheck_cache_info sci;
683     struct sockcheck_client *client;
684
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);
688         return;
689     }
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++;
693     if (!tests) return;
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);
699 }
700
701 void
702 sockcheck_queue_address(struct in_addr addr)
703 {
704     sockcheck_cache_info sci;
705     char *ipstr=inet_ntoa(addr);
706
707     sci = dict_find(checked_ip_dict, ipstr, NULL);
708     if (sci) {
709         switch (sci->decision) {
710         case CHECKING:
711             /* We are already checking this host. */
712             return;
713         case ACCEPT:
714             if ((sci->last_touched + sockcheck_conf.max_cache_age) >= (unsigned)now) return;
715             break;
716         case REJECT:
717             if ((sci->last_touched + sockcheck_conf.gline_duration) >= (unsigned)now) {
718                 sockcheck_issue_gline(sci);
719                 return;
720             }
721             break;
722         }
723         dict_remove(checked_ip_dict, sci->hostname);
724     }
725     sci = calloc(1, sizeof(*sci));
726     sci->decision = CHECKING;
727     sci->last_touched = now;
728     sci->reason = NULL;
729     sci->addr = addr;
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);
735 }
736
737 int
738 sockcheck_uncache_host(const char *name)
739 {
740     sockcheck_cache_info sci;
741     if ((sci = dict_find(checked_ip_dict, name, NULL))
742         && (sci->decision == CHECKING)) {
743         return -1;
744     }
745     return dict_remove(checked_ip_dict, name);
746 }
747
748 static int
749 sockcheck_create_response(const char *key, void *data, void *extra)
750 {
751     const char *str, *end;
752     struct record_data *rd = data;
753     struct sockcheck_state *parent = extra;
754     struct sockcheck_response *resp;
755     dict_t resps;
756     char *templ;
757
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;
775         } else {
776             log_module(PC_LOG, LOG_ERROR, "Error: unknown sockcheck decision `%s', defaulting to accept.", str);
777             resp->next->type = ACCEPT;
778         }
779         if (str[6]) {
780             resp->next->template = strdup(str+7);
781         } else {
782             resp->next->template = strdup("No explanation given");
783         }
784     } else if ((resps = GET_RECORD_OBJECT(rd))) {
785         resp->next->type = CHECKING;
786         response_list_init(&resp->next->responses);
787         if (*end == ':') {
788             resp->next->template = strdup(end+1);
789             if (!sockcheck_check_template(resp->next->template, 0)) _exit(1);
790         } else {
791             resp->next->template = strdup("");
792         }
793         dict_foreach(resps, sockcheck_create_response, resp->next);
794     }
795     return 0;
796 }
797
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
801  */
802 static int
803 sockcheck_create_test(const char *key, void *data, void *extra)
804 {
805     char *end;
806     struct record_data *rd;
807     dict_t object;
808     struct sockcheck_state *new_test;
809     unsigned int n;
810
811     rd = data;
812     new_test = malloc(sizeof(*new_test));
813     new_test->template = NULL;
814     new_test->reps = 1;
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);
821         free(new_test);
822         return 1;
823     }
824     while (*end) {
825         switch (*end) {
826         case '@': new_test->timeout = strtoul(end+1, &end, 0); break;
827         case '*': new_test->reps = strtoul(end+1, &end, 0); break;
828         case ':':
829             new_test->template = strdup(end+1);
830             end += strlen(end);
831             if (!sockcheck_check_template(new_test->template, 0)) _exit(1);
832             break;
833         default:
834             log_module(PC_LOG, LOG_ERROR, "Error: misformed sockcheck test `%s', skipping it.", key);
835             free(new_test);
836             return 1;
837         }
838     }
839     if (!new_test->template) {
840         log_module(PC_LOG, LOG_ERROR, "Error: misformed sockcheck test `%s', skipping it.", key);
841         free(new_test);
842         return 1;
843     }
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;
849     }
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;
860     }
861     if (new_test->responses.used > max_responses) {
862         max_responses = new_test->responses.used;
863     }
864     sockcheck_list_append(extra, new_test);
865     return 0;
866 }
867
868 static void
869 sockcheck_read_tests(void)
870 {
871     dict_t test_db;
872     struct sockcheck_list *new_tests;
873     test_db = parse_database(SOCKCHECK_TEST_DB);
874     if (!test_db)
875         return;
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);
880         tests = new_tests;
881     } else {
882         log_module(PC_LOG, LOG_ERROR, "%s was empty - disabling sockcheck.", SOCKCHECK_TEST_DB);
883     }
884     free_database(test_db);
885 }
886
887 void
888 sockcheck_free_state(struct sockcheck_state *state)
889 {
890     unsigned int n;
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]);
896         }
897         response_list_clean(&state->responses);
898     }
899     free((char*)state->template);
900     free(state);
901 }
902
903 const char *
904 sockcheck_add_test(const char *desc)
905 {
906     struct sockcheck_list *new_tests;
907     const char *reason;
908     char *name;
909     struct record_data *rd;
910
911     if ((reason = parse_record(desc, &name, &rd)))
912         return reason;
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";
917     }
918     sockcheck_list_unref(tests);
919     tests = new_tests;
920     return 0;
921 }
922
923 static void
924 sockcheck_shutdown(void)
925 {
926     unsigned int n;
927
928     if (client_list) {
929         for (n=0; n<sockcheck_conf.max_clients; n++) {
930             if (client_list[n])
931                 sockcheck_free_client(client_list[n]);
932         }
933         free(client_list);
934     }
935     sockcheck_num_clients = 0;
936     dict_delete(checked_ip_dict);
937     sci_list_clean(&pending_sci_list);
938     if (tests)
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;
945     }
946 }
947
948 static void
949 sockcheck_clean_cache(UNUSED_ARG(void *data))
950 {
951     dict_t curr_clients;
952     dict_iterator_t it, next;
953     sockcheck_cache_info sci;
954     unsigned int nn;
955     int max_age;
956
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])
963                 continue;
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);
967         }
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);
971     } else {
972         for (curr_clients = dict_new(), nn=0; nn < sockcheck_conf.max_clients; nn++) {
973             if (!client_list[nn])
974                 continue;
975             dict_insert(curr_clients, client_list[nn]->addr->hostname, client_list[nn]);
976         }
977     }
978
979     for (it=dict_first(checked_ip_dict); it; it=next) {
980         next = iter_next(it);
981         sci = iter_data(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);
987             }
988             dict_remove(checked_ip_dict, sci->hostname);
989         }
990     }
991     dict_delete(curr_clients);
992     timeq_add(now+sockcheck_conf.max_cache_age, sockcheck_clean_cache, 0);
993 }
994
995 static MODCMD_FUNC(cmd_defproxy)
996 {
997     const char *reason;
998
999     if ((reason = sockcheck_add_test(unsplit_string(argv+1, argc-1, NULL)))) {
1000         reply("PCMSG_PROXY_DEFINITION_FAILED", reason);
1001         return 0;
1002     }
1003     reply("PCMSG_PROXY_DEFINITION_SUCCEEDED");
1004     return 1;
1005 }
1006
1007 static MODCMD_FUNC(cmd_hostscan)
1008 {
1009     unsigned int n;
1010     unsigned long addr;
1011     struct in_addr ipaddr;
1012     char hnamebuf[64];
1013
1014     for (n=1; n<argc; n++) {
1015         struct userNode *un = GetUserH(argv[n]);
1016
1017         if (un) {
1018             if ((un->ip.s_addr == 0) || (ntohl(un->ip.s_addr) == INADDR_LOOPBACK)) {
1019                 reply("PCMSG_UNSCANNABLE_IP", un->nick);
1020             } else {
1021                 strcpy(hnamebuf, inet_ntoa(un->ip));
1022                 sockcheck_queue_address(un->ip);
1023                 reply("PCMSG_ADDRESS_QUEUED", hnamebuf);
1024             }
1025         } else {
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);
1031             } else {
1032                 reply("PCMSG_ADDRESS_UNRESOLVED", scanhost);
1033             }
1034         }
1035     }
1036     return 1;
1037 }
1038
1039 static MODCMD_FUNC(cmd_clearhost)
1040 {
1041     unsigned int n;
1042     char hnamebuf[64];
1043
1044     for (n=1; n<argc; n++) {
1045         struct userNode *un = GetUserH(argv[n]);
1046         const char *scanhost;
1047
1048         if (un) {
1049             strcpy(hnamebuf, inet_ntoa(un->ip));
1050             scanhost = hnamebuf;
1051         } else {
1052             scanhost = argv[n];
1053         }
1054         switch (sockcheck_uncache_host(scanhost)) {
1055         case -1:
1056             reply("PCMSG_CHECKING_ADDRESS", scanhost);
1057             break;
1058         case 0:
1059             reply("PCMSG_NOT_REMOVED_FROM_CACHE", scanhost);
1060             break;
1061         default:
1062             reply("PCMSG_REMOVED_FROM_CACHE", scanhost);
1063             break;
1064         }
1065     }
1066     return 1;
1067 }
1068
1069 static MODCMD_FUNC(cmd_stats_proxycheck)
1070 {
1071     if (argc > 1) {
1072         const char *hostname = argv[1];
1073         char elapse_buf[INTERVALLEN];
1074         const char *msg;
1075
1076         sockcheck_cache_info sci = dict_find(checked_ip_dict, hostname, NULL);
1077         if (!sci) {
1078             reply("PCMSG_NOT_CACHED", hostname);
1079             return 0;
1080         }
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;
1087         }
1088         reply(msg, sci->hostname, elapse_buf, sci->reason);
1089         return 1;
1090     } else {
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));
1092         return 1;
1093     }
1094 }
1095
1096 static int
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. */
1099     if (user->ip.s_addr
1100         && (ntohl(user->ip.s_addr) != INADDR_LOOPBACK)
1101         && !user->uplink->burst)
1102         sockcheck_queue_address(user->ip);
1103     return 0;
1104 }
1105
1106 static void
1107 _sockcheck_init(void)
1108 {
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);
1117 }
1118
1119 static void
1120 sockcheck_read_conf(void)
1121 {
1122     dict_t my_node;
1123     const char *str;
1124
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;
1133     }
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);
1147         if (str) {
1148             struct sockaddr_in *sin;
1149             unsigned long addr;
1150
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;
1155                 sin->sin_port = 0;
1156                 sin->sin_addr.s_addr = addr;
1157 #ifdef HAVE_SIN_LEN
1158                 sin->sin_len = 0;
1159 #endif
1160                 memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
1161                 sockcheck_conf.local_addr = sin;
1162             } else {
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;
1165             }
1166         }
1167     }
1168 }
1169
1170 int
1171 sockcheck_init(void)
1172 {
1173     PC_LOG = log_register_type("ProxyCheck", "file:proxycheck.log");
1174     conf_register_reload(sockcheck_read_conf);
1175     reg_exit_func(sockcheck_shutdown);
1176     _sockcheck_init();
1177     message_register_table(msgtab);
1178
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);
1185     return 1;
1186 }
1187
1188 int
1189 sockcheck_finalize(void)
1190 {
1191     return 1;
1192 }