--- a/ChangeLog Fri Aug 30 03:11:27 2002 -0400
+++ b/ChangeLog Fri Aug 30 07:54:54 2002 -0400
@@ -16,6 +16,9 @@
* Prevent a possible crash in unhide_buddy_list() (Thanks Ari
* Fixed a compilation problem on systems without iconv.
+ * GtkIMHtml can be set to render font sizes as point size + or AIMish relative sizes -- no more huge Yahoo fonts. (Thanks version 0.59.1 (08/25/2002)
* Created a gtk1-stable branch for GTK+ 1.2 bugfix releases.
--- a/src/conversation.c Fri Aug 30 03:11:27 2002 -0400
+++ b/src/conversation.c Fri Aug 30 07:54:54 2002 -0400
@@ -92,7 +92,7 @@
static void remove_checkbox(struct conversation *);
static void update_smilies(struct conversation *c);
+static void update_fontmode(struct conversation *c); /*------------------------------------------------------------------------*/
@@ -193,6 +193,7 @@
plugin_event(event_new_conversation, name, 0, 0, 0);
@@ -1766,6 +1767,12 @@
+ if (c->gc && c->gc->prpl->options & OPT_PROTO_USE_POINT_SIZE) if (c->is_chat && (!c->gc || !g_slist_find(c->gc->buddy_chats, c)))
@@ -1819,11 +1826,11 @@
if (flags & WFLAG_SYSTEM) {
if (convo_options & OPT_CONVO_SHOW_TIME)
- g_snprintf(buf, BUF_LONG, "<FONT SIZE=\"2\">(%s) </FONT><B>%s</B>", mdate, what);
+ g_snprintf(buf, BUF_LONG, "<FONT SIZE=\"%d\">(%s) </FONT><B>%s</B>", timesize, mdate, what); g_snprintf(buf, BUF_LONG, "<B>%s</B>", what);
- g_snprintf(buf2, sizeof(buf2), "<FONT SIZE=\"2\"><!--(%s) --></FONT><B>%s</B><BR>",
+ g_snprintf(buf2, sizeof(buf2), "<FONT SIZE=\"%d\"><!--(%s) --></FONT><B>%s</B><BR>", + timesize, mdate, what); gtk_imhtml_append_text(GTK_IMHTML(c->text), buf2, -1, 0);
@@ -1905,12 +1912,12 @@
if (convo_options & OPT_CONVO_SHOW_TIME)
- g_snprintf(buf, BUF_LONG, "<FONT COLOR=\"%s\"><FONT SIZE=\"2\">(%s) </FONT>"
- "<B>%s</B></FONT> ", colour, mdate, str);
+ g_snprintf(buf, BUF_LONG, "<FONT COLOR=\"%s\"><FONT SIZE=\"%d\">(%s) </FONT>" + "<B>%s</B></FONT> ", colour, timesize, mdate, str); g_snprintf(buf, BUF_LONG, "<FONT COLOR=\"%s\"><B>%s</B></FONT> ", colour, str);
- g_snprintf(buf2, BUF_LONG, "<FONT COLOR=\"%s\"><FONT SIZE=\"2\"><!--(%s) --></FONT>"
- "<B>%s</B></FONT> ", colour, mdate, str);
+ g_snprintf(buf2, BUF_LONG, "<FONT COLOR=\"%s\"><FONT SIZE=\"%d\"><!--(%s) --></FONT>" + "<B>%s</B></FONT> ", colour, timesize, mdate, str); @@ -2328,6 +2335,7 @@
gaim_setup_imhtml_smileys(cnv->text);
@@ -2487,6 +2495,7 @@
gaim_setup_imhtml_smileys(c->text);
@@ -3528,6 +3537,15 @@
+void update_fontmode(struct conversation *c) + gtk_imhtml_set_use_pointsize(GTK_IMHTML(c->text), + c->gc->prpl->options & OPT_PROTO_USE_POINT_SIZE); void update_smilies(struct conversation *c)
--- a/src/gtkimhtml.c Fri Aug 30 03:11:27 2002 -0400
+++ b/src/gtkimhtml.c Fri Aug 30 07:54:54 2002 -0400
@@ -69,7 +69,8 @@
-#define POINT_SIZE(x) (_point_sizes [MIN ((x), MAX_FONT_SIZE) - 1])
+#define POINT_SIZE(x) (imhtml->use_pointsize ? x * 10 : \ + _point_sizes [MIN ((x), MAX_FONT_SIZE) - 1]) static gint _point_sizes [] = { 80, 100, 120, 140, 200, 300, 400 };
#define DEFAULT_PRE_FACE "courier"
@@ -260,6 +261,11 @@
gtk_imhtml_init_smileys (imhtml);
+gtk_imhtml_set_use_pointsize (GtkIMHtml *imhtml, gboolean point) + imhtml->use_pointsize = point; --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/jabber/jabber.c Fri Aug 30 07:54:54 2002 -0400
@@ -0,0 +1,3857 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + * 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 +#include <sys/utsname.h> +#include "pixmaps/available.xpm" +#include "pixmaps/available-away.xpm" +#include "pixmaps/available-chat.xpm" +#include "pixmaps/available-xa.xpm" +#include "pixmaps/available-dnd.xpm" +#include "pixmaps/available-error.xpm" +/* 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 UC_AWAY (0x02 | UC_UNAVAILABLE) +#define UC_XA (0x08 | UC_UNAVAILABLE) +#define UC_DND (0x10 | UC_UNAVAILABLE) +#define UC_ERROR (0x20 | UC_UNAVAILABLE) +#define DEFAULT_SERVER "jabber.org" +#define DEFAULT_GROUPCHAT "conference.jabber.org" +#define DEFAULT_PORT 5222 +#define JABBER_TYPING_NOTIFY_INT 15 /* Delay (in seconds) between sending typing notifications */ + * 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 { + pool p; /* Memory allocation pool */ + int state; /* Connection state flag */ + int was_connected; /* We were once connected */ + int fd; /* Connection file descriptor */ + jid user; /* User info */ + char *pass; /* User passwd */ + 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 */ +} *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 + * It is *this* to which we point the buddy proto_data +struct jabber_buddy_data { +typedef struct jabber_resource_info { + gboolean has_composing; + * For our own jid handling + * We do our own so we can cleanly parse buddy names + * (user@server/resource) and rid ourselves of the + * struct when we're done with it. The Jabber lib + * structs last the life of the pool--we frequently + * We use the real jid structs so we can make use of + * jid_safe(), jid_cmp() and some others. + * BE CAREFUL using the Jabber lib routines. + * Many of them assume pool use and are not + * amenable to use with our own! + * We give them special names so we know, throughout + * the code, that they're not alloc'd out of pool + * memory and we can, and must, dispose of them when +#define gaim_jid_struct jid_struct +typedef struct gaim_jid_struct *gaim_jid; + * Jabber "chat group" info. Pointers to these go in jabber_data + * pending and existing chats lists. + struct gaim_connection *gc; + struct conversation *b; + * 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, +#define JCS_PENDING 1 /* pending */ +#define JCS_ACTIVE 2 /* active */ +#define JCS_CLOSED 3 /* closed */ +static char *jabber_name() +#define STATE_EVT(arg) if(gjc->on_state) { (gjc->on_state)(gjc, (arg) ); } +static void jabber_handlevcard(gjconn, xmlnode, char *); +static char *create_valid_jid(const char *given, char *server, char *resource) + 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); + valid = g_strdup(given); + * Dispose of a gaim_jid_struct +static void gaim_jid_free(gaim_jid gjid) + * Create a new gjid struct + * Unlike jid_new(), also creates "full." + * Shamelessly copied, in part, from jid.c: jid_new() + * Caller is responsible for freeing the space allocated by this via + * JFIXME: Has a local declaration for jid.c:jid_safe(). I've put in a + * request to have that added to libjabber's lib.h file. (JSeymour) +static gaim_jid gaim_jid_new(char *name) + extern jid jid_safe(jid); /* *retch* */ + if(name && strlen(name)) { + char *server, *resource, *type, *str; + /* user@server/resource */ + str = strdup(name); /* we mangle a copy */ + gjid = calloc(1, sizeof(struct gaim_jid_struct)); + if((resource = strstr(str, "/")) != NULL) { + if((full_len = strlen(resource)) > 0) { + gjid->resource = strdup(resource); + ++full_len; /* for later "/" addition */ + resource = str + strlen(str); /* point to end */ + type = strstr(str, ":"); + if(type != NULL && type < resource) { + str = type; /* ignore the type: prefix */ + server = strstr(str, "@"); + * if there's no @, it's just the server address + if(server == NULL || server > resource) { + gjid->server = strdup(str); + full_len += strlen(str); + gjid->server = strdup(server); + full_len += strlen(server) + 1; /* account for later "@" */ + gjid->user = strdup(str); + full_len += strlen(str); + char *s = gjid->full = malloc(++full_len); + s += strlen(gjid->user); + strcpy(s, gjid->server); + s += strlen(gjid->server); + strcpy(s, gjid->resource); + * Get a "username@server" from unadorned "username" + * If there's no "@server" part and "who" doesn't match the + * gjconn server (which would indicate that "who" *is* the + * server in case of server messages), the gjconn server is + * If incl_resource is TRUE (non-0), the returned string + * includes the "/resource" part (if it exists), otherwise not. + * Allocates space for returned string. Caller is + * responsible for freeing it with g_free(). + * If "gjid" is non-null, sets that as well. Caller is + * reponsible for freeing that via gaim_jid_free() when done +static gchar *get_realwho(gjconn gjc, char *who, int incl_resource, gaim_jid *gjid) + * Bare username and "username" not the server itself? + if(!strchr(who, '@') && strcasecmp(who, gjc->user->server)) { + my_who = g_strdup_printf("%s@%s", who, gjc->user->server); + my_who = g_strdup(who); + if((my_gjid = gaim_jid_new(my_who)) != NULL) { + * If there's no "user" part, "who" was just the server or perhaps a transport (?) + * Include "/resource" bit? + realwho = g_strdup(my_gjid->full); + realwho = g_strdup_printf("%s@%s", my_gjid->user, my_gjid->server); + realwho = g_strdup(my_gjid->server); + gaim_jid_free(my_gjid); +static gjconn gjab_new(char *user, char *pass, void *priv) + gjc = pmalloc_x(p, sizeof(gjconn_struct), 0); + pool_free(p); /* no need for this anymore! */ + if((gjc->user = jid_new(p, user)) == NULL) { + pool_free(p); /* no need for this anymore! */ + gjc->pass = strdup(pass); + gjc->state = JCONN_STATE_OFF; + gjc->was_connected = 0; +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_stop(gjconn gjc) + if (!gjc || gjc->state == JCONN_STATE_OFF) + gjab_send_raw(gjc, "</stream:stream>"); + gjc->state = JCONN_STATE_OFF; + gjc->was_connected = 0; + XML_ParserFree(gjc->parser); +static int gjab_getfd(gjconn gjc) +static jid gjab_getjid(gjconn gjc) +static char *gjab_getsid(gjconn gjc) +static char *gjab_getid(gjconn gjc) + snprintf(gjc->idbuf, 8, "%d", gjc->id++); +static void gjab_send(gjconn gjc, xmlnode x) + if (gjc && gjc->state != JCONN_STATE_OFF) { + char *buf = xmlnode2str(x); + write(gjc->fd, buf, strlen(buf)); + debug_printf("gjab_send: %s\n", buf); +static void gjab_send_raw(gjconn gjc, const char *str) + if (gjc && gjc->state != JCONN_STATE_OFF) { + * JFIXME: No error detection?!?! + if(write(gjc->fd, str, strlen(str)) < 0) { + fprintf(stderr, "DBG: Problem sending. Error: %d\n", errno); + debug_printf("gjab_send_raw: %s\n", str); +static void gjab_reqroster(gjconn gjc) + x = jutil_iqnew(JPACKET__GET, NS_ROSTER); + xmlnode_put_attrib(x, "id", gjab_getid(gjc)); +static void gjab_reqauth(gjconn gjc) + x = jutil_iqnew(JPACKET__GET, NS_AUTH); + xmlnode_put_attrib(x, "id", IQID_AUTH); + y = xmlnode_get_tag(x, "query"); + user = gjc->user->user; + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, user, -1); +static void gjab_auth(gjconn gjc) + x = jutil_iqnew(JPACKET__SET, NS_AUTH); + xmlnode_put_attrib(x, "id", IQID_AUTH); + y = xmlnode_get_tag(x, "query"); + user = gjc->user->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); + debug_printf("digest authentication (sid %s)\n", 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); + xmlnode_insert_cdata(z, hash, 40); + z = xmlnode_insert_tag(y, "password"); + xmlnode_insert_cdata(z, gjc->pass, -1); +static void gjab_recv(gjconn gjc) + if (!gjc || gjc->state == JCONN_STATE_OFF) + if ((len = read(gjc->fd, buf, sizeof(buf) - 1)) > 0) { + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + debug_printf("input (len %d): %s\n", len, buf); + XML_Parse(gjc->parser, buf, len, 0); + } else if (len < 0 || errno != EAGAIN) { + STATE_EVT(JCONN_STATE_OFF) +static void startElement(void *userdata, const char *name, const char **attribs) + gjconn gjc = (gjconn) userdata; + /* Append the node to the current one */ + x = xmlnode_insert_tag(gjc->current, name); + xmlnode_put_expat_attribs(x, attribs); + 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) */ +static void endElement(void *userdata, const char *name) + gjconn gjc = (gjconn) userdata; + if (gjc->current == NULL) { + /* we got </stream:stream> */ + STATE_EVT(JCONN_STATE_OFF) + x = xmlnode_get_parent(gjc->current); + /* it is time to fire the event */ + p = jpacket_new(gjc->current); + (gjc->on_packet) (gjc, p); + xmlnode_free(gjc->current); +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; +static void charData(void *userdata, const char *s, int slen) + gjconn gjc = (gjconn) userdata; + xmlnode_insert_cdata(gjc->current, s, slen); +static void gjab_connected(gpointer data, gint source, GaimInputCondition cond) + struct gaim_connection *gc = data; + struct jabber_data *jd; + if (!g_slist_find(connections, gc)) { + STATE_EVT(JCONN_STATE_OFF) + gjc->state = JCONN_STATE_CONNECTED; + STATE_EVT(JCONN_STATE_CONNECTED) + x = jutil_header(NS_CLIENT, gjc->user->server); + /* this is ugly, we can create the string here instead of jutil_header */ + /* what do you think about it? -madcat */ + gjab_send_raw(gjc, "<?xml version='1.0'?>"); + gjc->state = JCONN_STATE_ON; + STATE_EVT(JCONN_STATE_ON); + gc->inpa = gaim_input_add(gjc->fd, GAIM_INPUT_READ, jabber_callback, gc); +static void gjab_start(gjconn gjc) + if (!gjc || gjc->state != JCONN_STATE_OFF) + user = GJ_GC(gjc)->user; + port = user->proto_opt[USEROPT_PORT][0] ? atoi(user->proto_opt[USEROPT_PORT]) : DEFAULT_PORT; + gjc->parser = XML_ParserCreate(NULL); + XML_SetUserData(gjc->parser, (void *)gjc); + XML_SetElementHandler(gjc->parser, startElement, endElement); + XML_SetCharacterDataHandler(gjc->parser, charData); + gjc->fd = proxy_connect(gjc->user->server, port, gjab_connected, GJ_GC(gjc)); + if (!user->gc || (gjc->fd < 0)) { + STATE_EVT(JCONN_STATE_OFF) + * Find chat by chat group name +static struct conversation *find_chat(struct gaim_connection *gc, char *name) + GSList *bcs = gc->buddy_chats; + struct conversation *b = NULL; + char *chat = g_strdup(normalize(name)); + if (!strcasecmp(normalize(b->name), chat)) + * Find chat by "chat id" + * Returns: 0 on success and jabber_chat pointer set + * or -EINVAL on error and jabber_chat pointer is + * TBD: Slogging through the buddy_chats list seems + * redundant since the chat i.d. is mirrored in the + * jabber_chat struct list. But that's the way it + * was, so that's the way I'm leaving it--for now. +static int jabber_find_chat_by_convo_id(struct gaim_connection *gc, int id, struct jabber_chat **jc) + GSList *bcs = gc->buddy_chats; + struct conversation *b = NULL; + struct jabber_data *jd = gc->proto_data; + if ((*jc)->state == JCS_ACTIVE && (*jc)->b == b) + return(bcs == NULL? -EINVAL : 0); +static struct jabber_chat *find_any_chat(struct gaim_connection *gc, jid chat) + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; + struct jabber_chat *jc = NULL; + if (!jid_cmpx(chat, jc->gjid, JID_USER | JID_SERVER)) + * 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; + if (jc->state == JCS_ACTIVE && !jid_cmpx(chat, jc->gjid, JID_USER | JID_SERVER)) +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; + if (jc->state == JCS_PENDING && !jid_cmpx(chat, jc->gjid, JID_USER | JID_SERVER)) +static gboolean find_chat_buddy(struct conversation *b, char *name) + if (!strcmp(m->data, name)) + * Remove a buddy from the (gaim) buddylist (if he's on it) +static void jabber_remove_gaim_buddy(struct gaim_connection *gc, char *buddyname) + if ((b = find_buddy(gc, buddyname)) != NULL) { + group = find_group_by_buddy(gc, buddyname); + debug_printf("removing buddy [1]: %s, from group: %s\n", buddyname, group->name); + remove_buddy(gc, group, b); +static void jabber_change_passwd(struct gaim_connection *gc, char *old, char *new) + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + if(strcmp(old, gjc->pass)) + do_error_dialog(_("Incorrect current password! Password NOT Changed!"), + _("Password Change Error!")); + else if(!strcmp(old, new)) + do_error_dialog(_("New password same as old password! Password NOT Changed!"), + _("Password Change Error!")); + x = jutil_iqnew(JPACKET__SET, NS_REGISTER); + xmlnode_put_attrib(x, "to", gjc->user->server); + y = xmlnode_get_tag(x, "query"); + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, gjc->user->user, -1); + z = xmlnode_insert_tag(y, "password"); + xmlnode_insert_cdata(z, new, -1); + xmlnode_put_attrib(x, "id", id); + gjc->pass = strdup(new); + g_hash_table_insert(gjc->queries, g_strdup(id), g_strdup("change_password")); +static struct jabber_buddy_data* jabber_find_buddy(struct gaim_connection *gc, char *buddy) + struct jabber_data *jd = gc->proto_data; + if((realwho = get_realwho(jd->gjc, buddy, FALSE, NULL)) == NULL) + val = g_hash_table_lookup(jd->buddies, realwho); + return (struct jabber_buddy_data *)val; + struct jabber_buddy_data *jbd = g_new0(struct jabber_buddy_data, 1); + g_hash_table_insert(jd->buddies, g_strdup(realwho), jbd); + * find a resource by name, or if no name given, return the "default" resource + * default being the highest priority one. +static jab_res_info jabber_find_resource(struct gaim_connection *gc, char *who) + struct jabber_buddy_data *jbd = jabber_find_buddy(gc, who); + jab_res_info jri = NULL; + char *res = strstr(who, "/"); + resources = jbd->resources; + jri = (jab_res_info) resources->data; + } else if(!res) { /* we're looking for the default priority, so... */ + if(((jab_res_info) resources->data)->priority >= jri->priority) + jri = (jab_res_info) resources->data; + if(!strcasecmp(((jab_res_info) resources->data)->name, res)) { + jri = (jab_res_info) resources->data; + resources = resources->next; + * if the resource doesn't exist, create it. otherwise, just update the priority +static void jabber_track_resource(struct gaim_connection *gc, + struct jabber_buddy_data *jbd = jabber_find_buddy(gc, buddy); + char *who = g_strdup_printf("%s/%s", buddy, res); + jab_res_info jri = jabber_find_resource(gc, who); + jri = g_new0(struct jabber_resource_info, 1); + jri->name = g_strdup(res); + jbd->resources = g_slist_append(jbd->resources, jri); + jri->priority = priority; + * remove the resource, if it exists +static void jabber_remove_resource(struct gaim_connection *gc, char *buddy, char *res) + struct jabber_buddy_data *jbd = jabber_find_buddy(gc, buddy); + char *who = g_strdup_printf("%s/%s", buddy, res); + jab_res_info jri = jabber_find_resource(gc, who); + jbd->resources = g_slist_remove(jbd->resources, jri); + * grab the away message for the default resource +static char *jabber_lookup_away(gjconn gjc, char *name) + jab_res_info jri = jabber_find_resource(GJ_GC(gjc), name); + if(!jri || !jri->away_msg) +static void jabber_track_away(gjconn gjc, jpacket p, char *type) + jab_res_info jri = NULL; + if(!p || !p->from || !p->from->resource || !p->from->user) + jri = jabber_find_resource(GJ_GC(gjc), jid_full(p->from)); + if (type && (strcasecmp(type, "unavailable") == 0)) { + vshow = _("Unavailable"); + if((show = xmlnode_get_tag_data(p->x, "show")) != NULL) { + if (!strcasecmp(show, "away")) { + } else if (!strcasecmp(show, "chat")) { + } else if (!strcasecmp(show, "xa")) { + vshow = _("Extended Away"); + } else if (!strcasecmp(show, "dnd")) { + vshow = _("Do Not Disturb"); + status = g_strdup(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)); + msg = g_strdup(_("Online")); +static void jabber_convo_closed(struct gaim_connection *gc, char *name) + jab_res_info jri = jabber_find_resource(gc, name); + g_free(jri->thread_id); +static void jabber_track_convo_thread(gjconn gjc, char *name, char *thread_id) + jab_res_info jri = jabber_find_resource(GJ_GC(gjc), name); + g_free(jri->thread_id); + jri->thread_id = g_strdup(thread_id); +static char *jabber_get_convo_thread(gjconn gjc, char *name) + jab_res_info jri = jabber_find_resource(GJ_GC(gjc), name); + ct = g_strdup(jri->thread_id); +static time_t iso8601_to_time(char *timestamp) + 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)) + tzset(); /* making sure */ +static void jabber_handlemessage(gjconn gjc, jpacket p) + time_t time_sent = time(NULL); + gboolean typing = FALSE; + char *from = NULL, *msg = NULL, *type = NULL, *topic = NULL; + char *thread_id = NULL; + char *conference_room = NULL; + type = xmlnode_get_attrib(p->x, "type"); + if ((y = xmlnode_get_tag(p->x, "thread"))) + thread_id = xmlnode_get_data(y); + y = xmlnode_get_firstchild(p->x); + if(NSCHECK(y, NS_DELAY)) { + char *timestamp = xmlnode_get_attrib(y, "stamp"); + time_sent = iso8601_to_time(timestamp); + } else if(NSCHECK(y, "jabber:x:event")) { + if(xmlnode_get_tag(y, "composing")) + } else if(NSCHECK(y, "jabber:x:conference")) { + conference_room = xmlnode_get_attrib(y, "jid"); + y = xmlnode_get_nextsibling(y); + if (!type || !strcasecmp(type, "normal") || !strcasecmp(type, "chat")) { + from = jid_full(p->from); + if ((y = xmlnode_get_tag(p->x, "html"))) { + msg = xmlnode_get_data(y); + if ((y = xmlnode_get_tag(p->x, "body"))) { + msg = xmlnode_get_data(y); + msg = utf8_to_str(msg); + data = g_strsplit(conference_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)); + serv_got_chat_invite(GJ_GC(gjc), conference_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, + jab_res_info jri = jabber_find_resource(GJ_GC(gjc), jid_full(p->from)); + jri->has_composing = TRUE; + if (xmlnode_get_tag(p->x, "gaim")) + flags = IM_FLAG_GAIMUSER; + if ((y = xmlnode_get_tag(p->x, "thread"))) + jabber_track_convo_thread(gjc, jid_full(p->from), + if (find_conversation(jid_full(p->from))) + serv_got_im(GJ_GC(gjc), jid_full(p->from), m, flags, + from = g_strdup_printf("%s@%s", p->from->user, + from = g_strdup(p->from->server); + serv_got_im(GJ_GC(gjc), from, m, flags, time_sent, -1); + /* a non-message message! */ + from = g_strdup_printf("%s@%s", p->from->user, p->from->server); + serv_got_typing(GJ_GC(gjc), from, 0); + serv_got_typing_stopped(GJ_GC(gjc), from); + } else if (!strcasecmp(type, "error")) { + if ((y = xmlnode_get_tag(p->x, "error"))) { + type = xmlnode_get_attrib(y, "code"); + msg = xmlnode_get_data(y); + from = g_strdup_printf("Error %s", type ? type : ""); + do_error_dialog(msg, from); + } else if (!strcasecmp(type, "groupchat")) { + struct jabber_chat *jc; + if ((y = xmlnode_get_tag(p->x, "html"))) { + msg = xmlnode_get_data(y); + 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); + /* 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->state = JCS_ACTIVE; + /* no, we're not supposed to be. */ + if (p->from->resource) { + 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"))) { + jabber_track_away(gjc, p, NULL); + } else if (jc->b && msg) { + g_snprintf(tbuf, sizeof(tbuf), "%s", topic); + chat_set_topic(jc->b, p->from->resource, tbuf); + g_snprintf(buf, sizeof(buf), "%s", msg); + serv_got_chat_in(GJ_GC(gjc), jc->b->id, p->from->resource, 0, buf, + } else { /* message from the server */ + g_snprintf(tbuf, sizeof(tbuf), "%s", topic); + chat_set_topic(jc->b, "", tbuf); + debug_printf("unhandled message %s\n", type); +static void jabber_handlepresence(gjconn gjc, jpacket p) + char *to, *from, *type; + struct buddy *b = NULL; + struct conversation *cnv = NULL; + struct jabber_chat *jc = NULL; + struct jabber_buddy_data *jbd; + to = xmlnode_get_attrib(p->x, "to"); + from = xmlnode_get_attrib(p->x, "from"); + type = xmlnode_get_attrib(p->x, "type"); + if((buddy = get_realwho(gjc, from, FALSE, &gjid)) == NULL) + if (gjid->user == NULL) { + jbd = jabber_find_buddy(GJ_GC(gjc), buddy); + g_free(jbd->error_msg); + if(type && !strcasecmp(type, "error")) { + if((y = xmlnode_get_tag(p->x, "error")) != NULL) { + jbd->error_msg = g_strdup_printf(_("Error %s: %s"), + xmlnode_get_attrib(y, "code"), xmlnode_get_data(y)); + jbd->error_msg = g_strdup(_("Unknown Error in presence")); + if ((y = xmlnode_get_tag(p->x, "show"))) { + show = xmlnode_get_data(y); + } else if (!strcasecmp(show, "away")) { + } else if (!strcasecmp(show, "chat")) { + } else if (!strcasecmp(show, "xa")) { + } else if (!strcasecmp(show, "dnd")) { + if ((y = xmlnode_get_tag(p->x, "priority"))) + priority = atoi(xmlnode_get_data(y)); + /* 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 = find_chat(GJ_GC(gjc), gjid->user)) == NULL) { + if ((jc = find_pending_chat(GJ_GC(gjc), gjid)) != NULL) { + jc->b = cnv = serv_got_joined_chat(GJ_GC(gjc), i++, gjid->user); + jc->state = JCS_ACTIVE; + } else if ((b = find_buddy(GJ_GC(gjc), buddy)) == NULL) { + if (type && (strcasecmp(type, "unavailable") == 0)) + jabber_remove_resource(GJ_GC(gjc), buddy, gjid->resource); + jabber_track_resource(GJ_GC(gjc), buddy, gjid->resource, priority, state); + /* keep track of away msg somewhat the same as the yahoo plugin */ + jabber_track_away(gjc, p, type); + /* this is where we handle presence information for "regular" buddies */ + jab_res_info jri = jabber_find_resource(GJ_GC(gjc), buddy); + serv_got_update(GJ_GC(gjc), buddy, 1, 0, b->signon, b->idle, jri->state, 0); + serv_got_update(GJ_GC(gjc), buddy, 0, 0, 0, 0, 0, 0); + if (type && (!strcasecmp(type, "unavailable"))) { + struct jabber_data *jd; + if (!jc && !(jc = find_existing_chat(GJ_GC(gjc), gjid))) { + jd = jc->gc->proto_data; + /* if it's not ourselves...*/ + if (strcmp(gjid->resource, jc->gjid->resource) && jc->b) { + remove_chat_buddy(jc->b, gjid->resource, NULL); + 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); + if ((!jc && !(jc = find_existing_chat(GJ_GC(gjc), gjid))) || !jc->b) { + if (!find_chat_buddy(jc->b, gjid->resource)) { + add_chat_buddy(jc->b, gjid->resource); + * Used only by Jabber accept/deny add stuff just below +struct jabber_add_permit { + * 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); + * 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); + * 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"); + * Handle subscription requests +static void jabber_handles10n(gjconn gjc, jpacket p) + 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."), + jap->user = g_strdup(Jid); + do_ask_dialog(msg, jap, jabber_accept_add, jabber_deny_add); + xmlnode_free(g); /* Never needed it here anyway */ + } else if (!strcmp(type, "unsubscribe")) { + * An "unsubscribe to us" was received - simply "approve" it + xmlnode_put_attrib(g, "type", "unsubscribed"); + * Did we attempt to subscribe to somebody and they do not exist? + if (!strcmp(type, "unsubscribed")) { + 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(msg, _("Jabber Error")); + * Pending subscription to a buddy? +#define BUD_SUB_TO_PEND(sub, ask) ((!strcasecmp((sub), "none") || !strcasecmp((sub), "from")) && \ + (ask) != NULL && !strcasecmp((ask), "subscribe")) + * Subscribed to a buddy? +#define BUD_SUBD_TO(sub, ask) ((!strcasecmp((sub), "to") || !strcasecmp((sub), "both")) && \ + ((ask) == NULL || !strcasecmp((ask), "subscribe"))) + * Pending unsubscription to a buddy? +#define BUD_USUB_TO_PEND(sub, ask) ((!strcasecmp((sub), "to") || !strcasecmp((sub), "both")) && \ + (ask) != NULL && !strcasecmp((ask), "unsubscribe")) + * Unsubscribed to a buddy? +#define BUD_USUBD_TO(sub, ask) ((!strcasecmp((sub), "none") || !strcasecmp((sub), "from")) && \ + ((ask) == NULL || !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) + char *who, *name, *sub, *ask; + struct buddy *b = NULL; + char *buddyname, *groupname = NULL; + who = xmlnode_get_attrib(x, "jid"); + name = xmlnode_get_attrib(x, "name"); + sub = xmlnode_get_attrib(x, "subscription"); + ask = xmlnode_get_attrib(x, "ask"); + if((buddyname = get_realwho(gjc, who, FALSE, &gjid)) == NULL) + /* JFIXME: jabber_handleroster() had a "FIXME: transport" at this + * equivilent point. So... + * We haven't done anything interesting to this point, so we'll + * violate Good Coding Structure here by simply bailing out. + if((g = xmlnode_get_tag(x, "group")) != NULL) { + groupname = xmlnode_get_data(g); + * Add or remove a buddy? Change buddy's alias or group? + name = utf8_to_str(name); + if (BUD_SUB_TO_PEND(sub, ask) || BUD_SUBD_TO(sub, ask)) { + if ((b = find_buddy(GJ_GC(gjc), buddyname)) == NULL) { + debug_printf("adding buddy [4]: %s\n", buddyname); + b = add_buddy(GJ_GC(gjc), groupname ? groupname : _("Buddies"), buddyname, + name ? name : buddyname); + 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 && c_grp && strcmp(c_grp->name, groupname)) { + int present = b->present; /* save presence state */ + int uc = b->uc; /* and away state (?) */ + int signon = b->signon; + * seems rude, but it seems to be the only way... + remove_buddy(GJ_GC(gjc), c_grp, b); + b = add_buddy(GJ_GC(gjc), groupname, buddyname, + name ? name : buddyname); + serv_got_update(GJ_GC(gjc), buddyname, 1, 0, signon, idle, + } 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 */ + handle_buddy_rename(b, buddyname); + } else if (BUD_USUB_TO_PEND(sub, ask) || BUD_USUBD_TO(sub, ask) || !strcasecmp(sub, "remove")) { + jabber_remove_gaim_buddy(GJ_GC(gjc), buddyname); +static void jabber_handleroster(gjconn gjc, xmlnode querynode) + x = xmlnode_get_firstchild(querynode); + jabber_handlebuddy(gjc, x); + x = xmlnode_get_nextsibling(x); + x = jutil_presnew(0, NULL, "Online"); +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")) { + debug_printf("auth success\n"); + account_online(GJ_GC(gjc)); + serv_finish_login(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; + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + debug_printf("auth failed\n"); + xerr = xmlnode_get_tag(p->x, "error"); + 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); + g_snprintf(msg, sizeof(msg), "%s", errmsg); + hide_login_progress(GJ_GC(gjc), msg); + hide_login_progress(GJ_GC(gjc), _("Unknown login error")); +static void jabber_handleversion(gjconn gjc, xmlnode iqnode) { + g_snprintf(os, sizeof os, "%s %s %s", osinfo.sysname, osinfo.release, osinfo.machine); + 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"), VERSION, -1); + xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "os"), os, -1); +static void jabber_handletime(gjconn gjc, xmlnode iqnode) { + 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); +static void jabber_handlelast(gjconn gjc, xmlnode iqnode) { + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + 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); + * 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(queries != NULL && key != NULL) { + if(g_hash_table_lookup_extended(queries, key, &my_key, &my_val)) { + ret_val = g_strdup((gchar *) my_val); + g_hash_table_remove(queries, key); +static void jabber_handlepacket(gjconn gjc, jpacket p) + jabber_handlemessage(gjc, p); + jabber_handlepresence(gjc, p); + debug_printf("jpacket_subtype: %d\n", jpacket_subtype(p)); + id = xmlnode_get_attrib(p->x, "id"); + if (id != NULL && !strcmp(id, IQID_AUTH)) { + jabber_handleauthresp(gjc, p); + if (jpacket_subtype(p) == JPACKET__SET) { + 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) { + 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; + * 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"); + 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); + jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */ + jabber_handlevcard(gjc, vcard, from); + } else if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL) { + debug_printf("jabber:iq:query: %s\n", xmlns); + debug_printf("jabber:iq: %s\n", xmlnode2str(p->x)); + /* handle "null" query results */ + if((val = jabber_track_queries(gjc->queries, id, TRUE)) != NULL) { + if(strcmp((char *) val, "vCard") == 0) { + * No actual vCard, but there's other stuff. This + * way the user always gets some kind of response. + jabber_handlevcard(gjc, NULL, from); + } else if(!strcmp((char *) val, "change_password")) { + sprintf(buf, _("Password successfully changed.")); + do_error_dialog(buf, _("Password Change")); + } else if (jpacket_subtype(p) == JPACKET__ERROR) { + char *from, *errmsg = NULL; + from = xmlnode_get_attrib(p->x, "from"); + xerr = xmlnode_get_tag(p->x, "error"); + 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(errmsg, from); + jabber_handles10n(gjc, p); + debug_printf("jabber: packet type %d (%s)\n", p->type, xmlnode2str(p->x)); +static void jabber_handlestate(gjconn gjc, int state) + if(gjc->was_connected) { + hide_login_progress_error(GJ_GC(gjc), _("Connection lost")); + hide_login_progress(GJ_GC(gjc), _("Unable to connect")); + case JCONN_STATE_CONNECTED: + gjc->was_connected = 1; + set_login_progress(GJ_GC(gjc), 2, _("Connected")); + set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method")); + debug_printf("state change: %d\n", state); +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, "GAIM"); + jd->buddies = 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))) { + debug_printf("jabber: unable to connect (jab_new failed)\n"); + hide_login_progress(gc, _("Unable to connect")); + 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); +static gboolean jabber_destroy_hash(gpointer key, gpointer val, gpointer data) { +static gboolean jabber_destroy_buddy_hash(gpointer key, gpointer val, gpointer data) { + struct jabber_buddy_data *jbd = val; + while (jbd->resources) { + g_free(((jab_res_info) ((GSList *)jbd->resources)->data)->name); + if(((jab_res_info) ((GSList *)jbd->resources)->data)->away_msg) + g_free(((jab_res_info) ((GSList *)jbd->resources)->data)->away_msg); + g_free(((GSList *)jbd->resources)->data); + jbd->resources = g_slist_remove(jbd->resources, ((GSList *)jbd->resources)->data); + g_free(jbd->error_msg); +static gboolean jabber_free(gpointer data) + struct jabber_data *jd = data; +static void jabber_close(struct gaim_connection *gc) + struct jabber_data *jd = gc->proto_data; + GSList *jcs = jd->chats; + /* Free-up the jabber_chat struct allocs and the list */ + gaim_jid_free(((struct jabber_chat *)jcs->data)->gjid); + g_slist_free(jd->chats); + /* Free-up the buddy data hash */ + if(jd->buddies != NULL) + g_hash_table_foreach_remove(jd->buddies, jabber_destroy_buddy_hash, NULL); + g_hash_table_destroy(jd->buddies); + /* 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; + gaim_input_remove(gc->inpa); + g_timeout_add(50, jabber_free, jd); + xmlnode_free(jd->gjc->current); +static int jabber_send_typing(struct gaim_connection *gc, char *who, int typing) + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + jab_res_info jri = jabber_find_resource(gc, who); + if(!jri || !jri->has_composing) + if((realwho = get_realwho(gjc, who, FALSE, NULL)) == NULL) + x = xmlnode_new_tag("message"); + xmlnode_put_attrib(x, "to", realwho); + xmlnode_insert_tag(x, "gaim"); + y = xmlnode_insert_tag(x, "x"); + xmlnode_put_attrib(y, "xmlns", "jabber:x:event"); + xmlnode_insert_tag(y, "composing"); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + return JABBER_TYPING_NOTIFY_INT; +static int jabber_send_im(struct gaim_connection *gc, char *who, char *message, int len, int flags) + char *thread_id = NULL; + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + if((realwho = get_realwho(gjc, who, FALSE, NULL)) == NULL) + x = xmlnode_new_tag("message"); + xmlnode_put_attrib(x, "to", realwho); + thread_id = jabber_get_convo_thread(gjc, realwho); + y = xmlnode_insert_tag(x, "thread"); + xmlnode_insert_cdata(y, thread_id, -1); + xmlnode_insert_tag(x, "gaim"); + xmlnode_put_attrib(x, "type", "chat"); + /* let other clients know we support typing notification */ + y = xmlnode_insert_tag(x, "x"); + xmlnode_put_attrib(y, "xmlns", "jabber:x:event"); + xmlnode_insert_tag(y, "composing"); + if (message && strlen(message)) { + char *utf8 = str_to_utf8(message); + y = xmlnode_insert_tag(x, "body"); + xmlnode_insert_cdata(y, utf8, -1); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + * Add/update buddy's roster entry on server +static void jabber_roster_update(struct gaim_connection *gc, char *name) + 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((realwho = get_realwho(gjc, name, FALSE, &gjid)) == NULL) + if(gjid->user == NULL) { + 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)) + char *utf8 = str_to_utf8(buddy->show); + xmlnode_put_attrib(y, "name", utf8); + * Find out what group the buddy's in and send that along + * with the roster item. + if((buddy_group = find_group_by_buddy(gc, realwho)) != NULL) { + z = xmlnode_insert_tag(y, "group"); + xmlnode_insert_cdata(z, buddy_group->name, -1); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); + * 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) + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + if (!((struct jabber_data *)gc->proto_data)->did_import) + * If there's no name or the name is ourself + if(!name || !strcmp(gc->username, name)) + if((realwho = get_realwho(gjc, name, FALSE, &gjid)) == NULL) { + char *msg = g_strdup_printf("%s: \"%s\"", _("Invalid Jabber I.D."), name); + do_error_dialog(msg, _("Jabber Error")); + jabber_remove_gaim_buddy(gc, name); + if(gjid->user == NULL) { + 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); + jabber_roster_update(gc, realwho); +static void jabber_remove_buddy(struct gaim_connection *gc, char *name, char *group) + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + if(!name || (realwho = get_realwho(gjc, name, FALSE, NULL)) == NULL) + 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); + * Remove a buddy item from the roster entirely +static void jabber_remove_buddy_roster_item(struct gaim_connection *gc, char *name) + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + if(!name || (realwho = get_realwho(gjc, name, FALSE, NULL)) == NULL) + x = jutil_iqnew(JPACKET__SET, NS_ROSTER); + y = xmlnode_insert_tag(xmlnode_get_tag(x, "query"), "item"); + xmlnode_put_attrib(y, "jid", realwho); + xmlnode_put_attrib(y, "subscription", "remove"); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); +static char **jabber_list_icon(int uc) + return available_away_xpm; + return available_chat_xpm; + return available_xa_xpm; + return available_dnd_xpm; + return available_error_xpm; +static GList *jabber_chat_info(struct gaim_connection *gc) + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + static char *confserv = NULL; /* this pointer must be persistent */ + struct proto_chat_entry *pce; + /* This is a scientific wild-ass guess... + * If there are more than two "components" to the current server name, + * lop-off the left-most component and replace with "conference." + g_free(confserv); /* dispose of the old value */ + if((server = g_strdup(gjc->user->server)) == NULL) { + confserv = g_strdup(DEFAULT_GROUPCHAT); + gchar **splits, **index; + index = splits = g_strsplit(server, ".", -1); /* split the connected server */ + while(*(index++)) /* index to the end--counting the parts */ + * If we've more than two parts, point to the second part. Else point + confserv = g_strjoin(".", "conference", (tmp = g_strjoinv(".", index)), NULL); + g_free(server); /* we don't need this stuff no more */ + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("Room:"); + m = g_list_append(m, pce); + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("Server:"); + m = g_list_append(m, pce); + pce = g_new0(struct proto_chat_entry, 1); + pce->label = _("Handle:"); + pce->def = gjc->user->user; + m = g_list_append(m, pce); +static void jabber_join_chat(struct gaim_connection *gc, GList *data) + gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc; + GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats; + struct jabber_chat *jc; + if (!data || !data->next || !data->next->next) + realwho = create_valid_jid(data->data, data->next->data, data->next->next->data); + debug_printf("%s\n", realwho); + if((gjid = gaim_jid_new(realwho)) == NULL) { + char *msg = g_strdup_printf("%s: \"%s\"", _("Invalid Jabber I.D."), realwho); + do_error_dialog(msg, _("Jabber Error")); + if((jc = find_any_chat(gc, gjid)) != NULL) { + debug_printf("attempt to re-join already pending Jabber chat! (ignoring)\n"); + g_free(realwho); /* yuck! */ + debug_printf("attempt to re-join already active Jabber chat! (ignoring)\n"); + g_free(realwho); /* yuck! */ + debug_printf("rejoining previously closed Jabber chat\n"); + debug_printf("found Jabber chat in unknown state! (ignoring)\n"); + g_free(realwho); /* yuck! */ + debug_printf("joining completely new Jabber chat\n"); + jc = g_new0(struct jabber_chat, 1); + ((struct jabber_data *)gc->proto_data)->chats = g_slist_append(jcs, jc); + add_buddy(gc, _("Chats"), realwho, realwho); + jc->state = JCS_PENDING; + x = jutil_presnew(0, realwho, NULL); +static void jabber_chat_invite(struct gaim_connection *gc, int id, char *message, char *name) + struct jabber_data *jd = gc->proto_data; + struct jabber_chat *jc = NULL; + char *realwho, *subject; + if(!name || (realwho = get_realwho(gjc, name, FALSE, NULL)) == NULL) + /* find which chat we're inviting to */ + if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0) + x = xmlnode_new_tag("message"); + xmlnode_put_attrib(x, "to", realwho); + y = xmlnode_insert_tag(x, "x"); + xmlnode_put_attrib(y, "xmlns", "jabber:x:conference"); + subject = g_strdup_printf("%s@%s", jc->gjid->user, jc->gjid->server); + xmlnode_put_attrib(y, "jid", subject); + if (message && strlen(message)) { + char *utf8 = str_to_utf8(message); + y = xmlnode_insert_tag(x, "body"); + xmlnode_insert_cdata(y, utf8, -1); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); +static void jabber_chat_leave(struct gaim_connection *gc, int id) + struct jabber_data *jd = gc->proto_data; + struct jabber_chat *jc = NULL; + /* Find out which chat we're leaving */ + if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0) + chatname = g_strdup_printf("%s@%s", jc->gjid->user, jc->gjid->server); + x = jutil_presnew(0, chatname, NULL); + xmlnode_put_attrib(x, "type", "unavailable"); +static int jabber_chat_send(struct gaim_connection *gc, int id, char *message) + struct jabber_chat *jc = NULL; + /* Find out which chat we're sending to */ + if((retval = jabber_find_chat_by_convo_id(gc, id, &jc)) != 0) + x = xmlnode_new_tag("message"); + xmlnode_put_attrib(x, "from", jc->gjid->full); + chatname = g_strdup_printf("%s@%s", jc->gjid->user, jc->gjid->server); + xmlnode_put_attrib(x, "to", chatname); + xmlnode_put_attrib(x, "type", "groupchat"); + if (message && strlen(message) > strlen("/topic ") && + !g_strncasecmp(message, "/topic ", strlen("/topic "))) { + char *utf8 = str_to_utf8(message + strlen("/topic ")); + y = xmlnode_insert_tag(x, "subject"); + xmlnode_insert_cdata(y, utf8, -1); + y = xmlnode_insert_tag(x, "body"); + g_snprintf(buf, sizeof(buf), "/me has changed the subject to: %s", utf8); + xmlnode_insert_cdata(y, buf, -1); + } else if (message && strlen(message)) { + char *utf8 = str_to_utf8(message); + y = xmlnode_insert_tag(x, "body"); + xmlnode_insert_cdata(y, utf8, -1); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); +static void jabber_chat_whisper(struct gaim_connection *gc, int id, char *who, char *message) + struct jabber_chat *jc = NULL; + /* Find out which chat we're whispering to */ + if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0) + x = xmlnode_new_tag("message"); + xmlnode_put_attrib(x, "from", jc->gjid->full); + chatname = g_strdup_printf("%s@%s/%s", jc->gjid->user, jc->gjid->server, who); + xmlnode_put_attrib(x, "to", chatname); + xmlnode_put_attrib(x, "type", "normal"); + if (message && strlen(message)) { + char *utf8 = str_to_utf8(message); + y = xmlnode_insert_tag(x, "body"); + xmlnode_insert_cdata(y, utf8, -1); + gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x); +static char *jabber_normalize(const char *s) + static char buf[BUF_LEN]; + g_return_val_if_fail((s != NULL), NULL); + /* Somebody called us with s == NULL once... */ + while (*t && (x < BUF_LEN - 1)) { + if (!strchr(buf, '@')) { + strcat(buf, "@jabber.org"); /* this isn't always right, but eh */ + } else if ((u = strchr(strchr(buf, '@'), '/')) != NULL) { +static void jabber_get_info(struct gaim_connection *gc, char *who) { + struct jabber_data *jd = gc->proto_data; + if((realwho = get_realwho(gjc, who, TRUE, NULL)) == NULL) + x = jutil_iqnew(JPACKET__GET, NS_VCARD); + xmlnode_put_attrib(x, "to", realwho); + xmlnode_put_attrib(x, "id", id); + g_hash_table_insert(jd->gjc->queries, g_strdup(id), g_strdup("vCard")); +static void jabber_get_error_msg(struct gaim_connection *gc, char *who) { + struct jabber_data *jd = gc->proto_data; + gchar **str_arr = (gchar **) g_new(gpointer, 3); + gchar *realwho, *final; + struct jabber_buddy_data *jbd; + if((realwho = get_realwho(gjc, who, FALSE, NULL)) == NULL) { + jbd = jabber_find_buddy(gc, realwho); + *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", realwho); + *ap++ = g_strdup_printf("<B>Error:</B> %s<BR>\n", jbd->error_msg); + final= g_strjoinv(NULL, str_arr); + g_show_info_text(gc, realwho, 2, final, NULL); +static void jabber_get_away_msg(struct gaim_connection *gc, char *who) { + struct jabber_data *jd = gc->proto_data; + char *buddy = get_realwho(gjc, who, FALSE, &gjid); + struct jabber_buddy_data *jbd = jabber_find_buddy(gc, buddy); + gchar *realwho, *final; + num_resources = g_slist_length(jbd->resources); + resources = jbd->resources; + resources = jbd->resources; + while(strcasecmp(((jab_res_info)resources->data)->name, gjid->resource)) + resources = resources->next; + /* space for all elements: Jabber I.D. + "status" + NULL (list terminator) */ + str_arr = (gchar **) g_new(gpointer, num_resources*2 + 1); + for(i=0; i<num_resources; i++) + jab_res_info jri = resources->data; + realwho = g_strdup_printf("%s/%s", buddy, jri->name); + *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", realwho); + *ap++ = g_strdup_printf("<B>Status:</B> %s<BR>\n", jabber_lookup_away(gjc, realwho)); + resources = resources->next; + final= g_strjoinv(NULL, str_arr); + g_show_info_text(gc, who, 2, final, NULL); +static void jabber_get_cb_info(struct gaim_connection *gc, int cid, char *who) { + struct jabber_chat *jc = NULL; + /* Find out which chat */ + if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0) + realwho = g_strdup_printf("%s@%s/%s", jc->gjid->user, jc->gjid->server, who); + jabber_get_info(gc, realwho); +static void jabber_get_cb_away_msg(struct gaim_connection *gc, int cid, char *who) { + struct jabber_chat *jc = NULL; + /* Find out which chat */ + if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0) + realwho = g_strdup_printf("%s@%s/%s", jc->gjid->user, jc->gjid->server, who); + jabber_get_away_msg(gc, realwho); +static GList *jabber_buddy_menu(struct gaim_connection *gc, char *who) { + struct proto_buddy_menu *pbm; + struct buddy *b = find_buddy(gc, who); + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("View Error Msg"); + pbm->callback = jabber_get_error_msg; + m = g_list_append(m, pbm); + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Get Info"); + pbm->callback = jabber_get_info; + m = g_list_append(m, pbm); + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Get Away Msg"); + pbm->callback = jabber_get_away_msg; + m = g_list_append(m, pbm); + * Jabber protocol-specific "edit buddy menu" item(s) +static GList *jabber_edit_buddy_menu(struct gaim_connection *gc, char *who) { + struct proto_buddy_menu *pbm; + pbm = g_new0(struct proto_buddy_menu, 1); + pbm->label = _("Remove From Roster"); + pbm->callback = jabber_remove_buddy_roster_item; + m = g_list_append(m, pbm); +static GList *jabber_away_states(struct gaim_connection *gc) { + 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"); +static void jabber_set_away(struct gaim_connection *gc, char *state, char *message) + struct jabber_data *jd = gc->proto_data; + struct jabber_chat *jc; + 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. */ + /* Gaim wants us to be away */ + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "away", -1); + y = xmlnode_insert_tag(x, "status"); + xmlnode_insert_cdata(y, message, -1); + /* Gaim wants us to not be away */ + /* but for Jabber, we can just send presence with no other information. */ + /* state is one of our own strings. it won't be NULL. */ + if (!strcmp(state, "Online")) { + /* once again, we don't have to put anything here */ + } else if (!strcmp(state, "Chatty")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "chat", -1); + } else if (!strcmp(state, "Away")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "away", -1); + } else if (!strcmp(state, "Extended Away")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "xa", -1); + } else if (!strcmp(state, "Do Not Disturb")) { + y = xmlnode_insert_tag(x, "show"); + xmlnode_insert_cdata(y, "dnd", -1); + gjab_send(gjc, x); /* Notify "individuals" */ + * As of jabberd-1.4.2: simply sending presence to the server doesn't result in + * it being propagated to conference rooms. So we wade thru the list of chats, + * sending our new presence status to each and every one. + for(jcs = jd->chats; jcs; jcs = jcs->next) { + if(jc->state == JCS_ACTIVE) { + xmlnode_put_attrib(x, "from", jc->gjid->full); + chatname = g_strdup_printf("%s@%s", jc->gjid->user, jc->gjid->server); + xmlnode_put_attrib(x, "to", chatname); +static void jabber_set_idle(struct gaim_connection *gc, int idle) { + struct jabber_data *jd = (struct jabber_data *)gc->proto_data; + debug_printf("jabber_set_idle: setting idle %i\n", idle); + 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 GList *jabber_user_opts() + struct proto_user_opt *puo; + puo = g_new0(struct proto_user_opt, 1); + puo->pos = USEROPT_PORT; + m = g_list_append(m, puo); +/*---------------------------------------*/ +/* Jabber "set info" (vCard) support */ +/*---------------------------------------*/ + * <vCard prodid='' version='' xmlns=''> + * 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 + * 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 + * The above changes will (obviously) require changes to the vCard + * construction routines. + 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} + * The "vCard" tag's attibute list... +} vcard_tag_attr_list[] = { + {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"}, + {"xmlns", "vcard-temp", }, + * V-Card user instructions +static char *multi_entry_instructions = + N_("All items below are optional. Enter only the information with which you feel comfortable"); +static char *entries_title = N_("User Identity"); + * Used by routines to parse an XML-encoded string into an xmlnode tree +} *xmlstr2xmlnode_parser, xmlstr2xmlnode_parser_struct; + * Display a Jabber vCard +static void jabber_handlevcard(gjconn gjc, xmlnode querynode, char *from) + struct gaim_connection *gc = GJ_GC(gjc); + struct vcard_template *vc_tp = vcard_template_data; + /* space for all vCard elements + Jabber I.D. + "status" + NULL (list terminator) */ + gchar **str_arr = (gchar **) g_new(gpointer, + (sizeof(vcard_template_data)/sizeof(struct vcard_template)) + 3); + if((buddy = get_realwho(gjc, from, TRUE, NULL)) == NULL) { + *ap++ = g_strdup_printf("<B>Jabber ID:</B> %s<BR>\n", buddy); + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + if(strcmp(vc_tp->tag, "DESC") == 0) + continue; /* special handling later */ + if(vc_tp->ptag == NULL) { + cdata = xmlnode_get_tag_data(querynode, vc_tp->tag); + gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag); + cdata = xmlnode_get_tag_data(querynode, tag); + if(vc_tp->url == NULL) { + *ap++ = g_strdup_printf("<B>%s:</B> %s<BR>\n", vc_tp->label, cdata); + gchar *fmt = g_strdup_printf("<B>%%s:</B> %s<BR>\n", vc_tp->url); + *ap++ = g_strdup_printf(fmt, vc_tp->label, cdata, cdata); + status = jabber_lookup_away(gjc, buddy); + *ap++ = g_strdup_printf("<B>Status:</B> %s<BR>\n", status); + * "Description" handled as a special case: get a copy of the + if((cdata = xmlnode_get_tag_data(querynode, "DESC")) != NULL) { + gchar *tmp = g_strdup_printf("<HR>%s<BR>", cdata); + *ap++ = strdup_withhtml(tmp); + final= g_strjoinv(NULL, str_arr); + g_show_info_text(gc, buddy, 2, final, NULL); + * Used by XML_Parse on parsing CDATA +static void xmlstr2xmlnode_charData(void *userdata, const char *s, int slen) + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + 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) + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + /* Append the node to the current one */ + x = xmlnode_insert_tag(xmlp->current, name); + xmlnode_put_expat_attribs(x, attribs); + x = xmlnode_new_tag(name); + xmlnode_put_expat_attribs(x, attribs); + * Used by XML_Parse to end an xmlnode +static void xmlstr2xmlnode_endElement(void *userdata, const char *name) + xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata; + if (xmlp->current != NULL && (x = xmlnode_get_parent(xmlp->current)) != NULL) { + * 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); + 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); + * Insert a tag node into an xmlnode tree, recursively inserting parent tag + * 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) + * 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; + * 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) { + char *grand_parent = strcpy(g_malloc(strlen(parent_tag) + 1), parent_tag); + if((parent = strrchr(grand_parent, '/')) != NULL) { + x = insert_tag_to_parent_tag(start, grand_parent, parent); + x = xmlnode_insert_tag(start, grand_parent); + * We found *something* to be the parent node. + * Note: may be the "root" node! + if((y = xmlnode_get_tag(x, new_tag)) != NULL) { + * insert the new tag into its parent node + return(xmlnode_insert_tag((x == NULL? start : x), new_tag)); + * Find the tag name for a label + * Returns NULL on not found +static char *tag_for_label(const char *label) + struct vcard_template *vc_tp = vcard_template_data; + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + if(strcmp(label, vc_tp->label) == 0) { + * Send vCard info to Jabber server +static void jabber_set_info(struct gaim_connection *gc, char *info) + struct jabber_data *jd = gc->proto_data; + x = xmlnode_new_tag("iq"); + xmlnode_put_attrib(x, "type", "set"); + 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); + debug_printf("jabber: vCard packet: %s\n", xmlnode2str(x)); + * This is the callback from the "ok clicked" for "set vCard" + * Formats GSList data into XML-encoded string and returns a pointer + * g_free()'ing the returned string space is the responsibility of +static gchar *jabber_format_info(MultiEntryDlg *b) + struct tag_attr *tag_attr; + vc_node = xmlnode_new_tag("vCard"); + for(tag_attr = vcard_tag_attr_list; tag_attr->attr != NULL; ++tag_attr) + xmlnode_put_attrib(vc_node, tag_attr->attr, tag_attr->value); + for(list = b->multi_entry_items; list != NULL; list = list->next) { + med = (MultiEntryData *) list->data; + if(med->label != NULL && med->text != NULL && (med->text)[0] != '\0') { + if((p = tag_for_label(med->label)) != NULL) { + if((xp = insert_tag_to_parent_tag(vc_node, NULL, p)) != NULL) { + xmlnode_insert_cdata(xp, med->text, -1); + for(list = b->multi_text_items; list != NULL; list = list->next) { + mtd = (MultiTextData *) list->data; + if(mtd->label != NULL && mtd->text != NULL && (mtd->text)[0] != '\0') { + if((p = tag_for_label(mtd->label)) != NULL) { + if((xp = insert_tag_to_parent_tag(vc_node, NULL, p)) != NULL) { + xmlnode_insert_cdata(xp, mtd->text, -1); + p = g_strdup(xmlnode2str(vc_node)); + * This gets executed by the proto action + * Creates a new MultiEntryDlg struct, gets the XML-formatted user_info + * string (if any) into GSLists for the (multi-entry) edit dialog and + * calls the set_vcard dialog. +static void jabber_setup_set_info(struct gaim_connection *gc) + const struct vcard_template *vc_tp; + MultiEntryDlg *b = multi_entry_dialog_new(); + xmlnode x_vc_data = NULL; + struct aim_user *tmp = gc->user; + * Get existing, XML-formatted, user info + if((user_info = g_malloc(strlen(tmp->user_info) + 1)) != NULL) { + strcpy(user_info, tmp->user_info); + x_vc_data = xmlstr2xmlnode(user_info); + * Set up GSLists for edit with labels from "template," data from user info + for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) { + if((vc_tp->label)[0] == '\0') + if(vc_tp->ptag == NULL) { + cdata = xmlnode_get_tag_data(x_vc_data, vc_tp->tag); + gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag); + cdata = xmlnode_get_tag_data(x_vc_data, tag); + if(strcmp(vc_tp->tag, "DESC") == 0) { + multi_text_list_update(&(b->multi_text_items), + vc_tp->label, cdata, TRUE); + data = multi_entry_list_update(&(b->multi_entry_items), + vc_tp->label, cdata, TRUE); + data->visible = vc_tp->visible; + data->editable = vc_tp->editable; + if(x_vc_data != NULL) { + xmlnode_free(x_vc_data); + * Early Beta versions had a different user_info storage format--let's + const char *record_separator = "<BR>"; + const char *field_separator = ": "; + gchar **str_list, **str_list_ptr, **str_list2; + if((str_list = g_strsplit(user_info, record_separator, 0)) != NULL) { + for(str_list_ptr = str_list; *str_list_ptr != NULL; ++str_list_ptr) { + str_list2 = g_strsplit(*str_list_ptr, field_separator, 2); + if(str_list2[0] != NULL && str_list2[1] != NULL) { + g_strstrip(str_list2[0]); + g_strstrip(str_list2[1]); + /* this is ugly--so far */ + if(strcmp(str_list2[0], "Description") == 0) { + multi_text_list_update(&(b->multi_text_items), + str_list2[0], str_list2[1], FALSE); + multi_entry_list_update(&(b->multi_entry_items), + str_list2[0], str_list2[1], FALSE); + if(user_info != NULL) { + b->title = _("Gaim - Edit Jabber vCard"); + b->wmclass_name = "set_info"; + b->wmclass_class = "Gaim"; + b->instructions->text = g_strdup(multi_entry_instructions); + b->entries_title = g_strdup(entries_title); + b->custom = (void *) jabber_format_info; +/*---------------------------------------*/ +/* End Jabber "set info" (vCard) support */ +/*---------------------------------------*/ +/*----------------------------------------*/ +/* Jabber "user registration" support */ +/*----------------------------------------*/ + * Three of the following four functions duplicate much of what + * jabber_handleregresp() + * jabber_handle_registration_state() + * It may be that an additional flag could be added to one of + * the "local" structs and the duplicated code modified to + * account for it--thus eliminating the duplication. Then again: + * doing it the way it is may be much cleaner. + * TBD: Code to support requesting additional information server + * wants at registration--incl. dialog. + * Like jabber_handlepacket(), only different +static void jabber_handleregresp(gjconn gjc, jpacket p) + if (jpacket_subtype(p) == JPACKET__RESULT) { + if((querynode = xmlnode_get_tag(p->x, "query")) != NULL) { + /* we damn well *better* have this! */ + if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL && + strcmp(xmlns, NS_REGISTER) == 0) { + xmlnode child = xmlnode_get_firstchild(querynode); + debug_printf("got registration requirments response!\n"); + if((tag = xmlnode_get_name(child)) != NULL) { + fprintf(stderr, "DBG: got node: \"%s\"\n", tag); + if((data = xmlnode_get_data(child)) != NULL) { + fprintf(stderr, "DBG: got data: \"%s\"\n", data); + child = xmlnode_get_nextsibling(child); + debug_printf("registration successful!\n"); + hide_login_progress_notice(GJ_GC(gjc), _("Server Registration successful!")); + * TBD: is this the correct way to do away with a + * gaim_connection and all its associated memory + struct jabber_data *jd = GJ_GC(gjc)->proto_data; + debug_printf("registration failed\n"); + xerr = xmlnode_get_tag(p->x, "error"); + 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); + g_snprintf(msg, sizeof(msg), "%s", errmsg); + hide_login_progress(GJ_GC(gjc), msg); + hide_login_progress(GJ_GC(gjc), _("Unknown registration error")); + * Like gjab_reqauth(), only different +static void gjab_reqreg(gjconn gjc) + x = jutil_iqnew(JPACKET__SET, NS_REGISTER); + y = xmlnode_get_tag(x, "query"); + user = gjc->user->user; + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, user, -1); + z = xmlnode_insert_tag(y, "password"); + xmlnode_insert_cdata(z, gjc->pass, -1); + debug_printf("jabber: registration packet: %s\n", xmlnode2str(x)); + * Like jabber_handlestate(), only different +static void jabber_handle_registration_state(gjconn gjc, int state) + if(gjc->was_connected) { + hide_login_progress_error(GJ_GC(gjc), _("Connection lost")); + hide_login_progress(GJ_GC(gjc), _("Unable to connect")); + case JCONN_STATE_CONNECTED: + gjc->was_connected = 1; + set_login_progress(GJ_GC(gjc), 2, _("Connected")); + set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method")); + * TBD: A work-in-progress + debug_printf("state change: %d\n", state); + * Like jabber_login(), only different +void jabber_register_user(struct aim_user *au) + struct gaim_connection *gc = new_gaim_conn(au); + struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1); + char *loginname = create_valid_jid(au->username, DEFAULT_SERVER, "GAIM"); + * These do nothing during registration + if ((jd->gjc = gjab_new(loginname, au->password, gc)) == NULL) { + debug_printf("jabber: unable to connect (jab_new failed)\n"); + hide_login_progress(gc, _("Unable to connect")); + gjab_state_handler(jd->gjc, jabber_handle_registration_state); + gjab_packet_handler(jd->gjc, jabber_handleregresp); + jd->gjc->queries = NULL; +/*----------------------------------------*/ +/* End Jabber "user registration" support */ +/*----------------------------------------*/ +static void jabber_do_action(struct gaim_connection *gc, char *act) + if (!strcmp(act, _("Set User Info"))) { + jabber_setup_set_info(gc); + } else if (!strcmp(act, _("Set Dir Info"))) { + } else if (!strcmp(act, _("Change Password"))) { + show_change_passwd(gc); +static GList *jabber_actions() + m = g_list_append(m, _("Set User Info")); + m = g_list_append(m, _("Set Dir Info")); + m = g_list_append(m, _("Change Password")); +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->options = OPT_PROTO_UNIQUE_CHATNAME | + OPT_PROTO_USE_POINT_SIZE; + ret->name = jabber_name; + ret->list_icon = jabber_list_icon; + ret->away_states = jabber_away_states; + ret->actions = jabber_actions; + ret->do_action = jabber_do_action; + ret->buddy_menu = jabber_buddy_menu; + ret->edit_buddy_menu = jabber_edit_buddy_menu; + ret->user_opts = jabber_user_opts; + 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->get_cb_info = jabber_get_cb_info; + ret->get_cb_away = jabber_get_cb_away_msg; + ret->set_away = jabber_set_away; + ret->dir_search = NULL; + ret->set_idle = jabber_set_idle; + ret->change_passwd = jabber_change_passwd; + ret->add_buddy = jabber_add_buddy; + ret->add_buddies = NULL; + ret->remove_buddy = jabber_remove_buddy; + ret->add_permit = NULL; + ret->rem_permit = NULL; + ret->set_permit_deny = NULL; + ret->chat_info = jabber_chat_info; + ret->join_chat = jabber_join_chat; + ret->chat_invite = jabber_chat_invite; + ret->chat_leave = jabber_chat_leave; + ret->chat_whisper = jabber_chat_whisper; + ret->chat_send = jabber_chat_send; + ret->keepalive = jabber_keepalive; + ret->normalize = jabber_normalize; + ret->register_user = jabber_register_user; + ret->alias_buddy = jabber_roster_update; + ret->group_buddy = jabber_group_change; + ret->send_typing = jabber_send_typing; + ret->convo_closed = jabber_convo_closed; +char *gaim_plugin_init(GModule *handle) + load_protocol(jabber_init, sizeof(struct prpl)); +void gaim_plugin_remove() + struct prpl *p = find_prpl(PROTO_JABBER); + return PRPL_DESC("Jabber"); + * c-indentation-style: k&r + * indent-tabs-mode: notnil --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/yahoo/yahoo.c Fri Aug 30 07:54:54 2002 -0400
@@ -0,0 +1,1481 @@
+ * 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 +extern char *yahoo_crypt(char *, char *); +#include "pixmaps/status-away.xpm" +#include "pixmaps/status-here.xpm" +#include "pixmaps/status-idle.xpm" +#include "pixmaps/status-game.xpm" +/* Yahoo Smilies go here */ +#include "pixmaps/protocols/yahoo/yahoo_alien.xpm" +#include "pixmaps/protocols/yahoo/yahoo_angel.xpm" +#include "pixmaps/protocols/yahoo/yahoo_angry.xpm" +#include "pixmaps/protocols/yahoo/yahoo_bigsmile.xpm" +#include "pixmaps/protocols/yahoo/yahoo_blush.xpm" +#include "pixmaps/protocols/yahoo/yahoo_bye.xpm" +#include "pixmaps/protocols/yahoo/yahoo_clown.xpm" +#include "pixmaps/protocols/yahoo/yahoo_cow.xpm" +#include "pixmaps/protocols/yahoo/yahoo_cowboy.xpm" +#include "pixmaps/protocols/yahoo/yahoo_cry.xpm" +#include "pixmaps/protocols/yahoo/yahoo_devil.xpm" +#include "pixmaps/protocols/yahoo/yahoo_flower.xpm" +#include "pixmaps/protocols/yahoo/yahoo_ghost.xpm" +#include "pixmaps/protocols/yahoo/yahoo_glasses.xpm" +#include "pixmaps/protocols/yahoo/yahoo_laughloud.xpm" +#include "pixmaps/protocols/yahoo/yahoo_love.xpm" +#include "pixmaps/protocols/yahoo/yahoo_mean.xpm" +#include "pixmaps/protocols/yahoo/yahoo_neutral.xpm" +#include "pixmaps/protocols/yahoo/yahoo_ooooh.xpm" +#include "pixmaps/protocols/yahoo/yahoo_question.xpm" +#include "pixmaps/protocols/yahoo/yahoo_sad.xpm" +#include "pixmaps/protocols/yahoo/yahoo_sleep.xpm" +#include "pixmaps/protocols/yahoo/yahoo_smiley.xpm" +#include "pixmaps/protocols/yahoo/yahoo_sunglas.xpm" +#include "pixmaps/protocols/yahoo/yahoo_tongue.xpm" +#include "pixmaps/protocols/yahoo/yahoo_wink.xpm" +#define USEROPT_PAGERHOST 3 +#define YAHOO_PAGER_HOST "scs.yahoo.com" +#define USEROPT_PAGERPORT 4 +#define YAHOO_PAGER_PORT 5050 +enum yahoo_service { /* these are easier to see in hex */ + YAHOO_SERVICE_LOGON = 1, + YAHOO_SERVICE_IDLE, /* 5 (placemarker) */ + YAHOO_SERVICE_MAILSTAT, + YAHOO_SERVICE_USERSTAT, /* 0xa */ + YAHOO_SERVICE_CHATINVITE, + YAHOO_SERVICE_CALENDAR, + YAHOO_SERVICE_NEWPERSONALMAIL, + YAHOO_SERVICE_NEWCONTACT, + YAHOO_SERVICE_ADDIDENT, /* 0x10 */ + YAHOO_SERVICE_ADDIGNORE, + YAHOO_SERVICE_GROUPRENAME, + YAHOO_SERVICE_SYSMESSAGE = 0x14, + YAHOO_SERVICE_PASSTHROUGH2 = 0x16, + YAHOO_SERVICE_CONFINVITE = 0x18, + YAHOO_SERVICE_CONFLOGON, + YAHOO_SERVICE_CONFDECLINE, + YAHOO_SERVICE_CONFLOGOFF, + YAHOO_SERVICE_CONFADDINVITE, + YAHOO_SERVICE_CHATLOGON, + YAHOO_SERVICE_CHATLOGOFF, + YAHOO_SERVICE_CHATMSG = 0x20, + YAHOO_SERVICE_GAMELOGON = 0x28, + YAHOO_SERVICE_GAMELOGOFF, + YAHOO_SERVICE_GAMEMSG = 0x2a, + YAHOO_SERVICE_FILETRANSFER = 0x46, + YAHOO_SERVICE_NOTIFY = 0x4B, + YAHOO_SERVICE_AUTHRESP = 0x54, + YAHOO_SERVICE_LIST = 0x55, + YAHOO_SERVICE_AUTH = 0x57, + YAHOO_SERVICE_ADDBUDDY = 0x83, + YAHOO_SERVICE_REMBUDDY = 0x84 + YAHOO_STATUS_AVAILABLE = 0, + YAHOO_STATUS_NOTATHOME, + YAHOO_STATUS_NOTATDESK, + YAHOO_STATUS_NOTINOFFICE, + YAHOO_STATUS_ONVACATION, + YAHOO_STATUS_OUTTOLUNCH, + YAHOO_STATUS_STEPPEDOUT, + YAHOO_STATUS_INVISIBLE = 12, + YAHOO_STATUS_CUSTOM = 99, + YAHOO_STATUS_IDLE = 999, + YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */ + YAHOO_STATUS_TYPING = 0x16 +#define YAHOO_STATUS_GAME 0x2 /* Games don't fit into the regular status model */ +static char *yahoo_name() { +#define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4) +static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, enum yahoo_status status, int id) + struct yahoo_packet *pkt = g_new0(struct yahoo_packet, 1); + pkt->service = service; +static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, char *value) + struct yahoo_pair *pair = g_new0(struct yahoo_pair, 1); + pair->value = g_strdup(value); + pkt->hash = g_slist_append(pkt->hash, pair); +static int yahoo_packet_length(struct yahoo_packet *pkt) + struct yahoo_pair *pair = l->data; + len += strlen(pair->value); +/* sometimes i wish prpls could #include things from other prpls. then i could just + * use the routines from libfaim and not have to admit to knowing how they work. */ +#define yahoo_put16(buf, data) ( \ + (*(buf) = (u_char)((data)>>8)&0xff), \ + (*((buf)+1) = (u_char)(data)&0xff), \ +#define yahoo_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff)) +#define yahoo_put32(buf, data) ( \ + (*((buf)) = (u_char)((data)>>24)&0xff), \ + (*((buf)+1) = (u_char)((data)>>16)&0xff), \ + (*((buf)+2) = (u_char)((data)>>8)&0xff), \ + (*((buf)+3) = (u_char)(data)&0xff), \ +#define yahoo_get32(buf) ((((*(buf))<<24)&0xff000000) + \ + (((*((buf)+1))<<16)&0x00ff0000) + \ + (((*((buf)+2))<< 8)&0x0000ff00) + \ + (((*((buf)+3) )&0x000000ff))) +static void yahoo_packet_read(struct yahoo_packet *pkt, guchar *data, int len) + while (pos + 1 < len) { + char key[64], *value = NULL; + struct yahoo_pair *pair = g_new0(struct yahoo_pair, 1); + while (pos + 1 < len) { + if (data[pos] == 0xc0 && data[pos + 1] == 0x80) + key[x++] = data[pos++]; + pair->key = strtol(key, NULL, 10); + accept = x; /* if x is 0 there was no key, so don't accept it */ + value = g_malloc(len - pos + 1); + while (pos + 1 < len) { + if (data[pos] == 0xc0 && data[pos + 1] == 0x80) + value[x++] = data[pos++]; + pair->value = g_strdup(value); + pkt->hash = g_slist_append(pkt->hash, pair); + debug_printf("Key: %d \tValue: %s\n", pair->key, pair->value); +static void yahoo_packet_write(struct yahoo_packet *pkt, guchar *data) + struct yahoo_pair *pair = l->data; + g_snprintf(buf, sizeof(buf), "%d", pair->key); + strcpy(data + pos, buf); + strcpy(data + pos, pair->value); + pos += strlen(pair->value); +static void yahoo_packet_dump(guchar *data, int len) + for (i = 0; i + 1 < len; i += 2) { + if ((i % 16 == 0) && i) + debug_printf("%02x", data[i]); + debug_printf("%02x ", data[i+1]); + debug_printf("%02x", data[i]); + for (i = 0; i < len; i++) { + if ((i % 16 == 0) && i) + debug_printf("%c ", data[i]); +static int yahoo_send_packet(struct yahoo_data *yd, struct yahoo_packet *pkt) + int pktlen = yahoo_packet_length(pkt); + int len = YAHOO_PACKET_HDRLEN + pktlen; + data = g_malloc0(len + 1); + memcpy(data + pos, "YMSG", 4); pos += 4; + pos += yahoo_put16(data + pos, 0x0600); + pos += yahoo_put16(data + pos, 0x0000); + pos += yahoo_put16(data + pos, pktlen); + pos += yahoo_put16(data + pos, pkt->service); + pos += yahoo_put32(data + pos, pkt->status); + pos += yahoo_put32(data + pos, pkt->id); + yahoo_packet_write(pkt, data + pos); + yahoo_packet_dump(data, len); + ret = write(yd->fd, data, len); +static void yahoo_packet_free(struct yahoo_packet *pkt) + struct yahoo_pair *pair = pkt->hash->data; + pkt->hash = g_slist_remove(pkt->hash, pair); +static void yahoo_process_status(struct gaim_connection *gc, struct yahoo_packet *pkt) + struct yahoo_data *yd = gc->proto_data; + struct yahoo_pair *pair = l->data; + case 0: /* we won't actually do anything with this */ + case 1: /* we don't get the full buddy list here. */ + g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", pair->value); + /* this requests the list. i have a feeling that this is very evil + * scs.yahoo.com sends you the list before this packet without it being + * newpkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_OFFLINE, 0); + * yahoo_send_packet(yd, newpkt); + * yahoo_packet_free(newpkt); + case 8: /* how many online buddies we have */ + case 7: /* the current buddy */ + state = strtol(pair->value, NULL, 10); + case 19: /* custom message */ + case 11: /* i didn't know what this was in the old protocol either */ + case 17: /* in chat? */ + case 13: /* in pager? */ + if (pkt->service == YAHOO_SERVICE_LOGOFF || + strtol(pair->value, NULL, 10) == 0) { + serv_got_update(gc, name, 0, 0, 0, 0, 0, 0); + if (g_hash_table_lookup(yd->games, name)) + gamestate = YAHOO_STATUS_GAME; + if (state == YAHOO_STATUS_AVAILABLE) + serv_got_update(gc, name, 1, 0, 0, 0, gamestate, 0); + serv_got_update(gc, name, 1, 0, 0, 0, (state << 2) | UC_UNAVAILABLE | gamestate, 0); + if (state == YAHOO_STATUS_CUSTOM) { + gpointer val = g_hash_table_lookup(yd->hash, name); + g_hash_table_insert(yd->hash, name, + msg ? g_strdup(msg) : g_malloc0(1)); + g_hash_table_insert(yd->hash, g_strdup(name), + msg ? g_strdup(msg) : g_malloc0(1)); + case 16: /* Custom error message */ + do_error_dialog(pair->value, "Gaim -- Yahoo! Error"); + debug_printf("unknown status key %d\n", pair->key); +static void yahoo_process_list(struct gaim_connection *gc, struct yahoo_packet *pkt) + gboolean export = FALSE; + struct yahoo_pair *pair = l->data; + lines = g_strsplit(pair->value, "\n", -1); + for (tmp = lines; *tmp; tmp++) { + split = g_strsplit(*tmp, ":", 2); + if (!split[0] || !split[1]) { + buddies = g_strsplit(split[1], ",", -1); + for (bud = buddies; bud && *bud; bud++) + if (!find_buddy(gc, *bud)) { + add_buddy(gc, split[0], *bud, *bud); +static void yahoo_process_notify(struct gaim_connection *gc, struct yahoo_packet *pkt) + struct yahoo_data *yd = (struct yahoo_data*) gc->proto_data; + struct yahoo_pair *pair = l->data; + if (!g_strncasecmp(msg, "TYPING", strlen("TYPING"))) { + serv_got_typing(gc, from, 0); + serv_got_typing_stopped(gc, from); + } else if (!g_strncasecmp(msg, "GAME", strlen("GAME"))) { + struct buddy *bud = find_buddy(gc, from); + void *free1=NULL, *free2=NULL; + debug_printf("%s is playing a game, and doesn't want you to know.\n"); + if (g_hash_table_lookup_extended (yd->games, from, free1, free2)) { + g_hash_table_insert (yd->games, g_strdup(from), g_strdup(game)); + serv_got_update(gc, from, 1, 0, 0, 0, bud->uc | YAHOO_STATUS_GAME, 0); + if (g_hash_table_lookup_extended (yd->games, from, free1, free2)) { + g_hash_table_remove (yd->games, from); + serv_got_update(gc, from, 1, 0, 0, 0, bud->uc & ~YAHOO_STATUS_GAME, 0); +static void yahoo_process_message(struct gaim_connection *gc, struct yahoo_packet *pkt) + time_t tm = time(NULL); + struct yahoo_pair *pair = l->data; + tm = strtol(pair->value, NULL, 10); + if (pkt->status <= 1 || pkt->status == 5) { + for (i = 0, j = 0; m[i]; i++) { + while (m[i] && (m[i] != 'm')) + serv_got_im(gc, from, msg, 0, tm, -1); + } else if (pkt->status == 2) { + do_error_dialog(_("Your message did not get sent."), _("Gaim - Error")); +static void yahoo_process_contact(struct gaim_connection *gc, struct yahoo_packet *pkt) + struct yahoo_data *yd = gc->proto_data; + int state = YAHOO_STATUS_AVAILABLE; + struct yahoo_pair *pair = l->data; + else if (pair->key == 3) + else if (pair->key == 14) + else if (pair->key == 7) + else if (pair->key == 10) + state = strtol(pair->value, NULL, 10); + else if (pair->key == 13) + online = strtol(pair->value, NULL, 10); + show_got_added(gc, id, who, NULL, msg); + if (state == YAHOO_STATUS_AVAILABLE) + serv_got_update(gc, name, 1, 0, 0, 0, 0, 0); + else if (state == YAHOO_STATUS_IDLE) + serv_got_update(gc, name, 1, 0, 0, time(NULL) - 600, (state << 2), 0); + serv_got_update(gc, name, 1, 0, 0, 0, (state << 2) | UC_UNAVAILABLE, 0); + if (state == YAHOO_STATUS_CUSTOM) { + gpointer val = g_hash_table_lookup(yd->hash, name); + g_hash_table_insert(yd->hash, name, + msg ? g_strdup(msg) : g_malloc0(1)); + g_hash_table_insert(yd->hash, g_strdup(name), + msg ? g_strdup(msg) : g_malloc0(1)); +static void yahoo_process_mail(struct gaim_connection *gc, struct yahoo_packet *pkt) + struct yahoo_pair *pair = l->data; + count = strtol(pair->value, NULL, 10); + else if (pair->key == 43) + else if (pair->key == 42) + else if (pair->key == 18) + if (who && email && subj) { + char *from = g_strdup_printf("%s (%s)", who, email); + connection_has_mail(gc, -1, from, subj, "http://mail.yahoo.com/"); + connection_has_mail(gc, count, NULL, NULL, "http://mail.yahoo.com/"); +/* This is the y64 alphabet... it's like base64, but has a . and a _ */ +char base64digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; +/* This is taken from Sylpheed by Hiroyuki Yamamoto. We have our own tobase64 function + * in util.c, but it has a bug I don't feel like finding right now ;) */ +void to_y64(unsigned char *out, const unsigned char *in, int inlen) + /* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */ + for (; inlen >= 3; inlen -= 3) + *out++ = base64digits[in[0] >> 2]; + *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)]; + *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)]; + *out++ = base64digits[in[2] & 0x3f]; + unsigned char fragment; + *out++ = base64digits[in[0] >> 2]; + fragment = (in[0] << 4) & 0x30; + fragment |= in[1] >> 4; + *out++ = base64digits[fragment]; + *out++ = (inlen < 2) ? '-' : base64digits[(in[1] << 2) & 0x3c]; +static void yahoo_process_auth(struct gaim_connection *gc, struct yahoo_packet *pkt) + struct yahoo_data *yd = gc->proto_data; + struct yahoo_pair *pair = l->data; + struct yahoo_packet *pack; + /* So, Yahoo has stopped supporting its older clients in India, and undoubtedly + * will soon do so in the rest of the world. + * The new clients use this authentication method. I warn you in advance, it's + * bizzare, convoluted, inordinately complicated. It's also no more secure than + * crypt() was. The only purpose this scheme could serve is to prevent third + * part clients from connecting to their servers. + char *password_hash = g_malloc(25); + char *crypt_hash = g_malloc(25); + char *hash_string_p = g_malloc(50 + strlen(sn)); + char *hash_string_c = g_malloc(50 + strlen(sn)); + char *result6 = g_malloc(25); + char *result96 = g_malloc(25); + md5_append(&ctx, gc->password, strlen(gc->password)); + md5_finish(&ctx, result); + to_y64(password_hash, result, 16); + crypt_result = yahoo_crypt(gc->password, "$1$_2S43d5f$"); + md5_append(&ctx, crypt_result, strlen(crypt_result)); + md5_finish(&ctx, result); + to_y64(crypt_hash, result, 16); + checksum = seed[seed[9] % 16]; + g_snprintf(hash_string_p, strlen(sn) + 50, + "%c%s%s%s", checksum, gc->username, seed, password_hash); + g_snprintf(hash_string_c, strlen(sn) + 50, + "%c%s%s%s", checksum, gc->username, seed, crypt_hash); + checksum = seed[seed[15] % 16]; + g_snprintf(hash_string_p, strlen(sn) + 50, + "%c%s%s%s", checksum, seed, password_hash, gc->username); + g_snprintf(hash_string_c, strlen(sn) + 50, + "%c%s%s%s", checksum, seed, crypt_hash, gc->username); + checksum = seed[seed[1] % 16]; + g_snprintf(hash_string_p, strlen(sn) + 50, + "%c%s%s%s", checksum, gc->username, password_hash, seed); + g_snprintf(hash_string_c, strlen(sn) + 50, + "%c%s%s%s", checksum, gc->username, crypt_hash, seed); + checksum = seed[seed[3] % 16]; + g_snprintf(hash_string_p, strlen(sn) + 50, + "%c%s%s%s", checksum, password_hash, seed, gc->username); + g_snprintf(hash_string_c, strlen(sn) + 50, + "%c%s%s%s", checksum, crypt_hash, seed, gc->username); + checksum = seed[seed[7] % 16]; + g_snprintf(hash_string_p, strlen(sn) + 50, + "%c%s%s%s", checksum, password_hash, gc->username, seed); + g_snprintf(hash_string_c, strlen(sn) + 50, + "%c%s%s%s", checksum, crypt_hash, gc->username, seed); + md5_append(&ctx, hash_string_p, strlen(hash_string_p)); + md5_finish(&ctx, result); + to_y64(result6, result, 16); + md5_append(&ctx, hash_string_c, strlen(hash_string_c)); + md5_finish(&ctx, result); + to_y64(result96, result, 16); + pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pack, 0, gc->username); + yahoo_packet_hash(pack, 6, result6); + yahoo_packet_hash(pack, 96, result96); + yahoo_packet_hash(pack, 1, gc->username); + yahoo_send_packet(yd, pack); + yahoo_packet_free(pack); +static void yahoo_packet_process(struct gaim_connection *gc, struct yahoo_packet *pkt) + case YAHOO_SERVICE_LOGON: + case YAHOO_SERVICE_LOGOFF: + case YAHOO_SERVICE_ISAWAY: + case YAHOO_SERVICE_ISBACK: + case YAHOO_SERVICE_GAMELOGON: + case YAHOO_SERVICE_GAMELOGOFF: + yahoo_process_status(gc, pkt); + case YAHOO_SERVICE_NOTIFY: + yahoo_process_notify(gc, pkt); + case YAHOO_SERVICE_MESSAGE: + case YAHOO_SERVICE_GAMEMSG: + yahoo_process_message(gc, pkt); + case YAHOO_SERVICE_NEWMAIL: + yahoo_process_mail(gc, pkt); + case YAHOO_SERVICE_NEWCONTACT: + yahoo_process_contact(gc, pkt); + case YAHOO_SERVICE_LIST: + yahoo_process_list(gc, pkt); + case YAHOO_SERVICE_AUTH: + yahoo_process_auth(gc, pkt); + debug_printf("unhandled service 0x%02x\n", pkt->service); +static void yahoo_pending(gpointer data, gint source, GaimInputCondition cond) + struct gaim_connection *gc = data; + struct yahoo_data *yd = gc->proto_data; + len = read(yd->fd, buf, sizeof(buf)); + hide_login_progress_error(gc, "Unable to read"); + yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen); + memcpy(yd->rxqueue + yd->rxlen, buf, len); + struct yahoo_packet *pkt; + if (yd->rxlen < YAHOO_PACKET_HDRLEN) + pktlen = yahoo_get16(yd->rxqueue + pos); pos += 2; + debug_printf("%d bytes to read, rxlen is %d\n", pktlen, yd->rxlen); + if (yd->rxlen < (YAHOO_PACKET_HDRLEN + pktlen)) + yahoo_packet_dump(yd->rxqueue, YAHOO_PACKET_HDRLEN + pktlen); + pkt = yahoo_packet_new(0, 0, 0); + pkt->service = yahoo_get16(yd->rxqueue + pos); pos += 2; + pkt->status = yahoo_get32(yd->rxqueue + pos); pos += 4; + debug_printf("Yahoo Service: 0x%02x Status: %d\n", pkt->service, pkt->status); + pkt->id = yahoo_get32(yd->rxqueue + pos); pos += 4; + yahoo_packet_read(pkt, yd->rxqueue + pos, pktlen); + yd->rxlen -= YAHOO_PACKET_HDRLEN + pktlen; + char *tmp = g_memdup(yd->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yd->rxlen); + yahoo_packet_process(gc, pkt); + yahoo_packet_free(pkt); +static void yahoo_got_connected(gpointer data, gint source, GaimInputCondition cond) + struct gaim_connection *gc = data; + struct yahoo_packet *pkt; + if (!g_slist_find(connections, gc)) { + hide_login_progress(gc, "Unable to connect"); + pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, 1, gc->username); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); + gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc); +static void yahoo_login(struct aim_user *user) { + struct gaim_connection *gc = new_gaim_conn(user); + struct yahoo_data *yd = gc->proto_data = g_new0(struct yahoo_data, 1); + set_login_progress(gc, 1, "Connecting"); + yd->hash = g_hash_table_new(g_str_hash, g_str_equal); + yd->games = g_hash_table_new(g_str_hash, g_str_equal); + if (!g_strncasecmp(user->proto_opt[USEROPT_PAGERHOST], "cs.yahoo.com", strlen("cs.yahoo.com"))) { + /* Figured out the new auth method -- cs.yahoo.com likes to disconnect on buddy remove and add now */ + debug_printf("Setting new Yahoo! server.\n"); + g_snprintf(user->proto_opt[USEROPT_PAGERHOST], strlen("scs.yahoo.com") + 1, "scs.yahoo.com"); + if (proxy_connect(user->proto_opt[USEROPT_PAGERHOST][0] ? + user->proto_opt[USEROPT_PAGERHOST] : YAHOO_PAGER_HOST, + user->proto_opt[USEROPT_PAGERPORT][0] ? + atoi(user->proto_opt[USEROPT_PAGERPORT]) : YAHOO_PAGER_PORT, + yahoo_got_connected, gc) < 0) { + hide_login_progress(gc, "Connection problem"); +static gboolean yahoo_destroy_hash(gpointer key, gpointer val, gpointer data) +static void yahoo_close(struct gaim_connection *gc) { + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + g_hash_table_foreach_remove(yd->hash, yahoo_destroy_hash, NULL); + g_hash_table_destroy(yd->hash); + g_hash_table_foreach_remove(yd->games, yahoo_destroy_hash, NULL); + g_hash_table_destroy(yd->games); + gaim_input_remove(gc->inpa); +static char **yahoo_list_icon(int uc) + if ((uc >> 2) == YAHOO_STATUS_IDLE) + return status_idle_xpm; + else if (uc & UC_UNAVAILABLE) + return status_away_xpm; + else if (uc & YAHOO_STATUS_GAME) + return status_game_xpm; + return status_here_xpm; +static char *yahoo_get_status_string(enum yahoo_status a) + return "Be Right Back"; + case YAHOO_STATUS_BUSY: + case YAHOO_STATUS_NOTATHOME: + case YAHOO_STATUS_NOTATDESK: + case YAHOO_STATUS_NOTINOFFICE: + return "Not In Office"; + case YAHOO_STATUS_ONPHONE: + case YAHOO_STATUS_ONVACATION: + case YAHOO_STATUS_OUTTOLUNCH: + case YAHOO_STATUS_STEPPEDOUT: + case YAHOO_STATUS_INVISIBLE: +static void yahoo_game(struct gaim_connection *gc, char *name) { + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + char *game = g_hash_table_lookup(yd->games, name); + t = game = g_strdup(strstr(game, "ante?room=")); + g_snprintf(url, sizeof url, "http://games.yahoo.com/games/%s", game); +static GList *yahoo_buddy_menu(struct gaim_connection *gc, char *who) + struct proto_buddy_menu *pbm; + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + struct buddy *b = find_buddy(gc, who); /* this should never be null. if it is, + segfault and get the bug report. */ + static char buf2[1024]; + if (b->uc & UC_UNAVAILABLE && b->uc >> 2 != YAHOO_STATUS_IDLE) { + pbm = g_new0(struct proto_buddy_menu, 1); + if ((b->uc >> 2) != YAHOO_STATUS_CUSTOM) + g_snprintf(buf, sizeof buf, + "Status: %s", yahoo_get_status_string(b->uc >> 2)); + g_snprintf(buf, sizeof buf, "Custom Status: %s", + (char *)g_hash_table_lookup(yd->hash, b->name)); + m = g_list_append(m, pbm); + if (b->uc | YAHOO_STATUS_GAME) { + char *game = g_hash_table_lookup(yd->games, b->name); + pbm = g_new0(struct proto_buddy_menu, 1); + if (!(room = strstr(game, "&follow="))) /* skip ahead to the url */ + while (*room && *room != '\t') /* skip to the tab */ + t = room++; /* room as now at the name */ + t++; /* replace the \n with a space */ + g_snprintf(buf2, sizeof buf2, "%s", room); + pbm->callback = yahoo_game; + m = g_list_append(m, pbm); +static GList *yahoo_user_opts() + struct proto_user_opt *puo; + puo = g_new0(struct proto_user_opt, 1); + puo->label = "Pager Host:"; + puo->def = YAHOO_PAGER_HOST; + puo->pos = USEROPT_PAGERHOST; + m = g_list_append(m, puo); + puo = g_new0(struct proto_user_opt, 1); + puo->label = "Pager Port:"; + puo->pos = USEROPT_PAGERPORT; + m = g_list_append(m, puo); +static void yahoo_act_id(gpointer data, char *entry) + struct gaim_connection *gc = data; + struct yahoo_data *yd = gc->proto_data; + struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_IDACT, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, 3, entry); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); + g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", entry); +static void yahoo_do_action(struct gaim_connection *gc, char *act) + if (!strcmp(act, "Activate ID")) { + do_prompt_dialog("Activate which ID:", gc->displayname, gc, yahoo_act_id, NULL); +static GList *yahoo_actions() { + m = g_list_append(m, "Activate ID"); +static int yahoo_send_im(struct gaim_connection *gc, char *who, char *what, int len, int flags) + struct yahoo_data *yd = gc->proto_data; + struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, 0); + yahoo_packet_hash(pkt, 1, gc->displayname); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 14, what); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); +int yahoo_send_typing(struct gaim_connection *gc, char *who, int typ) + struct yahoo_data *yd = gc->proto_data; + struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_TYPING, 0); + yahoo_packet_hash(pkt, 49, "TYPING"); + yahoo_packet_hash(pkt, 1, gc->displayname); + yahoo_packet_hash(pkt, 14, " "); + yahoo_packet_hash(pkt, 13, typ ? "1" : "0"); + yahoo_packet_hash(pkt, 5, who); + yahoo_packet_hash(pkt, 1002, "1"); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); +static void yahoo_set_away(struct gaim_connection *gc, char *state, char *msg) + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + struct yahoo_packet *pkt; + yd->current_status = YAHOO_STATUS_CUSTOM; + if (!strcmp(state, "Available")) { + yd->current_status = YAHOO_STATUS_AVAILABLE; + } else if (!strcmp(state, "Be Right Back")) { + yd->current_status = YAHOO_STATUS_BRB; + } else if (!strcmp(state, "Busy")) { + yd->current_status = YAHOO_STATUS_BUSY; + } else if (!strcmp(state, "Not At Home")) { + yd->current_status = YAHOO_STATUS_NOTATHOME; + } else if (!strcmp(state, "Not At Desk")) { + yd->current_status = YAHOO_STATUS_NOTATDESK; + } else if (!strcmp(state, "Not In Office")) { + yd->current_status = YAHOO_STATUS_NOTINOFFICE; + } else if (!strcmp(state, "On Phone")) { + yd->current_status = YAHOO_STATUS_ONPHONE; + } else if (!strcmp(state, "On Vacation")) { + yd->current_status = YAHOO_STATUS_ONVACATION; + } else if (!strcmp(state, "Out To Lunch")) { + yd->current_status = YAHOO_STATUS_OUTTOLUNCH; + } else if (!strcmp(state, "Stepped Out")) { + yd->current_status = YAHOO_STATUS_STEPPEDOUT; + } else if (!strcmp(state, "Invisible")) { + yd->current_status = YAHOO_STATUS_INVISIBLE; + } else if (!strcmp(state, GAIM_AWAY_CUSTOM)) { + yd->current_status = YAHOO_STATUS_IDLE; + yd->current_status = YAHOO_STATUS_AVAILABLE; + } else if (gc->is_idle) { + yd->current_status = YAHOO_STATUS_IDLE; + yd->current_status = YAHOO_STATUS_AVAILABLE; + if (yd->current_status == YAHOO_STATUS_AVAILABLE) + service = YAHOO_SERVICE_ISBACK; + service = YAHOO_SERVICE_ISAWAY; + pkt = yahoo_packet_new(service, yd->current_status, 0); + g_snprintf(s, sizeof(s), "%d", yd->current_status); + yahoo_packet_hash(pkt, 10, s); + if (yd->current_status == YAHOO_STATUS_CUSTOM) + yahoo_packet_hash(pkt, 19, msg); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); +static void yahoo_set_idle(struct gaim_connection *gc, int idle) + struct yahoo_data *yd = gc->proto_data; + struct yahoo_packet *pkt = NULL; + if (idle && yd->current_status == YAHOO_STATUS_AVAILABLE) { + pkt = yahoo_packet_new(YAHOO_SERVICE_ISAWAY, YAHOO_STATUS_IDLE, 0); + yd->current_status = YAHOO_STATUS_IDLE; + } else if (!idle && yd->current_status == YAHOO_STATUS_IDLE) { + pkt = yahoo_packet_new(YAHOO_SERVICE_ISAWAY, YAHOO_STATUS_AVAILABLE, 0); + yd->current_status = YAHOO_STATUS_AVAILABLE; + g_snprintf(buf, sizeof(buf), "%d", yd->current_status); + yahoo_packet_hash(pkt, 10, buf); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); +static GList *yahoo_away_states(struct gaim_connection *gc) + m = g_list_append(m, "Available"); + m = g_list_append(m, "Be Right Back"); + m = g_list_append(m, "Busy"); + m = g_list_append(m, "Not At Home"); + m = g_list_append(m, "Not At Desk"); + m = g_list_append(m, "Not In Office"); + m = g_list_append(m, "On Phone"); + m = g_list_append(m, "On Vacation"); + m = g_list_append(m, "Out To Lunch"); + m = g_list_append(m, "Stepped Out"); + m = g_list_append(m, "Invisible"); + m = g_list_append(m, GAIM_AWAY_CUSTOM); +static void yahoo_keepalive(struct gaim_connection *gc) + struct yahoo_data *yd = gc->proto_data; + struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, 0); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); +static void yahoo_add_buddy(struct gaim_connection *gc, char *who) + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + struct yahoo_packet *pkt; + g = find_group_by_buddy(gc, who); + pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, 1, gc->displayname); + yahoo_packet_hash(pkt, 7, who); + yahoo_packet_hash(pkt, 65, group); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); +static void yahoo_remove_buddy(struct gaim_connection *gc, char *who, char *group) + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, 1, gc->displayname); + yahoo_packet_hash(pkt, 7, who); + yahoo_packet_hash(pkt, 65, group); + yahoo_send_packet(yd, pkt); + yahoo_packet_free(pkt); +GSList *yahoo_smiley_list() + GSList *smilies = NULL; + smilies = add_smiley(smilies, "=:)", yahoo_alien, 1); + smilies = add_smiley(smilies, "=:-)", yahoo_alien, 0); + smilies = add_smiley(smilies, "o:)", yahoo_angel, 0); + smilies = add_smiley(smilies, "o:-)", yahoo_angel, 0); + smilies = add_smiley(smilies, "0:)", yahoo_angel, 0); + smilies = add_smiley(smilies, "0:-)", yahoo_angel, 0); + smilies = add_smiley(smilies, "X-(", yahoo_angry, 1); + smilies = add_smiley(smilies, "X(", yahoo_angry, 0); + smilies = add_smiley(smilies, "x-(", yahoo_angry, 0); + smilies = add_smiley(smilies, "x(", yahoo_angry, 0); + smilies = add_smiley(smilies, ":D", yahoo_bigsmile, 1); + smilies = add_smiley(smilies, ":-D", yahoo_bigsmile, 0); + smilies = add_smiley(smilies, ":\">", yahoo_blush, 1); + smilies = add_smiley(smilies, "=;", yahoo_bye, 1); + smilies = add_smiley(smilies, ":o)", yahoo_clown, 1); + smilies = add_smiley(smilies, ":0)", yahoo_clown, 0); + smilies = add_smiley(smilies, ":O)", yahoo_clown, 0); + smilies = add_smiley(smilies, "<@:)", yahoo_clown, 0); + smilies = add_smiley(smilies, "3:-0", yahoo_cow, 1); + smilies = add_smiley(smilies, "3:-o", yahoo_cow, 0); + smilies = add_smiley(smilies, "3:-O", yahoo_cow, 0); + smilies = add_smiley(smilies, "3:O", yahoo_cow, 0); + smilies = add_smiley(smilies, "<):)", yahoo_cowboy, 1); + smilies = add_smiley(smilies, ":((", yahoo_cry, 1); + smilies = add_smiley(smilies, ":-((", yahoo_cry, 0); + smilies = add_smiley(smilies, ">:)", yahoo_devil, 1); + smilies = add_smiley(smilies, "@};-", yahoo_flower, 1); + smilies = add_smiley(smilies, "8-X", yahoo_ghost, 1); + smilies = add_smiley(smilies, ":B", yahoo_glasses, 1); + smilies = add_smiley(smilies, ":-B", yahoo_glasses, 0); + smilies = add_smiley(smilies, ":))", yahoo_laughloud, 1); + smilies = add_smiley(smilies, ":-))", yahoo_laughloud, 0); + smilies = add_smiley(smilies, ":x", yahoo_love, 1); + smilies = add_smiley(smilies, ":-x", yahoo_love, 0); + smilies = add_smiley(smilies, ":X", yahoo_love, 0); + smilies = add_smiley(smilies, ":-X", yahoo_love, 0); + smilies = add_smiley(smilies, ":>", yahoo_mean, 1); + smilies = add_smiley(smilies, ":->", yahoo_mean, 0); + smilies = add_smiley(smilies, ":|", yahoo_neutral, 1); + smilies = add_smiley(smilies, ":-|", yahoo_neutral, 0); + smilies = add_smiley(smilies, ":O", yahoo_ooooh, 1); + smilies = add_smiley(smilies, ":-O", yahoo_ooooh, 0); + smilies = add_smiley(smilies, ":-\\", yahoo_question, 1); + smilies = add_smiley(smilies, ":-/", yahoo_question, 0); + smilies = add_smiley(smilies, ":(", yahoo_sad, 1); + smilies = add_smiley(smilies, ":-(", yahoo_sad, 0); + smilies = add_smiley(smilies, "I-)", yahoo_sleep, 1); + smilies = add_smiley(smilies, "|-)", yahoo_sleep, 0); + smilies = add_smiley(smilies, "I-|", yahoo_sleep, 0); + smilies = add_smiley(smilies, ":)", yahoo_smiley, 1); + smilies = add_smiley(smilies, ":-)", yahoo_smiley, 0); + smilies = add_smiley(smilies, "(:", yahoo_smiley, 0); + smilies = add_smiley(smilies, "(-:", yahoo_smiley, 0); + smilies = add_smiley(smilies, "B-)", yahoo_sunglas, 1); + smilies = add_smiley(smilies, ":-p", yahoo_tongue, 1); + smilies = add_smiley(smilies, ":p", yahoo_tongue, 0); + smilies = add_smiley(smilies, ":P", yahoo_tongue, 0); + smilies = add_smiley(smilies, ":-P", yahoo_tongue, 0); + smilies = add_smiley(smilies, ";)", yahoo_wink, 1); + smilies = add_smiley(smilies, ";-)", yahoo_wink, 0); +static struct prpl *my_protocol = NULL; +void yahoo_init(struct prpl *ret) { + ret->protocol = PROTO_YAHOO; + ret->options = OPT_PROTO_MAIL_CHECK | OPT_PROTO_USE_POINT_SIZE; + ret->name = yahoo_name; + ret->user_opts = yahoo_user_opts; + ret->login = yahoo_login; + ret->close = yahoo_close; + ret->buddy_menu = yahoo_buddy_menu; + ret->list_icon = yahoo_list_icon; + ret->actions = yahoo_actions; + ret->do_action = yahoo_do_action; + ret->send_im = yahoo_send_im; + ret->away_states = yahoo_away_states; + ret->set_away = yahoo_set_away; + ret->set_idle = yahoo_set_idle; + ret->keepalive = yahoo_keepalive; + ret->add_buddy = yahoo_add_buddy; + ret->remove_buddy = yahoo_remove_buddy; + ret->send_typing = yahoo_send_typing; + ret->smiley_list = yahoo_smiley_list; +char *gaim_plugin_init(GModule *handle) + load_protocol(yahoo_init, sizeof(struct prpl)); +void gaim_plugin_remove() + struct prpl *p = find_prpl(PROTO_YAHOO); + return PRPL_DESC("Yahoo");