view src/protocols/irc/irc.c @ 3725:dd48b1ac5bd8

[gaim-migrate @ 3861] This is better because Duffman says so, oh yeah! No, but seriously... before these changes, if you got new email on an account, but didn't read it, gaim would pop up a little "read yo email, sucka!" notice every once in a while, because AIM sends you a little email status thing every once in a while. This should alleviate that problem (by attempting to keep track of the number of unread emails in your account). committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Thu, 17 Oct 2002 05:06:15 +0000
parents 34c95669952f
children a20bf3d247ff
line wrap: on
line source

/*
 * gaim - IRC Protocol Plugin
 *
 * Copyright (C) 2000-2001, Rob Flynn <rob@tgflinux.com>
 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
 *
 * A large portion of this was copied more or less directly from X-Chat,
 * the world's most rocking IRC client. http://www.xchat.org/
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <config.h>

#ifndef _WIN32
#include <unistd.h>
#else
#include <winsock.h>
#endif

#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <ctype.h>
#include "multi.h"
#include "prpl.h"
#include "gaim.h"
#include "proxy.h"

#ifdef _WIN32
#include "win32dep.h"
#endif

#include "pixmaps/protocols/irc/irc_icon.xpm"

#define IRC_BUF_LEN 4096
#define PDIWORDS 32

#define USEROPT_SERV      0
#define USEROPT_PORT      1

/* for win32 compatability */
G_MODULE_IMPORT GSList *connections;


static void irc_start_chat(struct gaim_connection *gc, char *who);
static void irc_ctcp_clientinfo(struct gaim_connection *gc, char *who);
static void irc_ctcp_userinfo(struct gaim_connection *gc, char *who);
static void irc_ctcp_version(struct gaim_connection *gc, char *who);

struct dcc_chat
{
	struct gaim_connection *gc;	
	char ip_address[12];	
	int port;		
	int fd;			
	int inpa;		
	char nick[80];		
};

struct irc_file_transfer {
	enum { IFT_SENDFILE_IN, IFT_SENDFILE_OUT } type;
	struct file_transfer *xfer;
	char *sn;
	char *name;
	int len;
	int watcher;
	int awatcher;
	char ip[12];
	int port;
	int fd;
	int cur;
	struct gaim_connection *gc;
};

GSList *dcc_chat_list = NULL;

struct irc_data {
	int fd;
	gboolean online;
	guint32 timer;

	char *rxqueue;
	int rxlen;

	GString *str;
	int bc;

	char *chantypes;
	char *chanmodes;
	char *nickmodes;
	gboolean six_modes;

	gboolean in_whois;
	gboolean in_list;
	GString *liststr;
	GSList *file_transfers;
};

struct dcc_chat *
find_dcc_chat (struct gaim_connection *gc, char *nick)
{
	GSList *tmp;
	struct dcc_chat *data;
	tmp = dcc_chat_list;
	while (tmp != NULL)
		{
			data = (struct dcc_chat *) (tmp)->data;
			if (data 
			    && data->nick 
			    && strcmp (nick, data->nick) == 0
			    && gc == data->gc)
				{
					return data;
				}
			tmp = tmp->next;
		}
	return NULL;
}

static int irc_write(int fd, char *data, int len)
{
	debug_printf("IRC C: %s", data);
	return write(fd, data, len);
}

static struct conversation *irc_find_chat(struct gaim_connection *gc, char *name)
{
	GSList *bcs = gc->buddy_chats;

	while (bcs) {
		struct conversation *b = bcs->data;
		if (!g_strcasecmp(b->name, name))
			return b;
		bcs = bcs->next;
	}
	return NULL;
}

static struct conversation *irc_find_chat_by_id(struct gaim_connection *gc, int id)
{
	GSList *bcs = gc->buddy_chats;

	while (bcs) {
		struct conversation *b = bcs->data;
		if (b->id == id)
			return b;
		bcs = bcs->next;
	}
	return NULL;
}

static void process_data_init(char *buf, char *cmd, char *word[], char *eol[], gboolean quotes)
{
	int wordcount = 2;
	gboolean space = FALSE;
	gboolean quote = FALSE;
	int j = 0;

	word[1] = cmd;
	eol[1] = buf;

	while (TRUE) {
		switch (*cmd) {
		case 0:
			buf[j] = 0;
			for (j = wordcount; j < PDIWORDS; j++) {
				word[j] = "\000\000";
				eol[j] = "\000\000";
			}
			return;
		case '"':
			if (!quotes) {
				space = FALSE;
				buf[j++] = *cmd;
				break;
			}
			quote = !quote;
			break;
		case ' ':
			if (quote) {
				space = FALSE;
				buf[j++] = *cmd;
				break;
			}
			if (space)
				break;
			buf[j++] = 0;
			word[wordcount] = &buf[j];
			eol[wordcount++] = cmd + 1;
			if (wordcount == PDIWORDS - 1) {
				buf[j] = 0;
				return;
			}
			space = TRUE;
			break;
		default:
			space = FALSE;
			buf[j++] = *cmd;
		}
		cmd++;
	}
}

static void handle_005(struct gaim_connection *gc, char *word[], char *word_eol[])
{
	int w = 4;
	struct irc_data *id = gc->proto_data;

	while (w < PDIWORDS && *word[w]) {
		if (!strncmp(word[w], "MODES=", 5)) {
			if (atoi(word[w] + 6) >= 6)
				id->six_modes = TRUE;
		} else if (!strncmp(word[w], "CHANTYPES=", 10)) {
			g_free(id->chantypes);
			id->chantypes = g_strdup(word[w] + 10);
		} else if (!strncmp(word[w], "CHANMODES=", 10)) {
			g_free(id->chanmodes);
			id->chanmodes = g_strdup(word[w] + 10);
		} else if (!strncmp(word[w], "PREFIX=", 7)) {
			char *pre = strchr(word[w] + 7, ')');
			if (pre) {
				*pre = 0;
				g_free(id->nickmodes);
				id->nickmodes = g_strdup(word[w] + 8);
			}
		}
		w++;
	}
}

static char *int_to_col(int c)
{
	switch(c) {
	case 1:
		return "#ffffff";
	case 2:
		return "#000066";
	case 3:
		return "#006600";
	case 4:
		return "#ff0000";
	case 5:
		return "#660000";
	case 6:
		return "#660066";
	case 7:
		return "#666600";
	case 8:
		return "#cccc00";
	case 9:
		return "#33cc33";
	case 10:
		return "#00acac";
	case 11:
		return "#00ccac";
	case 12:
		return "#0000ff";
	case 13:
		return "#cc00cc";
	case 14:
		return "#666666";
	case 15:
		return "#00ccac";
	default:
		return "#000000";
	}
}

static GString *encode_html(char *msg)
{
	GString *str = g_string_new("");
	char *cur = msg, *end = msg;
	gboolean bold = FALSE, underline = FALSE, italics = FALSE;
	
	while ((end = strchr(cur, '<'))) {
		*end = 0;
		str = g_string_append(str, cur);
		cur = ++end;
		if (!g_strncasecmp(cur, "B>", 2)) {
			if (!bold) {
				bold = TRUE;
				str = g_string_append_c(str, '\2');
			}
			cur = cur + 2;
		} else if (!g_strncasecmp(cur, "I>", 2)) { /* use bold for italics too */
			if (!italics) {
				italics = TRUE;
				str = g_string_append_c(str, '\2');
			}
			cur = cur + 2;
		} else if (!g_strncasecmp(cur, "U>", 2)) {
			if (!underline) {
				underline = TRUE;
				str = g_string_append_c(str, '\37');
			}
			cur = cur + 2;
		}  else if (!g_strncasecmp(cur, "/B>", 3)) { 
			if (bold) {
				bold = FALSE;
				str = g_string_append_c(str, '\2');
			}
			cur = cur + 3;
		}  else if (!g_strncasecmp(cur, "/I>", 3)) { 
			if (italics) {
				italics = FALSE;
				str = g_string_append_c(str, '\2');
			}
			cur = cur + 3;
		}  else if (!g_strncasecmp(cur, "/U>", 3)) { 
			if (underline) {
				underline = FALSE;
				str = g_string_append_c(str, '\37');
			}
			cur = cur + 3;
		}  else {
			str = g_string_append_c(str, '<');
		}
		
	}
	str = g_string_append(str, cur);
	return str;
}

static GString *decode_html(char *msg)
{
	GString /* oo la la */ *str = g_string_new("");
	char *cur = msg, *end = msg;
	gboolean bold = FALSE, underline = FALSE, fg = FALSE, bg = FALSE;
	int fore, back;
	while (*end) {
		switch (*end) {
		case 02: /* ^B */
			*end = 0;
			str = g_string_append(str, cur);
			if (bold)
				str = g_string_append(str, "</B>");
			else
				str = g_string_append(str, "<B>");
			bold = !bold;
			cur = end + 1;
			break;
		case 03: /* ^C */
			*end++ = 0;
			str = g_string_append(str, cur);
			fore = back = -1;
			if (isdigit(*end)) {
				fore = *end++ - '0';
				if (isdigit(*end)) {
					fore *= 10;
					fore += *end++ - '0';
				}
				if (*end == ',' && isdigit(end[1])) {
					end++;
					back = *end++ - '0';
					if (isdigit(*end)) {
						back *= 10;
						back += *end++ - '0';
					}
				}
			}
			if (fore == -1) {
				if (fg)
					str = g_string_append(str, "</FONT>");
				if (bg)
					str = g_string_append(str, "</FONT>");
				fg = bg = FALSE;
			} else {
				fore %= 16;
				if (fg)
					str = g_string_append(str, "</FONT>");
				if (back != -1) {
					if (bg)
						str = g_string_append(str, "</FONT>");
					back %= 16;
					str = g_string_append(str, "<FONT BACK=");
					str = g_string_append(str, int_to_col(back));
					str = g_string_append_c(str, '>');
					bg = TRUE;
				}
				str = g_string_append(str, "<FONT COLOR=");
				str = g_string_append(str, int_to_col(fore));
				str = g_string_append_c(str, '>');
				fg = TRUE;
			}
			cur = end--;
			break;
		case 017: /* ^O */
			if (!bold && !underline && !fg && !bg)
				break;
			*end = 0;
			str = g_string_append(str, cur);
			if (bold)
				str = g_string_append(str, "</B>");
			if (underline)
				str = g_string_append(str, "</U>");
			if (fg)
				str = g_string_append(str, "</FONT>");
			if (bg)
				str = g_string_append(str, "</FONT>");
			bold = underline = fg = bg = FALSE;
			cur = end + 1;
			break;
		case 037: /* ^_ */
			*end = 0;
			str = g_string_append(str, cur);
			if (underline)
				str = g_string_append(str, "</U>");
			else
				str = g_string_append(str, "<U>");
			underline = !underline;
			cur = end + 1;
			break;
		}
		end++;
	}
	if (*cur)
		str = g_string_append(str, cur);
	return str;
}

