Cleanup source structure.
[apps/pfixtools.git] / postlicyd / greylist.c
1 /******************************************************************************/
2 /*          pfixtools: a collection of postfix related tools                  */
3 /*          ~~~~~~~~~                                                         */
4 /*  ________________________________________________________________________  */
5 /*                                                                            */
6 /*  Redistribution and use in source and binary forms, with or without        */
7 /*  modification, are permitted provided that the following conditions        */
8 /*  are met:                                                                  */
9 /*                                                                            */
10 /*  1. Redistributions of source code must retain the above copyright         */
11 /*     notice, this list of conditions and the following disclaimer.          */
12 /*  2. Redistributions in binary form must reproduce the above copyright      */
13 /*     notice, this list of conditions and the following disclaimer in the    */
14 /*     documentation and/or other materials provided with the distribution.   */
15 /*  3. The names of its contributors may not be used to endorse or promote    */
16 /*     products derived from this software without specific prior written     */
17 /*     permission.                                                            */
18 /*                                                                            */
19 /*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
20 /*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
21 /*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
22 /*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
23 /*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
24 /*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
25 /*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
26 /*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
27 /*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
28 /*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
29 /*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
30 /******************************************************************************/
31
32 /*
33  * Copyright © 2007 Pierre Habouzit
34  */
35
36 #include <tcbdb.h>
37
38 #include "common.h"
39 #include "greylist.h"
40 #include "str.h"
41
42 struct greylist_cfg greylist_cfg = {
43    .lookup_by_host = false,
44    .delay          = 300,
45    .retry_window   = 2 * 24 * 2600,
46    .client_awl     = 5,
47 };
48 static TCBDB *awl_db, *obj_db;
49
50 struct awl_entry {
51     int32_t count;
52     time_t  last;
53 };
54
55 struct obj_entry {
56     time_t first;
57     time_t last;
58 };
59
60 int greylist_initialize(const char *directory, const char *prefix)
61 {
62     char path[PATH_MAX];
63
64     if (greylist_cfg.client_awl) {
65         snprintf(path, sizeof(path), "%s/%swhitelist.db", directory, prefix);
66         awl_db = tcbdbnew();
67         if (!tcbdbopen(awl_db, path, BDBOWRITER | BDBOCREAT)) {
68             tcbdbdel(awl_db);
69             awl_db = NULL;
70         }
71         return -1;
72     }
73
74     snprintf(path, sizeof(path), "%s/%sgreylist.db", directory, prefix);
75     obj_db = tcbdbnew();
76     if (!tcbdbopen(obj_db, path, BDBOWRITER | BDBOCREAT)) {
77         tcbdbdel(obj_db);
78         obj_db = NULL;
79         if (awl_db) {
80             tcbdbdel(awl_db);
81             awl_db = NULL;
82         }
83         return -1;
84     }
85
86     return 0;
87 }
88
89 static void greylist_shutdown(void)
90 {
91     if (awl_db) {
92         tcbdbsync(awl_db);
93         tcbdbdel(awl_db);
94         awl_db = NULL;
95     }
96     if (obj_db) {
97         tcbdbsync(obj_db);
98         tcbdbdel(obj_db);
99         obj_db = NULL;
100     }
101 }
102 module_exit(greylist_shutdown);
103
104 const char *sender_normalize(const char *sender, char *buf, int len)
105 {
106     const char *at = strchr(sender, '@');
107     int rpos = 0, wpos = 0, userlen;
108
109     if (!at)
110         return sender;
111
112     /* strip extension used for VERP or alike */
113     userlen = ((char *)memchr(sender, '+', at - sender) ?: at) - sender;
114
115     while (rpos < userlen) {
116         int count = 0;
117
118         while (isdigit(sender[rpos + count]) && rpos + count < userlen)
119             count++;
120         if (count && !isalnum(sender[rpos + count])) {
121             /* replace \<\d+\> with '#' */
122             wpos += m_strputc(buf + wpos, len - wpos, '#');
123             rpos += count;
124             count = 0;
125         }
126         while (isalnum(sender[rpos + count]) && rpos + count < userlen)
127             count++;
128         while (!isalnum(sender[rpos + count]) && rpos + count < userlen)
129             count++;
130         wpos += m_strncpy(buf + wpos, len - wpos, sender + rpos, count);
131         rpos += count;
132     }
133
134     wpos += m_strputc(buf + wpos, len - wpos, '#');
135     wpos += m_strcpy(buf + wpos, len - wpos, at + 1);
136     return buf;
137 }
138
139 static const char *
140 c_net(const char *c_addr, const char *c_name, char *cnet, int cnetlen)
141 {
142     char ip2[4], ip3[4];
143     const char *dot, *p;
144
145     if (greylist_cfg.lookup_by_host)
146         return c_addr;
147
148     if (!(dot = strchr(c_addr, '.')))
149         return c_addr;
150     if (!(dot = strchr(dot + 1, '.')))
151         return c_addr;
152
153     p = ++dot;
154     if (!(dot = strchr(dot, '.')) || dot - p > 3)
155         return c_addr;
156     m_strncpy(ip2, sizeof(ip2), p, dot - p);
157
158     p = ++dot;
159     if (!(dot = strchr(dot, '.')) || dot - p > 3)
160         return c_addr;
161     m_strncpy(ip3, sizeof(ip3), p, dot - p);
162
163     /* skip if contains the last two ip numbers in the hostname,
164        we assume it's a pool of dialup of a provider */
165     if (strstr(c_name, ip2) && strstr(c_name, ip3))
166         return c_addr;
167
168     m_strncpy(cnet, cnetlen, c_addr, dot - c_addr);
169     return cnet;
170 }
171
172 bool try_greylist(const char *sender, const char *c_addr,
173                   const char *c_name, const char *rcpt)
174 {
175 #define INCR_AWL                                              \
176     aent.count++;                                             \
177     aent.last = now;                                          \
178     tcbdbput(awl_db, c_addr, c_addrlen, &aent, sizeof(aent));
179
180     char sbuf[BUFSIZ], cnet[64], key[BUFSIZ];
181     const void *res;
182
183     time_t now = time(NULL);
184     struct obj_entry oent = { now, now };
185     struct awl_entry aent = { 0, 0 };
186
187     int len, klen, c_addrlen = strlen(c_addr);
188
189     /* Auto whitelist clients.
190      */
191     if (greylist_cfg.client_awl) {
192         res = tcbdbget3(awl_db, c_addr, c_addrlen, &len);
193         if (res && len == sizeof(aent)) {
194             memcpy(&aent, res, len);
195         }
196
197         /* Whitelist if count is enough.
198          */
199         if (aent.count > greylist_cfg.client_awl) {
200             if (now < aent.last + 3600) {
201                 INCR_AWL
202             }
203
204             /* OK.
205              */
206             return true;
207         }
208     }
209
210     /* Lookup.
211      */
212     klen = snprintf(key, sizeof(key), "%s/%s/%s",
213                     c_net(c_addr, c_name, cnet, sizeof(cnet)),
214                     sender_normalize(sender, sbuf, sizeof(sbuf)), rcpt);
215     klen = MIN(klen, ssizeof(key) - 1);
216
217     res = tcbdbget3(obj_db, key, klen, &len);
218     if (res && len == sizeof(oent)) {
219         memcpy(&oent, res, len);
220     }
221
222     /* Discard stored first-seen if it is the first retrial and
223      * it is beyong the retry window.
224      */
225     if (oent.last - oent.first < greylist_cfg.delay
226         &&  now - oent.first > greylist_cfg.retry_window) {
227         oent.first = now;
228     }
229
230     /* Update.
231      */
232     oent.last = now;
233     tcbdbput(obj_db, key, klen, &oent, sizeof(oent));
234
235     /* Auto whitelist clients:
236      *  algorithm:
237      *    - on successful entry in the greylist db of a triplet:
238      *        - client not whitelisted yet ? -> increase count
239      *                                       -> withelist if count > limit
240      *        - client whitelisted already ? -> update last-seen timestamp.
241      */
242     if (oent.first + greylist_cfg.delay < now) {
243         if (greylist_cfg.client_awl) {
244             INCR_AWL
245         }
246
247         /* OK
248          */
249         return true;
250     }
251
252     /* DUNNO
253      */
254     return false;
255 }