/*
 * libyahoo2: libyahoo2.c
 *
 * Some code copyright (C) 2002-2004, Philip S Tellis <philip.tellis AT gmx.net>
 * YMSG16 code copyright (C) 2009, 
 * 		Siddhesh Poyarekar <siddhesh dot poyarekar at gmail dot com>
 *
 * Yahoo Search copyright (C) 2003, Konstantin Klyagin <konst AT konst.org.ua>
 *
 * Much of this code was taken and adapted from the yahoo module for
 * gaim released under the GNU GPL.  This code is also released under the 
 * GNU GPL.
 *
 * This code is derivitive of Gaim <http://gaim.sourceforge.net>
 * copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
 *	       1998-1999, Adam Fritzler <afritz@marko.net>
 *	       1998-2002, Rob Flynn <rob@marko.net>
 *	       2000-2002, Eric Warmenhoven <eric@warmenhoven.org>
 *	       2001-2002, Brian Macke <macke@strangelove.net>
 *		    2001, Anand Biligiri S <abiligiri@users.sf.net>
 *		    2001, Valdis Kletnieks
 *		    2002, Sean Egan <bj91704@binghamton.edu>
 *		    2002, Toby Gray <toby.gray@ntlworld.com>
 *
 * This library also uses code from other libraries, namely:
 *     Portions from libfaim copyright 1998, 1999 Adam Fritzler
 *     <afritz@auk.cx>
 *     Portions of Sylpheed copyright 2000-2002 Hiroyuki Yamamoto
 *     <hiro-y@kcn.ne.jp>
 *
 * YMSG16 authentication code based mostly on write-up at:
 * 	http://www.carbonize.co.uk/ymsg16.html
 *
 * 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
 *
 */

#ifndef _WIN32
#include <unistd.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>

#if STDC_HEADERS
# include <string.h>
#else
# if !HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
# if !HAVE_MEMCPY
#  define memcpy(d, s, n) bcopy ((s), (d), (n))
#  define memmove(d, s, n) bcopy ((s), (d), (n))
# endif
#endif

#include <sys/types.h>

#ifdef __MINGW32__
# include <winsock2.h>
#endif

#include <stdlib.h>
#include <ctype.h>

#include "sha1.h"
#include "md5.h"
#include "yahoo2.h"
#include "yahoo_httplib.h"
#include "yahoo_util.h"

#include "yahoo2_callbacks.h"
#include "yahoo_debug.h"
#if defined(__MINGW32__) && !defined(HAVE_GLIB)
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif

#include "base64.h"
#include "http_client.h"

#ifdef USE_STRUCT_CALLBACKS
struct yahoo_callbacks *yc = NULL;

void yahoo_register_callbacks(struct yahoo_callbacks *tyc)
{
	yc = tyc;
}

#define YAHOO_CALLBACK(x)	yc->x
#else
#define YAHOO_CALLBACK(x)	x
#endif

static int yahoo_send_data(void *fd, void *data, int len);
static void _yahoo_http_connected(int id, void *fd, int error, void *data);
static void yahoo_connected(void *fd, int error, void *data);

int yahoo_log_message(char *fmt, ...)
{
	char out[1024];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(out, sizeof(out), fmt, ap);
	va_end(ap);
	return YAHOO_CALLBACK(ext_yahoo_log) ("%s", out);
}

static enum yahoo_log_level log_level = YAHOO_LOG_NONE;

enum yahoo_log_level yahoo_get_log_level()
{
	return log_level;
}

int yahoo_set_log_level(enum yahoo_log_level level)
{
	enum yahoo_log_level l = log_level;
	log_level = level;
	return l;
}

/* default values for servers */
static char *default_pager_hosts[] = {	"scs.msg.yahoo.com",
					"scsa.msg.yahoo.com",
					"scsb.msg.yahoo.com",
					"scsc.msg.yahoo.com",
					NULL};

static int pager_port = 5050;
static int fallback_ports[] = { 23, 25, 80, 20, 119, 8001, 8002, 5050, 0 };

static char filetransfer_host[] = "filetransfer.msg.yahoo.com";
static int filetransfer_port = 80;
static char webcam_host[] = "webcam.yahoo.com";
static int webcam_port = 5100;
static char webcam_description[] = "";
static char local_host[] = "";
static int conn_type = Y_WCM_DSL;

static char profile_url[] = "http://profiles.yahoo.com/";

struct connect_callback_data {
	struct yahoo_data *yd;
	int tag;
	int i;
	int server_i;
};

struct yahoo_pair {
	int key;
	char *value;
};

struct yahoo_packet {
	unsigned short int service;
	unsigned int status;
	unsigned int id;
	YList *hash;
};

struct yahoo_search_state {
	int lsearch_type;
	char *lsearch_text;
	int lsearch_gender;
	int lsearch_agerange;
	int lsearch_photo;
	int lsearch_yahoo_only;
	int lsearch_nstart;
	int lsearch_nfound;
	int lsearch_ntotal;
};

struct data_queue {
	unsigned char *queue;
	int len;
};

struct yahoo_input_data {
	struct yahoo_data *yd;
	struct yahoo_webcam *wcm;
	struct yahoo_webcam_data *wcd;
	struct yahoo_search_state *ys;

	void *fd;
	enum yahoo_connection_type type;

	unsigned char *rxqueue;
	int rxlen;
	int read_tag;

	YList *txqueues;
	int write_tag;
};

struct yahoo_server_settings {
	char *pager_host;
	int pager_port;
	char *filetransfer_host;
	int filetransfer_port;
	char *webcam_host;
	int webcam_port;
	char *webcam_description;
	char *local_host;
	int conn_type;
	char **pager_host_list;
};

static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over);

static void yahoo_process_filetransfer(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt);
static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt);
static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt);

static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn);

static void *_yahoo_default_server_settings()
{
	struct yahoo_server_settings *yss =
		y_new0(struct yahoo_server_settings, 1);

	/* Give preference to the default host list
	 * Make sure that only one of the two is set at any time 
	 */
	yss->pager_host = NULL;
	yss->pager_host_list = default_pager_hosts;

	yss->pager_port = pager_port;
	yss->filetransfer_host = strdup(filetransfer_host);
	yss->filetransfer_port = filetransfer_port;
	yss->webcam_host = strdup(webcam_host);
	yss->webcam_port = webcam_port;
	yss->webcam_description = strdup(webcam_description);
	yss->local_host = strdup(local_host);
	yss->conn_type = conn_type;

	return yss;
}

static void *_yahoo_assign_server_settings(va_list ap)
{
	struct yahoo_server_settings *yss = _yahoo_default_server_settings();
	char *key;
	char *svalue;
	int nvalue;
	char **pvalue;

	while (1) {
		key = va_arg(ap, char *);
		if (key == NULL)
			break;

		if (!strcmp(key, "pager_host")) {
			svalue = va_arg(ap, char *);
			free(yss->pager_host);
			yss->pager_host = strdup(svalue);
			yss->pager_host_list = NULL;
		} else if (!strcmp(key, "pager_host_list")) {
			pvalue = va_arg(ap, char **);
			yss->pager_host_list = pvalue;
			free(yss->pager_host);
			yss->pager_host = NULL;
		} else if (!strcmp(key, "pager_port")) {
			nvalue = va_arg(ap, int);
			yss->pager_port = nvalue;
		} else if (!strcmp(key, "filetransfer_host")) {
			svalue = va_arg(ap, char *);
			free(yss->filetransfer_host);
			yss->filetransfer_host = strdup(svalue);
		} else if (!strcmp(key, "filetransfer_port")) {
			nvalue = va_arg(ap, int);
			yss->filetransfer_port = nvalue;
		} else if (!strcmp(key, "webcam_host")) {
			svalue = va_arg(ap, char *);
			free(yss->webcam_host);
			yss->webcam_host = strdup(svalue);
		} else if (!strcmp(key, "webcam_port")) {
			nvalue = va_arg(ap, int);
			yss->webcam_port = nvalue;
		} else if (!strcmp(key, "webcam_description")) {
			svalue = va_arg(ap, char *);
			free(yss->webcam_description);
			yss->webcam_description = strdup(svalue);
		} else if (!strcmp(key, "local_host")) {
			svalue = va_arg(ap, char *);
			free(yss->local_host);
			yss->local_host = strdup(svalue);
		} else if (!strcmp(key, "conn_type")) {
			nvalue = va_arg(ap, int);
			yss->conn_type = nvalue;
		} else {
			WARNING(("Unknown key passed to yahoo_init, "
					"perhaps you didn't terminate the list "
					"with NULL"));
		}
	}

	return yss;
}

static void yahoo_free_server_settings(struct yahoo_server_settings *yss)
{
	if (!yss)
		return;

	free(yss->pager_host);
	free(yss->filetransfer_host);
	free(yss->webcam_host);
	free(yss->webcam_description);
	free(yss->local_host);

	free(yss);
}

static YList *conns = NULL;
static YList *inputs = NULL;
static int last_id = 0;

static void add_to_list(struct yahoo_data *yd)
{
	conns = y_list_prepend(conns, yd);
}

static struct yahoo_data *find_conn_by_id(int id)
{
	YList *l;
	for (l = conns; l; l = y_list_next(l)) {
		struct yahoo_data *yd = l->data;
		if (yd->client_id == id)
			return yd;
	}
	return NULL;
}

static void del_from_list(struct yahoo_data *yd)
{
	conns = y_list_remove(conns, yd);
}

/* call repeatedly to get the next one */
/*
static struct yahoo_input_data * find_input_by_id(int id)
{
	YList *l;
	for(l = inputs; l; l = y_list_next(l)) {
		struct yahoo_input_data *yid = l->data;
		if(yid->yd->client_id == id)
			return yid;
	}
	return NULL;
}
*/

#if 0
static struct yahoo_input_data *find_input_by_id_and_webcam_user(int id,
	const char *who)
{
	YList *l;
	LOG(("find_input_by_id_and_webcam_user"));
	for (l = inputs; l; l = y_list_next(l)) {
		struct yahoo_input_data *yid = l->data;
		if (yid->type == YAHOO_CONNECTION_WEBCAM
			&& yid->yd->client_id == id && yid->wcm && ((who
					&& yid->wcm->user
					&& !strcmp(who, yid->wcm->user))
				|| !(yid->wcm->user && !who)))
			return yid;
	}
	return NULL;
}
#endif

static struct yahoo_input_data *find_input_by_id_and_type(int id,
	enum yahoo_connection_type type)
{
	YList *l;
	LOG(("find_input_by_id_and_type"));
	for (l = inputs; l; l = y_list_next(l)) {
		struct yahoo_input_data *yid = l->data;
		if (yid->type == type && yid->yd->client_id == id)
			return yid;
	}
	return NULL;
}

static struct yahoo_input_data *find_input_by_id_and_fd(int id, void *fd)
{
	YList *l;
	LOG(("find_input_by_id_and_fd"));
	for (l = inputs; l; l = y_list_next(l)) {
		struct yahoo_input_data *yid = l->data;
		if (yid->fd == fd && yid->yd->client_id == id)
			return yid;
	}
	return NULL;
}

static int count_inputs_with_id(int id)
{
	int c = 0;
	YList *l;
	LOG(("counting %d", id));
	for (l = inputs; l; l = y_list_next(l)) {
		struct yahoo_input_data *yid = l->data;
		if (yid->yd->client_id == id)
			c++;
	}
	LOG(("%d", c));
	return c;
}

/* Free a buddy list */
static void yahoo_free_buddies(YList *list)
{
	YList *l;

	for (l = list; l; l = l->next) {
		struct yahoo_buddy *bud = l->data;
		if (!bud)
			continue;

		FREE(bud->group);
		FREE(bud->id);
		FREE(bud->real_name);
		if (bud->yab_entry) {
			FREE(bud->yab_entry->fname);
			FREE(bud->yab_entry->lname);
			FREE(bud->yab_entry->nname);
			FREE(bud->yab_entry->id);
			FREE(bud->yab_entry->email);
			FREE(bud->yab_entry->hphone);
			FREE(bud->yab_entry->wphone);
			FREE(bud->yab_entry->mphone);
			FREE(bud->yab_entry);
		}
		FREE(bud);
		l->data = bud = NULL;
	}

	y_list_free(list);
}

/* Free an identities list */
static void yahoo_free_identities(YList *list)
{
	while (list) {
		YList *n = list;
		FREE(list->data);
		list = y_list_remove_link(list, list);
		y_list_free_1(n);
	}
}

/* Free webcam data */
static void yahoo_free_webcam(struct yahoo_webcam *wcm)
{
	if (wcm) {
		FREE(wcm->user);
		FREE(wcm->server);
		FREE(wcm->key);
		FREE(wcm->description);
		FREE(wcm->my_ip);
	}
	FREE(wcm);
}

static void yahoo_free_data(struct yahoo_data *yd)
{
	FREE(yd->user);
	FREE(yd->password);
	FREE(yd->cookie_y);
	FREE(yd->cookie_t);
	FREE(yd->cookie_b);
	FREE(yd->cookie_c);
	FREE(yd->login_cookie);
	FREE(yd->login_id);

	yahoo_free_buddies(yd->buddies);
	yahoo_free_buddies(yd->ignore);
	yahoo_free_identities(yd->identities);

	yahoo_free_server_settings(yd->server_settings);

	FREE(yd);
}

#define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)

static struct yahoo_packet *yahoo_packet_new(enum yahoo_service service,
	enum ypacket_status status, int id)
{
	struct yahoo_packet *pkt = y_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,
	const char *value)
{
	struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1);
	pair->key = key;
	pair->value = strdup(value);
	pkt->hash = y_list_append(pkt->hash, pair);
}

static int yahoo_packet_length(struct yahoo_packet *pkt)
{
	YList *l;

	int len = 0;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		int tmp = pair->key;
		do {
			tmp /= 10;
			len++;
		} while (tmp);
		len += 2;
		len += strlen(pair->value);
		len += 2;
	}

	return len;
}

#define yahoo_put16(buf, data) ( \
		(*(buf) = (unsigned char)((data)>>8)&0xff), \
		(*((buf)+1) = (unsigned char)(data)&0xff),  \
		2)
#define yahoo_get16(buf) ((((*(buf))&0xff)<<8) + ((*((buf)+1)) & 0xff))
#define yahoo_put32(buf, data) ( \
		(*((buf)) = (unsigned char)((data)>>24)&0xff), \
		(*((buf)+1) = (unsigned char)((data)>>16)&0xff), \
		(*((buf)+2) = (unsigned char)((data)>>8)&0xff), \
		(*((buf)+3) = (unsigned char)(data)&0xff), \
		4)
#define yahoo_get32(buf) ((((*(buf)   )&0xff)<<24) + \
			 (((*((buf)+1))&0xff)<<16) + \
			 (((*((buf)+2))&0xff)<< 8) + \
			 (((*((buf)+3))&0xff)))

static void yahoo_packet_read(struct yahoo_packet *pkt, unsigned char *data,
	int len)
{
	int pos = 0;

	while (pos + 1 < len) {
		char *key, *value = NULL;
		int accept;
		int x;

		struct yahoo_pair *pair = y_new0(struct yahoo_pair, 1);

		key = malloc(len + 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);
		free(key);
		
		/* Libyahoo2 developer(s) don't seem to have the time to fix
		   this problem, so for now try to work around it:
		   
		   Sometimes we receive an invalid packet with not any more
		   data at this point. I don't know how to handle this in a
		   clean way, but let's hope this is clean enough: */
		
		if (pos + 1 < len) {
			accept = x; 
			/* if x is 0 there was no key, so don't accept it */
			if (accept)
				value = 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;
		} else {
			accept = 0;
		}
		
		if (accept) {
			pair->value = strdup(value);
			FREE(value);
			pkt->hash = y_list_append(pkt->hash, pair);
			DEBUG_MSG(("Key: %d  \tValue: %s", pair->key,
					pair->value));
		} else {
			FREE(pair);
		}
	}
}

static void yahoo_packet_write(struct yahoo_packet *pkt, unsigned char *data)
{
	YList *l;
	int pos = 0;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		unsigned char buf[100];

		snprintf((char *)buf, sizeof(buf), "%d", pair->key);
		strcpy((char *)data + pos, (char *)buf);
		pos += strlen((char *)buf);
		data[pos++] = 0xc0;
		data[pos++] = 0x80;

		strcpy((char *)data + pos, pair->value);
		pos += strlen(pair->value);
		data[pos++] = 0xc0;
		data[pos++] = 0x80;
	}
}

static void yahoo_dump_unhandled(struct yahoo_packet *pkt)
{
	YList *l;

	NOTICE(("Service: 0x%02x\tStatus: %d", pkt->service, pkt->status));
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		NOTICE(("\t%d => %s", pair->key, pair->value));
	}
}

