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 <inttypes.h>
24 #include <netdb.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <syslog.h>
29 #include <unistd.h>
30 #include <asr.h>
32 #include "log.h"
33 #include "smtp_proc.h"
35 struct dnsbl_session;
37 struct dnsbl_query {
38 struct asr_query *query;
39 struct event_asr *event;
40 struct event timeout;
41 int resolved;
42 int blacklist;
43 struct dnsbl_session *session;
44 };
46 struct dnsbl_session {
47 uint64_t reqid;
48 uint64_t token;
49 char addr[INET6_ADDRSTRLEN];
50 int listed;
51 int set_header;
52 int logged_mark;
53 struct dnsbl_query *query;
54 RB_ENTRY(dnsbl_session) entry;
55 };
57 RB_HEAD(dnsbl_sessions, dnsbl_session) dnsbl_sessions = RB_INITIALIZER(NULL);
58 RB_PROTOTYPE(dnsbl_sessions, dnsbl_session, entry, dnsbl_session_cmp);
60 static char **blacklists = NULL;
61 static size_t nblacklists = 0;
62 static int markspam = 0;
64 void usage(void);
65 void dnsbl_connect(char *, int, struct timespec *, char *, char *, uint64_t,
66 uint64_t, char *, struct inx_addr *);
67 void dnsbl_data(char *, int, struct timespec *, char *, char *, uint64_t,
68 uint64_t);
69 void dnsbl_dataline(char *, int, struct timespec *, char *, char *, uint64_t,
70 uint64_t, char *);
71 void dnsbl_disconnect(char *, int, struct timespec *, char *, char *, uint64_t);
72 void dnsbl_resolve(struct asr_result *, void *);
73 void dnsbl_timeout(int, short, void *);
74 void dnsbl_session_query_done(struct dnsbl_session *);
75 void dnsbl_session_free(struct dnsbl_session *);
76 int dnsbl_session_cmp(struct dnsbl_session *, struct dnsbl_session *);
78 int
79 main(int argc, char *argv[])
80 {
81 int ch;
82 int i;
83 int debug = 0;
85 while ((ch = getopt(argc, argv, "dm")) != -1) {
86 switch (ch) {
87 case 'd':
88 debug = 1;
89 break;
90 case 'm':
91 markspam = 1;
92 break;
93 default:
94 usage();
95 }
96 }
98 log_init(debug, LOG_MAIL);
99 if (pledge("stdio dns", NULL) == -1)
100 fatal("pledge");
102 if ((nblacklists = argc - optind) == 0)
103 fatalx("No blacklist specified");
105 if ((blacklists = calloc(nblacklists, sizeof(*blacklists))) == NULL)
106 fatal(NULL);
107 for (i = 0; i < nblacklists; i++)
108 blacklists[i] = argv[optind + i];
110 smtp_register_filter_connect(dnsbl_connect);
111 if (markspam) {
112 smtp_register_filter_data(dnsbl_data);
113 smtp_register_filter_dataline(dnsbl_dataline);
115 smtp_in_register_report_disconnect(dnsbl_disconnect);
116 smtp_run(debug);
118 return 0;
121 void
122 dnsbl_connect(char *type, int version, struct timespec *tm, char *direction,
123 char *phase, uint64_t reqid, uint64_t token, char *hostname,
124 struct inx_addr *xaddr)
126 struct dnsbl_session *session;
127 struct timeval timeout = {1, 0};
128 char query[255];
129 u_char *addr;
130 int i, try;
132 if ((session = calloc(1, sizeof(*session))) == NULL)
133 fatal(NULL);
134 if ((session->query = calloc(nblacklists, sizeof(*(session->query))))
135 == NULL)
136 fatal(NULL);
137 session->reqid = reqid;
138 session->token = token;
139 session->listed = -1;
140 session->set_header = 0;
141 session->logged_mark = 0;
142 if (inet_ntop(xaddr->af, xaddr->af == AF_INET ?
143 (void *)&(xaddr->addr) : (void *)&(xaddr->addr6), session->addr,
144 sizeof(session->addr)) == NULL)
145 fatal("inet_ntop");
147 RB_INSERT(dnsbl_sessions, &dnsbl_sessions, session);
149 if (xaddr->af == AF_INET)
150 addr = (u_char *)&(xaddr->addr);
151 else
152 addr = (u_char *)&(xaddr->addr6);
153 for (i = 0; i < nblacklists; i++) {
154 if (xaddr->af == AF_INET) {
155 if (snprintf(query, sizeof(query), "%u.%u.%u.%u.%s",
156 addr[3], addr[2], addr[1], addr[0],
157 blacklists[i]) >= sizeof(query))
158 fatalx("Can't create query, domain too long");
159 } else if (xaddr->af == AF_INET6) {
160 if (snprintf(query, sizeof(query), "%hhx.%hhx.%hhx.%hhx"
161 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
162 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
163 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%s",
164 (u_char) (addr[15] & 0xf), (u_char) (addr[15] >> 4),
165 (u_char) (addr[14] & 0xf), (u_char) (addr[14] >> 4),
166 (u_char) (addr[13] & 0xf), (u_char) (addr[13] >> 4),
167 (u_char) (addr[12] & 0xf), (u_char) (addr[12] >> 4),
168 (u_char) (addr[11] & 0xf), (u_char) (addr[11] >> 4),
169 (u_char) (addr[10] & 0xf), (u_char) (addr[10] >> 4),
170 (u_char) (addr[9] & 0xf), (u_char) (addr[9] >> 4),
171 (u_char) (addr[8] & 0xf), (u_char) (addr[8] >> 4),
172 (u_char) (addr[7] & 0xf), (u_char) (addr[8] >> 4),
173 (u_char) (addr[6] & 0xf), (u_char) (addr[7] >> 4),
174 (u_char) (addr[5] & 0xf), (u_char) (addr[5] >> 4),
175 (u_char) (addr[4] & 0xf), (u_char) (addr[4] >> 4),
176 (u_char) (addr[3] & 0xf), (u_char) (addr[3] >> 4),
177 (u_char) (addr[2] & 0xf), (u_char) (addr[2] >> 4),
178 (u_char) (addr[1] & 0xf), (u_char) (addr[1] >> 4),
179 (u_char) (addr[0] & 0xf), (u_char) (addr[0] >> 4),
180 blacklists[i]) >= sizeof(query))
181 fatalx( "Can't create query, domain too long");
182 } else
183 fatalx("Invalid address family received");
185 session->query[i].query = gethostbyname_async(query, NULL);
186 session->query[i].event = event_asr_run(session->query[i].query,
187 dnsbl_resolve, &(session->query[i]));
188 session->query[i].blacklist = i;
189 session->query[i].session = session;
190 evtimer_set(&(session->query[i].timeout), dnsbl_timeout,
191 &(session->query[i]));
192 evtimer_add(&(session->query[i].timeout), &timeout);
196 void
197 dnsbl_resolve(struct asr_result *result, void *arg)
199 struct dnsbl_query *query = arg;
200 struct dnsbl_session *session = query->session;
201 int i, blacklist;
203 query->resolved = 1;
204 query->event = NULL;
205 query->query = NULL;
206 evtimer_del(&(query->timeout));
207 if (result->ar_hostent != NULL) {
208 if (!markspam) {
209 smtp_filter_disconnect(session->reqid, session->token,
210 "Listed at %s", blacklists[query->blacklist]);
211 log_info("%016"PRIx64" listed at %s: rejected",
212 session->reqid, blacklists[query->blacklist]);
213 dnsbl_session_free(session);
214 } else {
215 dnsbl_session_query_done(session);
216 session->listed = query->blacklist;
217 smtp_filter_proceed(session->reqid, session->token);
218 /* Delay logging until we have a message */
220 return;
222 if (result->ar_h_errno != HOST_NOT_FOUND) {
223 smtp_filter_disconnect(session->reqid, session->token,
224 "DNS error on %s", blacklists[query->blacklist]);
225 dnsbl_session_free(session);
226 return;
229 for (i = 0; i < nblacklists; i++) {
230 if (!session->query[i].resolved)
231 return;
233 smtp_filter_proceed(session->reqid, session->token);
234 log_info("%016"PRIx64" not listed", session->reqid);
237 void
238 dnsbl_timeout(int fd, short event, void *arg)
240 struct dnsbl_query *query = arg;
241 struct dnsbl_session *session = query->session;
243 smtp_filter_disconnect(session->reqid, session->token,
244 "DNS timeout on %s", blacklists[query->blacklist]);
245 dnsbl_session_free(session);
248 void
249 dnsbl_disconnect(char *type, int version, struct timespec *tm, char *direction,
250 char *phase, uint64_t reqid)
252 struct dnsbl_session *session, search;
254 search.reqid = reqid;
255 if ((session = RB_FIND(dnsbl_sessions, &dnsbl_sessions, &search)) != NULL)
256 dnsbl_session_free(session);
259 void
260 dnsbl_data(char *type, int version, struct timespec *tm, char *direction,
261 char *phase, uint64_t reqid, uint64_t token)
263 struct dnsbl_session *session, search;
265 search.reqid = reqid;
266 session = RB_FIND(dnsbl_sessions, &dnsbl_sessions, &search);
268 if (session->listed != -1) {
269 if (!session->logged_mark) {
270 log_info("%016"PRIx64" listed at %s: Marking as spam",
271 session->reqid, blacklists[session->listed]);
272 session->logged_mark = 1;
274 session->set_header = 1;
276 smtp_filter_proceed(reqid, token);
279 void
280 dnsbl_dataline(char *type, int version, struct timespec *tm, char *direction,
281 char *phase, uint64_t reqid, uint64_t token, char *line)
283 struct dnsbl_session *session, search;
285 search.reqid = reqid;
286 session = RB_FIND(dnsbl_sessions, &dnsbl_sessions, &search);
288 if (session->set_header) {
289 smtp_filter_dataline(reqid, token, "X-Spam: yes");
290 smtp_filter_dataline(reqid, token, "X-Spam-DNSBL: Listed at %s",
291 blacklists[session->listed]);
292 session->set_header = 0;
295 smtp_filter_dataline(reqid, token, "%s", line);
298 void
299 dnsbl_session_query_done(struct dnsbl_session *session)
301 int i;
303 for (i = 0; i < nblacklists; i++) {
304 if (!session->query[i].resolved) {
305 event_asr_abort(session->query[i].event);
306 evtimer_del(&(session->query[i].timeout));
307 session->query[i].resolved = 1;
312 void
313 dnsbl_session_free(struct dnsbl_session *session)
315 RB_REMOVE(dnsbl_sessions, &dnsbl_sessions, session);
316 dnsbl_session_query_done(session);
317 free(session->query);
318 free(session);
321 int
322 dnsbl_session_cmp(struct dnsbl_session *s1, struct dnsbl_session *s2)
324 return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid);
327 __dead void
328 usage(void)
330 fprintf(stderr, "usage: %s [-m] blacklist [...]\n",
331 getprogname());
332 exit(1);
335 RB_GENERATE(dnsbl_sessions, dnsbl_session, entry, dnsbl_session_cmp);