Author: Bleep <tomh@inxpress.net>
[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 "IPcheck.h"
26 #include "client.h"
27 #include "ircd.h"
28 #include "numnicks.h"       /* NumNick, NumServ (GODMODE) */
29 #include "querycmds.h"      /* UserStats */
30 #include "ircd_alloc.h"
31 #include "s_bsd.h"          /* SetIPChecked */
32 #include "s_user.h"         /* TARGET_DELAY */
33 #include "send.h"
34 #include "struct.h"
35
36 #include <assert.h>
37 #include <stdio.h>          /* NULL ... bleah */
38
39 /*
40  * IP number and last targets of a user that just disconnected.
41  * Used to allow a user that shortly disconnected to rejoin
42  * the channels he/she was on.
43  */
44 struct ip_targets_st {
45   struct in_addr ip;
46   unsigned char free_targets;
47   unsigned char targets[MAXTARGETS];
48 };
49
50 /* We keep one IPregistry for each IP number (for both, remote and local clients) */
51 struct IPregistry {
52   union {
53     struct in_addr ip;          /* The IP number of the registry entry. */
54     struct ip_targets_st *ptr;  /* a list of targets */
55   } ip_targets;
56   unsigned int last_connect:16; /* Time of last connect (attempt), see BITMASK
57                                  * below, or time of last disconnect when 
58                                  * `connected' is zero. */
59   unsigned int connected:8;     /* Used for IP# throttling: Number of
60                                  * currently on-line clients with this IP */
61   unsigned int connect_attempts:4;      /* Used for connect speed throttling: Number of clients that connected with this IP number
62                                            or `15' when then real value is >= 15.  This value is only valid when the last connect
63                                            was less then IPCHECK_CLONE_PERIOD seconds ago, it should considered to be 0 otherwise. */
64   unsigned int free_targets:4;  /* Number of free targets that the next local client will inherit on connect,
65                                    or HAS_TARGETS_MAGIC when ip_targets.ptr is a pointer to a ip_targets_st. */
66 };
67
68 struct IPregistry_vector {
69   unsigned short length;
70   unsigned short allocated_length;
71   struct IPregistry *vector;
72 };
73
74 #define HASHTABSIZE 0x2000      /* Must be power of 2 */
75 static struct IPregistry_vector IPregistry_hashtable[HASHTABSIZE];
76
77 /*
78  * Calculate a `hash' value between 0 and HASHTABSIZE, from the internet address `in_addr'.
79  * Apply it immedeately to the table, effectively hiding the table itself.
80  */
81 #define CALCULATE_HASH(in_addr) \
82   struct IPregistry_vector *hash; \
83   do { unsigned int ip = (in_addr).s_addr; \
84        hash = &IPregistry_hashtable[((ip >> 14) + (ip >> 7) + ip) & (HASHTABSIZE - 1)]; } while(0)
85
86 /*
87  * Fit `now' in an unsigned short, the advantage is that we use less memory
88  * `struct IPregistry::last_connect' can be smaller while the only disadvantage 
89  * is that if someone reconnects after exactly 18 hours and 12 minutes, and NOBODY with the
90  * same _hash_ value for this IP-number did disconnect in the meantime, then the server
91  * will think he reconnected immedeately. In other words: No disadvantage at all.
92  */
93 #define BITMASK 0xffff          /* Same number of bits as `struct IPregistry::last_connect' */
94 #define NOW ((unsigned short)(CurrentTime & BITMASK))
95 #define CONNECTED_SINCE(x) ((unsigned short)((CurrentTime & BITMASK) - (x)->last_connect))
96
97 #define IPCHECK_CLONE_LIMIT 2
98 #define IPCHECK_CLONE_PERIOD 20
99 #define IPCHECK_CLONE_DELAY 600
100
101 #define HAS_TARGETS_MAGIC 15
102 #define HAS_TARGETS(entry) ((entry)->free_targets == HAS_TARGETS_MAGIC)
103
104 #if STARTTARGETS >= HAS_TARGETS_MAGIC
105 #error "That doesn't fit in 4 bits, does it?"
106 #endif
107
108 /* IP(entry) returns the `struct in_addr' of the IPregistry. */
109 #define IP(entry) (HAS_TARGETS(entry) ? (entry)->ip_targets.ptr->ip : (entry)->ip_targets.ip)
110 #define FREE_TARGETS(entry) (HAS_TARGETS(entry) ? (entry)->ip_targets.ptr->free_targets : (entry)->free_targets)
111
112 static unsigned short count = 10000, average_length = 4;
113
114 static struct IPregistry *IPregistry_add(struct IPregistry_vector *iprv)
115 {
116   assert(0 != iprv);
117   if (iprv->length == iprv->allocated_length)
118   {
119     iprv->allocated_length += 4;
120     if (iprv->vector) {
121       iprv->vector = 
122               (struct IPregistry*) MyRealloc(iprv->vector,
123                        iprv->allocated_length * sizeof(struct IPregistry));
124     }
125     else {
126       iprv->vector = 
127               (struct IPregistry*) MyMalloc(
128                        iprv->allocated_length * sizeof(struct IPregistry));
129     }
130   }
131   return &iprv->vector[iprv->length++];
132 }
133
134 static struct IPregistry *IPregistry_find(struct IPregistry_vector *iprv,
135     struct in_addr ip)
136 {
137   if (iprv->length > 0)
138   {
139     struct IPregistry *i, *end = &iprv->vector[iprv->length];
140     for (i = &iprv->vector[0]; i < end; ++i)
141       if (IP(i).s_addr == ip.s_addr)
142         return i;
143   }
144   return NULL;
145 }
146
147 static struct IPregistry *IPregistry_find_with_expire(struct IPregistry_vector
148     *iprv, struct in_addr ip)
149 {
150   struct IPregistry *last;
151   struct IPregistry *curr;
152   struct IPregistry *retval = NULL;
153
154   /*
155    * if the vector is empty, IPcheck_disconnect will cause the server
156    * to core when NDEBUG is defined
157    */
158   if (iprv->length < 1)
159     return retval;
160
161   last = &iprv->vector[iprv->length - 1];
162
163   for (curr = &iprv->vector[0]; curr < last;)
164   {
165     if (IP(curr).s_addr == ip.s_addr)
166       /* `curr' is element we looked for */
167       retval = curr;
168     else if (curr->connected == 0)
169     {
170       if (CONNECTED_SINCE(curr) > 600U) /* Don't touch this number, it has statistical significance */
171       {
172         /* `curr' expired */
173         if (HAS_TARGETS(curr))
174           MyFree(curr->ip_targets.ptr);
175         *curr = *last--;
176         iprv->length--;
177         if (--count == 0)
178         {
179           /* Make ever 10000 disconnects an estimation of the average vector length */
180           count = 10000;
181           average_length =
182               (UserStats.clients + UserStats.unknowns + UserStats.local_servers) / HASHTABSIZE;
183         }
184         /* Now check the new element (last) that was moved to this position */
185         continue;
186       }
187       else if (CONNECTED_SINCE(curr) > 120U && HAS_TARGETS(curr))
188       {
189         /* Expire storage of targets */
190         struct in_addr ip1 = curr->ip_targets.ptr->ip;
191         curr->free_targets = curr->ip_targets.ptr->free_targets;
192         MyFree(curr->ip_targets.ptr);
193         curr->ip_targets.ip = ip1;
194       }
195     }
196     /* Did not expire, check next element */
197     ++curr;
198   }
199   /* Now check the last element in the list (curr == last) */
200   if (IP(curr).s_addr == ip.s_addr)
201     /* `curr' is element we looked for */
202     retval = curr;
203   else if (curr->connected == 0)
204   {
205     if (CONNECTED_SINCE(curr) > 600U)   /* Don't touch this number, it has statistical significance */
206     {
207       /* `curr' expired */
208       if (HAS_TARGETS(curr))
209         MyFree(curr->ip_targets.ptr);
210       iprv->length--;
211       if (--count == 0)
212       {
213         /* Make ever 10000 disconnects an estimation of the average vector length */
214         count = 10000;
215         average_length =
216             (UserStats.clients + UserStats.unknowns + UserStats.local_servers) / HASHTABSIZE;
217       }
218     }
219     else if (CONNECTED_SINCE(curr) > 120U && HAS_TARGETS(curr))
220     {
221       /* Expire storage of targets */
222       struct in_addr ip1 = curr->ip_targets.ptr->ip;
223       curr->free_targets = curr->ip_targets.ptr->free_targets;
224       MyFree(curr->ip_targets.ptr);
225       curr->ip_targets.ip = ip1;
226     }
227   }
228   /* Do we need to shrink the vector? */
229   if (iprv->allocated_length > average_length
230       && iprv->allocated_length - iprv->length >= 4)
231   {
232     struct IPregistry *newpos;
233     iprv->allocated_length = iprv->length;
234     newpos =
235         (struct IPregistry *)MyRealloc(iprv->vector,
236         iprv->allocated_length * sizeof(struct IPregistry));
237     if (newpos != iprv->vector) /* Is this ever true? */
238     {
239       retval =
240           (struct IPregistry *)((char *)retval + ((char *)newpos -
241           (char *)iprv->vector));
242       iprv->vector = newpos;
243     }
244   }
245   return retval;
246 }
247
248 static void reset_connect_time(struct IPregistry *entry)
249 {
250   unsigned int previous_free_targets;
251
252   /* Apply aging */
253   previous_free_targets =
254       FREE_TARGETS(entry) + CONNECTED_SINCE(entry) / TARGET_DELAY;
255   if (previous_free_targets > STARTTARGETS)
256     previous_free_targets = STARTTARGETS;
257   if (HAS_TARGETS(entry))
258     entry->ip_targets.ptr->free_targets = previous_free_targets;
259   else
260     entry->free_targets = previous_free_targets;
261
262   entry->last_connect = NOW;
263 }
264
265 /*
266  * IPcheck_local_connect
267  *
268  * Event:
269  *   A new connection was accept()-ed with IP number `cptr->ip.s_addr'.
270  *
271  * Action:
272  *   Update the IPcheck registry.
273  *   Return:
274  *     1 : You're allowed to connect.
275  *     0 : You're not allowed to connect.
276  *
277  * Throttling:
278  *
279  * A connection should be rejected when a connection from the same IP number was
280  * received IPCHECK_CLONE_LIMIT times before this connect attempt, with
281  * reconnect intervals of IPCHECK_CLONE_PERIOD seconds or less.
282  *
283  * Free target inheritance:
284  *
285  * When the client is accepted, then the number of Free Targets
286  * of the cptr is set to the value stored in the found IPregistry
287  * structure, or left at STARTTARGETS.  This can be done by changing
288  * cptr->nexttarget to be `now - (TARGET_DELAY * (FREE_TARGETS - 1))',
289  * where FREE_TARGETS may range from 0 till STARTTARGETS.
290  */
291 int IPcheck_local_connect(struct in_addr a, time_t* next_target_out)
292 {
293   struct IPregistry *entry;
294   CALCULATE_HASH(a);
295   assert(0 != next_target_out);
296
297   if (!(entry = IPregistry_find(hash, a)))
298   {
299     entry = IPregistry_add(hash);
300     entry->ip_targets.ip = a;          /* The IP number of registry entry */
301     entry->last_connect = NOW;          /* Seconds since last connect attempt */
302     entry->connected = 1;               /* connected clients for this IP */
303     entry->connect_attempts = 1;        /* Number attempts for this IP */
304     entry->free_targets = STARTTARGETS; /* free targets a client gets */
305     return 1;
306   }
307   /* Note that this also connects server connects.
308    * It is hard and not interesting, to change that.
309    *
310    * Don't allow more then 255 connects from one IP number, ever
311    */
312   if (0 == ++entry->connected)
313     return 0;
314
315   if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_PERIOD)
316     entry->connect_attempts = 0;
317
318   reset_connect_time(entry);
319
320   if (0 == ++entry->connect_attempts)   /* Check for overflow */
321     --entry->connect_attempts;
322
323   if (entry->connect_attempts <= IPCHECK_CLONE_LIMIT)
324     *next_target_out = CurrentTime - (TARGET_DELAY * (FREE_TARGETS(entry) - 1));
325
326   /* Don't refuse connection when we just rebooted the server */
327   else if (CurrentTime - me.since > IPCHECK_CLONE_DELAY)
328 #ifndef NOTHROTTLE 
329     return 0;
330 #else
331     return 1;
332 #endif        
333   return 1;
334 }
335
336 /*
337  * IPcheck_remote_connect
338  *
339  * Event:
340  *   A remote client connected to Undernet, with IP number `cptr->ip.s_addr'
341  *   and hostname `hostname'.
342  *
343  * Action:
344  *   Update the IPcheck registry.
345  *   Return -1 on failure, 0 on success.
346  */
347 int IPcheck_remote_connect(struct Client *cptr, const char *hostname,
348     int is_burst)
349 {
350   struct IPregistry *entry;
351   CALCULATE_HASH(cptr->ip);
352   SetIPChecked(cptr);           /* Mark that we did add/update an IPregistry entry */
353   if (!(entry = IPregistry_find(hash, cptr->ip)))
354   {
355     entry = IPregistry_add(hash);
356     entry->ip_targets.ip = cptr->ip;    /* The IP number of registry entry */
357     entry->last_connect = NOW;  /* Seconds since last connect (attempt) */
358     entry->connected = 1;       /* Number of currently connected clients with this IP number */
359     entry->connect_attempts = is_burst ? 1 : 0; /* Number of clients that connected with this IP number */
360     entry->free_targets = STARTTARGETS; /* Number of free targets that a client gets on connect */
361   }
362   else
363   {
364 #ifdef GODMODE
365     sendto_one(cptr,
366         "%s NOTICE %s%s :I saw your face before my friend (connected: %u; connect_attempts %u; free_targets %u)",
367         NumServ(&me), NumNick(cptr), entry->connected, entry->connect_attempts,
368         FREE_TARGETS(entry));
369 #endif
370     if (++(entry->connected) == 0)      /* Don't allow more then 255 connects from one IP number, ever */
371       return -1;
372     if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_PERIOD)
373       entry->connect_attempts = 0;
374     if (!is_burst)
375     {
376       if (++(entry->connect_attempts) == 0)     /* Check for overflow */
377         --(entry->connect_attempts);
378       reset_connect_time(entry);
379     }
380   }
381   return 0;
382 }
383
384 /*
385  * IPcheck_connect_fail
386  *
387  * Event:
388  *   This local client failed to connect due to legal reasons.
389  *
390  * Action:
391  *   Neutralize the effect of calling IPcheck_local_connect, in such
392  *   a way that the client won't be penalized when trying to reconnect
393  *   again.
394  */
395 void IPcheck_connect_fail(struct in_addr a)
396 {
397   struct IPregistry *entry;
398   CALCULATE_HASH(a);
399   if ((entry = IPregistry_find(hash, a)))
400     --entry->connect_attempts;
401 }
402
403 /*
404  * IPcheck_connect_succeeded
405  *
406  * Event:
407  *   A client succeeded to finish the registration.
408  *
409  * Finish IPcheck registration of a successfully, locally connected client.
410  */
411 void IPcheck_connect_succeeded(struct Client *cptr)
412 {
413   struct IPregistry *entry;
414   const char *tr = "";
415   CALCULATE_HASH(cptr->ip);
416   entry = IPregistry_find(hash, cptr->ip);
417   if (HAS_TARGETS(entry))
418   {
419     memcpy(cptr->targets, entry->ip_targets.ptr->targets, MAXTARGETS);
420     tr = " tr";
421   }
422   sendto_one(cptr, ":%s NOTICE %s :on %u ca %u(%u) ft %u(%u)%s",
423       me.name, cptr->name, entry->connected, entry->connect_attempts,
424       IPCHECK_CLONE_LIMIT, FREE_TARGETS(entry), STARTTARGETS, tr);
425 }
426
427 /*
428  * IPcheck_disconnect
429  *
430  * Event:
431  *   A local client disconnected or a remote client left Undernet.
432  *
433  * Action:
434  *   Update the IPcheck registry.
435  *   Remove all expired IPregistry structures from the hash bucket
436  *     that belongs to this clients IP number.
437  */
438 void IPcheck_disconnect(struct Client *cptr)
439 {
440   struct IPregistry *entry;
441   CALCULATE_HASH(cptr->ip);
442   entry = IPregistry_find_with_expire(hash, cptr->ip);
443   if (0 == entry) {
444     /*
445      * trying to find an entry for a server causes this to happen,
446      * servers should never have FLAGS_IPCHECK set
447      */
448     assert(0 != entry);
449     return;
450   }
451   /*
452    * If this was the last one, set `last_connect' to disconnect time (used for expiration)
453    */
454   if (--(entry->connected) == 0) {
455     if (CONNECTED_SINCE(entry) > IPCHECK_CLONE_LIMIT * IPCHECK_CLONE_PERIOD)
456       /*
457        * Otherwise we'd penetalize for this old value if the client reconnects within 20 seconds
458        */
459       entry->connect_attempts = 0;
460     reset_connect_time(entry);
461   }
462   if (MyConnect(cptr)) {
463     unsigned int inheritance;
464     /*
465      * Copy the clients targets
466      */
467     if (HAS_TARGETS(entry)) {
468       entry->free_targets = entry->ip_targets.ptr->free_targets;
469       MyFree(entry->ip_targets.ptr);
470     }
471     entry->ip_targets.ptr =
472         (struct ip_targets_st*) MyMalloc(sizeof(struct ip_targets_st));
473
474     assert(0 != entry->ip_targets.ptr);
475     entry->ip_targets.ptr->ip = cptr->ip;
476     entry->ip_targets.ptr->free_targets = entry->free_targets;
477     entry->free_targets = HAS_TARGETS_MAGIC;
478     memcpy(entry->ip_targets.ptr->targets, cptr->targets, MAXTARGETS);
479     /*
480      * This calculation can be pretty unfair towards large multi-user hosts, but
481      * there is "nothing" we can do without also allowing spam bots to send more
482      * messages or by drastically increasing the ammount of memory used in the IPregistry.
483      *
484      * The problem is that when a client disconnects, leaving no free targets, then
485      * the next client from that IP number has to pay for it (getting no free targets).
486      * But ALSO the next client, and the next client, and the next client etc - until
487      * another client disconnects that DOES leave free targets.  The reason for this
488      * is that if there are 10 SPAM bots, and they all disconnect at once, then they
489      * ALL should get no free targets when reconnecting.  We'd need to store an entry
490      * per client (instead of per IP number) to avoid this.
491      */
492     if (cptr->nexttarget <= CurrentTime)
493         /*
494          * Number of free targets
495          */
496       inheritance = (CurrentTime - cptr->nexttarget) / TARGET_DELAY + 1;
497     else
498       inheritance = 0;
499     /*
500      * Add bonus, this is pretty fuzzy, but it will help in some cases.
501      */
502     if (CurrentTime - cptr->firsttime > 600)
503       /*
504        * Was longer then 10 minutes online?
505        */
506       inheritance += (CurrentTime - cptr->firsttime - 600) / TARGET_DELAY;
507     /*
508      * Finally, store smallest value for Judgement Day
509      */
510     if (inheritance < entry->ip_targets.ptr->free_targets)
511       entry->ip_targets.ptr->free_targets = inheritance;
512   }
513 }
514
515 /*
516  * IPcheck_nr
517  *
518  * Returns number of clients with the same IP number
519  */
520 unsigned short IPcheck_nr(struct Client *cptr)
521 {
522   struct IPregistry *entry;
523   CALCULATE_HASH(cptr->ip);
524   entry = IPregistry_find(hash, cptr->ip);
525   return (entry ? entry->connected : 0);
526 }
527