/* This file is part of Mailfromd.
Copyright (C) 2005-2020 Sergey Poznyakoff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "libmf.h"
#include "dns.h"
#define DEFAULT_QFLAGS \
(adns_qf_quoteok_cname|adns_qf_cname_loose|adns_qf_quoteok_query)
static mu_debug_handle_t debug_handle;
static adns_state state;
static void
dns_log_cb(adns_state ads, void *logfndata, const char *fmt, va_list al)
{
/* FIXME: Could have used just:
mu_diag_vprintf(MU_DIAG_DEBUG, fmt, al);
but it will emit \e directives in the middle of the string, which
upsets the mailutils' logstream implementation.
A possible work over would be to use logfndata to select between
mu_diag_vprintf,mu_diag_cont_vprintf or appropriate mu_debug_log_
call.
For the time being, a simplified approach is used: */
mu_stream_vprintf(mu_strerr, fmt, al);
}
void
dnsbase_init(void)
{
if (!debug_handle)
debug_handle = mu_debug_register_category("dns");
}
void
dnsbase_real_init(char *configtext)
{
int rc;
int flags;
mu_debug_level_t lev;
flags = adns_if_nosigpipe;
if (mu_debug_get_category_level(debug_handle, &lev) == 0
&& (lev & MU_DEBUG_LEVEL_MASK(MU_DEBUG_TRACE9)))
flags |= adns_if_debug;
rc = adns_init_logfn(&state, flags, configtext, dns_log_cb, NULL);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR, "adns_init", NULL, rc);
exit(1);
}
}
void
dnsbase_file_init(char *filename)
{
if (!filename)
dnsbase_real_init(NULL);
else {
mu_stream_t str;
mu_off_t sz;
int rc;
char *cfg;
rc = mu_file_stream_create(&str, filename, MU_STREAM_READ);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR, "mu_file_stream_create",
filename, rc);
return;
}
rc = mu_stream_size(str, &sz);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_size",
filename, rc);
mu_stream_destroy(&str);
return;
}
if (sz > ((size_t)~0)) {
mu_error(_("%s too big"), filename);
mu_stream_destroy(&str);
return;
}
cfg = mu_alloc(sz + 1);
rc = mu_stream_read(str, cfg, sz, NULL);
mu_stream_destroy(&str);
if (rc) {
mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read",
filename, rc);
return;
}
cfg[sz] = 0;
dnsbase_real_init(cfg);
free(cfg);
}
}
static adns_state
get_state(void)
{
if (!state)
dnsbase_real_init(NULL);
return state;
}
static inline size_t
dns_reply_elsize(struct dns_reply *reply)
{
switch (reply->type) {
case dns_reply_ip:
return sizeof(reply->data.ip[0]);
case dns_reply_str:
return sizeof(reply->data.str[0]);
}
abort();
}
void
dns_reply_init(struct dns_reply *reply, dns_reply_type type, size_t count)
{
reply->type = type;
reply->count = count;
reply->maxcount = count;
if (count)
reply->data.ptr = mu_calloc(count, dns_reply_elsize(reply));
else
reply->data.ptr = NULL;
}
void
dns_reply_ip_push(struct dns_reply *reply, void *item)
{
if (reply->count == reply->maxcount)
reply->data.ip = mu_2nrealloc(reply->data.ip,
&reply->maxcount,
sizeof(reply->data.ip[0]));
reply->data.ip[reply->count++] = *(GACOPYZ_UINT32_T*)item;
}
void
dns_reply_str_push(struct dns_reply *reply, void *item)
{
if (reply->count == reply->maxcount)
reply->data.str = mu_2nrealloc(reply->data.ip,
&reply->maxcount,
sizeof(reply->data.str[0]));
reply->data.str[reply->count++] = item;
}
void
dns_reply_push(struct dns_reply *reply, void *item)
{
switch (reply->type) {
case dns_reply_ip:
dns_reply_ip_push(reply, item);
break;
case dns_reply_str:
dns_reply_str_push(reply, item);
break;
default:
abort();
}
}
void
dns_reply_free(struct dns_reply *reply)
{
int i;
switch (reply->type) {
case dns_reply_str:
for (i = 0; i < reply->count; i++)
free(reply->data.str[i]);
free(reply->data.str);
break;
case dns_reply_ip:
free(reply->data.ip);
break;
}
}
int
dns_str_is_ipv4(const char *addr)
{
int dot_count;
int digit_count;
dot_count = 0;
digit_count = 0;
while (*addr != 0) {
if (*addr == '.') {
if (++dot_count > 4)
return 0;
digit_count = 0;
} else if (!(isdigit(*addr) && ++digit_count <= 3)) {
return 0;
}
addr++;
}
return dot_count == 3;
}
static int
errno_to_dns_status(int e)
{
switch (e) {
case 0:
return dns_success;
case EAGAIN:
#ifdef EINPROGRESS
case EINPROGRESS:
#endif
#ifdef ETIMEDOUT
case ETIMEDOUT:
#endif
return dns_temp_failure;
default:
return dns_failure;
}
}
/* Table of correspondence between ADNS status codes and dns status.
Values are increased by 1 to be able to tell whether the entry is
initialized or not. */
int adns_to_dns_tab[] = {
#define STAT(s) ((s)+1)
[adns_s_ok] = STAT(dns_success),
[adns_s_nomemory] = STAT(dns_failure),
[adns_s_unknownrrtype] = STAT(dns_failure),
[adns_s_systemfail] = STAT(dns_failure),
/* remotely induced errors), detected locally */
[adns_s_timeout] = STAT(dns_temp_failure),
[adns_s_allservfail] = STAT(dns_temp_failure),
[adns_s_norecurse] = STAT(dns_temp_failure),
[adns_s_invalidresponse] = STAT(dns_failure),
[adns_s_unknownformat] = STAT(dns_failure),
/* remotely induced errors), reported by remote server to us */
[adns_s_rcodeservfail] = STAT(dns_not_found),
[adns_s_rcodeformaterror] = STAT(dns_not_found),
[adns_s_rcodenotimplemented] = STAT(dns_not_found),
[adns_s_rcoderefused] = STAT(dns_not_found),
[adns_s_rcodeunknown] = STAT(dns_not_found),
/* remote configuration errors */
[adns_s_inconsistent] = STAT(dns_not_found),
[adns_s_prohibitedcname] = STAT(dns_not_found),
[adns_s_answerdomaininvalid] = STAT(dns_not_found),
[adns_s_answerdomaintoolong] = STAT(dns_not_found),
[adns_s_invaliddata] = STAT(dns_not_found),
/* permanent problems with the query */
[adns_s_querydomainwrong] = STAT(dns_failure),
[adns_s_querydomaininvalid] = STAT(dns_failure),
[adns_s_querydomaintoolong] = STAT(dns_failure),
/* permanent errors */
[adns_s_nxdomain] = STAT(dns_not_found),
[adns_s_nodata] = STAT(dns_not_found),
#undef STAT
};
/* Convert ADNS status code E to DNS status. */
static int
adns_to_dns_status(int e)
{
int r;
/* If it is negative, fail right away */
if (e < 0)
return dns_failure;
/* If it is not in table, it still can be a valid, but unhandled
value */
if (e >= MU_ARRAY_SIZE(adns_to_dns_tab))
return e < adns_s_max_permfail ? dns_not_found : dns_failure;
/* Now, look up in the table */
if ((r = adns_to_dns_tab[e]) > 0)
return r - 1;
/* If not found in table, use adns_s_max_ constants to decide the
error class.
*/
if (e < adns_s_max_localfail)
return dns_failure;
if (e < adns_s_max_remotefail)
return dns_not_found;
if (e < adns_s_max_tempfail)
return dns_temp_failure;
if (e < adns_s_max_misconfig)
return dns_not_found;
if (e < adns_s_max_misquery)
return dns_not_found;
return dns_not_found;
}
dns_status
soa_check(const char *name, int ip, struct dns_reply *reply)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
rc = adns_synchronous(get_state(), name, adns_r_soa_raw,
DEFAULT_QFLAGS,
&ans);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status == dns_success) {
if (ip) {
status = a_lookup(ans->rrs.soa->mname, reply);
} else {
dns_reply_init(reply, dns_reply_str, 1);
reply->data.str[0] = mu_strdup (ans->rrs.soa->mname);
}
free(ans);
}
return status;
}
static dns_status
dns_reply_resolve(struct dns_reply *reply)
{
size_t i;
struct dns_reply res;
dns_reply_init(&res, dns_reply_ip, 0);
for (i = 0; i < reply->count; i++) {
struct dns_reply r;
dns_status stat = a_lookup(reply->data.str[i], &r);
if (stat == dns_success) {
size_t n;
for (n = 0; n < r.count; n++) {
dns_reply_push(&res, &r.data.ip[n]);
}
dns_reply_free(&r);
}
}
dns_reply_free(reply);
*reply = res;
if (res.count == 0)
return dns_not_found;
return dns_success;
}
/* Return MX records for the given HOST. */
dns_status
mx_lookup(const char *host, int resolve, struct dns_reply *reply)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
int i;
rc = adns_synchronous(get_state(), host, adns_r_mx,
DEFAULT_QFLAGS,
&ans);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status != dns_success)
return status;
dns_reply_init(reply, dns_reply_str, ans->nrrs);
for (i = 0; i < ans->nrrs; i++)
reply->data.str[i] = mu_strdup(ans->rrs.inthostaddr[i].ha.host);
free(ans);
if (resolve)
status = dns_reply_resolve(reply);
return status;
}
typedef char IPBUF[3*4+3+1];
int
dns_reverse_ipstr(const char *ipstr, char *revipstr)
{
int i;
const char *p;
char *q;
q = revipstr + strlen(ipstr);
*q = 0;
for (i = 0, p = ipstr; *p && i < 4; i++) {
int len;
for (len = 0; p[len] && p[len] != '.'; len++)
;
q -= len;
memcpy(q, p, len);
if (q > revipstr)
*--q = '.';
p += len;
if (*p == '.')
p++;
}
return *p || i != 4;
}
dns_status
dns_resolve_ipstr(const char *ipstr, const char *domain, char **hbuf)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
char *name;
adns_rrtype type;
if (!domain || strcasecmp(domain, "in-addr.arpa") == 0) {
IPBUF ipbuf;
if (!dns_str_is_ipv4(ipstr))
return dns_failure;
if (dns_reverse_ipstr(ipstr, ipbuf))
return dns_failure;
mu_asprintf(&name, "%s.in-addr.arpa", ipbuf);
type = adns_r_ptr_raw;
} else {
mu_asprintf(&name, "%s.%s", ipstr, domain);
type = adns_r_a;
}
rc = adns_synchronous(get_state(), name, type,
DEFAULT_QFLAGS,
&ans);
free(name);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status == dns_success) {
if (ans->type == adns_r_ptr_raw) {
*hbuf = mu_strdup(ans->rrs.str[0]);
} else {
*hbuf = mu_strdup(inet_ntoa(ans->rrs.inaddr[0]));
}
}
free(ans);
return status;
}
dns_status
dns_resolve_hostname(const char *host, char **ipbuf)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
rc = adns_synchronous(get_state(), host, adns_r_a,
DEFAULT_QFLAGS,
&ans);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status == dns_success)
*ipbuf = mu_strdup(inet_ntoa(ans->rrs.inaddr[0]));
free(ans);
return status;
}
dns_status
a_lookup(const char *host, struct dns_reply *reply)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
rc = adns_synchronous(get_state(), host, adns_r_a,
DEFAULT_QFLAGS,
&ans);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status == dns_success) {
int i;
dns_reply_init(reply, dns_reply_ip, ans->nrrs);
for (i = 0; i < ans->nrrs; i++)
reply->data.ip[i] = ans->rrs.inaddr[i].s_addr;
}
free(ans);
return status;
}
dns_status
ptr_lookup(struct in_addr ip, struct dns_reply *reply)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
char *name;
ip.s_addr = ntohl(ip.s_addr);
mu_asprintf(&name, "%s.in-addr.arpa", inet_ntoa(ip));
rc = adns_synchronous(get_state(), name, adns_r_ptr_raw,
DEFAULT_QFLAGS,
&ans);
free(name);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status == dns_success) {
int i;
dns_reply_init(reply, dns_reply_str, ans->nrrs);
for (i = 0; i < ans->nrrs; i++)
reply->data.str[i] = mu_strdup(ans->rrs.str[i]);
}
free(ans);
return status;
}
dns_status
txt_lookup(const char *name, struct dns_reply *reply)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
rc = adns_synchronous(get_state(), name, adns_r_txt,
DEFAULT_QFLAGS,
&ans);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status == dns_success) {
int i;
dns_reply_init(reply, dns_reply_str, ans->nrrs);
for (i = 0; i < ans->nrrs; i++) {
size_t l = 0;
int j;
for (j = 0; ans->rrs.manyistr[i][j].i > 0; j++)
l += ans->rrs.manyistr[i][j].i;
reply->data.str[i] = mu_alloc(l + 1);
reply->data.str[i][0] = 0;
l = 0;
for (j = 0; ans->rrs.manyistr[i][j].i > 0; j++) {
memcpy(reply->data.str[i] + l,
ans->rrs.manyistr[i][j].str,
ans->rrs.manyistr[i][j].i);
l += ans->rrs.manyistr[i][j].i;
}
reply->data.str[i][l] = 0;
}
}
free(ans);
return status;
}
#define VSPF1_STR "v=spf1"
#define VSPF1_LEN (sizeof(VSPF1_STR)-1)
dns_status
spf_lookup(const char *domain, char **rec)
{
dns_status status;
struct dns_reply reply;
status = txt_lookup(domain, &reply);
if (status == dns_success) {
int i;
status = dns_not_found;
for (i = 0; i < reply.count; i++) {
if (mu_c_strncasecmp(reply.data.str[i],
VSPF1_STR, VSPF1_LEN) == 0
&& (reply.data.str[i][VSPF1_LEN] == 0
|| mu_isspace(reply.data.str[i][VSPF1_LEN]))) {
*rec = mu_strdup(reply.data.str[i]);
status = dns_success;
break;
}
}
dns_reply_free(&reply);
}
return status;
}
dns_status
dkim_lookup(const char *domain, const char *sel, char ***retval)
{
dns_status status;
struct dns_reply reply;
char *dk;
if (mu_asprintf(&dk, "%s._domainkey.%s", sel, domain))
mu_alloc_die();
status = txt_lookup(dk, &reply);
free(dk);
if (status == dns_success) {
int i;
char **rv = mu_calloc(reply.count + 1, sizeof(*rv));
for (i = 0; i < reply.count; i++) {
rv[i] = mu_strdup(reply.data.str[i]);
}
*retval = rv;
dns_reply_free(&reply);
}
return status;
}
/* rfc4408, chapter 5.5 */
dns_status
ptr_validate(const char *ipstr, struct dns_reply *reply)
{
struct in_addr ip;
size_t i;
dns_status status;
struct dns_reply ptr_reply;
dns_status result = dns_not_found;
if (!inet_aton(ipstr, &ip))
return dns_failure;
status = ptr_lookup(ip, &ptr_reply);
if (status != dns_success)
return status;
if (reply)
dns_reply_init(reply, dns_reply_str, 0);
for (i = 0; i < ptr_reply.count; i++) {
struct dns_reply r;
status = a_lookup(ptr_reply.data.str[i], &r);
if (status == dns_success) {
size_t k;
for (k = 0; k < r.count; k++) {
if (r.data.ip[k] == ip.s_addr) {
result = dns_success;
if (reply)
dns_reply_push(reply,
mu_strdup(ptr_reply.data.str[i]));
break;
}
}
dns_reply_free(&r);
}
}
dns_reply_free(&ptr_reply);
return result;
}
mf_status
dns_to_mf_status(dns_status stat)
{
return (mf_status) stat;
}
dns_status
mf_to_dns_status(mf_status stat)
{
return (dns_status) stat;
}
mf_status
resolve_ipstr_domain(const char *ipstr, const char *domain, char **phbuf)
{
char *hbuf;
dns_status dstat;
mu_debug(debug_handle, MU_DEBUG_TRACE8,
("Getting canonical name for %s", ipstr));
dstat = dns_resolve_ipstr(ipstr, domain, &hbuf);
switch (dstat) {
case dns_success:
mu_debug(debug_handle, MU_DEBUG_TRACE8,
("%s resolved to %s", ipstr, hbuf));
*phbuf = hbuf;
break;
default:
mu_debug(debug_handle, MU_DEBUG_TRACE8,
("%s not resolved", ipstr));
}
return dns_to_mf_status(dstat);
}
mf_status
resolve_ipstr(const char *ipstr, char **phbuf)
{
return resolve_ipstr_domain(ipstr, NULL, phbuf);
}
mf_status
resolve_hostname(const char *host, char **pipbuf)
{
char *ipbuf;
dns_status dstat;
mu_debug(debug_handle, MU_DEBUG_TRACE8,
("Getting IP address for %s", host));
dstat = dns_resolve_hostname(host, &ipbuf);
switch (dstat) {
case dns_success:
mu_debug(debug_handle, MU_DEBUG_TRACE8,
("%s resolved to %s", host, ipbuf));
*pipbuf = ipbuf;
break;
default:
mu_debug(debug_handle, MU_DEBUG_TRACE8,
("%s not resolved", host));
}
return dns_to_mf_status(dstat);
}
/* Return NS records for the given DOMAIN. */
dns_status
ns_lookup(const char *domain, int resolve, struct dns_reply *reply)
{
dns_status status = dns_failure;
int rc;
adns_answer *ans;
int i;
rc = adns_synchronous(get_state(), domain, adns_r_ns_raw,
DEFAULT_QFLAGS,
&ans);
if (rc)
return errno_to_dns_status(rc);
status = adns_to_dns_status(ans->status);
if (status != dns_success)
return status;
dns_reply_init(reply, dns_reply_str, ans->nrrs);
for (i = 0; i < ans->nrrs; i++)
reply->data.str[i] = mu_strdup(ans->rrs.str[i]);
free(ans);
if (resolve)
status = dns_reply_resolve(reply);
return status;
}