/*
 * gaim
 *
 * Some code copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
 * libfaim code copyright 1998, 1999 Adam Fritzler <afritz@auk.cx>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

//  #ifdef HAVE_CONFIG_H
//  #include "config.h"
//  #endif
//
//
//  #include <netdb.h>
//  #include <unistd.h>
//  #include <errno.h>
//  #include <netinet/in.h>
//  #include <arpa/inet.h>
//  #include <string.h>
//  #include <stdlib.h>
//  #include <stdio.h>
//  #include <time.h>
//  #include <sys/socket.h>
//  #include <sys/stat.h>
//  #include <ctype.h>
//  #include "multi.h"
//  #include "prpl.h"
//  #include "gaim.h"
//  #include "proxy.h"
#include "yahoo-transport.h"
#include "md5.h"
#include "gaim-sha.h"
#include "yahoo-auth.h"

extern char *yahoo_crypt(const char *, const char *);

//  #include "pixmaps/status-away.xpm"
//  #include "pixmaps/status-here.xpm"
//  #include "pixmaps/status-idle.xpm"
//  #include "pixmaps/status-game.xpm"
//
//  /* Yahoo Smilies go here */
//  #include "pixmaps/protocols/yahoo/yahoo_alien.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_angel.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_angry.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_bigsmile.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_blush.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_bye.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_clown.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_cow.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_cowboy.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_cry.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_devil.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_flower.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_ghost.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_glasses.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_laughloud.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_love.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_mean.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_neutral.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_ooooh.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_question.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_sad.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_sleep.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_smiley.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_sunglas.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_tongue.xpm"
//  #include "pixmaps/protocols/yahoo/yahoo_wink.xpm"
//
//  #define YAHOO_DEBUG
//
#define USEROPT_MAIL 0
//
#define USEROPT_PAGERHOST 3
//  #define YAHOO_PAGER_HOST "scs.yahoo.com"
#define USEROPT_PAGERPORT 4
//  #define YAHOO_PAGER_PORT 5050

#define YAHOO_PROTO_VER 0x000b

//  enum yahoo_service { /* these are easier to see in hex */
//  	YAHOO_SERVICE_LOGON = 1,
//  	YAHOO_SERVICE_LOGOFF,
//  	YAHOO_SERVICE_ISAWAY,
//  	YAHOO_SERVICE_ISBACK,
//  	YAHOO_SERVICE_IDLE, /* 5 (placemarker) */
//  	YAHOO_SERVICE_MESSAGE,
//  	YAHOO_SERVICE_IDACT,
//  	YAHOO_SERVICE_IDDEACT,
//  	YAHOO_SERVICE_MAILSTAT,
//  	YAHOO_SERVICE_USERSTAT, /* 0xa */
//  	YAHOO_SERVICE_NEWMAIL,
//  	YAHOO_SERVICE_CHATINVITE,
//  	YAHOO_SERVICE_CALENDAR,
//  	YAHOO_SERVICE_NEWPERSONALMAIL,
//  	YAHOO_SERVICE_NEWCONTACT,
//  	YAHOO_SERVICE_ADDIDENT, /* 0x10 */
//  	YAHOO_SERVICE_ADDIGNORE,
//  	YAHOO_SERVICE_PING,
//  	YAHOO_SERVICE_GROUPRENAME,
//  	YAHOO_SERVICE_SYSMESSAGE = 0x14,
//  	YAHOO_SERVICE_PASSTHROUGH2 = 0x16,
//  	YAHOO_SERVICE_CONFINVITE = 0x18,
//  	YAHOO_SERVICE_CONFLOGON,
//  	YAHOO_SERVICE_CONFDECLINE,
//  	YAHOO_SERVICE_CONFLOGOFF,
//  	YAHOO_SERVICE_CONFADDINVITE,
//  	YAHOO_SERVICE_CONFMSG,
//  	YAHOO_SERVICE_CHATLOGON,
//  	YAHOO_SERVICE_CHATLOGOFF,
//  	YAHOO_SERVICE_CHATMSG = 0x20,
//  	YAHOO_SERVICE_GAMELOGON = 0x28,
//  	YAHOO_SERVICE_GAMELOGOFF,
//  	YAHOO_SERVICE_GAMEMSG = 0x2a,
//  	YAHOO_SERVICE_FILETRANSFER = 0x46,
//  	YAHOO_SERVICE_NOTIFY = 0x4B,
//  	YAHOO_SERVICE_AUTHRESP = 0x54,
//  	YAHOO_SERVICE_LIST = 0x55,
//  	YAHOO_SERVICE_AUTH = 0x57,
//  	YAHOO_SERVICE_ADDBUDDY = 0x83,
//  	YAHOO_SERVICE_REMBUDDY = 0x84
//  };
//
//  enum yahoo_status {
//  	YAHOO_STATUS_AVAILABLE = 0,
//  	YAHOO_STATUS_BRB,
//  	YAHOO_STATUS_BUSY,
//  	YAHOO_STATUS_NOTATHOME,
//  	YAHOO_STATUS_NOTATDESK,
//  	YAHOO_STATUS_NOTINOFFICE,
//  	YAHOO_STATUS_ONPHONE,
//  	YAHOO_STATUS_ONVACATION,
//  	YAHOO_STATUS_OUTTOLUNCH,
//  	YAHOO_STATUS_STEPPEDOUT,
//  	YAHOO_STATUS_INVISIBLE = 12,
//  	YAHOO_STATUS_CUSTOM = 99,
//  	YAHOO_STATUS_IDLE = 999,
//  	YAHOO_STATUS_OFFLINE = 0x5a55aa56, /* don't ask */
//  	YAHOO_STATUS_TYPING = 0x16
//  };
//  #define YAHOO_STATUS_GAME 0x2 /* Games don't fit into the regular status model */
//
//  struct yahoo_data {
//  	int fd;
//  	guchar *rxqueue;
//  	int rxlen;
//  	GHashTable *hash;
//  	GHashTable *games;
//  	int current_status;
//  	gboolean logged_in;
//  };

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

struct yahoo_packet {
    guint16 service;
    guint32 status;
    guint32 id;
    GSList *hash;
};
/*
static char *yahoo_name() {
    return "Yahoo";
}
*/
#define YAHOO_PACKET_HDRLEN (4 + 2 + 2 + 2 + 2 + 4 + 4)

static char *normalize(const char *s) {
    static char buf[64];
    char tmp[64];
    int i, j;

    memset(tmp, 0, 64);
    strncpy(buf, s, sizeof(buf));
    for (i=0, j=0; buf[j]; i++, j++) {
        while (buf[j] == ' ')
            j++;
        tmp[i] = buf[j];
    }
    tmp[i] = '\0';

    g_strdown(tmp);
    memset(buf, 0, 64);
    g_snprintf(buf, sizeof(buf), "%s", tmp);

    return buf;
}

struct yahoo_packet *yahoo_packet_new(enum yahoo_service service, enum yahoo_status status, int id) {
    struct yahoo_packet *pkt = g_new0(struct yahoo_packet, 1);

    pkt->service = service;
    pkt->status = status;
    pkt->id = id;

    return pkt;
}

static void yahoo_packet_hash(struct yahoo_packet *pkt, int key, const char *value) {
    struct yahoo_pair *pair = g_new0(struct yahoo_pair, 1);
    pair->key = key;
    pair->value = g_strdup(value);
    pkt->hash = g_slist_append(pkt->hash, pair);
}

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

    int len = 0;

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

    return len;
}

/* sometimes i wish prpls could #include things from other prpls. then i could just
 * use the routines from libfaim and not have to admit to knowing how they work. */
