#include "jabberd.h"
#include "srv_resolv.h"
#include <sys/wait.h>
typedef struct __dns_resend_list
{
char* service;
char* host;
struct __dns_resend_list* next;
} *dns_resend_list, _dns_resend_list;
typedef struct
{
int in;
int out;
int pid;
HASHTABLE packet_table;
int packet_timeout;
HASHTABLE cache_table;
int cache_timeout;
pool mempool;
dns_resend_list svclist;
} *dns_io, _dns_io;
typedef int (*RESOLVEFUNC)(dns_io di);
typedef struct __dns_packet_list
{
dpacket packet;
int stamp;
struct __dns_packet_list* next;
} *dns_packet_list, _dns_packet_list;
void _dnsrv_signal(int sig)
{
exit(0);
}
void dnsrv_child_process_xstream_io(int type, xmlnode x, void* args)
{
dns_io di = (dns_io)args;
char* hostname;
char* str = NULL;
dns_resend_list iternode = NULL;
if (type == XSTREAM_NODE)
{
hostname = xmlnode_get_data(x);
log_debug(ZONE, "dnsrv: Recv'd lookup request for %s", hostname);
if (hostname != NULL)
{
iternode = di->svclist;
while (iternode != NULL)
{
str = srv_lookup(x->p, iternode->service, hostname);
if (str != NULL)
{
log_debug(ZONE, "Resolved %s(%s): %s\tresend to:%s", hostname, iternode->service, str, iternode->host);
xmlnode_put_attrib(x, "ip", str);
xmlnode_put_attrib(x, "to", iternode->host);
break;
}
iternode = iternode->next;
}
str = xmlnode2str(x);
write(di->out, str, strlen(str));
}
}
xmlnode_free(x);
}
int dnsrv_child_main(dns_io di)
{
pool p = pool_new();
xstream xs = xstream_new(p, dnsrv_child_process_xstream_io, di);
int len;
char readbuf[1024];
log_debug(ZONE,"DNSRV CHILD: starting");
write(di->out, "<stream>", 8);
while (1)
{
len = read(di->in, &readbuf, 1024);
if (len <= 0)
{
log_debug(ZONE,"dnsrv: Read error on coprocess(%d): %d %s",getppid(),errno,strerror(errno));
break;
}
log_debug(ZONE, "DNSRV CHILD: Read from buffer: %.*s",len,readbuf);
if (xstream_eat(xs, readbuf, len) > XSTREAM_NODE)
{
log_debug(ZONE, "DNSRV CHILD: xstream died");
break;
}
}
log_debug(ZONE, "DNSRV CHILD: out of loop.. exiting normal");
pool_free(p);
exit(0);
return 0;
}
int dnsrv_fork_and_capture(RESOLVEFUNC f, dns_io di)
{
int left_fds[2], right_fds[2];
int pid;
if (pipe(left_fds) < 0 || pipe(right_fds) < 0)
return -1;
pid = fork();
if (pid < 0)
return -1;
else if (pid > 0)
{
close(left_fds[STDIN_FILENO]);
close(right_fds[STDOUT_FILENO]);
di->in = right_fds[STDIN_FILENO];
di->out = left_fds[STDOUT_FILENO];
pth_write(di->out, "<stream>", 8);
return pid;
}
else
{
pth_kill();
signal(SIGHUP,_dnsrv_signal);
signal(SIGINT,_dnsrv_signal);
signal(SIGTERM,_dnsrv_signal);
close(left_fds[STDOUT_FILENO]);
close(right_fds[STDIN_FILENO]);
di->in = left_fds[STDIN_FILENO]; di->out = right_fds[STDOUT_FILENO];
return (*f)(di);
}
}
void dnsrv_resend(xmlnode pkt, char *ip, char *to)
{
if(ip != NULL)
{
pkt = xmlnode_wrap(pkt,"route");
xmlnode_put_attrib(pkt, "to", to);
xmlnode_put_attrib(pkt, "ip", ip);
}else{
jutil_error(pkt, (terror){502, "Unable to resolve hostname."});
xmlnode_put_attrib(pkt, "iperror", "");
}
deliver(dpacket_new(pkt),NULL);
}
void dnsrv_lookup(dns_io d, dpacket p)
{
dns_packet_list l, lnew;
xmlnode req;
char *reqs;
if(d->out <= 0)
{
deliver_fail(p, "DNS Resolver Error");
return;
}
l = (dns_packet_list)ghash_get(d->packet_table, p->host);
if (l != NULL)
{
log_debug(ZONE, "dnsrv: Adding lookup request for %s to pending queue.", p->host);
lnew = pmalloco(p->p, sizeof(_dns_packet_list));
lnew->packet = p;
lnew->stamp = time(NULL);
lnew->next = l;
ghash_put(d->packet_table, p->host, lnew);
return;
}
log_debug(ZONE, "dnsrv: Creating lookup request queue for %s", p->host);
l = pmalloco(p->p, sizeof(_dns_packet_list));
l->packet = p;
l->stamp = time(NULL);
ghash_put(d->packet_table, p->host, l);
req = xmlnode_new_tag_pool(p->p,"host");
xmlnode_insert_cdata(req,p->host,-1);
reqs = xmlnode2str(req);
log_debug(ZONE, "dnsrv: Transmitting lookup request: %s", reqs);
pth_write(d->out, reqs, strlen(reqs));
}
result dnsrv_deliver(instance i, dpacket p, void* args)
{
dns_io di = (dns_io)args;
xmlnode c;
int timeout = di->cache_timeout;
char *ip;
jid to;
if(p->type == p_ROUTE)
{
if(j_strcmp(p->host,i->id) != 0 || (to = jid_new(p->p,xmlnode_get_attrib(xmlnode_get_firstchild(p->x),"to"))) == NULL)
return r_ERR;
p->x=xmlnode_get_firstchild(p->x);
p->id = to;
p->host = to->server;
}
if(xmlnode_get_attrib(p->x, "ip") || xmlnode_get_attrib(p->x, "iperror"))
{
log_notice(p->host, "dropping looping dns lookup request: %s", xmlnode2str(p->x));
xmlnode_free(p->x);
return r_DONE;
}
if((c = ghash_get(di->cache_table, p->host)) != NULL)
{
if((ip = xmlnode_get_attrib(c,"ip")) == NULL)
timeout = timeout / 10;
if((time(NULL) - *(time_t*)xmlnode_get_vattrib(c,"t")) > timeout)
{
ghash_remove(di->cache_table,p->host);
xmlnode_free(c);
}else{
dnsrv_resend(p->x, ip, xmlnode_get_attrib(c,"to"));
return r_DONE;
}
}
dnsrv_lookup(di, p);
return r_DONE;
}
void dnsrv_process_xstream_io(int type, xmlnode x, void* arg)
{
dns_io di = (dns_io)arg;
char* hostname = NULL;
char* ipaddr = NULL;
char* resendhost = NULL;
dns_packet_list head = NULL;
dns_packet_list heado = NULL;
time_t *ttmp;
if (type == XSTREAM_NODE)
{
log_debug(ZONE,"incoming resolution: %s",xmlnode2str(x));
hostname = xmlnode_get_data(x);
xmlnode_free((xmlnode)ghash_get(di->cache_table,hostname));
ttmp = pmalloc(xmlnode_pool(x),sizeof(time_t));
time(ttmp);
xmlnode_put_vattrib(x,"t",(void*)ttmp);
ghash_put(di->cache_table,hostname,(void*)x);
head = ghash_get(di->packet_table, hostname);
if (head != NULL)
{
ipaddr = xmlnode_get_attrib(x, "ip");
resendhost = xmlnode_get_attrib(x, "to");
ghash_remove(di->packet_table, hostname);
while(head != NULL)
{
heado = head;
head = head->next;
dnsrv_resend(heado->packet->x, ipaddr, resendhost);
}
}
else
log_debug(ZONE, "Resolved unknown host/ip request: %s\n", xmlnode2str(x));
return;
}
xmlnode_free(x);
}
void* dnsrv_process_io(void* threadarg)
{
dns_io di = (dns_io)threadarg;
int readlen = 0;
char readbuf[1024];
xstream xs = NULL;
xs = xstream_new(di->mempool, dnsrv_process_xstream_io, di);
while (1)
{
readlen = pth_read(di->in, readbuf, sizeof(readbuf));
if (readlen <= 0)
{
log_debug(ZONE,"dnsrv: Read error on coprocess: %d %s",errno,strerror(errno));
break;
}
if (xstream_eat(xs, readbuf, readlen) > XSTREAM_NODE)
break;
}
close(di->in);
close(di->out);
di->out = 0;
waitpid(di->pid, &readlen, WNOHANG);
if(jabberd__signalflag == SIGTERM || jabberd__signalflag == SIGINT) return NULL;
log_debug(ZONE, "child being restarted...");
di->pid = dnsrv_fork_and_capture(dnsrv_child_main, di);
pth_spawn(PTH_ATTR_DEFAULT, dnsrv_process_io, (void*)di);
return NULL;
}
void *dnsrv_thread(void *arg)
{
dns_io di=(dns_io)arg;
di->pid = dnsrv_fork_and_capture(dnsrv_child_main, di);
return NULL;
}
int _dnsrv_beat_packets(void *arg, const void *key, void *data)
{
dns_io di = (dns_io)arg;
dns_packet_list n, l = (dns_packet_list)data;
int now = time(NULL);
int reap = 0;
if((now - l->stamp) > di->packet_timeout)
{
log_notice(l->packet->host,"timed out from dnsrv queue");
ghash_remove(di->packet_table,l->packet->host);
reap = 1;
}else{
while(l->next != NULL)
{
if((now - l->next->stamp) > di->packet_timeout)
{
reap = 1;
n = l->next;
l->next = NULL;
l = n;
break;
}
l = l->next;
}
}
if(reap == 0) return 1;
while(l != NULL)
{
n = l->next;
deliver_fail(l->packet,"Hostname Resolution Timeout");
l = n;
}
return 1;
}
result dnsrv_beat_packets(void *arg)
{
dns_io di = (dns_io)arg;
ghash_walk(di->packet_table,_dnsrv_beat_packets,arg);
return r_DONE;
}
void dnsrv(instance i, xmlnode x)
{
xdbcache xc = NULL;
xmlnode config = NULL;
xmlnode iternode = NULL;
dns_resend_list tmplist = NULL;
dns_io di;
di = pmalloco(i->p, sizeof(_dns_io));
di->mempool = i->p;
xc = xdb_cache(i);
config = xdb_get(xc, jid_new(xmlnode_pool(x), "config@-internal"), "jabber:config:dnsrv");
iternode = xmlnode_get_lastchild(config);
while (iternode != NULL)
{
if (j_strcmp("resend", xmlnode_get_name(iternode)) != 0)
{
iternode = xmlnode_get_prevsibling(iternode);
continue;
}
tmplist = pmalloco(di->mempool, sizeof(_dns_resend_list));
tmplist->service = pstrdup(di->mempool, xmlnode_get_attrib(iternode, "service"));
tmplist->host = pstrdup(di->mempool, xmlnode_get_data(iternode));
tmplist->next = di->svclist;
di->svclist = tmplist;
iternode = xmlnode_get_prevsibling(iternode);
}
log_debug(ZONE, "dnsrv debug: %s\n", xmlnode2str(config));
di->packet_table = ghash_create(j_atoi(xmlnode_get_attrib(config,"queuemax"),101), (KEYHASHFUNC)str_hash_code, (KEYCOMPAREFUNC)j_strcmp);
di->packet_timeout = j_atoi(xmlnode_get_attrib(config,"queuetimeout"),60);
register_beat(di->packet_timeout, dnsrv_beat_packets, (void *)di);
di->cache_table = ghash_create(j_atoi(xmlnode_get_attrib(config,"cachemax"),1999), (KEYHASHFUNC)str_hash_code, (KEYCOMPAREFUNC)j_strcmp);
di->cache_timeout = j_atoi(xmlnode_get_attrib(config,"cachetimeout"),3600);
xmlnode_free(config);
pth_join(pth_spawn(PTH_ATTR_DEFAULT,(void*)dnsrv_thread,(void*)di),NULL);
if(di->pid < 0)
{
log_error(i->id,"dnsrv failed to start, unable to fork and/or create pipes");
return;
}
pth_spawn(PTH_ATTR_DEFAULT, dnsrv_process_io, di);
register_phandler(i, o_DELIVER, dnsrv_deliver, (void*)di);
}