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 */
17 #include "compat.h"
19 #include <sys/types.h>
20 #include <sys/socket.h>
22 #include <arpa/inet.h>
23 #include <errno.h>
24 #include <event.h>
25 #include <inttypes.h>
26 #include <netdb.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <syslog.h>
32 #include <unistd.h>
33 #include <asr.h>
35 #include "opensmtpd.h"
37 struct dnsbl_session;
39 struct dnsbl_query {
40 struct event_asr *event;
41 int running;
42 int blacklist;
43 struct dnsbl_session *session;
44 };
46 struct dnsbl_session {
47 int listed;
48 int set_header;
49 int logged_mark;
50 struct dnsbl_query *query;
51 struct osmtpd_ctx *ctx;
52 };
54 static const char **blacklists = NULL;
55 static const char **printblacklists;
56 static size_t nblacklists = 0;
57 static int markspam = 0;
58 static int verbose = 0;
60 const char *dnsbl_printblacklist(const char *);
61 int dnsbl_connect(struct osmtpd_ctx *, const char *,
62 struct sockaddr_storage *);
63 int dnsbl_begin(struct osmtpd_ctx *, uint32_t);
64 int dnsbl_dataline(struct osmtpd_ctx *, const char *);
65 void dnsbl_resolve(struct asr_result *, void *);
66 void dnsbl_session_query_done(struct dnsbl_session *);
67 void *dnsbl_session_new(struct osmtpd_ctx *);
68 void dnsbl_session_free(struct osmtpd_ctx *, void *);
69 void usage(void);
71 int
72 main(int argc, char *argv[])
73 {
74 int ch;
75 size_t i;
77 while ((ch = getopt(argc, argv, "mv")) != -1) {
78 switch (ch) {
79 case 'm':
80 markspam = 1;
81 break;
82 case 'v':
83 verbose = 1;
84 break;
85 default:
86 usage();
87 }
88 }
90 if (pledge("stdio dns", NULL) == -1)
91 osmtpd_err(1, "pledge");
93 if ((nblacklists = argc - optind) == 0)
94 osmtpd_errx(1, "No blacklist specified");
96 blacklists = calloc(nblacklists, sizeof(*blacklists));
97 printblacklists = calloc(nblacklists, sizeof(*printblacklists));
98 if (printblacklists == NULL || blacklists == NULL)
99 osmtpd_err(1, "malloc");
100 for (i = 0; i < nblacklists; i++) {
101 blacklists[i] = argv[optind + i];
102 printblacklists[i] = dnsbl_printblacklist(argv[optind + i]);
105 osmtpd_register_filter_connect(dnsbl_connect);
106 osmtpd_local_session(dnsbl_session_new, dnsbl_session_free);
107 if (markspam) {
108 osmtpd_register_report_begin(1, dnsbl_begin);
109 osmtpd_register_filter_dataline(dnsbl_dataline);
111 osmtpd_run();
113 return 0;
116 const char *
117 dnsbl_printblacklist(const char *blacklist)
119 /* All of abusix is paid and has a key in the first spot */
120 if (strcasestr(blacklist, ".mail.abusix.zone") != NULL)
121 return strchr(blacklist, '.') + 1;
122 /* XXX assume dq.spamhaus.net is paid and has a key in the first spot */
123 if (strcasestr(blacklist, ".dq.spamhaus.net") != NULL)
124 return strchr(blacklist, '.') + 1;
125 return blacklist;
128 int
129 dnsbl_connect(struct osmtpd_ctx *ctx, const char *hostname,
130 struct sockaddr_storage *ss)
132 struct dnsbl_session *session = ctx->local_session;
133 struct asr_query *aq;
134 char query[255];
135 u_char *addr;
136 size_t i;
138 if (ss->ss_family == AF_INET)
139 addr = (u_char *)(&(((struct sockaddr_in *)ss)->sin_addr));
140 else
141 addr = (u_char *)(&(((struct sockaddr_in6 *)ss)->sin6_addr));
142 for (i = 0; i < nblacklists; i++) {
143 if (ss->ss_family == AF_INET) {
144 if (snprintf(query, sizeof(query), "%u.%u.%u.%u.%s",
145 addr[3], addr[2], addr[1], addr[0],
146 blacklists[i]) >= (int) sizeof(query))
147 osmtpd_errx(1,
148 "Can't create query, domain too long");
149 } else if (ss->ss_family == AF_INET6) {
150 if (snprintf(query, sizeof(query), "%hhx.%hhx.%hhx.%hhx"
151 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
152 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
153 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%s",
154 (u_char) (addr[15] & 0xf), (u_char) (addr[15] >> 4),
155 (u_char) (addr[14] & 0xf), (u_char) (addr[14] >> 4),
156 (u_char) (addr[13] & 0xf), (u_char) (addr[13] >> 4),
157 (u_char) (addr[12] & 0xf), (u_char) (addr[12] >> 4),
158 (u_char) (addr[11] & 0xf), (u_char) (addr[11] >> 4),
159 (u_char) (addr[10] & 0xf), (u_char) (addr[10] >> 4),
160 (u_char) (addr[9] & 0xf), (u_char) (addr[9] >> 4),
161 (u_char) (addr[8] & 0xf), (u_char) (addr[8] >> 4),
162 (u_char) (addr[7] & 0xf), (u_char) (addr[8] >> 4),
163 (u_char) (addr[6] & 0xf), (u_char) (addr[7] >> 4),
164 (u_char) (addr[5] & 0xf), (u_char) (addr[5] >> 4),
165 (u_char) (addr[4] & 0xf), (u_char) (addr[4] >> 4),
166 (u_char) (addr[3] & 0xf), (u_char) (addr[3] >> 4),
167 (u_char) (addr[2] & 0xf), (u_char) (addr[2] >> 4),
168 (u_char) (addr[1] & 0xf), (u_char) (addr[1] >> 4),
169 (u_char) (addr[0] & 0xf), (u_char) (addr[0] >> 4),
170 blacklists[i]) >= (int) sizeof(query))
171 osmtpd_errx(1,
172 "Can't create query, domain too long");
173 } else
174 osmtpd_errx(1, "Invalid address family received");
176 aq = gethostbyname_async(query, NULL);
177 session->query[i].event = event_asr_run(aq, dnsbl_resolve,
178 &(session->query[i]));
179 session->query[i].blacklist = i;
180 session->query[i].session = session;
181 session->query[i].running = 1;
184 return 0;
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 size_t i;
194 query->running = 0;
195 query->event = NULL;
196 if (result->ar_hostent != NULL) {
197 if (!markspam) {
198 osmtpd_filter_disconnect(session->ctx, "Listed at %s",
199 printblacklists[query->blacklist]);
200 fprintf(stderr, "%016"PRIx64" listed at %s: rejected\n",
201 session->ctx->reqid,
202 printblacklists[query->blacklist]);
203 } else {
204 session->listed = query->blacklist;
205 osmtpd_filter_proceed(session->ctx);
206 /* Delay logging until we have a message */
208 dnsbl_session_query_done(session);
209 return;
211 if (result->ar_h_errno != HOST_NOT_FOUND) {
212 osmtpd_filter_disconnect(session->ctx, "DNS error on %s",
213 printblacklists[query->blacklist]);
214 dnsbl_session_query_done(session);
215 return;
218 for (i = 0; i < nblacklists; i++) {
219 if (session->query[i].running)
220 return;
222 osmtpd_filter_proceed(session->ctx);
223 if (verbose)
224 fprintf(stderr, "%016"PRIx64" not listed\n",
225 session->ctx->reqid);
228 int
229 dnsbl_begin(struct osmtpd_ctx *ctx, uint32_t msgid)
231 struct dnsbl_session *session = ctx->local_session;
233 if (session->listed != -1) {
234 if (!session->logged_mark) {
235 fprintf(stderr, "%016"PRIx64" listed at %s: Marking as "
236 "spam\n", ctx->reqid,
237 printblacklists[session->listed]);
238 session->logged_mark = 1;
240 session->set_header = 1;
243 return 0;
246 int
247 dnsbl_dataline(struct osmtpd_ctx *ctx, const char *line)
249 struct dnsbl_session *session = ctx->local_session;
251 if (session->set_header) {
252 osmtpd_filter_dataline(ctx, "X-Spam: yes");
253 osmtpd_filter_dataline(ctx, "X-Spam-DNSBL: Listed at %s",
254 printblacklists[session->listed]);
255 session->set_header = 0;
258 osmtpd_filter_dataline(ctx, "%s", line);
260 return 0;
263 void
264 dnsbl_session_query_done(struct dnsbl_session *session)
266 size_t i;
268 for (i = 0; i < nblacklists; i++) {
269 if (session->query[i].running) {
270 event_asr_abort(session->query[i].event);
271 session->query[i].running = 0;
276 void *
277 dnsbl_session_new(struct osmtpd_ctx *ctx)
279 struct dnsbl_session *session;
281 if ((session = calloc(1, sizeof(*session))) == NULL)
282 osmtpd_err(1, "malloc");
283 if ((session->query = calloc(nblacklists, sizeof(*(session->query))))
284 == NULL)
285 osmtpd_err(1, "malloc");
286 session->listed = -1;
287 session->set_header = 0;
288 session->logged_mark = 0;
289 session->ctx = ctx;
291 return session;
294 void
295 dnsbl_session_free(struct osmtpd_ctx *ctx, void *data)
297 struct dnsbl_session *session = data;
299 dnsbl_session_query_done(session);
300 free(session->query);
301 free(session);
304 __dead void
305 usage(void)
307 fprintf(stderr, "usage: filter-dnsbl [-m] blacklist [...]\n");
308 exit(1);