1 /*
2  * jabberd - Jabber Open Source Server
3  * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
4  *                    Ryan Eatmon, Robert Norris
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
19  */
20 
21 #include "sm.h"
22 
23 /** @file sm/mod_roster.c
24   * @brief roster managment & subscriptions
25   * @author Robert Norris
26   * $Date: 2005/09/09 05:34:13 $
27   * $Revision: 1.61 $
28   */
29 
30 typedef struct _mod_roster_st {
31     int maxitems;
32 } *mod_roster_t;
33 
34 typedef struct _roster_walker_st {
35     pkt_t  pkt;
36     int    req_ver;
37     int    ver;
38     sess_t sess;
39 } *roster_walker_t;
40 
41 /** free a single roster item */
_roster_freeuser_walker(const char * key,int keylen,void * val,void * arg)42 static void _roster_freeuser_walker(const char *key, int keylen, void *val, void *arg)
43 {
44     item_t item = (item_t) val;
45     int i;
46 
47     jid_free(item->jid);
48 
49     if(item->name != NULL)
50         free((void*)item->name);
51 
52     for(i = 0; i < item->ngroups; i++)
53         free((void*)item->groups[i]);
54     free(item->groups);
55 
56     free(item);
57 }
58 
59 /** free the roster */
_roster_freeuser(user_t user)60 static void _roster_freeuser(user_t user)
61 {
62     if(user->roster == NULL)
63         return;
64 
65     log_debug(ZONE, "freeing roster for %s", jid_user(user->jid));
66 
67     xhash_walk(user->roster, _roster_freeuser_walker, NULL);
68 
69     xhash_free(user->roster);
70     user->roster = NULL;
71 }
72 
_roster_save_item(user_t user,item_t item)73 static void _roster_save_item(user_t user, item_t item) {
74     os_t os;
75     os_object_t o;
76     char filter[4096];
77     int i;
78 
79     log_debug(ZONE, "saving roster item %s for %s", jid_full(item->jid), jid_user(user->jid));
80 
81     os = os_new();
82     o = os_object_new(os);
83 
84     os_object_put(o, "jid", jid_full(item->jid), os_type_STRING);
85 
86     if(item->name != NULL)
87         os_object_put(o, "name", item->name, os_type_STRING);
88 
89     os_object_put(o, "to", &item->to, os_type_BOOLEAN);
90     os_object_put(o, "from", &item->from, os_type_BOOLEAN);
91     os_object_put(o, "ask", &item->ask, os_type_INTEGER);
92 
93     snprintf(filter, 4096, "(jid=%zu:%s)", strlen(jid_full(item->jid)), jid_full(item->jid));
94 
95     storage_replace(user->sm->st, "roster-items", jid_user(user->jid), filter, os);
96 
97     os_free(os);
98 
99     if(item->ngroups == 0) {
100         storage_delete(user->sm->st, "roster-groups", jid_user(user->jid), filter);
101         return;
102     }
103 
104     os = os_new();
105 
106     for(i = 0; i < item->ngroups; i++) {
107         o = os_object_new(os);
108 
109         os_object_put(o, "jid", jid_full(item->jid), os_type_STRING);
110         os_object_put(o, "group", item->groups[i], os_type_STRING);
111     }
112 
113     storage_replace(user->sm->st, "roster-groups", jid_user(user->jid), filter, os);
114 
115     os_free(os);
116 }
117 
118 /** insert a roster item into this pkt, starting at elem */
_roster_insert_item(pkt_t pkt,item_t item,int elem)119 static void _roster_insert_item(pkt_t pkt, item_t item, int elem)
120 {
121     int ns, i;
122     char *sub;
123 
124     ns = nad_add_namespace(pkt->nad, uri_CLIENT, NULL);
125     elem = nad_insert_elem(pkt->nad, elem, ns, "item", NULL);
126     nad_set_attr(pkt->nad, elem, -1, "jid", jid_full(item->jid), 0);
127 
128     if(item->to && item->from)
129         sub = "both";
130     else if(item->to)
131         sub = "to";
132     else if(item->from)
133         sub = "from";
134     else
135         sub = "none";
136 
137     nad_set_attr(pkt->nad, elem, -1, "subscription", sub, 0);
138 
139     if(item->ask == 1)
140         nad_set_attr(pkt->nad, elem, -1, "ask", "subscribe", 9);
141     else if(item->ask == 2) /* XXX there is no ask='unsubscribe' in RFC bis anymore */
142         nad_set_attr(pkt->nad, elem, -1, "ask", "unsubscribe", 11);
143 
144     if(item->name != NULL)
145         nad_set_attr(pkt->nad, elem, -1, "name", item->name, 0);
146 
147     for(i = 0; i < item->ngroups; i++)
148         nad_insert_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", item->groups[i]);
149 }
150 
151 /** push this packet to all sessions except the given one */
_roster_push(user_t user,pkt_t pkt,int mod_index)152 static int _roster_push(user_t user, pkt_t pkt, int mod_index)
153 {
154     sess_t scan;
155     pkt_t push;
156     int pushes = 0;
157 
158     /* do the push */
159     for(scan = user->sessions; scan != NULL; scan = scan->next)
160     {
161         /* don't push to us or to anyone who hasn't loaded the roster */
162         if(scan->module_data[mod_index] == NULL)
163             continue;
164 
165         push = pkt_dup(pkt, jid_full(scan->jid), NULL);
166         pkt_sess(push, scan);
167         pushes++;
168     }
169 
170     /* return the pushed packets count */
171     return pushes;
172 }
173 
_roster_in_sess_s10n(mod_instance_t mi,sess_t sess,pkt_t pkt)174 static mod_ret_t _roster_in_sess_s10n(mod_instance_t mi, sess_t sess, pkt_t pkt)
175 {
176     mod_roster_t mroster = (mod_roster_t) mi->mod->private;
177     module_t mod = mi->mod;
178     item_t item;
179     pkt_t push;
180     int ns, elem, ret, items = -1;
181 
182     log_debug(ZONE, "got s10n packet");
183 
184     /* s10ns have to go to someone */
185     if(pkt->to == NULL)
186         return -stanza_err_BAD_REQUEST;
187 
188     /* add a proper from address (no resource) */
189     if(pkt->from != NULL)
190         jid_free(pkt->from);
191 
192     pkt->from = jid_new(jid_user(sess->jid), -1);
193     nad_set_attr(pkt->nad, 1, -1, "from", jid_full(pkt->from), 0);
194 
195     /* see if they're already on the roster */
196     item = xhash_get(sess->user->roster, jid_full(pkt->to));
197     if(item == NULL)
198     {
199         /* if they're not on the roster, there's no subscription,
200          * so quietly pass it on */
201         if(pkt->type == pkt_S10N_UN || pkt->type == pkt_S10N_UNED)
202             return mod_PASS;
203 
204         /* check if user exceedes maximum roster items */
205         if(mroster->maxitems > 0) {
206             ret = storage_count(sess->user->sm->st, "roster-items", jid_user(sess->user->jid), NULL, &items);
207 
208             log_debug(ZONE, "user has %i roster-items, maximum is %i", items, mroster->maxitems);
209 
210             /* if the limit is reached, return an error */
211             if (ret == st_SUCCESS && items >= mroster->maxitems)
212                 return -stanza_err_NOT_ACCEPTABLE;
213         }
214 
215         /* make a new one */
216         item = (item_t) calloc(1, sizeof(struct item_st));
217 
218         item->jid = jid_dup(pkt->to);
219 
220         /* remember it */
221         xhash_put(sess->user->roster, jid_full(item->jid), (void *) item);
222 
223         log_debug(ZONE, "made new empty roster item for %s", jid_full(item->jid));
224     }
225 
226     /* a request */
227     if(pkt->type == pkt_S10N && ! item->to)
228         item->ask = 1;
229     else if(pkt->type == pkt_S10N_UN && item->to)
230         item->ask = 2;
231 
232     /* changing states */
233     else if(pkt->type == pkt_S10N_ED)
234     {
235         /* they're allowed to see us, send them presence */
236         item->from = 1;
237         pres_roster(sess, item);
238     }
239     else if(pkt->type == pkt_S10N_UNED)
240     {
241         /* they're not allowed to see us anymore */
242         item->from = 0;
243         pres_roster(sess, item);
244     }
245 
246     if (sm_storage_rate_limit(sess->user->sm, jid_user(sess->user->jid)))
247         return -stanza_err_RESOURCE_CONSTRAINT;
248 
249     /* save changes */
250     _roster_save_item(sess->user, item);
251 
252     /* build a new packet to push out to everyone */
253     push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL);
254     pkt_id_new(push);
255     ns = nad_add_namespace(push->nad, uri_ROSTER, NULL);
256     elem = nad_append_elem(push->nad, ns, "query", 3);
257 
258     _roster_insert_item(push, item, elem);
259 
260     /* tell everyone */
261     _roster_push(sess->user, push, mod->index);
262 
263     /* everyone knows */
264     pkt_free(push);
265 
266     /* pass it on */
267     return mod_PASS;
268 }
269 
270 /** build the iq:roster packet from the hash */
_roster_get_walker(const char * id,int idlen,void * val,void * arg)271 static void _roster_get_walker(const char *id, int idlen, void *val, void *arg)
272 {
273     item_t item = (item_t) val;
274     roster_walker_t rw = (roster_walker_t) arg;
275 
276     _roster_insert_item(rw->pkt, item, 2);
277 
278     /* remember largest item version */
279     if(item->ver > rw->ver) rw->ver = item->ver;
280 }
281 
282 /** push roster XEP-0237 updates to client */
_roster_update_walker(const char * id,int idlen,void * val,void * arg)283 static void _roster_update_walker(const char *id, int idlen, void *val, void *arg)
284 {
285     pkt_t push;
286     char *buf;
287     int elem, ns;
288     item_t item = (item_t) val;
289     roster_walker_t rw = (roster_walker_t) arg;
290 
291     /* skip unneded roster items */
292     if(item->ver <= rw->req_ver) return;
293 
294     /* build a interim roster push packet */
295     push = pkt_create(rw->sess->user->sm, "iq", "set", NULL, NULL);
296     pkt_id_new(push);
297     ns = nad_add_namespace(push->nad, uri_ROSTER, NULL);
298     elem = nad_append_elem(push->nad, ns, "query", 3);
299 
300     buf = (char *) malloc(sizeof(char) * 128);
301     sprintf(buf, "%d", item->ver);
302     nad_set_attr(push->nad, elem, -1, "ver", buf, 0);
303     free(buf);
304 
305     _roster_insert_item(push, item, elem);
306 
307     pkt_sess(push, rw->sess);
308 }
309 
_roster_set_item(pkt_t pkt,int elem,sess_t sess,mod_instance_t mi)310 static void _roster_set_item(pkt_t pkt, int elem, sess_t sess, mod_instance_t mi)
311 {
312     mod_roster_t mroster = (mod_roster_t) mi->mod->private;
313     module_t mod = mi->mod;
314     int attr, ns, i, ret, items = -1;
315     jid_t jid;
316     item_t item;
317     pkt_t push;
318     char filter[4096];
319 
320     /* extract the jid */
321     attr = nad_find_attr(pkt->nad, elem, -1, "jid", NULL);
322     jid = jid_new(NAD_AVAL(pkt->nad, attr), NAD_AVAL_L(pkt->nad, attr));
323     if(jid == NULL) {
324         log_debug(ZONE, "jid failed prep check, skipping");
325         return;
326     }
327 
328     /* check for removals */
329     if(nad_find_attr(pkt->nad, elem, -1, "subscription", "remove") >= 0)
330     {
331         /* trash the item */
332         item = xhash_get(sess->user->roster, jid_full(jid));
333         if(item != NULL)
334         {
335             /* tell them they're unsubscribed */
336             if(item->from) {
337                 log_debug(ZONE, "telling %s that they're unsubscribed", jid_user(item->jid));
338                 pkt_router(pkt_create(sess->user->sm, "presence", "unsubscribed", jid_user(item->jid), jid_user(sess->jid)));
339             }
340             item->from = 0;
341 
342             /* tell them to unsubscribe us */
343             if(item->to) {
344                 log_debug(ZONE, "unsubscribing from %s", jid_user(item->jid));
345                 pkt_router(pkt_create(sess->user->sm, "presence", "unsubscribe", jid_user(item->jid), jid_user(sess->jid)));
346             }
347             item->to = 0;
348 
349             /* send unavailable */
350             pres_roster(sess, item);
351 
352             /* kill it */
353             xhash_zap(sess->user->roster, jid_full(jid));
354             _roster_freeuser_walker((const char *) jid_full(jid), strlen(jid_full(jid)), (void *) item, NULL);
355 
356             snprintf(filter, 4096, "(jid=%zu:%s)", strlen(jid_full(jid)), jid_full(jid));
357             storage_delete(sess->user->sm->st, "roster-items", jid_user(sess->jid), filter);
358             storage_delete(sess->user->sm->st, "roster-groups", jid_user(sess->jid), filter);
359         }
360 
361         log_debug(ZONE, "removed %s from roster", jid_full(jid));
362 
363         /* build a new packet to push out to everyone */
364         push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL);
365         pkt_id_new(push);
366         ns = nad_add_namespace(push->nad, uri_ROSTER, NULL);
367 
368         nad_append_elem(push->nad, ns, "query", 3);
369         elem = nad_append_elem(push->nad, ns, "item", 4);
370         nad_set_attr(push->nad, elem, -1, "jid", jid_full(jid), 0);
371         nad_set_attr(push->nad, elem, -1, "subscription", "remove", 6);
372 
373         /* tell everyone */
374         _roster_push(sess->user, push, mod->index);
375 
376         /* we're done */
377         pkt_free(push);
378 
379         jid_free(jid);
380 
381         return;
382     }
383 
384     /* find a pre-existing one */
385     item = xhash_get(sess->user->roster, jid_full(jid));
386     if(item == NULL)
387     {
388         /* check if user exceedes maximum roster items */
389         if(mroster->maxitems > 0) {
390             ret = storage_count(sess->user->sm->st, "roster-items", jid_user(sess->user->jid), NULL, &items);
391 
392             log_debug(ZONE, "user has %i roster-items, maximum is %i", items, mroster->maxitems);
393 
394             /* if the limit is reached, skip it */
395             if (ret == st_SUCCESS && items >= mroster->maxitems)
396                 return;
397         }
398 
399         /* make a new one */
400         item = (item_t) calloc(1, sizeof(struct item_st));
401 
402         /* add the jid */
403         item->jid = jid;
404 
405         /* add it to the roster */
406         xhash_put(sess->user->roster, jid_full(item->jid), (void *) item);
407 
408         log_debug(ZONE, "created new roster item %s", jid_full(item->jid));
409     }
410 
411     else
412         jid_free(jid);
413 
414     /* extract the name */
415     attr = nad_find_attr(pkt->nad, elem, -1, "name", NULL);
416     if(attr >= 0)
417     {
418         /* free the old name */
419         if(item->name != NULL) {
420             free((void*)item->name);
421             item->name = NULL;
422         }
423 
424         if (NAD_AVAL_L(pkt->nad, attr) > 0)
425         {
426             item->name = (const char *) malloc(sizeof(char) * (NAD_AVAL_L(pkt->nad, attr) + 1));
427             sprintf((char *)item->name, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr));
428         }
429     }
430 
431     /* free the old groups */
432     if(item->groups != NULL)
433     {
434         for(i = 0; i < item->ngroups; i++)
435             free((void*)item->groups[i]);
436         free(item->groups);
437         item->ngroups = 0;
438         item->groups = NULL;
439     }
440 
441     /* loop over the groups, adding them to the array */
442     elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", 1);
443     while(elem >= 0)
444     {
445         /* empty group tags get skipped */
446         if(NAD_CDATA_L(pkt->nad, elem) >= 0)
447         {
448             /* make room and shove it in */
449             item->groups = (const char **) realloc(item->groups, sizeof(char *) * (item->ngroups + 1));
450 
451             item->groups[item->ngroups] = (const char *) malloc(sizeof(char) * (NAD_CDATA_L(pkt->nad, elem) + 1));
452             sprintf((char *)(item->groups[item->ngroups]), "%.*s", NAD_CDATA_L(pkt->nad, elem), NAD_CDATA(pkt->nad, elem));
453 
454             item->ngroups++;
455         }
456 
457         elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "group", 0);
458     }
459 
460     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);
461 
462     if (sm_storage_rate_limit(sess->user->sm, jid_user(sess->user->jid)))
463         return;
464 
465     /* save changes */
466     _roster_save_item(sess->user, item);
467 
468     /* build a new packet to push out to everyone */
469     push = pkt_create(sess->user->sm, "iq", "set", NULL, NULL);
470     pkt_id_new(push);
471     ns = nad_add_namespace(push->nad, uri_ROSTER, NULL);
472     elem = nad_append_elem(push->nad, ns, "query", 3);
473 
474     _roster_insert_item(push, item, elem);
475 
476     /* tell everyone */
477     _roster_push(sess->user, push, mod->index);
478 
479     /* we're done */
480     pkt_free(push);
481 }
482 
483 /** our main handler for packets arriving from a session */
_roster_in_sess(mod_instance_t mi,sess_t sess,pkt_t pkt)484 static mod_ret_t _roster_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt)
485 {
486     module_t mod = mi->mod;
487     int elem, attr, ver = 0;
488     pkt_t result;
489     char *buf;
490     roster_walker_t rw;
491 
492     /* handle s10ns in a different function */
493     if(pkt->type & pkt_S10N)
494         return _roster_in_sess_s10n(mi, sess, pkt);
495 
496     /* we only want to play with iq:roster packets */
497     if(pkt->ns != ns_ROSTER)
498         return mod_PASS;
499 
500     /* quietly drop results, its probably them responding to a push */
501     if(pkt->type == pkt_IQ_RESULT) {
502         pkt_free(pkt);
503         return mod_HANDLED;
504     }
505 
506     /* need gets or sets */
507     if(pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET)
508         return mod_PASS;
509 
510     /* get */
511     if(pkt->type == pkt_IQ)
512     {
513 		/* check for "XEP-0237: Roster Versioning request" */
514         if((elem = nad_find_elem(pkt->nad, 1, -1, "query", 1)) >= 0
515          &&(attr = nad_find_attr(pkt->nad, elem, -1, "ver", NULL)) >= 0) {
516             if (NAD_AVAL_L(pkt->nad, attr) > 0)
517             {
518                 buf = (char *) malloc(sizeof(char) * (NAD_AVAL_L(pkt->nad, attr) + 1));
519                 sprintf(buf, "%.*s", NAD_AVAL_L(pkt->nad, attr), NAD_AVAL(pkt->nad, attr));
520                 ver = j_atoi(buf, 0);
521                 free(buf);
522             }
523         }
524 
525         /* build the packet */
526         rw = (roster_walker_t) calloc(1, sizeof(struct _roster_walker_st));
527         rw->pkt = pkt;
528         rw->req_ver = ver;
529         rw->sess = sess;
530 
531         nad_set_attr(pkt->nad, 1, -1, "type", "result", 6);
532 
533         if(ver > 0) {
534 			/* send XEP-0237 empty result */
535             nad_drop_elem(pkt->nad, elem);
536             pkt_sess(pkt_tofrom(pkt), sess);
537             xhash_walk(sess->user->roster, _roster_update_walker, (void *) rw);
538         }
539         else {
540             xhash_walk(sess->user->roster, _roster_get_walker, (void *) rw);
541             if(elem >= 0 && attr >= 0) {
542                 buf = (char *) malloc(sizeof(char) * 128);
543                 sprintf(buf, "%d", rw->ver);
544                 nad_set_attr(pkt->nad, elem, -1, "ver", buf, 0);
545                 free(buf);
546             }
547             pkt_sess(pkt_tofrom(pkt), sess);
548         }
549 
550         free(rw);
551 
552         /* remember that they loaded it, so we know to push updates to them */
553         sess->module_data[mod->index] = (void *) 1;
554 
555         return mod_HANDLED;
556     }
557 
558     /* set, find the item */
559     elem = nad_find_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 2), "item", 1);
560     if(elem < 0)
561         /* no item, abort */
562         return -stanza_err_BAD_REQUEST;
563 
564     /* loop over items and stick them in */
565     while(elem >= 0)
566     {
567         /* extract the jid */
568         attr = nad_find_attr(pkt->nad, elem, -1, "jid", NULL);
569         if(attr < 0 || NAD_AVAL_L(pkt->nad, attr) == 0)
570         {
571             log_debug(ZONE, "no jid on this item, aborting");
572 
573             /* no jid, abort */
574             return -stanza_err_BAD_REQUEST;
575         }
576 
577         /* utility */
578         _roster_set_item(pkt, elem, sess, mi);
579 
580         /* next one */
581         elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, elem), "item", 0);
582     }
583 
584     /* send the result */
585     result = pkt_create(sess->user->sm, "iq", "result", NULL, NULL);
586 
587     pkt_id(pkt, result);
588 
589     /* tell them */
590     pkt_sess(result, sess);
591 
592     /* free the request */
593     pkt_free(pkt);
594 
595     return mod_HANDLED;
596 }
597 
598 /** handle incoming s10ns */
_roster_pkt_user(mod_instance_t mi,user_t user,pkt_t pkt)599 static mod_ret_t _roster_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt)
600 {
601     module_t mod = mi->mod;
602     item_t item;
603     int ns, elem;
604 
605     /* only want s10ns */
606     if(!(pkt->type & pkt_S10N))
607         return mod_PASS;
608 
609     /* drop route errors */
610     if(pkt->rtype & route_ERROR) {
611         pkt_free(pkt);
612         return mod_HANDLED;
613     }
614 
615     /* get the roster item */
616     item = (item_t) xhash_get(user->roster, jid_full(pkt->from));
617     if(item == NULL) {
618         /* subs are handled by the client */
619         if(pkt->type == pkt_S10N) {
620             /* if the user is online broadcast it like roster push */
621             if(user->top != NULL && _roster_push(user, pkt, mod->index) > 0) {
622                 /* pushed, thus handled */
623                 pkt_free(pkt);
624                 return mod_HANDLED;
625             }
626             else {
627                 /* not pushed to any online resource - pass it on (to mod_offline) */
628                 return mod_PASS;
629             }
630         }
631 
632         /* other S10Ns: we didn't ask for this, so we don't care */
633         pkt_free(pkt);
634         return mod_HANDLED;
635     }
636 
637     /* ignore bogus answers */
638     if( (pkt->type == pkt_S10N_ED && (item->ask != 1 || item->to) )
639      || (pkt->type == pkt_S10N_UNED && ! item->to) )
640     {
641         /* remove pending ask */
642         if( (pkt->type == pkt_S10N_ED && item->ask == 1)
643          || (pkt->type == pkt_S10N_UNED && item->ask == 2) )
644         {
645             item->ask = 0;
646             /* save changes */
647             _roster_save_item(user, item);
648         }
649 
650         pkt_free(pkt);
651         return mod_HANDLED;
652     }
653 
654     /* trying to subscribe */
655     if(pkt->type == pkt_S10N)
656     {
657         if(item->from)
658         {
659             /* already subscribed, tell them */
660             nad_set_attr(pkt->nad, 1, -1, "type", "subscribed", 10);
661             pkt_router(pkt_tofrom(pkt));
662 
663             /* update their presence from the leading session */
664             if(user->top != NULL)
665                 pres_roster(user->top, item);
666 
667             return mod_HANDLED;
668         }
669 
670         return mod_PASS;
671     }
672 
673     /* handle unsubscribe */
674     if(pkt->type == pkt_S10N_UN)
675     {
676         if(!item->from)
677         {
678             /* already unsubscribed, tell them */
679             nad_set_attr(pkt->nad, 1, -1, "type", "unsubscribed", 12);
680             pkt_router(pkt_tofrom(pkt));
681 
682             return mod_HANDLED;
683         }
684 
685         /* change state */
686         item->from = 0;
687 
688         /* confirm unsubscription */
689         pkt_router(pkt_create(user->sm, "presence", "unsubscribed", jid_user(pkt->from), jid_user(user->jid)));
690 
691         /* update their presence from the leading session */
692         if(user->top != NULL)
693             pres_roster(user->top, item);
694     }
695 
696     /* update our s10n */
697     if(pkt->type == pkt_S10N_ED)
698     {
699         item->to = 1;
700         if(item->ask == 1)
701             item->ask = 0;
702     }
703     if(pkt->type == pkt_S10N_UNED)
704     {
705         item->to = 0;
706         if(item->ask == 2)
707             item->ask = 0;
708     }
709 
710     if (sm_storage_rate_limit(user->sm, jid_user(pkt->from)))
711         return -stanza_err_RESOURCE_CONSTRAINT;
712 
713     /* save changes */
714     _roster_save_item(user, item);
715 
716     /* if there's no sessions, then we're done */
717     if(user->sessions == NULL)
718         return mod_PASS;
719 
720     /* build a new packet to push out to everyone */
721     pkt = pkt_create(user->sm, "iq", "set", NULL, NULL);
722     pkt_id_new(pkt);
723     ns = nad_add_namespace(pkt->nad, uri_ROSTER, NULL);
724     elem = nad_append_elem(pkt->nad, ns, "query", 3);
725 
726     _roster_insert_item(pkt, item, elem);
727 
728     /* tell everyone */
729     _roster_push(user, pkt, mod->index);
730 
731     /* everyone knows */
732     pkt_free(pkt);
733 
734     return mod_PASS;
735 }
736 
737 /** load the roster from the database */
_roster_user_load(mod_instance_t mi,user_t user)738 static int _roster_user_load(mod_instance_t mi, user_t user) {
739     os_t os;
740     os_object_t o;
741     char *str;
742     item_t item, olditem;
743 
744     log_debug(ZONE, "loading roster for %s", jid_user(user->jid));
745 
746     user->roster = xhash_new(101);
747 
748     /* pull all the items */
749     if(storage_get(user->sm->st, "roster-items", jid_user(user->jid), NULL, &os) == st_SUCCESS) {
750         if(os_iter_first(os))
751             do {
752                 o = os_iter_object(os);
753 
754                 if(os_object_get_str(os, o, "jid", &str)) {
755                     /* new one */
756                     item = (item_t) calloc(1, sizeof(struct item_st));
757 
758                     item->jid = jid_new(str, -1);
759                     if(item->jid == NULL) {
760                         log_debug(ZONE, "eek! invalid jid %s, skipping it", str);
761                         free(item);
762 
763                     } else {
764                         if(os_object_get_str(os, o, "name", &str))
765                             item->name = strdup(str);
766 
767                         os_object_get_bool(os, o, "to", &item->to);
768                         os_object_get_bool(os, o, "from", &item->from);
769                         os_object_get_int(os, o, "ask", &item->ask);
770                         os_object_get_int(os, o, "object-sequence", &item->ver);
771 
772                         olditem = xhash_get(user->roster, jid_full(item->jid));
773                         if(olditem) {
774                             log_debug(ZONE, "removing old %s roster entry", jid_full(item->jid));
775                             xhash_zap(user->roster, jid_full(item->jid));
776                             _roster_freeuser_walker(jid_full(item->jid), strlen(jid_full(item->jid)), (void *) olditem, NULL);
777                         }
778 
779                         /* its good */
780                         xhash_put(user->roster, jid_full(item->jid), (void *) item);
781 
782                         log_debug(ZONE, "added %s to roster (to %d from %d ask %d ver %d name %s)",
783                                   jid_full(item->jid), item->to, item->from, item->ask, item->ver, item->name);
784                     }
785                 }
786             } while(os_iter_next(os));
787 
788        os_free(os);
789     }
790 
791     /* pull the groups and match them up */
792     if(storage_get(user->sm->st, "roster-groups", jid_user(user->jid), NULL, &os) == st_SUCCESS) {
793         if(os_iter_first(os))
794             do {
795                 o = os_iter_object(os);
796 
797                 if(os_object_get_str(os, o, "jid", &str)) {
798                     item = xhash_get(user->roster, str);
799 
800                     if(item != NULL && os_object_get_str(os, o, "group", &str)) {
801                         item->groups = realloc(item->groups, sizeof(char *) * (item->ngroups + 1));
802                         item->groups[item->ngroups] = strdup(str);
803                         item->ngroups++;
804 
805                         log_debug(ZONE, "added group %s to item %s", str, jid_full(item->jid));
806                     }
807                 }
808             } while(os_iter_next(os));
809 
810         os_free(os);
811     }
812 
813     pool_cleanup(user->p, (void (*))(void *) _roster_freeuser, user);
814 
815     return 0;
816 }
817 
_roster_user_delete(mod_instance_t mi,jid_t jid)818 static void _roster_user_delete(mod_instance_t mi, jid_t jid) {
819     log_debug(ZONE, "deleting roster data for %s", jid_user(jid));
820 
821     storage_delete(mi->sm->st, "roster-items", jid_user(jid), NULL);
822     storage_delete(mi->sm->st, "roster-groups", jid_user(jid), NULL);
823 }
824 
_roster_free(module_t mod)825 static void _roster_free(module_t mod)
826 {
827     mod_roster_t mroster = (mod_roster_t) mod->private;
828     free(mroster);
829 }
830 
module_init(mod_instance_t mi,const char * arg)831 DLLEXPORT int module_init(mod_instance_t mi, const char *arg) {
832     module_t mod = mi->mod;
833     mod_roster_t mroster;
834 
835     if(mod->init) return 0;
836 
837     mroster = (mod_roster_t) calloc(1, sizeof(struct _mod_roster_st));
838 
839     mroster->maxitems = j_atoi(config_get_one(mod->mm->sm->config, "roster.maxitems", 0), 0);
840 
841     mod->private = mroster;
842 
843     mod->in_sess = _roster_in_sess;
844     mod->pkt_user = _roster_pkt_user;
845     mod->user_load = _roster_user_load;
846     mod->user_delete = _roster_user_delete;
847     mod->free = _roster_free;
848 
849     feature_register(mod->mm->sm, uri_ROSTER);
850 
851     return 0;
852 }
853