#define yahoo_put16(buf, data) ( \
(*(buf) = (u_char)((data)>>8)&0xff), \
(*((buf)+1) = (u_char)(data)&0xff),  \
2)
#define yahoo_get16(buf) ((((*(buf))<<8)&0xff00) + ((*((buf)+1)) & 0xff))
#define yahoo_put32(buf, data) ( \
(*((buf)) = (u_char)((data)>>24)&0xff), \
(*((buf)+1) = (u_char)((data)>>16)&0xff), \
(*((buf)+2) = (u_char)((data)>>8)&0xff), \
(*((buf)+3) = (u_char)(data)&0xff), \
4)
#define yahoo_get32(buf) ((((*(buf))<<24)&0xff000000) + \
(((*((buf)+1))<<16)&0x00ff0000) + \
(((*((buf)+2))<< 8)&0x0000ff00) + \
(((*((buf)+3)    )&0x000000ff)))

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

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

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

        x = 0;
        while (pos + 1 < len) {
            if (data[pos] == 0xc0 && data[pos + 1] == 0x80)
                break;
            key[x++] = data[pos++];
        }
        key[x] = 0;
        pos += 2;
        pair->key = strtol(key, NULL, 10);
        accept = x; /* if x is 0 there was no key, so don't accept it */

        if (accept)
            value = g_malloc(len - pos + 1);
        x = 0;
        while (pos + 1 < len) {
            if (data[pos] == 0xc0 && data[pos + 1] == 0x80)
                break;
            if (accept)
                value[x++] = data[pos++];
        }
        if (pos > len)
            return;
        if (accept)
            value[x] = 0;
        pos += 2;
        if (accept) {
            pair->value = g_strdup(value);
            g_free(value);
            pkt->hash = g_slist_append(pkt->hash, pair);
            log_debug(ZONE, "[YAHOO]: Read Key: %d  \tValue: %s\n", pair->key, pair->value);
        } else {
            g_free(pair);
        }
    }
}

static void yahoo_packet_write(struct yahoo_packet *pkt, guchar *data) {
    GSList *l = pkt->hash;
    int pos = 0;

    while (l) {
        struct yahoo_pair *pair = l->data;
        guchar buf[100];

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

        strcpy(data + pos, pair->value);
        pos += strlen(pair->value);
        data[pos++] = 0xc0;
        data[pos++] = 0x80;
        log_debug(ZONE, "[YAHOO]: Write Key: %d  \tValue: %s\n", pair->key, pair->value);
        l = l->next;
    }
}

static void yahoo_packet_dump(guchar *data, int len) {
#ifdef YAHOO_DEBUG
    int i;
    log_debug(ZONE, "[YPKT]: start");
    for (i = 0; i + 1 < len; i += 2) {
        log_debug(ZONE, "[YPKT]: %02x", data[i]);
        log_debug(ZONE, "[YPKT]: %02x ", data[i+1]);
    }
    if (i < len)
        log_debug(ZONE, "[YPKT]: %02x", data[i]);
    log_debug(ZONE, "[YPKT]: --------");
    for (i = 0; i < len; i++) {
        if (isprint(data[i])) {
            log_debug(ZONE, "[YPKT]: %c ", data[i]);
        } else {
            log_debug(ZONE, "[YPKT]: . ");
        }
    }
    log_debug(ZONE, "[YPKT]: end");
#endif
}

static int yahoo_send_packet(struct yahoo_data *yd, struct yahoo_packet *pkt) {
    int pktlen = yahoo_packet_length(pkt);
    int len = YAHOO_PACKET_HDRLEN + pktlen;

    guchar *data;
    int pos = 0;

    if (yd->fd->fd < 0)
        return -1;

    data = g_malloc0(len + 1);

    memcpy(data + pos, "YMSG", 4); pos += 4;
    pos += yahoo_put16(data + pos, YAHOO_PROTO_VER);
    pos += yahoo_put16(data + pos, 0x0000);
    pos += yahoo_put16(data + pos, pktlen);
    pos += yahoo_put16(data + pos, pkt->service);
    pos += yahoo_put32(data + pos, pkt->status);
    pos += yahoo_put32(data + pos, pkt->id);

    yahoo_packet_write(pkt, data + pos);

    yahoo_packet_dump(data, len);
    log_debug(ZONE, "[YAHOO]: Writing %d bytes to Yahoo! (fd=%d) state=%d", len, yd->fd->fd, yd->connection_state);
    mio_write(yd->fd, NULL, data, len);
    yd->yi->stats->bytes_out += len;
    g_free(data);

    return len;
}

static void yahoo_packet_free(struct yahoo_packet *pkt) {
    while (pkt->hash) {
        struct yahoo_pair *pair = pkt->hash->data;
        g_free(pair->value);
        g_free(pair);
        pkt->hash = g_slist_remove(pkt->hash, pair);
    }
    g_free(pkt);
}

static void yahoo_process_status(struct yahoo_data *yd, struct yahoo_packet *pkt) {
    GSList *l = pkt->hash;
    char *name = NULL;
    int state = 0;
    // int gamestate = 0;
    char *msg = NULL;

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

        log_debug(ZONE, "[YAHOO]: Process Status: %d '%s'\n", pair->key, pair->value);

        switch (pair->key) {
        case 0: /* we won't actually do anything with this */
            break;
        case 1: /* we don't get the full buddy list here. */
            if (!yd->logged_in) {
                // FUNC				account_online(gc);
                // FUNC				serv_finish_login(gc);
                g_snprintf(yd->displayname, sizeof(yd->displayname), "%s", pair->value);
                yd->logged_in = TRUE;
                log_notice(ZONE, "[YAHOO]: '%s' Logged in as '%s' (fd=%d)", jid_full(yd->me), yd->username, yd->fd->fd);
                yahoo_update_session_state(yd, 0, "yahoo_process_status");

                /* this requests the list. i have a feeling that this is very evil
                 *
                 * scs.yahoo.com sends you the list before this packet without  it being 
                 * requested
                 *
                 * do_import(gc, NULL);
                 * newpkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_OFFLINE, 0);
                 * yahoo_send_packet(yd, newpkt);
                 * yahoo_packet_free(newpkt);
                 */

            }
            break;
        case 8: /* how many online buddies we have */
            break;
        case 7: /* the current buddy */
            name = pair->value;
            break;
        case 10: /* state */
            state = strtol(pair->value, NULL, 10);
            break;
        case 19: /* custom message */
            msg = pair->value;
            break;
        case 11: /* i didn't know what this was in the old protocol either */
            break;
        case 17: /* in chat? */
            break;
        case 13: /* in pager? */
            if (pkt->service == YAHOO_SERVICE_LOGOFF ||
                        strtol(pair->value, NULL, 10) == 0) {
                yahoo_set_jabber_presence(yd, name, 2, NULL);
                break;
            }
            //       if (g_hash_table_lookup(yd->games, name))
            //          gamestate = YAHOO_STATUS_GAME;
            if (state == YAHOO_STATUS_AVAILABLE) {
                if (msg)
                    yahoo_set_jabber_presence(yd, name, 0, msg);
                else
                    yahoo_set_jabber_presence(yd, name, 0, yahoo_get_status_string(state));
            } else {
                if (msg)
                    yahoo_set_jabber_presence(yd, name, 1, msg);
                else
                    yahoo_set_jabber_presence(yd, name, 1, yahoo_get_status_string(state));
            }
            //       if (state == YAHOO_STATUS_CUSTOM) {
            //          gpointer val = g_hash_table_lookup(yd->hash, name);
            //          if (val) {
            //             g_free(val);
            //             g_hash_table_insert(yd->hash, name,
            //                                 msg ? g_strdup(msg) : g_malloc0(1));
            //          } else
            //             g_hash_table_insert(yd->hash, g_strdup(name),
            //                                 msg ? g_strdup(msg) : g_malloc0(1));
            //       }
            msg = NULL;
            break;
        case 60: /* no clue */
            break;
        case 16: /* Custom error message */
            log_debug(ZONE, "[YAHOO]: Error Message: %s\n", pair->value);
            break;
        default:
            log_debug(ZONE, "[YAHOO]: unknown status key %d\n", pair->key);
            break;
        }

        l = l->next;
    }
}

static void yahoo_process_list(struct yahoo_data *yd, struct yahoo_packet *pkt) {
    GSList *l = pkt->hash;
    // gboolean export = FALSE;

    while (l) {
        char **lines;
        char **split;
        char **buddies;
        char **tmp, **bud;

        struct yahoo_pair *pair = l->data;
        l = l->next;

        if (pair->key != 87)
            continue;

        // FUNC		do_import(gc, NULL);
        lines = g_strsplit(pair->value, "\n", -1);
        for (tmp = lines; *tmp; tmp++) {
            split = g_strsplit(*tmp, ":", 2);
            if (!split)
                continue;
            if (!split[0] || !split[1]) {
                g_strfreev(split);
                continue;
            }
            buddies = g_strsplit(split[1], ",", -1);
            for (bud = buddies; bud && *bud; bud++)
                // FUNC				if (!find_buddy(gc, *bud)) {
                yahoo_set_jabber_buddy(yd, *bud, split[0]);
            //					export = TRUE;
            //				}
            g_strfreev(buddies);
            g_strfreev(split);
        }
        g_strfreev(lines);
    }

    //	if (export)
    // FUNC	 	do_export(gc);
}

