view src/protocols/trepia/trepia.c @ 6982:083d1e4a9c78

[gaim-migrate @ 7538] This is Mr. Holland's Opus. And by Mr. Holland I mean Robot101. He rewrote the coreish IM image support so that the binary data gets ripped out in the prpl and put in an imgstore instead of just being passed in the same huge as char string as the actual message. This is good because it's prpl agnostic, or something. It also means we don't have a silly length of "-1" with pretty much every send or receive IM function. It should be crash free, bug free, and memleak free, but additional testing is always a good thing. If you like good stuff then you'll love this patch. But don't take my word for it--ba dun dunt! committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Sat, 27 Sep 2003 19:17:21 +0000
parents faa491042c66
children db6bd3e794d8
line wrap: on
line source

/**
 * @file trepia.c The Trepia protocol plugin
 *
 * gaim
 *
 * Copyright (C) 2003 Christian Hammond <chipx86@gnupdate.org>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include "internal.h"

#include "account.h"
#include "accountopt.h"
#include "debug.h"
#include "notify.h"
#include "request.h"
#include "server.h"
#include "util.h"

#include "md5.h"
#include "profile.h"

/* XXX */
#include "gaim.h"
#include "multi.h"

#ifndef _WIN32
# include <sys/socket.h>
# include <sys/ioctl.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <net/if_arp.h>
#endif

#define TREPIA_VERSION "2.52"

#define TREPIA_CONNECT_STEPS 3

static GaimPlugin *my_protocol = NULL;

typedef enum
{
	TREPIA_LOGIN          = 'C',
	TREPIA_PROFILE_REQ    = 'D',
	TREPIA_MSG_OUTGOING   = 'F',
	TREPIA_REGISTER       = 'J',
	TREPIA_USER_LIST      = 'L',
	TREPIA_MEMBER_UPDATE  = 'M',
	TREPIA_MEMBER_OFFLINE = 'N',
	TREPIA_MEMBER_PROFILE = 'O',
	TREPIA_MSG_INCOMING   = 'Q'

} TrepiaMessageType;

typedef struct
{
	GaimConnection *gc;

	int fd;

	GString *rxqueue;

	GList *pending_users;
	GHashTable *user_profiles;
	GHashTable *user_ids;

} TrepiaSession;

typedef struct
{
	TrepiaMessageType *type;
	char *tag;

	GHashTable *keys;

	GString *buffer;

} TrepiaParserData;

#define TREPIA_SERVER    "trepia.com"
#define TREPIA_PORT       8201
#define TREPIA_REG_PORT   8209

static const char alphabet[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	"0123456789+/";

static char *
base64_enc(const char *data, int len)
{
	char *dest;
	char *buf;

	buf = dest = g_malloc(4 * len / 3 + 4);

	/* Encode 3 bytes at a time */
	while (len >= 3) {
		buf[0] = alphabet[(data[0] >> 2) & 0x3F];
		buf[1] = alphabet[((data[0] << 4) & 0x30) | ((data[1] >> 4) & 0x0F)];
		buf[2] = alphabet[((data[1] << 2) & 0x3C) | ((data[2] >> 6) & 0x03)];
		buf[3] = alphabet[data[2] & 0x3F];
		data += 3;
		buf += 4;
		len -= 3;
	}

	if (len > 0) {
		buf[0] = alphabet[(data[0] >> 2) & 0x3F];
		buf[1] = alphabet[(data[0] << 4) & 0x30];

		if (len > 1) {
			buf[1] += (data[1] >> 4) & 0x0F;
			buf[2] = alphabet[(data[1] << 2) & 0x3C];
		}

		else
			buf[2] = '=';

		buf[3] = '=';
		buf += 4;
	}

	*buf = '\0';

	return dest;
}


static int
trepia_write(int fd, const char *data, size_t len)
{
	gaim_debug(GAIM_DEBUG_MISC, "trepia", "C: %s%c", data,
			   (data[strlen(data) - 1] == '\n' ? '\0' : '\n'));

	return write(fd, data, len);
}

#if 0
static void
_remove_user_fnc(gpointer key, gpointer value, gpointer user_data)
{
	TrepiaSession *session;
	TrepiaProfile *profile;
	const char *name;

	name    = (const char *)key;
	profile = (TrepiaProfile *)value;
	session = (TrepiaSession *)user_data;

	gaim_blist_remove_buddy(profile->buddy);
}
#endif

static void
__clear_user_list(TrepiaSession *session)
{
	GaimBlistNode *gnode, *bnode;
	for(gnode = gaim_get_blist()->root; gnode; gnode = gnode->next) {
		bnode = gnode->child;
		while(bnode) {
			struct buddy *buddy = (struct buddy *)bnode;
			if(GAIM_BLIST_NODE_IS_BUDDY(bnode) &&
					buddy->account == session->gc->account) {
				bnode = bnode->next;
				gaim_blist_remove_buddy(buddy);
			} else {
				bnode = bnode->next;
			}
		}
	}
}

#if 0
static char *
__get_mac_address(const char *ip)
{
	char *mac = NULL;
#ifndef _WIN32
	struct sockaddr_in sin = { 0 };
	struct arpreq myarp = { { 0 } };
	int sockfd;
	unsigned char *ptr;

	sin.sin_family = AF_INET;

	if (inet_aton(ip, &sin.sin_addr) == 0) {
		gaim_debug(GAIM_DEBUG_ERROR, "trepia", "Invalid IP address %s\n", ip);
		return NULL;
	}

	memcpy(&myarp.arp_pa, &sin, sizeof(myarp.arp_pa));
	strcpy(myarp.arp_dev, "eth0");

	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		gaim_debug(GAIM_DEBUG_ERROR, "trepia",
				   "Cannot open socket for retrieving MAC address.\n");
		return NULL;
	}

	if (ioctl(sockfd, SIOCGARP, &myarp) == -1) {
		gaim_debug(GAIM_DEBUG_ERROR, "trepia",
				   "No entry in in arp_cache for %s\n", ip);
		return NULL;
	}

	ptr = &myarp.arp_ha.sa_data[0];

	mac = g_strdup_printf("%x:%x:%x:%x:%x:%x",
						  ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]);