static void irc_got_im(struct gaim_connection *gc, char *who, char *what, int flags, time_t t)
{
	GString *str = decode_html(what);
	serv_got_im(gc, who, str->str, flags, t, -1);
	g_string_free(str, TRUE);
}

static void dcc_chat_cancel(void *, struct dcc_chat *);

void
dcc_chat_in (gpointer data, gint source, GaimInputCondition condition)
{
	struct dcc_chat *chat = data;
	gchar buffer[IRC_BUF_LEN];
	gchar buf[128];
	int n = 0, l;
	struct conversation *convo;
	debug_printf("THIS IS TOO MUCH EFFORT\n");
	n = read (chat->fd, buffer, IRC_BUF_LEN);
	if (n > 0) {

		buffer[n] = 0;
		g_strstrip(buffer);

		/* Convert to HTML */
		if (strlen(buffer)) {
			debug_printf ("DCC Message from: %s\n", chat->nick);
			irc_got_im(chat->gc, chat->nick, buffer, 0, 
				   time(NULL));
		}
	}	
	else	{
		g_snprintf (buf, sizeof buf, _("DCC Chat with %s closed"),
			    chat->nick);
		convo = new_conversation (chat->nick);
		write_to_conv (convo, buf, WFLAG_SYSTEM, NULL,
			       time ((time_t) NULL), -1);
		dcc_chat_cancel (NULL,chat);
	}
}

static void irc_file_transfer_do(struct gaim_connection *gc, struct irc_file_transfer *ift) {
	/* Ok, we better be receiving some crap here boyeee */
	if (transfer_in_do(ift->xfer, ift->fd, ift->name, ift->len)) {
		gaim_input_remove(ift->watcher);
		ift->watcher = 0;
	}
}


void irc_read_dcc_ack (gpointer data, gint source, GaimInputCondition condition) {
	/* Read ACK Here */

}

void dcc_send_callback (gpointer data, gint source, GaimInputCondition condition) {
	struct irc_file_transfer *ift = data;
	struct sockaddr_in addr;
	int len = sizeof(addr);
		
	addr.sin_family = AF_INET;
	addr.sin_port = htons(ift->port);
	addr.sin_addr.s_addr = INADDR_ANY;

	ift->fd = accept(ift->fd, (struct sockaddr *)&addr, &len);
	if (!ift->fd) {
		/* FIXME: Handle this gracefully XXX */
		printf("Something bad happened here, bubba!\n");
		return;
	}
		
	/*	ift->awatcher = gaim_input_add(ift->fd, GAIM_INPUT_READ, irc_read_dcc_ack, ift); */
		
	if (transfer_out_do(ift->xfer, ift->fd, 0)) {
		gaim_input_remove(ift->watcher);
		ift->watcher = 0;
	}
}

void dcc_recv_callback (gpointer data, gint source, GaimInputCondition condition) {
	struct irc_file_transfer *ift = data;
	
	ift->fd = source;
	irc_file_transfer_do(ift->gc, ift);
}

void dcc_chat_callback (gpointer data, gint source, GaimInputCondition condition) {
	struct dcc_chat *chat = data;
	struct conversation *convo = new_conversation (chat->nick);
	char buf[IRC_BUF_LEN];
	chat->fd = source;
	g_snprintf (buf, sizeof buf,
		    "DCC Chat with %s established",
		    chat->nick);
	write_to_conv (convo, buf, WFLAG_SYSTEM, NULL,
		       time ((time_t) NULL), -1);
	debug_printf ("Chat with %s established\n", chat->nick);
	dcc_chat_list =  g_slist_append (dcc_chat_list, chat);
	gaim_input_remove(chat->inpa);
	chat->inpa = gaim_input_add(source, GAIM_INPUT_READ, dcc_chat_in, chat);
}

static void irc_got_chat_in(struct gaim_connection *gc, int id, char *who, int whisper, char *msg, time_t t)
{
	GString *str = decode_html(msg);
	serv_got_chat_in(gc, id, who, whisper, str->str, t);
	g_string_free(str, TRUE);
}

static void handle_list(struct gaim_connection *gc, char *list)
{
	struct irc_data *id = gc->proto_data;
	GSList *gr;

	id->str = g_string_append_c(id->str, ' ');
	id->str = g_string_append(id->str, list);
	id->bc--;
	if (id->bc)
		return;

	g_strdown(id->str->str);
	gr = gc->groups;
	while (gr) {
		GSList *m = ((struct group *)gr->data)->members;
		while (m) {
			struct buddy *b = m->data;
			char *tmp = g_strdup(b->name);
			char *x, *l;
			g_strdown(tmp);
			x = strstr(id->str->str, tmp);
			l = x + strlen(b->name);
			if (x && (*l != ' ' && *l != 0))
				x = 0;
			if (!b->present && x)
				serv_got_update(gc, b->name, 1, 0, 0, 0, 0, 0);
			else if (b->present && !x)
				serv_got_update(gc, b->name, 0, 0, 0, 0, 0, 0);
			g_free(tmp);
			m = m->next;
		}
		gr = gr->next;
	}
	g_string_free(id->str, TRUE);
	id->str = g_string_new("");
}

static gboolean irc_request_buddy_update(gpointer data)
{
	struct gaim_connection *gc = data;
	struct irc_data *id = gc->proto_data;
	char buf[500];
	int n = g_snprintf(buf, sizeof(buf), "ISON");

	GSList *gr = gc->groups;
	if (!gr || id->bc)
		return TRUE;

	while (gr) {
		struct group *g = gr->data;
		GSList *m = g->members;
		while (m) {
			struct buddy *b = m->data;
			if (n + strlen(b->name) + 2 > sizeof(buf)) {
				g_snprintf(buf + n, sizeof(buf) - n, "\r\n");
				irc_write(id->fd, buf, n);
				id->bc++;
				n = g_snprintf(buf, sizeof(buf), "ISON");
			}
			n += g_snprintf(buf + n, sizeof(buf) - n, " %s", b->name);
			m = m->next;
		}
		gr = gr->next;
	}
	g_snprintf(buf + n, sizeof(buf) - n, "\r\n");
	irc_write(id->fd, buf, strlen(buf));
	id->bc++;
	return TRUE;
}

static void handle_names(struct gaim_connection *gc, char *chan, char *names)
{
	struct conversation *c = irc_find_chat(gc, chan);
	char **buf, **tmp;
	if (!c) return;
	if (*names == ':') names++;
	buf = g_strsplit(names, " ", -1);
	for (tmp = buf; *tmp; tmp++)
		add_chat_buddy(c, *tmp, NULL);
	g_strfreev(buf);
}

static void handle_notopic(struct gaim_connection *gc, char *text)
{
	struct conversation *c;
	if ((c = irc_find_chat(gc, text))) {
		char buf[IRC_BUF_LEN];
		g_snprintf(buf, sizeof(buf), _("No topic is set"));
		chat_set_topic(c, NULL, buf);
	}
}

static void handle_topic(struct gaim_connection *gc, char *text)
{
	struct conversation *c;
	char *po = strchr(text, ' ');

	if (!po)
		return;

	*po = 0;
	po += 2;

	if ((c = irc_find_chat(gc, text))) {
		char buf[IRC_BUF_LEN];
		chat_set_topic(c, NULL, po);
		g_snprintf(buf, sizeof(buf), _("<B>%s has changed the topic to: %s</B>"),
			   text, po);
		write_to_conv(c, buf, WFLAG_SYSTEM, NULL, time(NULL), -1);
	}
}

static gboolean mode_has_arg(struct gaim_connection *gc, char sign, char mode)
{
	struct irc_data *id = gc->proto_data;
	char *cm = id->chanmodes;
	int type = 0;

	if (strchr(id->nickmodes, mode))
		return TRUE;

	while (*cm) {
		if (*cm == ',')
			type++;
		else if (*cm == mode) {
			switch (type) {
			case 0:
			case 1:
				return TRUE;
			case 2:
				if (sign == '+')
					return TRUE;
			case 3:
				return FALSE;
			}
		}
		cm++;
	}

	return FALSE;
}

static void irc_chan_mode(struct gaim_connection *gc, char *room, char sign, char mode, char *argstr, char *who)
{
	struct conversation *c = irc_find_chat(gc, room);
	char buf[IRC_BUF_LEN];
	char *nick = g_strndup(who, strchr(who, '!') - who);

	g_snprintf(buf, sizeof(buf), "-:- mode/%s [%c%c %s] by %s", 
		   room, sign, mode, strlen(argstr) ? argstr : "",
		   nick);
	g_free(nick);
	write_to_conv(c, buf, WFLAG_SYSTEM, NULL, time((time_t)NULL), -1);
}

static void irc_user_mode(struct gaim_connection *gc, char *room, char sign, char mode, char *nick)
{
	struct conversation *c = irc_find_chat(gc, room);
	GList *r;

	if (mode != 'o' && mode != 'v')
		return;

	if (!c)
		return;

	r = c->in_room;
	while (r) {
		gboolean op = FALSE, voice = FALSE;
		char *who = r->data;
		if (*who == '@') {
			op = TRUE;
			who++;
		}
		if (*who == '+') {
			voice = TRUE;
			who++;
		}
		if (!strcmp(who, nick)) {
			char *tmp, buf[IRC_BUF_LEN];
			if (mode == 'o') {
				if (sign == '-')
					op = FALSE;
				else
					op = TRUE;
			}
			if (mode == 'v') {
				if (sign == '-')
					voice = FALSE;
				else
					voice = TRUE;
			}
			tmp = g_strdup(r->data);
			g_snprintf(buf, sizeof(buf), "%s%s%s", op ? "@" : "",
				   voice ? "+" : "", nick);
			rename_chat_buddy(c, tmp, buf);
			g_free(tmp);
			return;
		}
		r = r->next;
	}
}

