source: protocols/jabber/jabber.c @ a323a22

Last change on this file since a323a22 was 1fa6a23, checked in by Wilmer van der Gaast <wilmer@…>, at 2005-12-20T20:53:38Z

Jabber module now requests the buddy list before setting an away state, this
was at least one cause of not seeing someone online. I'm afraid there are
more (there's at least one more I know about), as mentioned in #15.

  • Property mode set to 100644
File size: 59.9 KB
RevLine 
[b7d3cc34]1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2/*
3 * gaim
4 *
5 * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
6 * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 *
22 */
23
24#ifndef _WIN32
25#include <sys/utsname.h>
26#endif
27#include <errno.h>
28#include <string.h>
29#include <stdlib.h>
30#include <stdio.h>
31#include <time.h>
32#include <sys/stat.h>
33#include "jabber.h"
34#include "nogaim.h"
35#include "bitlbee.h"
36#include "proxy.h"
37#include "ssl_client.h"
38
39/* The priv member of gjconn's is a gaim_connection for now. */
40#define GJ_GC(x) ((struct gaim_connection *)(x)->priv)
41
42#define IQID_AUTH "__AUTH__"
43
44#define IQ_NONE -1
45#define IQ_AUTH 0
46#define IQ_ROSTER 1
47
48#define UC_AWAY (0x02 | UC_UNAVAILABLE)
49#define UC_CHAT  0x04
50#define UC_XA   (0x08 | UC_UNAVAILABLE)
51#define UC_DND  (0x10 | UC_UNAVAILABLE)
52
53#define DEFAULT_SERVER "jabber.org"
54#define DEFAULT_GROUPCHAT "conference.jabber.org"
55#define DEFAULT_PORT 5222
56#define DEFAULT_PORT_SSL 5223
[027d2eb]57#define JABBER_PORT_MIN 5220
58#define JABBER_PORT_MAX 5229
[b7d3cc34]59
60#define JABBER_GROUP "Friends"
61
62/* i18n disabled - Bitlbee */
63#define N_(String) String
64
65/*
66 * Note: "was_connected" may seem redundant, but it was needed and I
67 * didn't want to touch the Jabber state stuff not specific to Gaim.
68 */
69typedef struct gjconn_struct {
70        /* Core structure */
71        pool p;                 /* Memory allocation pool */
72        int state;              /* Connection state flag */
73        int was_connected;      /* We were once connected */
74        int fd;                 /* Connection file descriptor */
75        void *ssl;              /* SSL connection */
76        jid user;               /* User info */
77        char *pass;             /* User passwd */
78
79        /* Stream stuff */
80        int id;                 /* id counter for jab_getid() function */
81        char idbuf[9];          /* temporary storage for jab_getid() */
82        char *sid;              /* stream id from server, for digest auth */
83        XML_Parser parser;      /* Parser instance */
84        xmlnode current;        /* Current node in parsing instance.. */
85
86        /* Event callback ptrs */
87        void (*on_state)(struct gjconn_struct *gjc, int state);
88        void (*on_packet)(struct gjconn_struct *gjc, jpacket p);
89
90        GHashTable *queries;    /* query tracker */
91
92        void *priv;
93} *gjconn, gjconn_struct;
94
95typedef void (*gjconn_state_h)(gjconn gjc, int state);
96typedef void (*gjconn_packet_h)(gjconn gjc, jpacket p);
97
98static gjconn gjab_new(char *user, char *pass, void *priv);
99static void gjab_delete(gjconn gjc);
100static void gjab_state_handler(gjconn gjc, gjconn_state_h h);
101static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h);
102static void gjab_start(gjconn gjc);
103static void gjab_stop(gjconn gjc);
104/*
105static int gjab_getfd(gjconn gjc);
106static jid gjab_getjid(gjconn gjc);
107static char *gjab_getsid(gjconn gjc);
108*/
109static char *gjab_getid(gjconn gjc);
110static void gjab_send(gjconn gjc, xmlnode x);
111static void gjab_send_raw(gjconn gjc, const char *str);
112static void gjab_recv(gjconn gjc);
113static void gjab_auth(gjconn gjc);
114
115/*
116 * It is *this* to which we point the gaim_connection proto_data
117 */
118struct jabber_data {
119        gjconn gjc;
120        gboolean did_import;
121        GSList *chats;
122        GHashTable *hash;
123        time_t idle;
124        gboolean die;
125};
126
127/*
128 * Jabber "chat group" info.  Pointers to these go in jabber_data
129 * pending and existing chats lists.
130 */
131struct jabber_chat {
132        jid Jid;
133        struct gaim_connection *gc;
134        struct conversation *b;
135        int id;
136        int state;
137};
138
139/*
140 * Jabber chat states...
141 *
142 * Note: due to a bug in one version of the Jabber server, subscriptions
143 * to chat groups aren't (always?) properly removed at the server.  The
144 * result is clients receive Jabber "presence" notifications for JIDs
145 * they no longer care about.  The problem with such vestigial notifies is
146 * that we really have no way of telling if it's vestigial or if it's a
147 * valid "buddy" presence notification.  So we keep jabber_chat structs
148 * around after leaving a chat group and simply mark them "closed."  That
149 * way we can test for such errant presence notifications.  I.e.: if we
150 * get a presence notfication from a JID that matches a chat group JID,
151 * we disregard it.
152 */
153#define JCS_PENDING 1   /* pending */
154#define JCS_ACTIVE  2   /* active */
155#define JCS_CLOSED  3   /* closed */
156
157
158#define STATE_EVT(arg) if(gjc->on_state) { (gjc->on_state)(gjc, (arg) ); }
159
160static void jabber_remove_buddy(struct gaim_connection *gc, char *name, char *group);
161static void jabber_handlevcard(gjconn gjc, xmlnode querynode, char *from);
162
163static char *create_valid_jid(const char *given, char *server, char *resource)
164{
165        char *valid;
166
167        if (!strchr(given, '@'))
168                valid = g_strdup_printf("%s@%s/%s", given, server, resource);
169        else if (!strchr(strchr(given, '@'), '/'))
170                valid = g_strdup_printf("%s/%s", given, resource);
171        else
172                valid = g_strdup(given);
173
174        return valid;
175}
176
177static gjconn gjab_new(char *user, char *pass, void *priv)
178{
179        pool p;
180        gjconn gjc;
181
182        if (!user)
183                return (NULL);
184
185        p = pool_new();
186        if (!p)
187                return (NULL);
188        gjc = pmalloc_x(p, sizeof(gjconn_struct), 0);
189        if (!gjc) {
190                pool_free(p);   /* no need for this anymore! */
191                return (NULL);
192        }
193        gjc->p = p;
194
195        if((gjc->user = jid_new(p, user)) == NULL) {
196                pool_free(p);   /* no need for this anymore! */
197                return (NULL);
198        }
199        gjc->pass = pstrdup(p, pass);
200
201        gjc->state = JCONN_STATE_OFF;
202        gjc->was_connected = 0;
203        gjc->id = 1;
204        gjc->fd = -1;
205
206        gjc->priv = priv;
207
208        return gjc;
209}
210
211static void gjab_delete(gjconn gjc)
212{
213        if (!gjc)
214                return;
215
216        gjab_stop(gjc);
217        pool_free(gjc->p);
218}
219
220static void gjab_state_handler(gjconn gjc, gjconn_state_h h)
221{
222        if (!gjc)
223                return;
224
225        gjc->on_state = h;
226}
227
228static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h)
229{
230        if (!gjc)
231                return;
232
233        gjc->on_packet = h;
234}
235
236static void gjab_stop(gjconn gjc)
237{
238        if (!gjc || gjc->state == JCONN_STATE_OFF)
239                return;
240
241        gjab_send_raw(gjc, "</stream:stream>");
242        gjc->state = JCONN_STATE_OFF;
243        gjc->was_connected = 0;
244        if (gjc->ssl) {
245                ssl_disconnect(gjc->ssl);
246                gjc->ssl = NULL;
247        } else {
248                closesocket(gjc->fd);
249        }
250        gjc->fd = -1;
251        XML_ParserFree(gjc->parser);
252        gjc->parser = NULL;
253}
254
255/*
256static int gjab_getfd(gjconn gjc)
257{
258        if (gjc)
259                return gjc->fd;
260        else
261                return -1;
262}
263
264static jid gjab_getjid(gjconn gjc)
265{
266        if (gjc)
267                return (gjc->user);
268        else
269                return NULL;
270}
271
272static char *gjab_getsid(gjconn gjc)
273{
274        if (gjc)
275                return (gjc->sid);
276        else
277                return NULL;
278}
279*/
280
281static char *gjab_getid(gjconn gjc)
282{
283        g_snprintf(gjc->idbuf, 8, "%d", gjc->id++);
284        return &gjc->idbuf[0];
285}
286
287static void gjab_send(gjconn gjc, xmlnode x)
288{
289        if (gjc && gjc->state != JCONN_STATE_OFF) {
290                char *buf = xmlnode2str(x);
291                if (!buf)
292                        return;
293                else if (gjc->ssl)
294                        ssl_write(gjc->ssl, buf, strlen(buf));
295                else
296                        write(gjc->fd, buf, strlen(buf));
297        }
298}
299
300static void gjab_send_raw(gjconn gjc, const char *str)
301{
302        if (gjc && gjc->state != JCONN_STATE_OFF) {
303                int len;
304               
305                /*
306                 * JFIXME: No error detection?!?!
307                 */
308                if (gjc->ssl)
309                        len = ssl_write(gjc->ssl, str, strlen(str));
310                else
311                        len = write(gjc->fd, str, strlen(str));
312                       
313                if(len < 0) {
314                        /* Do NOT write to stdout/stderr directly, IRC clients
315                           might get confused, and we don't want that...
316                        fprintf(stderr, "DBG: Problem sending.  Error: %d\n", errno);
317                        fflush(stderr); */
318                }
319        }
320}
321
322static void gjab_reqroster(gjconn gjc)
323{
324        xmlnode x;
325
326        x = jutil_iqnew(JPACKET__GET, NS_ROSTER);
327        xmlnode_put_attrib(x, "id", gjab_getid(gjc));
328
329        gjab_send(gjc, x);
330        xmlnode_free(x);
331}
332
333static void gjab_reqauth(gjconn gjc)
334{
335        xmlnode x, y, z;
336        char *user;
337
338        if (!gjc)
339                return;
340
341        x = jutil_iqnew(JPACKET__GET, NS_AUTH);
342        xmlnode_put_attrib(x, "id", IQID_AUTH);
343        y = xmlnode_get_tag(x, "query");
344
345        user = gjc->user->user;
346
347        if (user) {
348                z = xmlnode_insert_tag(y, "username");
349                xmlnode_insert_cdata(z, user, -1);
350        }
351
352        gjab_send(gjc, x);
353        xmlnode_free(x);
354}
355
356static void gjab_auth(gjconn gjc)
357{
358        xmlnode x, y, z;
359        char *hash, *user;
360
361        if (!gjc)
362                return;
363
364        x = jutil_iqnew(JPACKET__SET, NS_AUTH);
365        xmlnode_put_attrib(x, "id", IQID_AUTH);
366        y = xmlnode_get_tag(x, "query");
367
368        user = gjc->user->user;
369
370        if (user) {
371                z = xmlnode_insert_tag(y, "username");
372                xmlnode_insert_cdata(z, user, -1);
373        }
374
375        z = xmlnode_insert_tag(y, "resource");
376        xmlnode_insert_cdata(z, gjc->user->resource, -1);
377
378        if (gjc->sid) {
379                z = xmlnode_insert_tag(y, "digest");
380                hash = pmalloc(x->p, strlen(gjc->sid) + strlen(gjc->pass) + 1);
381                strcpy(hash, gjc->sid);
382                strcat(hash, gjc->pass);
383                hash = shahash(hash);
384                xmlnode_insert_cdata(z, hash, 40);
385        } else {
386                z = xmlnode_insert_tag(y, "password");
387                xmlnode_insert_cdata(z, gjc->pass, -1);
388        }
389
390        gjab_send(gjc, x);
391        xmlnode_free(x);
392
393        return;
394}
395
396static void gjab_recv(gjconn gjc)
397{
398        static char buf[4096];
399        int len;
400
401        if (!gjc || gjc->state == JCONN_STATE_OFF)
402                return;
403       
404        if (gjc->ssl)
405                len = ssl_read(gjc->ssl, buf, sizeof(buf) - 1);
406        else
407                len = read(gjc->fd, buf, sizeof(buf) - 1);
408       
409        if (len > 0) {
410                struct jabber_data *jd = GJ_GC(gjc)->proto_data;
411                buf[len] = '\0';
412                XML_Parse(gjc->parser, buf, len, 0);
413                if (jd->die)
414                        signoff(GJ_GC(gjc));
415        } else if (len < 0 || errno != EAGAIN) {
416                STATE_EVT(JCONN_STATE_OFF)
417        }
418}
419
420static void startElement(void *userdata, const char *name, const char **attribs)
421{
422        xmlnode x;
423        gjconn gjc = (gjconn) userdata;
424
425        if (gjc->current) {
426                /* Append the node to the current one */
427                x = xmlnode_insert_tag(gjc->current, name);
428                xmlnode_put_expat_attribs(x, attribs);
429
430                gjc->current = x;
431        } else {
432                x = xmlnode_new_tag(name);
433                xmlnode_put_expat_attribs(x, attribs);
434                if (strcmp(name, "stream:stream") == 0) {
435                        /* special case: name == stream:stream */
436                        /* id attrib of stream is stored for digest auth */
437                        gjc->sid = g_strdup(xmlnode_get_attrib(x, "id"));
438                        /* STATE_EVT(JCONN_STATE_AUTH) */
439                        xmlnode_free(x);
440                } else {
441                        gjc->current = x;
442                }
443        }
444}
445
446static void endElement(void *userdata, const char *name)
447{
448        gjconn gjc = (gjconn) userdata;
449        xmlnode x;
450        jpacket p;
451
452        if (gjc->current == NULL) {
453                /* we got </stream:stream> */
454                STATE_EVT(JCONN_STATE_OFF)
455                    return;
456        }
457
458        x = xmlnode_get_parent(gjc->current);
459
460        if (!x) {
461                /* it is time to fire the event */
462                p = jpacket_new(gjc->current);
463
464                if (gjc->on_packet)
465                        (gjc->on_packet) (gjc, p);
466                else
467                        xmlnode_free(gjc->current);
468        }
469
470        gjc->current = x;
471}
472
473static void jabber_callback(gpointer data, gint source, GaimInputCondition condition)
474{
475        struct gaim_connection *gc = (struct gaim_connection *)data;
476        struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
477
478        gjab_recv(jd->gjc);
479}
480
481static void charData(void *userdata, const char *s, int slen)
482{
483        gjconn gjc = (gjconn) userdata;
484
485        if (gjc->current)
486                xmlnode_insert_cdata(gjc->current, s, slen);
487}
488
489static void gjab_connected(gpointer data, gint source, GaimInputCondition cond)
490{
491        xmlnode x;
492        char *t, *t2;
493        struct gaim_connection *gc = data;
494        struct jabber_data *jd;
495        gjconn gjc;
496
497        if (!g_slist_find(get_connections(), gc)) {
498                closesocket(source);
499                return;
500        }
501
502        jd = gc->proto_data;
503        gjc = jd->gjc;
504
505        if (gjc->fd != source)
506                gjc->fd = source;
507
508        if (source == -1) {
509                STATE_EVT(JCONN_STATE_OFF)
510                return;
511        }
512
513        gjc->state = JCONN_STATE_CONNECTED;
514        STATE_EVT(JCONN_STATE_CONNECTED)
515
516        /* start stream */
517        x = jutil_header(NS_CLIENT, gjc->user->server);
518        t = xmlnode2str(x);
519        /* this is ugly, we can create the string here instead of jutil_header */
520        /* what do you think about it? -madcat */
521        t2 = strstr(t, "/>");
522        *t2++ = '>';
523        *t2 = '\0';
524        gjab_send_raw(gjc, "<?xml version='1.0'?>");
525        gjab_send_raw(gjc, t);
526        xmlnode_free(x);
527
528        gjc->state = JCONN_STATE_ON;
529        STATE_EVT(JCONN_STATE_ON);
530
531        gc = GJ_GC(gjc);
532        gc->inpa = gaim_input_add(gjc->fd, GAIM_INPUT_READ, jabber_callback, gc);
533}
534
535static void gjab_connected_ssl(gpointer data, void *source, GaimInputCondition cond)
536{
537        struct gaim_connection *gc = data;
538        struct jabber_data *jd;
539        gjconn gjc;
540       
541        jd = gc->proto_data;
542        gjc = jd->gjc;
543       
544        if (source == NULL) {
545                STATE_EVT(JCONN_STATE_OFF)
546                return;
547        }
548       
549        if (!g_slist_find(get_connections(), gc)) {
550                ssl_disconnect(source);
551                return;
552        }
553       
554        gjab_connected(data, gjc->fd, cond);
555}
556
557static void gjab_start(gjconn gjc)
558{
559        struct aim_user *user;
560        int port = -1, ssl = 0;
561        char *server = NULL, *s;
562
563        if (!gjc || gjc->state != JCONN_STATE_OFF)
564                return;
565
566        user = GJ_GC(gjc)->user;
567        if (*user->proto_opt[0]) {
568                /* If there's a dot, assume there's a hostname in the beginning */
569                if (strchr(user->proto_opt[0], '.')) {
570                        server = g_strdup(user->proto_opt[0]);
571                        if ((s = strchr(server, ':')))
572                                *s = 0;
573                }
574               
575                /* After the hostname, there can be a port number */
576                s = strchr(user->proto_opt[0], ':');
577                if (s && isdigit(s[1]))
578                        sscanf(s + 1, "%d", &port);
579               
580                /* And if there's the string ssl, the user wants an SSL-connection */
581                if (strstr(user->proto_opt[0], ":ssl") || g_strcasecmp(user->proto_opt[0], "ssl") == 0)
582                        ssl = 1;
583        }
584       
585        if (port == -1 && !ssl)
586                port = DEFAULT_PORT;
587        else if (port == -1 && ssl)
588                port = DEFAULT_PORT_SSL;
[027d2eb]589        else if (port < JABBER_PORT_MIN || port > JABBER_PORT_MAX) {
590                serv_got_crap(GJ_GC(gjc), "For security reasons, the Jabber port number must be in the %d-%d range.", JABBER_PORT_MIN, JABBER_PORT_MAX);
[626b446]591                STATE_EVT(JCONN_STATE_OFF)
592                return;
593        }
[b7d3cc34]594       
595        if (server == NULL)
596                server = g_strdup(gjc->user->server);
597
598        gjc->parser = XML_ParserCreate(NULL);
599        XML_SetUserData(gjc->parser, (void *)gjc);
600        XML_SetElementHandler(gjc->parser, startElement, endElement);
601        XML_SetCharacterDataHandler(gjc->parser, charData);
602       
603        if (ssl) {
604                if ((gjc->ssl = ssl_connect(server, port, gjab_connected_ssl, GJ_GC(gjc))))
605                        gjc->fd = ssl_getfd(gjc->ssl);
606                else
607                        gjc->fd = -1;
608        } else {
609                gjc->fd = proxy_connect(server, port, gjab_connected, GJ_GC(gjc));
610        }
611       
612        g_free(server);
613       
614        if (!user->gc || (gjc->fd < 0)) {
615                STATE_EVT(JCONN_STATE_OFF)
616                return;
617        }
618}
619
620/*
621 * Find existing/active Jabber chat
622 */
623static struct jabber_chat *find_existing_chat(struct gaim_connection *gc, jid chat)
624{
625        GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
626        struct jabber_chat *jc = NULL;
627
628        while (jcs) {
629                jc = jcs->data;
630                if (jc->state == JCS_ACTIVE && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER))
631                        break;
632                jc = NULL;
633                jcs = jcs->next;
634        }
635
636        return jc;
637}
638
639/*
640 * Find pending chat
641 */
642static struct jabber_chat *find_pending_chat(struct gaim_connection *gc, jid chat)
643{
644        GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
645        struct jabber_chat *jc = NULL;
646
647        while (jcs) {
648                jc = jcs->data;
649                if (jc->state == JCS_PENDING && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER))
650                        break;
651                jc = NULL;
652                jcs = jcs->next;
653        }
654
655        return jc;
656}
657
658static gboolean find_chat_buddy(struct conversation *b, char *name)
659{
660        GList *m = b->in_room;
661
662        while (m) {
663                if (!strcmp(m->data, name))
664                        return TRUE;
665                m = m->next;
666        }
667
668        return FALSE;
669}
670
671/*
672 * Remove a buddy from the (gaim) buddylist (if he's on it)
673 */
674static void jabber_remove_gaim_buddy(struct gaim_connection *gc, char *buddyname)
675{
676        struct buddy *b;
677
678        if ((b = find_buddy(gc, buddyname)) != NULL) {
679                /* struct group *group;
680
681                group = find_group_by_buddy(gc, buddyname);
682                remove_buddy(gc, group, b); */
683                jabber_remove_buddy(gc, b->name, JABBER_GROUP);
684        }
685}
686
687/*
688 * keep track of away msg same as yahoo plugin
689 */
690static void jabber_track_away(gjconn gjc, jpacket p, char *name, char *type)
691{
692        struct jabber_data *jd = GJ_GC(gjc)->proto_data;
693        gpointer val = g_hash_table_lookup(jd->hash, name);
694        char *show;
695        char *vshow = NULL;
696        char *status = NULL;
697        char *msg = NULL;
698
699        if (type && (g_strcasecmp(type, "unavailable") == 0)) {
700                vshow = _("Unavailable");
701        } else {
702                if((show = xmlnode_get_tag_data(p->x, "show")) != NULL) {
703                        if (!g_strcasecmp(show, "away")) {
704                                vshow = _("Away");
705                        } else if (!g_strcasecmp(show, "chat")) {
706                                vshow = _("Online");
707                        } else if (!g_strcasecmp(show, "xa")) {
708                                vshow = _("Extended Away");
709                        } else if (!g_strcasecmp(show, "dnd")) {
710                                vshow = _("Do Not Disturb");
711                        }
712                }
713        }
714
715        status = xmlnode_get_tag_data(p->x, "status");
716
717        if(vshow != NULL || status != NULL ) {
718                /* kinda hokey, but it works :-) */
719                msg = g_strdup_printf("%s%s%s",
720                        (vshow == NULL? "" : vshow),
721                        (vshow == NULL || status == NULL? "" : ": "),
722                        (status == NULL? "" : status));
723        } else {
724                msg = g_strdup(_("Online"));
725        }
726
727        if (val) {
728                g_free(val);
729                g_hash_table_insert(jd->hash, name, msg);
730        } else {
731                g_hash_table_insert(jd->hash, g_strdup(name), msg);
732        }
733}
734
735static time_t iso8601_to_time(char *timestamp)
736{
737        struct tm t;
738        time_t retval = 0;
739
740        if(sscanf(timestamp,"%04d%02d%02dT%02d:%02d:%02d",
741                &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec))
742        {
743                t.tm_year -= 1900;
744                t.tm_mon -= 1;
745                t.tm_isdst = 0;
746                retval = mktime(&t);
747#               ifdef HAVE_TM_GMTOFF
748                        retval += t.tm_gmtoff;
749#               else
750#                       ifdef HAVE_TIMEZONE
751                                tzset();        /* making sure */
752                                retval -= timezone;
753#                       endif
754#               endif
755        }
756
757        return retval;
758}
759
760static void jabber_handlemessage(gjconn gjc, jpacket p)
761{
[43e3368]762        xmlnode y, xmlns, z;
[b7d3cc34]763        time_t time_sent = time(NULL);
764
[43e3368]765        char *from = NULL, *msg = NULL, *type = NULL;
[b7d3cc34]766        char m[BUF_LONG * 2];
767
768        type = xmlnode_get_attrib(p->x, "type");
769
770        z = xmlnode_get_firstchild(p->x);
771
772        while(z)
773        {
774           if(NSCHECK(z,NS_DELAY))
775           {
776              char *timestamp = xmlnode_get_attrib(z,"stamp");
777              time_sent = iso8601_to_time(timestamp);
778           }
779           z = xmlnode_get_nextsibling(z);
780        }
781
782        if (!type || !g_strcasecmp(type, "normal") || !g_strcasecmp(type, "chat")) {
783
784                /* XXX namespaces could be handled better. (mid) */
785                if ((xmlns = xmlnode_get_tag(p->x, "x")))
786                        type = xmlnode_get_attrib(xmlns, "xmlns");
787
788                from = jid_full(p->from);
789                /*
790                if ((y = xmlnode_get_tag(p->x, "html"))) {
791                        msg = xmlnode_get_data(y);
792                } else
793                */
794                if ((y = xmlnode_get_tag(p->x, "body"))) {
795                        msg = xmlnode_get_data(y);
796                }
797
798
799                if (!from)
800                        return;
801
802                if (type && !g_strcasecmp(type, "jabber:x:conference")) {
[43e3368]803                        /* do nothing */
[b7d3cc34]804                } else if (msg) { /* whisper */
805                        struct jabber_chat *jc;
806                        g_snprintf(m, sizeof(m), "%s", msg);
807                        if (((jc = find_existing_chat(GJ_GC(gjc), p->from)) != NULL) && jc->b)
808                                serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 1, m, time_sent);
809                        else {
810                                int flags = 0;
[43e3368]811                               
812                                if(p->from->user) {
813                                    from = g_strdup_printf("%s@%s", p->from->user, p->from->server);
814                                } else {
815                                    /* server message? */
816                                    from = g_strdup(p->from->server);
817                                }
818                                serv_got_im(GJ_GC(gjc), from, m, flags, time_sent, -1);
819                                g_free(from);
[b7d3cc34]820                        }
821                }
822
823        } else if (!g_strcasecmp(type, "error")) {
824                if ((y = xmlnode_get_tag(p->x, "error"))) {
825                        type = xmlnode_get_attrib(y, "code");
826                        msg = xmlnode_get_data(y);
827                }
828
829                if (msg) {
830                        from = g_strdup_printf("Error %s", type ? type : "");
831                        do_error_dialog(GJ_GC(gjc), msg, from);
832                        g_free(from);
833                }
[43e3368]834        } else if (!g_strcasecmp(type, "headline")) {
835                char *subject, *body, *url;
[b7d3cc34]836               
[43e3368]837                y = xmlnode_get_tag( p->x, "body" );
838                body = y ? g_strdup( xmlnode_get_data( y ) ) : NULL;
839               
840                y = xmlnode_get_tag( p->x, "subject" );
841                subject = y ? g_strdup( xmlnode_get_data( y ) ) : NULL;
842               
843                url = NULL;
844                z = xmlnode_get_firstchild(p->x);
845                while( z )
846                {
847                        char *xtype = xmlnode_get_attrib( z, "xmlns" );
848                       
849                        if( xtype && g_strcasecmp( xtype, "jabber:x:oob" ) == 0 &&
850                                     ( y = xmlnode_get_tag( z, "url" ) ) )
851                        {
852                                url = g_strdup( xmlnode_get_data( y ) );
853                                break;
[b7d3cc34]854                        }
[43e3368]855                       
856                        z = xmlnode_get_nextsibling( z );
[b7d3cc34]857                }
[43e3368]858               
859                g_snprintf( m, BUF_LONG, "Subject: %s\nURL: %s\nMessage:\n%s", subject ? subject : "(none)",
860                                     url ? url : "(none)", body ? body : "(none)" );
[b7d3cc34]861
[43e3368]862                if( p->from->user )
863                        from = g_strdup_printf( "%s@%s", p->from->user, p->from->server );
864                else
865                        from = g_strdup( p->from->server );
866               
867                serv_got_im( GJ_GC(gjc), from, m, 0, time_sent, -1 );
868               
869                g_free( from );
870                g_free( subject );
871                g_free( body );
872                g_free( url );
[b7d3cc34]873        }
874}
875           
876static void jabber_handlepresence(gjconn gjc, jpacket p)
877{
878        char *to, *from, *type;
879        struct buddy *b = NULL;
880        jid who;
881        char *buddy;
882        xmlnode y;
883        char *show;
884        int state = 0;
885        GSList *resources;
886        char *res;
887        struct conversation *cnv = NULL;
888        struct jabber_chat *jc = NULL;
889
890        to = xmlnode_get_attrib(p->x, "to");
891        from = xmlnode_get_attrib(p->x, "from");
892        type = xmlnode_get_attrib(p->x, "type");
893       
894        if (type && g_strcasecmp(type, "error") == 0) {
895                return;
896        }
897        else if ((y = xmlnode_get_tag(p->x, "show"))) {
898                show = xmlnode_get_data(y);
899                if (!show) {
900                        state = 0;
901                } else if (!g_strcasecmp(show, "away")) {
902                        state = UC_AWAY;
903                } else if (!g_strcasecmp(show, "chat")) {
904                        state = UC_CHAT;
905                } else if (!g_strcasecmp(show, "xa")) {
906                        state = UC_XA;
907                } else if (!g_strcasecmp(show, "dnd")) {
908                        state = UC_DND;
909                }
910        } else {
911                state = 0;
912        }
913
914        who = jid_new(gjc->p, from);
915        if (who->user == NULL) {
916                /* FIXME: transport */
917                return;
918        }
919
920        buddy = g_strdup_printf("%s@%s", who->user, who->server);
921
922        /* um. we're going to check if it's a chat. if it isn't, and there are pending
923         * chats, create the chat. if there aren't pending chats and we don't have the
924         * buddy on our list, simply bail out. */
925        if ((cnv = NULL) == NULL) {
926                static int i = 0x70;
927                if ((jc = find_pending_chat(GJ_GC(gjc), who)) != NULL) {
928                        jc->b = cnv = serv_got_joined_chat(GJ_GC(gjc), i++, who->user);
929                        jc->id = jc->b->id;
930                        jc->state = JCS_ACTIVE;
931                } else if ((b = find_buddy(GJ_GC(gjc), buddy)) == NULL) {
932                        g_free(buddy);
933                        return;
934                }
935        }
936
937        if (!cnv) {
938                resources = b->proto_data;
939                res = who->resource;
940                if (res)
941                        while (resources) {
942                                if (!strcmp(res, resources->data))
943                                        break;
944                                resources = resources->next;
945                        }
946
947                /* keep track of away msg same as yahoo plugin */
948                jabber_track_away(gjc, p, normalize(b->name), type);
949
950                if (type && (g_strcasecmp(type, "unavailable") == 0)) {
951                        if (resources) {
952                                g_free(resources->data);
953                                b->proto_data = g_slist_remove(b->proto_data, resources->data);
954                        }
955                        if (!b->proto_data) {
956                                serv_got_update(GJ_GC(gjc), buddy, 0, 0, 0, 0, 0, 0);
957                        }
958                } else {
959                        if (!resources) {
960                                b->proto_data = g_slist_append(b->proto_data, g_strdup(res));
961                        }
962
963                        serv_got_update(GJ_GC(gjc), buddy, 1, 0, b->signon, b->idle, state, 0);
964
965                }
966        } else {
967                if (who->resource) {
968                        char *buf;
969
970                        buf = g_strdup_printf("%s@%s/%s", who->user, who->server, who->resource);
971                        jabber_track_away(gjc, p, buf, type);
972                        g_free(buf);
973
974                        if (type && !g_strcasecmp(type, "unavailable")) {
975                                struct jabber_data *jd;
976                                if (!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) {
977                                        g_free(buddy);
978                                        return;
979                                }
980                                jd = jc->gc->proto_data;
981                                /* if it's not ourselves...*/
982                                if (strcmp(who->resource, jc->Jid->resource) && jc->b) {
983                                        remove_chat_buddy(jc->b, who->resource, NULL);
984                                        g_free(buddy);
985                                        return;
986                                }
987
988                                jc->state = JCS_CLOSED;
989                                serv_got_chat_left(GJ_GC(gjc), jc->id);
990                                /*
991                                 * TBD: put back some day?
992                                jd->chats = g_slist_remove(jd->chats, jc);
993                                g_free(jc);
994                                 */
995                        } else {
996                                if ((!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) || !jc->b) {
997                                        g_free(buddy);
998                                        return;
999                                }
1000                                if (!find_chat_buddy(jc->b, who->resource)) {
1001                                        add_chat_buddy(jc->b, who->resource);
1002                                }
1003                        }
1004                }
1005        }
1006
1007        g_free(buddy);
1008
1009        return;
1010}
1011
1012/*
1013 * Used only by Jabber accept/deny add stuff just below
1014 */
1015struct jabber_add_permit {
1016        gjconn gjc;
1017        gchar *user;
1018};
1019
1020/*
1021 * Common part for Jabber accept/deny adds
1022 *
1023 * "type" says whether we'll permit/deny the subscribe request
1024 */
1025static void jabber_accept_deny_add(struct jabber_add_permit *jap, const char *type)
1026{
1027        xmlnode g = xmlnode_new_tag("presence");
1028
1029        xmlnode_put_attrib(g, "to", jap->user);
1030        xmlnode_put_attrib(g, "type", type);
1031        gjab_send(jap->gjc, g);
1032
1033        xmlnode_free(g);
1034}
1035
1036/*
1037 * Callback from "accept" in do_ask_dialog() invoked by jabber_handles10n()
1038 */
1039static void jabber_accept_add(gpointer w, struct jabber_add_permit *jap)
1040{
1041        jabber_accept_deny_add(jap, "subscribed");
1042        /*
1043         * If we don't already have the buddy on *our* buddylist,
1044         * ask if we want him or her added.
1045         */
1046        if(find_buddy(GJ_GC(jap->gjc), jap->user) == NULL) {
1047                show_got_added(GJ_GC(jap->gjc), NULL, jap->user, NULL, NULL);
1048        }
1049        g_free(jap->user);
1050        g_free(jap);
1051}
1052
1053/*
1054 * Callback from "deny/cancel" in do_ask_dialog() invoked by jabber_handles10n()
1055 */
1056static void jabber_deny_add(gpointer w, struct jabber_add_permit *jap)
1057{
1058        jabber_accept_deny_add(jap, "unsubscribed");
1059        g_free(jap->user);
1060        g_free(jap);
1061}
1062
1063/*
1064 * Handle subscription requests
1065 */
1066static void jabber_handles10n(gjconn gjc, jpacket p)
1067{
1068        xmlnode g;
1069        char *Jid = xmlnode_get_attrib(p->x, "from");
1070        char *type = xmlnode_get_attrib(p->x, "type");
1071
1072        g = xmlnode_new_tag("presence");
1073        xmlnode_put_attrib(g, "to", Jid);
1074
1075        if (!strcmp(type, "subscribe")) {
1076                /*
1077                 * A "subscribe to us" request was received - put up the approval dialog
1078                 */
1079                struct jabber_add_permit *jap = g_new0(struct jabber_add_permit, 1);
[5c09a59]1080                gchar *msg = g_strdup_printf(_("The user %s wants to add you to his/her buddy list."),
[b7d3cc34]1081                                Jid);
1082
1083                jap->gjc = gjc;
1084                jap->user = g_strdup(Jid);
1085                do_ask_dialog(GJ_GC(gjc), msg, jap, jabber_accept_add, jabber_deny_add);
1086
1087                g_free(msg);
1088                xmlnode_free(g);        /* Never needed it here anyway */
1089                return;
1090
1091        } else if (!strcmp(type, "unsubscribe")) {
1092                /*
1093                 * An "unsubscribe to us" was received - simply "approve" it
1094                 */
1095                xmlnode_put_attrib(g, "type", "unsubscribed");
1096        } else {
1097                /*
1098                 * Did we attempt to subscribe to somebody and they do not exist?
1099                 */
1100                if (!strcmp(type, "unsubscribed")) {
1101                        xmlnode y;
1102                        char *status;
1103                        if((y = xmlnode_get_tag(p->x, "status")) && (status = xmlnode_get_data(y)) &&
1104                                        !strcmp(status, "Not Found")) {
1105                                char *msg = g_strdup_printf("%s: \"%s\"", _("No such user"), 
1106                                        xmlnode_get_attrib(p->x, "from"));
1107                                do_error_dialog(GJ_GC(gjc), msg, _("Jabber Error"));
1108                                g_free(msg);
1109                        }
1110                }
1111
1112                xmlnode_free(g);
1113                return;
1114        }
1115
1116        gjab_send(gjc, g);
1117        xmlnode_free(g);
1118}
1119
1120/*
1121 * Pending subscription to a buddy?
1122 */
1123#define BUD_SUB_TO_PEND(sub, ask) ((!g_strcasecmp((sub), "none") || !g_strcasecmp((sub), "from")) && \
1124                                        (ask) != NULL && !g_strcasecmp((ask), "subscribe"))
1125
1126/*
1127 * Subscribed to a buddy?
1128 */
1129#define BUD_SUBD_TO(sub, ask) ((!g_strcasecmp((sub), "to") || !g_strcasecmp((sub), "both")) && \
1130                                        ((ask) == NULL || !g_strcasecmp((ask), "subscribe")))
1131
1132/*
1133 * Pending unsubscription to a buddy?
1134 */
1135#define BUD_USUB_TO_PEND(sub, ask) ((!g_strcasecmp((sub), "to") || !g_strcasecmp((sub), "both")) && \
1136                                        (ask) != NULL && !g_strcasecmp((ask), "unsubscribe"))
1137
1138/*
1139 * Unsubscribed to a buddy?
1140 */
1141#define BUD_USUBD_TO(sub, ask) ((!g_strcasecmp((sub), "none") || !g_strcasecmp((sub), "from")) && \
1142                                        ((ask) == NULL || !g_strcasecmp((ask), "unsubscribe")))
1143
1144/*
1145 * If a buddy is added or removed from the roster on another resource
1146 * jabber_handlebuddy is called
1147 *
1148 * Called with roster item node.
1149 */
1150static void jabber_handlebuddy(gjconn gjc, xmlnode x)
1151{
1152        xmlnode g;
1153        char *Jid, *name, *sub, *ask;
1154        jid who;
1155        struct buddy *b = NULL;
1156        char *buddyname, *groupname = NULL;
1157
1158        Jid = xmlnode_get_attrib(x, "jid");
1159        name = xmlnode_get_attrib(x, "name");
1160        sub = xmlnode_get_attrib(x, "subscription");
1161        ask = xmlnode_get_attrib(x, "ask");
1162        who = jid_new(gjc->p, Jid);
1163
1164        /* JFIXME: jabber_handleroster() had a "FIXME: transport" at this
1165         * equivilent point.  So...
1166         *
1167         * We haven't allocated any memory or done anything interesting to
1168         * this point, so we'll violate Good Coding Structure here by
1169         * simply bailing out.
1170         */
1171        if (!who || !who->user) {
1172                return;
1173        }
1174
1175        buddyname = g_strdup_printf("%s@%s", who->user, who->server);
1176
1177        if((g = xmlnode_get_tag(x, "group")) != NULL) {
1178                groupname = xmlnode_get_data(g);
1179        }
1180
1181        /*
1182         * Add or remove a buddy?  Change buddy's alias or group?
1183         */
1184        if (BUD_SUB_TO_PEND(sub, ask) || BUD_SUBD_TO(sub, ask)) {
1185                if ((b = find_buddy(GJ_GC(gjc), buddyname)) == NULL) {
1186                        add_buddy(GJ_GC(gjc), groupname ? groupname : _("Buddies"), buddyname,
1187                                name ? name : buddyname);
1188                } else {
1189                        /* struct group *c_grp = find_group_by_buddy(GJ_GC(gjc), buddyname); */
1190
1191                        /*
1192                         * If the buddy's in a new group or his/her alias is changed...
1193                         */
1194                        if(groupname) {
1195                                int present = b->present;       /* save presence state */
1196                                int uc = b->uc;                 /* and away state (?) */
1197                                int idle = b->idle;
1198                                int signon = b->signon;
1199
1200                                /*
1201                                 * seems rude, but it seems to be the only way...
1202                                 */
1203                                /* remove_buddy(GJ_GC(gjc), c_grp, b); */
1204                                jabber_remove_buddy(GJ_GC(gjc), b->name, JABBER_GROUP);
1205                               
1206                                add_buddy(GJ_GC(gjc), groupname, buddyname,
1207                                        name ? name : buddyname);
1208                                if(present) {
1209                                        serv_got_update(GJ_GC(gjc), buddyname, 1, 0, signon, idle, uc, 0);
1210                                }
1211                        } else if(name != NULL && strcmp(b->show, name)) {
1212                                strncpy(b->show, name, BUDDY_ALIAS_MAXLEN);
1213                                b->show[BUDDY_ALIAS_MAXLEN - 1] = '\0'; /* cheap safety feature */
1214                                serv_buddy_rename(GJ_GC(gjc), buddyname, b->show);
1215                        }
1216                }
1217        }  else if (BUD_USUB_TO_PEND(sub, ask) || BUD_USUBD_TO(sub, ask) || !g_strcasecmp(sub, "remove")) {
1218                jabber_remove_gaim_buddy(GJ_GC(gjc), buddyname);
1219        }
1220        g_free(buddyname);
1221
1222}
1223
1224static void jabber_handleroster(gjconn gjc, xmlnode querynode)
1225{
1226        xmlnode x;
1227
1228        x = xmlnode_get_firstchild(querynode);
1229        while (x) {
1230                jabber_handlebuddy(gjc, x);
1231                x = xmlnode_get_nextsibling(x);
1232        }
1233
1234        x = jutil_presnew(0, NULL, "Online");
1235        gjab_send(gjc, x);
1236        xmlnode_free(x);
1237}
1238
1239static void jabber_handleauthresp(gjconn gjc, jpacket p)
1240{
1241        if (jpacket_subtype(p) == JPACKET__RESULT) {
1242                if (xmlnode_has_children(p->x)) {
1243                        xmlnode query = xmlnode_get_tag(p->x, "query");
1244                        set_login_progress(GJ_GC(gjc), 4, _("Authenticating"));
1245                        if (!xmlnode_get_tag(query, "digest")) {
1246                                g_free(gjc->sid);
1247                                gjc->sid = NULL;
1248                        }
1249                        gjab_auth(gjc);
1250                } else {
[1fa6a23]1251                        gjab_reqroster(gjc);
[b7d3cc34]1252                        account_online(GJ_GC(gjc));
[1fa6a23]1253                       
[b7d3cc34]1254                        ((struct jabber_data *)GJ_GC(gjc)->proto_data)->did_import = TRUE;
1255                }
1256        } else {
1257                xmlnode xerr;
1258                char *errmsg = NULL;
1259                int errcode = 0;
1260                struct jabber_data *jd = GJ_GC(gjc)->proto_data;
1261
1262                xerr = xmlnode_get_tag(p->x, "error");
1263                if (xerr) {
1264                        char msg[BUF_LONG];
1265                        errmsg = xmlnode_get_data(xerr);
1266                        if (xmlnode_get_attrib(xerr, "code")) {
1267                                errcode = atoi(xmlnode_get_attrib(xerr, "code"));
1268                                g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg ? errmsg : "Unknown error");
1269                        } else
1270                                g_snprintf(msg, sizeof(msg), "%s", errmsg);
1271                        hide_login_progress(GJ_GC(gjc), msg);
1272                } else {
1273                        hide_login_progress(GJ_GC(gjc), _("Unknown login error"));
1274                }
1275
1276                jd->die = TRUE;
1277        }
1278}
1279
1280static void jabber_handleversion(gjconn gjc, xmlnode iqnode) {
1281        xmlnode querynode, x;
1282        char *id, *from;
1283        char os[1024];
1284#ifndef _WIN32
1285        struct utsname osinfo;
1286
1287        uname(&osinfo);
1288        g_snprintf(os, sizeof os, "%s %s %s", osinfo.sysname, osinfo.release, osinfo.machine);
1289#else
1290        g_snprintf(os, sizeof os, "Windows %d %d", _winmajor, _winminor);
1291#endif
1292
1293
1294        id = xmlnode_get_attrib(iqnode, "id");
1295        from = xmlnode_get_attrib(iqnode, "from");
1296
1297        x = jutil_iqnew(JPACKET__RESULT, NS_VERSION);
1298
1299        xmlnode_put_attrib(x, "to", from);
1300        xmlnode_put_attrib(x, "id", id);
1301        querynode = xmlnode_get_tag(x, "query");
1302        xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "name"), PACKAGE, -1);
1303        xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "version"), BITLBEE_VERSION, -1);
1304        xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "os"), os, -1);
1305
1306        gjab_send(gjc, x);
1307
1308        xmlnode_free(x);
1309}
1310
1311static void jabber_handletime(gjconn gjc, xmlnode iqnode) {
1312        xmlnode querynode, x;
1313        char *id, *from;
1314        time_t now_t; 
1315        struct tm *now;
1316        char buf[1024];
1317
1318        time(&now_t);
1319        now = localtime(&now_t);
1320
1321        id = xmlnode_get_attrib(iqnode, "id");
1322        from = xmlnode_get_attrib(iqnode, "from");
1323
1324        x = jutil_iqnew(JPACKET__RESULT, NS_TIME);
1325
1326        xmlnode_put_attrib(x, "to", from);
1327        xmlnode_put_attrib(x, "id", id);
1328        querynode = xmlnode_get_tag(x, "query");
1329
1330        strftime(buf, 1024, "%Y%m%dT%T", now);
1331        xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "utc"), buf, -1);
1332        strftime(buf, 1024, "%Z", now);
1333        xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "tz"), buf, -1);
1334        strftime(buf, 1024, "%d %b %Y %T", now);
1335        xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "display"), buf, -1);
1336       
1337        gjab_send(gjc, x);
1338
1339        xmlnode_free(x);
1340}
1341
1342static void jabber_handlelast(gjconn gjc, xmlnode iqnode) {
1343        xmlnode x, querytag;
1344        char *id, *from;
1345        struct jabber_data *jd = GJ_GC(gjc)->proto_data;
1346        char idle_time[32];
1347       
1348        id = xmlnode_get_attrib(iqnode, "id");
1349        from = xmlnode_get_attrib(iqnode, "from");
1350
1351        x = jutil_iqnew(JPACKET__RESULT, "jabber:iq:last");
1352
1353        xmlnode_put_attrib(x, "to", from);
1354        xmlnode_put_attrib(x, "id", id);
1355        querytag = xmlnode_get_tag(x, "query");
1356        g_snprintf(idle_time, sizeof idle_time, "%ld", jd->idle ? time(NULL) - jd->idle : 0);
1357        xmlnode_put_attrib(querytag, "seconds", idle_time);
1358
1359        gjab_send(gjc, x);
1360        xmlnode_free(x);
1361}
1362
1363/*
1364 * delete == TRUE: delete found entry
1365 *
1366 * returns pointer to (local) copy of value if found, NULL otherwise
1367 *
1368 * Note: non-reentrant!  Local static storage re-used on subsequent calls.
1369 * If you're going to need to keep the returned value, make a copy!
1370 */
1371static gchar *jabber_track_queries(GHashTable *queries, gchar *key, gboolean delete)
1372{
1373        gpointer my_key, my_val;
1374        static gchar *ret_val = NULL;
1375
1376        if(ret_val != NULL) {
1377                g_free(ret_val);
1378                ret_val = NULL;
1379        }
1380
1381        /* self-protection */
1382        if(queries != NULL && key != NULL) {
1383                if(g_hash_table_lookup_extended(queries, key, &my_key, &my_val)) {
1384                        ret_val = g_strdup((gchar *) my_val);
1385                        if(delete) {
1386                                g_hash_table_remove(queries, key);
1387                                g_free(my_key);
1388                                g_free(my_val);
1389                        }
1390                }
1391        }
1392
1393        return(ret_val);
1394}
1395
1396static void jabber_handlepacket(gjconn gjc, jpacket p)
1397{
1398        char *id;
1399        switch (p->type) {
1400        case JPACKET_MESSAGE:
1401                jabber_handlemessage(gjc, p);
1402                break;
1403        case JPACKET_PRESENCE:
1404                jabber_handlepresence(gjc, p);
1405                break;
1406        case JPACKET_IQ:
1407                id = xmlnode_get_attrib(p->x, "id");
1408                if (id != NULL && !strcmp(id, IQID_AUTH)) {
1409                        jabber_handleauthresp(gjc, p);
1410                        break;
1411                }
1412
1413                if (jpacket_subtype(p) == JPACKET__SET) {
1414                        xmlnode querynode;
1415                        querynode = xmlnode_get_tag(p->x, "query");
1416                        if (NSCHECK(querynode, "jabber:iq:roster")) {
1417                                jabber_handlebuddy(gjc, xmlnode_get_firstchild(querynode));
1418                        }
1419                } else if (jpacket_subtype(p) == JPACKET__GET) {
1420                        xmlnode querynode;
1421                        querynode = xmlnode_get_tag(p->x, "query");
1422                        if (NSCHECK(querynode, NS_VERSION)) {
1423                                jabber_handleversion(gjc, p->x);
1424                        } else if (NSCHECK(querynode, NS_TIME)) {
1425                                jabber_handletime(gjc, p->x);
1426                        } else if (NSCHECK(querynode, "jabber:iq:last")) {
1427                                jabber_handlelast(gjc, p->x);
1428                        }
1429                } else if (jpacket_subtype(p) == JPACKET__RESULT) {
1430                        xmlnode querynode, vcard;
1431                        /* char *xmlns; */
1432                        char *from;
1433
1434                        /*
1435                         * TBD: ISTM maybe this part could use a serious re-work?
1436                         */
1437                        from = xmlnode_get_attrib(p->x, "from");
1438                        querynode = xmlnode_get_tag(p->x, "query");
1439                        vcard = xmlnode_get_tag(p->x, "vCard");
1440                        if (!vcard)
1441                                vcard = xmlnode_get_tag(p->x, "VCARD");
1442
1443                        if (NSCHECK(querynode, NS_ROSTER)) {
1444                                jabber_handleroster(gjc, querynode);
1445                        } else if (NSCHECK(querynode, NS_VCARD)) {
1446                                jabber_track_queries(gjc->queries, id, TRUE);   /* delete query track */
1447                                jabber_handlevcard(gjc, querynode, from);
1448                        } else if (vcard) {
1449                                jabber_track_queries(gjc->queries, id, TRUE);   /* delete query track */
1450                                jabber_handlevcard(gjc, vcard, from);
1451                        } else {
1452                                char *val;
1453
1454                                /* handle "null" query results */
1455                                if((val = jabber_track_queries(gjc->queries, id, TRUE)) != NULL) {
1456                                        if (!g_strncasecmp(val, "vcard", 5)) {
1457                                                jabber_handlevcard(gjc, NULL, from);
1458                                        }
1459
1460                                        /* No-op */
1461                                }
1462                        }
1463
1464                } else if (jpacket_subtype(p) == JPACKET__ERROR) {
1465                        xmlnode xerr;
1466                        char *from, *errmsg = NULL;
1467                        int errcode = 0;
1468
1469                        from = xmlnode_get_attrib(p->x, "from");
1470                        xerr = xmlnode_get_tag(p->x, "error");
1471                        if (xerr) {
1472                                errmsg = xmlnode_get_data(xerr);
1473                                if (xmlnode_get_attrib(xerr, "code"))
1474                                        errcode = atoi(xmlnode_get_attrib(xerr, "code"));
1475                        }
1476
1477                        from = g_strdup_printf("Error %d (%s)", errcode, from);
1478                        do_error_dialog(GJ_GC(gjc), errmsg, from);
1479                        g_free(from);
1480
1481                }
1482
1483                break;
1484        case JPACKET_S10N:
1485                jabber_handles10n(gjc, p);
1486                break;
1487        }
1488
1489        xmlnode_free(p->x);
1490
1491        return;
1492}
1493
1494static void jabber_handlestate(gjconn gjc, int state)
1495{
1496        switch (state) {
1497        case JCONN_STATE_OFF:
1498                if(gjc->was_connected) {
1499                        hide_login_progress_error(GJ_GC(gjc), _("Connection lost"));
1500                } else {
1501                        hide_login_progress(GJ_GC(gjc), _("Unable to connect"));
1502                }
1503                signoff(GJ_GC(gjc));
1504                break;
1505        case JCONN_STATE_CONNECTED:
1506                gjc->was_connected = 1;
1507                set_login_progress(GJ_GC(gjc), 2, _("Connected"));
1508                break;
1509        case JCONN_STATE_ON:
1510                set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method"));
1511                gjab_reqauth(gjc);
1512                break;
1513        }
1514        return;
1515}
1516
1517static void jabber_login(struct aim_user *user)
1518{
1519        struct gaim_connection *gc = new_gaim_conn(user);
1520        struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1);
1521        char *loginname = create_valid_jid(user->username, DEFAULT_SERVER, "BitlBee");
1522
1523        jd->hash = g_hash_table_new(g_str_hash, g_str_equal);
1524        jd->chats = NULL;       /* we have no chats yet */
1525
1526        set_login_progress(gc, 1, _("Connecting"));
1527
1528        if (!(jd->gjc = gjab_new(loginname, user->password, gc))) {
1529                g_free(loginname);
1530                hide_login_progress(gc, _("Unable to connect"));
1531                signoff(gc);
1532                return;
1533        }
1534
1535        g_free(loginname);
1536        gjab_state_handler(jd->gjc, jabber_handlestate);
1537        gjab_packet_handler(jd->gjc, jabber_handlepacket);
1538        jd->gjc->queries = g_hash_table_new(g_str_hash, g_str_equal);
1539        gjab_start(jd->gjc);
1540}
1541
1542static gboolean jabber_destroy_hash(gpointer key, gpointer val, gpointer data) {
1543        g_free(key);
1544        g_free(val);
1545        return TRUE;
1546}
1547
1548static gboolean jabber_free(gpointer data)
1549{
1550        struct jabber_data *jd = data;
1551
1552        if(jd->gjc != NULL) {
1553                gjab_delete(jd->gjc);
1554                g_free(jd->gjc->sid);
1555                jd->gjc = NULL;
1556        }
1557        g_free(jd);
1558
1559        return FALSE;
1560}
1561
1562static void jabber_close(struct gaim_connection *gc)
1563{
1564        struct jabber_data *jd = gc->proto_data;
1565
1566        if(jd) {
1567                GSList *jcs = jd->chats;
1568
1569                /* Free-up the jabber_chat struct allocs and the list */
1570                while (jcs) {
1571                        g_free(jcs->data);
1572                        jcs = jcs->next;
1573                }
1574                g_slist_free(jd->chats);
1575
1576                /* Free-up the away status memories and the list */
1577                if(jd->hash != NULL) {
1578                        g_hash_table_foreach_remove(jd->hash, jabber_destroy_hash, NULL);
1579                        g_hash_table_destroy(jd->hash);
1580                        jd->hash = NULL;
1581                }
1582
1583                /* Free-up the pending queries memories and the list */
1584                if(jd->gjc != NULL && jd->gjc->queries != NULL) {
1585                        g_hash_table_foreach_remove(jd->gjc->queries, jabber_destroy_hash, NULL);
1586                        g_hash_table_destroy(jd->gjc->queries);
1587                        jd->gjc->queries = NULL;
1588                }
1589        }
1590        if (gc->inpa)
1591                gaim_input_remove(gc->inpa);
1592
1593        if(jd) {
1594                g_timeout_add(50, jabber_free, jd);
1595                if(jd->gjc != NULL)
1596                        xmlnode_free(jd->gjc->current);
1597        }
1598        gc->proto_data = NULL;
1599}
1600
1601static int jabber_send_im(struct gaim_connection *gc, char *who, char *message, int len, int flags)
1602{
1603        xmlnode x, y;
1604        char *realwho;
1605        gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1606
1607        if (!who || !message)
1608                return 0;
1609
1610        x = xmlnode_new_tag("message");
1611        /* Bare username and "username" not the server itself? */
1612        if (!strchr(who, '@') && strcmp(who, gjc->user->server) != 0)
1613                realwho = g_strdup_printf("%s@%s", who, gjc->user->server);
1614        else
1615                realwho = g_strdup(who);
1616        xmlnode_put_attrib(x, "to", realwho);
1617        g_free(realwho);
1618
1619        xmlnode_insert_tag(x, "bitlbee");
1620        xmlnode_put_attrib(x, "type", "chat");
1621
1622        if (message && strlen(message)) {
1623                y = xmlnode_insert_tag(x, "body");
1624                xmlnode_insert_cdata(y, message, -1);
1625        }
1626
1627        gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1628        xmlnode_free(x);
1629        return 1;
1630}
1631
1632/*
1633 * Add/update buddy's roster entry on server
1634 */
1635static void jabber_roster_update(struct gaim_connection *gc, char *name)
1636{
1637        xmlnode x, y;
1638        char *realwho;
1639        gjconn gjc;
1640        struct buddy *buddy = NULL;
1641        /* struct group *buddy_group = NULL; */
1642       
1643        if(gc && gc->proto_data && ((struct jabber_data *)gc->proto_data)->gjc && name) {
1644                gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1645
1646                if (!strchr(name, '@'))
1647                        realwho = g_strdup_printf("%s@%s", name, gjc->user->server);
1648                else {
1649                        jid who = jid_new(gjc->p, name);
1650                        if (who->user == NULL) {
1651                                /* FIXME: transport */
1652                                return;
1653                        }
1654                        realwho = g_strdup_printf("%s@%s", who->user, who->server);
1655                }
1656
1657
1658                x = jutil_iqnew(JPACKET__SET, NS_ROSTER);
1659                y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item");
1660                xmlnode_put_attrib(y, "jid", realwho);
1661
1662
1663                /* If we can find the buddy, there's an alias for him, it's not 0-length
1664                 * and it doesn't match his JID, add the "name" attribute.
1665                 */
1666                if((buddy = find_buddy(gc, realwho)) != NULL &&
1667                        buddy->show != NULL && buddy->show[0] != '\0' && strcmp(realwho, buddy->show)) {
1668
1669                        xmlnode_put_attrib(y, "name", buddy->show);
1670                }
1671
1672                /*
1673                 * Find out what group the buddy's in and send that along
1674                 * with the roster item.
1675                 */
1676                /* ** Bitlbee disabled **
1677                if((buddy_group = NULL) != NULL) {
1678                        xmlnode z;
1679                        z = xmlnode_insert_tag(y, "group");
1680                        xmlnode_insert_cdata(z, buddy_group->name, -1);
1681                }
1682                ** End - Bitlbee ** */
1683
1684                gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1685
1686                xmlnode_free(x);
1687                g_free(realwho);
1688        }
1689}
1690
1691/*
1692 * Change buddy's group on server roster
1693 */
1694static void jabber_group_change(struct gaim_connection *gc, char *name, char *old_group, char *new_group)
1695{
1696        if(strcmp(old_group, new_group)) {
1697                jabber_roster_update(gc, name);
1698        }
1699}
1700
1701static void jabber_add_buddy(struct gaim_connection *gc, char *name)
1702{
1703        xmlnode x;
1704        char *realwho;
1705        gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1706
1707        if (!((struct jabber_data *)gc->proto_data)->did_import)
1708                return;
1709
1710        if (!name)
1711                return;
1712
1713        if (!strcmp(gc->username, name))
1714                return;
1715
1716        if (!strchr(name, '@'))
1717                realwho = g_strdup_printf("%s@%s", name, gjc->user->server);
1718        else {
1719                jid who;
1720               
1721                if((who = jid_new(gjc->p, name)) == NULL) {
1722                        char *msg = g_strdup_printf("%s: \"%s\"", _("Invalid Jabber I.D."), name);
1723                        do_error_dialog(GJ_GC(gjc), msg, _("Jabber Error"));
1724                        g_free(msg);
1725                        jabber_remove_gaim_buddy(gc, name);
1726                        return;
1727                }
1728                if (who->user == NULL) {
1729                        /* FIXME: transport */
1730                        return;
1731                }
1732                realwho = g_strdup_printf("%s@%s", who->user, who->server);
1733        }
1734
1735        x = xmlnode_new_tag("presence");
1736        xmlnode_put_attrib(x, "to", realwho);
1737        xmlnode_put_attrib(x, "type", "subscribe");
1738        gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1739        xmlnode_free(x);
1740
1741        jabber_roster_update(gc, realwho);
1742
1743        g_free(realwho);
1744}
1745
1746static void jabber_remove_buddy(struct gaim_connection *gc, char *name, char *group)
1747{
1748        xmlnode x;
1749        char *realwho;
1750        gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
1751
1752        if (!name)
1753                return;
1754
1755        if (!strchr(name, '@'))
1756                realwho = g_strdup_printf("%s@%s", name, gjc->user->server);
1757        else
1758                realwho = g_strdup(name);
1759
1760        x = xmlnode_new_tag("presence");
1761        xmlnode_put_attrib(x, "to", realwho);
1762        xmlnode_put_attrib(x, "type", "unsubscribe");
1763        gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
1764        g_free(realwho);
1765        xmlnode_free(x);
1766}
1767
1768static void jabber_get_info(struct gaim_connection *gc, char *who) {
1769        xmlnode x;
1770        char *id;
1771        char *realwho;
1772        struct jabber_data *jd = gc->proto_data;
1773        gjconn gjc = jd->gjc;
1774
1775        x = jutil_iqnew(JPACKET__GET, NS_VCARD);
1776        /* Bare username? */
1777        if (!strchr(who, '@')) {
1778                realwho = g_strdup_printf("%s@%s", who, gjc->user->server);
1779        } else {
1780                realwho = g_strdup(who);
1781        }
1782        xmlnode_put_attrib(x, "to", realwho);
1783        g_free(realwho);
1784
1785        id = gjab_getid(gjc);
1786        xmlnode_put_attrib(x, "id", id);
1787
1788        g_hash_table_insert(jd->gjc->queries, g_strdup(id), g_strdup("vCard"));
1789
1790        gjab_send(gjc, x);
1791
1792        xmlnode_free(x);
1793       
1794}
1795
1796static void jabber_get_away_msg(struct gaim_connection *gc, char *who) {
1797        struct jabber_data *jd = gc->proto_data;
1798        gjconn gjc = jd->gjc;
1799        char *status;
1800
1801        /* space for all elements: Jabber I.D. + "status" + NULL (list terminator) */
1802        gchar **str_arr = (gchar **) g_new(gpointer, 3);
1803        gchar **ap = str_arr;
1804        gchar *realwho, *final;
1805
1806        /* Bare username? */
1807        if (!strchr(who, '@')) {
1808                realwho = g_strdup_printf("%s@%s", who, gjc->user->server);
1809        } else {
1810                realwho = g_strdup(who);
1811        }
1812        *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", realwho);
1813
1814        if((status = g_hash_table_lookup(jd->hash, realwho)) == NULL) {
1815                status = _("Unknown");
1816        }
1817        *ap++ = g_strdup_printf("<B>Status:</B> %s<BR>\n", status);
1818
1819        *ap = NULL;
1820
1821        final= g_strjoinv(NULL, str_arr);
1822        g_strfreev(str_arr);
1823
1824        g_free(realwho);
1825        g_free(final);
1826       
1827}
1828
1829static GList *jabber_away_states(struct gaim_connection *gc) {
1830        GList *m = NULL;
1831
1832        m = g_list_append(m, "Online");
1833        m = g_list_append(m, "Chatty");
1834        m = g_list_append(m, "Away");
1835        m = g_list_append(m, "Extended Away");
1836        m = g_list_append(m, "Do Not Disturb");
1837
1838        return m;
1839}
1840
1841static void jabber_set_away(struct gaim_connection *gc, char *state, char *message)
1842{
1843        xmlnode x, y;
1844        struct jabber_data *jd = gc->proto_data;
1845        gjconn gjc = jd->gjc;
1846
1847        gc->away = NULL; /* never send an auto-response */
1848
1849        x = xmlnode_new_tag("presence");
1850
1851        if (!strcmp(state, GAIM_AWAY_CUSTOM)) {
1852                /* oh goody. Gaim is telling us what to do. */
1853                if (message) {
1854                        /* Gaim wants us to be away */
1855                        y = xmlnode_insert_tag(x, "show");
1856                        xmlnode_insert_cdata(y, "away", -1);
1857                        y = xmlnode_insert_tag(x, "status");
1858                        {
1859                                char *utf8 = str_to_utf8(message);
1860                                xmlnode_insert_cdata(y, utf8, -1);
1861                                g_free(utf8);
1862                        }
1863                        gc->away = "";
1864                } else {
1865                        /* Gaim wants us to not be away */
1866                        /* but for Jabber, we can just send presence with no other information. */
1867                }
1868        } else {
1869                /* state is one of our own strings. it won't be NULL. */
1870                if (!g_strcasecmp(state, "Online")) {
1871                        /* once again, we don't have to put anything here */
1872                } else if (!g_strcasecmp(state, "Chatty")) {
1873                        y = xmlnode_insert_tag(x, "show");
1874                        xmlnode_insert_cdata(y, "chat", -1);
1875                } else if (!g_strcasecmp(state, "Away")) {
1876                        y = xmlnode_insert_tag(x, "show");
1877                        xmlnode_insert_cdata(y, "away", -1);
1878                        gc->away = "";
1879                } else if (!g_strcasecmp(state, "Extended Away")) {
1880                        y = xmlnode_insert_tag(x, "show");
1881                        xmlnode_insert_cdata(y, "xa", -1);
1882                        gc->away = "";
1883                } else if (!g_strcasecmp(state, "Do Not Disturb")) {
1884                        y = xmlnode_insert_tag(x, "show");
1885                        xmlnode_insert_cdata(y, "dnd", -1);
1886                        gc->away = "";
1887                }
1888        }
1889
1890        gjab_send(gjc, x);
1891        xmlnode_free(x);
1892}
1893
1894static void jabber_set_idle(struct gaim_connection *gc, int idle) {
1895        struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
1896        jd->idle = idle ? time(NULL) - idle : idle;
1897}
1898
1899static void jabber_keepalive(struct gaim_connection *gc) {
1900        struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
1901        gjab_send_raw(jd->gjc, \t  ");
1902}
1903
1904static void jabber_buddy_free(struct buddy *b)
1905{
1906        while (b->proto_data) {
1907                g_free(((GSList *)b->proto_data)->data);
1908                b->proto_data = g_slist_remove(b->proto_data, ((GSList *)b->proto_data)->data);
1909        }
1910}
1911
1912/*---------------------------------------*/
1913/* Jabber "set info" (vCard) support     */
1914/*---------------------------------------*/
1915
1916/*
1917 * V-Card format:
1918 *
1919 *  <vCard prodid='' version='' xmlns=''>
1920 *    <FN></FN>
1921 *    <N>
1922 *      <FAMILY/>
1923 *      <GIVEN/>
1924 *    </N>
1925 *    <NICKNAME/>
1926 *    <URL/>
1927 *    <ADR>
1928 *      <STREET/>
1929 *      <EXTADD/>
1930 *      <LOCALITY/>
1931 *      <REGION/>
1932 *      <PCODE/>
1933 *      <COUNTRY/>
1934 *    </ADR>
1935 *    <TEL/>
1936 *    <EMAIL/>
1937 *    <ORG>
1938 *      <ORGNAME/>
1939 *      <ORGUNIT/>
1940 *    </ORG>
1941 *    <TITLE/>
1942 *    <ROLE/>
1943 *    <DESC/>
1944 *    <BDAY/>
1945 *  </vCard>
1946 *
1947 * See also:
1948 *
1949 *      http://docs.jabber.org/proto/html/vcard-temp.html
1950 *      http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
1951 */
1952
1953/*
1954 * Cross-reference user-friendly V-Card entry labels to vCard XML tags
1955 * and attributes.
1956 *
1957 * Order is (or should be) unimportant.  For example: we have no way of
1958 * knowing in what order real data will arrive.
1959 *
1960 * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
1961 *         name, XML tag's parent tag "path" (relative to vCard node).
1962 *
1963 *         List is terminated by a NULL label pointer.
1964 *
1965 *         Entries with no label text, but with XML tag and parent tag
1966 *         entries, are used by V-Card XML construction routines to
1967 *         "automagically" construct the appropriate XML node tree.
1968 *
1969 * Thoughts on future direction/expansion
1970 *
1971 *      This is a "simple" vCard.
1972 *
1973 *      It is possible for nodes other than the "vCard" node to have
1974 *      attributes.  Should that prove necessary/desirable, add an
1975 *      "attributes" pointer to the vcard_template struct, create the
1976 *      necessary tag_attr structs, and add 'em to the vcard_dflt_data
1977 *      array.
1978 *
1979 *      The above changes will (obviously) require changes to the vCard
1980 *      construction routines.
1981 */
1982
1983static struct vcard_template {
1984        char *label;                    /* label text pointer */
1985        char *text;                     /* entry text pointer */
1986        int  visible;                   /* should entry field be "visible?" */
1987        int  editable;                  /* should entry field be editable? */
1988        char *tag;                      /* tag text */
1989        char *ptag;                     /* parent tag "path" text */
1990        char *url;                      /* vCard display format if URL */
1991} vcard_template_data[] = {
1992        {N_("Full Name"),          NULL, TRUE, TRUE, "FN",        NULL,  NULL},
1993        {N_("Family Name"),        NULL, TRUE, TRUE, "FAMILY",    "N",   NULL},
1994        {N_("Given Name"),         NULL, TRUE, TRUE, "GIVEN",     "N",   NULL},
1995        {N_("Nickname"),           NULL, TRUE, TRUE, "NICKNAME",  NULL,  NULL},
1996        {N_("URL"),                NULL, TRUE, TRUE, "URL",       NULL,  "<A HREF=\"%s\">%s</A>"},
1997        {N_("Street Address"),     NULL, TRUE, TRUE, "STREET",    "ADR", NULL},
1998        {N_("Extended Address"),   NULL, TRUE, TRUE, "EXTADD",    "ADR", NULL},
1999        {N_("Locality"),           NULL, TRUE, TRUE, "LOCALITY",  "ADR", NULL},
2000        {N_("Region"),             NULL, TRUE, TRUE, "REGION",    "ADR", NULL},
2001        {N_("Postal Code"),        NULL, TRUE, TRUE, "PCODE",     "ADR", NULL},
2002        {N_("Country"),            NULL, TRUE, TRUE, "COUNTRY",   "ADR", NULL},
2003        {N_("Telephone"),          NULL, TRUE, TRUE, "TELEPHONE", NULL,  NULL},
2004        {N_("Email"),              NULL, TRUE, TRUE, "EMAIL",     NULL,  "<A HREF=\"mailto:%s\">%s</A>"},
2005        {N_("Organization Name"),  NULL, TRUE, TRUE, "ORGNAME",   "ORG", NULL},
2006        {N_("Organization Unit"),  NULL, TRUE, TRUE, "ORGUNIT",   "ORG", NULL},
2007        {N_("Title"),              NULL, TRUE, TRUE, "TITLE",     NULL,  NULL},
2008        {N_("Role"),               NULL, TRUE, TRUE, "ROLE",      NULL,  NULL},
2009        {N_("Birthday"),           NULL, TRUE, TRUE, "BDAY",      NULL,  NULL},
2010        {N_("Description"),        NULL, TRUE, TRUE, "DESC",      NULL,  NULL},
2011        {"", NULL, TRUE, TRUE, "N",     NULL, NULL},
2012        {"", NULL, TRUE, TRUE, "ADR",   NULL, NULL},
2013        {"", NULL, TRUE, TRUE, "ORG",   NULL, NULL},
2014        {NULL, NULL, 0, 0, NULL, NULL, NULL}
2015};
2016
2017/*
2018 * Used by routines to parse an XML-encoded string into an xmlnode tree
2019 */
2020typedef struct {
2021        XML_Parser parser;
2022        xmlnode current;
2023} *xmlstr2xmlnode_parser, xmlstr2xmlnode_parser_struct;
2024
2025
2026/*
2027 * Used by XML_Parse on parsing CDATA
2028 */
2029static void xmlstr2xmlnode_charData(void *userdata, const char *s, int slen)
2030{
2031        xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
2032
2033        if (xmlp->current)
2034                xmlnode_insert_cdata(xmlp->current, s, slen);
2035}
2036
2037/*
2038 * Used by XML_Parse to start or append to an xmlnode
2039 */
2040static void xmlstr2xmlnode_startElement(void *userdata, const char *name, const char **attribs)
2041{
2042        xmlnode x;
2043        xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
2044
2045        if (xmlp->current) {
2046                /* Append the node to the current one */
2047                x = xmlnode_insert_tag(xmlp->current, name);
2048                xmlnode_put_expat_attribs(x, attribs);
2049
2050                xmlp->current = x;
2051        } else {
2052                x = xmlnode_new_tag(name);
2053                xmlnode_put_expat_attribs(x, attribs);
2054                xmlp->current = x;
2055        }
2056}
2057
2058/*
2059 * Used by XML_Parse to end an xmlnode
2060 */
2061static void xmlstr2xmlnode_endElement(void *userdata, const char *name)
2062{
2063        xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
2064        xmlnode x;
2065
2066        if (xmlp->current != NULL && (x = xmlnode_get_parent(xmlp->current)) != NULL) {
2067                xmlp->current = x;
2068        }
2069}
2070
2071/*
2072 * Parse an XML-encoded string into an xmlnode tree
2073 *
2074 * Caller is responsible for freeing the returned xmlnode
2075 */
2076static xmlnode xmlstr2xmlnode(char *xmlstring)
2077{
2078        xmlstr2xmlnode_parser my_parser = g_new(xmlstr2xmlnode_parser_struct, 1);
2079        xmlnode x = NULL;
2080
2081        my_parser->parser = XML_ParserCreate(NULL);
2082        my_parser->current = NULL;
2083
2084        XML_SetUserData(my_parser->parser, (void *)my_parser);
2085        XML_SetElementHandler(my_parser->parser, xmlstr2xmlnode_startElement, xmlstr2xmlnode_endElement);
2086        XML_SetCharacterDataHandler(my_parser->parser, xmlstr2xmlnode_charData);
2087        XML_Parse(my_parser->parser, xmlstring, strlen(xmlstring), 0);
2088
2089        x = my_parser->current;
2090
2091        XML_ParserFree(my_parser->parser);
2092        g_free(my_parser);
2093
2094        return(x);
2095}
2096
2097/*
2098 * Insert a tag node into an xmlnode tree, recursively inserting parent tag
2099 * nodes as necessary
2100 *
2101 * Returns pointer to inserted node
2102 *
2103 * Note to hackers: this code is designed to be re-entrant (it's recursive--it
2104 * calls itself), so don't put any "static"s in here!
2105 */
2106static xmlnode insert_tag_to_parent_tag(xmlnode start, const char *parent_tag, const char *new_tag)
2107{
2108        xmlnode x = NULL;
2109
2110        /*
2111         * If the parent tag wasn't specified, see if we can get it
2112         * from the vCard template struct.
2113         */
2114        if(parent_tag == NULL) {
2115                struct vcard_template *vc_tp = vcard_template_data;
2116
2117                while(vc_tp->label != NULL) {
2118                        if(strcmp(vc_tp->tag, new_tag) == 0) {
2119                                parent_tag = vc_tp->ptag;
2120                                break;
2121                        }
2122                        ++vc_tp;
2123                }
2124        }
2125
2126        /*
2127         * If we have a parent tag...
2128         */
2129        if(parent_tag != NULL ) {
2130                /*
2131                 * Try to get the parent node for a tag
2132                 */
2133                if((x = xmlnode_get_tag(start, parent_tag)) == NULL) {
2134                        /*
2135                         * Descend?
2136                         */
2137                        char *grand_parent = strcpy(g_malloc(strlen(parent_tag) + 1), parent_tag);
2138                        char *parent;
2139
2140                        if((parent = strrchr(grand_parent, '/')) != NULL) {
2141                                *(parent++) = '\0';
2142                                x = insert_tag_to_parent_tag(start, grand_parent, parent);
2143                        } else {
2144                                x = xmlnode_insert_tag(start, grand_parent);
2145                        }
2146                        g_free(grand_parent);
2147                } else {
2148                        /*
2149                         * We found *something* to be the parent node.
2150                         * Note: may be the "root" node!
2151                         */
2152                        xmlnode y;
2153                        if((y = xmlnode_get_tag(x, new_tag)) != NULL) {
2154                                return(y);
2155                        }
2156                }
2157        }
2158
2159        /*
2160         * insert the new tag into its parent node
2161         */
2162        return(xmlnode_insert_tag((x == NULL? start : x), new_tag));
2163}
2164
2165/*
2166 * Send vCard info to Jabber server
2167 */
2168static void jabber_set_info(struct gaim_connection *gc, char *info)
2169{
2170        xmlnode x, vc_node;
2171        char *id;
2172        struct jabber_data *jd = gc->proto_data;
2173        gjconn gjc = jd->gjc;
2174
2175        x = xmlnode_new_tag("iq");
2176        xmlnode_put_attrib(x,"type","set");
2177
2178        id = gjab_getid(gjc);
2179       
2180        xmlnode_put_attrib(x, "id", id);
2181
2182        /*
2183         * Send only if there's actually any *information* to send
2184         */
2185        if((vc_node = xmlstr2xmlnode(info)) != NULL && xmlnode_get_name(vc_node) != NULL &&
2186                        g_strncasecmp(xmlnode_get_name(vc_node), "vcard", 5) == 0) {
2187                xmlnode_insert_tag_node(x, vc_node);
2188                gjab_send(gjc, x);
2189        }
2190
2191        xmlnode_free(x);
2192}
2193
2194/*
2195 * displays a Jabber vCard
2196 */
2197static void jabber_handlevcard(gjconn gjc, xmlnode querynode, char *from)
2198{
2199        struct jabber_data *jd = GJ_GC(gjc)->proto_data;
2200        jid who = jid_new(gjc->p, from);
2201        char *status = NULL, *text = NULL;
2202        GString *str = g_string_sized_new(100);
2203        xmlnode child;
2204
2205        gchar *buddy = NULL;
2206       
2207        if(querynode == NULL) {
2208                serv_got_crap(GJ_GC(gjc), "%s - Received empty info reply from %s", _("User Info"), from);
2209                return;
2210        }
2211
2212        if(who->resource != NULL && (who->resource)[0] != '\0') {
2213                buddy = g_strdup_printf("%s@%s/%s", who->user, who->server, who->resource);
2214        } else {
2215                buddy = g_strdup_printf("%s@%s", who->user, who->server);
2216        }
2217
2218        if((status = g_hash_table_lookup(jd->hash, buddy)) == NULL) {
2219                status = _("Unknown");
2220        }
2221
2222        g_string_sprintfa(str, "%s: %s - %s: %s", _("Jabber ID"), buddy, _("Status"),
2223                               status);
2224
2225        for(child = querynode->firstchild; child; child = child->next)
2226        {
2227                xmlnode child2;
2228
2229                if(child->type != NTYPE_TAG)
2230                        continue;
2231
2232                text = xmlnode_get_data(child);
2233                if(text && !strcmp(child->name, "FN")) {
2234                        info_string_append(str, "\n", _("Full Name"), text);
2235                } else if (!strcmp(child->name, "N")) {
2236                        for (child2 = child->firstchild; child2; child2 = child2->next) {
2237                                char *text2 = NULL;
2238
2239                                if (child2->type != NTYPE_TAG)
2240                                        continue;
2241
2242                                text2 = xmlnode_get_data(child2);
2243                                if (text2 && !strcmp(child2->name, "FAMILY")) {
2244                                        info_string_append(str, "\n", _("Family Name"), text2);
2245                                } else if (text2 && !strcmp(child2->name, "GIVEN")) {
2246                                        info_string_append(str, "\n", _("Given Name"), text2);
2247                                } else if (text2 && !strcmp(child2->name, "MIDDLE")) {
2248                                        info_string_append(str, "\n", _("Middle Name"), text2);
2249                                }
2250                        }
2251                } else if (text && !strcmp(child->name, "NICKNAME")) {
2252                        info_string_append(str, "\n", _("Nickname"), text);
2253                } else if (text && !strcmp(child->name, "BDAY")) {
2254                        info_string_append(str, "\n", _("Birthday"), text);
2255                } else if (!strcmp(child->name, "ADR")) {
2256                        /* show wich address it is */
2257                        /* Just for the beauty of bitlbee
2258                        if (child->firstchild)
2259                                g_string_sprintfa(str, "%s:\n", _("Address"));
2260                        */
2261                        for(child2 = child->firstchild; child2; child2 = child2->next) {
2262                                char *text2 = NULL;
2263
2264                                if(child2->type != NTYPE_TAG)
2265                                        continue;
2266
2267                                text2 = xmlnode_get_data(child2);
2268                                if(text2 && !strcmp(child2->name, "POBOX")) {
2269                                        info_string_append(str, "\n",
2270                                                        _("P.O. Box"), text2);
2271                                } else if(text2 && !strcmp(child2->name, "EXTADR")) {
2272                                        info_string_append(str, "\n",
2273                                                        _("Extended Address"), text2);
2274                                } else if(text2 && !strcmp(child2->name, "STREET")) {
2275                                        info_string_append(str, "\n",
2276                                                        _("Street Address"), text2);
2277                                } else if(text2 && !strcmp(child2->name, "LOCALITY")) {
2278                                        info_string_append(str, "\n",
2279                                                        _("Locality"), text2);
2280                                } else if(text2 && !strcmp(child2->name, "REGION")) {
2281                                        info_string_append(str, "\n",
2282                                                        _("Region"), text2);
2283                                } else if(text2 && !strcmp(child2->name, "PCODE")) {
2284                                        info_string_append(str, "\n",
2285                                                        _("Postal Code"), text2);
2286                                } else if(text2 && (!strcmp(child2->name, "CTRY")
2287                                                        || !strcmp(child2->name, "COUNTRY"))) {
2288                                        info_string_append(str, "\n", _("Country"), text2);
2289                                }
2290                        }
2291                } else if(!strcmp(child->name, "TEL")) {
2292                        char *number = NULL;
2293                        if ((child2 = xmlnode_get_tag(child, "NUMBER"))) {
2294                                /* show what kind of number it is */
2295                                number = xmlnode_get_data(child2);
2296                                if(number) {
2297                                        info_string_append(str, "\n", _("Telephone"), number);
2298                                }
2299                        } else if((number = xmlnode_get_data(child))) {
2300                                /* lots of clients (including gaim) do this,
2301                                 * but it's out of spec */
2302                                info_string_append(str, "\n", _("Telephone"), number);
2303                        }
2304                } else if(!strcmp(child->name, "EMAIL")) {
2305                        char *userid = NULL;
2306                        if((child2 = xmlnode_get_tag(child, "USERID"))) {
2307                                /* show what kind of email it is */
2308                                userid = xmlnode_get_data(child2);
2309                                if(userid) {
2310                                        info_string_append(str, "\n", _("Email"), userid);
2311                                }
2312                        } else if((userid = xmlnode_get_data(child))) {
2313                                /* lots of clients (including gaim) do this,
2314                                 * but it's out of spec */
2315                                info_string_append(str, "\n", _("Email"), userid);
2316                        }
2317                } else if(!strcmp(child->name, "ORG")) {
2318                        for(child2 = child->firstchild; child2; child2 = child2->next) {
2319                                char *text2 = NULL;
2320
2321                                if(child2->type != NTYPE_TAG)
2322                                        continue;
2323
2324                                text2 = xmlnode_get_data(child2);
2325                                if(text2 && !strcmp(child2->name, "ORGNAME")) {
2326                                        info_string_append(str, "\n", _("Organization Name"), text2);
2327                                } else if(text2 && !strcmp(child2->name, "ORGUNIT")) {
2328                                        info_string_append(str, "\n", _("Organization Unit"), text2);
2329                                }
2330                        }
2331                } else if(text && !strcmp(child->name, "TITLE")) {
2332                        info_string_append(str, "\n", _("Title"), text);
2333                } else if(text && !strcmp(child->name, "ROLE")) {
2334                        info_string_append(str, "\n", _("Role"), text);
2335                } else if(text && !strcmp(child->name, "DESC")) {
2336                        g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Description"), 
2337                                        text, _("End of Description"));
2338                }
2339        }
2340
2341        serv_got_crap(GJ_GC(gjc), "%s\n%s", _("User Info"), str->str);
2342
2343        g_free(buddy);
2344        g_string_free(str, TRUE);
2345}
2346
2347
2348static GList *jabber_actions()
2349{
2350        GList *m = NULL;
2351
2352        m = g_list_append(m, _("Set User Info"));
2353        /*
2354        m = g_list_append(m, _("Set Dir Info"));
2355        m = g_list_append(m, _("Change Password"));
2356         */
2357
2358        return m;
2359}
2360
2361
[7b23afd]2362void jabber_init()
[b7d3cc34]2363{
[7b23afd]2364        struct prpl *ret = g_new0(struct prpl, 1);
2365
[b7d3cc34]2366        /* the NULL's aren't required but they're nice to have */
[7b23afd]2367        ret->name = "jabber";
[b7d3cc34]2368        ret->away_states = jabber_away_states;
2369        ret->actions = jabber_actions;
2370        ret->login = jabber_login;
2371        ret->close = jabber_close;
2372        ret->send_im = jabber_send_im;
2373        ret->set_info = jabber_set_info;
2374        ret->get_info = jabber_get_info;
2375        ret->set_away = jabber_set_away;
2376        ret->get_away = jabber_get_away_msg;
2377        ret->set_idle = jabber_set_idle;
2378        ret->add_buddy = jabber_add_buddy;
2379        ret->remove_buddy = jabber_remove_buddy;
2380        ret->add_permit = NULL;
2381        ret->add_deny = NULL;
2382        ret->rem_permit = NULL;
2383        ret->rem_deny = NULL;
2384        ret->set_permit_deny = NULL;
2385        ret->keepalive = jabber_keepalive;
2386        ret->buddy_free = jabber_buddy_free;
2387        ret->alias_buddy = jabber_roster_update;
2388        ret->group_buddy = jabber_group_change;
[9cb9868]2389        ret->cmp_buddynames = g_strcasecmp;
[b7d3cc34]2390
[7b23afd]2391        register_protocol (ret);
[b7d3cc34]2392}
Note: See TracBrowser for help on using the repository browser.