static void yahoo_process_notify(struct yahoo_data *yd, struct yahoo_packet *pkt) {
    char *msg = NULL;
    char *from = NULL;
    char *stat = NULL;
    char *game = NULL;
    GSList *l = pkt->hash;

    while (l) {
        struct yahoo_pair *pair = l->data;
        if (pair->key == 4)
            from = pair->value;
        if (pair->key == 49)
            msg = pair->value;
        if (pair->key == 13)
            stat = pair->value;
        if (pair->key == 14)
            game = pair->value;
        l = l->next;
    }

    if (!g_strncasecmp(msg, "TYPING", strlen("TYPING"))) {
        if (*stat == '1')
          yahoo_send_jabber_composing_start(yd, from);
        else
          yahoo_send_jabber_composing_stop(yd, from);
    }
}

static void yahoo_process_message(struct yahoo_data *yd, struct yahoo_packet *pkt) {
    char *msg = NULL;
    char *from = NULL;
    time_t tm = time(NULL);
    int isUTF8 = 0;
    GSList *l = pkt->hash;

    while (l) {
        struct yahoo_pair *pair = l->data;
        if (pair->key == 4)
            from = pair->value;
        if (pair->key == 97) {
            if (strncasecmp(pair->value, "1", 1) == 0)
               isUTF8 = 1;
        }
        if (pair->key == 14)
            msg = pair->value;
        if (pair->key == 15)
            tm = strtol(pair->value, NULL, 10);
        l = l->next;
    }

    if (pkt->status <= 1 || pkt->status == 5) {
        char *m;
        int i, j;
        //		strip_linefeed(msg);
        m = msg;
        for (i = 0, j = 0; m[i]; i++) {
            if (m[i] == 033) {
                while (m[i] && (m[i] != 'm'))
                    i++;
                if (!m[i])
                    i--;
                continue;
            }
            msg[j++] = m[i];
        }
        msg[j] = 0;
        yahoo_send_jabber_message(yd, "chat", from, msg, isUTF8);
    } else if (pkt->status == 2) {
        // FUNC		do_error_dialog(_("Your message did not get sent."), _("Gaim - Error"));
    }
}


static void yahoo_process_contact(struct yahoo_data *yd, struct yahoo_packet *pkt) {
    char *id = NULL;
    char *who = NULL;
    char *msg = NULL;
    char *name = NULL;
    int state = YAHOO_STATUS_AVAILABLE;
    int online = FALSE;

    GSList *l = pkt->hash;

    while (l) {
        struct yahoo_pair *pair = l->data;
        if (pair->key == 1)
            id = pair->value;
        else if (pair->key == 3)
            who = pair->value;
        else if (pair->key == 14)
            msg = pair->value;
        else if (pair->key == 7)
            name = pair->value;
        else if (pair->key == 10)
            state = strtol(pair->value, NULL, 10);
        else if (pair->key == 13)
            online = strtol(pair->value, NULL, 10);
        l = l->next;
    }

    if (id)
        yahoo_set_jabber_buddy(yd, who, "Buddies");
    // FUNC		show_got_added(gc, id, who, NULL, msg);
    if (name) {
        //		if (state == YAHOO_STATUS_AVAILABLE)
        //			serv_got_update(gc, name, 1, 0, 0, 0, 0, 0);
        //		else if (state == YAHOO_STATUS_IDLE)
        //			serv_got_update(gc, name, 1, 0, 0, time(NULL) - 600, (state << 2), 0);
        //		else
        //			serv_got_update(gc, name, 1, 0, 0, 0, (state << 2) | UC_UNAVAILABLE, 0);
        //		if (state == YAHOO_STATUS_CUSTOM) {
        //			gpointer val = g_hash_table_lookup(yd->hash, name);
        //			if (val) {
        //				g_free(val);
        //				g_hash_table_insert(yd->hash, name,
        //						msg ? g_strdup(msg) : g_malloc0(1));
        //			} else
        //				g_hash_table_insert(yd->hash, g_strdup(name),
        //						msg ? g_strdup(msg) : g_malloc0(1));
        //		}
    }
}

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

    while (l) {
        struct yahoo_pair *pair = l->data;
        if (pair->key == 9)
            count = strtol(pair->value, NULL, 10);
        else if (pair->key == 43)
            who = pair->value;
        else if (pair->key == 42)
            email = pair->value;
        else if (pair->key == 18)
            subj = pair->value;
        l = l->next;
    }

    if (yd->yi->mail) {
    	if (who && email && subj) {
    	  char *from = g_strdup_printf("%s (%s)", who, email);
          yahoo_send_jabber_mail_notify(yd, -1, from, subj);
          g_free(from);
    	} else
          yahoo_send_jabber_mail_notify(yd, count, NULL, NULL);
    }
}

/* This is taken from Sylpheed by Hiroyuki Yamamoto.  We have our own tobase64 function
 * in util.c, but it has a bug I don't feel like finding right now ;) */
void to_y64(unsigned char *out, const unsigned char *in, int inlen) {
    char base64digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
    /* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */


    for (; inlen >= 3; inlen -= 3) {
        *out++ = base64digits[in[0] >> 2];
        *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
        *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
        *out++ = base64digits[in[2] & 0x3f];
        in += 3;
    }
    if (inlen > 0) {
        unsigned char fragment;

        *out++ = base64digits[in[0] >> 2];
        fragment = (in[0] << 4) & 0x30;
        if (inlen > 1)
            fragment |= in[1] >> 4;
        *out++ = base64digits[fragment];
        *out++ = (inlen < 2) ? '-' : base64digits[(in[1] << 2) & 0x3c];
        *out++ = '-';
    }
    *out = '\0';
}

