Blob


1 /*
2 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 #include <sys/tree.h>
17 #include <sys/types.h>
18 #include <sys/socket.h>
20 #include <arpa/inet.h>
21 #include <errno.h>
22 #include <event.h>
23 #include <netdb.h>
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <syslog.h>
28 #include <unistd.h>
29 #include <asr.h>
31 #include "log.h"
32 #include "smtp_proc.h"
34 struct dnsbl_session;
36 struct dnsbl_query {
37 struct asr_query *query;
38 struct event_asr *event;
39 struct event timeout;
40 int resolved;
41 int blacklist;
42 struct dnsbl_session *session;
43 };
45 struct dnsbl_session {
46 uint64_t reqid;
47 uint64_t token;
48 char addr[INET6_ADDRSTRLEN];
49 int listed;
50 struct dnsbl_query *query;
51 RB_ENTRY(dnsbl_session) entry;
52 };
54 RB_HEAD(dnsbl_sessions, dnsbl_session) dnsbl_sessions = RB_INITIALIZER(NULL);
55 RB_PROTOTYPE(dnsbl_sessions, dnsbl_session, entry, dnsbl_session_cmp);
57 static char **blacklists = NULL;
58 static size_t nblacklists = 0;
59 static int markspam = 0;
61 void usage(void);
62 void dnsbl_connect(char *, int, struct timespec *, char *, char *, uint64_t,
63 uint64_t, char *, struct inx_addr *);
64 void dnsbl_dataline(char *, int, struct timespec *, char *, char *, uint64_t,
65 uint64_t, char *);
66 void dnsbl_disconnect(char *, int, struct timespec *, char *, char *, uint64_t);
67 void dnsbl_resolve(struct asr_result *, void *);
68 void dnsbl_timeout(int, short, void *);
69 void dnsbl_session_query_done(struct dnsbl_session *);
70 void dnsbl_session_free(struct dnsbl_session *);
71 int dnsbl_session_cmp(struct dnsbl_session *, struct dnsbl_session *);
73 int
74 main(int argc, char *argv[])
75 {
76 int ch;
77 int i;
78 int debug = 0;
80 while ((ch = getopt(argc, argv, "dm")) != -1) {
81 switch (ch) {
82 case 'd':
83 debug = 1;
84 break;
85 case 'm':
86 markspam = 1;
87 break;
88 default:
89 usage();
90 }
91 }
93 log_init(debug, LOG_MAIL);
94 if (pledge("stdio dns", NULL) == -1)
95 fatal("pledge");
97 if ((nblacklists = argc - optind) == 0)
98 fatalx("No blacklist specified");
100 if ((blacklists = calloc(nblacklists, sizeof(*blacklists))) == NULL)
101 fatal(NULL);
102 for (i = 0; i < nblacklists; i++)
103 blacklists[i] = argv[optind + i];
105 smtp_register_filter_connect(dnsbl_connect);
106 if (markspam)
107 smtp_register_filter_dataline(dnsbl_dataline);
108 smtp_in_register_report_disconnect(dnsbl_disconnect);
109 smtp_run(debug);
111 return 0;
114 void
115 dnsbl_connect(char *type, int version, struct timespec *tm, char *direction,
116 char *phase, uint64_t reqid, uint64_t token, char *hostname,
117 struct inx_addr *xaddr)
119 struct dnsbl_session *session;
120 struct timeval timeout = {1, 0};
121 char query[255];
122 u_char *addr;
123 int i, try;
125 if ((session = calloc(1, sizeof(*session))) == NULL)
126 fatal(NULL);
127 if ((session->query = calloc(nblacklists, sizeof(*(session->query))))
128 == NULL)
129 fatal(NULL);
130 session->reqid = reqid;
131 session->token = token;
132 session->listed = -1;
133 if (inet_ntop(xaddr->af, xaddr->af == AF_INET ?
134 (void *)&(xaddr->addr) : (void *)&(xaddr->addr6), session->addr,
135 sizeof(session->addr)) == NULL)
136 fatal("inet_ntop");
138 RB_INSERT(dnsbl_sessions, &dnsbl_sessions, session);
140 if (xaddr->af == AF_INET)
141 addr = (u_char *)&(xaddr->addr);
142 else
143 addr = (u_char *)&(xaddr->addr6);
144 for (i = 0; i < nblacklists; i++) {
145 if (xaddr->af == AF_INET) {
146 if (snprintf(query, sizeof(query), "%u.%u.%u.%u.%s",
147 addr[3], addr[2], addr[1], addr[0],
148 blacklists[i]) >= sizeof(query))
149 fatalx("Can't create query, domain too long");
150 } else if (xaddr->af == AF_INET6) {
151 if (snprintf(query, sizeof(query), "%hhx.%hhx.%hhx.%hhx"
152 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
153 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
154 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%s",
155 (u_char) (addr[15] & 0xf), (u_char) (addr[15] >> 4),
156 (u_char) (addr[14] & 0xf), (u_char) (addr[14] >> 4),
157 (u_char) (addr[13] & 0xf), (u_char) (addr[13] >> 4),
158 (u_char) (addr[12] & 0xf), (u_char) (addr[12] >> 4),
159 (u_char) (addr[11] & 0xf), (u_char) (addr[11] >> 4),
160 (u_char) (addr[10] & 0xf), (u_char) (addr[10] >> 4),
161 (u_char) (addr[9] & 0xf), (u_char) (addr[9] >> 4),
162 (u_char) (addr[8] & 0xf), (u_char) (addr[8] >> 4),
163 (u_char) (addr[7] & 0xf), (u_char) (addr[8] >> 4),
164 (u_char) (addr[6] & 0xf), (u_char) (addr[7] >> 4),
165 (u_char) (addr[5] & 0xf), (u_char) (addr[5] >> 4),
166 (u_char) (addr[4] & 0xf), (u_char) (addr[4] >> 4),
167 (u_char) (addr[3] & 0xf), (u_char) (addr[3] >> 4),
168 (u_char) (addr[2] & 0xf), (u_char) (addr[2] >> 4),
169 (u_char) (addr[1] & 0xf), (u_char) (addr[1] >> 4),
170 (u_char) (addr[0] & 0xf), (u_char) (addr[0] >> 4),
171 blacklists[i]) >= sizeof(query))
172 fatalx( "Can't create query, domain too long");
173 } else
174 fatalx("Invalid address family received");
176 session->query[i].query = gethostbyname_async(query, NULL);
177 session->query[i].event = event_asr_run(session->query[i].query,
178 dnsbl_resolve, &(session->query[i]));
179 session->query[i].blacklist = i;
180 session->query[i].session = session;
181 evtimer_set(&(session->query[i].timeout), dnsbl_timeout,
182 &(session->query[i]));
183 evtimer_add(&(session->query[i].timeout), &timeout);
187 void
188 dnsbl_resolve(struct asr_result *result, void *arg)
190 struct dnsbl_query *query = arg;
191 struct dnsbl_session *session = query->session;
192 int i, blacklist;
194 query->resolved = 1;
195 query->event = NULL;
196 query->query = NULL;
197 evtimer_del(&(query->timeout));
198 if (result->ar_hostent != NULL) {
199 if (!markspam) {
200 smtp_filter_disconnect(session->reqid, session->token,
201 "Listed at %s", blacklists[query->blacklist]);
202 log_info("Rejected %s: listed at %s", session->addr,
203 blacklists[query->blacklist]);
204 dnsbl_session_free(session);
205 } else {
206 dnsbl_session_query_done(session);
207 session->listed = query->blacklist;
208 smtp_filter_proceed(session->reqid, session->token);
209 log_info("Marked as spam %s: listed at %s", session->addr,
210 blacklists[query->blacklist]);
212 return;
214 if (result->ar_h_errno != HOST_NOT_FOUND) {
215 smtp_filter_disconnect(session->reqid, session->token,
216 "DNS error on %s", blacklists[query->blacklist]);
217 dnsbl_session_free(session);
218 return;
221 for (i = 0; i < nblacklists; i++) {
222 if (!session->query[i].resolved)
223 return;
225 smtp_filter_proceed(session->reqid, session->token);
226 log_info("%s not listed", session->addr);
227 if (!markspam)
228 dnsbl_session_free(session);
231 void
232 dnsbl_timeout(int fd, short event, void *arg)
234 struct dnsbl_query *query = arg;
235 struct dnsbl_session *session = query->session;
237 smtp_filter_disconnect(session->reqid, session->token,
238 "DNS timeout on %s", blacklists[query->blacklist]);
239 dnsbl_session_free(session);
242 void
243 dnsbl_disconnect(char *type, int version, struct timespec *tm, char *direction,
244 char *phase, uint64_t reqid)
246 struct dnsbl_session *session, search;
248 search.reqid = reqid;
249 if ((session = RB_FIND(dnsbl_sessions, &dnsbl_sessions, &search)) != NULL)
250 dnsbl_session_free(session);
253 void
254 dnsbl_dataline(char *type, int version, struct timespec *tm, char *direction,
255 char *phase, uint64_t reqid, uint64_t token, char *line)
257 struct dnsbl_session *session, search;
259 search.reqid = reqid;
260 session = RB_FIND(dnsbl_sessions, &dnsbl_sessions, &search);
262 if (session->listed != -1) {
263 smtp_filter_dataline(reqid, token, "X-Spam: yes");
264 smtp_filter_dataline(reqid, token, "X-Spam-DNSBL: Listed at %s",
265 blacklists[session->listed]);
266 session->listed = -1;
268 smtp_filter_dataline(reqid, token, "%s", line);
271 void
272 dnsbl_session_query_done(struct dnsbl_session *session)
274 int i;
276 for (i = 0; i < nblacklists; i++) {
277 if (!session->query[i].resolved) {
278 event_asr_abort(session->query[i].event);
279 evtimer_del(&(session->query[i].timeout));
284 void
285 dnsbl_session_free(struct dnsbl_session *session)
287 RB_REMOVE(dnsbl_sessions, &dnsbl_sessions, session);
288 dnsbl_session_query_done(session);
289 free(session->query);
290 free(session);
293 int
294 dnsbl_session_cmp(struct dnsbl_session *s1, struct dnsbl_session *s2)
296 return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid);
299 __dead void
300 usage(void)
302 fprintf(stderr, "usage: %s [-m] blacklist [...]\n",
303 getprogname());
304 exit(1);
307 RB_GENERATE(dnsbl_sessions, dnsbl_session, entry, dnsbl_session_cmp);