Author: Isomer / ZenShadow
[ircu2.10.12-pk.git] / ircd / IPcheck.c
1 /*
2  * IRC - Internet Relay Chat, ircd/IPcheck.c
3  * Copyright (C) 1998 Carlo Wood ( Run @ undernet.org )
4  *
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)
8  * any later version.
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, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *
19  * $Id$
20  *
21  */
22
23 /*----------------------------------------------------------------------------
24  * Platform Includes
25  *--------------------------------------------------------------------------*/
26 #include <assert.h>
27 #include <stdio.h>
28 #include <string.h>
29
30
31 /*----------------------------------------------------------------------------
32  * Application Includes
33  *--------------------------------------------------------------------------*/
34 #include "IPcheck.h"
35 #include "client.h"
36 #include "ircd.h"
37 #include "numnicks.h"
38 #include "ircd_alloc.h"
39 #include "msg.h"
40 #include "s_bsd.h"
41 #include "s_debug.h"
42 #include "s_user.h"
43 #include "send.h"
44
45
46 /*----------------------------------------------------------------------------
47  * Data Structures (should be moved to IPcheck.h)
48  *--------------------------------------------------------------------------*/
49 typedef struct IPTargetEntry {
50   int           count;
51   unsigned char targets[MAXTARGETS];
52 } iptarget_entry_t;
53
54 typedef struct IPRegistryEntry {
55   struct IPRegistryEntry *next;
56   struct IPTargetEntry   *target;
57   unsigned int             addr;
58   time_t                  last_connect;
59   unsigned char            connected;
60   unsigned char            attempts;
61 } ip_reg_entry_t;
62
63
64 /*
65  * Hash table for IPv4 address registry
66  *
67  * Hash table size must be a power of 2
68  * Use 64K hash table to conserve memory
69  */
70 /*----------------------------------------------------------------------------
71  * Compile-time Configuration
72  *--------------------------------------------------------------------------*/
73 #define IP_REGISTRY_TABLE_SIZE 0x10000
74 #define MASK_16                0xffff
75
76 #define IPCHECK_CLONE_LIMIT 2
77 #define IPCHECK_CLONE_PERIOD 20
78 #define IPCHECK_CLONE_DELAY  1
79
80
81 /*----------------------------------------------------------------------------
82  * Handy Macros
83  *--------------------------------------------------------------------------*/
84 #define NOW (CurrentTime)
85 #define CONNECTED_SINCE(x) (NOW - (x->last_connect))
86
87
88 /*----------------------------------------------------------------------------
89  * Global Data (ugly!)
90  *--------------------------------------------------------------------------*/
91 static ip_reg_entry_t *hashTable[IP_REGISTRY_TABLE_SIZE];
92 static ip_reg_entry_t *freeList = 0;
93
94
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);
103 }
104
105
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;
111
112   for (entry = hashTable[ip_registry_hash(ip)]; entry; entry = entry->next) {
113     if (entry->addr == ip)
114       break;
115   }
116
117   return entry;
118 }
119
120
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);
126
127   entry->next = hashTable[bucket];
128   hashTable[bucket] = entry;
129 }
130   
131
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);
137
138   if (hashTable[bucket] == entry)
139     hashTable[bucket] = entry->next;
140   else {
141     ip_reg_entry_t *prev;
142
143     for (prev = hashTable[bucket]; prev; prev = prev->next) {
144       if (prev->next == entry) {
145         prev->next = entry->next;
146         break;
147       }
148     }
149   }
150 }
151  
152
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;
159
160   if (entry)
161     freeList = entry->next;
162   else
163     entry = (ip_reg_entry_t *)MyMalloc(sizeof(ip_reg_entry_t));
164
165   assert(0 != entry);
166
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                 */
172
173   ip_registry_add(entry);
174
175   return entry;
176 }
177
178
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) {
186   if (entry->target)
187     MyFree(entry->target);
188
189   entry->next = freeList;
190   freeList = entry;
191 }
192
193
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;
199
200   if (entry->target) {
201     free_targets = (entry->target->count +
202                     (CONNECTED_SINCE(entry) / TARGET_DELAY));
203
204     if (free_targets > STARTTARGETS)
205       free_targets = STARTTARGETS;
206
207     entry->target->count = free_targets;
208   }
209
210   return free_targets;
211 }
212
213
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) {
220   /*
221    * Don't touch this number, it has statistical significance
222    * XXX - blah blah blah
223    * ZS - Just -what- statistical significance does it -have-?
224    */
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);
230     entry->target = 0;
231   }
232 }
233
234
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;
241   int i;
242
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);
248     }
249   }
250 }
251
252
253 /*----------------------------------------------------------------------------
254  * IPcheck_local_connect
255  *
256  * Event:
257  *   A new connection was accept()-ed with IP number `cptr->ip.s_addr'.
258  *
259  * Action:
260  *   Update the IPcheck registry.
261  *   Return:
262  *     1 : You're allowed to connect.
263  *     0 : You're not allowed to connect.
264  *
265  * Throttling:
266  *
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
270  * or less.
271  *
272  * Free target inheritance:
273  *
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)
281 {
282   ip_reg_entry_t *entry        = ip_registry_find(addr);
283   unsigned int free_targets = STARTTARGETS;
284  
285   if (0 == entry) {
286     entry = ip_registry_new_entry(addr, 1);
287     return 1;
288   }
289
290   /* Do not allow more than 255 connects from a single IP, EVER. */
291   if (0 == ++entry->connected)
292     return 0;
293
294   /* If our threshhold has elapsed, reset the counter so we don't throttle */
295   if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_PERIOD)
296     entry->attempts = 0;
297   else if (0 == ++entry->attempts)
298     --entry->attempts;  /* Disallow overflow */
299
300   entry->last_connect = NOW;
301   free_targets = ip_registry_update_free_targets(entry);
302
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) {
306 #ifdef NOTHROTTLE 
307     return 1;
308 #else
309     --entry->connected;
310     return 0;
311 #endif        
312   }
313
314   return 1;
315 }
316
317
318 /*----------------------------------------------------------------------------
319  * IPcheck_remote_connect
320  *
321  * Event:
322  *   A remote client connected to Undernet, with IP number `cptr->ip.s_addr'
323  *   and hostname `hostname'.
324  *
325  * Action:
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);
331
332   SetIPChecked(cptr);
333
334   if (0 == entry)
335     entry = ip_registry_new_entry(cptr->ip.s_addr, (is_burst ? 0 : 1));
336   else {
337     /* NEVER more than 255 connections. */
338     if (0 == ++entry->connected)
339       return 0;
340
341     /* Make sure we don't bounce if our threshhold has expired */
342     if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_PERIOD)
343       entry->attempts = 0;
344
345     /* If we're not part of a burst, go ahead and process the rest */
346     if (!is_burst) {
347       if (0 == ++entry->attempts)
348         --entry->attempts;  /* Overflows are bad, mmmkay? */
349       ip_registry_update_free_targets(entry);
350       entry->last_connect = NOW;
351     }
352   }
353
354   return 1;
355 }
356
357 /*----------------------------------------------------------------------------
358  * IPcheck_connect_fail
359  *
360  * Event:
361  *   This local client failed to connect due to legal reasons.
362  *
363  * Action:
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
366  *   again.
367  *--------------------------------------------------------------------------*/
368 void ip_registry_connect_fail(unsigned int addr) {
369   ip_reg_entry_t *entry = ip_registry_find(addr);
370
371   if (entry)
372     --entry->attempts;
373 }
374
375
376 /*----------------------------------------------------------------------------
377  * IPcheck_connect_succeeded
378  *
379  * Event:
380  *   A client succeeded to finish the registration.
381  *
382  * Finish IPcheck registration of a successfully, locally connected client.
383  *--------------------------------------------------------------------------*/
384 void ip_registry_connect_succeeded(struct Client *cptr) {
385   const char     *tr           = "";
386   unsigned int free_targets     = STARTTARGETS;
387   ip_reg_entry_t *entry        = ip_registry_find(cptr->ip.s_addr);
388
389   if (!entry) {
390     Debug((DEBUG_ERROR, "Missing registry entry for: %s", cptr->sock_ip));
391     return;
392   }
393
394   if (entry->target) {
395     memcpy(cptr->targets, entry->target->targets, MAXTARGETS);
396     free_targets = entry->target->count;
397     tr = " tr";
398   }
399
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);
403 }
404
405
406 /*----------------------------------------------------------------------------
407  * IPcheck_disconnect
408  *
409  * Event:
410  *   A local client disconnected or a remote client left Undernet.
411  *
412  * Action:
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);
419
420   /* Entry is probably a server if this happens. */
421   if (0 == entry)
422     return;
423
424
425   /*
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.
429    */
430   if (0 == --entry->connected) {
431     if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_LIMIT * IPCHECK_CLONE_PERIOD)
432       entry->attempts = 0;
433     ip_registry_update_free_targets(entry);
434     entry->last_connect = NOW;
435   }
436
437
438   if (MyConnect(cptr)) {
439     unsigned int free_targets;
440
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;
445     }
446     memcpy(entry->target->targets, cptr->targets, MAXTARGETS);
447
448     /*
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.
453      *
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.  
462          */
463     if (cptr->nexttarget < CurrentTime)
464       free_targets = (CurrentTime - cptr->nexttarget) / TARGET_DELAY + 1;
465     else
466       free_targets = 0;
467
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;
471
472     /* Finally, store smallest value for Judgement Day */
473     if (free_targets < entry->target->count)
474       entry->target->count = free_targets;
475   }
476 }
477
478 /*----------------------------------------------------------------------------
479  * IPcheck_nr
480  *
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;
486 }
487
488
489 /*----------------------------------------------------------------------------
490  * IPcheck_local_connect
491  *
492  * Event:
493  *   A new connection was accept()-ed with IP number `cptr->ip.s_addr'.
494  *
495  * Action:
496  *   Update the IPcheck registry.
497  *   Return:
498  *     1 : You're allowed to connect.
499  *     0 : You're not allowed to connect.
500  *
501  * Throttling:
502  *
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.
506  *
507  * Free target inheritance:
508  *
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);
518 }
519
520
521 /*----------------------------------------------------------------------------
522  * IPcheck_remote_connect
523  *
524  * Event:
525  *   A remote client connected to Undernet, with IP number `cptr->ip.s_addr'
526  *   and hostname `hostname'.
527  *
528  * Action:
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) {
533   assert(0 != cptr);
534   return ip_registry_check_remote(cptr, is_burst);
535 }
536
537
538 /*----------------------------------------------------------------------------
539  * IPcheck_connect_fail
540  *
541  * Event:
542  *   This local client failed to connect due to legal reasons.
543  *
544  * Action:
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
547  *   again.
548  *--------------------------------------------------------------------------*/
549 void IPcheck_connect_fail(struct in_addr a) {
550   ip_registry_connect_fail(a.s_addr);
551 }
552
553
554 /*----------------------------------------------------------------------------
555  * IPcheck_connect_succeeded
556  *
557  * Event:
558  *   A client succeeded to finish the registration.
559  *
560  * Finish IPcheck registration of a successfully, locally connected client.
561  *--------------------------------------------------------------------------*/
562 void IPcheck_connect_succeeded(struct Client *cptr) {
563   assert(0 != cptr);
564   ip_registry_connect_succeeded(cptr);
565 }
566
567
568 /*----------------------------------------------------------------------------
569  * IPcheck_disconnect
570  *
571  * Event:
572  *   A local client disconnected or a remote client left Undernet.
573  *
574  * Action:
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) {
580   assert(0 != cptr);
581   ip_registry_disconnect(cptr);
582 }
583
584
585 /*----------------------------------------------------------------------------
586  * IPcheck_nr
587  *
588  * Returns number of clients with the same IP number
589  *--------------------------------------------------------------------------*/
590 unsigned short IPcheck_nr(struct Client *cptr) {
591   assert(0 != cptr);
592   return ip_registry_count(cptr->ip.s_addr);
593 }
594
595
596 /*----------------------------------------------------------------------------
597  * IPcheck_expire
598  *
599  * Expire old entries
600  *--------------------------------------------------------------------------*/
601 void IPcheck_expire() {
602   static time_t next_expire = 0;
603
604   if (next_expire < CurrentTime) {
605     ip_registry_expire();
606     next_expire = CurrentTime + 60;
607   }
608 }