#define SHA_CTX GAIM_SHA_CTX
#define shaInit(a) gaim_shaInit(a)
#define shaUpdate(a,b,c) gaim_shaUpdate((a),(b),(c))
#define shaFinal(a,b) gaim_shaFinal((a),(b))
static void yahoo_process_auth_new(struct yahoo_data *yd, const char *seed) {
    struct yahoo_packet *pack = NULL;
    const char *name = normalize(yd->username);
    const char *pass = yd->password;

	md5_byte_t			result[16];
	md5_state_t			ctx;

	SHA_CTX				ctx1;
	SHA_CTX				ctx2;

	char				*alphabet1			= "FBZDWAGHrJTLMNOPpRSKUVEXYChImkwQ";
	char				*alphabet2			= "F0E1D2C3B4A59687abcdefghijklmnop";

	char				*challenge_lookup	= "qzec2tb3um1olpar8whx4dfgijknsvy5";
	char				*operand_lookup		= "+|&%/*^-";
	char				*delimit_lookup		= ",;";

	char				*password_hash		= (char *)g_malloc(25);
	char				*crypt_hash		= (char *)g_malloc(25);
	char				*crypt_result		= NULL;

	char				pass_hash_xor1[64];
	char				pass_hash_xor2[64];
	char				crypt_hash_xor1[64];
	char				crypt_hash_xor2[64];
	char				resp_6[100];
	char				resp_96[100];

	unsigned char		digest1[20];
	unsigned char		digest2[20];
	unsigned char		comparison_src[20]; 
	unsigned char		magic_key_char[4];
	const unsigned char		*magic_ptr;

	unsigned int		magic[64];
	unsigned int		magic_work = 0;
	unsigned int		magic_4 = 0;

	int					x;
	int					y;
	int					cnt = 0;
	int					magic_cnt = 0;
	int					magic_len;

	memset(password_hash, 0, 25);
	memset(crypt_hash, 0, 25);
	memset(&pass_hash_xor1, 0, 64);
	memset(&pass_hash_xor2, 0, 64);
	memset(&crypt_hash_xor1, 0, 64);
	memset(&crypt_hash_xor2, 0, 64);
	memset(&digest1, 0, 20);
	memset(&digest2, 0, 20);
	memset(&magic, 0, 64);
	memset(&resp_6, 0, 100);
	memset(&resp_96, 0, 100);
	memset(&magic_key_char, 0, 4);
	memset(&comparison_src, 0, 20);

	/* 
	 * Magic: Phase 1.  Generate what seems to be a 30 byte value (could change if base64
	 * ends up differently?  I don't remember and I'm tired, so use a 64 byte buffer.
	 */

	magic_ptr = seed;

	while (*magic_ptr != (int)NULL) {
		char   *loc;
		
		/* Ignore parentheses.
		 */
		
		if (*magic_ptr == '(' || *magic_ptr == ')') {
			magic_ptr++;
			continue;
		}
		
		/* Characters and digits verify against the challenge lookup.
		 */
		
		if (isalpha(*magic_ptr) || isdigit(*magic_ptr)) {
			loc = strchr(challenge_lookup, *magic_ptr);
			if (!loc) {
			  /* SME XXX Error - disconnect here */
			}
			
			/* Get offset into lookup table and shl 3.
			 */
			
			magic_work = loc - challenge_lookup;
			magic_work <<= 3;
			
			magic_ptr++;
			continue;
		} else {
			unsigned int	local_store;
			
			loc = strchr(operand_lookup, *magic_ptr);
			if (!loc) {
				/* SME XXX Disconnect */
			}
			
			local_store = loc - operand_lookup;
			
			/* Oops; how did this happen?
			 */
			
			if (magic_cnt >= 64) 
				break;
			
			magic[magic_cnt++] = magic_work | local_store;
			magic_ptr++;
			continue;
		}
			}
	
	magic_len = magic_cnt;
	magic_cnt = 0;
	
	/* Magic: Phase 2.  Take generated magic value and sprinkle fairy dust on the values.
	 */

	for (magic_cnt = magic_len-2; magic_cnt >= 0; magic_cnt--) {
		unsigned char	byte1;
		unsigned char	byte2;
		
		/* Bad.  Abort.
		 */
		
		if ((magic_cnt + 1 > magic_len) || (magic_cnt > magic_len))
			break;
		
		byte1 = magic[magic_cnt];
		byte2 = magic[magic_cnt+1];
		
		byte1 *= 0xcd;
		byte1 ^= byte2;
		
		magic[magic_cnt+1] = byte1;
			}
	
	/* 
	 * Magic: Phase 3.  This computes 20 bytes.  The first 4 bytes are used as our magic
	 * key (and may be changed later); the next 16 bytes are an MD5 sum of the magic key
	 * plus 3 bytes.  The 3 bytes are found by looping, and they represent the offsets
	 * into particular functions we'll later call to potentially alter the magic key.
	 *
	 * %-)
	 */
	
	magic_cnt = 1;
	x = 0;
	
	do {
		unsigned int	bl = 0; 
		unsigned int	cl = magic[magic_cnt++];
		
		if (magic_cnt >= magic_len)
			break;
		
		if (cl > 0x7F) {
			if (cl < 0xe0) 
				bl = cl = (cl & 0x1f) << 6; 
			else {
				bl = magic[magic_cnt++]; 
				cl = (cl & 0x0f) << 6; 
				bl = ((bl & 0x3f) + cl) << 6; 
			} 

			cl = magic[magic_cnt++]; 
			bl = (cl & 0x3f) + bl; 
		} else
			bl = cl; 
		
		comparison_src[x++] = (bl & 0xff00) >> 8; 
		comparison_src[x++] = bl & 0xff; 
	} while (x < 20);
	
	/* First four bytes are magic key.
	 */
	
	memcpy(&magic_key_char[0], comparison_src, 4);
	magic_4 = magic_key_char[0] | (magic_key_char[1]<<8) | (magic_key_char[2]<<16) | (magic_key_char[3]<<24);
	
	/* 
	 * Magic: Phase 4.  Determine what function to use later by getting outside/inside
	 * loop values until we match our previous buffer.
	 */
	
	for (x = 0; x < 65535; x++) {
		int			leave = 0;

		for (y = 0; y < 5; y++) {
			md5_byte_t		result[16];
			md5_state_t		ctx;
			
			unsigned char	test[3];
			
			memset(&result, 0, 16);
			memset(&test, 0, 3);
			
			/* Calculate buffer.
			 */

			test[0] = x;
			test[1] = x >> 8;
			test[2] = y;
			
			md5_init(&ctx);
			md5_append(&ctx, magic_key_char, 4);
			md5_append(&ctx, test, 3);
			md5_finish(&ctx, result);
			
			if (!memcmp(result, comparison_src+4, 16)) {
				leave = 1;
				break;
			}
		}
		
		if (leave == 1)
			break;
	}
	
	/* If y != 0, we need some help.
	 */
	
	if (y != 0) {
		unsigned int	updated_key;
		
		/* Update magic stuff.   Call it twice because Yahoo's encryption is super bad ass.
		 */
		
		updated_key = yahoo_auth_finalCountdown(magic_4, 0x60, y, x);
		updated_key = yahoo_auth_finalCountdown(updated_key, 0x60, y, x);
		
		magic_key_char[0] = updated_key & 0xff;
		magic_key_char[1] = (updated_key >> 8) & 0xff;
		magic_key_char[2] = (updated_key >> 16) & 0xff;
		magic_key_char[3] = (updated_key >> 24) & 0xff;
	} 
	
/* Get password and crypt hashes as per usual.
	 */
	
	md5_init(&ctx);
	md5_append(&ctx, pass, strlen(pass));
	md5_finish(&ctx, result);
	to_y64(password_hash, result, 16);
	
	md5_init(&ctx);
	crypt_result = yahoo_crypt(pass, "$1$_2S43d5f$");  
	md5_append(&ctx, crypt_result, strlen(crypt_result));
	md5_finish(&ctx, result);
	to_y64(crypt_hash, result, 16);

	/* Our first authentication response is based off of the password hash.
	 */
	
	for (x = 0; x < (int)strlen(password_hash); x++) 
		pass_hash_xor1[cnt++] = password_hash[x] ^ 0x36;
	
	if (cnt < 64) 
		memset(&(pass_hash_xor1[cnt]), 0x36, 64-cnt);

	cnt = 0;
	
	for (x = 0; x < (int)strlen(password_hash); x++) 
		pass_hash_xor2[cnt++] = password_hash[x] ^ 0x5c;
	
	if (cnt < 64) 
		memset(&(pass_hash_xor2[cnt]), 0x5c, 64-cnt);
	
	shaInit(&ctx1);
	shaInit(&ctx2);
	
	/* 
	 * The first context gets the password hash XORed with 0x36 plus a magic value
	 * which we previously extrapolated from our challenge.
	 */
	
	shaUpdate(&ctx1, pass_hash_xor1, 64);
	if (y >= 3)
		ctx1.sizeLo = 0x1ff;
	shaUpdate(&ctx1, magic_key_char, 4);
	shaFinal(&ctx1, digest1);
	
	/* 
	 * The second context gets the password hash XORed with 0x5c plus the SHA-1 digest
	 * of the first context.
	 */
	
	shaUpdate(&ctx2, pass_hash_xor2, 64);
	shaUpdate(&ctx2, digest1, 20);
	shaFinal(&ctx2, digest2);
	
	/* 
	 * Now that we have digest2, use it to fetch characters from an alphabet to construct
	 * our first authentication response.
	 */

	for (x = 0; x < 20; x += 2) {
		unsigned int	val = 0;
		unsigned int	lookup = 0;
		
		char			byte[6];
				
		memset(&byte, 0, 6);

		/* First two bytes of digest stuffed together.
		 */

		val = digest2[x];
		val <<= 8;
		val += digest2[x+1];
		
		lookup = (val >> 0x0b);
		lookup &= 0x1f;
		if (lookup >= strlen(alphabet1))
			break;
		sprintf(byte, "%c", alphabet1[lookup]);
		strcat(resp_6, byte);
		strcat(resp_6, "=");

		lookup = (val >> 0x06);
		lookup &= 0x1f;
		if (lookup >= strlen(alphabet2))
			break;
		sprintf(byte, "%c", alphabet2[lookup]);
		strcat(resp_6, byte);
		
		lookup = (val >> 0x01);
		lookup &= 0x1f;
		if (lookup >= strlen(alphabet2))
			break;
		sprintf(byte, "%c", alphabet2[lookup]);
		strcat(resp_6, byte);

		lookup = (val & 0x01);
		if (lookup >= strlen(delimit_lookup))
			break;
		sprintf(byte, "%c", delimit_lookup[lookup]);
		strcat(resp_6, byte);
	}
	
	/* Our second authentication response is based off of the crypto hash.
	 */
	
	cnt = 0;
	memset(&digest1, 0, 20);
	memset(&digest2, 0, 20);
	
	for (x = 0; x < (int)strlen(crypt_hash); x++) 
		crypt_hash_xor1[cnt++] = crypt_hash[x] ^ 0x36;
	
	if (cnt < 64) 
		memset(&(crypt_hash_xor1[cnt]), 0x36, 64-cnt);

	cnt = 0;
	
	for (x = 0; x < (int)strlen(crypt_hash); x++) 
		crypt_hash_xor2[cnt++] = crypt_hash[x] ^ 0x5c;
	
	if (cnt < 64) 
		memset(&(crypt_hash_xor2[cnt]), 0x5c, 64-cnt);
	
	shaInit(&ctx1);
	shaInit(&ctx2);
	
	/* 
	 * The first context gets the password hash XORed with 0x36 plus a magic value
	 * which we previously extrapolated from our challenge.
	 */
	
	shaUpdate(&ctx1, crypt_hash_xor1, 64);
	if (y >= 3)
		ctx1.sizeLo = 0x1ff;
	shaUpdate(&ctx1, magic_key_char, 4);
	shaFinal(&ctx1, digest1);
	
	/* 
	 * The second context gets the password hash XORed with 0x5c plus the SHA-1 digest
	 * of the first context.
	 */
	
	shaUpdate(&ctx2, crypt_hash_xor2, 64);
	shaUpdate(&ctx2, digest1, 20);
	shaFinal(&ctx2, digest2);
	
	/* 
	 * Now that we have digest2, use it to fetch characters from an alphabet to construct
	 * our first authentication response.
	 */
	
	for (x = 0; x < 20; x += 2) {
		unsigned int	val = 0;
		unsigned int	lookup = 0;
		
		char			byte[6];
		
		memset(&byte, 0, 6);
		
		/* First two bytes of digest stuffed together.
		 */
		
		val = digest2[x];
		val <<= 8;
		val += digest2[x+1];

		lookup = (val >> 0x0b);
		lookup &= 0x1f;
		if (lookup >= strlen(alphabet1))
			break;
		sprintf(byte, "%c", alphabet1[lookup]);
		strcat(resp_96, byte);
		strcat(resp_96, "=");
		
		lookup = (val >> 0x06);
		lookup &= 0x1f;
		if (lookup >= strlen(alphabet2))
			break;
		sprintf(byte, "%c", alphabet2[lookup]);
		strcat(resp_96, byte);
		
		lookup = (val >> 0x01);
		lookup &= 0x1f;
		if (lookup >= strlen(alphabet2))
			break;
		sprintf(byte, "%c", alphabet2[lookup]);
		strcat(resp_96, byte);
		
		lookup = (val & 0x01);
		if (lookup >= strlen(delimit_lookup))
			break;
		sprintf(byte, "%c", delimit_lookup[lookup]);
		strcat(resp_96, byte);
	}
	
	pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP,	YAHOO_STATUS_AVAILABLE, 0);
	yahoo_packet_hash(pack, 0, name);
	yahoo_packet_hash(pack, 6, resp_6);
	yahoo_packet_hash(pack, 96, resp_96);
	yahoo_packet_hash(pack, 1, name);
	yahoo_send_packet(yd, pack);
	yahoo_packet_free(pack);

	g_free(password_hash);
	g_free(crypt_hash);
}


