Author: Kev <klmitch@mit.edu>
[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  * This file should be edited in a window with a width of 141 characters
23  * ick
24  */
25 #include "config.h"
26
27 #include "IPcheck.h"
28 #include "client.h"
29 #include "ircd.h"
30 #include "msg.h"
31 #include "numnicks.h"       /* NumNick, NumServ (GODMODE) */
32 #include "ircd_alloc.h"
33 #include "s_debug.h"        /* Debug */
34 #include "s_user.h"         /* TARGET_DELAY */
35 #include "send.h"
36
37 #include <assert.h>
38
39
40 struct IPTargetEntry {
41   int           count;
42   unsigned char targets[MAXTARGETS];
43 };
44
45 struct IPRegistryEntry {
46   struct IPRegistryEntry*  next;
47   struct IPTargetEntry*    target;
48   unsigned int             addr;
49   int                      last_connect;
50   unsigned short           connected;
51   unsigned char            attempts;
52 };
53
54 /*
55  * Hash table for IPv4 address registry
56  *
57  * Hash table size must be a power of 2
58  * Use 64K hash table to conserve memory
59  */
60 #define IP_REGISTRY_TABLE_SIZE 0x10000
61 #define MASK_16                0xffff
62
63 #define NOW ((unsigned short)(CurrentTime & MASK_16))
64 #define CONNECTED_SINCE(x) (NOW - (x))
65
66 #define IPCHECK_CLONE_LIMIT 4
67 #define IPCHECK_CLONE_PERIOD 40
68 #define IPCHECK_CLONE_DELAY 600
69
70
71 static struct IPRegistryEntry* hashTable[IP_REGISTRY_TABLE_SIZE];
72 static struct IPRegistryEntry* freeList = 0;
73
74 static unsigned int ip_registry_hash(unsigned int ip)
75 {
76   return ((ip >> 16) ^ ip) & (IP_REGISTRY_TABLE_SIZE - 1);
77 }
78
79 static struct IPRegistryEntry* ip_registry_find(unsigned int ip)
80 {
81   struct IPRegistryEntry* entry = hashTable[ip_registry_hash(ip)];
82   for ( ; entry; entry = entry->next) {
83     if (entry->addr == ip)
84       break;
85   }
86   return entry;
87 }
88
89 static void ip_registry_add(struct IPRegistryEntry* entry)
90 {
91   unsigned int bucket = ip_registry_hash(entry->addr);
92   entry->next = hashTable[bucket];
93   hashTable[bucket] = entry;
94 }
95   
96 static void ip_registry_remove(struct IPRegistryEntry* entry)
97 {
98   unsigned int bucket = ip_registry_hash(entry->addr);
99   if (hashTable[bucket] == entry)
100     hashTable[bucket] = entry->next;
101   else {
102     struct IPRegistryEntry* prev = hashTable[bucket];
103     for ( ; prev; prev = prev->next) {
104       if (prev->next == entry) {
105         prev->next = entry->next;
106         break;
107       }
108     }
109   }
110 }
111  
112 static struct IPRegistryEntry* ip_registry_new_entry()
113 {
114   struct IPRegistryEntry* entry = freeList;
115   if (entry)
116     freeList = entry->next;
117   else
118     entry = (struct IPRegistryEntry*) MyMalloc(sizeof(struct IPRegistryEntry));
119
120   assert(0 != entry);
121   memset(entry, 0, sizeof(struct IPRegistryEntry));
122   entry->last_connect = NOW;     /* Seconds since last connect attempt */
123   entry->connected    = 1;       /* connected clients for this IP */
124   entry->attempts     = 1;       /* Number attempts for this IP */
125   return entry;
126 }
127
128 static void ip_registry_delete_entry(struct IPRegistryEntry* entry)
129 {
130   if (entry->target)
131     MyFree(entry->target);
132   entry->next = freeList;
133   freeList = entry;
134 }
135
136 static unsigned int ip_registry_update_free_targets(struct IPRegistryEntry* entry)
137 {
138   unsigned int free_targets = STARTTARGETS;
139
140   if (entry->target) {
141     free_targets = entry->target->count + (CONNECTED_SINCE(entry->last_connect) / TARGET_DELAY);
142     if (free_targets > STARTTARGETS)
143       free_targets = STARTTARGETS;
144     entry->target->count = free_targets;
145   }
146   return free_targets;
147 }
148
149 static void ip_registry_expire_entry(struct IPRegistryEntry* entry)
150 {
151   /*
152    * Don't touch this number, it has statistical significance
153    * XXX - blah blah blah
154    */
155   if (CONNECTED_SINCE(entry->last_connect) > 600) {
156     /*
157      * expired
158      */
159     ip_registry_remove(entry);
160     ip_registry_delete_entry(entry);
161   }
162   else if (CONNECTED_SINCE(entry->last_connect) > 120 && 0 != entry->target) {
163     /*
164      * Expire storage of targets
165      */
166     MyFree(entry->target);
167     entry->target = 0;
168   }
169 }
170
171 /*
172  * ip_registry_expire
173  */
174 static void ip_registry_expire()
175 {
176   int i;
177   struct IPRegistryEntry* entry;
178   struct IPRegistryEntry* entry_next;
179
180   for (i = 0; i < IP_REGISTRY_TABLE_SIZE; ++i) {
181     for (entry = hashTable[i]; entry; entry = entry_next) {
182       entry_next = entry->next;
183       if (0 == entry->connected)
184         ip_registry_expire_entry(entry);
185     }
186   }
187 }
188
189 /*
190  * IPcheck_local_connect
191  *
192  * Event:
193  *   A new connection was accept()-ed with IP number `cptr->ip.s_addr'.
194  *
195  * Action:
196  *   Update the IPcheck registry.
197  *   Return:
198  *     1 : You're allowed to connect.
199  *     0 : You're not allowed to connect.
200  *
201  * Throttling:
202  *
203  * A connection should be rejected when a connection from the same IP number was
204  * received IPCHECK_CLONE_LIMIT times before this connect attempt, with
205  * reconnect intervals of IPCHECK_CLONE_PERIOD seconds or less.
206  *
207  * Free target inheritance:
208  *
209  * When the client is accepted, then the number of Free Targets
210  * of the cptr is set to the value stored in the found IPregistry
211  * structure, or left at STARTTARGETS.  This can be done by changing
212  * cptr->nexttarget to be `now - (TARGET_DELAY * (FREE_TARGETS - 1))',
213  * where FREE_TARGETS may range from 0 till STARTTARGETS.
214  */
215 int ip_registry_check_local(unsigned int addr, time_t* next_target_out)
216 {
217   struct IPRegistryEntry* entry = ip_registry_find(addr);
218   unsigned int free_targets = STARTTARGETS;
219  
220   if (0 == entry) {
221     entry       = ip_registry_new_entry();
222     entry->addr = addr;    /* The IP number of registry entry */
223     ip_registry_add(entry);
224     return 1;
225   }
226   /* Note that this also connects server connects.
227    * It is hard and not interesting, to change that.
228    *
229    * Don't allow more then 255 connects from one IP number, ever
230    */
231   if (0 == ++entry->connected)
232     return 0;
233
234   if (CONNECTED_SINCE(entry->last_connect) > IPCHECK_CLONE_PERIOD)
235     entry->attempts = 0;
236
237   free_targets = ip_registry_update_free_targets(entry);
238   entry->last_connect = NOW;
239
240   if (0 == ++entry->attempts)   /* Check for overflow */
241     --entry->attempts;
242
243   if (entry->attempts < IPCHECK_CLONE_LIMIT) {
244     if (next_target_out)
245       *next_target_out = CurrentTime - (TARGET_DELAY * free_targets - 1);
246   }
247   else if ((CurrentTime - cli_since(&me)) > IPCHECK_CLONE_DELAY) {
248     /* 
249      * Don't refuse connection when we just rebooted the server
250      */
251 #ifdef NOTHROTTLE 
252     return 1;
253 #else
254     --entry->connected;
255     return 0;
256 #endif        
257   }
258   return 1;
259 }
260
261 /*
262  * IPcheck_remote_connect
263  *
264  * Event:
265  *   A remote client connected to Undernet, with IP number `cptr->ip.s_addr'
266  *   and hostname `hostname'.
267  *
268  * Action:
269  *   Update the IPcheck registry.
270  *   Return 0 on failure, 1 on success.
271  */
272 int ip_registry_check_remote(struct Client* cptr, int is_burst)
273 {
274   struct IPRegistryEntry* entry = ip_registry_find((cli_ip(cptr)).s_addr);
275
276   /*
277    * Mark that we did add/update an IPregistry entry
278    */
279   SetIPChecked(cptr);
280   if (0 == entry) {
281     entry = ip_registry_new_entry();
282     entry->addr = (cli_ip(cptr)).s_addr;
283     if (is_burst)
284       entry->attempts = 0;
285     ip_registry_add(entry);
286   }
287   else {
288     if (0 == ++entry->connected) {
289       /* 
290        * Don't allow more then 255 connects from one IP number, ever
291        */
292       return 0;
293     }
294     if (CONNECTED_SINCE(entry->last_connect) > IPCHECK_CLONE_PERIOD)
295       entry->attempts = 0;
296     if (!is_burst) {
297       if (0 == ++entry->attempts) {
298         /*
299          * Check for overflow
300          */
301         --entry->attempts;
302       }
303       ip_registry_update_free_targets(entry);
304       entry->last_connect = NOW;
305     }
306   }
307   SetIPChecked(cptr);
308   return 1;
309 }
310
311 /*
312  * IPcheck_connect_fail
313  *
314  * Event:
315  *   This local client failed to connect due to legal reasons.
316  *
317  * Action:
318  *   Neutralize the effect of calling IPcheck_local_connect, in such
319  *   a way that the client won't be penalized when trying to reconnect
320  *   again.
321  */
322 void ip_registry_connect_fail(unsigned int addr)
323 {
324   struct IPRegistryEntry* entry = ip_registry_find(addr);
325   if (entry)
326     --entry->attempts;
327 }
328
329 /*
330  * IPcheck_connect_succeeded
331  *
332  * Event:
333  *   A client succeeded to finish the registration.
334  *
335  * Finish IPcheck registration of a successfully, locally connected client.
336  */
337 void ip_registry_connect_succeeded(struct Client *cptr)
338 {
339   const char*             tr    = "";
340   unsigned int free_targets     = STARTTARGETS;
341   struct IPRegistryEntry* entry = ip_registry_find((cli_ip(cptr)).s_addr);
342
343   if (!entry) {
344     Debug((DEBUG_ERROR, "Missing registry entry for: %s", cli_sock_ip(cptr)));
345     return;
346   }
347   if (entry->target) {
348     memcpy(cli_targets(cptr), entry->target->targets, MAXTARGETS);
349     free_targets = entry->target->count;
350     tr = " tr";
351   }
352   sendcmdto_one(&me, CMD_NOTICE, cptr, ":on %u ca %u(%u) ft %u(%u)%s",
353                 entry->connected, entry->attempts, IPCHECK_CLONE_LIMIT,
354                 free_targets, STARTTARGETS, tr);
355 }
356
357 /*
358  * IPcheck_disconnect
359  *
360  * Event:
361  *   A local client disconnected or a remote client left Undernet.
362  *
363  * Action:
364  *   Update the IPcheck registry.
365  *   Remove all expired IPregistry structures from the hash bucket
366  *     that belongs to this clients IP number.
367  */
368 void ip_registry_disconnect(struct Client *cptr)
369 {
370   struct IPRegistryEntry* entry = ip_registry_find((cli_ip(cptr)).s_addr);
371   if (0 == entry) {
372     /*
373      * trying to find an entry for a server causes this to happen,
374      * servers should never have FLAGS_IPCHECK set
375      */
376     return;
377   }
378   /*
379    * If this was the last one, set `last_connect' to disconnect time (used for expiration)
380    */
381   if (0 == --entry->connected) {
382     if (CONNECTED_SINCE(entry->last_connect) > IPCHECK_CLONE_LIMIT * IPCHECK_CLONE_PERIOD) {
383       /*
384        * Otherwise we'd penetalize for this old value if the client reconnects within 20 seconds
385        */
386       entry->attempts = 0;
387     }
388     ip_registry_update_free_targets(entry);
389     entry->last_connect = NOW;
390   }
391   if (MyConnect(cptr)) {
392     unsigned int free_targets;
393     /*
394      * Copy the clients targets
395      */
396     if (0 == entry->target) {
397       entry->target = (struct IPTargetEntry*) MyMalloc(sizeof(struct IPTargetEntry));
398       entry->target->count = STARTTARGETS;
399     }
400     assert(0 != entry->target);
401
402     memcpy(entry->target->targets, cli_targets(cptr), MAXTARGETS);
403     /*
404      * This calculation can be pretty unfair towards large multi-user hosts, but
405      * there is "nothing" we can do without also allowing spam bots to send more
406      * messages or by drastically increasing the ammount of memory used in the IPregistry.
407      *
408      * The problem is that when a client disconnects, leaving no free targets, then
409      * the next client from that IP number has to pay for it (getting no free targets).
410      * But ALSO the next client, and the next client, and the next client etc - until
411      * another client disconnects that DOES leave free targets.  The reason for this
412      * is that if there are 10 SPAM bots, and they all disconnect at once, then they
413      * ALL should get no free targets when reconnecting.  We'd need to store an entry
414      * per client (instead of per IP number) to avoid this.
415      */
416     if (cli_nexttarget(cptr) < CurrentTime) {
417         /*
418          * Number of free targets
419          */
420       free_targets = (CurrentTime - cli_nexttarget(cptr)) / TARGET_DELAY + 1;
421     }
422     else
423       free_targets = 0;
424     /*
425      * Add bonus, this is pretty fuzzy, but it will help in some cases.
426      */
427     if ((CurrentTime - cli_firsttime(cptr)) > 600)
428       /*
429        * Was longer then 10 minutes online?
430        */
431       free_targets += (CurrentTime - cli_firsttime(cptr) - 600) / TARGET_DELAY;
432     /*
433      * Finally, store smallest value for Judgement Day
434      */
435     if (free_targets < entry->target->count)
436       entry->target->count = free_targets;
437   }
438 }
439
440 /*
441  * IPcheck_nr
442  *
443  * Returns number of clients with the same IP number
444  */
445 int ip_registry_count(unsigned int addr)
446 {
447   struct IPRegistryEntry* entry = ip_registry_find(addr);
448   return (entry) ? entry->connected : 0;
449 }
450
451 /*
452  * IPcheck_local_connect
453  *
454  * Event:
455  *   A new connection was accept()-ed with IP number `cptr->ip.s_addr'.
456  *
457  * Action:
458  *   Update the IPcheck registry.
459  *   Return:
460  *     1 : You're allowed to connect.
461  *     0 : You're not allowed to connect.
462  *
463  * Throttling:
464  *
465  * A connection should be rejected when a connection from the same IP number was
466  * received IPCHECK_CLONE_LIMIT times before this connect attempt, with
467  * reconnect intervals of IPCHECK_CLONE_PERIOD seconds or less.
468  *
469  * Free target inheritance:
470  *
471  * When the client is accepted, then the number of Free Targets
472  * of the cptr is set to the value stored in the found IPregistry
473  * structure, or left at STARTTARGETS.  This can be done by changing
474  * cptr->nexttarget to be `now - (TARGET_DELAY * (FREE_TARGETS - 1))',
475  * where FREE_TARGETS may range from 0 till STARTTARGETS.
476  */
477 int IPcheck_local_connect(struct in_addr a, time_t* next_target_out)
478 {
479   assert(0 != next_target_out);
480   return ip_registry_check_local(a.s_addr, next_target_out);
481 }
482
483 /*
484  * IPcheck_remote_connect
485  *
486  * Event:
487  *   A remote client connected to Undernet, with IP number `cptr->ip.s_addr'
488  *   and hostname `hostname'.
489  *
490  * Action:
491  *   Update the IPcheck registry.
492  *   Return 0 on failure, 1 on success.
493  */
494 int IPcheck_remote_connect(struct Client *cptr, int is_burst)
495 {
496   assert(0 != cptr);
497   return ip_registry_check_remote(cptr, is_burst);
498 }
499
500 /*
501  * IPcheck_connect_fail
502  *
503  * Event:
504  *   This local client failed to connect due to legal reasons.
505  *
506  * Action:
507  *   Neutralize the effect of calling IPcheck_local_connect, in such
508  *   a way that the client won't be penalized when trying to reconnect
509  *   again.
510  */
511 void IPcheck_connect_fail(struct in_addr a)
512 {
513   ip_registry_connect_fail(a.s_addr);
514 }
515
516 /*
517  * IPcheck_connect_succeeded
518  *
519  * Event:
520  *   A client succeeded to finish the registration.
521  *
522  * Finish IPcheck registration of a successfully, locally connected client.
523  */
524 void IPcheck_connect_succeeded(struct Client *cptr)
525 {
526   assert(0 != cptr);
527   ip_registry_connect_succeeded(cptr);
528 }
529
530 /*
531  * IPcheck_disconnect
532  *
533  * Event:
534  *   A local client disconnected or a remote client left Undernet.
535  *
536  * Action:
537  *   Update the IPcheck registry.
538  *   Remove all expired IPregistry structures from the hash bucket
539  *     that belongs to this clients IP number.
540  */
541 void IPcheck_disconnect(struct Client *cptr)
542 {
543   assert(0 != cptr);
544   ip_registry_disconnect(cptr);
545 }
546
547 /*
548  * IPcheck_nr
549  *
550  * Returns number of clients with the same IP number
551  */
552 unsigned short IPcheck_nr(struct Client *cptr)
553 {
554   assert(0 != cptr);
555   return ip_registry_count(cli_ip(cptr).s_addr);
556 }
557
558 void IPcheck_expire()
559 {
560   static time_t next_expire = 0;
561   if (next_expire < CurrentTime) {
562     ip_registry_expire();
563     next_expire = CurrentTime + 60;
564   }
565 }
566
567