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