#else
#endif

	return mac;
}
#endif

/**************************************************************************
 * Callbacks
 **************************************************************************/
#define SET_STRING_FIELD(tag, id) \
	if ((value = gaim_request_fields_get_string(fields, (id))) != NULL) { \
		buf = g_strdup_printf("%s<%s>%s</%s>", temp, (tag), value, (tag)); \
		g_free(temp); \
		temp = buf; \
	}

#define SET_INT_FIELD(tag, id) \
	int_val = gaim_request_fields_get_integer(fields, (id)); \
	buf = g_strdup_printf("%s<%s>%d</%s>", temp, (tag), int_val, (tag)); \
	g_free(temp); \
	temp = buf;

static void
save_profile_cb(GaimConnection *gc, GaimRequestFields *fields)
{
	TrepiaSession *session = gc->proto_data;
	const char *value;
	char *buf, *temp;
	int int_val;

	buf = g_strdup("<K>");
	temp = buf;

	SET_STRING_FIELD("b", "email");
	SET_STRING_FIELD("c", "homepage");
	SET_STRING_FIELD("d", "firstname");
	SET_STRING_FIELD("e", "lastname");
	SET_STRING_FIELD("f", "icq");
	SET_STRING_FIELD("g", "aim");
	SET_STRING_FIELD("h", "msn");
	SET_STRING_FIELD("i", "yahoo");
	SET_INT_FIELD(   "j", "age");

	int_val = gaim_request_fields_get_choice(fields, "gender");

	buf = g_strdup_printf("%s<k>%c</k>", temp, (int_val == 1 ? 'F' : 'M'));
	g_free(temp);
	temp = buf;

	SET_STRING_FIELD("l", "profile");
	SET_STRING_FIELD("n", "country");
	SET_STRING_FIELD("o", "state");
	SET_STRING_FIELD("p", "city");

	buf = g_strdup_printf("%s</K>", temp);

	if (trepia_write(session->fd, buf, strlen(buf)) < 0) {
		gaim_connection_error(session->gc, _("Write error"));
		return;
	}
}

