source: protocols/jabber/jabber.c @ 2cdd8ce

Last change on this file since 2cdd8ce was 2cdd8ce, checked in by Jelmer Vernooij <jelmer@…>, at 2005-11-19T15:17:03Z

Merge Wilmer

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