static void yahoo_packet_dump(unsigned char *data, int len)
{
	if (yahoo_get_log_level() >= YAHOO_LOG_DEBUG) {
		int i;
		for (i = 0; i < len; i++) {
			if ((i % 8 == 0) && i)
				YAHOO_CALLBACK(ext_yahoo_log) (" ");
			if ((i % 16 == 0) && i)
				YAHOO_CALLBACK(ext_yahoo_log) ("\n");
			YAHOO_CALLBACK(ext_yahoo_log) ("%02x ", data[i]);
		}
		YAHOO_CALLBACK(ext_yahoo_log) ("\n");
		for (i = 0; i < len; i++) {
			if ((i % 8 == 0) && i)
				YAHOO_CALLBACK(ext_yahoo_log) (" ");
			if ((i % 16 == 0) && i)
				YAHOO_CALLBACK(ext_yahoo_log) ("\n");
			if (isprint(data[i]))
				YAHOO_CALLBACK(ext_yahoo_log) (" %c ", data[i]);
			else
				YAHOO_CALLBACK(ext_yahoo_log) (" . ");
		}
		YAHOO_CALLBACK(ext_yahoo_log) ("\n");
	}
}

/* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
static void to_y64(unsigned char *out, const unsigned char *in, int inlen)
{
	base64_encode_real(in, inlen, out, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-");
}

static void yahoo_add_to_send_queue(struct yahoo_input_data *yid, void *data,
	int length)
{
	struct data_queue *tx = y_new0(struct data_queue, 1);
	tx->queue = y_new0(unsigned char, length);
	tx->len = length;
	memcpy(tx->queue, data, length);

	yid->txqueues = y_list_append(yid->txqueues, tx);

	if (!yid->write_tag)
		yid->write_tag =
			YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->
			client_id, yid->fd, YAHOO_INPUT_WRITE, yid);
}

static void yahoo_send_packet(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt, int extra_pad)
{
	int pktlen = yahoo_packet_length(pkt);
	int len = YAHOO_PACKET_HDRLEN + pktlen;
	unsigned char *data;
	int pos = 0;

	if (yid->fd < 0)
		return;

	data = y_new0(unsigned char, len + 1);

	memcpy(data + pos, "YMSG", 4);
	pos += 4;
	pos += yahoo_put16(data + pos, YAHOO_PROTO_VER);	/* version [latest 12 0x000c] */
	pos += yahoo_put16(data + pos, 0x0000);	/* HIWORD pkt length??? */
	pos += yahoo_put16(data + pos, pktlen + extra_pad);	/* LOWORD pkt length? */
	pos += yahoo_put16(data + pos, pkt->service);	/* service */
	pos += yahoo_put32(data + pos, pkt->status);	/* status [4bytes] */
	pos += yahoo_put32(data + pos, pkt->id);	/* session [4bytes] */

	yahoo_packet_write(pkt, data + pos);

	yahoo_packet_dump(data, len);

	if (yid->type == YAHOO_CONNECTION_FT)
		yahoo_send_data(yid->fd, data, len);
	else
		yahoo_add_to_send_queue(yid, data, len);
	FREE(data);
}

static void yahoo_packet_free(struct yahoo_packet *pkt)
{
	while (pkt->hash) {
		struct yahoo_pair *pair = pkt->hash->data;
		YList *tmp;
		FREE(pair->value);
		FREE(pair);
		tmp = pkt->hash;
		pkt->hash = y_list_remove_link(pkt->hash, pkt->hash);
		y_list_free_1(tmp);
	}
	FREE(pkt);
}

static int yahoo_send_data(void *fd, void *data, int len)
{
	int ret;
	int e;

	if (fd == NULL)
		return -1;

	yahoo_packet_dump(data, len);

	do {
		ret = YAHOO_CALLBACK(ext_yahoo_write) (fd, data, len);
	} while (ret == -1 && errno == EINTR);
	e = errno;

	if (ret == -1) {
		LOG(("wrote data: ERR %s", strerror(errno)));
	} else {
		LOG(("wrote data: OK"));
	}

	errno = e;
	return ret;
}

void yahoo_close(int id)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	if (!yd)
		return;

	del_from_list(yd);

	yahoo_free_data(yd);
	if (id == last_id)
		last_id--;
}

static void yahoo_input_close(struct yahoo_input_data *yid)
{
	inputs = y_list_remove(inputs, yid);

	LOG(("yahoo_input_close(read)"));
	YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id,
		yid->read_tag);
	LOG(("yahoo_input_close(write)"));
	YAHOO_CALLBACK(ext_yahoo_remove_handler) (yid->yd->client_id,
		yid->write_tag);
	yid->read_tag = yid->write_tag = 0;
	if (yid->fd)
		YAHOO_CALLBACK(ext_yahoo_close) (yid->fd);
	yid->fd = 0;
	FREE(yid->rxqueue);
	if (count_inputs_with_id(yid->yd->client_id) == 0) {
		LOG(("closing %d", yid->yd->client_id));
		yahoo_close(yid->yd->client_id);
	}
	yahoo_free_webcam(yid->wcm);
	if (yid->wcd)
		FREE(yid->wcd);
	if (yid->ys) {
		FREE(yid->ys->lsearch_text);
		FREE(yid->ys);
	}
	FREE(yid);
}

static int is_same_bud(const void *a, const void *b)
{
	const struct yahoo_buddy *subject = a;
	const struct yahoo_buddy *object = b;

	return strcmp(subject->id, object->id);
}

static char *getcookie(char *rawcookie)
{
	char *cookie = NULL;
	char *tmpcookie;
	char *cookieend;

	if (strlen(rawcookie) < 2)
		return NULL;

	tmpcookie = strdup(rawcookie + 2);
	cookieend = strchr(tmpcookie, ';');

	if (cookieend)
		*cookieend = '\0';

	cookie = strdup(tmpcookie);
	FREE(tmpcookie);
	/* cookieend=NULL;  not sure why this was there since the value is not preserved in the stack -dd */

	return cookie;
}

static char *getlcookie(char *cookie)
{
	char *tmp;
	char *tmpend;
	char *login_cookie = NULL;

	tmpend = strstr(cookie, "n=");
	if (tmpend) {
		tmp = strdup(tmpend + 2);
		tmpend = strchr(tmp, '&');
		if (tmpend)
			*tmpend = '\0';
		login_cookie = strdup(tmp);
		FREE(tmp);
	}

	return login_cookie;
}

static void yahoo_process_notify(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *msg = NULL;
	char *from = NULL;
	char *to = NULL;
	int stat = 0;
	int accept = 0;
	char *ind = NULL;
	YList *l;
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 4)
			from = pair->value;
		if (pair->key == 5)
			to = pair->value;
		if (pair->key == 49)
			msg = pair->value;
		if (pair->key == 13)
			stat = atoi(pair->value);
		if (pair->key == 14)
			ind = pair->value;
		if (pair->key == 16) {	/* status == -1 */
			NOTICE((pair->value));
			return;
		}

	}

	if (!msg)
		return;

	if (!strncasecmp(msg, "TYPING", strlen("TYPING")))
		YAHOO_CALLBACK(ext_yahoo_typing_notify) (yd->client_id, to,
			from, stat);
	else if (!strncasecmp(msg, "GAME", strlen("GAME")))
		YAHOO_CALLBACK(ext_yahoo_game_notify) (yd->client_id, to, from,
			stat, ind);
	else if (!strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) {
		if (!strcmp(ind, " ")) {
			YAHOO_CALLBACK(ext_yahoo_webcam_invite) (yd->client_id,
				to, from);
		} else {
			accept = atoi(ind);
			/* accept the invitation (-1 = deny 1 = accept) */
			if (accept < 0)
				accept = 0;
			YAHOO_CALLBACK(ext_yahoo_webcam_invite_reply) (yd->
				client_id, to, from, accept);
		}
	} else
		LOG(("Got unknown notification: %s", msg));
}

static void yahoo_process_conference(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *msg = NULL;
	char *host = NULL;
	char *who = NULL;
	char *room = NULL;
	char *id = NULL;
	int utf8 = 0;
	YList *members = NULL;
	YList *l;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 50)
			host = pair->value;

		if (pair->key == 52) {	/* invite */
			members = y_list_append(members, strdup(pair->value));
		}
		if (pair->key == 53)	/* logon */
			who = pair->value;
		if (pair->key == 54)	/* decline */
			who = pair->value;
		if (pair->key == 55)	/* unavailable (status == 2) */
			who = pair->value;
		if (pair->key == 56)	/* logoff */
			who = pair->value;

		if (pair->key == 57)
			room = pair->value;

		if (pair->key == 58)	/* join message */
			msg = pair->value;
		if (pair->key == 14)	/* decline/conf message */
			msg = pair->value;

		if (pair->key == 13) ;
		if (pair->key == 16)	/* error */
			msg = pair->value;

		if (pair->key == 1)	/* my id */
			id = pair->value;
		if (pair->key == 3)	/* message sender */
			who = pair->value;

		if (pair->key == 97)
			utf8 = atoi(pair->value);
	}

	if (!room)
		return;

	if (host) {
		for (l = members; l; l = l->next) {
			char *w = l->data;
			if (!strcmp(w, host))
				break;
		}
		if (!l)
			members = y_list_append(members, strdup(host));
	}
	/* invite, decline, join, left, message -> status == 1 */

	switch (pkt->service) {
	case YAHOO_SERVICE_CONFINVITE:
		if (pkt->status == 2) ;
		else if (members)
			YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd->
				client_id, id, host, room, msg, members);
		else if (msg)
			YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id, msg, 0,
				E_CONFNOTAVAIL);
		break;
	case YAHOO_SERVICE_CONFADDINVITE:
		if (pkt->status == 1)
			YAHOO_CALLBACK(ext_yahoo_got_conf_invite) (yd->
				client_id, id, host, room, msg, members);
		break;
	case YAHOO_SERVICE_CONFDECLINE:
		if (who)
			YAHOO_CALLBACK(ext_yahoo_conf_userdecline) (yd->
				client_id, id, who, room, msg);
		break;
	case YAHOO_SERVICE_CONFLOGON:
		if (who)
			YAHOO_CALLBACK(ext_yahoo_conf_userjoin) (yd->client_id,
				id, who, room);
		break;
	case YAHOO_SERVICE_CONFLOGOFF:
		if (who)
			YAHOO_CALLBACK(ext_yahoo_conf_userleave) (yd->client_id,
				id, who, room);
		break;
	case YAHOO_SERVICE_CONFMSG:
		if (who)
			YAHOO_CALLBACK(ext_yahoo_conf_message) (yd->client_id,
				id, who, room, msg, utf8);
		break;
	}
}

static void yahoo_process_chat(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	char *msg = NULL;
	char *id = NULL;
	char *who = NULL;
	char *room = NULL;
	char *topic = NULL;
	YList *members = NULL;
	struct yahoo_chat_member *currentmember = NULL;
	int msgtype = 1;
	int utf8 = 0;
	int firstjoin = 0;
	int membercount = 0;
	int chaterr = 0;
	YList *l;

	yahoo_dump_unhandled(pkt);
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;

		if (pair->key == 1) {
			/* My identity */
			id = pair->value;
		}

		if (pair->key == 104) {
			/* Room name */
			room = pair->value;
		}

		if (pair->key == 105) {
			/* Room topic */
			topic = pair->value;
		}

		if (pair->key == 108) {
			/* Number of members in this packet */
			membercount = atoi(pair->value);
		}

		if (pair->key == 109) {
			/* message sender */
			who = pair->value;

			if (pkt->service == YAHOO_SERVICE_CHATJOIN) {
				currentmember =
					y_new0(struct yahoo_chat_member, 1);
				currentmember->id = strdup(pair->value);
				members = y_list_append(members, currentmember);
			}
		}

		if (pair->key == 110) {
			/* age */
			if (pkt->service == YAHOO_SERVICE_CHATJOIN)
				currentmember->age = atoi(pair->value);
		}

		if (pair->key == 113) {
			/* attribs */
			if (pkt->service == YAHOO_SERVICE_CHATJOIN)
				currentmember->attribs = atoi(pair->value);
		}

		if (pair->key == 141) {
			/* alias */
			if (pkt->service == YAHOO_SERVICE_CHATJOIN)
				currentmember->alias = strdup(pair->value);
		}

		if (pair->key == 142) {
			/* location */
			if (pkt->service == YAHOO_SERVICE_CHATJOIN)
				currentmember->location = strdup(pair->value);
		}

		if (pair->key == 130) {
			/* first join */
			firstjoin = 1;
		}

		if (pair->key == 117) {
			/* message */
			msg = pair->value;
		}

		if (pair->key == 124) {
			/* Message type */
			msgtype = atoi(pair->value);
		}
		if (pair->key == 114) {
			/* message error not sure what all the pair values mean */
			/* but -1 means no session in room */
			chaterr = atoi(pair->value);
		}
	}

	if (!room) {
		if (pkt->service == YAHOO_SERVICE_CHATLOGOUT) {	/* yahoo originated chat logout */
			YAHOO_CALLBACK(ext_yahoo_chat_yahoologout) (yid->yd->
				client_id, id);
			return;
		}
		if (pkt->service == YAHOO_SERVICE_COMMENT && chaterr) {
			YAHOO_CALLBACK(ext_yahoo_chat_yahooerror) (yid->yd->
				client_id, id);
			return;
		}

		WARNING(("We didn't get a room name, ignoring packet"));
		return;
	}

	switch (pkt->service) {
	case YAHOO_SERVICE_CHATJOIN:
		if (y_list_length(members) != membercount) {
			WARNING(("Count of members doesn't match No. of members we got"));
		}
		if (firstjoin && members) {
			YAHOO_CALLBACK(ext_yahoo_chat_join) (yid->yd->client_id,
				id, room, topic, members, yid->fd);
		} else if (who) {
			if (y_list_length(members) != 1) {
				WARNING(("Got more than 1 member on a normal join"));
			}
			/* this should only ever have one, but just in case */
			while (members) {
				YList *n = members->next;
				currentmember = members->data;
				YAHOO_CALLBACK(ext_yahoo_chat_userjoin) (yid->
					yd->client_id, id, room, currentmember);
				y_list_free_1(members);
				members = n;
			}
		}
		break;
	case YAHOO_SERVICE_CHATEXIT:
		if (who) {
			YAHOO_CALLBACK(ext_yahoo_chat_userleave) (yid->yd->
				client_id, id, room, who);
		}
		break;
	case YAHOO_SERVICE_COMMENT:
		if (who) {
			YAHOO_CALLBACK(ext_yahoo_chat_message) (yid->yd->
				client_id, id, who, room, msg, msgtype, utf8);
		}
		break;
	}
}

static void yahoo_process_message(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	YList *l;
	YList *messages = NULL;

	struct m {
		int i_31;
		int i_32;
		char *to;
		char *from;
		long tm;
		char *msg;
		int utf8;
		char *gunk;
	} *message = y_new0(struct m, 1);

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 1 || pair->key == 4) {
			if (!message->from)
				message->from = pair->value;
		} else if (pair->key == 5)
			message->to = pair->value;
		else if (pair->key == 15)
			message->tm = strtol(pair->value, NULL, 10);
		else if (pair->key == 97)
			message->utf8 = atoi(pair->value);
		/* This comes when the official client sends us a message */
		else if (pair->key == 429)
			message->gunk = pair->value;
		/* user message *//* sys message */
		else if (pair->key == 14 || pair->key == 16)
			message->msg = pair->value;
		else if (pair->key == 31) {
			if (message->i_31) {
				messages = y_list_append(messages, message);
				message = y_new0(struct m, 1);
			}
			message->i_31 = atoi(pair->value);
		} else if (pair->key == 32)
			message->i_32 = atoi(pair->value);
		else
			LOG(("yahoo_process_message: status: %d, key: %d, value: %s", pkt->status, pair->key, pair->value));
	}

	messages = y_list_append(messages, message);

	for (l = messages; l; l = l->next) {
		message = l->data;
		if (pkt->service == YAHOO_SERVICE_SYSMESSAGE) {
			YAHOO_CALLBACK(ext_yahoo_system_message) (yd->client_id,
				message->to, message->from, message->msg);
		} else if (pkt->status <= 2 || pkt->status == 5) {
			/* Confirm message receipt if we got the gunk */
			if(message->gunk) {
				struct yahoo_packet *outpkt;
                        
				outpkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_CONFIRM,
					YPACKET_STATUS_DEFAULT, 0);
				yahoo_packet_hash(outpkt, 1, yd->user);
				yahoo_packet_hash(outpkt, 5, message->from);
				yahoo_packet_hash(outpkt, 302, "430");
				yahoo_packet_hash(outpkt, 430, message->gunk);
				yahoo_packet_hash(outpkt, 303, "430");
				yahoo_packet_hash(outpkt, 450, "0");
				yahoo_send_packet(yid, outpkt, 0);
                        
				yahoo_packet_free(outpkt);
			}

			if (!strcmp(message->msg, "<ding>"))
				YAHOO_CALLBACK(ext_yahoo_got_buzz) (yd->client_id,
					message->to, message->from, message->tm);
			else
				YAHOO_CALLBACK(ext_yahoo_got_im) (yd->client_id,
					message->to, message->from, message->msg,
					message->tm, pkt->status, message->utf8);
		} else if (pkt->status == 0xffffffff) {
			YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id,
				message->msg, 0, E_SYSTEM);
		}
		FREE(message);
	}

	y_list_free(messages);
}

