#include "sm.h"
typedef struct service_st *service_t;
struct service_st {
jid_t jid;
char name[257];
char category[257];
char type[257];
xht features;
};
typedef struct disco_st *disco_t;
struct disco_st {
char *category;
char *type;
char *name;
int agents;
int browse;
xht dyn;
xht stat;
xht un;
pkt_t disco_info_result;
pkt_t disco_items_result;
pkt_t agents_result;
pkt_t browse_result;
};
union xhashv
{
void **val;
service_t *svc_val;
sess_t *sess_val;
const char **char_val;
};
static void _disco_unify_walker(xht list, const char *key, void *val, void *arg) {
service_t svc = (service_t) val;
xht dest = (xht) arg;
if(xhash_get(dest, jid_full(svc->jid)) != NULL)
return;
log_debug(ZONE, "unify: %s", jid_full(svc->jid));
xhash_put(dest, jid_full(svc->jid), (void *) svc);
}
static void _disco_unify_lists(disco_t d) {
log_debug(ZONE, "unifying lists");
if(d->un != NULL)
xhash_free(d->un);
d->un = xhash_new(101);
xhash_walk(d->dyn, _disco_unify_walker, (void *) d->un);
xhash_walk(d->stat, _disco_unify_walker, (void *) d->un);
}
static pkt_t _disco_items_result(module_t mod, disco_t d) {
pkt_t pkt;
int ns;
service_t svc;
union xhashv xhv;
pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL);
ns = nad_add_namespace(pkt->nad, uri_DISCO_ITEMS, NULL);
nad_append_elem(pkt->nad, ns, "query", 2);
if(xhash_iter_first(d->un))
do {
xhv.svc_val = &svc;
xhash_iter_get(d->un, NULL, xhv.val);
nad_append_elem(pkt->nad, ns, "item", 3);
nad_append_attr(pkt->nad, -1, "jid", jid_full(svc->jid));
if(svc->name[0] != '\0')
nad_append_attr(pkt->nad, -1, "name", svc->name);
} while(xhash_iter_next(d->un));
return pkt;
}
static pkt_t _disco_info_result(module_t mod, disco_t d) {
pkt_t pkt;
int ns;
const char *key;
pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL);
ns = nad_add_namespace(pkt->nad, uri_DISCO_INFO, NULL);
nad_append_elem(pkt->nad, ns, "query", 2);
nad_append_elem(pkt->nad, ns, "identity", 3);
nad_append_attr(pkt->nad, -1, "category", d->category);
nad_append_attr(pkt->nad, -1, "type", d->type);
nad_append_attr(pkt->nad, -1, "name", d->name);
if(xhash_iter_first(mod->mm->sm->features))
do {
xhash_iter_get(mod->mm->sm->features, &key, NULL);
nad_append_elem(pkt->nad, ns, "feature", 3);
nad_append_attr(pkt->nad, -1, "var", (char *) key);
} while(xhash_iter_next(mod->mm->sm->features));
return pkt;
}
static pkt_t _disco_agents_result(module_t mod, disco_t d) {
pkt_t pkt;
int ns;
const char *key;
service_t svc;
union xhashv xhv;
pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL);
ns = nad_add_namespace(pkt->nad, uri_AGENTS, NULL);
nad_append_elem(pkt->nad, ns, "query", 2);
if(xhash_iter_first(d->un))
do {
xhv.svc_val = &svc;
xhash_iter_get(d->un, &key, xhv.val);
nad_append_elem(pkt->nad, ns, "agent", 3);
nad_append_attr(pkt->nad, -1, "jid", jid_full(svc->jid));
if(svc->name[0] != '\0') {
nad_append_elem(pkt->nad, ns, "name", 4);
nad_append_cdata(pkt->nad, svc->name, strlen(svc->name), 5);
}
nad_append_elem(pkt->nad, ns, "service", 4);
nad_append_cdata(pkt->nad, svc->type, strlen(svc->type), 5);
if(xhash_get(svc->features, uri_REGISTER) != NULL)
nad_append_elem(pkt->nad, ns, "register", 4);
if(xhash_get(svc->features, uri_SEARCH) != NULL)
nad_append_elem(pkt->nad, ns, "search", 4);
if(xhash_get(svc->features, uri_GATEWAY) != NULL)
nad_append_elem(pkt->nad, ns, "transport", 4);
if(strcmp(svc->category, "conference") == 0)
nad_append_elem(pkt->nad, ns, "groupchat", 4);
} while(xhash_iter_next(d->un));
return pkt;
}
static pkt_t _disco_browse_result(module_t mod, disco_t d) {
pkt_t pkt;
int ns;
const char *key;
service_t svc;
union xhashv xhv;
pkt = pkt_create(mod->mm->sm, "iq", "result", NULL, NULL);
ns = nad_add_namespace(pkt->nad, uri_BROWSE, NULL);
nad_append_elem(pkt->nad, ns, "service", 2);
nad_append_attr(pkt->nad, -1, "jid", mod->mm->sm->id);
nad_append_attr(pkt->nad, -1, "type", "jabber");
if(xhash_iter_first(mod->mm->sm->features))
do {
xhash_iter_get(mod->mm->sm->features, &key, NULL);
if(!((strlen(key) >= 7 && (strncmp(key, "jabber:", 7) == 0 || strncmp(key, "http://", 7) == 0)) || strcmp(key, "vcard-temp") == 0))
continue;
nad_append_elem(pkt->nad, ns, "ns", 3);
nad_append_cdata(pkt->nad, (char *) key, strlen(key), 4);
} while(xhash_iter_next(mod->mm->sm->features));
if(xhash_iter_first(d->un))
do {
xhv.svc_val = &svc;
xhash_iter_get(d->un, NULL, xhv.val);
if(strcmp(svc->category, "gateway") == 0)
nad_append_elem(pkt->nad, ns, "service", 3);
else
nad_append_elem(pkt->nad, ns, svc->category, 3);
nad_append_attr(pkt->nad, -1, "jid", jid_full(svc->jid));
if(svc->name[0] != '\0')
nad_append_attr(pkt->nad, -1, "name", svc->name);
nad_append_attr(pkt->nad, -1, "type", svc->type);
if(xhash_iter_first(svc->features))
do {
xhash_iter_get(svc->features, &key, NULL);
if(!((strlen(key) >= 7 && (strncmp(key, "jabber:", 7) == 0 || strncmp(key, "http://", 7) == 0)) || strcmp(key, "vcard-temp") == 0))
continue;
nad_append_elem(pkt->nad, ns, "ns", 4);
nad_append_cdata(pkt->nad, (char *) key, strlen(key), 5);
} while(xhash_iter_next(svc->features));
} while(xhash_iter_next(d->un));
return pkt;
}
static void _disco_generate_packets(module_t mod, disco_t d) {
log_debug(ZONE, "regenerating packets");
if(d->disco_items_result != NULL)
pkt_free(d->disco_items_result);
d->disco_items_result = _disco_items_result(mod, d);
if(d->disco_info_result != NULL)
pkt_free(d->disco_info_result);
d->disco_info_result = _disco_info_result(mod, d);
if(d->agents) {
if(d->agents_result != NULL)
pkt_free(d->agents_result);
d->agents_result = _disco_agents_result(mod, d);
}
if(d->browse) {
if(d->browse_result != NULL)
pkt_free(d->browse_result);
d->browse_result = _disco_browse_result(mod, d);
}
}
static mod_ret_t _disco_pkt_sm_populate(mod_instance_t mi, pkt_t pkt)
{
module_t mod = mi->mod;
disco_t d = (disco_t) mod->private;
int ns, qelem, elem, attr;
service_t svc;
if(pkt->from->node[0] != '\0' || pkt->from->resource[0] != '\0')
{
log_debug(ZONE, "disco response from %s, not allowed", jid_full(pkt->from));
return -stanza_err_NOT_ALLOWED;
}
ns = nad_find_scoped_namespace(pkt->nad, uri_DISCO_INFO, NULL);
qelem = nad_find_elem(pkt->nad, 1, ns, "query", 1);
elem = nad_find_elem(pkt->nad, qelem, ns, "identity", 1);
if(elem < 0)
return -stanza_err_BAD_REQUEST;
svc = xhash_get(d->dyn, jid_full(pkt->from));
if(svc == NULL)
{
svc = (service_t) malloc(sizeof(struct service_st));
memset(svc, 0, sizeof(struct service_st));
svc->jid = jid_dup(pkt->from);
svc->features = xhash_new(9);
xhash_put(d->dyn, jid_full(svc->jid), (void *) svc);
_disco_unify_lists(d);
}
attr = nad_find_attr(pkt->nad, elem, -1, "name", NULL);
if(attr < 0)
svc->name[0] = '\0';
else
snprintf(svc->name, 257, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr));
attr = nad_find_attr(pkt->nad, elem, -1, "category", NULL);
if(attr >= 0)
snprintf(svc->category, 257, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr));
else
strcpy(svc->category, "unknown");
attr = nad_find_attr(pkt->nad, elem, -1, "type", NULL);
if(attr >= 0)
snprintf(svc->type, 257, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr));
else
strcpy(svc->type, "unknown");
elem = nad_find_elem(pkt->nad, qelem, -1, "feature", 1);
while(elem >= 0)
{
attr = nad_find_attr(pkt->nad, elem, -1, "var", NULL);
if(attr < 0)
{
elem = nad_find_elem(pkt->nad, elem, -1, "feature", 0);
continue;
}
xhash_put(svc->features, pstrdupx(xhash_pool(svc->features), NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr)), (void *) 1);
elem = nad_find_elem(pkt->nad, elem, -1, "feature", 0);
}
_disco_generate_packets(mod, d);
pkt_free(pkt);
return mod_HANDLED;
}
static void _disco_sessions_result(module_t mod, disco_t d, pkt_t pkt) {
int ns;
sess_t sess;
union xhashv xhv;
ns = nad_add_namespace(pkt->nad, uri_DISCO_ITEMS, NULL);
nad_append_elem(pkt->nad, ns, "query", 2);
nad_append_attr(pkt->nad, -1, "node", "sessions");
if(xhash_iter_first(mod->mm->sm->sessions))
do {
xhv.sess_val = &sess;
xhash_iter_get(mod->mm->sm->sessions, NULL, xhv.val);
nad_append_elem(pkt->nad, ns, "item", 3);
nad_append_attr(pkt->nad, -1, "jid", jid_full(sess->jid));
nad_append_attr(pkt->nad, -1, "name", "Active session");
} while(xhash_iter_next(mod->mm->sm->sessions));
}
static mod_ret_t _disco_pkt_sm(mod_instance_t mi, pkt_t pkt) {
module_t mod = mi->mod;
disco_t d = (disco_t) mod->private;
pkt_t result;
int node;
if(pkt->type == pkt_IQ_RESULT && pkt->ns == ns_DISCO_INFO)
return _disco_pkt_sm_populate(mi, pkt);
if(pkt->type != pkt_IQ || !(pkt->ns == ns_DISCO_INFO || pkt->ns == ns_DISCO_ITEMS || pkt->ns == ns_BROWSE || pkt->ns == ns_AGENTS))
return mod_PASS;
if(d->disco_info_result == NULL)
_disco_generate_packets(mod, d);
if(pkt->ns == ns_DISCO_INFO) {
result = pkt_dup(d->disco_info_result, jid_full(pkt->from), jid_full(pkt->to));
pkt_id(pkt, result);
pkt_free(pkt);
pkt_router(result);
return mod_HANDLED;
}
if(pkt->ns == ns_AGENTS) {
if(!d->agents)
return -stanza_err_NOT_ALLOWED;
result = pkt_dup(d->agents_result, jid_full(pkt->from), jid_full(pkt->to));
pkt_id(pkt, result);
pkt_free(pkt);
pkt_router(result);
return mod_HANDLED;
}
if(pkt->ns == ns_BROWSE) {
if(!d->browse)
return -stanza_err_NOT_ALLOWED;
result = pkt_dup(d->browse_result, jid_full(pkt->from), jid_full(pkt->to));
pkt_id(pkt, result);
pkt_free(pkt);
pkt_router(result);
return mod_HANDLED;
}
node = nad_find_attr(pkt->nad, 2, -1, "node", NULL);
if(node < 0) {
result = pkt_dup(d->disco_items_result, jid_full(pkt->from), jid_full(pkt->to));
pkt_id(pkt, result);
pkt_free(pkt);
if(aci_check(mod->mm->sm->acls, "disco", result->to)) {
nad_append_elem(result->nad, NAD_ENS(result->nad, 2), "item", 3);
nad_append_attr(result->nad, -1, "jid", mod->mm->sm->id);
nad_append_attr(result->nad, -1, "node", "sessions");
nad_append_attr(result->nad, -1, "name", "Active sessions");
}
pkt_router(result);
return mod_HANDLED;
}
if(NAD_AVAL_L(pkt->nad, node) == 8 && strncmp("sessions", NAD_AVAL(pkt->nad, node), 8) == 0) {
if(!aci_check(mod->mm->sm->acls, "disco", pkt->from))
return -stanza_err_ITEM_NOT_FOUND;
result = pkt_create(mod->mm->sm, "iq", "result", jid_full(pkt->from), jid_full(pkt->to));
pkt_id(pkt, result);
pkt_free(pkt);
_disco_sessions_result(mod, d, result);
pkt_router(result);
return mod_HANDLED;
}
return -stanza_err_ITEM_NOT_FOUND;
}
static mod_ret_t _disco_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) {
module_t mod = mi->mod;
disco_t d = (disco_t) mod->private;
pkt_t result;
if(pkt->type != pkt_IQ || pkt->ns != ns_AGENTS || pkt->to != NULL)
return mod_PASS;
if(!d->agents)
return -stanza_err_NOT_ALLOWED;
if(d->disco_info_result == NULL)
_disco_generate_packets(mod, d);
result = pkt_dup(d->agents_result, NULL, NULL);
pkt_id(pkt, result);
pkt_free(pkt);
pkt_sess(result, sess);
return mod_HANDLED;
}
static mod_ret_t _disco_pkt_router(mod_instance_t mi, pkt_t pkt)
{
module_t mod = mi->mod;
disco_t d = (disco_t) mod->private;
service_t svc;
pkt_t request;
int ns;
if(pkt->from == NULL || !(pkt->rtype & route_ADV))
return mod_PASS;
if(pkt->rtype == route_ADV)
{
log_debug(ZONE, "presence from component %s, issuing discovery request", jid_full(pkt->from));
request = pkt_create(mod->mm->sm, "iq", "get", jid_full(pkt->from), mod->mm->sm->id);
ns = nad_add_namespace(request->nad, uri_DISCO_INFO, NULL);
nad_append_elem(request->nad, ns, "query", 2);
pkt_router(request);
pkt_free(pkt);
return mod_HANDLED;
}
svc = xhash_get(d->dyn, jid_full(pkt->from));
if(svc != NULL)
{
log_debug(ZONE, "dropping entry for %s", jid_full(pkt->from));
xhash_zap(d->dyn, jid_full(pkt->from));
jid_free(svc->jid);
xhash_free(svc->features);
free(svc);
_disco_unify_lists(d);
_disco_generate_packets(mod, d);
}
pkt_free(pkt);
return mod_HANDLED;
}
static void _disco_free_walker(xht h, const char *key, void *val, void *arg) {
service_t svc = (service_t) val;
jid_free(svc->jid);
xhash_free(svc->features);
free(svc);
}
static void _disco_free(module_t mod) {
disco_t d = (disco_t) mod->private;
xhash_walk(d->stat, _disco_free_walker, NULL);
xhash_walk(d->dyn, _disco_free_walker, NULL);
xhash_free(d->stat);
xhash_free(d->dyn);
xhash_free(d->un);
if(d->disco_info_result != NULL) pkt_free(d->disco_info_result);
if(d->disco_items_result != NULL) pkt_free(d->disco_items_result);
if(d->agents_result != NULL) pkt_free(d->agents_result);
if(d->browse_result != NULL) pkt_free(d->browse_result);
free(d);
}
int disco_init(mod_instance_t mi, char *arg)
{
module_t mod = mi->mod;
disco_t d;
nad_t nad;
int items, item, jid, name, category, type, ns;
service_t svc;
if(mod->init) return 0;
log_debug(ZONE, "disco module init");
d = (disco_t) malloc(sizeof(struct disco_st));
memset(d, 0, sizeof(struct disco_st));
d->dyn = xhash_new(51);
d->stat = xhash_new(51);
d->category = config_get_one(mod->mm->sm->config, "discovery.identity.category", 0);
if(d->category == NULL) d->category = "server";
d->type = config_get_one(mod->mm->sm->config, "discovery.identity.type", 0);
if(d->type == NULL) d->type = "im";
d->name = config_get_one(mod->mm->sm->config, "discovery.identity.name", 0);
if(d->name == NULL) d->name = "Jabber IM server";
d->agents = (int) config_get(mod->mm->sm->config, "discovery.agents");
d->browse = (int) config_get(mod->mm->sm->config, "discovery.browse");
if(d->agents)
log_debug(ZONE, "agents compat enabled");
if(d->browse)
log_debug(ZONE, "browse compat enabled");
mod->private = (void *) d;
mod->pkt_sm = _disco_pkt_sm;
mod->in_sess = _disco_in_sess;
mod->pkt_router = _disco_pkt_router;
mod->free = _disco_free;
nad = mod->mm->sm->config->nad;
feature_register(mod->mm->sm, uri_DISCO);
if(d->agents)
feature_register(mod->mm->sm, uri_AGENTS);
if(d->browse)
feature_register(mod->mm->sm, uri_BROWSE);
if((items = nad_find_elem(nad, 0, -1, "discovery", 1)) < 0 || (items = nad_find_elem(nad, items, -1, "items", 1)) < 0)
return 0;
item = nad_find_elem(nad, items, -1, "item", 1);
while(item >= 0)
{
jid = nad_find_attr(nad, item, -1, "jid", NULL);
if(jid < 0)
{
item = nad_find_elem(nad, item, -1, "item", 0);
continue;
}
svc = (service_t) malloc(sizeof(struct service_st));
memset(svc, 0, sizeof(struct service_st));
svc->features = xhash_new(13);
svc->jid = jid_new(mod->mm->sm->pc, NAD_AVAL(nad, jid), NAD_AVAL_L(nad, jid));
xhash_put(d->stat, jid_full(svc->jid), (void *) svc);
name = nad_find_attr(nad, item, -1, "name", NULL);
if(name >= 0)
snprintf(svc->name, 257, "%.*s", NAD_AVAL_L(nad, name), NAD_AVAL(nad, name));
category = nad_find_attr(nad, item, -1, "category", NULL);
if(category >= 0)
snprintf(svc->category, 257, "%.*s", NAD_AVAL_L(nad, category), NAD_AVAL(nad, category));
else
strcpy(svc->category, "unknown");
type = nad_find_attr(nad, item, -1, "type", NULL);
if(type >= 0)
snprintf(svc->type, 257, "%.*s", NAD_AVAL_L(nad, type), NAD_AVAL(nad, type));
else
strcpy(svc->type, "unknown");
ns = nad_find_elem(nad, item, -1, "ns", 1);
while(ns >= 0)
{
if(NAD_CDATA_L(nad, ns) > 0)
xhash_put(svc->features, pstrdupx(xhash_pool(svc->features), NAD_CDATA(nad, ns), NAD_CDATA_L(nad, ns)), (void *) 1);
ns = nad_find_elem(nad, ns, -1, "ns", 0);
}
item = nad_find_elem(nad, item, -1, "item", 0);
log_debug(ZONE, "added %s to static list", jid_full(svc->jid));
}
_disco_unify_lists(d);
return 0;
}