static void handle_mode(struct gaim_connection *gc, char *word[], char *word_eol[], gboolean n324)
{
	struct irc_data *id = gc->proto_data;
	int offset = n324 ? 4 : 3;
	char *chan = word[offset];
	struct conversation *c = irc_find_chat(gc, chan);
	char *modes = word[offset + 1];
	int len = strlen(word_eol[offset]) - 1;
	char sign = *modes++;
	int arg = 1;
	char *argstr;
	char *who = word[1];

	if (!c)
		return;

	if (word_eol[offset][len] == ' ')
		word_eol[offset][len] = 0;

	while (TRUE) {
		switch (*modes) {
		case 0:
			return;
		case '+':
		case '-':
			sign = *modes;
			break;
		default:
			if (mode_has_arg(gc, sign, *modes))
				argstr = word[++arg + offset];
			else
				argstr = "";
			if (strchr(id->nickmodes, *modes))
				irc_user_mode(gc, chan, sign, *modes, argstr);
			else if (strchr(who, '!'))				
				irc_chan_mode(gc, chan, sign, *modes, argstr, who);
		}
		modes++;
	}
}

static void handle_version(struct gaim_connection *gc, char *word[], char *word_eol[], int num)
{
	struct irc_data *id = gc->proto_data;
	GString *str;

	id->liststr = g_string_new("");

	id->liststr = g_string_append(id->liststr, "<b>Version: </b>");
	id->liststr = g_string_append(id->liststr, word_eol[4]);

	str = decode_html(id->liststr->str);
	g_show_info_text(gc, NULL, 2, str->str, NULL);
	g_string_free(str, TRUE);
	g_string_free(id->liststr, TRUE);
	id->liststr = NULL;
}

static void handle_who(struct gaim_connection *gc, char *word[], char *word_eol[], int num)
{
	struct irc_data *id = gc->proto_data;
	char buf[IRC_BUF_LEN];

	if (!id->in_whois) {
		id->in_whois = TRUE;
		id->liststr = g_string_new("");
	}

	switch (num) {
	case 352:
		g_snprintf(buf, sizeof(buf), "<b>%s</b> (%s@%s): %s<br>", 
			   word[8], word[5], word[6], word_eol[11]);
		id->liststr = g_string_append(id->liststr, buf);
		break;
	}
}

/* Handle our whois stuff here.  You know what, I have a sore throat.  You know
 * what I think about that? I'm not too pleased with it.  Perhaps I should take
 * some medicine, or perhaps I should go to bed? Blah!! */

static void handle_whois(struct gaim_connection *gc, char *word[], char *word_eol[], int num)
{
	struct irc_data *id = gc->proto_data;
	char tmp[1024];

	if (!id->in_whois) {
		id->in_whois = TRUE;
		id->liststr = g_string_new("");
	} else {
		/* I can't decide if we should have one break or two */
		id->liststr = g_string_append(id->liststr, "<BR>");
		id->in_whois = TRUE;
	}

	switch (num) {
	case 311:
		id->liststr = g_string_append(id->liststr, "<b>User: </b>");
		break;
	case 312:
		id->liststr = g_string_append(id->liststr, "<b>Server: </b>");
		break;
	case 313:
		g_snprintf(tmp, sizeof(tmp), "<b>IRC Operator:</b> %s ", word[4]);
		id->liststr = g_string_append(id->liststr, tmp);
		break;
	case 314:
		id->liststr = g_string_append(id->liststr, "<b>User: </b>");
		g_snprintf(tmp, sizeof(tmp), "<b>%s</b> (%s@%s) %s",
			   word[4], word[5], word[6], word_eol[8]);
		id->liststr = g_string_append(id->liststr, tmp);
		return;
	case 317:
		id->liststr = g_string_append(id->liststr, "<b>Idle Time: </b>");
		break;
	case 319:
		id->liststr = g_string_append(id->liststr, "<b>Channels: </b>");
		break;
	default:
		break;
	}

	if (word_eol[5][0] == ':')
		id->liststr = g_string_append(id->liststr, word_eol[5] + 1);
	/* Nicer idle time output, by jonas@birme.se */
	else if (isdigit(word_eol[5][0])) {
		time_t idle = atol(word_eol[5]);
		time_t signon = atol(strchr(word_eol[5], ' '));
				
		g_snprintf(tmp, sizeof(tmp), 
			   "%ld seconds [signon: %s]", (idle / 1000), ctime(&signon));
		id->liststr = g_string_append(id->liststr, tmp);
	}
	else
		id->liststr = g_string_append(id->liststr, word_eol[5]);
}

static void handle_roomlist(struct gaim_connection *gc, char *word[], char *word_eol[])
{
	struct irc_data *id = gc->proto_data;

	if (!id->in_list) {
		id->in_list = TRUE;
		id->liststr = g_string_new("");
	} else {
		id->liststr = g_string_append(id->liststr, "<BR>");
		id->in_list = TRUE;
	}

	id->liststr = g_string_append(id->liststr, word_eol[4]);
}

static void irc_change_nick(void *a, char *b) {
	struct gaim_connection *gc = a;
	struct irc_data *id = gc->proto_data;
	char buf[IRC_BUF_LEN];	
	g_snprintf(buf, sizeof(buf), "NICK %s\r\n", b);
	irc_write(id->fd, buf, strlen(buf));
}

static void process_numeric(struct gaim_connection *gc, char *word[], char *word_eol[])
{
	struct irc_data *id = gc->proto_data;
	char *text = word_eol[3];
	int n = atoi(word[2]);

	if (!g_strncasecmp(gc->displayname, text, strlen(gc->displayname)))
		text += strlen(gc->displayname) + 1;
	if (*text == ':')
		text++;

	/* RPL_ and ERR_ */
	switch (n) {
	case 4:
		if (!strncmp(word[5], "u2.10", 5))
			id->six_modes = TRUE;
		else
			id->six_modes = FALSE;
		break;
	case 5:
		handle_005(gc, word, word_eol);
		break;
	case 301: /* RPL_AWAY */
		if (id->in_whois) {
			id->liststr = g_string_append(id->liststr, "<BR><b>Away: </b>");

			if (word_eol[5][0] == ':')
				id->liststr = g_string_append(id->liststr, word_eol[5] + 1);
			else
				id->liststr = g_string_append(id->liststr, word_eol[5]);
		} else
			irc_got_im(gc, word[4], word_eol[5], IM_FLAG_AWAY, time(NULL));
		break;
	case 303: /* RPL_ISON */
		handle_list(gc, &word_eol[4][1]);
		break;
	case 311: /* RPL_WHOISUSER */
	case 312: /* RPL_WHOISSERVER */
	case 313: /* RPL_WHOISOPERATOR */
	case 314: /* RPL_WHOWASUSER */
	case 317: /* RPL_WHOISIDLE */
	case 319: /* RPL_WHOISCHANNELS */
		handle_whois(gc, word, word_eol, n);
		break;
	case 322: /* RPL_LIST */
		handle_roomlist(gc, word, word_eol);
		break;
	case 315: /* RPL_ENDOFWHO */
	case 318: /* RPL_ENDOFWHOIS */
	case 323: /* RPL_LISTEND */
	case 369: /* RPL_ENDOFWHOWAS */
		if ((id->in_whois || id->in_list) && id->liststr) {
			GString *str = decode_html(id->liststr->str);
			g_show_info_text(gc, NULL, 2, str->str, NULL);
			g_string_free(str, TRUE);
			g_string_free(id->liststr, TRUE);
			id->liststr = NULL;
			id->in_whois = FALSE;
			id->in_list = FALSE;
		}
		break;
	case 324: /* RPL_CHANNELMODEIS */
		handle_mode(gc, word, word_eol, TRUE);
		break;
	case 331: /* RPL_NOTOPIC */
		handle_notopic(gc, text);
		break;
	case 332: /* RPL_TOPIC */
		handle_topic(gc, text);
		break;
	case 351: /* RPL_VERSION */
		handle_version(gc, word, word_eol, n);
		break;
	case 352: /* RPL_WHOREPLY */
		handle_who(gc, word, word_eol, n);
		break;
	case 353: /* RPL_NAMREPLY */
		handle_names(gc, word[5], word_eol[6]);
		break;
	case 376: /* RPL_ENDOFMOTD */
		irc_request_buddy_update(gc);
		break;
	case 382: /* RPL_REHASHING */
		do_error_dialog(_("Rehashing server"), _("IRC Operator"), GAIM_ERROR);
		break;
	case 401: /* ERR_NOSUCHNICK */
		do_error_dialog(_("No such nick/channel"), _("IRC Error"), GAIM_ERROR);
		break;
	case 402: /* ERR_NOSUCHSERVER */
		do_error_dialog(_("No such server"), _("IRC Error"), GAIM_ERROR);
	case 431: /* ERR_NONICKNAMEGIVEN */
		do_error_dialog(_("No nickname given"), _("IRC Error"), GAIM_ERROR);
		break;
	case 481: /* ERR_NOPRIVILEGES */
		do_error_dialog(_("You're not an IRC operator!"), _("IRC Error"), GAIM_ERROR);
		break;		
	case 433:
		do_prompt_dialog(_("That nick is already in use.  Please enter a new nick"), gc->displayname, gc, irc_change_nick, NULL);
		break;
	default:
		/* Other error messages */
		if (n > 400 && n < 502) {
			char errmsg[IRC_BUF_LEN];
			char *errmsg1 = strrchr(text, ':');
			g_snprintf(errmsg, sizeof(errmsg), "IRC Error %d", n);
			if (errmsg)
				do_error_dialog(errmsg, errmsg1 ? errmsg1+1 : NULL, GAIM_ERROR);
		}
		break;
	}
}

