sendmail transition: Do not pre-generate sendmail.cf
[dragonfly.git] / contrib / sendmail-8.14 / sendmail / ratectrl.c
1 /*
2  * Copyright (c) 2003 Sendmail, Inc. and its suppliers.
3  *      All rights reserved.
4  *
5  * By using this file, you agree to the terms and conditions set
6  * forth in the LICENSE file which can be found at the top level of
7  * the sendmail distribution.
8  *
9  * Contributed by Jose Marcio Martins da Cruz - Ecole des Mines de Paris
10  *   Jose-Marcio.Martins@ensmp.fr
11  */
12
13 /* a part of this code is based on inetd.c for which this copyright applies: */
14 /*
15  * Copyright (c) 1983, 1991, 1993, 1994
16  *      The Regents of the University of California.  All rights reserved.
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions
20  * are met:
21  * 1. Redistributions of source code must retain the above copyright
22  *    notice, this list of conditions and the following disclaimer.
23  * 2. Redistributions in binary form must reproduce the above copyright
24  *    notice, this list of conditions and the following disclaimer in the
25  *    documentation and/or other materials provided with the distribution.
26  * 3. All advertising materials mentioning features or use of this software
27  *    must display the following acknowledgement:
28  *      This product includes software developed by the University of
29  *      California, Berkeley and its contributors.
30  * 4. Neither the name of the University nor the names of its contributors
31  *    may be used to endorse or promote products derived from this software
32  *    without specific prior written permission.
33  *
34  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
35  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
38  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
40  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
42  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44  * SUCH DAMAGE.
45  */
46
47 #include <sendmail.h>
48 SM_RCSID("@(#)$Id: ratectrl.c,v 8.13 2009/05/05 23:19:34 ca Exp $")
49
50 /*
51 **  stuff included - given some warnings (inet_ntoa)
52 **      - surely not everything is needed
53 */
54
55 #if NETINET || NETINET6
56 # include <arpa/inet.h>
57 #endif  /* NETINET || NETINET6 */
58
59 #include <sm/time.h>
60
61 #ifndef HASH_ALG
62 # define HASH_ALG       2
63 #endif /* HASH_ALG */
64
65 #ifndef RATECTL_DEBUG
66 # define RATECTL_DEBUG  0
67 #endif /* RATECTL_DEBUG */
68
69 /* forward declarations */
70 static int client_rate __P((time_t, SOCKADDR *, bool));
71 static int total_rate __P((time_t, bool));
72
73 /*
74 **  CONNECTION_RATE_CHECK - updates connection history data
75 **      and computes connection rate for the given host
76 **
77 **    Parameters:
78 **      hostaddr -- ip address of smtp client
79 **      e -- envelope
80 **
81 **    Returns:
82 **      true (always)
83 **
84 **    Side Effects:
85 **      updates connection history
86 **
87 **    Warnings:
88 **      For each connection, this call shall be
89 **      done only once with the value true for the
90 **      update parameter.
91 **      Typically, this call is done with the value
92 **      true by the father, and once again with
93 **      the value false by the children.
94 **
95 */
96
97 bool
98 connection_rate_check(hostaddr, e)
99         SOCKADDR *hostaddr;
100         ENVELOPE *e;
101 {
102         time_t now;
103         int totalrate, clientrate;
104         static int clientconn = 0;
105
106         now = time(NULL);
107 #if RATECTL_DEBUG
108         sm_syslog(LOG_INFO, NOQID, "connection_rate_check entering...");
109 #endif /* RATECTL_DEBUG */
110
111         /* update server connection rate */
112         totalrate = total_rate(now, e == NULL);
113 #if RATECTL_DEBUG
114         sm_syslog(LOG_INFO, NOQID, "global connection rate: %d", totalrate);
115 #endif /* RATECTL_DEBUG */
116
117         /* update client connection rate */
118         clientrate = client_rate(now, hostaddr, e == NULL);
119
120         if (e == NULL)
121                 clientconn = count_open_connections(hostaddr);
122
123         if (e != NULL)
124         {
125                 char s[16];
126
127                 sm_snprintf(s, sizeof(s), "%d", clientrate);
128                 macdefine(&e->e_macro, A_TEMP, macid("{client_rate}"), s);
129                 sm_snprintf(s, sizeof(s), "%d", totalrate);
130                 macdefine(&e->e_macro, A_TEMP, macid("{total_rate}"), s);
131                 sm_snprintf(s, sizeof(s), "%d", clientconn);
132                 macdefine(&e->e_macro, A_TEMP, macid("{client_connections}"),
133                                 s);
134         }
135         return true;
136 }
137
138 /*
139 **  Data declarations needed to evaluate connection rate
140 */
141
142 static int CollTime = 60;
143
144 /* this should be a power of 2, otherwise CPMHMASK doesn't work well */
145 #ifndef CPMHSIZE
146 # define CPMHSIZE       1024
147 #endif /* CPMHSIZE */
148
149 #define CPMHMASK        (CPMHSIZE-1)
150
151 #ifndef MAX_CT_STEPS
152 # define MAX_CT_STEPS   10
153 #endif /* MAX_CT_STEPS */
154
155 /*
156 **  time granularity: 10s (that's one "tick")
157 **  will be initialised to ConnectionRateWindowSize/CHTSIZE
158 **  before being used the first time
159 */
160
161 static int ChtGran = -1;
162
163 #define CHTSIZE         6
164
165 /* Number of connections for a certain "tick" */
166 typedef struct CTime
167 {
168         unsigned long   ct_Ticks;
169         int             ct_Count;
170 }
171 CTime_T;
172
173 typedef struct CHash
174 {
175 #if NETINET6 && NETINET
176         union
177         {
178                 struct in_addr  c4_Addr;
179                 struct in6_addr c6_Addr;
180         } cu_Addr;
181 # define ch_Addr4       cu_Addr.c4_Addr
182 # define ch_Addr6       cu_Addr.c6_Addr
183 #else /* NETINET6 && NETINET */
184 # if NETINET6
185         struct in6_addr ch_Addr;
186 #  define ch_Addr6      ch_Addr
187 # else /* NETINET6 */
188         struct in_addr ch_Addr;
189 #  define ch_Addr4      ch_Addr
190 # endif /* NETINET6 */
191 #endif /* NETINET6 && NETINET */
192
193         int             ch_Family;
194         time_t          ch_LTime;
195         unsigned long   ch_colls;
196
197         /* 6 buckets for ticks: 60s */
198         CTime_T         ch_Times[CHTSIZE];
199 }
200 CHash_T;
201
202 static CHash_T CHashAry[CPMHSIZE];
203 static bool CHashAryOK = false;
204
205 /*
206 **  CLIENT_RATE - Evaluate connection rate per smtp client
207 **
208 **      Parameters:
209 **              now - current time in secs
210 **              saddr - client address
211 **              update - update data / check only
212 **
213 **      Returns:
214 **              connection rate (connections / ConnectionRateWindowSize)
215 **
216 **      Side effects:
217 **              update static global data
218 **
219 */
220
221 static int
222 client_rate(now, saddr, update)
223          time_t now;
224          SOCKADDR *saddr;
225          bool update;
226 {
227         unsigned int hv;
228         int i;
229         int cnt;
230         bool coll;
231         CHash_T *chBest = NULL;
232         unsigned int ticks;
233
234         cnt = 0;
235         hv = 0xABC3D20F;
236         if (ChtGran < 0)
237                 ChtGran = ConnectionRateWindowSize / CHTSIZE;
238         if (ChtGran <= 0)
239                 ChtGran = 10;
240
241         ticks = now / ChtGran;
242
243         if (!CHashAryOK)
244         {
245                 memset(CHashAry, 0, sizeof(CHashAry));
246                 CHashAryOK = true;
247         }
248
249         {
250                 char *p;
251                 int addrlen;
252 #if HASH_ALG != 1
253                 int c, d;
254 #endif /* HASH_ALG != 1 */
255
256                 switch (saddr->sa.sa_family)
257                 {
258 #if NETINET
259                   case AF_INET:
260                         p = (char *)&saddr->sin.sin_addr;
261                         addrlen = sizeof(struct in_addr);
262                         break;
263 #endif /* NETINET */
264 #if NETINET6
265                   case AF_INET6:
266                         p = (char *)&saddr->sin6.sin6_addr;
267                         addrlen = sizeof(struct in6_addr);
268                         break;
269 #endif /* NETINET6 */
270                   default:
271                         /* should not happen */
272                         return -1;
273                 }
274
275                 /* compute hash value */
276                 for (i = 0; i < addrlen; ++i, ++p)
277 #if HASH_ALG == 1
278                         hv = (hv << 5) ^ (hv >> 23) ^ *p;
279                 hv = (hv ^ (hv >> 16));
280 #elif HASH_ALG == 2
281                 {
282                         d = *p;
283                         c = d;
284                         c ^= c<<6;
285                         hv += (c<<11) ^ (c>>1);
286                         hv ^= (d<<14) + (d<<7) + (d<<4) + d;
287                 }
288 #elif HASH_ALG == 3
289                 {
290                         hv = (hv << 4) + *p;
291                         d = hv & 0xf0000000;
292                         if (d != 0)
293                         {
294                                 hv ^= (d >> 24);
295                                 hv ^= d;
296                         }
297                 }
298 #else /* HASH_ALG == 1 */
299                         hv = ((hv << 1) ^ (*p & 0377)) % cctx->cc_size;
300 #endif /* HASH_ALG == 1 */
301         }
302
303         coll = true;
304         for (i = 0; i < MAX_CT_STEPS; ++i)
305         {
306                 CHash_T *ch = &CHashAry[(hv + i) & CPMHMASK];
307
308 #if NETINET
309                 if (saddr->sa.sa_family == AF_INET &&
310                     ch->ch_Family == AF_INET &&
311                     (saddr->sin.sin_addr.s_addr == ch->ch_Addr4.s_addr ||
312                      ch->ch_Addr4.s_addr == 0))
313                 {
314                         chBest = ch;
315                         coll = false;
316                         break;
317                 }
318 #endif /* NETINET */
319 #if NETINET6
320                 if (saddr->sa.sa_family == AF_INET6 &&
321                     ch->ch_Family == AF_INET6 &&
322                     (IN6_ARE_ADDR_EQUAL(&saddr->sin6.sin6_addr,
323                                        &ch->ch_Addr6) != 0 ||
324                      IN6_IS_ADDR_UNSPECIFIED(&ch->ch_Addr6)))
325                 {
326                         chBest = ch;
327                         coll = false;
328                         break;
329                 }
330 #endif /* NETINET6 */
331                 if (chBest == NULL || ch->ch_LTime == 0 ||
332                     ch->ch_LTime < chBest->ch_LTime)
333                         chBest = ch;
334         }
335
336         /* Let's update data... */
337         if (update)
338         {
339                 if (coll && (now - chBest->ch_LTime < CollTime))
340                 {
341                         /*
342                         **  increment the number of collisions last
343                         **  CollTime for this client
344                         */
345
346                         chBest->ch_colls++;
347
348                         /*
349                         **  Maybe shall log if collision rate is too high...
350                         **  and take measures to resize tables
351                         **  if this is the case
352                         */
353                 }
354
355                 /*
356                 **  If it's not a match, then replace the data.
357                 **  Note: this purges the history of a colliding entry,
358                 **  which may cause "overruns", i.e., if two entries are
359                 **  "cancelling" each other out, then they may exceed
360                 **  the limits that are set. This might be mitigated a bit
361                 **  by the above "best of 5" function however.
362                 **
363                 **  Alternative approach: just use the old data, which may
364                 **  cause false positives however.
365                 **  To activate this, change deactivate following memset call.
366                 */
367
368                 if (coll)
369                 {
370 #if NETINET
371                         if (saddr->sa.sa_family == AF_INET)
372                         {
373                                 chBest->ch_Family = AF_INET;
374                                 chBest->ch_Addr4 = saddr->sin.sin_addr;
375                         }
376 #endif /* NETINET */
377 #if NETINET6
378                         if (saddr->sa.sa_family == AF_INET6)
379                         {
380                                 chBest->ch_Family = AF_INET6;
381                                 chBest->ch_Addr6 = saddr->sin6.sin6_addr;
382                         }
383 #endif /* NETINET6 */
384 #if 1
385                         memset(chBest->ch_Times, '\0',
386                                sizeof(chBest->ch_Times));
387 #endif /* 1 */
388                 }
389
390                 chBest->ch_LTime = now;
391                 {
392                         CTime_T *ct = &chBest->ch_Times[ticks % CHTSIZE];
393
394                         if (ct->ct_Ticks != ticks)
395                         {
396                                 ct->ct_Ticks = ticks;
397                                 ct->ct_Count = 0;
398                         }
399                         ++ct->ct_Count;
400                 }
401         }
402
403         /* Now let's count connections on the window */
404         for (i = 0; i < CHTSIZE; ++i)
405         {
406                 CTime_T *ct = &chBest->ch_Times[i];
407
408                 if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
409                         cnt += ct->ct_Count;
410         }
411
412 #if RATECTL_DEBUG
413         sm_syslog(LOG_WARNING, NOQID,
414                 "cln: cnt=(%d), CHTSIZE=(%d), ChtGran=(%d)",
415                 cnt, CHTSIZE, ChtGran);
416 #endif /* RATECTL_DEBUG */
417         return cnt;
418 }
419
420 /*
421 **  TOTAL_RATE - Evaluate global connection rate
422 **
423 **      Parameters:
424 **              now - current time in secs
425 **              update - update data / check only
426 **
427 **      Returns:
428 **              connection rate (connections / ConnectionRateWindowSize)
429 */
430
431 static CTime_T srv_Times[CHTSIZE];
432 static bool srv_Times_OK = false;
433
434 static int
435 total_rate(now, update)
436          time_t now;
437          bool update;
438 {
439         int i;
440         int cnt = 0;
441         CTime_T *ct;
442         unsigned int ticks;
443
444         if (ChtGran < 0)
445                 ChtGran = ConnectionRateWindowSize / CHTSIZE;
446         if (ChtGran == 0)
447                 ChtGran = 10;
448         ticks = now / ChtGran;
449         if (!srv_Times_OK)
450         {
451                 memset(srv_Times, 0, sizeof(srv_Times));
452                 srv_Times_OK = true;
453         }
454
455         /* Let's update data */
456         if (update)
457         {
458                 ct = &srv_Times[ticks % CHTSIZE];
459
460                 if (ct->ct_Ticks != ticks)
461                 {
462                         ct->ct_Ticks = ticks;
463                         ct->ct_Count = 0;
464                 }
465                 ++ct->ct_Count;
466         }
467
468         /* Let's count connections on the window */
469         for (i = 0; i < CHTSIZE; ++i)
470         {
471                 ct = &srv_Times[i];
472
473                 if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
474                         cnt += ct->ct_Count;
475         }
476
477 #if RATECTL_DEBUG
478         sm_syslog(LOG_WARNING, NOQID,
479                 "srv: cnt=(%d), CHTSIZE=(%d), ChtGran=(%d)",
480                  cnt, CHTSIZE, ChtGran);
481 #endif /* RATECTL_DEBUG */
482
483         return cnt;
484 }