1/* This file is part of Mailfromd. -*- c -*- 2 Copyright (C) 2006-2021 Sergey Poznyakoff 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 3, or (at your option) 7 any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17MF_BUILTIN_MODULE 18 19#include <sys/socket.h> 20#include <netinet/in.h> 21#include <netdb.h> 22#include <arpa/inet.h> 23#include <limits.h> 24#include "srvcfg.h" 25#include "global.h" 26 27MF_DEFUN(primitive_hostname, STRING, STRING string) 28{ 29 char *hbuf; 30 mf_status stat; 31 32 stat = resolve_ipstr(string, &hbuf); 33 MF_ASSERT(stat == mf_success, 34 mf_status_to_exception(stat), 35 _("cannot resolve IP %s"), 36 string); 37 38 pushs(env, hbuf); 39 free(hbuf); 40} 41END 42 43MF_DEFUN(primitive_resolve, STRING, STRING string, OPTIONAL, STRING domain) 44{ 45 char *ipstr; 46 mf_status stat; 47 48 if (MF_OPTVAL(domain,"")[0]) { 49 stat = resolve_ipstr_domain(string, domain, &ipstr); 50 MF_ASSERT(stat == mf_success, 51 mf_status_to_exception(stat), 52 _("cannot resolve %s.%s"), string, domain); 53 } else { 54 stat = resolve_hostname(string, &ipstr); 55 MF_ASSERT(stat == mf_success, 56 mf_status_to_exception(stat), 57 _("cannot resolve %s"), string); 58 } 59 pushs(env, ipstr); 60 free(ipstr); 61} 62END 63 64MF_DEFUN(primitive_hasmx, NUMBER, STRING string) 65{ 66 struct dns_reply repl; 67 mf_status mxstat; 68 69 mxstat = dns_to_mf_status(mx_lookup(string, 0, &repl)); 70 71 MF_ASSERT(mxstat == mf_success || mxstat == mf_not_found, 72 mf_status_to_exception(mxstat), 73 _("cannot get MX records for %s"), 74 string); 75 dns_reply_free(&repl); 76 if (mxstat == mf_success) { 77 MF_RETURN(1); 78 } 79 MF_RETURN(0); 80} 81END 82 83static dns_status 84resolve_host(const char *string, struct dns_reply *reply) 85{ 86 struct in_addr addr; 87 88 if (inet_aton(string, &addr)) { 89 dns_reply_init(reply, dns_reply_ip, 1); 90 reply->data.ip[0] = addr.s_addr; 91 return dns_success; 92 } 93 return a_lookup(string, reply); 94} 95 96static int 97dns_replies_intersect(struct dns_reply const *a, struct dns_reply const *b) 98{ 99 int i, j; 100 101 if (a->type == b->type && a->type == dns_reply_ip) { 102 for (i = 0; i < a->count; i++) 103 for (j = 0; j < b->count; j++) 104 if (a->data.ip[i] == b->data.ip[j]) 105 return 1; 106 } 107 return 0; 108} 109 110MF_DEFUN(primitive_ismx, NUMBER, STRING domain, STRING ipstr) 111{ 112 struct dns_reply areply, mxreply; 113 dns_status status; 114 int rc = 0; 115 116 status = resolve_host(ipstr, &areply); 117 MF_ASSERT(status == dns_success, 118 mf_status_to_exception(dns_to_mf_status(status)), 119 _("cannot resolve host name %s"), ipstr); 120 status = mx_lookup(domain, 1, &mxreply); 121 if (status != dns_success) { 122 dns_reply_free(&areply); 123 MF_THROW(mf_status_to_exception(dns_to_mf_status(status)), 124 _("cannot get MXs for %s"), domain); 125 } 126 rc = dns_replies_intersect(&areply, &mxreply); 127 dns_reply_free(&areply); 128 dns_reply_free(&mxreply); 129 MF_RETURN(rc); 130} 131END 132 133MF_DEFUN(relayed, NUMBER, STRING s) 134{ 135 MF_RETURN(relayed_domain_p(s)); 136} 137END 138 139MF_DEFUN(ptr_validate, NUMBER, STRING s) 140{ 141 int rc, res; 142 switch (rc = ptr_validate(s, NULL)) { 143 case dns_success: 144 res = 1; 145 break; 146 case dns_not_found: 147 res = 0; 148 break; 149 default: 150 MF_THROW(mf_status_to_exception(dns_to_mf_status(rc)), 151 _("failed to get PTR record for %s"), s); 152 } 153 MF_RETURN(res); 154} 155END 156 157MF_DEFUN(primitive_hasns, NUMBER, STRING dom) 158{ 159 struct dns_reply repl; 160 mf_status stat = dns_to_mf_status(ns_lookup(dom, 0, &repl)); 161 MF_ASSERT(stat == mf_success || stat == mf_not_found, 162 mf_status_to_exception(stat), 163 _("cannot get NS records for %s"), 164 dom); 165 dns_reply_free(&repl); 166 if (stat == mf_success) { 167 MF_RETURN(1); 168 } 169 MF_RETURN(0); 170} 171END 172 173static int 174cmp_ip(void const *a, void const *b) 175{ 176 GACOPYZ_UINT32_T ipa = ntohl(*(GACOPYZ_UINT32_T const *)a); 177 GACOPYZ_UINT32_T ipb = ntohl(*(GACOPYZ_UINT32_T const *)b); 178 if (ipa < ipb) 179 return -1; 180 if (ipa > ipb) 181 return 1; 182 return 0; 183} 184 185static int 186cmp_str(void const *a, void const *b) 187{ 188 char * const *stra = a; 189 char * const *strb = b; 190 return strcmp(*stra, *strb); 191} 192 193static int 194cmp_hostname(const void *a, const void *b) 195{ 196 return strcasecmp(*(const char**) a, *(const char**) b); 197} 198 199typedef long DNS_REPLY_COUNT; 200#define DNS_REPLY_MAX LONG_MAX 201 202struct dns_reply_storage { 203 DNS_REPLY_COUNT reply_count; 204 size_t reply_max; 205 struct dns_reply *reply_tab; 206}; 207 208static inline int 209dns_reply_entry_unused(struct dns_reply_storage *rs, DNS_REPLY_COUNT n) 210{ 211 return n == -1 || rs->reply_tab[n].type == -1; 212} 213 214static inline void 215dns_reply_entry_release(struct dns_reply_storage *rs, DNS_REPLY_COUNT n) 216{ 217 rs->reply_tab[n].type = -1; 218} 219 220static inline struct dns_reply * 221dns_reply_entry_locate(struct dns_reply_storage *rs, DNS_REPLY_COUNT n) 222{ 223 if (n < 0 || n >= rs->reply_count || dns_reply_entry_unused(rs, n)) 224 return NULL; 225 return &rs->reply_tab[n]; 226} 227 228static void * 229dns_reply_storage_alloc(void) 230{ 231 struct dns_reply_storage *rs = mu_alloc(sizeof(*rs)); 232 rs->reply_count = 0; 233 rs->reply_max = 0; 234 rs->reply_tab = NULL; 235 return rs; 236} 237 238static void 239dns_reply_storage_destroy(void *data) 240{ 241 struct dns_reply_storage *rs = data; 242 DNS_REPLY_COUNT i; 243 244 for (i = 0; i < rs->reply_count; i++) { 245 if (!dns_reply_entry_unused(rs, i)) { 246 dns_reply_free(&rs->reply_tab[i]); 247 dns_reply_entry_release(rs, i); 248 } 249 } 250 free(rs->reply_tab); 251 free(rs); 252} 253 254MF_DECLARE_DATA(DNS, dns_reply_storage_alloc, dns_reply_storage_destroy) 255 256static inline DNS_REPLY_COUNT 257dns_reply_entry_alloc(struct dns_reply_storage *rs, 258 struct dns_reply **return_reply) 259{ 260 DNS_REPLY_COUNT i; 261 262 for (i = 0; i < rs->reply_count; i++) { 263 if (dns_reply_entry_unused(rs, i)) { 264 *return_reply = &rs->reply_tab[i]; 265 return i; 266 } 267 } 268 if (rs->reply_count == DNS_REPLY_MAX) 269 return -1; 270 if (rs->reply_count == rs->reply_max) { 271 size_t n = rs->reply_max; 272 rs->reply_tab = mu_2nrealloc(rs->reply_tab, &rs->reply_max, 273 sizeof(rs->reply_tab[0])); 274 for (; n < rs->reply_max; n++) 275 dns_reply_entry_release(rs, n); 276 } 277 *return_reply = &rs->reply_tab[rs->reply_count]; 278 return rs->reply_count++; 279} 280 281MF_DEFUN(dns_reply_release, VOID, NUMBER n) 282{ 283 struct dns_reply_storage *repl = MF_GET_DATA; 284 if (!dns_reply_entry_unused(repl, n)) { 285 dns_reply_free(&repl->reply_tab[n]); 286 dns_reply_entry_release(repl, n); 287 } 288} 289END 290 291MF_DEFUN(dns_reply_count, NUMBER, NUMBER n) 292{ 293 struct dns_reply_storage *rs = MF_GET_DATA; 294 struct dns_reply *reply; 295 296 if (n == -1) 297 MF_RETURN(0); 298 if (n < 0) 299 MF_THROW(mfe_failure, 300 _("invalid DNS reply number: %ld"), n); 301 reply = dns_reply_entry_locate(rs, n); 302 if (!reply) 303 MF_THROW(mfe_failure, 304 _("no such reply: %ld"), n); 305 MF_RETURN(reply->count); 306} 307END 308 309MF_DEFUN(dns_reply_string, STRING, NUMBER n, NUMBER i) 310{ 311 struct dns_reply_storage *rs = MF_GET_DATA; 312 struct dns_reply *reply = dns_reply_entry_locate(rs, n); 313 if (!reply) 314 MF_THROW(mfe_failure, 315 _("no such reply: %ld"), n); 316 if (i < 0 || i >= reply->count) 317 MF_THROW(mfe_range, 318 _("index out of range: %ld"), i); 319 if (reply->type == dns_reply_ip) { 320 struct in_addr addr; 321 addr.s_addr = reply->data.ip[i]; 322 MF_RETURN(inet_ntoa(addr)); 323 } 324 MF_RETURN(reply->data.str[i]); 325} 326END 327 328MF_DEFUN(dns_reply_ip, STRING, NUMBER n, NUMBER i) 329{ 330 struct dns_reply_storage *rs = MF_GET_DATA; 331 struct dns_reply *reply = dns_reply_entry_locate(rs, n); 332 if (!reply) 333 MF_THROW(mfe_failure, 334 _("no such reply: %ld"), n); 335 if (i < 0 || i >= reply->count) 336 MF_THROW(mfe_range, 337 _("index out of range: %ld"), i); 338 if (reply->type == dns_reply_str) { 339 MF_THROW(mfe_failure, 340 _("can't use dns_reply_ip on string replies")); 341 } 342 MF_RETURN(reply->data.str[i]); 343} 344END 345 346enum { 347 DNS_TYPE_A = 1, 348 DNS_TYPE_NS = 2, 349 DNS_TYPE_PTR = 12, 350 DNS_TYPE_MX = 15, 351 DNS_TYPE_TXT = 16, 352}; 353 354static char *dns_type_name[] = { 355 [DNS_TYPE_A] = "a", 356 [DNS_TYPE_NS] = "ns", 357 [DNS_TYPE_PTR] = "ptr", 358 [DNS_TYPE_MX] = "mx", 359 [DNS_TYPE_TXT] = "txt" 360}; 361 362MF_DEFUN(dns_query, NUMBER, NUMBER type, STRING domain, OPTIONAL, NUMBER sort, 363 NUMBER resolve) 364{ 365 struct dns_reply_storage *rs = MF_GET_DATA; 366 dns_status dnsstat; 367 mf_status stat; 368 DNS_REPLY_COUNT ret; 369 struct dns_reply *reply; 370 int (*cmpfun)(const void *, const void *) = NULL; 371 struct in_addr addr; 372 373 ret = dns_reply_entry_alloc(rs, &reply); 374 MF_ASSERT(ret >= 0, 375 mfe_failure, 376 _("DNS reply table full")); 377 378 switch (type) { 379 case DNS_TYPE_PTR: 380 MF_ASSERT(inet_aton(domain, &addr), 381 mfe_invip, 382 _("invalid IP: %s"), domain); 383 dnsstat = ptr_lookup(addr, reply); 384 cmpfun = cmp_hostname; 385 break; 386 387 case DNS_TYPE_A: 388 dnsstat = resolve_host(domain, reply); 389 break; 390 391 case DNS_TYPE_TXT: 392 dnsstat = txt_lookup(domain, reply); 393 break; 394 395 case DNS_TYPE_MX: 396 dnsstat = mx_lookup(domain, MF_OPTVAL(resolve), reply); 397 break; 398 399 case DNS_TYPE_NS: 400 dnsstat = ns_lookup(domain, MF_OPTVAL(resolve), reply); 401 break; 402 403 default: 404 dns_reply_entry_release(rs, ret); 405 MF_THROW(mfe_failure, 406 _("unsupported type: %ld"), type); 407 } 408 409 stat = dns_to_mf_status(dnsstat); 410 411 if (!mf_resolved(stat)) { 412 dns_reply_entry_release(rs, ret); 413 MF_THROW(mf_status_to_exception(stat), 414 _("cannot get %s records for %s"), 415 dns_type_name[type], domain); 416 } 417 if (stat == mf_not_found) { 418 dns_reply_entry_release(rs, ret); 419 MF_RETURN(-1); 420 } 421 if (sort) { 422 size_t entry_size; 423 424 if (!cmpfun) { 425 if (reply->type == dns_reply_str) { 426 cmpfun = cmp_str; 427 } else { 428 cmpfun = cmp_ip; 429 } 430 } 431 if (reply->type == dns_reply_str) { 432 entry_size = sizeof(reply->data.str[0]); 433 } else { 434 entry_size = sizeof(reply->data.ip[0]); 435 } 436 437 qsort(reply->data.ptr, reply->count, entry_size, cmpfun); 438 } 439 MF_RETURN(ret); 440} 441END 442