/* -------------------------------------------------------------------------- * * License * * The contents of this file are subject to the Jabber Open Source License * Version 1.0 (the "License"). You may not copy or use this file, in either * source code or executable form, except in compliance with the License. You * may obtain a copy of the License at http://www.jabber.com/license/ or at * http://www.opensource.org/. * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Copyrights * * Portions created by or assigned to Jabber.com, Inc. are * Copyright (c) 1999-2000 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. * * --------------------------------------------------------------------------*/ /* WARNING: the comments in here are random ramblings across the timespan this file has lived, don't rely on them for anything except entertainment (and if this entertains you, heh, you need to get out more :) <jer mode="pondering"> ok, the whole <xdb/> <log/> <service/> and id="" and <host/> have gotten us this far, barely, but it needs some rethought. there seem to be four types of conversations, xdb, log, route, and normal packets each type can be sub-divided based on different criteria, such as hostname, namespace, log type, etc. to do this right, base modules need to be able to assert their own logic within the delivery process and we need to do it efficiently and logically, so the administrator is able to understand where the packets are flowing upon startup, like normal configuration directive callbacks, base modules can register a filter config callback with an arg per-type (xdb/log/service) configuration calls each one of those, which use the arg to identify the instance that they are within this one is special, jabberd tracks which instances each callback is associated with based on during configuration, jabberd tracks which base modules were called so during the configuration process, each base module registers a callback PER-INSTANCE that it's configured in first, break down by first step with a packet to be delivered is identify the instance it belongs to filter_host filter_ns filter_logtype it's an & operation, host & ns must have to match first get type (xdb/log/svc) then find which filters for the list of instances then ask each filter to return a sub-set after all the filters, deliver to the final instance(s) what if we make <route/> addressing seperate, and only use the id="" for it? we need to add a <drain> ... <drain> which matches any undelivered packet, and can send to another jabberd via accept/exec/stdout/etc </jer> <jer mode="pretty sure"> id="" is only required on services HT host_norm HT host_xdb HT host_log HT ns ilist log_notice, log_alert, log_warning to deliver the dpacket, first check the right host hashtable and get a list second, if it's xdb or log, check the ns HT or log type list find intersection of lists if a host, ns, or type is used in any instance, it must be used in ALL of that type, or configuration error! if intersection has multiple results, fail, or none, find uplink or fail deliver() deliver_norm if(host_norm != NULL) ilista = host_norm(host) deliver_xdb if(host_xdb != NULL) ilista = host_xdb(host) if(ns != NULL) ilistb = ns(namespace) i = intersect(ilista, ilistb) if result multiple, return NULL if result single, return if result NULL, return uplink deliver_instance(i) deliver_log host_log, if logtype_flag switch on type </jer> */ /* modified by Lukas Karwacki registering and unregistering instances are thread safe now */ #include "jabberd.h" extern pool jabberd__runtime; typedef struct ilist_struct { WPHASH_BUCKET; instance i; struct ilist_struct *next; } *ilist, _ilist; ilist ilist_add(ilist il, instance i, pool p) { ilist cur, ilnew; for(cur = il; cur != NULL; cur = cur->next) if(cur->i == i) return il; ilnew = pmalloco(p, sizeof(_ilist)); ilnew->i = i; ilnew->next = il; return ilnew; } ilist ilist_rem(ilist il, instance i) { ilist cur; if(il == NULL) return NULL; if(il->i == i) return il->next; for(cur = il; cur->next != NULL; cur = cur->next) if(cur->next->i == i) { cur->next = cur->next->next; return il; } return il; } /* hash table rebuild when add/remove instance */ void deliver_hashtable_rebuild(wpxht h, const char *key, void *val, void *arg) { ilist a,cur; wpxht h2 = (wpxht)arg; char *nkey; a = NULL; for (cur = (ilist)val; cur != NULL; cur = cur->next) { a = ilist_add(a, cur->i, h2->p); } nkey = pstrdup(h2->p,key); wpxhash_put(h2,nkey,a); } /* remove old hash after change */ result deliver_hashtable_free(void * arg) { wpxht ht = (wpxht)arg; pool_free(ht->p); return r_UNREG; } jabber_deliver j__deliver; /* utility to find the right hashtable based on type */ inline HASHTABLE deliver_hashtable(ptype type) { switch(type) { case p_LOG: return j__deliver->deliver__hlog; case p_XDB: return j__deliver->deliver__hxdb; default: return j__deliver->deliver__hnorm; } } inline void set_deliver_hash_table(ptype type,wpxht ht_new) { switch(type) { case p_LOG: j__deliver->deliver__hlog = ht_new; return; case p_XDB: j__deliver->deliver__hxdb = ht_new; return; default: j__deliver->deliver__hnorm = ht_new; return; } } inline int deliver_hashtable_size(ptype type) { switch(type) { case p_LOG: return 31; case p_XDB: return 31; default: return 107; } } /* utility to find the right ilist in the hashtable */ inline ilist deliver_hashmatch(HASHTABLE ht, char *key) { ilist l; l = wpxhash_get(ht, key); if(l == NULL) { l = wpxhash_get(ht, "*"); } return l; } /* find and return the instance intersecting both lists, or react intelligently */ inline instance deliver_intersect(ilist a, ilist b) { ilist cur = NULL, cur2; instance i = NULL; if(a == NULL) cur = b; if(b == NULL) cur = a; if(cur != NULL) /* we've only got one list */ { if(cur->next != NULL) return NULL; /* multiple results is a failure */ else return cur->i; } for(cur = a; cur != NULL; cur = cur->next) { for(cur2 = b; cur2 != NULL; cur2 = cur2->next) { if(cur->i == cur2->i) /* yay, intersection! */ { if(i != NULL) return NULL; /* multiple results is a failure */ i = cur->i; } } } if(i == NULL) /* no match, use uplink */ return j__deliver->deliver__uplink; return i; } /* special case handler for xdb calls @-internal */ void deliver_internal(dpacket p, instance i) { xmlnode x; char *ns = xmlnode_get_attrib(p->x, "ns"); log_debug(ZONE,"@-internal processing %s",xmlnode2str(p->x)); if(j_strcmp(p->id->user,"config") == 0) { /* config@-internal means it's a special xdb request to get data from the config file */ for(x = xmlnode_get_firstchild(i->x); x != NULL; x = xmlnode_get_nextsibling(x)) { if(j_strcmp(xmlnode_get_attrib(x,"xmlns"),ns) != 0) continue; /* insert results */ xmlnode_insert_tag_node(p->x, x); } /* reformat packet as a reply */ xmlnode_put_attrib(p->x,"type","result"); jutil_tofrom(p->x); p->type = p_NORM; /* deliver back to the sending instance */ deliver_instance(i, p); return; } if(j_strcmp(p->id->user,"host") == 0) { /* dynamic register_instance crap */ register_instance(i,p->id->resource); return; } if(j_strcmp(p->id->user,"unhost") == 0) { /* dynamic register_instance crap */ unregister_instance(i,p->id->resource); return; } } /* register this instance as a possible recipient of packets to this host */ void register_instance(instance i, char *host) { ilist l; wpxht ht,ht_new; log_debug(ZONE,"Registering %s with instance %s",host,i->id); /* fail, since ns is required on every XDB instance if it's used on any one */ if(i->type == p_XDB && j__deliver->deliver__ns != NULL && xmlnode_get_tag(i->x, "ns") == NULL) { fprintf(stderr, "Configuration Error! If <ns> is used in any xdb section, it must be used in all sections for correct packet routing."); exit(1); } /* fail, since logtype is required on every LOG instance if it's used on any one */ if(i->type == p_LOG && j__deliver->deliver__logtype != NULL && xmlnode_get_tag(i->x, "logtype") == NULL) { fprintf(stderr, "Configuration Error! If <logtype> is used in any log section, it must be used in all sections for correct packet routing."); exit(1); } SEM_LOCK(j__deliver->deliver__sem); ht = deliver_hashtable(i->type); /* copy hash table */ ht_new = wpxhash_new_extra(deliver_hashtable_size(i->type), deliver_hashtable_size(i->type)*sizeof(_ilist)); wpxhash_walk(ht,deliver_hashtable_rebuild,ht_new); /* add element to new hash */ l = wpxhash_get(ht_new, host); l = ilist_add(l, i, ht_new->p); wpxhash_put(ht_new, pstrdup(ht_new->p,host), (void *)l); /* replace old hash */ set_deliver_hash_table(i->type,ht_new); SEM_UNLOCK(j__deliver->deliver__sem); /* free old hash */ if(ht != NULL) { register_beat(20, deliver_hashtable_free, (void *)ht); } } void unregister_instance(instance i, char *host) { ilist l; wpxht ht,ht_new; log_debug(ZONE,"Unregistering %s with instance %s",host,i->id); SEM_LOCK(j__deliver->deliver__sem); ht = deliver_hashtable(i->type); /* copy hash table */ ht_new = wpxhash_new_extra(deliver_hashtable_size(i->type), deliver_hashtable_size(i->type)*sizeof(_ilist)); wpxhash_walk(ht,deliver_hashtable_rebuild,ht_new); /* remove element */ l = wpxhash_get(ht_new, host); l = ilist_rem(l, i); if(l == NULL) wpxhash_zap(ht_new, host); else wpxhash_put(ht_new, pstrdup(ht_new->p,host), (void *)l); /* replace old hash */ set_deliver_hash_table(i->type,ht_new); SEM_UNLOCK(j__deliver->deliver__sem); /* free old hash */ if(ht != NULL) { register_beat(20, deliver_hashtable_free, (void *)ht); } } result deliver_config_host(instance i, xmlnode x, void *arg) { char *host; int c; if(i == NULL) return r_PASS; host = xmlnode_get_data(x); if(host == NULL) { register_instance(i, "*"); return r_DONE; } for(c = 0; host[c] != '\0'; c++) if(isspace(host[c])) { xmlnode_put_attrib(x,"error","The host tag contains illegal whitespace."); return r_ERR; } register_instance(i, host); return r_DONE; } result deliver_config_ns(instance i, xmlnode x, void *arg) { ilist l; wpxht ht,ht_new; char *ns, star[] = "*"; if(i == NULL) return r_PASS; if(i->type != p_XDB) return r_ERR; ns = xmlnode_get_data(x); if(ns == NULL) ns = pstrdup(xmlnode_pool(x),star); log_debug(ZONE,"Registering namespace %s with instance %s",ns,i->id); SEM_LOCK(j__deliver->deliver__sem); ht = j__deliver->deliver__ns; ht_new = wpxhash_new_extra(deliver_hashtable_size(p_XDB), deliver_hashtable_size(p_XDB)*sizeof(_ilist)); if(ht != NULL) { wpxhash_walk(ht,deliver_hashtable_rebuild,ht_new); } l = wpxhash_get(ht_new, ns); l = ilist_add(l, i, ht_new->p); wpxhash_put(ht_new, pstrdup(ht_new->p,ns), (void *)l); j__deliver->deliver__ns = ht_new; SEM_UNLOCK(j__deliver->deliver__sem); /* free old hash */ if(ht != NULL) { register_beat(20, deliver_hashtable_free, (void *)ht); } return r_DONE; } result deliver_config_logtype(instance i, xmlnode x, void *arg) { ilist l; wpxht ht,ht_new; char *type, star[] = "*"; if(i == NULL) return r_PASS; if(i->type != p_LOG) return r_ERR; type = xmlnode_get_data(x); if(type == NULL) type = pstrdup(xmlnode_pool(x),star); log_debug(ZONE,"Registering logtype %s with instance %s",type,i->id); SEM_LOCK(j__deliver->deliver__sem); ht = j__deliver->deliver__logtype; ht_new = wpxhash_new_extra(deliver_hashtable_size(p_LOG), deliver_hashtable_size(p_LOG)*sizeof(_ilist)); if(ht != NULL) { wpxhash_walk(ht,deliver_hashtable_rebuild,ht_new); } l = wpxhash_get(ht_new, type); l = ilist_add(l, i, ht_new->p); wpxhash_put(ht_new, pstrdup(ht_new->p,type), (void *)l); j__deliver->deliver__logtype = ht_new; SEM_UNLOCK(j__deliver->deliver__sem); /* free old hash */ if(ht != NULL) { register_beat(20, deliver_hashtable_free, (void *)ht); } return r_DONE; } result deliver_config_uplink(instance i, xmlnode x, void *arg) { if(i == NULL) return r_PASS; if(j__deliver->deliver__uplink != NULL) return r_ERR; j__deliver->deliver__uplink = i; return r_DONE; } /* NULL that sukkah! */ result deliver_null(instance i, dpacket p, void* arg) { pool_free(p->p); return r_DONE; } result deliver_config_null(instance i, xmlnode x, void *arg) { if(i == NULL) return r_PASS; register_phandler(i, o_DELIVER, deliver_null, NULL); return r_DONE; } void deliver(dpacket p, instance i) { ilist a, b; /* if(deliver__flag == 1 && p == NULL && i == NULL) { deliver_msg d; while((d=(deliver_msg)pth_msgport_get(deliver__mp))!=NULL) { deliver(d->p,d->i); } pth_msgport_destroy(deliver__mp); deliver__mp = NULL; deliver__flag = -1; } */ /* Ensure the packet is valid */ if (p == NULL) return; /* catch the @-internal xdb crap */ if(p->type == p_XDB && *(p->host) == '-') return deliver_internal(p, i); /* if(deliver__flag == 0) { deliver_msg d = pmalloco(xmlnode_pool(p->x) ,sizeof(_deliver_msg)); if(deliver__mp == NULL) deliver__mp = pth_msgport_create("deliver__"); d->i = i; d->p = p; pth_msgport_put(deliver__mp, (void*)d); return; } */ log_debug(ZONE,"DELIVER %d:%s %s",p->type,p->host,xmlnode2str(p->x)); b = NULL; /* take ilist from hashtable */ a = deliver_hashmatch(deliver_hashtable(p->type), p->host); if(p->type == p_XDB) b = deliver_hashmatch(j__deliver->deliver__ns, xmlnode_get_attrib(p->x,"ns")); else if(p->type == p_LOG) b = deliver_hashmatch(j__deliver->deliver__logtype, xmlnode_get_attrib(p->x,"type")); deliver_instance(deliver_intersect(a, b), p); } void deliver_init(void) { j__deliver = pmalloco(jabberd__runtime,sizeof(_jabber_deliver)); j__deliver->deliver__ns = NULL; j__deliver->deliver__logtype = NULL; j__deliver->deliver__uplink = NULL; j__deliver->deliver__flag = 0; SEM_INIT(j__deliver->deliver__sem); j__deliver->deliver__hnorm = wpxhash_new_extra(deliver_hashtable_size(p_NORM), deliver_hashtable_size(p_NORM)*sizeof(_ilist)); j__deliver->deliver__hlog = wpxhash_new_extra(deliver_hashtable_size(p_LOG), deliver_hashtable_size(p_LOG)*sizeof(_ilist)); j__deliver->deliver__hxdb = wpxhash_new_extra(deliver_hashtable_size(p_XDB), deliver_hashtable_size(p_XDB)*sizeof(_ilist)); register_config("host",deliver_config_host,NULL); register_config("ns",deliver_config_ns,NULL); register_config("logtype",deliver_config_logtype,NULL); register_config("uplink",deliver_config_uplink,NULL); register_config("null",deliver_config_null,NULL); } /* register a function to handle delivery for this instance */ void register_phandler(instance id, order o, phandler f, void *arg) { handel newh, h1, last; pool p; /* create handel and setup */ p = pool_new(); /* use our own little pool */ newh = pmalloc_x(p, sizeof(_handel), 0); newh->p = p; newh->f = f; newh->arg = arg; newh->o = o; /* if we're the only handler, easy */ if(id->hds == NULL) { id->hds = newh; return; } /* place according to handler preference */ switch(o) { case o_PRECOND: /* always goes to front of list */ newh->next = id->hds; id->hds = newh; break; case o_COND: h1 = id->hds; last = NULL; while(h1->o < o_PREDELIVER) { last = h1; h1 = h1->next; if(h1 == NULL) break; } if(last == NULL) { /* goes to front of list */ newh->next = h1; id->hds = newh; } else if(h1 == NULL) { /* goes at end of list */ last->next = newh; } else { /* goes between last and h1 */ newh->next = h1; last->next = newh; } break; case o_PREDELIVER: h1 = id->hds; last = NULL; while(h1->o < o_DELIVER) { last = h1; h1 = h1->next; if(h1 == NULL) break; } if(last == NULL) { /* goes to front of list */ newh->next = h1; id->hds = newh; } else if(h1 == NULL) { /* goes at end of list */ last->next = newh; } else { /* goes between last and h1 */ newh->next = h1; last->next = newh; } break; case o_DELIVER: /* always add to the end */ for(h1 = id->hds; h1->next != NULL; h1 = h1->next); h1->next = newh; break; } } /* bounce on the delivery, use the result to better gague what went wrong */ void deliver_fail(dpacket p, char *err) { terror t; char message[MAX_LOG_SIZE]; log_debug(ZONE,"delivery failed (%s)",err); if(p==NULL) return; switch(p->type) { case p_LOG: /* stderr and drop */ snprintf(message, MAX_LOG_SIZE, "WARNING! Logging Failed: %s\n",xmlnode2str(p->x)); fprintf(stderr, "%s\n", message); pool_free(p->p); break; case p_XDB: /* log_warning and drop */ log_warn(p->host,"dropping a %s xdb request to %s for %s",xmlnode_get_attrib(p->x,"type"),xmlnode_get_attrib(p->x,"to"),xmlnode_get_attrib(p->x,"ns")); /* drop through and treat like a route failure */ case p_ROUTE: /* route packet bounce */ if(j_strcmp(xmlnode_get_attrib(p->x,"type"),"error") == 0) { /* already bounced once, drop */ log_warn(p->host,"dropping a routed packet to %s from %s: %s",xmlnode_get_attrib(p->x,"to"),xmlnode_get_attrib(p->x,"from"),err); pool_free(p->p); }else{ log_notice(p->host,"bouncing a routed packet to %s from %s: %s",xmlnode_get_attrib(p->x,"to"),xmlnode_get_attrib(p->x,"from"),err); /* turn into an error and bounce */ jutil_tofrom(p->x); xmlnode_put_attrib(p->x,"type","error"); xmlnode_put_attrib(p->x,"error",err); deliver(dpacket_new(p->x),NULL); } break; case p_NORM: /* normal packet bounce */ if(j_strcmp(xmlnode_get_attrib(p->x,"type"),"error") == 0) { /* can't bounce an error */ log_warn(p->host,"dropping a packet to %s from %s: %s",xmlnode_get_attrib(p->x,"to"),xmlnode_get_attrib(p->x,"from"),err); pool_free(p->p); }else{ char* xto=xmlnode_get_attrib(p->x,"to"); if (xto && !strchr(xto,'@')) { xmlnode_free(p->x); break; } log_notice(p->host,"bouncing a packet to %s from %s: %s",xto,xmlnode_get_attrib(p->x,"from"),err); /* turn into an error */ if(err == NULL) { jutil_error(p->x,TERROR_EXTERNAL); }else{ t.code = 502; t.msg[0] = '\0'; strcat(t.msg,err); /* c sucks */ jutil_error(p->x,t); } deliver(dpacket_new(p->x),NULL); } break; case p_NONE: break; } } /* actually perform the delivery to an instance */ void deliver_instance(instance i, dpacket p) { handel h, hlast; result r; dpacket pig = p; if(i == NULL) { deliver_fail(p, "Unable to deliver, destination unknown"); return; } log_debug(ZONE,"delivering to instance '%s'",i->id); /* try all the handlers */ hlast = h = i->hds; while(h != NULL) { /* there may be multiple delivery handlers, make a backup copy first if we have to */ if(h->o == o_DELIVER && h->next != NULL) pig = dpacket_copy(p); /* call the handler */ if((r = (h->f)(i,p,h->arg)) == r_ERR) { deliver_fail(p, "Internal Delivery Error"); break; } /* if a non-delivery handler says it handled it, we have to be done */ if(h->o != o_DELIVER && r == r_DONE) break; /* if a conditional handler wants to halt processing */ if(h->o == o_COND && r == r_LAST) break; /* deal with that backup copy we made */ if(h->o == o_DELIVER && h->next != NULL) { if(r == r_DONE) /* they ate it, use copy */ p = pig; else pool_free(pig->p); /* they never used it, trash copy */ } /* unregister this handler */ if(r == r_UNREG) { /* XXX make it thread safe */ if(h == i->hds) { /* removing the first in the list */ i->hds = h->next; pool_free(h->p); hlast = h = i->hds; }else{ /* removing from anywhere in the list */ hlast->next = h->next; pool_free(h->p); h = hlast->next; } continue; } hlast = h; h = h->next; } } dpacket dpacket_new(xmlnode x) { dpacket p; char *str; if(x == NULL) return NULL; /* create the new packet */ p = pmalloc_x(xmlnode_pool(x),sizeof(_dpacket),0); p->x = x; p->p = xmlnode_pool(x); /* determine it's type */ p->type = p_NORM; if(*(xmlnode_get_name(x)) == 'r') p->type = p_ROUTE; else if(*(xmlnode_get_name(x)) == 'x') p->type = p_XDB; else if(*(xmlnode_get_name(x)) == 'l') p->type = p_LOG; /* xdb results are shipped as normal packets */ if(p->type == p_XDB && (str = xmlnode_get_attrib(p->x,"type")) != NULL && (*str == 'r' || *str == 'e' )) p->type = p_NORM; /* determine who to route it to, overriding the default to="" attrib only for logs where we use from */ if(p->type == p_LOG) p->id = jid_new(p->p, xmlnode_get_attrib(x, "from")); else p->id = jid_new(p->p, xmlnode_get_attrib(x, "to")); if(p->id == NULL) { log_warn(NULL,"Packet Delivery Failed, invalid packet, dropping %s",xmlnode2str(x)); xmlnode_free(x); return NULL; } /* make sure each packet has the basics, norm has a to/from, log has a type, xdb has a namespace */ switch(p->type) { case p_LOG: if(xmlnode_get_attrib(x,"type")==NULL) p=NULL; break; case p_XDB: if(xmlnode_get_attrib(x,"ns") == NULL) p=NULL; /* fall through */ case p_NORM: if(xmlnode_get_attrib(x,"to")==NULL||xmlnode_get_attrib(x,"from")==NULL) p=NULL; break; case p_ROUTE: if(xmlnode_get_attrib(x,"to")==NULL) p=NULL; break; case p_NONE: p=NULL; break; } if(p==NULL) { log_warn(NULL,"Packet Delivery Failed, invalid packet, dropping %s",xmlnode2str(x)); xmlnode_free(x); return NULL; } p->host = p->id->server; return p; } dpacket dpacket_copy(dpacket p) { dpacket p2; p2 = dpacket_new(xmlnode_dup(p->x)); return p2; }