[gaim-migrate @ 3063]
[pidgin-git.git] / src / protocols / jabber / jabber.c
blob7f403ad4557fa0c4b88ed443715936b11f9f8774
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * gaim
5 * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
6 * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
29 #include <netdb.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
34 #include <string.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <time.h>
38 #include <sys/socket.h>
39 #include <sys/utsname.h>
40 #include <sys/stat.h>
41 #include "multi.h"
42 #include "prpl.h"
43 #include "gaim.h"
44 #ifdef MAX
45 #undef MAX
46 #endif
47 #ifdef MIN
48 #undef MIN
49 #endif
50 #include "jabber.h"
51 #include "proxy.h"
53 #include "pixmaps/available.xpm"
54 #include "pixmaps/available-away.xpm"
55 #include "pixmaps/available-chat.xpm"
56 #include "pixmaps/available-xa.xpm"
57 #include "pixmaps/available-dnd.xpm"
59 /* The priv member of gjconn's is a gaim_connection for now. */
60 #define GJ_GC(x) ((struct gaim_connection *)(x)->priv)
62 #define IQID_AUTH "__AUTH__"
64 #define IQ_NONE -1
65 #define IQ_AUTH 0
66 #define IQ_ROSTER 1
68 #define UC_AWAY (0x02 | UC_UNAVAILABLE)
69 #define UC_CHAT 0x04
70 #define UC_XA (0x08 | UC_UNAVAILABLE)
71 #define UC_DND (0x10 | UC_UNAVAILABLE)
73 #define DEFAULT_SERVER "jabber.org"
74 #define DEFAULT_GROUPCHAT "conference.jabber.org"
75 #define DEFAULT_PORT 5222
77 #define USEROPT_PORT 0
79 typedef struct gjconn_struct {
80 /* Core structure */
81 pool p; /* Memory allocation pool */
82 int state; /* Connection state flag */
83 int fd; /* Connection file descriptor */
84 jid user; /* User info */
85 char *pass; /* User passwd */
87 /* Stream stuff */
88 int id; /* id counter for jab_getid() function */
89 char idbuf[9]; /* temporary storage for jab_getid() */
90 char *sid; /* stream id from server, for digest auth */
91 XML_Parser parser; /* Parser instance */
92 xmlnode current; /* Current node in parsing instance.. */
94 /* Event callback ptrs */
95 void (*on_state)(struct gjconn_struct *gjc, int state);
96 void (*on_packet)(struct gjconn_struct *gjc, jpacket p);
98 GHashTable *queries; /* query tracker */
100 void *priv;
102 } *gjconn, gjconn_struct;
104 typedef void (*gjconn_state_h)(gjconn gjc, int state);
105 typedef void (*gjconn_packet_h)(gjconn gjc, jpacket p);
107 static gjconn gjab_new(char *user, char *pass, void *priv);
108 static void gjab_delete(gjconn gjc);
109 static void gjab_state_handler(gjconn gjc, gjconn_state_h h);
110 static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h);
111 static void gjab_start(gjconn gjc);
112 static void gjab_stop(gjconn gjc);
114 static int gjab_getfd(gjconn gjc);
115 static jid gjab_getjid(gjconn gjc);
116 static char *gjab_getsid(gjconn gjc);
118 static char *gjab_getid(gjconn gjc);
119 static void gjab_send(gjconn gjc, xmlnode x);
120 static void gjab_send_raw(gjconn gjc, const char *str);
121 static void gjab_recv(gjconn gjc);
122 static void gjab_auth(gjconn gjc);
125 * It is *this* to which we point the gaim_connection proto_data
127 struct jabber_data {
128 gjconn gjc;
129 gboolean did_import;
130 GSList *chats;
131 GHashTable *hash;
132 time_t idle;
133 gboolean die;
137 * Jabber "chat group" info. Pointers to these go in jabber_data
138 * pending and existing chats lists.
140 struct jabber_chat {
141 jid Jid;
142 struct gaim_connection *gc;
143 struct conversation *b;
144 int id;
145 int state;
149 * Jabber chat states...
151 * Note: due to a bug in one version of the Jabber server, subscriptions
152 * to chat groups aren't (always?) properly removed at the server. The
153 * result is clients receive Jabber "presence" notifications for JIDs
154 * they no longer care about. The problem with such vestigial notifies is
155 * that we really have no way of telling if it's vestigial or if it's a
156 * valid "buddy" presence notification. So we keep jabber_chat structs
157 * around after leaving a chat group and simply mark them "closed." That
158 * way we can test for such errant presence notifications. I.e.: if we
159 * get a presence notfication from a JID that matches a chat group JID,
160 * we disregard it.
162 #define JCS_PENDING 1 /* pending */
163 #define JCS_ACTIVE 2 /* active */
164 #define JCS_CLOSED 3 /* closed */
167 static char *jabber_name()
169 return "Jabber";
172 #define STATE_EVT(arg) if(gjc->on_state) { (gjc->on_state)(gjc, (arg) ); }
174 static void jabber_handlevcard(gjconn, xmlnode, char *);
176 static char *create_valid_jid(const char *given, char *server, char *resource)
178 char *valid;
180 if (!strchr(given, '@'))
181 valid = g_strdup_printf("%s@%s/%s", given, server, resource);
182 else if (!strchr(strchr(given, '@'), '/'))
183 valid = g_strdup_printf("%s/%s", given, resource);
184 else
185 valid = g_strdup(given);
187 return valid;
190 static gjconn gjab_new(char *user, char *pass, void *priv)
192 pool p;
193 gjconn gjc;
195 if (!user)
196 return (NULL);
198 p = pool_new();
199 if (!p)
200 return (NULL);
201 gjc = pmalloc_x(p, sizeof(gjconn_struct), 0);
202 if (!gjc) {
203 pool_free(p); /* no need for this anymore! */
204 return (NULL);
206 gjc->p = p;
208 gjc->user = jid_new(p, user);
209 gjc->pass = pstrdup(p, pass);
211 gjc->state = JCONN_STATE_OFF;
212 gjc->id = 1;
213 gjc->fd = -1;
215 gjc->priv = priv;
217 return gjc;
220 static void gjab_delete(gjconn gjc)
222 if (!gjc)
223 return;
225 gjab_stop(gjc);
226 pool_free(gjc->p);
229 static void gjab_state_handler(gjconn gjc, gjconn_state_h h)
231 if (!gjc)
232 return;
234 gjc->on_state = h;
237 static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h)
239 if (!gjc)
240 return;
242 gjc->on_packet = h;
245 static void gjab_stop(gjconn gjc)
247 if (!gjc || gjc->state == JCONN_STATE_OFF)
248 return;
250 gjab_send_raw(gjc, "</stream:stream>");
251 gjc->state = JCONN_STATE_OFF;
252 close(gjc->fd);
253 gjc->fd = -1;
254 XML_ParserFree(gjc->parser);
255 gjc->parser = NULL;
259 static int gjab_getfd(gjconn gjc)
261 if (gjc)
262 return gjc->fd;
263 else
264 return -1;
267 static jid gjab_getjid(gjconn gjc)
269 if (gjc)
270 return (gjc->user);
271 else
272 return NULL;
275 static char *gjab_getsid(gjconn gjc)
277 if (gjc)
278 return (gjc->sid);
279 else
280 return NULL;
284 static char *gjab_getid(gjconn gjc)
286 snprintf(gjc->idbuf, 8, "%d", gjc->id++);
287 return &gjc->idbuf[0];
290 static void gjab_send(gjconn gjc, xmlnode x)
292 if (gjc && gjc->state != JCONN_STATE_OFF) {
293 char *buf = xmlnode2str(x);
294 if (buf)
295 write(gjc->fd, buf, strlen(buf));
296 debug_printf("gjab_send: %s\n", buf);
300 static void gjab_send_raw(gjconn gjc, const char *str)
302 if (gjc && gjc->state != JCONN_STATE_OFF) {
304 * JFIXME: No error detection?!?!
306 if(write(gjc->fd, str, strlen(str)) < 0) {
307 fprintf(stderr, "DBG: Problem sending. Error: %d\n", errno);
308 fflush(stderr);
310 debug_printf("gjab_send_raw: %s\n", str);
314 static void gjab_reqroster(gjconn gjc)
316 xmlnode x;
318 x = jutil_iqnew(JPACKET__GET, NS_ROSTER);
319 xmlnode_put_attrib(x, "id", gjab_getid(gjc));
321 gjab_send(gjc, x);
322 xmlnode_free(x);
325 static void gjab_reqauth(gjconn gjc)
327 xmlnode x, y, z;
328 char *user;
330 if (!gjc)
331 return;
333 x = jutil_iqnew(JPACKET__GET, NS_AUTH);
334 xmlnode_put_attrib(x, "id", IQID_AUTH);
335 y = xmlnode_get_tag(x, "query");
337 user = gjc->user->user;
339 if (user) {
340 z = xmlnode_insert_tag(y, "username");
341 xmlnode_insert_cdata(z, user, -1);
344 gjab_send(gjc, x);
345 xmlnode_free(x);
348 static void gjab_auth(gjconn gjc)
350 xmlnode x, y, z;
351 char *hash, *user;
353 if (!gjc)
354 return;
356 x = jutil_iqnew(JPACKET__SET, NS_AUTH);
357 xmlnode_put_attrib(x, "id", IQID_AUTH);
358 y = xmlnode_get_tag(x, "query");
360 user = gjc->user->user;
362 if (user) {
363 z = xmlnode_insert_tag(y, "username");
364 xmlnode_insert_cdata(z, user, -1);
367 z = xmlnode_insert_tag(y, "resource");
368 xmlnode_insert_cdata(z, gjc->user->resource, -1);
370 if (gjc->sid) {
371 debug_printf("digest authentication (sid %s)\n", gjc->sid);
372 z = xmlnode_insert_tag(y, "digest");
373 hash = pmalloc(x->p, strlen(gjc->sid) + strlen(gjc->pass) + 1);
374 strcpy(hash, gjc->sid);
375 strcat(hash, gjc->pass);
376 hash = shahash(hash);
377 xmlnode_insert_cdata(z, hash, 40);
378 } else {
379 z = xmlnode_insert_tag(y, "password");
380 xmlnode_insert_cdata(z, gjc->pass, -1);
383 gjab_send(gjc, x);
384 xmlnode_free(x);
386 return;
389 static void gjab_recv(gjconn gjc)
391 static char buf[4096];
392 int len;
394 if (!gjc || gjc->state == JCONN_STATE_OFF)
395 return;
397 if ((len = read(gjc->fd, buf, sizeof(buf) - 1))) {
398 struct jabber_data *jd = GJ_GC(gjc)->proto_data;
399 buf[len] = '\0';
400 debug_printf("input (len %d): %s\n", len, buf);
401 XML_Parse(gjc->parser, buf, len, 0);
402 if (jd->die)
403 signoff(GJ_GC(gjc));
404 } else if (len <= 0) {
405 STATE_EVT(JCONN_STATE_OFF)
409 static void startElement(void *userdata, const char *name, const char **attribs)
411 xmlnode x;
412 gjconn gjc = (gjconn) userdata;
414 if (gjc->current) {
415 /* Append the node to the current one */
416 x = xmlnode_insert_tag(gjc->current, name);
417 xmlnode_put_expat_attribs(x, attribs);
419 gjc->current = x;
420 } else {
421 x = xmlnode_new_tag(name);
422 xmlnode_put_expat_attribs(x, attribs);
423 if (strcmp(name, "stream:stream") == 0) {
424 /* special case: name == stream:stream */
425 /* id attrib of stream is stored for digest auth */
426 gjc->sid = g_strdup(xmlnode_get_attrib(x, "id"));
427 /* STATE_EVT(JCONN_STATE_AUTH) */
428 xmlnode_free(x);
429 } else {
430 gjc->current = x;
435 static void endElement(void *userdata, const char *name)
437 gjconn gjc = (gjconn) userdata;
438 xmlnode x;
439 jpacket p;
441 if (gjc->current == NULL) {
442 /* we got </stream:stream> */
443 STATE_EVT(JCONN_STATE_OFF)
444 return;
447 x = xmlnode_get_parent(gjc->current);
449 if (!x) {
450 /* it is time to fire the event */
451 p = jpacket_new(gjc->current);
453 if (gjc->on_packet)
454 (gjc->on_packet) (gjc, p);
455 else
456 xmlnode_free(gjc->current);
459 gjc->current = x;
462 static void jabber_callback(gpointer data, gint source, GaimInputCondition condition)
464 struct gaim_connection *gc = (struct gaim_connection *)data;
465 struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
467 gjab_recv(jd->gjc);
470 static void charData(void *userdata, const char *s, int slen)
472 gjconn gjc = (gjconn) userdata;
474 if (gjc->current)
475 xmlnode_insert_cdata(gjc->current, s, slen);
478 static void gjab_connected(gpointer data, gint source, GaimInputCondition cond)
480 xmlnode x;
481 char *t, *t2;
482 struct gaim_connection *gc = data;
483 struct jabber_data *jd;
484 gjconn gjc;
486 if (!g_slist_find(connections, gc)) {
487 close(source);
488 return;
491 jd = gc->proto_data;
492 gjc = jd->gjc;
494 if (gjc->fd != source)
495 gjc->fd = source;
497 if (source == -1) {
498 STATE_EVT(JCONN_STATE_OFF)
499 return;
502 gjc->state = JCONN_STATE_CONNECTED;
503 STATE_EVT(JCONN_STATE_CONNECTED)
505 /* start stream */
506 x = jutil_header(NS_CLIENT, gjc->user->server);
507 t = xmlnode2str(x);
508 /* this is ugly, we can create the string here instead of jutil_header */
509 /* what do you think about it? -madcat */
510 t2 = strstr(t, "/>");
511 *t2++ = '>';
512 *t2 = '\0';
513 gjab_send_raw(gjc, "<?xml version='1.0'?>");
514 gjab_send_raw(gjc, t);
515 xmlnode_free(x);
517 gjc->state = JCONN_STATE_ON;
518 STATE_EVT(JCONN_STATE_ON);
520 gc = GJ_GC(gjc);
521 gc->inpa = gaim_input_add(gjc->fd, GAIM_INPUT_READ, jabber_callback, gc);
524 static void gjab_start(gjconn gjc)
526 struct aim_user *user;
527 int port;
529 if (!gjc || gjc->state != JCONN_STATE_OFF)
530 return;
532 user = GJ_GC(gjc)->user;
533 port = user->proto_opt[USEROPT_PORT][0] ? atoi(user->proto_opt[USEROPT_PORT]) : DEFAULT_PORT;
535 gjc->parser = XML_ParserCreate(NULL);
536 XML_SetUserData(gjc->parser, (void *)gjc);
537 XML_SetElementHandler(gjc->parser, startElement, endElement);
538 XML_SetCharacterDataHandler(gjc->parser, charData);
540 gjc->fd = proxy_connect(gjc->user->server, port, gjab_connected, GJ_GC(gjc));
541 if (!user->gc || (gjc->fd < 0)) {
542 STATE_EVT(JCONN_STATE_OFF)
543 return;
548 * Find chat by chat group name
550 static struct conversation *find_chat(struct gaim_connection *gc, char *name)
552 GSList *bcs = gc->buddy_chats;
553 struct conversation *b = NULL;
554 char *chat = g_strdup(normalize(name));
556 while (bcs) {
557 b = bcs->data;
558 if (!strcasecmp(normalize(b->name), chat))
559 break;
560 b = NULL;
561 bcs = bcs->next;
564 g_free(chat);
565 return b;
569 * Find chat by "chat id"
571 * Returns: 0 on success and jabber_chat pointer set
572 * or -EINVAL on error and jabber_chat pointer is
573 * undefined.
575 * TBD: Slogging through the buddy_chats list seems
576 * redundant since the chat i.d. is mirrored in the
577 * jabber_chat struct list. But that's the way it
578 * was, so that's the way I'm leaving it--for now.
580 static int jabber_find_chat_by_convo_id(struct gaim_connection *gc, int id, struct jabber_chat **jc)
582 GSList *bcs = gc->buddy_chats;
583 struct conversation *b = NULL;
584 struct jabber_data *jd = gc->proto_data;
586 *jc = NULL;
588 while(bcs != NULL) {
589 b = bcs->data;
590 if (id == b->id)
591 break;
592 bcs = bcs->next;
595 if (bcs != NULL) {
596 bcs = jd->chats;
597 while (bcs != NULL) {
598 *jc = bcs->data;
599 if ((*jc)->state == JCS_ACTIVE && (*jc)->b == b)
600 break;
601 bcs = bcs->next;
605 return(bcs == NULL? -EINVAL : 0);
609 * Find any chat
611 static struct jabber_chat *find_any_chat(struct gaim_connection *gc, jid chat)
613 GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
614 struct jabber_chat *jc = NULL;
616 while (jcs) {
617 jc = jcs->data;
618 if (!jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER))
619 break;
620 jc = NULL;
621 jcs = jcs->next;
624 return jc;
629 * Find existing/active Jabber chat
631 static struct jabber_chat *find_existing_chat(struct gaim_connection *gc, jid chat)
633 GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
634 struct jabber_chat *jc = NULL;
636 while (jcs) {
637 jc = jcs->data;
638 if (jc->state == JCS_ACTIVE && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER))
639 break;
640 jc = NULL;
641 jcs = jcs->next;
644 return jc;
648 * Find pending chat
650 static struct jabber_chat *find_pending_chat(struct gaim_connection *gc, jid chat)
652 GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
653 struct jabber_chat *jc = NULL;
655 while (jcs) {
656 jc = jcs->data;
657 if (jc->state == JCS_PENDING && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER))
658 break;
659 jc = NULL;
660 jcs = jcs->next;
663 return jc;
666 static gboolean find_chat_buddy(struct conversation *b, char *name)
668 GList *m = b->in_room;
670 while (m) {
671 if (!strcmp(m->data, name))
672 return TRUE;
673 m = m->next;
676 return FALSE;
680 * keep track of away msg same as yahoo plugin
682 static void jabber_track_away(gjconn gjc, jpacket p, char *name)
684 struct jabber_data *jd = GJ_GC(gjc)->proto_data;
685 gpointer val = g_hash_table_lookup(jd->hash, name);
686 char *show;
687 char *vshow = NULL;
688 char *status = NULL;
689 char *msg = NULL;
691 if((show = xmlnode_get_tag_data(p->x, "show")) != NULL) {
692 if (!strcasecmp(show, "away")) {
693 vshow = _("Away");
694 } else if (!strcasecmp(show, "chat")) {
695 vshow = _("Online");
696 } else if (!strcasecmp(show, "xa")) {
697 vshow = _("Extended Away");
698 } else if (!strcasecmp(show, "dnd")) {
699 vshow = _("Do Not Disturb");
703 status = xmlnode_get_tag_data(p->x, "status");
705 if(vshow != NULL || status != NULL ) {
706 /* kinda hokey, but it works :-) */
707 msg = g_strdup_printf("%s%s%s",
708 (vshow == NULL? "" : vshow),
709 (vshow == NULL || status == NULL? "" : ": "),
710 (status == NULL? "" : status));
713 if (val) {
714 g_free(val);
715 g_hash_table_insert(jd->hash, name, msg);
716 } else {
717 g_hash_table_insert(jd->hash, g_strdup(name), msg);
721 static void jabber_handlemessage(gjconn gjc, jpacket p)
723 xmlnode y, xmlns, subj;
725 char *from = NULL, *msg = NULL, *type = NULL, *topic = NULL;
726 char m[BUF_LONG * 2];
728 type = xmlnode_get_attrib(p->x, "type");
730 if (!type || !strcasecmp(type, "normal") || !strcasecmp(type, "chat")) {
732 /* XXX namespaces could be handled better. (mid) */
733 if ((xmlns = xmlnode_get_tag(p->x, "x")))
734 type = xmlnode_get_attrib(xmlns, "xmlns");
736 from = jid_full(p->from);
738 if ((y = xmlnode_get_tag(p->x, "html"))) {
739 msg = xmlnode_get_data(y);
740 } else
742 if ((y = xmlnode_get_tag(p->x, "body"))) {
743 msg = xmlnode_get_data(y);
746 msg = utf8_to_str(msg);
748 if (!from)
749 return;
751 if (type && !strcasecmp(type, "jabber:x:conference")) {
752 char *room;
753 GList *m = NULL;
754 char **data;
756 room = xmlnode_get_attrib(xmlns, "jid");
757 data = g_strsplit(room, "@", 2);
758 m = g_list_append(m, g_strdup(data[0]));
759 m = g_list_append(m, g_strdup(data[1]));
760 m = g_list_append(m, g_strdup(gjc->user->user));
761 g_strfreev(data);
763 serv_got_chat_invite(GJ_GC(gjc), room, from, msg, m);
764 } else if (msg) { /* whisper */
765 struct jabber_chat *jc;
766 g_snprintf(m, sizeof(m), "%s", msg);
767 if (((jc = find_existing_chat(GJ_GC(gjc), p->from)) != NULL) && jc->b)
768 serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 1, m, time(NULL));
769 else {
770 int flags = 0;
771 if (xmlnode_get_tag(p->x, "gaim"))
772 flags = IM_FLAG_GAIMUSER;
773 if (find_conversation(jid_full(p->from)))
774 serv_got_im(GJ_GC(gjc), jid_full(p->from), m, flags, time(NULL), -1);
775 else {
776 if(p->from->user) {
777 from = g_strdup_printf("%s@%s", p->from->user, p->from->server);
778 } else {
779 /* server message? */
780 from = g_strdup(p->from->server);
782 serv_got_im(GJ_GC(gjc), from, m, flags, time(NULL), -1);
783 g_free(from);
788 if (msg)
789 g_free(msg);
791 } else if (!strcasecmp(type, "error")) {
792 if ((y = xmlnode_get_tag(p->x, "error"))) {
793 type = xmlnode_get_attrib(y, "code");
794 msg = xmlnode_get_data(y);
797 if (msg) {
798 from = g_strdup_printf("Error %s", type ? type : "");
799 do_error_dialog(msg, from);
800 g_free(from);
802 } else if (!strcasecmp(type, "groupchat")) {
803 struct jabber_chat *jc;
804 static int i = 0;
807 if ((y = xmlnode_get_tag(p->x, "html"))) {
808 msg = xmlnode_get_data(y);
809 } else
811 if ((y = xmlnode_get_tag(p->x, "body"))) {
812 msg = xmlnode_get_data(y);
815 msg = utf8_to_str(msg);
817 if ((subj = xmlnode_get_tag(p->x, "subject"))) {
818 topic = xmlnode_get_data(subj);
820 topic = utf8_to_str(topic);
822 jc = find_existing_chat(GJ_GC(gjc), p->from);
823 if (!jc) {
824 /* we're not in this chat. are we supposed to be? */
825 if ((jc = find_pending_chat(GJ_GC(gjc), p->from)) != NULL) {
826 /* yes, we're supposed to be. so now we are. */
827 jc->b = serv_got_joined_chat(GJ_GC(gjc), i++, p->from->user);
828 jc->id = jc->b->id;
829 jc->state = JCS_ACTIVE;
830 } else {
831 /* no, we're not supposed to be. */
832 g_free(msg);
833 return;
836 if (p->from->resource) {
837 if (!y) {
838 if (!find_chat_buddy(jc->b, p->from->resource)) {
839 add_chat_buddy(jc->b, p->from->resource);
840 } else if ((y = xmlnode_get_tag(p->x, "status"))) {
841 char buf[8192];
843 g_snprintf(buf, sizeof(buf), "%s@%s/%s",
844 p->from->user, p->from->server, p->from->resource);
845 jabber_track_away(gjc, p, buf);
848 } else if (jc->b && msg) {
849 char buf[8192];
851 if (topic) {
852 char tbuf[8192];
853 g_snprintf(tbuf, sizeof(tbuf), "%s", topic);
854 chat_set_topic(jc->b, p->from->resource, tbuf);
858 g_snprintf(buf, sizeof(buf), "%s", msg);
859 serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 0, buf, time(NULL));
861 } else { /* message from the server */
862 if(jc->b && topic) {
863 char tbuf[8192];
864 g_snprintf(tbuf, sizeof(tbuf), "%s", topic);
865 chat_set_topic(jc->b, "", tbuf);
869 g_free(msg);
870 g_free(topic);
872 } else {
873 debug_printf("unhandled message %s\n", type);
877 static void jabber_handlepresence(gjconn gjc, jpacket p)
879 char *to, *from, *type;
880 struct buddy *b = NULL;
881 jid who;
882 char *buddy;
883 xmlnode y;
884 char *show;
885 int state = 0;
886 GSList *resources;
887 char *res;
888 struct conversation *cnv = NULL;
889 struct jabber_chat *jc = NULL;
891 to = xmlnode_get_attrib(p->x, "to");
892 from = xmlnode_get_attrib(p->x, "from");
893 type = xmlnode_get_attrib(p->x, "type");
895 if ((y = xmlnode_get_tag(p->x, "show"))) {
896 show = xmlnode_get_data(y);
897 if (!show) {
898 state = 0;
899 } else if (!strcasecmp(show, "away")) {
900 state = UC_AWAY;
901 } else if (!strcasecmp(show, "chat")) {
902 state = UC_CHAT;
903 } else if (!strcasecmp(show, "xa")) {
904 state = UC_XA;
905 } else if (!strcasecmp(show, "dnd")) {
906 state = UC_DND;
908 } else {
909 state = 0;
912 who = jid_new(gjc->p, from);
913 if (who->user == NULL) {
914 /* FIXME: transport */
915 return;
918 buddy = g_strdup_printf("%s@%s", who->user, who->server);
920 /* um. we're going to check if it's a chat. if it isn't, and there are pending
921 * chats, create the chat. if there aren't pending chats and we don't have the
922 * buddy on our list, simply bail out. */
923 if ((cnv = find_chat(GJ_GC(gjc), who->user)) == NULL) {
924 static int i = 0x70;
925 if ((jc = find_pending_chat(GJ_GC(gjc), who)) != NULL) {
926 jc->b = cnv = serv_got_joined_chat(GJ_GC(gjc), i++, who->user);
927 jc->id = jc->b->id;
928 jc->state = JCS_ACTIVE;
929 } else if ((b = find_buddy(GJ_GC(gjc), buddy)) == NULL) {
930 g_free(buddy);
931 return;
935 if (!cnv) {
936 resources = b->proto_data;
937 res = who->resource;
938 if (res)
939 while (resources) {
940 if (!strcmp(res, resources->data))
941 break;
942 resources = resources->next;
945 if (type && (strcasecmp(type, "unavailable") == 0)) {
946 if (resources) {
947 g_free(resources->data);
948 b->proto_data = g_slist_remove(b->proto_data, resources->data);
950 if (!b->proto_data) {
951 serv_got_update(GJ_GC(gjc), buddy, 0, 0, 0, 0, 0, 0);
953 } else {
954 /* keep track of away msg same as yahoo plugin */
955 jabber_track_away(gjc, p, normalize(b->name));
957 if (!resources) {
958 b->proto_data = g_slist_append(b->proto_data, g_strdup(res));
961 serv_got_update(GJ_GC(gjc), buddy, 1, 0, 0, 0, state, 0);
964 } else {
965 if (who->resource) {
966 if (type && !strcasecmp(type, "unavailable")) {
967 struct jabber_data *jd;
968 if (!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) {
969 g_free(buddy);
970 return;
972 jd = jc->gc->proto_data;
973 /* if it's not ourselves...*/
974 if (strcmp(who->resource, jc->Jid->resource) && jc->b) {
975 remove_chat_buddy(jc->b, who->resource, NULL);
976 g_free(buddy);
977 return;
980 jc->state = JCS_CLOSED;
981 serv_got_chat_left(GJ_GC(gjc), jc->id);
983 * TBD: put back some day?
984 jd->chats = g_slist_remove(jd->chats, jc);
985 g_free(jc);
987 } else {
988 if ((!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) || !jc->b) {
989 g_free(buddy);
990 return;
992 if (!find_chat_buddy(jc->b, who->resource)) {
993 add_chat_buddy(jc->b, who->resource);
994 } else {
995 char buf[8192];
996 g_snprintf(buf, sizeof(buf), "%s@%s/%s",
997 who->user, who->server, who->resource);
998 jabber_track_away(gjc, p, buf);
1004 g_free(buddy);
1006 return;
1009 static void jabber_handles10n(gjconn gjc, jpacket p)
1011 xmlnode g;
1012 char *Jid = xmlnode_get_attrib(p->x, "from");
1013 char *ask = xmlnode_get_attrib(p->x, "type");
1015 g = xmlnode_new_tag("presence");
1016 xmlnode_put_attrib(g, "to", Jid);
1017 if (!strcmp(ask, "subscribe"))
1018 xmlnode_put_attrib(g, "type", "subscribed");
1019 else if (!strcmp(ask, "unsubscribe"))
1020 xmlnode_put_attrib(g, "type", "unsubscribed");
1021 else {
1022 xmlnode_free(g);
1023 return;
1026 gjab_send(gjc, g);
1027 xmlnode_free(g);
1031 * Pending subscription to a buddy?
1033 #define BUD_SUB_TO_PEND(sub, ask) ((!strcasecmp((sub), "none") || !strcasecmp((sub), "from")) && \
1034 (ask) != NULL && !strcasecmp((ask), "subscribe"))
1037 * Subscribed to a buddy?
1039 #define BUD_SUBD_TO(sub, ask) ((!strcasecmp((sub), "to") || !strcasecmp((sub), "both")) && \
1040 ((ask) == NULL || !strcasecmp((ask), "subscribe")))
1043 * Pending unsubscription to a buddy?
1045 #define BUD_USUB_TO_PEND(sub, ask) ((!strcasecmp((sub), "to") || !strcasecmp((sub), "both")) && \
1046 (ask) != NULL && !strcasecmp((ask), "unsubscribe"))
1049 * Unsubscribed to a buddy?
1051 #define BUD_USUBD_TO(sub, ask) ((!strcasecmp((sub), "none") || !strcasecmp((sub), "from")) && \
1052 ((ask) == NULL || !strcasecmp((ask), "unsubscribe")))
1055 * If a buddy is added or removed from the roster on another resource
1056 * jabber_handlebuddy is called
1058 * Called with roster item node.
1060 static void jabber_handlebuddy(gjconn gjc, xmlnode x)
1062 xmlnode g;
1063 char *Jid, *name, *sub, *ask;
1064 jid who;
1065 struct buddy *b = NULL;
1066 char *buddyname, *groupname;
1068 Jid = xmlnode_get_attrib(x, "jid");
1069 name = xmlnode_get_attrib(x, "name");
1070 sub = xmlnode_get_attrib(x, "subscription");
1071 ask = xmlnode_get_attrib(x, "ask");
1072 who = jid_new(gjc->p, Jid);
1074 /* JFIXME: jabber_handleroster() had a "FIXME: transport" at this
1075 * equivilent point. So...
1077 * We haven't allocated any memory or done anything interesting to
1078 * this point, so we'll violate Good Coding Structure here by
1079 * simply bailing out.
1081 if (!who || !who->user) {
1082 return;
1085 buddyname = g_strdup_printf("%s@%s", who->user, who->server);
1087 if((g = xmlnode_get_tag(x, "group")) == NULL || (groupname = xmlnode_get_data(g)) == NULL) {
1088 groupname = _("Buddies");
1092 if (BUD_SUB_TO_PEND(sub, ask) || BUD_SUBD_TO(sub, ask)) {
1093 if ((b = find_buddy(GJ_GC(gjc), buddyname)) == NULL) {
1094 debug_printf("adding buddy [4]: %s\n", buddyname);
1095 b = add_buddy(GJ_GC(gjc), groupname, buddyname,
1096 name ? name : buddyname);
1097 do_export(GJ_GC(gjc));
1100 * TBD: this is what we *would* do if we could. But the
1101 * remove_buddy() code doesn't update the UI. And if the
1102 * user selects and tries to remove a buddy already removed
1103 * via here: *ka-boom*!
1105 } else if (BUD_USUB_TO_PEND(sub, ask) || BUD_USUBD_TO(sub, ask) || !strcasecmp(sub, "remove")) {
1106 if ((b = find_buddy(GJ_GC(gjc), buddyname)) != NULL) {
1107 struct group *group;
1109 group = find_group_by_buddy(GJ_GC(gjc), buddyname);
1110 debug_printf("removing buddy [1]: %s, from group: %s\n",
1111 buddyname, group->name);
1112 remove_buddy(GJ_GC(gjc), group, b);
1113 do_export(GJ_GC(gjc));
1117 g_free(buddyname);
1121 static void jabber_handleroster(gjconn gjc, xmlnode querynode)
1123 xmlnode x;
1125 x = xmlnode_get_firstchild(querynode);
1126 while (x) {
1127 jabber_handlebuddy(gjc, x);
1128 x = xmlnode_get_nextsibling(x);
1131 x = jutil_presnew(0, NULL, "Online");
1132 gjab_send(gjc, x);
1133 xmlnode_free(x);
1136 static void jabber_handleauthresp(gjconn gjc, jpacket p)
1138 if (jpacket_subtype(p) == JPACKET__RESULT) {
1139 if (xmlnode_has_children(p->x)) {
1140 xmlnode query = xmlnode_get_tag(p->x, "query");
1141 set_login_progress(GJ_GC(gjc), 4, _("Authenticating"));
1142 if (!xmlnode_get_tag(query, "digest")) {
1143 g_free(gjc->sid);
1144 gjc->sid = NULL;
1146 gjab_auth(gjc);
1147 } else {
1148 debug_printf("auth success\n");
1150 account_online(GJ_GC(gjc));
1151 serv_finish_login(GJ_GC(gjc));
1153 if (bud_list_cache_exists(GJ_GC(gjc)))
1154 do_import(GJ_GC(gjc), NULL);
1156 ((struct jabber_data *)GJ_GC(gjc)->proto_data)->did_import = TRUE;
1158 gjab_reqroster(gjc);
1160 } else {
1161 xmlnode xerr;
1162 char *errmsg = NULL;
1163 int errcode = 0;
1164 struct jabber_data *jd = GJ_GC(gjc)->proto_data;
1166 debug_printf("auth failed\n");
1167 xerr = xmlnode_get_tag(p->x, "error");
1168 if (xerr) {
1169 char msg[BUF_LONG];
1170 errmsg = xmlnode_get_data(xerr);
1171 if (xmlnode_get_attrib(xerr, "code")) {
1172 errcode = atoi(xmlnode_get_attrib(xerr, "code"));
1173 g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg);
1174 } else
1175 g_snprintf(msg, sizeof(msg), "%s", errmsg);
1176 hide_login_progress(GJ_GC(gjc), msg);
1177 } else {
1178 hide_login_progress(GJ_GC(gjc), _("Unknown login error"));
1181 jd->die = TRUE;
1185 static void jabber_handleversion(gjconn gjc, xmlnode iqnode) {
1186 xmlnode querynode, x;
1187 char *id, *from;
1188 char os[1024];
1189 struct utsname osinfo;
1191 uname(&osinfo);
1192 g_snprintf(os, sizeof os, "%s %s %s", osinfo.sysname, osinfo.release, osinfo.machine);
1194 id = xmlnode_get_attrib(iqnode, "id");
1195 from = xmlnode_get_attrib(iqnode, "from");
1197 x = jutil_iqnew(JPACKET__RESULT, NS_VERSION);
1199 xmlnode_put_attrib(x, "to", from);
1200 xmlnode_put_attrib(x, "id", id);
1201 querynode = xmlnode_get_tag(x, "query");
1202 xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "name"), PACKAGE, -1);
1203 xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "version"), VERSION, -1);
1204 xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "os"), os, -1);
1206 gjab_send(gjc, x);
1208 xmlnode_free(x);
1211 static void jabber_handletime(gjconn gjc, xmlnode iqnode) {
1212 xmlnode querynode, x;
1213 char *id, *from;
1214 time_t now_t;
1215 struct tm *now;
1216 char buf[1024];
1218 time(&now_t);
1219 now = localtime(&now_t);
1221 id = xmlnode_get_attrib(iqnode, "id");
1222 from = xmlnode_get_attrib(iqnode, "from");
1224 x = jutil_iqnew(JPACKET__RESULT, NS_TIME);
1226 xmlnode_put_attrib(x, "to", from);
1227 xmlnode_put_attrib(x, "id", id);
1228 querynode = xmlnode_get_tag(x, "query");
1230 strftime(buf, 1024, "%Y%m%dT%T", now);
1231 xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "utc"), buf, -1);
1232 strftime(buf, 1024, "%Z", now);
1233 xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "tz"), buf, -1);
1234 strftime(buf, 1024, "%d %b %Y %T", now);
1235 xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "display"), buf, -1);
1237 gjab_send(gjc, x);
1239 xmlnode_free(x);
1242 static void jabber_handlelast(gjconn gjc, xmlnode iqnode) {
1243 xmlnode x, querytag;
1244 char *id, *from;
1245 struct jabber_data *jd = GJ_GC(gjc)->proto_data;
1246 char idle_time[32];
1248 id = xmlnode_get_attrib(iqnode, "id");
1249 from = xmlnode_get_attrib(iqnode, "from");
1251 x = jutil_iqnew(JPACKET__RESULT, "jabber:iq:last");
1253 xmlnode_put_attrib(x, "to", from);
1254 xmlnode_put_attrib(x, "id", id);
1255 querytag = xmlnode_get_tag(x, "query");
1256 g_snprintf(idle_time, sizeof idle_time, "%ld", jd->idle ? time(NULL) - jd->idle : 0);
1257 xmlnode_put_attrib(querytag, "seconds", idle_time);
1259 gjab_send(gjc, x);
1260 xmlnode_free(x);
1264 * delete == TRUE: delete found entry
1266 * returns pointer to (local) copy of value if found, NULL otherwise
1268 * Note: non-reentrant! Local static storage re-used on subsequent calls.
1269 * If you're going to need to keep the returned value, make a copy!
1271 static gchar *jabber_track_queries(GHashTable *queries, gchar *key, gboolean delete)
1273 gpointer my_key, my_val;
1274 static gchar *ret_val = NULL;
1276 if(ret_val != NULL) {
1277 g_free(ret_val);
1278 ret_val = NULL;
1281 /* self-protection */
1282 if(queries != NULL && key != NULL) {
1283 if(g_hash_table_lookup_extended(queries, key, &my_key, &my_val)) {
1284 ret_val = g_strdup((gchar *) my_val);
1285 if(delete) {
1286 g_hash_table_remove(queries, key);
1287 g_free(my_key);
1288 g_free(my_val);
1293 return(ret_val);
1296 static void jabber_handlepacket(gjconn gjc, jpacket p)
1298 char *id;
1299 switch (p->type) {
1300 case JPACKET_MESSAGE:
1301 jabber_handlemessage(gjc, p);
1302 break;
1303 case JPACKET_PRESENCE:
1304 jabber_handlepresence(gjc, p);
1305 break;
1306 case JPACKET_IQ:
1307 debug_printf("jpacket_subtype: %d\n", jpacket_subtype(p));
1309 id = xmlnode_get_attrib(p->x, "id");
1310 if (id != NULL && !strcmp(id, IQID_AUTH)) {
1311 jabber_handleauthresp(gjc, p);
1312 break;
1315 if (jpacket_subtype(p) == JPACKET__SET) {
1316 xmlnode querynode;
1317 querynode = xmlnode_get_tag(p->x, "query");
1318 if (NSCHECK(querynode, "jabber:iq:roster")) {
1319 jabber_handlebuddy(gjc, xmlnode_get_firstchild(querynode));
1321 } else if (jpacket_subtype(p) == JPACKET__GET) {
1322 xmlnode querynode;
1323 querynode = xmlnode_get_tag(p->x, "query");
1324 if (NSCHECK(querynode, NS_VERSION)) {
1325 jabber_handleversion(gjc, p->x);
1326 } else if (NSCHECK(querynode, NS_TIME)) {
1327 jabber_handletime(gjc, p->x);
1328 } else if (NSCHECK(querynode, "jabber:iq:last")) {
1329 jabber_handlelast(gjc, p->x);
1331 } else if (jpacket_subtype(p) == JPACKET__RESULT) {
1332 xmlnode querynode, vcard;
1333 char *xmlns, *from;
1336 * TBD: ISTM maybe this part could use a serious re-work?
1338 from = xmlnode_get_attrib(p->x, "from");
1339 querynode = xmlnode_get_tag(p->x, "query");
1340 vcard = xmlnode_get_tag(p->x, "vCard");
1341 if (!vcard)
1342 vcard = xmlnode_get_tag(p->x, "VCARD");
1344 if (NSCHECK(querynode, NS_ROSTER)) {
1345 jabber_handleroster(gjc, querynode);
1346 } else if (NSCHECK(querynode, NS_VCARD)) {
1347 jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */
1348 jabber_handlevcard(gjc, querynode, from);
1349 } else if (vcard) {
1350 jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */
1351 jabber_handlevcard(gjc, vcard, from);
1352 } else if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL) {
1353 debug_printf("jabber:iq:query: %s\n", xmlns);
1354 } else {
1355 char *val;
1357 debug_printf("jabber:iq: %s\n", xmlnode2str(p->x));
1359 /* handle "null" query results */
1360 if((val = jabber_track_queries(gjc->queries, id, TRUE)) != NULL) {
1361 if(strcmp((char *) val, "vCard") == 0) {
1363 * No actual vCard, but there's other stuff. This
1364 * way the user always gets some kind of response.
1366 jabber_handlevcard(gjc, NULL, from);
1371 } else if (jpacket_subtype(p) == JPACKET__ERROR) {
1372 xmlnode xerr;
1373 char *from, *errmsg = NULL;
1374 int errcode = 0;
1376 from = xmlnode_get_attrib(p->x, "from");
1377 xerr = xmlnode_get_tag(p->x, "error");
1378 if (xerr) {
1379 errmsg = xmlnode_get_data(xerr);
1380 if (xmlnode_get_attrib(xerr, "code"))
1381 errcode = atoi(xmlnode_get_attrib(xerr, "code"));
1384 from = g_strdup_printf("Error %d (%s)", errcode, from);
1385 do_error_dialog(errmsg, from);
1386 g_free(from);
1390 break;
1392 case JPACKET_S10N:
1393 jabber_handles10n(gjc, p);
1394 break;
1395 default:
1396 debug_printf("jabber: packet type %d (%s)\n", p->type, xmlnode2str(p->x));
1399 xmlnode_free(p->x);
1401 return;
1404 static void jabber_handlestate(gjconn gjc, int state)
1406 switch (state) {
1407 case JCONN_STATE_OFF:
1408 hide_login_progress(GJ_GC(gjc), _("Unable to connect"));
1409 signoff(GJ_GC(gjc));
1410 break;
1411 case JCONN_STATE_CONNECTED:
1412 set_login_progress(GJ_GC(gjc), 2, _("Connected"));
1413 break;
1414 case JCONN_STATE_ON:
1415 set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method"));
1416 gjab_reqauth(gjc);
1417 break;
1418 default:
1419 debug_printf("state change: %d\n", state);
1421 return;
1424 static void jabber_login(struct aim_user *user)
1426 struct gaim_connection *gc = new_gaim_conn(user);
1427 struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1);
1428 char *loginname = create_valid_jid(user->username, DEFAULT_SERVER, "GAIM");
1430 jd->hash = g_hash_table_new(g_str_hash, g_str_equal);
1431 jd->chats = NULL; /* we have no chats yet */
1433 set_login_progress(gc, 1, _("Connecting"));
1435 if (!(jd->gjc = gjab_new(loginname, user->password, gc))) {
1436 g_free(loginname);
1437 debug_printf("jabber: unable to connect (jab_new failed)\n");
1438 hide_login_progress(gc, _("Unable to connect"));
1439 signoff(gc);
1440 return;
1443 g_free(loginname);
1444 gjab_state_handler(jd->gjc, jabber_handlestate);
1445 gjab_packet_handler(jd->gjc, jabber_handlepacket);
1446 jd->gjc->queries = g_hash_table_new(g_str_hash, g_str_equal);
1447 gjab_start(jd->gjc);
1450 static gboolean jabber_destroy_hash(gpointer key, gpointer val, gpointer data) {
1451 g_free(key);
1452 g_free(val);
1453 return TRUE;
1456 static gboolean jabber_free(gpointer data)
1458 struct jabber_data *jd = data;
1460 gjab_delete(jd->gjc);
1461 g_free(jd->gjc->sid);
1462 jd->gjc = NULL;
1463 g_free(jd);
1465 return FALSE;
1468 static void jabber_close(struct gaim_connection *gc)
1470 struct jabber_data *jd = gc->proto_data;
1472 if(jd) {
1473 GSList *jcs = jd->chats;
1475 /* Free-up the jabber_chat struct allocs and the list */
1476 while (jcs) {
1477 g_free(jcs->data);
1478 jcs = jcs->next;
1480 g_slist_free(jd->chats);
1482 /* Free-up the away status memories and the list */
1483 if(jd->hash != NULL) {
1484 g_hash_table_foreach_remove(jd->hash, jabber_destroy_hash, NULL);
1485 g_hash_table_destroy(jd->hash);
1486 jd->hash = NULL;
1489 /* Free-up the pending queries memories and the list */
1490 if(jd->gjc->queries != NULL) {
1491 g_hash_table_foreach_remove(jd->gjc->queries, jabber_destroy_hash, NULL);
1492 g_hash_table_destroy(jd->gjc->queries);
1493 jd->gjc->queries = NULL;
1496 if (gc->inpa)
1497 gaim_input_remove(gc->inpa);
1499 if(jd) {
1500 g_timeout_add(50, jabber_free, jd);
1501 xmlnode_free(jd->gjc->current);
1503 gc->proto_data = NULL;
1506 static int jabber_send_im(struct gaim_connection *gc, char *who, char *message, int len, int flags)
1508 xmlnode x, y;
1509 char *realwho;
1510 gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1512 if (!who || !message)
1513 return 0;
1515 x = xmlnode_new_tag("message");
1516 /* Bare username and "username" not the server itself? */
1517 if (!strchr(who, '@') && strcmp(who, gjc->user->server) != 0)
1518 realwho = g_strdup_printf("%s@%s", who, gjc->user->server);
1519 else
1520 realwho = g_strdup(who);
1521 xmlnode_put_attrib(x, "to", realwho);
1522 g_free(realwho);
1524 xmlnode_insert_tag(x, "gaim");
1525 xmlnode_put_attrib(x, "type", "chat");
1527 if (message && strlen(message)) {
1528 char *utf8 = str_to_utf8(message);
1529 y = xmlnode_insert_tag(x, "body");
1530 xmlnode_insert_cdata(y, utf8, -1);
1531 g_free(utf8);
1534 gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1535 xmlnode_free(x);
1536 return 1;
1539 static void jabber_add_buddy(struct gaim_connection *gc, char *name)
1541 xmlnode x, y;
1542 char *realwho;
1543 gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1544 struct buddy *buddy = NULL;
1545 struct group *buddy_group = NULL;
1547 if (!((struct jabber_data *)gc->proto_data)->did_import)
1548 return;
1550 if (!name)
1551 return;
1553 if (!strcmp(gc->username, name))
1554 return;
1556 if (!strchr(name, '@'))
1557 realwho = g_strdup_printf("%s@%s", name, gjc->user->server);
1558 else {
1559 jid who = jid_new(gjc->p, name);
1560 if (who->user == NULL) {
1561 /* FIXME: transport */
1562 return;
1564 realwho = g_strdup_printf("%s@%s", who->user, who->server);
1568 x = jutil_iqnew(JPACKET__SET, NS_ROSTER);
1569 y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item");
1570 xmlnode_put_attrib(y, "jid", realwho);
1572 /* If we can find the buddy, there's an alias for him and
1573 * it's not 0-length, add the "name" attribute.
1575 if((buddy = find_buddy(gc, realwho)) != NULL &&
1576 buddy->show != NULL && (buddy->show)[0] != '\0') {
1578 xmlnode_put_attrib(y, "name", buddy->show);
1582 * Find out what group the buddy's in and send that along
1583 * with the roster item.
1585 if((buddy_group = find_group_by_buddy(gc, realwho)) != NULL) {
1586 xmlnode z;
1587 z = xmlnode_insert_tag(y, "group");
1588 xmlnode_insert_cdata(z, buddy_group->name, -1);
1591 gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1592 xmlnode_free(x);
1594 x = xmlnode_new_tag("presence");
1595 xmlnode_put_attrib(x, "to", realwho);
1596 xmlnode_put_attrib(x, "type", "subscribe");
1597 gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1598 xmlnode_free(x);
1600 g_free(realwho);
1603 static void jabber_remove_buddy(struct gaim_connection *gc, char *name, char *group)
1605 xmlnode x;
1606 char *realwho;
1607 gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1609 if (!name)
1610 return;
1612 if (!strchr(name, '@'))
1613 realwho = g_strdup_printf("%s@%s", name, gjc->user->server);
1614 else
1615 realwho = g_strdup(name);
1617 x = xmlnode_new_tag("presence");
1618 xmlnode_put_attrib(x, "to", realwho);
1619 xmlnode_put_attrib(x, "type", "unsubscribe");
1620 gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1621 g_free(realwho);
1622 xmlnode_free(x);
1625 static char **jabber_list_icon(int uc)
1627 switch (uc) {
1628 case UC_AWAY:
1629 return available_away_xpm;
1630 case UC_CHAT:
1631 return available_chat_xpm;
1632 case UC_XA:
1633 return available_xa_xpm;
1634 case UC_DND:
1635 return available_dnd_xpm;
1636 default:
1637 return available_xpm;
1641 static GList *jabber_chat_info(struct gaim_connection *gc)
1643 gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1645 static char *confserv = NULL; /* this pointer must be persistent */
1646 gchar *server;
1648 GList *m = NULL;
1649 struct proto_chat_entry *pce;
1651 /* This is a scientific wild-ass guess...
1653 * If there are more than two "components" to the current server name,
1654 * lop-off the left-most component and replace with "conference."
1656 if(confserv != NULL) {
1657 g_free(confserv); /* dispose of the old value */
1660 if((server = g_strdup(gjc->user->server)) == NULL) {
1661 confserv = g_strdup(DEFAULT_GROUPCHAT);
1662 } else {
1663 gchar **splits, **index;
1664 gchar *tmp;
1665 int cnt = 0;
1668 index = splits = g_strsplit(server, ".", -1); /* split the connected server */
1670 while(*(index++)) /* index to the end--counting the parts */
1671 ++cnt;
1674 * If we've more than two parts, point to the second part. Else point
1675 * to the start.
1677 if(cnt > 2) {
1678 index -= cnt;
1679 } else {
1680 index = splits;
1683 /* Put it together */
1684 confserv = g_strjoin(".", "conference", (tmp = g_strjoinv(".", index)), NULL);
1686 g_free(server); /* we don't need this stuff no more */
1687 g_free(tmp);
1688 g_strfreev(splits);
1691 pce = g_new0(struct proto_chat_entry, 1);
1692 pce->label = _("Room:");
1693 m = g_list_append(m, pce);
1695 pce = g_new0(struct proto_chat_entry, 1);
1696 pce->label = _("Server:");
1697 pce->def = confserv;
1698 m = g_list_append(m, pce);
1700 pce = g_new0(struct proto_chat_entry, 1);
1701 pce->label = _("Handle:");
1702 pce->def = gjc->user->user;
1703 m = g_list_append(m, pce);
1705 return m;
1708 static void jabber_join_chat(struct gaim_connection *gc, GList *data)
1710 xmlnode x;
1711 char *realwho;
1712 gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1713 GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
1714 struct jabber_chat *jc;
1715 jid Jid;
1717 if (!data || !data->next || !data->next->next)
1718 return;
1720 realwho = create_valid_jid(data->data, data->next->data,
1721 data->next->next->data);
1722 debug_printf("%s\n", realwho);
1724 Jid = jid_new(gjc->p, realwho);
1726 if((jc = find_any_chat(gc, Jid)) != NULL) {
1727 free(Jid); /* don't need it, after all */
1728 switch(jc->state) {
1729 case JCS_PENDING:
1730 debug_printf("attempt to re-join already pending Jabber chat! (ignoring)\n");
1731 g_free(realwho); /* yuck! */
1732 return;
1733 case JCS_ACTIVE:
1734 debug_printf("attempt to re-join already active Jabber chat! (ignoring)\n");
1735 g_free(realwho); /* yuck! */
1736 return;
1737 case JCS_CLOSED:
1738 debug_printf("rejoining previously closed Jabber chat\n");
1739 break;
1740 default:
1741 debug_printf("found Jabber chat in unknown state! (ignoring)\n");
1742 g_free(realwho); /* yuck! */
1743 return;
1745 } else {
1746 debug_printf("joining completely new Jabber chat\n");
1747 jc = g_new0(struct jabber_chat, 1);
1748 jc->Jid = Jid;
1749 jc->gc = gc;
1750 ((struct jabber_data *)gc->proto_data)->chats = g_slist_append(jcs, jc);
1753 jc->state = JCS_PENDING;
1755 x = jutil_presnew(0, realwho, NULL);
1756 gjab_send(gjc, x);
1757 xmlnode_free(x);
1758 g_free(realwho);
1761 static void jabber_chat_invite(struct gaim_connection *gc, int id, char *message, char *name)
1763 xmlnode x, y;
1764 struct jabber_data *jd = gc->proto_data;
1765 gjconn gjc = jd->gjc;
1766 struct jabber_chat *jc = NULL;
1767 char *realwho, *subject;
1769 if (!name)
1770 return;
1772 /* find which chat we're inviting to */
1773 if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0)
1774 return;
1776 x = xmlnode_new_tag("message");
1777 if (!strchr(name, '@'))
1778 realwho = g_strdup_printf("%s@%s", name, gjc->user->server);
1779 else
1780 realwho = g_strdup(name);
1781 xmlnode_put_attrib(x, "to", realwho);
1782 g_free(realwho);
1784 y = xmlnode_insert_tag(x, "x");
1785 xmlnode_put_attrib(y, "xmlns", "jabber:x:conference");
1786 subject = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server);
1787 xmlnode_put_attrib(y, "jid", subject);
1788 g_free(subject);
1790 if (message && strlen(message)) {
1791 char *utf8 = str_to_utf8(message);
1792 y = xmlnode_insert_tag(x, "body");
1793 xmlnode_insert_cdata(y, utf8, -1);
1794 g_free(utf8);
1797 gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1798 xmlnode_free(x);
1801 static void jabber_chat_leave(struct gaim_connection *gc, int id)
1803 struct jabber_data *jd = gc->proto_data;
1804 gjconn gjc = jd->gjc;
1805 struct jabber_chat *jc = NULL;
1806 char *realwho;
1807 xmlnode x;
1809 /* Find out which chat we're leaving */
1810 if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0)
1811 return;
1813 realwho = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server);
1814 x = jutil_presnew(0, realwho, NULL);
1815 g_free(realwho);
1816 xmlnode_put_attrib(x, "type", "unavailable");
1817 gjab_send(gjc, x);
1818 xmlnode_free(x);
1819 jc->b = NULL;
1822 static int jabber_chat_send(struct gaim_connection *gc, int id, char *message)
1824 xmlnode x, y;
1825 struct jabber_chat *jc = NULL;
1826 char *chatname;
1827 int retval = 0;
1829 /* Find out which chat we're sending to */
1830 if((retval = jabber_find_chat_by_convo_id(gc, id, &jc)) != 0)
1831 return(retval);
1833 x = xmlnode_new_tag("message");
1834 xmlnode_put_attrib(x, "from", jid_full(jc->Jid));
1835 chatname = g_strdup_printf("%s@%s", jc->Jid->user, jc->Jid->server);
1836 xmlnode_put_attrib(x, "to", chatname);
1837 g_free(chatname);
1838 xmlnode_put_attrib(x, "type", "groupchat");
1840 if (message && strlen(message) > strlen("/topic ") &&
1841 !g_strncasecmp(message, "/topic ", strlen("/topic "))) {
1842 char buf[8192];
1843 char *utf8 = str_to_utf8(message + strlen("/topic "));
1844 y = xmlnode_insert_tag(x, "subject");
1845 xmlnode_insert_cdata(y, utf8, -1);
1846 y = xmlnode_insert_tag(x, "body");
1847 g_snprintf(buf, sizeof(buf), "/me has changed the subject to: %s", utf8);
1848 xmlnode_insert_cdata(y, buf, -1);
1849 g_free(utf8);
1850 } else if (message && strlen(message)) {
1851 char *utf8 = str_to_utf8(message);
1852 y = xmlnode_insert_tag(x, "body");
1853 xmlnode_insert_cdata(y, utf8, -1);
1854 g_free(utf8);
1857 gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1858 xmlnode_free(x);
1859 return 0;
1862 static void jabber_chat_whisper(struct gaim_connection *gc, int id, char *who, char *message)
1864 xmlnode x, y;
1865 struct jabber_chat *jc = NULL;
1866 char *chatname;
1868 /* Find out which chat we're whispering to */
1869 if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0)
1870 return;
1872 x = xmlnode_new_tag("message");
1873 xmlnode_put_attrib(x, "from", jid_full(jc->Jid));
1874 chatname = g_strdup_printf("%s@%s/%s", jc->Jid->user, jc->Jid->server, who);
1875 xmlnode_put_attrib(x, "to", chatname);
1876 g_free(chatname);
1877 xmlnode_put_attrib(x, "type", "normal");
1879 if (message && strlen(message)) {
1880 char *utf8 = str_to_utf8(message);
1881 y = xmlnode_insert_tag(x, "body");
1882 xmlnode_insert_cdata(y, utf8, -1);
1883 g_free(utf8);
1886 gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1887 xmlnode_free(x);
1890 static char *jabber_normalize(const char *s)
1892 static char buf[BUF_LEN];
1893 char *t, *u;
1894 int x = 0;
1896 g_return_val_if_fail((s != NULL), NULL);
1898 /* Somebody called us with s == NULL once... */
1899 if(s == NULL) {
1900 return(NULL);
1901 } else {
1902 u = t = g_strdup(s);
1904 g_strdown(t);
1906 while (*t && (x < BUF_LEN - 1)) {
1907 if (*t != ' ')
1908 buf[x++] = *t;
1909 t++;
1911 buf[x] = '\0';
1912 g_free(u);
1914 if (!strchr(buf, '@')) {
1915 strcat(buf, "@jabber.org"); /* this isn't always right, but eh */
1916 } else if ((u = strchr(strchr(buf, '@'), '/')) != NULL) {
1917 *u = '\0';
1920 return buf;
1924 static void jabber_get_info(struct gaim_connection *gc, char *who) {
1925 xmlnode x;
1926 char *id;
1927 char *realwho;
1928 struct jabber_data *jd = gc->proto_data;
1929 gjconn gjc = jd->gjc;
1931 x = jutil_iqnew(JPACKET__GET, NS_VCARD);
1932 /* Bare username? */
1933 if (!strchr(who, '@')) {
1934 realwho = g_strdup_printf("%s@%s", who, gjc->user->server);
1935 } else {
1936 realwho = g_strdup(who);
1938 xmlnode_put_attrib(x, "to", realwho);
1939 g_free(realwho);
1941 id = gjab_getid(gjc);
1942 xmlnode_put_attrib(x, "id", id);
1944 g_hash_table_insert(jd->gjc->queries, g_strdup(id), g_strdup("vCard"));
1946 gjab_send(gjc, x);
1948 xmlnode_free(x);
1952 static void jabber_get_away_msg(struct gaim_connection *gc, char *who) {
1953 struct jabber_data *jd = gc->proto_data;
1954 gjconn gjc = jd->gjc;
1955 char *status;
1957 /* space for all elements: Jabber I.D. + "status" + NULL (list terminator) */
1958 gchar **str_arr = (gchar **) g_new(gpointer, 3);
1959 gchar **ap = str_arr;
1960 gchar *realwho, *final;
1962 /* Bare username? */
1963 if (!strchr(who, '@')) {
1964 realwho = g_strdup_printf("%s@%s", who, gjc->user->server);
1965 } else {
1966 realwho = g_strdup(who);
1968 *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", realwho);
1970 if((status = g_hash_table_lookup(jd->hash, realwho)) == NULL) {
1971 status = _("Online");
1973 *ap++ = g_strdup_printf("<B>Status:</B> %s<BR>\n", status);
1975 *ap = NULL;
1977 final= g_strjoinv(NULL, str_arr);
1978 g_strfreev(str_arr);
1980 g_show_info_text(gc, realwho, 2, final, NULL);
1981 g_free(realwho);
1982 g_free(final);
1986 static void jabber_get_cb_info(struct gaim_connection *gc, int cid, char *who) {
1987 struct jabber_chat *jc = NULL;
1988 char *realwho;
1990 /* Find out which chat */
1991 if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0)
1992 return;
1994 realwho = g_strdup_printf("%s@%s/%s", jc->Jid->user, jc->Jid->server, who);
1996 jabber_get_info(gc, realwho);
1997 g_free(realwho);
2000 static void jabber_get_cb_away_msg(struct gaim_connection *gc, int cid, char *who) {
2001 struct jabber_chat *jc = NULL;
2002 char *realwho;
2004 /* Find out which chat */
2005 if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0)
2006 return;
2008 realwho = g_strdup_printf("%s@%s/%s", jc->Jid->user, jc->Jid->server, who);
2010 jabber_get_away_msg(gc, realwho);
2012 g_free(realwho);
2016 static GList *jabber_buddy_menu(struct gaim_connection *gc, char *who) {
2017 GList *m = NULL;
2018 struct proto_buddy_menu *pbm;
2020 pbm = g_new0(struct proto_buddy_menu, 1);
2021 pbm->label = _("Get Info");
2022 pbm->callback = jabber_get_info;
2023 pbm->gc = gc;
2024 m = g_list_append(m, pbm);
2025 pbm = g_new0(struct proto_buddy_menu, 1);
2026 pbm->label = _("Get Away Msg");
2027 pbm->callback = jabber_get_away_msg;
2028 pbm->gc = gc;
2029 m = g_list_append(m, pbm);
2031 return m;
2034 static GList *jabber_away_states(struct gaim_connection *gc) {
2035 GList *m = NULL;
2037 m = g_list_append(m, "Online");
2038 m = g_list_append(m, "Chatty");
2039 m = g_list_append(m, "Away");
2040 m = g_list_append(m, "Extended Away");
2041 m = g_list_append(m, "Do Not Disturb");
2043 return m;
2046 static void jabber_set_away(struct gaim_connection *gc, char *state, char *message)
2048 xmlnode x, y;
2049 struct jabber_data *jd = gc->proto_data;
2050 gjconn gjc = jd->gjc;
2052 gc->away = NULL; /* never send an auto-response */
2054 x = xmlnode_new_tag("presence");
2056 if (!strcmp(state, GAIM_AWAY_CUSTOM)) {
2057 /* oh goody. Gaim is telling us what to do. */
2058 if (message) {
2059 /* Gaim wants us to be away */
2060 y = xmlnode_insert_tag(x, "show");
2061 xmlnode_insert_cdata(y, "away", -1);
2062 y = xmlnode_insert_tag(x, "status");
2063 xmlnode_insert_cdata(y, message, -1);
2064 gc->away = "";
2065 } else {
2066 /* Gaim wants us to not be away */
2067 /* but for Jabber, we can just send presence with no other information. */
2069 } else {
2070 /* state is one of our own strings. it won't be NULL. */
2071 if (!strcmp(state, "Online")) {
2072 /* once again, we don't have to put anything here */
2073 } else if (!strcmp(state, "Chatty")) {
2074 y = xmlnode_insert_tag(x, "show");
2075 xmlnode_insert_cdata(y, "chat", -1);
2076 } else if (!strcmp(state, "Away")) {
2077 y = xmlnode_insert_tag(x, "show");
2078 xmlnode_insert_cdata(y, "away", -1);
2079 gc->away = "";
2080 } else if (!strcmp(state, "Extended Away")) {
2081 y = xmlnode_insert_tag(x, "show");
2082 xmlnode_insert_cdata(y, "xa", -1);
2083 gc->away = "";
2084 } else if (!strcmp(state, "Do Not Disturb")) {
2085 y = xmlnode_insert_tag(x, "show");
2086 xmlnode_insert_cdata(y, "dnd", -1);
2087 gc->away = "";
2091 gjab_send(gjc, x);
2092 xmlnode_free(x);
2095 static void jabber_set_idle(struct gaim_connection *gc, int idle) {
2096 struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
2097 debug_printf("jabber_set_idle: setting idle %i\n", idle);
2098 jd->idle = idle ? time(NULL) - idle : idle;
2101 static void jabber_keepalive(struct gaim_connection *gc) {
2102 struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
2103 gjab_send_raw(jd->gjc, " \t ");
2106 static GList *jabber_user_opts()
2108 GList *m = NULL;
2109 struct proto_user_opt *puo;
2111 puo = g_new0(struct proto_user_opt, 1);
2112 puo->label = "Port:";
2113 puo->def = "5222";
2114 puo->pos = USEROPT_PORT;
2115 m = g_list_append(m, puo);
2117 return m;
2120 static void jabber_buddy_free(struct buddy *b)
2122 while (b->proto_data) {
2123 g_free(((GSList *)b->proto_data)->data);
2124 b->proto_data = g_slist_remove(b->proto_data, ((GSList *)b->proto_data)->data);
2128 /*---------------------------------------*/
2129 /* Jabber "set info" (vCard) support */
2130 /*---------------------------------------*/
2133 * V-Card format:
2135 * <vCard prodid='' version='' xmlns=''>
2136 * <FN></FN>
2137 * <N>
2138 * <FAMILY/>
2139 * <GIVEN/>
2140 * </N>
2141 * <NICKNAME/>
2142 * <URL/>
2143 * <ADR>
2144 * <STREET/>
2145 * <EXTADD/>
2146 * <LOCALITY/>
2147 * <REGION/>
2148 * <PCODE/>
2149 * <COUNTRY/>
2150 * </ADR>
2151 * <TEL/>
2152 * <EMAIL/>
2153 * <ORG>
2154 * <ORGNAME/>
2155 * <ORGUNIT/>
2156 * </ORG>
2157 * <TITLE/>
2158 * <ROLE/>
2159 * <DESC/>
2160 * <BDAY/>
2161 * </vCard>
2163 * See also:
2165 * http://docs.jabber.org/proto/html/vcard-temp.html
2166 * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
2170 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
2171 * and attributes.
2173 * Order is (or should be) unimportant. For example: we have no way of
2174 * knowing in what order real data will arrive.
2176 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
2177 * name, XML tag's parent tag "path" (relative to vCard node).
2179 * List is terminated by a NULL label pointer.
2181 * Entries with no label text, but with XML tag and parent tag
2182 * entries, are used by V-Card XML construction routines to
2183 * "automagically" construct the appropriate XML node tree.
2185 * Thoughts on future direction/expansion
2187 * This is a "simple" vCard.
2189 * It is possible for nodes other than the "vCard" node to have
2190 * attributes. Should that prove necessary/desirable, add an
2191 * "attributes" pointer to the vcard_template struct, create the
2192 * necessary tag_attr structs, and add 'em to the vcard_dflt_data
2193 * array.
2195 * The above changes will (obviously) require changes to the vCard
2196 * construction routines.
2199 struct vcard_template {
2200 char *label; /* label text pointer */
2201 char *text; /* entry text pointer */
2202 int visible; /* should entry field be "visible?" */
2203 int editable; /* should entry field be editable? */
2204 char *tag; /* tag text */
2205 char *ptag; /* parent tag "path" text */
2206 char *url; /* vCard display format if URL */
2207 } vcard_template_data[] = {
2208 {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL},
2209 {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL},
2210 {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL},
2211 {N_("Nickname"), NULL, TRUE, TRUE, "NICKNAME", NULL, NULL},
2212 {N_("URL"), NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"},
2213 {N_("Street Address"), NULL, TRUE, TRUE, "STREET", "ADR", NULL},
2214 {N_("Extended Address"), NULL, TRUE, TRUE, "EXTADD", "ADR", NULL},
2215 {N_("Locality"), NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL},
2216 {N_("Region"), NULL, TRUE, TRUE, "REGION", "ADR", NULL},
2217 {N_("Postal Code"), NULL, TRUE, TRUE, "PCODE", "ADR", NULL},
2218 {N_("Country"), NULL, TRUE, TRUE, "COUNTRY", "ADR", NULL},
2219 {N_("Telephone"), NULL, TRUE, TRUE, "TELEPHONE", NULL, NULL},
2220 {N_("Email"), NULL, TRUE, TRUE, "EMAIL", NULL, "<A HREF=\"mailto:%s\">%s</A>"},
2221 {N_("Organization Name"), NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL},
2222 {N_("Organization Unit"), NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL},
2223 {N_("Title"), NULL, TRUE, TRUE, "TITLE", NULL, NULL},
2224 {N_("Role"), NULL, TRUE, TRUE, "ROLE", NULL, NULL},
2225 {N_("Birthday"), NULL, TRUE, TRUE, "BDAY", NULL, NULL},
2226 {N_("Description"), NULL, TRUE, TRUE, "DESC", NULL, NULL},
2227 {"", NULL, TRUE, TRUE, "N", NULL, NULL},
2228 {"", NULL, TRUE, TRUE, "ADR", NULL, NULL},
2229 {"", NULL, TRUE, TRUE, "ORG", NULL, NULL},
2230 {NULL, NULL, 0, 0, NULL, NULL, NULL}
2234 * The "vCard" tag's attibute list...
2236 struct tag_attr {
2237 char *attr;
2238 char *value;
2239 } vcard_tag_attr_list[] = {
2240 {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
2241 {"version", "2.0", },
2242 {"xmlns", "vcard-temp", },
2243 {NULL, NULL},
2248 * V-Card user instructions
2250 static char *multi_entry_instructions =
2251 N_("All items below are optional. Enter only the information with which you feel comfortable");
2252 static char *entries_title = N_("User Identity");
2255 * Used by routines to parse an XML-encoded string into an xmlnode tree
2257 typedef struct {
2258 XML_Parser parser;
2259 xmlnode current;
2260 } *xmlstr2xmlnode_parser, xmlstr2xmlnode_parser_struct;
2264 * Display a Jabber vCard
2266 static void jabber_handlevcard(gjconn gjc, xmlnode querynode, char *from)
2268 struct gaim_connection *gc = GJ_GC(gjc);
2269 struct jabber_data *jd = GJ_GC(gjc)->proto_data;
2270 jid who = jid_new(gjc->p, from);
2271 char *cdata, *status;
2273 struct vcard_template *vc_tp = vcard_template_data;
2275 /* space for all vCard elements + Jabber I.D. + "status" + NULL (list terminator) */
2276 gchar **str_arr = (gchar **) g_new(gpointer,
2277 (sizeof(vcard_template_data)/sizeof(struct vcard_template)) + 3);
2278 gchar **ap = str_arr;
2279 gchar *buddy, *final;
2281 if(who->resource != NULL && (who->resource)[0] != '\0') {
2282 buddy = g_strdup_printf("%s@%s/%s", who->user, who->server, who->resource);
2283 } else {
2284 buddy = g_strdup_printf("%s@%s", who->user, who->server);
2286 *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", buddy);
2288 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
2289 if(strcmp(vc_tp->tag, "DESC") == 0)
2290 continue; /* special handling later */
2291 if(vc_tp->ptag == NULL) {
2292 cdata = xmlnode_get_tag_data(querynode, vc_tp->tag);
2293 } else {
2294 gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
2295 cdata = xmlnode_get_tag_data(querynode, tag);
2296 g_free(tag);
2298 if(cdata != NULL) {
2299 if(vc_tp->url == NULL) {
2300 *ap++ = g_strdup_printf("<B>%s:</B> %s<BR>\n", vc_tp->label, cdata);
2301 } else {
2302 gchar *fmt = g_strdup_printf("<B>%%s:</B> %s<BR>\n", vc_tp->url);
2303 *ap++ = g_strdup_printf(fmt, vc_tp->label, cdata, cdata);
2304 g_free(fmt);
2309 if((status = g_hash_table_lookup(jd->hash, buddy)) == NULL) {
2310 status = _("Online");
2312 *ap++ = g_strdup_printf("<B>Status:</B> %s<BR>\n", status);
2315 * "Description" handled as a special case: get a copy of the
2316 * string and HTML-ize.
2318 if((cdata = xmlnode_get_tag_data(querynode, "DESC")) != NULL) {
2319 gchar *tmp = g_strdup_printf("<HR>%s<BR>", cdata);
2320 *ap++ = strdup_withhtml(tmp);
2321 g_free(tmp);
2324 *ap = NULL;
2326 final= g_strjoinv(NULL, str_arr);
2327 g_strfreev(str_arr);
2329 g_show_info_text(gc, buddy, 2, final, NULL);
2330 g_free(buddy);
2331 g_free(final);
2335 * Used by XML_Parse on parsing CDATA
2337 static void xmlstr2xmlnode_charData(void *userdata, const char *s, int slen)
2339 xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
2341 if (xmlp->current)
2342 xmlnode_insert_cdata(xmlp->current, s, slen);
2346 * Used by XML_Parse to start or append to an xmlnode
2348 static void xmlstr2xmlnode_startElement(void *userdata, const char *name, const char **attribs)
2350 xmlnode x;
2351 xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
2353 if (xmlp->current) {
2354 /* Append the node to the current one */
2355 x = xmlnode_insert_tag(xmlp->current, name);
2356 xmlnode_put_expat_attribs(x, attribs);
2358 xmlp->current = x;
2359 } else {
2360 x = xmlnode_new_tag(name);
2361 xmlnode_put_expat_attribs(x, attribs);
2362 xmlp->current = x;
2367 * Used by XML_Parse to end an xmlnode
2369 static void xmlstr2xmlnode_endElement(void *userdata, const char *name)
2371 xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
2372 xmlnode x;
2374 if (xmlp->current != NULL && (x = xmlnode_get_parent(xmlp->current)) != NULL) {
2375 xmlp->current = x;
2380 * Parse an XML-encoded string into an xmlnode tree
2382 * Caller is responsible for freeing the returned xmlnode
2384 static xmlnode xmlstr2xmlnode(char *xmlstring)
2386 xmlstr2xmlnode_parser my_parser = g_new(xmlstr2xmlnode_parser_struct, 1);
2387 xmlnode x = NULL;
2389 my_parser->parser = XML_ParserCreate(NULL);
2390 my_parser->current = NULL;
2392 XML_SetUserData(my_parser->parser, (void *)my_parser);
2393 XML_SetElementHandler(my_parser->parser, xmlstr2xmlnode_startElement, xmlstr2xmlnode_endElement);
2394 XML_SetCharacterDataHandler(my_parser->parser, xmlstr2xmlnode_charData);
2395 XML_Parse(my_parser->parser, xmlstring, strlen(xmlstring), 0);
2397 x = my_parser->current;
2399 XML_ParserFree(my_parser->parser);
2400 g_free(my_parser);
2402 return(x);
2406 * Insert a tag node into an xmlnode tree, recursively inserting parent tag
2407 * nodes as necessary
2409 * Returns pointer to inserted node
2411 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
2412 * calls itself), so don't put any "static"s in here!
2414 static xmlnode insert_tag_to_parent_tag(xmlnode start, const char *parent_tag, const char *new_tag)
2416 xmlnode x = NULL;
2419 * If the parent tag wasn't specified, see if we can get it
2420 * from the vCard template struct.
2422 if(parent_tag == NULL) {
2423 struct vcard_template *vc_tp = vcard_template_data;
2425 while(vc_tp->label != NULL) {
2426 if(strcmp(vc_tp->tag, new_tag) == 0) {
2427 parent_tag = vc_tp->ptag;
2428 break;
2430 ++vc_tp;
2435 * If we have a parent tag...
2437 if(parent_tag != NULL ) {
2439 * Try to get the parent node for a tag
2441 if((x = xmlnode_get_tag(start, parent_tag)) == NULL) {
2443 * Descend?
2445 char *grand_parent = strcpy(g_malloc(strlen(parent_tag) + 1), parent_tag);
2446 char *parent;
2448 if((parent = strrchr(grand_parent, '/')) != NULL) {
2449 *(parent++) = '\0';
2450 x = insert_tag_to_parent_tag(start, grand_parent, parent);
2451 } else {
2452 x = xmlnode_insert_tag(start, grand_parent);
2454 g_free(grand_parent);
2455 } else {
2457 * We found *something* to be the parent node.
2458 * Note: may be the "root" node!
2460 xmlnode y;
2461 if((y = xmlnode_get_tag(x, new_tag)) != NULL) {
2462 return(y);
2468 * insert the new tag into its parent node
2470 return(xmlnode_insert_tag((x == NULL? start : x), new_tag));
2474 * Find the tag name for a label
2476 * Returns NULL on not found
2478 static char *tag_for_label(const char *label)
2480 struct vcard_template *vc_tp = vcard_template_data;
2481 char *p = NULL;
2483 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
2484 if(strcmp(label, vc_tp->label) == 0) {
2485 p = vc_tp->tag;
2486 break;
2490 return(p);
2494 * Send vCard info to Jabber server
2496 static void jabber_set_info(struct gaim_connection *gc, char *info)
2498 xmlnode x, vc_node;
2499 char *id;
2500 struct jabber_data *jd = gc->proto_data;
2501 gjconn gjc = jd->gjc;
2503 x = xmlnode_new_tag("iq");
2504 xmlnode_put_attrib(x,"type","set");
2506 id = gjab_getid(gjc);
2508 xmlnode_put_attrib(x, "id", id);
2511 * Send only if there's actually any *information* to send
2513 if((vc_node = xmlstr2xmlnode(info)) != NULL && xmlnode_get_name(vc_node) != NULL &&
2514 g_strncasecmp(xmlnode_get_name(vc_node), "vcard", 5) == 0) {
2515 xmlnode_insert_tag_node(x, vc_node);
2516 debug_printf("jabber: vCard packet: %s\n", xmlnode2str(x));
2517 gjab_send(gjc, x);
2520 xmlnode_free(x);
2524 * This is the callback from the "ok clicked" for "set vCard"
2526 * Formats GSList data into XML-encoded string and returns a pointer
2527 * to said string.
2529 * g_free()'ing the returned string space is the responsibility of
2530 * the caller.
2532 static gchar *jabber_format_info(MultiEntryDlg *b)
2534 xmlnode vc_node;
2535 GSList *list;
2536 MultiEntryData *med;
2537 MultiTextData *mtd;
2538 char *p;
2540 struct tag_attr *tag_attr;
2542 vc_node = xmlnode_new_tag("vCard");
2544 for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr)
2545 xmlnode_put_attrib(vc_node, tag_attr->attr, tag_attr->value);
2547 for(list = b->multi_entry_items; list != NULL; list = list->next) {
2548 med = (MultiEntryData *) list->data;
2549 if(med->label != NULL && med->text != NULL && (med->text)[0] != '\0') {
2550 if((p = tag_for_label(med->label)) != NULL) {
2551 xmlnode xp;
2552 if((xp = insert_tag_to_parent_tag(vc_node, NULL, p)) != NULL) {
2553 xmlnode_insert_cdata(xp, med->text, -1);
2559 for(list = b->multi_text_items; list != NULL; list = list->next) {
2560 mtd = (MultiTextData *) list->data;
2561 if(mtd->label != NULL && mtd->text != NULL && (mtd->text)[0] != '\0') {
2562 if((p = tag_for_label(mtd->label)) != NULL) {
2563 xmlnode xp;
2564 if((xp = insert_tag_to_parent_tag(vc_node, NULL, p)) != NULL) {
2565 xmlnode_insert_cdata(xp, mtd->text, -1);
2572 p = g_strdup(xmlnode2str(vc_node));
2573 xmlnode_free(vc_node);
2575 return(p);
2579 * This gets executed by the proto action
2581 * Creates a new MultiEntryDlg struct, gets the XML-formatted user_info
2582 * string (if any) into GSLists for the (multi-entry) edit dialog and
2583 * calls the set_vcard dialog.
2585 static void jabber_setup_set_info(struct gaim_connection *gc)
2587 MultiEntryData *data;
2588 const struct vcard_template *vc_tp;
2589 char *user_info;
2590 MultiEntryDlg *b = multi_entry_dialog_new();
2591 char *cdata;
2592 xmlnode x_vc_data = NULL;
2593 struct aim_user *tmp = gc->user;
2594 b->user = tmp;
2598 * Get existing, XML-formatted, user info
2600 if((user_info = g_malloc(strlen(tmp->user_info) + 1)) != NULL) {
2601 strcpy(user_info, tmp->user_info);
2602 x_vc_data = xmlstr2xmlnode(user_info);
2606 * Set up GSLists for edit with labels from "template," data from user info
2608 for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
2609 if((vc_tp->label)[0] == '\0')
2610 continue;
2611 if(vc_tp->ptag == NULL) {
2612 cdata = xmlnode_get_tag_data(x_vc_data, vc_tp->tag);
2613 } else {
2614 gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
2615 cdata = xmlnode_get_tag_data(x_vc_data, tag);
2616 g_free(tag);
2618 if(strcmp(vc_tp->tag, "DESC") == 0) {
2619 multi_text_list_update(&(b->multi_text_items),
2620 vc_tp->label, cdata, TRUE);
2621 } else {
2622 data = multi_entry_list_update(&(b->multi_entry_items),
2623 vc_tp->label, cdata, TRUE);
2624 data->visible = vc_tp->visible;
2625 data->editable = vc_tp->editable;
2630 if(x_vc_data != NULL) {
2631 xmlnode_free(x_vc_data);
2632 } else {
2634 * Early Beta versions had a different user_info storage format--let's
2635 * see if that works.
2637 * This goes away RSN.
2639 const char *record_separator = "<BR>";
2640 const char *field_separator = ": ";
2641 gchar **str_list, **str_list_ptr, **str_list2;
2643 if((str_list = g_strsplit(user_info, record_separator, 0)) != NULL) {
2644 for(str_list_ptr = str_list; *str_list_ptr != NULL; ++str_list_ptr) {
2645 str_list2 = g_strsplit(*str_list_ptr, field_separator, 2);
2646 if(str_list2[0] != NULL && str_list2[1] != NULL) {
2647 g_strstrip(str_list2[0]);
2648 g_strstrip(str_list2[1]);
2649 /* this is ugly--so far */
2650 if(strcmp(str_list2[0], "Description") == 0) {
2651 multi_text_list_update(&(b->multi_text_items),
2652 str_list2[0], str_list2[1], FALSE);
2653 } else {
2654 multi_entry_list_update(&(b->multi_entry_items),
2655 str_list2[0], str_list2[1], FALSE);
2658 g_strfreev(str_list2);
2660 g_strfreev(str_list);
2664 if(user_info != NULL) {
2665 g_free(user_info);
2668 b->title = _("Gaim - Edit Jabber vCard");
2669 b->wmclass_name = "set_info";
2670 b->wmclass_class = "Gaim";
2671 b->instructions->text = g_strdup(multi_entry_instructions);
2672 b->entries_title = g_strdup(entries_title);
2674 b->custom = (void *) jabber_format_info;
2676 show_set_vcard(b);
2679 /*---------------------------------------*/
2680 /* End Jabber "set info" (vCard) support */
2681 /*---------------------------------------*/
2683 /*----------------------------------------*/
2684 /* Jabber "user registration" support */
2685 /*----------------------------------------*/
2688 * Three of the following four functions duplicate much of what
2689 * exists elsewhere:
2691 * jabber_handleregresp()
2692 * gjab_reqreg()
2693 * jabber_handle_registration_state()
2695 * It may be that an additional flag could be added to one of
2696 * the "local" structs and the duplicated code modified to
2697 * account for it--thus eliminating the duplication. Then again:
2698 * doing it the way it is may be much cleaner.
2700 * TBD: Code to support requesting additional information server
2701 * wants at registration--incl. dialog.
2705 * Like jabber_handlepacket(), only different
2707 static void jabber_handleregresp(gjconn gjc, jpacket p)
2709 if (jpacket_subtype(p) == JPACKET__RESULT) {
2710 xmlnode querynode;
2712 if((querynode = xmlnode_get_tag(p->x, "query")) != NULL) {
2713 char *xmlns;
2715 /* we damn well *better* have this! */
2716 if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL &&
2717 strcmp(xmlns, NS_REGISTER) == 0) {
2719 char *tag;
2720 xmlnode child = xmlnode_get_firstchild(querynode);
2722 debug_printf("got registration requirments response!\n");
2724 while(child != NULL) {
2725 if((tag = xmlnode_get_name(child)) != NULL) {
2726 char *data;
2728 fprintf(stderr, "DBG: got node: \"%s\"\n", tag);
2729 fflush(stderr);
2731 if((data = xmlnode_get_data(child)) != NULL) {
2732 fprintf(stderr, "DBG: got data: \"%s\"\n", data);
2733 fflush(stderr);
2736 child = xmlnode_get_nextsibling(child);
2739 } else {
2740 debug_printf("registration successful!\n");
2742 hide_login_progress_notice(GJ_GC(gjc), _("Server Registration successful!"));
2744 * TBD: is this the correct way to do away with a
2745 * gaim_connection and all its associated memory
2746 * allocs, etc.?
2748 signoff(GJ_GC(gjc));
2751 } else {
2752 xmlnode xerr;
2753 char *errmsg = NULL;
2754 int errcode = 0;
2755 struct jabber_data *jd = GJ_GC(gjc)->proto_data;
2757 debug_printf("registration failed\n");
2758 xerr = xmlnode_get_tag(p->x, "error");
2759 if (xerr) {
2760 char msg[BUF_LONG];
2761 errmsg = xmlnode_get_data(xerr);
2762 if (xmlnode_get_attrib(xerr, "code")) {
2763 errcode = atoi(xmlnode_get_attrib(xerr, "code"));
2764 g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg);
2765 } else
2766 g_snprintf(msg, sizeof(msg), "%s", errmsg);
2767 hide_login_progress(GJ_GC(gjc), msg);
2768 } else {
2769 hide_login_progress(GJ_GC(gjc), _("Unknown registration error"));
2772 jd->die = TRUE;
2777 * Like gjab_reqauth(), only different
2779 static void gjab_reqreg(gjconn gjc)
2781 xmlnode x, y, z;
2782 char *user;
2784 if (!gjc)
2785 return;
2787 x = jutil_iqnew(JPACKET__SET, NS_REGISTER);
2788 y = xmlnode_get_tag(x, "query");
2790 user = gjc->user->user;
2792 if (user) {
2793 z = xmlnode_insert_tag(y, "username");
2794 xmlnode_insert_cdata(z, user, -1);
2796 z = xmlnode_insert_tag(y, "password");
2797 xmlnode_insert_cdata(z, gjc->pass, -1);
2799 debug_printf("jabber: registration packet: %s\n", xmlnode2str(x));
2800 gjab_send(gjc, x);
2801 xmlnode_free(x);
2805 * Like jabber_handlestate(), only different
2807 static void jabber_handle_registration_state(gjconn gjc, int state)
2809 switch (state) {
2810 case JCONN_STATE_OFF:
2811 hide_login_progress(GJ_GC(gjc), _("Unable to connect"));
2812 signoff(GJ_GC(gjc));
2813 break;
2814 case JCONN_STATE_CONNECTED:
2816 * TBD?
2817 set_login_progress(GJ_GC(gjc), 2, _("Connected"));
2819 break;
2820 case JCONN_STATE_ON:
2822 * TBD?
2823 set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method"));
2825 gjab_reqreg(gjc);
2827 * TBD: A work-in-progress
2828 gjab_reqregreqs(gjc);
2830 break;
2831 default:
2832 debug_printf("state change: %d\n", state);
2834 return;
2838 * Like jabber_login(), only different
2840 void jabber_register_user(struct aim_user *au)
2842 struct gaim_connection *gc = new_gaim_conn(au);
2843 struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1);
2844 char *loginname = create_valid_jid(au->username, DEFAULT_SERVER, "GAIM");
2847 * These do nothing during registration
2849 jd->hash = NULL;
2850 jd->chats = NULL;
2852 if ((jd->gjc = gjab_new(loginname, au->password, gc)) == NULL) {
2853 g_free(loginname);
2854 debug_printf("jabber: unable to connect (jab_new failed)\n");
2855 hide_login_progress(gc, _("Unable to connect"));
2856 signoff(gc);
2857 } else {
2858 gjab_state_handler(jd->gjc, jabber_handle_registration_state);
2859 gjab_packet_handler(jd->gjc, jabber_handleregresp);
2860 jd->gjc->queries = NULL;
2861 gjab_start(jd->gjc);
2864 g_free(loginname);
2867 /*----------------------------------------*/
2868 /* End Jabber "user registration" support */
2869 /*----------------------------------------*/
2871 static void jabber_do_action(struct gaim_connection *gc, char *act)
2873 if (!strcmp(act, _("Set User Info"))) {
2874 jabber_setup_set_info(gc);
2876 } else if (!strcmp(act, _("Set Dir Info"))) {
2877 show_set_dir(gc);
2878 } else if (!strcmp(act, _("Change Password"))) {
2879 show_change_passwd(gc);
2884 static GList *jabber_actions()
2886 GList *m = NULL;
2888 m = g_list_append(m, _("Set User Info"));
2890 m = g_list_append(m, _("Set Dir Info"));
2891 m = g_list_append(m, _("Change Password"));
2894 return m;
2897 static struct prpl *my_protocol = NULL;
2899 void jabber_init(struct prpl *ret)
2901 /* the NULL's aren't required but they're nice to have */
2902 ret->protocol = PROTO_JABBER;
2903 ret->options = OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_CHAT_TOPIC;
2904 ret->name = jabber_name;
2905 ret->list_icon = jabber_list_icon;
2906 ret->away_states = jabber_away_states;
2907 ret->actions = jabber_actions;
2908 ret->do_action = jabber_do_action;
2909 ret->buddy_menu = jabber_buddy_menu;
2910 ret->user_opts = jabber_user_opts;
2911 ret->login = jabber_login;
2912 ret->close = jabber_close;
2913 ret->send_im = jabber_send_im;
2914 ret->set_info = jabber_set_info;
2915 ret->get_info = jabber_get_info;
2916 ret->get_cb_info = jabber_get_cb_info;
2917 ret->get_cb_away = jabber_get_cb_away_msg;
2918 ret->set_away = jabber_set_away;
2919 ret->set_dir = NULL;
2920 ret->get_dir = NULL;
2921 ret->dir_search = NULL;
2922 ret->set_idle = jabber_set_idle;
2923 ret->change_passwd = NULL;
2924 ret->add_buddy = jabber_add_buddy;
2925 ret->add_buddies = NULL;
2926 ret->remove_buddy = jabber_remove_buddy;
2927 ret->add_permit = NULL;
2928 ret->add_deny = NULL;
2929 ret->rem_permit = NULL;
2930 ret->rem_deny = NULL;
2931 ret->set_permit_deny = NULL;
2932 ret->warn = NULL;
2933 ret->chat_info = jabber_chat_info;
2934 ret->join_chat = jabber_join_chat;
2935 ret->chat_invite = jabber_chat_invite;
2936 ret->chat_leave = jabber_chat_leave;
2937 ret->chat_whisper = jabber_chat_whisper;
2938 ret->chat_send = jabber_chat_send;
2939 ret->keepalive = jabber_keepalive;
2940 ret->normalize = jabber_normalize;
2941 ret->buddy_free = jabber_buddy_free;
2942 ret->register_user = jabber_register_user;
2944 my_protocol = ret;
2947 #ifndef STATIC
2949 char *gaim_plugin_init(GModule *handle)
2951 load_protocol(jabber_init, sizeof(struct prpl));
2952 return NULL;
2955 void gaim_plugin_remove()
2957 struct prpl *p = find_prpl(PROTO_JABBER);
2958 if (p == my_protocol)
2959 unload_protocol(p);
2962 char *name()
2964 return "Jabber";
2967 char *description()
2969 return PRPL_DESC("Jabber");
2972 #endif