#include "dialback.h"
typedef struct dboq_struct
{
int stamp;
xmlnode x;
struct dboq_struct *next;
} *dboq, _dboq;
typedef struct
{
char *ip;
int stamp;
db d;
jid key;
xmlnode verifies;
pool p;
dboq q;
mio m;
} *dboc, _dboc;
void dialback_out_read(mio m, int flags, void *arg, xmlnode x);
void dialback_out_connect(dboc c)
{
char *ip, *col;
int port = 5269;
if(c->ip == NULL)
return;
ip = c->ip;
c->ip = strchr(ip,',');
if(c->ip != NULL)
{
*c->ip = '\0';
c->ip++;
}
log_debug(ZONE, "Attempting to connect to %s at %s",jid_full(c->key),ip);
#ifdef WITH_IPV6
if(ip[0] == '[')
{
ip++;
col=strchr(ip,']');
if(col != NULL)
{
*col = '\0';
if(col[1]==':')
{
col++;
}
}
}
else
{
col = strchr(ip, ':');
if(col!=NULL && strchr(col+1,':'))
{
col = NULL;
}
}
#else
col = strchr(ip,':');
#endif
if(col != NULL)
{
*col = '\0';
col++;
port = atoi(col);
}
mio_connect(ip, port, dialback_out_read, (void *)c, 20, MIO_CONNECT_XML);
}
dboc dialback_out_connection(db d, jid key, char *ip)
{
dboc c;
pool p;
if((c = ghash_get(d->out_connecting, jid_full(key))) != NULL)
return c;
if(ip == NULL)
return NULL;
p = pool_heap(2*1024);
c = pmalloco(p, sizeof(_dboc));
c->p = p;
c->d = d;
c->key = jid_new(p,jid_full(key));
c->stamp = time(NULL);
c->verifies = xmlnode_new_tag_pool(p,"v");
c->ip = pstrdup(p,ip);
ghash_put(d->out_connecting, jid_full(c->key), (void *)c);
dialback_out_connect(c);
return c;
}
void dialback_out_connection_cleanup(dboc c)
{
dboq cur, next;
xmlnode x;
ghash_remove(c->d->out_connecting,jid_full(c->key));
if(c->m == NULL && c->q != NULL)
log_notice(c->key->server,"failed to establish connection");
cur = c->q;
while(cur != NULL)
{
next = cur->next;
deliver_fail(dpacket_new(cur->x),"Server Connect Failed");
cur = next;
}
for(x = xmlnode_get_firstchild(c->verifies); x != NULL; x = xmlnode_get_nextsibling(x))
{
jutil_tofrom(x);
dialback_in_verify(c->d, xmlnode_dup(x));
}
pool_free(c->p);
}
void dialback_out_packet(db d, xmlnode x, char *ip)
{
jid to, from, key;
miod md;
int verify = 0;
dboq q;
dboc c;
to = jid_new(xmlnode_pool(x),xmlnode_get_attrib(x,"to"));
from = jid_new(xmlnode_pool(x),xmlnode_get_attrib(x,"from"));
if(to == NULL || from == NULL)
{
log_warn(d->i->id, "dropping packet, invalid to or from: %s", xmlnode2str(x));
xmlnode_free(x);
return;
}
log_debug(ZONE,"dbout packet[%s]: %s",ip,xmlnode2str(x));
if(j_strcmp(from->server,d->i->id) == 0)
{
verify = 1;
xmlnode_put_attrib(x,"from",xmlnode_get_attrib(x,"ofrom"));
xmlnode_hide_attrib(x,"ofrom");
from = jid_new(xmlnode_pool(x),xmlnode_get_attrib(x,"from"));
}
key = jid_new(xmlnode_pool(x),to->server);
jid_set(key, from->server, JID_RESOURCE);
if((md = ghash_get(d->out_ok_db, jid_full(key))) == NULL && verify == 0)
md = ghash_get(d->out_ok_legacy, jid_full(key));
log_debug(ZONE,"outgoing packet with key %s and located existing %X",jid_full(key),md);
if(md != NULL)
{
if(ip != NULL)
register_instance(md->d->i, key->server);
dialback_miod_write(md, x);
return;
}
c = dialback_out_connection(d, key, dialback_ip_get(d, key, ip));
if(verify)
{
if(c == NULL)
{
jutil_tofrom(x);
dialback_in_verify(d, x);
return;
}
if(c->m != NULL)
{
mio_write(c->m, x, NULL, -1);
}else{
xmlnode_insert_tag_node(c->verifies,x);
xmlnode_free(x);
}
return;
}
if(c == NULL)
{
log_warn(d->i->id,"dropping a packet that was missing an ip to connect to: %s",xmlnode2str(x));
xmlnode_free(x);
return;
}
q = pmalloco(xmlnode_pool(x), sizeof(_dboq));
q->stamp = time(NULL);
q->x = x;
q->next = c->q;
c->q = q;
}
void dialback_out_read_db(mio m, int flags, void *arg, xmlnode x)
{
db d = (db)arg;
if(flags != MIO_XML_NODE) return;
if(j_strcmp(xmlnode_get_name(x),"db:verify") == 0)
{
dialback_in_verify(d, x);
return;
}
if(j_strcmp(xmlnode_get_name(x),"stream:error") == 0)
{
log_debug(ZONE,"reveived stream error: %s",xmlnode_get_data(x));
}else{
mio_write(m, NULL, "<stream:error>Not Allowed to send data on this socket!</stream:error>", -1);
}
mio_close(m);
xmlnode_free(x);
}
void dialback_out_read_legacy(mio m, int flags, void *arg, xmlnode x)
{
if(flags != MIO_XML_NODE) return;
if(j_strcmp(xmlnode_get_name(x),"stream:error") == 0)
{
log_debug(ZONE,"reveived stream error: %s",xmlnode_get_data(x));
}else{
mio_write(m, NULL, "<stream:error>Not Allowed to send data on this socket!</stream:error>", -1);
}
mio_close(m);
xmlnode_free(x);
}
void dialback_out_qflush(miod md, dboq q)
{
dboq cur, next;
cur = q;
while(cur != NULL)
{
next = cur->next;
dialback_miod_write(md, cur->x);
cur = next;
}
}
void dialback_out_read(mio m, int flags, void *arg, xmlnode x)
{
dboc c = (dboc)arg;
xmlnode cur;
miod md;
log_debug(ZONE,"dbout read: fd %d flag %d key %s",m->fd, flags, jid_full(c->key));
switch(flags)
{
case MIO_NEW:
log_debug(ZONE,"NEW outgoing server socket connected at %d",m->fd);
cur = xstream_header("jabber:server", c->key->server, NULL);
xmlnode_put_attrib(cur,"xmlns:db","jabber:server:dialback");
mio_write(m, NULL, xstream_header_char(cur), -1);
xmlnode_free(cur);
return;
case MIO_XML_ROOT:
log_debug(ZONE,"Incoming root %s",xmlnode2str(x));
if(j_strcmp(xmlnode_get_attrib(x,"xmlns"),"jabber:server") != 0)
{
mio_write(m, NULL, "<stream:error>Invalid Stream Header!</stream:error>", -1);
mio_close(m);
break;
}
if(ghash_get(c->d->in_id,xmlnode_get_attrib(x,"id")) != NULL)
{
log_alert(c->key->server,"hostname maps back to ourselves!- No service defined for this hostname, can not handle request. Check jabberd configuration.");
mio_write(m, NULL, "<stream:error>Mirror Mirror on the wall</stream:error>", -1);
mio_close(m);
break;
}
if(xmlnode_get_attrib(x,"xmlns:db") == NULL)
{
if(!c->d->legacy)
{
log_notice(c->key->server,"Legacy server access denied due to configuration");
mio_write(m, NULL, "<stream:error>Legacy Access Denied!</stream:error>", -1);
mio_close(m);
break;
}
mio_reset(m, dialback_out_read_legacy, (void *)c->d);
md = dialback_miod_new(c->d, m);
dialback_miod_hash(md, c->d->out_ok_legacy, c->key);
dialback_out_qflush(md, c->q);
c->q = NULL;
dialback_out_connection_cleanup(c);
break;
}
cur = xmlnode_new_tag("db:result");
xmlnode_put_attrib(cur, "to", c->key->server);
xmlnode_put_attrib(cur, "from", c->key->resource);
xmlnode_insert_cdata(cur, dialback_merlin(xmlnode_pool(cur), c->d->secret, c->key->server, xmlnode_get_attrib(x,"id")), -1);
mio_write(m,cur, NULL, 0);
c->m = m;
for(cur = xmlnode_get_firstchild(c->verifies); cur != NULL; cur = xmlnode_get_nextsibling(cur))
{
mio_write(m, xmlnode_dup(cur), NULL, -1);
xmlnode_hide(cur);
}
break;
case MIO_XML_NODE:
if(j_strcmp(xmlnode_get_name(x),"db:result") == 0)
{
if(j_strcmp(xmlnode_get_attrib(x,"from"),c->key->server) != 0 || j_strcmp(xmlnode_get_attrib(x,"to"),c->key->resource) != 0)
{
log_warn(c->d->i->id,"Received illegal dialback validation remote %s != %s or to %s != %s",c->key->server,xmlnode_get_attrib(x,"from"),c->key->resource,xmlnode_get_attrib(x,"to"));
mio_write(m, NULL, "<stream:error>Invalid Dialback Result</stream:error>", -1);
mio_close(m);
break;
}
if(j_strcmp(xmlnode_get_attrib(x,"type"),"valid") == 0)
{
mio_reset(m, dialback_out_read_db, (void *)(c->d));
md = dialback_miod_new(c->d, m);
dialback_miod_hash(md, c->d->out_ok_db, c->key);
dialback_out_qflush(md, c->q);
c->q = NULL;
dialback_out_connection_cleanup(c);
break;
}
log_alert(c->d->i->id,"We were told by %s that our sending name %s is invalid, either something went wrong on their end, we tried using that name improperly, or dns does not resolve to us",c->key->server,c->key->resource);
mio_write(m, NULL, "<stream:error>I guess we're trying to use the wrong name, sorry</stream:error>", -1);
mio_close(m);
break;
}
if(j_strcmp(xmlnode_get_name(x),"db:verify") == 0)
{
dialback_in_verify(c->d, x);
return;
}
log_warn(c->d->i->id,"Dropping connection due to illegal incoming packet on an unverified socket from %s to %s (%s): %s",c->key->resource,c->key->server,m->ip,xmlnode2str(x));
mio_write(m, NULL, "<stream:error>Not Allowed to send data on this socket!</stream:error>", -1);
mio_close(m);
break;
case MIO_CLOSED:
if(c->ip == NULL)
dialback_out_connection_cleanup(c);
else
dialback_out_connect(c);
return;
default:
return;
}
xmlnode_free(x);
}
int _dialback_out_beat_packets(void *arg, const void *key, void *data)
{
dboc c = (dboc)data;
dboq cur, next, last;
int now = time(NULL);
cur = c->q;
while(cur != NULL)
{
if((now - cur->stamp) <= c->d->timeout_packets)
{
last = cur;
cur = cur->next;
continue;
}
next = cur->next;
if(c->q == cur)
c->q = next;
else
last->next = next;
deliver_fail(dpacket_new(cur->x),"Server Connect Timeout");
cur = next;
}
return 1;
}
result dialback_out_beat_packets(void *arg)
{
db d = (db)arg;
ghash_walk(d->out_connecting,_dialback_out_beat_packets,NULL);
return r_DONE;
}