1 /*
2  * purple - Jabber Protocol Plugin
3  *
4  * Purple is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  *
22  */
23 #include "internal.h"
24 #include "debug.h"
25 #include "glibcompat.h"
26 #include "imgstore.h"
27 #include "prpl.h"
28 #include "notify.h"
29 #include "request.h"
30 #include "util.h"
31 #include "xmlnode.h"
32 
33 #include "buddy.h"
34 #include "chat.h"
35 #include "jabber.h"
36 #include "iq.h"
37 #include "presence.h"
38 #include "useravatar.h"
39 #include "xdata.h"
40 #include "pep.h"
41 #include "adhoccommands.h"
42 #include "google/google.h"
43 
44 typedef struct {
45 	long idle_seconds;
46 } JabberBuddyInfoResource;
47 
48 typedef struct {
49 	JabberStream *js;
50 	JabberBuddy *jb;
51 	char *jid;
52 	GSList *ids;
53 	GHashTable *resources;
54 	guint timeout_handle;
55 	GSList *vcard_imgids;
56 	PurpleNotifyUserInfo *user_info;
57 	long last_seconds;
58 	gchar *last_message;
59 } JabberBuddyInfo;
60 
61 static void
jabber_buddy_resource_free(JabberBuddyResource * jbr)62 jabber_buddy_resource_free(JabberBuddyResource *jbr)
63 {
64 	g_return_if_fail(jbr != NULL);
65 
66 	jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
67 
68 	while(jbr->commands) {
69 		JabberAdHocCommands *cmd = jbr->commands->data;
70 		g_free(cmd->jid);
71 		g_free(cmd->node);
72 		g_free(cmd->name);
73 		g_free(cmd);
74 		jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
75 	}
76 
77 	while (jbr->caps.exts) {
78 		g_free(jbr->caps.exts->data);
79 		jbr->caps.exts = g_list_delete_link(jbr->caps.exts, jbr->caps.exts);
80 	}
81 
82 	g_free(jbr->name);
83 	g_free(jbr->status);
84 	g_free(jbr->thread_id);
85 	g_free(jbr->client.name);
86 	g_free(jbr->client.version);
87 	g_free(jbr->client.os);
88 	g_free(jbr);
89 }
90 
jabber_buddy_free(JabberBuddy * jb)91 void jabber_buddy_free(JabberBuddy *jb)
92 {
93 	g_return_if_fail(jb != NULL);
94 
95 	g_free(jb->error_msg);
96 	while(jb->resources)
97 		jabber_buddy_resource_free(jb->resources->data);
98 
99 	g_free(jb);
100 }
101 
jabber_buddy_find(JabberStream * js,const char * name,gboolean create)102 JabberBuddy *jabber_buddy_find(JabberStream *js, const char *name,
103 		gboolean create)
104 {
105 	JabberBuddy *jb;
106 	char *realname;
107 
108 	if (js->buddies == NULL)
109 		return NULL;
110 
111 	if(!(realname = jabber_get_bare_jid(name)))
112 		return NULL;
113 
114 	jb = g_hash_table_lookup(js->buddies, realname);
115 
116 	if(!jb && create) {
117 		jb = g_new0(JabberBuddy, 1);
118 		g_hash_table_insert(js->buddies, realname, jb);
119 	} else
120 		g_free(realname);
121 
122 	return jb;
123 }
124 
125 /* Returns -1 if a is a higher priority resource than b, or is
126  * "more available" than b.  0 if they're the same, and 1 if b is
127  * higher priority/more available than a.
128  */
resource_compare_cb(gconstpointer a,gconstpointer b)129 static gint resource_compare_cb(gconstpointer a, gconstpointer b)
130 {
131 	const JabberBuddyResource *jbra = a;
132 	const JabberBuddyResource *jbrb = b;
133 	JabberBuddyState state_a, state_b;
134 
135 	if (jbra->priority != jbrb->priority)
136 		return jbra->priority > jbrb->priority ? -1 : 1;
137 
138 	/* Fold the states for easier comparison */
139 	/* TODO: Differentiate online/chat and away/dnd? */
140 	switch (jbra->state) {
141 		case JABBER_BUDDY_STATE_ONLINE:
142 		case JABBER_BUDDY_STATE_CHAT:
143 			state_a = JABBER_BUDDY_STATE_ONLINE;
144 			break;
145 		case JABBER_BUDDY_STATE_AWAY:
146 		case JABBER_BUDDY_STATE_DND:
147 			state_a = JABBER_BUDDY_STATE_AWAY;
148 			break;
149 		case JABBER_BUDDY_STATE_XA:
150 			state_a = JABBER_BUDDY_STATE_XA;
151 			break;
152 		case JABBER_BUDDY_STATE_UNAVAILABLE:
153 			state_a = JABBER_BUDDY_STATE_UNAVAILABLE;
154 			break;
155 		default:
156 			state_a = JABBER_BUDDY_STATE_UNKNOWN;
157 			break;
158 	}
159 
160 	switch (jbrb->state) {
161 		case JABBER_BUDDY_STATE_ONLINE:
162 		case JABBER_BUDDY_STATE_CHAT:
163 			state_b = JABBER_BUDDY_STATE_ONLINE;
164 			break;
165 		case JABBER_BUDDY_STATE_AWAY:
166 		case JABBER_BUDDY_STATE_DND:
167 			state_b = JABBER_BUDDY_STATE_AWAY;
168 			break;
169 		case JABBER_BUDDY_STATE_XA:
170 			state_b = JABBER_BUDDY_STATE_XA;
171 			break;
172 		case JABBER_BUDDY_STATE_UNAVAILABLE:
173 			state_b = JABBER_BUDDY_STATE_UNAVAILABLE;
174 			break;
175 		default:
176 			state_b = JABBER_BUDDY_STATE_UNKNOWN;
177 			break;
178 	}
179 
180 	if (state_a == state_b) {
181 		if (jbra->idle == jbrb->idle)
182 			return 0;
183 		else if ((jbra->idle && !jbrb->idle) ||
184 				(jbra->idle && jbrb->idle && jbra->idle < jbrb->idle))
185 			return 1;
186 		else
187 			return -1;
188 	}
189 
190 	if (state_a == JABBER_BUDDY_STATE_ONLINE)
191 		return -1;
192 	else if (state_a == JABBER_BUDDY_STATE_AWAY &&
193 				(state_b == JABBER_BUDDY_STATE_XA ||
194 				 state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
195 				 state_b == JABBER_BUDDY_STATE_UNKNOWN))
196 		return -1;
197 	else if (state_a == JABBER_BUDDY_STATE_XA &&
198 				(state_b == JABBER_BUDDY_STATE_UNAVAILABLE ||
199 				 state_b == JABBER_BUDDY_STATE_UNKNOWN))
200 		return -1;
201 	else if (state_a == JABBER_BUDDY_STATE_UNAVAILABLE &&
202 				state_b == JABBER_BUDDY_STATE_UNKNOWN)
203 		return -1;
204 
205 	return 1;
206 }
207 
jabber_buddy_find_resource(JabberBuddy * jb,const char * resource)208 JabberBuddyResource *jabber_buddy_find_resource(JabberBuddy *jb,
209 		const char *resource)
210 {
211 	GList *l;
212 
213 	if (!jb)
214 		return NULL;
215 
216 	if (resource == NULL)
217 		return jb->resources ? jb->resources->data : NULL;
218 
219 	for (l = jb->resources; l; l = l->next)
220 	{
221 		JabberBuddyResource *jbr = l->data;
222 		if (purple_strequal(resource, jbr->name))
223 			return jbr;
224 	}
225 
226 	return NULL;
227 }
228 
jabber_buddy_track_resource(JabberBuddy * jb,const char * resource,int priority,JabberBuddyState state,const char * status)229 JabberBuddyResource *jabber_buddy_track_resource(JabberBuddy *jb, const char *resource,
230 		int priority, JabberBuddyState state, const char *status)
231 {
232 	/* TODO: Optimization: Only reinsert if priority+state changed */
233 	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
234 	if (jbr) {
235 		jb->resources = g_list_remove(jb->resources, jbr);
236 	} else {
237 		jbr = g_new0(JabberBuddyResource, 1);
238 		jbr->jb = jb;
239 		jbr->name = g_strdup(resource);
240 		jbr->capabilities = JABBER_CAP_NONE;
241 		jbr->tz_off = PURPLE_NO_TZ_OFF;
242 	}
243 	jbr->priority = priority;
244 	jbr->state = state;
245 	g_free(jbr->status);
246 	jbr->status = g_strdup(status);
247 
248 	jb->resources = g_list_insert_sorted(jb->resources, jbr,
249 	                                     resource_compare_cb);
250 	return jbr;
251 }
252 
jabber_buddy_remove_resource(JabberBuddy * jb,const char * resource)253 void jabber_buddy_remove_resource(JabberBuddy *jb, const char *resource)
254 {
255 	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
256 
257 	if(!jbr)
258 		return;
259 
260 	jabber_buddy_resource_free(jbr);
261 }
262 
263 /*******
264  * This is the old vCard stuff taken from the old prpl.  vCards, by definition
265  * are a temporary thing until jabber can get its act together and come up
266  * with a format for user information, hence the namespace of 'vcard-temp'
267  *
268  * Since I don't feel like putting that much work into something that's
269  * _supposed_ to go away, i'm going to just copy the kludgy old code here,
270  * and make it purdy when jabber comes up with a standards-track JEP to
271  * replace vcard-temp
272  *                                 --Nathan
273  *******/
274 
275 /*---------------------------------------*/
276 /* Jabber "set info" (vCard) support     */
277 /*---------------------------------------*/
278 
279 /*
280  * V-Card format:
281  *
282  *  <vCard prodid='' version='' xmlns=''>
283  *    <FN></FN>
284  *    <N>
285  *	<FAMILY/>
286  *	<GIVEN/>
287  *    </N>
288  *    <NICKNAME/>
289  *    <URL/>
290  *    <ADR>
291  *	<STREET/>
292  *	<EXTADD/>
293  *	<LOCALITY/>
294  *	<REGION/>
295  *	<PCODE/>
296  *	<COUNTRY/>
297  *    </ADR>
298  *    <TEL/>
299  *    <EMAIL/>
300  *    <ORG>
301  *	<ORGNAME/>
302  *	<ORGUNIT/>
303  *    </ORG>
304  *    <TITLE/>
305  *    <ROLE/>
306  *    <DESC/>
307  *    <BDAY/>
308  *  </vCard>
309  *
310  * See also:
311  *
312  *	http://docs.jabber.org/proto/html/vcard-temp.html
313  *	http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
314  */
315 
316 /*
317  * Cross-reference user-friendly V-Card entry labels to vCard XML tags
318  * and attributes.
319  *
320  * Order is (or should be) unimportant.  For example: we have no way of
321  * knowing in what order real data will arrive.
322  *
323  * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
324  *         name, XML tag's parent tag "path" (relative to vCard node).
325  *
326  *         List is terminated by a NULL label pointer.
327  *
328  *	   Entries with no label text, but with XML tag and parent tag
329  *	   entries, are used by V-Card XML construction routines to
330  *	   "automagically" construct the appropriate XML node tree.
331  *
332  * Thoughts on future direction/expansion
333  *
334  *	This is a "simple" vCard.
335  *
336  *	It is possible for nodes other than the "vCard" node to have
337  *      attributes.  Should that prove necessary/desirable, add an
338  *      "attributes" pointer to the vcard_template struct, create the
339  *      necessary tag_attr structs, and add 'em to the vcard_dflt_data
340  *      array.
341  *
342  *	The above changes will (obviously) require changes to the vCard
343  *      construction routines.
344  */
345 
346 struct vcard_template {
347 	char *label;			/* label text pointer */
348 	char *tag;			/* tag text */
349 	char *ptag;			/* parent tag "path" text */
350 } const vcard_template_data[] = {
351 	{N_("Full Name"),          "FN",        NULL},
352 	{N_("Family Name"),        "FAMILY",    "N"},
353 	{N_("Given Name"),         "GIVEN",     "N"},
354 	{N_("Nickname"),           "NICKNAME",  NULL},
355 	{N_("URL"),                "URL",       NULL},
356 	{N_("Street Address"),     "STREET",    "ADR"},
357 	{N_("Extended Address"),   "EXTADD",    "ADR"},
358 	{N_("Locality"),           "LOCALITY",  "ADR"},
359 	{N_("Region"),             "REGION",    "ADR"},
360 	{N_("Postal Code"),        "PCODE",     "ADR"},
361 	{N_("Country"),            "CTRY",      "ADR"},
362 	{N_("Telephone"),          "NUMBER",    "TEL"},
363 	{N_("Email"),              "USERID",    "EMAIL"},
364 	{N_("Organization Name"),  "ORGNAME",   "ORG"},
365 	{N_("Organization Unit"),  "ORGUNIT",   "ORG"},
366 	{N_("Job Title"),          "TITLE",     NULL},
367 	{N_("Role"),               "ROLE",      NULL},
368 	{N_("Birthday"),           "BDAY",      NULL},
369 	{N_("Description"),        "DESC",      NULL},
370 	{"",                       "N",         NULL},
371 	{"",                       "ADR",       NULL},
372 	{"",                       "ORG",       NULL},
373 	{NULL,                     NULL,        NULL}
374 };
375 
376 /*
377  * The "vCard" tag's attribute list...
378  */
379 struct tag_attr {
380 	char *attr;
381 	char *value;
382 } const vcard_tag_attr_list[] = {
383 	{"prodid",   "-//HandGen//NONSGML vGen v1.0//EN"},
384 	{"version",  "2.0",                             },
385 	{"xmlns",    "vcard-temp",                      },
386 	{NULL, NULL},
387 };
388 
389 
390 /*
391  * Insert a tag node into an xmlnode tree, recursively inserting parent tag
392  * nodes as necessary
393  *
394  * Returns pointer to inserted node
395  *
396  * Note to hackers: this code is designed to be re-entrant (it's recursive--it
397  * calls itself), so don't put any "static"s in here!
398  */
insert_tag_to_parent_tag(xmlnode * start,const char * parent_tag,const char * new_tag)399 static xmlnode *insert_tag_to_parent_tag(xmlnode *start, const char *parent_tag, const char *new_tag)
400 {
401 	xmlnode *x = NULL;
402 
403 	/*
404 	 * If the parent tag wasn't specified, see if we can get it
405 	 * from the vCard template struct.
406 	 */
407 	if(parent_tag == NULL) {
408 		const struct vcard_template *vc_tp = vcard_template_data;
409 
410 		while(vc_tp->label != NULL) {
411 			if(purple_strequal(vc_tp->tag, new_tag)) {
412 				parent_tag = vc_tp->ptag;
413 				break;
414 			}
415 			++vc_tp;
416 		}
417 	}
418 
419 	/*
420 	 * If we have a parent tag...
421 	 */
422 	if(parent_tag != NULL ) {
423 		/*
424 		 * Try to get the parent node for a tag
425 		 */
426 		if((x = xmlnode_get_child(start, parent_tag)) == NULL) {
427 			/*
428 			 * Descend?
429 			 */
430 			char *grand_parent = g_strdup(parent_tag);
431 			char *parent;
432 
433 			if((parent = strrchr(grand_parent, '/')) != NULL) {
434 				*(parent++) = '\0';
435 				x = insert_tag_to_parent_tag(start, grand_parent, parent);
436 			} else {
437 				x = xmlnode_new_child(start, grand_parent);
438 			}
439 			g_free(grand_parent);
440 		} else {
441 			/*
442 			 * We found *something* to be the parent node.
443 			 * Note: may be the "root" node!
444 			 */
445 			xmlnode *y;
446 			if((y = xmlnode_get_child(x, new_tag)) != NULL) {
447 				return(y);
448 			}
449 		}
450 	}
451 
452 	/*
453 	 * insert the new tag into its parent node
454 	 */
455 	return(xmlnode_new_child((x == NULL? start : x), new_tag));
456 }
457 
458 /*
459  * Send vCard info to Jabber server
460  */
jabber_set_info(PurpleConnection * gc,const char * info)461 void jabber_set_info(PurpleConnection *gc, const char *info)
462 {
463 	PurpleStoredImage *img;
464 	JabberIq *iq;
465 	JabberStream *js = purple_connection_get_protocol_data(gc);
466 	xmlnode *vc_node;
467 	const struct tag_attr *tag_attr;
468 
469 	/* if we have't grabbed the remote vcard yet, we can't
470 	 * assume that what we have here is correct */
471 	if(!js->vcard_fetched)
472 		return;
473 
474 	if (js->vcard_timer) {
475 		purple_timeout_remove(js->vcard_timer);
476 		js->vcard_timer = 0;
477 	}
478 
479 	g_free(js->avatar_hash);
480 	js->avatar_hash = NULL;
481 
482 	/*
483 	 * Send only if there's actually any *information* to send
484 	 */
485 	vc_node = info ? xmlnode_from_str(info, -1) : NULL;
486 
487 	if (vc_node && (!vc_node->name ||
488 			g_ascii_strncasecmp(vc_node->name, "vCard", 5))) {
489 		xmlnode_free(vc_node);
490 		vc_node = NULL;
491 	}
492 
493 	if ((img = purple_buddy_icons_find_account_icon(gc->account))) {
494 		gconstpointer avatar_data;
495 		gsize avatar_len;
496 		xmlnode *photo, *binval, *type;
497 		gchar *enc;
498 
499 		if(!vc_node) {
500 			vc_node = xmlnode_new("vCard");
501 			for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
502 				xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
503 		}
504 
505 		avatar_data = purple_imgstore_get_data(img);
506 		avatar_len = purple_imgstore_get_size(img);
507 		/* Get rid of an old PHOTO if one exists.
508 		 * TODO: This may want to be modified to remove all old PHOTO
509 		 * children, at the moment some people have managed to get
510 		 * multiple PHOTO entries in their vCard. */
511 		if((photo = xmlnode_get_child(vc_node, "PHOTO"))) {
512 			xmlnode_free(photo);
513 		}
514 		photo = xmlnode_new_child(vc_node, "PHOTO");
515 		type = xmlnode_new_child(photo, "TYPE");
516 		xmlnode_insert_data(type, "image/png", -1);
517 		binval = xmlnode_new_child(photo, "BINVAL");
518 		enc = purple_base64_encode(avatar_data, avatar_len);
519 
520 		js->avatar_hash =
521 			jabber_calculate_data_hash(avatar_data, avatar_len, "sha1");
522 
523 		xmlnode_insert_data(binval, enc, -1);
524 		g_free(enc);
525 		purple_imgstore_unref(img);
526 	} else if (vc_node) {
527 		xmlnode *photo;
528 		/* TODO: Remove all PHOTO children? (see above note) */
529 		if ((photo = xmlnode_get_child(vc_node, "PHOTO"))) {
530 			xmlnode_free(photo);
531 		}
532 	}
533 
534 	if (vc_node != NULL) {
535 		iq = jabber_iq_new(js, JABBER_IQ_SET);
536 		xmlnode_insert_child(iq->node, vc_node);
537 		jabber_iq_send(iq);
538 
539 		/* Send presence to update vcard-temp:x:update */
540 		jabber_presence_send(js, FALSE);
541 	}
542 }
543 
jabber_set_buddy_icon(PurpleConnection * gc,PurpleStoredImage * img)544 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
545 {
546 	PurpleAccount *account = purple_connection_get_account(gc);
547 
548 	/* Publish the avatar as specified in XEP-0084 */
549 	jabber_avatar_set(gc->proto_data, img);
550 	/* Set the image in our vCard */
551 	jabber_set_info(gc, purple_account_get_user_info(account));
552 
553 	/* TODO: Fake image to ourselves, since a number of servers do not echo
554 	 * back our presence to us. To do this without uselessly copying the data
555 	 * of the image, we need purple_buddy_icons_set_for_user_image (i.e. takes
556 	 * an existing icon/stored image). */
557 }
558 
559 /*
560  * This is the callback from the "ok clicked" for "set vCard"
561  *
562  * Sets the vCard with data from PurpleRequestFields.
563  */
564 static void
jabber_format_info(PurpleConnection * gc,PurpleRequestFields * fields)565 jabber_format_info(PurpleConnection *gc, PurpleRequestFields *fields)
566 {
567 	xmlnode *vc_node;
568 	PurpleRequestField *field;
569 	const char *text;
570 	char *p;
571 	const struct vcard_template *vc_tp;
572 	const struct tag_attr *tag_attr;
573 
574 	vc_node = xmlnode_new("vCard");
575 
576 	for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
577 		xmlnode_set_attrib(vc_node, tag_attr->attr, tag_attr->value);
578 
579 	for (vc_tp = vcard_template_data; vc_tp->label != NULL; vc_tp++) {
580 		if (*vc_tp->label == '\0')
581 			continue;
582 
583 		field = purple_request_fields_get_field(fields, vc_tp->tag);
584 		text  = purple_request_field_string_get_value(field);
585 
586 
587 		if (text != NULL && *text != '\0') {
588 			xmlnode *xp;
589 
590 			purple_debug_info("jabber", "Setting %s to '%s'\n", vc_tp->tag, text);
591 
592 			if ((xp = insert_tag_to_parent_tag(vc_node,
593 											   NULL, vc_tp->tag)) != NULL) {
594 
595 				xmlnode_insert_data(xp, text, -1);
596 			}
597 		}
598 	}
599 
600 	p = xmlnode_to_str(vc_node, NULL);
601 	xmlnode_free(vc_node);
602 
603 	purple_account_set_user_info(purple_connection_get_account(gc), p);
604 	serv_set_info(gc, p);
605 
606 	g_free(p);
607 }
608 
609 /*
610  * This gets executed by the proto action
611  *
612  * Creates a new PurpleRequestFields struct, gets the XML-formatted user_info
613  * string (if any) into GSLists for the (multi-entry) edit dialog and
614  * calls the set_vcard dialog.
615  */
jabber_setup_set_info(PurplePluginAction * action)616 void jabber_setup_set_info(PurplePluginAction *action)
617 {
618 	PurpleConnection *gc = (PurpleConnection *) action->context;
619 	PurpleRequestFields *fields;
620 	PurpleRequestFieldGroup *group;
621 	PurpleRequestField *field;
622 	const struct vcard_template *vc_tp;
623 	const char *user_info;
624 	char *cdata = NULL;
625 	xmlnode *x_vc_data = NULL;
626 
627 	fields = purple_request_fields_new();
628 	group = purple_request_field_group_new(NULL);
629 	purple_request_fields_add_group(fields, group);
630 
631 	/*
632 	 * Get existing, XML-formatted, user info
633 	 */
634 	if((user_info = purple_account_get_user_info(gc->account)) != NULL)
635 		x_vc_data = xmlnode_from_str(user_info, -1);
636 
637 	/*
638 	 * Set up GSLists for edit with labels from "template," data from user info
639 	 */
640 	for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
641 		xmlnode *data_node;
642 		if((vc_tp->label)[0] == '\0')
643 			continue;
644 
645 		if (x_vc_data != NULL) {
646 			if(vc_tp->ptag == NULL) {
647 				data_node = xmlnode_get_child(x_vc_data, vc_tp->tag);
648 			} else {
649 				gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
650 				data_node = xmlnode_get_child(x_vc_data, tag);
651 				g_free(tag);
652 			}
653 			if(data_node)
654 				cdata = xmlnode_get_data(data_node);
655 		}
656 
657 		if(purple_strequal(vc_tp->tag, "DESC")) {
658 			field = purple_request_field_string_new(vc_tp->tag,
659 												  _(vc_tp->label), cdata,
660 												  TRUE);
661 		} else {
662 			field = purple_request_field_string_new(vc_tp->tag,
663 												  _(vc_tp->label), cdata,
664 												  FALSE);
665 		}
666 
667 		g_free(cdata);
668 		cdata = NULL;
669 
670 		purple_request_field_group_add_field(group, field);
671 	}
672 
673 	if(x_vc_data != NULL)
674 		xmlnode_free(x_vc_data);
675 
676 	purple_request_fields(gc, _("Edit XMPP vCard"),
677 						_("Edit XMPP vCard"),
678 						_("All items below are optional. Enter only the "
679 						  "information with which you feel comfortable."),
680 						fields,
681 						_("Save"), G_CALLBACK(jabber_format_info),
682 						_("Cancel"), NULL,
683 						purple_connection_get_account(gc), NULL, NULL,
684 						gc);
685 }
686 
687 /*---------------------------------------*/
688 /* End Jabber "set info" (vCard) support */
689 /*---------------------------------------*/
690 
691 /******
692  * end of that ancient crap that needs to die
693  ******/
694 
jabber_buddy_info_destroy(JabberBuddyInfo * jbi)695 static void jabber_buddy_info_destroy(JabberBuddyInfo *jbi)
696 {
697 	/* Remove the timeout, which would otherwise trigger jabber_buddy_get_info_timeout() */
698 	if (jbi->timeout_handle > 0)
699 		purple_timeout_remove(jbi->timeout_handle);
700 
701 	g_free(jbi->jid);
702 	g_hash_table_destroy(jbi->resources);
703 	g_free(jbi->last_message);
704 	purple_notify_user_info_destroy(jbi->user_info);
705 	g_free(jbi);
706 }
707 
708 static void
add_jbr_info(JabberBuddyInfo * jbi,const char * resource,JabberBuddyResource * jbr)709 add_jbr_info(JabberBuddyInfo *jbi, const char *resource,
710              JabberBuddyResource *jbr)
711 {
712 	JabberBuddyInfoResource *jbir;
713 	PurpleNotifyUserInfo *user_info;
714 
715 	jbir = g_hash_table_lookup(jbi->resources, resource);
716 	user_info = jbi->user_info;
717 
718 	if (jbr && jbr->client.name) {
719 		char *tmp =
720 			g_strdup_printf("%s%s%s", jbr->client.name,
721 		                    (jbr->client.version ? " " : ""),
722 		                    (jbr->client.version ? jbr->client.version : ""));
723 		purple_notify_user_info_prepend_pair(user_info, _("Client"), tmp);
724 		g_free(tmp);
725 
726 		if (jbr->client.os)
727 			purple_notify_user_info_prepend_pair(user_info, _("Operating System"), jbr->client.os);
728 	}
729 
730 	if (jbr && jbr->tz_off != PURPLE_NO_TZ_OFF) {
731 		time_t now_t;
732 		struct tm *now;
733 		char *timestamp;
734 		time(&now_t);
735 		now_t += jbr->tz_off;
736 		now = gmtime(&now_t);
737 
738 		timestamp =
739 			g_strdup_printf("%s %c%02d%02d", purple_time_format(now),
740 		                    jbr->tz_off < 0 ? '-' : '+',
741 		                    abs(jbr->tz_off / (60*60)),
742 		                    abs((jbr->tz_off % (60*60)) / 60));
743 		purple_notify_user_info_prepend_pair(user_info, _("Local Time"), timestamp);
744 		g_free(timestamp);
745 	}
746 
747 	if (jbir && jbir->idle_seconds > 0) {
748 		char *idle = purple_str_seconds_to_string(jbir->idle_seconds);
749 		purple_notify_user_info_prepend_pair(user_info, _("Idle"), idle);
750 		g_free(idle);
751 	}
752 
753 	if (jbr) {
754 		char *purdy = NULL;
755 		char *tmp;
756 		char priority[12];
757 		const char *status_name = jabber_buddy_state_get_name(jbr->state);
758 
759 		if (jbr->status) {
760 			tmp = purple_markup_escape_text(jbr->status, -1);
761 			purdy = purple_strdup_withhtml(tmp);
762 			g_free(tmp);
763 
764 			if (purple_strequal(status_name, purdy))
765 				status_name = NULL;
766 		}
767 
768 		tmp = g_strdup_printf("%s%s%s", (status_name ? status_name : ""),
769 						((status_name && purdy) ? ": " : ""),
770 						(purdy ? purdy : ""));
771 		purple_notify_user_info_prepend_pair(user_info, _("Status"), tmp);
772 
773 		g_snprintf(priority, sizeof(priority), "%d", jbr->priority);
774 		purple_notify_user_info_prepend_pair(user_info, _("Priority"), priority);
775 
776 		g_free(tmp);
777 		g_free(purdy);
778 	} else {
779 		purple_notify_user_info_prepend_pair(user_info, _("Status"), _("Unknown"));
780 	}
781 }
782 
jabber_buddy_info_show_if_ready(JabberBuddyInfo * jbi)783 static void jabber_buddy_info_show_if_ready(JabberBuddyInfo *jbi)
784 {
785 	char *resource_name;
786 	JabberBuddyResource *jbr;
787 	GList *resources;
788 	PurpleNotifyUserInfo *user_info;
789 
790 	/* not yet */
791 	if (jbi->ids)
792 		return;
793 
794 	user_info = jbi->user_info;
795 	resource_name = jabber_get_resource(jbi->jid);
796 
797 	/* If we have one or more pairs from the vcard, put a section break above it */
798 	if (purple_notify_user_info_get_entries(user_info))
799 		purple_notify_user_info_prepend_section_break(user_info);
800 
801 	/* Add the information about the user's resource(s) */
802 	if (resource_name) {
803 		jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
804 		add_jbr_info(jbi, resource_name, jbr);
805 	} else {
806 		/* TODO: This is in priority-ascending order (lowest prio first), because
807 		 * everything is prepended.  Is that ok? */
808 		for (resources = jbi->jb->resources; resources; resources = resources->next) {
809 			jbr = resources->data;
810 
811 			/* put a section break between resources, this is not needed if
812 			 we are at the first, because one was already added for the vcard
813 			 section */
814 			if (resources != jbi->jb->resources)
815 				purple_notify_user_info_prepend_section_break(user_info);
816 
817 			add_jbr_info(jbi, jbr->name, jbr);
818 
819 			if (jbr->name)
820 				purple_notify_user_info_prepend_pair(user_info, _("Resource"), jbr->name);
821 		}
822 	}
823 
824 	if (!jbi->jb->resources) {
825 		/* the buddy is offline */
826 		gboolean is_domain = jabber_jid_is_domain(jbi->jid);
827 
828 		if (jbi->last_seconds > 0) {
829 			char *last = purple_str_seconds_to_string(jbi->last_seconds);
830 			gchar *message = NULL;
831 			const gchar *title = NULL;
832 			if (is_domain) {
833 				title = _("Uptime");
834 				message = last;
835 				last = NULL;
836 			} else {
837 				title = _("Logged Off");
838 				message = g_strdup_printf(_("%s ago"), last);
839 			}
840 			purple_notify_user_info_prepend_pair(user_info, title, message);
841 			g_free(last);
842 			g_free(message);
843 		}
844 
845 		if (!is_domain) {
846 			gchar *status =
847 				g_strdup_printf("%s%s%s",	_("Offline"),
848 				                jbi->last_message ? ": " : "",
849 				                jbi->last_message ? jbi->last_message : "");
850 			purple_notify_user_info_prepend_pair(user_info, _("Status"), status);
851 			g_free(status);
852 		}
853 	}
854 
855 	g_free(resource_name);
856 
857 	purple_notify_userinfo(jbi->js->gc, jbi->jid, user_info, NULL, NULL);
858 
859 	while (jbi->vcard_imgids) {
860 		purple_imgstore_unref_by_id(GPOINTER_TO_INT(jbi->vcard_imgids->data));
861 		jbi->vcard_imgids = g_slist_delete_link(jbi->vcard_imgids, jbi->vcard_imgids);
862 	}
863 
864 	jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
865 
866 	jabber_buddy_info_destroy(jbi);
867 }
868 
jabber_buddy_info_remove_id(JabberBuddyInfo * jbi,const char * id)869 static void jabber_buddy_info_remove_id(JabberBuddyInfo *jbi, const char *id)
870 {
871 	GSList *l = jbi->ids;
872 	char *comp_id;
873 
874 	if(!id)
875 		return;
876 
877 	while(l) {
878 		comp_id = l->data;
879 		if(purple_strequal(id, comp_id)) {
880 			jbi->ids = g_slist_remove(jbi->ids, comp_id);
881 			g_free(comp_id);
882 			return;
883 		}
884 		l = l->next;
885 	}
886 }
887 
888 static gboolean
set_own_vcard_cb(gpointer data)889 set_own_vcard_cb(gpointer data)
890 {
891 	JabberStream *js = data;
892 	PurpleAccount *account = purple_connection_get_account(js->gc);
893 
894 	js->vcard_timer = 0;
895 
896 	jabber_set_info(js->gc, purple_account_get_user_info(account));
897 
898 	return FALSE;
899 }
900 
jabber_vcard_save_mine(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)901 static void jabber_vcard_save_mine(JabberStream *js, const char *from,
902                                    JabberIqType type, const char *id,
903                                    xmlnode *packet, gpointer data)
904 {
905 	xmlnode *vcard, *photo, *binval;
906 	char *txt, *vcard_hash = NULL;
907 	PurpleAccount *account;
908 
909 	if (type == JABBER_IQ_ERROR) {
910 		xmlnode *error;
911 		purple_debug_warning("jabber", "Server returned error while retrieving vCard\n");
912 
913 		error = xmlnode_get_child(packet, "error");
914 		if (!error || !xmlnode_get_child(error, "item-not-found"))
915 			return;
916 	}
917 
918 	account = purple_connection_get_account(js->gc);
919 
920 	if((vcard = xmlnode_get_child(packet, "vCard")) ||
921 			(vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp")))
922 	{
923 		txt = xmlnode_to_str(vcard, NULL);
924 		purple_account_set_user_info(account, txt);
925 		g_free(txt);
926 	} else {
927 		/* if we have no vCard, then lets not overwrite what we might have locally */
928 	}
929 
930 	js->vcard_fetched = TRUE;
931 
932 	if (vcard && (photo = xmlnode_get_child(vcard, "PHOTO")) &&
933 	             (binval = xmlnode_get_child(photo, "BINVAL"))) {
934 		gsize size;
935 		char *bintext = xmlnode_get_data(binval);
936 		if (bintext) {
937 			guchar *data = purple_base64_decode(bintext, &size);
938 			g_free(bintext);
939 
940 			if (data) {
941 				vcard_hash = jabber_calculate_data_hash(data, size, "sha1");
942 				g_free(data);
943 			}
944 		}
945 	}
946 
947 	/* Republish our vcard if the photo is different than the server's */
948 	if (js->initial_avatar_hash && !purple_strequal(vcard_hash, js->initial_avatar_hash)) {
949 		/*
950 		 * Google Talk has developed the behavior that it will not accept
951 		 * a vcard set in the first 10 seconds (or so) of the connection;
952 		 * it returns an error (namespaces trimmed):
953 		 * <error code="500" type="wait"><internal-server-error/></error>.
954 		 */
955 		if (js->googletalk)
956 			js->vcard_timer = purple_timeout_add_seconds(10, set_own_vcard_cb,
957 			                                             js);
958 		else
959 			jabber_set_info(js->gc, purple_account_get_user_info(account));
960 	} else if (vcard_hash) {
961 		/* A photo is in the vCard. Advertise its hash */
962 		js->avatar_hash = vcard_hash;
963 		vcard_hash = NULL;
964 
965 		/* Send presence to update vcard-temp:x:update */
966 		jabber_presence_send(js, FALSE);
967 	}
968 
969 	g_free(vcard_hash);
970 }
971 
jabber_vcard_fetch_mine(JabberStream * js)972 void jabber_vcard_fetch_mine(JabberStream *js)
973 {
974 	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
975 
976 	xmlnode *vcard = xmlnode_new_child(iq->node, "vCard");
977 	xmlnode_set_namespace(vcard, "vcard-temp");
978 	jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL);
979 
980 	jabber_iq_send(iq);
981 }
982 
jabber_vcard_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)983 static void jabber_vcard_parse(JabberStream *js, const char *from,
984                                JabberIqType type, const char *id,
985                                xmlnode *packet, gpointer data)
986 {
987 	char *bare_jid;
988 	char *text;
989 	char *serverside_alias = NULL;
990 	xmlnode *vcard;
991 	PurpleAccount *account;
992 	JabberBuddyInfo *jbi = data;
993 	PurpleNotifyUserInfo *user_info;
994 
995 	g_return_if_fail(jbi != NULL);
996 
997 	jabber_buddy_info_remove_id(jbi, id);
998 
999 	if (type == JABBER_IQ_ERROR) {
1000 		purple_debug_info("jabber", "Got error response for vCard\n");
1001 		jabber_buddy_info_show_if_ready(jbi);
1002 		return;
1003 	}
1004 
1005 	user_info = jbi->user_info;
1006 	account = purple_connection_get_account(js->gc);
1007 	bare_jid = jabber_get_bare_jid(from ? from : purple_account_get_username(account));
1008 
1009 	/* TODO: Is the query xmlns='vcard-temp' version of this still necessary? */
1010 	if((vcard = xmlnode_get_child(packet, "vCard")) ||
1011 			(vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) {
1012 		xmlnode *child;
1013 		for(child = vcard->child; child; child = child->next)
1014 		{
1015 			xmlnode *child2;
1016 
1017 			if(child->type != XMLNODE_TYPE_TAG)
1018 				continue;
1019 
1020 			text = xmlnode_get_data(child);
1021 			if(text && purple_strequal(child->name, "FN")) {
1022 				if (!serverside_alias)
1023 					serverside_alias = g_strdup(text);
1024 
1025 				purple_notify_user_info_add_pair_plaintext(user_info, _("Full Name"), text);
1026 			} else if(purple_strequal(child->name, "N")) {
1027 				for(child2 = child->child; child2; child2 = child2->next)
1028 				{
1029 					char *text2;
1030 
1031 					if(child2->type != XMLNODE_TYPE_TAG)
1032 						continue;
1033 
1034 					text2 = xmlnode_get_data(child2);
1035 					if(text2 && purple_strequal(child2->name, "FAMILY")) {
1036 						purple_notify_user_info_add_pair_plaintext(user_info, _("Family Name"), text2);
1037 					} else if(text2 && purple_strequal(child2->name, "GIVEN")) {
1038 						purple_notify_user_info_add_pair_plaintext(user_info, _("Given Name"), text2);
1039 					} else if(text2 && purple_strequal(child2->name, "MIDDLE")) {
1040 						purple_notify_user_info_add_pair_plaintext(user_info, _("Middle Name"), text2);
1041 					}
1042 					g_free(text2);
1043 				}
1044 			} else if(text && purple_strequal(child->name, "NICKNAME")) {
1045 				/* Prefer the Nickcname to the Full Name as the serverside alias if it's not just part of the jid.
1046 				 * Ignore it if it's part of the jid. */
1047 				if (strstr(bare_jid, text) == NULL) {
1048 					g_free(serverside_alias);
1049 					serverside_alias = g_strdup(text);
1050 
1051 					purple_notify_user_info_add_pair_plaintext(user_info, _("Nickname"), text);
1052 				}
1053 			} else if(text && purple_strequal(child->name, "BDAY")) {
1054 				purple_notify_user_info_add_pair_plaintext(user_info, _("Birthday"), text);
1055 			} else if(purple_strequal(child->name, "ADR")) {
1056 				gboolean address_line_added = FALSE;
1057 
1058 				for(child2 = child->child; child2; child2 = child2->next)
1059 				{
1060 					char *text2;
1061 
1062 					if(child2->type != XMLNODE_TYPE_TAG)
1063 						continue;
1064 
1065 					text2 = xmlnode_get_data(child2);
1066 					if (text2 == NULL)
1067 						continue;
1068 
1069 					/* We do this here so that it's not added if all the child
1070 					 * elements are empty. */
1071 					if (!address_line_added)
1072 					{
1073 						purple_notify_user_info_add_section_header(user_info, _("Address"));
1074 						address_line_added = TRUE;
1075 					}
1076 
1077 					if(purple_strequal(child2->name, "POBOX")) {
1078 						purple_notify_user_info_add_pair_plaintext(user_info, _("P.O. Box"), text2);
1079 					} else if (purple_strequal(child2->name, "EXTADD") || purple_strequal(child2->name, "EXTADR")) {
1080 						/*
1081 						 * EXTADD is correct, EXTADR is generated by other
1082 						 * clients. The next time someone reads this, remove
1083 						 * EXTADR.
1084 						 */
1085 						purple_notify_user_info_add_pair_plaintext(user_info, _("Extended Address"), text2);
1086 					} else if(purple_strequal(child2->name, "STREET")) {
1087 						purple_notify_user_info_add_pair_plaintext(user_info, _("Street Address"), text2);
1088 					} else if(purple_strequal(child2->name, "LOCALITY")) {
1089 						purple_notify_user_info_add_pair_plaintext(user_info, _("Locality"), text2);
1090 					} else if(purple_strequal(child2->name, "REGION")) {
1091 						purple_notify_user_info_add_pair_plaintext(user_info, _("Region"), text2);
1092 					} else if(purple_strequal(child2->name, "PCODE")) {
1093 						purple_notify_user_info_add_pair_plaintext(user_info, _("Postal Code"), text2);
1094 					} else if(purple_strequal(child2->name, "CTRY")
1095 								|| purple_strequal(child2->name, "COUNTRY")) {
1096 						purple_notify_user_info_add_pair_plaintext(user_info, _("Country"), text2);
1097 					}
1098 					g_free(text2);
1099 				}
1100 
1101 				if (address_line_added)
1102 					purple_notify_user_info_add_section_break(user_info);
1103 
1104 			} else if(purple_strequal(child->name, "TEL")) {
1105 				char *number;
1106 				if((child2 = xmlnode_get_child(child, "NUMBER"))) {
1107 					/* show what kind of number it is */
1108 					number = xmlnode_get_data(child2);
1109 					if(number) {
1110 						purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1111 						g_free(number);
1112 					}
1113 				} else if((number = xmlnode_get_data(child))) {
1114 					/* lots of clients (including purple) do this, but it's
1115 					 * out of spec */
1116 					purple_notify_user_info_add_pair_plaintext(user_info, _("Telephone"), number);
1117 					g_free(number);
1118 				}
1119 			} else if(purple_strequal(child->name, "EMAIL")) {
1120 				char *userid, *escaped;
1121 				if((child2 = xmlnode_get_child(child, "USERID"))) {
1122 					/* show what kind of email it is */
1123 					userid = xmlnode_get_data(child2);
1124 					if(userid) {
1125 						char *mailto;
1126 						escaped = g_markup_escape_text(userid, -1);
1127 						mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1128 						purple_notify_user_info_add_pair(user_info, _("Email"), mailto);
1129 
1130 						g_free(mailto);
1131 						g_free(escaped);
1132 						g_free(userid);
1133 					}
1134 				} else if((userid = xmlnode_get_data(child))) {
1135 					/* lots of clients (including purple) do this, but it's
1136 					 * out of spec */
1137 					char *mailto;
1138 
1139 					escaped = g_markup_escape_text(userid, -1);
1140 					mailto = g_strdup_printf("<a href=\"mailto:%s\">%s</a>", escaped, escaped);
1141 					purple_notify_user_info_add_pair(user_info, _("Email"), mailto);
1142 
1143 					g_free(mailto);
1144 					g_free(escaped);
1145 					g_free(userid);
1146 				}
1147 			} else if(purple_strequal(child->name, "ORG")) {
1148 				for(child2 = child->child; child2; child2 = child2->next)
1149 				{
1150 					char *text2;
1151 
1152 					if(child2->type != XMLNODE_TYPE_TAG)
1153 						continue;
1154 
1155 					text2 = xmlnode_get_data(child2);
1156 					if(text2 && purple_strequal(child2->name, "ORGNAME")) {
1157 						purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Name"), text2);
1158 					} else if(text2 && purple_strequal(child2->name, "ORGUNIT")) {
1159 						purple_notify_user_info_add_pair_plaintext(user_info, _("Organization Unit"), text2);
1160 					}
1161 					g_free(text2);
1162 				}
1163 			} else if(text && purple_strequal(child->name, "TITLE")) {
1164 				purple_notify_user_info_add_pair_plaintext(user_info, _("Job Title"), text);
1165 			} else if(text && purple_strequal(child->name, "ROLE")) {
1166 				purple_notify_user_info_add_pair_plaintext(user_info, _("Role"), text);
1167 			} else if(text && purple_strequal(child->name, "DESC")) {
1168 				purple_notify_user_info_add_pair_plaintext(user_info, _("Description"), text);
1169 			} else if(purple_strequal(child->name, "PHOTO") ||
1170 					purple_strequal(child->name, "LOGO")) {
1171 				char *bintext = NULL;
1172 				xmlnode *binval;
1173 
1174 				if ((binval = xmlnode_get_child(child, "BINVAL")) &&
1175 						(bintext = xmlnode_get_data(binval))) {
1176 					gsize size;
1177 					guchar *data;
1178 					gboolean photo = purple_strequal(child->name, "PHOTO");
1179 
1180 					data = purple_base64_decode(bintext, &size);
1181 					if (data) {
1182 						char *img_text;
1183 						char *hash;
1184 
1185 						jbi->vcard_imgids = g_slist_prepend(jbi->vcard_imgids, GINT_TO_POINTER(purple_imgstore_add_with_id(g_memdup2(data, size), size, "logo.png")));
1186 						img_text = g_strdup_printf("<img id='%d'>", GPOINTER_TO_INT(jbi->vcard_imgids->data));
1187 
1188 						purple_notify_user_info_add_pair(user_info, (photo ? _("Photo") : _("Logo")), img_text);
1189 
1190 						hash = jabber_calculate_data_hash(data, size, "sha1");
1191 						purple_buddy_icons_set_for_user(account, bare_jid, data, size, hash);
1192 						g_free(hash);
1193 						g_free(img_text);
1194 					}
1195 					g_free(bintext);
1196 				}
1197 			}
1198 			g_free(text);
1199 		}
1200 	}
1201 
1202 	if (serverside_alias) {
1203 		PurpleBuddy *b;
1204 		/* If we found a serverside alias, set it and tell the core */
1205 		serv_got_alias(js->gc, bare_jid, serverside_alias);
1206 		b = purple_find_buddy(account, bare_jid);
1207 		if (b) {
1208 			purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", serverside_alias);
1209 		}
1210 
1211 		g_free(serverside_alias);
1212 	}
1213 
1214 	g_free(bare_jid);
1215 
1216 	jabber_buddy_info_show_if_ready(jbi);
1217 }
1218 
jabber_buddy_info_resource_free(gpointer data)1219 static void jabber_buddy_info_resource_free(gpointer data)
1220 {
1221 	JabberBuddyInfoResource *jbri = data;
1222 	g_free(jbri);
1223 }
1224 
jbir_hash(gconstpointer v)1225 static guint jbir_hash(gconstpointer v)
1226 {
1227 	if (v)
1228 		return g_str_hash(v);
1229 	else
1230 		return 0;
1231 }
1232 
jbir_equal(gconstpointer v1,gconstpointer v2)1233 static gboolean jbir_equal(gconstpointer v1, gconstpointer v2)
1234 {
1235 	const gchar *resource_1 = v1;
1236 	const gchar *resource_2 = v2;
1237 
1238 	return purple_strequal(resource_1, resource_2);
1239 }
1240 
jabber_version_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1241 static void jabber_version_parse(JabberStream *js, const char *from,
1242                                  JabberIqType type, const char *id,
1243                                  xmlnode *packet, gpointer data)
1244 {
1245 	JabberBuddyInfo *jbi = data;
1246 	xmlnode *query;
1247 	char *resource_name;
1248 
1249 	g_return_if_fail(jbi != NULL);
1250 
1251 	jabber_buddy_info_remove_id(jbi, id);
1252 
1253 	if(!from)
1254 		return;
1255 
1256 	resource_name = jabber_get_resource(from);
1257 
1258 	if(resource_name) {
1259 		if (type == JABBER_IQ_RESULT) {
1260 			if((query = xmlnode_get_child(packet, "query"))) {
1261 				JabberBuddyResource *jbr = jabber_buddy_find_resource(jbi->jb, resource_name);
1262 				if(jbr) {
1263 					xmlnode *node;
1264 					if((node = xmlnode_get_child(query, "name"))) {
1265 						jbr->client.name = xmlnode_get_data(node);
1266 					}
1267 					if((node = xmlnode_get_child(query, "version"))) {
1268 						jbr->client.version = xmlnode_get_data(node);
1269 					}
1270 					if((node = xmlnode_get_child(query, "os"))) {
1271 						jbr->client.os = xmlnode_get_data(node);
1272 					}
1273 				}
1274 			}
1275 		}
1276 		g_free(resource_name);
1277 	}
1278 
1279 	jabber_buddy_info_show_if_ready(jbi);
1280 }
1281 
jabber_last_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1282 static void jabber_last_parse(JabberStream *js, const char *from,
1283                               JabberIqType type, const char *id,
1284                               xmlnode *packet, gpointer data)
1285 {
1286 	JabberBuddyInfo *jbi = data;
1287 	xmlnode *query;
1288 	char *resource_name;
1289 	const char *seconds;
1290 
1291 	g_return_if_fail(jbi != NULL);
1292 
1293 	jabber_buddy_info_remove_id(jbi, id);
1294 
1295 	if(!from)
1296 		return;
1297 
1298 	resource_name = jabber_get_resource(from);
1299 
1300 	if(resource_name) {
1301 		if (type == JABBER_IQ_RESULT) {
1302 			if((query = xmlnode_get_child(packet, "query"))) {
1303 				seconds = xmlnode_get_attrib(query, "seconds");
1304 				if(seconds) {
1305 					char *end = NULL;
1306 					long sec = strtol(seconds, &end, 10);
1307 					JabberBuddy *jb = NULL;
1308 					char *resource = NULL;
1309 					char *buddy_name = NULL;
1310 					JabberBuddyResource *jbr = NULL;
1311 
1312 					if(end != seconds) {
1313 						JabberBuddyInfoResource *jbir = g_hash_table_lookup(jbi->resources, resource_name);
1314 						if(jbir) {
1315 							jbir->idle_seconds = sec;
1316 						}
1317 					}
1318 					/* Update the idle time of the buddy resource, if we got it.
1319 					 This will correct the value when a server doesn't mark
1320 					 delayed presence and we got the presence when signing on */
1321 					jb = jabber_buddy_find(js, from, FALSE);
1322 					if (jb) {
1323 						resource = jabber_get_resource(from);
1324 						buddy_name = jabber_get_bare_jid(from);
1325 						/* if the resource already has an idle time set, we
1326 						 must have gotten it originally from a presence. In
1327 						 this case we update it. Otherwise don't update it, to
1328 						 avoid setting an idle and not getting informed about
1329 						 the resource getting unidle */
1330 						if (resource && buddy_name) {
1331 							jbr = jabber_buddy_find_resource(jb, resource);
1332 							if (jbr) {
1333 								if (jbr->idle) {
1334 									if (sec) {
1335 										jbr->idle = time(NULL) - sec;
1336 									} else {
1337 										jbr->idle = 0;
1338 									}
1339 
1340 									if (jbr ==
1341 										jabber_buddy_find_resource(jb, NULL)) {
1342 										purple_prpl_got_user_idle(js->gc->account,
1343 											buddy_name, jbr->idle, jbr->idle);
1344 									}
1345 								}
1346 							}
1347 						}
1348 						g_free(resource);
1349 						g_free(buddy_name);
1350 					}
1351 				}
1352 			}
1353 		}
1354 		g_free(resource_name);
1355 	}
1356 
1357 	jabber_buddy_info_show_if_ready(jbi);
1358 }
1359 
jabber_last_offline_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1360 static void jabber_last_offline_parse(JabberStream *js, const char *from,
1361 									  JabberIqType type, const char *id,
1362 									  xmlnode *packet, gpointer data)
1363 {
1364 	JabberBuddyInfo *jbi = data;
1365 	xmlnode *query;
1366 	const char *seconds;
1367 
1368 	g_return_if_fail(jbi != NULL);
1369 
1370 	jabber_buddy_info_remove_id(jbi, id);
1371 
1372 	if (type == JABBER_IQ_RESULT) {
1373 		if((query = xmlnode_get_child(packet, "query"))) {
1374 			seconds = xmlnode_get_attrib(query, "seconds");
1375 			if(seconds) {
1376 				char *end = NULL;
1377 				long sec = strtol(seconds, &end, 10);
1378 				if(end != seconds) {
1379 					jbi->last_seconds = sec;
1380 				}
1381 			}
1382 			jbi->last_message = xmlnode_get_data(query);
1383 		}
1384 	}
1385 
1386 	jabber_buddy_info_show_if_ready(jbi);
1387 }
1388 
jabber_time_parse(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1389 static void jabber_time_parse(JabberStream *js, const char *from,
1390                               JabberIqType type, const char *id,
1391                               xmlnode *packet, gpointer data)
1392 {
1393 	JabberBuddyInfo *jbi = data;
1394 	JabberBuddyResource *jbr;
1395 	char *resource_name;
1396 
1397 	g_return_if_fail(jbi != NULL);
1398 
1399 	jabber_buddy_info_remove_id(jbi, id);
1400 
1401 	if (!from)
1402 		return;
1403 
1404 	resource_name = jabber_get_resource(from);
1405 	jbr = resource_name ? jabber_buddy_find_resource(jbi->jb, resource_name) : NULL;
1406 	g_free(resource_name);
1407 	if (jbr) {
1408 		if (type == JABBER_IQ_RESULT) {
1409 			xmlnode *time = xmlnode_get_child(packet, "time");
1410 			xmlnode *tzo = time ? xmlnode_get_child(time, "tzo") : NULL;
1411 			char *tzo_data = tzo ? xmlnode_get_data(tzo) : NULL;
1412 			if (tzo_data) {
1413 				char *c = tzo_data;
1414 				int hours, minutes;
1415 				if (tzo_data[0] == 'Z' && tzo_data[1] == '\0') {
1416 					jbr->tz_off = 0;
1417 				} else {
1418 					gboolean offset_positive = (tzo_data[0] == '+');
1419 					/* [+-]HH:MM */
1420 					if (((*c == '+' || *c == '-') && (c = c + 1)) &&
1421 							sscanf(c, "%02d:%02d", &hours, &minutes) == 2) {
1422 						jbr->tz_off = 60*60*hours + 60*minutes;
1423 						if (!offset_positive)
1424 							jbr->tz_off *= -1;
1425 					} else {
1426 						purple_debug_info("jabber", "Ignoring malformed timezone %s",
1427 						                  tzo_data);
1428 					}
1429 				}
1430 
1431 				g_free(tzo_data);
1432 			}
1433 		}
1434 	}
1435 
1436 	jabber_buddy_info_show_if_ready(jbi);
1437 }
1438 
jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream * js)1439 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js)
1440 {
1441 	if (js->pending_buddy_info_requests)
1442 	{
1443 		JabberBuddyInfo *jbi;
1444 		GSList *l = js->pending_buddy_info_requests;
1445 		while (l) {
1446 			jbi = l->data;
1447 
1448 			g_slist_free(jbi->ids);
1449 			jabber_buddy_info_destroy(jbi);
1450 
1451 			l = l->next;
1452 		}
1453 
1454 		g_slist_free(js->pending_buddy_info_requests);
1455 		js->pending_buddy_info_requests = NULL;
1456 	}
1457 }
1458 
jabber_buddy_get_info_timeout(gpointer data)1459 static gboolean jabber_buddy_get_info_timeout(gpointer data)
1460 {
1461 	JabberBuddyInfo *jbi = data;
1462 
1463 	/* remove the pending callbacks */
1464 	while(jbi->ids) {
1465 		char *id = jbi->ids->data;
1466 		jabber_iq_remove_callback_by_id(jbi->js, id);
1467 		jbi->ids = g_slist_remove(jbi->ids, id);
1468 		g_free(id);
1469 	}
1470 
1471 	jbi->js->pending_buddy_info_requests = g_slist_remove(jbi->js->pending_buddy_info_requests, jbi);
1472 	jbi->timeout_handle = 0;
1473 
1474 	jabber_buddy_info_show_if_ready(jbi);
1475 
1476 	return FALSE;
1477 }
1478 
_client_is_blacklisted(JabberBuddyResource * jbr,const char * ns)1479 static gboolean _client_is_blacklisted(JabberBuddyResource *jbr, const char *ns)
1480 {
1481 	/* can't be blacklisted if we don't know what you're running yet */
1482 	if(!jbr->client.name)
1483 		return FALSE;
1484 
1485 	if(purple_strequal(ns, NS_LAST_ACTIVITY)) {
1486 		if(purple_strequal(jbr->client.name, "Trillian")) {
1487 			/* verified by nwalp 2007/05/09 */
1488 			if(purple_strequal(jbr->client.version, "3.1.0.121") ||
1489 					/* verified by nwalp 2007/09/19 */
1490 					purple_strequal(jbr->client.version, "3.1.7.0")) {
1491 				return TRUE;
1492 			}
1493 		}
1494 	}
1495 
1496 	return FALSE;
1497 }
1498 
1499 static void
dispatch_queries_for_resource(JabberStream * js,JabberBuddyInfo * jbi,gboolean is_bare_jid,const char * jid,JabberBuddyResource * jbr)1500 dispatch_queries_for_resource(JabberStream *js, JabberBuddyInfo *jbi,
1501                               gboolean is_bare_jid, const char *jid,
1502                               JabberBuddyResource *jbr)
1503 {
1504 	JabberIq *iq;
1505 	JabberBuddyInfoResource *jbir;
1506 	char *full_jid = NULL;
1507 	const char *to;
1508 
1509 	if (is_bare_jid && jbr->name) {
1510 		full_jid = g_strdup_printf("%s/%s", jid, jbr->name);
1511 		to = full_jid;
1512 	} else
1513 		to = jid;
1514 
1515 	jbir = g_new0(JabberBuddyInfoResource, 1);
1516 	g_hash_table_insert(jbi->resources, g_strdup(jbr->name), jbir);
1517 
1518 	if(!jbr->client.name) {
1519 		iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:version");
1520 		xmlnode_set_attrib(iq->node, "to", to);
1521 		jabber_iq_set_callback(iq, jabber_version_parse, jbi);
1522 		jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1523 		jabber_iq_send(iq);
1524 	}
1525 
1526 	/* this is to fix the feeling of irritation I get when trying
1527 	 * to get info on a friend running Trillian, which doesn't
1528 	 * respond (with an error or otherwise) to jabber:iq:last
1529 	 * requests.  There are a number of Trillian users in my
1530 	 * office. */
1531 	if(!_client_is_blacklisted(jbr, NS_LAST_ACTIVITY)) {
1532 		iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1533 		xmlnode_set_attrib(iq->node, "to", to);
1534 		jabber_iq_set_callback(iq, jabber_last_parse, jbi);
1535 		jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1536 		jabber_iq_send(iq);
1537 	}
1538 
1539 	if (jbr->tz_off == PURPLE_NO_TZ_OFF &&
1540 			(!jbr->caps.info ||
1541 			 	jabber_resource_has_capability(jbr, NS_ENTITY_TIME))) {
1542 		xmlnode *child;
1543 		iq = jabber_iq_new(js, JABBER_IQ_GET);
1544 		xmlnode_set_attrib(iq->node, "to", to);
1545 		child = xmlnode_new_child(iq->node, "time");
1546 		xmlnode_set_namespace(child, NS_ENTITY_TIME);
1547 		jabber_iq_set_callback(iq, jabber_time_parse, jbi);
1548 		jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1549 		jabber_iq_send(iq);
1550 	}
1551 
1552 	g_free(full_jid);
1553 }
1554 
jabber_buddy_get_info_for_jid(JabberStream * js,const char * jid)1555 static void jabber_buddy_get_info_for_jid(JabberStream *js, const char *jid)
1556 {
1557 	JabberIq *iq;
1558 	xmlnode *vcard;
1559 	GList *resources;
1560 	JabberBuddy *jb;
1561 	JabberBuddyInfo *jbi;
1562 	const char *slash;
1563 	gboolean is_bare_jid;
1564 
1565 	jb = jabber_buddy_find(js, jid, TRUE);
1566 
1567 	/* invalid JID */
1568 	if(!jb)
1569 		return;
1570 
1571 	slash = strchr(jid, '/');
1572 	is_bare_jid = (slash == NULL);
1573 
1574 	jbi = g_new0(JabberBuddyInfo, 1);
1575 	jbi->jid = g_strdup(jid);
1576 	jbi->js = js;
1577 	jbi->jb = jb;
1578 	jbi->resources = g_hash_table_new_full(jbir_hash, jbir_equal, g_free, jabber_buddy_info_resource_free);
1579 	jbi->user_info = purple_notify_user_info_new();
1580 
1581 	iq = jabber_iq_new(js, JABBER_IQ_GET);
1582 
1583 	xmlnode_set_attrib(iq->node, "to", jid);
1584 	vcard = xmlnode_new_child(iq->node, "vCard");
1585 	xmlnode_set_namespace(vcard, "vcard-temp");
1586 
1587 	jabber_iq_set_callback(iq, jabber_vcard_parse, jbi);
1588 	jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1589 
1590 	jabber_iq_send(iq);
1591 
1592 	if (is_bare_jid) {
1593 		if (jb->resources) {
1594 			for(resources = jb->resources; resources; resources = resources->next) {
1595 				JabberBuddyResource *jbr = resources->data;
1596 				dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1597 			}
1598 		} else {
1599 			/* user is offline, send a jabber:iq:last to find out last time online */
1600 			iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_LAST_ACTIVITY);
1601 			xmlnode_set_attrib(iq->node, "to", jid);
1602 			jabber_iq_set_callback(iq, jabber_last_offline_parse, jbi);
1603 			jbi->ids = g_slist_prepend(jbi->ids, g_strdup(iq->id));
1604 			jabber_iq_send(iq);
1605 		}
1606 	} else {
1607 		JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, slash + 1);
1608 		if (jbr)
1609 			dispatch_queries_for_resource(js, jbi, is_bare_jid, jid, jbr);
1610 		else
1611 			purple_debug_warning("jabber", "jabber_buddy_get_info_for_jid() "
1612 					"was passed JID %s, but there is no corresponding "
1613 					"JabberBuddyResource!\n", jid);
1614 	}
1615 
1616 	js->pending_buddy_info_requests = g_slist_prepend(js->pending_buddy_info_requests, jbi);
1617 	jbi->timeout_handle = purple_timeout_add_seconds(30, jabber_buddy_get_info_timeout, jbi);
1618 }
1619 
jabber_buddy_get_info(PurpleConnection * gc,const char * who)1620 void jabber_buddy_get_info(PurpleConnection *gc, const char *who)
1621 {
1622 	JabberStream *js = purple_connection_get_protocol_data(gc);
1623 	JabberID *jid = jabber_id_new(who);
1624 
1625 	if (!jid)
1626 		return;
1627 
1628 	if (jid->node && jabber_chat_find(js, jid->node, jid->domain)) {
1629 		/* For a conversation, include the resource (indicates the user). */
1630 		jabber_buddy_get_info_for_jid(js, who);
1631 	} else {
1632 		char *bare_jid = jabber_get_bare_jid(who);
1633 		jabber_buddy_get_info_for_jid(js, bare_jid);
1634 		g_free(bare_jid);
1635 	}
1636 
1637 	jabber_id_free(jid);
1638 }
1639 
jabber_buddy_set_invisibility(JabberStream * js,const char * who,gboolean invisible)1640 static void jabber_buddy_set_invisibility(JabberStream *js, const char *who,
1641 		gboolean invisible)
1642 {
1643 	PurplePresence *gpresence;
1644 	PurpleAccount *account;
1645 	PurpleStatus *status;
1646 	JabberBuddy *jb = jabber_buddy_find(js, who, TRUE);
1647 	xmlnode *presence;
1648 	JabberBuddyState state;
1649 	char *msg;
1650 	int priority;
1651 
1652 	account   = purple_connection_get_account(js->gc);
1653 	gpresence = purple_account_get_presence(account);
1654 	status    = purple_presence_get_active_status(gpresence);
1655 
1656 	purple_status_to_jabber(status, &state, &msg, &priority);
1657 	presence = jabber_presence_create_js(js, state, msg, priority);
1658 
1659 	g_free(msg);
1660 
1661 	xmlnode_set_attrib(presence, "to", who);
1662 	if(invisible) {
1663 		xmlnode_set_attrib(presence, "type", "invisible");
1664 		jb->invisible |= JABBER_INVIS_BUDDY;
1665 	} else {
1666 		jb->invisible &= ~JABBER_INVIS_BUDDY;
1667 	}
1668 
1669 	jabber_send(js, presence);
1670 	xmlnode_free(presence);
1671 }
1672 
jabber_buddy_make_invisible(PurpleBlistNode * node,gpointer data)1673 static void jabber_buddy_make_invisible(PurpleBlistNode *node, gpointer data)
1674 {
1675 	PurpleBuddy *buddy;
1676 	PurpleConnection *gc;
1677 	JabberStream *js;
1678 
1679 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1680 
1681 	buddy = (PurpleBuddy *) node;
1682 	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1683 	js = purple_connection_get_protocol_data(gc);
1684 
1685 	jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), TRUE);
1686 }
1687 
jabber_buddy_make_visible(PurpleBlistNode * node,gpointer data)1688 static void jabber_buddy_make_visible(PurpleBlistNode *node, gpointer data)
1689 {
1690 	PurpleBuddy *buddy;
1691 	PurpleConnection *gc;
1692 	JabberStream *js;
1693 
1694 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1695 
1696 	buddy = (PurpleBuddy *) node;
1697 	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1698 	js = purple_connection_get_protocol_data(gc);
1699 
1700 	jabber_buddy_set_invisibility(js, purple_buddy_get_name(buddy), FALSE);
1701 }
1702 
cancel_presence_notification(gpointer data)1703 static void cancel_presence_notification(gpointer data)
1704 {
1705 	PurpleBuddy *buddy;
1706 	PurpleConnection *gc;
1707 	JabberStream *js;
1708 
1709 	buddy = data;
1710 	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1711 	js = purple_connection_get_protocol_data(gc);
1712 
1713 	jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribed");
1714 }
1715 
1716 static void
jabber_buddy_cancel_presence_notification(PurpleBlistNode * node,gpointer data)1717 jabber_buddy_cancel_presence_notification(PurpleBlistNode *node,
1718                                           gpointer data)
1719 {
1720 	PurpleBuddy *buddy;
1721 	PurpleAccount *account;
1722 	PurpleConnection *gc;
1723 	const gchar *name;
1724 	char *msg;
1725 
1726 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1727 
1728 	buddy = (PurpleBuddy *) node;
1729 	name = purple_buddy_get_name(buddy);
1730 	account = purple_buddy_get_account(buddy);
1731 	gc = purple_account_get_connection(account);
1732 
1733 	msg = g_strdup_printf(_("%s will no longer be able to see your status "
1734 	                        "updates.  Do you want to continue?"), name);
1735 	purple_request_yes_no(gc, NULL, _("Cancel Presence Notification"),
1736 	                      msg, 0 /* Yes */, account, name, NULL, buddy,
1737 	                      cancel_presence_notification, NULL /* Do nothing */);
1738 	g_free(msg);
1739 }
1740 
jabber_buddy_rerequest_auth(PurpleBlistNode * node,gpointer data)1741 static void jabber_buddy_rerequest_auth(PurpleBlistNode *node, gpointer data)
1742 {
1743 	PurpleBuddy *buddy;
1744 	PurpleConnection *gc;
1745 	JabberStream *js;
1746 
1747 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1748 
1749 	buddy = (PurpleBuddy *) node;
1750 	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1751 	js = purple_connection_get_protocol_data(gc);
1752 
1753 	jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "subscribe");
1754 }
1755 
1756 
jabber_buddy_unsubscribe(PurpleBlistNode * node,gpointer data)1757 static void jabber_buddy_unsubscribe(PurpleBlistNode *node, gpointer data)
1758 {
1759 	PurpleBuddy *buddy;
1760 	PurpleConnection *gc;
1761 	JabberStream *js;
1762 
1763 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
1764 
1765 	buddy = (PurpleBuddy *) node;
1766 	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1767 	js = purple_connection_get_protocol_data(gc);
1768 
1769 	jabber_presence_subscription_set(js, purple_buddy_get_name(buddy), "unsubscribe");
1770 }
1771 
jabber_buddy_login(PurpleBlistNode * node,gpointer data)1772 static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
1773 	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1774 		/* simply create a directed presence of the current status */
1775 		PurpleBuddy *buddy = (PurpleBuddy *) node;
1776 		PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1777 		JabberStream *js = purple_connection_get_protocol_data(gc);
1778 		PurpleAccount *account = purple_connection_get_account(gc);
1779 		PurplePresence *gpresence = purple_account_get_presence(account);
1780 		PurpleStatus *status = purple_presence_get_active_status(gpresence);
1781 		xmlnode *presence;
1782 		JabberBuddyState state;
1783 		char *msg;
1784 		int priority;
1785 
1786 		purple_status_to_jabber(status, &state, &msg, &priority);
1787 		presence = jabber_presence_create_js(js, state, msg, priority);
1788 
1789 		g_free(msg);
1790 
1791 		xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1792 
1793 		jabber_send(js, presence);
1794 		xmlnode_free(presence);
1795 	}
1796 }
1797 
jabber_buddy_logout(PurpleBlistNode * node,gpointer data)1798 static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
1799 	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1800 		/* simply create a directed unavailable presence */
1801 		PurpleBuddy *buddy = (PurpleBuddy *) node;
1802 		PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1803 		JabberStream *js = purple_connection_get_protocol_data(gc);
1804 		xmlnode *presence;
1805 
1806 		presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
1807 
1808 		xmlnode_set_attrib(presence, "to", purple_buddy_get_name(buddy));
1809 
1810 		jabber_send(js, presence);
1811 		xmlnode_free(presence);
1812 	}
1813 }
1814 
jabber_buddy_menu(PurpleBuddy * buddy)1815 static GList *jabber_buddy_menu(PurpleBuddy *buddy)
1816 {
1817 	PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
1818 	JabberStream *js = purple_connection_get_protocol_data(gc);
1819 	const char *name = purple_buddy_get_name(buddy);
1820 	JabberBuddy *jb = jabber_buddy_find(js, name, TRUE);
1821 	GList *jbrs;
1822 
1823 	GList *m = NULL;
1824 	PurpleMenuAction *act;
1825 
1826 	if(!jb)
1827 		return m;
1828 
1829 	if (js->protocol_version.major == 0 && js->protocol_version.minor == 9 &&
1830 			jb != js->user_jb) {
1831 		if(jb->invisible & JABBER_INVIS_BUDDY) {
1832 			act = purple_menu_action_new(_("Un-hide From"),
1833 			                           PURPLE_CALLBACK(jabber_buddy_make_visible),
1834 			                           NULL, NULL);
1835 		} else {
1836 			act = purple_menu_action_new(_("Temporarily Hide From"),
1837 			                           PURPLE_CALLBACK(jabber_buddy_make_invisible),
1838 			                           NULL, NULL);
1839 		}
1840 		m = g_list_append(m, act);
1841 	}
1842 
1843 	if(jb->subscription & JABBER_SUB_FROM && jb != js->user_jb) {
1844 		act = purple_menu_action_new(_("Cancel Presence Notification"),
1845 		                           PURPLE_CALLBACK(jabber_buddy_cancel_presence_notification),
1846 		                           NULL, NULL);
1847 		m = g_list_append(m, act);
1848 	}
1849 
1850 	if(!(jb->subscription & JABBER_SUB_TO)) {
1851 		act = purple_menu_action_new(_("(Re-)Request authorization"),
1852 		                           PURPLE_CALLBACK(jabber_buddy_rerequest_auth),
1853 		                           NULL, NULL);
1854 		m = g_list_append(m, act);
1855 
1856 	} else if (jb != js->user_jb) {
1857 
1858 		/* shouldn't this just happen automatically when the buddy is
1859 		   removed? */
1860 		act = purple_menu_action_new(_("Unsubscribe"),
1861 		                           PURPLE_CALLBACK(jabber_buddy_unsubscribe),
1862 		                           NULL, NULL);
1863 		m = g_list_append(m, act);
1864 	}
1865 
1866 	if (js->googletalk) {
1867 		act = purple_menu_action_new(_("Initiate _Chat"),
1868 		                           PURPLE_CALLBACK(google_buddy_node_chat),
1869 		                           NULL, NULL);
1870 		m = g_list_append(m, act);
1871 	}
1872 
1873 	/*
1874 	 * This if-condition implements parts of XEP-0100: Gateway Interaction
1875 	 *
1876 	 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
1877 	 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
1878 	 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
1879 	 * people don't tend to have a server or other service there.
1880 	 *
1881 	 * TODO: Use disco#info...
1882 	 */
1883 	if (strchr(name, '@') == NULL) {
1884 		act = purple_menu_action_new(_("Log In"),
1885 									 PURPLE_CALLBACK(jabber_buddy_login),
1886 									 NULL, NULL);
1887 		m = g_list_append(m, act);
1888 		act = purple_menu_action_new(_("Log Out"),
1889 									 PURPLE_CALLBACK(jabber_buddy_logout),
1890 									 NULL, NULL);
1891 		m = g_list_append(m, act);
1892 	}
1893 
1894 	/* add all ad hoc commands to the action menu */
1895 	for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
1896 		JabberBuddyResource *jbr = jbrs->data;
1897 		GList *commands;
1898 		if (!jbr->commands)
1899 			continue;
1900 		for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
1901 			JabberAdHocCommands *cmd = commands->data;
1902 			act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
1903 			m = g_list_append(m, act);
1904 		}
1905 	}
1906 
1907 	return m;
1908 }
1909 
1910 GList *
jabber_blist_node_menu(PurpleBlistNode * node)1911 jabber_blist_node_menu(PurpleBlistNode *node)
1912 {
1913 	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
1914 		return jabber_buddy_menu((PurpleBuddy *) node);
1915 	} else {
1916 		return NULL;
1917 	}
1918 }
1919 
1920 
user_search_result_add_buddy_cb(PurpleConnection * gc,GList * row,void * user_data)1921 static void user_search_result_add_buddy_cb(PurpleConnection *gc, GList *row, void *user_data)
1922 {
1923 	/* XXX find out the jid */
1924 	purple_blist_request_add_buddy(purple_connection_get_account(gc),
1925 			g_list_nth_data(row, 0), NULL, NULL);
1926 }
1927 
user_search_result_cb(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)1928 static void user_search_result_cb(JabberStream *js, const char *from,
1929                                   JabberIqType type, const char *id,
1930                                   xmlnode *packet, gpointer data)
1931 {
1932 	PurpleNotifySearchResults *results;
1933 	PurpleNotifySearchColumn *column;
1934 	xmlnode *x, *query, *item, *field;
1935 
1936 	/* XXX error checking? */
1937 	if(!(query = xmlnode_get_child(packet, "query")))
1938 		return;
1939 
1940 	results = purple_notify_searchresults_new();
1941 	if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
1942 		xmlnode *reported;
1943 		GSList *column_vars = NULL;
1944 
1945 		purple_debug_info("jabber", "new-skool\n");
1946 
1947 		if((reported = xmlnode_get_child(x, "reported"))) {
1948 			xmlnode *field = xmlnode_get_child(reported, "field");
1949 			while(field) {
1950 				const char *var = xmlnode_get_attrib(field, "var");
1951 				const char *label = xmlnode_get_attrib(field, "label");
1952 				if(var) {
1953 					column = purple_notify_searchresults_column_new(label ? label : var);
1954 					purple_notify_searchresults_column_add(results, column);
1955 					column_vars = g_slist_append(column_vars, (char *)var);
1956 				}
1957 				field = xmlnode_get_next_twin(field);
1958 			}
1959 		}
1960 
1961 		item = xmlnode_get_child(x, "item");
1962 		while(item) {
1963 			GList *row = NULL;
1964 			GSList *l;
1965 			xmlnode *valuenode;
1966 			const char *var;
1967 
1968 			for (l = column_vars; l != NULL; l = l->next) {
1969 				/*
1970 				 * Build a row containing the strings that correspond
1971 				 * to each column of the search results.
1972 				 */
1973 				for (field = xmlnode_get_child(item, "field");
1974 						field != NULL;
1975 						field = xmlnode_get_next_twin(field))
1976 				{
1977 					if ((var = xmlnode_get_attrib(field, "var")) &&
1978 							purple_strequal(var, l->data) &&
1979 							(valuenode = xmlnode_get_child(field, "value")))
1980 					{
1981 						char *value = xmlnode_get_data(valuenode);
1982 						row = g_list_append(row, value);
1983 						break;
1984 					}
1985 				}
1986 				if (field == NULL)
1987 					/* No data for this column */
1988 					row = g_list_append(row, NULL);
1989 			}
1990 			purple_notify_searchresults_row_add(results, row);
1991 			item = xmlnode_get_next_twin(item);
1992 		}
1993 
1994 		g_slist_free(column_vars);
1995 	} else {
1996 		/* old skool */
1997 		purple_debug_info("jabber", "old-skool\n");
1998 
1999 		column = purple_notify_searchresults_column_new(_("JID"));
2000 		purple_notify_searchresults_column_add(results, column);
2001 		column = purple_notify_searchresults_column_new(_("First Name"));
2002 		purple_notify_searchresults_column_add(results, column);
2003 		column = purple_notify_searchresults_column_new(_("Last Name"));
2004 		purple_notify_searchresults_column_add(results, column);
2005 		column = purple_notify_searchresults_column_new(_("Nickname"));
2006 		purple_notify_searchresults_column_add(results, column);
2007 		column = purple_notify_searchresults_column_new(_("Email"));
2008 		purple_notify_searchresults_column_add(results, column);
2009 
2010 		for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) {
2011 			const char *jid;
2012 			xmlnode *node;
2013 			GList *row = NULL;
2014 
2015 			if(!(jid = xmlnode_get_attrib(item, "jid")))
2016 				continue;
2017 
2018 			row = g_list_append(row, g_strdup(jid));
2019 			node = xmlnode_get_child(item, "first");
2020 			row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2021 			node = xmlnode_get_child(item, "last");
2022 			row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2023 			node = xmlnode_get_child(item, "nick");
2024 			row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2025 			node = xmlnode_get_child(item, "email");
2026 			row = g_list_append(row, node ? xmlnode_get_data(node) : NULL);
2027 			purple_debug_info("jabber", "row=%p\n", row);
2028 			purple_notify_searchresults_row_add(results, row);
2029 		}
2030 	}
2031 
2032 	purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD,
2033 			user_search_result_add_buddy_cb);
2034 
2035 	purple_notify_searchresults(js->gc, NULL, NULL, _("The following are the results of your search"), results, NULL, NULL);
2036 }
2037 
user_search_x_data_cb(JabberStream * js,xmlnode * result,gpointer data)2038 static void user_search_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
2039 {
2040 	xmlnode *query;
2041 	JabberIq *iq;
2042 	char *dir_server = data;
2043 	const char *type;
2044 
2045 	/* if they've cancelled the search, we're
2046 	 * just going to get an error if we send
2047 	 * a cancel, so skip it */
2048 	type = xmlnode_get_attrib(result, "type");
2049 	if(purple_strequal(type, "cancel")) {
2050 		g_free(dir_server);
2051 		return;
2052 	}
2053 
2054 	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2055 	query = xmlnode_get_child(iq->node, "query");
2056 
2057 	xmlnode_insert_child(query, result);
2058 
2059 	jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2060 	xmlnode_set_attrib(iq->node, "to", dir_server);
2061 	jabber_iq_send(iq);
2062 	g_free(dir_server);
2063 }
2064 
2065 struct user_search_info {
2066 	JabberStream *js;
2067 	char *directory_server;
2068 };
2069 
user_search_cancel_cb(struct user_search_info * usi,PurpleRequestFields * fields)2070 static void user_search_cancel_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2071 {
2072 	g_free(usi->directory_server);
2073 	g_free(usi);
2074 }
2075 
user_search_cb(struct user_search_info * usi,PurpleRequestFields * fields)2076 static void user_search_cb(struct user_search_info *usi, PurpleRequestFields *fields)
2077 {
2078 	JabberStream *js = usi->js;
2079 	JabberIq *iq;
2080 	xmlnode *query;
2081 	GList *groups, *flds;
2082 
2083 	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:search");
2084 	query = xmlnode_get_child(iq->node, "query");
2085 
2086 	for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
2087 		for(flds = purple_request_field_group_get_fields(groups->data);
2088 				flds; flds = flds->next) {
2089 			PurpleRequestField *field = flds->data;
2090 			const char *id = purple_request_field_get_id(field);
2091 			const char *value = purple_request_field_string_get_value(field);
2092 
2093 			if(value && (purple_strequal(id, "first") || purple_strequal(id, "last") || purple_strequal(id, "nick") || purple_strequal(id, "email"))) {
2094 				xmlnode *y = xmlnode_new_child(query, id);
2095 				xmlnode_insert_data(y, value, -1);
2096 			}
2097 		}
2098 	}
2099 
2100 	jabber_iq_set_callback(iq, user_search_result_cb, NULL);
2101 	xmlnode_set_attrib(iq->node, "to", usi->directory_server);
2102 	jabber_iq_send(iq);
2103 
2104 	g_free(usi->directory_server);
2105 	g_free(usi);
2106 }
2107 
2108 #if 0
2109 /* This is for gettext only -- it will see this even though there's an #if 0. */
2110 
2111 /*
2112  * An incomplete list of server generated original language search
2113  * comments for Jabber User Directories
2114  *
2115  * See discussion thread "Search comment for Jabber is not translatable"
2116  * in purple-i18n@lists.sourceforge.net (March 2006)
2117  */
2118 static const char * jabber_user_dir_comments [] = {
2119 	/* current comment from Jabber User Directory users.jabber.org */
2120 	N_("Find a contact by entering the search criteria in the given fields. "
2121 	   "Note: Each field supports wild card searches (%)"),
2122 	NULL
2123 };
2124 #endif
2125 
user_search_fields_result_cb(JabberStream * js,const char * from,JabberIqType type,const char * id,xmlnode * packet,gpointer data)2126 static void user_search_fields_result_cb(JabberStream *js, const char *from,
2127                                          JabberIqType type, const char *id,
2128                                          xmlnode *packet, gpointer data)
2129 {
2130 	xmlnode *query, *x;
2131 
2132 	if (!from)
2133 		return;
2134 
2135 	if (type == JABBER_IQ_ERROR) {
2136 		char *msg = jabber_parse_error(js, packet, NULL);
2137 
2138 		if(!msg)
2139 			msg = g_strdup(_("Unknown error"));
2140 
2141 		purple_notify_error(js->gc, _("Directory Query Failed"),
2142 				  _("Could not query the directory server."), msg);
2143 		g_free(msg);
2144 
2145 		return;
2146 	}
2147 
2148 
2149 	if(!(query = xmlnode_get_child(packet, "query")))
2150 		return;
2151 
2152 	if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
2153 		jabber_x_data_request(js, x, user_search_x_data_cb, g_strdup(from));
2154 		return;
2155 	} else {
2156 		struct user_search_info *usi;
2157 		xmlnode *instnode;
2158 		char *instructions = NULL;
2159 		PurpleRequestFields *fields;
2160 		PurpleRequestFieldGroup *group;
2161 		PurpleRequestField *field;
2162 
2163 		/* old skool */
2164 		fields = purple_request_fields_new();
2165 		group = purple_request_field_group_new(NULL);
2166 		purple_request_fields_add_group(fields, group);
2167 
2168 		if((instnode = xmlnode_get_child(query, "instructions")))
2169 		{
2170 			char *tmp = xmlnode_get_data(instnode);
2171 
2172 			if(tmp)
2173 			{
2174 				/* Try to translate the message (see static message
2175 				   list in jabber_user_dir_comments[]) */
2176 				instructions = g_strdup_printf(_("Server Instructions: %s"), _(tmp));
2177 				g_free(tmp);
2178 			}
2179 		}
2180 
2181 		if(!instructions)
2182 		{
2183 			instructions = g_strdup(_("Fill in one or more fields to search "
2184 						  "for any matching XMPP users."));
2185 		}
2186 
2187 		if(xmlnode_get_child(query, "first")) {
2188 			field = purple_request_field_string_new("first", _("First Name"),
2189 					NULL, FALSE);
2190 			purple_request_field_group_add_field(group, field);
2191 		}
2192 		if(xmlnode_get_child(query, "last")) {
2193 			field = purple_request_field_string_new("last", _("Last Name"),
2194 					NULL, FALSE);
2195 			purple_request_field_group_add_field(group, field);
2196 		}
2197 		if(xmlnode_get_child(query, "nick")) {
2198 			field = purple_request_field_string_new("nick", _("Nickname"),
2199 					NULL, FALSE);
2200 			purple_request_field_group_add_field(group, field);
2201 		}
2202 		if(xmlnode_get_child(query, "email")) {
2203 			field = purple_request_field_string_new("email", _("Email Address"),
2204 					NULL, FALSE);
2205 			purple_request_field_group_add_field(group, field);
2206 		}
2207 
2208 		usi = g_new0(struct user_search_info, 1);
2209 		usi->js = js;
2210 		usi->directory_server = g_strdup(from);
2211 
2212 		purple_request_fields(js->gc, _("Search for XMPP users"),
2213 				_("Search for XMPP users"), instructions, fields,
2214 				_("Search"), G_CALLBACK(user_search_cb),
2215 				_("Cancel"), G_CALLBACK(user_search_cancel_cb),
2216 				purple_connection_get_account(js->gc), NULL, NULL,
2217 				usi);
2218 
2219 		g_free(instructions);
2220 	}
2221 }
2222 
jabber_user_search(JabberStream * js,const char * directory)2223 void jabber_user_search(JabberStream *js, const char *directory)
2224 {
2225 	JabberIq *iq;
2226 
2227 	/* XXX: should probably better validate the directory we're given */
2228 	if(!directory || !*directory) {
2229 		purple_notify_error(js->gc, _("Invalid Directory"), _("Invalid Directory"), NULL);
2230 		return;
2231 	}
2232 
2233 	/* If the value provided isn't the disco#info default, persist it.  Otherwise,
2234 	   make sure we aren't persisting an old value */
2235 	if(js->user_directories && js->user_directories->data &&
2236 	   purple_strequal(directory, js->user_directories->data)) {
2237 		purple_account_set_string(js->gc->account, "user_directory", "");
2238 	}
2239 	else {
2240 		purple_account_set_string(js->gc->account, "user_directory", directory);
2241 	}
2242 
2243 	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:search");
2244 	xmlnode_set_attrib(iq->node, "to", directory);
2245 
2246 	jabber_iq_set_callback(iq, user_search_fields_result_cb, NULL);
2247 
2248 	jabber_iq_send(iq);
2249 }
2250 
jabber_user_search_begin(PurplePluginAction * action)2251 void jabber_user_search_begin(PurplePluginAction *action)
2252 {
2253 	PurpleConnection *gc = (PurpleConnection *) action->context;
2254 	JabberStream *js = purple_connection_get_protocol_data(gc);
2255 	const char *def_val = purple_account_get_string(js->gc->account, "user_directory", "");
2256 	if(!*def_val && js->user_directories)
2257 		def_val = js->user_directories->data;
2258 
2259 	purple_request_input(gc, _("Enter a User Directory"), _("Enter a User Directory"),
2260 			_("Select a user directory to search"),
2261 			def_val,
2262 			FALSE, FALSE, NULL,
2263 			_("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
2264 			_("Cancel"), NULL,
2265 			NULL, NULL, NULL,
2266 			js);
2267 }
2268 
2269 gboolean
jabber_resource_know_capabilities(const JabberBuddyResource * jbr)2270 jabber_resource_know_capabilities(const JabberBuddyResource *jbr)
2271 {
2272 	return jbr->caps.info != NULL;
2273 }
2274 
2275 gboolean
jabber_resource_has_capability(const JabberBuddyResource * jbr,const gchar * cap)2276 jabber_resource_has_capability(const JabberBuddyResource *jbr, const gchar *cap)
2277 {
2278 	const GList *node = NULL;
2279 	const JabberCapsNodeExts *exts;
2280 
2281 	if (!jbr->caps.info) {
2282 		purple_debug_info("jabber",
2283 			"Unable to find caps: nothing known about buddy\n");
2284 		return FALSE;
2285 	}
2286 
2287 	node = g_list_find_custom(jbr->caps.info->features, cap, (GCompareFunc)strcmp);
2288 	if (!node && jbr->caps.exts && jbr->caps.info->exts) {
2289 		const GList *ext;
2290 		exts = jbr->caps.info->exts;
2291 		/* Walk through all the enabled caps, checking each list for the cap.
2292 		 * Don't check it twice, though. */
2293 		for (ext = jbr->caps.exts; ext && !node; ext = ext->next) {
2294 			GList *features = g_hash_table_lookup(exts->exts, ext->data);
2295 			if (features)
2296 				node = g_list_find_custom(features, cap, (GCompareFunc)strcmp);
2297 		}
2298 	}
2299 
2300 	return (node != NULL);
2301 }
2302 
2303 gboolean
jabber_buddy_has_capability(const JabberBuddy * jb,const gchar * cap)2304 jabber_buddy_has_capability(const JabberBuddy *jb, const gchar *cap)
2305 {
2306 	JabberBuddyResource *jbr = jabber_buddy_find_resource((JabberBuddy*)jb, NULL);
2307 
2308 	if (!jbr) {
2309 		purple_debug_info("jabber",
2310 			"Unable to find caps: buddy might be offline\n");
2311 		return FALSE;
2312 	}
2313 
2314 	return jabber_resource_has_capability(jbr, cap);
2315 }
2316 
2317 const gchar *
jabber_resource_get_identity_category_type(const JabberBuddyResource * jbr,const gchar * category)2318 jabber_resource_get_identity_category_type(const JabberBuddyResource *jbr,
2319 	const gchar *category)
2320 {
2321 	const GList *iter = NULL;
2322 
2323 	if (jbr->caps.info) {
2324 		for (iter = jbr->caps.info->identities ; iter ; iter = g_list_next(iter)) {
2325 			const JabberIdentity *identity =
2326 				(JabberIdentity *) iter->data;
2327 
2328 			if (purple_strequal(identity->category, category)) {
2329 				return identity->type;
2330 			}
2331 		}
2332 	}
2333 
2334 	return NULL;
2335 }
2336