static gboolean is_channel(struct gaim_connection *gc, char *name)
{
	struct irc_data *id = gc->proto_data;
	if (strchr(id->chantypes, *name))
		return TRUE;
	return FALSE;
}

static void irc_rem_chat_bud(struct gaim_connection *gc, char *nick, char *reason)
{
	GSList *bcs = gc->buddy_chats;

	while (bcs) {
		struct conversation *b = bcs->data;

		GList *r = b->in_room;
		while (r) {
			char *who = r->data;
			if (*who == '@')
				who++;
			if (*who == '+')
				who++;
			if (!g_strcasecmp(who, nick)) {
				char *tmp = g_strdup(r->data);
				remove_chat_buddy(b, tmp, reason);
				g_free(tmp);
				break;
			}
			r = r->next;
		}
		bcs = bcs->next;
	}
}

static void irc_change_name(struct gaim_connection *gc, char *old, char *new)
{
	GSList *bcs = gc->buddy_chats;
	char buf[IRC_BUF_LEN];

	while (bcs) {
		struct conversation *b = bcs->data;

		GList *r = b->in_room;
		while (r) {
			char *who = r->data;
			int n = 0;
			if (*who == '@')
				buf[n++] = *who++;
			if (*who == '+')
				buf[n++] = *who++;
			g_snprintf(buf + n, sizeof(buf) - n, "%s", new);
			if (!strcmp(who, old)) {
				char *tmp = g_strdup(r->data);
				rename_chat_buddy(b, tmp, buf);
				r = b->in_room;
				g_free(tmp);
				break;
			} else
				r = r->next;
		}
		bcs = bcs->next;
	}
}

static void handle_privmsg(struct gaim_connection *gc, char *to, char *nick, char *msg)
{
	if (is_channel(gc, to)) {
		struct conversation *c = irc_find_chat(gc, to);
		if (!c)
			return;
		irc_got_chat_in(gc, c->id, nick, 0, msg, time(NULL));
	} else {
		char *tmp = g_malloc(strlen(nick) + 2);
		g_snprintf(tmp, strlen(nick) + 2, "@%s", nick);
		if (find_conversation(tmp))
			irc_got_im(gc, tmp, msg, 0, time(NULL));
		else {
			*tmp = '+';
			if (find_conversation(tmp))
				irc_got_im(gc, tmp, msg, 0, time(NULL));
			else
				irc_got_im(gc, nick, msg, 0, time(NULL));
		}
		g_free(tmp);
	}
}

static void dcc_chat_init(gpointer obj, struct dcc_chat *data) {
	struct dcc_chat * chat = g_new0(struct dcc_chat, 1);
	
	memcpy(chat, data, sizeof(struct dcc_chat));  /* we have to make a new one
						       * because the old one get's freed by
						       * dcc_chat_cancel. */

	printf("ONE MORE TIME: %s:%d\n", chat->ip_address, chat->port);
	proxy_connect(chat->ip_address, chat->port, dcc_chat_callback, chat);
}

static void dcc_chat_cancel(gpointer obj, struct dcc_chat *data){
	if (find_dcc_chat(data->gc, data->nick)) {
		dcc_chat_list = g_slist_remove(dcc_chat_list, data); 
		gaim_input_remove (data->inpa);
		close (data->fd);
	}
	g_free(data);
}

static void irc_convo_closed(struct gaim_connection *gc, char *who)
{
	struct dcc_chat *dchat = find_dcc_chat(gc, who);
	if (!dchat)
		return;

	dcc_chat_cancel(NULL, dchat);
}

static void handle_ctcp(struct gaim_connection *gc, char *to, char *nick,
			char *msg, char *word[], char *word_eol[])
{
	struct irc_data *id = gc->proto_data;
	char buf[IRC_BUF_LEN];

	if (!g_strncasecmp(msg, "VERSION", 7)) {
		g_snprintf(buf, sizeof(buf), "NOTICE %s :\001VERSION Gaim " VERSION ": The Penguin Pimpin' "
			   "Multi-protocol Messaging Client: " WEBSITE "\001\r\n", nick);
		irc_write(id->fd, buf, strlen(buf));
	}
	if (!g_strncasecmp(msg, "CLIENTINFO", 10)) {
		g_snprintf(buf, sizeof(buf), "NOTICE %s :\001CLIENTINFO USERINFO CLIENTINFO VERSION\001\r\n", nick);
		irc_write(id->fd, buf, strlen(buf));
	}
	if (!g_strncasecmp(msg, "USERINFO", 8)) {
		g_snprintf(buf, sizeof(buf), "NOTICE %s :\001USERINFO Alias: %s\001\r\n", nick, gc->user->alias);
		irc_write(id->fd, buf, strlen(buf));
	}
	if (!g_strncasecmp(msg, "ACTION", 6)) {
		char *po = strchr(msg + 6, 1);
		char *tmp;
		if (po) *po = 0;
		tmp = g_strconcat("/me", msg + 6, NULL);
		handle_privmsg(gc, to, nick, tmp);
		g_free(tmp);
	}
	if (!g_strncasecmp(msg, "DCC CHAT", 8)) {
		char **chat_args = g_strsplit(msg, " ", 5);
		char ask[1024];
		struct dcc_chat *dccchat = g_new0(struct dcc_chat, 1);
		dccchat->gc = gc;	
		g_snprintf(dccchat->ip_address, sizeof(dccchat->ip_address), chat_args[3]);	
		printf("DCC CHAT DEBUG CRAP: %s\n", dccchat->ip_address);
		dccchat->port=atoi(chat_args[4]);		
		g_snprintf(dccchat->nick, sizeof(dccchat->nick), nick);	
		g_snprintf(ask, sizeof(ask), _("%s has requested a DCC chat.  "
					       "Would you like to establish the direct connection?"), nick);
		do_ask_dialog(ask, dccchat, dcc_chat_init, dcc_chat_cancel);
	}


	if (!g_strncasecmp(msg, "DCC SEND", 8)) {
		struct irc_file_transfer *ift = g_new0(struct irc_file_transfer, 1);
		char **send_args = g_strsplit(msg, " ", 6);
		send_args[5][strlen(send_args[5])-1] = 0;

		ift->type = IFT_SENDFILE_IN;
		ift->sn = g_strdup(nick);
		ift->gc = gc;
		g_snprintf(ift->ip, sizeof(ift->ip), send_args[3]);	
		ift->port = atoi(send_args[4]);
		ift->len = atoi(send_args[5]);
		ift->name = g_strdup(send_args[2]);
		ift->cur = 0;

		id->file_transfers = g_slist_append(id->file_transfers, ift);

		ift->xfer = transfer_in_add(gc, nick, ift->name, ift->len, 1, NULL);
	}
	
	/* XXX should probably write_to_conv or something here */
}

