Blame


1 15cacd92 2019-03-29 martijn /*
2 15cacd92 2019-03-29 martijn * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
3 15cacd92 2019-03-29 martijn *
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.
7 15cacd92 2019-03-29 martijn *
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.
15 15cacd92 2019-03-29 martijn */
16 734e1bdf 2025-12-20 op
17 734e1bdf 2025-12-20 op #include "compat.h"
18 734e1bdf 2025-12-20 op
19 aeda4d4e 2019-03-28 martijn #include <sys/types.h>
20 a248a3c1 2019-03-27 martijn #include <sys/socket.h>
21 a248a3c1 2019-03-27 martijn
22 2e3e5d5f 2019-03-29 martijn #include <arpa/inet.h>
23 a248a3c1 2019-03-27 martijn #include <errno.h>
24 aeda4d4e 2019-03-28 martijn #include <event.h>
25 a287838f 2019-03-31 martijn #include <inttypes.h>
26 a248a3c1 2019-03-27 martijn #include <netdb.h>
27 a248a3c1 2019-03-27 martijn #include <stdlib.h>
28 d4e702ee 2019-08-22 martijn #include <stdarg.h>
29 a248a3c1 2019-03-27 martijn #include <stdio.h>
30 a248a3c1 2019-03-27 martijn #include <string.h>
31 b633fa4c 2019-03-29 martijn #include <syslog.h>
32 a248a3c1 2019-03-27 martijn #include <unistd.h>
33 aeda4d4e 2019-03-28 martijn #include <asr.h>
34 a248a3c1 2019-03-27 martijn
35 5e62db7b 2019-08-22 martijn #include "opensmtpd.h"
36 a248a3c1 2019-03-27 martijn
37 aeda4d4e 2019-03-28 martijn struct dnsbl_session;
38 aeda4d4e 2019-03-28 martijn
39 aeda4d4e 2019-03-28 martijn struct dnsbl_query {
40 aeda4d4e 2019-03-28 martijn struct event_asr *event;
41 a7f2d00f 2019-10-22 martijn int running;
42 aeda4d4e 2019-03-28 martijn int blacklist;
43 aeda4d4e 2019-03-28 martijn struct dnsbl_session *session;
44 aeda4d4e 2019-03-28 martijn };
45 aeda4d4e 2019-03-28 martijn
46 aeda4d4e 2019-03-28 martijn struct dnsbl_session {
47 5468729e 2019-03-29 martijn int listed;
48 a287838f 2019-03-31 martijn int set_header;
49 a287838f 2019-03-31 martijn int logged_mark;
50 aeda4d4e 2019-03-28 martijn struct dnsbl_query *query;
51 5e62db7b 2019-08-22 martijn struct osmtpd_ctx *ctx;
52 aeda4d4e 2019-03-28 martijn };
53 aeda4d4e 2019-03-28 martijn
54 1aa52bf5 2021-10-27 martijn static const char **blacklists = NULL;
55 1aa52bf5 2021-10-27 martijn static const char **printblacklists;
56 a248a3c1 2019-03-27 martijn static size_t nblacklists = 0;
57 5468729e 2019-03-29 martijn static int markspam = 0;
58 3b767c06 2019-08-22 martijn static int verbose = 0;
59 a248a3c1 2019-03-27 martijn
60 1aa52bf5 2021-10-27 martijn const char *dnsbl_printblacklist(const char *);
61 3ca25b7a 2025-05-19 martijn int dnsbl_connect(struct osmtpd_ctx *, const char *,
62 5e62db7b 2019-08-22 martijn struct sockaddr_storage *);
63 3ca25b7a 2025-05-19 martijn int dnsbl_begin(struct osmtpd_ctx *, uint32_t);
64 3ca25b7a 2025-05-19 martijn int dnsbl_dataline(struct osmtpd_ctx *, const char *);
65 aeda4d4e 2019-03-28 martijn void dnsbl_resolve(struct asr_result *, void *);
66 5468729e 2019-03-29 martijn void dnsbl_session_query_done(struct dnsbl_session *);
67 5e62db7b 2019-08-22 martijn void *dnsbl_session_new(struct osmtpd_ctx *);
68 5e62db7b 2019-08-22 martijn void dnsbl_session_free(struct osmtpd_ctx *, void *);
69 1aa52bf5 2021-10-27 martijn void usage(void);
70 a248a3c1 2019-03-27 martijn
71 a248a3c1 2019-03-27 martijn int
72 a248a3c1 2019-03-27 martijn main(int argc, char *argv[])
73 a248a3c1 2019-03-27 martijn {
74 a248a3c1 2019-03-27 martijn int ch;
75 5e62db7b 2019-08-22 martijn size_t i;
76 a248a3c1 2019-03-27 martijn
77 3b767c06 2019-08-22 martijn while ((ch = getopt(argc, argv, "mv")) != -1) {
78 a248a3c1 2019-03-27 martijn switch (ch) {
79 5468729e 2019-03-29 martijn case 'm':
80 5468729e 2019-03-29 martijn markspam = 1;
81 5468729e 2019-03-29 martijn break;
82 3b767c06 2019-08-22 martijn case 'v':
83 3b767c06 2019-08-22 martijn verbose = 1;
84 3b767c06 2019-08-22 martijn break;
85 a248a3c1 2019-03-27 martijn default:
86 a248a3c1 2019-03-27 martijn usage();
87 a248a3c1 2019-03-27 martijn }
88 a248a3c1 2019-03-27 martijn }
89 a248a3c1 2019-03-27 martijn
90 b633fa4c 2019-03-29 martijn if (pledge("stdio dns", NULL) == -1)
91 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "pledge");
92 b633fa4c 2019-03-29 martijn
93 96a01110 2019-03-29 martijn if ((nblacklists = argc - optind) == 0)
94 8b87eb2e 2019-08-22 martijn osmtpd_errx(1, "No blacklist specified");
95 deafb383 2019-03-29 martijn
96 1aa52bf5 2021-10-27 martijn blacklists = calloc(nblacklists, sizeof(*blacklists));
97 1aa52bf5 2021-10-27 martijn printblacklists = calloc(nblacklists, sizeof(*printblacklists));
98 1aa52bf5 2021-10-27 martijn if (printblacklists == NULL || blacklists == NULL)
99 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "malloc");
100 1aa52bf5 2021-10-27 martijn for (i = 0; i < nblacklists; i++) {
101 deafb383 2019-03-29 martijn blacklists[i] = argv[optind + i];
102 1aa52bf5 2021-10-27 martijn printblacklists[i] = dnsbl_printblacklist(argv[optind + i]);
103 1aa52bf5 2021-10-27 martijn }
104 deafb383 2019-03-29 martijn
105 5e62db7b 2019-08-22 martijn osmtpd_register_filter_connect(dnsbl_connect);
106 5e62db7b 2019-08-22 martijn osmtpd_local_session(dnsbl_session_new, dnsbl_session_free);
107 a287838f 2019-03-31 martijn if (markspam) {
108 5e62db7b 2019-08-22 martijn osmtpd_register_report_begin(1, dnsbl_begin);
109 5e62db7b 2019-08-22 martijn osmtpd_register_filter_dataline(dnsbl_dataline);
110 a287838f 2019-03-31 martijn }
111 5e62db7b 2019-08-22 martijn osmtpd_run();
112 a248a3c1 2019-03-27 martijn
113 a248a3c1 2019-03-27 martijn return 0;
114 a248a3c1 2019-03-27 martijn }
115 a248a3c1 2019-03-27 martijn
116 1aa52bf5 2021-10-27 martijn const char *
117 1aa52bf5 2021-10-27 martijn dnsbl_printblacklist(const char *blacklist)
118 1aa52bf5 2021-10-27 martijn {
119 1aa52bf5 2021-10-27 martijn /* All of abusix is paid and has a key in the first spot */
120 1aa52bf5 2021-10-27 martijn if (strcasestr(blacklist, ".mail.abusix.zone") != NULL)
121 1aa52bf5 2021-10-27 martijn return strchr(blacklist, '.') + 1;
122 1aa52bf5 2021-10-27 martijn /* XXX assume dq.spamhaus.net is paid and has a key in the first spot */
123 1aa52bf5 2021-10-27 martijn if (strcasestr(blacklist, ".dq.spamhaus.net") != NULL)
124 1aa52bf5 2021-10-27 martijn return strchr(blacklist, '.') + 1;
125 1aa52bf5 2021-10-27 martijn return blacklist;
126 1aa52bf5 2021-10-27 martijn }
127 1aa52bf5 2021-10-27 martijn
128 3ca25b7a 2025-05-19 martijn int
129 5e62db7b 2019-08-22 martijn dnsbl_connect(struct osmtpd_ctx *ctx, const char *hostname,
130 5e62db7b 2019-08-22 martijn struct sockaddr_storage *ss)
131 a248a3c1 2019-03-27 martijn {
132 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = ctx->local_session;
133 5e62db7b 2019-08-22 martijn struct asr_query *aq;
134 a248a3c1 2019-03-27 martijn char query[255];
135 a248a3c1 2019-03-27 martijn u_char *addr;
136 5e62db7b 2019-08-22 martijn size_t i;
137 a248a3c1 2019-03-27 martijn
138 5e62db7b 2019-08-22 martijn if (ss->ss_family == AF_INET)
139 5e62db7b 2019-08-22 martijn addr = (u_char *)(&(((struct sockaddr_in *)ss)->sin_addr));
140 03a6f52a 2019-03-29 martijn else
141 5e62db7b 2019-08-22 martijn addr = (u_char *)(&(((struct sockaddr_in6 *)ss)->sin6_addr));
142 a248a3c1 2019-03-27 martijn for (i = 0; i < nblacklists; i++) {
143 5e62db7b 2019-08-22 martijn if (ss->ss_family == AF_INET) {
144 a248a3c1 2019-03-27 martijn if (snprintf(query, sizeof(query), "%u.%u.%u.%u.%s",
145 a248a3c1 2019-03-27 martijn addr[3], addr[2], addr[1], addr[0],
146 5e62db7b 2019-08-22 martijn blacklists[i]) >= (int) sizeof(query))
147 4525b998 2019-08-22 martijn osmtpd_errx(1,
148 4525b998 2019-08-22 martijn "Can't create query, domain too long");
149 5e62db7b 2019-08-22 martijn } else if (ss->ss_family == AF_INET6) {
150 a248a3c1 2019-03-27 martijn if (snprintf(query, sizeof(query), "%hhx.%hhx.%hhx.%hhx"
151 a248a3c1 2019-03-27 martijn ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
152 a248a3c1 2019-03-27 martijn ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
153 a248a3c1 2019-03-27 martijn ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%s",
154 a248a3c1 2019-03-27 martijn (u_char) (addr[15] & 0xf), (u_char) (addr[15] >> 4),
155 a248a3c1 2019-03-27 martijn (u_char) (addr[14] & 0xf), (u_char) (addr[14] >> 4),
156 a248a3c1 2019-03-27 martijn (u_char) (addr[13] & 0xf), (u_char) (addr[13] >> 4),
157 a248a3c1 2019-03-27 martijn (u_char) (addr[12] & 0xf), (u_char) (addr[12] >> 4),
158 a248a3c1 2019-03-27 martijn (u_char) (addr[11] & 0xf), (u_char) (addr[11] >> 4),
159 a248a3c1 2019-03-27 martijn (u_char) (addr[10] & 0xf), (u_char) (addr[10] >> 4),
160 a248a3c1 2019-03-27 martijn (u_char) (addr[9] & 0xf), (u_char) (addr[9] >> 4),
161 a248a3c1 2019-03-27 martijn (u_char) (addr[8] & 0xf), (u_char) (addr[8] >> 4),
162 a248a3c1 2019-03-27 martijn (u_char) (addr[7] & 0xf), (u_char) (addr[8] >> 4),
163 a248a3c1 2019-03-27 martijn (u_char) (addr[6] & 0xf), (u_char) (addr[7] >> 4),
164 a248a3c1 2019-03-27 martijn (u_char) (addr[5] & 0xf), (u_char) (addr[5] >> 4),
165 a248a3c1 2019-03-27 martijn (u_char) (addr[4] & 0xf), (u_char) (addr[4] >> 4),
166 a248a3c1 2019-03-27 martijn (u_char) (addr[3] & 0xf), (u_char) (addr[3] >> 4),
167 a248a3c1 2019-03-27 martijn (u_char) (addr[2] & 0xf), (u_char) (addr[2] >> 4),
168 a248a3c1 2019-03-27 martijn (u_char) (addr[1] & 0xf), (u_char) (addr[1] >> 4),
169 a248a3c1 2019-03-27 martijn (u_char) (addr[0] & 0xf), (u_char) (addr[0] >> 4),
170 5e62db7b 2019-08-22 martijn blacklists[i]) >= (int) sizeof(query))
171 4525b998 2019-08-22 martijn osmtpd_errx(1,
172 4525b998 2019-08-22 martijn "Can't create query, domain too long");
173 a248a3c1 2019-03-27 martijn } else
174 8b87eb2e 2019-08-22 martijn osmtpd_errx(1, "Invalid address family received");
175 a248a3c1 2019-03-27 martijn
176 5e62db7b 2019-08-22 martijn aq = gethostbyname_async(query, NULL);
177 5e62db7b 2019-08-22 martijn session->query[i].event = event_asr_run(aq, dnsbl_resolve,
178 5e62db7b 2019-08-22 martijn &(session->query[i]));
179 aeda4d4e 2019-03-28 martijn session->query[i].blacklist = i;
180 aeda4d4e 2019-03-28 martijn session->query[i].session = session;
181 a7f2d00f 2019-10-22 martijn session->query[i].running = 1;
182 a248a3c1 2019-03-27 martijn }
183 3ca25b7a 2025-05-19 martijn
184 3ca25b7a 2025-05-19 martijn return 0;
185 a248a3c1 2019-03-27 martijn }
186 a248a3c1 2019-03-27 martijn
187 aeda4d4e 2019-03-28 martijn void
188 aeda4d4e 2019-03-28 martijn dnsbl_resolve(struct asr_result *result, void *arg)
189 aeda4d4e 2019-03-28 martijn {
190 aeda4d4e 2019-03-28 martijn struct dnsbl_query *query = arg;
191 aeda4d4e 2019-03-28 martijn struct dnsbl_session *session = query->session;
192 5e62db7b 2019-08-22 martijn size_t i;
193 aeda4d4e 2019-03-28 martijn
194 a7f2d00f 2019-10-22 martijn query->running = 0;
195 aeda4d4e 2019-03-28 martijn query->event = NULL;
196 aeda4d4e 2019-03-28 martijn if (result->ar_hostent != NULL) {
197 5468729e 2019-03-29 martijn if (!markspam) {
198 5e62db7b 2019-08-22 martijn osmtpd_filter_disconnect(session->ctx, "Listed at %s",
199 1aa52bf5 2021-10-27 martijn printblacklists[query->blacklist]);
200 d4e702ee 2019-08-22 martijn fprintf(stderr, "%016"PRIx64" listed at %s: rejected\n",
201 1aa52bf5 2021-10-27 martijn session->ctx->reqid,
202 1aa52bf5 2021-10-27 martijn printblacklists[query->blacklist]);
203 5468729e 2019-03-29 martijn } else {
204 5468729e 2019-03-29 martijn session->listed = query->blacklist;
205 5e62db7b 2019-08-22 martijn osmtpd_filter_proceed(session->ctx);
206 a287838f 2019-03-31 martijn /* Delay logging until we have a message */
207 5468729e 2019-03-29 martijn }
208 a7f2d00f 2019-10-22 martijn dnsbl_session_query_done(session);
209 aeda4d4e 2019-03-28 martijn return;
210 aeda4d4e 2019-03-28 martijn }
211 cf6af233 2019-03-28 martijn if (result->ar_h_errno != HOST_NOT_FOUND) {
212 5e62db7b 2019-08-22 martijn osmtpd_filter_disconnect(session->ctx, "DNS error on %s",
213 1aa52bf5 2021-10-27 martijn printblacklists[query->blacklist]);
214 a7f2d00f 2019-10-22 martijn dnsbl_session_query_done(session);
215 cf6af233 2019-03-28 martijn return;
216 cf6af233 2019-03-28 martijn }
217 aeda4d4e 2019-03-28 martijn
218 aeda4d4e 2019-03-28 martijn for (i = 0; i < nblacklists; i++) {
219 a7f2d00f 2019-10-22 martijn if (session->query[i].running)
220 aeda4d4e 2019-03-28 martijn return;
221 aeda4d4e 2019-03-28 martijn }
222 5e62db7b 2019-08-22 martijn osmtpd_filter_proceed(session->ctx);
223 3b767c06 2019-08-22 martijn if (verbose)
224 d4e702ee 2019-08-22 martijn fprintf(stderr, "%016"PRIx64" not listed\n",
225 d4e702ee 2019-08-22 martijn session->ctx->reqid);
226 aeda4d4e 2019-03-28 martijn }
227 aeda4d4e 2019-03-28 martijn
228 3ca25b7a 2025-05-19 martijn int
229 5e62db7b 2019-08-22 martijn dnsbl_begin(struct osmtpd_ctx *ctx, uint32_t msgid)
230 c75461cd 2019-03-29 martijn {
231 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = ctx->local_session;
232 c75461cd 2019-03-29 martijn
233 a287838f 2019-03-31 martijn if (session->listed != -1) {
234 a287838f 2019-03-31 martijn if (!session->logged_mark) {
235 d4e702ee 2019-08-22 martijn fprintf(stderr, "%016"PRIx64" listed at %s: Marking as "
236 1aa52bf5 2021-10-27 martijn "spam\n", ctx->reqid,
237 1aa52bf5 2021-10-27 martijn printblacklists[session->listed]);
238 a287838f 2019-03-31 martijn session->logged_mark = 1;
239 a287838f 2019-03-31 martijn }
240 a287838f 2019-03-31 martijn session->set_header = 1;
241 a287838f 2019-03-31 martijn }
242 3ca25b7a 2025-05-19 martijn
243 3ca25b7a 2025-05-19 martijn return 0;
244 a287838f 2019-03-31 martijn }
245 a287838f 2019-03-31 martijn
246 3ca25b7a 2025-05-19 martijn int
247 5e62db7b 2019-08-22 martijn dnsbl_dataline(struct osmtpd_ctx *ctx, const char *line)
248 aeda4d4e 2019-03-28 martijn {
249 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = ctx->local_session;
250 5468729e 2019-03-29 martijn
251 a287838f 2019-03-31 martijn if (session->set_header) {
252 5e62db7b 2019-08-22 martijn osmtpd_filter_dataline(ctx, "X-Spam: yes");
253 5e62db7b 2019-08-22 martijn osmtpd_filter_dataline(ctx, "X-Spam-DNSBL: Listed at %s",
254 1aa52bf5 2021-10-27 martijn printblacklists[session->listed]);
255 a287838f 2019-03-31 martijn session->set_header = 0;
256 a287838f 2019-03-31 martijn
257 5468729e 2019-03-29 martijn }
258 5e62db7b 2019-08-22 martijn osmtpd_filter_dataline(ctx, "%s", line);
259 3ca25b7a 2025-05-19 martijn
260 3ca25b7a 2025-05-19 martijn return 0;
261 5468729e 2019-03-29 martijn }
262 5468729e 2019-03-29 martijn
263 5468729e 2019-03-29 martijn void
264 5468729e 2019-03-29 martijn dnsbl_session_query_done(struct dnsbl_session *session)
265 5468729e 2019-03-29 martijn {
266 5e62db7b 2019-08-22 martijn size_t i;
267 aeda4d4e 2019-03-28 martijn
268 aeda4d4e 2019-03-28 martijn for (i = 0; i < nblacklists; i++) {
269 a7f2d00f 2019-10-22 martijn if (session->query[i].running) {
270 aeda4d4e 2019-03-28 martijn event_asr_abort(session->query[i].event);
271 a7f2d00f 2019-10-22 martijn session->query[i].running = 0;
272 76e3b8e2 2019-03-28 martijn }
273 aeda4d4e 2019-03-28 martijn }
274 5468729e 2019-03-29 martijn }
275 5468729e 2019-03-29 martijn
276 5e62db7b 2019-08-22 martijn void *
277 5e62db7b 2019-08-22 martijn dnsbl_session_new(struct osmtpd_ctx *ctx)
278 5e62db7b 2019-08-22 martijn {
279 5e62db7b 2019-08-22 martijn struct dnsbl_session *session;
280 5e62db7b 2019-08-22 martijn
281 5e62db7b 2019-08-22 martijn if ((session = calloc(1, sizeof(*session))) == NULL)
282 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "malloc");
283 5e62db7b 2019-08-22 martijn if ((session->query = calloc(nblacklists, sizeof(*(session->query))))
284 5e62db7b 2019-08-22 martijn == NULL)
285 8b87eb2e 2019-08-22 martijn osmtpd_err(1, "malloc");
286 5e62db7b 2019-08-22 martijn session->listed = -1;
287 5e62db7b 2019-08-22 martijn session->set_header = 0;
288 5e62db7b 2019-08-22 martijn session->logged_mark = 0;
289 5e62db7b 2019-08-22 martijn session->ctx = ctx;
290 5e62db7b 2019-08-22 martijn
291 5e62db7b 2019-08-22 martijn return session;
292 5e62db7b 2019-08-22 martijn }
293 5e62db7b 2019-08-22 martijn
294 5468729e 2019-03-29 martijn void
295 5e62db7b 2019-08-22 martijn dnsbl_session_free(struct osmtpd_ctx *ctx, void *data)
296 5468729e 2019-03-29 martijn {
297 5e62db7b 2019-08-22 martijn struct dnsbl_session *session = data;
298 5e62db7b 2019-08-22 martijn
299 5468729e 2019-03-29 martijn dnsbl_session_query_done(session);
300 aeda4d4e 2019-03-28 martijn free(session->query);
301 aeda4d4e 2019-03-28 martijn free(session);
302 aeda4d4e 2019-03-28 martijn }
303 aeda4d4e 2019-03-28 martijn
304 f8356fc3 2019-03-28 martijn __dead void
305 a248a3c1 2019-03-27 martijn usage(void)
306 a248a3c1 2019-03-27 martijn {
307 ce077085 2019-11-14 martijn fprintf(stderr, "usage: filter-dnsbl [-m] blacklist [...]\n");
308 a248a3c1 2019-03-27 martijn exit(1);
309 a248a3c1 2019-03-27 martijn }