static void yahoo_process_auth_old(struct yahoo_data *yd, const char *seed) {
    struct yahoo_packet *pack;
    const char *name = normalize(yd->username);
    const char *pass = yd->password;

    /* So, Yahoo has stopped supporting its older clients in India, and undoubtedly
     * will soon do so in the rest of the world.
     *
     * The new clients use this authentication method.  I warn you in advance, it's
     * bizzare, convoluted, inordinately complicated.  It's also no more secure than
     * crypt() was.  The only purpose this scheme could serve is to prevent third
     * part clients from connecting to their servers.
     *
     * Sorry, Yahoo.
     */

    md5_byte_t result[16];
    md5_state_t ctx;

    char *crypt_result;
    char password_hash[25];
    char crypt_hash[25];
    char *hash_string_p = g_malloc(50 + strlen(name));
    char *hash_string_c = g_malloc(50 + strlen(name));

    char checksum;

    int sv;

    char result6[25];
    char result96[25];

    sv = seed[15];
    sv = sv % 8;

    md5_init(&ctx);
    md5_append(&ctx, pass, strlen(pass));
    md5_finish(&ctx, result);
    to_y64(password_hash, result, 16);

    md5_init(&ctx);
    crypt_result = yahoo_crypt(pass, "$1$_2S43d5f$");
    md5_append(&ctx, crypt_result, strlen(crypt_result));
    md5_finish(&ctx, result);
    to_y64(crypt_hash, result, 16);

    switch (sv) {
    case 1:
    case 6:
        checksum = seed[seed[9] % 16];
        g_snprintf(hash_string_p, strlen(name) + 50,
                   "%c%s%s%s", checksum, name, seed, password_hash);
        g_snprintf(hash_string_c, strlen(name) + 50,
                   "%c%s%s%s", checksum, name, seed, crypt_hash);
        break;
    case 2:
    case 7:
        checksum = seed[seed[15] % 16];
        g_snprintf(hash_string_p, strlen(name) + 50,
                   "%c%s%s%s", checksum, seed, password_hash, name);
        g_snprintf(hash_string_c, strlen(name) + 50,
                   "%c%s%s%s", checksum, seed, crypt_hash, name);
        break;
    case 3:
        checksum = seed[seed[1] % 16];
        g_snprintf(hash_string_p, strlen(name) + 50,
                   "%c%s%s%s", checksum, name, password_hash, seed);
        g_snprintf(hash_string_c, strlen(name) + 50,
                   "%c%s%s%s", checksum, name, crypt_hash, seed);
        break;
    case 4:
        checksum = seed[seed[3] % 16];
        g_snprintf(hash_string_p, strlen(name) + 50,
                   "%c%s%s%s", checksum, password_hash, seed, name);
        g_snprintf(hash_string_c, strlen(name) + 50,
                   "%c%s%s%s", checksum, crypt_hash, seed, name);
        break;
    case 0:
    case 5:
        checksum = seed[seed[7] % 16];
        g_snprintf(hash_string_p, strlen(name) + 50,
                   "%c%s%s%s", checksum, password_hash, name, seed);
        g_snprintf(hash_string_c, strlen(name) + 50,
                   "%c%s%s%s", checksum, crypt_hash, name, seed);
        break;
    }

    md5_init(&ctx);
    md5_append(&ctx, hash_string_p, strlen(hash_string_p));
    md5_finish(&ctx, result);
    to_y64(result6, result, 16);

    md5_init(&ctx);
    md5_append(&ctx, hash_string_c, strlen(hash_string_c));
    md5_finish(&ctx, result);
    to_y64(result96, result, 16);

    pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP,	YAHOO_STATUS_AVAILABLE, 0);
    yahoo_packet_hash(pack, 0, name);
    yahoo_packet_hash(pack, 6, result6);
    yahoo_packet_hash(pack, 96, result96);
    yahoo_packet_hash(pack, 1, name);

    yahoo_send_packet(yd, pack);

    g_free(hash_string_p);
    g_free(hash_string_c);

    yahoo_packet_free(pack);

}

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


    while (l) {
        struct yahoo_pair *pair = l->data;
        if (pair->key == 94)
            seed = pair->value;
        if (pair->key == 1)
            sn = pair->value;
        if (pair->key == 13)
            m = atoi(pair->value);
        l = l->next;
    }

    if (seed) {
        switch (m) {
        case 0:
            yahoo_process_auth_old(yd, seed);
            break;
        case 1:
            yahoo_process_auth_new(yd, seed);
            break;
        default:
            yahoo_process_auth_new(yd, seed); /* Can't hurt to try it anyway. */
        }
    }
}

