2 * IRC - Internet Relay Chat, ircd/IPcheck.c
3 * Copyright (C) 1998 Carlo Wood ( Run @ undernet.org )
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
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, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 /*----------------------------------------------------------------------------
25 *--------------------------------------------------------------------------*/
31 /*----------------------------------------------------------------------------
32 * Application Includes
33 *--------------------------------------------------------------------------*/
38 #include "ircd_alloc.h"
46 /*----------------------------------------------------------------------------
47 * Data Structures (should be moved to IPcheck.h)
48 *--------------------------------------------------------------------------*/
49 typedef struct IPTargetEntry {
51 unsigned char targets[MAXTARGETS];
54 typedef struct IPRegistryEntry {
55 struct IPRegistryEntry *next;
56 struct IPTargetEntry *target;
59 unsigned char connected;
60 unsigned char attempts;
65 * Hash table for IPv4 address registry
67 * Hash table size must be a power of 2
68 * Use 64K hash table to conserve memory
70 /*----------------------------------------------------------------------------
71 * Compile-time Configuration
72 *--------------------------------------------------------------------------*/
73 #define IP_REGISTRY_TABLE_SIZE 0x10000
74 #define MASK_16 0xffff
76 #define IPCHECK_CLONE_LIMIT 2
77 #define IPCHECK_CLONE_PERIOD 20
78 #define IPCHECK_CLONE_DELAY 1
81 /*----------------------------------------------------------------------------
83 *--------------------------------------------------------------------------*/
84 #define NOW (CurrentTime)
85 #define CONNECTED_SINCE(x) (NOW - (x->last_connect))
88 /*----------------------------------------------------------------------------
90 *--------------------------------------------------------------------------*/
91 static ip_reg_entry_t *hashTable[IP_REGISTRY_TABLE_SIZE];
92 static ip_reg_entry_t *freeList = 0;
95 /*----------------------------------------------------------------------------
96 * ip_registry_hash: Create a hash key for an IP registry entry and return
97 * the value. (Is unsigned int really a good type to give
98 * to the IP argument? Ugly. This should probably be a
99 * struct in_addr. This is asking for trouble. --ZS)
100 *--------------------------------------------------------------------------*/
101 static unsigned int ip_registry_hash(unsigned int ip) {
102 return ((ip >> 16) ^ ip) & (IP_REGISTRY_TABLE_SIZE - 1);
106 /*----------------------------------------------------------------------------
107 * ip_registry_find: Find a given IP registry entry and return it.
108 *--------------------------------------------------------------------------*/
109 static ip_reg_entry_t *ip_registry_find(unsigned int ip) {
110 ip_reg_entry_t *entry;
112 for (entry = hashTable[ip_registry_hash(ip)]; entry; entry = entry->next) {
113 if (entry->addr == ip)
121 /*----------------------------------------------------------------------------
122 * ip_registry_add: Add an entry to the IP registry
123 *--------------------------------------------------------------------------*/
124 static void ip_registry_add(ip_reg_entry_t *entry) {
125 unsigned int bucket = ip_registry_hash(entry->addr);
127 entry->next = hashTable[bucket];
128 hashTable[bucket] = entry;
132 /*----------------------------------------------------------------------------
133 * ip_registry_remove: Remove an entry from the IP registry
134 *--------------------------------------------------------------------------*/
135 static void ip_registry_remove(ip_reg_entry_t *entry) {
136 unsigned int bucket = ip_registry_hash(entry->addr);
138 if (hashTable[bucket] == entry)
139 hashTable[bucket] = entry->next;
141 ip_reg_entry_t *prev;
143 for (prev = hashTable[bucket]; prev; prev = prev->next) {
144 if (prev->next == entry) {
145 prev->next = entry->next;
153 /*----------------------------------------------------------------------------
154 * ip_registry_new_entry(): Creates and initializes an IP Registry entry.
155 * NOW ALSO ADDS IT TO THE LIST! --ZS
156 *--------------------------------------------------------------------------*/
157 static ip_reg_entry_t *ip_registry_new_entry(unsigned int addr, int attempt) {
158 ip_reg_entry_t *entry = freeList;
161 freeList = entry->next;
163 entry = (ip_reg_entry_t *)MyMalloc(sizeof(ip_reg_entry_t));
167 memset(entry, 0, sizeof(ip_reg_entry_t));
168 entry->last_connect = NOW; /* Seconds since last connect attempt */
169 entry->connected = 1; /* connected clients for this IP */
170 entry->attempts = attempt; /* Number attempts for this IP */
171 entry->addr = addr; /* Entry's IP Address */
173 ip_registry_add(entry);
179 /*----------------------------------------------------------------------------
180 * ip_registry_delete_entry: Frees an entry and adds the structure to a list
181 * of free structures. (We should probably reclaim
182 * the freelist every once in a while! This is
183 * potentially a way to DoS the server... -ZS)
184 *--------------------------------------------------------------------------*/
185 static void ip_registry_delete_entry(ip_reg_entry_t *entry) {
187 MyFree(entry->target);
189 entry->next = freeList;
194 /*----------------------------------------------------------------------------
195 * ip_registry_update_free_targets:
196 *--------------------------------------------------------------------------*/
197 static unsigned int ip_registry_update_free_targets(ip_reg_entry_t *entry) {
198 unsigned int free_targets = STARTTARGETS;
201 free_targets = (entry->target->count +
202 (CONNECTED_SINCE(entry) / TARGET_DELAY));
204 if (free_targets > STARTTARGETS)
205 free_targets = STARTTARGETS;
207 entry->target->count = free_targets;
214 /*----------------------------------------------------------------------------
215 * ip_registry_expire_entry: expire an IP entry if it needs to be. If an
216 * entry isn't expired, then also check the target
217 * list to see if it needs to be expired.
218 *--------------------------------------------------------------------------*/
219 static void ip_registry_expire_entry(ip_reg_entry_t *entry) {
221 * Don't touch this number, it has statistical significance
222 * XXX - blah blah blah
223 * ZS - Just -what- statistical significance does it -have-?
225 if (CONNECTED_SINCE(entry) > 600) {
226 ip_registry_remove(entry);
227 ip_registry_delete_entry(entry);
228 } else if (CONNECTED_SINCE(entry) > 120 && 0 != entry->target) {
229 MyFree(entry->target);
235 /*----------------------------------------------------------------------------
236 * ip_registry_expire: Expire all of the needed entries in the hash table
237 *--------------------------------------------------------------------------*/
238 static void ip_registry_expire(void) {
239 ip_reg_entry_t *entry;
240 ip_reg_entry_t *entry_next;
243 for (i = 0; i < IP_REGISTRY_TABLE_SIZE; ++i) {
244 for (entry = hashTable[i]; entry; entry = entry_next) {
245 entry_next = entry->next;
246 if (0 == entry->connected)
247 ip_registry_expire_entry(entry);
253 /*----------------------------------------------------------------------------
254 * IPcheck_local_connect
257 * A new connection was accept()-ed with IP number `cptr->ip.s_addr'.
260 * Update the IPcheck registry.
262 * 1 : You're allowed to connect.
263 * 0 : You're not allowed to connect.
267 * A connection should be rejected when a connection from the same IP
268 * number was received IPCHECK_CLONE_LIMIT times before this connect
269 * attempt, with reconnect intervals of IPCHECK_CLONE_PERIOD seconds
272 * Free target inheritance:
274 * When the client is accepted, then the number of Free Targets
275 * of the cptr is set to the value stored in the found IPregistry
276 * structure, or left at STARTTARGETS. This can be done by changing
277 * cptr->nexttarget to be `now - (TARGET_DELAY * (FREE_TARGETS - 1))',
278 * where FREE_TARGETS may range from 0 till STARTTARGETS.
279 *--------------------------------------------------------------------------*/
280 int ip_registry_check_local(unsigned int addr, time_t *next_target_out)
282 ip_reg_entry_t *entry = ip_registry_find(addr);
283 unsigned int free_targets = STARTTARGETS;
286 entry = ip_registry_new_entry(addr, 1);
290 /* Do not allow more than 255 connects from a single IP, EVER. */
291 if (0 == ++entry->connected)
294 /* If our threshhold has elapsed, reset the counter so we don't throttle */
295 if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_PERIOD)
297 else if (0 == ++entry->attempts)
298 --entry->attempts; /* Disallow overflow */
300 entry->last_connect = NOW;
301 free_targets = ip_registry_update_free_targets(entry);
303 if (entry->attempts < IPCHECK_CLONE_LIMIT && next_target_out)
304 *next_target_out = CurrentTime - (TARGET_DELAY * free_targets - 1);
305 else if ((CurrentTime - me.since) > IPCHECK_CLONE_DELAY) {
318 /*----------------------------------------------------------------------------
319 * IPcheck_remote_connect
322 * A remote client connected to Undernet, with IP number `cptr->ip.s_addr'
323 * and hostname `hostname'.
326 * Update the IPcheck registry.
327 * Return 0 on failure, 1 on success.
328 *--------------------------------------------------------------------------*/
329 int ip_registry_check_remote(struct Client* cptr, int is_burst) {
330 ip_reg_entry_t *entry = ip_registry_find(cptr->ip.s_addr);
335 entry = ip_registry_new_entry(cptr->ip.s_addr, (is_burst ? 0 : 1));
337 /* NEVER more than 255 connections. */
338 if (0 == ++entry->connected)
341 /* Make sure we don't bounce if our threshhold has expired */
342 if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_PERIOD)
345 /* If we're not part of a burst, go ahead and process the rest */
347 if (0 == ++entry->attempts)
348 --entry->attempts; /* Overflows are bad, mmmkay? */
349 ip_registry_update_free_targets(entry);
350 entry->last_connect = NOW;
357 /*----------------------------------------------------------------------------
358 * IPcheck_connect_fail
361 * This local client failed to connect due to legal reasons.
364 * Neutralize the effect of calling IPcheck_local_connect, in such
365 * a way that the client won't be penalized when trying to reconnect
367 *--------------------------------------------------------------------------*/
368 void ip_registry_connect_fail(unsigned int addr) {
369 ip_reg_entry_t *entry = ip_registry_find(addr);
376 /*----------------------------------------------------------------------------
377 * IPcheck_connect_succeeded
380 * A client succeeded to finish the registration.
382 * Finish IPcheck registration of a successfully, locally connected client.
383 *--------------------------------------------------------------------------*/
384 void ip_registry_connect_succeeded(struct Client *cptr) {
386 unsigned int free_targets = STARTTARGETS;
387 ip_reg_entry_t *entry = ip_registry_find(cptr->ip.s_addr);
390 Debug((DEBUG_ERROR, "Missing registry entry for: %s", cptr->sock_ip));
395 memcpy(cptr->targets, entry->target->targets, MAXTARGETS);
396 free_targets = entry->target->count;
400 sendcmdto_one(&me, CMD_NOTICE, cptr, "%C :on %u ca %u(%u) ft %u(%u)%s",
401 cptr, entry->connected, entry->attempts, IPCHECK_CLONE_LIMIT,
402 free_targets, STARTTARGETS, tr);
406 /*----------------------------------------------------------------------------
410 * A local client disconnected or a remote client left Undernet.
413 * Update the IPcheck registry.
414 * Remove all expired IPregistry structures from the hash bucket
415 * that belongs to this clients IP number.
416 *--------------------------------------------------------------------------*/
417 void ip_registry_disconnect(struct Client *cptr) {
418 ip_reg_entry_t *entry = ip_registry_find(cptr->ip.s_addr);
420 /* Entry is probably a server if this happens. */
426 * If this was the last one, set `last_connect' to disconnect time
427 * (used for expiration) Note that we reset attempts here as well if our
428 * threshhold hasn't been crossed.
430 if (0 == --entry->connected) {
431 if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_LIMIT * IPCHECK_CLONE_PERIOD)
433 ip_registry_update_free_targets(entry);
434 entry->last_connect = NOW;
438 if (MyConnect(cptr)) {
439 unsigned int free_targets;
441 if (0 == entry->target) {
442 entry->target = (iptarget_entry_t *)MyMalloc(sizeof(iptarget_entry_t));
443 assert(0 != entry->target);
444 entry->target->count = STARTTARGETS;
446 memcpy(entry->target->targets, cptr->targets, MAXTARGETS);
449 * This calculation can be pretty unfair towards large multi-user hosts,
450 * but there is "nothing" we can do without also allowing spam bots to
451 * send more messages or by drastically increasing the ammount of memory
452 * used in the IPregistry.
454 * The problem is that when a client disconnects, leaving no free targets,
455 * then the next client from that IP number has to pay for it (getting no
456 * free targets). But ALSO the next client, and the next client, and the
457 * next client etc - until another client disconnects that DOES leave free
458 * targets. The reason for this is that if there are 10 SPAM bots, and
459 * they all disconnect at once, then they ALL should get no free targets
460 * when reconnecting. We'd need to store an entry per client (instead of
461 * per IP number) to avoid this.
463 if (cptr->nexttarget < CurrentTime)
464 free_targets = (CurrentTime - cptr->nexttarget) / TARGET_DELAY + 1;
468 /* Add bonus, this is pretty fuzzy, but it will help in some cases. */
469 if ((CurrentTime - cptr->firsttime) > 600)
470 free_targets += (CurrentTime - cptr->firsttime - 600) / TARGET_DELAY;
472 /* Finally, store smallest value for Judgement Day */
473 if (free_targets < entry->target->count)
474 entry->target->count = free_targets;
478 /*----------------------------------------------------------------------------
481 * Returns number of clients with the same IP number
482 *--------------------------------------------------------------------------*/
483 int ip_registry_count(unsigned int addr) {
484 ip_reg_entry_t *entry = ip_registry_find(addr);
485 return (entry) ? entry->connected : 0;
489 /*----------------------------------------------------------------------------
490 * IPcheck_local_connect
493 * A new connection was accept()-ed with IP number `cptr->ip.s_addr'.
496 * Update the IPcheck registry.
498 * 1 : You're allowed to connect.
499 * 0 : You're not allowed to connect.
503 * A connection should be rejected when a connection from the same IP number was
504 * received IPCHECK_CLONE_LIMIT times before this connect attempt, with
505 * reconnect intervals of IPCHECK_CLONE_PERIOD seconds or less.
507 * Free target inheritance:
509 * When the client is accepted, then the number of Free Targets
510 * of the cptr is set to the value stored in the found IPregistry
511 * structure, or left at STARTTARGETS. This can be done by changing
512 * cptr->nexttarget to be `now - (TARGET_DELAY * (FREE_TARGETS - 1))',
513 * where FREE_TARGETS may range from 0 till STARTTARGETS.
514 *--------------------------------------------------------------------------*/
515 int IPcheck_local_connect(struct in_addr a, time_t* next_target_out) {
516 assert(0 != next_target_out);
517 return ip_registry_check_local(a.s_addr, next_target_out);
521 /*----------------------------------------------------------------------------
522 * IPcheck_remote_connect
525 * A remote client connected to Undernet, with IP number `cptr->ip.s_addr'
526 * and hostname `hostname'.
529 * Update the IPcheck registry.
530 * Return 0 on failure, 1 on success.
531 *--------------------------------------------------------------------------*/
532 int IPcheck_remote_connect(struct Client *cptr, int is_burst) {
534 return ip_registry_check_remote(cptr, is_burst);
538 /*----------------------------------------------------------------------------
539 * IPcheck_connect_fail
542 * This local client failed to connect due to legal reasons.
545 * Neutralize the effect of calling IPcheck_local_connect, in such
546 * a way that the client won't be penalized when trying to reconnect
548 *--------------------------------------------------------------------------*/
549 void IPcheck_connect_fail(struct in_addr a) {
550 ip_registry_connect_fail(a.s_addr);
554 /*----------------------------------------------------------------------------
555 * IPcheck_connect_succeeded
558 * A client succeeded to finish the registration.
560 * Finish IPcheck registration of a successfully, locally connected client.
561 *--------------------------------------------------------------------------*/
562 void IPcheck_connect_succeeded(struct Client *cptr) {
564 ip_registry_connect_succeeded(cptr);
568 /*----------------------------------------------------------------------------
572 * A local client disconnected or a remote client left Undernet.
575 * Update the IPcheck registry.
576 * Remove all expired IPregistry structures from the hash bucket
577 * that belongs to this clients IP number.
578 *--------------------------------------------------------------------------*/
579 void IPcheck_disconnect(struct Client *cptr) {
581 ip_registry_disconnect(cptr);
585 /*----------------------------------------------------------------------------
588 * Returns number of clients with the same IP number
589 *--------------------------------------------------------------------------*/
590 unsigned short IPcheck_nr(struct Client *cptr) {
592 return ip_registry_count(cptr->ip.s_addr);
596 /*----------------------------------------------------------------------------
600 *--------------------------------------------------------------------------*/
601 void IPcheck_expire() {
602 static time_t next_expire = 0;
604 if (next_expire < CurrentTime) {
605 ip_registry_expire();
606 next_expire = CurrentTime + 60;