solanum/ircd/dns.c
Elizabeth Myers 394b8dde17 authd: allow querying the list of DNS servers.
This was an asston of pain, and it still feels "dirty" as it introduces
an async call where there normally wouldn't be one. Better
implementation more than welcome.
2016-03-08 02:53:25 -06:00

365 lines
7 KiB
C

/*
* dns.c: An interface to the resolver module in authd
* (based somewhat on ircd-ratbox dns.c)
*
* Copyright (C) 2005 Aaron Sethman <androsyn@ratbox.org>
* Copyright (C) 2005-2012 ircd-ratbox development team
* Copyright (C) 2016 William Pitcock <nenolod@dereferenced.org>
*
* 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 2 of the License, 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "stdinc.h"
#include "rb_lib.h"
#include "client.h"
#include "ircd_defs.h"
#include "parse.h"
#include "dns.h"
#include "match.h"
#include "logger.h"
#include "s_conf.h"
#include "client.h"
#include "send.h"
#include "numeric.h"
#include "msg.h"
#include "hash.h"
#define DNS_IDTABLE_SIZE 0x2000
#define DNS_STATTABLE_SIZE 0x10
#define DNS_HOST_IPV4 ((char)'4')
#define DNS_HOST_IPV6 ((char)'6')
#define DNS_REVERSE_IPV4 ((char)'R')
#define DNS_REVERSE_IPV6 ((char)'S')
static void submit_dns(uint16_t uid, char type, const char *addr);
static void submit_dns_stat(uint16_t uid);
struct dnsreq
{
DNSCB callback;
void *data;
};
struct dnsstatreq
{
DNSLISTCB callback;
void *data;
};
struct dnsstatreq_data
{
char uid[IDLEN];
char statchar;
};
static struct dnsreq querytable[DNS_IDTABLE_SIZE];
static struct dnsstatreq stattable[DNS_STATTABLE_SIZE];
static uint16_t
assign_dns_id(void)
{
static uint16_t id = 1;
int loopcnt = 0;
while(1)
{
if(++loopcnt > DNS_IDTABLE_SIZE)
return 0;
if(id < DNS_IDTABLE_SIZE - 1 || id == 0)
id++;
else
id = 1;
if(querytable[id].callback == NULL)
break;
}
return (id);
}
static uint8_t
assign_dns_stat_id(void)
{
static uint8_t id = 1;
int loopcnt = 0;
while(1)
{
if(++loopcnt > DNS_STATTABLE_SIZE)
return 0;
if(id < DNS_STATTABLE_SIZE - 1 || id == 0)
id++;
else
id = 1;
if(stattable[id].callback == NULL)
break;
}
return (id);
}
static void
handle_dns_failure(uint16_t xid)
{
struct dnsreq *req;
req = &querytable[xid];
if(req->callback == NULL)
return;
req->callback("FAILED", 0, 0, req->data);
req->callback = NULL;
req->data = NULL;
}
static void
handle_dns_stat_failure(uint8_t xid)
{
struct dnsstatreq *req;
const char *err[] = { "Unknown failure" };
req = &stattable[xid];
if(req->callback == NULL)
return;
req->callback(1, err, 2, req->data);
/* NOTE - this assumes req->data is on the heap */
rb_free(req->data);
req->callback = NULL;
req->data = NULL;
}
void
cancel_lookup(uint16_t xid)
{
querytable[xid].callback = NULL;
querytable[xid].data = NULL;
}
void
cancel_dns_stats(uint16_t xid)
{
/* NOTE - this assumes data is on the heap */
rb_free(stattable[xid].data);
stattable[xid].callback = NULL;
stattable[xid].data = NULL;
}
uint16_t
lookup_hostname(const char *hostname, int aftype, DNSCB callback, void *data)
{
struct dnsreq *req;
int aft;
uint16_t nid;
check_authd();
nid = assign_dns_id();
if((nid = assign_dns_id()) == 0)
return 0;
req = &querytable[nid];
req->callback = callback;
req->data = data;
#ifdef RB_IPV6
if(aftype == AF_INET6)
aft = 6;
else
#endif
aft = 4;
submit_dns(nid, aft == 4 ? DNS_HOST_IPV4 : DNS_HOST_IPV6, hostname);
return (nid);
}
uint16_t
lookup_ip(const char *addr, int aftype, DNSCB callback, void *data)
{
struct dnsreq *req;
int aft;
uint16_t nid;
check_authd();
if((nid = assign_dns_id()) == 0)
return 0;
req = &querytable[nid];
req->callback = callback;
req->data = data;
#ifdef RB_IPV6
if(aftype == AF_INET6)
aft = 6;
else
#endif
aft = 4;
submit_dns(nid, aft == 4 ? DNS_REVERSE_IPV4 : DNS_REVERSE_IPV6, addr);
return (nid);
}
uint8_t
get_nameservers(DNSLISTCB callback, void *data)
{
struct dnsstatreq *req;
uint8_t nid;
check_authd();
if((nid = assign_dns_stat_id()) == 0)
return 0;
req = &stattable[nid];
req->callback = callback;
req->data = data;
submit_dns_stat(nid);
return (nid);
}
void
dns_results_callback(const char *callid, const char *status, const char *type, const char *results)
{
struct dnsreq *req;
uint16_t nid;
int st;
int aft;
long lnid = strtol(callid, NULL, 16);
if(lnid > DNS_IDTABLE_SIZE || lnid == 0)
return;
nid = (uint16_t)lnid;
req = &querytable[nid];
st = (*status == 'O');
aft = *type == '6' || *type == 'S' ? 6 : 4;
if(req->callback == NULL)
{
/* got cancelled..oh well */
req->data = NULL;
return;
}
#ifdef RB_IPV6
if(aft == 6)
aft = AF_INET6;
else
#endif
aft = AF_INET;
req->callback(results, st, aft, req->data);
req->callback = NULL;
req->data = NULL;
}
void
dns_stats_results_callback(const char *callid, const char *status, int resc, const char *resv[])
{
struct dnsstatreq *req;
uint8_t nid;
int st, i;
long lnid = strtol(callid, NULL, 16);
if(lnid > DNS_STATTABLE_SIZE || lnid == 0)
return;
nid = (uint8_t)lnid;
req = &stattable[nid];
if(req->callback == NULL)
{
/* NOTE - this assumes req->data is strdup'd or such */
rb_free(req->data);
req->data = NULL;
return;
}
switch(*status)
{
case 'Y':
st = 0;
break;
case 'X':
/* Error */
st = 1;
break;
default:
/* Shouldn't happen... */
return;
}
/* Query complete */
req->callback(resc, resv, st, stattable[nid].data);
rb_free(req->data);
req->data = NULL;
req->callback = NULL;
}
static void
report_dns_servers_cb(int resc, const char *resv[], int status, void *data)
{
struct Client *source_p;
struct dnsstatreq_data *c_data = data;
if(!(source_p = find_id(c_data->uid)))
/* Client's gone, oh well. */
return;
if(status == 0)
{
for(int i = 0; i < resc; i++)
sendto_one_numeric(source_p, RPL_STATSDEBUG, "A %s", resv[i]);
}
else
{
if(resc && resv[resc][0])
/* XXX is this the right reply? */
sendto_one_numeric(source_p, RPL_STATSDEBUG, "A Error: %s", resv[resc]);
}
sendto_one_numeric(source_p, RPL_ENDOFSTATS, form_str(RPL_ENDOFSTATS), c_data->statchar);
}
void
report_dns_servers(struct Client *source_p, char statchar)
{
/* Use the UID to avoid a race where source_p goes away */
struct dnsstatreq_data *data = rb_malloc(sizeof(struct dnsstatreq_data));
rb_strlcpy(data->uid, source_p->id, IDLEN);
data->statchar = statchar;
get_nameservers(report_dns_servers_cb, data);
}
static void
submit_dns(uint16_t nid, char type, const char *addr)
{
if(authd_helper == NULL)
{
handle_dns_failure(nid);
return;
}
rb_helper_write(authd_helper, "D %x %c %s", nid, type, addr);
}
static void
submit_dns_stat(uint16_t nid)
{
if(authd_helper == NULL)
{
handle_dns_stat_failure(nid);
return;
}
rb_helper_write(authd_helper, "S %x D", nid);
}