/*
 * Here's what multi-level packets look like. Data in brackets is the value.
 *
 * 3 level:
 * =======
 * 
 * 302 (318) - Beginning level 1
 * 	300 (318) - Begin level 2
 * 	302 (319) - End level 2 header
 * 		300 (319) - Begin level 3
 * 		301 (319) - End level 3
 * 	303 (319) - End level 2
 * 303 (318) - End level 1
 *
 * 2 level:
 * =======
 *
 * 302 (315) - Beginning level 1
 * 	300 (315) - Begin level 2
 * 	301 (315) - End level 2
 * 303 (315) - End level 1
 *
 */
static void yahoo_process_status(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	YList *l;
	struct yahoo_data *yd = yid->yd;

	struct yahoo_process_status_entry *u;

	YList *users = 0;

	if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
			YAHOO_LOGIN_DUPL, NULL);
		return;
	}

	/* 
	 * Status updates may be spread accross multiple packets and not 
	 * even on buddy boundaries, so keeping some state is important. 
	 * So, continue where we left off, and only add a user entry to 
	 * the list once it's complete (301-315 End buddy).
	 */
	u = yd->half_user;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;

		switch (pair->key) {
		case 300:	/* Begin buddy */
			if (!strcmp(pair->value, "315") && !u) {
				u = yd->half_user = y_new0(struct yahoo_process_status_entry, 1);
			}
			break;
		case 301:	/* End buddy */
			if (!strcmp(pair->value, "315") && u) {
				/* Sometimes user info comes in an odd format with no
				   "begin buddy" but *with* an "end buddy". Don't add
				   it twice. */
				if (!y_list_find(users, u))
					users = y_list_prepend(users, u);
				u = yd->half_user = NULL;
			}
			break;
		case 0:	/* we won't actually do anything with this */
			NOTICE(("key %d:%s", pair->key, pair->value));
			break;
		case 1:	/* we don't get the full buddy list here. */
			if (!yd->logged_in) {
				yd->logged_in = 1;
				if (yd->current_status < 0)
					yd->current_status = yd->initial_status;
				YAHOO_CALLBACK(ext_yahoo_login_response) (yd->
					client_id, YAHOO_LOGIN_OK, NULL);
			}
			break;
		case 8:	/* how many online buddies we have */
			NOTICE(("key %d:%s", pair->key, pair->value));
			break;
		case 7:	/* the current buddy */
			if (!u) {
				/* This will only happen in case of a single level message */
				u = y_new0(struct yahoo_process_status_entry, 1);
				users = y_list_prepend(users, u);
			}
			u->name = pair->value;
			break;
		case 10:	/* state */
			u->state = strtol(pair->value, NULL, 10);
			break;
		case 19:	/* custom status message */
			u->msg = pair->value;
			break;
		case 47:	/* is it an away message or not. Not applicable for YMSG16 anymore */
			u->away = atoi(pair->value);
			break;
		case 137:	/* seconds idle */
			u->idle = atoi(pair->value);
			break;
		case 11:	/* this is the buddy's session id */
			u->buddy_session = atoi(pair->value);
			break;
		case 17:	/* in chat? */
			u->f17 = atoi(pair->value);
			break;
		case 13:	/* bitmask, bit 0 = pager, bit 1 = chat, bit 2 = game */
			u->flags = atoi(pair->value);
			break;
		case 60:	/* SMS -> 1 MOBILE USER */
			/* sometimes going offline makes this 2, but invisible never sends it */
			u->mobile = atoi(pair->value);
			break;
		case 138:
			u->f138 = atoi(pair->value);
			break;
		case 184:
			u->f184 = pair->value;
			break;
		case 192:
			u->f192 = atoi(pair->value);
			break;
		case 10001:
			u->f10001 = atoi(pair->value);
			break;
		case 10002:
			u->f10002 = atoi(pair->value);
			break;
		case 198:
			u->f198 = atoi(pair->value);
			break;
		case 197:
			u->f197 = pair->value;
			break;
		case 205:
			u->f205 = pair->value;
			break;
		case 213:
			u->f213 = atoi(pair->value);
			break;
		case 16:	/* Custom error message */
			YAHOO_CALLBACK(ext_yahoo_error) (yd->client_id,
				pair->value, 0, E_CUSTOM);
			break;
		default:
			WARNING(("unknown status key %d:%s", pair->key,
					pair->value));
			break;
		}
	}

	while (users) {
		YList *t = users;
		struct yahoo_process_status_entry *u = users->data;

		if (u->name != NULL) {
			if (pkt->service ==
				YAHOO_SERVICE_LOGOFF
				/*|| u->flags == 0 No flags for YMSG16 */ ) {
				YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->
					client_id, u->name,
					YAHOO_STATUS_OFFLINE, NULL, 1, 0, 0);
			} else {
				/* Key 47 always seems to be 1 for YMSG16 */
				if (!u->state)
					u->away = 0;
				else
					u->away = 1;

				YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->
					client_id, u->name, u->state, u->msg,
					u->away, u->idle, u->mobile);
			}
		}

		users = y_list_remove_link(users, users);
		y_list_free_1(t);
		FREE(u);
	}
}

static void yahoo_process_buddy_list(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	YList *l;
	int last_packet = 0;
	char *cur_group = NULL;
	struct yahoo_buddy *newbud = NULL;

	/* we could be getting multiple packets here */
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;

		switch (pair->key) {
		case 300:
		case 301:
		case 302:
			break;	/* Separators. Our logic does not need them */
		case 303:
			if (318 == atoi(pair->value))
				last_packet = 1;
			break;
		case 65:
			cur_group = strdup(pair->value);
			break;
		case 7:
			newbud = y_new0(struct yahoo_buddy, 1);
			newbud->id = strdup(pair->value);
			if (cur_group)
				newbud->group = strdup(cur_group);
			else if (yd->buddies) {
				struct yahoo_buddy *lastbud =
					(struct yahoo_buddy *)y_list_nth(yd->
					buddies,
					y_list_length(yd->buddies) - 1)->data;
				newbud->group = strdup(lastbud->group);
			} else
				newbud->group = strdup("Buddies");

			yd->buddies = y_list_append(yd->buddies, newbud);

			break;
		}
	}

	/* we could be getting multiple packets here */
	if (pkt->hash && !last_packet)
		return;

	YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, yd->buddies);

	/* Logged in */
	if (!yd->logged_in) {
		yd->logged_in = 1;
		if (yd->current_status < 0)
			yd->current_status = yd->initial_status;
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
			YAHOO_LOGIN_OK, NULL);

		/*
		yahoo_set_away(yd->client_id, yd->initial_status, NULL,
			(yd->initial_status == YAHOO_STATUS_AVAILABLE) ? 0 : 1);

		yahoo_get_yab(yd->client_id);
		*/
	}

}

static void yahoo_process_list(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	YList *l;

	/* we could be getting multiple packets here */
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;

		switch (pair->key) {
		case 89:	/* identities */
			{
				char **identities =
					y_strsplit(pair->value, ",", -1);
				int i;
				for (i = 0; identities[i]; i++)
					yd->identities =
						y_list_append(yd->identities,
						strdup(identities[i]));
				y_strfreev(identities);
			}
			YAHOO_CALLBACK(ext_yahoo_got_identities) (yd->client_id,
				yd->identities);
			break;
		case 59:	/* cookies */
			if (pair->value[0] == 'Y') {
				FREE(yd->cookie_y);
				FREE(yd->login_cookie);

				yd->cookie_y = getcookie(pair->value);
				yd->login_cookie = getlcookie(yd->cookie_y);

			} else if (pair->value[0] == 'T') {
				FREE(yd->cookie_t);
				yd->cookie_t = getcookie(pair->value);

			} else if (pair->value[0] == 'C') {
				FREE(yd->cookie_c);
				yd->cookie_c = getcookie(pair->value);
			}

			break;
		case 3:	/* my id */
		case 90:	/* 1 */
		case 100:	/* 0 */
		case 101:	/* NULL */
		case 102:	/* NULL */
		case 93:	/* 86400/1440 */
			break;
		}
	}

	if (yd->cookie_y && yd->cookie_t)	/* We don't get cookie_c anymore */
		YAHOO_CALLBACK(ext_yahoo_got_cookies) (yd->client_id);
}

static void yahoo_process_verify(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;

	if (pkt->status != 0x01) {
		DEBUG_MSG(("expected status: 0x01, got: %d", pkt->status));
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
			YAHOO_LOGIN_LOCK, "");
		return;
	}

	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);

}

static void yahoo_process_picture_checksum(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *from = NULL;
	char *to = NULL;
	int checksum = 0;
	YList *l;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;

		switch (pair->key) {
		case 1:
		case 4:
			from = pair->value;
		case 5:
			to = pair->value;
			break;
		case 212:
			break;
		case 192:
			checksum = atoi(pair->value);
			break;
		}
	}

	YAHOO_CALLBACK(ext_yahoo_got_buddyicon_checksum) (yd->client_id, to,
		from, checksum);
}

static void yahoo_process_picture(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *url = NULL;
	char *from = NULL;
	char *to = NULL;
	int status = 0;
	int checksum = 0;
	YList *l;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;

		switch (pair->key) {
		case 1:
		case 4:	/* sender */
			from = pair->value;
			break;
		case 5:	/* we */
			to = pair->value;
			break;
		case 13:	/* request / sending */
			status = atoi(pair->value);
			break;
		case 20:	/* url */
			url = pair->value;
			break;
		case 192:	/*checksum */
			checksum = atoi(pair->value);
			break;
		}
	}

	switch (status) {
	case 1:		/* this is a request, ignore for now */
		YAHOO_CALLBACK(ext_yahoo_got_buddyicon_request) (yd->client_id,
			to, from);
		break;
	case 2:		/* this is cool - we get a picture :) */
		YAHOO_CALLBACK(ext_yahoo_got_buddyicon) (yd->client_id, to,
			from, url, checksum);
		break;
	}
}

static void yahoo_process_picture_upload(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	YList *l;
	char *url = NULL;

	if (pkt->status != 1)
		return;		/* something went wrong */

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;

		switch (pair->key) {
		case 5:	/* we */
			break;
		case 20:	/* url */
			url = pair->value;
			break;
		case 27:	/* local filename */
			break;
		case 38:	/* time */
			break;
		}
	}

	YAHOO_CALLBACK(ext_yahoo_buddyicon_uploaded) (yd->client_id, url);
}

void yahoo_login(int id, int initial)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	struct connect_callback_data *ccd;
	struct yahoo_server_settings *yss;
	int tag;

	char *host;

	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
	yid->yd = yd;
	yid->type = YAHOO_CONNECTION_PAGER;
	inputs = y_list_prepend(inputs, yid);

	yd->initial_status = initial;
	yss = yd->server_settings;

	ccd = y_new0(struct connect_callback_data, 1);
	ccd->yd = yd;

	host = yss->pager_host;

	if (!host)
		host = yss->pager_host_list[0];

	tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id,
		host, yss->pager_port, yahoo_connected, ccd, 0);

	/*
	 * if tag <= 0, then callback has already been called
	 * so ccd will have been freed
	 */
	if (tag > 0)
		ccd->tag = tag;
	else if (tag < 0)
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
			YAHOO_LOGIN_SOCK, NULL);
}

struct yahoo_https_auth_data
{
	struct yahoo_input_data *yid;
	char *token;
	char *chal;
};

static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had);
static void yahoo_https_auth_token_finish(struct http_request *req);
static void yahoo_https_auth_init(struct yahoo_https_auth_data *had);
static void yahoo_https_auth_finish(struct http_request *req);

/* Extract a value from a login.yahoo.com response. Assume CRLF-linebreaks
   and FAIL miserably if they're not there... */
static char *yahoo_ha_find_key(char *response, char *key)
{
	char *s, *end;
	int len = strlen(key);
	
	s = response;
	do {
		if (strncmp(s, key, len) == 0 && s[len] == '=') {
			s += len + 1;
			if ((end = strchr(s, '\r')))
				return g_strndup(s, end - s);
			else
				return g_strdup(s);
		}
		
		if ((s = strchr(s, '\n')))
			s ++;
	} while (s && *s);
	
	return NULL;
}

static enum yahoo_status yahoo_https_status_parse(int code)
{
	switch (code)
	{
		case 1212: return YAHOO_LOGIN_PASSWD;
		case 1213: return YAHOO_LOGIN_LOCK;
		case 1235: return YAHOO_LOGIN_UNAME;
		default: return (enum yahoo_status) code;
	}
}

static void yahoo_https_auth(struct yahoo_input_data *yid, const char *seed, const char *sn)
{
	struct yahoo_https_auth_data *had = g_new0(struct yahoo_https_auth_data, 1);
	
	had->yid = yid;
	had->chal = g_strdup(seed);
	
	yahoo_https_auth_token_init(had);
}

static void yahoo_https_auth_token_init(struct yahoo_https_auth_data *had)
{
	struct yahoo_input_data *yid = had->yid;
	struct yahoo_data *yd = yid->yd;
	char *login, *passwd, *chal;
	char *url;
	
	login = g_strndup(yd->user, 3 * strlen(yd->user));
	http_encode(login);
	passwd = g_strndup(yd->password, 3 * strlen(yd->password));
	http_encode(passwd);
	chal = g_strndup(had->chal, 3 * strlen(had->chal));
	http_encode(chal);
	
	url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_get?src=ymsgr&ts=%d&login=%s&passwd=%s&chal=%s",
	                       (int) time(NULL), login, passwd, chal);
	
	http_dorequest_url(url, yahoo_https_auth_token_finish, had);
	
	g_free(url);
	g_free(chal);
	g_free(passwd);
	g_free(login);
}

static void yahoo_https_auth_token_finish(struct http_request *req)
{
	struct yahoo_https_auth_data *had = req->data;
	struct yahoo_input_data *yid;
	struct yahoo_data *yd;
	int st;
	
	if (y_list_find(inputs, had->yid) == NULL)
		return;
	
	yid = had->yid;
	yd = yid->yd;
	
	if (req->status_code != 200) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL);
		goto fail;
	}
	
	if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL);
		goto fail;
	}
	
	if ((had->token = yahoo_ha_find_key(req->reply_body, "ymsgr")) == NULL) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3001, NULL);
		goto fail;
	}
	
	yahoo_https_auth_init(had);
	return;
	
fail:
	g_free(had->token);
	g_free(had->chal);
	g_free(had);
}

static void yahoo_https_auth_init(struct yahoo_https_auth_data *had)
{
	char *url;
	
	url = g_strdup_printf("https://login.yahoo.com/config/pwtoken_login?src=ymsgr&ts=%d&token=%s",
	                      (int) time(NULL), had->token);
	
	http_dorequest_url(url, yahoo_https_auth_finish, had);
	
	g_free(url);
}

static void yahoo_https_auth_finish(struct http_request *req)
{
	struct yahoo_https_auth_data *had = req->data;
	struct yahoo_input_data *yid;
	struct yahoo_data *yd;
	struct yahoo_packet *pack;
	char *crumb = NULL;
	int st;
	
	if (y_list_find(inputs, had->yid) == NULL)
		return;
	
	yid = had->yid;
	yd = yid->yd;
	
	md5_byte_t result[16];
	md5_state_t ctx;
	
	unsigned char yhash[32];

	if (req->status_code != 200) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 2000 + req->status_code, NULL);
		goto fail;
	}
	
	if (sscanf(req->reply_body, "%d", &st) != 1 || st != 0) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, yahoo_https_status_parse(st), NULL);
		goto fail;
	}
	
	if ((yd->cookie_y = yahoo_ha_find_key(req->reply_body, "Y")) == NULL ||
	    (yd->cookie_t = yahoo_ha_find_key(req->reply_body, "T")) == NULL ||
	    (crumb = yahoo_ha_find_key(req->reply_body, "crumb")) == NULL) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id, 3002, NULL);
		goto fail;
	}
	
	md5_init(&ctx);  
	md5_append(&ctx, (unsigned char*) crumb, 11);
	md5_append(&ctx, (unsigned char*) had->chal, strlen(had->chal));
	md5_finish(&ctx, result);
	to_y64(yhash, result, 16);

	pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->initial_status, yd->session_id);
	yahoo_packet_hash(pack, 1, yd->user);
	yahoo_packet_hash(pack, 0, yd->user);
	yahoo_packet_hash(pack, 277, yd->cookie_y);
	yahoo_packet_hash(pack, 278, yd->cookie_t);
	yahoo_packet_hash(pack, 307, (char*) yhash);
	yahoo_packet_hash(pack, 244, "524223");
	yahoo_packet_hash(pack, 2, yd->user);
	yahoo_packet_hash(pack, 2, "1");
	yahoo_packet_hash(pack, 98, "us");
	yahoo_packet_hash(pack, 135, "7.5.0.647");
	
	yahoo_send_packet(yid, pack, 0);
		
	yahoo_packet_free(pack);
	
fail:
	g_free(crumb);
	g_free(had->token);
	g_free(had->chal);
	g_free(had);
}

static void yahoo_process_auth(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	char *seed = NULL;
	char *sn = NULL;
	YList *l = pkt->hash;
	int m = 0;
	struct yahoo_data *yd = yid->yd;

	while (l) {
		struct yahoo_pair *pair = l->data;

		switch (pair->key) {
		case 94:
			seed = pair->value;
			break;
		case 1:
			sn = pair->value;
			break;
		case 13:
			m = atoi(pair->value);
			break;
		}
		l = l->next;
	}

	if (!seed)
		return;

	if (m==2)
		yahoo_https_auth(yid, seed, sn);
	else {
		/* call error */
		WARNING(("unknown auth type %d", m));
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
			YAHOO_LOGIN_UNKNOWN, NULL);
	}
}

