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