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