static gboolean irc_parse(struct gaim_connection *gc, char *buf)
{
	struct irc_data *idata = gc->proto_data;
	gchar outbuf[IRC_BUF_LEN];
	char *word[PDIWORDS], *word_eol[PDIWORDS];
	char pdibuf[522];
	char *ex, ip[128], nick[128];
	char *cmd;

	/* Check for errors */
	
	if (*buf != ':') {
		if (!strncmp(buf, "NOTICE ", 7))
			buf += 7;
		if (!strncmp(buf, "PING ", 5)) {
			int r = FALSE;
			g_snprintf(outbuf, sizeof(outbuf), "PONG %s\r\n", buf + 5);
			if (irc_write(idata->fd, outbuf, strlen(outbuf)) < 0) {
				hide_login_progress(gc, _("Unable to write"));
				signoff(gc);
				r = TRUE;
			}
			return r;
		}
		/* XXX doesn't handle ERROR */
		return FALSE;
	}

	if (!idata->online) {
		/* Now lets sign ourselves on */
		account_online(gc);
		serv_finish_login(gc);

		if (bud_list_cache_exists(gc))
			do_import(gc, NULL);

		/* we don't call this now because otherwise some IRC servers might not like us */
		idata->timer = g_timeout_add(20000, irc_request_buddy_update, gc);
		idata->online = TRUE;
	}

	buf++;

	process_data_init(pdibuf, buf, word, word_eol, FALSE);

	if (atoi(word[2])) {
		if (*word_eol[3])
			process_numeric(gc, word, word_eol);
		return FALSE;
	}

	cmd = word[2];

	ex = strchr(pdibuf, '!');
	if (!ex) {
		strncpy(ip, pdibuf, sizeof(ip));
		ip[sizeof(ip)-1] = 0;
		strncpy(nick, pdibuf, sizeof(nick));
		nick[sizeof(nick)-1] = 0;
	} else {
		strncpy(ip, ex + 1, sizeof(ip));
		ip[sizeof(ip)-1] = 0;
		strncpy(nick, pdibuf, sizeof(nick));
		nick[sizeof(nick)-1] = 0;
		if ((ex - pdibuf) < sizeof (nick))
			nick[ex - pdibuf] = 0; /* cut the buffer at the '!' */
	}

	if (!strcmp(cmd, "INVITE")) {
		char *chan = g_strdup(word[4]);
		serv_got_chat_invite(gc, chan + 1, nick, NULL, g_list_append(NULL, chan));
	} else if (!strcmp(cmd, "JOIN")) {
		char *chan = *word[3] == ':' ? word[3] + 1 : word[3];
		if (!g_strcasecmp(gc->displayname, nick)) {
			static int id = 1;
			serv_got_joined_chat(gc, id++, chan);
		} else {
			struct conversation *c = irc_find_chat(gc, chan);
			if (c) {
				char *hostmask = g_strdup(word[1]);
				char *p = strchr(hostmask, '!');
				if (p) {
					char *pend = strchr(p, ' ');
					if (pend) {
						*pend = 0;
					}
					add_chat_buddy(c, nick, p+1);
					g_free(hostmask);
				}
			}
		}

	} else if (!strcmp(cmd, "KICK")) {
		if (!strcmp(gc->displayname, word[4])) {
			struct conversation *c = irc_find_chat(gc, word[3]);
			if (!c)
				return FALSE;
			gc->buddy_chats = g_slist_remove(gc->buddy_chats, c);
			c->gc = NULL;
			g_snprintf(outbuf, sizeof(outbuf), _("You have been kicked from %s: %s"),
				   word[3], *word_eol[5] == ':' ? word_eol[5] + 1 : word_eol[5]);
			do_error_dialog(outbuf, _("IRC Error"), GAIM_ERROR);
		} else {
			char *reason = *word_eol[5] == ':' ? word_eol[5] + 1 : word_eol[5];
			char *msg = g_strdup_printf(_("Kicked by %s: %s"), nick, reason);
			irc_rem_chat_bud(gc, word[4], msg);
			g_free(msg);
		}
	} else if (!strcmp(cmd, "KILL")) { /* */
	} else if (!strcmp(cmd, "MODE")) {
		handle_mode(gc, word, word_eol, FALSE);
	} else if (!strcmp(cmd, "NICK")) {
		char *new = *word_eol[3] == ':' ? word_eol[3] + 1 : word_eol[3];
		if (!strcmp(gc->displayname, nick))
			g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", new);
		irc_change_name(gc, nick, new);
	} else if (!strcmp(cmd, "NOTICE")) {
		char buf[IRC_BUF_LEN];
		if (!g_strcasecmp(word[4], ":\001CLIENTINFO")) {
			char *p = strrchr(word_eol[5], '\001');
			*p = 0;
			g_snprintf(buf, sizeof(buf), "CTCP Answer: %s",
				   word_eol[5]);
			do_error_dialog(buf, _("CTCP ClientInfo"), GAIM_INFO);
		} else if (!g_strcasecmp(word[4], ":\001USERINFO")) {
			char *p = strrchr(word_eol[5], '\001');
			*p = 0;
			g_snprintf(buf, sizeof(buf), "CTCP Answer: %s", 
				   word_eol[5]);
			do_error_dialog(buf, _("CTCP UserInfo"), GAIM_INFO);
		} else if (!g_strcasecmp(word[4], ":\001VERSION")) {
			char *p = strrchr(word_eol[5], '\001');
			*p = 0;
			g_snprintf(buf, sizeof(buf), "CTCP Answer: %s", 
				   word_eol[5]);
			do_error_dialog(buf, _("CTCP Version"), GAIM_INFO);
		} else {
			if (*word_eol[4] == ':') word_eol[4]++;
			if (ex)
				irc_got_im(gc, nick, word_eol[4], 0, 
					   time(NULL));		
		}
	} else if (!strcmp(cmd, "PART")) {
		char *chan = cmd + 5;
		struct conversation *c;
		char *reason = word_eol[4];
		GList *r;
		if (*chan == ':')
			chan++;
		if (*reason == ':')
			reason++;
		if (!(c = irc_find_chat(gc, chan)))
			return FALSE;
		if (!strcmp(nick, gc->displayname)) {
			serv_got_chat_left(gc, c->id);
			return FALSE;
		}
		r = c->in_room;
		while (r) {
			char *who = r->data;
			if (*who == '@')
				who++;
			if (*who == '+')
				who++;
			if (!g_strcasecmp(who, nick)) {
				char *tmp = g_strdup(r->data);
				remove_chat_buddy(c, tmp, reason);
				g_free(tmp);
				break;
			}
			r = r->next;
		}
	} else if (!strcmp(cmd, "PRIVMSG")) {
		char *to, *msg;
		if (!*word[3])
			return FALSE;
		to = word[3];
		msg = *word_eol[4] == ':' ? word_eol[4] + 1 : word_eol[4];
		if (msg[0] == 1 && msg[strlen (msg) - 1] == 1) { /* ctcp */
			if (!g_strncasecmp(msg + 1, "DCC ", 4))
				process_data_init(pdibuf, buf, word, word_eol, TRUE);
			handle_ctcp(gc, to, nick, msg + 1, word, word_eol);
		} else {
			handle_privmsg(gc, to, nick, msg);
		}
	} else if (!strcmp(cmd, "PONG")) { /* */
	} else if (!strcmp(cmd, "QUIT")) {
		irc_rem_chat_bud(gc, nick, *word_eol[3] == ':' ? word_eol[3] + 1 : word_eol[3]);
	} else if (!strcmp(cmd, "TOPIC")) {
		struct conversation *c = irc_find_chat(gc, word[3]);
		char *topic = *word_eol[4] == ':' ? word_eol[4] + 1 : word_eol[4];
		if (c) {
			char buf[IRC_BUF_LEN];
			chat_set_topic(c, nick, topic);
			g_snprintf(buf, sizeof(buf), _("<B>%s has changed the topic to: %s</B>"),
				   nick, topic);
			write_to_conv(c, buf, WFLAG_SYSTEM, NULL, time(NULL), -1);
		}
	} else if (!strcmp(cmd, "WALLOPS")) { /* Don't know if a dialog box is the right way? */
		char *msg = strrchr(word_eol[0], ':');
		if (msg)
			do_error_dialog(msg+1, _("IRC Operator"), GAIM_ERROR);
	}

	return FALSE;
}

static void irc_callback(gpointer data, gint source, GaimInputCondition condition)
{
	struct gaim_connection *gc = data;
	struct irc_data *idata = gc->proto_data;
	int i = 0;
	gchar buf[1024];
	gboolean off;

	i = read(idata->fd, buf, 1024);
	if (i <= 0) {
		hide_login_progress_error(gc, "Read error");
		signoff(gc);
		return;
	}

	idata->rxqueue = g_realloc(idata->rxqueue, i + idata->rxlen + 1);
	memcpy(idata->rxqueue + idata->rxlen, buf, i);
	idata->rxlen += i;
	idata->rxqueue[idata->rxlen] = 0;

	while (1) {
		char *d, *e;
		int len;

		if (!idata->rxqueue || ((e = strchr(idata->rxqueue, '\n')) == NULL))
			return;

		len = e - idata->rxqueue + 1;
		d = g_strndup(idata->rxqueue, len);
		g_strchomp(d);
		debug_printf("IRC S: %s\n", d);

		/* REMOVE ME BEFORE SUBMIT! */
		/* fprintf(stderr, "IRC S: %s\n", d); */

		idata->rxlen -= len;
		if (idata->rxlen) {
			char *tmp = g_strdup(e + 1);
			g_free(idata->rxqueue);
			idata->rxqueue = tmp;
		} else {
			g_free(idata->rxqueue);
			idata->rxqueue = NULL;
		}

		off = irc_parse(gc, d);

		g_free(d);

		if (off)
			return;
	}
}

static void irc_login_callback(gpointer data, gint source, GaimInputCondition condition)
{
	struct gaim_connection *gc = data;
	struct irc_data *idata;
	char hostname[256];
	char buf[IRC_BUF_LEN];

	if (!g_slist_find(connections, gc)) {
		close(source);
		return;
	}

	idata = gc->proto_data;

	if (source == -1) {
		hide_login_progress(gc, "Write error");
		signoff(gc);
		return;
	}

	if (idata->fd != source)
		idata->fd = source;

	gethostname(hostname, sizeof(hostname) - 1);
	hostname[sizeof(hostname) - 1] = 0;
	if (!*hostname)
		g_snprintf(hostname, sizeof(hostname), "localhost");
	g_snprintf(buf, sizeof(buf), "USER %s %s %s :%s\r\n",
		   g_get_user_name(), hostname, 
		   gc->user->proto_opt[USEROPT_SERV], 
		   gc->user->alias && strlen(gc->user->alias) ? gc->user->alias : "gaim");
	if (irc_write(idata->fd, buf, strlen(buf)) < 0) {
		hide_login_progress(gc, "Write error");
		signoff(gc);
		return;
	}

	g_snprintf(buf, sizeof(buf), "NICK %s\r\n", gc->username);
	if (irc_write(idata->fd, buf, strlen(buf)) < 0) {
		hide_login_progress(gc, "Write error");
		signoff(gc);
		return;
	}

	gc->inpa = gaim_input_add(idata->fd, GAIM_INPUT_READ, irc_callback, gc);
}

static void irc_login(struct aim_user *user)
{
	char buf[IRC_BUF_LEN];

	struct gaim_connection *gc = new_gaim_conn(user);
	struct irc_data *idata = gc->proto_data = g_new0(struct irc_data, 1);

	g_snprintf(gc->displayname, sizeof(gc->displayname), "%s", gc->username);

	g_snprintf(buf, sizeof(buf), "Signon: %s", gc->username);
	set_login_progress(gc, 2, buf);

	idata->chantypes = g_strdup("#&!+");
	idata->chanmodes = g_strdup("beI,k,lnt");
	idata->nickmodes = g_strdup("ohv");
	idata->str = g_string_new("");

	idata->fd = proxy_connect(user->proto_opt[USEROPT_SERV],
				  user->proto_opt[USEROPT_PORT][0] ? atoi(user->
									  proto_opt[USEROPT_PORT]) :
				  6667, irc_login_callback, gc);
	if (!user->gc || (idata->fd < 0)) {
		hide_login_progress(gc, "Unable to create socket");
		signoff(gc);
		return;
	}
}

static void irc_close(struct gaim_connection *gc)
{
	struct irc_data *idata = (struct irc_data *)gc->proto_data;

	gchar buf[IRC_BUF_LEN];

	if (idata->str->len > 0) {
		g_snprintf(buf, sizeof(buf), "QUIT :%s\r\n", idata->str->str);
	} else {
		g_snprintf(buf, sizeof(buf), 
			   "QUIT :Download Gaim [%s]\r\n", WEBSITE);
	}
	irc_write(idata->fd, buf, strlen(buf));

	if (idata->rxqueue)
		g_free(idata->rxqueue);

	idata->rxqueue = NULL;
	idata->rxlen = 0;

	/* Kill any existing transfers */
	while (idata->file_transfers) {
		struct irc_file_transfer *ift = (struct irc_file_transfer *)idata->file_transfers->data;

		g_free(ift->sn);
		g_free(ift->name);
		gaim_input_remove(ift->watcher);

		close(ift->fd);

		idata->file_transfers = idata->file_transfers->next;
	}
	idata->file_transfers = NULL;


	g_free(idata->chantypes);
	g_free(idata->chanmodes);
	g_free(idata->nickmodes);

	g_string_free(idata->str, TRUE);
	if (idata->liststr)
		g_string_free(idata->liststr, TRUE);

	if (idata->timer)
		g_source_remove(idata->timer);

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

	close(idata->fd);
	g_free(gc->proto_data);
}

