1 /*
2  * Copyright (C) 2007 Colin DIDIER
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16  */
17 
18 #include <stdlib.h>
19 #include <string.h>
20 
21 #include "module.h"
22 #include "signals.h"
23 
24 #include "xmpp-servers.h"
25 #include "rosters-tools.h"
26 #include "tools.h"
27 
28 #define XMLNS_ROSTER "jabber:iq:roster"
29 
30 const char *xmpp_presence_show[] = {
31 	"-",
32 	"X",
33 	"xa",
34 	"dnd",
35 	"away",
36 	"+",
37 	"chat",
38 	"online",
39 	NULL
40 };
41 
42 const char *xmpp_subscription[] = {
43 	"remove",
44 	"none",
45 	"to",
46 	"from",
47 	"both",
48 	NULL
49 };
50 
51 static int
func_find_group(gconstpointer group,gconstpointer name)52 func_find_group(gconstpointer group, gconstpointer name)
53 {
54 	char *group_name;
55 
56 	group_name = ((XMPP_ROSTER_GROUP_REC *)group)->name;
57 	if (group_name == name)
58 		return 0;
59 	if (group_name == NULL || name == NULL)
60 		return -1;
61 	return strcmp(group_name, name);
62 }
63 
64 static int
func_sort_group(gconstpointer group1,gconstpointer group2)65 func_sort_group(gconstpointer group1, gconstpointer group2)
66 {
67 	char *group1_name, *group2_name;
68 
69 	group1_name = ((XMPP_ROSTER_GROUP_REC *)group1)->name;
70 	group2_name = ((XMPP_ROSTER_GROUP_REC *)group2)->name;
71 	if (group1_name == NULL)
72 		return -1;
73 	if (group2_name == NULL)
74 		 return 1;
75 	return strcmp(group1_name, group2_name);
76 }
77 
78 static int
func_sort_resource(gconstpointer resource1_ptr,gconstpointer resource2_ptr)79 func_sort_resource(gconstpointer resource1_ptr, gconstpointer resource2_ptr)
80 {
81 	int cmp;
82 	XMPP_ROSTER_RESOURCE_REC *resource1, *resource2;
83 
84 	resource1 = (XMPP_ROSTER_RESOURCE_REC *)resource1_ptr;
85 	resource2 = (XMPP_ROSTER_RESOURCE_REC *)resource2_ptr;
86 	if ((cmp = resource2->priority - resource1->priority) == 0
87 	    && (cmp = resource2->show - resource1->show) == 0
88 	    && resource1->name && resource2->name)
89 		return strcmp(resource1->name, resource2->name);
90 	return cmp;
91 }
92 
93 static int
func_sort_user_by_name(XMPP_ROSTER_USER_REC * user1,XMPP_ROSTER_USER_REC * user2)94 func_sort_user_by_name(XMPP_ROSTER_USER_REC *user1, XMPP_ROSTER_USER_REC *user2)
95 {
96 	if (user1->name == NULL && user2->name != NULL)
97 		return strcmp(user1->jid, user2->name);
98 	if (user1->name != NULL && user2->name == NULL)
99 		return strcmp(user1->name, user2->jid);
100 	if (user1->name != NULL && user2->name != NULL)
101 		return strcmp(user1->name, user2->name);
102 	return strcmp(user1->jid, user2->jid);
103 }
104 
105 static int
func_sort_user(gconstpointer user1_ptr,gconstpointer user2_ptr)106 func_sort_user(gconstpointer user1_ptr, gconstpointer user2_ptr)
107 {
108 	GSList *resources1_list, *resources2_list;
109 	XMPP_ROSTER_USER_REC *user1, *user2;
110 	XMPP_ROSTER_RESOURCE_REC *fisrt_resources1, *fisrt_resources2;
111 
112 	user1 = (XMPP_ROSTER_USER_REC *)user1_ptr;
113 	resources1_list = user1->resources;
114 	user2 = (XMPP_ROSTER_USER_REC *)user2_ptr;
115 	resources2_list = user2->resources;
116 	if (resources1_list == NULL && resources2_list == NULL
117 	    && user1->error == user2->error)
118 		return func_sort_user_by_name(user1, user2);
119 	if (user1->error || resources1_list == NULL)
120 		return 1;
121 	if (user2->error || resources2_list == NULL)
122 		return -1;
123 	fisrt_resources1 = (XMPP_ROSTER_RESOURCE_REC *)resources1_list->data;
124 	fisrt_resources2 = (XMPP_ROSTER_RESOURCE_REC *)resources2_list->data;
125 	if (fisrt_resources1->show == fisrt_resources2->show)
126 		return func_sort_user_by_name(user1, user2);
127 	return fisrt_resources2->show - fisrt_resources1->show;
128 }
129 
130 static XMPP_ROSTER_RESOURCE_REC *
create_resource(const char * name)131 create_resource(const char *name)
132 {
133 	XMPP_ROSTER_RESOURCE_REC *resource;
134 
135 	resource = g_new(XMPP_ROSTER_RESOURCE_REC, 1);
136 	resource->name = g_strdup(name == NULL ? "" : name);
137 	resource->priority = 0;
138 	resource->show= XMPP_PRESENCE_UNAVAILABLE;
139 	resource->status = NULL;
140 	resource->composing_id = NULL;
141 	return resource;
142 }
143 
144 static void
cleanup_resource(gpointer data,gpointer user_data)145 cleanup_resource(gpointer data, gpointer user_data)
146 {
147 	XMPP_ROSTER_RESOURCE_REC *resource;
148 
149 	if (data == NULL)
150 		return;
151 	resource = (XMPP_ROSTER_RESOURCE_REC *)data;
152 	g_free(resource->name);
153 	g_free(resource->status);
154 	g_free(resource->composing_id);
155 	g_free(resource);
156 }
157 
158 static XMPP_ROSTER_USER_REC *
create_user(const char * jid,const char * name)159 create_user(const char *jid, const char *name)
160 {
161 	XMPP_ROSTER_USER_REC *user;
162 
163 	g_return_val_if_fail(jid != NULL, NULL);
164 	user = g_new(XMPP_ROSTER_USER_REC, 1);
165 	user->jid = g_strdup(jid);
166 	user->name = g_strdup(name);
167 	user->subscription = XMPP_SUBSCRIPTION_NONE;
168 	user->error = FALSE;
169 	user->resources = NULL;
170 	return user;
171 }
172 
173 static void
cleanup_user(gpointer data,gpointer user_data)174 cleanup_user(gpointer data, gpointer user_data)
175 {
176 	XMPP_ROSTER_USER_REC *user;
177 
178 	if (data == NULL)
179 		return;
180 	user = (XMPP_ROSTER_USER_REC *)data;
181 	g_slist_foreach(user->resources, cleanup_resource, NULL);
182 	g_slist_free(user->resources);
183 	g_free(user->name);
184 	g_free(user->jid);
185 	g_free(user);
186 }
187 
188 static XMPP_ROSTER_GROUP_REC *
create_group(const char * name)189 create_group(const char *name)
190 {
191 	XMPP_ROSTER_GROUP_REC *group;
192 
193 	group = g_new(XMPP_ROSTER_GROUP_REC, 1);
194 	group->name = g_strdup(name);
195 	group->users = NULL;
196 	return group;
197 }
198 
199 static void
cleanup_group(gpointer data,gpointer user_data)200 cleanup_group(gpointer data, gpointer user_data)
201 {
202 	XMPP_ROSTER_GROUP_REC *group;
203 
204 	if (data == NULL)
205 		return;
206 	group = (XMPP_ROSTER_GROUP_REC *)data;
207 	g_slist_foreach(group->users, cleanup_user, group);
208 	g_slist_free(group->users);
209 	g_free(group->name);
210 	g_free(group);
211 }
212 
213 static void
roster_cleanup(XMPP_SERVER_REC * server)214 roster_cleanup(XMPP_SERVER_REC *server)
215 {
216 	if (!IS_XMPP_SERVER(server) || server->roster == NULL)
217 		return;
218 	g_slist_foreach(server->roster, cleanup_group, server);
219 	g_slist_free(server->roster);
220 	server->roster = NULL;
221 	g_slist_foreach(server->my_resources, cleanup_resource, NULL);
222 	g_slist_free(server->my_resources);
223 	server->my_resources = NULL;
224 }
225 
226 static XMPP_ROSTER_GROUP_REC *
find_or_add_group(XMPP_SERVER_REC * server,const char * group_name)227 find_or_add_group(XMPP_SERVER_REC *server, const char *group_name)
228 {
229 	GSList *group_list;
230 	XMPP_ROSTER_GROUP_REC *group;
231 
232 	g_return_val_if_fail(IS_XMPP_SERVER(server), NULL);
233 	group_list = g_slist_find_custom(server->roster, group_name,
234 	    func_find_group);
235 	if (group_list == NULL) {
236 		group = create_group(group_name);
237 		server->roster = g_slist_insert_sorted(server->roster, group,
238 		    func_sort_group);
239 	} else
240 		group = group_list->data;
241 	return group;
242 }
243 
244 static XMPP_ROSTER_USER_REC *
add_user(XMPP_SERVER_REC * server,const char * jid,const char * name,const char * group_name,XMPP_ROSTER_GROUP_REC ** return_group)245 add_user(XMPP_SERVER_REC *server, const char *jid, const char *name,
246     const char *group_name, XMPP_ROSTER_GROUP_REC **return_group)
247 {
248 	XMPP_ROSTER_GROUP_REC *group;
249 	XMPP_ROSTER_USER_REC *user;
250 
251 	g_return_val_if_fail(IS_XMPP_SERVER(server), NULL);
252 	g_return_val_if_fail(jid != NULL, NULL);
253 	group = find_or_add_group(server, group_name);
254 	user = create_user(jid, name);
255 	group->users = g_slist_append(group->users, user);
256 	if (return_group != NULL)
257 		*return_group = group;
258 	return user;
259 }
260 
261 static XMPP_ROSTER_GROUP_REC *
move_user(XMPP_SERVER_REC * server,XMPP_ROSTER_USER_REC * user,XMPP_ROSTER_GROUP_REC * group,const char * group_name)262 move_user(XMPP_SERVER_REC *server, XMPP_ROSTER_USER_REC *user,
263     XMPP_ROSTER_GROUP_REC *group, const char *group_name)
264 {
265 	XMPP_ROSTER_GROUP_REC *new_group;
266 
267 	g_return_val_if_fail(IS_XMPP_SERVER(server), group);
268         g_return_val_if_fail(user != NULL, group);
269 	new_group = find_or_add_group(server, group_name);
270 	group->users = g_slist_remove(group->users, user);
271 	new_group->users = g_slist_append(new_group->users, user);
272 	return new_group;
273 }
274 
275 static void
update_subscription(XMPP_SERVER_REC * server,XMPP_ROSTER_USER_REC * user,XMPP_ROSTER_GROUP_REC * group,const char * subscription)276 update_subscription(XMPP_SERVER_REC *server, XMPP_ROSTER_USER_REC *user,
277     XMPP_ROSTER_GROUP_REC *group, const char *subscription)
278 {
279 	g_return_if_fail(IS_XMPP_SERVER(server));
280 	g_return_if_fail(user != NULL);
281 	g_return_if_fail(group != NULL);
282 	g_return_if_fail(subscription != NULL);
283 	if (g_ascii_strcasecmp(subscription,
284 	    xmpp_subscription[XMPP_SUBSCRIPTION_NONE]) == 0)
285 		user->subscription = XMPP_SUBSCRIPTION_NONE;
286 	else if (g_ascii_strcasecmp(subscription,
287 	    xmpp_subscription[XMPP_SUBSCRIPTION_FROM]) == 0)
288 		user->subscription = XMPP_SUBSCRIPTION_FROM;
289 	else if (g_ascii_strcasecmp(subscription,
290 	    xmpp_subscription[XMPP_SUBSCRIPTION_TO]) == 0)
291 		user->subscription = XMPP_SUBSCRIPTION_TO;
292 	else if (g_ascii_strcasecmp(subscription,
293 	    xmpp_subscription[XMPP_SUBSCRIPTION_BOTH]) == 0)
294 		user->subscription = XMPP_SUBSCRIPTION_BOTH;
295 	else if (g_ascii_strcasecmp(subscription,
296 	    xmpp_subscription[XMPP_SUBSCRIPTION_REMOVE]) == 0) {
297 		group->users = g_slist_remove(group->users, user);
298 		cleanup_user(user, server);
299 		/* remove empty group */
300 		if (group->users == NULL) {
301 			server->roster = g_slist_remove(server->roster, group);
302 			cleanup_group(group, server);
303 		}
304 	}
305 }
306 
307 static void
update_user(XMPP_SERVER_REC * server,const char * jid,const char * subscription,const char * name,const char * group_name)308 update_user(XMPP_SERVER_REC *server, const char *jid, const char *subscription,
309     const char *name, const char *group_name)
310 {
311 	XMPP_ROSTER_GROUP_REC *group;
312 	XMPP_ROSTER_USER_REC *user;
313 
314 	g_return_if_fail(IS_XMPP_SERVER(server));
315 	g_return_if_fail(jid != NULL);
316 	user = rosters_find_user(server->roster, jid, &group, NULL);
317 	if (user == NULL)
318 		user = add_user(server, jid, name, group_name, &group);
319 	else {
320 		/* move to another group and sort it */
321 		if ((group->name == NULL && group_name != NULL)
322 		    || (group->name != NULL && group_name == NULL)
323 		    || (group->name != NULL && group_name != NULL
324 		    && strcmp(group->name, group_name) != 0)) {
325 			group = move_user(server, user, group, group_name);
326 			group->users = g_slist_sort(group->users,
327 			    func_sort_user);
328 		}
329 		/* change name */
330 		if ((user->name == NULL && name != NULL)
331 		    || (user->name != NULL && name == NULL)
332 		    || (user->name != NULL && name != NULL
333 		    && strcmp(user->name, name) != 0)) {
334 			g_free(user->name);
335 			user->name = g_strdup(name);
336 			group->users = g_slist_sort(group->users,
337 			    func_sort_user);
338 		}
339 	}
340 	update_subscription(server, user, group, subscription);
341 }
342 
343 static void
update_user_presence(XMPP_SERVER_REC * server,const char * full_jid,const char * show_str,const char * status,const char * priority_str)344 update_user_presence(XMPP_SERVER_REC *server, const char *full_jid,
345     const char *show_str, const char *status, const char *priority_str)
346 {
347 	XMPP_ROSTER_GROUP_REC *group;
348 	XMPP_ROSTER_USER_REC *user;
349 	XMPP_ROSTER_RESOURCE_REC *resource;
350 	char *jid, *res;
351 	int show, priority;
352 	gboolean new, own;
353 
354 	g_return_if_fail(IS_XMPP_SERVER(server));
355 	g_return_if_fail(full_jid != NULL);
356 	new = own = FALSE;
357 	jid = xmpp_strip_resource(full_jid);
358 	res = xmpp_extract_resource(full_jid);
359 	user = rosters_find_user(server->roster, jid, &group, NULL);
360 	if (user == NULL) {
361 		if (!(own = strcmp(jid, server->jid) == 0
362 		     && strcmp(res, server->resource) != 0))
363 			goto out;
364 	} else
365 		user->error = FALSE;
366 	/* find resource or create it if it doesn't exist */
367 	resource = rosters_find_resource(!own ?
368 	    user->resources : server->my_resources, res);
369 	if (resource == NULL) {
370 		resource = create_resource(res);
371 		new = TRUE;
372 		if (!own)
373 			user->resources =
374 			    g_slist_prepend(user->resources, resource);
375 		else
376 			server->my_resources =
377 			    g_slist_prepend(server->my_resources, resource);
378 		signal_emit("xmpp presence online", 4, server, full_jid,
379 		    jid, res);
380 	}
381 	show = xmpp_get_show(show_str);
382 	priority = (priority_str != NULL) ?
383 	    atoi(priority_str) : resource->priority;
384 	if (new || xmpp_presence_changed(show, resource->show, status,
385 	    resource->status, priority, resource->priority)) {
386 		resource->show = show;
387 		resource->status = g_strdup(status);
388 		resource->priority = priority;
389 		if (!own) {
390 			user->resources = g_slist_sort(
391 			    user->resources, func_sort_resource);
392 			group->users = g_slist_sort(group->users,
393 			    func_sort_user);
394 		} else
395 			server->my_resources = g_slist_sort(
396 			    server->my_resources, func_sort_resource);
397 		signal_emit("xmpp presence changed", 4, server, full_jid,
398 		    resource->show, resource->status);
399 	}
400 
401 out:
402 	g_free(jid);
403 	g_free(res);
404 }
405 
406 static void
user_unavailable(XMPP_SERVER_REC * server,const char * full_jid,const char * status)407 user_unavailable(XMPP_SERVER_REC *server, const char *full_jid,
408     const char *status)
409 {
410 	XMPP_ROSTER_GROUP_REC *group;
411 	XMPP_ROSTER_USER_REC *user;
412 	XMPP_ROSTER_RESOURCE_REC *resource;
413 	char *jid, *res;
414 	gboolean own;
415 
416 	g_return_if_fail(IS_XMPP_SERVER(server));
417 	g_return_if_fail(full_jid != NULL);
418 	own = FALSE;
419 	jid = xmpp_strip_resource(full_jid);
420 	res = xmpp_extract_resource(full_jid);
421 	user = rosters_find_user(server->roster, jid, &group, NULL);
422 	if (user == NULL) {
423 		if (!(own = strcmp(jid, server->jid) == 0))
424 			goto out;
425 	} else
426 		user->error = FALSE;
427 	resource = rosters_find_resource(!own ?
428 	    user->resources : server->my_resources, res);
429 	if (resource == NULL)
430 		goto out;
431 	signal_emit("xmpp presence offline", 4, server, full_jid, jid, res);
432 	signal_emit("xmpp presence changed", 4, server, full_jid,
433 	    XMPP_PRESENCE_UNAVAILABLE, status);
434 	if (!own)
435 		user->resources = g_slist_remove(user->resources, resource);
436 	else
437 		server->my_resources = g_slist_remove(server->my_resources,
438 		    resource);
439 	cleanup_resource(resource, NULL);
440 	if (!own) /* sort the group */
441 		group->users = g_slist_sort(group->users, func_sort_user);
442 
443 out:
444 	g_free(jid);
445 	g_free(res);
446 }
447 
448 static void
user_presence_error(XMPP_SERVER_REC * server,const char * full_jid)449 user_presence_error(XMPP_SERVER_REC *server, const char *full_jid)
450 {
451 	XMPP_ROSTER_GROUP_REC *group;
452 	XMPP_ROSTER_USER_REC *user;
453 	XMPP_ROSTER_RESOURCE_REC *resource;
454 	char *jid, *res;
455 	gboolean own;
456 
457 	g_return_if_fail(IS_XMPP_SERVER(server));
458 	g_return_if_fail(full_jid != NULL);
459 	own = FALSE;
460 	jid = xmpp_strip_resource(full_jid);
461 	res = xmpp_extract_resource(full_jid);
462 	user = rosters_find_user(server->roster, jid, &group, NULL);
463 	if (user == NULL && !(own = strcmp(jid, server->jid) == 0))
464 		goto out;
465 	resource = rosters_find_resource(!own ?
466 	    user->resources : server->my_resources, res);
467 	if (resource != NULL) {
468 		resource->show = XMPP_PRESENCE_ERROR;
469 		if (!own) /* sort the group */
470 			group->users = g_slist_sort(group->users,
471 			    func_sort_user);
472 		signal_emit("xmpp presence changed", 4, server, full_jid,
473 		    XMPP_PRESENCE_ERROR, NULL);
474 	} else if (user != NULL)
475 		user->error = TRUE;
476 
477 out:
478 	g_free(jid);
479 	g_free(res);
480 }
481 
482 
483 static void
sig_recv_presence(XMPP_SERVER_REC * server,LmMessage * lmsg,const int type,const char * id,const char * from,const char * to)484 sig_recv_presence(XMPP_SERVER_REC *server, LmMessage *lmsg, const int type,
485     const char *id, const char *from, const char *to)
486 {
487 	LmMessageNode *node, *node_show, *node_priority;
488 	char *status;
489 
490 	if (server->ischannel(SERVER(server), from))
491 		return;
492 	switch(type) {
493 	case LM_MESSAGE_SUB_TYPE_AVAILABLE:
494 		node_show = lm_message_node_get_child(lmsg->node, "show");
495 		node = lm_message_node_get_child(lmsg->node, "status");
496 		status = node != NULL ? xmpp_recode_in(node->value) : NULL;
497 		node_priority = lm_message_node_get_child(lmsg->node, "priority");
498 		update_user_presence(server, from,
499 		    node_show != NULL ? node_show->value : NULL, status,
500 		    node_priority != NULL ? node_priority->value : NULL);
501 		g_free(status);
502 		break;
503 	case LM_MESSAGE_SUB_TYPE_UNAVAILABLE:
504 		node = lm_message_node_get_child(lmsg->node, "status");
505 		status = node != NULL ? xmpp_recode_in(node->value) : NULL;
506 		user_unavailable(server, from, status);
507 		g_free(status);
508 		break;
509 	case LM_MESSAGE_SUB_TYPE_SUBSCRIBE:
510 		node = lm_message_node_get_child(lmsg->node, "status");
511 		status = node != NULL ? xmpp_recode_in(node->value) : NULL;
512 		signal_emit("xmpp presence subscribe", 3, server, from, status);
513 		g_free(status);
514 		break;
515 	case LM_MESSAGE_SUB_TYPE_UNSUBSCRIBE:
516 		signal_emit("xmpp presence unsubscribe", 2, server, from);
517 		break;
518 	case LM_MESSAGE_SUB_TYPE_SUBSCRIBED:
519 		signal_emit("xmpp presence subscribed", 2, server, from);
520 		break;
521 	case LM_MESSAGE_SUB_TYPE_UNSUBSCRIBED:
522 		signal_emit("xmpp presence unsubscribed", 2, server, from);
523 		break;
524 	case LM_MESSAGE_SUB_TYPE_ERROR:
525 		user_presence_error(server, from);
526 		break;
527 	}
528 }
529 
530 static void
sig_recv_iq(XMPP_SERVER_REC * server,LmMessage * lmsg,const int type,const char * id,const char * from,const char * to)531 sig_recv_iq(XMPP_SERVER_REC *server, LmMessage *lmsg, const int type,
532     const char *id, const char *from, const char *to)
533 {
534 	LmMessageNode *node, *item, *group_node;
535 	char *jid, *name, *group;
536 	const char *subscription;
537 
538 	if (type != LM_MESSAGE_SUB_TYPE_RESULT
539 	    && type != LM_MESSAGE_SUB_TYPE_SET)
540 		return;
541 	node = lm_find_node(lmsg->node, "query", "xmlns", XMLNS_ROSTER);
542 	if (node == NULL)
543 		return;
544 	for (item = node->children; item != NULL; item = item->next) {
545 		if (strcmp(item->name, "item") != 0)
546 			continue;
547 		jid = xmpp_recode_in(lm_message_node_get_attribute(item, "jid"));
548 		name = xmpp_recode_in(lm_message_node_get_attribute(item, "name"));
549 		group_node = lm_message_node_get_child(item, "group");
550 		group = group_node != NULL ?
551 		    xmpp_recode_in(group_node->value) : NULL;
552 		subscription = lm_message_node_get_attribute(item,
553 		    "subscription");
554 		update_user(server, jid, subscription, name, group);
555 		g_free(jid);
556 		g_free(name);
557 		g_free(group);
558 	}
559 }
560 
561 static void
sig_connected(XMPP_SERVER_REC * server)562 sig_connected(XMPP_SERVER_REC *server)
563 {
564 	LmMessage *lmsg;
565 	LmMessageNode *node;
566 
567 	if (!IS_XMPP_SERVER(server))
568 		return;
569 	signal_emit("xmpp server status", 2, server, "Requesting the roster.");
570 	lmsg = lm_message_new_with_sub_type(NULL, LM_MESSAGE_TYPE_IQ,
571 	    LM_MESSAGE_SUB_TYPE_GET);
572 	node = lm_message_node_add_child(lmsg->node, "query", NULL);
573 	lm_message_node_set_attribute(node, "xmlns", "jabber:iq:roster");
574 	signal_emit("xmpp send iq", 2, server, lmsg);
575 	lm_message_unref(lmsg);
576 }
577 
578 void
rosters_init(void)579 rosters_init(void)
580 {
581 	signal_add("server connected", sig_connected);
582 	signal_add_first("server disconnected", roster_cleanup);
583 	signal_add("xmpp recv presence", sig_recv_presence);
584 	signal_add("xmpp recv iq", sig_recv_iq);
585 }
586 
587 void
rosters_deinit(void)588 rosters_deinit(void)
589 {
590 	signal_remove("server connected", sig_connected);
591 	signal_remove("server disconnected", roster_cleanup);
592 	signal_remove("xmpp recv presence", sig_recv_presence);
593 	signal_remove("xmpp recv iq", sig_recv_iq);
594 }
595