diff libpurple/protocols/irc/irc.c @ 15374:5fe8042783c1

Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author Sean Egan <seanegan@gmail.com>
date Sat, 20 Jan 2007 02:32:10 +0000
parents
children 0b6f337a46d5
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/irc/irc.c	Sat Jan 20 02:32:10 2007 +0000
@@ -0,0 +1,954 @@
+/**
+ * @file irc.c
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Robbert Haarman <gaim@inglorion.net>
+ * Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ * Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
+ * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "internal.h"
+
+#include "accountopt.h"
+#include "blist.h"
+#include "conversation.h"
+#include "debug.h"
+#include "notify.h"
+#include "prpl.h"
+#include "plugin.h"
+#include "util.h"
+#include "version.h"
+
+#include "irc.h"
+
+#define PING_TIMEOUT 60
+
+static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string);
+
+static const char *irc_blist_icon(GaimAccount *a, GaimBuddy *b);
+static void irc_blist_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne);
+static GList *irc_status_types(GaimAccount *account);
+static GList *irc_actions(GaimPlugin *plugin, gpointer context);
+/* static GList *irc_chat_info(GaimConnection *gc); */
+static void irc_login(GaimAccount *account);
+static void irc_login_cb_ssl(gpointer data, GaimSslConnection *gsc, GaimInputCondition cond);
+static void irc_login_cb(gpointer data, gint source, const gchar *error_message);
+static void irc_ssl_connect_failure(GaimSslConnection *gsc, GaimSslErrorType error, gpointer data);
+static void irc_close(GaimConnection *gc);
+static int irc_im_send(GaimConnection *gc, const char *who, const char *what, GaimMessageFlags flags);
+static int irc_chat_send(GaimConnection *gc, int id, const char *what, GaimMessageFlags flags);
+static void irc_chat_join (GaimConnection *gc, GHashTable *data);
+static void irc_input_cb(gpointer data, gint source, GaimInputCondition cond);
+static void irc_input_cb_ssl(gpointer data, GaimSslConnection *gsc, GaimInputCondition cond);
+
+static guint irc_nick_hash(const char *nick);
+static gboolean irc_nick_equal(const char *nick1, const char *nick2);
+static void irc_buddy_free(struct irc_buddy *ib);
+
+GaimPlugin *_irc_plugin = NULL;
+
+static const char *status_chars = "@+%&";
+
+static void irc_view_motd(GaimPluginAction *action)
+{
+	GaimConnection *gc = (GaimConnection *) action->context;
+	struct irc_conn *irc;
+	char *title;
+
+	if (gc == NULL || gc->proto_data == NULL) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "got MOTD request for NULL gc\n");
+		return;
+	}
+	irc = gc->proto_data;
+	if (irc->motd == NULL) {
+		gaim_notify_error(gc, _("Error displaying MOTD"), _("No MOTD available"),
+				  _("There is no MOTD associated with this connection."));
+		return;
+	}
+	title = g_strdup_printf(_("MOTD for %s"), irc->server);
+	gaim_notify_formatted(gc, title, title, NULL, irc->motd->str, NULL, NULL);
+	g_free(title);
+}
+
+static int do_send(struct irc_conn *irc, const char *buf, gsize len)
+{
+	int ret;
+
+	if (irc->gsc) {
+		ret = gaim_ssl_write(irc->gsc, buf, len);
+	} else {
+		ret = write(irc->fd, buf, len);
+	}
+
+	return ret;
+}
+
+static int irc_send_raw(GaimConnection *gc, const char *buf, int len)
+{
+	struct irc_conn *irc = (struct irc_conn*)gc->proto_data;
+	return do_send(irc, buf, len);
+}
+
+static void
+irc_send_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct irc_conn *irc = data;
+	int ret, writelen;
+
+	writelen = gaim_circ_buffer_get_max_read(irc->outbuf);
+
+	if (writelen == 0) {
+		gaim_input_remove(irc->writeh);
+		irc->writeh = 0;
+		return;
+	}
+
+	ret = do_send(irc, irc->outbuf->outptr, writelen);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		gaim_connection_error(gaim_account_get_connection(irc->account),
+			      _("Server has disconnected"));
+		return;
+	}
+
+	gaim_circ_buffer_mark_read(irc->outbuf, ret);
+
+#if 0
+	/* We *could* try to write more if we wrote it all */
+	if (ret == write_len) {
+		irc_send_cb(data, source, cond);
+	}
+#endif
+}
+
+int irc_send(struct irc_conn *irc, const char *buf)
+{
+	int ret, buflen;
+ 	char *tosend= g_strdup(buf);
+
+	gaim_signal_emit(_irc_plugin, "irc-sending-text", gaim_account_get_connection(irc->account), &tosend);
+	if (tosend == NULL)
+		return 0;
+	
+	buflen = strlen(tosend);
+	
+	
+	/* If we're not buffering writes, try to send immediately */
+	if (!irc->writeh)
+		ret = do_send(irc, tosend, buflen);
+	else {
+		ret = -1;
+		errno = EAGAIN;
+	}
+
+	/* gaim_debug(GAIM_DEBUG_MISC, "irc", "sent%s: %s",
+		irc->gsc ? " (ssl)" : "", tosend); */
+	if (ret <= 0 && errno != EAGAIN) {
+		gaim_connection_error(gaim_account_get_connection(irc->account),
+				      _("Server has disconnected"));
+	} else if (ret < buflen) {
+		if (ret < 0)
+			ret = 0;
+		if (!irc->writeh)
+			irc->writeh = gaim_input_add(
+				irc->gsc ? irc->gsc->fd : irc->fd,
+				GAIM_INPUT_WRITE, irc_send_cb, irc);
+		gaim_circ_buffer_append(irc->outbuf, tosend + ret,
+			buflen - ret);
+	}
+	g_free(tosend);
+	return ret;
+}
+
+/* XXX I don't like messing directly with these buddies */
+gboolean irc_blist_timeout(struct irc_conn *irc)
+{
+	GString *string = g_string_sized_new(512);
+	char *list, *buf;
+
+	g_hash_table_foreach(irc->buddies, (GHFunc)irc_buddy_append, (gpointer)string);
+
+	list = g_string_free(string, FALSE);
+	if (!list || !strlen(list)) {
+		g_free(list);
+		return TRUE;
+	}
+
+	buf = irc_format(irc, "vn", "ISON", list);
+	g_free(list);
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return TRUE;
+}
+
+static void irc_buddy_append(char *name, struct irc_buddy *ib, GString *string)
+{
+	ib->flag = FALSE;
+	g_string_append_printf(string, "%s ", name);
+}
+
+static void irc_ison_one(struct irc_conn *irc, struct irc_buddy *ib)
+{
+	char *buf;
+
+	ib->flag = FALSE;
+	buf = irc_format(irc, "vn", "ISON", ib->name);
+	irc_send(irc, buf);
+	g_free(buf);
+}
+
+
+static const char *irc_blist_icon(GaimAccount *a, GaimBuddy *b)
+{
+	return "irc";
+}
+
+static void irc_blist_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne)
+{
+	GaimPresence *presence = gaim_buddy_get_presence(b);
+
+	if (gaim_presence_is_online(presence) == FALSE) {
+		*se = "offline";
+	}
+}
+
+static GList *irc_status_types(GaimAccount *account)
+{
+	GaimStatusType *type;
+	GList *types = NULL;
+
+	type = gaim_status_type_new(GAIM_STATUS_AVAILABLE, NULL, NULL, TRUE);
+	types = g_list_append(types, type);
+
+	type = gaim_status_type_new_with_attrs(
+		GAIM_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
+		"message", _("Message"), gaim_value_new(GAIM_TYPE_STRING),
+		NULL);
+	types = g_list_append(types, type);
+
+	type = gaim_status_type_new(GAIM_STATUS_OFFLINE, NULL, NULL, TRUE);
+	types = g_list_append(types, type);
+
+	return types;
+}
+
+static GList *irc_actions(GaimPlugin *plugin, gpointer context)
+{
+	GList *list = NULL;
+	GaimPluginAction *act = NULL;
+
+	act = gaim_plugin_action_new(_("View MOTD"), irc_view_motd);
+	list = g_list_append(list, act);
+
+	return list;
+}
+
+static GList *irc_chat_join_info(GaimConnection *gc)
+{
+	GList *m = NULL;
+	struct proto_chat_entry *pce;
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Channel:");
+	pce->identifier = "channel";
+	pce->required = TRUE;
+	m = g_list_append(m, pce);
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Password:");
+	pce->identifier = "password";
+	pce->secret = TRUE;
+	m = g_list_append(m, pce);
+
+	return m;
+}
+
+static GHashTable *irc_chat_info_defaults(GaimConnection *gc, const char *chat_name)
+{
+	GHashTable *defaults;
+
+	defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+	if (chat_name != NULL)
+		g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
+
+	return defaults;
+}
+
+static void irc_login(GaimAccount *account)
+{
+	GaimConnection *gc;
+	struct irc_conn *irc;
+	char **userparts;
+	const char *username = gaim_account_get_username(account);
+
+	gc = gaim_account_get_connection(account);
+	gc->flags |= GAIM_CONNECTION_NO_NEWLINES;
+
+	if (strpbrk(username, " \t\v\r\n") != NULL) {
+		gaim_connection_error(gc, _("IRC nicks may not contain whitespace"));
+		return;
+	}
+
+	gc->proto_data = irc = g_new0(struct irc_conn, 1);
+	irc->fd = -1;
+	irc->account = account;
+	irc->outbuf = gaim_circ_buffer_new(512);
+
+	userparts = g_strsplit(username, "@", 2);
+	gaim_connection_set_display_name(gc, userparts[0]);
+	irc->server = g_strdup(userparts[1]);
+	g_strfreev(userparts);
+
+	irc->buddies = g_hash_table_new_full((GHashFunc)irc_nick_hash, (GEqualFunc)irc_nick_equal,
+					     NULL, (GDestroyNotify)irc_buddy_free);
+	irc->cmds = g_hash_table_new(g_str_hash, g_str_equal);
+	irc_cmd_table_build(irc);
+	irc->msgs = g_hash_table_new(g_str_hash, g_str_equal);
+	irc_msg_table_build(irc);
+
+	gaim_connection_update_progress(gc, _("Connecting"), 1, 2);
+
+	if (gaim_account_get_bool(account, "ssl", FALSE)) {
+		if (gaim_ssl_is_supported()) {
+			irc->gsc = gaim_ssl_connect(account, irc->server,
+					gaim_account_get_int(account, "port", IRC_DEFAULT_SSL_PORT),
+					irc_login_cb_ssl, irc_ssl_connect_failure, gc);
+		} else {
+			gaim_connection_error(gc, _("SSL support unavailable"));
+			return;
+		}
+	}
+
+	if (!irc->gsc) {
+
+		if (gaim_proxy_connect(gc, account, irc->server,
+				 gaim_account_get_int(account, "port", IRC_DEFAULT_PORT),
+				 irc_login_cb, gc) == NULL)
+		{
+			gaim_connection_error(gc, _("Couldn't create socket"));
+			return;
+		}
+	}
+}
+
+static gboolean do_login(GaimConnection *gc) {
+	char *buf;
+	char hostname[256];
+	const char *username, *realname;
+	struct irc_conn *irc = gc->proto_data;
+	const char *pass = gaim_connection_get_password(gc);
+
+	if (pass && *pass) {
+		buf = irc_format(irc, "vv", "PASS", pass);
+		if (irc_send(irc, buf) < 0) {
+/*			gaim_connection_error(gc, "Error sending password"); */
+			g_free(buf);
+			return FALSE;
+		}
+		g_free(buf);
+	}
+
+	gethostname(hostname, sizeof(hostname));
+	hostname[sizeof(hostname) - 1] = '\0';
+	username = gaim_account_get_string(irc->account, "username", "");
+	realname = gaim_account_get_string(irc->account, "realname", "");
+	buf = irc_format(irc, "vvvv:", "USER", strlen(username) ? username : g_get_user_name(), hostname, irc->server,
+			      strlen(realname) ? realname : IRC_DEFAULT_ALIAS);
+	if (irc_send(irc, buf) < 0) {
+/*		gaim_connection_error(gc, "Error registering with server");*/
+		g_free(buf);
+		return FALSE;
+	}
+	g_free(buf);
+	buf = irc_format(irc, "vn", "NICK", gaim_connection_get_display_name(gc));
+	if (irc_send(irc, buf) < 0) {
+/*		gaim_connection_error(gc, "Error sending nickname");*/
+		g_free(buf);
+		return FALSE;
+	}
+	g_free(buf);
+
+	irc->recv_time = time(NULL);
+
+	return TRUE;
+}
+
+static void irc_login_cb_ssl(gpointer data, GaimSslConnection *gsc,
+	GaimInputCondition cond)
+{
+	GaimConnection *gc = data;
+
+	if (do_login(gc)) {
+		gaim_ssl_input_add(gsc, irc_input_cb_ssl, gc);
+	}
+}
+
+static void irc_login_cb(gpointer data, gint source, const gchar *error_message)
+{
+	GaimConnection *gc = data;
+	struct irc_conn *irc = gc->proto_data;
+
+	if (source < 0) {
+		gaim_connection_error(gc, _("Couldn't connect to host"));
+		return;
+	}
+
+	irc->fd = source;
+
+	if (do_login(gc)) {
+		gc->inpa = gaim_input_add(irc->fd, GAIM_INPUT_READ, irc_input_cb, gc);
+	}
+}
+
+static void
+irc_ssl_connect_failure(GaimSslConnection *gsc, GaimSslErrorType error,
+		gpointer data)
+{
+	GaimConnection *gc = data;
+	struct irc_conn *irc = gc->proto_data;
+
+	irc->gsc = NULL;
+
+	switch(error) {
+		case GAIM_SSL_CONNECT_FAILED:
+			gaim_connection_error(gc, _("Connection Failed"));
+			break;
+		case GAIM_SSL_HANDSHAKE_FAILED:
+			gaim_connection_error(gc, _("SSL Handshake Failed"));
+			break;
+	}
+}
+
+static void irc_close(GaimConnection *gc)
+{
+	struct irc_conn *irc = gc->proto_data;
+
+	if (irc == NULL)
+		return;
+
+	if (irc->gsc || (irc->fd >= 0))
+		irc_cmd_quit(irc, "quit", NULL, NULL);
+
+	if (gc->inpa)
+		gaim_input_remove(gc->inpa);
+
+	g_free(irc->inbuf);
+	if (irc->gsc) {
+		gaim_ssl_close(irc->gsc);
+	} else if (irc->fd >= 0) {
+		close(irc->fd);
+	}
+	if (irc->timer)
+		gaim_timeout_remove(irc->timer);
+	g_hash_table_destroy(irc->cmds);
+	g_hash_table_destroy(irc->msgs);
+	g_hash_table_destroy(irc->buddies);
+	if (irc->motd)
+		g_string_free(irc->motd, TRUE);
+	g_free(irc->server);
+
+	if (irc->writeh)
+		gaim_input_remove(irc->writeh);
+
+	gaim_circ_buffer_destroy(irc->outbuf);
+
+	g_free(irc);
+}
+
+static int irc_im_send(GaimConnection *gc, const char *who, const char *what, GaimMessageFlags flags)
+{
+	struct irc_conn *irc = gc->proto_data;
+	char *plain;
+	const char *args[2];
+
+	if (strchr(status_chars, *who) != NULL)
+		args[0] = who + 1;
+	else
+		args[0] = who;
+
+	plain = gaim_unescape_html(what);
+	args[1] = plain;
+
+	irc_cmd_privmsg(irc, "msg", NULL, args);
+	g_free(plain);
+	return 1;
+}
+
+static void irc_get_info(GaimConnection *gc, const char *who)
+{
+	struct irc_conn *irc = gc->proto_data;
+	const char *args[2];
+	args[0] = who;
+	args[1] = NULL;
+	irc_cmd_whois(irc, "whois", NULL, args);
+}
+
+static void irc_set_status(GaimAccount *account, GaimStatus *status)
+{
+	GaimConnection *gc = gaim_account_get_connection(account);
+	struct irc_conn *irc;
+	const char *args[1];
+	const char *status_id = gaim_status_get_id(status);
+
+	g_return_if_fail(gc != NULL);
+	irc = gc->proto_data;
+
+	if (!gaim_status_is_active(status))
+		return;
+
+	args[0] = NULL;
+
+	if (!strcmp(status_id, "away")) {
+		args[0] = gaim_status_get_attr_string(status, "message");
+		if ((args[0] == NULL) || (*args[0] == '\0'))
+			args[0] = _("Away");
+		irc_cmd_away(irc, "away", NULL, args);
+	} else if (!strcmp(status_id, "available")) {
+		irc_cmd_away(irc, "back", NULL, args);
+	}
+}
+
+static void irc_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
+	struct irc_buddy *ib = g_new0(struct irc_buddy, 1);
+	ib->name = g_strdup(buddy->name);
+	g_hash_table_insert(irc->buddies, ib->name, ib);
+
+	/* if the timer isn't set, this is during signon, so we don't want to flood
+	 * ourself off with ISON's, so we don't, but after that we want to know when
+	 * someone's online asap */
+	if (irc->timer)
+		irc_ison_one(irc, ib);
+}
+
+static void irc_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+	struct irc_conn *irc = (struct irc_conn *)gc->proto_data;
+	g_hash_table_remove(irc->buddies, buddy->name);
+}
+
+static void read_input(struct irc_conn *irc, int len)
+{
+	char *cur, *end;
+
+	irc->inbufused += len;
+	irc->inbuf[irc->inbufused] = '\0';
+
+	cur = irc->inbuf;
+	
+	/* This is a hack to work around the fact that marv gets messages
+	 * with null bytes in them while using some weird irc server at work
+	 */
+	while ((cur < (irc->inbuf + irc->inbufused)) && !*cur)
+		cur++;
+	
+	while (cur < irc->inbuf + irc->inbufused &&
+	       ((end = strstr(cur, "\r\n")) || (end = strstr(cur, "\n")))) {
+		int step = (*end == '\r' ? 2 : 1);
+		*end = '\0';
+		irc_parse_msg(irc, cur);
+		cur = end + step;
+	}
+	if (cur != irc->inbuf + irc->inbufused) { /* leftover */
+		irc->inbufused -= (cur - irc->inbuf);
+		memmove(irc->inbuf, cur, irc->inbufused);
+	} else {
+		irc->inbufused = 0;
+	}
+}
+
+static void irc_input_cb_ssl(gpointer data, GaimSslConnection *gsc,
+		GaimInputCondition cond)
+{
+
+	GaimConnection *gc = data;
+	struct irc_conn *irc = gc->proto_data;
+	int len;
+
+	if(!g_list_find(gaim_connections_get_all(), gc)) {
+		gaim_ssl_close(gsc);
+		return;
+	}
+
+	if (irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE) {
+		irc->inbuflen += IRC_INITIAL_BUFSIZE;
+		irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
+	}
+
+	len = gaim_ssl_read(gsc, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1);
+
+	if (len < 0 && errno == EAGAIN) {
+		/* Try again later */
+		return;
+	} else if (len < 0) {
+		gaim_connection_error(gc, _("Read error"));
+		return;
+	} else if (len == 0) {
+		gaim_connection_error(gc, _("Server has disconnected"));
+		return;
+	}
+
+	read_input(irc, len);
+}
+
+static void irc_input_cb(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimConnection *gc = data;
+	struct irc_conn *irc = gc->proto_data;
+	int len;
+
+	if (irc->inbuflen < irc->inbufused + IRC_INITIAL_BUFSIZE) {
+		irc->inbuflen += IRC_INITIAL_BUFSIZE;
+		irc->inbuf = g_realloc(irc->inbuf, irc->inbuflen);
+	}
+
+	len = read(irc->fd, irc->inbuf + irc->inbufused, IRC_INITIAL_BUFSIZE - 1);
+	if (len < 0 && errno == EAGAIN) {
+		return;
+	} else if (len < 0) {
+		gaim_connection_error(gc, _("Read error"));
+		return;
+	} else if (len == 0) {
+		gaim_connection_error(gc, _("Server has disconnected"));
+		return;
+	}
+
+	read_input(irc, len);
+}
+
+static void irc_chat_join (GaimConnection *gc, GHashTable *data)
+{
+	struct irc_conn *irc = gc->proto_data;
+	const char *args[2];
+
+	args[0] = g_hash_table_lookup(data, "channel");
+	args[1] = g_hash_table_lookup(data, "password");
+	irc_cmd_join(irc, "join", NULL, args);
+}
+
+static char *irc_get_chat_name(GHashTable *data) {
+	return g_strdup(g_hash_table_lookup(data, "channel"));
+}
+
+static void irc_chat_invite(GaimConnection *gc, int id, const char *message, const char *name) 
+{
+	struct irc_conn *irc = gc->proto_data;
+	GaimConversation *convo = gaim_find_chat(gc, id);
+	const char *args[2];
+
+	if (!convo) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "Got chat invite request for bogus chat\n");
+		return;
+	}
+	args[0] = name;
+	args[1] = gaim_conversation_get_name(convo);
+	irc_cmd_invite(irc, "invite", gaim_conversation_get_name(convo), args);
+}
+
+
+static void irc_chat_leave (GaimConnection *gc, int id)
+{
+	struct irc_conn *irc = gc->proto_data;
+	GaimConversation *convo = gaim_find_chat(gc, id);
+	const char *args[2];
+
+	if (!convo)
+		return;
+
+	args[0] = gaim_conversation_get_name(convo);
+	args[1] = NULL;
+	irc_cmd_part(irc, "part", gaim_conversation_get_name(convo), args);
+	serv_got_chat_left(gc, id);
+}
+
+static int irc_chat_send(GaimConnection *gc, int id, const char *what, GaimMessageFlags flags)
+{
+	struct irc_conn *irc = gc->proto_data;
+	GaimConversation *convo = gaim_find_chat(gc, id);
+	const char *args[2];
+	char *tmp;
+
+	if (!convo) {
+		gaim_debug(GAIM_DEBUG_ERROR, "irc", "chat send on nonexistent chat\n");
+		return -EINVAL;
+	}
+#if 0
+	if (*what == '/') {
+		return irc_parse_cmd(irc, convo->name, what + 1);
+	}
+#endif
+	tmp = gaim_unescape_html(what);
+	args[0] = convo->name;
+	args[1] = tmp;
+
+	irc_cmd_privmsg(irc, "msg", NULL, args);
+
+	serv_got_chat_in(gc, id, gaim_connection_get_display_name(gc), 0, what, time(NULL));
+	g_free(tmp);
+	return 0;
+}
+
+static guint irc_nick_hash(const char *nick)
+{
+	char *lc;
+	guint bucket;
+
+	lc = g_utf8_strdown(nick, -1);
+	bucket = g_str_hash(lc);
+	g_free(lc);
+
+	return bucket;
+}
+
+static gboolean irc_nick_equal(const char *nick1, const char *nick2)
+{
+	return (gaim_utf8_strcasecmp(nick1, nick2) == 0);
+}
+
+static void irc_buddy_free(struct irc_buddy *ib)
+{
+	g_free(ib->name);
+	g_free(ib);
+}
+
+static void irc_chat_set_topic(GaimConnection *gc, int id, const char *topic)
+{
+	char *buf;
+	const char *name = NULL;
+	struct irc_conn *irc;
+
+	irc = gc->proto_data;
+	name = gaim_conversation_get_name(gaim_find_chat(gc, id));
+
+	if (name == NULL)
+		return;
+
+	buf = irc_format(irc, "vt:", "TOPIC", name, topic);
+	irc_send(irc, buf);
+	g_free(buf);
+}
+
+static GaimRoomlist *irc_roomlist_get_list(GaimConnection *gc)
+{
+	struct irc_conn *irc;
+	GList *fields = NULL;
+	GaimRoomlistField *f;
+	char *buf;
+
+	irc = gc->proto_data;
+
+	if (irc->roomlist)
+		gaim_roomlist_unref(irc->roomlist);
+
+	irc->roomlist = gaim_roomlist_new(gaim_connection_get_account(gc));
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_INT, _("Users"), "users", FALSE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
+	fields = g_list_append(fields, f);
+
+	gaim_roomlist_set_fields(irc->roomlist, fields);
+
+	buf = irc_format(irc, "v", "LIST");
+	irc_send(irc, buf);
+	g_free(buf);
+
+	return irc->roomlist;
+}
+
+static void irc_roomlist_cancel(GaimRoomlist *list)
+{
+	GaimConnection *gc = gaim_account_get_connection(list->account);
+	struct irc_conn *irc;
+
+	if (gc == NULL)
+		return;
+
+	irc = gc->proto_data;
+
+	gaim_roomlist_set_in_progress(list, FALSE);
+
+	if (irc->roomlist == list) {
+		irc->roomlist = NULL;
+		gaim_roomlist_unref(list);
+	}
+}
+
+static void irc_keepalive(GaimConnection *gc)
+{
+	struct irc_conn *irc = gc->proto_data;
+	if ((time(NULL) - irc->recv_time) > PING_TIMEOUT)
+		irc_cmd_ping(irc, NULL, NULL, NULL);
+}
+
+static GaimPluginProtocolInfo prpl_info =
+{
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL,
+	NULL,					/* user_splits */
+	NULL,					/* protocol_options */
+	NO_BUDDY_ICONS,		/* icon_spec */
+	irc_blist_icon,		/* list_icon */
+	irc_blist_emblems,	/* list_emblems */
+	NULL,					/* status_text */
+	NULL,					/* tooltip_text */
+	irc_status_types,	/* away_states */
+	NULL,					/* blist_node_menu */
+	irc_chat_join_info,	/* chat_info */
+	irc_chat_info_defaults,	/* chat_info_defaults */
+	irc_login,		/* login */
+	irc_close,		/* close */
+	irc_im_send,		/* send_im */
+	NULL,					/* set_info */
+	NULL,					/* send_typing */
+	irc_get_info,		/* get_info */
+	irc_set_status,		/* set_status */
+	NULL,					/* set_idle */
+	NULL,					/* change_passwd */
+	irc_add_buddy,		/* add_buddy */
+	NULL,					/* add_buddies */
+	irc_remove_buddy,	/* remove_buddy */
+	NULL,					/* remove_buddies */
+	NULL,					/* add_permit */
+	NULL,					/* add_deny */
+	NULL,					/* rem_permit */
+	NULL,					/* rem_deny */
+	NULL,					/* set_permit_deny */
+	irc_chat_join,		/* join_chat */
+	NULL,					/* reject_chat */
+	irc_get_chat_name,	/* get_chat_name */
+	irc_chat_invite,	/* chat_invite */
+	irc_chat_leave,		/* chat_leave */
+	NULL,					/* chat_whisper */
+	irc_chat_send,		/* chat_send */
+	irc_keepalive,		/* keepalive */
+	NULL,					/* register_user */
+	NULL,					/* get_cb_info */
+	NULL,					/* get_cb_away */
+	NULL,					/* alias_buddy */
+	NULL,					/* group_buddy */
+	NULL,					/* rename_group */
+	NULL,					/* buddy_free */
+	NULL,					/* convo_closed */
+	gaim_normalize_nocase,	/* normalize */
+	NULL,					/* set_buddy_icon */
+	NULL,					/* remove_group */
+	NULL,					/* get_cb_real_name */
+	irc_chat_set_topic,	/* set_chat_topic */
+	NULL,					/* find_blist_chat */
+	irc_roomlist_get_list,	/* roomlist_get_list */
+	irc_roomlist_cancel,	/* roomlist_cancel */
+	NULL,					/* roomlist_expand_category */
+	NULL,					/* can_receive_file */
+	irc_dccsend_send_file,	/* send_file */
+	irc_dccsend_new_xfer,	/* new_xfer */
+	NULL,					/* offline_message */
+	NULL,					/* whiteboard_prpl_ops */
+	irc_send_raw,			/* send_raw */
+	NULL,					/* roomlist_room_serialize */
+};
+
+static gboolean load_plugin (GaimPlugin *plugin) {
+
+	gaim_signal_register(plugin, "irc-sending-text",
+			     gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+			     gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_CONNECTION),
+			     gaim_value_new_outgoing(GAIM_TYPE_STRING));
+	gaim_signal_register(plugin, "irc-receiving-text",
+			     gaim_marshal_VOID__POINTER_POINTER, NULL, 2,
+			     gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_CONNECTION),
+			     gaim_value_new_outgoing(GAIM_TYPE_STRING));
+	return TRUE;
+}
+
+
+static GaimPluginInfo info =
+{
+	GAIM_PLUGIN_MAGIC,
+	GAIM_MAJOR_VERSION,
+	GAIM_MINOR_VERSION,
+	GAIM_PLUGIN_PROTOCOL,                             /**< type           */
+	NULL,                                             /**< ui_requirement */
+	0,                                                /**< flags          */
+	NULL,                                             /**< dependencies   */
+	GAIM_PRIORITY_DEFAULT,                            /**< priority       */
+
+	"prpl-irc",                                       /**< id             */
+	"IRC",                                            /**< name           */
+	VERSION,                                          /**< version        */
+	N_("IRC Protocol Plugin"),                        /**  summary        */
+	N_("The IRC Protocol Plugin that Sucks Less"),    /**  description    */
+	NULL,                                             /**< author         */
+	GAIM_WEBSITE,                                     /**< homepage       */
+
+	load_plugin,                                      /**< load           */
+	NULL,                                             /**< unload         */
+	NULL,                                             /**< destroy        */
+
+	NULL,                                             /**< ui_info        */
+	&prpl_info,                                       /**< extra_info     */
+	NULL,                                             /**< prefs_info     */
+	irc_actions
+};
+
+static void _init_plugin(GaimPlugin *plugin)
+{
+	GaimAccountUserSplit *split;
+	GaimAccountOption *option;
+
+	split = gaim_account_user_split_new(_("Server"), IRC_DEFAULT_SERVER, '@');
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+
+	option = gaim_account_option_int_new(_("Port"), "port", IRC_DEFAULT_PORT);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = gaim_account_option_string_new(_("Encodings"), "encoding", IRC_DEFAULT_CHARSET);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = gaim_account_option_string_new(_("Username"), "username", "");
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = gaim_account_option_string_new(_("Real name"), "realname", "");
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	/*
+	option = gaim_account_option_string_new(_("Quit message"), "quitmsg", IRC_DEFAULT_QUIT);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	*/
+
+	option = gaim_account_option_bool_new(_("Use SSL"), "ssl", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	_irc_plugin = plugin;
+
+	gaim_prefs_remove("/plugins/prpl/irc/quitmsg");
+	gaim_prefs_remove("/plugins/prpl/irc");
+
+	irc_register_commands();
+}
+
+GAIM_INIT_PLUGIN(irc, _init_plugin, info);