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>
48SM_RCSID("@(#)$Id: ratectrl.c,v 8.14 2013-11-22 20:51:56 ca Exp $")
49
50static int client_rate __P((time_t, SOCKADDR *, int));
51static int total_rate __P((time_t, bool));
52static unsigned int gen_hash __P((SOCKADDR *));
53static 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
78void
79connection_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
123static 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
131static int ChtGran = -1;
132static CHash_T CHashAry[CPMHSIZE];
133static 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
152static void
153rate_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
175static unsigned int
176gen_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
258int
259conn_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
478static int
479client_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
499static int
500total_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
544void
545dump_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