source: protocols/jabber/jabber.c @ ab49fdc

Last change on this file since ab49fdc was 027d2eb, checked in by Wilmer van der Gaast <wilmer@…>, at 2005-12-02T11:43:47Z

Modified CHANGES, and extended the allowed port range a bit.

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