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/types.h>
17 #include <sys/socket.h>
19 #include <arpa/inet.h>
20 #include <errno.h>
21 #include <event.h>
22 #include <inttypes.h>
23 #include <netdb.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <syslog.h>
29 #include <unistd.h>
30 #include <asr.h>
32 #include "opensmtpd.h"
34 struct dnsbl_session;
36 struct dnsbl_query {
37 struct event_asr *event;
38 int resolved;
39 int blacklist;
40 struct dnsbl_session *session;
41 };
43 struct dnsbl_session {
44 int listed;
45 int set_header;
46 int logged_mark;
47 struct dnsbl_query *query;
48 struct osmtpd_ctx *ctx;
49 };
51 static char **blacklists = NULL;
52 static size_t nblacklists = 0;
53 static int markspam = 0;
54 static int verbose = 0;
56 void usage(void);
57 void dnsbl_connect(struct osmtpd_ctx *, const char *,
58 struct sockaddr_storage *);
59 void dnsbl_begin(struct osmtpd_ctx *, uint32_t);
60 void dnsbl_dataline(struct osmtpd_ctx *, const char *);
61 void dnsbl_resolve(struct asr_result *, void *);
62 void dnsbl_session_query_done(struct dnsbl_session *);
63 void *dnsbl_session_new(struct osmtpd_ctx *);
64 void dnsbl_session_free(struct osmtpd_ctx *, void *);
66 int
67 main(int argc, char *argv[])
68 {
69 int ch;
70 size_t i;
72 while ((ch = getopt(argc, argv, "mv")) != -1) {
73 switch (ch) {
74 case 'm':
75 markspam = 1;
76 break;
77 case 'v':
78 verbose = 1;
79 break;
80 default:
81 usage();
82 }
83 }
85 if (pledge("stdio dns", NULL) == -1)
86 osmtpd_err(1, "pledge");
88 if ((nblacklists = argc - optind) == 0)
89 osmtpd_errx(1, "No blacklist specified");
91 if ((blacklists = calloc(nblacklists, sizeof(*blacklists))) == NULL)
92 osmtpd_err(1, "malloc");
93 for (i = 0; i < nblacklists; i++)
94 blacklists[i] = argv[optind + i];
96 osmtpd_register_filter_connect(dnsbl_connect);
97 osmtpd_local_session(dnsbl_session_new, dnsbl_session_free);
98 if (markspam) {
99 osmtpd_register_report_begin(1, dnsbl_begin);
100 osmtpd_register_filter_dataline(dnsbl_dataline);
102 osmtpd_run();
104 return 0;
107 void
108 dnsbl_connect(struct osmtpd_ctx *ctx, const char *hostname,
109 struct sockaddr_storage *ss)
111 struct dnsbl_session *session = ctx->local_session;
112 struct asr_query *aq;
113 char query[255];
114 u_char *addr;
115 size_t i;
117 if (ss->ss_family == AF_INET)
118 addr = (u_char *)(&(((struct sockaddr_in *)ss)->sin_addr));
119 else
120 addr = (u_char *)(&(((struct sockaddr_in6 *)ss)->sin6_addr));
121 for (i = 0; i < nblacklists; i++) {
122 if (ss->ss_family == AF_INET) {
123 if (snprintf(query, sizeof(query), "%u.%u.%u.%u.%s",
124 addr[3], addr[2], addr[1], addr[0],
125 blacklists[i]) >= (int) sizeof(query))
126 osmtpd_errx(1,
127 "Can't create query, domain too long");
128 } else if (ss->ss_family == AF_INET6) {
129 if (snprintf(query, sizeof(query), "%hhx.%hhx.%hhx.%hhx"
130 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
131 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
132 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%s",
133 (u_char) (addr[15] & 0xf), (u_char) (addr[15] >> 4),
134 (u_char) (addr[14] & 0xf), (u_char) (addr[14] >> 4),
135 (u_char) (addr[13] & 0xf), (u_char) (addr[13] >> 4),
136 (u_char) (addr[12] & 0xf), (u_char) (addr[12] >> 4),
137 (u_char) (addr[11] & 0xf), (u_char) (addr[11] >> 4),
138 (u_char) (addr[10] & 0xf), (u_char) (addr[10] >> 4),
139 (u_char) (addr[9] & 0xf), (u_char) (addr[9] >> 4),
140 (u_char) (addr[8] & 0xf), (u_char) (addr[8] >> 4),
141 (u_char) (addr[7] & 0xf), (u_char) (addr[8] >> 4),
142 (u_char) (addr[6] & 0xf), (u_char) (addr[7] >> 4),
143 (u_char) (addr[5] & 0xf), (u_char) (addr[5] >> 4),
144 (u_char) (addr[4] & 0xf), (u_char) (addr[4] >> 4),
145 (u_char) (addr[3] & 0xf), (u_char) (addr[3] >> 4),
146 (u_char) (addr[2] & 0xf), (u_char) (addr[2] >> 4),
147 (u_char) (addr[1] & 0xf), (u_char) (addr[1] >> 4),
148 (u_char) (addr[0] & 0xf), (u_char) (addr[0] >> 4),
149 blacklists[i]) >= (int) sizeof(query))
150 osmtpd_errx(1,
151 "Can't create query, domain too long");
152 } else
153 osmtpd_errx(1, "Invalid address family received");
155 aq = gethostbyname_async(query, NULL);
156 session->query[i].event = event_asr_run(aq, dnsbl_resolve,
157 &(session->query[i]));
158 session->query[i].blacklist = i;
159 session->query[i].session = session;
163 void
164 dnsbl_resolve(struct asr_result *result, void *arg)
166 struct dnsbl_query *query = arg;
167 struct dnsbl_session *session = query->session;
168 size_t i;
170 query->resolved = 1;
171 query->event = NULL;
172 if (result->ar_hostent != NULL) {
173 if (!markspam) {
174 osmtpd_filter_disconnect(session->ctx, "Listed at %s",
175 blacklists[query->blacklist]);
176 fprintf(stderr, "%016"PRIx64" listed at %s: rejected\n",
177 session->ctx->reqid, blacklists[query->blacklist]);
178 } else {
179 dnsbl_session_query_done(session);
180 session->listed = query->blacklist;
181 osmtpd_filter_proceed(session->ctx);
182 /* Delay logging until we have a message */
184 return;
186 if (result->ar_h_errno != HOST_NOT_FOUND) {
187 osmtpd_filter_disconnect(session->ctx, "DNS error on %s",
188 blacklists[query->blacklist]);
189 return;
192 for (i = 0; i < nblacklists; i++) {
193 if (!session->query[i].resolved)
194 return;
196 osmtpd_filter_proceed(session->ctx);
197 if (verbose)
198 fprintf(stderr, "%016"PRIx64" not listed\n",
199 session->ctx->reqid);
202 void
203 dnsbl_begin(struct osmtpd_ctx *ctx, uint32_t msgid)
205 struct dnsbl_session *session = ctx->local_session;
207 if (session->listed != -1) {
208 if (!session->logged_mark) {
209 fprintf(stderr, "%016"PRIx64" listed at %s: Marking as "
210 "spam\n", ctx->reqid, blacklists[session->listed]);
211 session->logged_mark = 1;
213 session->set_header = 1;
217 void
218 dnsbl_dataline(struct osmtpd_ctx *ctx, const char *line)
220 struct dnsbl_session *session = ctx->local_session;
222 if (session->set_header) {
223 osmtpd_filter_dataline(ctx, "X-Spam: yes");
224 osmtpd_filter_dataline(ctx, "X-Spam-DNSBL: Listed at %s",
225 blacklists[session->listed]);
226 session->set_header = 0;
229 osmtpd_filter_dataline(ctx, "%s", line);
232 void
233 dnsbl_session_query_done(struct dnsbl_session *session)
235 size_t i;
237 for (i = 0; i < nblacklists; i++) {
238 if (!session->query[i].resolved) {
239 event_asr_abort(session->query[i].event);
240 session->query[i].resolved = 1;
245 void *
246 dnsbl_session_new(struct osmtpd_ctx *ctx)
248 struct dnsbl_session *session;
250 if ((session = calloc(1, sizeof(*session))) == NULL)
251 osmtpd_err(1, "malloc");
252 if ((session->query = calloc(nblacklists, sizeof(*(session->query))))
253 == NULL)
254 osmtpd_err(1, "malloc");
255 session->listed = -1;
256 session->set_header = 0;
257 session->logged_mark = 0;
258 session->ctx = ctx;
260 return session;
263 void
264 dnsbl_session_free(struct osmtpd_ctx *ctx, void *data)
266 struct dnsbl_session *session = data;
268 dnsbl_session_query_done(session);
269 free(session->query);
270 free(session);
273 __dead void
274 usage(void)
276 fprintf(stderr, "usage: %s [-m] blacklist [...]\n",
277 getprogname());
278 exit(1);