#include "resolver.h"
static sig_atomic_t resolver_shutdown = 0;
static sig_atomic_t resolver_lost_router = 0;
static sig_atomic_t resolver_logrotate = 0;
static void _resolver_signal(int signum)
{
resolver_shutdown = 1;
resolver_lost_router = 0;
}
static void _resolver_signal_hup(int signum)
{
resolver_logrotate = 1;
}
static void _resolver_pidfile(resolver_t r) {
char *pidfile;
FILE *f;
pid_t pid;
pidfile = config_get_one(r->config, "pidfile", 0);
if(pidfile == NULL)
return;
pid = getpid();
if((f = fopen(pidfile, "w+")) == NULL) {
log_write(r->log, LOG_ERR, "couldn't open %s for writing: %s", pidfile, strerror(errno));
return;
}
if(fprintf(f, "%d", pid) < 0) {
log_write(r->log, LOG_ERR, "couldn't write to %s: %s", pidfile, strerror(errno));
return;
}
fclose(f);
log_write(r->log, LOG_INFO, "process id is %d, written to %s", pid, pidfile);
}
static void _resolver_config_expand(resolver_t r)
{
char *str;
config_elem_t elem;
r->id = config_get_one(r->config, "id", 0);
if(r->id == NULL)
r->id = "resolver";
r->router_ip = config_get_one(r->config, "router.ip", 0);
if(r->router_ip == NULL)
r->router_ip = "127.0.0.1";
r->router_port = j_atoi(config_get_one(r->config, "router.port", 0), 5347);
r->router_user = config_get_one(r->config, "router.user", 0);
if(r->router_user == NULL)
r->router_user = "jabberd";
r->router_pass = config_get_one(r->config, "router.pass", 0);
if(r->router_pass == NULL)
r->router_pass = "secret";
r->router_pemfile = config_get_one(r->config, "router.pemfile", 0);
r->retry_init = j_atoi(config_get_one(r->config, "router.retry.init", 0), 3);
r->retry_lost = j_atoi(config_get_one(r->config, "router.retry.lost", 0), 3);
if((r->retry_sleep = j_atoi(config_get_one(r->config, "router.retry.sleep", 0), 2)) < 1)
r->retry_sleep = 1;
r->log_type = log_STDOUT;
if(config_get(r->config, "log") != NULL) {
if((str = config_get_attr(r->config, "log", 0, "type")) != NULL) {
if(strcmp(str, "file") == 0)
r->log_type = log_FILE;
else if(strcmp(str, "syslog") == 0)
r->log_type = log_SYSLOG;
}
}
if(r->log_type == log_SYSLOG) {
r->log_facility = config_get_one(r->config, "log.facility", 0);
r->log_ident = config_get_one(r->config, "log.ident", 0);
if(r->log_ident == NULL)
r->log_ident = "jabberd/resolver";
} else if(r->log_type == log_FILE)
r->log_ident = config_get_one(r->config, "log.file", 0);
if((elem = config_get(r->config, "lookup.srv")) != NULL) {
r->lookup_srv = elem->values;
r->lookup_nsrv = elem->nvalues;
}
r->resolve_aaaa = config_count(r->config, "ipv6") ? 1 : 0;
}
static int _resolver_sx_callback(sx_t s, sx_event_t e, void *data, void *arg) {
resolver_t r = (resolver_t) arg;
sx_buf_t buf = (sx_buf_t) data;
sx_error_t *sxe;
int elem, len, attr, ns, aname, eip, srv, nres;
nad_t nad;
char zone[256], num[10];
dns_host_t srvs, srvscan, as, ascan;
switch(e) {
case event_WANT_READ:
log_debug(ZONE, "want read");
mio_read(r->mio, r->fd);
break;
case event_WANT_WRITE:
log_debug(ZONE, "want write");
mio_write(r->mio, r->fd);
break;
case event_READ:
log_debug(ZONE, "reading from %d", r->fd);
len = recv(r->fd, buf->data, buf->len, 0);
if(len < 0) {
if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN) {
buf->len = 0;
return 0;
}
log_write(r->log, LOG_NOTICE, "[%d] [router] read error: %s (%d)", r->fd, strerror(errno), errno);
sx_kill(s);
return -1;
}
else if(len == 0) {
sx_kill(s);
return -1;
}
log_debug(ZONE, "read %d bytes", len);
buf->len = len;
return len;
case event_WRITE:
log_debug(ZONE, "writing to %d", r->fd);
len = send(r->fd, buf->data, buf->len, 0);
if(len >= 0) {
log_debug(ZONE, "%d bytes written", len);
return len;
}
if(errno == EWOULDBLOCK || errno == EINTR || errno == EAGAIN)
return 0;
log_write(r->log, LOG_NOTICE, "[%d] [router] write error: %s (%d)", r->fd, strerror(errno), errno);
sx_kill(s);
return -1;
case event_ERROR:
sxe = (sx_error_t *) data;
log_write(r->log, LOG_NOTICE, "error from router: %s (%s)", sxe->generic, sxe->specific);
if(sxe->code == SX_ERR_AUTH)
sx_close(s);
break;
case event_STREAM:
break;
case event_OPEN:
log_write(r->log, LOG_NOTICE, "connection to router established");
r->retry_left = r->retry_init;
nad = nad_new(r->router->nad_cache);
ns = nad_add_namespace(nad, uri_COMPONENT, NULL);
nad_append_elem(nad, ns, "bind", 0);
nad_append_attr(nad, -1, "name", r->id);
log_debug(ZONE, "requesting component bind for '%s'", r->id);
sx_nad_write(r->router, nad);
break;
case event_PACKET:
nad = (nad_t) data;
if(NAD_ENS(nad, 0) < 0) {
nad_free(nad);
return 0;
}
if(s->state == state_STREAM) {
if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_STREAMS) || strncmp(uri_STREAMS, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_STREAMS)) != 0 || NAD_ENAME_L(nad, 0) != 8 || strncmp("features", NAD_ENAME(nad, 0), 8)) {
log_debug(ZONE, "got a non-features packet on an unauth'd stream, dropping");
nad_free(nad);
return 0;
}
#ifdef HAVE_SSL
if(r->sx_ssl != NULL && s->ssf == 0) {
ns = nad_find_scoped_namespace(nad, uri_TLS, NULL);
if(ns >= 0) {
elem = nad_find_elem(nad, 0, ns, "starttls", 1);
if(elem >= 0) {
if(sx_ssl_client_starttls(r->sx_ssl, s, NULL) == 0) {
nad_free(nad);
return 0;
}
log_write(r->log, LOG_NOTICE, "unable to establish encrypted session with router");
}
}
}
#endif
sx_sasl_auth(r->sx_sasl, s, "jabberd-router", "DIGEST-MD5", r->router_user, r->router_pass);
nad_free(nad);
return 0;
}
if(s->state == state_OPEN && !r->online) {
if(NAD_NURI_L(nad, NAD_ENS(nad, 0)) != strlen(uri_COMPONENT) || strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) != 0 || NAD_ENAME_L(nad, 0) != 4 || strncmp("bind", NAD_ENAME(nad, 0), 4)) {
log_debug(ZONE, "got a packet from router, but we're not online, dropping");
nad_free(nad);
return 0;
}
attr = nad_find_attr(nad, 0, -1, "error", NULL);
if(attr >= 0) {
log_write(r->log, LOG_NOTICE, "router refused bind request (%.*s)", NAD_AVAL_L(nad, attr), NAD_AVAL(nad, attr));
exit(1);
}
log_debug(ZONE, "coming online");
r->online = r->started = 1;
r->retry_left = r->retry_lost;
log_write(r->log, LOG_NOTICE, "ready to resolve", r->id);
nad_free(nad);
return 0;
}
if(nad_find_attr(nad, 1, -1, "type", "error") >= 0) {
nad_free(nad);
return 0;
}
if(!(
nad->ecur > 1 &&
(NAD_NURI_L(nad, NAD_ENS(nad, 0)) == strlen(uri_COMPONENT) && strncmp(uri_COMPONENT, NAD_NURI(nad, NAD_ENS(nad, 0)), strlen(uri_COMPONENT)) == 0) &&
(NAD_ENAME_L(nad, 0) == 5 && strncmp("route", NAD_ENAME(nad, 0), 5) == 0) &&
(NAD_NURI_L(nad, NAD_ENS(nad, 1)) == strlen(uri_RESOLVER) && strncmp(uri_RESOLVER, NAD_NURI(nad, NAD_ENS(nad, 1)), strlen(uri_RESOLVER)) == 0) &&
(NAD_ENAME_L(nad, 1) == 7 && strncmp("resolve", NAD_ENAME(nad, 1), 7) == 0) &&
nad_find_attr(nad, 1, -1, "type", "query") >= 0 &&
(aname = nad_find_attr(nad, 1, -1, "name", NULL)) >= 0))
{
nad_free(nad);
return 0;
}
srv = 0; nres = 0;
while(srv < r->lookup_nsrv && nres == 0) {
snprintf(zone, 256, "%s.%.*s", r->lookup_srv[srv], NAD_AVAL_L(nad, aname), NAD_AVAL(nad, aname));
log_debug(ZONE, "trying srv lookup for %s", zone);
srvs = dns_resolve(zone, DNS_QUERY_TYPE_SRV);
if(srvs != NULL) {
for(srvscan = srvs; srvscan != NULL; srvscan = srvscan->next) {
log_debug(ZONE, "%s has srv %s, doing A lookup", zone, ((dns_srv_t) srvscan->rr)->name);
as = dns_resolve(((dns_srv_t) srvscan->rr)->name, DNS_QUERY_TYPE_A);
for(ascan = as; ascan != NULL; ascan = ascan->next) {
log_write(r->log, LOG_NOTICE, "[%s] resolved to %s:%d (%d seconds to live)", zone, (char *) ascan->rr, ((dns_srv_t) srvscan->rr)->port, ascan->ttl);
eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *) ascan->rr);
snprintf(num, 10, "%d", ((dns_srv_t) srvscan->rr)->port);
nad_set_attr(nad, eip, -1, "port", num, 0);
snprintf(num, 10, "%d", ascan->ttl);
nad_set_attr(nad, eip, -1, "ttl", num, 0);
nres++;
}
dns_free(as);
}
if(r->resolve_aaaa) {
for(srvscan = srvs; srvscan != NULL; srvscan = srvscan->next) {
log_debug(ZONE, "%s has srv %s, doing AAAA lookup", zone, ((dns_srv_t) srvscan->rr)->name);
as = dns_resolve(((dns_srv_t) srvscan->rr)->name, DNS_QUERY_TYPE_AAAA);
for(ascan = as; ascan != NULL; ascan = ascan->next) {
log_write(r->log, LOG_NOTICE, "[%s] resolved to [%s]:%d (%d seconds to live)", zone, (char *)ascan->rr, ((dns_srv_t) srvscan->rr)->port, ascan->ttl);
eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *)ascan->rr);
snprintf(num, 10, "%d", ((dns_srv_t) srvscan->rr)->port);
nad_set_attr(nad, eip, -1, "port", num, 0);
snprintf(num, 10, "%d", ascan->ttl);
nad_set_attr(nad, eip, -1, "ttl", num, 0);
nres++;
}
dns_free(as);
}
}
dns_free(srvs);
}
srv++;
}
if(nres == 0) {
snprintf(zone, 256, "%.*s", NAD_AVAL_L(nad, aname), NAD_AVAL(nad, aname));
log_debug(ZONE, "doing A lookup for %s", zone);
as = dns_resolve(zone, DNS_QUERY_TYPE_A);
for(ascan = as; ascan != NULL; ascan = ascan->next) {
log_write(r->log, LOG_NOTICE, "[%s] resolved to [%s:5269] (%d seconds to live)", zone, (char *) ascan->rr, ascan->ttl);
eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *) ascan->rr);
nad_set_attr(nad, eip, -1, "port", "5269", 4);
snprintf(num, 10, "%d", ascan->ttl);
nad_set_attr(nad, eip, -1, "ttl", num, 0);
nres++;
}
dns_free(as);
if(r->resolve_aaaa) {
log_debug(ZONE, "doing AAAA lookup for %s", zone);
as = dns_resolve(zone, DNS_QUERY_TYPE_AAAA);
for(ascan = as; ascan != NULL; ascan = ascan->next)
{
log_write(r->log, LOG_NOTICE, "[%s] resolved to [%s]:5269 (%d seconds to live)", zone, (char *)ascan->rr, ascan->ttl);
eip = nad_insert_elem(nad, 1, NAD_ENS(nad, 1), "ip", (char *)ascan->rr);
nad_set_attr(nad, eip, -1, "port", "5269", 4);
snprintf(num, 10, "%d", ascan->ttl);
nad_set_attr(nad, eip, -1, "ttl", num, 0);
nres++;
}
dns_free(as);
}
}
nad_set_attr(nad, 1, -1, "type", "result", 6);
sx_nad_write(r->router, stanza_tofrom(nad, 0));
if (nres == 0) {
log_write(r->log, LOG_NOTICE, "[%s] could not be resolved", zone);
}
break;
case event_CLOSED:
mio_close(r->mio, r->fd);
break;
}
return 0;
}
static int _resolver_mio_callback(mio_t m, mio_action_t a, int fd, void *data, void *arg) {
resolver_t r = (resolver_t) arg;
int nbytes;
switch(a) {
case action_READ:
ioctl(fd, FIONREAD, &nbytes);
if(nbytes == 0) {
sx_kill(r->router);
return 0;
}
log_debug(ZONE, "read action on fd %d", fd);
return sx_can_read(r->router);
case action_WRITE:
log_debug(ZONE, "write action on fd %d", fd);
return sx_can_write(r->router);
case action_CLOSE:
log_debug(ZONE, "close action on fd %d", fd);
log_write(r->log, LOG_NOTICE, "connection to router closed");
resolver_lost_router = 1;
r->online = 0;
break;
case action_ACCEPT:
break;
}
return 0;
}
static int _resolver_router_connect(resolver_t r) {
log_write(r->log, LOG_NOTICE, "attempting connection to router at %s, port=%d", r->router_ip, r->router_port);
r->fd = mio_connect(r->mio, r->router_port, r->router_ip, _resolver_mio_callback, (void *) r);
if(r->fd < 0) {
if(errno == ECONNREFUSED)
resolver_lost_router = 1;
log_write(r->log, LOG_NOTICE, "connection attempt to router failed: %s (%d)", strerror(errno), errno);
return 1;
}
r->router = sx_new(r->sx_env, r->fd, _resolver_sx_callback, (void *) r);
sx_client_init(r->router, 0, NULL, NULL, NULL, "1.0");
return 0;
}
int main(int argc, char **argv)
{
resolver_t r;
char *config_file;
int optchar;
struct passwd *p;
int newgid, newuid;
#ifdef POOL_DEBUG
time_t pool_time = 0;
#endif
#ifdef HAVE_UMASK
umask((mode_t) 0027);
#endif
#ifdef JABBER_USER
p = getpwnam(JABBER_USER);
if (p == NULL) {
printf("Error: could not find user %s\n", JABBER_USER);
return 1;
}
newuid = p->pw_uid;
newgid = p->pw_gid;
memset(p, 0, sizeof(struct passwd));
if (initgroups(JABBER_USER, newgid)) {
printf("cannot initialize groups for user %s: %s\n", JABBER_USER, strerror(errno));
return 1;
}
if (setgid(newgid)) {
printf("cannot setgid: %s\n", strerror(errno));
return 1;
}
if (seteuid(newuid)) {
printf("cannot seteuid: %s\n", strerror(errno));
return 1;
}
#else
printf("No user is defined for setuid/setgid, continuing\n");
#endif
srand(time(NULL));
#ifdef HAVE_WINSOCK2_H
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return 0;
}
}
#endif
jabber_signal(SIGINT, _resolver_signal);
jabber_signal(SIGTERM, _resolver_signal);
#ifdef SIGHUP
jabber_signal(SIGHUP, _resolver_signal_hup);
#endif
#ifdef SIGPIPE
jabber_signal(SIGPIPE, SIG_IGN);
#endif
r = (resolver_t) malloc(sizeof(struct resolver_st));
memset(r, 0, sizeof(struct resolver_st));
r->config = config_new();
config_file = CONFIG_DIR "/resolver.xml";
while((optchar = getopt(argc, argv, "Dc:h?")) >= 0)
{
switch(optchar)
{
case 'c':
config_file = optarg;
break;
case 'D':
#ifdef DEBUG
set_debug_flag(1);
#else
printf("WARN: Debugging not enabled. Ignoring -D.\n");
#endif
break;
case 'h': case '?': default:
fputs(
"resolver - jabberd asynchronous dns resolver (" VERSION ")\n"
"Usage: resolver <options>\n"
"Options are:\n"
" -c <config> config file to use [default: " CONFIG_DIR "/resolver.xml]\n"
#ifdef DEBUG
" -D Show debug output\n"
#endif
,
stdout);
config_free(r->config);
free(r);
return 1;
}
}
if(config_load(r->config, config_file) != 0)
{
fputs("resolver: couldn't load config, aborting\n", stderr);
config_free(r->config);
free(r);
return 2;
}
_resolver_config_expand(r);
r->log = log_new(r->log_type, r->log_ident, r->log_facility);
log_write(r->log, LOG_NOTICE, "starting up");
_resolver_pidfile(r);
r->sx_env = sx_env_new();
#ifdef HAVE_SSL
#ifdef JABBER_USER
if (seteuid(0)) {
log_write(r->log, LOG_ERR, "cannot seteuid to root: %s", strerror(errno));
return 1;
}
#else
log_write(r->log, LOG_NOTICE, "No user is defined for setuid/setgid, continuing");
#endif // JABBER_USER
if(r->router_pemfile != NULL) {
r->sx_ssl = sx_env_plugin(r->sx_env, sx_ssl_init, r->router_pemfile, NULL);
if(r->sx_ssl == NULL) {
log_write(r->log, LOG_ERR, "failed to load SSL pemfile, SSL disabled");
r->router_pemfile = NULL;
}
}
#endif // HAVE_SSL
#ifdef JABBER_USER
if (setuid(newuid)) {
log_write(r->log, LOG_ERR, "cannot setuid(%d): %s", newuid, strerror(errno));
return 1;
}
#else
log_write(r->log, LOG_NOTICE, "No user is defined for setuid/setgid, continuing");
#endif // JABBER_USER
r->sx_sasl = sx_env_plugin(r->sx_env, sx_sasl_init, "jabberd-router", NULL, NULL, 0);
if(r->sx_sasl == NULL) {
log_write(r->log, LOG_ERR, "failed to initialise SASL context, aborting");
exit(1);
}
r->mio = mio_new(1023);
r->retry_left = r->retry_init;
_resolver_router_connect(r);
while(!resolver_shutdown) {
mio_run(r->mio, 5);
if(resolver_logrotate) {
log_write(r->log, LOG_NOTICE, "reopening log ...");
log_free(r->log);
r->log = log_new(r->log_type, r->log_ident, r->log_facility);
log_write(r->log, LOG_NOTICE, "log started");
resolver_logrotate = 0;
}
if(resolver_lost_router) {
if(r->retry_left < 0) {
log_write(r->log, LOG_NOTICE, "attempting reconnect");
sleep(r->retry_sleep);
resolver_lost_router = 0;
_resolver_router_connect(r);
}
else if(r->retry_left == 0) {
resolver_shutdown = 1;
}
else {
log_write(r->log, LOG_NOTICE, "attempting reconnect (%d left)", r->retry_left);
r->retry_left--;
sleep(r->retry_sleep);
resolver_lost_router = 0;
_resolver_router_connect(r);
}
}
#ifdef POOL_DEBUG
if(time(NULL) > pool_time + 60) {
pool_stat(1);
pool_time = time(NULL);
}
#endif
}
log_write(r->log, LOG_NOTICE, "shutting down");
sx_free(r->router);
sx_env_free(r->sx_env);
mio_free(r->mio);
log_free(r->log);
config_free(r->config);
free(r);
#ifdef POOL_DEBUG
pool_stat(1);
#endif
#ifdef HAVE_WINSOCK2_H
WSACleanup();
#endif
return 0;
}