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