static void
set_profile(GaimConnection *gc)
{
	GaimRequestFields *fields;
	GaimRequestFieldGroup *group;
	GaimRequestField *field;

	fields = gaim_request_fields_new();

	/* Basic Profile group. */
	group = gaim_request_field_group_new(_("Basic Profile"));
	gaim_request_fields_add_group(fields, group);

	/* First Name */
	field = gaim_request_field_string_new("firstname", _("First Name"), NULL,
										  FALSE);
	gaim_request_field_group_add_field(group, field);
	gaim_debug(GAIM_DEBUG_MISC, "trepia", "feld type = %d\n",
			   field->type);

	/* Last Name */
	field = gaim_request_field_string_new("lastname", _("Last Name"), NULL,
										  FALSE);
	gaim_request_field_group_add_field(group, field);

	/* Gender */
	field = gaim_request_field_choice_new("gender", _("Gender"), 0);
	gaim_request_field_choice_add(field, _("Male"));
	gaim_request_field_choice_add(field, _("Female"));
	gaim_request_field_group_add_field(group, field);

	/* Age */
	field = gaim_request_field_int_new("age", _("Age"), 0);
	gaim_request_field_group_add_field(group, field);

	/* Homepage */
	field = gaim_request_field_string_new("homepage", _("Homepage"), NULL,
										  FALSE);
	gaim_request_field_group_add_field(group, field);

	/* E-Mail Address */
	field = gaim_request_field_string_new("email", _("E-Mail Address"), NULL,
										  FALSE);
	gaim_request_field_group_add_field(group, field);

	/* Profile String */
	field = gaim_request_field_string_new("profile",
										  _("Profile Information"), NULL,
										  TRUE);
	gaim_request_field_group_add_field(group, field);


	/* Instant Messagers */
	group = gaim_request_field_group_new(_("Instant Messagers"));
	gaim_request_fields_add_group(fields, group);

	/* AIM */
	field = gaim_request_field_string_new("aim", _("AIM"), NULL, FALSE);
	gaim_request_field_group_add_field(group, field);

	/* ICQ */
	field = gaim_request_field_string_new("icq", _("ICQ UIN"), NULL, FALSE);
	gaim_request_field_group_add_field(group, field);

	/* MSN */
	field = gaim_request_field_string_new("msn", _("MSN"), NULL, FALSE);
	gaim_request_field_group_add_field(group, field);

	/* Yahoo */
	field = gaim_request_field_string_new("yahoo", _("Yahoo"), NULL, FALSE);
	gaim_request_field_group_add_field(group, field);


	/* I'm From */
	group = gaim_request_field_group_new(_("I'm From"));
	gaim_request_fields_add_group(fields, group);

	/* City */
	field = gaim_request_field_string_new("city", _("City"), NULL, FALSE);
	gaim_request_field_group_add_field(group, field);

	/* State */
	field = gaim_request_field_string_new("state", _("State"), NULL, FALSE);
	gaim_request_field_group_add_field(group, field);

	/* Country */
	field = gaim_request_field_string_new("country", _("Country"), NULL, FALSE);
	gaim_request_field_group_add_field(group, field);


	/* Call the dialog. */
	gaim_request_fields(gc, NULL, _("Set your Trepia profile data."),
						NULL, fields,
						_("Save"), G_CALLBACK(save_profile_cb),
						_("Cancel"), NULL, gc);
}

/**************************************************************************
 * Protocol Plugin ops
 **************************************************************************/

static const char *
trepia_list_icon(GaimAccount *a, struct buddy *b)
{
	return "trepia";
}

static void
trepia_list_emblems(struct buddy *b, char **se, char **sw,
				 char **nw, char **ne)
{
	TrepiaProfile *profile = (TrepiaProfile *)b->proto_data;

	if (trepia_profile_get_sex(profile) == 'M')
		*sw = "male";
	else if (trepia_profile_get_sex(profile))
		*sw = "female";
}

static char *
trepia_status_text(struct buddy *b)
{
	TrepiaProfile *profile = (TrepiaProfile *)b->proto_data;
	const char *value;
	char *text = NULL;

	if ((value = trepia_profile_get_profile(profile)) != NULL)
		text = g_markup_escape_text(value, -1);

	return text;
}

static char *
trepia_tooltip_text(struct buddy *b)
{
	TrepiaProfile *profile = (TrepiaProfile *)b->proto_data;
	const char *value;
	const char *first_name, *last_name;
	int int_value;
	char *text = NULL;
	char *tmp, *tmp2;

	text = g_strdup("");

	first_name = trepia_profile_get_first_name(profile);
	last_name  = trepia_profile_get_last_name(profile);

	if (first_name != NULL || last_name != NULL) {
	    tmp = g_strdup_printf("<b>%s:</b> %s%s%s\n",_("Name"),
							  (first_name == NULL ? "" : first_name),
							  (first_name == NULL ? "" : " "),
							  (last_name == NULL ? "" : last_name));

		tmp2 = g_strconcat(text, tmp, NULL);
		g_free(tmp);
		g_free(text);
		text = tmp2;
	}

	if ((int_value = trepia_profile_get_age(profile)) != 0) {
		tmp = g_strdup_printf("<b>%s:</b> %d\n", _("Age"),int_value);

		tmp2 = g_strconcat(text, tmp, NULL);
		g_free(tmp);
		g_free(text);
		text = tmp2;
	}

	tmp = g_strdup_printf("<b>%s:</b> %s\n",_("Gender"),
			(trepia_profile_get_sex(profile) == 'F' ? _("Female") : _("Male")));

	tmp2 = g_strconcat(text, tmp, NULL);
	g_free(tmp);
	g_free(text);
	text = tmp2;

	if ((value = trepia_profile_get_city(profile)) != NULL) {
		tmp = g_strdup_printf("<b>%s:</b> %s\n", _("City"), value);

		tmp2 = g_strconcat(text, tmp, NULL);
		g_free(tmp);
		g_free(text);
		text = tmp2;
	}

	if ((value = trepia_profile_get_state(profile)) != NULL) {
		tmp = g_strdup_printf("<b>%s:</b> %s\n", _("State"), value);

		tmp2 = g_strconcat(text, tmp, NULL);
		g_free(tmp);
		g_free(text);
		text = tmp2;
	}

	if ((value = trepia_profile_get_country(profile)) != NULL) {
		tmp = g_strdup_printf("<b>%s:</b> %s\n", _("Country"), value);

		tmp2 = g_strconcat(text, tmp, NULL);
		g_free(tmp);
		g_free(text);
		text = tmp2;
	}

	if ((value = trepia_profile_get_homepage(profile)) != NULL) {
		tmp = g_strdup_printf("<b>%s:</b> %s\n", _("Homepage"), value);

		tmp2 = g_strconcat(text, tmp, NULL);
		g_free(tmp);
		g_free(text);
		text = tmp2;
	}

	if ((value = trepia_profile_get_profile(profile)) != NULL) {
		char *escaped_val = g_markup_escape_text(value, -1);

		tmp = g_strdup_printf("<b>%s:</b> %s\n", _("Profile"), escaped_val);

		g_free(escaped_val);

		tmp2 = g_strconcat(text, tmp, NULL);
		g_free(tmp);
		g_free(text);
		text = tmp2;
	}

	text[strlen(text) - 1] = '\0';

	return text;
}

