diff options
author | Wilmer van der Gaast <wilmer@gaast.net> | 2005-11-06 19:23:18 +0100 |
---|---|---|
committer | Wilmer van der Gaast <wilmer@gaast.net> | 2005-11-06 19:23:18 +0100 |
commit | b7d3cc34f68dab7b8f7d0777711317b334fc2219 (patch) | |
tree | 6aa4d6332c96654fda79fe18993ab0e35d36a52b /protocols/jabber/jabber.c |
Initial repository (0.99 release tree)0.99
Diffstat (limited to 'protocols/jabber/jabber.c')
-rw-r--r-- | protocols/jabber/jabber.c | 2445 |
1 files changed, 2445 insertions, 0 deletions
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c new file mode 100644 index 00000000..931a2182 --- /dev/null +++ b/protocols/jabber/jabber.c @@ -0,0 +1,2445 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gaim + * + * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net> + * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef _WIN32 +#include <sys/utsname.h> +#endif +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <sys/stat.h> +#include "jabber.h" +#include "nogaim.h" +#include "bitlbee.h" +#include "proxy.h" +#include "ssl_client.h" + +/* The priv member of gjconn's is a gaim_connection for now. */ +#define GJ_GC(x) ((struct gaim_connection *)(x)->priv) + +#define IQID_AUTH "__AUTH__" + +#define IQ_NONE -1 +#define IQ_AUTH 0 +#define IQ_ROSTER 1 + +#define UC_AWAY (0x02 | UC_UNAVAILABLE) +#define UC_CHAT 0x04 +#define UC_XA (0x08 | UC_UNAVAILABLE) +#define UC_DND (0x10 | UC_UNAVAILABLE) + +#define DEFAULT_SERVER "jabber.org" +#define DEFAULT_GROUPCHAT "conference.jabber.org" +#define DEFAULT_PORT 5222 +#define DEFAULT_PORT_SSL 5223 + +#define JABBER_GROUP "Friends" + +/* i18n disabled - Bitlbee */ +#define N_(String) String + +/* + * Note: "was_connected" may seem redundant, but it was needed and I + * didn't want to touch the Jabber state stuff not specific to Gaim. + */ +typedef struct gjconn_struct { + /* Core structure */ + pool p; /* Memory allocation pool */ + int state; /* Connection state flag */ + int was_connected; /* We were once connected */ + int fd; /* Connection file descriptor */ + void *ssl; /* SSL connection */ + jid user; /* User info */ + char *pass; /* User passwd */ + + /* Stream stuff */ + int id; /* id counter for jab_getid() function */ + char idbuf[9]; /* temporary storage for jab_getid() */ + char *sid; /* stream id from server, for digest auth */ + XML_Parser parser; /* Parser instance */ + xmlnode current; /* Current node in parsing instance.. */ + + /* Event callback ptrs */ + void (*on_state)(struct gjconn_struct *gjc, int state); + void (*on_packet)(struct gjconn_struct *gjc, jpacket p); + + GHashTable *queries; /* query tracker */ + + void *priv; +} *gjconn, gjconn_struct; + +typedef void (*gjconn_state_h)(gjconn gjc, int state); +typedef void (*gjconn_packet_h)(gjconn gjc, jpacket p); + +static gjconn gjab_new(char *user, char *pass, void *priv); +static void gjab_delete(gjconn gjc); +static void gjab_state_handler(gjconn gjc, gjconn_state_h h); +static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h); +static void gjab_start(gjconn gjc); +static void gjab_stop(gjconn gjc); +/* +static int gjab_getfd(gjconn gjc); +static jid gjab_getjid(gjconn gjc); +static char *gjab_getsid(gjconn gjc); +*/ +static char *gjab_getid(gjconn gjc); +static void gjab_send(gjconn gjc, xmlnode x); +static void gjab_send_raw(gjconn gjc, const char *str); +static void gjab_recv(gjconn gjc); +static void gjab_auth(gjconn gjc); + +/* + * It is *this* to which we point the gaim_connection proto_data + */ +struct jabber_data { + gjconn gjc; + gboolean did_import; + GSList *chats; + GHashTable *hash; + time_t idle; + gboolean die; +}; + +/* + * Jabber "chat group" info. Pointers to these go in jabber_data + * pending and existing chats lists. + */ +struct jabber_chat { + jid Jid; + struct gaim_connection *gc; + struct conversation *b; + int id; + int state; +}; + +/* + * Jabber chat states... + * + * Note: due to a bug in one version of the Jabber server, subscriptions + * to chat groups aren't (always?) properly removed at the server. The + * result is clients receive Jabber "presence" notifications for JIDs + * they no longer care about. The problem with such vestigial notifies is + * that we really have no way of telling if it's vestigial or if it's a + * valid "buddy" presence notification. So we keep jabber_chat structs + * around after leaving a chat group and simply mark them "closed." That + * way we can test for such errant presence notifications. I.e.: if we + * get a presence notfication from a JID that matches a chat group JID, + * we disregard it. + */ +#define JCS_PENDING 1 /* pending */ +#define JCS_ACTIVE 2 /* active */ +#define JCS_CLOSED 3 /* closed */ + + +static char *jabber_name() +{ + return "Jabber"; +} + +#define STATE_EVT(arg) if(gjc->on_state) { (gjc->on_state)(gjc, (arg) ); } + +static void jabber_remove_buddy(struct gaim_connection *gc, char *name, char *group); +static void jabber_handlevcard(gjconn gjc, xmlnode querynode, char *from); + +static char *create_valid_jid(const char *given, char *server, char *resource) +{ + char *valid; + + if (!strchr(given, '@')) + valid = g_strdup_printf("%s@%s/%s", given, server, resource); + else if (!strchr(strchr(given, '@'), '/')) + valid = g_strdup_printf("%s/%s", given, resource); + else + valid = g_strdup(given); + + return valid; +} + +static gjconn gjab_new(char *user, char *pass, void *priv) +{ + pool p; + gjconn gjc; + + if (!user) + return (NULL); + + p = pool_new(); + if (!p) + return (NULL); + gjc = pmalloc_x(p, sizeof(gjconn_struct), 0); + if (!gjc) { + pool_free(p); /* no need for this anymore! */ + return (NULL); + } + gjc->p = p; + + if((gjc->user = jid_new(p, user)) == NULL) { + pool_free(p); /* no need for this anymore! */ + return (NULL); + } + gjc->pass = pstrdup(p, pass); + + gjc->state = JCONN_STATE_OFF; + gjc->was_connected = 0; + gjc->id = 1; + gjc->fd = -1; + + gjc->priv = priv; + + return gjc; +} + +static void gjab_delete(gjconn gjc) +{ + if (!gjc) + return; + + gjab_stop(gjc); + pool_free(gjc->p); +} + +static void gjab_state_handler(gjconn gjc, gjconn_state_h h) +{ + if (!gjc) + return; + + gjc->on_state = h; +} + +static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h) +{ + if (!gjc) + return; + + gjc->on_packet = h; +} + +static void gjab_stop(gjconn gjc) +{ + if (!gjc || gjc->state == JCONN_STATE_OFF) + return; + + gjab_send_raw(gjc, "</stream:stream>"); + gjc->state = JCONN_STATE_OFF; + gjc->was_connected = 0; + if (gjc->ssl) { + ssl_disconnect(gjc->ssl); + gjc->ssl = NULL; + } else { + closesocket(gjc->fd); + } + gjc->fd = -1; + XML_ParserFree(gjc->parser); + gjc->parser = NULL; +} + +/* +static int gjab_getfd(gjconn gjc) +{ + if (gjc) + return gjc->fd; + else + return -1; +} + +static jid gjab_getjid(gjconn gjc) +{ + if (gjc) + return (gjc->user); + else + return NULL; +} + +static char *gjab_getsid(gjconn gjc) +{ + if (gjc) + return (gjc->sid); + else + return NULL; +} +*/ + +static char *gjab_getid(gjconn gjc) +{ + g_snprintf(gjc->idbuf, 8, "%d", gjc->id++); + return &gjc->idbuf[0]; +} + +static void gjab_send(gjconn gjc, xmlnode x) +{ + if (gjc && gjc->state != JCONN_STATE_OFF) { + char *buf = xmlnode2str(x); + if (!buf) + return; + else if (gjc->ssl) + ssl_write(gjc->ssl, buf, strlen(buf)); + else + write(gjc->fd, buf, strlen(buf)); + } +} + +static void gjab_send_raw(gjconn gjc, const char *str) +{ + if (gjc && gjc->state != JCONN_STATE_OFF) { + int len; + + /* + * JFIXME: No error detection?!?! + */ + if (gjc->ssl) + len = ssl_write(gjc->ssl, str, strlen(str)); + else + len = write(gjc->fd, str, strlen(str)); + + if(len < 0) { + /* Do NOT write to stdout/stderr directly, IRC clients + might get confused, and we don't want that... + fprintf(stderr, "DBG: Problem sending. Error: %d\n", errno); + fflush(stderr); */ + } + } +} + +static void gjab_reqroster(gjconn gjc) +{ + xmlnode x; + + x = jutil_iqnew(JPACKET__GET, NS_ROSTER); + xmlnode_put_attrib(x, "id", gjab_getid(gjc)); + + gjab_send(gjc, x); + xmlnode_free(x); +} + +static void gjab_reqauth(gjconn gjc) +{ + xmlnode x, y, z; + char *user; + + if (!gjc) + return; + + x = jutil_iqnew(JPACKET__GET, NS_AUTH); + xmlnode_put_attrib(x, "id", IQID_AUTH); + y = xmlnode_get_tag(x, "query"); + + user = gjc->user->user; + + if (user) { + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, user, -1); + } + + gjab_send(gjc, x); + xmlnode_free(x); +} + +static void gjab_auth(gjconn gjc) +{ + xmlnode x, y, z; + char *hash, *user; + + if (!gjc) + return; + + x = jutil_iqnew(JPACKET__SET, NS_AUTH); + xmlnode_put_attrib(x, "id", IQID_AUTH); + y = xmlnode_get_tag(x, "query"); + + user = gjc->user->user; + + if (user) { + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, user, -1); + } + + z = xmlnode_insert_tag(y, "resource"); + xmlnode_insert_cdata(z, gjc->user->resource, -1); + + if (gjc->sid) { + z = xmlnode_insert_tag(y, "digest"); + hash = pmalloc(x->p, strlen(gjc->sid) + strlen(gjc->pass) + 1); + strcpy(hash, gjc->sid); + strcat(hash, gjc->pass); + hash = shahash(hash); + xmlnode_insert_cdata(z, hash, 40); + } else { + z = xmlnode_insert_tag(y, "password"); + xmlnode_insert_cdata(z, gjc->pass, -1); + } + + gjab_send(gjc, x); + xmlnode_free(x); + + return; +} + +static void gjab_recv(gjconn gjc) +{ + static char buf[4096]; + int len; + + if (!gjc || gjc->state == JCONN_STATE_OFF) + return; + + if (gjc->ssl) + len = ssl_read(gjc->ssl, buf, sizeof(buf) - 1); + else + len = read(gjc->fd, buf, sizeof(buf) - 1); + + if (len > 0) { + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + buf[len] = '\0'; + XML_Parse(gjc->parser, buf, len, 0); + if (jd->die) + signoff(GJ_GC(gjc)); + } else if (len < 0 || errno != EAGAIN) { + STATE_EVT(JCONN_STATE_OFF) + } +} + +static void startElement(void *userdata, const char *name, const char **attribs) +{ + xmlnode x; + gjconn gjc = (gjconn) userdata; + + if (gjc->current) { + /* Append the node to the current one */ + x = xmlnode_insert_tag(gjc->current, name); + xmlnode_put_expat_attribs(x, attribs); + + gjc->current = x; + } else { + x = xmlnode_new_tag(name); + xmlnode_put_expat_attribs(x, attribs); + if (strcmp(name, "stream:stream") == 0) { + /* special case: name == stream:stream */ + /* id attrib of stream is stored for digest auth */ + gjc->sid = g_strdup(xmlnode_get_attrib(x, "id")); + /* STATE_EVT(JCONN_STATE_AUTH) */ + xmlnode_free(x); + } else { + gjc->current = x; + } + } +} + +static void endElement(void *userdata, const char *name) +{ + gjconn gjc = (gjconn) userdata; + xmlnode x; + jpacket p; + + if (gjc->current == NULL) { + /* we got </stream:stream> */ + STATE_EVT(JCONN_STATE_OFF) + return; + } + + x = xmlnode_get_parent(gjc->current); + + if (!x) { + /* it is time to fire the event */ + p = jpacket_new(gjc->current); + + if (gjc->on_packet) + (gjc->on_packet) (gjc, p); + else + xmlnode_free(gjc->current); + } + + gjc->current = x; +} + +static void jabber_callback(gpointer data, gint source, GaimInputCondition condition) +{ + struct gaim_connection *gc = (struct gaim_connection *)data; + struct jabber_data *jd = (struct jabber_data *)gc->proto_data; + + gjab_recv(jd->gjc); +} + +static void charData(void *userdata, const char *s, int slen) +{ + gjconn gjc = (gjconn) userdata; + + if (gjc->current) + xmlnode_insert_cdata(gjc->current, s, slen); +} + +static void gjab_connected(gpointer data, gint source, GaimInputCondition cond) +{ + xmlnode x; + char *t, *t2; + struct gaim_connection *gc = data; + struct jabber_data *jd; + gjconn gjc; + + if (!g_slist_find(get_connections(), gc)) { + closesocket(source); + return; + } + + jd = gc->proto_data; + gjc = jd->gjc; + + if (gjc->fd != source) + gjc->fd = source; + + if (source == -1) { + STATE_EVT(JCONN_STATE_OFF) + return; + } + + gjc->state = JCONN_STATE_CONNECTED; + STATE_EVT(JCONN_STATE_CONNECTED) + + /* start stream */ + x = jutil_header(NS_CLIENT, gjc->user->server); + t = xmlnode2str(x); + /* this is ugly, we can create the string here instead of jutil_header */ + /* what do you think about it? -madcat */ + t2 = strstr(t, "/>"); + *t2++ = '>'; + *t2 = '\0'; + gjab_send_raw(gjc, "<?xml version='1.0'?>"); + gjab_send_raw(gjc, t); + xmlnode_free(x); + + gjc->state = JCONN_STATE_ON; + STATE_EVT(JCONN_STATE_ON); + + gc = GJ_GC(gjc); + gc->inpa = gaim_input_add(gjc->fd, GAIM_INPUT_READ, jabber_callback, gc); +} + +static void gjab_connected_ssl(gpointer data, void *source, GaimInputCondition cond) +{ + struct gaim_connection *gc = data; + struct jabber_data *jd; + gjconn gjc; + + if (!g_slist_find(get_connections(), gc)) { + ssl_disconnect(source); + return; + } + + jd = gc->proto_data; + gjc = jd->gjc; + + if (source == NULL) { + STATE_EVT(JCONN_STATE_OFF) + return; + } + + gjab_connected(data, gjc->fd, cond); +} + +static void gjab_start(gjconn gjc) +{ + struct aim_user *user; + int port = -1, ssl = 0; + char *server = NULL, *s; + + if (!gjc || gjc->state != JCONN_STATE_OFF) + return; + + user = GJ_GC(gjc)->user; + if (*user->proto_opt[0]) { + /* If there's a dot, assume there's a hostname in the beginning */ + if (strchr(user->proto_opt[0], '.')) { + server = g_strdup(user->proto_opt[0]); + if ((s = strchr(server, ':'))) + *s = 0; + } + + /* After the hostname, there can be a port number */ + s = strchr(user->proto_opt[0], ':'); + if (s && isdigit(s[1])) + sscanf(s + 1, "%d", &port); + + /* And if there's the string ssl, the user wants an SSL-connection */ + if (strstr(user->proto_opt[0], ":ssl") || g_strcasecmp(user->proto_opt[0], "ssl") == 0) + ssl = 1; + } + + if (port == -1 && !ssl) + port = DEFAULT_PORT; + else if (port == -1 && ssl) + port = DEFAULT_PORT_SSL; + + if (server == NULL) + server = g_strdup(gjc->user->server); + + gjc->parser = XML_ParserCreate(NULL); + XML_SetUserData(gjc->parser, (void *)gjc); + XML_SetElementHandler(gjc->parser, startElement, endElement); + XML_SetCharacterDataHandler(gjc->parser, charData); + + if (ssl) { + if ((gjc->ssl = ssl_connect(server, port, gjab_connected_ssl, GJ_GC(gjc)))) + gjc->fd = ssl_getfd(gjc->ssl); + else + gjc->fd = -1; + } else { + gjc->fd = proxy_connect(server, port, gjab_connected, GJ_GC(gjc)); + } + + g_free(server); + + if (!user->gc || (gjc->fd < 0)) { + STATE_EVT(JCONN_STATE_OFF) + return; + } +} + +/* + * Find existing/active Jabber chat + */ +static struct jabber_chat *find_existing_chat(struct gaim_connection *gc, jid chat) +{ + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; + struct jabber_chat *jc = NULL; + + while (jcs) { + jc = jcs->data; + if (jc->state == JCS_ACTIVE && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER)) + break; + jc = NULL; + jcs = jcs->next; + } + + return jc; +} + +/* + * Find pending chat + */ +static struct jabber_chat *find_pending_chat(struct gaim_connection *gc, jid chat) +{ + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; + struct jabber_chat *jc = NULL; + + while (jcs) { + jc = jcs->data; + if (jc->state == JCS_PENDING && !jid_cmpx(chat, jc->Jid, JID_USER | JID_SERVER)) + break; + jc = NULL; + jcs = jcs->next; + } + + return jc; +} + +static gboolean find_chat_buddy(struct conversation *b, char *name) +{ + GList *m = b->in_room; + + while (m) { + if (!strcmp(m->data, name)) + return TRUE; + m = m->next; + } + + return FALSE; +} + +/* + * Remove a buddy from the (gaim) buddylist (if he's on it) + */ +static void jabber_remove_gaim_buddy(struct gaim_connection *gc, char *buddyname) +{ + struct buddy *b; + + if ((b = find_buddy(gc, buddyname)) != NULL) { + /* struct group *group; + + group = find_group_by_buddy(gc, buddyname); + remove_buddy(gc, group, b); */ + jabber_remove_buddy(gc, b->name, JABBER_GROUP); + } +} + +/* + * keep track of away msg same as yahoo plugin + */ +static void jabber_track_away(gjconn gjc, jpacket p, char *name, char *type) +{ + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + gpointer val = g_hash_table_lookup(jd->hash, name); + char *show; + char *vshow = NULL; + char *status = NULL; + char *msg = NULL; + + if (type && (g_strcasecmp(type, "unavailable") == 0)) { + vshow = _("Unavailable"); + } else { + if((show = xmlnode_get_tag_data(p->x, "show")) != NULL) { + if (!g_strcasecmp(show, "away")) { + vshow = _("Away"); + } else if (!g_strcasecmp(show, "chat")) { + vshow = _("Online"); + } else if (!g_strcasecmp(show, "xa")) { + vshow = _("Extended Away"); + } else if (!g_strcasecmp(show, "dnd")) { + vshow = _("Do Not Disturb"); + } + } + } + + status = xmlnode_get_tag_data(p->x, "status"); + + if(vshow != NULL || status != NULL ) { + /* kinda hokey, but it works :-) */ + msg = g_strdup_printf("%s%s%s", + (vshow == NULL? "" : vshow), + (vshow == NULL || status == NULL? "" : ": "), + (status == NULL? "" : status)); + } else { + msg = g_strdup(_("Online")); + } + + if (val) { + g_free(val); + g_hash_table_insert(jd->hash, name, msg); + } else { + g_hash_table_insert(jd->hash, g_strdup(name), msg); + } +} + +static time_t iso8601_to_time(char *timestamp) +{ + struct tm t; + time_t retval = 0; + + if(sscanf(timestamp,"%04d%02d%02dT%02d:%02d:%02d", + &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec)) + { + t.tm_year -= 1900; + t.tm_mon -= 1; + t.tm_isdst = 0; + retval = mktime(&t); +# ifdef HAVE_TM_GMTOFF + retval += t.tm_gmtoff; +# else +# ifdef HAVE_TIMEZONE + tzset(); /* making sure */ + retval -= timezone; +# endif +# endif + } + + return retval; +} + +static void jabber_handlemessage(gjconn gjc, jpacket p) +{ + xmlnode y, xmlns, subj, z; + time_t time_sent = time(NULL); + + char *from = NULL, *msg = NULL, *type = NULL, *topic = NULL; + char m[BUF_LONG * 2]; + + type = xmlnode_get_attrib(p->x, "type"); + + z = xmlnode_get_firstchild(p->x); + + while(z) + { + if(NSCHECK(z,NS_DELAY)) + { + char *timestamp = xmlnode_get_attrib(z,"stamp"); + time_sent = iso8601_to_time(timestamp); + } + z = xmlnode_get_nextsibling(z); + } + + if (!type || !g_strcasecmp(type, "normal") || !g_strcasecmp(type, "chat")) { + + /* XXX namespaces could be handled better. (mid) */ + if ((xmlns = xmlnode_get_tag(p->x, "x"))) + type = xmlnode_get_attrib(xmlns, "xmlns"); + + from = jid_full(p->from); + /* + if ((y = xmlnode_get_tag(p->x, "html"))) { + msg = xmlnode_get_data(y); + } else + */ + if ((y = xmlnode_get_tag(p->x, "body"))) { + msg = xmlnode_get_data(y); + } + + + if (!from) + return; + + if (type && !g_strcasecmp(type, "jabber:x:conference")) { + char *room; + GList *m = NULL; + char **data; + + room = xmlnode_get_attrib(xmlns, "jid"); + data = g_strsplit(room, "@", 2); + m = g_list_append(m, g_strdup(data[0])); + m = g_list_append(m, g_strdup(data[1])); + m = g_list_append(m, g_strdup(gjc->user->user)); + g_strfreev(data); + + /* ** Bitlbee ** serv_got_chat_invite(GJ_GC(gjc), room, from, msg, m); */ + } else if (msg) { /* whisper */ + struct jabber_chat *jc; + g_snprintf(m, sizeof(m), "%s", msg); + if (((jc = find_existing_chat(GJ_GC(gjc), p->from)) != NULL) && jc->b) + serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 1, m, time_sent); + else { + int flags = 0; + /* ** Bitlbee ** + if (xmlnode_get_tag(p->x, "gaim")) + flags = IM_FLAG_GAIMUSER; + if (find_conversation(jid_full(p->from))) + serv_got_im(GJ_GC(gjc), jid_full(p->from), m, flags, time_sent, -1); + else { + ** End - Bitlbee ** */ + if(p->from->user) { + from = g_strdup_printf("%s@%s", p->from->user, p->from->server); + } else { + /* server message? */ + from = g_strdup(p->from->server); + } + serv_got_im(GJ_GC(gjc), from, m, flags, time_sent, -1); + g_free(from); + /* ** Bitlbee ** } ** End - Bitlbee ** */ + } + } + + } else if (!g_strcasecmp(type, "error")) { + if ((y = xmlnode_get_tag(p->x, "error"))) { + type = xmlnode_get_attrib(y, "code"); + msg = xmlnode_get_data(y); + } + + if (msg) { + from = g_strdup_printf("Error %s", type ? type : ""); + do_error_dialog(GJ_GC(gjc), msg, from); + g_free(from); + } + } else if (!g_strcasecmp(type, "groupchat")) { + struct jabber_chat *jc; + static int i = 0; + + /* + if ((y = xmlnode_get_tag(p->x, "html"))) { + msg = xmlnode_get_data(y); + } else + */ + if ((y = xmlnode_get_tag(p->x, "body"))) { + msg = xmlnode_get_data(y); + } + + msg = utf8_to_str(msg); + + if ((subj = xmlnode_get_tag(p->x, "subject"))) { + topic = xmlnode_get_data(subj); + } + topic = utf8_to_str(topic); + + jc = find_existing_chat(GJ_GC(gjc), p->from); + if (!jc) { + /* we're not in this chat. are we supposed to be? */ + if ((jc = find_pending_chat(GJ_GC(gjc), p->from)) != NULL) { + /* yes, we're supposed to be. so now we are. */ + jc->b = serv_got_joined_chat(GJ_GC(gjc), i++, p->from->user); + jc->id = jc->b->id; + jc->state = JCS_ACTIVE; + } else { + /* no, we're not supposed to be. */ + g_free(msg); + return; + } + } + if (p->from->resource) { + if (!y) { + if (!find_chat_buddy(jc->b, p->from->resource)) { + add_chat_buddy(jc->b, p->from->resource); + } else if ((y = xmlnode_get_tag(p->x, "status"))) { + char *buf; + + buf = g_strdup_printf("%s@%s/%s", + p->from->user, p->from->server, p->from->resource); + jabber_track_away(gjc, p, buf, NULL); + g_free(buf); + + } + } else if (jc->b && msg) { + char buf[8192]; + + if (topic) { + char tbuf[8192]; + g_snprintf(tbuf, sizeof(tbuf), "%s", topic); + } + + + g_snprintf(buf, sizeof(buf), "%s", msg); + serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 0, buf, time_sent); + } + } else { /* message from the server */ + if(jc->b && topic) { + char tbuf[8192]; + g_snprintf(tbuf, sizeof(tbuf), "%s", topic); + } + } + + g_free(msg); + g_free(topic); + + } +} + +static void jabber_handlepresence(gjconn gjc, jpacket p) +{ + char *to, *from, *type; + struct buddy *b = NULL; + jid who; + char *buddy; + xmlnode y; + char *show; + int state = 0; + GSList *resources; + char *res; + struct conversation *cnv = NULL; + struct jabber_chat *jc = NULL; + + to = xmlnode_get_attrib(p->x, "to"); + from = xmlnode_get_attrib(p->x, "from"); + type = xmlnode_get_attrib(p->x, "type"); + + if (type && g_strcasecmp(type, "error") == 0) { + return; + } + else if ((y = xmlnode_get_tag(p->x, "show"))) { + show = xmlnode_get_data(y); + if (!show) { + state = 0; + } else if (!g_strcasecmp(show, "away")) { + state = UC_AWAY; + } else if (!g_strcasecmp(show, "chat")) { + state = UC_CHAT; + } else if (!g_strcasecmp(show, "xa")) { + state = UC_XA; + } else if (!g_strcasecmp(show, "dnd")) { + state = UC_DND; + } + } else { + state = 0; + } + + who = jid_new(gjc->p, from); + if (who->user == NULL) { + /* FIXME: transport */ + return; + } + + buddy = g_strdup_printf("%s@%s", who->user, who->server); + + /* um. we're going to check if it's a chat. if it isn't, and there are pending + * chats, create the chat. if there aren't pending chats and we don't have the + * buddy on our list, simply bail out. */ + if ((cnv = NULL) == NULL) { + static int i = 0x70; + if ((jc = find_pending_chat(GJ_GC(gjc), who)) != NULL) { + jc->b = cnv = serv_got_joined_chat(GJ_GC(gjc), i++, who->user); + jc->id = jc->b->id; + jc->state = JCS_ACTIVE; + } else if ((b = find_buddy(GJ_GC(gjc), buddy)) == NULL) { + g_free(buddy); + return; + } + } + + if (!cnv) { + resources = b->proto_data; + res = who->resource; + if (res) + while (resources) { + if (!strcmp(res, resources->data)) + break; + resources = resources->next; + } + + /* keep track of away msg same as yahoo plugin */ + jabber_track_away(gjc, p, normalize(b->name), type); + + if (type && (g_strcasecmp(type, "unavailable") == 0)) { + if (resources) { + g_free(resources->data); + b->proto_data = g_slist_remove(b->proto_data, resources->data); + } + if (!b->proto_data) { + serv_got_update(GJ_GC(gjc), buddy, 0, 0, 0, 0, 0, 0); + } + } else { + if (!resources) { + b->proto_data = g_slist_append(b->proto_data, g_strdup(res)); + } + + serv_got_update(GJ_GC(gjc), buddy, 1, 0, b->signon, b->idle, state, 0); + + } + } else { + if (who->resource) { + char *buf; + + buf = g_strdup_printf("%s@%s/%s", who->user, who->server, who->resource); + jabber_track_away(gjc, p, buf, type); + g_free(buf); + + if (type && !g_strcasecmp(type, "unavailable")) { + struct jabber_data *jd; + if (!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) { + g_free(buddy); + return; + } + jd = jc->gc->proto_data; + /* if it's not ourselves...*/ + if (strcmp(who->resource, jc->Jid->resource) && jc->b) { + remove_chat_buddy(jc->b, who->resource, NULL); + g_free(buddy); + return; + } + + jc->state = JCS_CLOSED; + serv_got_chat_left(GJ_GC(gjc), jc->id); + /* + * TBD: put back some day? + jd->chats = g_slist_remove(jd->chats, jc); + g_free(jc); + */ + } else { + if ((!jc && !(jc = find_existing_chat(GJ_GC(gjc), who))) || !jc->b) { + g_free(buddy); + return; + } + if (!find_chat_buddy(jc->b, who->resource)) { + add_chat_buddy(jc->b, who->resource); + } + } + } + } + + g_free(buddy); + + return; +} + +/* + * Used only by Jabber accept/deny add stuff just below + */ +struct jabber_add_permit { + gjconn gjc; + gchar *user; +}; + +/* + * Common part for Jabber accept/deny adds + * + * "type" says whether we'll permit/deny the subscribe request + */ +static void jabber_accept_deny_add(struct jabber_add_permit *jap, const char *type) +{ + xmlnode g = xmlnode_new_tag("presence"); + + xmlnode_put_attrib(g, "to", jap->user); + xmlnode_put_attrib(g, "type", type); + gjab_send(jap->gjc, g); + + xmlnode_free(g); +} + +/* + * Callback from "accept" in do_ask_dialog() invoked by jabber_handles10n() + */ +static void jabber_accept_add(gpointer w, struct jabber_add_permit *jap) +{ + jabber_accept_deny_add(jap, "subscribed"); + /* + * If we don't already have the buddy on *our* buddylist, + * ask if we want him or her added. + */ + if(find_buddy(GJ_GC(jap->gjc), jap->user) == NULL) { + show_got_added(GJ_GC(jap->gjc), NULL, jap->user, NULL, NULL); + } + g_free(jap->user); + g_free(jap); +} + +/* + * Callback from "deny/cancel" in do_ask_dialog() invoked by jabber_handles10n() + */ +static void jabber_deny_add(gpointer w, struct jabber_add_permit *jap) +{ + jabber_accept_deny_add(jap, "unsubscribed"); + g_free(jap->user); + g_free(jap); +} + +/* + * Handle subscription requests + */ +static void jabber_handles10n(gjconn gjc, jpacket p) +{ + xmlnode g; + char *Jid = xmlnode_get_attrib(p->x, "from"); + char *type = xmlnode_get_attrib(p->x, "type"); + + g = xmlnode_new_tag("presence"); + xmlnode_put_attrib(g, "to", Jid); + + if (!strcmp(type, "subscribe")) { + /* + * A "subscribe to us" request was received - put up the approval dialog + */ + struct jabber_add_permit *jap = g_new0(struct jabber_add_permit, 1); + gchar *msg = g_strdup_printf(_("The user %s wants to add you to their buddy list."), + Jid); + + jap->gjc = gjc; + jap->user = g_strdup(Jid); + do_ask_dialog(GJ_GC(gjc), msg, jap, jabber_accept_add, jabber_deny_add); + + g_free(msg); + xmlnode_free(g); /* Never needed it here anyway */ + return; + + } else if (!strcmp(type, "unsubscribe")) { + /* + * An "unsubscribe to us" was received - simply "approve" it + */ + xmlnode_put_attrib(g, "type", "unsubscribed"); + } else { + /* + * Did we attempt to subscribe to somebody and they do not exist? + */ + if (!strcmp(type, "unsubscribed")) { + xmlnode y; + char *status; + if((y = xmlnode_get_tag(p->x, "status")) && (status = xmlnode_get_data(y)) && + !strcmp(status, "Not Found")) { + char *msg = g_strdup_printf("%s: \"%s\"", _("No such user"), + xmlnode_get_attrib(p->x, "from")); + do_error_dialog(GJ_GC(gjc), msg, _("Jabber Error")); + g_free(msg); + } + } + + xmlnode_free(g); + return; + } + + gjab_send(gjc, g); + xmlnode_free(g); +} + +/* + * Pending subscription to a buddy? + */ +#define BUD_SUB_TO_PEND(sub, ask) ((!g_strcasecmp((sub), "none") || !g_strcasecmp((sub), "from")) && \ + (ask) != NULL && !g_strcasecmp((ask), "subscribe")) + +/* + * Subscribed to a buddy? + */ +#define BUD_SUBD_TO(sub, ask) ((!g_strcasecmp((sub), "to") || !g_strcasecmp((sub), "both")) && \ + ((ask) == NULL || !g_strcasecmp((ask), "subscribe"))) + +/* + * Pending unsubscription to a buddy? + */ +#define BUD_USUB_TO_PEND(sub, ask) ((!g_strcasecmp((sub), "to") || !g_strcasecmp((sub), "both")) && \ + (ask) != NULL && !g_strcasecmp((ask), "unsubscribe")) + +/* + * Unsubscribed to a buddy? + */ +#define BUD_USUBD_TO(sub, ask) ((!g_strcasecmp((sub), "none") || !g_strcasecmp((sub), "from")) && \ + ((ask) == NULL || !g_strcasecmp((ask), "unsubscribe"))) + +/* + * If a buddy is added or removed from the roster on another resource + * jabber_handlebuddy is called + * + * Called with roster item node. + */ +static void jabber_handlebuddy(gjconn gjc, xmlnode x) +{ + xmlnode g; + char *Jid, *name, *sub, *ask; + jid who; + struct buddy *b = NULL; + char *buddyname, *groupname = NULL; + + Jid = xmlnode_get_attrib(x, "jid"); + name = xmlnode_get_attrib(x, "name"); + sub = xmlnode_get_attrib(x, "subscription"); + ask = xmlnode_get_attrib(x, "ask"); + who = jid_new(gjc->p, Jid); + + /* JFIXME: jabber_handleroster() had a "FIXME: transport" at this + * equivilent point. So... + * + * We haven't allocated any memory or done anything interesting to + * this point, so we'll violate Good Coding Structure here by + * simply bailing out. + */ + if (!who || !who->user) { + return; + } + + buddyname = g_strdup_printf("%s@%s", who->user, who->server); + + if((g = xmlnode_get_tag(x, "group")) != NULL) { + groupname = xmlnode_get_data(g); + } + + /* + * Add or remove a buddy? Change buddy's alias or group? + */ + if (BUD_SUB_TO_PEND(sub, ask) || BUD_SUBD_TO(sub, ask)) { + if ((b = find_buddy(GJ_GC(gjc), buddyname)) == NULL) { + add_buddy(GJ_GC(gjc), groupname ? groupname : _("Buddies"), buddyname, + name ? name : buddyname); + } else { + /* struct group *c_grp = find_group_by_buddy(GJ_GC(gjc), buddyname); */ + + /* + * If the buddy's in a new group or his/her alias is changed... + */ + if(groupname) { + int present = b->present; /* save presence state */ + int uc = b->uc; /* and away state (?) */ + int idle = b->idle; + int signon = b->signon; + + /* + * seems rude, but it seems to be the only way... + */ + /* remove_buddy(GJ_GC(gjc), c_grp, b); */ + jabber_remove_buddy(GJ_GC(gjc), b->name, JABBER_GROUP); + + add_buddy(GJ_GC(gjc), groupname, buddyname, + name ? name : buddyname); + if(present) { + serv_got_update(GJ_GC(gjc), buddyname, 1, 0, signon, idle, uc, 0); + } + } else if(name != NULL && strcmp(b->show, name)) { + strncpy(b->show, name, BUDDY_ALIAS_MAXLEN); + b->show[BUDDY_ALIAS_MAXLEN - 1] = '\0'; /* cheap safety feature */ + serv_buddy_rename(GJ_GC(gjc), buddyname, b->show); + } + } + } else if (BUD_USUB_TO_PEND(sub, ask) || BUD_USUBD_TO(sub, ask) || !g_strcasecmp(sub, "remove")) { + jabber_remove_gaim_buddy(GJ_GC(gjc), buddyname); + } + g_free(buddyname); + +} + +static void jabber_handleroster(gjconn gjc, xmlnode querynode) +{ + xmlnode x; + + x = xmlnode_get_firstchild(querynode); + while (x) { + jabber_handlebuddy(gjc, x); + x = xmlnode_get_nextsibling(x); + } + + x = jutil_presnew(0, NULL, "Online"); + gjab_send(gjc, x); + xmlnode_free(x); +} + +static void jabber_handleauthresp(gjconn gjc, jpacket p) +{ + if (jpacket_subtype(p) == JPACKET__RESULT) { + if (xmlnode_has_children(p->x)) { + xmlnode query = xmlnode_get_tag(p->x, "query"); + set_login_progress(GJ_GC(gjc), 4, _("Authenticating")); + if (!xmlnode_get_tag(query, "digest")) { + g_free(gjc->sid); + gjc->sid = NULL; + } + gjab_auth(gjc); + } else { + account_online(GJ_GC(gjc)); + + if (bud_list_cache_exists(GJ_GC(gjc))) + do_import(GJ_GC(gjc), NULL); + + ((struct jabber_data *)GJ_GC(gjc)->proto_data)->did_import = TRUE; + + gjab_reqroster(gjc); + } + } else { + xmlnode xerr; + char *errmsg = NULL; + int errcode = 0; + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + + xerr = xmlnode_get_tag(p->x, "error"); + if (xerr) { + char msg[BUF_LONG]; + errmsg = xmlnode_get_data(xerr); + if (xmlnode_get_attrib(xerr, "code")) { + errcode = atoi(xmlnode_get_attrib(xerr, "code")); + g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg ? errmsg : "Unknown error"); + } else + g_snprintf(msg, sizeof(msg), "%s", errmsg); + hide_login_progress(GJ_GC(gjc), msg); + } else { + hide_login_progress(GJ_GC(gjc), _("Unknown login error")); + } + + jd->die = TRUE; + } +} + +static void jabber_handleversion(gjconn gjc, xmlnode iqnode) { + xmlnode querynode, x; + char *id, *from; + char os[1024]; +#ifndef _WIN32 + struct utsname osinfo; + + uname(&osinfo); + g_snprintf(os, sizeof os, "%s %s %s", osinfo.sysname, osinfo.release, osinfo.machine); +#else + g_snprintf(os, sizeof os, "Windows %d %d", _winmajor, _winminor); +#endif + + + id = xmlnode_get_attrib(iqnode, "id"); + from = xmlnode_get_attrib(iqnode, "from"); + + x = jutil_iqnew(JPACKET__RESULT, NS_VERSION); + + xmlnode_put_attrib(x, "to", from); + xmlnode_put_attrib(x, "id", id); + querynode = xmlnode_get_tag(x, "query"); + xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "name"), PACKAGE, -1); + xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "version"), BITLBEE_VERSION, -1); + xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "os"), os, -1); + + gjab_send(gjc, x); + + xmlnode_free(x); +} + +static void jabber_handletime(gjconn gjc, xmlnode iqnode) { + xmlnode querynode, x; + char *id, *from; + time_t now_t; + struct tm *now; + char buf[1024]; + + time(&now_t); + now = localtime(&now_t); + + id = xmlnode_get_attrib(iqnode, "id"); + from = xmlnode_get_attrib(iqnode, "from"); + + x = jutil_iqnew(JPACKET__RESULT, NS_TIME); + + xmlnode_put_attrib(x, "to", from); + xmlnode_put_attrib(x, "id", id); + querynode = xmlnode_get_tag(x, "query"); + + strftime(buf, 1024, "%Y%m%dT%T", now); + xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "utc"), buf, -1); + strftime(buf, 1024, "%Z", now); + xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "tz"), buf, -1); + strftime(buf, 1024, "%d %b %Y %T", now); + xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "display"), buf, -1); + + gjab_send(gjc, x); + + xmlnode_free(x); +} + +static void jabber_handlelast(gjconn gjc, xmlnode iqnode) { + xmlnode x, querytag; + char *id, *from; + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + char idle_time[32]; + + id = xmlnode_get_attrib(iqnode, "id"); + from = xmlnode_get_attrib(iqnode, "from"); + + x = jutil_iqnew(JPACKET__RESULT, "jabber:iq:last"); + + xmlnode_put_attrib(x, "to", from); + xmlnode_put_attrib(x, "id", id); + querytag = xmlnode_get_tag(x, "query"); + g_snprintf(idle_time, sizeof idle_time, "%ld", jd->idle ? time(NULL) - jd->idle : 0); + xmlnode_put_attrib(querytag, "seconds", idle_time); + + gjab_send(gjc, x); + xmlnode_free(x); +} + +/* + * delete == TRUE: delete found entry + * + * returns pointer to (local) copy of value if found, NULL otherwise + * + * Note: non-reentrant! Local static storage re-used on subsequent calls. + * If you're going to need to keep the returned value, make a copy! + */ +static gchar *jabber_track_queries(GHashTable *queries, gchar *key, gboolean delete) +{ + gpointer my_key, my_val; + static gchar *ret_val = NULL; + + if(ret_val != NULL) { + g_free(ret_val); + ret_val = NULL; + } + + /* self-protection */ + if(queries != NULL && key != NULL) { + if(g_hash_table_lookup_extended(queries, key, &my_key, &my_val)) { + ret_val = g_strdup((gchar *) my_val); + if(delete) { + g_hash_table_remove(queries, key); + g_free(my_key); + g_free(my_val); + } + } + } + + return(ret_val); +} + +static void jabber_handlepacket(gjconn gjc, jpacket p) +{ + char *id; + switch (p->type) { + case JPACKET_MESSAGE: + jabber_handlemessage(gjc, p); + break; + case JPACKET_PRESENCE: + jabber_handlepresence(gjc, p); + break; + case JPACKET_IQ: + id = xmlnode_get_attrib(p->x, "id"); + if (id != NULL && !strcmp(id, IQID_AUTH)) { + jabber_handleauthresp(gjc, p); + break; + } + + if (jpacket_subtype(p) == JPACKET__SET) { + xmlnode querynode; + querynode = xmlnode_get_tag(p->x, "query"); + if (NSCHECK(querynode, "jabber:iq:roster")) { + jabber_handlebuddy(gjc, xmlnode_get_firstchild(querynode)); + } + } else if (jpacket_subtype(p) == JPACKET__GET) { + xmlnode querynode; + querynode = xmlnode_get_tag(p->x, "query"); + if (NSCHECK(querynode, NS_VERSION)) { + jabber_handleversion(gjc, p->x); + } else if (NSCHECK(querynode, NS_TIME)) { + jabber_handletime(gjc, p->x); + } else if (NSCHECK(querynode, "jabber:iq:last")) { + jabber_handlelast(gjc, p->x); + } + } else if (jpacket_subtype(p) == JPACKET__RESULT) { + xmlnode querynode, vcard; + /* char *xmlns; */ + char *from; + + /* + * TBD: ISTM maybe this part could use a serious re-work? + */ + from = xmlnode_get_attrib(p->x, "from"); + querynode = xmlnode_get_tag(p->x, "query"); + vcard = xmlnode_get_tag(p->x, "vCard"); + if (!vcard) + vcard = xmlnode_get_tag(p->x, "VCARD"); + + if (NSCHECK(querynode, NS_ROSTER)) { + jabber_handleroster(gjc, querynode); + } else if (NSCHECK(querynode, NS_VCARD)) { + jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */ + jabber_handlevcard(gjc, querynode, from); + } else if (vcard) { + jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */ + jabber_handlevcard(gjc, vcard, from); + } else { + char *val; + + /* handle "null" query results */ + if((val = jabber_track_queries(gjc->queries, id, TRUE)) != NULL) { + if (!g_strncasecmp(val, "vcard", 5)) { + jabber_handlevcard(gjc, NULL, from); + } + + /* No-op */ + } + } + + } else if (jpacket_subtype(p) == JPACKET__ERROR) { + xmlnode xerr; + char *from, *errmsg = NULL; + int errcode = 0; + + from = xmlnode_get_attrib(p->x, "from"); + xerr = xmlnode_get_tag(p->x, "error"); + if (xerr) { + errmsg = xmlnode_get_data(xerr); + if (xmlnode_get_attrib(xerr, "code")) + errcode = atoi(xmlnode_get_attrib(xerr, "code")); + } + + from = g_strdup_printf("Error %d (%s)", errcode, from); + do_error_dialog(GJ_GC(gjc), errmsg, from); + g_free(from); + + } + + break; + case JPACKET_S10N: + jabber_handles10n(gjc, p); + break; + } + + xmlnode_free(p->x); + + return; +} + +static void jabber_handlestate(gjconn gjc, int state) +{ + switch (state) { + case JCONN_STATE_OFF: + if(gjc->was_connected) { + hide_login_progress_error(GJ_GC(gjc), _("Connection lost")); + } else { + hide_login_progress(GJ_GC(gjc), _("Unable to connect")); + } + signoff(GJ_GC(gjc)); + break; + case JCONN_STATE_CONNECTED: + gjc->was_connected = 1; + set_login_progress(GJ_GC(gjc), 2, _("Connected")); + break; + case JCONN_STATE_ON: + set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method")); + gjab_reqauth(gjc); + break; + } + return; +} + +static void jabber_login(struct aim_user *user) +{ + struct gaim_connection *gc = new_gaim_conn(user); + struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1); + char *loginname = create_valid_jid(user->username, DEFAULT_SERVER, "BitlBee"); + + jd->hash = g_hash_table_new(g_str_hash, g_str_equal); + jd->chats = NULL; /* we have no chats yet */ + + set_login_progress(gc, 1, _("Connecting")); + + if (!(jd->gjc = gjab_new(loginname, user->password, gc))) { + g_free(loginname); + hide_login_progress(gc, _("Unable to connect")); + signoff(gc); + return; + } + + g_free(loginname); + gjab_state_handler(jd->gjc, jabber_handlestate); + gjab_packet_handler(jd->gjc, jabber_handlepacket); + jd->gjc->queries = g_hash_table_new(g_str_hash, g_str_equal); + gjab_start(jd->gjc); +} + +static gboolean jabber_destroy_hash(gpointer key, gpointer val, gpointer data) { + g_free(key); + g_free(val); + return TRUE; +} + +static gboolean jabber_free(gpointer data) +{ + struct jabber_data *jd = data; + + if(jd->gjc != NULL) { + gjab_delete(jd->gjc); + g_free(jd->gjc->sid); + jd->gjc = NULL; + } + g_free(jd); + + return FALSE; +} + +static void jabber_close(struct gaim_connection *gc) +{ + struct jabber_data *jd = gc->proto_data; + + if(jd) { + GSList *jcs = jd->chats; + + /* Free-up the jabber_chat struct allocs and the list */ + while (jcs) { + g_free(jcs->data); + jcs = jcs->next; + } + g_slist_free(jd->chats); + + /* Free-up the away status memories and the list */ + if(jd->hash != NULL) { + g_hash_table_foreach_remove(jd->hash, jabber_destroy_hash, NULL); + g_hash_table_destroy(jd->hash); + jd->hash = NULL; + } + + /* Free-up the pending queries memories and the list */ + if(jd->gjc != NULL && jd->gjc->queries != NULL) { + g_hash_table_foreach_remove(jd->gjc->queries, jabber_destroy_hash, NULL); + g_hash_table_destroy(jd->gjc->queries); + jd->gjc->queries = NULL; + } + } + if (gc->inpa) + gaim_input_remove(gc->inpa); + + if(jd) { + g_timeout_add(50, jabber_free, jd); + if(jd->gjc != NULL) + xmlnode_free(jd->gjc->current); + } + gc->proto_data = NULL; +} + +static int jabber_send_im(struct gaim_connection *gc, char *who, char *message, int len, int flags) +{ + xmlnode x, y; + char *realwho; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + + if (!who || !message) + return 0; + + x = xmlnode_new_tag("message"); + /* Bare username and "username" not the server itself? */ + if (!strchr(who, '@') && strcmp(who, gjc->user->server) != 0) + realwho = g_strdup_printf("%s@%s", who, gjc->user->server); + else + realwho = g_strdup(who); + xmlnode_put_attrib(x, "to", realwho); + g_free(realwho); + + xmlnode_insert_tag(x, "bitlbee"); + xmlnode_put_attrib(x, "type", "chat"); + + if (message && strlen(message)) { + y = xmlnode_insert_tag(x, "body"); + xmlnode_insert_cdata(y, message, -1); + } + + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + xmlnode_free(x); + return 1; +} + +/* + * Add/update buddy's roster entry on server + */ +static void jabber_roster_update(struct gaim_connection *gc, char *name) +{ + xmlnode x, y; + char *realwho; + gjconn gjc; + struct buddy *buddy = NULL; + /* struct group *buddy_group = NULL; */ + + if(gc && gc->proto_data && ((struct jabber_data *)gc->proto_data)->gjc && name) { + gjc = ((struct jabber_data *)gc->proto_data)->gjc; + + if (!strchr(name, '@')) + realwho = g_strdup_printf("%s@%s", name, gjc->user->server); + else { + jid who = jid_new(gjc->p, name); + if (who->user == NULL) { + /* FIXME: transport */ + return; + } + realwho = g_strdup_printf("%s@%s", who->user, who->server); + } + + + x = jutil_iqnew(JPACKET__SET, NS_ROSTER); + y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item"); + xmlnode_put_attrib(y, "jid", realwho); + + + /* If we can find the buddy, there's an alias for him, it's not 0-length + * and it doesn't match his JID, add the "name" attribute. + */ + if((buddy = find_buddy(gc, realwho)) != NULL && + buddy->show != NULL && buddy->show[0] != '\0' && strcmp(realwho, buddy->show)) { + + xmlnode_put_attrib(y, "name", buddy->show); + } + + /* + * Find out what group the buddy's in and send that along + * with the roster item. + */ + /* ** Bitlbee disabled ** + if((buddy_group = NULL) != NULL) { + xmlnode z; + z = xmlnode_insert_tag(y, "group"); + xmlnode_insert_cdata(z, buddy_group->name, -1); + } + ** End - Bitlbee ** */ + + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + + xmlnode_free(x); + g_free(realwho); + } +} + +/* + * Change buddy's group on server roster + */ +static void jabber_group_change(struct gaim_connection *gc, char *name, char *old_group, char *new_group) +{ + if(strcmp(old_group, new_group)) { + jabber_roster_update(gc, name); + } +} + +static void jabber_add_buddy(struct gaim_connection *gc, char *name) +{ + xmlnode x; + char *realwho; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + + if (!((struct jabber_data *)gc->proto_data)->did_import) + return; + + if (!name) + return; + + if (!strcmp(gc->username, name)) + return; + + if (!strchr(name, '@')) + realwho = g_strdup_printf("%s@%s", name, gjc->user->server); + else { + jid who; + + if((who = jid_new(gjc->p, name)) == NULL) { + char *msg = g_strdup_printf("%s: \"%s\"", _("Invalid Jabber I.D."), name); + do_error_dialog(GJ_GC(gjc), msg, _("Jabber Error")); + g_free(msg); + jabber_remove_gaim_buddy(gc, name); + return; + } + if (who->user == NULL) { + /* FIXME: transport */ + return; + } + realwho = g_strdup_printf("%s@%s", who->user, who->server); + } + + x = xmlnode_new_tag("presence"); + xmlnode_put_attrib(x, "to", realwho); + xmlnode_put_attrib(x, "type", "subscribe"); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + xmlnode_free(x); + + jabber_roster_update(gc, realwho); + + g_free(realwho); +} + +static void jabber_remove_buddy(struct gaim_connection *gc, char *name, char *group) +{ + xmlnode x; + char *realwho; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + + if (!name) + return; + + if (!strchr(name, '@')) + realwho = g_strdup_printf("%s@%s", name, gjc->user->server); + else + realwho = g_strdup(name); + + x = xmlnode_new_tag("presence"); + xmlnode_put_attrib(x, "to", realwho); + xmlnode_put_attrib(x, "type", "unsubscribe"); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + g_free(realwho); + xmlnode_free(x); +} + +static void jabber_get_info(struct gaim_connection *gc, char *who) { + xmlnode x; + char *id; + char *realwho; + struct jabber_data *jd = gc->proto_data; + gjconn gjc = jd->gjc; + + x = jutil_iqnew(JPACKET__GET, NS_VCARD); + /* Bare username? */ + if (!strchr(who, '@')) { + realwho = g_strdup_printf("%s@%s", who, gjc->user->server); + } else { + realwho = g_strdup(who); + } + xmlnode_put_attrib(x, "to", realwho); + g_free(realwho); + + id = gjab_getid(gjc); + xmlnode_put_attrib(x, "id", id); + + g_hash_table_insert(jd->gjc->queries, g_strdup(id), g_strdup("vCard")); + + gjab_send(gjc, x); + + xmlnode_free(x); + +} + +static void jabber_get_away_msg(struct gaim_connection *gc, char *who) { + struct jabber_data *jd = gc->proto_data; + gjconn gjc = jd->gjc; + char *status; + + /* space for all elements: Jabber I.D. + "status" + NULL (list terminator) */ + gchar **str_arr = (gchar **) g_new(gpointer, 3); + gchar **ap = str_arr; + gchar *realwho, *final; + + /* Bare username? */ + if (!strchr(who, '@')) { + realwho = g_strdup_printf("%s@%s", who, gjc->user->server); + } else { + realwho = g_strdup(who); + } + *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", realwho); + + if((status = g_hash_table_lookup(jd->hash, realwho)) == NULL) { + status = _("Unknown"); + } + *ap++ = g_strdup_printf("<B>Status:</B> %s<BR>\n", status); + + *ap = NULL; + + final= g_strjoinv(NULL, str_arr); + g_strfreev(str_arr); + + g_free(realwho); + g_free(final); + +} + +static GList *jabber_away_states(struct gaim_connection *gc) { + GList *m = NULL; + + m = g_list_append(m, "Online"); + m = g_list_append(m, "Chatty"); + m = g_list_append(m, "Away"); + m = g_list_append(m, "Extended Away"); + m = g_list_append(m, "Do Not Disturb"); + + return m; +} + +static void jabber_set_away(struct gaim_connection *gc, char *state, char *message) +{ + xmlnode x, y; + struct jabber_data *jd = gc->proto_data; + gjconn gjc = jd->gjc; + + gc->away = NULL; /* never send an auto-response */ + + x = xmlnode_new_tag("presence"); + + if (!strcmp(state, GAIM_AWAY_CUSTOM)) { + /* oh goody. Gaim is telling us what to do. */ + if (message) { + /* Gaim wants us to be away */ + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "away", -1); + y = xmlnode_insert_tag(x, "status"); + { + char *utf8 = str_to_utf8(message); + xmlnode_insert_cdata(y, utf8, -1); + g_free(utf8); + } + gc->away = ""; + } else { + /* Gaim wants us to not be away */ + /* but for Jabber, we can just send presence with no other information. */ + } + } else { + /* state is one of our own strings. it won't be NULL. */ + if (!g_strcasecmp(state, "Online")) { + /* once again, we don't have to put anything here */ + } else if (!g_strcasecmp(state, "Chatty")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "chat", -1); + } else if (!g_strcasecmp(state, "Away")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "away", -1); + gc->away = ""; + } else if (!g_strcasecmp(state, "Extended Away")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "xa", -1); + gc->away = ""; + } else if (!g_strcasecmp(state, "Do Not Disturb")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "dnd", -1); + gc->away = ""; + } + } + + gjab_send(gjc, x); + xmlnode_free(x); +} + +static void jabber_set_idle(struct gaim_connection *gc, int idle) { + struct jabber_data *jd = (struct jabber_data *)gc->proto_data; + jd->idle = idle ? time(NULL) - idle : idle; +} + +static void jabber_keepalive(struct gaim_connection *gc) { + struct jabber_data *jd = (struct jabber_data *)gc->proto_data; + gjab_send_raw(jd->gjc, " \t "); +} + +static void jabber_buddy_free(struct buddy *b) +{ + while (b->proto_data) { + g_free(((GSList *)b->proto_data)->data); + b->proto_data = g_slist_remove(b->proto_data, ((GSList *)b->proto_data)->data); + } +} + +/*---------------------------------------*/ +/* Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + +/* + * V-Card format: + * + * <vCard prodid='' version='' xmlns=''> + * <FN></FN> + * <N> + * <FAMILY/> + * <GIVEN/> + * </N> + * <NICKNAME/> + * <URL/> + * <ADR> + * <STREET/> + * <EXTADD/> + * <LOCALITY/> + * <REGION/> + * <PCODE/> + * <COUNTRY/> + * </ADR> + * <TEL/> + * <EMAIL/> + * <ORG> + * <ORGNAME/> + * <ORGUNIT/> + * </ORG> + * <TITLE/> + * <ROLE/> + * <DESC/> + * <BDAY/> + * </vCard> + * + * See also: + * + * http://docs.jabber.org/proto/html/vcard-temp.html + * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd + */ + +/* + * Cross-reference user-friendly V-Card entry labels to vCard XML tags + * and attributes. + * + * Order is (or should be) unimportant. For example: we have no way of + * knowing in what order real data will arrive. + * + * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag + * name, XML tag's parent tag "path" (relative to vCard node). + * + * List is terminated by a NULL label pointer. + * + * Entries with no label text, but with XML tag and parent tag + * entries, are used by V-Card XML construction routines to + * "automagically" construct the appropriate XML node tree. + * + * Thoughts on future direction/expansion + * + * This is a "simple" vCard. + * + * It is possible for nodes other than the "vCard" node to have + * attributes. Should that prove necessary/desirable, add an + * "attributes" pointer to the vcard_template struct, create the + * necessary tag_attr structs, and add 'em to the vcard_dflt_data + * array. + * + * The above changes will (obviously) require changes to the vCard + * construction routines. + */ + +static struct vcard_template { + char *label; /* label text pointer */ + char *text; /* entry text pointer */ + int visible; /* should entry field be "visible?" */ + int editable; /* should entry field be editable? */ + char *tag; /* tag text */ + char *ptag; /* parent tag "path" text */ + char *url; /* vCard display format if URL */ +} vcard_template_data[] = { + {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL}, + {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL}, + {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL}, + {N_("Nickname"), NULL, TRUE, TRUE, "NICKNAME", NULL, NULL}, + {N_("URL"), NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"}, + {N_("Street Address"), NULL, TRUE, TRUE, "STREET", "ADR", NULL}, + {N_("Extended Address"), NULL, TRUE, TRUE, "EXTADD", "ADR", NULL}, + {N_("Locality"), NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL}, + {N_("Region"), NULL, TRUE, TRUE, "REGION", "ADR", NULL}, + {N_("Postal Code"), NULL, TRUE, TRUE, "PCODE", "ADR", NULL}, + {N_("Country"), NULL, TRUE, TRUE, "COUNTRY", "ADR", NULL}, + {N_("Telephone"), NULL, TRUE, TRUE, "TELEPHONE", NULL, NULL}, + {N_("Email"), NULL, TRUE, TRUE, "EMAIL", NULL, "<A HREF=\"mailto:%s\">%s</A>"}, + {N_("Organization Name"), NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL}, + {N_("Organization Unit"), NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL}, + {N_("Title"), NULL, TRUE, TRUE, "TITLE", NULL, NULL}, + {N_("Role"), NULL, TRUE, TRUE, "ROLE", NULL, NULL}, + {N_("Birthday"), NULL, TRUE, TRUE, "BDAY", NULL, NULL}, + {N_("Description"), NULL, TRUE, TRUE, "DESC", NULL, NULL}, + {"", NULL, TRUE, TRUE, "N", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ADR", NULL, NULL}, + {"", NULL, TRUE, TRUE, "ORG", NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL, NULL} +}; + +/* + * Used by routines to parse an XML-encoded string into an xmlnode tree + */ +typedef struct { + XML_Parser parser; + xmlnode current; +} *xmlstr2xmlnode_parser, xmlstr2xmlnode_parser_struct; + + +/* + * Used by XML_Parse on parsing CDATA + */ +static void xmlstr2xmlnode_charData(void *userdata, const char *s, int slen) +{ + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + + if (xmlp->current) + xmlnode_insert_cdata(xmlp->current, s, slen); +} + +/* + * Used by XML_Parse to start or append to an xmlnode + */ +static void xmlstr2xmlnode_startElement(void *userdata, const char *name, const char **attribs) +{ + xmlnode x; + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + + if (xmlp->current) { + /* Append the node to the current one */ + x = xmlnode_insert_tag(xmlp->current, name); + xmlnode_put_expat_attribs(x, attribs); + + xmlp->current = x; + } else { + x = xmlnode_new_tag(name); + xmlnode_put_expat_attribs(x, attribs); + xmlp->current = x; + } +} + +/* + * Used by XML_Parse to end an xmlnode + */ +static void xmlstr2xmlnode_endElement(void *userdata, const char *name) +{ + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + xmlnode x; + + if (xmlp->current != NULL && (x = xmlnode_get_parent(xmlp->current)) != NULL) { + xmlp->current = x; + } +} + +/* + * Parse an XML-encoded string into an xmlnode tree + * + * Caller is responsible for freeing the returned xmlnode + */ +static xmlnode xmlstr2xmlnode(char *xmlstring) +{ + xmlstr2xmlnode_parser my_parser = g_new(xmlstr2xmlnode_parser_struct, 1); + xmlnode x = NULL; + + my_parser->parser = XML_ParserCreate(NULL); + my_parser->current = NULL; + + XML_SetUserData(my_parser->parser, (void *)my_parser); + XML_SetElementHandler(my_parser->parser, xmlstr2xmlnode_startElement, xmlstr2xmlnode_endElement); + XML_SetCharacterDataHandler(my_parser->parser, xmlstr2xmlnode_charData); + XML_Parse(my_parser->parser, xmlstring, strlen(xmlstring), 0); + + x = my_parser->current; + + XML_ParserFree(my_parser->parser); + g_free(my_parser); + + return(x); +} + +/* + * Insert a tag node into an xmlnode tree, recursively inserting parent tag + * nodes as necessary + * + * Returns pointer to inserted node + * + * Note to hackers: this code is designed to be re-entrant (it's recursive--it + * calls itself), so don't put any "static"s in here! + */ +static xmlnode insert_tag_to_parent_tag(xmlnode start, const char *parent_tag, const char *new_tag) +{ + xmlnode x = NULL; + + /* + * If the parent tag wasn't specified, see if we can get it + * from the vCard template struct. + */ + if(parent_tag == NULL) { + struct vcard_template *vc_tp = vcard_template_data; + + while(vc_tp->label != NULL) { + if(strcmp(vc_tp->tag, new_tag) == 0) { + parent_tag = vc_tp->ptag; + break; + } + ++vc_tp; + } + } + + /* + * If we have a parent tag... + */ + if(parent_tag != NULL ) { + /* + * Try to get the parent node for a tag + */ + if((x = xmlnode_get_tag(start, parent_tag)) == NULL) { + /* + * Descend? + */ + char *grand_parent = strcpy(g_malloc(strlen(parent_tag) + 1), parent_tag); + char *parent; + + if((parent = strrchr(grand_parent, '/')) != NULL) { + *(parent++) = '\0'; + x = insert_tag_to_parent_tag(start, grand_parent, parent); + } else { + x = xmlnode_insert_tag(start, grand_parent); + } + g_free(grand_parent); + } else { + /* + * We found *something* to be the parent node. + * Note: may be the "root" node! + */ + xmlnode y; + if((y = xmlnode_get_tag(x, new_tag)) != NULL) { + return(y); + } + } + } + + /* + * insert the new tag into its parent node + */ + return(xmlnode_insert_tag((x == NULL? start : x), new_tag)); +} + +/* + * Send vCard info to Jabber server + */ +static void jabber_set_info(struct gaim_connection *gc, char *info) +{ + xmlnode x, vc_node; + char *id; + struct jabber_data *jd = gc->proto_data; + gjconn gjc = jd->gjc; + + x = xmlnode_new_tag("iq"); + xmlnode_put_attrib(x,"type","set"); + + id = gjab_getid(gjc); + + xmlnode_put_attrib(x, "id", id); + + /* + * Send only if there's actually any *information* to send + */ + if((vc_node = xmlstr2xmlnode(info)) != NULL && xmlnode_get_name(vc_node) != NULL && + g_strncasecmp(xmlnode_get_name(vc_node), "vcard", 5) == 0) { + xmlnode_insert_tag_node(x, vc_node); + gjab_send(gjc, x); + } + + xmlnode_free(x); +} + +/* + * displays a Jabber vCard + */ +static void jabber_handlevcard(gjconn gjc, xmlnode querynode, char *from) +{ + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + jid who = jid_new(gjc->p, from); + char *status = NULL, *text = NULL; + GString *str = g_string_sized_new(100); + xmlnode child; + + gchar *buddy = NULL; + + if(querynode == NULL) { + serv_got_crap(GJ_GC(gjc), "%s - Received empty info reply from %s", _("User Info"), from); + return; + } + + if(who->resource != NULL && (who->resource)[0] != '\0') { + buddy = g_strdup_printf("%s@%s/%s", who->user, who->server, who->resource); + } else { + buddy = g_strdup_printf("%s@%s", who->user, who->server); + } + + if((status = g_hash_table_lookup(jd->hash, buddy)) == NULL) { + status = _("Unknown"); + } + + g_string_sprintfa(str, "%s: %s - %s: %s", _("Jabber ID"), buddy, _("Status"), + status); + + for(child = querynode->firstchild; child; child = child->next) + { + xmlnode child2; + + if(child->type != NTYPE_TAG) + continue; + + text = xmlnode_get_data(child); + if(text && !strcmp(child->name, "FN")) { + info_string_append(str, "\n", _("Full Name"), text); + } else if (!strcmp(child->name, "N")) { + for (child2 = child->firstchild; child2; child2 = child2->next) { + char *text2 = NULL; + + if (child2->type != NTYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if (text2 && !strcmp(child2->name, "FAMILY")) { + info_string_append(str, "\n", _("Family Name"), text2); + } else if (text2 && !strcmp(child2->name, "GIVEN")) { + info_string_append(str, "\n", _("Given Name"), text2); + } else if (text2 && !strcmp(child2->name, "MIDDLE")) { + info_string_append(str, "\n", _("Middle Name"), text2); + } + } + } else if (text && !strcmp(child->name, "NICKNAME")) { + info_string_append(str, "\n", _("Nickname"), text); + } else if (text && !strcmp(child->name, "BDAY")) { + info_string_append(str, "\n", _("Birthday"), text); + } else if (!strcmp(child->name, "ADR")) { + /* show wich address it is */ + /* Just for the beauty of bitlbee + if (child->firstchild) + g_string_sprintfa(str, "%s:\n", _("Address")); + */ + for(child2 = child->firstchild; child2; child2 = child2->next) { + char *text2 = NULL; + + if(child2->type != NTYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if(text2 && !strcmp(child2->name, "POBOX")) { + info_string_append(str, "\n", + _("P.O. Box"), text2); + } else if(text2 && !strcmp(child2->name, "EXTADR")) { + info_string_append(str, "\n", + _("Extended Address"), text2); + } else if(text2 && !strcmp(child2->name, "STREET")) { + info_string_append(str, "\n", + _("Street Address"), text2); + } else if(text2 && !strcmp(child2->name, "LOCALITY")) { + info_string_append(str, "\n", + _("Locality"), text2); + } else if(text2 && !strcmp(child2->name, "REGION")) { + info_string_append(str, "\n", + _("Region"), text2); + } else if(text2 && !strcmp(child2->name, "PCODE")) { + info_string_append(str, "\n", + _("Postal Code"), text2); + } else if(text2 && (!strcmp(child2->name, "CTRY") + || !strcmp(child2->name, "COUNTRY"))) { + info_string_append(str, "\n", _("Country"), text2); + } + } + } else if(!strcmp(child->name, "TEL")) { + char *number = NULL; + if ((child2 = xmlnode_get_tag(child, "NUMBER"))) { + /* show what kind of number it is */ + number = xmlnode_get_data(child2); + if(number) { + info_string_append(str, "\n", _("Telephone"), number); + } + } else if((number = xmlnode_get_data(child))) { + /* lots of clients (including gaim) do this, + * but it's out of spec */ + info_string_append(str, "\n", _("Telephone"), number); + } + } else if(!strcmp(child->name, "EMAIL")) { + char *userid = NULL; + if((child2 = xmlnode_get_tag(child, "USERID"))) { + /* show what kind of email it is */ + userid = xmlnode_get_data(child2); + if(userid) { + info_string_append(str, "\n", _("Email"), userid); + } + } else if((userid = xmlnode_get_data(child))) { + /* lots of clients (including gaim) do this, + * but it's out of spec */ + info_string_append(str, "\n", _("Email"), userid); + } + } else if(!strcmp(child->name, "ORG")) { + for(child2 = child->firstchild; child2; child2 = child2->next) { + char *text2 = NULL; + + if(child2->type != NTYPE_TAG) + continue; + + text2 = xmlnode_get_data(child2); + if(text2 && !strcmp(child2->name, "ORGNAME")) { + info_string_append(str, "\n", _("Organization Name"), text2); + } else if(text2 && !strcmp(child2->name, "ORGUNIT")) { + info_string_append(str, "\n", _("Organization Unit"), text2); + } + } + } else if(text && !strcmp(child->name, "TITLE")) { + info_string_append(str, "\n", _("Title"), text); + } else if(text && !strcmp(child->name, "ROLE")) { + info_string_append(str, "\n", _("Role"), text); + } else if(text && !strcmp(child->name, "DESC")) { + g_string_sprintfa(str, "\n%s:\n%s\n%s", _("Description"), + text, _("End of Description")); + } + } + + serv_got_crap(GJ_GC(gjc), "%s\n%s", _("User Info"), str->str); + + g_free(buddy); + g_string_free(str, TRUE); +} + + +static GList *jabber_actions() +{ + GList *m = NULL; + + m = g_list_append(m, _("Set User Info")); + /* + m = g_list_append(m, _("Set Dir Info")); + m = g_list_append(m, _("Change Password")); + */ + + return m; +} + +static struct prpl *my_protocol = NULL; + +void jabber_init(struct prpl *ret) +{ + /* the NULL's aren't required but they're nice to have */ + ret->protocol = PROTO_JABBER; + ret->name = jabber_name; + ret->away_states = jabber_away_states; + ret->actions = jabber_actions; + ret->login = jabber_login; + ret->close = jabber_close; + ret->send_im = jabber_send_im; + ret->set_info = jabber_set_info; + ret->get_info = jabber_get_info; + ret->set_away = jabber_set_away; + ret->get_away = jabber_get_away_msg; + ret->set_idle = jabber_set_idle; + ret->add_buddy = jabber_add_buddy; + ret->remove_buddy = jabber_remove_buddy; + ret->add_permit = NULL; + ret->add_deny = NULL; + ret->rem_permit = NULL; + ret->rem_deny = NULL; + ret->set_permit_deny = NULL; + ret->keepalive = jabber_keepalive; + ret->buddy_free = jabber_buddy_free; + ret->alias_buddy = jabber_roster_update; + ret->group_buddy = jabber_group_change; + + my_protocol = ret; +} |