summaryrefslogtreecommitdiffstats
path: root/src/dnsblog/dnsblog.c
blob: bc87c4b5948eee0a8854b5bd99981cc892bf2c5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/*++
/* NAME
/*	dnsblog 8
/* SUMMARY
/*	Postfix DNS allow/denylist logger
/* SYNOPSIS
/*	\fBdnsblog\fR [generic Postfix daemon options]
/* DESCRIPTION
/*	The \fBdnsblog\fR(8) server implements an ad-hoc DNS
/*	allow/denylist lookup service. This may eventually be
/*	replaced by an UDP client that is built directly into the
/*	\fBpostscreen\fR(8) server.
/* PROTOCOL
/* .ad
/* .fi
/*	With each connection, the \fBdnsblog\fR(8) server receives
/*	a DNS allow/denylist domain name, an IP address, and an ID.
/*	If the IP address is listed under the DNS allow/denylist, the
/*	\fBdnsblog\fR(8) server logs the match and replies with the
/*	query arguments plus an address list with the resulting IP
/*	addresses, separated by whitespace, and the reply TTL.
/*	Otherwise it replies with the query arguments plus an empty
/*	address list and the reply TTL; the reply TTL is -1 if there
/*	is no reply, or a negative reply that contains no SOA record.
/*	Finally, the \fBdnsblog\fR(8) server closes the connection.
/* DIAGNOSTICS
/*	Problems and transactions are logged to \fBsyslogd\fR(8)
/*	or \fBpostlogd\fR(8).
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
/*	Changes to \fBmain.cf\fR are picked up automatically, as
/*	\fBdnsblog\fR(8) processes run for only a limited amount
/*	of time. Use the command "\fBpostfix reload\fR" to speed
/*	up a change.
/*
/*	The text below provides only a parameter summary. See
/*	\fBpostconf\fR(5) for more details including examples.
/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
/*	The default location of the Postfix main.cf and master.cf
/*	configuration files.
/* .IP "\fBdaemon_timeout (18000s)\fR"
/*	How much time a Postfix daemon process may take to handle a
/*	request before it is terminated by a built-in watchdog timer.
/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR"
/*	Optional list of DNS allow/denylist domains, filters and weight
/*	factors.
/* .IP "\fBipc_timeout (3600s)\fR"
/*	The time limit for sending or receiving information over an internal
/*	communication channel.
/* .IP "\fBprocess_id (read-only)\fR"
/*	The process ID of a Postfix command or daemon process.
/* .IP "\fBprocess_name (read-only)\fR"
/*	The process name of a Postfix command or daemon process.
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
/*	The location of the Postfix top-level queue directory.
/* .IP "\fBsyslog_facility (mail)\fR"
/*	The syslog facility of Postfix logging.
/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
/*	A prefix that is prepended to the process name in syslog
/*	records, so that, for example, "smtpd" becomes "prefix/smtpd".
/* .PP
/*	Available in Postfix 3.3 and later:
/* .IP "\fBservice_name (read-only)\fR"
/*	The master.cf service name of a Postfix daemon process.
/* SEE ALSO
/*	smtpd(8), Postfix SMTP server
/*	postconf(5), configuration parameters
/*	postlogd(8), Postfix logging
/*	syslogd(8), system logging
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* HISTORY
/* .ad
/* .fi
/*	This service was introduced with Postfix version 2.8.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*--*/

/* System library. */

#include <sys_defs.h>
#include <limits.h>

/* Utility library. */

#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <argv.h>
#include <myaddrinfo.h>
#include <valid_hostname.h>
#include <sock_addr.h>

/* Global library. */

#include <mail_conf.h>
#include <mail_version.h>
#include <mail_proto.h>
#include <mail_params.h>

/* DNS library. */

#include <dns.h>

/* Server skeleton. */

#include <mail_server.h>

/* Application-specific. */

 /*
  * Tunable parameters.
  */
int     var_dnsblog_delay;

 /*
  * Static so we don't allocate and free on every request.
  */
static VSTRING *rbl_domain;
static VSTRING *addr;
static VSTRING *query;
static VSTRING *why;
static VSTRING *result;

 /*
  * Silly little macros.
  */
#define STR(x)			vstring_str(x)
#define LEN(x)			VSTRING_LEN(x)

/* static void dnsblog_query - query DNSBL for client address */

static VSTRING *dnsblog_query(VSTRING *result, int *result_ttl,
			              const char *dnsbl_domain,
			              const char *addr)
{
    const char *myname = "dnsblog_query";
    ARGV   *octets;
    int     i;
    struct addrinfo *res;
    unsigned char *ipv6_addr;
    int     dns_status;
    DNS_RR *addr_list;
    DNS_RR *rr;
    MAI_HOSTADDR_STR hostaddr;

    if (msg_verbose)
	msg_info("%s: addr %s dnsbl_domain %s",
		 myname, addr, dnsbl_domain);

    VSTRING_RESET(query);

    /*
     * Reverse the client IPV6 address, represented as 32 hexadecimal
     * nibbles. We use the binary address to avoid tricky code. Asking for an
     * AAAA record makes no sense here. Just like with IPv4 we use the lookup
     * result as a bit mask, not as an IP address.
     */
#ifdef HAS_IPV6
    if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) {
	if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0
	    || res->ai_family != PF_INET6)
	    msg_fatal("%s: unable to convert address %s", myname, addr);
	ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr);
	for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--)
	    vstring_sprintf_append(query, "%x.%x.",
				   ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4);
	freeaddrinfo(res);
    } else