static GList *
trepia_actions(GaimConnection *gc)
{
	GList *m = NULL;
	struct proto_actions_menu *pam;

	pam = g_new0(struct proto_actions_menu, 1);
	pam->label = _("Set Profile");
	pam->callback = set_profile;
	pam->gc = gc;
	m = g_list_append(m, pam);

	return m;
}

static void
trepia_visit_homepage(GaimConnection *gc, const char *who)
{
	TrepiaProfile *profile;
	struct buddy *b;
	const char *value;

	b = gaim_find_buddy(gaim_connection_get_account(gc), who);
	profile = b->proto_data;

	if ((value = trepia_profile_get_homepage(profile)) != NULL)
		gaim_notify_uri(gc, value);
}

static GList *
trepia_buddy_menu(GaimConnection *gc, const char *who)
{
	TrepiaProfile *profile;
	struct buddy *b;
	const char *value = NULL;
	GList *m = NULL;
	struct proto_buddy_menu *pbm;

	b = gaim_find_buddy(gaim_connection_get_account(gc), who);
	profile = b->proto_data;

	if ((value = trepia_profile_get_homepage(profile)) != NULL) {
		pbm = g_new0(struct proto_buddy_menu, 1);
		pbm->label = _("Visit Homepage");
		pbm->callback = trepia_visit_homepage;
		pbm->gc = gc;
		m = g_list_append(m, pbm);
	}

	return m;
}

static void
__free_parser_data(gpointer user_data)
{
#if 0
	TrepiaParserData *data = user_data;

	if (data->buffer != NULL)
		g_string_free(data->buffer, TRUE);

	if (data->tag != NULL)
		g_free(data->tag);

	g_free(data);
#endif
}

static void
__start_element_handler(GMarkupParseContext *context,
						const gchar *element_name,
						const gchar **attribute_names,
						const gchar **attribute_values,
						gpointer user_data, GError **error)
{
	TrepiaParserData *data = user_data;

	if (data->buffer != NULL) {
		g_string_free(data->buffer, TRUE);
		data->buffer = NULL;
	}

	if (*data->type == 0) {
		*data->type = *element_name;
	}
	else {
		data->tag = g_strdup(element_name); /* XXX - Make sure this is freed */
	}
}

static void
__end_element_handler(GMarkupParseContext *context, const gchar *element_name,
					  gpointer user_data,  GError **error)
{
	TrepiaParserData *data = user_data;
	gchar *buffer;

	if (*element_name == *data->type)
		return;

	if (data->buffer == NULL || data->tag == NULL) {
		data->tag = NULL;
		return;
	}

	buffer = g_string_free(data->buffer, FALSE);
	data->buffer = NULL;

	if (buffer != NULL) {
		if (*buffer != '\0')
			g_hash_table_insert(data->keys, data->tag, buffer);
		else
			free(buffer);
	}

	data->tag = NULL;
}

static void
__text_handler(GMarkupParseContext *context, const gchar *text,
			   gsize text_len, gpointer user_data, GError **error)
{
	TrepiaParserData *data = user_data;

	if (data->buffer == NULL)
		data->buffer = g_string_new_len(text, text_len);
	else
		g_string_append_len(data->buffer, text, text_len);
}

static GMarkupParser accounts_parser =
{
	__start_element_handler,
	__end_element_handler,
	__text_handler,
	NULL,
	NULL
};

