2 * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
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.
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.
17 #include <sys/types.h>
18 #include <sys/socket.h>
31 #include "smtp_proc.h"
36 struct asr_query *query;
37 struct event_asr *event;
41 struct dnsbl_session *session;
44 struct dnsbl_session {
48 struct dnsbl_query *query;
49 RB_ENTRY(dnsbl_session) entry;
52 RB_HEAD(dnsbl_sessions, dnsbl_session) dnsbl_sessions = RB_INITIALIZER(NULL);
53 RB_PROTOTYPE(dnsbl_sessions, dnsbl_session, entry, dnsbl_session_cmp);
55 static char **blacklists = NULL;
56 static size_t nblacklists = 0;
57 static int markspam = 0;
60 void dnsbl_connect(char *, int, struct timespec *, char *, char *, uint64_t,
61 uint64_t, char *, struct inx_addr *);
62 void dnsbl_dataline(char *, int, struct timespec *, char *, char *, uint64_t,
64 void dnsbl_disconnect(char *, int, struct timespec *, char *, char *, uint64_t);
65 void dnsbl_resolve(struct asr_result *, void *);
66 void dnsbl_timeout(int, short, void *);
67 void dnsbl_session_query_done(struct dnsbl_session *);
68 void dnsbl_session_free(struct dnsbl_session *);
69 int dnsbl_session_cmp(struct dnsbl_session *, struct dnsbl_session *);
72 main(int argc, char *argv[])
78 while ((ch = getopt(argc, argv, "d:m")) != -1) {
91 log_init(debug, LOG_MAIL);
92 if (pledge("stdio dns", NULL) == -1)
95 if ((nblacklists = argc - optind) == 0)
96 fatalx("No blacklist specified");
98 if ((blacklists = calloc(nblacklists, sizeof(*blacklists))) == NULL)
100 for (i = 0; i < nblacklists; i++)
101 blacklists[i] = argv[optind + i];
103 smtp_register_filter_connect(dnsbl_connect);
105 smtp_register_filter_dataline(dnsbl_dataline);
106 smtp_in_register_report_disconnect(dnsbl_disconnect);
113 dnsbl_connect(char *type, int version, struct timespec *tm, char *direction,
114 char *phase, uint64_t reqid, uint64_t token, char *hostname,
115 struct inx_addr *xaddr)
117 struct dnsbl_session *session;
118 struct timeval timeout = {1, 0};
123 if ((session = calloc(1, sizeof(*session))) == NULL)
125 if ((session->query = calloc(nblacklists, sizeof(*(session->query))))
128 session->reqid = reqid;
129 session->token = token;
130 session->listed = -1;
131 RB_INSERT(dnsbl_sessions, &dnsbl_sessions, session);
133 if (xaddr->af == AF_INET)
134 addr = (u_char *)&(xaddr->addr);
136 addr = (u_char *)&(xaddr->addr6);
137 for (i = 0; i < nblacklists; i++) {
138 if (xaddr->af == AF_INET) {
139 if (snprintf(query, sizeof(query), "%u.%u.%u.%u.%s",
140 addr[3], addr[2], addr[1], addr[0],
141 blacklists[i]) >= sizeof(query))
142 fatalx("Can't create query, domain too long");
143 } else if (xaddr->af == AF_INET6) {
144 if (snprintf(query, sizeof(query), "%hhx.%hhx.%hhx.%hhx"
145 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
146 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx"
147 ".%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%hhx.%s",
148 (u_char) (addr[15] & 0xf), (u_char) (addr[15] >> 4),
149 (u_char) (addr[14] & 0xf), (u_char) (addr[14] >> 4),
150 (u_char) (addr[13] & 0xf), (u_char) (addr[13] >> 4),
151 (u_char) (addr[12] & 0xf), (u_char) (addr[12] >> 4),
152 (u_char) (addr[11] & 0xf), (u_char) (addr[11] >> 4),
153 (u_char) (addr[10] & 0xf), (u_char) (addr[10] >> 4),
154 (u_char) (addr[9] & 0xf), (u_char) (addr[9] >> 4),
155 (u_char) (addr[8] & 0xf), (u_char) (addr[8] >> 4),
156 (u_char) (addr[7] & 0xf), (u_char) (addr[8] >> 4),
157 (u_char) (addr[6] & 0xf), (u_char) (addr[7] >> 4),
158 (u_char) (addr[5] & 0xf), (u_char) (addr[5] >> 4),
159 (u_char) (addr[4] & 0xf), (u_char) (addr[4] >> 4),
160 (u_char) (addr[3] & 0xf), (u_char) (addr[3] >> 4),
161 (u_char) (addr[2] & 0xf), (u_char) (addr[2] >> 4),
162 (u_char) (addr[1] & 0xf), (u_char) (addr[1] >> 4),
163 (u_char) (addr[0] & 0xf), (u_char) (addr[0] >> 4),
164 blacklists[i]) >= sizeof(query))
165 fatalx( "Can't create query, domain too long");
167 fatalx("Invalid address family received");
169 session->query[i].query = gethostbyname_async(query, NULL);
170 session->query[i].event = event_asr_run(session->query[i].query,
171 dnsbl_resolve, &(session->query[i]));
172 session->query[i].blacklist = i;
173 session->query[i].session = session;
174 evtimer_set(&(session->query[i].timeout), dnsbl_timeout,
175 &(session->query[i]));
176 evtimer_add(&(session->query[i].timeout), &timeout);
181 dnsbl_resolve(struct asr_result *result, void *arg)
183 struct dnsbl_query *query = arg;
184 struct dnsbl_session *session = query->session;
190 evtimer_del(&(query->timeout));
191 if (result->ar_hostent != NULL) {
193 smtp_filter_disconnect(session->reqid, session->token,
194 "Listed at %s", blacklists[query->blacklist]);
195 dnsbl_session_free(session);
197 dnsbl_session_query_done(session);
198 session->listed = query->blacklist;
199 smtp_filter_proceed(session->reqid, session->token);
203 if (result->ar_h_errno != HOST_NOT_FOUND) {
204 smtp_filter_disconnect(session->reqid, session->token,
205 "DNS error on %s", blacklists[query->blacklist]);
206 dnsbl_session_free(session);
210 for (i = 0; i < nblacklists; i++) {
211 if (!session->query[i].resolved)
214 smtp_filter_proceed(session->reqid, session->token);
216 dnsbl_session_free(session);
220 dnsbl_timeout(int fd, short event, void *arg)
222 struct dnsbl_query *query = arg;
223 struct dnsbl_session *session = query->session;
225 smtp_filter_disconnect(session->reqid, session->token,
226 "DNS timeout on %s", blacklists[query->blacklist]);
227 dnsbl_session_free(session);
231 dnsbl_disconnect(char *type, int version, struct timespec *tm, char *direction,
232 char *phase, uint64_t reqid)
234 struct dnsbl_session *session, search;
236 search.reqid = reqid;
237 if ((session = RB_FIND(dnsbl_sessions, &dnsbl_sessions, &search)) != NULL)
238 dnsbl_session_free(session);
242 dnsbl_dataline(char *type, int version, struct timespec *tm, char *direction,
243 char *phase, uint64_t reqid, uint64_t token, char *line)
245 struct dnsbl_session *session, search;
247 search.reqid = reqid;
248 session = RB_FIND(dnsbl_sessions, &dnsbl_sessions, &search);
250 if (session->listed != -1) {
251 smtp_filter_dataline(reqid, token, "X-Spam: yes");
252 smtp_filter_dataline(reqid, token, "X-Spam-DNSBL: Listed at %s",
253 blacklists[session->listed]);
254 session->listed = -1;
256 smtp_filter_dataline(reqid, token, "%s", line);
260 dnsbl_session_query_done(struct dnsbl_session *session)
264 for (i = 0; i < nblacklists; i++) {
265 if (!session->query[i].resolved) {
266 event_asr_abort(session->query[i].event);
267 evtimer_del(&(session->query[i].timeout));
273 dnsbl_session_free(struct dnsbl_session *session)
275 RB_REMOVE(dnsbl_sessions, &dnsbl_sessions, session);
276 dnsbl_session_query_done(session);
277 free(session->query);
282 dnsbl_session_cmp(struct dnsbl_session *s1, struct dnsbl_session *s2)
284 return (s1->reqid < s2->reqid ? -1 : s1->reqid > s2->reqid);
290 fprintf(stderr, "usage: %s [-m] blacklist [...]\n",
295 RB_GENERATE(dnsbl_sessions, dnsbl_session, entry, dnsbl_session_cmp);