/* -------------------------------------------------------------------------- * * License * * The contents of this file are subject to the Jabber Open Source License * Version 1.0 (the "JOSL"). You may not copy or use this file, in either * source code or executable form, except in compliance with the JOSL. You * may obtain a copy of the JOSL at http://www.jabber.org/ or at * http://www.opensource.org/. * * Software distributed under the JOSL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the JOSL * for the specific language governing rights and limitations under the * JOSL. * * Copyrights * * Portions created by or assigned to Jabber.com, Inc. are * Copyright (c) 1999-2002 Jabber.com, Inc. All Rights Reserved. Contact * information for Jabber.com, Inc. is available at http://www.jabber.com/. * * Portions Copyright (c) 1998-1999 Jeremie Miller. * * Acknowledgements * * Special thanks to the Jabber Open Source Contributors for their * suggestions and support of Jabber. * * Alternatively, the contents of this file may be used under the terms of the * GNU General Public License Version 2 or later (the "GPL"), in which case * the provisions of the GPL are applicable instead of those above. If you * wish to allow use of your version of this file only under the terms of the * GPL and not to allow others to use your version of this file under the JOSL, * indicate your decision by deleting the provisions above and replace them * with the notice and other provisions required by the GPL. If you do not * delete the provisions above, a recipient may use your version of this file * under either the JOSL or the GPL. * * * --------------------------------------------------------------------------*/ #include "dialback.h" /* On outgoing connections, we need to send a result and any verifies, and watch for their responses We'll send: ... We'll get back: We'll send: ... We'll get back: */ /* simple queue for out_queue */ typedef struct dboq_struct { int stamp; xmlnode x; struct dboq_struct *next; } *dboq, _dboq; /* for connecting db sockets */ typedef struct { char *ip; int stamp; db d; jid key; xmlnode verifies; pool p; dboq q; mio m; /* for that short time when we're connected and open, but haven't auth'd ourselves yet */ } *dboc, _dboc; void dialback_out_read(mio m, int flags, void *arg, xmlnode x); /* try to start a connection based upon this connect object */ 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) { /* chop off this ip if there is another, track the other */ *c->ip = '\0'; c->ip++; } log_debug(ZONE, "Attempting to connect to %s at %s",jid_full(c->key),ip); /* get the ip/port for io_select */ #ifdef WITH_IPV6 if(ip[0] == '[') { /* format "[ipaddr]:port" or "[ipaddr]" */ ip++; col=strchr(ip,']'); if(col != NULL) { *col = '\0'; if(col[1]==':') { col++; } } } else { /* format "ipaddr" or "ipaddr:port" */ col = strchr(ip, ':'); /* if it has at least two colons it is an IPv6 address */ 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); } /* new connection object */ 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; /* none, make a new one */ 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); /* insert in the hash */ ghash_put(d->out_connecting, jid_full(c->key), (void *)c); /* start the conneciton process */ dialback_out_connect(c); return c; } /* either we're connected, or failed, or something like that, but the connection process is kaput */ void dialback_out_connection_cleanup(dboc c) { dboq cur, next; xmlnode x; ghash_remove(c->d->out_connecting,jid_full(c->key)); /* if there was never any ->m set but there's a queue yet, then we probably never got connected, just make a note of it */ if(c->m == NULL && c->q != NULL) log_notice(c->key->server,"failed to establish connection"); /* if there's any packets in the queue, flush them! */ cur = c->q; while(cur != NULL) { next = cur->next; deliver_fail(dpacket_new(cur->x),"Server Connect Failed"); cur = next; } /* also kill any validations still waiting */ 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)); /* it'll take these verifies and trash them */ } 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)); /* db:verify packets come in with us as the sender */ if(j_strcmp(from->server,d->i->id) == 0) { verify = 1; /* fix the headers, restore the real from */ 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")); } /* build the standard key */ key = jid_new(xmlnode_pool(x),to->server); jid_set(key, from->server, JID_RESOURCE); /* try to get an active connection */ 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); /* yay! that was easy, just send the packet :) */ if(md != NULL) { /* if we've got an ip sent, and a connected host, we should be registered! */ if(ip != NULL) register_instance(md->d->i, key->server); dialback_miod_write(md, x); return; } /* get a connection to the other server */ c = dialback_out_connection(d, key, dialback_ip_get(d, key, ip)); /* verify requests can't be queued, they need to be sent outright */ if(verify) { if(c == NULL) { jutil_tofrom(x); /* pretend it bounced */ dialback_in_verify(d, x); /* no connection to send db:verify to, bounce back to in to send failure */ return; } /* if the server is already connected, just write it */ if(c->m != NULL) { mio_write(c->m, x, NULL, -1); }else{ /* queue it so that it's written after we're connected */ 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; } /* insert into the queue */ q = pmalloco(xmlnode_pool(x), sizeof(_dboq)); q->stamp = time(NULL); q->x = x; q->next = c->q; c->q = q; } /* handle the events on an outgoing dialback socket, which isn't much of a job */ void dialback_out_read_db(mio m, int flags, void *arg, xmlnode x) { db d = (db)arg; if(flags != MIO_XML_NODE) return; /* it's either a valid verify response, or bust! */ 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, "Not Allowed to send data on this socket!", -1); } mio_close(m); xmlnode_free(x); } /* handle the events on an outgoing legacy socket, in other words, nothing */ void dialback_out_read_legacy(mio m, int flags, void *arg, xmlnode x) { if(flags != MIO_XML_NODE) return; /* other data on the stream? naughty you! */ 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, "Not Allowed to send data on this socket!", -1); } mio_close(m); xmlnode_free(x); } /* util to flush queue to mio */ 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; } } /* handle the early connection process */ 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); /* outgoing conneciton, write the header */ cur = xstream_header("jabber:server", c->key->server, NULL); xmlnode_put_attrib(cur,"xmlns:db","jabber:server:dialback"); /* flag ourselves as dialback capable */ 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)); /* validate namespace */ if(j_strcmp(xmlnode_get_attrib(x,"xmlns"),"jabber:server") != 0) { mio_write(m, NULL, "Invalid Stream Header!", -1); mio_close(m); break; } /* make sure we're not connecting to ourselves */ 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, "Mirror Mirror on the wall", -1); mio_close(m); break; } /* check for old servers */ if(xmlnode_get_attrib(x,"xmlns:db") == NULL) { if(!c->d->legacy) { /* Muahahaha! you suck! *click* */ log_notice(c->key->server,"Legacy server access denied due to configuration"); mio_write(m, NULL, "Legacy Access Denied!", -1); mio_close(m); break; } mio_reset(m, dialback_out_read_legacy, (void *)c->d); /* different handler now */ md = dialback_miod_new(c->d, m); /* set up the mio wrapper */ dialback_miod_hash(md, c->d->out_ok_legacy, c->key); /* this registers us to get stuff now */ dialback_out_qflush(md, c->q); /* flush the queue of packets */ c->q = NULL; dialback_out_connection_cleanup(c); /* we're connected already, trash this */ break; } /* create and send our result request to initiate dialback */ 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); /* well, we're connected to a dialback server, we can at least send verify requests now */ 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: /* watch for a valid result, then we're set to rock! */ 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) { /* naughty... *click* */ 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, "Invalid Dialback Result", -1); mio_close(m); break; } /* process the returned result */ if(j_strcmp(xmlnode_get_attrib(x,"type"),"valid") == 0) { mio_reset(m, dialback_out_read_db, (void *)(c->d)); /* different handler now */ md = dialback_miod_new(c->d, m); /* set up the mio wrapper */ dialback_miod_hash(md, c->d->out_ok_db, c->key); /* this registers us to get stuff directly now */ /* flush the queue of packets */ dialback_out_qflush(md, c->q); c->q = NULL; /* we are connected, and can trash this now */ dialback_out_connection_cleanup(c); break; } /* something went wrong, we were invalid? */ 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, "I guess we're trying to use the wrong name, sorry", -1); mio_close(m); break; } /* otherwise it's either a verify response, or bust! */ 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, "Not Allowed to send data on this socket!", -1); mio_close(m); break; case MIO_CLOSED: if(c->ip == NULL) dialback_out_connection_cleanup(c); /* buh bye! */ else dialback_out_connect(c); /* this one failed, try another */ return; default: return; } xmlnode_free(x); } /* callback for walking the connecting hash tree */ int _dialback_out_beat_packets(void *arg, const void *key, void *data) { dboc c = (dboc)data; dboq cur, next, last; int now = time(NULL); /* time out individual queue'd packets */ cur = c->q; while(cur != NULL) { if((now - cur->stamp) <= c->d->timeout_packets) { last = cur; cur = cur->next; continue; } /* timed out sukkah! */ 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; }