static int
__parse_message(const char *buf, TrepiaMessageType *type, GHashTable **info)
{
	TrepiaParserData *parser_data = g_new0(TrepiaParserData, 1);
	GMarkupParseContext *context;
	GHashTable *keys;

	keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
	parser_data->keys = keys;
	parser_data->type = type;

	context = g_markup_parse_context_new(&accounts_parser, 0,
										 parser_data, __free_parser_data);

	if (!g_markup_parse_context_parse(context, buf, strlen(buf), NULL)) {
		g_markup_parse_context_free(context);
		g_free(parser_data);
		g_hash_table_destroy(keys);

		return 1;
	}

	if (!g_markup_parse_context_end_parse(context, NULL)) {
		g_markup_parse_context_free(context);
		g_free(parser_data);
		g_hash_table_destroy(keys);

		return 1;
	}

	g_markup_parse_context_free(context);
	g_free(parser_data);
	*info = keys;

	return 0;
}

static gboolean
_parse_data(TrepiaSession *session, char *buf)
{
	GHashTable *info;
	GaimAccount *account;
	TrepiaMessageType type = 0;
	TrepiaProfile *profile = NULL;
	int ret;
	char *buffer;
	struct buddy *b;
	int id = 0;
	const char *value;
	char *username;
	int *int_p;
	GMainContext *ctx;

	account = gaim_connection_get_account(session->gc);

	ret = __parse_message(buf, &type, &info);

	if (ret == 1)
		return TRUE;

	if (info != NULL) {
		switch (type) {
			case TREPIA_USER_LIST:
				gaim_connection_update_progress(session->gc,
						_("Retrieving buddy list"), 2, TREPIA_CONNECT_STEPS);

				gaim_connection_set_state(session->gc, GAIM_CONNECTED);
				serv_finish_login(session->gc);
				break;

			case TREPIA_MSG_INCOMING: /* Incoming Message */
				id = atoi(g_hash_table_lookup(info, "a"));

				profile = g_hash_table_lookup(session->user_profiles, &id);
				serv_got_im(session->gc,
							trepia_profile_get_login(profile),
							(char *)g_hash_table_lookup(info, "b"),
							0, time(NULL));
				break;

			case TREPIA_MEMBER_UPDATE:
				profile = trepia_profile_new();

				if ((value = g_hash_table_lookup(info, "a")) != NULL) {
					id = atoi(value);
					trepia_profile_set_id(profile, id);
				}

				if ((value = g_hash_table_lookup(info, "b")) != NULL)
					trepia_profile_set_login_time(profile, atoi(value));

				if ((value = g_hash_table_lookup(info, "c")) != NULL)
					trepia_profile_set_type(profile, atoi(value));
				else
					trepia_profile_set_type(profile, 2);

				session->pending_users = g_list_append(session->pending_users,
													   profile);


#if 0
				if (trepia_profile_get_type(profile) == 1) {
					buffer = g_strdup_printf(
						"<D>"
						"<a>%d</a>"
						"<b>1</b>"
						"</D>",
						id);
				}
				else {
#endif
					buffer = g_strdup_printf(
						"<D>"
						"<a>%d</a>"
						"<b>1</b>"
						"</D>"
						"<D>"
						"<a>%d</a>"
						"<b>2</b>"
						"</D>",
						id,
						id);
#if 0
				}
#endif

				if (trepia_write(session->fd, buffer, strlen(buffer)) < 0) {
					gaim_connection_error(session->gc, _("Write error"));
					g_free(buffer);
					return 1;
				}

				g_free(buffer);
				break;

			case TREPIA_MEMBER_PROFILE:
				if ((value = g_hash_table_lookup(info, "a")) != NULL) {
					GList *l;

					id = atoi(value);

					for (l = session->pending_users; l != NULL; l = l->next) {
						profile = l->data;

						if (trepia_profile_get_id(profile) == id)
							break;

						profile = NULL;
					}
				}
				else
					break;

				if (profile == NULL) {
					profile = g_hash_table_lookup(session->user_profiles, &id);

					if (profile == NULL)
						break;
				}

				/* Age */
				if ((value = g_hash_table_lookup(info, "m")) != NULL)
					trepia_profile_set_age(profile, atoi(value));

				/* ICQ */
				if ((value = g_hash_table_lookup(info, "i")) != NULL)
					trepia_profile_set_icq(profile, atoi(value));

				/* Sex */
				if ((value = g_hash_table_lookup(info, "n")) != NULL)
					trepia_profile_set_sex(profile, *value);

				/* Location */
				if ((value = g_hash_table_lookup(info, "p")) != NULL)
					trepia_profile_set_location(profile, value);

				/* First Name */
				if ((value = g_hash_table_lookup(info, "g")) != NULL)
					trepia_profile_set_first_name(profile, value);

				/* Last Name */
				if ((value = g_hash_table_lookup(info, "h")) != NULL)
					trepia_profile_set_last_name(profile, value);

				/* Profile */
				if ((value = g_hash_table_lookup(info, "o")) != NULL)
					trepia_profile_set_profile(profile, value);

				/* E-mail */
				if ((value = g_hash_table_lookup(info, "e")) != NULL)
					trepia_profile_set_email(profile, value);

				/* AIM */
				if ((value = g_hash_table_lookup(info, "j")) != NULL)
					trepia_profile_set_aim(profile, value);

				/* MSN */
				if ((value = g_hash_table_lookup(info, "k")) != NULL)
					trepia_profile_set_msn(profile, value);

				/* Yahoo */
				if ((value = g_hash_table_lookup(info, "l")) != NULL)
					trepia_profile_set_yahoo(profile, value);

				/* Homepage */
				if ((value = g_hash_table_lookup(info, "f")) != NULL)
					trepia_profile_set_homepage(profile, value);

				/* Country */
				if ((value = g_hash_table_lookup(info, "r")) != NULL)
					trepia_profile_set_country(profile, value);

				/* State */
				if ((value = g_hash_table_lookup(info, "s")) != NULL)
					trepia_profile_set_state(profile, value);

				/* City */
				if ((value = g_hash_table_lookup(info, "t")) != NULL)
					trepia_profile_set_city(profile, value);

				/* Languages */
				if ((value = g_hash_table_lookup(info, "u")) != NULL)
					trepia_profile_set_languages(profile, value);

				/* School */
				if ((value = g_hash_table_lookup(info, "v")) != NULL)
					trepia_profile_set_school(profile, value);

				/* Company */
				if ((value = g_hash_table_lookup(info, "w")) != NULL)
					trepia_profile_set_company(profile, value);

				/* Login Name */
				if ((value = g_hash_table_lookup(info, "d")) != NULL) {
					trepia_profile_set_login(profile, value);
					username = g_strdup(value);
				}
				else if ((value = trepia_profile_get_login(profile)) != NULL) {
					username = g_strdup(value);
				}
				else {
					username = g_strdup_printf("%d", id);
					trepia_profile_set_login(profile, username);
				}

				b = gaim_find_buddy(account, username);

				if (b == NULL) {
					struct group *g;

					g = gaim_find_group(_("Local Users"));

					if (g == NULL) {
						g = gaim_group_new(_("Local Users"));
						gaim_blist_add_group(g, NULL);
					}

					b = gaim_buddy_new(account, username, NULL);

					gaim_blist_add_buddy(b, g, NULL);
				}

				profile->buddy = b;

				b->proto_data = profile;

				session->pending_users = g_list_remove(session->pending_users,
													   profile);

				int_p = g_new0(int, 1);
				*int_p = id;
				g_hash_table_insert(session->user_profiles, int_p, profile);

				serv_got_update(session->gc,
								username, 1, 0,
								trepia_profile_get_login_time(profile), 0, 0);

				/* Buddy Icon */
				if ((value = g_hash_table_lookup(info, "q")) != NULL) {
					char *icon;
					int icon_len;

					frombase64(value, &icon, &icon_len);

					set_icon_data(session->gc, username, icon, icon_len);

					g_free(icon);

					serv_got_update(session->gc, username, 1, 0, 0, 0, 0);
				}

				ctx = g_main_context_default();

				while (g_main_context_pending(ctx))
					g_main_context_iteration(ctx, FALSE);

				g_free(username);

				break;

			case TREPIA_MEMBER_OFFLINE:
				if ((value = g_hash_table_lookup(info, "a")) != NULL)
					id = atoi(value);
				else
					break;

				profile = g_hash_table_lookup(session->user_profiles, &id);

				if (profile == NULL)
					break;

				g_hash_table_remove(session->user_profiles, &id);

				b = profile->buddy;

				if (b != NULL)
					serv_got_update(session->gc,
									trepia_profile_get_login(profile),
									0, 0, 0, 0, 0);

				gaim_blist_remove_buddy(b);

				break;

			default:
				break;
		}

		g_hash_table_destroy(info);
	}
	else {
		gaim_debug(GAIM_DEBUG_WARNING, "trepia",
				   "Unknown data received. Possibly an image?\n");
	}

	return TRUE;
}