static void yahoo_process_auth_resp(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *url = NULL;
	int login_status = -1;

	YList *l;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 0)
			; /* login_id */
		else if (pair->key == 1)
			; /* handle */
		else if (pair->key == 20)
			url = pair->value;
		else if (pair->key == 66)
			login_status = atoi(pair->value);
	}

	if (pkt->status == YPACKET_STATUS_DISCONNECTED) {
		YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
			login_status, url);
		/*      yahoo_logoff(yd->client_id); */
	}
}

static void yahoo_process_mail(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *who = NULL;
	char *email = NULL;
	char *subj = NULL;
	int count = 0;
	YList *l;

	for (l = pkt->hash; l; l = l->next) {
		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;
		else
			LOG(("key: %d => value: %s", pair->key, pair->value));
	}

	if (who && email && subj) {
		char from[1024];
		snprintf(from, sizeof(from), "%s (%s)", who, email);
		YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, from,
			subj, count);
	} else if (count > 0)
		YAHOO_CALLBACK(ext_yahoo_mail_notify) (yd->client_id, NULL,
			NULL, count);
}

static void yahoo_process_new_contact(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *me = NULL;
	char *who = NULL;
	char *msg = NULL;
	int online = -1;

	YList *l;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 4)
			who = pair->value;
		else if (pair->key == 5)
			me = pair->value;
		else if (pair->key == 14)
			msg = pair->value;
		else if (pair->key == 13)
			online = strtol(pair->value, NULL, 10);
	}

	if (who && online < 0)
		YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, me, who,
			msg);
	else if (online == 2)
		YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg);
}

/* UNUSED? */
static void yahoo_process_contact(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *id = NULL;
	char *who = NULL;
	char *msg = NULL;
	char *name = NULL;
	int state = YAHOO_STATUS_AVAILABLE;
	int away = 0;
	int idle = 0;
	int mobile = 0;

	YList *l;

	for (l = pkt->hash; l; l = l->next) {
		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 == 15)
			; /* tm */
		else if (pair->key == 13)
			; /* online */
		else if (pair->key == 47)
			away = strtol(pair->value, NULL, 10);
		else if (pair->key == 137)
			idle = strtol(pair->value, NULL, 10);
		else if (pair->key == 60)
			mobile = strtol(pair->value, NULL, 10);

	}

	if (id)
		YAHOO_CALLBACK(ext_yahoo_contact_added) (yd->client_id, id, who,
			msg);
	else if (name)
		YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, name,
			state, msg, away, idle, mobile);
	else if (pkt->status == 0x07)
		YAHOO_CALLBACK(ext_yahoo_rejected) (yd->client_id, who, msg);
}

static void yahoo_process_buddyadd(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *who = NULL;
	char *where = NULL;
	int status = 0;

	struct yahoo_buddy *bud = NULL;

	YList *l;
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 1)
			; /* Me... don't care */
		if (pair->key == 7)
			who = pair->value;
		if (pair->key == 65)
			where = pair->value;
		if (pair->key == 66)
			status = strtol(pair->value, NULL, 10);
	}

	if (!who)
		return;
	if (!where)
		where = "Unknown";

	bud = y_new0(struct yahoo_buddy, 1);
	bud->id = strdup(who);
	bud->group = strdup(where);
	bud->real_name = NULL;

	yd->buddies = y_list_append(yd->buddies, bud);

#if 0
	/* BitlBee: This seems to be wrong in my experience. I think:
	   status = 0: Success
	   status = 2: Already on list
	   status = 3: Doesn't exist
	   status = 42: Invalid handle (possibly banned/reserved, I get it for
	                handles like joe or jjjjjj)
	   Haven't seen others yet. But whenever the add is successful, there
	   will be a separate "went online" packet when the auth. request is
	   accepted. Couldn't find any test account that doesn't require auth.
	   unfortunately (if there is even such a thing?) */
	   
	/* A non-zero status (i've seen 2) seems to mean the buddy is already 
	 * added and is online */
	if (status) {
		LOG(("Setting online see packet for info"));
		yahoo_dump_unhandled(pkt);
		YAHOO_CALLBACK(ext_yahoo_status_changed) (yd->client_id, who,
			YAHOO_STATUS_AVAILABLE, NULL, 0, 0, 0);
	}
#endif
	/* BitlBee: Need ACK of added buddy, if it was successful. */
	if (status == 0) {
		YList *tmp = y_list_append(NULL, bud);
		YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id, tmp);
		y_list_free(tmp);
	}
}

static void yahoo_process_buddydel(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	struct yahoo_data *yd = yid->yd;
	char *who = NULL;
	char *where = NULL;
	struct yahoo_buddy *bud;

	YList *buddy;

	YList *l;
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 1)
			; /* Me... don't care */
		else if (pair->key == 7)
			who = pair->value;
		else if (pair->key == 65)
			where = pair->value;
		else if (pair->key == 66)
			; /* unk_66 */
		else
			DEBUG_MSG(("unknown key: %d = %s", pair->key,
					pair->value));
	}

	if (!who || !where)
		return;

	bud = y_new0(struct yahoo_buddy, 1);
	bud->id = strdup(who);
	bud->group = strdup(where);

	buddy = y_list_find_custom(yd->buddies, bud, is_same_bud);

	FREE(bud->id);
	FREE(bud->group);
	FREE(bud);

	if (buddy) {
		bud = buddy->data;
		yd->buddies = y_list_remove_link(yd->buddies, buddy);
		y_list_free_1(buddy);

		FREE(bud->id);
		FREE(bud->group);
		FREE(bud->real_name);
		FREE(bud);

		bud = NULL;
	}
}

static void yahoo_process_ignore(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	YList *l;
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 0)
			; /* who */
		if (pair->key == 1)
			; /* Me... don't care */
		if (pair->key == 13)	/* 1 == ignore, 2 == unignore */
			;
		if (pair->key == 66)
			; /* status */
	}

	/*
	 * status
	 *      0  - ok
	 *      2  - already in ignore list, could not add
	 *      3  - not in ignore list, could not delete
	 *      12 - is a buddy, could not add
	 */

/*	if(status)
		YAHOO_CALLBACK(ext_yahoo_error)(yd->client_id, who, 0, status);
*/
}

static void yahoo_process_voicechat(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	char *who = NULL;
	char *me = NULL;
	char *room = NULL;

	YList *l;
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 4)
			who = pair->value;
		if (pair->key == 5)
			me = pair->value;
		if (pair->key == 13)
			; /* voice room */
		if (pair->key == 57)
			room = pair->value;
	}

	NOTICE(("got voice chat invite from %s in %s to identity %s", who, room,
			me));
	/* 
	 * send: s:0 1:me 5:who 57:room 13:1
	 * ????  s:4 5:who 10:99 19:-1615114531
	 * gotr: s:4 5:who 10:99 19:-1615114615
	 * ????  s:1 5:me 4:who 57:room 13:3room
	 * got:  s:1 5:me 4:who 57:room 13:1room
	 * rej:  s:0 1:me 5:who 57:room 13:3
	 * rejr: s:4 5:who 10:99 19:-1617114599
	 */
}

static void yahoo_process_ping(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	char *errormsg = NULL;

	YList *l;
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 16)
			errormsg = pair->value;
	}

	NOTICE(("got ping packet"));
	YAHOO_CALLBACK(ext_yahoo_got_ping) (yid->yd->client_id, errormsg);
}

static void yahoo_process_buddy_change_group(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	YList *l;
	char *me = NULL;
	char *who = NULL;
	char *old_group = NULL;
	char *new_group = NULL;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 1)
			me = pair->value;
		if (pair->key == 7)
			who = pair->value;
		if (pair->key == 224)
			old_group = pair->value;
		if (pair->key == 264)
			new_group = pair->value;
	}

	YAHOO_CALLBACK(ext_yahoo_got_buddy_change_group) (yid->yd->client_id,
		me, who, old_group, new_group);
}

static void _yahoo_webcam_get_server_connected(void *fd, int error, void *d)
{
	struct yahoo_input_data *yid = d;
	char *who = yid->wcm->user;
	char *data = NULL;
	char *packet = NULL;
	unsigned char magic_nr[] = { 0, 1, 0 };
	unsigned char header_len = 8;
	unsigned int len = 0;
	unsigned int pos = 0;

	if (error || !fd) {
		FREE(who);
		FREE(yid);
		return;
	}

	yid->fd = fd;
	inputs = y_list_prepend(inputs, yid);

	/* send initial packet */
	if (who)
		data = strdup("<RVWCFG>");
	else
		data = strdup("<RUPCFG>");
	yahoo_add_to_send_queue(yid, data, strlen(data));
	FREE(data);

	/* send data */
	if (who) {
		data = strdup("g=");
		data = y_string_append(data, who);
		data = y_string_append(data, "\r\n");
	} else {
		data = strdup("f=1\r\n");
	}
	len = strlen(data);
	packet = y_new0(char, header_len + len);
	packet[pos++] = header_len;
	memcpy(packet + pos, magic_nr, sizeof(magic_nr));
	pos += sizeof(magic_nr);
	pos += yahoo_put32(packet + pos, len);
	memcpy(packet + pos, data, len);
	pos += len;
	yahoo_add_to_send_queue(yid, packet, pos);
	FREE(packet);
	FREE(data);

	yid->read_tag =
		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
		YAHOO_INPUT_READ, yid);
}

static void yahoo_webcam_get_server(struct yahoo_input_data *y, char *who,
	char *key)
{
	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);
	struct yahoo_server_settings *yss = y->yd->server_settings;

	yid->type = YAHOO_CONNECTION_WEBCAM_MASTER;
	yid->yd = y->yd;
	yid->wcm = y_new0(struct yahoo_webcam, 1);
	yid->wcm->user = who ? strdup(who) : NULL;
	yid->wcm->direction = who ? YAHOO_WEBCAM_DOWNLOAD : YAHOO_WEBCAM_UPLOAD;
	yid->wcm->key = strdup(key);

	YAHOO_CALLBACK(ext_yahoo_connect_async) (yid->yd->client_id,
		yss->webcam_host, yss->webcam_port,
		_yahoo_webcam_get_server_connected, yid, 0);

}

static YList *webcam_queue = NULL;
static void yahoo_process_webcam_key(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	char *key = NULL;
	char *who = NULL;

	YList *l;
	yahoo_dump_unhandled(pkt);
	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		if (pair->key == 5)
			; /* me */
		if (pair->key == 61)
			key = pair->value;
	}

	l = webcam_queue;
	if (!l)
		return;
	who = l->data;
	webcam_queue = y_list_remove_link(webcam_queue, webcam_queue);
	y_list_free_1(l);
	yahoo_webcam_get_server(yid, who, key);
	FREE(who);
}

static void yahoo_packet_process(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	DEBUG_MSG(("yahoo_packet_process: 0x%02x", pkt->service));
	switch (pkt->service) {
	case YAHOO_SERVICE_USERSTAT:
	case YAHOO_SERVICE_LOGON:
	case YAHOO_SERVICE_LOGOFF:
	case YAHOO_SERVICE_ISAWAY:
	case YAHOO_SERVICE_ISBACK:
	case YAHOO_SERVICE_GAMELOGON:
	case YAHOO_SERVICE_GAMELOGOFF:
	case YAHOO_SERVICE_IDACT:
	case YAHOO_SERVICE_IDDEACT:
	case YAHOO_SERVICE_Y6_STATUS_UPDATE:
	case YAHOO_SERVICE_Y8_STATUS:
		yahoo_process_status(yid, pkt);
		break;
	case YAHOO_SERVICE_NOTIFY:
		yahoo_process_notify(yid, pkt);
		break;
	case YAHOO_SERVICE_MESSAGE:
	case YAHOO_SERVICE_GAMEMSG:
	case YAHOO_SERVICE_SYSMESSAGE:
		yahoo_process_message(yid, pkt);
		break;
	case YAHOO_SERVICE_NEWMAIL:
		yahoo_process_mail(yid, pkt);
		break;
	case YAHOO_SERVICE_Y7_AUTHORIZATION:
		yahoo_process_new_contact(yid, pkt);
		break;
	case YAHOO_SERVICE_NEWCONTACT:
		yahoo_process_contact(yid, pkt);
		break;
	case YAHOO_SERVICE_LIST:
		yahoo_process_list(yid, pkt);
		break;
	case YAHOO_SERVICE_VERIFY:
		yahoo_process_verify(yid, pkt);
		break;
	case YAHOO_SERVICE_AUTH:
		yahoo_process_auth(yid, pkt);
		break;
	case YAHOO_SERVICE_AUTHRESP:
		yahoo_process_auth_resp(yid, pkt);
		break;
	case YAHOO_SERVICE_CONFINVITE:
	case YAHOO_SERVICE_CONFADDINVITE:
	case YAHOO_SERVICE_CONFDECLINE:
	case YAHOO_SERVICE_CONFLOGON:
	case YAHOO_SERVICE_CONFLOGOFF:
	case YAHOO_SERVICE_CONFMSG:
		yahoo_process_conference(yid, pkt);
		break;
	case YAHOO_SERVICE_CHATONLINE:
	case YAHOO_SERVICE_CHATGOTO:
	case YAHOO_SERVICE_CHATJOIN:
	case YAHOO_SERVICE_CHATLEAVE:
	case YAHOO_SERVICE_CHATEXIT:
	case YAHOO_SERVICE_CHATLOGOUT:
	case YAHOO_SERVICE_CHATPING:
	case YAHOO_SERVICE_COMMENT:
		yahoo_process_chat(yid, pkt);
		break;
	case YAHOO_SERVICE_P2PFILEXFER:
	case YAHOO_SERVICE_Y7_FILETRANSFER:
		yahoo_process_filetransfer(yid, pkt);
		break;
	case YAHOO_SERVICE_Y7_FILETRANSFERINFO:
		yahoo_process_filetransferinfo(yid, pkt);
		break;
	case YAHOO_SERVICE_Y7_FILETRANSFERACCEPT:
		yahoo_process_filetransferaccept(yid, pkt);
		break;
	case YAHOO_SERVICE_ADDBUDDY:
		yahoo_process_buddyadd(yid, pkt);
		break;
	case YAHOO_SERVICE_REMBUDDY:
		yahoo_process_buddydel(yid, pkt);
		break;
	case YAHOO_SERVICE_IGNORECONTACT:
		yahoo_process_ignore(yid, pkt);
		break;
	case YAHOO_SERVICE_VOICECHAT:
		yahoo_process_voicechat(yid, pkt);
		break;
	case YAHOO_SERVICE_WEBCAM:
		yahoo_process_webcam_key(yid, pkt);
		break;
	case YAHOO_SERVICE_PING:
		yahoo_process_ping(yid, pkt);
		break;
	case YAHOO_SERVICE_Y7_CHANGE_GROUP:
		yahoo_process_buddy_change_group(yid, pkt);
		break;
	case YAHOO_SERVICE_IDLE:
	case YAHOO_SERVICE_MAILSTAT:
	case YAHOO_SERVICE_CHATINVITE:
	case YAHOO_SERVICE_CALENDAR:
	case YAHOO_SERVICE_NEWPERSONALMAIL:
	case YAHOO_SERVICE_ADDIDENT:
	case YAHOO_SERVICE_ADDIGNORE:
	case YAHOO_SERVICE_GOTGROUPRENAME:
	case YAHOO_SERVICE_GROUPRENAME:
	case YAHOO_SERVICE_PASSTHROUGH2:
	case YAHOO_SERVICE_CHATLOGON:
	case YAHOO_SERVICE_CHATLOGOFF:
	case YAHOO_SERVICE_CHATMSG:
	case YAHOO_SERVICE_REJECTCONTACT:
	case YAHOO_SERVICE_PEERTOPEER:
		WARNING(("unhandled service 0x%02x", pkt->service));
		yahoo_dump_unhandled(pkt);
		break;
	case YAHOO_SERVICE_PICTURE:
		yahoo_process_picture(yid, pkt);
		break;
	case YAHOO_SERVICE_PICTURE_CHECKSUM:
		yahoo_process_picture_checksum(yid, pkt);
		break;
	case YAHOO_SERVICE_PICTURE_UPLOAD:
		yahoo_process_picture_upload(yid, pkt);
		break;
	case YAHOO_SERVICE_Y8_LIST:	/* Buddy List */
		yahoo_process_buddy_list(yid, pkt);
		break;
	default:
		WARNING(("unknown service 0x%02x", pkt->service));
		yahoo_dump_unhandled(pkt);
		break;
	}
}

static struct yahoo_packet *yahoo_getdata(struct yahoo_input_data *yid)
{
	struct yahoo_packet *pkt;
	struct yahoo_data *yd = yid->yd;
	int pos = 0;
	int pktlen;

	if (!yd)
		return NULL;

	DEBUG_MSG(("rxlen is %d", yid->rxlen));
	if (yid->rxlen < YAHOO_PACKET_HDRLEN) {
		DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN"));
		return NULL;
	}