static void yahoo_packet_process(struct yahoo_data *yd, struct yahoo_packet *pkt) {
    switch (pkt->service) {
    case YAHOO_SERVICE_LOGON:
    case YAHOO_SERVICE_LOGOFF:
    case YAHOO_SERVICE_ISAWAY:
    case YAHOO_SERVICE_ISBACK:
    case YAHOO_SERVICE_GAMELOGON:
    case YAHOO_SERVICE_GAMELOGOFF:
        log_debug(ZONE, "[YAHOO]: Process Status");
        yahoo_process_status(yd, pkt);
        break;
    case YAHOO_SERVICE_NOTIFY:
        log_debug(ZONE, "[YAHOO]: Process Service Notify");
        yahoo_process_notify(yd, pkt);
        break;
    case YAHOO_SERVICE_MESSAGE:
    case YAHOO_SERVICE_GAMEMSG:
        log_debug(ZONE, "[YAHOO]: Process Message");
        yahoo_process_message(yd, pkt);
        break;
    case YAHOO_SERVICE_NEWMAIL:
        log_debug(ZONE, "[YAHOO]: Process New Mail");
        yahoo_process_mail(yd, pkt);
        break;
    case YAHOO_SERVICE_NEWCONTACT:
        log_debug(ZONE, "[YAHOO]: Process New Contact");
        yahoo_process_contact(yd, pkt);
        break;
    case YAHOO_SERVICE_LIST:
        log_debug(ZONE, "[YAHOO]: Process Service List");
        yahoo_process_list(yd, pkt);
        break;
    case YAHOO_SERVICE_AUTH:
        log_debug(ZONE, "[YAHOO]: Process Auth");
        yahoo_process_auth(yd, pkt);
        break;
    default:
        log_debug(ZONE, "unhandled service 0x%02x\n", pkt->service);
        break;
    }
}

void yahoo_pending(mio m, int flag, void *arg, char *buf, int len) {
    struct yahoo_data *yd = (struct yahoo_data *)arg;

    if (flag == MIO_CLOSED) {
        log_debug(ZONE, "[YAHOO]: MIO_CLOSE (fd=%d)", m->fd);
        if (yd)
            yahoo_remove_session_yd(yd);
        return;
    }

    if (flag == MIO_ERROR) {
        xmlnode x;
        while((x = mio_cleanup(m)) != NULL)
            deliver_fail(dpacket_new(x), "Socket Error to Yahoo! Server");
        log_debug(ZONE, "[YAHOO]: ERROR from Yahoo! server.");
        if (yd)
            yahoo_remove_session_yd(yd);
        return;
    }

    if(flag == MIO_NEW) {
        log_debug(ZONE, "[YAHOO]: '%s' connected to Yahoo! server. [%s]", jid_full(yd->me),yd->username);
        yd->fd = m;
        yahoo_got_connected(yd);
        return;
    }

    if (len <= 0) {
        // FUNC		hide_login_progress_error(gc, "Unable to read");
        // FUNC		signoff(gc);
        return;
    }
    /*
       From here on, this *should* be a read from the Yahoo server. In this case, we want to 
       send this off to its own thread for processing.
    */
    yd->buf = buf;
    yd->len = len;
#ifdef _JCOMP
    yahoo_read_data((void *)yd);
#else
    pth_spawn(NULL, yahoo_read_data, (void *)yd);
#endif
}

void yahoo_read_data(void *td) {
    struct yahoo_data *yd = (struct yahoo_data *)td;

    log_debug(ZONE, "[YAHOO]: Read %d [%d] bytes (fd=%d)  for '%s'", yd->len, yd->rxlen, yd->fd->fd, jid_full(yd->me));
    yd->yi->stats->bytes_in += yd->len;
    yd->rxqueue = g_realloc(yd->rxqueue, yd->len + yd->rxlen);
    memcpy(yd->rxqueue + yd->rxlen, yd->buf, yd->len);
    yd->rxlen += yd->len;

    while (1) {
        struct yahoo_packet *pkt;
        int pos = 0;
        int pktlen;

        if (yd->rxlen < YAHOO_PACKET_HDRLEN)
            return;

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

        pktlen = yahoo_get16(yd->rxqueue + pos); pos += 2;
        log_debug(ZONE, "[YAHOO]: %d bytes to read, rxlen is %d\n", pktlen, yd->rxlen);

        if (yd->rxlen < (YAHOO_PACKET_HDRLEN + pktlen))
            return;

        yahoo_packet_dump(yd->rxqueue, YAHOO_PACKET_HDRLEN + pktlen);

        pkt = yahoo_packet_new(0, 0, 0);

        pkt->service = yahoo_get16(yd->rxqueue + pos); pos += 2;
        pkt->status = yahoo_get32(yd->rxqueue + pos); pos += 4;
        log_debug(ZONE, "[YAHOO]: Service: 0x%02x Status: %d\n", pkt->service, pkt->status);
        pkt->id = yahoo_get32(yd->rxqueue + pos); pos += 4;

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

        yd->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
        if (yd->rxlen) {
            char *tmp = g_memdup(yd->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yd->rxlen);
            g_free(yd->rxqueue);
            yd->rxqueue = tmp;
        } else {
            g_free(yd->rxqueue);
            yd->rxqueue = NULL;
        }

        yahoo_packet_process(yd, pkt);

        yahoo_packet_free(pkt);
    }
}

void yahoo_got_connected(struct yahoo_data *yd) {
    struct yahoo_packet *pkt;

    //	if (!g_slist_find(connections, gc)) {
    //		close(source);
    //		return;
    //  	}
    //
    //  	if (source < 0) {
    //  		hide_login_progress(gc, "Unable to connect");
    //  		signoff(gc);
    //  		return;
    //  	}
    //
    //  	yd = gc->proto_data;
    //  	yd->fd = source;
    //
    pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, YAHOO_STATUS_AVAILABLE, 0);

    yahoo_packet_hash(pkt, 1, normalize(yd->username));
    yahoo_send_packet(yd, pkt);

    yahoo_packet_free(pkt);
    //
    //  	gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc);
}

//  static void yahoo_login(struct aim_user *user) {
//  	jpacket jp = new_gaim_conn(user);
//  	struct yahoo_data *yd = (struct yahoo_data *)jp->aux1;
//
//  	set_login_progress(gc, 1, "Connecting");
//
//  	yd->fd = -1;
//  	yd->hash = g_hash_table_new(g_str_hash, g_str_equal);
//  	yd->games = g_hash_table_new(g_str_hash, g_str_equal);
//
//
//  	if (!g_strncasecmp(user->proto_opt[USEROPT_PAGERHOST], "cs.yahoo.com", strlen("cs.yahoo.com"))) {
//  		/* Figured out the new auth method -- cs.yahoo.com likes to disconnect on buddy remove and add now */
//  		debug_printf("Setting new Yahoo! server.\n");
//  		g_snprintf(user->proto_opt[USEROPT_PAGERHOST], strlen("scs.yahoo.com") + 1, "scs.yahoo.com");
//  		save_prefs();
//  	}
//
//
//         	if (proxy_connect(user->proto_opt[USEROPT_PAGERHOST][0] ?
//  				user->proto_opt[USEROPT_PAGERHOST] : YAHOO_PAGER_HOST,
//  			   user->proto_opt[USEROPT_PAGERPORT][0] ?
//  				atoi(user->proto_opt[USEROPT_PAGERPORT]) : YAHOO_PAGER_PORT,
//  			   yahoo_got_connected, gc) < 0) {
//  		hide_login_progress(gc, "Connection problem");
//  		signoff(gc);
//  		return;
//  	}
//
//  }
/*
static gboolean yahoo_destroy_hash(gpointer key, gpointer val, gpointer data) {
    g_free(key);
    g_free(val);
    return TRUE;
}
*/
void yahoo_close(struct yahoo_data *yd) {

    // g_hash_table_foreach_remove(yd->hash, yahoo_destroy_hash, NULL);
    // g_hash_table_destroy(yd->hash);
    // g_hash_table_foreach_remove(yd->games, yahoo_destroy_hash, NULL);
    // g_hash_table_destroy(yd->games);
    log_debug(ZONE, "[YAHOO] yahoo_close() called");
    if (yd->fd)
        mio_close(yd->fd);
    if (yd->rxqueue)
        g_free(yd->rxqueue);
    yd->rxlen = 0;
    yahoo_remove_session_yd(yd);
    //	if (gc->inpa)
    //		gaim_input_remove(gc->inpa);
    //	g_free(yd);
}

//  static char **yahoo_list_icon(int uc)
//  {
//  	if ((uc >> 2) == YAHOO_STATUS_IDLE)
//  		return status_idle_xpm;
//  	else if (uc & UC_UNAVAILABLE)
//  		return status_away_xpm;
//  	else if (uc & YAHOO_STATUS_GAME)
//  		return status_game_xpm;
//  	return status_here_xpm;
//  }