static void
_data_cb(gpointer data, gint source, GaimInputCondition cond)
{
	TrepiaSession *session = data;
	int i = 0;
	char buf[1025];
	gboolean cont = TRUE;

	i = read(session->fd, buf, 1024);

	if (i <= 0) {
		gaim_connection_error(session->gc, _("Read error"));
		return;
	}

	buf[i] = '\0';

	if (session->rxqueue == NULL)
		session->rxqueue = g_string_new(buf);
	else
		g_string_append(session->rxqueue, buf);

	while (cont) {
		char end_tag[5] = "</ >";
		char *end_s;

		end_tag[2] = session->rxqueue->str[1];

		end_s = strstr(session->rxqueue->str, end_tag);

		if (end_s != NULL) {
			char *buffer;
			size_t len;
			int ret;

			end_s += 4;

			len = end_s - session->rxqueue->str;
			buffer = g_new0(char, len + 1);
			strncpy(buffer, session->rxqueue->str, len);

			g_string_erase(session->rxqueue, 0, len);

			if (*session->rxqueue->str == '\n')
				g_string_erase(session->rxqueue, 0, 1);

			gaim_debug(GAIM_DEBUG_MISC, "trepia", "S: %s\n", buffer);

			ret = _parse_data(session, buffer);

			g_free(buffer);
		}
		else
			break;
	}
}

