1 /*
2 * Copyright (c) 2003 Proofpoint, 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 * [email protected]
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 <ratectrl.h>
48 SM_RCSID("@(#)$Id: ratectrl.c,v 8.14 2013-11-22 20:51:56 ca Exp $")
49
50 static int client_rate __P((time_t, SOCKADDR *, int));
51 static int total_rate __P((time_t, bool));
52 static unsigned int gen_hash __P((SOCKADDR *));
53 static void rate_init __P((void));
54
55 /*
56 ** CONNECTION_RATE_CHECK - updates connection history data
57 ** and computes connection rate for the given host
58 **
59 ** Parameters:
60 ** hostaddr -- IP address of SMTP client
61 ** e -- envelope
62 **
63 ** Returns:
64 ** none
65 **
66 ** Side Effects:
67 ** updates connection history
68 **
69 ** Warnings:
70 ** For each connection, this call shall be
71 ** done only once with the value true for the
72 ** update parameter.
73 ** Typically, this call is done with the value
74 ** true by the father, and once again with
75 ** the value false by the children.
76 */
77
78 void
connection_rate_check(hostaddr,e)79 connection_rate_check(hostaddr, e)
80 SOCKADDR *hostaddr;
81 ENVELOPE *e;
82 {
83 time_t now;
84 int totalrate, clientrate;
85 static int clientconn = 0;
86
87 now = time(NULL);
88 #if RATECTL_DEBUG
89 sm_syslog(LOG_INFO, NOQID, "connection_rate_check entering...");
90 #endif
91
92 /* update server connection rate */
93 totalrate = total_rate(now, e == NULL);
94 #if RATECTL_DEBUG
95 sm_syslog(LOG_INFO, NOQID, "global connection rate: %d", totalrate);
96 #endif
97
98 /* update client connection rate */
99 clientrate = client_rate(now, hostaddr, e == NULL ? SM_CLFL_UPDATE : SM_CLFL_NONE);
100
101 if (e == NULL)
102 clientconn = count_open_connections(hostaddr);
103
104 if (e != NULL)
105 {
106 char s[16];
107
108 sm_snprintf(s, sizeof(s), "%d", clientrate);
109 macdefine(&e->e_macro, A_TEMP, macid("{client_rate}"), s);
110 sm_snprintf(s, sizeof(s), "%d", totalrate);
111 macdefine(&e->e_macro, A_TEMP, macid("{total_rate}"), s);
112 sm_snprintf(s, sizeof(s), "%d", clientconn);
113 macdefine(&e->e_macro, A_TEMP, macid("{client_connections}"),
114 s);
115 }
116 return;
117 }
118
119 /*
120 ** Data declarations needed to evaluate connection rate
121 */
122
123 static int CollTime = 60;
124
125 /*
126 ** time granularity: 10s (that's one "tick")
127 ** will be initialised to ConnectionRateWindowSize/CHTSIZE
128 ** before being used the first time
129 */
130
131 static int ChtGran = -1;
132 static CHash_T CHashAry[CPMHSIZE];
133 static CTime_T srv_Times[CHTSIZE];
134
135 #ifndef MAX_CT_STEPS
136 # define MAX_CT_STEPS 10
137 #endif
138
139 /*
140 ** RATE_INIT - initialize local data
141 **
142 ** Parameters:
143 ** none
144 **
145 ** Returns:
146 ** none
147 **
148 ** Side effects:
149 ** initializes static global data
150 */
151
152 static void
rate_init()153 rate_init()
154 {
155 if (ChtGran > 0)
156 return;
157 ChtGran = ConnectionRateWindowSize / CHTSIZE;
158 if (ChtGran <= 0)
159 ChtGran = 10;
160 memset(CHashAry, 0, sizeof(CHashAry));
161 memset(srv_Times, 0, sizeof(srv_Times));
162 return;
163 }
164
165 /*
166 ** GEN_HASH - calculate a hash value
167 **
168 ** Parameters:
169 ** saddr - client address
170 **
171 ** Returns:
172 ** hash value
173 */
174
175 static unsigned int
gen_hash(saddr)176 gen_hash(saddr)
177 SOCKADDR *saddr;
178 {
179 unsigned int hv;
180 int i;
181 int addrlen;
182 char *p;
183 #if HASH_ALG != 1
184 int c, d;
185 #endif
186
187 hv = 0xABC3D20F;
188 switch (saddr->sa.sa_family)
189 {
190 #if NETINET
191 case AF_INET:
192 p = (char *)&saddr->sin.sin_addr;
193 addrlen = sizeof(struct in_addr);
194 break;
195 #endif /* NETINET */
196 #if NETINET6
197 case AF_INET6:
198 p = (char *)&saddr->sin6.sin6_addr;
199 addrlen = sizeof(struct in6_addr);
200 break;
201 #endif /* NETINET6 */
202 default:
203 /* should not happen */
204 return -1;
205 }
206
207 /* compute hash value */
208 for (i = 0; i < addrlen; ++i, ++p)
209 #if HASH_ALG == 1
210 hv = (hv << 5) ^ (hv >> 23) ^ *p;
211 hv = (hv ^ (hv >> 16));
212 #elif HASH_ALG == 2
213 {
214 d = *p;
215 c = d;
216 c ^= c<<6;
217 hv += (c<<11) ^ (c>>1);
218 hv ^= (d<<14) + (d<<7) + (d<<4) + d;
219 }
220 #elif HASH_ALG == 3
221 {
222 hv = (hv << 4) + *p;
223 d = hv & 0xf0000000;
224 if (d != 0)
225 {
226 hv ^= (d >> 24);
227 hv ^= d;
228 }
229 }
230 #else /* HASH_ALG == 1 */
231 # ERROR: unsupported HASH_ALG
232 hv = ((hv << 1) ^ (*p & 0377)) % cctx->cc_size; ???
233 #endif /* HASH_ALG == 1 */
234
235 return hv;
236 }
237
238 /*
239 ** CONN_LIMIT - Evaluate connection limits
240 **
241 ** Parameters:
242 ** e -- envelope (_FFR_OCC, for logging only)
243 ** now - current time in secs
244 ** saddr - client address
245 ** clflags - update data / check only / ...
246 ** hashary - hash array
247 ** ratelimit - rate limit (_FFR_OCC only)
248 ** conclimit - concurrency limit (_FFR_OCC only)
249 **
250 ** Returns:
251 #if _FFR_OCC
252 ** outgoing: limit exceeded?
253 #endif
254 ** incoming:
255 ** connection rate (connections / ConnectionRateWindowSize)
256 */
257
258 int
conn_limits(e,now,saddr,clflags,hashary,ratelimit,conclimit)259 conn_limits(e, now, saddr, clflags, hashary, ratelimit, conclimit)
260 ENVELOPE *e;
261 time_t now;
262 SOCKADDR *saddr;
263 int clflags;
264 CHash_T hashary[];
265 int ratelimit;
266 int conclimit;
267 {
268 int i;
269 int cnt;
270 bool coll;
271 CHash_T *chBest = NULL;
272 CTime_T *ct = NULL;
273 unsigned int ticks;
274 unsigned int hv;
275 #if _FFR_OCC
276 bool exceeded = false;
277 int *prv, *pcv;
278 #endif
279 #if RATECTL_DEBUG || _FFR_OCC
280 bool logit = false;
281 #endif
282
283 cnt = 0;
284 hv = gen_hash(saddr);
285 ticks = now / ChtGran;
286
287 coll = true;
288 for (i = 0; i < MAX_CT_STEPS; ++i)
289 {
290 CHash_T *ch = &hashary[(hv + i) & CPMHMASK];
291
292 #if NETINET
293 if (saddr->sa.sa_family == AF_INET &&
294 ch->ch_Family == AF_INET &&
295 (saddr->sin.sin_addr.s_addr == ch->ch_Addr4.s_addr ||
296 ch->ch_Addr4.s_addr == 0))
297 {
298 chBest = ch;
299 coll = false;
300 break;
301 }
302 #endif /* NETINET */
303 #if NETINET6
304 if (saddr->sa.sa_family == AF_INET6 &&
305 ch->ch_Family == AF_INET6 &&
306 (IN6_ARE_ADDR_EQUAL(&saddr->sin6.sin6_addr,
307 &ch->ch_Addr6) != 0 ||
308 IN6_IS_ADDR_UNSPECIFIED(&ch->ch_Addr6)))
309 {
310 chBest = ch;
311 coll = false;
312 break;
313 }
314 #endif /* NETINET6 */
315 if (chBest == NULL || ch->ch_LTime == 0 ||
316 ch->ch_LTime < chBest->ch_LTime)
317 chBest = ch;
318 }
319
320 /* Let's update data... */
321 if ((clflags & (SM_CLFL_UPDATE|SM_CLFL_EXC)) != 0)
322 {
323 if (coll && (now - chBest->ch_LTime < CollTime))
324 {
325 /*
326 ** increment the number of collisions last
327 ** CollTime for this client
328 */
329
330 chBest->ch_colls++;
331
332 /*
333 ** Maybe shall log if collision rate is too high...
334 ** and take measures to resize tables
335 ** if this is the case
336 */
337 }
338
339 /*
340 ** If it's not a match, then replace the data.
341 ** Note: this purges the history of a colliding entry,
342 ** which may cause "overruns", i.e., if two entries are
343 ** "cancelling" each other out, then they may exceed
344 ** the limits that are set. This might be mitigated a bit
345 ** by the above "best of 5" function however.
346 **
347 ** Alternative approach: just use the old data, which may
348 ** cause false positives however.
349 ** To activate this, deactivate the memset() call.
350 */
351
352 if (coll)
353 {
354 #if NETINET
355 if (saddr->sa.sa_family == AF_INET)
356 {
357 chBest->ch_Family = AF_INET;
358 chBest->ch_Addr4 = saddr->sin.sin_addr;
359 }
360 #endif /* NETINET */
361 #if NETINET6
362 if (saddr->sa.sa_family == AF_INET6)
363 {
364 chBest->ch_Family = AF_INET6;
365 chBest->ch_Addr6 = saddr->sin6.sin6_addr;
366 }
367 #endif /* NETINET6 */
368 memset(chBest->ch_Times, '\0',
369 sizeof(chBest->ch_Times));
370 }
371
372 chBest->ch_LTime = now;
373 ct = &chBest->ch_Times[ticks % CHTSIZE];
374
375 if (ct->ct_Ticks != ticks)
376 {
377 ct->ct_Ticks = ticks;
378 ct->ct_Count = 0;
379 }
380 if ((clflags & SM_CLFL_UPDATE) != 0)
381 ++ct->ct_Count;
382 }
383
384 /* Now let's count connections on the window */
385 for (i = 0; i < CHTSIZE; ++i)
386 {
387 CTime_T *cth;
388
389 cth = &chBest->ch_Times[i];
390 if (cth->ct_Ticks <= ticks && cth->ct_Ticks >= ticks - CHTSIZE)
391 cnt += cth->ct_Count;
392 }
393 #if _FFR_OCC
394 prv = pcv = NULL;
395 if (ct != NULL && ((clflags & SM_CLFL_EXC) != 0))
396 {
397 if (ratelimit > 0)
398 {
399 if (cnt < ratelimit)
400 prv = &(ct->ct_Count);
401 else
402 exceeded = true;
403 }
404 else if (ratelimit < 0 && ct->ct_Count > 0)
405 --ct->ct_Count;
406 }
407
408 if (chBest != NULL && ((clflags & SM_CLFL_EXC) != 0))
409 {
410 if (conclimit > 0)
411 {
412 if (chBest->ch_oc < conclimit)
413 pcv = &(chBest->ch_oc);
414 else
415 exceeded = true;
416 }
417 else if (conclimit < 0 && chBest->ch_oc > 0)
418 --chBest->ch_oc;
419 }
420 #endif
421
422
423 #if RATECTL_DEBUG
424 logit = true;
425 #endif
426 #if RATECTL_DEBUG || _FFR_OCC
427 #if _FFR_OCC
428 if (!exceeded)
429 {
430 if (prv != NULL)
431 ++*prv, ++cnt;
432 if (pcv != NULL)
433 ++*pcv;
434 }
435 logit = exceeded || LogLevel > 11;
436 #endif
437 if (logit)
438 sm_syslog(LOG_DEBUG, e != NULL ? e->e_id : NOQID,
439 "conn_limits: addr=%s, flags=0x%x, rate=%d/%d, conc=%d/%d, exc=%d",
440 saddr->sa.sa_family == AF_INET
441 ? inet_ntoa(saddr->sin.sin_addr) : "???",
442 clflags, cnt, ratelimit,
443 # if _FFR_OCC
444 chBest != NULL ? chBest->ch_oc : -1
445 # else
446 -2
447 # endif
448 , conclimit
449 # if _FFR_OCC
450 , exceeded
451 # else
452 , 0
453 # endif
454 );
455 #endif
456 #if _FFR_OCC
457 if ((clflags & SM_CLFL_EXC) != 0)
458 return exceeded;
459 #endif
460 return cnt;
461 }
462
463 /*
464 ** CLIENT_RATE - Evaluate connection rate per SMTP client
465 **
466 ** Parameters:
467 ** now - current time in secs
468 ** saddr - client address
469 ** clflags - update data / check only
470 **
471 ** Returns:
472 ** connection rate (connections / ConnectionRateWindowSize)
473 **
474 ** Side effects:
475 ** update static global data
476 */
477
478 static int
client_rate(now,saddr,clflags)479 client_rate(now, saddr, clflags)
480 time_t now;
481 SOCKADDR *saddr;
482 int clflags;
483 {
484 rate_init();
485 return conn_limits(NULL, now, saddr, clflags, CHashAry, 0, 0);
486 }
487
488 /*
489 ** TOTAL_RATE - Evaluate global connection rate
490 **
491 ** Parameters:
492 ** now - current time in secs
493 ** update - update data / check only
494 **
495 ** Returns:
496 ** connection rate (connections / ConnectionRateWindowSize)
497 */
498
499 static int
total_rate(now,update)500 total_rate(now, update)
501 time_t now;
502 bool update;
503 {
504 int i;
505 int cnt = 0;
506 CTime_T *ct;
507 unsigned int ticks;
508
509 rate_init();
510 ticks = now / ChtGran;
511
512 /* Let's update data */
513 if (update)
514 {
515 ct = &srv_Times[ticks % CHTSIZE];
516
517 if (ct->ct_Ticks != ticks)
518 {
519 ct->ct_Ticks = ticks;
520 ct->ct_Count = 0;
521 }
522 ++ct->ct_Count;
523 }
524
525 /* Let's count connections on the window */
526 for (i = 0; i < CHTSIZE; ++i)
527 {
528 ct = &srv_Times[i];
529
530 if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
531 cnt += ct->ct_Count;
532 }
533
534 #if RATECTL_DEBUG
535 sm_syslog(LOG_WARNING, NOQID,
536 "total: cnt=%d, CHTSIZE=%d, ChtGran=%d",
537 cnt, CHTSIZE, ChtGran);
538 #endif
539
540 return cnt;
541 }
542
543 #if RATECTL_DEBUG || _FFR_OCC
544 void
dump_ch(fp)545 dump_ch(fp)
546 SM_FILE_T *fp;
547 {
548 int i, j, cnt;
549 unsigned int ticks;
550
551 ticks = time(NULL) / ChtGran;
552 sm_io_fprintf(fp, SM_TIME_DEFAULT, "dump_ch\n");
553 for (i = 0; i < CPMHSIZE; i++)
554 {
555 CHash_T *ch = &CHashAry[i];
556 bool valid;
557
558 valid = false;
559 #if NETINET
560 valid = (ch->ch_Family == AF_INET);
561 if (valid)
562 sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ",
563 inet_ntoa(ch->ch_Addr4));
564 #endif /* NETINET */
565 #if NETINET6
566 if (ch->ch_Family == AF_INET6)
567 {
568 char buf[64], *str;
569
570 valid = true;
571 str = anynet_ntop(&ch->ch_Addr6, buf, sizeof(buf));
572 if (str != NULL)
573 sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ",
574 str);
575 }
576 #endif /* NETINET6 */
577 if (!valid)
578 continue;
579
580 cnt = 0;
581 for (j = 0; j < CHTSIZE; ++j)
582 {
583 CTime_T *cth;
584
585 cth = &ch->ch_Times[j];
586 if (cth->ct_Ticks <= ticks && cth->ct_Ticks >= ticks - CHTSIZE)
587 cnt += cth->ct_Count;
588 }
589
590 sm_io_fprintf(fp, SM_TIME_DEFAULT, "time=%ld cnt=%d ",
591 (long) ch->ch_LTime, cnt);
592 #if _FFR_OCC
593 sm_io_fprintf(fp, SM_TIME_DEFAULT, "oc=%d", ch->ch_oc);
594 #endif
595 sm_io_fprintf(fp, SM_TIME_DEFAULT, "\n");
596 }
597 sm_io_flush(fp, SM_TIME_DEFAULT);
598 }
599
600 #endif /* RATECTL_DEBUG || _FFR_OCC */
601