	pos += 4;		/* YMSG */
	pos += 2;
	pos += 2;

	pktlen = yahoo_get16(yid->rxqueue + pos);
	pos += 2;
	DEBUG_MSG(("%d bytes to read, rxlen is %d", pktlen, yid->rxlen));

	if (yid->rxlen < (YAHOO_PACKET_HDRLEN + pktlen)) {
		DEBUG_MSG(("len < YAHOO_PACKET_HDRLEN + pktlen"));
		return NULL;
	}

	LOG(("reading packet"));
	yahoo_packet_dump(yid->rxqueue, YAHOO_PACKET_HDRLEN + pktlen);

	pkt = yahoo_packet_new(0, 0, 0);

	pkt->service = yahoo_get16(yid->rxqueue + pos);
	pos += 2;
	pkt->status = yahoo_get32(yid->rxqueue + pos);
	pos += 4;
	DEBUG_MSG(("Yahoo Service: 0x%02x Status: %d", pkt->service,
			pkt->status));
	pkt->id = yahoo_get32(yid->rxqueue + pos);
	pos += 4;

	yd->session_id = pkt->id;

	yahoo_packet_read(pkt, yid->rxqueue + pos, pktlen);

	yid->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
	DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
	if (yid->rxlen > 0) {
		unsigned char *tmp = y_memdup(yid->rxqueue + YAHOO_PACKET_HDRLEN
			+ pktlen, yid->rxlen);
		FREE(yid->rxqueue);
		yid->rxqueue = tmp;
		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
				yid->rxqueue));
	} else {
		DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
		FREE(yid->rxqueue);
	}

	return pkt;
}

#if 0
static struct yab *yahoo_yab_read(unsigned char *d, int len)
{
	char *st, *en;
	char *data = (char *)d;
	struct yab *yab = NULL;

	data[len] = '\0';

	DEBUG_MSG(("Got yab: %s", data));
	st = en = strstr(data, "e0=\"");
	if (st) {
		yab = y_new0(struct yab, 1);

		st += strlen("e0=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->email = yahoo_xmldecode(st);
	}

	if (!en)
		return NULL;

	st = strstr(en, "id=\"");
	if (st) {
		st += strlen("id=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->yid = atoi(yahoo_xmldecode(st));
	}

	st = strstr(en, "fn=\"");
	if (st) {
		st += strlen("fn=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->fname = yahoo_xmldecode(st);
	}

	st = strstr(en, "ln=\"");
	if (st) {
		st += strlen("ln=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->lname = yahoo_xmldecode(st);
	}

	st = strstr(en, "nn=\"");
	if (st) {
		st += strlen("nn=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->nname = yahoo_xmldecode(st);
	}

	st = strstr(en, "yi=\"");
	if (st) {
		st += strlen("yi=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->id = yahoo_xmldecode(st);
	}

	st = strstr(en, "hphone=\"");
	if (st) {
		st += strlen("hphone=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->hphone = yahoo_xmldecode(st);
	}

	st = strstr(en, "wphone=\"");
	if (st) {
		st += strlen("wphone=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->wphone = yahoo_xmldecode(st);
	}

	st = strstr(en, "mphone=\"");
	if (st) {
		st += strlen("mphone=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->mphone = yahoo_xmldecode(st);
	}

	st = strstr(en, "dbid=\"");
	if (st) {
		st += strlen("dbid=\"");
		en = strchr(st, '"');
		*en++ = '\0';
		yab->dbid = atoi(st);
	}

	return yab;
}

static struct yab *yahoo_getyab(struct yahoo_input_data *yid)
{
	struct yab *yab = NULL;
	int pos = 0, end = 0;
	struct yahoo_data *yd = yid->yd;

	if (!yd)
		return NULL;

	do {
		DEBUG_MSG(("rxlen is %d", yid->rxlen));

		if (yid->rxlen <= strlen("<ct"))
			return NULL;

		/* start with <ct */
		while (pos < yid->rxlen - strlen("<ct") + 1
			&& memcmp(yid->rxqueue + pos, "<ct", strlen("<ct")))
			pos++;

		if (pos >= yid->rxlen - 1)
			return NULL;

		end = pos + 2;
		/* end with > */
		while (end < yid->rxlen - strlen(">")
			&& memcmp(yid->rxqueue + end, ">", strlen(">")))
			end++;

		if (end >= yid->rxlen - 1)
			return NULL;

		yab = yahoo_yab_read(yid->rxqueue + pos, end + 2 - pos);

		yid->rxlen -= end + 1;
		DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen,
				yid->rxqueue));
		if (yid->rxlen > 0) {
			unsigned char *tmp =
				y_memdup(yid->rxqueue + end + 1, yid->rxlen);
			FREE(yid->rxqueue);
			yid->rxqueue = tmp;
			DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
					yid->rxqueue));
		} else {
			DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
			FREE(yid->rxqueue);
		}

	} while (!yab && end < yid->rxlen - 1);

	return yab;
}
#endif

static char *yahoo_getwebcam_master(struct yahoo_input_data *yid)
{
	unsigned int pos = 0;
	unsigned int len = 0;
	unsigned int status = 0;
	char *server = NULL;
	struct yahoo_data *yd = yid->yd;

	if (!yid || !yd)
		return NULL;

	DEBUG_MSG(("rxlen is %d", yid->rxlen));

	len = yid->rxqueue[pos++];
	if (yid->rxlen < len)
		return NULL;

	/* extract status (0 = ok, 6 = webcam not online) */
	status = yid->rxqueue[pos++];

	if (status == 0) {
		pos += 2;	/* skip next 2 bytes */
		server = y_memdup(yid->rxqueue + pos, 16);
		pos += 16;
	} else if (status == 6) {
		YAHOO_CALLBACK(ext_yahoo_webcam_closed)
			(yd->client_id, yid->wcm->user, 4);
	}

	/* skip rest of the data */

	yid->rxlen -= len;
	DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
	if (yid->rxlen > 0) {
		unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen);
		FREE(yid->rxqueue);
		yid->rxqueue = tmp;
		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
				yid->rxqueue));
	} else {
		DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
		FREE(yid->rxqueue);
	}

	return server;
}

static int yahoo_get_webcam_data(struct yahoo_input_data *yid)
{
	unsigned char reason = 0;
	unsigned int pos = 0;
	unsigned int begin = 0;
	unsigned int end = 0;
	unsigned int closed = 0;
	unsigned char header_len = 0;
	char *who;
	int connect = 0;
	struct yahoo_data *yd = yid->yd;

	if (!yd)
		return -1;

	if (!yid->wcm || !yid->wcd || !yid->rxlen)
		return -1;

	DEBUG_MSG(("rxlen is %d", yid->rxlen));

	/* if we are not reading part of image then read header */
	if (!yid->wcd->to_read) {
		header_len = yid->rxqueue[pos++];
		yid->wcd->packet_type = 0;

		if (yid->rxlen < header_len)
			return 0;

		if (header_len >= 8) {
			reason = yid->rxqueue[pos++];
			/* next 2 bytes should always be 05 00 */
			pos += 2;
			yid->wcd->data_size = yahoo_get32(yid->rxqueue + pos);
			pos += 4;
			yid->wcd->to_read = yid->wcd->data_size;
		}
		if (header_len >= 13) {
			yid->wcd->packet_type = yid->rxqueue[pos++];
			yid->wcd->timestamp = yahoo_get32(yid->rxqueue + pos);
			pos += 4;
		}

		/* skip rest of header */
		pos = header_len;
	}

	begin = pos;
	pos += yid->wcd->to_read;
	if (pos > yid->rxlen)
		pos = yid->rxlen;

	/* if it is not an image then make sure we have the whole packet */
	if (yid->wcd->packet_type != 0x02) {
		if ((pos - begin) != yid->wcd->data_size) {
			yid->wcd->to_read = 0;
			return 0;
		} else {
			yahoo_packet_dump(yid->rxqueue + begin, pos - begin);
		}
	}

	DEBUG_MSG(("packet type %.2X, data length %d", yid->wcd->packet_type,
			yid->wcd->data_size));

	/* find out what kind of packet we got */
	switch (yid->wcd->packet_type) {
	case 0x00:
		/* user requests to view webcam (uploading) */
		if (yid->wcd->data_size &&
			yid->wcm->direction == YAHOO_WEBCAM_UPLOAD) {
			end = begin;
			while (end <= yid->rxlen && yid->rxqueue[end++] != 13) ;
			if (end > begin) {
				who = y_memdup(yid->rxqueue + begin,
					end - begin);
				who[end - begin - 1] = 0;
				YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->
					client_id, who + 2, 2);
				FREE(who);
			}
		}

		if (yid->wcm->direction == YAHOO_WEBCAM_DOWNLOAD) {
			/* timestamp/status field */
			/* 0 = declined viewing permission */
			/* 1 = accepted viewing permission */
			if (yid->wcd->timestamp == 0) {
				YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->
					client_id, yid->wcm->user, 3);
			}
		}
		break;
	case 0x01:		/* status packets?? */
		/* timestamp contains status info */
		/* 00 00 00 01 = we have data?? */
		break;
	case 0x02:		/* image data */
		YAHOO_CALLBACK(ext_yahoo_got_webcam_image) (yd->client_id,
			yid->wcm->user, yid->rxqueue + begin,
			yid->wcd->data_size, pos - begin, yid->wcd->timestamp);
		break;
	case 0x05:		/* response packets when uploading */
		if (!yid->wcd->data_size) {
			YAHOO_CALLBACK(ext_yahoo_webcam_data_request) (yd->
				client_id, yid->wcd->timestamp);
		}
		break;
	case 0x07:		/* connection is closing */
		switch (reason) {
		case 0x01:	/* user closed connection */
			closed = 1;
			break;
		case 0x0F:	/* user cancelled permission */
			closed = 2;
			break;
		}
		YAHOO_CALLBACK(ext_yahoo_webcam_closed) (yd->client_id,
			yid->wcm->user, closed);
		break;
	case 0x0C:		/* user connected */
	case 0x0D:		/* user disconnected */
		if (yid->wcd->data_size) {
			who = y_memdup(yid->rxqueue + begin, pos - begin + 1);
			who[pos - begin] = 0;
			if (yid->wcd->packet_type == 0x0C)
				connect = 1;
			else
				connect = 0;
			YAHOO_CALLBACK(ext_yahoo_webcam_viewer) (yd->client_id,
				who, connect);
			FREE(who);
		}
		break;
	case 0x13:		/* user data */
		/* i=user_ip (ip of the user we are viewing) */
		/* j=user_ext_ip (external ip of the user we */
		/*                are viewing) */
		break;
	case 0x17:		/* ?? */
		break;
	}
	yid->wcd->to_read -= pos - begin;

	yid->rxlen -= pos;
	DEBUG_MSG(("rxlen == %d, rxqueue == %p", yid->rxlen, yid->rxqueue));
	if (yid->rxlen > 0) {
		unsigned char *tmp = y_memdup(yid->rxqueue + pos, yid->rxlen);
		FREE(yid->rxqueue);
		yid->rxqueue = tmp;
		DEBUG_MSG(("new rxlen == %d, rxqueue == %p", yid->rxlen,
				yid->rxqueue));
	} else {
		DEBUG_MSG(("freed rxqueue == %p", yid->rxqueue));
		FREE(yid->rxqueue);
	}

	/* If we read a complete packet return success */
	if (!yid->wcd->to_read)
		return 1;

	return 0;
}

int yahoo_write_ready(int id, void *fd, void *data)
{
	struct yahoo_input_data *yid = data;
	int len;
	struct data_queue *tx;

	LOG(("write callback: id=%d fd=%p data=%p", id, fd, data));
	if (!yid || !yid->txqueues)
		return -2;

	tx = yid->txqueues->data;
	LOG(("writing %d bytes", tx->len));
	len = yahoo_send_data(fd, tx->queue, MIN(1024, tx->len));

	if (len == -1 && errno == EAGAIN)
		return 1;

	if (len <= 0) {
		int e = errno;
		DEBUG_MSG(("len == %d (<= 0)", len));
		while (yid->txqueues) {
			YList *l = yid->txqueues;
			tx = l->data;
			free(tx->queue);
			free(tx);
			yid->txqueues =
				y_list_remove_link(yid->txqueues,
				yid->txqueues);
			y_list_free_1(l);
		}
		LOG(("yahoo_write_ready(%d, %p) len < 0", id, fd));
		YAHOO_CALLBACK(ext_yahoo_remove_handler) (id, yid->write_tag);
		yid->write_tag = 0;
		errno = e;
		return 0;
	}


	tx->len -= len;
	if (tx->len > 0) {
		unsigned char *tmp = y_memdup(tx->queue + len, tx->len);
		FREE(tx->queue);
		tx->queue = tmp;
	} else {
		YList *l = yid->txqueues;
		free(tx->queue);
		free(tx);
		yid->txqueues =
			y_list_remove_link(yid->txqueues, yid->txqueues);
		y_list_free_1(l);
		/*
		   if(!yid->txqueues) 
		   LOG(("yahoo_write_ready(%d, %d) !yxqueues", id, fd));
		 */
		if (!yid->txqueues) {
			LOG(("yahoo_write_ready(%d, %p) !txqueues", id, fd));
			YAHOO_CALLBACK(ext_yahoo_remove_handler) (id,
				yid->write_tag);
			yid->write_tag = 0;
		}
	}

	return 1;
}

static void yahoo_process_pager_connection(struct yahoo_input_data *yid,
	int over)
{
	struct yahoo_packet *pkt;
	struct yahoo_data *yd = yid->yd;
	int id = yd->client_id;

	if (over)
		return;

	while (find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER)
		&& (pkt = yahoo_getdata(yid)) != NULL) {

		yahoo_packet_process(yid, pkt);

		yahoo_packet_free(pkt);
	}
}

static void yahoo_process_chatcat_connection(struct yahoo_input_data *yid,
	int over)
{
	if (over)
		return;

	if (strstr((char *)yid->rxqueue + (yid->rxlen - 20), "</content>")) {
		YAHOO_CALLBACK(ext_yahoo_chat_cat_xml) (yid->yd->client_id,
			(char *)yid->rxqueue);
	}
}

#if 0
static void yahoo_process_yab_connection(struct yahoo_input_data *yid, int over)
{
	struct yahoo_data *yd = yid->yd;
	struct yab *yab;
	YList *buds;
	int changed = 0;
	int id = yd->client_id;
	int yab_used = 0;

	LOG(("Got data for YAB"));

	if (over)
		return;

	while (find_input_by_id_and_type(id, YAHOO_CONNECTION_YAB)
		&& (yab = yahoo_getyab(yid)) != NULL) {
		if (!yab->id)
			continue;

		changed = 1;
		yab_used = 0;
		for (buds = yd->buddies; buds; buds = buds->next) {
			struct yahoo_buddy *bud = buds->data;
			if (!strcmp(bud->id, yab->id)) {
				yab_used = 1;
				bud->yab_entry = yab;
				if (yab->nname) {
					bud->real_name = strdup(yab->nname);
				} else if (yab->fname && yab->lname) {
					bud->real_name = y_new0(char,
						strlen(yab->fname) +
						strlen(yab->lname) + 2);
					sprintf(bud->real_name, "%s %s",
						yab->fname, yab->lname);
				} else if (yab->fname) {
					bud->real_name = strdup(yab->fname);
				}
				break;	/* for */
			}
		}

		if (!yab_used) {
			FREE(yab->fname);
			FREE(yab->lname);
			FREE(yab->nname);
			FREE(yab->id);
			FREE(yab->email);
			FREE(yab->hphone);
			FREE(yab->wphone);
			FREE(yab->mphone);
			FREE(yab);
		}

	}

	if (changed)
		YAHOO_CALLBACK(ext_yahoo_got_buddies) (yd->client_id,
			yd->buddies);
}
#endif

static void yahoo_process_search_connection(struct yahoo_input_data *yid,
	int over)
{
	struct yahoo_found_contact *yct = NULL;
	char *p = (char *)yid->rxqueue, *np, *cp;
	int k, n;
	int start = 0, found = 0, total = 0;
	YList *contacts = NULL;
	struct yahoo_input_data *pyid =
		find_input_by_id_and_type(yid->yd->client_id,
		YAHOO_CONNECTION_PAGER);

	if (!over || !pyid)
		return;

	if (p && (p = strstr(p, "\r\n\r\n"))) {
		p += 4;

		for (k = 0; (p = strchr(p, 4)) && (k < 4); k++) {
			p++;
			n = atoi(p);
			switch (k) {
			case 0:
				found = pyid->ys->lsearch_nfound = n;
				break;
			case 2:
				start = pyid->ys->lsearch_nstart = n;
				break;
			case 3:
				total = pyid->ys->lsearch_ntotal = n;
				break;
			}
		}

		if (p)
			p++;

		k = 0;
		while (p && *p) {
			cp = p;
			np = strchr(p, 4);

			if (!np)
				break;
			*np = 0;
			p = np + 1;

			switch (k++) {
			case 1:
				if (strlen(cp) > 2
					&& y_list_length(contacts) < total) {
					yct = y_new0(struct yahoo_found_contact,
						1);
					contacts = y_list_append(contacts, yct);
					yct->id = cp + 2;
				} else {
					*p = 0;
				}
				break;
			case 2:
				yct->online = !strcmp(cp, "2") ? 1 : 0;
				break;
			case 3:
				yct->gender = cp;
				break;
			case 4:
				yct->age = atoi(cp);
				break;
			case 5:
				/* not worth the context switch for strcmp */
				if (cp[0] != '\005' || cp[1] != '\000')
					yct->location = cp;
				k = 0;
				break;
			}
		}
	}

	YAHOO_CALLBACK(ext_yahoo_got_search_result) (yid->yd->client_id, found,
		start, total, contacts);

	while (contacts) {
		YList *node = contacts;
		contacts = y_list_remove_link(contacts, node);
		free(node->data);
		y_list_free_1(node);
	}
}

