#include "sm.h"
static void _roster_free_walker(xht roster, const char *key, void *val, void *arg)
{
item_t item = (item_t) val;
int i;
jid_free(item->jid);
if(item->name != NULL)
free(item->name);
for(i = 0; i < item->ngroups; i++)
free(item->groups[i]);
free(item->groups);
free(item);
}
static void _roster_free(user_t user)
{
if(user->roster == NULL)
return;
log_debug(ZONE, "freeing roster for %s", jid_user(user->jid));
xhash_walk(user->roster, _roster_free_walker, NULL);
xhash_free(user->roster);
user->roster = NULL;
}
static void _roster_save_item(user_t user, item_t item) {
os_t os;
os_object_t o;
char filter[4096];
int i;
log_debug(ZONE, "saving roster item %s for %s", jid_full(item->jid), jid_user(user->jid));
os = os_new();
o = os_object_new(os);
os_object_put(o, "jid", jid_full(item->jid), os_type_STRING);
if(item->name != NULL)
os_object_put(o, "name", item->name, os_type_STRING);
os_object_put(o, "to", &item->to, os_type_BOOLEAN);
os_object_put(o, "from", &item->from, os_type_BOOLEAN);
os_object_put(o, "ask", &item->ask, os_type_INTEGER);
snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(item->jid)), jid_full(item->jid));
storage_replace(user->sm->st, "roster-items", jid_user(user->jid), filter, os);
os_free(os);
snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(item->jid)), jid_full(item->jid));
if(item->ngroups == 0) {
storage_delete(user->sm->st, "roster-groups", jid_user(user->jid), filter);
return;
}
os = os_new();
for(i = 0; i < item->ngroups; i++) {
o = os_object_new(os);
os_object_put(o, "jid", jid_full(item->jid), os_type_STRING);
os_object_put(o, "group", item->groups[i], os_type_STRING);
}
storage_replace(user->sm->st, "roster-groups", jid_user(user->jid), filter, os);
os_free(os);
}
static void _roster_insert_item(pkt_t pkt, item_t item, int elem)
{
int ns, i;
char *sub;
ns = nad_add_namespace(pkt->nad, uri_CLIENT, NULL);
elem = nad_insert_elem(pkt->nad, elem, ns, "item", NULL);
nad_set_attr(pkt->nad, elem, -1, "jid", jid_full(item->jid), 0);
if(item->to && item->from)
sub = "both";
else if(item->to)
sub = "to";
else if(item->from)
sub = "from";
else
sub = "none";
nad_set_attr(pkt->nad, elem, -1, "subscription", sub, 0);
if(item->ask == 1)
nad_set_attr(pkt->nad, elem, -1, "ask", "subscribe", 9);
else if(item->ask == 2)
nad_set_attr(pkt->nad, elem, -1, "ask", "unsubscribe", 11);
if(item->name != NULL)
nad_set_attr(pkt->nad, elem, -1, "name", item->name, 0);
for(i = 0; i < item->ngroups; i++)
nad_insert_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", item->groups[i]);
}
static void _roster_push(user_t user, pkt_t pkt, int mod_index)
{
sess_t scan;
pkt_t push;
for(scan = user->sessions; scan != NULL; scan = scan->next)
{
if((int) scan->module_data[mod_index] == 0)
continue;
push = pkt_dup(pkt, jid_full(scan->jid), NULL);
pkt_sess(push, scan);
}
}
static mod_ret_t _roster_in_sess_s10n(mod_instance_t mi, sess_t sess, pkt_t pkt)
{
module_t mod = mi->mod;
item_t item;
pkt_t push;
int ns, elem;
log_debug(ZONE, "got s10n packet");
if(pkt->to == NULL)
return -stanza_err_BAD_REQUEST;
if(pkt->from != NULL)
jid_free(pkt->from);
pkt->from = jid_new(mod->mm->sm->pc, jid_user(sess->jid), -1);
nad_set_attr(pkt->nad, 1, -1, "from", jid_full(pkt->from), 0);
item = xhash_get(sess->user->roster, jid_full(pkt->to));
if(item == NULL)
{
if(pkt->type == pkt_S10N_UN || pkt->type == pkt_S10N_UNED)
return mod_PASS;
item = (item_t) malloc(sizeof(struct item_st));
memset(item, 0, sizeof(struct item_st));
item->jid = jid_dup(pkt->to);
xhash_put(sess->user->roster, jid_full(item->jid), (void *) item);
log_debug(ZONE, "made new empty roster item for %s", jid_full(item->jid));
}
if(pkt->type == pkt_S10N)
item->ask = 1;
else if(pkt->type == pkt_S10N_UN)
item->ask = 2;
else if(pkt->type == pkt_S10N_ED)
{
item->ask = 0;
item->from = 1;
pres_roster(sess, item);
}
else if(pkt->type == pkt_S10N_UNED)
{
item->ask = 0;
item->from = 0;
pres_roster(sess, item);
}
_roster_save_item(sess->user, item);
push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL);
ns = nad_add_namespace(push->nad, uri_ROSTER, NULL);
elem = nad_append_elem(push->nad, ns, "query", 3);
_roster_insert_item(push, item, elem);
_roster_push(sess->user, push, mod->index);
pkt_free(push);
return mod_PASS;
}
static void _roster_get_walker(xht roster, const char *id, void *val, void *arg)
{
item_t item = (item_t) val;
pkt_t pkt = (pkt_t) arg;
_roster_insert_item(pkt, item, 2);
}
static void _roster_set_item(pkt_t pkt, int elem, sess_t sess, mod_instance_t mi)
{
module_t mod = mi->mod;
int attr, ns, i;
jid_t jid;
item_t item;
pkt_t push;
char filter[4096];
attr = nad_find_attr(pkt->nad, elem, -1, "jid", NULL);
jid = jid_new(pkt->sm->pc, NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr));
if(jid == NULL) {
log_debug(ZONE, "jid failed prep check, skipping");
return;
}
if(nad_find_attr(pkt->nad, elem, -1, "subscription", "remove") >= 0)
{
item = xhash_get(sess->user->roster, jid_full(jid));
if(item != NULL)
{
if(item->from) {
log_debug(ZONE, "telling %s that they're unsubscribed", jid_user(item->jid));
pkt_router(pkt_create(sess->user->sm, "presence", "unsubscribed", jid_user(item->jid), jid_user(sess->jid)));
}
item->from = 0;
if(item->to) {
log_debug(ZONE, "unsubscribing from %s", jid_user(item->jid));
pkt_router(pkt_create(sess->user->sm, "presence", "unsubscribe", jid_user(item->jid), jid_user(sess->jid)));
}
item->to = 0;
pres_roster(sess, item);
xhash_zap(sess->user->roster, jid_full(jid));
_roster_free_walker(NULL, (const char *) jid_full(jid), (void *) item, NULL);
snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(jid)), jid_full(jid));
storage_delete(sess->user->sm->st, "roster-items", jid_user(sess->jid), filter);
snprintf(filter, 4096, "(jid=%i:%s)", strlen(jid_full(jid)), jid_full(jid));
storage_delete(sess->user->sm->st, "roster-groups", jid_user(sess->jid), filter);
}
log_debug(ZONE, "removed %s from roster", jid_full(jid));
push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL);
ns = nad_add_namespace(push->nad, uri_ROSTER, NULL);
nad_append_elem(push->nad, ns, "query", 3);
elem = nad_append_elem(push->nad, ns, "item", 4);
nad_set_attr(push->nad, elem, -1, "jid", jid_full(jid), 0);
nad_set_attr(push->nad, elem, -1, "subscription", "remove", 6);
_roster_push(sess->user, push, mod->index);
pkt_free(push);
jid_free(jid);
return;
}
item = xhash_get(sess->user->roster, jid_full(jid));
if(item == NULL)
{
item = (item_t) malloc(sizeof(struct item_st));
memset(item, 0, sizeof(struct item_st));
item->jid = jid;
xhash_put(sess->user->roster, jid_full(item->jid), (void *) item);
log_debug(ZONE, "created new roster item %s", jid_full(item->jid));
}
else
jid_free(jid);
if(item->name != NULL) {
free(item->name);
item->name = NULL;
}
attr = nad_find_attr(pkt->nad, elem, -1, "name", NULL);
if(attr >= 0)
{
item->name = (char *) malloc(sizeof(char) * (NAD_AVAL_L(pkt->nad, attr) + 1));
sprintf(item->name, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr));
}
if(item->groups != NULL)
{
for(i = 0; i < item->ngroups; i++)
free(item->groups[i]);
free(item->groups);
item->ngroups = 0;
item->groups = NULL;
}
elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", 1);
while(elem >= 0)
{
if(NAD_CDATA_L(pkt->nad, elem) >= 0)
{
item->groups = (char **) realloc(item->groups, sizeof(char *) * (item->ngroups + 1));
item->groups[item->ngroups] = (char *) malloc(sizeof(char) * (NAD_CDATA_L(pkt->nad, elem) + 1));
sprintf(item->groups[item->ngroups], "%.*s", NAD_CDATA_L(pkt->nad, elem), NAD_CDATA(pkt->nad, elem));
item->ngroups++;
}
elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", 0);
}
log_debug(ZONE, "added %s to roster (to %d from %d ask %d name %s ngroups %d)", jid_full(item->jid), item->to, item->from, item->ask, item->name, item->ngroups);
_roster_save_item(sess->user, item);
push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL);
ns = nad_add_namespace(push->nad, uri_ROSTER, NULL);
elem = nad_append_elem(push->nad, ns, "query", 3);
_roster_insert_item(push, item, elem);
_roster_push(sess->user, push, mod->index);
pkt_free(push);
}
static mod_ret_t _roster_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt)
{
module_t mod = mi->mod;
int elem, attr;
pkt_t result;
if(pkt->type & pkt_S10N)
return _roster_in_sess_s10n(mi, sess, pkt);
if(pkt->ns != ns_ROSTER)
return mod_PASS;
if(pkt->type == pkt_IQ_RESULT) {
pkt_free(pkt);
return mod_HANDLED;
}
if(pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET)
return mod_PASS;
if(pkt->type == pkt_IQ)
{
xhash_walk(sess->user->roster, _roster_get_walker, (void *) pkt);
nad_set_attr(pkt->nad, 1, -1, "type", "result", 6);
pkt_sess(pkt, sess);
sess->module_data[mod->index] = (void *) 1;
return mod_HANDLED;
}
elem = nad_find_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 2), "item", 1);
if(elem < 0)
return -stanza_err_BAD_REQUEST;
while(elem >= 0)
{
attr = nad_find_attr(pkt->nad, elem, -1, "jid", NULL);
if(attr < 0 || NAD_AVAL_L(pkt->nad, attr) == 0)
{
log_debug(ZONE, "no jid on this item, aborting");
return -stanza_err_BAD_REQUEST;
}
_roster_set_item(pkt, elem, sess, mi);
elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "item", 0);
}
result = pkt_create(sess->user->sm, "iq", "result", NULL, NULL);
pkt_id(pkt, result);
pkt_sess(result, sess);
pkt_free(pkt);
return mod_HANDLED;
}
static mod_ret_t _roster_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt)
{
module_t mod = mi->mod;
item_t item;
int ns, elem;
if(!(pkt->type & pkt_S10N))
return mod_PASS;
if(pkt->rtype & route_ERROR) {
pkt_free(pkt);
return mod_HANDLED;
}
item = (item_t) xhash_get(user->roster, jid_full(pkt->from));
if(item == NULL) {
if(pkt->type == pkt_S10N || pkt->type == pkt_S10N_UN)
return mod_PASS;
pkt_free(pkt);
return mod_HANDLED;
}
if(pkt->type == pkt_S10N)
{
if(item->from)
{
nad_set_attr(pkt->nad, 1, -1, "type", "subscribed", 10);
pkt_router(pkt_tofrom(pkt));
if(user->top != NULL)
pres_roster(user->top, item);
return mod_HANDLED;
}
return mod_PASS;
}
if(pkt->type == pkt_S10N_UN)
{
if(!item->from)
{
nad_set_attr(pkt->nad, 1, -1, "type", "unsubscribed", 12);
pkt_router(pkt_tofrom(pkt));
if(user->top != NULL)
pres_roster(user->top, item);
return mod_HANDLED;
}
return mod_PASS;
}
if(pkt->type == pkt_S10N_ED)
item->to = 1;
else
item->to = 0;
item->ask = 0;
_roster_save_item(user, item);
if(user->sessions == NULL)
return mod_PASS;
pkt = pkt_create(user->sm, "iq", "set", NULL, NULL);
ns = nad_add_namespace(pkt->nad, uri_ROSTER, NULL);
elem = nad_append_elem(pkt->nad, ns, "query", 3);
_roster_insert_item(pkt, item, elem);
_roster_push(user, pkt, mod->index);
pkt_free(pkt);
return mod_PASS;
}
static int _roster_user_load(mod_instance_t mi, user_t user) {
os_t os;
os_object_t o;
char *str;
item_t item, olditem;
log_debug(ZONE, "loading roster for %s", jid_user(user->jid));
user->roster = xhash_new(101);
if(storage_get(user->sm->st, "roster-items", jid_user(user->jid), NULL, &os) == st_SUCCESS) {
if(os_iter_first(os))
do {
o = os_iter_object(os);
if(os_object_get_str(os, o, "jid", &str)) {
item = (item_t) malloc(sizeof(struct item_st));
memset(item, 0, sizeof(struct item_st));
item->jid = jid_new(mi->mod->mm->sm->pc, str, -1);
if(item->jid == NULL) {
log_debug(ZONE, "eek! invalid jid %s, skipping it", str);
free(item);
} else {
if(os_object_get_str(os, o, "name", &str))
item->name = strdup(str);
os_object_get_bool(os, o, "to", &item->to);
os_object_get_bool(os, o, "from", &item->from);
os_object_get_int(os, o, "ask", &item->ask);
log_debug(ZONE, "adding %s to roster (to %d from %d ask %d name %s)", jid_full(item->jid), item->to, item->from, item->ask, item->name);
olditem = xhash_get(user->roster, jid_full(item->jid));
if(olditem) {
log_debug(ZONE, "removing old %s entry first", jid_full(item->jid));
xhash_zap(user->roster, jid_full(item->jid));
_roster_free_walker(user->roster, jid_full(item->jid), (void *) olditem, NULL);
}
xhash_put(user->roster, jid_full(item->jid), (void *) item);
}
}
} while(os_iter_next(os));
os_free(os);
}
if(storage_get(user->sm->st, "roster-groups", jid_user(user->jid), NULL, &os) == st_SUCCESS) {
if(os_iter_first(os))
do {
o = os_iter_object(os);
if(os_object_get_str(os, o, "jid", &str)) {
item = xhash_get(user->roster, str);
if(item != NULL && os_object_get_str(os, o, "group", &str)) {
item->groups = realloc(item->groups, sizeof(char *) * (item->ngroups + 1));
item->groups[item->ngroups] = strdup(str);
item->ngroups++;
log_debug(ZONE, "added group %s to item %s", str, jid_full(item->jid));
}
}
} while(os_iter_next(os));
os_free(os);
}
pool_cleanup(user->p, (void (*))(void *) _roster_free, user);
return 0;
}
static void _roster_user_delete(mod_instance_t mi, jid_t jid) {
log_debug(ZONE, "deleting roster data for %s", jid_user(jid));
storage_delete(mi->sm->st, "roster-items", jid_user(jid), NULL);
storage_delete(mi->sm->st, "roster-groups", jid_user(jid), NULL);
}
int roster_init(mod_instance_t mi, char *arg) {
module_t mod = mi->mod;
if(mod->init) return 0;
mod->in_sess = _roster_in_sess;
mod->pkt_user = _roster_pkt_user;
mod->user_load = _roster_user_load;
mod->user_delete = _roster_user_delete;
feature_register(mod->mm->sm, uri_ROSTER);
return 0;
}