1 /*
2  * Copyright (c) 2007-2012, Vsevolod Stakhov
3  * All rights reserved.
4 
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * Redistributions of source code must retain the above copyright notice, this
8  * list of conditions and the following disclaimer. Redistributions in binary form
9  * must reproduce the above copyright notice, this list of conditions and the
10  * following disclaimer in the documentation and/or other materials provided with
11  * the distribution. Neither the name of the author nor the names of its
12  * contributors may be used to endorse or promote products derived from this
13  * software without specific prior written permission.
14 
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 
29 #include "radix.h"
30 #include "cfg_file.h"
31 #include "rmilter.h"
32 #include "cache.h"
33 #include "upstream.h"
34 #include "ratelimit.h"
35 
36 #define EXPIRE_TIME 86400
37 
38 struct ratelimit_bucket_s
39 {
40 	double tm;
41 	double count;
42 };
43 
44 enum keytype
45 {
46 	TO = 0, TO_IP, TO_IP_FROM, BOUNCE_TO, BOUNCE_TO_IP
47 };
48 
49 /* Convert string to lowercase */
convert_to_lowercase(char * str,unsigned int size)50 static void convert_to_lowercase(char *str, unsigned int size)
51 {
52 	while (size--) {
53 		*str = tolower (*str);
54 		str++;
55 	}
56 }
57 
58 /* Return lenth of user part */
extract_user_part(const char * str)59 static size_t extract_user_part(const char *str)
60 {
61 	size_t user_part_len;
62 	const char *p;
63 
64 	/* Extract user part from rcpt */
65 	p = str;
66 	user_part_len = 0;
67 	while (*p++) {
68 		if (*p == '@') {
69 			break;
70 		}
71 		user_part_len++;
72 	}
73 
74 	return user_part_len;
75 }
76 
77 static int
is_whitelisted(struct rmilter_inet_address * addr,const char * rcpt,struct config_file * cfg)78 is_whitelisted (struct rmilter_inet_address *addr, const char *rcpt,
79 		struct config_file *cfg)
80 {
81 	if (is_whitelisted_rcpt (&cfg->wlist_rcpt_limit, rcpt)
82 			|| is_whitelisted_rcpt (&cfg->wlist_rcpt_global, rcpt)) {
83 		return 1;
84 	}
85 
86 	if (radix_find_rmilter_addr (cfg->limit_whitelist_tree, addr)
87 			!= RADIX_NO_VALUE) {
88 		return 2;
89 	}
90 
91 	return 0;
92 }
93 
is_bounce(char * from,struct config_file * cfg)94 static int is_bounce(char *from, struct config_file *cfg)
95 {
96 	size_t user_part_len;
97 	struct addr_list_entry *cur_addr = NULL;
98 
99 	user_part_len = extract_user_part (from);
100 	HASH_FIND (hh, cfg->bounce_addrs, from, user_part_len, cur_addr);
101 
102 	if (cur_addr) {
103 		return 1;
104 	}
105 
106 	return 0;
107 }
108 
make_key(char * buf,size_t buflen,enum keytype type,struct mlfi_priv * priv,const char * rcpt)109 static int make_key(char *buf, size_t buflen, enum keytype type,
110 		struct mlfi_priv *priv, const char *rcpt)
111 {
112 	int r = 0;
113 	switch (type) {
114 	case TO:
115 		r = snprintf(buf, buflen, "%s", rcpt);
116 		break;
117 	case TO_IP:
118 		r = snprintf(buf, buflen, "%s:%s", rcpt, priv->priv_ip);
119 		break;
120 	case TO_IP_FROM:
121 		r = snprintf(buf, buflen, "%s:%s:%s", rcpt, priv->priv_ip,
122 				priv->priv_from);
123 		break;
124 	case BOUNCE_TO:
125 		r = snprintf(buf, buflen, "%s:<>", rcpt);
126 		break;
127 	case BOUNCE_TO_IP:
128 		r = snprintf(buf, buflen, "%s:%s:<>", rcpt, priv->priv_ip);
129 		break;
130 	}
131 
132 	if (r >= buflen) {
133 		return 0;
134 	}
135 
136 	convert_to_lowercase (buf, r);
137 
138 	return r;
139 }
140 
check_specific_limit(struct mlfi_priv * priv,struct config_file * cfg,enum keytype type,bucket_t * bucket,double tm,const char * rcpt,int is_update)141 static int check_specific_limit(struct mlfi_priv *priv, struct config_file *cfg,
142 		enum keytype type, bucket_t *bucket, double tm, const char *rcpt,
143 		int is_update)
144 {
145 	struct memcached_server *selected;
146 	struct ratelimit_bucket_s *b;
147 	char key[MAXKEYLEN];
148 	size_t klen, dlen;
149 
150 	if (bucket->burst == 0 || bucket->rate == 0) {
151 		return 1;
152 	}
153 
154 	klen = make_key (key, sizeof(key), type, priv, rcpt);
155 
156 	if (klen == 0) {
157 		msg_err("<%s>; check_specific_limit: got error bad too long key", priv->mlfi_id);
158 		return -1;
159 	}
160 
161 	dlen = sizeof (*b);
162 
163 	if (!rmilter_query_cache (cfg, RMILTER_QUERY_RATELIMIT, key, klen,
164 			(unsigned char **) &b, &dlen, priv)) {
165 		b = calloc (1, sizeof (*b));
166 		dlen = sizeof (*b);
167 
168 		if (b == NULL) {
169 			msg_err("<%s>; check_specific_limit: calloc failed: %s",
170 				priv->mlfi_id, strerror (errno));
171 			return -1;
172 		}
173 	}
174 
175 	msg_debug("<%s>; check_specific_limit: got limit for key: '%s', "
176 			"count: %.1f, time: %.1f", priv->mlfi_id, key, b->count, b->tm);
177 	/* Leak from bucket at specified rate */
178 	if (b->count > 0) {
179 		b->count -= (tm - b->tm) * bucket->rate;
180 	}
181 
182 	b->count += is_update;
183 	b->tm = tm;
184 	if (b->count < 0) {
185 		b->count = 0;
186 	}
187 
188 	if (is_update && b->count == 0) {
189 		/* Delete key if bucket is empty */
190 		rmilter_delete_cache (cfg, RMILTER_QUERY_RATELIMIT, key, klen, priv);
191 	}
192 	else {
193 		/* Update rate limit */
194 		rmilter_set_cache (cfg, RMILTER_QUERY_RATELIMIT, key, klen,
195 				(unsigned char *) b, dlen, EXPIRE_TIME, priv);
196 	}
197 
198 	if (b->count > bucket->burst && !is_update) {
199 		/* Rate limit exceeded */
200 		msg_info(
201 				"<%s>; rate_check: ratelimit exceeded for key: %s, count: %.2f, burst: %u",
202 				priv->mlfi_id, key, b->count, bucket->burst);
203 		free (b);
204 
205 		return 0;
206 	}
207 
208 	free (b);
209 	/* Rate limit not exceeded */
210 	return 1;
211 }
212 
213 int
rate_check(struct mlfi_priv * priv,struct config_file * cfg,const char * rcpt,int is_update)214 rate_check (struct mlfi_priv *priv, struct config_file *cfg,
215 		const char *rcpt, int is_update)
216 {
217 	double t;
218 	struct timeval tm;
219 	int r;
220 
221 	if (!cfg->ratelimit_enable) {
222 		return 1;
223 	}
224 
225 	if (priv->priv_addr.family == AF_INET
226 			&& is_whitelisted (&priv->priv_addr, rcpt, cfg)
227 					!= 0) {
228 		msg_info("<%s>; rate_check: address is whitelisted, skipping checks", priv->mlfi_id);
229 		return 1;
230 	}
231 
232 	tm.tv_sec = priv->conn_tm.tv_sec;
233 	tm.tv_usec = priv->conn_tm.tv_usec;
234 
235 	t = tm.tv_sec + tm.tv_usec / 1000000.;
236 
237 	if (is_bounce (priv->priv_from, cfg) != 0) {
238 		msg_debug(
239 				"<%s>; rate_check: bounce address detected, doing special checks: %s",
240 				priv->mlfi_id, priv->priv_from);
241 		r = check_specific_limit (priv, cfg, BOUNCE_TO, &cfg->limit_bounce_to,
242 				t, rcpt, is_update);
243 		if (r != 1) {
244 			return r;
245 		}
246 		r = check_specific_limit (priv, cfg, BOUNCE_TO_IP,
247 				&cfg->limit_bounce_to_ip, t, rcpt, is_update);
248 		if (r != 1) {
249 			return r;
250 		}
251 	}
252 	/* Check other limits */
253 	r = check_specific_limit (priv, cfg, TO_IP_FROM, &cfg->limit_to_ip_from, t,
254 			rcpt, is_update);
255 	if (r != 1) {
256 		return r;
257 	}
258 	r = check_specific_limit (priv, cfg, TO_IP, &cfg->limit_to_ip, t, rcpt,
259 			is_update);
260 	if (r != 1) {
261 		return r;
262 	}
263 	r = check_specific_limit (priv, cfg, TO, &cfg->limit_to, t, rcpt,
264 			is_update);
265 	if (r != 1) {
266 		return r;
267 	}
268 
269 	return 1;
270 }
271 
272 /*
273  * vi:ts=4
274  */
275