static void _yahoo_webcam_connected(void *fd, int error, void *d)
{
	struct yahoo_input_data *yid = d;
	struct yahoo_webcam *wcm = yid->wcm;
	struct yahoo_data *yd = yid->yd;
	char conn_type[100];
	char *data = NULL;
	char *packet = NULL;
	unsigned char magic_nr[] = { 1, 0, 0, 0, 1 };
	unsigned header_len = 0;
	unsigned int len = 0;
	unsigned int pos = 0;

	if (error || !fd) {
		FREE(yid);
		return;
	}

	yid->fd = fd;
	inputs = y_list_prepend(inputs, yid);

	LOG(("Connected"));
	/* send initial packet */
	switch (wcm->direction) {
	case YAHOO_WEBCAM_DOWNLOAD:
		data = strdup("<REQIMG>");
		break;
	case YAHOO_WEBCAM_UPLOAD:
		data = strdup("<SNDIMG>");
		break;
	default:
		return;
	}
	yahoo_add_to_send_queue(yid, data, strlen(data));
	FREE(data);

	/* send data */
	switch (wcm->direction) {
	case YAHOO_WEBCAM_DOWNLOAD:
		header_len = 8;
		data = strdup("a=2\r\nc=us\r\ne=21\r\nu=");
		data = y_string_append(data, yd->user);
		data = y_string_append(data, "\r\nt=");
		data = y_string_append(data, wcm->key);
		data = y_string_append(data, "\r\ni=");
		data = y_string_append(data, wcm->my_ip);
		data = y_string_append(data, "\r\ng=");
		data = y_string_append(data, wcm->user);
		data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
		snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
		data = y_string_append(data, conn_type);
		data = y_string_append(data, "\r\n");
		break;
	case YAHOO_WEBCAM_UPLOAD:
		header_len = 13;
		data = strdup("a=2\r\nc=us\r\nu=");
		data = y_string_append(data, yd->user);
		data = y_string_append(data, "\r\nt=");
		data = y_string_append(data, wcm->key);
		data = y_string_append(data, "\r\ni=");
		data = y_string_append(data, wcm->my_ip);
		data = y_string_append(data, "\r\no=w-2-5-1\r\np=");
		snprintf(conn_type, sizeof(conn_type), "%d", wcm->conn_type);
		data = y_string_append(data, conn_type);
		data = y_string_append(data, "\r\nb=");
		data = y_string_append(data, wcm->description);
		data = y_string_append(data, "\r\n");
		break;
	}

	len = strlen(data);
	packet = y_new0(char, header_len + len);
	packet[pos++] = header_len;
	packet[pos++] = 0;
	switch (wcm->direction) {
	case YAHOO_WEBCAM_DOWNLOAD:
		packet[pos++] = 1;
		packet[pos++] = 0;
		break;
	case YAHOO_WEBCAM_UPLOAD:
		packet[pos++] = 5;
		packet[pos++] = 0;
		break;
	}

	pos += yahoo_put32(packet + pos, len);
	if (wcm->direction == YAHOO_WEBCAM_UPLOAD) {
		memcpy(packet + pos, magic_nr, sizeof(magic_nr));
		pos += sizeof(magic_nr);
	}
	memcpy(packet + pos, data, len);
	yahoo_add_to_send_queue(yid, packet, header_len + len);
	FREE(packet);
	FREE(data);

	yid->read_tag =
		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id,
		yid->fd, YAHOO_INPUT_READ, yid);
}

static void yahoo_webcam_connect(struct yahoo_input_data *y)
{
	struct yahoo_webcam *wcm = y->wcm;
	struct yahoo_input_data *yid;

	if (!wcm || !wcm->server || !wcm->key)
		return;

	yid = y_new0(struct yahoo_input_data, 1);
	yid->type = YAHOO_CONNECTION_WEBCAM;
	yid->yd = y->yd;

	/* copy webcam data to new connection */
	yid->wcm = y->wcm;
	y->wcm = NULL;

	yid->wcd = y_new0(struct yahoo_webcam_data, 1);

	LOG(("Connecting to: %s:%d", wcm->server, wcm->port));
	YAHOO_CALLBACK(ext_yahoo_connect_async) (y->yd->client_id, wcm->server,
		wcm->port, _yahoo_webcam_connected, yid, 0);

}

static void yahoo_process_webcam_master_connection(struct yahoo_input_data *yid,
	int over)
{
	char *server;
	struct yahoo_server_settings *yss;

	if (over)
		return;

	server = yahoo_getwebcam_master(yid);

	if (server) {
		yss = yid->yd->server_settings;
		yid->wcm->server = strdup(server);
		yid->wcm->port = yss->webcam_port;
		yid->wcm->conn_type = yss->conn_type;
		yid->wcm->my_ip = strdup(yss->local_host);
		if (yid->wcm->direction == YAHOO_WEBCAM_UPLOAD)
			yid->wcm->description = strdup(yss->webcam_description);
		yahoo_webcam_connect(yid);
		FREE(server);
	}
}

static void yahoo_process_webcam_connection(struct yahoo_input_data *yid,
	int over)
{
	int id = yid->yd->client_id;
	void *fd = yid->fd;

	if (over)
		return;

	/* as long as we still have packets available keep processing them */
	while (find_input_by_id_and_fd(id, fd)
		&& yahoo_get_webcam_data(yid) == 1) ;
}

static void (*yahoo_process_connection[]) (struct yahoo_input_data *,
	int over) = {
yahoo_process_pager_connection, yahoo_process_ft_connection,
		NULL, /*yahoo_process_yab_connection, */
		yahoo_process_webcam_master_connection,
		yahoo_process_webcam_connection,
		yahoo_process_chatcat_connection,
		yahoo_process_search_connection};

int yahoo_read_ready(int id, void *fd, void *data)
{
	struct yahoo_input_data *yid = data;
	char buf[1024];
	int len;

	LOG(("read callback: id=%d fd=%p data=%p", id, fd, data));
	if (!yid)
		return -2;

	do {
		len = YAHOO_CALLBACK(ext_yahoo_read) (fd, buf, sizeof(buf));
	} while (len == -1 && errno == EINTR);

	if (len == -1 && (errno == EAGAIN || errno == EINTR))	/* we'll try again later */
		return 1;

	if (len <= 0) {
		int e = errno;
		DEBUG_MSG(("len == %d (<= 0)", len));

		if (yid->type == YAHOO_CONNECTION_PAGER) {
			YAHOO_CALLBACK(ext_yahoo_login_response) (yid->yd->
				client_id, YAHOO_LOGIN_SOCK, NULL);
		}

		yahoo_process_connection[yid->type] (yid, 1);
		yahoo_input_close(yid);

		/* no need to return an error, because we've already fixed it */
		if (len == 0)
			return 1;

		errno = e;
		LOG(("read error: %s", strerror(errno)));
		return -1;
	}

	yid->rxqueue =
		y_renew(unsigned char, yid->rxqueue, len + yid->rxlen + 1);
	memcpy(yid->rxqueue + yid->rxlen, buf, len);
	yid->rxlen += len;
	yid->rxqueue[yid->rxlen] = 0;

	yahoo_process_connection[yid->type] (yid, 0);

	return len;
}

int yahoo_init_with_attributes(const char *username, const char *password, ...)
{
	va_list ap;
	struct yahoo_data *yd;

	yd = y_new0(struct yahoo_data, 1);

	if (!yd)
		return 0;

	yd->user = strdup(username);
	yd->password = strdup(password);

	yd->initial_status = -1;
	yd->current_status = -1;

	yd->client_id = ++last_id;

	add_to_list(yd);

	va_start(ap, password);
	yd->server_settings = _yahoo_assign_server_settings(ap);
	va_end(ap);

	return yd->client_id;
}

int yahoo_init(const char *username, const char *password)
{
	return yahoo_init_with_attributes(username, password, NULL);
}

static void yahoo_connected(void *fd, int error, void *data)
{
	struct connect_callback_data *ccd = data;
	struct yahoo_data *yd = ccd->yd;
	struct yahoo_packet *pkt;
	struct yahoo_input_data *yid;
	struct yahoo_server_settings *yss = yd->server_settings;

	if (error) {
		int tag;
		if (fallback_ports[ccd->i]) {
			char *host = yss->pager_host;

			if (!host)
				host = yss->pager_host_list[ccd->server_i];

			yss->pager_port = fallback_ports[ccd->i++];
			tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->
				client_id, host, yss->pager_port,
				yahoo_connected, ccd, 0);

			if (tag > 0)
				ccd->tag = tag;
		} else if (yss->pager_host_list
				&& yss->pager_host_list[ccd->server_i]) {

			/* Get back to the default port */
			yss->pager_port = pager_port;
			ccd->server_i++;
			LOG(("Fallback: Connecting to %s:%d", yss->pager_host_list[ccd->server_i], yss->pager_port));

			ccd->i = 0;
			tag = YAHOO_CALLBACK(ext_yahoo_connect_async) (yd->client_id,
				yss->pager_host_list[ccd->server_i], yss->pager_port,
				yahoo_connected, ccd, 0);
		} else {
			FREE(ccd);
			YAHOO_CALLBACK(ext_yahoo_login_response) (yd->client_id,
				YAHOO_LOGIN_SOCK, NULL);
		}
		return;
	}

	FREE(ccd);

	/* fd == NULL && error == 0 means connect was cancelled */
	if (!fd)
		return;

	pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YPACKET_STATUS_DEFAULT,
		yd->session_id);
	NOTICE(("Sending initial packet"));

	yahoo_packet_hash(pkt, 1, yd->user);

	yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER);
	yid->fd = fd;

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);

	yid->read_tag =
		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id,
		yid->fd, YAHOO_INPUT_READ, yid);
}

void *yahoo_get_fd(int id)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	if (!yid)
		return 0;
	else
		return yid->fd;
}

#if 0
void yahoo_send_buzz(int id, const char *from, const char *who)
{
	yahoo_send_im(id, from, who, "<ding>", 1, 0);
}
#endif

void yahoo_send_im(int id, const char *from, const char *who, const char *what,
	int utf8, int picture)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_packet *pkt = NULL;
	struct yahoo_data *yd;
	char pic_str[10];

	if (!yid)
		return;

	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE,
		yd->session_id);

	snprintf(pic_str, sizeof(pic_str), "%d", picture);

	if (from && strcmp(from, yd->user))
		yahoo_packet_hash(pkt, 0, yd->user);
	yahoo_packet_hash(pkt, 1, from ? from : yd->user);
	yahoo_packet_hash(pkt, 5, who);
	yahoo_packet_hash(pkt, 14, what);

	if (utf8)
		yahoo_packet_hash(pkt, 97, "1");

	yahoo_packet_hash(pkt, 63, ";0");	/* imvironment name; or ;0 */
	yahoo_packet_hash(pkt, 64, "0");
	yahoo_packet_hash(pkt, 206, pic_str);

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_send_typing(int id, const char *from, const char *who, int typ)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;
	if (!yid)
		return;

	yd = yid->yd;
	pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY,
		yd->session_id);

	yahoo_packet_hash(pkt, 5, who);
	yahoo_packet_hash(pkt, 1, from ? from : yd->user);
	yahoo_packet_hash(pkt, 14, " ");
	yahoo_packet_hash(pkt, 13, typ ? "1" : "0");
	yahoo_packet_hash(pkt, 49, "TYPING");

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_set_away(int id, enum yahoo_status state, const char *msg, int away)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;
	int old_status;
	char s[4];

	if (!yid)
		return;

	yd = yid->yd;

	old_status = yd->current_status;
	yd->current_status = state;

	/* Thank you libpurple :) */
	if (yd->current_status == YAHOO_STATUS_INVISIBLE) {
		pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE,
			YAHOO_STATUS_AVAILABLE, 0);
		yahoo_packet_hash(pkt, 13, "2");
		yahoo_send_packet(yid, pkt, 0);
		yahoo_packet_free(pkt);

		return;
	}

	pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE,
		yd->current_status, yd->session_id);
	snprintf(s, sizeof(s), "%d", yd->current_status);
	yahoo_packet_hash(pkt, 10, s);
	yahoo_packet_hash(pkt, 19, msg && state == YAHOO_STATUS_CUSTOM ? msg : "");
	yahoo_packet_hash(pkt, 47, (away == 2)? "2": (away) ?"1":"0");
	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);

	if (old_status == YAHOO_STATUS_INVISIBLE) {
		pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE,
			YAHOO_STATUS_AVAILABLE, 0);
		yahoo_packet_hash(pkt, 13, "1");
		yahoo_send_packet(yid, pkt, 0);
		yahoo_packet_free(pkt);
	}
}

void yahoo_logoff(int id)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;
	yd = yid->yd;

	LOG(("yahoo_logoff: current status: %d", yd->current_status));

	if (yd->current_status != -1 && 0) {
		/* Meh. Don't send this. The event handlers are not going to
		   get to do this so it'll just leak memory. And the TCP
		   connection reset will hopefully be clear enough. */
		pkt = yahoo_packet_new(YAHOO_SERVICE_LOGOFF,
			YPACKET_STATUS_DEFAULT, yd->session_id);
		yd->current_status = -1;

		if (pkt) {
			yahoo_send_packet(yid, pkt, 0);
			yahoo_packet_free(pkt);
		}
	}

/*	do {
		yahoo_input_close(yid);
	} while((yid = find_input_by_id(id)));*/

}

#if 0
void yahoo_get_list(int id)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YPACKET_STATUS_DEFAULT,
		yd->session_id);
	yahoo_packet_hash(pkt, 1, yd->user);
	if (pkt) {
		yahoo_send_packet(yid, pkt, 0);
		yahoo_packet_free(pkt);
	}
}
#endif

static void _yahoo_http_connected(int id, void *fd, int error, void *data)
{
	struct yahoo_input_data *yid = data;
	if (fd == NULL || error) {
		inputs = y_list_remove(inputs, yid);
		FREE(yid);
		return;
	}

	yid->fd = fd;
	yid->read_tag =
		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
		YAHOO_INPUT_READ, yid);
}

#if 0
/* FIXME Get address book from address.yahoo.com instead */
void yahoo_get_yab(int id)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	struct yahoo_input_data *yid;
	char url[1024];
	char buff[2048];

	if (!yd)
		return;

	yid = y_new0(struct yahoo_input_data, 1);
	yid->yd = yd;
	yid->type = YAHOO_CONNECTION_YAB;

	LOG(("Sending request for Address Book"));

	snprintf(url, 1024,
		"http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us"
		"&diffs=1&t=0&tags=short&rt=0&prog-ver=8.1.0.249&useutf8=1&legenc=codepage-1252");

	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);

	inputs = y_list_prepend(inputs, yid);

	yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
		_yahoo_http_connected, yid);
}

struct yahoo_post_data {
	struct yahoo_input_data *yid;
	char *data;
};

static void _yahoo_http_post_connected(int id, void *fd, int error, void *data)
{
	struct yahoo_post_data *yad = data;
	struct yahoo_input_data *yid = yad->yid;
	char *buff = yad->data;

	if (!fd) {
		inputs = y_list_remove(inputs, yid);
		FREE(yid);
		return;
	}

	YAHOO_CALLBACK(ext_yahoo_write) (fd, buff, strlen(buff));

	yid->fd = fd;
	yid->read_tag =
		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
		YAHOO_INPUT_READ, yid);

	FREE(buff);
	FREE(yad);
}

/* FIXME This is also likely affected */
void yahoo_set_yab(int id, struct yab *yab)
{
	struct yahoo_post_data *yad = y_new0(struct yahoo_post_data, 1);
	struct yahoo_data *yd = find_conn_by_id(id);
	struct yahoo_input_data *yid;
	char url[1024];
	char buff[1024];
	char post[1024];
	int size = 0;

	if (!yd)
		return;

	yid = y_new0(struct yahoo_input_data, 1);
	yid->type = YAHOO_CONNECTION_YAB;
	yid->yd = yd;

	if(yab->yid)
		size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
			"<ab k=\"%s\" cc=\"%d\">"
			"<ct id=\"%d\" e=\"1\" yi=\"%s\" nn=\"%s\" />"
			"</ab>", yd->user, 9, yab->yid,	/* Don't know why */
			yab->id, yab->nname?yab->nname:"");
	else
		size = snprintf(post, sizeof(post), "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
			"<ab k=\"%s\" cc=\"%d\">"
			"<ct a=\"1\" yi=\"%s\" nn=\"%s\" />"
			"</ab>", yd->user, 1,	/* Don't know why */
			yab->id, yab->nname?yab->nname:"");

	yad->yid = yid;
	yad->data = strdup(post);

	strcpy(url, "http://address.yahoo.com/yab/us?v=XM&prog=ymsgr&.intl=us"
		"&sync=1&tags=short&noclear=1&useutf8=1&legenc=codepage-1252");

	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);

	inputs = y_list_prepend(inputs, yid);

	yahoo_http_post(yid->yd->client_id, url, buff, size,
		_yahoo_http_post_connected, yad);
}