static void
__login_cb(gpointer data, gint source, GaimInputCondition cond)
{
	TrepiaSession *session = data;
	GaimAccount *account;
	const char *password;
	char *buffer;
	char *mac = "00:01:02:03:04:05";
	char buf[3];
	char md5_password[17];
	md5_state_t st;
	md5_byte_t di[16];
	int i;

	if (source < 0) {
		gaim_connection_error(session->gc, _("Write error"));
		return;
	}

#if 0
	mac = __get_mac_address();
#endif

	session->fd = source;

	account = gaim_connection_get_account(session->gc);

	password = gaim_account_get_password(account);

	md5_init(&st);
	md5_append(&st, (const md5_byte_t *)password, strlen(password));
	md5_finish(&st, di);

	*md5_password = '\0';

	for (i = 0; i < 16; i++) {
		g_snprintf(buf, sizeof(buf), "%02x", di[i]);
		strcat(md5_password, buf);
	}

	buffer = g_strdup_printf(
		"<C>\n"
		"<a>%s</a>\n"
		"<b1></b1>\n"
		"<c>%s</c>\n"
		"<d>%s</d>\n"
		"<e>%s</e>\n"
		"</C>",
		mac, gaim_account_get_username(account),
		md5_password, TREPIA_VERSION);

#if 0
	g_free(mac);
#endif

	gaim_connection_update_progress(session->gc, _("Logging in"), 1,
									TREPIA_CONNECT_STEPS);

	if (trepia_write(session->fd, buffer, strlen(buffer)) < 0) {
		gaim_connection_error(session->gc, _("Write error"));
		return;
	}

	g_free(buffer);

	session->gc->inpa = gaim_input_add(session->fd, GAIM_INPUT_READ,
									   _data_cb, session);
}

static void
trepia_login(GaimAccount *account)
{
	GaimConnection *gc;
	TrepiaSession *session;
	const char *server;
	int port;
	int i;

	server = gaim_account_get_string(account, "server", TREPIA_SERVER);
	port   = gaim_account_get_int(account,    "port",   TREPIA_PORT);

	gc = gaim_account_get_connection(account);

	session = g_new0(TrepiaSession, 1);
	gc->proto_data = session;
	session->gc = gc;
	session->fd = -1;
	session->user_profiles = g_hash_table_new_full(g_int_hash, g_int_equal,
												   g_free, NULL);

	__clear_user_list(session);

	gaim_connection_update_progress(gc, _("Connecting"), 0,
									TREPIA_CONNECT_STEPS);

	i = gaim_proxy_connect(account, server, port, __login_cb, session);

	if (i != 0)
		gaim_connection_error(gc, _("Unable to create socket"));
}

static void
trepia_close(GaimConnection *gc)
{
	TrepiaSession *session = gc->proto_data;

	if (session->rxqueue != NULL)
		g_string_free(session->rxqueue, TRUE);

	if (session->gc->inpa)
		gaim_input_remove(session->gc->inpa);

	gaim_debug(GAIM_DEBUG_INFO, "trepia", "Destroying user_profiles\n");
	g_hash_table_destroy(session->user_profiles);
	gaim_debug(GAIM_DEBUG_INFO, "trepia", "Destroying pending_users\n");
	g_list_free(session->pending_users);

	gaim_debug(GAIM_DEBUG_INFO, "trepia", "Closing socket\n");
	close(session->fd);

	g_free(session);

	gc->proto_data = NULL;
	gaim_debug(GAIM_DEBUG_INFO, "trepia", "Leaving trepia_close\n");
}

