gaim/gaim

Thanks javabsp
gtk1-stable
2002-08-30, Sean Egan
7dbb16589009
Parents 0d479c047825
Children 61244b0a5663
Thanks javabsp
--- 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
Pollak)
* 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
+ Ka-Hing Cheung)
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);
/*------------------------------------------------------------------------*/
/* Helpers */
@@ -193,6 +193,7 @@
update_icon(c);
update_checkbox(c);
update_smilies(c);
+ update_fontmode(c);
plugin_event(event_new_conversation, name, 0, 0, 0);
return c;
}
@@ -1766,6 +1767,12 @@
char buf2[BUF_LONG];
char mdate[64];
int unhighlight = 0;
+ int timesize;
+
+ if (c->gc && c->gc->prpl->options & OPT_PROTO_USE_POINT_SIZE)
+ timesize = 10;
+ else
+ timesize = 2;
if (c->is_chat && (!c->gc || !g_slist_find(c->gc->buddy_chats, c)))
return;
@@ -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);
else
g_snprintf(buf, BUF_LONG, "<B>%s</B>", what);
- g_snprintf(buf2, sizeof(buf2), "<FONT SIZE=\"2\"><!--(%s) --></FONT><B>%s</B><BR>",
- mdate, what);
+ 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);
else
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);
g_free(str);
@@ -2328,6 +2335,7 @@
update_icon(cnv);
update_checkbox(cnv);
update_smilies(cnv);
+ update_fontmode(cnv);
gaim_setup_imhtml_smileys(cnv->text);
}
@@ -2487,6 +2495,7 @@
update_icon(c);
update_checkbox(c);
update_smilies(c);
+ update_fontmode(c);
gaim_setup_imhtml_smileys(c->text);
}
@@ -3528,6 +3537,15 @@
#endif
}
+void update_fontmode(struct conversation *c)
+{
+ if (!c || !c->gc)
+ return;
+
+ gtk_imhtml_set_use_pointsize(GTK_IMHTML(c->text),
+ c->gc->prpl->options & OPT_PROTO_USE_POINT_SIZE);
+}
+
void update_smilies(struct conversation *c)
{
GSList *smilies;
--- 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 MAX_FONT_SIZE 7
-#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);
}
+void
+gtk_imhtml_set_use_pointsize (GtkIMHtml *imhtml, gboolean point)
+{
+ imhtml->use_pointsize = point;
+}
struct im_image {
gchar *filename;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkimhtml.h Fri Aug 30 07:54:54 2002 -0400
@@ -0,0 +1,155 @@
+/*
+ * GtkIMHtml
+ *
+ * Copyright (C) 2000, Eric Warmenhoven <warmenhoven@yahoo.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * 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
+ *
+ */
+
+#ifndef __GTK_IMHTML_H
+#define __GTK_IMHTML_H
+
+#include <gdk/gdk.h>
+#include <gtk/gtklayout.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define GTK_TYPE_IMHTML (gtk_imhtml_get_type ())
+#define GTK_IMHTML(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_IMHTML, GtkIMHtml))
+#define GTK_IMHTML_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_IMHTML, GtkIMHtmlClass))
+#define GTK_IS_IMHTML(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_IMHTML))
+#define GTK_IS_IMHTML_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IMHTML))
+
+typedef gchar** (*GtkIMHtmlImage) (gchar *url);
+
+typedef struct _GtkSmileyTree GtkSmileyTree;
+
+typedef struct _GtkIMHtml GtkIMHtml;
+typedef struct _GtkIMHtmlClass GtkIMHtmlClass;
+
+struct _GtkIMHtml {
+ GtkLayout layout;
+
+ GdkFont *default_font;
+ GdkColor *default_fg_color;
+ GdkColor *default_bg_color;
+ GdkColor *default_hl_color;
+ GdkColor *default_hlfg_color;
+
+ GdkCursor *hand_cursor;
+ GdkCursor *arrow_cursor;
+
+ GList *bits;
+ GList *click;
+ struct _GtkIMHtmlBit *tip_bit;
+ GList *im_images;
+ GtkWidget *tip_window;
+ guint tip_timer;
+
+ guint sel_startx, sel_starty;
+ guint sel_endx, sel_endy;
+ gboolean selection;
+ guint sel_mode;
+ GString *selected_text;
+ struct line_info *sel_endchunk;
+ guint scroll_timer;
+
+ guint x, y;
+ guint xsize;
+ guint llheight;
+ guint llascent;
+ GList *line;
+
+ GtkIMHtmlImage img;
+
+ gboolean smileys;
+ gboolean comments;
+ gboolean use_pointsize;
+
+ GtkSmileyTree *smiley_data;
+};
+
+struct _GtkIMHtmlClass {
+ GtkLayoutClass parent_class;
+
+ void (*url_clicked) (GtkIMHtml *, const gchar *);
+};
+
+typedef enum
+{
+ GTK_IMHTML_NO_COLOURS = 1 << 0,
+ GTK_IMHTML_NO_FONTS = 1 << 1,
+ GTK_IMHTML_NO_COMMENTS = 1 << 2,
+ GTK_IMHTML_NO_TITLE = 1 << 3,
+ GTK_IMHTML_NO_NEWLINE = 1 << 4,
+ GTK_IMHTML_NO_SIZES = 1 << 5,
+ GTK_IMHTML_NO_SCROLL = 1 << 6,
+ GTK_IMHTML_RETURN_LOG = 1 << 7
+} GtkIMHtmlOptions;
+
+GtkType gtk_imhtml_get_type (void);
+GtkWidget* gtk_imhtml_new (GtkAdjustment *hadj,
+ GtkAdjustment *vadj);
+
+void gtk_imhtml_set_adjustments (GtkIMHtml *imhtml,
+ GtkAdjustment *hadj,
+ GtkAdjustment *vadj);
+
+void gtk_imhtml_set_defaults (GtkIMHtml *imhtml,
+ GdkFont *font,
+ GdkColor *fg_color,
+ GdkColor *bg_color);
+
+void gtk_imhtml_set_img_handler (GtkIMHtml *imhtml,
+ GtkIMHtmlImage handler);
+
+void gtk_imhtml_associate_smiley (GtkIMHtml *imhtml,
+ gchar *text,
+ gchar **xpm);
+
+void gtk_imhtml_init_smileys (GtkIMHtml *imhtml);
+
+void gtk_imhtml_remove_smileys (GtkIMHtml *imhtml);
+
+void gtk_imhtml_reset_smileys (GtkIMHtml *imhtml);
+
+void gtk_imhtml_show_smileys (GtkIMHtml *imhtml,
+ gboolean show);
+
+void gtk_imhtml_show_comments (GtkIMHtml *imhtml,
+ gboolean show);
+
+void gtk_imhtml_set_use_pointsize(GtkIMHtml *imhtml,
+ gboolean point);
+
+GString* gtk_imhtml_append_text (GtkIMHtml *imhtml,
+ const gchar *text,
+ gint len,
+ GtkIMHtmlOptions options);
+
+void gtk_imhtml_clear (GtkIMHtml *imhtml);
+
+void gtk_imhtml_page_up (GtkIMHtml *imhtml);
+
+void gtk_imhtml_page_down (GtkIMHtml *imhtml);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- /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 -*- */
+/*
+ * gaim
+ *
+ * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include <netdb.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/stat.h>
+#include "multi.h"
+#include "prpl.h"
+#include "gaim.h"
+#ifdef MAX
+#undef MAX
+#endif
+#ifdef MIN
+#undef MIN
+#endif
+#include "jabber.h"
+#include "proxy.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 IQ_NONE -1
+#define IQ_AUTH 0
+#define IQ_ROSTER 1
+
+#define UC_AWAY (0x02 | UC_UNAVAILABLE)
+#define UC_CHAT 0x04
+#define UC_XA (0x08 | UC_UNAVAILABLE)
+#define UC_DND (0x10 | UC_UNAVAILABLE)
+#define UC_ERROR (0x20 | UC_UNAVAILABLE)
+
+#define DEFAULT_SERVER "jabber.org"
+#define DEFAULT_GROUPCHAT "conference.jabber.org"
+#define DEFAULT_PORT 5222
+
+#define USEROPT_PORT 0
+
+#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 {
+ /* Core structure */
+ 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 */
+
+ /* Stream stuff */
+ int id; /* id counter for jab_getid() function */
+ char idbuf[9]; /* temporary storage for jab_getid() */
+ char *sid; /* stream id from server, for digest auth */
+ XML_Parser parser; /* Parser instance */
+ xmlnode current; /* Current node in parsing instance.. */
+
+ /* Event callback ptrs */
+ void (*on_state)(struct gjconn_struct *gjc, int state);
+ void (*on_packet)(struct gjconn_struct *gjc, jpacket p);
+
+ GHashTable *queries; /* query tracker */
+
+ void *priv;
+
+} *gjconn, gjconn_struct;
+
+typedef void (*gjconn_state_h)(gjconn gjc, int state);
+typedef void (*gjconn_packet_h)(gjconn gjc, jpacket p);
+
+static gjconn gjab_new(char *user, char *pass, void *priv);
+static void gjab_delete(gjconn gjc);
+static void gjab_state_handler(gjconn gjc, gjconn_state_h h);
+static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h);
+static void gjab_start(gjconn gjc);
+static void gjab_stop(gjconn gjc);
+/*
+static int gjab_getfd(gjconn gjc);
+static jid gjab_getjid(gjconn gjc);
+static char *gjab_getsid(gjconn gjc);
+*/
+static char *gjab_getid(gjconn gjc);
+static void gjab_send(gjconn gjc, xmlnode x);
+static void gjab_send_raw(gjconn gjc, const char *str);
+static void gjab_recv(gjconn gjc);
+static void gjab_auth(gjconn gjc);
+
+/*
+ * It is *this* to which we point the gaim_connection proto_data
+ */
+struct jabber_data {
+ gjconn gjc;
+ gboolean did_import;
+ GSList *chats;
+ time_t idle;
+ gboolean die;
+ GHashTable *buddies;
+};
+
+/*
+ * It is *this* to which we point the buddy proto_data
+ */
+struct jabber_buddy_data {
+ GSList *resources;
+ char *error_msg;
+};
+
+/*
+ * per-resource info
+ */
+typedef struct jabber_resource_info {
+ char *name;
+ int priority;
+ int state;
+ char *away_msg;
+ char *thread_id;
+ gboolean has_composing;
+} *jab_res_info;
+
+/*
+ * 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
+ * don't want that.
+ *
+ * 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
+ * we're done with 'em.
+ */
+#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 jabber_chat {
+ gaim_jid gjid;
+ struct gaim_connection *gc;
+ struct conversation *b;
+ int id;
+ int state;
+};
+
+/*
+ * Jabber chat states...
+ *
+ * Note: due to a bug in one version of the Jabber server, subscriptions
+ * to chat groups aren't (always?) properly removed at the server. The
+ * result is clients receive Jabber "presence" notifications for JIDs
+ * they no longer care about. The problem with such vestigial notifies is
+ * that we really have no way of telling if it's vestigial or if it's a
+ * valid "buddy" presence notification. So we keep jabber_chat structs
+ * around after leaving a chat group and simply mark them "closed." That
+ * way we can test for such errant presence notifications. I.e.: if we
+ * get a presence notfication from a JID that matches a chat group JID,
+ * we disregard it.
+ */
+#define JCS_PENDING 1 /* pending */
+#define JCS_ACTIVE 2 /* active */
+#define JCS_CLOSED 3 /* closed */
+
+
+static char *jabber_name()
+{
+ return "Jabber";
+}
+
+#define STATE_EVT(arg) if(gjc->on_state) { (gjc->on_state)(gjc, (arg) ); }
+
+static void jabber_handlevcard(gjconn, xmlnode, char *);
+
+static char *create_valid_jid(const char *given, char *server, char *resource)
+{
+ char *valid;
+
+ if (!strchr(given, '@'))
+ valid = g_strdup_printf("%s@%s/%s", given, server, resource);
+ else if (!strchr(strchr(given, '@'), '/'))
+ valid = g_strdup_printf("%s/%s", given, resource);
+ else
+ valid = g_strdup(given);
+
+ return valid;
+}
+
+/*
+ * Dispose of a gaim_jid_struct
+ */
+static void gaim_jid_free(gaim_jid gjid)
+{
+ if(gjid) {
+ if(gjid->resource)
+ free(gjid->resource);
+ if(gjid->user)
+ free(gjid->user);
+ if(gjid->server)
+ free(gjid->server);
+ if(gjid->full)
+ free(gjid->full);
+ free(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
+ * gaim_jid_free().
+ *
+ * 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* */
+
+ gaim_jid gjid = NULL;
+
+ if(name && strlen(name)) {
+ char *server, *resource, *type, *str;
+ int full_len = 0;
+
+ /* user@server/resource */
+
+ str = strdup(name); /* we mangle a copy */
+
+ gjid = calloc(1, sizeof(struct gaim_jid_struct));
+
+ if((resource = strstr(str, "/")) != NULL) {
+ *resource = '\0';
+ ++resource;
+ if((full_len = strlen(resource)) > 0) {
+ gjid->resource = strdup(resource);
+ ++full_len; /* for later "/" addition */
+ }
+ } else {
+ resource = str + strlen(str); /* point to end */
+ }
+
+ type = strstr(str, ":");
+ if(type != NULL && type < resource) {
+ *type = '\0';
+ ++type;
+ 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);
+ } else {
+ *server = '\0';
+ ++server;
+ gjid->server = strdup(server);
+ full_len += strlen(server) + 1; /* account for later "@" */
+ if(strlen(str) > 0) {
+ gjid->user = strdup(str);
+ full_len += strlen(str);
+ }
+ }
+
+ free(str);
+
+ if(!jid_safe(gjid)) {
+ gaim_jid_free(gjid);
+ gjid = NULL;
+ } else {
+ if(full_len) {
+ char *s = gjid->full = malloc(++full_len);
+
+ if(gjid->user) {
+ strcpy(s, gjid->user);
+ s += strlen(gjid->user);
+ }
+ if(gjid->server) {
+ if(s > gjid->full)
+ *(s++) = '@';
+ strcpy(s, gjid->server);
+ s += strlen(gjid->server);
+ }
+ if(gjid->resource) {
+ *(s++) = '/';
+ strcpy(s, gjid->resource);
+ }
+ }
+ }
+ }
+
+ return gjid;
+}
+
+/*
+ * 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
+ * appended.
+ *
+ * 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
+ * with it.
+ */
+static gchar *get_realwho(gjconn gjc, char *who, int incl_resource, gaim_jid *gjid)
+{
+ gaim_jid my_gjid;
+ gchar *my_who;
+ gchar *realwho = NULL;
+
+ if(!(who && who[0])) {
+ return NULL;
+ }
+
+ /*
+ * 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);
+ } else {
+ 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 (?)
+ */
+ if(my_gjid->user) {
+ /*
+ * Include "/resource" bit?
+ */
+ if(incl_resource) {
+ realwho = g_strdup(my_gjid->full);
+ } else {
+ realwho = g_strdup_printf("%s@%s", my_gjid->user, my_gjid->server);
+ }
+ } else {
+ realwho = g_strdup(my_gjid->server);
+ }
+ }
+
+ g_free(my_who);
+
+ if(gjid) {
+ *gjid = my_gjid;
+ } else {
+ gaim_jid_free(my_gjid);
+ }
+
+ return realwho;
+}
+
+static gjconn gjab_new(char *user, char *pass, void *priv)
+{
+ pool p;
+ gjconn gjc;
+
+ if (!user)
+ return (NULL);
+
+ p = pool_new();
+ if (!p)
+ return (NULL);
+ gjc = pmalloc_x(p, sizeof(gjconn_struct), 0);
+ if (!gjc) {
+ pool_free(p); /* no need for this anymore! */
+ return (NULL);
+ }
+ gjc->p = p;
+
+ if((gjc->user = jid_new(p, user)) == NULL) {
+ pool_free(p); /* no need for this anymore! */
+ return (NULL);
+ }
+
+ gjc->pass = strdup(pass);
+
+ gjc->state = JCONN_STATE_OFF;
+ gjc->was_connected = 0;
+ gjc->id = 1;
+ gjc->fd = -1;
+
+ gjc->priv = priv;
+
+ return gjc;
+}
+
+static void gjab_delete(gjconn gjc)
+{
+ if (!gjc)
+ return;
+
+ gjab_stop(gjc);
+ free(gjc->pass);
+ pool_free(gjc->p);
+}
+
+static void gjab_state_handler(gjconn gjc, gjconn_state_h h)
+{
+ if (!gjc)
+ return;
+
+ gjc->on_state = h;
+}
+
+static void gjab_packet_handler(gjconn gjc, gjconn_packet_h h)
+{
+ if (!gjc)
+ return;
+
+ gjc->on_packet = h;
+}
+
+static void gjab_stop(gjconn gjc)
+{
+ if (!gjc || gjc->state == JCONN_STATE_OFF)
+ return;
+
+ gjab_send_raw(gjc, "</stream:stream>");
+ gjc->state = JCONN_STATE_OFF;
+ gjc->was_connected = 0;
+ close(gjc->fd);
+ gjc->fd = -1;
+ XML_ParserFree(gjc->parser);
+ gjc->parser = NULL;
+}
+
+/*
+static int gjab_getfd(gjconn gjc)
+{
+ if (gjc)
+ return gjc->fd;
+ else
+ return -1;
+}
+
+static jid gjab_getjid(gjconn gjc)
+{
+ if (gjc)
+ return (gjc->user);
+ else
+ return NULL;
+}
+
+static char *gjab_getsid(gjconn gjc)
+{
+ if (gjc)
+ return (gjc->sid);
+ else
+ return NULL;
+}
+*/
+
+static char *gjab_getid(gjconn gjc)
+{
+ snprintf(gjc->idbuf, 8, "%d", gjc->id++);
+ return &gjc->idbuf[0];
+}
+
+static void gjab_send(gjconn gjc, xmlnode x)
+{
+ if (gjc && gjc->state != JCONN_STATE_OFF) {
+ char *buf = xmlnode2str(x);
+ if (buf)
+ 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);
+ fflush(stderr);
+ }
+ debug_printf("gjab_send_raw: %s\n", str);
+ }
+}
+
+static void gjab_reqroster(gjconn gjc)
+{
+ xmlnode x;
+
+ x = jutil_iqnew(JPACKET__GET, NS_ROSTER);
+ xmlnode_put_attrib(x, "id", gjab_getid(gjc));
+
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+}
+
+static void gjab_reqauth(gjconn gjc)
+{
+ xmlnode x, y, z;
+ char *user;
+
+ if (!gjc)
+ return;
+
+ x = jutil_iqnew(JPACKET__GET, NS_AUTH);
+ xmlnode_put_attrib(x, "id", IQID_AUTH);
+ y = xmlnode_get_tag(x, "query");
+
+ user = gjc->user->user;
+
+ if (user) {
+ z = xmlnode_insert_tag(y, "username");
+ xmlnode_insert_cdata(z, user, -1);
+ }
+
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+}
+
+static void gjab_auth(gjconn gjc)
+{
+ xmlnode x, y, z;
+ char *hash, *user;
+
+ if (!gjc)
+ return;
+
+ x = jutil_iqnew(JPACKET__SET, NS_AUTH);
+ xmlnode_put_attrib(x, "id", IQID_AUTH);
+ y = xmlnode_get_tag(x, "query");
+
+ user = gjc->user->user;
+
+ if (user) {
+ z = xmlnode_insert_tag(y, "username");
+ xmlnode_insert_cdata(z, user, -1);
+ }
+
+ z = xmlnode_insert_tag(y, "resource");
+ xmlnode_insert_cdata(z, gjc->user->resource, -1);
+
+ if (gjc->sid) {
+ 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);
+ hash = shahash(hash);
+ xmlnode_insert_cdata(z, hash, 40);
+ } else {
+ z = xmlnode_insert_tag(y, "password");
+ xmlnode_insert_cdata(z, gjc->pass, -1);
+ }
+
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+
+ return;
+}
+
+static void gjab_recv(gjconn gjc)
+{
+ static char buf[4096];
+ int len;
+
+ if (!gjc || gjc->state == JCONN_STATE_OFF)
+ return;
+
+ if ((len = read(gjc->fd, buf, sizeof(buf) - 1)) > 0) {
+ struct jabber_data *jd = GJ_GC(gjc)->proto_data;
+ buf[len] = '\0';
+ debug_printf("input (len %d): %s\n", len, buf);
+ XML_Parse(gjc->parser, buf, len, 0);
+ if (jd->die)
+ signoff(GJ_GC(gjc));
+ } else if (len < 0 || errno != EAGAIN) {
+ STATE_EVT(JCONN_STATE_OFF)
+ }
+}
+
+static void startElement(void *userdata, const char *name, const char **attribs)
+{
+ xmlnode x;
+ gjconn gjc = (gjconn) userdata;
+
+ if (gjc->current) {
+ /* Append the node to the current one */
+ x = xmlnode_insert_tag(gjc->current, name);
+ xmlnode_put_expat_attribs(x, attribs);
+
+ gjc->current = x;
+ } else {
+ x = xmlnode_new_tag(name);
+ xmlnode_put_expat_attribs(x, attribs);
+ if (strcmp(name, "stream:stream") == 0) {
+ /* special case: name == stream:stream */
+ /* id attrib of stream is stored for digest auth */
+ gjc->sid = g_strdup(xmlnode_get_attrib(x, "id"));
+ /* STATE_EVT(JCONN_STATE_AUTH) */
+ xmlnode_free(x);
+ } else {
+ gjc->current = x;
+ }
+ }
+}
+
+static void endElement(void *userdata, const char *name)
+{
+ gjconn gjc = (gjconn) userdata;
+ xmlnode x;
+ jpacket p;
+
+ if (gjc->current == NULL) {
+ /* we got </stream:stream> */
+ STATE_EVT(JCONN_STATE_OFF)
+ return;
+ }
+
+ x = xmlnode_get_parent(gjc->current);
+
+ if (!x) {
+ /* it is time to fire the event */
+ p = jpacket_new(gjc->current);
+
+ if (gjc->on_packet)
+ (gjc->on_packet) (gjc, p);
+ else
+ xmlnode_free(gjc->current);
+ }
+
+ gjc->current = x;
+}
+
+static void jabber_callback(gpointer data, gint source, GaimInputCondition condition)
+{
+ struct gaim_connection *gc = (struct gaim_connection *)data;
+ struct jabber_data *jd = (struct jabber_data *)gc->proto_data;
+
+ gjab_recv(jd->gjc);
+}
+
+static void charData(void *userdata, const char *s, int slen)
+{
+ gjconn gjc = (gjconn) userdata;
+
+ if (gjc->current)
+ xmlnode_insert_cdata(gjc->current, s, slen);
+}
+
+static void gjab_connected(gpointer data, gint source, GaimInputCondition cond)
+{
+ xmlnode x;
+ char *t, *t2;
+ struct gaim_connection *gc = data;
+ struct jabber_data *jd;
+ gjconn gjc;
+
+ if (!g_slist_find(connections, gc)) {
+ close(source);
+ return;
+ }
+
+ jd = gc->proto_data;
+ gjc = jd->gjc;
+
+ if (gjc->fd != source)
+ gjc->fd = source;
+
+ if (source == -1) {
+ STATE_EVT(JCONN_STATE_OFF)
+ return;
+ }
+
+ gjc->state = JCONN_STATE_CONNECTED;
+ STATE_EVT(JCONN_STATE_CONNECTED)
+
+ /* start stream */
+ x = jutil_header(NS_CLIENT, gjc->user->server);
+ t = xmlnode2str(x);
+ /* this is ugly, we can create the string here instead of jutil_header */
+ /* what do you think about it? -madcat */
+ t2 = strstr(t, "/>");
+ *t2++ = '>';
+ *t2 = '\0';
+ gjab_send_raw(gjc, "<?xml version='1.0'?>");
+ gjab_send_raw(gjc, t);
+ xmlnode_free(x);
+
+ gjc->state = JCONN_STATE_ON;
+ STATE_EVT(JCONN_STATE_ON);
+
+ gc = GJ_GC(gjc);
+ gc->inpa = gaim_input_add(gjc->fd, GAIM_INPUT_READ, jabber_callback, gc);
+}
+
+static void gjab_start(gjconn gjc)
+{
+ struct aim_user *user;
+ int port;
+
+ if (!gjc || gjc->state != JCONN_STATE_OFF)
+ return;
+
+ 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)
+ return;
+ }
+}
+
+/*
+ * 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));
+
+ while (bcs) {
+ b = bcs->data;
+ if (!strcasecmp(normalize(b->name), chat))
+ break;
+ b = NULL;
+ bcs = bcs->next;
+ }
+
+ g_free(chat);
+ return b;
+}
+
+/*
+ * Find chat by "chat id"
+ *
+ * Returns: 0 on success and jabber_chat pointer set
+ * or -EINVAL on error and jabber_chat pointer is
+ * undefined.
+ *
+ * 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;
+
+ *jc = NULL;
+
+ while(bcs != NULL) {
+ b = bcs->data;
+ if (id == b->id)
+ break;
+ bcs = bcs->next;
+ }
+
+ if (bcs != NULL) {
+ bcs = jd->chats;
+ while (bcs != NULL) {
+ *jc = bcs->data;
+ if ((*jc)->state == JCS_ACTIVE && (*jc)->b == b)
+ break;
+ bcs = bcs->next;
+ }
+ }
+
+ return(bcs == NULL? -EINVAL : 0);
+}
+
+/*
+ * Find any chat
+ */
+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;
+
+ while (jcs) {
+ jc = jcs->data;
+ if (!jid_cmpx(chat, jc->gjid, JID_USER | JID_SERVER))
+ break;
+ jc = NULL;
+ jcs = jcs->next;
+ }
+
+ return jc;
+}
+
+
+/*
+ * Find existing/active Jabber chat
+ */
+static struct jabber_chat *find_existing_chat(struct gaim_connection *gc, jid chat)
+{
+ GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
+ struct jabber_chat *jc = NULL;
+
+ while (jcs) {
+ jc = jcs->data;
+ if (jc->state == JCS_ACTIVE && !jid_cmpx(chat, jc->gjid, JID_USER | JID_SERVER))
+ break;
+ jc = NULL;
+ jcs = jcs->next;
+ }
+
+ return jc;
+}
+
+/*
+ * Find pending chat
+ */
+static struct jabber_chat *find_pending_chat(struct gaim_connection *gc, jid chat)
+{
+ GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
+ struct jabber_chat *jc = NULL;
+
+ while (jcs) {
+ jc = jcs->data;
+ if (jc->state == JCS_PENDING && !jid_cmpx(chat, jc->gjid, JID_USER | JID_SERVER))
+ break;
+ jc = NULL;
+ jcs = jcs->next;
+ }
+
+ return jc;
+}
+
+static gboolean find_chat_buddy(struct conversation *b, char *name)
+{
+ GList *m = b->in_room;
+
+ while (m) {
+ if (!strcmp(m->data, name))
+ return TRUE;
+ m = m->next;
+ }
+
+ return FALSE;
+}
+
+/*
+ * Remove a buddy from the (gaim) buddylist (if he's on it)
+ */
+static void jabber_remove_gaim_buddy(struct gaim_connection *gc, char *buddyname)
+{
+ struct buddy *b;
+
+ if ((b = find_buddy(gc, buddyname)) != NULL) {
+ struct group *group;
+
+ group = find_group_by_buddy(gc, buddyname);
+ debug_printf("removing buddy [1]: %s, from group: %s\n", buddyname, group->name);
+ remove_buddy(gc, group, b);
+ do_export(gc);
+ }
+}
+
+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!"));
+ }
+ else
+ {
+ xmlnode x, y, z;
+ char *id;
+
+ 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);
+
+ id = gjab_getid(gjc);
+ xmlnode_put_attrib(x, "id", id);
+
+ free(gjc->pass);
+ gjc->pass = strdup(new);
+
+ g_hash_table_insert(gjc->queries, g_strdup(id), g_strdup("change_password"));
+
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+ }
+}
+
+static struct jabber_buddy_data* jabber_find_buddy(struct gaim_connection *gc, char *buddy)
+{
+ struct jabber_data *jd = gc->proto_data;
+ gpointer val;
+ char *realwho;
+
+ if((realwho = get_realwho(jd->gjc, buddy, FALSE, NULL)) == NULL)
+ return NULL;
+
+ val = g_hash_table_lookup(jd->buddies, realwho);
+ if(val) {
+ g_free(realwho);
+ return (struct jabber_buddy_data *)val;
+
+ } else {
+ struct jabber_buddy_data *jbd = g_new0(struct jabber_buddy_data, 1);
+ jbd->error_msg = NULL;
+ jbd->resources = NULL;
+ g_hash_table_insert(jd->buddies, g_strdup(realwho), jbd);
+ g_free(realwho);
+ return 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)
+{
+ GSList *resources;
+ struct jabber_buddy_data *jbd = jabber_find_buddy(gc, who);
+ jab_res_info jri = NULL;
+ char *res = strstr(who, "/");
+
+ if(res)
+ res++;
+
+ if(jbd)
+ {
+ resources = jbd->resources;
+ while(resources)
+ {
+ if(!jri && !res) {
+ 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;
+ } else {
+ if(!strcasecmp(((jab_res_info) resources->data)->name, res)) {
+ jri = (jab_res_info) resources->data;
+ break;
+ }
+ }
+ resources = resources->next;
+ }
+ }
+
+ return jri;
+}
+
+/*
+ * if the resource doesn't exist, create it. otherwise, just update the priority
+ */
+static void jabber_track_resource(struct gaim_connection *gc,
+ char *buddy,
+ char *res,
+ int priority,
+ int state)
+{
+ struct jabber_buddy_data *jbd = jabber_find_buddy(gc, buddy);
+
+ if(jbd && res) {
+ char *who = g_strdup_printf("%s/%s", buddy, res);
+ jab_res_info jri = jabber_find_resource(gc, who);
+ g_free(who);
+ if(!jri) {
+ jri = g_new0(struct jabber_resource_info, 1);
+ jri->name = g_strdup(res);
+ jri->away_msg = NULL;
+ jbd->resources = g_slist_append(jbd->resources, jri);
+ }
+ jri->priority = priority;
+ jri->state = state;
+ }
+}
+
+/*
+ * 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);
+ if(jbd && res) {
+ char *who = g_strdup_printf("%s/%s", buddy, res);
+ jab_res_info jri = jabber_find_resource(gc, who);
+ g_free(who);
+ if(jri) {
+ g_free(jri->name);
+ if(jri->away_msg)
+ g_free(jri->away_msg);
+ jbd->resources = g_slist_remove(jbd->resources, jri);
+ g_free(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)
+ return _("Unknown");
+
+ return jri->away_msg;
+}
+
+static void jabber_track_away(gjconn gjc, jpacket p, char *type)
+{
+ char *show;
+ char *vshow = NULL;
+ char *status = NULL;
+ char *msg = NULL;
+ jab_res_info jri = NULL;
+
+ if(!p || !p->from || !p->from->resource || !p->from->user)
+ return;
+
+ jri = jabber_find_resource(GJ_GC(gjc), jid_full(p->from));
+
+ if(!jri)
+ return;
+
+ if (type && (strcasecmp(type, "unavailable") == 0)) {
+ vshow = _("Unavailable");
+ } else {
+ if((show = xmlnode_get_tag_data(p->x, "show")) != NULL) {
+ if (!strcasecmp(show, "away")) {
+ vshow = _("Away");
+ } else if (!strcasecmp(show, "chat")) {
+ vshow = _("Online");
+ } 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));
+ } else {
+ msg = g_strdup(_("Online"));
+ }
+
+ g_free(status);
+
+ if(jri->away_msg)
+ g_free(jri->away_msg);
+
+ jri->away_msg = msg;
+}
+
+static void jabber_convo_closed(struct gaim_connection *gc, char *name)
+{
+ jab_res_info jri = jabber_find_resource(gc, name);
+
+ if(jri) {
+ if(jri->thread_id)
+ g_free(jri->thread_id);
+
+ jri->thread_id = NULL;
+ }
+}
+
+static void jabber_track_convo_thread(gjconn gjc, char *name, char *thread_id)
+{
+ jab_res_info jri = jabber_find_resource(GJ_GC(gjc), name);
+
+ if(jri) {
+ if(jri->thread_id)
+ g_free(jri->thread_id);
+
+ jri->thread_id = g_strdup(thread_id);
+ }
+}
+
+static char *jabber_get_convo_thread(gjconn gjc, char *name)
+{
+ char *ct = NULL;
+ jab_res_info jri = jabber_find_resource(GJ_GC(gjc), name);
+
+ if(jri) {
+ if(jri->thread_id)
+ ct = g_strdup(jri->thread_id);
+ }
+
+ return ct;
+}
+
+
+static time_t iso8601_to_time(char *timestamp)
+{
+ struct tm t;
+ time_t retval = 0;
+
+ if(sscanf(timestamp, "%04d%02d%02dT%02d:%02d:%02d",
+ &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec))
+ {
+ t.tm_year -= 1900;
+ t.tm_mon -= 1;
+ t.tm_isdst = 0;
+ retval = mktime(&t);
+# ifdef HAVE_TM_GMTOFF
+ retval += t.tm_gmtoff;
+# else
+# ifdef HAVE_TIMEZONE
+ tzset(); /* making sure */
+ retval -= timezone;
+# endif
+# endif
+ }
+
+ return retval;
+}
+
+static void jabber_handlemessage(gjconn gjc, jpacket p)
+{
+ xmlnode y, subj;
+ 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;
+ char m[BUF_LONG * 2];
+
+ 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);
+
+ while(y) {
+ 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"))
+ typing = TRUE;
+ } 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);
+ } else
+ */
+ if ((y = xmlnode_get_tag(p->x, "body"))) {
+ msg = xmlnode_get_data(y);
+ }
+
+ msg = utf8_to_str(msg);
+
+ if (!from)
+ return;
+
+ if (conference_room) {
+ GList *m = NULL;
+ char **data;
+
+ 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));
+ g_strfreev(data);
+
+ 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,
+ time_sent);
+ else {
+ int flags = 0;
+ jab_res_info jri = jabber_find_resource(GJ_GC(gjc), jid_full(p->from));
+ if(jri && typing)
+ 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),
+ xmlnode_get_data(y));
+ if (find_conversation(jid_full(p->from)))
+ serv_got_im(GJ_GC(gjc), jid_full(p->from), m, flags,
+ time_sent, -1);
+ else {
+ if(p->from->user) {
+ from = g_strdup_printf("%s@%s", p->from->user,
+ p->from->server);
+ } else {
+ /* server message? */
+ from = g_strdup(p->from->server);
+ }
+ serv_got_im(GJ_GC(gjc), from, m, flags, time_sent, -1);
+ g_free(from);
+ }
+ }
+ } else {
+ /* a non-message message! */
+ from = g_strdup_printf("%s@%s", p->from->user, p->from->server);
+ if(typing)
+ serv_got_typing(GJ_GC(gjc), from, 0);
+ else
+ serv_got_typing_stopped(GJ_GC(gjc), from);
+ g_free(from);
+ }
+
+ if (msg)
+ g_free(msg);
+
+ } else if (!strcasecmp(type, "error")) {
+ if ((y = xmlnode_get_tag(p->x, "error"))) {
+ type = xmlnode_get_attrib(y, "code");
+ msg = xmlnode_get_data(y);
+ }
+
+ if (msg) {
+ from = g_strdup_printf("Error %s", type ? type : "");
+ do_error_dialog(msg, from);
+ g_free(from);
+ }
+ } else if (!strcasecmp(type, "groupchat")) {
+ struct jabber_chat *jc;
+ static int i = 0;
+
+ /*
+ if ((y = xmlnode_get_tag(p->x, "html"))) {
+ msg = xmlnode_get_data(y);
+ } else
+ */
+ if ((y = xmlnode_get_tag(p->x, "body"))) {
+ msg = xmlnode_get_data(y);
+ }
+
+ msg = utf8_to_str(msg);
+
+ if ((subj = xmlnode_get_tag(p->x, "subject"))) {
+ topic = xmlnode_get_data(subj);
+ }
+ topic = utf8_to_str(topic);
+
+ jc = find_existing_chat(GJ_GC(gjc), p->from);
+ if (!jc) {
+ /* we're not in this chat. are we supposed to be? */
+ if ((jc = find_pending_chat(GJ_GC(gjc), p->from)) != NULL) {
+ /* yes, we're supposed to be. so now we are. */
+ jc->b = serv_got_joined_chat(GJ_GC(gjc), i++, p->from->user);
+ jc->id = jc->b->id;
+ jc->state = JCS_ACTIVE;
+ } else {
+ /* no, we're not supposed to be. */
+ g_free(msg);
+ return;
+ }
+ }
+ if (p->from->resource) {
+ if (!y) {
+ if (!find_chat_buddy(jc->b, p->from->resource)) {
+ add_chat_buddy(jc->b, p->from->resource);
+ } else if ((y = xmlnode_get_tag(p->x, "status"))) {
+ jabber_track_away(gjc, p, NULL);
+ }
+ } else if (jc->b && msg) {
+ char buf[8192];
+
+ if (topic) {
+ char tbuf[8192];
+ 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,
+ time_sent);
+ }
+ } else { /* message from the server */
+ if(jc->b && topic) {
+ char tbuf[8192];
+ g_snprintf(tbuf, sizeof(tbuf), "%s", topic);
+ chat_set_topic(jc->b, "", tbuf);
+ }
+ }
+
+ g_free(msg);
+ g_free(topic);
+
+ } else {
+ debug_printf("unhandled message %s\n", type);
+ }
+}
+
+static void jabber_handlepresence(gjconn gjc, jpacket p)
+{
+ char *to, *from, *type;
+ struct buddy *b = NULL;
+ gaim_jid gjid;
+ char *buddy;
+ xmlnode y;
+ char *show;
+ int state = 0;
+ struct conversation *cnv = NULL;
+ struct jabber_chat *jc = NULL;
+ int priority = 0;
+ 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)
+ return;
+
+ if (gjid->user == NULL) {
+ /* FIXME: transport */
+ g_free(buddy);
+ gaim_jid_free(gjid);
+ return;
+ }
+
+ jbd = jabber_find_buddy(GJ_GC(gjc), buddy);
+
+ if(jbd->error_msg) {
+ g_free(jbd->error_msg);
+ jbd->error_msg = NULL;
+ }
+
+ if(type && !strcasecmp(type, "error")) {
+ state = UC_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));
+ } else {
+ jbd->error_msg = g_strdup(_("Unknown Error in presence"));
+ }
+ } else {
+ if ((y = xmlnode_get_tag(p->x, "show"))) {
+ show = xmlnode_get_data(y);
+ if (!show) {
+ state = 0;
+ } else if (!strcasecmp(show, "away")) {
+ state = UC_AWAY;
+ } else if (!strcasecmp(show, "chat")) {
+ state = UC_CHAT;
+ } else if (!strcasecmp(show, "xa")) {
+ state = UC_XA;
+ } else if (!strcasecmp(show, "dnd")) {
+ state = UC_DND;
+ }
+ } else {
+ state = 0;
+ }
+ }
+
+ 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) {
+ static int i = 0x70;
+ if ((jc = find_pending_chat(GJ_GC(gjc), gjid)) != NULL) {
+ jc->b = cnv = serv_got_joined_chat(GJ_GC(gjc), i++, gjid->user);
+ jc->id = jc->b->id;
+ jc->state = JCS_ACTIVE;
+ } else if ((b = find_buddy(GJ_GC(gjc), buddy)) == NULL) {
+ g_free(buddy);
+ gaim_jid_free(gjid);
+ return;
+ }
+ }
+
+ if (type && (strcasecmp(type, "unavailable") == 0))
+ jabber_remove_resource(GJ_GC(gjc), buddy, gjid->resource);
+ else {
+ 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);
+ }
+
+
+ if (!cnv) {
+ /* this is where we handle presence information for "regular" buddies */
+ jab_res_info jri = jabber_find_resource(GJ_GC(gjc), buddy);
+ if(jri) {
+ serv_got_update(GJ_GC(gjc), buddy, 1, 0, b->signon, b->idle, jri->state, 0);
+ } else
+ serv_got_update(GJ_GC(gjc), buddy, 0, 0, 0, 0, 0, 0);
+
+ } else {
+ if (gjid->resource) {
+ if (type && (!strcasecmp(type, "unavailable"))) {
+ struct jabber_data *jd;
+ if (!jc && !(jc = find_existing_chat(GJ_GC(gjc), gjid))) {
+ g_free(buddy);
+ gaim_jid_free(gjid);
+ return;
+ }
+ 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);
+ g_free(buddy);
+ gaim_jid_free(gjid);
+ return;
+ }
+
+ jc->state = JCS_CLOSED;
+ serv_got_chat_left(GJ_GC(gjc), jc->id);
+ /*
+ * TBD: put back some day?
+ jd->chats = g_slist_remove(jd->chats, jc);
+ g_free(jc);
+ */
+ } else {
+ if ((!jc && !(jc = find_existing_chat(GJ_GC(gjc), gjid))) || !jc->b) {
+ g_free(buddy);
+ gaim_jid_free(gjid);
+ return;
+ }
+ if (!find_chat_buddy(jc->b, gjid->resource)) {
+ add_chat_buddy(jc->b, gjid->resource);
+ }
+ }
+ }
+ }
+
+ g_free(buddy);
+ gaim_jid_free(gjid);
+
+ return;
+}
+
+/*
+ * Used only by Jabber accept/deny add stuff just below
+ */
+struct jabber_add_permit {
+ gjconn gjc;
+ gchar *user;
+};
+
+/*
+ * Common part for Jabber accept/deny adds
+ *
+ * "type" says whether we'll permit/deny the subscribe request
+ */
+static void jabber_accept_deny_add(struct jabber_add_permit *jap, const char *type)
+{
+ xmlnode g = xmlnode_new_tag("presence");
+
+ xmlnode_put_attrib(g, "to", jap->user);
+ xmlnode_put_attrib(g, "type", type);
+ gjab_send(jap->gjc, g);
+
+ xmlnode_free(g);
+}
+
+/*
+ * Callback from "accept" in do_ask_dialog() invoked by jabber_handles10n()
+ */
+static void jabber_accept_add(gpointer w, struct jabber_add_permit *jap)
+{
+ jabber_accept_deny_add(jap, "subscribed");
+ /*
+ * If we don't already have the buddy on *our* buddylist,
+ * ask if we want him or her added.
+ */
+ if(find_buddy(GJ_GC(jap->gjc), jap->user) == NULL) {
+ show_got_added(GJ_GC(jap->gjc), NULL, jap->user, NULL, NULL);
+ }
+ g_free(jap->user);
+ g_free(jap);
+}
+
+/*
+ * Callback from "deny/cancel" in do_ask_dialog() invoked by jabber_handles10n()
+ */
+static void jabber_deny_add(gpointer w, struct jabber_add_permit *jap)
+{
+ jabber_accept_deny_add(jap, "unsubscribed");
+ g_free(jap->user);
+ g_free(jap);
+}
+
+/*
+ * Handle subscription requests
+ */
+static void jabber_handles10n(gjconn gjc, jpacket p)
+{
+ xmlnode g;
+ char *Jid = xmlnode_get_attrib(p->x, "from");
+ char *type = xmlnode_get_attrib(p->x, "type");
+
+ g = xmlnode_new_tag("presence");
+ xmlnode_put_attrib(g, "to", Jid);
+
+ if (!strcmp(type, "subscribe")) {
+ /*
+ * A "subscribe to us" request was received - put up the approval dialog
+ */
+ struct jabber_add_permit *jap = g_new0(struct jabber_add_permit, 1);
+ gchar *msg = g_strdup_printf(_("The user %s wants to add you to their buddy list."),
+ Jid);
+
+ jap->gjc = gjc;
+ jap->user = g_strdup(Jid);
+ do_ask_dialog(msg, jap, jabber_accept_add, jabber_deny_add);
+
+ g_free(msg);
+ xmlnode_free(g); /* Never needed it here anyway */
+ return;
+
+ } else if (!strcmp(type, "unsubscribe")) {
+ /*
+ * An "unsubscribe to us" was received - simply "approve" it
+ */
+ xmlnode_put_attrib(g, "type", "unsubscribed");
+ } else {
+ /*
+ * Did we attempt to subscribe to somebody and they do not exist?
+ */
+ if (!strcmp(type, "unsubscribed")) {
+ xmlnode y;
+ char *status;
+ if((y = xmlnode_get_tag(p->x, "status")) && (status = xmlnode_get_data(y)) &&
+ !strcmp(status, "Not Found")) {
+ char *msg = g_strdup_printf("%s: \"%s\"", _("No such user"),
+ xmlnode_get_attrib(p->x, "from"));
+ do_error_dialog(msg, _("Jabber Error"));
+ g_free(msg);
+ }
+ }
+
+ xmlnode_free(g);
+ return;
+ }
+
+ gjab_send(gjc, g);
+ xmlnode_free(g);
+}
+
+/*
+ * Pending subscription to a buddy?
+ */
+#define BUD_SUB_TO_PEND(sub, ask) ((!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)
+{
+ xmlnode g;
+ char *who, *name, *sub, *ask;
+ gaim_jid gjid;
+ 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)
+ return;
+
+ /* 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(!gjid->user) {
+ g_free(buddyname);
+ gaim_jid_free(gjid);
+ return;
+ }
+ gaim_jid_free(gjid);
+
+ if((g = xmlnode_get_tag(x, "group")) != NULL) {
+ groupname = xmlnode_get_data(g);
+ }
+
+ /*
+ * Add or remove a buddy? Change buddy's alias or group?
+ */
+ if(name)
+ 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);
+ do_export(GJ_GC(gjc));
+ } else {
+ struct group *c_grp = find_group_by_buddy(GJ_GC(gjc), buddyname);
+
+ /*
+ * If the buddy's in a new group or his/her alias is changed...
+ */
+ if(groupname && c_grp && strcmp(c_grp->name, groupname)) {
+ int present = b->present; /* save presence state */
+ int uc = b->uc; /* and away state (?) */
+ int idle = b->idle;
+ int signon = b->signon;
+
+ /*
+ * seems rude, but it seems to be the only way...
+ */
+ remove_buddy(GJ_GC(gjc), c_grp, b);
+ b = add_buddy(GJ_GC(gjc), groupname, buddyname,
+ name ? name : buddyname);
+ do_export(GJ_GC(gjc));
+ if(present) {
+ serv_got_update(GJ_GC(gjc), buddyname, 1, 0, signon, idle,
+ uc, 0);
+ }
+ } else if(name != NULL && strcmp(b->show, name)) {
+ strncpy(b->show, name, BUDDY_ALIAS_MAXLEN);
+ b->show[BUDDY_ALIAS_MAXLEN - 1] = '\0'; /* cheap safety feature */
+ 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);
+ }
+
+ if(name)
+ g_free(name);
+
+ g_free(buddyname);
+
+}
+
+static void jabber_handleroster(gjconn gjc, xmlnode querynode)
+{
+ xmlnode x;
+
+ x = xmlnode_get_firstchild(querynode);
+ while (x) {
+ jabber_handlebuddy(gjc, x);
+ x = xmlnode_get_nextsibling(x);
+ }
+
+ x = jutil_presnew(0, NULL, "Online");
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+}
+
+static void jabber_handleauthresp(gjconn gjc, jpacket p)
+{
+ if (jpacket_subtype(p) == JPACKET__RESULT) {
+ if (xmlnode_has_children(p->x)) {
+ xmlnode query = xmlnode_get_tag(p->x, "query");
+ set_login_progress(GJ_GC(gjc), 4, _("Authenticating"));
+ if (!xmlnode_get_tag(query, "digest")) {
+ g_free(gjc->sid);
+ gjc->sid = NULL;
+ }
+ gjab_auth(gjc);
+ } else {
+ 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;
+
+ gjab_reqroster(gjc);
+ }
+ } else {
+ xmlnode xerr;
+ char *errmsg = NULL;
+ int errcode = 0;
+ struct jabber_data *jd = GJ_GC(gjc)->proto_data;
+
+ debug_printf("auth failed\n");
+ xerr = xmlnode_get_tag(p->x, "error");
+ if (xerr) {
+ char msg[BUF_LONG];
+ errmsg = xmlnode_get_data(xerr);
+ if (xmlnode_get_attrib(xerr, "code")) {
+ errcode = atoi(xmlnode_get_attrib(xerr, "code"));
+ g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg);
+ } else
+ g_snprintf(msg, sizeof(msg), "%s", errmsg);
+ hide_login_progress(GJ_GC(gjc), msg);
+ } else {
+ hide_login_progress(GJ_GC(gjc), _("Unknown login error"));
+ }
+
+ jd->die = TRUE;
+ }
+}
+
+static void jabber_handleversion(gjconn gjc, xmlnode iqnode) {
+ xmlnode querynode, x;
+ char *id, *from;
+ char os[1024];
+ struct utsname osinfo;
+
+ uname(&osinfo);
+ 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);
+
+ gjab_send(gjc, x);
+
+ xmlnode_free(x);
+}
+
+static void jabber_handletime(gjconn gjc, xmlnode iqnode) {
+ xmlnode querynode, x;
+ char *id, *from;
+ time_t now_t;
+ struct tm *now;
+ char buf[1024];
+
+ time(&now_t);
+ now = localtime(&now_t);
+
+ id = xmlnode_get_attrib(iqnode, "id");
+ from = xmlnode_get_attrib(iqnode, "from");
+
+ x = jutil_iqnew(JPACKET__RESULT, NS_TIME);
+
+ xmlnode_put_attrib(x, "to", from);
+ xmlnode_put_attrib(x, "id", id);
+ querynode = xmlnode_get_tag(x, "query");
+
+ strftime(buf, 1024, "%Y%m%dT%T", now);
+ xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "utc"), buf, -1);
+ strftime(buf, 1024, "%Z", now);
+ xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "tz"), buf, -1);
+ strftime(buf, 1024, "%d %b %Y %T", now);
+ xmlnode_insert_cdata(xmlnode_insert_tag(querynode, "display"), buf, -1);
+
+ gjab_send(gjc, x);
+
+ xmlnode_free(x);
+}
+
+static void jabber_handlelast(gjconn gjc, xmlnode iqnode) {
+ xmlnode x, querytag;
+ char *id, *from;
+ struct jabber_data *jd = GJ_GC(gjc)->proto_data;
+ char idle_time[32];
+
+ id = xmlnode_get_attrib(iqnode, "id");
+ from = xmlnode_get_attrib(iqnode, "from");
+
+ x = jutil_iqnew(JPACKET__RESULT, "jabber:iq:last");
+
+ xmlnode_put_attrib(x, "to", from);
+ xmlnode_put_attrib(x, "id", id);
+ querytag = xmlnode_get_tag(x, "query");
+ g_snprintf(idle_time, sizeof idle_time, "%ld", jd->idle ? time(NULL) - jd->idle : 0);
+ xmlnode_put_attrib(querytag, "seconds", idle_time);
+
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+}
+
+/*
+ * delete == TRUE: delete found entry
+ *
+ * returns pointer to (local) copy of value if found, NULL otherwise
+ *
+ * Note: non-reentrant! Local static storage re-used on subsequent calls.
+ * If you're going to need to keep the returned value, make a copy!
+ */
+static gchar *jabber_track_queries(GHashTable *queries, gchar *key, gboolean delete)
+{
+ gpointer my_key, my_val;
+ static gchar *ret_val = NULL;
+
+ if(ret_val != NULL) {
+ g_free(ret_val);
+ ret_val = NULL;
+ }
+
+ /* self-protection */
+ if(queries != NULL && key != NULL) {
+ if(g_hash_table_lookup_extended(queries, key, &my_key, &my_val)) {
+ ret_val = g_strdup((gchar *) my_val);
+ if(delete) {
+ g_hash_table_remove(queries, key);
+ g_free(my_key);
+ g_free(my_val);
+ }
+ }
+ }
+
+ return(ret_val);
+}
+
+static void jabber_handlepacket(gjconn gjc, jpacket p)
+{
+ char *id;
+ switch (p->type) {
+ case JPACKET_MESSAGE:
+ jabber_handlemessage(gjc, p);
+ break;
+ case JPACKET_PRESENCE:
+ jabber_handlepresence(gjc, p);
+ break;
+ case JPACKET_IQ:
+ 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);
+ break;
+ }
+
+ if (jpacket_subtype(p) == JPACKET__SET) {
+ xmlnode querynode;
+ querynode = xmlnode_get_tag(p->x, "query");
+ if (NSCHECK(querynode, "jabber:iq:roster")) {
+ jabber_handlebuddy(gjc, xmlnode_get_firstchild(querynode));
+ }
+ } else if (jpacket_subtype(p) == JPACKET__GET) {
+ xmlnode querynode;
+ querynode = xmlnode_get_tag(p->x, "query");
+ if (NSCHECK(querynode, NS_VERSION)) {
+ jabber_handleversion(gjc, p->x);
+ } else if (NSCHECK(querynode, NS_TIME)) {
+ jabber_handletime(gjc, p->x);
+ } else if (NSCHECK(querynode, "jabber:iq:last")) {
+ jabber_handlelast(gjc, p->x);
+ }
+ } else if (jpacket_subtype(p) == JPACKET__RESULT) {
+ xmlnode querynode, vcard;
+ char *xmlns, *from;
+
+ /*
+ * TBD: ISTM maybe this part could use a serious re-work?
+ */
+ from = xmlnode_get_attrib(p->x, "from");
+ querynode = xmlnode_get_tag(p->x, "query");
+ vcard = xmlnode_get_tag(p->x, "vCard");
+ if (!vcard)
+ vcard = xmlnode_get_tag(p->x, "VCARD");
+
+ if (NSCHECK(querynode, NS_ROSTER)) {
+ jabber_handleroster(gjc, querynode);
+ } else if (NSCHECK(querynode, NS_VCARD)) {
+ jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */
+ jabber_handlevcard(gjc, querynode, from);
+ } else if (vcard) {
+ jabber_track_queries(gjc->queries, id, TRUE); /* delete query track */
+ jabber_handlevcard(gjc, vcard, from);
+ } else if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL) {
+ debug_printf("jabber:iq:query: %s\n", xmlns);
+ } else {
+ char *val;
+
+ 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")) {
+ char buf[BUF_LONG];
+ sprintf(buf, _("Password successfully changed."));
+
+ do_error_dialog(buf, _("Password Change"));
+ }
+ }
+ }
+
+ } else if (jpacket_subtype(p) == JPACKET__ERROR) {
+ xmlnode xerr;
+ char *from, *errmsg = NULL;
+ int errcode = 0;
+
+ from = xmlnode_get_attrib(p->x, "from");
+ xerr = xmlnode_get_tag(p->x, "error");
+ if (xerr) {
+ errmsg = xmlnode_get_data(xerr);
+ if (xmlnode_get_attrib(xerr, "code"))
+ errcode = atoi(xmlnode_get_attrib(xerr, "code"));
+ }
+
+ from = g_strdup_printf("Error %d (%s)", errcode, from);
+ do_error_dialog(errmsg, from);
+ g_free(from);
+
+ }
+
+ break;
+ case JPACKET_S10N:
+ jabber_handles10n(gjc, p);
+ break;
+ default:
+ debug_printf("jabber: packet type %d (%s)\n", p->type, xmlnode2str(p->x));
+ }
+
+ xmlnode_free(p->x);
+
+ return;
+}
+
+static void jabber_handlestate(gjconn gjc, int state)
+{
+ switch (state) {
+ case JCONN_STATE_OFF:
+ if(gjc->was_connected) {
+ hide_login_progress_error(GJ_GC(gjc), _("Connection lost"));
+ } else {
+ hide_login_progress(GJ_GC(gjc), _("Unable to connect"));
+ }
+ signoff(GJ_GC(gjc));
+ break;
+ case JCONN_STATE_CONNECTED:
+ gjc->was_connected = 1;
+ set_login_progress(GJ_GC(gjc), 2, _("Connected"));
+ break;
+ case JCONN_STATE_ON:
+ set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method"));
+ gjab_reqauth(gjc);
+ break;
+ default:
+ debug_printf("state change: %d\n", state);
+ }
+ return;
+}
+
+static void jabber_login(struct aim_user *user)
+{
+ struct gaim_connection *gc = new_gaim_conn(user);
+ struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1);
+ char *loginname = create_valid_jid(user->username, DEFAULT_SERVER, "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))) {
+ g_free(loginname);
+ debug_printf("jabber: unable to connect (jab_new failed)\n");
+ hide_login_progress(gc, _("Unable to connect"));
+ signoff(gc);
+ return;
+ }
+
+ g_free(loginname);
+ gjab_state_handler(jd->gjc, jabber_handlestate);
+ gjab_packet_handler(jd->gjc, jabber_handlepacket);
+ jd->gjc->queries = g_hash_table_new(g_str_hash, g_str_equal);
+ gjab_start(jd->gjc);
+}
+
+static gboolean jabber_destroy_hash(gpointer key, gpointer val, gpointer data) {
+ g_free(key);
+ g_free(val);
+ return TRUE;
+}
+
+static gboolean jabber_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);
+
+ }
+ if(jbd->error_msg)
+ g_free(jbd->error_msg);
+ g_free(key);
+ g_free(jbd);
+ return TRUE;
+}
+
+
+static gboolean jabber_free(gpointer data)
+{
+ struct jabber_data *jd = data;
+
+ if(jd->gjc != NULL) {
+ gjab_delete(jd->gjc);
+ g_free(jd->gjc->sid);
+ jd->gjc = NULL;
+ }
+ g_free(jd);
+
+ return FALSE;
+}
+
+static void jabber_close(struct gaim_connection *gc)
+{
+ struct jabber_data *jd = gc->proto_data;
+
+ if(jd) {
+ GSList *jcs = jd->chats;
+
+ /* Free-up the jabber_chat struct allocs and the list */
+ while (jcs) {
+ gaim_jid_free(((struct jabber_chat *)jcs->data)->gjid);
+ g_free(jcs->data);
+ jcs = jcs->next;
+ }
+ 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);
+ jd->buddies = NULL;
+ }
+
+ /* Free-up the pending queries memories and the list */
+ if(jd->gjc != NULL && jd->gjc->queries != NULL) {
+ g_hash_table_foreach_remove(jd->gjc->queries, jabber_destroy_hash, NULL);
+ g_hash_table_destroy(jd->gjc->queries);
+ jd->gjc->queries = NULL;
+ }
+ }
+ if (gc->inpa)
+ gaim_input_remove(gc->inpa);
+
+ if(jd) {
+ g_timeout_add(50, jabber_free, jd);
+ if(jd->gjc != NULL)
+ xmlnode_free(jd->gjc->current);
+ }
+ gc->proto_data = NULL;
+}
+
+static int jabber_send_typing(struct gaim_connection *gc, char *who, int typing)
+{
+ xmlnode x, y;
+ char *realwho;
+ gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
+ jab_res_info jri = jabber_find_resource(gc, who);
+
+ if(!jri || !jri->has_composing)
+ return 0;
+
+ if((realwho = get_realwho(gjc, who, FALSE, NULL)) == NULL)
+ return 0;
+
+ 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");
+
+ if(typing)
+ xmlnode_insert_tag(y, "composing");
+
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+ xmlnode_free(x);
+ g_free(realwho);
+ return JABBER_TYPING_NOTIFY_INT;
+}
+
+static int jabber_send_im(struct gaim_connection *gc, char *who, char *message, int len, int flags)
+{
+ xmlnode x, y;
+ char *realwho;
+ char *thread_id = NULL;
+ gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
+
+ if (!who || !message)
+ return 0;
+
+ if((realwho = get_realwho(gjc, who, FALSE, NULL)) == NULL)
+ return 0;
+
+ x = xmlnode_new_tag("message");
+ xmlnode_put_attrib(x, "to", realwho);
+
+ thread_id = jabber_get_convo_thread(gjc, realwho);
+ if(thread_id)
+ {
+ y = xmlnode_insert_tag(x, "thread");
+ xmlnode_insert_cdata(y, thread_id, -1);
+ g_free(thread_id);
+ }
+
+ g_free(realwho);
+
+ 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);
+ g_free(utf8);
+ }
+
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+ xmlnode_free(x);
+ return 1;
+}
+
+/*
+ * Add/update buddy's roster entry on server
+ */
+static void jabber_roster_update(struct gaim_connection *gc, char *name)
+{
+ xmlnode x, y;
+ char *realwho;
+ gjconn gjc;
+ struct buddy *buddy = NULL;
+ struct group *buddy_group = NULL;
+
+ if(gc && gc->proto_data && ((struct jabber_data *)gc->proto_data)->gjc && name) {
+ gaim_jid gjid;
+ gjc = ((struct jabber_data *)gc->proto_data)->gjc;
+
+ if((realwho = get_realwho(gjc, name, FALSE, &gjid)) == NULL)
+ return;
+
+ /* FIXME: transport */
+ if(gjid->user == NULL) {
+ g_free(realwho);
+ gaim_jid_free(gjid);
+ return;
+ }
+ gaim_jid_free(gjid);
+
+ 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);
+ g_free(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) {
+ xmlnode z;
+ z = xmlnode_insert_tag(y, "group");
+ xmlnode_insert_cdata(z, buddy_group->name, -1);
+ }
+
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+
+ xmlnode_free(x);
+ g_free(realwho);
+ }
+}
+
+/*
+ * Change buddy's group on server roster
+ */
+static void jabber_group_change(struct gaim_connection *gc, char *name, char *old_group, char *new_group)
+{
+ if(strcmp(old_group, new_group)) {
+ jabber_roster_update(gc, name);
+ }
+}
+
+static void jabber_add_buddy(struct gaim_connection *gc, char *name)
+{
+ xmlnode x;
+ char *realwho;
+ gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
+ gaim_jid gjid;
+
+ if (!((struct jabber_data *)gc->proto_data)->did_import)
+ return;
+
+ /*
+ * If there's no name or the name is ourself
+ */
+ if(!name || !strcmp(gc->username, name))
+ return;
+
+ 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"));
+ g_free(msg);
+ jabber_remove_gaim_buddy(gc, name);
+ return;
+ }
+
+ /* FIXME: transport */
+ if(gjid->user == NULL) {
+ g_free(realwho);
+ gaim_jid_free(gjid);
+ return;
+ }
+ gaim_jid_free(gjid);
+
+ x = xmlnode_new_tag("presence");
+ xmlnode_put_attrib(x, "to", realwho);
+ xmlnode_put_attrib(x, "type", "subscribe");
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+ xmlnode_free(x);
+
+ jabber_roster_update(gc, realwho);
+
+ g_free(realwho);
+}
+
+static void jabber_remove_buddy(struct gaim_connection *gc, char *name, char *group)
+{
+ xmlnode x;
+ char *realwho;
+ gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
+
+ if(!name || (realwho = get_realwho(gjc, name, FALSE, NULL)) == NULL)
+ return;
+
+ x = xmlnode_new_tag("presence");
+ xmlnode_put_attrib(x, "to", realwho);
+ xmlnode_put_attrib(x, "type", "unsubscribe");
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+ g_free(realwho);
+ xmlnode_free(x);
+}
+
+/*
+ * Remove a buddy item from the roster entirely
+ */
+static void jabber_remove_buddy_roster_item(struct gaim_connection *gc, char *name)
+{
+ xmlnode x, y;
+ char *realwho;
+ gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
+
+ if(!name || (realwho = get_realwho(gjc, name, FALSE, NULL)) == NULL)
+ return;
+
+ 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);
+ g_free(realwho);
+ xmlnode_free(x);
+}
+
+static char **jabber_list_icon(int uc)
+{
+ switch (uc) {
+ case UC_AWAY:
+ return available_away_xpm;
+ case UC_CHAT:
+ return available_chat_xpm;
+ case UC_XA:
+ return available_xa_xpm;
+ case UC_DND:
+ return available_dnd_xpm;
+ case UC_ERROR:
+ return available_error_xpm;
+ default:
+ return available_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 */
+ gchar *server;
+
+ GList *m = NULL;
+ 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."
+ */
+ if(confserv != NULL) {
+ g_free(confserv); /* dispose of the old value */
+ }
+
+ if((server = g_strdup(gjc->user->server)) == NULL) {
+ confserv = g_strdup(DEFAULT_GROUPCHAT);
+ } else {
+ gchar **splits, **index;
+ gchar *tmp;
+ int cnt = 0;
+
+
+ index = splits = g_strsplit(server, ".", -1); /* split the connected server */
+
+ while(*(index++)) /* index to the end--counting the parts */
+ ++cnt;
+
+ /*
+ * If we've more than two parts, point to the second part. Else point
+ * to the start.
+ */
+ if(cnt > 2) {
+ index -= cnt;
+ } else {
+ index = splits;
+ }
+
+ /* Put it together */
+ confserv = g_strjoin(".", "conference", (tmp = g_strjoinv(".", index)), NULL);
+
+ g_free(server); /* we don't need this stuff no more */
+ g_free(tmp);
+ g_strfreev(splits);
+ }
+
+ 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:");
+ pce->def = confserv;
+ 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);
+
+ return m;
+}
+
+static void jabber_join_chat(struct gaim_connection *gc, GList *data)
+{
+ xmlnode x;
+ char *realwho;
+ gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
+ GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
+ struct jabber_chat *jc;
+ gaim_jid gjid;
+
+ if (!data || !data->next || !data->next->next)
+ return;
+
+ 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"));
+ g_free(msg);
+ g_free(realwho);
+ return;
+ }
+
+ if((jc = find_any_chat(gc, gjid)) != NULL) {
+ switch(jc->state) {
+ case JCS_PENDING:
+ debug_printf("attempt to re-join already pending Jabber chat! (ignoring)\n");
+ g_free(realwho); /* yuck! */
+ gaim_jid_free(gjid);
+ return;
+ case JCS_ACTIVE:
+ debug_printf("attempt to re-join already active Jabber chat! (ignoring)\n");
+ g_free(realwho); /* yuck! */
+ gaim_jid_free(gjid);
+ return;
+ case JCS_CLOSED:
+ debug_printf("rejoining previously closed Jabber chat\n");
+ break;
+ default:
+ debug_printf("found Jabber chat in unknown state! (ignoring)\n");
+ g_free(realwho); /* yuck! */
+ gaim_jid_free(gjid);
+ return;
+ }
+ } else {
+ debug_printf("joining completely new Jabber chat\n");
+ jc = g_new0(struct jabber_chat, 1);
+ jc->gjid = gjid;
+ jc->gc = gc;
+ ((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);
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+ g_free(realwho);
+}
+
+static void jabber_chat_invite(struct gaim_connection *gc, int id, char *message, char *name)
+{
+ xmlnode x, y;
+ struct jabber_data *jd = gc->proto_data;
+ gjconn gjc = jd->gjc;
+ struct jabber_chat *jc = NULL;
+ char *realwho, *subject;
+
+ if(!name || (realwho = get_realwho(gjc, name, FALSE, NULL)) == NULL)
+ return;
+
+ /* find which chat we're inviting to */
+ if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0)
+ return;
+
+ x = xmlnode_new_tag("message");
+ xmlnode_put_attrib(x, "to", realwho);
+
+ g_free(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);
+ g_free(subject);
+
+ if (message && strlen(message)) {
+ char *utf8 = str_to_utf8(message);
+ y = xmlnode_insert_tag(x, "body");
+ xmlnode_insert_cdata(y, utf8, -1);
+ g_free(utf8);
+ }
+
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+ xmlnode_free(x);
+}
+
+static void jabber_chat_leave(struct gaim_connection *gc, int id)
+{
+ struct jabber_data *jd = gc->proto_data;
+ gjconn gjc = jd->gjc;
+ struct jabber_chat *jc = NULL;
+ char *chatname;
+ xmlnode x;
+
+ /* Find out which chat we're leaving */
+ if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0)
+ return;
+
+ chatname = g_strdup_printf("%s@%s", jc->gjid->user, jc->gjid->server);
+ x = jutil_presnew(0, chatname, NULL);
+ g_free(chatname);
+ xmlnode_put_attrib(x, "type", "unavailable");
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+ jc->b = NULL;
+}
+
+static int jabber_chat_send(struct gaim_connection *gc, int id, char *message)
+{
+ xmlnode x, y;
+ struct jabber_chat *jc = NULL;
+ char *chatname;
+ int retval = 0;
+
+ /* Find out which chat we're sending to */
+ if((retval = jabber_find_chat_by_convo_id(gc, id, &jc)) != 0)
+ return(retval);
+
+ 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);
+ g_free(chatname);
+ xmlnode_put_attrib(x, "type", "groupchat");
+
+ if (message && strlen(message) > strlen("/topic ") &&
+ !g_strncasecmp(message, "/topic ", strlen("/topic "))) {
+ char buf[8192];
+ 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);
+ g_free(utf8);
+ } else if (message && strlen(message)) {
+ char *utf8 = str_to_utf8(message);
+ y = xmlnode_insert_tag(x, "body");
+ xmlnode_insert_cdata(y, utf8, -1);
+ g_free(utf8);
+ }
+
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+ xmlnode_free(x);
+ return 0;
+}
+
+static void jabber_chat_whisper(struct gaim_connection *gc, int id, char *who, char *message)
+{
+ xmlnode x, y;
+ struct jabber_chat *jc = NULL;
+ char *chatname;
+
+ /* Find out which chat we're whispering to */
+ if(jabber_find_chat_by_convo_id(gc, id, &jc) != 0)
+ return;
+
+ 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);
+ g_free(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);
+ g_free(utf8);
+ }
+
+ gjab_send(((struct jabber_data *)gc->proto_data)->gjc, x);
+ xmlnode_free(x);
+}
+
+static char *jabber_normalize(const char *s)
+{
+ static char buf[BUF_LEN];
+ char *t, *u;
+ int x = 0;
+
+ g_return_val_if_fail((s != NULL), NULL);
+
+ /* Somebody called us with s == NULL once... */
+ if(s == NULL) {
+ return(NULL);
+ } else {
+ u = t = g_strdup(s);
+
+ g_strdown(t);
+
+ while (*t && (x < BUF_LEN - 1)) {
+ if (*t != ' ')
+ buf[x++] = *t;
+ t++;
+ }
+ buf[x] = '\0';
+ g_free(u);
+
+ if (!strchr(buf, '@')) {
+ strcat(buf, "@jabber.org"); /* this isn't always right, but eh */
+ } else if ((u = strchr(strchr(buf, '@'), '/')) != NULL) {
+ *u = '\0';
+ }
+
+ return buf;
+ }
+}
+
+static void jabber_get_info(struct gaim_connection *gc, char *who) {
+ xmlnode x;
+ char *id;
+ char *realwho;
+ struct jabber_data *jd = gc->proto_data;
+ gjconn gjc = jd->gjc;
+
+ if((realwho = get_realwho(gjc, who, TRUE, NULL)) == NULL)
+ return;
+
+ x = jutil_iqnew(JPACKET__GET, NS_VCARD);
+ xmlnode_put_attrib(x, "to", realwho);
+
+ g_free(realwho);
+
+ id = gjab_getid(gjc);
+ xmlnode_put_attrib(x, "id", id);
+
+ g_hash_table_insert(jd->gjc->queries, g_strdup(id), g_strdup("vCard"));
+
+ gjab_send(gjc, x);
+
+ xmlnode_free(x);
+}
+
+static void jabber_get_error_msg(struct gaim_connection *gc, char *who) {
+ struct jabber_data *jd = gc->proto_data;
+ gjconn gjc = jd->gjc;
+ gchar **str_arr = (gchar **) g_new(gpointer, 3);
+ gchar **ap = str_arr;
+ gchar *realwho, *final;
+ struct jabber_buddy_data *jbd;
+
+ if((realwho = get_realwho(gjc, who, FALSE, NULL)) == NULL) {
+ g_strfreev(str_arr);
+ return;
+ }
+
+ 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);
+ *ap = NULL;
+
+ final= g_strjoinv(NULL, str_arr);
+
+ g_strfreev(str_arr);
+
+ g_show_info_text(gc, realwho, 2, final, NULL);
+ g_free(realwho);
+ g_free(final);
+}
+
+static void jabber_get_away_msg(struct gaim_connection *gc, char *who) {
+ struct jabber_data *jd = gc->proto_data;
+ gjconn gjc = jd->gjc;
+ int num_resources;
+ gaim_jid gjid;
+ char *buddy = get_realwho(gjc, who, FALSE, &gjid);
+ struct jabber_buddy_data *jbd = jabber_find_buddy(gc, buddy);
+ gchar **str_arr;
+ gchar **ap;
+ gchar *realwho, *final;
+ GSList *resources;
+ int i;
+
+ if(!buddy)
+ return;
+
+ if(!gjid->resource) {
+ num_resources = g_slist_length(jbd->resources);
+ resources = jbd->resources;
+ } else {
+ num_resources = 1;
+ resources = jbd->resources;
+ while(strcasecmp(((jab_res_info)resources->data)->name, gjid->resource))
+ resources = resources->next;
+ }
+
+ gaim_jid_free(gjid);
+
+ /* space for all elements: Jabber I.D. + "status" + NULL (list terminator) */
+ str_arr = (gchar **) g_new(gpointer, num_resources*2 + 1);
+ ap = str_arr;
+
+ 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));
+ g_free(realwho);
+ resources = resources->next;
+ }
+
+ *ap = NULL;
+
+ g_free(buddy);
+
+ final= g_strjoinv(NULL, str_arr);
+ g_strfreev(str_arr);
+
+ g_show_info_text(gc, who, 2, final, NULL);
+ g_free(final);
+
+}
+
+static void jabber_get_cb_info(struct gaim_connection *gc, int cid, char *who) {
+ struct jabber_chat *jc = NULL;
+ char *realwho;
+
+ /* Find out which chat */
+ if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0)
+ return;
+
+ realwho = g_strdup_printf("%s@%s/%s", jc->gjid->user, jc->gjid->server, who);
+
+ jabber_get_info(gc, realwho);
+ g_free(realwho);
+}
+
+static void jabber_get_cb_away_msg(struct gaim_connection *gc, int cid, char *who) {
+ struct jabber_chat *jc = NULL;
+ char *realwho;
+
+ /* Find out which chat */
+ if(jabber_find_chat_by_convo_id(gc, cid, &jc) != 0)
+ return;
+
+ realwho = g_strdup_printf("%s@%s/%s", jc->gjid->user, jc->gjid->server, who);
+
+ jabber_get_away_msg(gc, realwho);
+ g_free(realwho);
+
+}
+
+static GList *jabber_buddy_menu(struct gaim_connection *gc, char *who) {
+ GList *m = NULL;
+ struct proto_buddy_menu *pbm;
+ struct buddy *b = find_buddy(gc, who);
+
+ if(b->uc == UC_ERROR)
+ {
+ pbm = g_new0(struct proto_buddy_menu, 1);
+ pbm->label = _("View Error Msg");
+ pbm->callback = jabber_get_error_msg;
+ pbm->gc = gc;
+ m = g_list_append(m, pbm);
+ } else {
+ pbm = g_new0(struct proto_buddy_menu, 1);
+ pbm->label = _("Get Info");
+ pbm->callback = jabber_get_info;
+ pbm->gc = gc;
+ 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;
+ pbm->gc = gc;
+ m = g_list_append(m, pbm);
+ }
+
+ return m;
+}
+
+/*
+ * Jabber protocol-specific "edit buddy menu" item(s)
+ */
+static GList *jabber_edit_buddy_menu(struct gaim_connection *gc, char *who) {
+ GList *m = NULL;
+ 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;
+ pbm->gc = gc;
+ m = g_list_append(m, pbm);
+
+ return m;
+}
+
+static GList *jabber_away_states(struct gaim_connection *gc) {
+ GList *m = NULL;
+
+ m = g_list_append(m, "Online");
+ m = g_list_append(m, "Chatty");
+ m = g_list_append(m, "Away");
+ m = g_list_append(m, "Extended Away");
+ m = g_list_append(m, "Do Not Disturb");
+
+ return m;
+}
+
+static void jabber_set_away(struct gaim_connection *gc, char *state, char *message)
+{
+ xmlnode x, y;
+ struct jabber_data *jd = gc->proto_data;
+ gjconn gjc = jd->gjc;
+ GSList *jcs;
+ struct jabber_chat *jc;
+ char *chatname;
+
+ gc->away = NULL; /* never send an auto-response */
+
+ x = xmlnode_new_tag("presence");
+
+ if (!strcmp(state, GAIM_AWAY_CUSTOM)) {
+ /* oh goody. Gaim is telling us what to do. */
+ if (message) {
+ /* Gaim wants us to be away */
+ y = xmlnode_insert_tag(x, "show");
+ xmlnode_insert_cdata(y, "away", -1);
+ y = xmlnode_insert_tag(x, "status");
+ xmlnode_insert_cdata(y, message, -1);
+ gc->away = "";
+ } else {
+ /* Gaim wants us to not be away */
+ /* but for Jabber, we can just send presence with no other information. */
+ }
+ } else {
+ /* state is one of our own strings. it won't be NULL. */
+ if (!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);
+ gc->away = "";
+ } else if (!strcmp(state, "Extended Away")) {
+ y = xmlnode_insert_tag(x, "show");
+ xmlnode_insert_cdata(y, "xa", -1);
+ gc->away = "";
+ } else if (!strcmp(state, "Do Not Disturb")) {
+ y = xmlnode_insert_tag(x, "show");
+ xmlnode_insert_cdata(y, "dnd", -1);
+ gc->away = "";
+ }
+ }
+
+ 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) {
+ jc = jcs->data;
+ 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);
+ gjab_send(gjc, x);
+ g_free(chatname);
+ }
+ }
+
+ xmlnode_free(x);
+}
+
+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()
+{
+ GList *m = NULL;
+ struct proto_user_opt *puo;
+
+ puo = g_new0(struct proto_user_opt, 1);
+ puo->label = "Port:";
+ puo->def = "5222";
+ puo->pos = USEROPT_PORT;
+ m = g_list_append(m, puo);
+
+ return m;
+}
+
+/*---------------------------------------*/
+/* Jabber "set info" (vCard) support */
+/*---------------------------------------*/
+
+/*
+ * V-Card format:
+ *
+ * <vCard prodid='' version='' xmlns=''>
+ * <FN></FN>
+ * <N>
+ * <FAMILY/>
+ * <GIVEN/>
+ * </N>
+ * <NICKNAME/>
+ * <URL/>
+ * <ADR>
+ * <STREET/>
+ * <EXTADD/>
+ * <LOCALITY/>
+ * <REGION/>
+ * <PCODE/>
+ * <COUNTRY/>
+ * </ADR>
+ * <TEL/>
+ * <EMAIL/>
+ * <ORG>
+ * <ORGNAME/>
+ * <ORGUNIT/>
+ * </ORG>
+ * <TITLE/>
+ * <ROLE/>
+ * <DESC/>
+ * <BDAY/>
+ * </vCard>
+ *
+ * See also:
+ *
+ * http://docs.jabber.org/proto/html/vcard-temp.html
+ * http://www.vcard-xml.org/dtd/vCard-XML-v2-20010520.dtd
+ */
+
+/*
+ * Cross-reference user-friendly V-Card entry labels to vCard XML tags
+ * and attributes.
+ *
+ * Order is (or should be) unimportant. For example: we have no way of
+ * knowing in what order real data will arrive.
+ *
+ * Format: Label, Pre-set text, "visible" flag, "editable" flag, XML tag
+ * name, XML tag's parent tag "path" (relative to vCard node).
+ *
+ * List is terminated by a NULL label pointer.
+ *
+ * Entries with no label text, but with XML tag and parent tag
+ * entries, are used by V-Card XML construction routines to
+ * "automagically" construct the appropriate XML node tree.
+ *
+ * Thoughts on future direction/expansion
+ *
+ * This is a "simple" vCard.
+ *
+ * It is possible for nodes other than the "vCard" node to have
+ * attributes. Should that prove necessary/desirable, add an
+ * "attributes" pointer to the vcard_template struct, create the
+ * necessary tag_attr structs, and add 'em to the vcard_dflt_data
+ * array.
+ *
+ * The above changes will (obviously) require changes to the vCard
+ * construction routines.
+ */
+
+struct vcard_template {
+ char *label; /* label text pointer */
+ char *text; /* entry text pointer */
+ int visible; /* should entry field be "visible?" */
+ int editable; /* should entry field be editable? */
+ char *tag; /* tag text */
+ char *ptag; /* parent tag "path" text */
+ char *url; /* vCard display format if URL */
+} vcard_template_data[] = {
+ {N_("Full Name"), NULL, TRUE, TRUE, "FN", NULL, NULL},
+ {N_("Family Name"), NULL, TRUE, TRUE, "FAMILY", "N", NULL},
+ {N_("Given Name"), NULL, TRUE, TRUE, "GIVEN", "N", NULL},
+ {N_("Nickname"), NULL, TRUE, TRUE, "NICKNAME", NULL, NULL},
+ {N_("URL"), NULL, TRUE, TRUE, "URL", NULL, "<A HREF=\"%s\">%s</A>"},
+ {N_("Street Address"), NULL, TRUE, TRUE, "STREET", "ADR", NULL},
+ {N_("Extended Address"), NULL, TRUE, TRUE, "EXTADD", "ADR", NULL},
+ {N_("Locality"), NULL, TRUE, TRUE, "LOCALITY", "ADR", NULL},
+ {N_("Region"), NULL, TRUE, TRUE, "REGION", "ADR", NULL},
+ {N_("Postal Code"), NULL, TRUE, TRUE, "PCODE", "ADR", NULL},
+ {N_("Country"), NULL, TRUE, TRUE, "COUNTRY", "ADR", NULL},
+ {N_("Telephone"), NULL, TRUE, TRUE, "TELEPHONE", NULL, NULL},
+ {N_("Email"), NULL, TRUE, TRUE, "EMAIL", NULL, "<A HREF=\"mailto:%s\">%s</A>"},
+ {N_("Organization Name"), NULL, TRUE, TRUE, "ORGNAME", "ORG", NULL},
+ {N_("Organization Unit"), NULL, TRUE, TRUE, "ORGUNIT", "ORG", NULL},
+ {N_("Title"), NULL, TRUE, TRUE, "TITLE", NULL, NULL},
+ {N_("Role"), NULL, TRUE, TRUE, "ROLE", NULL, NULL},
+ {N_("Birthday"), NULL, TRUE, TRUE, "BDAY", NULL, NULL},
+ {N_("Description"), NULL, TRUE, TRUE, "DESC", NULL, NULL},
+ {"", NULL, TRUE, TRUE, "N", NULL, NULL},
+ {"", NULL, TRUE, TRUE, "ADR", NULL, NULL},
+ {"", NULL, TRUE, TRUE, "ORG", NULL, NULL},
+ {NULL, NULL, 0, 0, NULL, NULL, NULL}
+};
+
+/*
+ * The "vCard" tag's attibute list...
+ */
+struct tag_attr {
+ char *attr;
+ char *value;
+} vcard_tag_attr_list[] = {
+ {"prodid", "-//HandGen//NONSGML vGen v1.0//EN"},
+ {"version", "2.0", },
+ {"xmlns", "vcard-temp", },
+ {NULL, NULL},
+};
+
+
+/*
+ * 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
+ */
+typedef struct {
+ XML_Parser parser;
+ xmlnode current;
+} *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);
+ char *cdata, *status;
+ 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);
+ gchar **ap = str_arr;
+ gchar *buddy, *final;
+
+ if((buddy = get_realwho(gjc, from, TRUE, NULL)) == NULL) {
+ g_strfreev(str_arr);
+ return;
+ }
+
+ *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);
+ } else {
+ gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
+ cdata = xmlnode_get_tag_data(querynode, tag);
+ g_free(tag);
+ }
+ if(cdata != NULL) {
+ if(vc_tp->url == NULL) {
+ *ap++ = g_strdup_printf("<B>%s:</B> %s<BR>\n", vc_tp->label, cdata);
+ } else {
+ gchar *fmt = g_strdup_printf("<B>%%s:</B> %s<BR>\n", vc_tp->url);
+ *ap++ = g_strdup_printf(fmt, vc_tp->label, cdata, cdata);
+ g_free(fmt);
+ }
+ }
+ }
+
+ 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
+ * string and HTML-ize.
+ */
+ if((cdata = xmlnode_get_tag_data(querynode, "DESC")) != NULL) {
+ gchar *tmp = g_strdup_printf("<HR>%s<BR>", cdata);
+ *ap++ = strdup_withhtml(tmp);
+ g_free(tmp);
+ }
+
+ *ap = NULL;
+
+ final= g_strjoinv(NULL, str_arr);
+ g_strfreev(str_arr);
+
+ g_show_info_text(gc, buddy, 2, final, NULL);
+ g_free(buddy);
+ g_free(final);
+}
+
+/*
+ * Used by XML_Parse on parsing CDATA
+ */
+static void xmlstr2xmlnode_charData(void *userdata, const char *s, int slen)
+{
+ xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
+
+ if (xmlp->current)
+ xmlnode_insert_cdata(xmlp->current, s, slen);
+}
+
+/*
+ * Used by XML_Parse to start or append to an xmlnode
+ */
+static void xmlstr2xmlnode_startElement(void *userdata, const char *name, const char **attribs)
+{
+ xmlnode x;
+ xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
+
+ if (xmlp->current) {
+ /* Append the node to the current one */
+ x = xmlnode_insert_tag(xmlp->current, name);
+ xmlnode_put_expat_attribs(x, attribs);
+
+ xmlp->current = x;
+ } else {
+ x = xmlnode_new_tag(name);
+ xmlnode_put_expat_attribs(x, attribs);
+ xmlp->current = x;
+ }
+}
+
+/*
+ * Used by XML_Parse to end an xmlnode
+ */
+static void xmlstr2xmlnode_endElement(void *userdata, const char *name)
+{
+ xmlstr2xmlnode_parser xmlp = (xmlstr2xmlnode_parser) userdata;
+ xmlnode x;
+
+ if (xmlp->current != NULL && (x = xmlnode_get_parent(xmlp->current)) != NULL) {
+ xmlp->current = x;
+ }
+}
+
+/*
+ * Parse an XML-encoded string into an xmlnode tree
+ *
+ * Caller is responsible for freeing the returned xmlnode
+ */
+static xmlnode xmlstr2xmlnode(char *xmlstring)
+{
+ xmlstr2xmlnode_parser my_parser = g_new(xmlstr2xmlnode_parser_struct, 1);
+ xmlnode x = NULL;
+
+ my_parser->parser = XML_ParserCreate(NULL);
+ my_parser->current = NULL;
+
+ XML_SetUserData(my_parser->parser, (void *)my_parser);
+ XML_SetElementHandler(my_parser->parser, xmlstr2xmlnode_startElement, xmlstr2xmlnode_endElement);
+ XML_SetCharacterDataHandler(my_parser->parser, xmlstr2xmlnode_charData);
+ XML_Parse(my_parser->parser, xmlstring, strlen(xmlstring), 0);
+
+ x = my_parser->current;
+
+ XML_ParserFree(my_parser->parser);
+ g_free(my_parser);
+
+ return(x);
+}
+
+/*
+ * Insert a tag node into an xmlnode tree, recursively inserting parent tag
+ * nodes as necessary
+ *
+ * Returns pointer to inserted node
+ *
+ * Note to hackers: this code is designed to be re-entrant (it's recursive--it
+ * calls itself), so don't put any "static"s in here!
+ */
+static xmlnode insert_tag_to_parent_tag(xmlnode start, const char *parent_tag, const char *new_tag)
+{
+ xmlnode x = NULL;
+
+ /*
+ * If the parent tag wasn't specified, see if we can get it
+ * from the vCard template struct.
+ */
+ if(parent_tag == NULL) {
+ struct vcard_template *vc_tp = vcard_template_data;
+
+ while(vc_tp->label != NULL) {
+ if(strcmp(vc_tp->tag, new_tag) == 0) {
+ parent_tag = vc_tp->ptag;
+ break;
+ }
+ ++vc_tp;
+ }
+ }
+
+ /*
+ * If we have a parent tag...
+ */
+ if(parent_tag != NULL ) {
+ /*
+ * Try to get the parent node for a tag
+ */
+ if((x = xmlnode_get_tag(start, parent_tag)) == NULL) {
+ /*
+ * Descend?
+ */
+ char *grand_parent = strcpy(g_malloc(strlen(parent_tag) + 1), parent_tag);
+ char *parent;
+
+ if((parent = strrchr(grand_parent, '/')) != NULL) {
+ *(parent++) = '\0';
+ x = insert_tag_to_parent_tag(start, grand_parent, parent);
+ } else {
+ x = xmlnode_insert_tag(start, grand_parent);
+ }
+ g_free(grand_parent);
+ } else {
+ /*
+ * We found *something* to be the parent node.
+ * Note: may be the "root" node!
+ */
+ xmlnode y;
+ if((y = xmlnode_get_tag(x, new_tag)) != NULL) {
+ return(y);
+ }
+ }
+ }
+
+ /*
+ * insert the new tag into its parent node
+ */
+ return(xmlnode_insert_tag((x == NULL? start : x), new_tag));
+}
+
+/*
+ * 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;
+ char *p = NULL;
+
+ for(vc_tp = vcard_template_data; vc_tp->label != NULL; ++vc_tp) {
+ if(strcmp(label, vc_tp->label) == 0) {
+ p = vc_tp->tag;
+ break;
+ }
+ }
+
+ return(p);
+}
+
+/*
+ * Send vCard info to Jabber server
+ */
+static void jabber_set_info(struct gaim_connection *gc, char *info)
+{
+ xmlnode x, vc_node;
+ char *id;
+ struct jabber_data *jd = gc->proto_data;
+ gjconn gjc = jd->gjc;
+
+ x = xmlnode_new_tag("iq");
+ xmlnode_put_attrib(x, "type", "set");
+
+ id = gjab_getid(gjc);
+
+ xmlnode_put_attrib(x, "id", id);
+
+ /*
+ * Send only if there's actually any *information* to send
+ */
+ if((vc_node = xmlstr2xmlnode(info)) != NULL && xmlnode_get_name(vc_node) != NULL &&
+ g_strncasecmp(xmlnode_get_name(vc_node), "vcard", 5) == 0) {
+ xmlnode_insert_tag_node(x, vc_node);
+ debug_printf("jabber: vCard packet: %s\n", xmlnode2str(x));
+ gjab_send(gjc, x);
+ }
+
+ xmlnode_free(x);
+}
+
+/*
+ * This is the callback from the "ok clicked" for "set vCard"
+ *
+ * Formats GSList data into XML-encoded string and returns a pointer
+ * to said string.
+ *
+ * g_free()'ing the returned string space is the responsibility of
+ * the caller.
+ */
+static gchar *jabber_format_info(MultiEntryDlg *b)
+{
+ xmlnode vc_node;
+ GSList *list;
+ MultiEntryData *med;
+ MultiTextData *mtd;
+ char *p;
+
+ 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) {
+ xmlnode xp;
+ 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) {
+ xmlnode xp;
+ 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));
+ xmlnode_free(vc_node);
+
+ return(p);
+}
+
+/*
+ * 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)
+{
+ MultiEntryData *data;
+ const struct vcard_template *vc_tp;
+ char *user_info;
+ MultiEntryDlg *b = multi_entry_dialog_new();
+ char *cdata;
+ xmlnode x_vc_data = NULL;
+ struct aim_user *tmp = gc->user;
+ b->user = tmp;
+
+
+ /*
+ * 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')
+ continue;
+ if(vc_tp->ptag == NULL) {
+ cdata = xmlnode_get_tag_data(x_vc_data, vc_tp->tag);
+ } else {
+ gchar *tag = g_strdup_printf("%s/%s", vc_tp->ptag, vc_tp->tag);
+ cdata = xmlnode_get_tag_data(x_vc_data, tag);
+ g_free(tag);
+ }
+ if(strcmp(vc_tp->tag, "DESC") == 0) {
+ multi_text_list_update(&(b->multi_text_items),
+ vc_tp->label, cdata, TRUE);
+ } else {
+ 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);
+ } else {
+ /*
+ * Early Beta versions had a different user_info storage format--let's
+ * see if that works.
+ *
+ * This goes away RSN.
+ */
+ 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);
+ } else {
+ multi_entry_list_update(&(b->multi_entry_items),
+ str_list2[0], str_list2[1], FALSE);
+ }
+ }
+ g_strfreev(str_list2);
+ }
+ g_strfreev(str_list);
+ }
+ }
+
+ if(user_info != NULL) {
+ g_free(user_info);
+ }
+
+ 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;
+
+ show_set_vcard(b);
+}
+
+/*---------------------------------------*/
+/* End Jabber "set info" (vCard) support */
+/*---------------------------------------*/
+
+/*----------------------------------------*/
+/* Jabber "user registration" support */
+/*----------------------------------------*/
+
+/*
+ * Three of the following four functions duplicate much of what
+ * exists elsewhere:
+ *
+ * jabber_handleregresp()
+ * gjab_reqreg()
+ * 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) {
+ xmlnode querynode;
+
+ if((querynode = xmlnode_get_tag(p->x, "query")) != NULL) {
+ char *xmlns;
+
+ /* we damn well *better* have this! */
+ if((xmlns = xmlnode_get_attrib(querynode, "xmlns")) != NULL &&
+ strcmp(xmlns, NS_REGISTER) == 0) {
+
+ char *tag;
+ xmlnode child = xmlnode_get_firstchild(querynode);
+
+ debug_printf("got registration requirments response!\n");
+
+ while(child != NULL) {
+ if((tag = xmlnode_get_name(child)) != NULL) {
+ char *data;
+
+ fprintf(stderr, "DBG: got node: \"%s\"\n", tag);
+ fflush(stderr);
+
+ if((data = xmlnode_get_data(child)) != NULL) {
+ fprintf(stderr, "DBG: got data: \"%s\"\n", data);
+ fflush(stderr);
+ }
+ }
+ child = xmlnode_get_nextsibling(child);
+ }
+ }
+ } else {
+ 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
+ * allocs, etc.?
+ */
+ signoff(GJ_GC(gjc));
+ }
+
+ } else {
+ xmlnode xerr;
+ char *errmsg = NULL;
+ int errcode = 0;
+ struct jabber_data *jd = GJ_GC(gjc)->proto_data;
+
+ debug_printf("registration failed\n");
+ xerr = xmlnode_get_tag(p->x, "error");
+ if (xerr) {
+ char msg[BUF_LONG];
+ errmsg = xmlnode_get_data(xerr);
+ if (xmlnode_get_attrib(xerr, "code")) {
+ errcode = atoi(xmlnode_get_attrib(xerr, "code"));
+ g_snprintf(msg, sizeof(msg), "Error %d: %s", errcode, errmsg);
+ } else
+ g_snprintf(msg, sizeof(msg), "%s", errmsg);
+ hide_login_progress(GJ_GC(gjc), msg);
+ } else {
+ hide_login_progress(GJ_GC(gjc), _("Unknown registration error"));
+ }
+
+ jd->die = TRUE;
+ }
+}
+
+/*
+ * Like gjab_reqauth(), only different
+ */
+static void gjab_reqreg(gjconn gjc)
+{
+ xmlnode x, y, z;
+ char *user;
+
+ if (!gjc)
+ return;
+
+ x = jutil_iqnew(JPACKET__SET, NS_REGISTER);
+ y = xmlnode_get_tag(x, "query");
+
+ user = gjc->user->user;
+
+ if (user) {
+ z = xmlnode_insert_tag(y, "username");
+ xmlnode_insert_cdata(z, user, -1);
+ }
+ z = xmlnode_insert_tag(y, "password");
+ xmlnode_insert_cdata(z, gjc->pass, -1);
+
+ debug_printf("jabber: registration packet: %s\n", xmlnode2str(x));
+ gjab_send(gjc, x);
+ xmlnode_free(x);
+}
+
+/*
+ * Like jabber_handlestate(), only different
+ */
+static void jabber_handle_registration_state(gjconn gjc, int state)
+{
+ switch (state) {
+ case JCONN_STATE_OFF:
+ if(gjc->was_connected) {
+ hide_login_progress_error(GJ_GC(gjc), _("Connection lost"));
+ } else {
+ hide_login_progress(GJ_GC(gjc), _("Unable to connect"));
+ }
+ signoff(GJ_GC(gjc));
+ break;
+ case JCONN_STATE_CONNECTED:
+ gjc->was_connected = 1;
+ /*
+ * TBD?
+ set_login_progress(GJ_GC(gjc), 2, _("Connected"));
+ */
+ break;
+ case JCONN_STATE_ON:
+ /*
+ * TBD?
+ set_login_progress(GJ_GC(gjc), 3, _("Requesting Authentication Method"));
+ */
+ gjab_reqreg(gjc);
+ /*
+ * TBD: A work-in-progress
+ gjab_reqregreqs(gjc);
+ */
+ break;
+ default:
+ debug_printf("state change: %d\n", state);
+ }
+ return;
+}
+
+/*
+ * 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
+ */
+ jd->buddies = NULL;
+ jd->chats = NULL;
+
+ if ((jd->gjc = gjab_new(loginname, au->password, gc)) == NULL) {
+ g_free(loginname);
+ debug_printf("jabber: unable to connect (jab_new failed)\n");
+ hide_login_progress(gc, _("Unable to connect"));
+ signoff(gc);
+ } else {
+ gjab_state_handler(jd->gjc, jabber_handle_registration_state);
+ gjab_packet_handler(jd->gjc, jabber_handleregresp);
+ jd->gjc->queries = NULL;
+ gjab_start(jd->gjc);
+ }
+
+ g_free(loginname);
+}
+
+/*----------------------------------------*/
+/* 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"))) {
+ show_set_dir(gc);
+ */
+ } else if (!strcmp(act, _("Change Password"))) {
+ show_change_passwd(gc);
+ }
+}
+
+static GList *jabber_actions()
+{
+ GList *m = NULL;
+
+ m = g_list_append(m, _("Set User Info"));
+ /*
+ m = g_list_append(m, _("Set Dir Info"));
+ */
+ m = g_list_append(m, _("Change Password"));
+
+ return m;
+}
+
+static struct prpl *my_protocol = NULL;
+
+void jabber_init(struct prpl *ret)
+{
+ /* the NULL's aren't required but they're nice to have */
+ ret->protocol = PROTO_JABBER;
+ ret->options = OPT_PROTO_UNIQUE_CHATNAME |
+ OPT_PROTO_CHAT_TOPIC |
+ 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->set_dir = NULL;
+ ret->get_dir = NULL;
+ 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->add_deny = NULL;
+ ret->rem_permit = NULL;
+ ret->rem_deny = NULL;
+ ret->set_permit_deny = NULL;
+ ret->warn = 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;
+
+ my_protocol = ret;
+}
+
+#ifndef STATIC
+
+char *gaim_plugin_init(GModule *handle)
+{
+ load_protocol(jabber_init, sizeof(struct prpl));
+ return NULL;
+}
+
+void gaim_plugin_remove()
+{
+ struct prpl *p = find_prpl(PROTO_JABBER);
+ if (p == my_protocol)
+ unload_protocol(p);
+}
+
+char *name()
+{
+ return "Jabber";
+}
+
+char *description()
+{
+ return PRPL_DESC("Jabber");
+}
+
+#endif
+
+/*
+ * Local variables:
+ * c-indentation-style: k&r
+ * c-basic-offset: 8
+ * indent-tabs-mode: notnil
+ * End:
+ *
+ * vim: shiftwidth=8:
+ */
--- /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 @@
+/*
+ * gaim
+ *
+ * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#include <netdb.h>
+#include <unistd.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include "multi.h"
+#include "prpl.h"
+#include "gaim.h"
+#include "proxy.h"
+#include "md5.h"
+
+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 YAHOO_DEBUG
+
+#define USEROPT_MAIL 0
+
+#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_LOGOFF,
+ YAHOO_SERVICE_ISAWAY,
+ YAHOO_SERVICE_ISBACK,
+ YAHOO_SERVICE_IDLE, /* 5 (placemarker) */
+ YAHOO_SERVICE_MESSAGE,
+ YAHOO_SERVICE_IDACT,
+ YAHOO_SERVICE_IDDEACT,
+ YAHOO_SERVICE_MAILSTAT,
+ YAHOO_SERVICE_USERSTAT, /* 0xa */
+ YAHOO_SERVICE_NEWMAIL,
+ YAHOO_SERVICE_CHATINVITE,
+ YAHOO_SERVICE_CALENDAR,
+ YAHOO_SERVICE_NEWPERSONALMAIL,
+ YAHOO_SERVICE_NEWCONTACT,
+ YAHOO_SERVICE_ADDIDENT, /* 0x10 */
+ YAHOO_SERVICE_ADDIGNORE,
+ YAHOO_SERVICE_PING,
+ 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_CONFMSG,
+ 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
+};
+
+enum yahoo_status {
+ YAHOO_STATUS_AVAILABLE = 0,
+ YAHOO_STATUS_BRB,
+ YAHOO_STATUS_BUSY,
+ YAHOO_STATUS_NOTATHOME,
+ YAHOO_STATUS_NOTATDESK,
+ YAHOO_STATUS_NOTINOFFICE,
+ YAHOO_STATUS_ONPHONE,
+ 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 */
+
+struct yahoo_data {
+ int fd;
+ guchar *rxqueue;
+ int rxlen;
+ GHashTable *hash;
+ GHashTable *games;
+ int current_status;
+ gboolean logged_in;
+};
+
+struct yahoo_pair {
+ int key;
+ char *value;
+};
+
+struct yahoo_packet {
+ guint16 service;
+ guint32 status;
+ guint32 id;
+ GSList *hash;
+};
+
+static char *yahoo_name() {
+ return "Yahoo";
+}
+
+#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;
+ pkt->status = status;
+ pkt->id = id;
+
+ return pkt;
+}
+
+static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, char *value)
+{
+ struct yahoo_pair *pair = g_new0(struct yahoo_pair, 1);
+ pair->key = key;
+ pair->value = g_strdup(value);
+ pkt->hash = g_slist_append(pkt->hash, pair);
+}
+
+static int yahoo_packet_length(struct yahoo_packet *pkt)
+{
+ GSList *l;
+
+ int len = 0;
+
+ l = pkt->hash;
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ int tmp = pair->key;
+ do {
+ tmp /= 10;
+ len++;
+ } while (tmp);
+ len += 2;
+ len += strlen(pair->value);
+ len += 2;
+ l = l->next;
+ }
+
+ return len;
+}
+
+/* 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), \
+ 2)
+#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), \
+ 4)
+#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)
+{
+ int pos = 0;
+
+ while (pos + 1 < len) {
+ char key[64], *value = NULL;
+ int accept;
+ int x;
+
+ struct yahoo_pair *pair = g_new0(struct yahoo_pair, 1);
+
+ x = 0;
+ while (pos + 1 < len) {
+ if (data[pos] == 0xc0 && data[pos + 1] == 0x80)
+ break;
+ key[x++] = data[pos++];
+ }
+ key[x] = 0;
+ pos += 2;
+ pair->key = strtol(key, NULL, 10);
+ accept = x; /* if x is 0 there was no key, so don't accept it */
+
+ if (accept)
+ value = g_malloc(len - pos + 1);
+ x = 0;
+ while (pos + 1 < len) {
+ if (data[pos] == 0xc0 && data[pos + 1] == 0x80)
+ break;
+ if (accept)
+ value[x++] = data[pos++];
+ }
+ if (accept)
+ value[x] = 0;
+ pos += 2;
+ if (accept) {
+ pair->value = g_strdup(value);
+ g_free(value);
+ pkt->hash = g_slist_append(pkt->hash, pair);
+ debug_printf("Key: %d \tValue: %s\n", pair->key, pair->value);
+ } else {
+ g_free(pair);
+ }
+ }
+}
+
+static void yahoo_packet_write(struct yahoo_packet *pkt, guchar *data)
+{
+ GSList *l = pkt->hash;
+ int pos = 0;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ guchar buf[100];
+
+ g_snprintf(buf, sizeof(buf), "%d", pair->key);
+ strcpy(data + pos, buf);
+ pos += strlen(buf);
+ data[pos++] = 0xc0;
+ data[pos++] = 0x80;
+
+ strcpy(data + pos, pair->value);
+ pos += strlen(pair->value);
+ data[pos++] = 0xc0;
+ data[pos++] = 0x80;
+
+ l = l->next;
+ }
+}
+
+static void yahoo_packet_dump(guchar *data, int len)
+{
+#ifdef YAHOO_DEBUG
+ int i;
+ for (i = 0; i + 1 < len; i += 2) {
+ if ((i % 16 == 0) && i)
+ debug_printf("\n");
+ debug_printf("%02x", data[i]);
+ debug_printf("%02x ", data[i+1]);
+ }
+ if (i < len)
+ debug_printf("%02x", data[i]);
+ debug_printf("\n");
+ for (i = 0; i < len; i++) {
+ if ((i % 16 == 0) && i)
+ debug_printf("\n");
+ if (isprint(data[i]))
+ debug_printf("%c ", data[i]);
+ else
+ debug_printf(". ");
+ }
+ debug_printf("\n");
+#endif
+}
+
+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;
+ int ret;
+
+ guchar *data;
+ int pos = 0;
+
+ if (yd->fd < 0)
+ return -1;
+
+ 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);
+
+ g_free(data);
+
+ return ret;
+}
+
+static void yahoo_packet_free(struct yahoo_packet *pkt)
+{
+ while (pkt->hash) {
+ struct yahoo_pair *pair = pkt->hash->data;
+ g_free(pair->value);
+ g_free(pair);
+ pkt->hash = g_slist_remove(pkt->hash, pair);
+ }
+ g_free(pkt);
+}
+
+static void yahoo_process_status(struct gaim_connection *gc, struct yahoo_packet *pkt)
+{
+ struct yahoo_data *yd = gc->proto_data;
+ GSList *l = pkt->hash;
+ char *name = NULL;
+ int state = 0;
+ int gamestate = 0;
+ char *msg = NULL;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+
+ switch (pair->key) {
+ case 0: /* we won't actually do anything with this */
+ break;
+ case 1: /* we don't get the full buddy list here. */
+ if (!yd->logged_in) {
+ account_online(gc);
+ serv_finish_login(gc);
+ g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", pair->value);
+ yd->logged_in = TRUE;
+
+ /* 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
+ * requested
+ *
+ * do_import(gc, NULL);
+ * newpkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_OFFLINE, 0);
+ * yahoo_send_packet(yd, newpkt);
+ * yahoo_packet_free(newpkt);
+ */
+
+ }
+ break;
+ case 8: /* how many online buddies we have */
+ break;
+ case 7: /* the current buddy */
+ name = pair->value;
+ break;
+ case 10: /* state */
+ state = strtol(pair->value, NULL, 10);
+ break;
+ case 19: /* custom message */
+ msg = pair->value;
+ break;
+ case 11: /* i didn't know what this was in the old protocol either */
+ break;
+ case 17: /* in chat? */
+ break;
+ 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);
+ break;
+ }
+ 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);
+ else
+ 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);
+ if (val) {
+ g_free(val);
+ g_hash_table_insert(yd->hash, name,
+ msg ? g_strdup(msg) : g_malloc0(1));
+ } else
+ g_hash_table_insert(yd->hash, g_strdup(name),
+ msg ? g_strdup(msg) : g_malloc0(1));
+ }
+ break;
+ case 60: /* no clue */
+ break;
+ case 16: /* Custom error message */
+ do_error_dialog(pair->value, "Gaim -- Yahoo! Error");
+ break;
+ default:
+ debug_printf("unknown status key %d\n", pair->key);
+ break;
+ }
+
+ l = l->next;
+ }
+}
+
+static void yahoo_process_list(struct gaim_connection *gc, struct yahoo_packet *pkt)
+{
+ GSList *l = pkt->hash;
+ gboolean export = FALSE;
+
+ while (l) {
+ char **lines;
+ char **split;
+ char **buddies;
+ char **tmp, **bud;
+
+ struct yahoo_pair *pair = l->data;
+ l = l->next;
+
+ if (pair->key != 87)
+ continue;
+
+ do_import(gc, NULL);
+ lines = g_strsplit(pair->value, "\n", -1);
+ for (tmp = lines; *tmp; tmp++) {
+ split = g_strsplit(*tmp, ":", 2);
+ if (!split)
+ continue;
+ if (!split[0] || !split[1]) {
+ g_strfreev(split);
+ continue;
+ }
+ buddies = g_strsplit(split[1], ",", -1);
+ for (bud = buddies; bud && *bud; bud++)
+ if (!find_buddy(gc, *bud)) {
+ add_buddy(gc, split[0], *bud, *bud);
+ export = TRUE;
+ }
+ g_strfreev(buddies);
+ g_strfreev(split);
+ }
+ g_strfreev(lines);
+ }
+
+ if (export)
+ do_export(gc);
+}
+
+static void yahoo_process_notify(struct gaim_connection *gc, struct yahoo_packet *pkt)
+{
+ char *msg = NULL;
+ char *from = NULL;
+ char *stat = NULL;
+ char *game = NULL;
+ GSList *l = pkt->hash;
+ struct yahoo_data *yd = (struct yahoo_data*) gc->proto_data;
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 4)
+ from = pair->value;
+ if (pair->key == 49)
+ msg = pair->value;
+ if (pair->key == 13)
+ stat = pair->value;
+ if (pair->key == 14)
+ game = pair->value;
+ l = l->next;
+ }
+
+ if (!g_strncasecmp(msg, "TYPING", strlen("TYPING"))) {
+ if (*stat == '1')
+ serv_got_typing(gc, from, 0);
+ else
+ 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;
+ if (!bud)
+ debug_printf("%s is playing a game, and doesn't want you to know.\n");
+ if (*stat == '1') {
+ if (g_hash_table_lookup_extended (yd->games, from, free1, free2)) {
+ g_free(free1);
+ g_free(free2);
+ }
+ g_hash_table_insert (yd->games, g_strdup(from), g_strdup(game));
+ if (bud)
+ serv_got_update(gc, from, 1, 0, 0, 0, bud->uc | YAHOO_STATUS_GAME, 0);
+ } else {
+ if (g_hash_table_lookup_extended (yd->games, from, free1, free2)) {
+ g_free(free1);
+ g_free(free2);
+ g_hash_table_remove (yd->games, from);
+ }
+ if (bud)
+ 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)
+{
+ char *msg = NULL;
+ char *from = NULL;
+ time_t tm = time(NULL);
+ GSList *l = pkt->hash;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 4)
+ from = pair->value;
+ if (pair->key == 14)
+ msg = pair->value;
+ if (pair->key == 15)
+ tm = strtol(pair->value, NULL, 10);
+ l = l->next;
+ }
+
+ if (pkt->status <= 1 || pkt->status == 5) {
+ char *m;
+ int i, j;
+ strip_linefeed(msg);
+ m = msg;
+ for (i = 0, j = 0; m[i]; i++) {
+ if (m[i] == 033) {
+ while (m[i] && (m[i] != 'm'))
+ i++;
+ if (!m[i])
+ i--;
+ continue;
+ }
+ msg[j++] = m[i];
+ }
+ msg[j] = 0;
+ 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;
+ char *id = NULL;
+ char *who = NULL;
+ char *msg = NULL;
+ char *name = NULL;
+ int state = YAHOO_STATUS_AVAILABLE;
+ int online = FALSE;
+
+ GSList *l = pkt->hash;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 1)
+ id = pair->value;
+ else if (pair->key == 3)
+ who = pair->value;
+ else if (pair->key == 14)
+ msg = pair->value;
+ else if (pair->key == 7)
+ name = pair->value;
+ else if (pair->key == 10)
+ state = strtol(pair->value, NULL, 10);
+ else if (pair->key == 13)
+ online = strtol(pair->value, NULL, 10);
+ l = l->next;
+ }
+
+ if (id)
+ show_got_added(gc, id, who, NULL, msg);
+ if (name) {
+ 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);
+ else
+ 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);
+ if (val) {
+ g_free(val);
+ g_hash_table_insert(yd->hash, name,
+ msg ? g_strdup(msg) : g_malloc0(1));
+ } else
+ 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)
+{
+ char *who = NULL;
+ char *email = NULL;
+ char *subj = NULL;
+ int count = 0;
+ GSList *l = pkt->hash;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 9)
+ count = strtol(pair->value, NULL, 10);
+ else if (pair->key == 43)
+ who = pair->value;
+ else if (pair->key == 42)
+ email = pair->value;
+ else if (pair->key == 18)
+ subj = pair->value;
+ l = l->next;
+ }
+
+ if (who && email && subj) {
+ char *from = g_strdup_printf("%s (%s)", who, email);
+ connection_has_mail(gc, -1, from, subj, "http://mail.yahoo.com/");
+ g_free(from);
+ } else
+ 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];
+ in += 3;
+ }
+ if (inlen > 0)
+ {
+ unsigned char fragment;
+
+ *out++ = base64digits[in[0] >> 2];
+ fragment = (in[0] << 4) & 0x30;
+ if (inlen > 1)
+ fragment |= in[1] >> 4;
+ *out++ = base64digits[fragment];
+ *out++ = (inlen < 2) ? '-' : base64digits[(in[1] << 2) & 0x3c];
+ *out++ = '-';
+ }
+ *out = '\0';
+}
+
+static void yahoo_process_auth(struct gaim_connection *gc, struct yahoo_packet *pkt)
+{
+ char *seed = NULL;
+ char *sn = NULL;
+ GSList *l = pkt->hash;
+ struct yahoo_data *yd = gc->proto_data;
+
+ while (l) {
+ struct yahoo_pair *pair = l->data;
+ if (pair->key == 94)
+ seed = pair->value;
+ if (pair->key == 1)
+ sn = pair->value;
+ l = l->next;
+ }
+
+ if (seed) {
+ 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.
+ *
+ * Sorry, Yahoo.
+ */
+
+ md5_byte_t result[16];
+ md5_state_t ctx;
+ char *crypt_result;
+ 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 checksum;
+
+ int sv;
+
+ char *result6 = g_malloc(25);
+ char *result96 = g_malloc(25);
+
+ sv = seed[15];
+ sv = sv % 8;
+
+ md5_init(&ctx);
+ md5_append(&ctx, gc->password, strlen(gc->password));
+ md5_finish(&ctx, result);
+ to_y64(password_hash, result, 16);
+
+ md5_init(&ctx);
+ 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);
+
+ switch (sv) {
+ case 1:
+ case 6:
+ 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);
+ break;
+ case 2:
+ case 7:
+ 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);
+ break;
+ case 3:
+ 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);
+ break;
+ case 4:
+ 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);
+ break;
+ case 0:
+ case 5:
+ 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);
+ break;
+ }
+
+ md5_init(&ctx);
+ md5_append(&ctx, hash_string_p, strlen(hash_string_p));
+ md5_finish(&ctx, result);
+ to_y64(result6, result, 16);
+
+ md5_init(&ctx);
+ 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);
+
+ g_free(password_hash);
+ g_free(crypt_hash);
+ g_free(hash_string_p);
+ g_free(hash_string_c);
+
+ yahoo_packet_free(pack);
+ }
+}
+
+static void yahoo_packet_process(struct gaim_connection *gc, struct yahoo_packet *pkt)
+{
+ switch (pkt->service)
+ {
+ 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);
+ break;
+ case YAHOO_SERVICE_NOTIFY:
+ yahoo_process_notify(gc, pkt);
+ break;
+ case YAHOO_SERVICE_MESSAGE:
+ case YAHOO_SERVICE_GAMEMSG:
+ yahoo_process_message(gc, pkt);
+ break;
+ case YAHOO_SERVICE_NEWMAIL:
+ yahoo_process_mail(gc, pkt);
+ break;
+ case YAHOO_SERVICE_NEWCONTACT:
+ yahoo_process_contact(gc, pkt);
+ break;
+ case YAHOO_SERVICE_LIST:
+ yahoo_process_list(gc, pkt);
+ break;
+ case YAHOO_SERVICE_AUTH:
+ yahoo_process_auth(gc, pkt);
+ break;
+ default:
+ debug_printf("unhandled service 0x%02x\n", pkt->service);
+ break;
+ }
+}
+
+static void yahoo_pending(gpointer data, gint source, GaimInputCondition cond)
+{
+ struct gaim_connection *gc = data;
+ struct yahoo_data *yd = gc->proto_data;
+ char buf[1024];
+ int len;
+
+ len = read(yd->fd, buf, sizeof(buf));
+
+ if (len <= 0) {
+ hide_login_progress_error(gc, "Unable to read");
+ signoff(gc);
+ return;
+ }
+
+ yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen);
+ memcpy(yd->rxqueue + yd->rxlen, buf, len);
+ yd->rxlen += len;
+
+ while (1) {
+ struct yahoo_packet *pkt;
+ int pos = 0;
+ int pktlen;
+
+ if (yd->rxlen < YAHOO_PACKET_HDRLEN)
+ return;
+
+ pos += 4; /* YMSG */
+ pos += 2;
+ pos += 2;
+
+ 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))
+ return;
+
+ 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;
+ if (yd->rxlen) {
+ char *tmp = g_memdup(yd->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yd->rxlen);
+ g_free(yd->rxqueue);
+ yd->rxqueue = tmp;
+ } else {
+ g_free(yd->rxqueue);
+ yd->rxqueue = NULL;
+ }
+
+ 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_data *yd;
+ struct yahoo_packet *pkt;
+
+ if (!g_slist_find(connections, gc)) {
+ close(source);
+ return;
+ }
+
+ if (source < 0) {
+ hide_login_progress(gc, "Unable to connect");
+ signoff(gc);
+ return;
+ }
+
+ yd = gc->proto_data;
+ yd->fd = source;
+
+ 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->fd = -1;
+ 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");
+ save_prefs();
+ }
+
+
+ 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");
+ signoff(gc);
+ return;
+ }
+
+}
+
+static gboolean yahoo_destroy_hash(gpointer key, gpointer val, gpointer data)
+{
+ g_free(key);
+ g_free(val);
+ return TRUE;
+}
+
+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);
+ if (yd->fd >= 0)
+ close(yd->fd);
+ if (yd->rxqueue)
+ g_free(yd->rxqueue);
+ yd->rxlen = 0;
+ if (gc->inpa)
+ gaim_input_remove(gc->inpa);
+ g_free(yd);
+}
+
+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)
+{
+ switch (a) {
+ case YAHOO_STATUS_BRB:
+ return "Be Right Back";
+ case YAHOO_STATUS_BUSY:
+ return "Busy";
+ case YAHOO_STATUS_NOTATHOME:
+ return "Not At Home";
+ case YAHOO_STATUS_NOTATDESK:
+ return "Not At Desk";
+ case YAHOO_STATUS_NOTINOFFICE:
+ return "Not In Office";
+ case YAHOO_STATUS_ONPHONE:
+ return "On Phone";
+ case YAHOO_STATUS_ONVACATION:
+ return "On Vacation";
+ case YAHOO_STATUS_OUTTOLUNCH:
+ return "Out To Lunch";
+ case YAHOO_STATUS_STEPPEDOUT:
+ return "Stepped Out";
+ case YAHOO_STATUS_INVISIBLE:
+ return "Invisible";
+ default:
+ return "Online";
+ }
+}
+
+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);
+ char *t;
+ char url[256];
+
+ if (!game)
+ return;
+ t = game = g_strdup(strstr(game, "ante?room="));
+ while (*t != '\t')
+ t++;
+ *t = 0;
+ g_snprintf(url, sizeof url, "http://games.yahoo.com/games/%s", game);
+ open_url(NULL, url);
+ g_free(game);
+}
+static GList *yahoo_buddy_menu(struct gaim_connection *gc, char *who)
+{
+ GList *m = NULL;
+ 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 buf[1024];
+ 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));
+ else
+ g_snprintf(buf, sizeof buf, "Custom Status: %s",
+ (char *)g_hash_table_lookup(yd->hash, b->name));
+ pbm->label = buf;
+ pbm->callback = NULL;
+ pbm->gc = gc;
+ m = g_list_append(m, pbm);
+ }
+
+ if (b->uc | YAHOO_STATUS_GAME) {
+ char *game = g_hash_table_lookup(yd->games, b->name);
+ char *room;
+ if (!game)
+ return m;
+ if (game) {
+ char *t;
+ pbm = g_new0(struct proto_buddy_menu, 1);
+ if (!(room = strstr(game, "&follow="))) /* skip ahead to the url */
+ return NULL;
+ while (*room && *room != '\t') /* skip to the tab */
+ room++;
+ t = room++; /* room as now at the name */
+ while (*t != '\n')
+ t++; /* replace the \n with a space */
+ *t = ' ';
+ g_snprintf(buf2, sizeof buf2, "%s", room);
+ pbm->label = buf2;
+ pbm->callback = yahoo_game;
+ pbm->gc = gc;
+ m = g_list_append(m, pbm);
+ }
+ }
+
+ return m;
+}
+
+static GList *yahoo_user_opts()
+{
+ GList *m = NULL;
+ 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->def = "5050";
+ puo->pos = USEROPT_PAGERPORT;
+ m = g_list_append(m, puo);
+
+ return m;
+}
+
+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() {
+ GList *m = NULL;
+
+ m = g_list_append(m, "Activate ID");
+
+ return m;
+}
+
+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);
+
+ return 1;
+}
+
+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);
+
+ return 0;
+}
+
+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;
+ int service;
+ char s[4];
+
+ gc->away = NULL;
+
+ if (msg) {
+ yd->current_status = YAHOO_STATUS_CUSTOM;
+ gc->away = "";
+ } else if (state) {
+ gc->away = "";
+ if (!strcmp(state, "Available")) {
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+ gc->away = NULL;
+ } 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)) {
+ if (gc->is_idle) {
+ yd->current_status = YAHOO_STATUS_IDLE;
+ } else {
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+ }
+ gc->away = NULL;
+ }
+ } else if (gc->is_idle) {
+ yd->current_status = YAHOO_STATUS_IDLE;
+ } else {
+ yd->current_status = YAHOO_STATUS_AVAILABLE;
+ }
+
+ if (yd->current_status == YAHOO_STATUS_AVAILABLE)
+ service = YAHOO_SERVICE_ISBACK;
+ else
+ 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;
+ }
+
+ if (pkt) {
+ char buf[4];
+ 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)
+{
+ GList *m = NULL;
+
+ 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);
+
+ return m;
+}
+
+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;
+ struct group *g;
+ char *group = NULL;
+
+ if (!yd->logged_in)
+ return;
+
+ g = find_group_by_buddy(gc, who);
+ if (g)
+ group = g->name;
+ else
+ group = "Buddies";
+
+ 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);
+
+
+ return smilies;
+}
+
+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;
+
+ my_protocol = ret;
+}
+
+#ifndef STATIC
+
+char *gaim_plugin_init(GModule *handle)
+{
+ load_protocol(yahoo_init, sizeof(struct prpl));
+ return NULL;
+}
+
+void gaim_plugin_remove()
+{
+ struct prpl *p = find_prpl(PROTO_YAHOO);
+ if (p == my_protocol)
+ unload_protocol(p);
+}
+
+char *name()
+{
+ return "Yahoo";
+}
+
+char *description()
+{
+ return PRPL_DESC("Yahoo");
+}
+
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/prpl.h Fri Aug 30 07:54:54 2002 -0400
@@ -0,0 +1,206 @@
+/*
+ * gaim
+ *
+ * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ *
+ * 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
+ *
+ */
+
+/* this file should be all that prpls need to include. therefore, by including
+ * this file, they should get glib, proxy, gaim_connection, prpl, etc. */
+
+#ifndef _PRPL_H_
+#define _PRPL_H_
+
+#include "core.h"
+#include "proxy.h"
+#include "multi.h"
+
+#define PROTO_TOC 0
+#define PROTO_OSCAR 1
+#define PROTO_YAHOO 2
+#define PROTO_ICQ 3
+#define PROTO_MSN 4
+#define PROTO_IRC 5
+#define PROTO_FTP 6
+#define PROTO_VGATE 7
+#define PROTO_JABBER 8
+#define PROTO_NAPSTER 9
+#define PROTO_ZEPHYR 10
+#define PROTO_GADUGADU 11
+/* DON'T TAKE AN UNASSIGNED NUMBER! Talk to Rob or Sean if you'd like
+ * to create a new PRPL. */
+
+#define PRPL_DESC(x) "Allows gaim to use the " x " protocol.\n\n" \
+ "Now that you have loaded this protocol, use the " \
+ "Account Editor to add an account that uses this " \
+ "protocol. You can access the Account Editor from " \
+ "the \"Accounts\" button on the login window or " \
+ "in the \"Tools\" menu in the buddy list window."
+
+#define DEFAULT_PROTO PROTO_OSCAR
+
+/* These should all be stuff that some plugins can do and others can't */
+/* TOC/Oscar send HTML-encoded messages; most other protocols don't */
+/* #define OPT_PROTO_HTML 0x00000001 this should be per-connection */
+/* TOC/Oscar have signon time, and the server's time needs to be adjusted to match
+ * your computer's time. We wouldn't need this if everyone used NTP. */
+#define OPT_PROTO_CORRECT_TIME 0x00000002
+/* Jabber lets you choose what name you want for chat. So it shouldn't be pulling
+ * the alias for when you're in chat; it gets annoying. */
+#define OPT_PROTO_UNIQUE_CHATNAME 0x00000004
+/* IRC, Jabber let you have chat room topics */
+#define OPT_PROTO_CHAT_TOPIC 0x00000008
+/* IRC and Zephyr don't require passwords, so there's no need for a password prompt */
+#define OPT_PROTO_NO_PASSWORD 0x00000010
+/* MSN and Yahoo notify you when you have new mail */
+#define OPT_PROTO_MAIL_CHECK 0x00000020
+/* Oscar and Jabber have buddy icons */
+#define OPT_PROTO_BUDDY_ICON 0x00000040
+/* Oscar lets you send images in direct IMs */
+#define OPT_PROTO_IM_IMAGE 0x00000080
+/* Oscar doesn't use point size, Yahoo and Jabber do */
+#define OPT_PROTO_USE_POINT_SIZE 0x00000100
+
+#define GAIM_AWAY_CUSTOM "Custom"
+
+typedef void (*proto_init)(struct prpl *);
+
+struct _prpl_smiley {
+ char *key;
+ char **xpm;
+ int show;
+};
+
+struct prpl {
+ int protocol;
+ int options;
+ char *(* name)();
+
+ /* for ICQ and Yahoo, who have off/on per-conversation options */
+ /* char *checkbox; this should be per-connection */
+
+ /* returns the XPM associated with the given user class */
+ char **(* list_icon)(int);
+ GList *(* away_states)(struct gaim_connection *gc);
+ GList *(* actions)();
+ void (* do_action)(struct gaim_connection *, char *);
+ /* user_opts returns a GList* of g_malloc'd struct proto_user_opts */
+ GList *(* user_opts)();
+ GList *(* buddy_menu)(struct gaim_connection *, char *);
+ GList *(* edit_buddy_menu)(struct gaim_connection *, char *);
+ GList *(* chat_info)(struct gaim_connection *);
+
+ GSList *(* smiley_list)();
+
+ /* all the server-related functions */
+
+ /* a lot of these (like get_dir) are protocol-dependent and should be removed. ones like
+ * set_dir (which is also protocol-dependent) can stay though because there's a dialog
+ * (i.e. the prpl says you can set your dir info, the ui shows a dialog and needs to call
+ * set_dir in order to set it) */
+
+ void (* login) (struct aim_user *);
+ void (* close) (struct gaim_connection *);
+ int (* send_im) (struct gaim_connection *, char *who, char *message, int len, int away);
+ void (* set_info) (struct gaim_connection *, char *info);
+ int (* send_typing) (struct gaim_connection *, char *name, int typing);
+ void (* get_info) (struct gaim_connection *, char *who);
+ void (* set_away) (struct gaim_connection *, char *state, char *message);
+ void (* get_away) (struct gaim_connection *, char *who);
+ void (* set_dir) (struct gaim_connection *, char *first,
+ char *middle,
+ char *last,
+ char *maiden,
+ char *city,
+ char *state,
+ char *country,
+ int web);
+ void (* get_dir) (struct gaim_connection *, char *who);
+ void (* dir_search) (struct gaim_connection *, char *first,
+ char *middle,
+ char *last,
+ char *maiden,
+ char *city,
+ char *state,
+ char *country,
+ char *email);
+ void (* set_idle) (struct gaim_connection *, int idletime);
+ void (* change_passwd) (struct gaim_connection *, char *old, char *new);
+ void (* add_buddy) (struct gaim_connection *, char *name);
+ void (* add_buddies) (struct gaim_connection *, GList *buddies);
+ void (* remove_buddy) (struct gaim_connection *, char *name, char *group);
+ void (* remove_buddies) (struct gaim_connection *, GList *buddies, char *group);
+ void (* add_permit) (struct gaim_connection *, char *name);
+ void (* add_deny) (struct gaim_connection *, char *name);
+ void (* rem_permit) (struct gaim_connection *, char *name);
+ void (* rem_deny) (struct gaim_connection *, char *name);
+ void (* set_permit_deny)(struct gaim_connection *);
+ void (* warn) (struct gaim_connection *, char *who, int anonymous);
+ void (* join_chat) (struct gaim_connection *, GList *data);
+ void (* chat_invite) (struct gaim_connection *, int id, char *who, char *message);
+ void (* chat_leave) (struct gaim_connection *, int id);
+ void (* chat_whisper) (struct gaim_connection *, int id, char *who, char *message);
+ int (* chat_send) (struct gaim_connection *, int id, char *message);
+ void (* keepalive) (struct gaim_connection *);
+
+ /* new user registration */
+ void (* register_user) (struct aim_user *);
+
+ /* get "chat buddy" info and away message */
+ void (* get_cb_info) (struct gaim_connection *, int, char *who);
+ void (* get_cb_away) (struct gaim_connection *, int, char *who);
+
+ /* save/store buddy's alias on server list/roster */
+ void (* alias_buddy) (struct gaim_connection *, char *who);
+
+ /* change a buddy's group on a server list/roster */
+ void (* group_buddy) (struct gaim_connection *, char *who, char *old_group, char *new_group);
+
+ void (* buddy_free) (struct buddy *);
+
+ /* this is really bad. */
+ void (* convo_closed) (struct gaim_connection *, char *who);
+
+ char *(* normalize)(const char *);
+};
+
+extern GSList *protocols;
+
+/* this is mostly just for aim.c, when it initializes the protocols */
+extern void static_proto_init();
+
+/* this is what should actually load the protocol. pass it the protocol's initializer */
+extern void load_protocol(proto_init, int);
+extern void unload_protocol(struct prpl *);
+
+extern struct prpl *find_prpl(int);
+extern void do_proto_menu();
+
+extern void show_got_added(struct gaim_connection *, const char *,
+ const char *, const char *, const char *);
+
+extern void do_ask_dialog(const char *, void *, void *, void *);
+extern void do_prompt_dialog(const char *, const char *, void *, void *, void *);
+
+extern void connection_has_mail(struct gaim_connection *, int, const char *, const char *, const char *);
+
+extern void set_icon_data(struct gaim_connection *, char *, void *, int);
+extern void *get_icon_data(struct gaim_connection *, char *, int *);
+
+extern GSList *add_smiley(GSList *, char *, char **, int) ;
+
+#endif /* _PRPL_H_ */