char *yahoo_get_status_string(enum yahoo_status a) {
    switch (a) {
    case YAHOO_STATUS_BRB:
        return "Be Right Back";
    case YAHOO_STATUS_BUSY:
        return "Busy";
    case YAHOO_STATUS_NOTATHOME:
        return "Not At Home";
    case YAHOO_STATUS_NOTATDESK:
        return "Not At Desk";
    case YAHOO_STATUS_NOTINOFFICE:
        return "Not In Office";
    case YAHOO_STATUS_ONPHONE:
        return "On Phone";
    case YAHOO_STATUS_ONVACATION:
        return "On Vacation";
    case YAHOO_STATUS_OUTTOLUNCH:
        return "Out To Lunch";
    case YAHOO_STATUS_STEPPEDOUT:
        return "Stepped Out";
    case YAHOO_STATUS_INVISIBLE:
        return "Invisible";
    default:
        return "Online";
    }
}

//  static void yahoo_game(struct yahoo_data *yd, char *name) {
//     char *game = g_hash_table_lookup(yd->games, name);
//     char *t;
//     char url[256];

//     if (!game)
//        return;
//     t = game = g_strdup(strstr(game, "ante?room="));
//     while (*t != '\t')
//        t++;
//     *t = 0;
//     g_snprintf(url, sizeof url, "http://games.yahoo.com/games/%s", game);
//     open_url(NULL, url);
//     g_free(game);
//  }
//  static GList *yahoo_buddy_menu(jpacket jp, char *who)
//  {
//  	GList *m = NULL;
//  	struct proto_buddy_menu *pbm;
//  	struct yahoo_data *yd = (struct yahoo_data *)jp->aux1;
//  	struct buddy *b = find_buddy(gc, who); /* this should never be null. if it is,
//  						  segfault and get the bug report. */
//  	static char buf[1024];
//  	static char buf2[1024];
//
//  	if (b->uc & UC_UNAVAILABLE && b->uc >> 2 != YAHOO_STATUS_IDLE) {
//  		pbm = g_new0(struct proto_buddy_menu, 1);
//  		if ((b->uc >> 2) != YAHOO_STATUS_CUSTOM)
//  			g_snprintf(buf, sizeof buf,
//  				   "Status: %s", yahoo_get_status_string(b->uc >> 2));
//  		else
//  			g_snprintf(buf, sizeof buf, "Custom Status: %s",
//  				   (char *)g_hash_table_lookup(yd->hash, b->name));
//  		pbm->label = buf;
//  		pbm->callback = NULL;
//  		pbm->gc = gc;
//  		m = g_list_append(m, pbm);
//  	}
//
//  	if (b->uc | YAHOO_STATUS_GAME) {
//  		char *game = g_hash_table_lookup(yd->games, b->name);
//  		char *room;
//  		if (!game)
//  			return m;
//  		if (game) {
//  			char *t;
//  			pbm = g_new0(struct proto_buddy_menu, 1);
//  			if (!(room = strstr(game, "&follow="))) /* skip ahead to the url */
//  				return NULL;
//  			while (*room && *room != '\t')          /* skip to the tab */
//  				room++;
//  			t = room++;                             /* room as now at the name */
//  			while (*t != '\n')
//  				t++;                            /* replace the \n with a space */
//  			*t = ' ';
//  			g_snprintf(buf2, sizeof buf2, "%s", room);
//  			pbm->label = buf2;
//  			pbm->callback = yahoo_game;
//  			pbm->gc = gc;
//  			m = g_list_append(m, pbm);
//  		}
//  	}
//
//  	return m;
//  }

//  static GList *yahoo_user_opts()
//  {
//  	GList *m = NULL;
//  	struct proto_user_opt *puo;
//
//  	puo = g_new0(struct proto_user_opt, 1);
//  	puo->label = "Pager Host:";
//  	puo->def = YAHOO_PAGER_HOST;
//  	puo->pos = USEROPT_PAGERHOST;
//  	m = g_list_append(m, puo);
//
//  	puo = g_new0(struct proto_user_opt, 1);
//  	puo->label = "Pager Port:";
//  	puo->def = "5050";
//  	puo->pos = USEROPT_PAGERPORT;
//  	m = g_list_append(m, puo);
//
//  	return m;
//  }
//
void yahoo_act_id(struct yahoo_data *yd, char *entry) {
    struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_IDACT, YAHOO_STATUS_AVAILABLE, 0);
    yahoo_packet_hash(pkt, 3, entry);
    yahoo_send_packet(yd, pkt);
    yahoo_packet_free(pkt);

    g_snprintf(yd->displayname, sizeof(yd->displayname), "%s", entry);
}
//
//  static void yahoo_do_action(jpacket jp, char *act)
//  {
//  	if (!strcmp(act, "Activate ID")) {
//  		do_prompt_dialog("Activate which ID:", gc->displayname, gc, yahoo_act_id, NULL);
//  	}
//  }
//
//  static GList *yahoo_actions() {
//  	GList *m = NULL;
//
//  	m = g_list_append(m, "Activate ID");
//
//  	return m;
//  }

int yahoo_send_im(struct yahoo_data *yd, char *who, char *what, int len, int flags) {
    struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_AVAILABLE, 0);

    yahoo_packet_hash(pkt, 1, yd->displayname);
    yahoo_packet_hash(pkt, 5, who);
    yahoo_packet_hash(pkt, 14, what);
    yahoo_packet_hash(pkt, 97, "1");
    yahoo_send_packet(yd, pkt);

    yahoo_packet_free(pkt);

    return 1;
}

int yahoo_send_typing(struct yahoo_data *yd, char *who, int typ) {
    struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_TYPING, 0);
    yahoo_packet_hash(pkt, 49, "TYPING");
    yahoo_packet_hash(pkt, 1, yd->displayname);
    yahoo_packet_hash(pkt, 14, " ");
    yahoo_packet_hash(pkt, 13, typ ? "1" : "0");
    yahoo_packet_hash(pkt, 5, who);
    yahoo_packet_hash(pkt, 1002, "1");

    yahoo_send_packet(yd, pkt);

    yahoo_packet_free(pkt);

    return 0;
}

void yahoo_set_away(struct yahoo_data *yd, int state, char *msg) {
    struct yahoo_packet *pkt;
    int service;
    char s[4];

    if (state == 0) {
        service = YAHOO_SERVICE_ISBACK;
        yd->current_status = YAHOO_STATUS_AVAILABLE;
    } else {
        service = YAHOO_SERVICE_ISAWAY;
        yd->current_status = YAHOO_STATUS_CUSTOM;
    }


    pkt = yahoo_packet_new(service, yd->current_status, 0);
    if (state  == 0) {
        g_snprintf(s, sizeof(s), "%d", YAHOO_STATUS_AVAILABLE);
        yahoo_packet_hash(pkt, 10, s);
    } else {
        g_snprintf(s, sizeof(s), "%d", YAHOO_STATUS_CUSTOM);
        yahoo_packet_hash(pkt, 10, s);
        g_snprintf(s, sizeof(s), "%d", 0);
        yahoo_packet_hash(pkt, 47, "1");
        yahoo_packet_hash(pkt, 19, msg);
    }

//  log_notice(ZONE, "[YAHOO]: Presence service=0x%04x status=0x%04x", service,yd->current_status);
    yahoo_send_packet(yd, pkt);
    yahoo_packet_free(pkt);
}
/*
static void yahoo_set_idle(struct yahoo_data *yd, int idle) {
    struct yahoo_packet *pkt = NULL;

    if (idle && yd->current_status == YAHOO_STATUS_AVAILABLE) {
        pkt = yahoo_packet_new(YAHOO_SERVICE_ISAWAY, YAHOO_STATUS_IDLE, 0);
        yd->current_status = YAHOO_STATUS_IDLE;
    } else if (!idle && yd->current_status == YAHOO_STATUS_IDLE) {
        pkt = yahoo_packet_new(YAHOO_SERVICE_ISAWAY, YAHOO_STATUS_AVAILABLE, 0);
        yd->current_status = YAHOO_STATUS_AVAILABLE;
    }

    if (pkt) {
        char buf[4];
        g_snprintf(buf, sizeof(buf), "%d", yd->current_status);
        yahoo_packet_hash(pkt, 10, buf);
        yahoo_send_packet(yd, pkt);
        yahoo_packet_free(pkt);
    }
}

static GList *yahoo_away_states(struct yahoo_data *yd) {
    GList *m = NULL;

    m = g_list_append(m, "Available");
    m = g_list_append(m, "Be Right Back");
    m = g_list_append(m, "Busy");
    m = g_list_append(m, "Not At Home");
    m = g_list_append(m, "Not At Desk");
    m = g_list_append(m, "Not In Office");
    m = g_list_append(m, "On Phone");
    m = g_list_append(m, "On Vacation");
    m = g_list_append(m, "Out To Lunch");
    m = g_list_append(m, "Stepped Out");
    m = g_list_append(m, "Invisible");
    //	m = g_list_append(m, GAIM_AWAY_CUSTOM);

    return m;
}

static void yahoo_keepalive(struct yahoo_data *yd) {
    struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, 0);
    yahoo_send_packet(yd, pkt);
    yahoo_packet_free(pkt);
}
*/
void yahoo_add_buddy(struct yahoo_data *yd, char *who, char *group) {
    struct yahoo_packet *pkt;
    //	struct group *g;
    //	char *group = NULL;
    //
    if (!yd->logged_in)
        return;

    //  	g = find_group_by_buddy(gc, who);
    //  	if (g)
    //  		group = g->name;
    //  	else
    //  		group = "Buddies";

    pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0);
    yahoo_packet_hash(pkt, 1, yd->displayname);
    yahoo_packet_hash(pkt, 7, who);
    yahoo_packet_hash(pkt, 65, group);
    yahoo_send_packet(yd, pkt);
    yahoo_packet_free(pkt);
}