static void set_mode_3(struct gaim_connection *gc, char *who, int sign, int mode,
		       int start, int end, char *word[])
{
	struct irc_data *id = gc->proto_data;
	char buf[IRC_BUF_LEN];
	int left;
	int i = start;

	while (1) {
		left = end - i;
		switch (left) {
		case 0:
			return;
		case 1:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c %s\r\n",
				   who, sign, mode, word[i]);
			i += 1;
			break;
		case 2:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c %s %s\r\n",
				   who, sign, mode, mode, word[i], word[i + 1]);
			i += 2;
			break;
		default:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c %s %s %s\r\n",
				   who, sign, mode, mode, mode,
				   word[i], word[i + 1], word[i + 2]);
			i += 2;
			break;
		}
		irc_write(id->fd, buf, strlen(buf));
		if (left < 3)
			return;
	}
}

static void set_mode_6(struct gaim_connection *gc, char *who, int sign, int mode,
		       int start, int end, char *word[])
{
	struct irc_data *id = gc->proto_data;
	char buf[IRC_BUF_LEN];
	int left;
	int i = start;

	while (1) {
		left = end - i;
		switch (left) {
		case 0:
			return;
		case 1:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c %s\r\n",
				   who, sign, mode, word[i]);
			i += 1;
			break;
		case 2:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c %s %s\r\n",
				   who, sign, mode, mode, word[i], word[i + 1]);
			i += 2;
			break;
		case 3:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c %s %s %s\r\n",
				   who, sign, mode, mode, mode,
				   word[i], word[i + 1], word[i + 2]);
			i += 3;
			break;
		case 4:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c%c %s %s %s %s\r\n",
				   who, sign, mode, mode, mode, mode,
				   word[i], word[i + 1], word[i + 2], word[i + 3]);
			i += 4;
			break;
		case 5:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c%c%c %s %s %s %s %s\r\n",
				   who, sign, mode, mode, mode, mode, mode,
				   word[i], word[i + 1], word[i + 2],
				   word[i + 3], word[i + 4]);
			i += 5;
			break;
		default:
			g_snprintf(buf, sizeof(buf), "MODE %s %c%c%c%c%c%c%c %s %s %s %s %s %s\r\n",
				   who, sign, mode, mode, mode, mode, mode, mode,
				   word[i], word[i + 1], word[i + 2],
				   word[i + 3], word[i + 4], word[i + 5]);
			i += 6;
			break;
		}
		irc_write(id->fd, buf, strlen(buf));
		if (left < 6)
			return;
	}
}

static void set_mode(struct gaim_connection *gc, char *who, int sign, int mode, char *word[])
{
	struct irc_data *id = gc->proto_data;
	int i = 2;

	while (1) {
		if (!*word[i]) {
			if (i == 2)
				return;
			if (id->six_modes)
				set_mode_6(gc, who, sign, mode, 2, i, word);
			else
				set_mode_3(gc, who, sign, mode, 2, i, word);
			return;
		}
		i++;
	}
}

static void set_chan_mode(struct gaim_connection *gc, char *chan, char *mode_str)
{
	struct irc_data *id = gc->proto_data;
	char buf[IRC_BUF_LEN];

	if ((mode_str[0] == '-') || (mode_str[0] == '+')) {
		g_snprintf(buf, sizeof(buf), "MODE %s %s\r\n", chan, mode_str);
		irc_write(id->fd, buf, strlen(buf));
	}
}

static int handle_command(struct gaim_connection *gc, char *who, char *what)
{
	char buf[IRC_BUF_LEN];
	char pdibuf[IRC_BUF_LEN];
	char *word[PDIWORDS], *word_eol[PDIWORDS];
	char *tmp = g_strdup(what);
	GString *str = encode_html(tmp);
	struct dcc_chat *dccchat = find_dcc_chat(gc, who);
	struct irc_data *id = gc->proto_data;
	g_free(tmp);
	what = str->str;
	if (*what != '/') {
		unsigned int max = 440 - strlen(who);
		char t;
		while (strlen(what) > max) {
			t = what[max];
			what[max] = 0;
			if (dccchat) {
				g_snprintf(buf, sizeof(buf), "%s\r\n", what);
				irc_write(dccchat->fd, buf, strlen(buf));
				g_string_free(str, TRUE);
				return 1;
			}
			g_snprintf(buf, sizeof(buf), "PRIVMSG %s :%s\r\n", who, what);
			irc_write(id->fd, buf, strlen(buf));
			what[max] = t;
			what = what + max;
		}
		if (dccchat) {
			g_snprintf(buf, sizeof(buf), "%s\r\n", what);
			irc_write(dccchat->fd, buf, strlen(buf));
			g_string_free(str, TRUE);
			return 1;
		}
		g_snprintf(buf, sizeof(buf), "PRIVMSG %s :%s\r\n", who, what);
		irc_write(id->fd, buf, strlen(buf));
		g_string_free(str, TRUE);
		return 1;
	}
	
	process_data_init(pdibuf, what + 1, word, word_eol, TRUE);
	g_string_free(str, FALSE);
	if (!g_strcasecmp(pdibuf, "ME")) {
		if (dccchat) {
			g_snprintf(buf, sizeof(buf), "\001ACTION %s\001\r\n", word_eol[2]);
			irc_write(dccchat->fd, buf, strlen(buf));
			g_free(what);
			return 1;
		}
		g_snprintf(buf, sizeof(buf), "PRIVMSG %s :\001ACTION %s\001\r\n", who, word_eol[2]);
		irc_write(id->fd, buf, strlen(buf));
		g_free(what);
		return 1;
	} else if (!g_strcasecmp(pdibuf, "INVITE")) {
		char buf[IRC_BUF_LEN];
		g_snprintf(buf, sizeof(buf), "INVITE %s\r\n", word_eol[2]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "TOPIC")) {
		if (!*word_eol[2]) {
			struct conversation *c;
			c = irc_find_chat(gc, who);
			g_snprintf(buf, sizeof(buf), _("Topic for %s is %s"), who, c->topic ? c->topic : "(no topic set)");
			write_to_conv(c, buf, WFLAG_SYSTEM | WFLAG_NOLOG, NULL, time(NULL), -1);
		} else {
			g_snprintf(buf, sizeof(buf), "TOPIC %s :%s\r\n", who, word_eol[2]);
			irc_write(id->fd, buf, strlen(buf));
		}
	} else if (!g_strcasecmp(pdibuf, "NICK")) {
		if (!*word_eol[2]) {
			g_free(what);
			return -EINVAL;
		}
		g_snprintf(buf, sizeof(buf), "NICK %s\r\n", word_eol[2]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "OP")) {
		set_mode(gc, who, '+', 'o', word);
	} else if (!g_strcasecmp(pdibuf, "DEOP")) {
		set_mode(gc, who, '-', 'o', word);
	} else if (!g_strcasecmp(pdibuf, "VOICE")) {
		set_mode(gc, who, '+', 'v', word);
	} else if (!g_strcasecmp(pdibuf, "DEVOICE")) {
		set_mode(gc, who, '-', 'v', word);
	} else if (!g_strcasecmp(pdibuf, "MODE")) {
		char *chan = who;
		set_chan_mode(gc, chan, word_eol[2]);
	} else if (!g_strcasecmp(pdibuf, "QUOTE")) {
		if (!*word_eol[2]) {
			g_free(what);
			return -EINVAL;
		}
		g_snprintf(buf, sizeof(buf), "%s\r\n", word_eol[2]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "SAY")) {
		if (!*word_eol[2]) {
			g_free(what);
			return -EINVAL;
		}
		g_snprintf(buf, sizeof(buf), "PRIVMSG %s :%s\r\n", who, word_eol[2]);
		irc_write(id->fd, buf, strlen(buf));
		return 1;
	} else if (!g_strcasecmp(pdibuf, "MSG")) {
		if (!*word[2]) {
			g_free(what);
			return -EINVAL;
		}
		if (!*word_eol[3]) {
			g_free(what);
			return -EINVAL;
		}
		g_snprintf(buf, sizeof(buf), "PRIVMSG %s :%s\r\n", word[2], word_eol[3]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "KICK")) {
		if (!*word[2]) {
			g_free(what);
			return -EINVAL;
		}
		if (*word_eol[3])
			g_snprintf(buf, sizeof(buf), "KICK %s %s :%s\r\n", who, word[2], word_eol[3]);
		else
			g_snprintf(buf, sizeof(buf), "KICK %s %s\r\n", who, word[2]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "JOIN") || !g_strcasecmp(pdibuf, "J")) {
		if (!*word[2]) {
			g_free(what);
			return -EINVAL;
		}
		if (*word[3])
			g_snprintf(buf, sizeof(buf), "JOIN %s %s\r\n", word[2], word[3]);
		else
			g_snprintf(buf, sizeof(buf), "JOIN %s\r\n", word[2]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "PART")) {
		char *chan = *word[2] ? word[2] : who;
		char *reason = word_eol[3];
		struct conversation *c;
		if (!is_channel(gc, chan)) {
			g_free(what);
			return -EINVAL;
		}
		c = irc_find_chat(gc, chan);
		g_snprintf(buf, sizeof(buf), "PART %s%s%s\r\n", chan,
			   *reason ? " :" : "",
			   *reason ? reason : "");
		irc_write(id->fd, buf, strlen(buf));
		if (c) {
			gc->buddy_chats = g_slist_remove(gc->buddy_chats, c);
			c->gc = NULL;
			g_snprintf(buf, sizeof(buf), _("You have left %s"), chan);
			do_error_dialog(buf, _("IRC Part"), GAIM_INFO);
		}
	} else if (!g_strcasecmp(pdibuf, "WHOIS")) {
		g_snprintf(buf, sizeof(buf), "WHOIS %s\r\n", word_eol[2]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "WHOWAS")) {
		g_snprintf(buf, sizeof(buf), "WHOWAS %s\r\n", word_eol[2]);
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "LIST")) {
		g_snprintf(buf, sizeof(buf), "LIST\r\n");
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "QUIT")) {
		char *reason = word_eol[2];
		id->str = g_string_insert(id->str, 0, reason);
		do_quit();
	} else if (!g_strcasecmp(pdibuf, "VERSION")) {
		g_snprintf(buf, sizeof(buf), "VERSION\r\n");
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "W")) {
		g_snprintf(buf, sizeof(buf), "WHO *\r\n");
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "REHASH")) {
		g_snprintf(buf, sizeof(buf), "REHASH\r\n");
		irc_write(id->fd, buf, strlen(buf));		
	} else if (!g_strcasecmp(pdibuf, "RESTART")) {
		g_snprintf(buf, sizeof(buf), "RESTART\r\n");
		irc_write(id->fd, buf, strlen(buf));
	} else if (!g_strcasecmp(pdibuf, "CTCP")) {
		if (!g_strcasecmp(word[2], "CLIENTINFO")) {
			if (word[3])
				irc_ctcp_clientinfo(gc, word[3]);
		} else if (!g_strcasecmp(word[2], "USERINFO")) {
			if (word[3])
				irc_ctcp_userinfo(gc, word[3]);
		} else if (!g_strcasecmp(word[2], "VERSION")) {
			if (word[3])
				irc_ctcp_version(gc, word[3]);
		}
	} else if (!g_strcasecmp(pdibuf, "DCC")) {
		struct conversation *c = NULL;
		if (!g_strcasecmp(word[2], "CHAT")) {
			if (word[3])
				irc_start_chat(gc, word[3]);
			
			if (is_channel(gc, who)) {
				c = irc_find_chat(gc, who);
			} else {
				c = find_conversation(who);
			}
			if (c) {
				write_to_conv(c, "<I>Requesting DCC CHAT</I>",
					      WFLAG_SYSTEM, NULL, 
					      time(NULL), -1);
			}
		}
	} else if (!g_strcasecmp(pdibuf, "HELP")) {
		struct conversation *c = NULL;
		if (is_channel(gc, who)) {
			c = irc_find_chat(gc, who);
		} else {
			c = find_conversation(who);
		}
		if (!c) {
			g_free(what);
			return -EINVAL;
		}
		if (!g_strcasecmp(word[2], "OPER")) {
			write_to_conv(c, "<B>Operator commands:<BR>"
				      "REHASH RESTART</B>",
				      WFLAG_NOLOG, NULL, time(NULL), -1);
		} else if (!g_strcasecmp(word[2], "CTCP")) {
			write_to_conv(c, "<B>CTCP commands:<BR>"
				      "CLIENTINFO <nick><BR>"
				      "USERINFO <nick><BR>"
				      "VERSION <nick></B>",
				      WFLAG_NOLOG, NULL, time(NULL), -1);
		} else if (!g_strcasecmp(word[2], "DCC")) {
			write_to_conv(c, "<B>DCC commands:<BR>"
				      "CHAT <nick></B>",
				      WFLAG_NOLOG, NULL, time(NULL), -1);
		} else {
			write_to_conv(c, "<B>Currently supported commands:<BR>"
				      "WHOIS INVITE NICK LIST<BR>"
				      "JOIN PART TOPIC KICK<BR>"
				      "OP DEOP VOICE DEVOICE<BR>"
				      "ME MSG QUOTE SAY QUIT<BR>"
				      "MODE VERSION W WHOWAS</B><BR>"
				      "Type /HELP OPER for operator commands<BR>"
				      "Type /HELP CTCP for CTCP commands<BR>"
				      "Type /HELP DCC for DCC commands",
				      WFLAG_NOLOG, NULL, time(NULL), -1);
		}
	} else {
		struct conversation *c = NULL;
		if (is_channel(gc, who)) {
			c = irc_find_chat(gc, who);
		} else {
			c = find_conversation(who);
		}
		if (!c) {
			g_free(what);
			return -EINVAL;
		}
		write_to_conv(c, "<B>Unknown command</B>", WFLAG_NOLOG, NULL, time(NULL), -1);
	}
	g_free(what);
	return 0;
}

