#include "jabberd.h"
#include "srv_resolv.h"
#include <sys/wait.h>
#ifdef __CYGWIN__
#include <process.h>
#endif
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;
#ifndef __CYGWIN__
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];
sigset_t sigs;
sigemptyset(&sigs);
sigaddset(&sigs, SIGHUP);
sigprocmask(SIG_BLOCK, &sigs, NULL);
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 = pth_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];
return pid;
}
else
{
pth_kill();
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);
}
}
#endif
#ifdef __CYGWIN__
int dnsrv_child_main(dns_io di) {
return 0;
}
int dnsrv_fork_and_capture(RESOLVEFUNC f, dns_io di)
{
int pid;
int childToParent[2];
int parentToChild[2];
char childWriteHandle[10];
char childReadHandle[10];
char debugging[10];
int READ=0;
int WRITE=1;
pipe(childToParent);
pipe(parentToChild);
sprintf(childWriteHandle, "%i", childToParent[WRITE]);
sprintf(childReadHandle, "%i", parentToChild[READ]);
sprintf(debugging, "%i", get_debug_flag());
pid = spawnlp(_P_NOWAIT, "jabadns", "jabadns",
childReadHandle, childWriteHandle, debugging, NULL);
if (pid < 0) {
return -1;
}
di->pid = pid;
di->in = childToParent[READ];
di->out = parentToChild[WRITE];
return pid;
}
#endif
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) - (int)xmlnode_get_vattrib(c,"t")) > timeout)
{
xmlnode_free(c);
ghash_remove(di->cache_table,p->host);
}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;
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));
xmlnode_put_vattrib(x,"t",(void*)time(NULL));
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 retcode = 0;
int pid = 0;
int readlen = 0;
char readbuf[1024];
xstream xs = NULL;
sigset_t sigs;
#ifdef __CYGWIN__
dns_resend_list iternode = NULL;
#endif
sigemptyset(&sigs);
sigaddset(&sigs, SIGHUP);
sigprocmask(SIG_BLOCK, &sigs, NULL);
xs = xstream_new(di->mempool, dnsrv_process_xstream_io, di);
pth_write(di->out, "<stream>", 8);
#ifdef __CYGWIN__
iternode = di->svclist;
while (iternode != NULL) {
if (iternode->service) {
sprintf(readbuf, "<resend service=\"%s\">%s</resend>",
iternode->service, iternode->host);
} else {
sprintf(readbuf, "<resend>%s</resend>",
iternode->host);
}
pth_write(di->out, readbuf, strlen(readbuf));
iternode = iternode->next;
}
#endif
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;
}
pid = pth_waitpid(di->pid, &retcode, 0);
if(pid == -1)
{
log_debug(ZONE, "pth_waitpid returned -1: %s", strerror(errno));
}
else if(pid == 0)
{
log_debug(ZONE, "no child available to call waitpid on");
}
else
{
log_debug(ZONE, "pid %d, exit status: %d", pid, WEXITSTATUS(retcode));
}
close(di->in);
close(di->out);
di->out = 0;
log_debug(ZONE,"child returned %d",WEXITSTATUS(retcode));
if(WIFEXITED(retcode))
{
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;
}
log_debug(ZONE, "child dying...");
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;
}
void dnsrv_shutdown(void *arg)
{
dns_io di=(dns_io)arg;
ghash_destroy(di->packet_table);
}
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);
pool_cleanup(i->p, dnsrv_shutdown, (void*)di);
}