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 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 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 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 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