static int send_msg(struct gaim_connection *gc, char *who, char *what)
{
	char *cr = strchr(what, '\n');
	if (cr) {
		int ret = 0;
		while (TRUE) {
			if (cr)
				*cr = 0;
			ret = handle_command(gc, who, what);
			if (!cr)
				break;
			what = cr + 1;
			if (!*what)
				break;
			*cr = '\n';
			cr = strchr(what, '\n');
		}
		return ret;
	} else
		return handle_command(gc, who, what);
}

static void irc_chat_invite(struct gaim_connection *gc, int idn, const char *message, const char *name) {
	char buf[IRC_BUF_LEN]; 
	struct irc_data *id = gc->proto_data;
	struct conversation *c = irc_find_chat_by_id(gc, idn);
	g_snprintf(buf, sizeof(buf), "INVITE %s %s\r\n", name, c->name);
	irc_write(id->fd, buf, strlen(buf));
}

static int irc_send_im(struct gaim_connection *gc, char *who, char *what, int len, int flags)
{
	if (*who == '@' || *who == '+')
		return send_msg(gc, who + 1, what);
	return send_msg(gc, who, what);
}

/* IRC doesn't have a buddy list, but we can still figure out who's online with ISON */
static void irc_add_buddy(struct gaim_connection *gc, const char *who) {}
static void irc_remove_buddy(struct gaim_connection *gc, char *who, char *group) {}

static GList *irc_chat_info(struct gaim_connection *gc)
{
	GList *m = NULL;
	struct proto_chat_entry *pce;

	pce = g_new0(struct proto_chat_entry, 1);
	pce->label = _("Channel:");
	m = g_list_append(m, pce);

	pce = g_new0(struct proto_chat_entry, 1);
	pce->label = _("Password:");
	m = g_list_append(m, pce);

	return m;
}

static void irc_join_chat(struct gaim_connection *gc, GList *data)
{
	struct irc_data *id = gc->proto_data;
	char buf[IRC_BUF_LEN];
	char *name, *pass;

	if (!data)
		return;
	name = data->data;
	if (data->next) {
		pass = data->next->data;
		g_snprintf(buf, sizeof(buf), "JOIN %s %s\r\n", name, pass);
	} else
		g_snprintf(buf, sizeof(buf), "JOIN %s\r\n", name);
	irc_write(id->fd, buf, strlen(buf));
}

static void irc_chat_leave(struct gaim_connection *gc, int id)
{
	struct irc_data *idata = gc->proto_data;
	struct conversation *c = irc_find_chat_by_id(gc, id);
	char buf[IRC_BUF_LEN];

	if (!c) return;

	g_snprintf(buf, sizeof(buf), "PART %s\r\n", c->name);
	irc_write(idata->fd, buf, strlen(buf));
}

static int irc_chat_send(struct gaim_connection *gc, int id, char *what)
{
	struct conversation *c = irc_find_chat_by_id(gc, id);
	if (!c)
		return -EINVAL;
	if (send_msg(gc, c->name, what) > 0)
		serv_got_chat_in(gc, c->id, gc->displayname, 0, what, time(NULL));
	return 0;
}

static GList *irc_away_states(struct gaim_connection *gc)
{
	return g_list_append(NULL, GAIM_AWAY_CUSTOM);
}

static void irc_set_away(struct gaim_connection *gc, char *state, char *msg)
{
	struct irc_data *idata = gc->proto_data;
	char buf[IRC_BUF_LEN];

	if (gc->away)
		gc->away = NULL;

	if (msg) {
		g_snprintf(buf, sizeof(buf), "AWAY :%s\r\n", msg);
		gc->away = "";
	} else
		g_snprintf(buf, sizeof(buf), "AWAY\r\n");
	irc_write(idata->fd, buf, strlen(buf));
}

static char **irc_list_icon(int uc)
{
	return irc_icon_xpm;
}

static int getlocalip(char *ip) /* Thanks, libfaim */
{
	struct hostent *hptr;
	char localhost[129];
	long unsigned add;
	
	/* XXX if available, use getaddrinfo() */
	/* XXX allow client to specify which IP to use for multihomed boxes */
	
	if (gethostname(localhost, 128) < 0)
		return -1;

	if (!(hptr = gethostbyname(localhost)))
		return -1;
	
	memcpy(&add, hptr->h_addr_list[0], 4);
	add = htonl(add);
	g_snprintf(ip, 11, "%lu", add);

	return 0;
}

static void dcc_chat_connected(gpointer data, gint source, GdkInputCondition condition)
{
	struct dcc_chat *chat = data;
	struct conversation *convo;
	char buf[128];
	struct sockaddr_in addr;
	int addrlen = sizeof (addr);
	addr.sin_family = AF_INET;
	addr.sin_port = htons (chat->port);
	addr.sin_addr.s_addr = INADDR_ANY;
	chat->fd = accept (chat->fd, (struct sockaddr *) (&addr), &addrlen);
	if (!chat->fd) {
		dcc_chat_cancel (NULL,chat);
		convo = new_conversation (chat->nick);
		g_snprintf (buf, sizeof buf, _("DCC Chat with %s closed"),
			    chat->nick);
		write_to_conv (convo, buf, WFLAG_SYSTEM, NULL,
			       time ((time_t) NULL), -1);
		return;
	}
	chat->inpa =
		gaim_input_add (chat->fd, GAIM_INPUT_READ, dcc_chat_in, chat);
	convo = new_conversation (chat->nick);
	g_snprintf (buf, sizeof buf, "DCC Chat with %s established",
		    chat->nick);
	write_to_conv (convo, buf, WFLAG_SYSTEM, NULL, time ((time_t) NULL), -1);
	debug_printf ("Chat with %s established\n", chat->nick);
	dcc_chat_list = g_slist_append (dcc_chat_list, chat);
}
#if 0
static void irc_ask_send_file(struct gaim_connection *gc, char *destsn) {
	struct irc_data *id = (struct irc_data *)gc->proto_data;
	struct irc_file_transfer *ift = g_new0(struct irc_file_transfer, 1);
	char *localip = (char *)malloc(12);

	if (getlocalip(localip) == -1) {
		free(localip);
		return;
	} 

	ift->type = IFT_SENDFILE_OUT;
	ift->sn = g_strdup(destsn);
	ift->gc = gc;
	snprintf(ift->ip, sizeof(ift->ip), "%s", localip);
	id->file_transfers = g_slist_append(id->file_transfers, ift);

	ift->xfer = transfer_out_add(gc, ift->sn);
}
#endif
static struct irc_file_transfer *find_ift_by_xfer(struct gaim_connection *gc, 
						  struct file_transfer *xfer) {
		
	GSList *g = ((struct irc_data *)gc->proto_data)->file_transfers;
	struct irc_file_transfer *f = NULL;

	while (g) {
		f = (struct irc_file_transfer *)g->data;
		if (f->xfer == xfer)
			break;
		g = g->next;
		f = NULL;
	}

	return f;
}

