1/*++
2/* NAME
3/*	dnsblog 8
4/* SUMMARY
5/*	Postfix DNS white/blacklist logger
6/* SYNOPSIS
7/*	\fBdnsblog\fR [generic Postfix daemon options]
8/* DESCRIPTION
9/*	The \fBdnsblog\fR(8) server implements an ad-hoc DNS
10/*	white/blacklist lookup service. This may eventually be
11/*	replaced by an UDP client that is built directly into the
12/*	\fBpostscreen\fR(8) server.
13/* PROTOCOL
14/* .ad
15/* .fi
16/*	With each connection, the \fBdnsblog\fR(8) server receives
17/*	a DNS white/blacklist domain name, IP address, and an ID.
18/*	If the address is listed under the DNS white/blacklist, the
19/*	\fBdnsblog\fR(8) server logs the match and replies with the
20/*	query arguments plus an address list with the resulting IP
21/*	addresses separated by whitespace.  Otherwise it replies
22/*	with the query arguments plus an empty address list.  Finally,
23/*	The \fBdnsblog\fR(8) server closes the connection.
24/* DIAGNOSTICS
25/*	Problems and transactions are logged to \fBsyslogd\fR(8).
26/* CONFIGURATION PARAMETERS
27/* .ad
28/* .fi
29/*	Changes to \fBmain.cf\fR are picked up automatically, as
30/*	\fBdnsblog\fR(8) processes run for only a limited amount
31/*	of time. Use the command "\fBpostfix reload\fR" to speed
32/*	up a change.
33/*
34/*	The text below provides only a parameter summary. See
35/*	\fBpostconf\fR(5) for more details including examples.
36/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
37/*	The default location of the Postfix main.cf and master.cf
38/*	configuration files.
39/* .IP "\fBdaemon_timeout (18000s)\fR"
40/*	How much time a Postfix daemon process may take to handle a
41/*	request before it is terminated by a built-in watchdog timer.
42/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR"
43/*	Optional list of DNS white/blacklist domains, filters and weight
44/*	factors.
45/* .IP "\fBipc_timeout (3600s)\fR"
46/*	The time limit for sending or receiving information over an internal
47/*	communication channel.
48/* .IP "\fBprocess_id (read-only)\fR"
49/*	The process ID of a Postfix command or daemon process.
50/* .IP "\fBprocess_name (read-only)\fR"
51/*	The process name of a Postfix command or daemon process.
52/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
53/*	The location of the Postfix top-level queue directory.
54/* .IP "\fBsyslog_facility (mail)\fR"
55/*	The syslog facility of Postfix logging.
56/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
57/*	The mail system name that is prepended to the process name in syslog
58/*	records, so that "smtpd" becomes, for example, "postfix/smtpd".
59/* SEE ALSO
60/*	smtpd(8), Postfix SMTP server
61/*	postconf(5), configuration parameters
62/*	syslogd(5), system logging
63/* LICENSE
64/* .ad
65/* .fi
66/*	The Secure Mailer license must be distributed with this software.
67/* HISTORY
68/* .ad
69/* .fi
70/*	This service was introduced with Postfix version 2.8.
71/* AUTHOR(S)
72/*	Wietse Venema
73/*	IBM T.J. Watson Research
74/*	P.O. Box 704
75/*	Yorktown Heights, NY 10598, USA
76/*--*/
77
78/* System library. */
79
80#include <sys_defs.h>
81
82/* Utility library. */
83
84#include <msg.h>
85#include <vstream.h>
86#include <vstring.h>
87#include <argv.h>
88#include <myaddrinfo.h>
89#include <valid_hostname.h>
90#include <sock_addr.h>
91
92/* Global library. */
93
94#include <mail_conf.h>
95#include <mail_version.h>
96#include <mail_proto.h>
97#include <mail_params.h>
98
99/* DNS library. */
100
101#include <dns.h>
102
103/* Server skeleton. */
104
105#include <mail_server.h>
106
107/* Application-specific. */
108
109 /*
110  * Tunable parameters.
111  */
112int     var_dnsblog_delay;
113
114 /*
115  * Static so we don't allocate and free on every request.
116  */
117static VSTRING *rbl_domain;
118static VSTRING *addr;
119static VSTRING *query;
120static VSTRING *why;
121static VSTRING *result;
122
123 /*
124  * Silly little macros.
125  */
126#define STR(x)			vstring_str(x)
127#define LEN(x)			VSTRING_LEN(x)
128
129/* static void dnsblog_query - query DNSBL for client address */
130
131static VSTRING *dnsblog_query(VSTRING *result, const char *dnsbl_domain,
132			              const char *addr)
133{
134    const char *myname = "dnsblog_query";
135    ARGV   *octets;
136    int     i;
137    struct addrinfo *res;
138    unsigned char *ipv6_addr;
139    int     dns_status;
140    DNS_RR *addr_list;
141    DNS_RR *rr;
142    MAI_HOSTADDR_STR hostaddr;
143
144    if (msg_verbose)
145	msg_info("%s: addr %s dnsbl_domain %s",
146		 myname, addr, dnsbl_domain);
147
148    VSTRING_RESET(query);
149
150    /*
151     * Reverse the client IPV6 address, represented as 32 hexadecimal
152     * nibbles. We use the binary address to avoid tricky code. Asking for an
153     * AAAA record makes no sense here. Just like with IPv4 we use the lookup
154     * result as a bit mask, not as an IP address.
155     */
156#ifdef HAS_IPV6
157    if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) {
158	if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0
159	    || res->ai_family != PF_INET6)
160	    msg_fatal("%s: unable to convert address %s", myname, addr);
161	ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr);
162	for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--)
163	    vstring_sprintf_append(query, "%x.%x.",
164				   ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4);
165	freeaddrinfo(res);
166    } else
167#endif
168
169	/*
170	 * Reverse the client IPV4 address, represented as four decimal octet
171	 * values. We use the textual address for convenience.
172	 */
173    {
174	octets = argv_split(addr, ".");
175	for (i = octets->argc - 1; i >= 0; i--) {
176	    vstring_strcat(query, octets->argv[i]);
177	    vstring_strcat(query, ".");
178	}
179	argv_free(octets);
180    }
181
182    /*
183     * Tack on the RBL domain name and query the DNS for an A record.
184     */
185    vstring_strcat(query, dnsbl_domain);
186    dns_status = dns_lookup(STR(query), T_A, 0, &addr_list, (VSTRING *) 0, why);
187    VSTRING_RESET(result);
188    if (dns_status == DNS_OK) {
189	for (rr = addr_list; rr != 0; rr = rr->next) {
190	    if (dns_rr_to_pa(rr, &hostaddr) == 0) {
191		msg_warn("%s: skipping reply record type %s for query %s: %m",
192			 myname, dns_strtype(rr->type), STR(query));
193	    } else {
194		msg_info("addr %s listed by domain %s as %s",
195			 addr, dnsbl_domain, hostaddr.buf);
196		if (LEN(result) > 0)
197		    vstring_strcat(result, " ");
198		vstring_strcat(result, hostaddr.buf);
199	    }
200	}
201	dns_rr_free(addr_list);
202    } else if (dns_status == DNS_NOTFOUND) {
203	if (msg_verbose)
204	    msg_info("%s: addr %s not listed by domain %s",
205		     myname, addr, dnsbl_domain);
206    } else {
207	msg_warn("%s: lookup error for DNS query %s: %s",
208		 myname, STR(query), STR(why));
209    }
210    VSTRING_TERMINATE(result);
211    return (result);
212}
213
214/* dnsblog_service - perform service for client */
215
216static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
217			            char **argv)
218{
219    int     request_id;
220
221    /*
222     * Sanity check. This service takes no command-line arguments.
223     */
224    if (argv[0])
225	msg_fatal("unexpected command-line argument: %s", argv[0]);
226
227    /*
228     * This routine runs whenever a client connects to the socket dedicated
229     * to the dnsblog service. All connection-management stuff is handled by
230     * the common code in single_server.c.
231     */
232    if (attr_scan(client_stream,
233		  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
234		  ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, rbl_domain,
235		  ATTR_TYPE_STR, MAIL_ATTR_ACT_CLIENT_ADDR, addr,
236		  ATTR_TYPE_INT, MAIL_ATTR_LABEL, &request_id,
237		  ATTR_TYPE_END) == 3) {
238	(void) dnsblog_query(result, STR(rbl_domain), STR(addr));
239	if (var_dnsblog_delay > 0)
240	    sleep(var_dnsblog_delay);
241	attr_print(client_stream, ATTR_FLAG_NONE,
242		   ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, STR(rbl_domain),
243		   ATTR_TYPE_STR, MAIL_ATTR_ACT_CLIENT_ADDR, STR(addr),
244		   ATTR_TYPE_INT, MAIL_ATTR_LABEL, request_id,
245		   ATTR_TYPE_STR, MAIL_ATTR_RBL_ADDR, STR(result),
246		   ATTR_TYPE_END);
247	vstream_fflush(client_stream);
248    }
249}
250
251/* post_jail_init - post-jail initialization */
252
253static void post_jail_init(char *unused_name, char **unused_argv)
254{
255    rbl_domain = vstring_alloc(100);
256    addr = vstring_alloc(100);
257    query = vstring_alloc(100);
258    why = vstring_alloc(100);
259    result = vstring_alloc(100);
260    var_use_limit = 0;
261}
262
263MAIL_VERSION_STAMP_DECLARE;
264
265/* main - pass control to the multi-threaded skeleton */
266
267int     main(int argc, char **argv)
268{
269    static const CONFIG_TIME_TABLE time_table[] = {
270	VAR_DNSBLOG_DELAY, DEF_DNSBLOG_DELAY, &var_dnsblog_delay, 0, 0,
271	0,
272    };
273
274    /*
275     * Fingerprint executables and core dumps.
276     */
277    MAIL_VERSION_STAMP_ALLOCATE;
278
279    single_server_main(argc, argv, dnsblog_service,
280		       MAIL_SERVER_TIME_TABLE, time_table,
281		       MAIL_SERVER_POST_INIT, post_jail_init,
282		       MAIL_SERVER_UNLIMITED,
283		       0);
284}
285