static int
trepia_send_im(GaimConnection *gc, const char *who, const char *message,
			GaimImFlags flags)
{
	TrepiaSession *session = gc->proto_data;
	TrepiaProfile *profile;
	struct buddy *b;
	char *escaped_msg;
	char *buffer;

	b = gaim_find_buddy(gaim_connection_get_account(gc), who);

	if (b == NULL) {
		gaim_debug(GAIM_DEBUG_ERROR, "trepia",
				   "Unable to send to buddy not on your list!\n");
		return 0;
	}

	profile = b->proto_data;

	escaped_msg = g_markup_escape_text(message, -1);

	buffer = g_strdup_printf(
		"<F>\n"
		"<a>%d</a>\n"
		"<b>%s</b>\n"
		"</F>",
		trepia_profile_get_id(profile), escaped_msg);

	g_free(escaped_msg);

	if (trepia_write(session->fd, buffer, strlen(buffer)) < 0) {
		gaim_connection_error(gc, _("Write error"));
		g_free(buffer);
		return 1;
	}

	return 1;
}

static void
trepia_add_buddy(GaimConnection *gc, const char *name, GaimGroup *group)
{
}

static void
trepia_rem_buddy(GaimConnection *gc, const char *who, const char *group)
{
}

static void
trepia_buddy_free(struct buddy *b)
{
	if (b->proto_data != NULL) {
		trepia_profile_destroy(b->proto_data);

		b->proto_data = NULL;
	}
}

static void
trepia_set_buddy_icon(GaimConnection *gc, const char *filename)
{
	TrepiaSession *session = gc->proto_data;
	struct stat sb;

	if (!stat(filename, &sb)) {
		FILE *fp;

		if ((fp = fopen(filename, "rb")) != NULL) {
			char *buf = g_malloc(sb.st_size + 1);
			char *temp;
			char *out_buf;

			fread(buf, 1, sb.st_size, fp);

			buf[sb.st_size] = '\0';

			temp = base64_enc(buf, sb.st_size);

			out_buf = g_strdup_printf("<K><m>%s</m></K>", temp);

			g_free(temp);
			g_free(buf);

			fclose(fp);

			if (trepia_write(session->fd, out_buf, strlen(out_buf)) < 0) {
				gaim_connection_error(session->gc, _("Write error"));
				return;
			}
		}
	}
}

static void
trepia_register_user(GaimAccount *account)
{
	char *buffer;
	char *mac = "00:01:02:03:04:05";

	buffer = g_strdup_printf(
		"<J><a>%s</a><b1>%s</b1><c>%s</c><d>%s</d><e>%s</e>"
		"<f></f><g></g><h></h><i></i><j></j><k></k><l></l>"
		"<m></m></J>",
		mac, "", TREPIA_VERSION, gaim_account_get_username(account),
		gaim_account_get_password(account));
}

static GaimPluginProtocolInfo prpl_info =
{
	GAIM_PROTO_TREPIA,
	OPT_PROTO_BUDDY_ICON,
	NULL,
	NULL,
	trepia_list_icon,
	trepia_list_emblems,
	trepia_status_text,
	trepia_tooltip_text,
	NULL,
	trepia_actions,
	trepia_buddy_menu,
	NULL,
	trepia_login,
	trepia_close,
	trepia_send_im,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	trepia_add_buddy,
	NULL,
	trepia_rem_buddy,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	trepia_register_user,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	trepia_buddy_free,
	NULL,
	NULL,
	trepia_set_buddy_icon
};

static GaimPluginInfo info =
{
	2,                                                /**< api_version    */
	GAIM_PLUGIN_PROTOCOL,                             /**< type           */
	NULL,                                             /**< ui_requirement */
	0,                                                /**< flags          */
	NULL,                                             /**< dependencies   */
	GAIM_PRIORITY_DEFAULT,                            /**< priority       */

	"prpl-trepia",                                    /**< id             */
	"Trepia",                                         /**< name           */
	VERSION,                                          /**< version        */
	                                                  /**  summary        */
	N_("Trepia Protocol Plugin"),
	                                                  /**  description    */
	N_("Trepia Protocol Plugin"),
	"Christian Hammond <chipx86@gnupdate.org>",       /**< author         */
	GAIM_WEBSITE,                                     /**< homepage       */

	NULL,                                             /**< load           */
	NULL,                                             /**< unload         */
	NULL,                                             /**< destroy        */

	NULL,                                             /**< ui_info        */
	&prpl_info                                        /**< extra_info     */
};

static void
init_plugin(GaimPlugin *plugin)
{
	GaimAccountOption *option;

	option = gaim_account_option_string_new(_("Login server"), "server",
											TREPIA_SERVER);
	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
											   option);

	option = gaim_account_option_int_new(_("Port"), "port", TREPIA_PORT);
	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
											   option);

	my_protocol = plugin;
}

GAIM_INIT_PLUGIN(trepia, init_plugin, info);