#endif

	/*
	 * Reverse the client IPV4 address, represented as four decimal octet
	 * values. We use the textual address for convenience.
	 */
    {
	octets = argv_split(addr, ".");
	for (i = octets->argc - 1; i >= 0; i--) {
	    vstring_strcat(query, octets->argv[i]);
	    vstring_strcat(query, ".");
	}
	argv_free(octets);
    }

    /*
     * Tack on the RBL domain name and query the DNS for an A record.
     */
    vstring_strcat(query, dnsbl_domain);
    dns_status = dns_lookup_x(STR(query), T_A, 0, &addr_list, (VSTRING *) 0,
			      why, (int *) 0, DNS_REQ_FLAG_NCACHE_TTL);

    /*
     * We return the lowest TTL in the response from the A record(s) if
     * found, or from the SOA record(s) if available. If the reply specifies
     * no TTL, or if the query fails, we return a TTL of -1.
     */
    VSTRING_RESET(result);
    *result_ttl = -1;
    if (dns_status == DNS_OK) {
	for (rr = addr_list; rr != 0; rr = rr->next) {
	    if (dns_rr_to_pa(rr, &hostaddr) == 0) {
		msg_warn("%s: skipping reply record type %s for query %s: %m",
			 myname, dns_strtype(rr->type), STR(query));
	    } else {
		msg_info("addr %s listed by domain %s as %s",
			 addr, dnsbl_domain, hostaddr.buf);
		if (LEN(result) > 0)
		    vstring_strcat(result, " ");
		vstring_strcat(result, hostaddr.buf);
		/* Grab the positive reply TTL. */
		if (*result_ttl < 0 || *result_ttl > rr->ttl)
		    *result_ttl = rr->ttl;
	    }
	}
	dns_rr_free(addr_list);
    } else if (dns_status == DNS_NOTFOUND) {
	if (msg_verbose)
	    msg_info("%s: addr %s not listed by domain %s",
		     myname, addr, dnsbl_domain);
	/* Grab the negative reply TTL. */
	for (rr = addr_list; rr != 0; rr = rr->next) {
	    if (rr->type == T_SOA && (*result_ttl < 0 || *result_ttl > rr->ttl))
		*result_ttl = rr->ttl;
	}
	dns_rr_free(addr_list);
    } else {
	msg_warn("%s: lookup error for DNS query %s: %s",
		 myname, STR(query), STR(why));
    }
    VSTRING_TERMINATE(result);
    return (result);
}

/* dnsblog_service - perform service for client */

static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
			            char **argv)
{
    int     request_id;
    int     result_ttl;

    /*
     * Sanity check. This service takes no command-line arguments.
     */
    if (argv[0])
	msg_fatal("unexpected command-line argument: %s", argv[0]);

    /*
     * This routine runs whenever a client connects to the socket dedicated
     * to the dnsblog service. All connection-management stuff is handled by
     * the common code in single_server.c.
     */
    if (attr_scan(client_stream,
		  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
		  RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, rbl_domain),
		  RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, addr),
		  RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id),
		  ATTR_TYPE_END) == 3) {
	(void) dnsblog_query(result, &result_ttl, STR(rbl_domain), STR(addr));
	if (var_dnsblog_delay > 0)
	    sleep(var_dnsblog_delay);
	attr_print(client_stream, ATTR_FLAG_NONE,
		   SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, STR(rbl_domain)),
		   SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, STR(addr)),
		   SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id),
		   SEND_ATTR_STR(MAIL_ATTR_RBL_ADDR, STR(result)),
		   SEND_ATTR_INT(MAIL_ATTR_TTL, result_ttl),
		   ATTR_TYPE_END);
	vstream_fflush(client_stream);
    }
}

/* post_jail_init - post-jail initialization */

static void post_jail_init(char *unused_name, char **unused_argv)
{
    rbl_domain = vstring_alloc(100);
    addr = vstring_alloc(100);
    query = vstring_alloc(100);
    why = vstring_alloc(100);
    result = vstring_alloc(100);
    var_use_limit = 0;
}

MAIL_VERSION_STAMP_DECLARE;

/* main - pass control to the multi-threaded skeleton */

int     main(int argc, char **argv)
{
    static const CONFIG_TIME_TABLE time_table[] = {
	VAR_DNSBLOG_DELAY, DEF_DNSBLOG_DELAY, &var_dnsblog_delay, 0, 0,
	0,
    };

    /*
     * Fingerprint executables and core dumps.
     */
    MAIL_VERSION_STAMP_ALLOCATE;

    single_server_main(argc, argv, dnsblog_service,
		       CA_MAIL_SERVER_TIME_TABLE(time_table),
		       CA_MAIL_SERVER_POST_INIT(post_jail_init),
		       CA_MAIL_SERVER_UNLIMITED,
		       CA_MAIL_SERVER_RETIRE_ME,
		       0);
}