void yahoo_set_identity_status(int id, const char *identity, int active)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(active ? YAHOO_SERVICE_IDACT :
		YAHOO_SERVICE_IDDEACT, YPACKET_STATUS_DEFAULT, yd->session_id);
	yahoo_packet_hash(pkt, 3, identity);
	if (pkt) {
		yahoo_send_packet(yid, pkt, 0);
		yahoo_packet_free(pkt);
	}
}

void yahoo_refresh(int id)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_USERSTAT, YPACKET_STATUS_DEFAULT,
		yd->session_id);
	if (pkt) {
		yahoo_send_packet(yid, pkt, 0);
		yahoo_packet_free(pkt);
	}
}
#endif

void yahoo_keepalive(int id)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;
	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YPACKET_STATUS_DEFAULT,
		yd->session_id);
	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}

#if 0
void yahoo_chat_keepalive(int id)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;

	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YPACKET_STATUS_DEFAULT,
		yd->session_id);
	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}
#endif

void yahoo_add_buddy(int id, const char *who, const char *group,
	const char *msg)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	if (!yd->logged_in)
		return;

	pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YPACKET_STATUS_DEFAULT,
		yd->session_id);
	if (msg != NULL)	/* add message/request "it's me add me" */
		yahoo_packet_hash(pkt, 14, msg);
	else
		yahoo_packet_hash(pkt, 14, "");
	yahoo_packet_hash(pkt, 65, group);
	yahoo_packet_hash(pkt, 97, "1");
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 302, "319");
	yahoo_packet_hash(pkt, 300, "319");
	yahoo_packet_hash(pkt, 7, who);
	yahoo_packet_hash(pkt, 334, "0");
	yahoo_packet_hash(pkt, 301, "319");
	yahoo_packet_hash(pkt, 303, "319");
	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}

void yahoo_remove_buddy(int id, const char *who, const char *group)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 7, who);
	yahoo_packet_hash(pkt, 65, group);
	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}

void yahoo_confirm_buddy(int id, const char *who, int reject, const char *msg)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	if (!yd->logged_in)
		return;

	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_AUTHORIZATION,
		YPACKET_STATUS_DEFAULT, yd->session_id);
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 5, who);
	if (reject)
		yahoo_packet_hash(pkt, 13, "2");
	else {
		yahoo_packet_hash(pkt, 241, "0");
		yahoo_packet_hash(pkt, 13, "1");
	}

	yahoo_packet_hash(pkt, 334, "0");

	if (reject) {
		yahoo_packet_hash(pkt, 14, msg ? msg : "");
		yahoo_packet_hash(pkt, 97, "1");
	}

	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}

#if 0
void yahoo_ignore_buddy(int id, const char *who, int unignore)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	if (!yd->logged_in)
		return;

	pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT,
		YPACKET_STATUS_DEFAULT, yd->session_id);
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 7, who);
	yahoo_packet_hash(pkt, 13, unignore ? "2" : "1");
	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}

void yahoo_stealth_buddy(int id, const char *who, int unstealth)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	if (!yd->logged_in)
		return;

	pkt = yahoo_packet_new(YAHOO_SERVICE_STEALTH_PERM,
		YPACKET_STATUS_DEFAULT, yd->session_id);
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 7, who);
	yahoo_packet_hash(pkt, 31, unstealth ? "2" : "1");
	yahoo_packet_hash(pkt, 13, "2");
	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}
#endif

void yahoo_change_buddy_group(int id, const char *who, const char *old_group,
	const char *new_group)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_CHANGE_GROUP,
		YPACKET_STATUS_DEFAULT, yd->session_id);
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 302, "240");
	yahoo_packet_hash(pkt, 300, "240");
	yahoo_packet_hash(pkt, 7, who);
	yahoo_packet_hash(pkt, 224, old_group);
	yahoo_packet_hash(pkt, 264, new_group);
	yahoo_packet_hash(pkt, 301, "240");
	yahoo_packet_hash(pkt, 303, "240");

	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}

#if 0
void yahoo_group_rename(int id, const char *old_group, const char *new_group)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt = NULL;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME,
		YPACKET_STATUS_DEFAULT, yd->session_id);
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 65, old_group);
	yahoo_packet_hash(pkt, 67, new_group);

	yahoo_send_packet(yid, pkt, 0);
	yahoo_packet_free(pkt);
}

void yahoo_conference_addinvite(int id, const char *from, const char *who,
	const char *room, const YList *members, const char *msg)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFADDINVITE,
		YPACKET_STATUS_DEFAULT, yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 51, who);
	yahoo_packet_hash(pkt, 57, room);
	yahoo_packet_hash(pkt, 58, msg);
	yahoo_packet_hash(pkt, 13, "0");
	for (; members; members = members->next) {
		yahoo_packet_hash(pkt, 52, (char *)members->data);
		yahoo_packet_hash(pkt, 53, (char *)members->data);
	}
	/* 52, 53 -> other members? */

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}
#endif

void yahoo_conference_invite(int id, const char *from, YList *who,
	const char *room, const char *msg)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFINVITE, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 50, yd->user);
	for (; who; who = who->next) {
		yahoo_packet_hash(pkt, 52, (char *)who->data);
	}
	yahoo_packet_hash(pkt, 57, room);
	yahoo_packet_hash(pkt, 58, msg);
	yahoo_packet_hash(pkt, 13, "0");

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_conference_logon(int id, const char *from, YList *who,
	const char *room)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGON, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 57, room);
	for (; who; who = who->next)
		yahoo_packet_hash(pkt, 3, (char *)who->data);

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_conference_decline(int id, const char *from, YList *who,
	const char *room, const char *msg)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFDECLINE,
		YPACKET_STATUS_DEFAULT, yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
	for (; who; who = who->next)
		yahoo_packet_hash(pkt, 3, (char *)who->data);
	yahoo_packet_hash(pkt, 57, room);
	yahoo_packet_hash(pkt, 14, msg);

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_conference_logoff(int id, const char *from, YList *who,
	const char *room)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFLOGOFF, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 3, (from ? from : yd->user));
	for (; who; who = who->next)
		yahoo_packet_hash(pkt, 3, (char *)who->data);

	yahoo_packet_hash(pkt, 57, room);

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_conference_message(int id, const char *from, YList *who,
	const char *room, const char *msg, int utf8)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;
	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CONFMSG, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 53, (from ? from : yd->user));
	for (; who; who = who->next)
		yahoo_packet_hash(pkt, 53, (char *)who->data);

	yahoo_packet_hash(pkt, 57, room);
	yahoo_packet_hash(pkt, 14, msg);

	if (utf8)
		yahoo_packet_hash(pkt, 97, "1");

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

#if 0
void yahoo_get_chatrooms(int id, int chatroomid)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	struct yahoo_input_data *yid;
	char url[1024];
	char buff[1024];

	if (!yd)
		return;

	yid = y_new0(struct yahoo_input_data, 1);
	yid->yd = yd;
	yid->type = YAHOO_CONNECTION_CHATCAT;

	if (chatroomid == 0) {
		snprintf(url, 1024,
			"http://insider.msg.yahoo.com/ycontent/?chatcat=0");
	} else {
		snprintf(url, 1024,
			"http://insider.msg.yahoo.com/ycontent/?chatroom_%d=0",
			chatroomid);
	}

	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);

	inputs = y_list_prepend(inputs, yid);

	yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
		_yahoo_http_connected, yid);
}

void yahoo_chat_logon(int id, const char *from, const char *room,
	const char *roomid)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;

	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 109, yd->user);
	yahoo_packet_hash(pkt, 6, "abcde");

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);

	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 104, room);
	yahoo_packet_hash(pkt, 129, roomid);
	yahoo_packet_hash(pkt, 62, "2");	/* ??? */

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_chat_message(int id, const char *from, const char *room,
	const char *msg, const int msgtype, const int utf8)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;
	char buf[2];

	if (!yid)
		return;

	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_COMMENT, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));
	yahoo_packet_hash(pkt, 104, room);
	yahoo_packet_hash(pkt, 117, msg);

	snprintf(buf, sizeof(buf), "%d", msgtype);
	yahoo_packet_hash(pkt, 124, buf);

	if (utf8)
		yahoo_packet_hash(pkt, 97, "1");

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_chat_logoff(int id, const char *from)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;

	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, (from ? from : yd->user));

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_buddyicon_request(int id, const char *who)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;

	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT,
		0);
	yahoo_packet_hash(pkt, 4, yd->user);
	yahoo_packet_hash(pkt, 5, who);
	yahoo_packet_hash(pkt, 13, "1");
	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_send_picture_info(int id, const char *who, const char *url,
	int checksum)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;
	char checksum_str[10];

	if (!yid)
		return;

	yd = yid->yd;

	snprintf(checksum_str, sizeof(checksum_str), "%d", checksum);

	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE, YPACKET_STATUS_DEFAULT,
		0);
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 4, yd->user);
	yahoo_packet_hash(pkt, 5, who);
	yahoo_packet_hash(pkt, 13, "2");
	yahoo_packet_hash(pkt, 20, url);
	yahoo_packet_hash(pkt, 192, checksum_str);
	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_send_picture_update(int id, const char *who, int type)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;
	char type_str[10];

	if (!yid)
		return;

	yd = yid->yd;

	snprintf(type_str, sizeof(type_str), "%d", type);

	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_UPDATE,
		YPACKET_STATUS_DEFAULT, 0);
	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 5, who);
	yahoo_packet_hash(pkt, 206, type_str);
	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_send_picture_checksum(int id, const char *who, int checksum)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;
	char checksum_str[10];

	if (!yid)
		return;

	yd = yid->yd;

	snprintf(checksum_str, sizeof(checksum_str), "%d", checksum);

	pkt = yahoo_packet_new(YAHOO_SERVICE_PICTURE_CHECKSUM,
		YPACKET_STATUS_DEFAULT, 0);
	yahoo_packet_hash(pkt, 1, yd->user);
	if (who != 0)
		yahoo_packet_hash(pkt, 5, who);
	yahoo_packet_hash(pkt, 192, checksum_str);
	yahoo_packet_hash(pkt, 212, "1");
	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_webcam_close_feed(int id, const char *who)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_webcam_user(id, who);

	if (yid)
		yahoo_input_close(yid);
}

void yahoo_webcam_get_feed(int id, const char *who)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_data *yd;
	struct yahoo_packet *pkt;

	if (!yid)
		return;

	/* 
	 * add the user to the queue.  this is a dirty hack, since
	 * the yahoo server doesn't tell us who's key it's returning,
	 * we have to just hope that it sends back keys in the same 
	 * order that we request them.
	 * The queue is popped in yahoo_process_webcam_key
	 */
	webcam_queue = y_list_append(webcam_queue, who ? strdup(who) : NULL);

	yd = yid->yd;

	pkt = yahoo_packet_new(YAHOO_SERVICE_WEBCAM, YPACKET_STATUS_DEFAULT,
		yd->session_id);

	yahoo_packet_hash(pkt, 1, yd->user);
	if (who != NULL)
		yahoo_packet_hash(pkt, 5, who);
	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_webcam_send_image(int id, unsigned char *image, unsigned int length,
	unsigned int timestamp)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
	unsigned char *packet;
	unsigned char header_len = 13;
	unsigned int pos = 0;

	if (!yid)
		return;

	packet = y_new0(unsigned char, header_len);

	packet[pos++] = header_len;
	packet[pos++] = 0;
	packet[pos++] = 5;	/* version byte?? */
	packet[pos++] = 0;
	pos += yahoo_put32(packet + pos, length);
	packet[pos++] = 2;	/* packet type, image */
	pos += yahoo_put32(packet + pos, timestamp);
	yahoo_add_to_send_queue(yid, packet, header_len);
	FREE(packet);

	if (length)
		yahoo_add_to_send_queue(yid, image, length);
}

void yahoo_webcam_accept_viewer(int id, const char *who, int accept)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_WEBCAM);
	char *packet = NULL;
	char *data = NULL;
	unsigned char header_len = 13;
	unsigned int pos = 0;
	unsigned int len = 0;

	if (!yid)
		return;

	data = strdup("u=");
	data = y_string_append(data, (char *)who);
	data = y_string_append(data, "\r\n");
	len = strlen(data);

	packet = y_new0(char, header_len + len);
	packet[pos++] = header_len;
	packet[pos++] = 0;
	packet[pos++] = 5;	/* version byte?? */
	packet[pos++] = 0;
	pos += yahoo_put32(packet + pos, len);
	packet[pos++] = 0;	/* packet type */
	pos += yahoo_put32(packet + pos, accept);
	memcpy(packet + pos, data, len);
	FREE(data);
	yahoo_add_to_send_queue(yid, packet, header_len + len);
	FREE(packet);
}

void yahoo_webcam_invite(int id, const char *who)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_packet *pkt;

	if (!yid)
		return;

	pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YPACKET_STATUS_NOTIFY,
		yid->yd->session_id);

	yahoo_packet_hash(pkt, 49, "WEBCAMINVITE");
	yahoo_packet_hash(pkt, 14, " ");
	yahoo_packet_hash(pkt, 13, "0");
	yahoo_packet_hash(pkt, 1, yid->yd->user);
	yahoo_packet_hash(pkt, 5, who);
	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

static void yahoo_search_internal(int id, int t, const char *text, int g,
	int ar, int photo, int yahoo_only, int startpos, int total)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	struct yahoo_input_data *yid;
	char url[1024];
	char buff[1024];
	char *ctext, *p;

	if (!yd)
		return;

	yid = y_new0(struct yahoo_input_data, 1);
	yid->yd = yd;
	yid->type = YAHOO_CONNECTION_SEARCH;

	/*
	   age range
	   .ar=1 - 13-18, 2 - 18-25, 3 - 25-35, 4 - 35-50, 5 - 50-70, 6 - 70+
	 */

	snprintf(buff, sizeof(buff), "&.sq=%%20&.tt=%d&.ss=%d", total,
		startpos);

	ctext = strdup(text);
	while ((p = strchr(ctext, ' ')))
		*p = '+';

	snprintf(url, 1024,
		"http://members.yahoo.com/interests?.oc=m&.kw=%s&.sb=%d&.g=%d&.ar=0%s%s%s",
		ctext, t, g, photo ? "&.p=y" : "", yahoo_only ? "&.pg=y" : "",
		startpos ? buff : "");

	FREE(ctext);

	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);

	inputs = y_list_prepend(inputs, yid);
	yahoo_http_get(yid->yd->client_id, url, buff, 0, 0,
		_yahoo_http_connected, yid);
}

void yahoo_search(int id, enum yahoo_search_type t, const char *text,
	enum yahoo_search_gender g, enum yahoo_search_agerange ar, int photo,
	int yahoo_only)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_search_state *yss;

	if (!yid)
		return;

	if (!yid->ys)
		yid->ys = y_new0(struct yahoo_search_state, 1);

	yss = yid->ys;

	FREE(yss->lsearch_text);
	yss->lsearch_type = t;
	yss->lsearch_text = strdup(text);
	yss->lsearch_gender = g;
	yss->lsearch_agerange = ar;
	yss->lsearch_photo = photo;
	yss->lsearch_yahoo_only = yahoo_only;

	yahoo_search_internal(id, t, text, g, ar, photo, yahoo_only, 0, 0);
}

void yahoo_search_again(int id, int start)
{
	struct yahoo_input_data *yid =
		find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	struct yahoo_search_state *yss;

	if (!yid || !yid->ys)
		return;

	yss = yid->ys;

	if (start == -1)
		start = yss->lsearch_nstart + yss->lsearch_nfound;

	yahoo_search_internal(id, yss->lsearch_type, yss->lsearch_text,
		yss->lsearch_gender, yss->lsearch_agerange,
		yss->lsearch_photo, yss->lsearch_yahoo_only,
		start, yss->lsearch_ntotal);
}

void yahoo_send_picture(int id, const char *name, unsigned long size,
	yahoo_get_fd_callback callback, void *data)
{
	/* Not Implemented */
}
#endif

/* File Transfer */
static YList *active_file_transfers = NULL;

enum {
	FT_STATE_HEAD = 1,
	FT_STATE_RECV,
	FT_STATE_RECV_START,
	FT_STATE_SEND
};

struct send_file_data {
	int client_id;
	char *id;
	char *who;
	char *filename;
	char *ip_addr;
	char *token;
	int size;

	struct yahoo_input_data *yid;
	int state;

	yahoo_get_fd_callback callback;
	void *data;
};

