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 * 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 <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 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 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 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 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 #if RATECTL_DEBUG 423 logit = true; 424 #endif 425 #if RATECTL_DEBUG || _FFR_OCC 426 # if _FFR_OCC 427 if (!exceeded) 428 { 429 if (prv != NULL) 430 ++*prv, ++cnt; 431 if (pcv != NULL) 432 ++*pcv; 433 } 434 logit = exceeded || LogLevel > 11; 435 # endif 436 if (logit) 437 sm_syslog(LOG_DEBUG, e != NULL ? e->e_id : NOQID, 438 "conn_limits: addr=%s, flags=0x%x, rate=%d/%d, conc=%d/%d, exc=%d", 439 saddr->sa.sa_family == AF_INET 440 ? inet_ntoa(saddr->sin.sin_addr) : "???", 441 clflags, cnt, ratelimit, 442 # if _FFR_OCC 443 chBest != NULL ? chBest->ch_oc : -1 444 # else 445 -2 446 # endif 447 , conclimit 448 # if _FFR_OCC 449 , exceeded 450 # else 451 , 0 452 # endif 453 ); 454 #endif 455 #if _FFR_OCC 456 if ((clflags & SM_CLFL_EXC) != 0) 457 return exceeded; 458 #endif 459 return cnt; 460 } 461 462 /* 463 ** CLIENT_RATE - Evaluate connection rate per SMTP client 464 ** 465 ** Parameters: 466 ** now - current time in secs 467 ** saddr - client address 468 ** clflags - update data / check only 469 ** 470 ** Returns: 471 ** connection rate (connections / ConnectionRateWindowSize) 472 ** 473 ** Side effects: 474 ** update static global data 475 */ 476 477 static int 478 client_rate(now, saddr, clflags) 479 time_t now; 480 SOCKADDR *saddr; 481 int clflags; 482 { 483 rate_init(); 484 return conn_limits(NULL, now, saddr, clflags, CHashAry, 0, 0); 485 } 486 487 /* 488 ** TOTAL_RATE - Evaluate global connection rate 489 ** 490 ** Parameters: 491 ** now - current time in secs 492 ** update - update data / check only 493 ** 494 ** Returns: 495 ** connection rate (connections / ConnectionRateWindowSize) 496 */ 497 498 static int 499 total_rate(now, update) 500 time_t now; 501 bool update; 502 { 503 int i; 504 int cnt = 0; 505 CTime_T *ct; 506 unsigned int ticks; 507 508 rate_init(); 509 ticks = now / ChtGran; 510 511 /* Let's update data */ 512 if (update) 513 { 514 ct = &srv_Times[ticks % CHTSIZE]; 515 516 if (ct->ct_Ticks != ticks) 517 { 518 ct->ct_Ticks = ticks; 519 ct->ct_Count = 0; 520 } 521 ++ct->ct_Count; 522 } 523 524 /* Let's count connections on the window */ 525 for (i = 0; i < CHTSIZE; ++i) 526 { 527 ct = &srv_Times[i]; 528 529 if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE) 530 cnt += ct->ct_Count; 531 } 532 533 #if RATECTL_DEBUG 534 sm_syslog(LOG_WARNING, NOQID, 535 "total: cnt=%d, CHTSIZE=%d, ChtGran=%d", 536 cnt, CHTSIZE, ChtGran); 537 #endif 538 539 return cnt; 540 } 541 542 #if RATECTL_DEBUG || _FFR_OCC 543 void 544 dump_ch(fp) 545 SM_FILE_T *fp; 546 { 547 int i, j, cnt; 548 unsigned int ticks; 549 550 ticks = time(NULL) / ChtGran; 551 sm_io_fprintf(fp, SM_TIME_DEFAULT, "dump_ch\n"); 552 for (i = 0; i < CPMHSIZE; i++) 553 { 554 CHash_T *ch = &CHashAry[i]; 555 bool valid; 556 557 valid = false; 558 # if NETINET 559 valid = (ch->ch_Family == AF_INET); 560 if (valid) 561 sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ", 562 inet_ntoa(ch->ch_Addr4)); 563 # endif /* NETINET */ 564 # if NETINET6 565 if (ch->ch_Family == AF_INET6) 566 { 567 char buf[64], *str; 568 569 valid = true; 570 str = anynet_ntop(&ch->ch_Addr6, buf, sizeof(buf)); 571 if (str != NULL) 572 sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ", 573 str); 574 } 575 # endif /* NETINET6 */ 576 if (!valid) 577 continue; 578 579 cnt = 0; 580 for (j = 0; j < CHTSIZE; ++j) 581 { 582 CTime_T *cth; 583 584 cth = &ch->ch_Times[j]; 585 if (cth->ct_Ticks <= ticks && cth->ct_Ticks >= ticks - CHTSIZE) 586 cnt += cth->ct_Count; 587 } 588 589 sm_io_fprintf(fp, SM_TIME_DEFAULT, "time=%ld cnt=%d ", 590 (long) ch->ch_LTime, cnt); 591 # if _FFR_OCC 592 sm_io_fprintf(fp, SM_TIME_DEFAULT, "oc=%d", ch->ch_oc); 593 # endif 594 sm_io_fprintf(fp, SM_TIME_DEFAULT, "\n"); 595 } 596 sm_io_flush(fp, SM_TIME_DEFAULT); 597 } 598 599 #endif /* RATECTL_DEBUG || _FFR_OCC */ 600