2 15cacd92 2019-03-29 martijn * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
4 15cacd92 2019-03-29 martijn * Permission to use, copy, modify, and distribute this software for any
5 15cacd92 2019-03-29 martijn * purpose with or without fee is hereby granted, provided that the above
6 15cacd92 2019-03-29 martijn * copyright notice and this permission notice appear in all copies.
8 15cacd92 2019-03-29 martijn * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 15cacd92 2019-03-29 martijn * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 15cacd92 2019-03-29 martijn * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 15cacd92 2019-03-29 martijn * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 15cacd92 2019-03-29 martijn * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 15cacd92 2019-03-29 martijn * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 15cacd92 2019-03-29 martijn * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 aeda4d4e 2019-03-28 martijn #include <sys/types.h>
17 a248a3c1 2019-03-27 martijn #include <sys/socket.h>
19 2e3e5d5f 2019-03-29 martijn #include <arpa/inet.h>
20 a248a3c1 2019-03-27 martijn #include <errno.h>
21 aeda4d4e 2019-03-28 martijn #include <event.h>
22 a287838f 2019-03-31 martijn #include <inttypes.h>
23 a248a3c1 2019-03-27 martijn #include <netdb.h>
24 a248a3c1 2019-03-27 martijn #include <stdlib.h>
25 d4e702ee 2019-08-22 martijn #include <stdarg.h>
26 a248a3c1 2019-03-27 martijn #include <stdio.h>
27 a248a3c1 2019-03-27 martijn #include <string.h>
28 b633fa4c 2019-03-29 martijn #include <syslog.h>
29 a248a3c1 2019-03-27 martijn #include <unistd.h>
30 aeda4d4e 2019-03-28 martijn #include <asr.h>
32 5e62db7b 2019-08-22 martijn #include "opensmtpd.h"
34 aeda4d4e 2019-03-28 martijn struct dnsbl_session;
36 aeda4d4e 2019-03-28 martijn struct dnsbl_query {
37 aeda4d4e 2019-03-28 martijn struct event_asr *event;
38 a7f2d00f 2019-10-22 martijn int running;
39 aeda4d4e 2019-03-28 martijn int blacklist;
40 aeda4d4e 2019-03-28 martijn struct dnsbl_session *session;
43 aeda4d4e 2019-03-28 martijn struct dnsbl_session {
44 5468729e 2019-03-29 martijn int listed;
45 a287838f 2019-03-31 martijn int set_header;
46 a287838f 2019-03-31 martijn int logged_mark;
47 aeda4d4e 2019-03-28 martijn struct dnsbl_query *query;
48 5e62db7b 2019-08-22 martijn struct osmtpd_ctx *ctx;
51 a248a3c1 2019-03-27 martijn static char **blacklists = NULL;
52 a248a3c1 2019-03-27 martijn static size_t nblacklists = 0;
53 5468729e 2019-03-29 martijn static int markspam = 0;
54 3b767c06 2019-08-22 martijn static int verbose = 0;
56 a248a3c1 2019-03-27 martijn void usage(void);
57 5e62db7b 2019-08-22 martijn void dnsbl_connect(struct osmtpd_ctx *, const char *,
58 5e62db7b 2019-08-22 martijn struct sockaddr_storage *);
59 5e62db7b 2019-08-22 martijn void dnsbl_begin(struct osmtpd_ctx *, uint32_t);
60 5e62db7b 2019-08-22 martijn void dnsbl_dataline(struct osmtpd_ctx *, const char *);
61 aeda4d4e 2019-03-28 martijn void dnsbl_resolve(struct asr_result *, void *);
62 5468729e 2019-03-29 martijn void dnsbl_session_query_done(struct dnsbl_session *);
63 5e62db7b 2019-08-22 martijn void *dnsbl_session_new(struct osmtpd_ctx *);
64 5e62db7b 2019-08-22 martijn void dnsbl_session_free(struct osmtpd_ctx *, void *);
67 a248a3c1 2019-03-27 martijn main(int argc, char *argv[])
70 5e62db7b 2019-08-22 martijn size_t i;
72 3b767c06 2019-08-22 martijn while ((ch = getopt(argc, argv, "mv")) != -1) {
73 a248a3c1 2019-03-27 martijn switch (ch) {
74 5468729e 2019-03-29 martijn case 'm':
75 5468729e 2019-03-29 martijn markspam = 1;
77 3b767c06 2019-08-22 martijn case 'v':
78 3b767c06 2019-08-22 martijn verbose = 1;
85 b633fa4c 2019-03-29 martijn if (pledge("stdio dns", NULL) == -1)
86 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "pledge");
88 96a01110 2019-03-29 martijn if ((nblacklists = argc - optind) == 0)
89 8b87eb2e 2019-08-22 martijn osmtpd_errx(1, "No blacklist specified");
91 deafb383 2019-03-29 martijn if ((blacklists = calloc(nblacklists, sizeof(*blacklists))) == NULL)
92 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "malloc");
93 deafb383 2019-03-29 martijn for (i = 0; i < nblacklists; i++)
94 deafb383 2019-03-29 martijn blacklists[i] = argv[optind + i];
96 5e62db7b 2019-08-22 martijn osmtpd_register_filter_connect(dnsbl_connect);
97 5e62db7b 2019-08-22 martijn osmtpd_local_session(dnsbl_session_new, dnsbl_session_free);
98 a287838f 2019-03-31 martijn if (markspam) {
99 5e62db7b 2019-08-22 martijn osmtpd_register_report_begin(1, dnsbl_begin);
100 5e62db7b 2019-08-22 martijn osmtpd_register_filter_dataline(dnsbl_dataline);
102 5e62db7b 2019-08-22 martijn osmtpd_run();
104 a248a3c1 2019-03-27 martijn return 0;
108 5e62db7b 2019-08-22 martijn dnsbl_connect(struct osmtpd_ctx *ctx, const char *hostname,
109 5e62db7b 2019-08-22 martijn struct sockaddr_storage *ss)
111 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = ctx->local_session;
112 5e62db7b 2019-08-22 martijn struct asr_query *aq;
113 a248a3c1 2019-03-27 martijn char query[255];
114 a248a3c1 2019-03-27 martijn u_char *addr;
115 5e62db7b 2019-08-22 martijn size_t i;
117 5e62db7b 2019-08-22 martijn if (ss->ss_family == AF_INET)
118 5e62db7b 2019-08-22 martijn addr = (u_char *)(&(((struct sockaddr_in *)ss)->sin_addr));
120 5e62db7b 2019-08-22 martijn addr = (u_char *)(&(((struct sockaddr_in6 *)ss)->sin6_addr));
121 a248a3c1 2019-03-27 martijn for (i = 0; i < nblacklists; i++) {
122 5e62db7b 2019-08-22 martijn if (ss->ss_family == AF_INET) {
123 a248a3c1 2019-03-27 martijn if (snprintf(query, sizeof(query), "%u.%u.%u.%u.%s",
124 a248a3c1 2019-03-27 martijn addr[3], addr[2], addr[1], addr[0],
125 5e62db7b 2019-08-22 martijn blacklists[i]) >= (int) sizeof(query))
126 4525b998 2019-08-22 martijn osmtpd_errx(1,
127 4525b998 2019-08-22 martijn "Can't create query, domain too long");
128 5e62db7b 2019-08-22 martijn } else if (ss->ss_family == AF_INET6) {
129 a248a3c1 2019-03-27 martijn if (snprintf(query, sizeof(query), "%hhx.%hhx.%hhx.%hhx"
130 a248a3c1 2019-03-27 martijn ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
131 a248a3c1 2019-03-27 martijn ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
132 a248a3c1 2019-03-27 martijn ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%s",
133 a248a3c1 2019-03-27 martijn (u_char) (addr[15] & 0xf), (u_char) (addr[15] >> 4),
134 a248a3c1 2019-03-27 martijn (u_char) (addr[14] & 0xf), (u_char) (addr[14] >> 4),
135 a248a3c1 2019-03-27 martijn (u_char) (addr[13] & 0xf), (u_char) (addr[13] >> 4),
136 a248a3c1 2019-03-27 martijn (u_char) (addr[12] & 0xf), (u_char) (addr[12] >> 4),
137 a248a3c1 2019-03-27 martijn (u_char) (addr[11] & 0xf), (u_char) (addr[11] >> 4),
138 a248a3c1 2019-03-27 martijn (u_char) (addr[10] & 0xf), (u_char) (addr[10] >> 4),
139 a248a3c1 2019-03-27 martijn (u_char) (addr[9] & 0xf), (u_char) (addr[9] >> 4),
140 a248a3c1 2019-03-27 martijn (u_char) (addr[8] & 0xf), (u_char) (addr[8] >> 4),
141 a248a3c1 2019-03-27 martijn (u_char) (addr[7] & 0xf), (u_char) (addr[8] >> 4),
142 a248a3c1 2019-03-27 martijn (u_char) (addr[6] & 0xf), (u_char) (addr[7] >> 4),
143 a248a3c1 2019-03-27 martijn (u_char) (addr[5] & 0xf), (u_char) (addr[5] >> 4),
144 a248a3c1 2019-03-27 martijn (u_char) (addr[4] & 0xf), (u_char) (addr[4] >> 4),
145 a248a3c1 2019-03-27 martijn (u_char) (addr[3] & 0xf), (u_char) (addr[3] >> 4),
146 a248a3c1 2019-03-27 martijn (u_char) (addr[2] & 0xf), (u_char) (addr[2] >> 4),
147 a248a3c1 2019-03-27 martijn (u_char) (addr[1] & 0xf), (u_char) (addr[1] >> 4),
148 a248a3c1 2019-03-27 martijn (u_char) (addr[0] & 0xf), (u_char) (addr[0] >> 4),
149 5e62db7b 2019-08-22 martijn blacklists[i]) >= (int) sizeof(query))
150 4525b998 2019-08-22 martijn osmtpd_errx(1,
151 4525b998 2019-08-22 martijn "Can't create query, domain too long");
153 8b87eb2e 2019-08-22 martijn osmtpd_errx(1, "Invalid address family received");
155 5e62db7b 2019-08-22 martijn aq = gethostbyname_async(query, NULL);
156 5e62db7b 2019-08-22 martijn session->query[i].event = event_asr_run(aq, dnsbl_resolve,
157 5e62db7b 2019-08-22 martijn &(session->query[i]));
158 aeda4d4e 2019-03-28 martijn session->query[i].blacklist = i;
159 aeda4d4e 2019-03-28 martijn session->query[i].session = session;
160 a7f2d00f 2019-10-22 martijn session->query[i].running = 1;
165 aeda4d4e 2019-03-28 martijn dnsbl_resolve(struct asr_result *result, void *arg)
167 aeda4d4e 2019-03-28 martijn struct dnsbl_query *query = arg;
168 aeda4d4e 2019-03-28 martijn struct dnsbl_session *session = query->session;
169 5e62db7b 2019-08-22 martijn size_t i;
171 a7f2d00f 2019-10-22 martijn query->running = 0;
172 aeda4d4e 2019-03-28 martijn query->event = NULL;
173 aeda4d4e 2019-03-28 martijn if (result->ar_hostent != NULL) {
174 5468729e 2019-03-29 martijn if (!markspam) {
175 5e62db7b 2019-08-22 martijn osmtpd_filter_disconnect(session->ctx, "Listed at %s",
176 5e62db7b 2019-08-22 martijn blacklists[query->blacklist]);
177 d4e702ee 2019-08-22 martijn fprintf(stderr, "%016"PRIx64" listed at %s: rejected\n",
178 5e62db7b 2019-08-22 martijn session->ctx->reqid, blacklists[query->blacklist]);
179 5468729e 2019-03-29 martijn } else {
180 5468729e 2019-03-29 martijn session->listed = query->blacklist;
181 5e62db7b 2019-08-22 martijn osmtpd_filter_proceed(session->ctx);
182 a287838f 2019-03-31 martijn /* Delay logging until we have a message */
184 a7f2d00f 2019-10-22 martijn dnsbl_session_query_done(session);
187 cf6af233 2019-03-28 martijn if (result->ar_h_errno != HOST_NOT_FOUND) {
188 5e62db7b 2019-08-22 martijn osmtpd_filter_disconnect(session->ctx, "DNS error on %s",
189 5e62db7b 2019-08-22 martijn blacklists[query->blacklist]);
190 a7f2d00f 2019-10-22 martijn dnsbl_session_query_done(session);
194 aeda4d4e 2019-03-28 martijn for (i = 0; i < nblacklists; i++) {
195 a7f2d00f 2019-10-22 martijn if (session->query[i].running)
198 5e62db7b 2019-08-22 martijn osmtpd_filter_proceed(session->ctx);
199 3b767c06 2019-08-22 martijn if (verbose)
200 d4e702ee 2019-08-22 martijn fprintf(stderr, "%016"PRIx64" not listed\n",
201 d4e702ee 2019-08-22 martijn session->ctx->reqid);
205 5e62db7b 2019-08-22 martijn dnsbl_begin(struct osmtpd_ctx *ctx, uint32_t msgid)
207 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = ctx->local_session;
209 a287838f 2019-03-31 martijn if (session->listed != -1) {
210 a287838f 2019-03-31 martijn if (!session->logged_mark) {
211 d4e702ee 2019-08-22 martijn fprintf(stderr, "%016"PRIx64" listed at %s: Marking as "
212 d4e702ee 2019-08-22 martijn "spam\n", ctx->reqid, blacklists[session->listed]);
213 a287838f 2019-03-31 martijn session->logged_mark = 1;
215 a287838f 2019-03-31 martijn session->set_header = 1;
220 5e62db7b 2019-08-22 martijn dnsbl_dataline(struct osmtpd_ctx *ctx, const char *line)
222 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = ctx->local_session;
224 a287838f 2019-03-31 martijn if (session->set_header) {
225 5e62db7b 2019-08-22 martijn osmtpd_filter_dataline(ctx, "X-Spam: yes");
226 5e62db7b 2019-08-22 martijn osmtpd_filter_dataline(ctx, "X-Spam-DNSBL: Listed at %s",
227 5468729e 2019-03-29 martijn blacklists[session->listed]);
228 a287838f 2019-03-31 martijn session->set_header = 0;
231 5e62db7b 2019-08-22 martijn osmtpd_filter_dataline(ctx, "%s", line);
235 5468729e 2019-03-29 martijn dnsbl_session_query_done(struct dnsbl_session *session)
237 5e62db7b 2019-08-22 martijn size_t i;
239 aeda4d4e 2019-03-28 martijn for (i = 0; i < nblacklists; i++) {
240 a7f2d00f 2019-10-22 martijn if (session->query[i].running) {
241 aeda4d4e 2019-03-28 martijn event_asr_abort(session->query[i].event);
242 a7f2d00f 2019-10-22 martijn session->query[i].running = 0;
248 5e62db7b 2019-08-22 martijn dnsbl_session_new(struct osmtpd_ctx *ctx)
250 5e62db7b 2019-08-22 martijn struct dnsbl_session *session;
252 5e62db7b 2019-08-22 martijn if ((session = calloc(1, sizeof(*session))) == NULL)
253 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "malloc");
254 5e62db7b 2019-08-22 martijn if ((session->query = calloc(nblacklists, sizeof(*(session->query))))
255 5e62db7b 2019-08-22 martijn == NULL)
256 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "malloc");
257 5e62db7b 2019-08-22 martijn session->listed = -1;
258 5e62db7b 2019-08-22 martijn session->set_header = 0;
259 5e62db7b 2019-08-22 martijn session->logged_mark = 0;
260 5e62db7b 2019-08-22 martijn session->ctx = ctx;
262 5e62db7b 2019-08-22 martijn return session;
266 5e62db7b 2019-08-22 martijn dnsbl_session_free(struct osmtpd_ctx *ctx, void *data)
268 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = data;
270 5468729e 2019-03-29 martijn dnsbl_session_query_done(session);
271 aeda4d4e 2019-03-28 martijn free(session->query);
272 aeda4d4e 2019-03-28 martijn free(session);
275 f8356fc3 2019-03-28 martijn __dead void
276 a248a3c1 2019-03-27 martijn usage(void)
278 deafb383 2019-03-29 martijn fprintf(stderr, "usage: %s [-m] blacklist [...]\n",
279 a248a3c1 2019-03-27 martijn getprogname());
280 a248a3c1 2019-03-27 martijn exit(1);