#if 0
static char *yahoo_get_random(void)
{
	int i = 0;
	int r = 0;
	int c = 0;
	char out[25];

	out[24] = '\0';
	out[23] = '$';
	out[22] = '$';
	
	for (i = 0; i < 22; i++) {
		if(r == 0)
			r = rand();

		c = r%61;

		if(c<26)
			out[i] = c + 'a';
		else if (c<52)
			out[i] = c - 26 + 'A';
		else
			out[i] = c - 52 + '0';

		r /= 61;
	}

	return strdup(out);
}
#endif

static int _are_same_id(const void *sfd1, const void *id)
{
	return strcmp(((struct send_file_data *)sfd1)->id, (char *)id);
}

static int _are_same_yid(const void *sfd1, const void *yid)
{
	if(((struct send_file_data *)sfd1)->yid == yid)
		return 0;
	else
		return 1;
}

static struct send_file_data *yahoo_get_active_transfer(char *id)
{
	YList *l = y_list_find_custom(active_file_transfers, id,
		_are_same_id);

	if(l)
		return (struct send_file_data *)l->data;
	
	return NULL;
}

static struct send_file_data *yahoo_get_active_transfer_with_yid(void *yid)
{
	YList *l = y_list_find_custom(active_file_transfers, yid,
		_are_same_yid);

	if(l)
		return (struct send_file_data *)l->data;
	
	return NULL;
}

static void yahoo_add_active_transfer(struct send_file_data *sfd)
{
	active_file_transfers = y_list_prepend(active_file_transfers, sfd);
}

static void yahoo_remove_active_transfer(struct send_file_data *sfd)
{
	if (sfd == NULL)
		return;
	
	active_file_transfers = y_list_remove(active_file_transfers, sfd);
	free(sfd->id);
	free(sfd->who);
	free(sfd->filename);
	free(sfd->ip_addr);
	FREE(sfd);
}

static void _yahoo_ft_upload_connected(int id, void *fd, int error, void *data)
{
	struct send_file_data *sfd = data;
	struct yahoo_input_data *yid = sfd->yid;

	if (!fd) {
		inputs = y_list_remove(inputs, yid);
		FREE(yid);
		return;
	}

	sfd->callback(id, fd, error, sfd->data);

	yid->fd = fd;
	yid->read_tag =
		YAHOO_CALLBACK(ext_yahoo_add_handler) (yid->yd->client_id, fd,
		YAHOO_INPUT_READ, yid);
}

static void yahoo_file_transfer_upload(struct yahoo_data *yd,
	struct send_file_data *sfd)
{
	char url[256];
	char buff[4096];
	char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;

	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);

	yid->yd = yd;
	yid->type = YAHOO_CONNECTION_FT;

	inputs = y_list_prepend(inputs, yid);
	sfd->yid = yid;
	sfd->state = FT_STATE_SEND;

	token_enc = yahoo_urlencode(sfd->token);
	sender_enc = yahoo_urlencode(yd->user);
	recv_enc = yahoo_urlencode(sfd->who);

	snprintf(url, sizeof(url), 
		"http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
		token_enc, sender_enc, recv_enc);

	snprintf(buff, sizeof(buff), "T=%s; Y=%s", yd->cookie_t, yd->cookie_y);

	yahoo_http_post(yd->client_id, url, buff, sfd->size,
		_yahoo_ft_upload_connected, sfd);

	FREE(token_enc);
	FREE(sender_enc);
	FREE(recv_enc);
}

static void yahoo_init_ft_recv(struct yahoo_data *yd,
	struct send_file_data *sfd)
{
	char url[256];
	char buff[1024];
	char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;

	struct yahoo_input_data *yid = y_new0(struct yahoo_input_data, 1);

	yid->yd = yd;
	yid->type = YAHOO_CONNECTION_FT;

	inputs = y_list_prepend(inputs, yid);
	sfd->yid = yid;
	sfd->state = FT_STATE_HEAD;

	token_enc = yahoo_urlencode(sfd->token);
	sender_enc = yahoo_urlencode(sfd->who);
	recv_enc = yahoo_urlencode(yd->user);

	snprintf(url, sizeof(url), 
		"http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
		token_enc, sender_enc, recv_enc);

	snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y, yd->cookie_t);

	yahoo_http_head(yid->yd->client_id, url, buff, 0, NULL,
		_yahoo_http_connected, yid);

	FREE(token_enc);
	FREE(sender_enc);
	FREE(recv_enc);
}

static void yahoo_file_transfer_accept(struct yahoo_input_data *yid,
	struct send_file_data *sfd)
{
	struct yahoo_packet *pkt;

	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERACCEPT,
		YPACKET_STATUS_DEFAULT, yid->yd->session_id);

	yahoo_packet_hash(pkt, 1, yid->yd->user);
	yahoo_packet_hash(pkt, 5, sfd->who);
	yahoo_packet_hash(pkt, 265, sfd->id);
	yahoo_packet_hash(pkt, 27, sfd->filename);
	yahoo_packet_hash(pkt, 249, "3");
	yahoo_packet_hash(pkt, 251, sfd->token);

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);

	yahoo_init_ft_recv(yid->yd, sfd);
}

static void yahoo_process_filetransferaccept(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	YList *l;
	struct send_file_data *sfd;
	char *id = NULL;
	char *token = NULL;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		switch (pair->key) {
		case 4:
			/* who */
			break;
		case 5:
			/* Me... don't care */
			break;
		case 249:
			break;
		case 265:
			id = pair->value;
			break;
		case 251:
			token = pair->value;
			break;
		case 27:
			/* filename */
			break;
		}
	}

	sfd = yahoo_get_active_transfer(id);

	if (sfd) {
		sfd->token = strdup(token);

		yahoo_file_transfer_upload(yid->yd, sfd);
	}
	else {
		YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
			(yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN,
			sfd ? sfd->data : NULL);

		yahoo_remove_active_transfer(sfd);
	}
}

static void yahoo_process_filetransferinfo(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	YList *l;
	char *id = NULL;
	char *token = NULL;
	char *ip_addr = NULL;

	struct send_file_data *sfd;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		switch (pair->key) {
		case 1:
		case 4:
			/* who */
			break;
		case 5:
			/* Me... don't care */
			break;
		case 249:
			break;
		case 265:
			id = pair->value;
			break;
		case 250:
			ip_addr = pair->value;
			break;
		case 251:
			token = pair->value;
			break;
		case 27:
			/* filename */
			break;
		}
	}

	sfd = yahoo_get_active_transfer(id);

	if (sfd) {
		sfd->token = strdup(token);
		sfd->ip_addr = strdup(ip_addr);

		yahoo_file_transfer_accept(yid, sfd);
	}
	else {
		YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
			(yid->yd->client_id, YAHOO_FILE_TRANSFER_UNKNOWN,
			sfd ? sfd->data : NULL);

		yahoo_remove_active_transfer(sfd);
	}
}

static void yahoo_send_filetransferinfo(struct yahoo_data *yd,
	struct send_file_data *sfd)
{
	struct yahoo_input_data *yid;
	struct yahoo_packet *pkt;

	yid = find_input_by_id_and_type(yd->client_id, YAHOO_CONNECTION_PAGER);
	sfd->ip_addr = YAHOO_CALLBACK(ext_yahoo_get_ip_addr)("relay.yahoo.com");

	if (!sfd->ip_addr) {
		YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
			(yd->client_id, YAHOO_FILE_TRANSFER_RELAY, sfd->data);

		yahoo_remove_active_transfer(sfd);

		return;
	}

	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFERINFO,
		YPACKET_STATUS_DEFAULT, yd->session_id);

	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 5, sfd->who);
	yahoo_packet_hash(pkt, 265, sfd->id);
	yahoo_packet_hash(pkt, 27, sfd->filename);
	yahoo_packet_hash(pkt, 249, "3");
	yahoo_packet_hash(pkt, 250, sfd->ip_addr);

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

static void yahoo_process_filetransfer(struct yahoo_input_data *yid,
	struct yahoo_packet *pkt)
{
	YList *l;
	char *who = NULL;
	char *filename = NULL;
	char *msg = NULL;
	char *id = NULL;
	int action = 0;
	int size = 0;
	struct yahoo_data *yd = yid->yd;

	struct send_file_data *sfd;

	for (l = pkt->hash; l; l = l->next) {
		struct yahoo_pair *pair = l->data;
		switch (pair->key) {
		case 4:
			who = pair->value;
			break;
		case 5:
			/* Me... don't care */
			break;
		case 222:
			action = atoi(pair->value);
			break;
		case 265:
			id = pair->value;
			break;
		case 266: /* Don't know */
			break;
		case 302: /* Start Data? */
			break;
		case 300:
			break;
		case 27:
			filename = pair->value;
			break;
		case 28:
			size = atoi(pair->value);
			break;
		case 14:
			msg = pair->value;
		case 301: /* End Data? */
			break;
		case 303:
			break;

		}
	}

	if (action == YAHOO_FILE_TRANSFER_INIT) {
		/* Received a FT request from buddy */
		sfd = y_new0(struct send_file_data, 1);
        
		sfd->client_id = yd->client_id;
		sfd->id = strdup(id);
		sfd->who = strdup(who);
		sfd->filename = strdup(filename);
		sfd->size = size;
        
		yahoo_add_active_transfer(sfd);

		YAHOO_CALLBACK(ext_yahoo_got_file) (yd->client_id, yd->user,
			who, msg, filename, size, sfd->id);
	}
	else {
		/* Response to our request */
		sfd = yahoo_get_active_transfer(id);

		if (sfd && action == YAHOO_FILE_TRANSFER_ACCEPT) {
			yahoo_send_filetransferinfo(yd, sfd);
		}
		else if (!sfd || action == YAHOO_FILE_TRANSFER_REJECT) {
			YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
				(yd->client_id, YAHOO_FILE_TRANSFER_REJECT,
				sfd ? sfd->data : NULL);

			yahoo_remove_active_transfer(sfd);
		}
	}
}

#if 0
void yahoo_send_file(int id, const char *who, const char *msg,
	const char *name, unsigned long size,
	yahoo_get_fd_callback callback, void *data)
{
	struct yahoo_packet *pkt = NULL;
	char size_str[10];
	struct yahoo_input_data *yid;
	struct yahoo_data *yd;
	struct send_file_data *sfd;
	
	yid = find_input_by_id_and_type(id, YAHOO_CONNECTION_PAGER);
	yd = find_conn_by_id(id);
	sfd = y_new0(struct send_file_data, 1);

	sfd->client_id = id;
	sfd->id = yahoo_get_random();
	sfd->who = strdup(who);
	sfd->filename = strdup(name);
	sfd->size = size;
	sfd->callback = callback;
	sfd->data = data;

	yahoo_add_active_transfer(sfd);

	if (!yd)
		return;

	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER,
		YPACKET_STATUS_DEFAULT, yd->session_id);

	snprintf(size_str, sizeof(size_str), "%ld", size);

	yahoo_packet_hash(pkt, 1, yd->user);
	yahoo_packet_hash(pkt, 5, who);
	yahoo_packet_hash(pkt, 265, sfd->id);
	yahoo_packet_hash(pkt, 222, "1");
	yahoo_packet_hash(pkt, 266, "1");
	yahoo_packet_hash(pkt, 302, "268");
	yahoo_packet_hash(pkt, 300, "268");
	yahoo_packet_hash(pkt, 27, name);
	yahoo_packet_hash(pkt, 28, size_str);
	yahoo_packet_hash(pkt, 301, "268");
	yahoo_packet_hash(pkt, 303, "268");

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);
}

void yahoo_send_file_transfer_response(int client_id, int response, char *id, void *data)
{
	struct yahoo_packet *pkt = NULL;
	char resp[2];
	struct yahoo_input_data *yid;
	
	struct send_file_data *sfd = yahoo_get_active_transfer(id);

	sfd->data = data;

	yid = find_input_by_id_and_type(client_id, YAHOO_CONNECTION_PAGER);

	pkt = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER,
		YPACKET_STATUS_DEFAULT, yid->yd->session_id);

	snprintf(resp, sizeof(resp), "%d", response);

	yahoo_packet_hash(pkt, 1, yid->yd->user);
	yahoo_packet_hash(pkt, 5, sfd->who);
	yahoo_packet_hash(pkt, 265, sfd->id);
	yahoo_packet_hash(pkt, 222, resp);

	yahoo_send_packet(yid, pkt, 0);

	yahoo_packet_free(pkt);

	if(response == YAHOO_FILE_TRANSFER_REJECT)
		yahoo_remove_active_transfer(sfd);
}
#endif

static void yahoo_process_ft_connection(struct yahoo_input_data *yid, int over)
{
	struct send_file_data *sfd;
	struct yahoo_data *yd = yid->yd;
	
	sfd = yahoo_get_active_transfer_with_yid(yid);

	if (!sfd) {
		LOG(("Something funny happened. yid %p has no sfd.\n", yid));
		return;
	}

	/* 
	 * We want to handle only the complete data with HEAD since we don't
	 * want a situation where both the GET and HEAD are active.
	 * With SEND, we really can't do much with partial response
	 */
	if ((sfd->state == FT_STATE_HEAD || sfd->state == FT_STATE_SEND) 
			&& !over)
		return;

	if (sfd->state == FT_STATE_HEAD) {
		/* Do a GET */
		char url[256];
		char buff[1024];
		char *sender_enc = NULL, *recv_enc = NULL, *token_enc = NULL;

		struct yahoo_input_data *yid_ft = 
			y_new0(struct yahoo_input_data, 1);
        
		yid_ft->yd = yid->yd;
		yid_ft->type = YAHOO_CONNECTION_FT;
        
		inputs = y_list_prepend(inputs, yid_ft);
		sfd->yid = yid_ft;
		sfd->state = FT_STATE_RECV;

		token_enc = yahoo_urlencode(sfd->token);
		sender_enc = yahoo_urlencode(sfd->who);
		recv_enc = yahoo_urlencode(yd->user);
        
		snprintf(url, sizeof(url), 
			"http://%s/relay?token=%s&sender=%s&recver=%s", sfd->ip_addr,
			token_enc, sender_enc, recv_enc);

		snprintf(buff, sizeof(buff), "Y=%s; T=%s", yd->cookie_y,
			yd->cookie_t);


		yahoo_http_get(yd->client_id, url, buff, 1, 1,
			_yahoo_http_connected, yid_ft);

		FREE(token_enc);
		FREE(sender_enc);
		FREE(recv_enc);
	}
	else if (sfd->state == FT_STATE_RECV || 
		sfd->state == FT_STATE_RECV_START) {

		unsigned char *data_begin = NULL;

		if (yid->rxlen == 0)
			yahoo_remove_active_transfer(sfd);

		if (sfd->state != FT_STATE_RECV_START &&
			(data_begin = 
				(unsigned char *)strstr((char *)yid->rxqueue,
				"\r\n\r\n"))) {

			sfd->state = FT_STATE_RECV_START;

			yid->rxlen -= 4+(data_begin-yid->rxqueue)/sizeof(char);
			data_begin += 4;

			if (yid->rxlen > 0)
				YAHOO_CALLBACK(ext_yahoo_got_ft_data) 
					(yd->client_id, data_begin,
					yid->rxlen, sfd->data);
		}
		else if (sfd->state == FT_STATE_RECV_START)
			YAHOO_CALLBACK(ext_yahoo_got_ft_data) (yd->client_id,
				yid->rxqueue, yid->rxlen, sfd->data);

		FREE(yid->rxqueue);
		yid->rxqueue = NULL;
		yid->rxlen = 0;
	}
	else if (sfd->state == FT_STATE_SEND) {
		/* Sent file completed */
		int len = 0;
		char *off = strstr((char *)yid->rxqueue, "Content-Length: ");

		if (off) {
			off += 16;
			len = atoi(off);
		}

		if (len < sfd->size)
			YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
				(yd->client_id,
				YAHOO_FILE_TRANSFER_FAILED, sfd->data);
		else
			YAHOO_CALLBACK(ext_yahoo_file_transfer_done)
				(yd->client_id,
				YAHOO_FILE_TRANSFER_DONE, sfd->data);

		yahoo_remove_active_transfer(sfd);
	}
}

/* End File Transfer */

#if 0
enum yahoo_status yahoo_current_status(int id)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	if (!yd)
		return YAHOO_STATUS_OFFLINE;
	return yd->current_status;
}

const YList *yahoo_get_buddylist(int id)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	if (!yd)
		return NULL;
	return yd->buddies;
}

const YList *yahoo_get_ignorelist(int id)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	if (!yd)
		return NULL;
	return yd->ignore;
}

const YList *yahoo_get_identities(int id)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	if (!yd)
		return NULL;
	return yd->identities;
}

const char *yahoo_get_cookie(int id, const char *which)
{
	struct yahoo_data *yd = find_conn_by_id(id);
	if (!yd)
		return NULL;
	if (!strncasecmp(which, "y", 1))
		return yd->cookie_y;
	if (!strncasecmp(which, "b", 1))
		return yd->cookie_b;
	if (!strncasecmp(which, "t", 1))
		return yd->cookie_t;
	if (!strncasecmp(which, "c", 1))
		return yd->cookie_c;
	if (!strncasecmp(which, "login", 5))
		return yd->login_cookie;
	return NULL;
}
#endif

const char *yahoo_get_profile_url(void)
{
	return profile_url;
}