void yahoo_remove_buddy(struct yahoo_data *yd, char *who, char *group) {

    struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0);
    yahoo_packet_hash(pkt, 1, yd->displayname);
    yahoo_packet_hash(pkt, 7, who);
    yahoo_packet_hash(pkt, 65, group);
    yahoo_send_packet(yd, pkt);
    yahoo_packet_free(pkt);
}
//
//
//  GSList *yahoo_smiley_list()
//  {
//  	GSList *smilies = NULL;
//
//  	smilies = add_smiley(smilies, "=:)", yahoo_alien, 1);
//  	smilies = add_smiley(smilies, "=:-)", yahoo_alien, 0);
//  	smilies = add_smiley(smilies, "o:)", yahoo_angel, 0);
//  	smilies = add_smiley(smilies, "o:-)", yahoo_angel, 0);
//  	smilies = add_smiley(smilies, "0:)", yahoo_angel, 0);
//  	smilies = add_smiley(smilies, "0:-)", yahoo_angel, 0);
//  	smilies = add_smiley(smilies, "X-(", yahoo_angry, 1);
//  	smilies = add_smiley(smilies, "X(", yahoo_angry, 0);
//  	smilies = add_smiley(smilies, "x-(", yahoo_angry, 0);
//  	smilies = add_smiley(smilies, "x(", yahoo_angry, 0);
//  	smilies = add_smiley(smilies, ":D", yahoo_bigsmile, 1);
//  	smilies = add_smiley(smilies, ":-D", yahoo_bigsmile, 0);
//  	smilies = add_smiley(smilies, ":\">", yahoo_blush, 1);
//  	smilies = add_smiley(smilies, "=;", yahoo_bye, 1);
//  	smilies = add_smiley(smilies, ":o)", yahoo_clown, 1);
//  	smilies = add_smiley(smilies, ":0)", yahoo_clown, 0);
//  	smilies = add_smiley(smilies, ":O)", yahoo_clown, 0);
//  	smilies = add_smiley(smilies, "<@:)", yahoo_clown, 0);
//  	smilies = add_smiley(smilies, "3:-0", yahoo_cow, 1);
//  	smilies = add_smiley(smilies, "3:-o", yahoo_cow, 0);
//  	smilies = add_smiley(smilies, "3:-O", yahoo_cow, 0);
//  	smilies = add_smiley(smilies, "3:O", yahoo_cow, 0);
//  	smilies = add_smiley(smilies, "<):)", yahoo_cowboy, 1);
//  	smilies = add_smiley(smilies, ":((", yahoo_cry, 1);
//  	smilies = add_smiley(smilies, ":-((", yahoo_cry, 0);
//  	smilies = add_smiley(smilies, ">:)", yahoo_devil, 1);
//  	smilies = add_smiley(smilies, "@};-", yahoo_flower, 1);
//  	smilies = add_smiley(smilies, "8-X", yahoo_ghost, 1);
//  	smilies = add_smiley(smilies, ":B", yahoo_glasses, 1);
//  	smilies = add_smiley(smilies, ":-B", yahoo_glasses, 0);
//  	smilies = add_smiley(smilies, ":))", yahoo_laughloud, 1);
//  	smilies = add_smiley(smilies, ":-))", yahoo_laughloud, 0);
//  	smilies = add_smiley(smilies, ":x", yahoo_love, 1);
//  	smilies = add_smiley(smilies, ":-x", yahoo_love, 0);
//  	smilies = add_smiley(smilies, ":X", yahoo_love, 0);
//  	smilies = add_smiley(smilies, ":-X", yahoo_love, 0);
//  	smilies = add_smiley(smilies, ":>", yahoo_mean, 1);
//  	smilies = add_smiley(smilies, ":->", yahoo_mean, 0);
//  	smilies = add_smiley(smilies, ":|", yahoo_neutral, 1);
//  	smilies = add_smiley(smilies, ":-|", yahoo_neutral, 0);
//  	smilies = add_smiley(smilies, ":O", yahoo_ooooh, 1);
//  	smilies = add_smiley(smilies, ":-O", yahoo_ooooh, 0);
//  	smilies = add_smiley(smilies, ":-\\", yahoo_question, 1);
//  	smilies = add_smiley(smilies, ":-/", yahoo_question, 0);
//  	smilies = add_smiley(smilies, ":(", yahoo_sad, 1);
//  	smilies = add_smiley(smilies, ":-(", yahoo_sad, 0);
//  	smilies = add_smiley(smilies, "I-)", yahoo_sleep, 1);
//  	smilies = add_smiley(smilies, "|-)", yahoo_sleep, 0);
//  	smilies = add_smiley(smilies, "I-|", yahoo_sleep, 0);
//  	smilies = add_smiley(smilies, ":)", yahoo_smiley, 1);
//  	smilies = add_smiley(smilies, ":-)", yahoo_smiley, 0);
//  	smilies = add_smiley(smilies, "(:", yahoo_smiley, 0);
//  	smilies = add_smiley(smilies, "(-:", yahoo_smiley, 0);
//  	smilies = add_smiley(smilies, "B-)", yahoo_sunglas, 1);
//  	smilies = add_smiley(smilies, ":-p", yahoo_tongue, 1);
//  	smilies = add_smiley(smilies, ":p", yahoo_tongue, 0);
//  	smilies = add_smiley(smilies, ":P", yahoo_tongue, 0);
//  	smilies = add_smiley(smilies, ":-P", yahoo_tongue, 0);
//  	smilies = add_smiley(smilies, ";)", yahoo_wink, 1);
//  	smilies = add_smiley(smilies, ";-)", yahoo_wink, 0);
//
//
//  	return smilies;
//  }
//
//  static struct prpl *my_protocol = NULL;
//
//  void yahoo_init(struct prpl *ret) {
//  	ret->protocol = PROTO_YAHOO;
//  	ret->options = OPT_PROTO_MAIL_CHECK;
//  	ret->name = yahoo_name;
//  	ret->user_opts = yahoo_user_opts;
//  	ret->login = yahoo_login;
//  	ret->close = yahoo_close;
//  	ret->buddy_menu = yahoo_buddy_menu;
//  	ret->list_icon = yahoo_list_icon;
//  	ret->actions = yahoo_actions;
//  	ret->do_action = yahoo_do_action;
//  	ret->send_im = yahoo_send_im;
//  	ret->away_states = yahoo_away_states;
//  	ret->set_away = yahoo_set_away;
//  	ret->set_idle = yahoo_set_idle;
//  	ret->keepalive = yahoo_keepalive;
//  	ret->add_buddy = yahoo_add_buddy;
//  	ret->remove_buddy = yahoo_remove_buddy;
//  	ret->send_typing = yahoo_send_typing;
//  	ret->smiley_list = yahoo_smiley_list;
//
//  	my_protocol = ret;
//  }
//
//  #ifndef STATIC
//
//  char *gaim_plugin_init(GModule *handle)
//  {
//  	load_protocol(yahoo_init, sizeof(struct prpl));
//  	return NULL;
//  }
//
//  void gaim_plugin_remove()
//  {
//  	struct prpl *p = find_prpl(PROTO_YAHOO);
//  	if (p == my_protocol)
//  		unload_protocol(p);
//  }
//
//  char *name()
//  {
//  	return "Yahoo";
//  }
//
//  char *description()
//  {
//  	return PRPL_DESC("Yahoo");
//  }
//
//  #endif
//