static void irc_file_transfer_data_chunk(struct gaim_connection *gc, struct file_transfer *xfer, const char *data, int len) {
	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);
	guint32 pos;
	
	ift->cur += len;
	pos = htonl(ift->cur);
	write(ift->fd, (char *)&pos, 4);

	// FIXME: You should check to verify that they received the data when
	// you are sending a file ...
}

static void irc_file_transfer_cancel (struct gaim_connection *gc, struct file_transfer *xfer) {
	struct irc_data *id = (struct irc_data *)gc->proto_data;
	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);

	printf("Our shit got canceled, yo!\n");

	/* Remove the FT from our list of transfers */
	id->file_transfers = g_slist_remove(id->file_transfers, ift);

	gaim_input_remove(ift->watcher);

	/* Close our FT because we're done */
	close(ift->fd);

	g_free(ift->sn);
	g_free(ift->name);

	g_free(ift);
}

static void irc_file_transfer_done(struct gaim_connection *gc, struct file_transfer *xfer) {
	struct irc_data *id = (struct irc_data *)gc->proto_data;
	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);


	printf("Our shit be done, yo.\n");
	
	/* Remove the FT from our list of transfers */
	id->file_transfers = g_slist_remove(id->file_transfers, ift);

	gaim_input_remove(ift->watcher);

	/* Close our FT because we're done */
	close(ift->fd);

	g_free(ift->sn);
	g_free(ift->name);

	g_free(ift);
}

static void irc_file_transfer_out (struct gaim_connection *gc, struct file_transfer *xfer, const char *name, int totfiles, int totsize) {
	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);
	struct sockaddr_in addr;
	char buf[IRC_BUF_LEN];
	int len;
	
	
	ift->fd = socket (AF_INET, SOCK_STREAM, 0);
	addr.sin_family = AF_INET;
	addr.sin_port = 0;
	addr.sin_addr.s_addr = INADDR_ANY;
	bind (ift->fd, (struct sockaddr *) &addr, sizeof(addr));
	listen(ift->fd, 1);

	len = sizeof(addr);
	getsockname (ift->fd, (struct sockaddr *) &addr, &len);

	ift->port = ntohs(addr.sin_port);

	ift->watcher = gaim_input_add (ift->fd, GAIM_INPUT_READ, dcc_send_callback, ift);
	printf("watcher is %d\n", ift->watcher);

	snprintf(buf, sizeof(buf), "\001DCC SEND %s %s %d %d\001\n", name, ift->ip, ift->port, totsize);
	printf("Trying: %s\n", buf);
	irc_send_im (gc, ift->sn, buf, -1, 0);
}

static void irc_file_transfer_in(struct gaim_connection *gc,
				 struct file_transfer *xfer, int offset) {

	struct irc_file_transfer *ift = find_ift_by_xfer(gc, xfer);

	ift->xfer = xfer;
	proxy_connect(ift->ip, ift->port, dcc_recv_callback, ift);
}

static void irc_ctcp_clientinfo(struct gaim_connection *gc, char *who)
{
	char buf[200];
	snprintf (buf, sizeof buf, "\001CLIENTINFO\001\n");
	irc_send_im (gc, who, buf, -1, 0);
}

static void irc_ctcp_userinfo(struct gaim_connection *gc, char *who)
{
	char buf[200];
	snprintf (buf, sizeof buf, "\001USERINFO\001\n");
	irc_send_im (gc, who, buf, -1, 0);
}

static void irc_ctcp_version(struct gaim_connection *gc, char *who)
{
	char buf[200];
	snprintf (buf, sizeof buf, "\001VERSION\001\n");
	irc_send_im (gc, who, buf, -1, 0);
}

static void irc_start_chat(struct gaim_connection *gc, char *who) {
	struct dcc_chat *chat;
	int len;
	struct sockaddr_in addr;
	char buf[200];
	
	/* Create a socket */
	chat = g_new0 (struct dcc_chat, 1);
	chat->fd = socket (AF_INET, SOCK_STREAM, 0);
	chat->gc = gc;
	g_snprintf (chat->nick, sizeof (chat->nick), "%s", who);
	if (chat->fd < 0)  {
		dcc_chat_cancel (NULL,chat);
		return;
	}
	addr.sin_family = AF_INET;
	addr.sin_port = 0;
	addr.sin_addr.s_addr = INADDR_ANY;
	bind (chat->fd, (struct sockaddr *) &addr, sizeof (addr));
	listen (chat->fd, 1);
	len = sizeof (addr);
	getsockname (chat->fd, (struct sockaddr *) &addr, &len);
	chat->port = ntohs (addr.sin_port);
	getlocalip(chat->ip_address);
	chat->inpa =
		gaim_input_add (chat->fd, GAIM_INPUT_READ, dcc_chat_connected,
				chat);
	g_snprintf (buf, sizeof buf, "\001DCC CHAT chat %s %d\001\n",
		    chat->ip_address, chat->port);
	irc_send_im (gc, who, buf, -1, 0);
}

static void irc_get_info(struct gaim_connection *gc, char *who)
{
	struct irc_data *idata = gc->proto_data;
	char buf[IRC_BUF_LEN];

	if (*who == '@')
		who++;
	if (*who == '+')
		who++;

	g_snprintf(buf, sizeof(buf), "WHOIS %s\r\n", who);
	irc_write(idata->fd, buf, strlen(buf));
}

static GList *irc_buddy_menu(struct gaim_connection *gc, char *who)
{
	GList *m = NULL;
	struct proto_buddy_menu *pbm;

	pbm = g_new0(struct proto_buddy_menu, 1);
	pbm->label = _("Get Info");
	pbm->callback = irc_get_info;
	pbm->gc = gc;
	m = g_list_append(m, pbm);

	pbm = g_new0(struct proto_buddy_menu, 1);
	pbm->label = _("DCC Chat");
	pbm->callback = irc_start_chat;
	pbm->gc = gc;
	m = g_list_append(m, pbm);
	/*
	  pbm = g_new0(struct proto_buddy_menu, 1);
	  pbm->label = _("DCC Send");
	  pbm->callback = irc_ask_send_file;
	  pbm->gc = gc;
	  m = g_list_append(m, pbm);
	*/
	
	pbm = g_new0(struct proto_buddy_menu, 1);
	pbm->label = _("CTCP ClientInfo");
	pbm->callback = irc_ctcp_clientinfo;
	pbm->gc = gc;
	m = g_list_append(m, pbm);
	
	pbm = g_new0(struct proto_buddy_menu, 1);
	pbm->label = _("CTCP UserInfo");
	pbm->callback = irc_ctcp_userinfo;
	pbm->gc = gc;
	m = g_list_append(m, pbm);

	pbm = g_new0(struct proto_buddy_menu, 1);
	pbm->label = _("CTCP Version");
	pbm->callback = irc_ctcp_version;
	pbm->gc = gc;
	m = g_list_append(m, pbm);

	return m;
}

static struct prpl *my_protocol = NULL;

G_MODULE_EXPORT void irc_init(struct prpl *ret)
{
	struct proto_user_opt *puo;
	ret->protocol = PROTO_IRC;
	ret->options = OPT_PROTO_CHAT_TOPIC | OPT_PROTO_NO_PASSWORD;
	ret->name = g_strdup("IRC");
	ret->list_icon = irc_list_icon;
	ret->login = irc_login;
	ret->close = irc_close;
	ret->send_im = irc_send_im;
	ret->add_buddy = irc_add_buddy;
	ret->remove_buddy = irc_remove_buddy;
	ret->chat_info = irc_chat_info;
	ret->join_chat = irc_join_chat;
	ret->chat_leave = irc_chat_leave;
	ret->chat_send = irc_chat_send;
	ret->away_states = irc_away_states;
	ret->set_away = irc_set_away;
	ret->get_info = irc_get_info;
	ret->buddy_menu = irc_buddy_menu;
	ret->chat_invite = irc_chat_invite;
	ret->convo_closed = irc_convo_closed;
	ret->file_transfer_out = irc_file_transfer_out; 
	ret->file_transfer_in = irc_file_transfer_in;
	ret->file_transfer_data_chunk = irc_file_transfer_data_chunk;
	ret->file_transfer_done = irc_file_transfer_done;
	ret->file_transfer_cancel =irc_file_transfer_cancel;

	puo = g_new0(struct proto_user_opt, 1);
	puo->label = g_strdup("Server:");
	puo->def = g_strdup("irc.openprojects.net");
	puo->pos = USEROPT_SERV;
	ret->user_opts = g_list_append(ret->user_opts, puo);

	puo = g_new0(struct proto_user_opt, 1);
	puo->label = g_strdup("Port:");
	puo->def = g_strdup("6667");
	puo->pos = USEROPT_PORT;
	ret->user_opts = g_list_append(ret->user_opts, puo);

	my_protocol = ret;
}

#ifndef STATIC
G_MODULE_EXPORT void gaim_prpl_init(struct prpl* prpl)
{
	irc_init(prpl);
	prpl->plug->desc.api_version = PLUGIN_API_VERSION;
}

#endif