Index: irc.c
===================================================================
--- irc.c	(revision 210d1d9cf4366d9933c02383300175e4b573be61)
+++ irc.c	(revision b7d3cc34f68dab7b8f7d0777711317b334fc2219)
@@ -1,9 +1,9 @@
-/********************************************************************\
+  /********************************************************************\
   * BitlBee -- An IRC to other IM-networks gateway                     *
   *                                                                    *
-  * Copyright 2002-2012 Wilmer van der Gaast and others                *
+  * Copyright 2002-2004 Wilmer van der Gaast and others                *
   \********************************************************************/
 
-/* The IRC-based UI (for now the only one)                              */
+/* The big hairy IRCd part of the project                               */
 
 /*
@@ -20,505 +20,349 @@
   You should have received a copy of the GNU General Public License with
   the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
-  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
-  Fifth Floor, Boston, MA  02110-1301  USA
+  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+  Suite 330, Boston, MA  02111-1307  USA
 */
 
+#define BITLBEE_CORE
 #include "bitlbee.h"
-#include "canohost.h"
-#include "ipc.h"
-#include "dcc.h"
-#include "lib/ssl_client.h"
-
-GSList *irc_connection_list;
-GSList *irc_plugins;
-
-static gboolean irc_userping(gpointer _irc, gint fd, b_input_condition cond);
-static char *set_eval_charset(set_t *set, char *value);
-static char *set_eval_password(set_t *set, char *value);
-static char *set_eval_bw_compat(set_t *set, char *value);
-static char *set_eval_utf8_nicks(set_t *set, char *value);
-
-irc_t *irc_new(int fd)
-{
-	irc_t *irc;
-	irc_user_t *iu;
-	GSList *l;
-	set_t *s;
-	bee_t *b;
-
-	irc = g_new0(irc_t, 1);
-
+#include "crypting.h"
+
+static gboolean irc_userping( gpointer _irc );
+
+GSList *irc_connection_list = NULL;
+
+irc_t *irc_new( int fd )
+{
+	irc_t *irc = g_new0( irc_t, 1 );
+	
+	struct sockaddr_in sock[1];
+#ifdef IPV6
+	struct sockaddr_in6 sock6[1];
+#endif
+	struct hostent *peer;
+	unsigned int i, j;
+	
 	irc->fd = fd;
-	sock_make_nonblocking(irc->fd);
-
-	irc->r_watch_source_id = b_input_add(irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc);
-
+	irc->io_channel = g_io_channel_unix_new( fd );
+#ifdef GLIB2
+	g_io_channel_set_encoding (irc->io_channel, NULL, NULL);
+	g_io_channel_set_buffered (irc->io_channel, FALSE);
+	g_io_channel_set_flags( irc->io_channel, G_IO_FLAG_NONBLOCK, NULL );
+#else
+	fcntl( irc->fd, F_SETFL, O_NONBLOCK);
+#endif
+	irc->r_watch_source_id = g_io_add_watch( irc->io_channel, G_IO_IN | G_IO_ERR | G_IO_HUP, bitlbee_io_current_client_read, irc );
+	
 	irc->status = USTATUS_OFFLINE;
 	irc->last_pong = gettime();
-
-	irc->nick_user_hash = g_hash_table_new(g_str_hash, g_str_equal);
-	irc->watches = g_hash_table_new(g_str_hash, g_str_equal);
-
-	irc->iconv = (GIConv) - 1;
-	irc->oconv = (GIConv) - 1;
-
-	irc->sendbuffer = g_string_sized_new(IRC_MAX_LINE * 2);
-
-	if (global.conf->ping_interval > 0 && global.conf->ping_timeout > 0) {
-		irc->ping_source_id = b_timeout_add(global.conf->ping_interval * 1000, irc_userping, irc);
-	}
-
-	irc_connection_list = g_slist_append(irc_connection_list, irc);
-
-	b = irc->b = bee_new();
-	b->ui_data = irc;
-	b->ui = &irc_ui_funcs;
-
-	s = set_add(&b->set, "allow_takeover", "true", set_eval_bool, irc);
-	s = set_add(&b->set, "away_devoice", "true", set_eval_bw_compat, irc);
-	s->flags |= SET_HIDDEN;
-	s = set_add(&b->set, "away_reply_timeout", "3600", set_eval_int, irc);
-	s = set_add(&b->set, "charset", "utf-8", set_eval_charset, irc);
-	s = set_add(&b->set, "default_target", "root", NULL, irc);
-	s = set_add(&b->set, "display_namechanges", "false", set_eval_bool, irc);
-	s = set_add(&b->set, "display_timestamps", "true", set_eval_bool, irc);
-	s = set_add(&b->set, "handle_unknown", "add_channel", NULL, irc);
-	s = set_add(&b->set, "last_version", "0", NULL, irc);
-	s->flags |= SET_HIDDEN;
-	s = set_add(&b->set, "nick_format", "%-@nick", NULL, irc);
-	s = set_add(&b->set, "nick_lowercase", "false", set_eval_bool, irc);
-	s = set_add(&b->set, "nick_underscores", "false", set_eval_bool, irc);
-	s = set_add(&b->set, "offline_user_quits", "true", set_eval_bool, irc);
-	s = set_add(&b->set, "ops", "both", set_eval_irc_channel_ops, irc);
-	s = set_add(&b->set, "paste_buffer", "false", set_eval_bool, irc);
-	s->old_key = g_strdup("buddy_sendbuffer");
-	s = set_add(&b->set, "paste_buffer_delay", "200", set_eval_int, irc);
-	s->old_key = g_strdup("buddy_sendbuffer_delay");
-	s = set_add(&b->set, "password", NULL, set_eval_password, irc);
-	s->flags |= SET_NULL_OK | SET_PASSWORD;
-	s = set_add(&b->set, "private", "true", set_eval_bool, irc);
-	s = set_add(&b->set, "query_order", "lifo", NULL, irc);
-	s = set_add(&b->set, "root_nick", ROOT_NICK, set_eval_root_nick, irc);
-	s->flags |= SET_HIDDEN;
-	s = set_add(&b->set, "show_offline", "false", set_eval_bw_compat, irc);
-	s->flags |= SET_HIDDEN;
-	s = set_add(&b->set, "self_messages", "true", set_eval_self_messages, irc);
-	s = set_add(&b->set, "simulate_netsplit", "true", set_eval_bool, irc);
-	s = set_add(&b->set, "timezone", "local", set_eval_timezone, irc);
-	s = set_add(&b->set, "to_char", ": ", set_eval_to_char, irc);
-	s = set_add(&b->set, "typing_notice", "false", set_eval_bool, irc);
-	s = set_add(&b->set, "utf8_nicks", "false", set_eval_utf8_nicks, irc);
-
-	irc->root = iu = irc_user_new(irc, ROOT_NICK);
-	iu->fullname = g_strdup(ROOT_FN);
-	iu->f = &irc_user_root_funcs;
-
-	iu = irc_user_new(irc, NS_NICK);
-	iu->fullname = g_strdup(ROOT_FN);
-	iu->f = &irc_user_root_funcs;
-
-	irc->user = g_new0(irc_user_t, 1);
-	
-	irc_set_hosts(irc, NULL, 0);
-
-	conf_loaddefaults(irc);
-
-	/* Evaluator sets the iconv/oconv structures. */
-	set_eval_charset(set_find(&b->set, "charset"), set_getstr(&b->set, "charset"));
-
-	irc_write(irc, ":%s NOTICE * :%s", irc->root->host, "BitlBee-IRCd initialized, please go on");
-	if (isatty(irc->fd)) {
-		irc_write(irc, ":%s NOTICE * :%s", irc->root->host,
-		          "If you read this, you most likely accidentally "
-		          "started BitlBee in inetd mode on the command line.");
-		irc_write(irc, ":%s NOTICE * :%s", irc->root->host,
-		          "You probably want to run it as a system service, "
-		          "or use (Fork)Daemon mode with the -F or -D switches. "
-		          "See doc/README or 'man bitlbee' for more information.");
-	}
-
-	/* libpurple doesn't like fork()s after initializing itself, so this
-	   is the right moment to initialize it. */
-#ifdef WITH_PURPLE
-	nogaim_init();
+	
+	irc->userhash = g_hash_table_new( g_str_hash, g_str_equal );
+	irc->watches = g_hash_table_new( g_str_hash, g_str_equal );
+	
+	strcpy( irc->umode, UMODE );
+	irc->mynick = g_strdup( ROOT_NICK );
+	irc->channel = g_strdup( ROOT_CHAN );
+	
+	i = sizeof( *sock );
+#ifdef IPV6
+	j = sizeof( *sock6 );
 #endif
-
-	/* SSL library initialization also should be done after the fork, to
-	   avoid shared CSPRNG state. This is required by NSS, which refuses to
-	   work if a fork is detected */
-	ssl_init();
-
-	for (l = irc_plugins; l; l = l->next) {
-		irc_plugin_t *p = l->data;
-		if (p->irc_new) {
-			p->irc_new(irc);
-		}
-	}
-
-	return irc;
-}
-
-void irc_set_hosts(irc_t *irc, const struct sockaddr *remote_addr, const socklen_t remote_addrlen)
-{
-	struct sockaddr_storage sock;
-	socklen_t socklen = sizeof(sock);
-	char *host = NULL, *myhost = NULL;
-	struct irc_user *iu;
-
-	if (global.conf->hostname) {
-		myhost = g_strdup(global.conf->hostname);
-	} else if (getsockname(irc->fd, (struct sockaddr*) &sock, &socklen) == 0) {
-		myhost = reverse_lookup((struct sockaddr*) &sock, socklen);
-	}
-
-	if (remote_addrlen > 0) {
-		host = reverse_lookup(remote_addr, remote_addrlen);
-	} else if (getpeername(irc->fd, (struct sockaddr*) &sock, &socklen) == 0) {
-		host = reverse_lookup((struct sockaddr*) &sock, socklen);
-	}
-
-	if (myhost == NULL) {
-		myhost = g_strdup("localhost.localdomain");
-	}
-	if (host == NULL) {
-		host = g_strdup("localhost.localdomain");
-	}
-	
-	if (irc->root->host != irc->root->nick) {
-		g_free(irc->root->host);
-	}
-	irc->root->host = g_strdup(myhost);
-	if ((iu = irc_user_by_name(irc, NS_NICK))) {
-		if (iu->host != iu->nick) {
-			g_free(iu->host);
-		}
-		iu->host = g_strdup(myhost);
-	}
-	
-	if (irc->user->host != irc->user->nick) {
-		g_free(irc->user->host);
-	}
-	irc->user->host = g_strdup(host);
-
-	g_free(myhost);
-	g_free(host);
-}
-
-/* immed=1 makes this function pretty much equal to irc_free(), except that
-   this one will "log". In case the connection is already broken and we
-   shouldn't try to write to it. */
-void irc_abort(irc_t *irc, int immed, char *format, ...)
-{
-	char *reason = NULL;
-
-	if (format != NULL) {
-		va_list params;
-
-		va_start(params, format);
-		reason = g_strdup_vprintf(format, params);
-		va_end(params);
-	}
-
-	if (reason) {
-		irc_write(irc, "ERROR :Closing link: %s", reason);
-	}
-
-	ipc_to_master_str("OPERMSG :Client exiting: %s@%s [%s]\r\n",
-	                  irc->user->nick ? irc->user->nick : "(NONE)",
-	                  irc->user->host, reason ? : "");
-
-	g_free(reason);
-
-	irc_flush(irc);
-	if (immed) {
-		irc_free(irc);
-	} else {
-		b_event_remove(irc->ping_source_id);
-		irc->ping_source_id = b_timeout_add(1, (b_event_handler) irc_free, irc);
-	}
-}
-
-static gboolean irc_free_hashkey(gpointer key, gpointer value, gpointer data);
-
+	if( global.conf->hostname )
+		irc->myhost = g_strdup( global.conf->hostname );
+	else if( getsockname( irc->fd, (struct sockaddr*) sock, &i ) == 0 && sock->sin_family == AF_INET )
+	{
+		if( ( peer = gethostbyaddr( (char*) &sock->sin_addr, sizeof( sock->sin_addr ), AF_INET ) ) )
+			irc->myhost = g_strdup( peer->h_name );
+	}
+#ifdef IPV6
+	else if( getsockname( irc->fd, (struct sockaddr*) sock6, &j ) == 0 && sock6->sin6_family == AF_INET6 )
+	{
+		if( ( peer = gethostbyaddr( (char*) &sock6->sin6_addr, sizeof( sock6->sin6_addr ), AF_INET6 ) ) )
+			irc->myhost = g_strdup( peer->h_name );
+	}
+#endif
+	
+	i = sizeof( *sock );
+#ifdef IPV6
+	j = sizeof( *sock6 );
+#endif
+	if( getpeername( irc->fd, (struct sockaddr*) sock, &i ) == 0 && sock->sin_family == AF_INET )
+	{
+		if( ( peer = gethostbyaddr( (char*) &sock->sin_addr, sizeof( sock->sin_addr ), AF_INET ) ) )
+			irc->host = g_strdup( peer->h_name );
+	}
+#ifdef IPV6
+	else if( getpeername( irc->fd, (struct sockaddr*) sock6, &j ) == 0 && sock6->sin6_family == AF_INET6 )
+	{
+		if( ( peer = gethostbyaddr( (char*) &sock6->sin6_addr, sizeof( sock6->sin6_addr ), AF_INET6 ) ) )
+			irc->host = g_strdup( peer->h_name );
+	}
+#endif
+	
+	if( !irc->host ) irc->host = g_strdup( "localhost." );
+	if( !irc->myhost ) irc->myhost = g_strdup( "localhost." );
+
+	if( global.conf->ping_interval > 0 && global.conf->ping_timeout > 0 )
+		irc->ping_source_id = g_timeout_add( global.conf->ping_interval * 1000, irc_userping, irc );
+	
+	irc_write( irc, ":%s NOTICE AUTH :%s", irc->myhost, "BitlBee-IRCd initialized, please go on" );
+
+	irc_connection_list = g_slist_append( irc_connection_list, irc );
+	
+	set_add( irc, "away_devoice", "true",  set_eval_away_devoice );
+	set_add( irc, "auto_connect", "true", set_eval_bool );
+	set_add( irc, "auto_reconnect", "false", set_eval_bool );
+	set_add( irc, "auto_reconnect_delay", "300", set_eval_int );
+	set_add( irc, "buddy_sendbuffer", "false", set_eval_bool );
+	set_add( irc, "buddy_sendbuffer_delay", "1", set_eval_int );
+	set_add( irc, "charset", "iso8859-1", set_eval_charset );
+	set_add( irc, "debug", "false", set_eval_bool );
+	set_add( irc, "default_target", "root", NULL );
+	set_add( irc, "display_namechanges", "false", set_eval_bool );
+	set_add( irc, "handle_unknown", "root", NULL );
+	/* set_add( irc, "html", "nostrip", NULL ); */
+	set_add( irc, "lcnicks", "true", set_eval_bool );
+	set_add( irc, "ops", "both", set_eval_ops );
+	set_add( irc, "private", "true", set_eval_bool );
+	set_add( irc, "query_order", "lifo", NULL );
+	set_add( irc, "save_on_quit", "true", set_eval_bool );
+	set_add( irc, "to_char", ": ", set_eval_to_char );
+	set_add( irc, "typing_notice", "false", set_eval_bool );
+	
+	conf_loaddefaults( irc );
+	
+	return( irc );
+}
+
+static gboolean irc_free_userhash( gpointer key, gpointer value, gpointer data )
+{
+	g_free( key );
+	
+	return( TRUE );
+}
+
+/* Because we have no garbage collection, this is quite annoying */
 void irc_free(irc_t * irc)
 {
-	GSList *l;
-	GHashTableIter iter;
-	gpointer itervalue;
-
-	irc->status |= USTATUS_SHUTDOWN;
-
-	log_message(LOGLVL_INFO, "Destroying connection with fd %d", irc->fd);
-
-	if (irc->status & USTATUS_IDENTIFIED && set_getbool(&irc->b->set, "save_on_quit")) {
-		if (storage_save(irc, NULL, TRUE) != STORAGE_OK) {
-			log_message(LOGLVL_WARNING, "Error while saving settings for user %s", irc->user->nick);
-		}
-	}
-
-	for (l = irc_plugins; l; l = l->next) {
-		irc_plugin_t *p = l->data;
-		if (p->irc_free) {
-			p->irc_free(irc);
-		}
-	}
-
-	irc_connection_list = g_slist_remove(irc_connection_list, irc);
-
-	while (irc->queries != NULL) {
+	account_t *account, *accounttmp;
+	user_t *user, *usertmp;
+	nick_t *nick, *nicktmp;
+	help_t *helpnode, *helpnodetmp;
+	set_t *setnode, *setnodetmp;
+	
+	log_message( LOGLVL_INFO, "Destroying connection with fd %d", irc->fd );
+	
+	if( irc->status >= USTATUS_IDENTIFIED && set_getint( irc, "save_on_quit" ) ) 
+		if( !bitlbee_save( irc ) )
+			irc_usermsg( irc, "Error while saving settings!" );
+	
+	if( irc->ping_source_id > 0 )
+		g_source_remove( irc->ping_source_id );
+	g_source_remove( irc->r_watch_source_id );
+	if( irc->w_watch_source_id > 0 )
+		g_source_remove( irc->w_watch_source_id );
+	g_io_channel_close( irc->io_channel );
+	g_io_channel_unref( irc->io_channel );
+	irc_connection_list = g_slist_remove( irc_connection_list, irc );
+	
+	for (account = irc->accounts; account; account = account->next)
+		if (account->gc)
+			signoff(account->gc);
+	
+	g_free(irc->sendbuffer);
+	g_free(irc->readbuffer);
+	
+	g_free(irc->nick);
+	g_free(irc->user);
+	g_free(irc->host);
+	g_free(irc->realname);
+	g_free(irc->password);
+	
+	g_free(irc->myhost);
+	g_free(irc->mynick);
+	
+	g_free(irc->channel);
+	
+	while (irc->queries != NULL)
 		query_del(irc, irc->queries);
-	}
-
-	/* This is a little bit messy: bee_free() frees all b->users which
-	   calls us back to free the corresponding irc->users. So do this
-	   before we clear the remaining ones ourselves. */
-	bee_free(irc->b);
-
-	g_hash_table_iter_init(&iter, irc->nick_user_hash);
-
-	while (g_hash_table_iter_next(&iter, NULL, &itervalue)) {
-		g_hash_table_iter_remove(&iter);
-		irc_user_free(irc, (irc_user_t *) itervalue);
-	}
-
-	while (irc->channels) {
-		irc_channel_free(irc->channels->data);
-	}
-
-	if (irc->ping_source_id > 0) {
-		b_event_remove(irc->ping_source_id);
-	}
-	if (irc->r_watch_source_id > 0) {
-		b_event_remove(irc->r_watch_source_id);
-	}
-	if (irc->w_watch_source_id > 0) {
-		b_event_remove(irc->w_watch_source_id);
-	}
-
-	closesocket(irc->fd);
-	irc->fd = -1;
-
-	g_hash_table_destroy(irc->nick_user_hash);
-
-	g_hash_table_foreach_remove(irc->watches, irc_free_hashkey, NULL);
+	
+	if (irc->accounts != NULL) {
+		account = irc->accounts;
+		while (account != NULL) {
+			g_free(account->user);
+			g_free(account->pass);
+			g_free(account->server);
+			accounttmp = account;
+			account = account->next;
+			g_free(accounttmp);
+		}
+	}
+	
+	if (irc->users != NULL) {
+		user = irc->users;
+		while (user != NULL) {
+			g_free(user->nick);
+			g_free(user->away);
+			g_free(user->handle);
+			if(user->user!=user->nick) g_free(user->user);
+			if(user->host!=user->nick) g_free(user->host);
+			if(user->realname!=user->nick) g_free(user->realname);
+			gaim_input_remove(user->sendbuf_timer);
+					
+			usertmp = user;
+			user = user->next;
+			g_free(usertmp);
+		}
+	}
+	
+	g_hash_table_foreach_remove(irc->userhash, irc_free_userhash, NULL);
+	g_hash_table_destroy(irc->userhash);
+	
+	g_hash_table_foreach_remove(irc->watches, irc_free_userhash, NULL);
 	g_hash_table_destroy(irc->watches);
-
-	if (irc->iconv != (GIConv) - 1) {
-		g_iconv_close(irc->iconv);
-	}
-	if (irc->oconv != (GIConv) - 1) {
-		g_iconv_close(irc->oconv);
-	}
-
-	g_string_free(irc->sendbuffer, TRUE);
-	g_free(irc->readbuffer);
-	g_free(irc->password);
-
+	
+	if (irc->nicks != NULL) {
+		nick = irc->nicks;
+		while (nick != NULL) {
+			g_free(nick->nick);
+			g_free(nick->handle);
+					
+			nicktmp = nick;
+			nick = nick->next;
+			g_free(nicktmp);
+		}
+	}
+	if (irc->help != NULL) {
+		helpnode = irc->help;
+		while (helpnode != NULL) {
+			g_free(helpnode->string);
+			
+			helpnodetmp = helpnode;
+			helpnode = helpnode->next;
+			g_free(helpnodetmp);
+		}
+	}
+	if (irc->set != NULL) {
+		setnode = irc->set;
+		while (setnode != NULL) {
+			g_free(setnode->key);
+			g_free(setnode->def);
+			g_free(setnode->value);
+			
+			setnodetmp = setnode;
+			setnode = setnode->next;
+			g_free(setnodetmp);
+		}
+	}
 	g_free(irc);
-
-	if (global.conf->runmode == RUNMODE_INETD ||
-	    global.conf->runmode == RUNMODE_FORKDAEMON ||
-	    (global.conf->runmode == RUNMODE_DAEMON &&
-	     global.listen_socket == -1 &&
-	     irc_connection_list == NULL)) {
-		b_main_quit();
-	}
-}
-
-static gboolean irc_free_hashkey(gpointer key, gpointer value, gpointer data)
-{
-	g_free(key);
-
-	return(TRUE);
-}
-
-/* USE WITH CAUTION!
-   Sets pass without checking */
-void irc_setpass(irc_t *irc, const char *pass)
-{
-	g_free(irc->password);
-
-	if (pass) {
-		irc->password = g_strdup(pass);
-	} else {
-		irc->password = NULL;
-	}
-}
-
-static char *set_eval_password(set_t *set, char *value)
-{
-	irc_t *irc = set->data;
-
-	if (irc->status & USTATUS_IDENTIFIED && value) {
-		irc_setpass(irc, value);
-		return NULL;
-	} else {
-		return SET_INVALID;
-	}
-}
-
-static char **irc_splitlines(char *buffer);
-
-void irc_process(irc_t *irc)
-{
-	char **lines, *temp, **cmd;
+	
+	if( global.conf->runmode == RUNMODE_INETD )
+		g_main_quit( global.loop );
+}
+
+int irc_process( irc_t *irc )
+{
+	char **lines, *temp;	
 	int i;
 
-	if (irc->readbuffer != NULL) {
-		lines = irc_splitlines(irc->readbuffer);
-
-		for (i = 0; *lines[i] != '\0'; i++) {
-			char *conv = NULL;
-
-			/* [WvG] If the last line isn't empty, it's an incomplete line and we
-			   should wait for the rest to come in before processing it. */
-			if (lines[i + 1] == NULL) {
-				temp = g_strdup(lines[i]);
-				g_free(irc->readbuffer);
+	if( irc->readbuffer != NULL ) {
+		lines = irc_tokenize(irc->readbuffer );
+		for( i = 0; *lines[i] != '\0'; i++ ) {
+			if( lines[i+1] == NULL ) {
+				temp = g_strdup( lines[i] );
+				g_free( irc->readbuffer );
 				irc->readbuffer = temp;
 				i++;
 				break;
-			}
-
-			if (irc->iconv != (GIConv) - 1) {
-				gsize bytes_read, bytes_written;
-
-				conv = g_convert_with_iconv(lines[i], -1, irc->iconv,
-				                            &bytes_read, &bytes_written, NULL);
-
-				if (conv == NULL || bytes_read != strlen(lines[i])) {
-					/* GLib can do strange things if things are not in the expected charset,
-					   so let's be a little bit paranoid here: */
-					if (irc->status & USTATUS_LOGGED_IN) {
-						irc_rootmsg(irc, "Error: Charset mismatch detected. The charset "
-						            "setting is currently set to %s, so please make "
-						            "sure your IRC client will send and accept text in "
-						            "that charset, or tell BitlBee which charset to "
-						            "expect by changing the charset setting. See "
-						            "`help set charset' for more information. Your "
-						            "message was ignored.",
-						            set_getstr(&irc->b->set, "charset"));
-
-						g_free(conv);
-						conv = NULL;
-					} else {
-						irc_write(irc, ":%s NOTICE * :%s", irc->root->host,
-						          "Warning: invalid characters received at login time.");
-
-						conv = g_strdup(lines[i]);
-						for (temp = conv; *temp; temp++) {
-							if (*temp & 0x80) {
-								*temp = '?';
-							}
-						}
-					}
-				}
-				lines[i] = conv;
-			}
-
-			if (lines[i] && (cmd = irc_parse_line(lines[i]))) {
-				irc_exec(irc, cmd);
-				g_free(cmd);
-			}
-
-			g_free(conv);
-
-			/* Shouldn't really happen, but just in case... */
-			if (!g_slist_find(irc_connection_list, irc)) {
-				g_free(lines);
-				return;
-			}
-		}
-
-		if (lines[i] != NULL) {
+			}			
+			if (!irc_process_line(irc, lines[i])) {
+				g_free( lines );
+				return 0;
+			}
+		}
+		if(lines[i]!=NULL) {
 			g_free(irc->readbuffer);
-			irc->readbuffer = NULL;
-		}
-
-		g_free(lines);
-	}
-}
-
-/* Splits a long string into separate lines. The array is NULL-terminated
-   and, unless the string contains an incomplete line at the end, ends with
-   an empty string. Could use g_strsplit() but this one does it in-place.
-   (So yes, it's destructive.) */
-static char **irc_splitlines(char *buffer)
-{
-	int i, j, n = 3;
+			irc->readbuffer=NULL;	
+		}
+		g_free( lines );
+	}
+	return 1;	
+}
+
+char **irc_tokenize( char *buffer )
+{
+	int i, j;
 	char **lines;
 
-	/* Allocate n+1 elements. */
-	lines = g_new(char *, n + 1);
-
-	lines[0] = buffer;
-
-	/* Split the buffer in several strings, and accept any kind of line endings,
-	 * knowing that ERC on Windows may send something interesting like \r\r\n,
-	 * and surely there must be clients that think just \n is enough... */
-	for (i = 0, j = 0; buffer[i] != '\0'; i++) {
-		if (buffer[i] == '\r' || buffer[i] == '\n') {
-			while (buffer[i] == '\r' || buffer[i] == '\n') {
-				buffer[i++] = '\0';
-			}
-
-			lines[++j] = buffer + i;
-
-			if (j >= n) {
-				n *= 2;
-				lines = g_renew(char *, lines, n + 1);
-			}
-
-			if (buffer[i] == '\0') {
-				break;
-			}
-		}
-	}
-
-	/* NULL terminate our list. */
-	lines[++j] = NULL;
-
-	return lines;
-}
-
-/* Split an IRC-style line into little parts/arguments. */
-char **irc_parse_line(char *line)
+	/* Count the number of elements we're gonna need. */
+	for(i=0, j=1; buffer[i]!='\0'; i++ ) {
+		if(buffer[i]=='\n' )
+			if(buffer[i+1]!='\r' && buffer[i+1]!='\n')
+				j++;
+	}
+	
+	/* Allocate j+1 elements. */
+	lines=g_new (char *, j+1);
+	
+	/* NULL terminate our list. */ 
+	lines[j]=NULL;
+	
+	lines[0]=buffer;
+	
+	/* Split the buffer in several strings, using \r\n as our seperator, where \r is optional.
+	 * Although this is not in the RFC, some braindead ircds (newnet's) use this, so some clients might too. 
+	 */
+	for( i=0, j=0; buffer[i]!='\0'; i++) {
+		if(buffer[i]=='\n') {
+			buffer[i]='\0';
+
+			/* We dont want to read 1 byte before our buffer
+			 * and (in rare cases) generate a SIGSEGV.
+			 */
+			if(i!=0)
+				if(buffer[i-1]=='\r')
+					buffer[i-1]='\0';
+			if(buffer[i+1]!='\r'&&buffer[i+1]!='\n')
+				lines[++j]=buffer+i+1;
+		}
+	}
+
+	return(lines);
+}
+
+int irc_process_line( irc_t *irc, char *line )
 {
 	int i, j;
 	char **cmd;
-
+	
 	/* Move the line pointer to the start of the command, skipping spaces and the optional prefix. */
-	if (line[0] == ':') {
-		for (i = 0; line[i] && line[i] != ' '; i++) {
-			;
-		}
-		line = line + i;
-	}
-	for (i = 0; line[i] == ' '; i++) {
-		;
-	}
-	line = line + i;
+	if(line[0]==':') {
+		for(i=0; line[i]!=32; i++);
+		line=line+i;
+	}
+	for(i=0; line[i]==32; i++);
+	line=line+i;
 
 	/* If we're already at the end of the line, return. If not, we're going to need at least one element. */
-	if (line[0] == '\0') {
-		return NULL;
-	}
-
-	/* Count the number of char **cmd elements we're going to need. */
-	j = 1;
-	for (i = 0; line[i] != '\0'; i++) {
-		if (line[i] == ' ') {
+	if(line[0]=='\0')
+		return 1;
+	else
+		j=1;	
+	
+	/* Count the number of char **cmd elements we're going to need. */	
+	for(i=0; line[i]!='\0'; i++) {
+		if((line[i]==32) && (line[i+1]!=32) && (line[i+1]!='\0') && (line[i+1]!=':'))		
 			j++;
-
-			if (line[i + 1] == ':') {
-				break;
-			}
-		}
-	}
+		else if((line[i]==':') && (line[i+1]!='\0') && (line[i-1]==32)) {
+			j++;
+			break;
+		}
+			
+	}	
 
 	/* Allocate the space we need. */
-	cmd = g_new(char *, j + 1);
-	cmd[j] = NULL;
-
+	cmd=g_new(char *, j+1);
+	cmd[j]=NULL;
+	
 	/* Do the actual line splitting, format is:
 	 * Input: "PRIVMSG #bitlbee :foo bar"
@@ -526,336 +370,1168 @@
 	 */
 
-	cmd[0] = line;
-	for (i = 0, j = 0; line[i] != '\0'; i++) {
-		if (line[i] == ' ') {
-			line[i] = '\0';
-			cmd[++j] = line + i + 1;
-
-			if (line[i + 1] == ':') {
-				cmd[j]++;
+	cmd[0]=line;
+	for(i=0, j=0; line[i]!='\0'; i++) {
+		if((line[i]==32)) {
+			line[i]='\0';
+			if((line[i+1]!=32) && (line[i+1]!='\0') && (line[i+1]!=':')) 		
+				cmd[++j]=line+i+1;
+		}
+		else if((line[i]==':') && (line[i+1]!='\0') && (line[i-1]=='\0')) {
+			cmd[++j]=line+i+1;
+			break;
+		}
+	}
+	
+	i=irc_exec(irc, cmd);
+	g_free(cmd);
+
+	return(i);	
+}
+
+int irc_exec( irc_t *irc, char **cmd )
+{	
+	int i;
+
+	if( (global.conf)->authmode == AUTHMODE_CLOSED && irc->status < USTATUS_AUTHORIZED )
+	{
+		if( g_strcasecmp( cmd[0], "PASS" ) == 0 )
+		{
+			if( !cmd[1] )
+			{
+				irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+			}
+			else if( strcmp( cmd[1], (global.conf)->password ) == 0 )
+			{
+				irc->status = USTATUS_AUTHORIZED;
+			}
+			else
+			{
+				irc_reply( irc, 464, ":Nope, maybe you should try it again..." );
+			}
+		}
+		else
+		{
+			irc_reply( irc, 464, ":Uhh, fine, but I want the password first." );
+		}
+		
+		return( 1 );
+	}
+	
+	if( g_strcasecmp( cmd[0], "USER" ) == 0 )
+	{
+		if( !( cmd[1] && cmd[2] && cmd[3] && cmd[4] ) )
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+		else if( irc->user )
+		{
+			irc_reply( irc, 462, ":You can't change your nick/userinfo" );
+		}
+		else
+		{
+			irc->user = g_strdup( cmd[1] );
+			irc->realname = g_strdup( cmd[4] );
+			if( irc->nick ) irc_login( irc );
+		}
+		return( 1 );
+	}
+	else if( g_strcasecmp( cmd[0], "NICK" ) == 0 )
+	{
+		if( !cmd[1] )
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+		else if( irc->nick )
+		{
+			irc_reply( irc, 438, ":The hand of the deity is upon thee, thy nick may not change" );
+		}
+		/* This is not clean, but for now it'll have to be like this... */
+		else if( ( nick_cmp( cmd[1], irc->mynick ) == 0 ) || ( nick_cmp( cmd[1], NS_NICK ) == 0 ) )
+		{
+			irc_reply( irc, 433, ":This nick is already in use" );
+		}
+		else if( !nick_ok( cmd[1] ) )
+		{
+			/* [SH] Invalid characters. */
+			irc_reply( irc, 432, ":This nick contains invalid characters" );
+		}
+		else
+		{
+			irc->nick = g_strdup( cmd[1] );
+			if( irc->user ) irc_login( irc );
+		}
+		return( 1 );
+	}
+	
+	if( !irc->user || !irc->nick )
+	{
+		irc_reply( irc, 451, ":Register first" );
+		return( 1 );
+	}
+	
+	if( g_strcasecmp( cmd[0], "PING" ) == 0 )
+	{
+		irc_write( irc, ":%s PONG %s :%s", irc->myhost, irc->myhost, cmd[1]?cmd[1]:irc->myhost );
+	}
+	else if( g_strcasecmp( cmd[0], "MODE" ) == 0 )
+	{
+		if( !cmd[1] )
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+		else if( *cmd[1] == '#' )
+		{
+			if( cmd[2] )
+			{
+				if( *cmd[2] == '+' || *cmd[2] == '-' )
+					irc_reply( irc, 477, "%s :Can't change channel modes", cmd[1] );
+				else if( *cmd[2] == 'b' )
+					irc_reply( irc, 368, "%s :No bans possible", cmd[1] );
+			}
+			else
+				irc_reply( irc, 324, "%s +%s", cmd[1], CMODE );
+		}
+		else
+		{
+			if( nick_cmp( cmd[1], irc->nick ) == 0 )
+			{
+				if( cmd[2] )
+					irc_umode_set( irc, irc->nick, cmd[2] );
+			}
+			else
+				irc_reply( irc, 502, ":Don't touch their modes" );
+		}
+	}
+	else if( g_strcasecmp( cmd[0], "NAMES" ) == 0 )
+	{
+		irc_names( irc, cmd[1]?cmd[1]:irc->channel );
+	}
+	else if( g_strcasecmp( cmd[0], "PART" ) == 0 )
+	{
+		struct conversation *c;
+		
+		if( !cmd[1] )
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+		else if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
+		{
+			user_t *u = user_find( irc, irc->nick );
+			
+			/* Not allowed to leave control channel */
+			irc_part( irc, u, irc->channel );
+			irc_join( irc, u, irc->channel );
+		}
+		else if( ( c = conv_findchannel( cmd[1] ) ) )
+		{
+			user_t *u = user_find( irc, irc->nick );
+			
+			irc_part( irc, u, c->channel );
+			
+			if( c->gc && c->gc->prpl )
+			{
+				c->joined = 0;
+				c->gc->prpl->chat_leave( c->gc, c->id );
+			}
+		}
+		else
+		{
+			irc_reply( irc, 403, "%s :No such channel", cmd[1] );
+		}
+	}
+	else if( g_strcasecmp( cmd[0], "JOIN" ) == 0 )
+	{
+		if( !cmd[1] )
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+		else if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
+			; /* Dude, you're already there...
+			     RFC doesn't have any reply for that though? */
+		else if( cmd[1] )
+		{
+			if( cmd[1][0] == '#' && cmd[1][1] )
+			{
+				user_t *u = user_find( irc, cmd[1] + 1 );
+				
+				if( u && u->gc && u->gc->prpl && u->gc->prpl->chat_open )
+				{
+					irc_reply( irc, 403, "%s :Initializing groupchat in a different channel", cmd[1] );
+					
+					if( !u->gc->prpl->chat_open( u->gc, u->handle ) )
+					{
+						irc_usermsg( irc, "Could not open a groupchat with %s, maybe you don't have a connection to him/her yet?", u->nick );
+					}
+				}
+				else
+				{
+					irc_reply( irc, 403, "%s :Groupchats are not possible with %s", cmd[1], cmd[1]+1 );
+				}
+			}
+			else
+			{
+				irc_reply( irc, 403, "%s :No such channel", cmd[1] );
+			}
+		}
+	}
+	else if( g_strcasecmp( cmd[0], "INVITE" ) == 0 )
+	{
+		if( cmd[1] && cmd[2] )
+			irc_invite( irc, cmd[1], cmd[2] );
+		else
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+	}
+	else if( g_strcasecmp( cmd[0], "PRIVMSG" ) == 0 || g_strcasecmp( cmd[0], "NOTICE" ) == 0 )
+	{
+		if( !cmd[1] )
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		} 
+		else if ( !cmd[2] ) 
+		{
+			irc_reply( irc, 412, ":No text to send" );
+		}
+		else if ( irc->nick && g_strcasecmp( cmd[1], irc->nick ) == 0 ) 
+		{
+			irc_write( irc, ":%s!%s@%s PRIVMSG %s :%s", irc->nick, irc->user, irc->host, cmd[1], cmd[2] ); 
+		}
+		else 
+		{
+			if( g_strcasecmp( cmd[1], irc->channel ) == 0 )
+			{
+				unsigned int i;
+				char *t = set_getstr( irc, "default_target" );
+				
+				if( g_strcasecmp( t, "last" ) == 0 && irc->last_target )
+					cmd[1] = irc->last_target;
+				else if( g_strcasecmp( t, "root" ) == 0 )
+					cmd[1] = irc->mynick;
+				
+				for( i = 0; i < strlen( cmd[2] ); i ++ )
+				{
+					if( cmd[2][i] == ' ' ) break;
+					if( cmd[2][i] == ':' || cmd[2][i] == ',' )
+					{
+						cmd[1] = cmd[2];
+						cmd[2] += i;
+						*cmd[2] = 0;
+						while( *(++cmd[2]) == ' ' );
+						break;
+					}
+				}
+				
+				irc->is_private = 0;
+				
+				if( cmd[1] != irc->last_target )
+				{
+					if( irc->last_target )
+						g_free( irc->last_target );
+					irc->last_target = g_strdup( cmd[1] );
+				}
+			}
+			else
+			{
+				irc->is_private = 1;
+			}
+			irc_send( irc, cmd[1], cmd[2], ( g_strcasecmp( cmd[0], "NOTICE" ) == 0 ) ? IM_FLAG_AWAY : 0 );
+		}
+	}
+	else if( g_strcasecmp( cmd[0], "QUIT" ) == 0 )
+	{
+		irc_write( irc, "ERROR :%s%s", cmd[1]?"Quit: ":"", cmd[1]?cmd[1]:"Client Quit" );
+		g_io_channel_close( irc->io_channel );
+		return( 0 );
+	}
+	else if( g_strcasecmp( cmd[0], "WHO" ) == 0 )
+	{
+		irc_who( irc, cmd[1] );
+	}
+	else if( g_strcasecmp( cmd[0], "USERHOST" ) == 0 )
+	{
+		user_t *u;
+		
+		if( !cmd[1] )
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+		/* [TV] Usable USERHOST-implementation according to
+			RFC1459. Without this, mIRC shows an error
+			while connecting, and the used way of rejecting
+			breaks standards.
+		*/
+		
+		for( i = 1; cmd[i]; i ++ )
+			if( ( u = user_find( irc, cmd[i] ) ) )
+			{
+				if( u->online && u->away )
+					irc_reply( irc, 302, ":%s=-%s@%s", u->nick, u->user, u->host );
+				else
+					irc_reply( irc, 302, ":%s=+%s@%s", u->nick, u->user, u->host );
+			}
+	}
+	else if( g_strcasecmp( cmd[0], "ISON" ) == 0 )
+	{
+		user_t *u;
+		char buff[IRC_MAX_LINE];
+		int lenleft;
+		
+		buff[0] = '\0';
+		
+		/* [SH] Leave room for : and \0 */
+		lenleft = IRC_MAX_LINE - 2;
+		
+		for( i = 1; cmd[i]; i ++ )
+		{
+			if( ( u = user_find( irc, cmd[i] ) ) && u->online )
+			{
+				/* [SH] Make sure we don't use too much buffer space. */
+				lenleft -= strlen( u->nick ) + 1;
+				
+				if( lenleft < 0 )
+				{
+					break;
+				}
+				
+				/* [SH] Add the nick to the buffer. Note
+				 * that an extra space is always added. Even
+				 * if it's the last nick in the list. Who
+				 * cares?
+				 */
+				
+				strcat( buff, u->nick );
+				strcat( buff, " " );
+			}
+		}
+		
+		/* [WvG] Well, maybe someone cares, so why not remove it? */
+		if( strlen( buff ) > 0 )
+			buff[strlen(buff)-1] = '\0';
+		
+		/* [SH] By the way, that really *was* WvG talking. */
+		/* [WvG] Really? */
+		/* [SH] Yeah... But *this* is WvG talking too. ;-P */
+		/* [WvG] *sigh* */
+		
+		irc_reply( irc, 303, ":%s", buff );
+	}
+	else if( g_strcasecmp( cmd[0], "WATCH" ) == 0 )
+	{
+		/* Obviously we could also mark a user structure as being
+		   watched, but what if the WATCH command is sent right
+		   after connecting? The user won't exist yet then... */
+		for( i = 1; cmd[i]; i ++ )
+		{
+			char *nick;
+			user_t *u;
+			
+			if( !cmd[i][0] || !cmd[i][1] )
 				break;
-			}
-		}
-	}
-
-	return cmd;
-}
-
-/* Converts such an array back into a command string. Mainly used for the IPC code right now. */
-char *irc_build_line(char **cmd)
-{
-	int i, len;
+			
+			nick = g_strdup( cmd[i] + 1 );
+			nick_lc( nick );
+			
+			u = user_find( irc, nick );
+			
+			if( cmd[i][0] == '+' )
+			{
+				if( !g_hash_table_lookup( irc->watches, nick ) )
+					g_hash_table_insert( irc->watches, nick, nick );
+				
+				if( u && u->online )
+					irc_reply( irc, 604, "%s %s %s %d :%s", u->nick, u->user, u->host, time( NULL ), "is online" );
+				else
+					irc_reply( irc, 605, "%s %s %s %d :%s", nick, "*", "*", time( NULL ), "is offline" );
+			}
+			else if( cmd[i][0] == '-' )
+			{
+				gpointer okey, ovalue;
+				
+				if( g_hash_table_lookup_extended( irc->watches, nick, &okey, &ovalue ) )
+				{
+					g_free( okey );
+					g_hash_table_remove( irc->watches, okey );
+					
+					irc_reply( irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching" );
+				}
+			}
+		}
+	}
+	else if( g_strcasecmp( cmd[0], "TOPIC" ) == 0 )
+	{
+		if( cmd[1] && cmd[2] )
+			irc_reply( irc, 482, "%s :Cannot change topic", cmd[1] );
+		else if( cmd[1] )
+			irc_topic( irc, cmd[1] );
+		else
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+	}
+	else if( g_strcasecmp( cmd[0], "AWAY" ) == 0 )
+	{
+		irc_away( irc, cmd[1] );
+	}
+	else if( g_strcasecmp( cmd[0], "WHOIS" ) == 0 )
+	{
+		if( cmd[1] )
+		{
+			irc_whois( irc, cmd[1] );
+		}
+		else
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+	}
+	else if( g_strcasecmp( cmd[0], "WHOWAS" ) == 0 )
+	{
+		/* For some reason irssi tries a whowas when whois fails. We can
+		   ignore this, but then the user never gets a "user not found"
+		   message from irssi which is a bit annoying. So just respond
+		   with not-found and irssi users will get better error messages */
+		
+		if( cmd[1] )
+		{
+			irc_reply( irc, 406, "%s :Nick does not exist", cmd[1] );
+			irc_reply( irc, 369, "%s :End of WHOWAS", cmd[1] );
+		}
+		else
+		{
+			irc_reply( irc, 461, "%s :Need more parameters", cmd[0] );
+		}
+	}
+	else if( ( g_strcasecmp( cmd[0], "NICKSERV" ) == 0 ) || ( g_strcasecmp( cmd[0], "NS" ) == 0 ) )
+	{
+		/* [SH] This aliases the NickServ command to PRIVMSG root */
+		/* [TV] This aliases the NS command to PRIVMSG root as well */
+		root_command( irc, cmd + 1 );
+	}
+	else if( g_strcasecmp( cmd[0], "MOTD" ) == 0 )
+	{
+		irc_motd( irc );
+	}
+	else if( g_strcasecmp( cmd[0], "PONG" ) == 0 )
+	{
+		/* We could check the value we get back from the user, but in
+		   fact we don't care, we're just happy he's still alive. */
+		irc->last_pong = gettime();
+		irc->pinging = 0;
+	}
+	else if( g_strcasecmp( cmd[0], "COMPLETIONS" ) == 0 )
+	{
+		user_t *u = user_find( irc, irc->mynick );
+		help_t *h;
+		set_t *s;
+		int i;
+		
+		irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "OK" );
+		
+		for( i = 0; commands[i].command; i ++ )
+			irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", commands[i].command );
+		
+		for( h = global.help; h; h = h->next )
+			irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS help ", h->string );
+		
+		for( s = irc->set; s; s = s->next )
+			irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS set ", s->key );
+		
+		irc_privmsg( irc, u, "NOTICE", irc->nick, "COMPLETIONS ", "END" );
+	}
+	else if( set_getint( irc, "debug" ) )
+	{
+		irc_usermsg( irc, "\002--- Unknown command:" );
+		for( i = 0; cmd[i]; i ++ ) irc_usermsg( irc, "%s", cmd[i] );
+		irc_usermsg( irc, "\002--------------------" );
+	}
+	
+	return( 1 );
+}
+
+void irc_reply( irc_t *irc, int code, char *format, ... )
+{
+	char text[IRC_MAX_LINE];
+	va_list params;
+	
+	va_start( params, format );
+	g_vsnprintf( text, IRC_MAX_LINE, format, params );
+	va_end( params );
+	irc_write( irc, ":%s %03d %s %s", irc->myhost, code, irc->nick?irc->nick:"*", text );
+	
+	return;
+}
+
+int irc_usermsg( irc_t *irc, char *format, ... )
+{
+	char text[1024];
+	va_list params;
+	char is_private = 0;
+	user_t *u;
+	
+	u = user_find( irc, irc->mynick );
+	if( u ) is_private = u->is_private;
+	
+	va_start( params, format );
+	g_vsnprintf( text, sizeof( text ), format, params );
+	va_end( params );
+	
+	return( irc_msgfrom( irc, u->nick, text ) );
+}
+
+void irc_write( irc_t *irc, char *format, ... ) 
+{
+	va_list params;
+
+	va_start( params, format );
+	irc_vawrite( irc, format, params );	
+	va_end( params );
+
+	return;
+
+}
+
+void irc_vawrite( irc_t *irc, char *format, va_list params )
+{
+	int size;
+	char line[IRC_MAX_LINE];
+	
+	if( irc->quit )
+		return;
+
+	g_vsnprintf( line, IRC_MAX_LINE - 3, format, params );
+
+	strip_newlines( line );
+	strcat( line, "\r\n" );
+
+	if( irc->sendbuffer != NULL ) {
+		size = strlen( irc->sendbuffer ) + strlen( line );
+#ifdef FLOOD_SEND
+		if( size > FLOOD_SEND_MAXBUFFER ) {
+			/* Die flooder, die! >:) */
+
+			g_free(irc->sendbuffer);
+			
+			/* We need the \r\n at the start because else we might append our string to a half
+			 * sent line. A bit hackish, but it works.
+			 */
+			irc->sendbuffer = g_strdup( "\r\nERROR :Sendq Exceeded\r\n" );
+			irc->quit = 1;
+			
+			return;
+		}
+#endif
+		irc->sendbuffer = g_renew ( char, irc->sendbuffer, size + 1 );
+		strcpy( ( irc->sendbuffer + strlen( irc->sendbuffer ) ), line );
+	}
+	else 
+		irc->sendbuffer = g_strdup(line);	
+	
+	if( irc->w_watch_source_id == 0 )
+	{
+		irc->w_watch_source_id = g_io_add_watch( irc->io_channel, G_IO_OUT, bitlbee_io_current_client_write, irc );
+	}
+	
+	return;
+}
+
+void irc_write_all( char *format, ... )
+{
+	va_list params;
+	GSList *temp;	
+
+	va_start( params, format );
+
+	temp = irc_connection_list;
+	while( temp!=NULL ) {
+		irc_vawrite( temp->data, format, params );
+		temp = temp->next;
+	}
+
+	va_end( params );
+	return;
+} 
+
+void irc_names( irc_t *irc, char *channel )
+{
+	user_t *u = irc->users;
 	char *s;
-
-	if (cmd[0] == NULL) {
-		return NULL;
-	}
-
-	len = 1;
-	for (i = 0; cmd[i]; i++) {
-		len += strlen(cmd[i]) + 1;
-	}
-
-	if (strchr(cmd[i - 1], ' ') != NULL) {
-		len++;
-	}
-
-	s = g_new0(char, len + 1);
-	for (i = 0; cmd[i]; i++) {
-		if (cmd[i + 1] == NULL && strchr(cmd[i], ' ') != NULL) {
-			strcat(s, ":");
-		}
-
-		strcat(s, cmd[i]);
-
-		if (cmd[i + 1]) {
-			strcat(s, " ");
-		}
-	}
-	strcat(s, "\r\n");
-
-	return s;
-}
-
-void irc_write(irc_t *irc, char *format, ...)
-{
-	va_list params;
-
-	va_start(params, format);
-	irc_vawrite(irc, format, params);
-	va_end(params);
-
-	return;
-}
-
-void irc_write_all(int now, char *format, ...)
-{
-	va_list params;
-	GSList *temp;
-
-	va_start(params, format);
-
-	temp = irc_connection_list;
-	while (temp != NULL) {
-		irc_t *irc = temp->data;
-
-		if (now) {
-			g_string_assign(irc->sendbuffer, "\r\n");
-		}
-		irc_vawrite(temp->data, format, params);
-		if (now) {
-			bitlbee_io_current_client_write(irc, irc->fd, B_EV_IO_WRITE);
-		}
-		temp = temp->next;
-	}
-
-	va_end(params);
-	return;
-}
-
-void irc_vawrite(irc_t *irc, char *format, va_list params)
-{
-	char line[IRC_MAX_LINE + 1];
-
-	/* Don't try to write anything new anymore when shutting down. */
-	if (irc->status & USTATUS_SHUTDOWN) {
-		return;
-	}
-
-	memset(line, 0, sizeof(line));
-	g_vsnprintf(line, IRC_MAX_LINE - 2, format, params);
-	strip_newlines(line);
-
-	if (irc->oconv != (GIConv) - 1) {
-		gsize bytes_read, bytes_written;
-		char *conv;
-
-		conv = g_convert_with_iconv(line, -1, irc->oconv,
-		                            &bytes_read, &bytes_written, NULL);
-
-		if (bytes_read == strlen(line)) {
-			strncpy(line, conv, IRC_MAX_LINE - 2);
-		}
-
-		g_free(conv);
-	}
-	g_strlcat(line, "\r\n", IRC_MAX_LINE + 1);
-
-	g_string_append(irc->sendbuffer, line);
-
-	if (irc->w_watch_source_id == 0) {
-		/* If the buffer is empty we can probably write, so call the write event handler
-		   immediately. If it returns TRUE, it should be called again, so add the event to
-		   the queue. If it's FALSE, we emptied the buffer and saved ourselves some work
-		   in the event queue. */
-		/* Really can't be done as long as the code doesn't do error checking very well:
-		if( bitlbee_io_current_client_write( irc, irc->fd, B_EV_IO_WRITE ) ) */
-
-		/* So just always do it via the event handler. */
-		irc->w_watch_source_id = b_input_add(irc->fd, B_EV_IO_WRITE, bitlbee_io_current_client_write, irc);
-	}
-
-	return;
-}
-
-/* Flush sendbuffer if you can. If it fails, fail silently and let some
-   I/O event handler clean up. */
-void irc_flush(irc_t *irc)
-{
-	ssize_t n;
-	size_t len = irc->sendbuffer->len;
-
-	if (len == 0) {
-		return;
-	}
-
-	if ((n = send(irc->fd, irc->sendbuffer->str, len, 0)) == len) {
-		g_string_truncate(irc->sendbuffer, 0);
-
-		b_event_remove(irc->w_watch_source_id);
-		irc->w_watch_source_id = 0;
-	} else if (n > 0) {
-		g_string_erase(irc->sendbuffer, 0, n);
-	}
-	/* Otherwise something went wrong and we don't currently care
-	   what the error was. We may or may not succeed later, we
-	   were just trying to flush the buffer immediately. */
-}
-
-/* Meant for takeover functionality. Transfer an IRC connection to a different
-   socket. */
-void irc_switch_fd(irc_t *irc, int fd)
-{
-	irc_write(irc, "ERROR :Transferring session to a new connection");
-	irc_flush(irc);   /* Write it now or forget about it forever. */
-
-	if (irc->sendbuffer) {
-		b_event_remove(irc->w_watch_source_id);
-		irc->w_watch_source_id = 0;
-		g_string_truncate(irc->sendbuffer, 0);
-	}
-
-	b_event_remove(irc->r_watch_source_id);
-	closesocket(irc->fd);
-	irc->fd = fd;
-	irc->r_watch_source_id = b_input_add(irc->fd, B_EV_IO_READ, bitlbee_io_current_client_read, irc);
-}
-
-void irc_sync(irc_t *irc)
-{
-	GSList *l;
-
-	irc_write(irc, ":%s!%s@%s MODE %s :+%s", irc->user->nick,
-	          irc->user->user, irc->user->host, irc->user->nick,
-	          irc->umode);
-
-	for (l = irc->channels; l; l = l->next) {
-		irc_channel_t *ic = l->data;
-		if (ic->flags & IRC_CHANNEL_JOINED) {
-			irc_send_join(ic, irc->user);
-		}
-	}
-
-	/* We may be waiting for a PONG from the previous client connection. */
-	irc->pinging = FALSE;
-}
-
-void irc_desync(irc_t *irc)
-{
-	GSList *l;
-
-	for (l = irc->channels; l; l = l->next) {
-		irc_channel_del_user(l->data, irc->user, IRC_CDU_KICK,
-		                     "Switching to old session");
-	}
-
-	irc_write(irc, ":%s!%s@%s MODE %s :-%s", irc->user->nick,
-	          irc->user->user, irc->user->host, irc->user->nick,
-	          irc->umode);
-}
-
-int irc_check_login(irc_t *irc)
-{
-	if (irc->user->user && irc->user->nick && !(irc->status & USTATUS_CAP_PENDING)) {
-		if (global.conf->authmode == AUTHMODE_CLOSED && !(irc->status & USTATUS_AUTHORIZED)) {
-			irc_send_num(irc, 464, ":This server is password-protected.");
-			return 0;
-		} else {
-			irc_channel_t *ic;
-			irc_user_t *iu = irc->user;
-
-			irc->user = irc_user_new(irc, iu->nick);
-			irc->user->user = iu->user;
-			irc->user->host = iu->host;
-			irc->user->fullname = iu->fullname;
-			irc->user->f = &irc_user_self_funcs;
-			g_free(iu->nick);
-			g_free(iu);
-
-			if (global.conf->runmode == RUNMODE_FORKDAEMON || global.conf->runmode == RUNMODE_DAEMON) {
-				ipc_to_master_str("CLIENT %s %s :%s\r\n", irc->user->host, irc->user->nick,
-				                  irc->user->fullname);
-			}
-
-			irc->status |= USTATUS_LOGGED_IN;
-
-			irc_send_login(irc);
-
-			irc->umode[0] = '\0';
-			irc_umode_set(irc, "+" UMODE, TRUE);
-
-			ic = irc->default_channel = irc_channel_new(irc, ROOT_CHAN);
-			irc_channel_set_topic(ic, CONTROL_TOPIC, irc->root);
-			set_setstr(&ic->set, "auto_join", "true");
-			irc_channel_auto_joins(irc, NULL);
-
-			irc->root->last_channel = irc->default_channel;
-
-			irc_rootmsg(irc,
-			            "Welcome to the BitlBee gateway!\n\n"
-			            "Running %s %s\n\n"
-			            "If you've never used BitlBee before, please do read the help "
-			            "information using the \x02help\x02 command. Lots of FAQs are "
-			            "answered there.\n"
-			            "If you already have an account on this server, just use the "
-			            "\x02identify\x02 command to identify yourself.",
-			            PACKAGE, BITLBEE_VERSION);
-
-			/* This is for bug #209 (use PASS to identify to NickServ). */
-			if (irc->password != NULL) {
-				char *send_cmd[] = { "identify", g_strdup(irc->password), NULL };
-
-				irc_setpass(irc, NULL);
-				root_command(irc, send_cmd);
-				g_free(send_cmd[1]);
-			}
-
-			return 1;
-		}
-	} else {
-		/* More information needed. */
-		return 0;
-	}
-}
-
-/* TODO: This is a mess, but this function is a bit too complicated to be
-   converted to something more generic. */
-void irc_umode_set(irc_t *irc, const char *s, gboolean allow_priv)
-{
-	/* allow_priv: Set to 0 if s contains user input, 1 if you want
-	   to set a "privileged" mode (+o, +R, etc). */
-	char m[128], st = 1;
-	const char *t;
+	int control = ( g_strcasecmp( channel, irc->channel ) == 0 );
+	struct conversation *c = NULL;
+	
+	if( !control )
+		c = conv_findchannel( channel );
+	
+	/* RFC's say there is no error reply allowed on NAMES, so when the
+	   channel is invalid, just give an empty reply. */
+	
+	if( control || c ) while( u )
+	{
+		if( u->online )
+		{
+			if( u->gc && control )
+			{
+				if( set_getint( irc, "away_devoice" ) && !u->away )
+					s = "+";
+				else
+					s = "";
+				
+				irc_reply( irc, 353, "@ %s :%s%s", channel, s, u->nick );
+			}
+			else if( !u->gc )
+			{
+				if( strcmp( u->nick, irc->mynick ) == 0 && ( strcmp( set_getstr( irc, "ops" ), "root" ) == 0 || strcmp( set_getstr( irc, "ops" ), "both" ) == 0 ) )
+					s = "@";
+				else if( strcmp( u->nick, irc->nick ) == 0 && ( strcmp( set_getstr( irc, "ops" ), "user" ) == 0 || strcmp( set_getstr( irc, "ops" ), "both" ) == 0 ) )
+					s = "@";
+				else
+					s = "";
+				
+				irc_reply( irc, 353, "@ %s :%s%s", channel, s, u->nick );
+			}
+		}
+		
+		u = u->next;
+	}
+	
+	/* For non-controlchannel channels (group conversations) only root and
+	   you are listed now. Time to show the channel people: */
+	if( !control && c )
+	{
+		GList *l;
+		
+		for( l = c->in_room; l; l = l->next )
+			if( ( u = user_findhandle( c->gc, l->data ) ) )
+				irc_reply( irc, 353, "@ %s :%s%s", channel, "", u->nick );
+	}
+	
+	irc_reply( irc, 366, "%s :End of /NAMES list", channel );
+}
+
+void irc_who( irc_t *irc, char *channel )
+{
+	user_t *u = irc->users;
+	struct conversation *c;
+	GList *l;
+	
+	if( !channel || *channel == '0' || *channel == '*' || !*channel )
+		while( u )
+		{
+			irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", u->online ? irc->channel : "*", u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
+			u = u->next;
+		}
+	else if( g_strcasecmp( channel, irc->channel ) == 0 )
+		while( u )
+		{
+			if( u->online )
+				irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
+			u = u->next;
+		}
+	else if( ( c = conv_findchannel( channel ) ) )
+		for( l = c->in_room; l; l = l->next )
+		{
+			if( ( u = user_findhandle( c->gc, l->data ) ) )
+				irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->away ? 'G' : 'H', u->realname );
+		}
+	else if( ( u = user_find( irc, channel ) ) )
+		irc_reply( irc, 352, "%s %s %s %s %s %c :0 %s", channel, u->user, u->host, irc->myhost, u->nick, u->online ? ( u->away ? 'G' : 'H' ) : 'G', u->realname );
+	
+	irc_reply( irc, 315, "%s :End of /WHO list.", channel?channel:"**" );
+}
+
+void irc_login( irc_t *irc )
+{
+	user_t *u;
+	
+	irc_reply( irc,   1, ":Welcome to the BitlBee gateway, %s", irc->nick );
+	irc_reply( irc,   2, ":Host %s is running BitlBee " BITLBEE_VERSION " " ARCH "/" CPU ".", irc->myhost );
+	irc_reply( irc,   3, ":%s", IRCD_INFO );
+	irc_reply( irc,   4, "%s %s %s %s", irc->myhost, BITLBEE_VERSION, UMODES, CMODES );
+	irc_motd( irc );
+	irc_umode_set( irc, irc->myhost, "+" UMODE );
+
+	u = user_add( irc, irc->mynick );
+	u->host = g_strdup( irc->myhost );
+	u->realname = g_strdup( ROOT_FN );
+	u->online = 1;
+	u->send_handler = root_command_string;
+	u->is_private = 0; /* [SH] The channel is root's personal playground. */
+	irc_spawn( irc, u );
+	
+	u = user_add( irc, NS_NICK );
+	u->host = g_strdup( irc->myhost );
+	u->realname = g_strdup( ROOT_FN );
+	u->online = 0;
+	u->send_handler = root_command_string;
+	u->is_private = 1; /* [SH] NickServ is not in the channel, so should always /query. */
+	
+	u = user_add( irc, irc->nick );
+	u->user = g_strdup( irc->user );
+	u->host = g_strdup( irc->host );
+	u->realname = g_strdup( irc->realname );
+	u->online = 1;
+//	u->send_handler = msg_echo;
+	irc_spawn( irc, u );
+	
+	irc_usermsg( irc, "Welcome to the BitlBee gateway!\n\nIf you've never used BitlBee before, please do read the help information using the help command. Lots of FAQ's are answered there." );
+	
+	irc->status = USTATUS_LOGGED_IN;
+}
+
+void irc_motd( irc_t *irc )
+{
+	int fd;
+	
+	fd = open( global.conf->motdfile, O_RDONLY );
+	if( fd == -1 )
+	{
+		irc_reply( irc, 422, ":We don't need MOTDs." );
+	}
+	else
+	{
+		char linebuf[80];	/* Max. line length for MOTD's is 79 chars. It's what most IRC networks seem to do. */
+		char *add, max;
+		int len;
+		
+		linebuf[79] = len = 0;
+		max = sizeof( linebuf ) - 1;
+		
+		irc_reply( irc, 375, ":- %s Message Of The Day - ", irc->myhost );
+		while( read( fd, linebuf + len, 1 ) == 1 )
+		{
+			if( linebuf[len] == '\n' || len == max )
+			{
+				linebuf[len] = 0;
+				irc_reply( irc, 372, ":- %s", linebuf );
+				len = 0;
+			}
+			else if( linebuf[len] == '%' )
+			{
+				read( fd, linebuf + len, 1 );
+				if( linebuf[len] == 'h' )
+					add = irc->myhost;
+				else if( linebuf[len] == 'v' )
+					add = BITLBEE_VERSION;
+				else if( linebuf[len] == 'n' )
+					add = irc->nick;
+				else
+					add = "%";
+				
+				strncpy( linebuf + len, add, max - len );
+				while( linebuf[++len] );
+			}
+			else if( len < max )
+			{
+				len ++;
+			}
+		}
+		irc_reply( irc, 376, ":End of MOTD" );
+		closesocket( fd );
+	}
+}
+
+void irc_topic( irc_t *irc, char *channel )
+{
+	if( g_strcasecmp( channel, irc->channel ) == 0 )
+	{
+		irc_reply( irc, 332, "%s :%s", channel, CONTROL_TOPIC );
+	}
+	else
+	{
+		struct conversation *c = conv_findchannel( channel );
+		
+		if( c )
+			irc_reply( irc, 332, "%s :BitlBee groupchat: \"%s\". Please keep in mind that root-commands won't work here. Have fun!", channel, c->title );
+		else
+			irc_reply( irc, 331, "%s :No topic for this channel" );
+	}
+}
+
+void irc_whois( irc_t *irc, char *nick )
+{
+	user_t *u = user_find( irc, nick );
+	
+	if( u )
+	{
+		irc_reply( irc, 311, "%s %s %s * :%s", u->nick, u->user, u->host, u->realname );
+		
+		if( u->gc )
+			irc_reply( irc, 312, "%s %s.%s :%s network", u->nick, u->gc->user->username,
+			           *u->gc->user->proto_opt[0] ? u->gc->user->proto_opt[0] : "", proto_name[u->gc->user->protocol] );
+		else
+			irc_reply( irc, 312, "%s %s :%s", u->nick, irc->myhost, IRCD_INFO );
+		
+		if( !u->online )
+			irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
+		else if( u->away )
+			irc_reply( irc, 301, "%s :%s", u->nick, u->away );
+		
+		irc_reply( irc, 318, "%s :End of /WHOIS list", nick );
+	}
+	else
+	{
+		irc_reply( irc, 401, "%s :Nick does not exist", nick );
+	}
+}
+
+
+void irc_umode_set( irc_t *irc, char *who, char *s )
+{
+	char m[256], st = 1, *t;
 	int i;
-	char changes[512], st2 = 2;
-	char badflag = 0;
-
-	memset(m, 0, sizeof(m));
-
-	/* Keep track of which modes are enabled in this array. */
-	for (t = irc->umode; *t; t++) {
-		if (*t < sizeof(m)) {
-			m[(int) *t] = 1;
-		}
-	}
-
-	i = 0;
-	for (t = s; *t && i < sizeof(changes) - 3; t++) {
-		if (*t == '+' || *t == '-') {
+	
+	memset( m, 0, sizeof( m ) );
+	
+	for( t = irc->umode; *t; t ++ )
+		m[(int)*t] = 1;
+	
+	for( t = s; *t; t ++ )
+	{
+		if( *t == '+' || *t == '-' )
 			st = *t == '+';
-		} else if ((st == 0 && (!strchr(UMODES_KEEP, *t) || allow_priv)) ||
-		           (st == 1 && strchr(UMODES, *t)) ||
-		           (st == 1 && allow_priv && strchr(UMODES_PRIV, *t))) {
-			if (m[(int) *t] != st) {
-				/* If we're actually making a change, remember this
-				   for the response. */
-				if (st != st2) {
-					st2 = st, changes[i++] = st ? '+' : '-';
+		else
+			m[(int)*t] = st;
+	}
+	
+	memset( irc->umode, 0, sizeof( irc->umode ) );
+	
+	for( i = 0; i < 256 && strlen( irc->umode ) < ( sizeof( irc->umode ) - 1 ); i ++ )
+		if( m[i] && strchr( UMODES, i ) )
+			irc->umode[strlen(irc->umode)] = i;
+	
+	irc_reply( irc, 221, "+%s", irc->umode );
+}
+
+int irc_away( irc_t *irc, char *away )
+{
+	user_t *u = user_find( irc, irc->nick );
+	GSList *c = get_connections();
+	
+	if( !u ) return( 0 );
+	
+	if( away && *away )
+	{
+		int i, j;
+		
+		/* Copy away string, but skip control chars. Mainly because
+		   Jabber really doesn't like them. */
+		u->away = g_malloc( strlen( away ) + 1 );
+		for( i = j = 0; away[i]; i ++ )
+			if( ( u->away[j] = away[i] ) >= ' ' )
+				j ++;
+		u->away[j] = 0;
+		
+		irc_reply( irc, 306, ":You're now away: %s", u->away );
+		/* irc_umode_set( irc, irc->myhost, "+a" ); */
+	}
+	else
+	{
+		if( u->away ) g_free( u->away );
+		u->away = NULL;
+		/* irc_umode_set( irc, irc->myhost, "-a" ); */
+		irc_reply( irc, 305, ":Welcome back" );
+	}
+	
+	while( c )
+	{
+		if( ((struct gaim_connection *)c->data)->flags & OPT_LOGGED_IN )
+			proto_away( c->data, u->away );
+		
+		c = c->next;
+	}
+	
+	return( 1 );
+}
+
+void irc_spawn( irc_t *irc, user_t *u )
+{
+	irc_join( irc, u, irc->channel );
+}
+
+void irc_join( irc_t *irc, user_t *u, char *channel )
+{
+	char *nick;
+	
+	if( ( g_strcasecmp( channel, irc->channel ) != 0 ) || user_find( irc, irc->nick ) )
+		irc_write( irc, ":%s!%s@%s JOIN :%s", u->nick, u->user, u->host, channel );
+	
+	if( nick_cmp( u->nick, irc->nick ) == 0 )
+	{
+		irc_write( irc, ":%s MODE %s +%s", irc->myhost, channel, CMODE );
+		irc_names( irc, channel );
+		irc_topic( irc, channel );
+	}
+	
+	nick = g_strdup( u->nick );
+	nick_lc( nick );
+	if( g_hash_table_lookup( irc->watches, nick ) )
+	{
+		irc_reply( irc, 600, "%s %s %s %d :%s", u->nick, u->user, u->host, time( NULL ), "logged online" );
+	}
+	g_free( nick );
+}
+
+void irc_part( irc_t *irc, user_t *u, char *channel )
+{
+	irc_write( irc, ":%s!%s@%s PART %s :%s", u->nick, u->user, u->host, channel, "" );
+}
+
+void irc_kick( irc_t *irc, user_t *u, char *channel, user_t *kicker )
+{
+	irc_write( irc, ":%s!%s@%s KICK %s %s :%s", kicker->nick, kicker->user, kicker->host, channel, u->nick, "" );
+}
+
+void irc_kill( irc_t *irc, user_t *u )
+{
+	char *nick;
+	
+	irc_write( irc, ":%s!%s@%s QUIT :%s", u->nick, u->user, u->host, "Leaving..." );
+	
+	nick = g_strdup( u->nick );
+	nick_lc( nick );
+	if( g_hash_table_lookup( irc->watches, nick ) )
+	{
+		irc_reply( irc, 601, "%s %s %s %d :%s", u->nick, u->user, u->host, time( NULL ), "logged offline" );
+	}
+	g_free( nick );
+}
+
+void irc_invite( irc_t *irc, char *nick, char *channel )
+{
+	struct conversation *c = conv_findchannel( channel );
+	user_t *u = user_find( irc, nick );
+	
+	if( u && c && ( u->gc == c->gc ) )
+		if( c->gc && c->gc->prpl && c->gc->prpl->chat_invite )
+		{
+			c->gc->prpl->chat_invite( c->gc, c->id, "", u->handle );
+			irc_reply( irc, 341, "%s %s", nick, channel );
+			return;
+		}
+	
+	irc_reply( irc, 482, "%s :Invite impossible; User/Channel non-existent or incompatible", channel );
+}
+
+int irc_send( irc_t *irc, char *nick, char *s, int flags )
+{
+	struct conversation *c = NULL;
+	user_t *u = NULL;
+	
+	if( *nick == '#' )
+	{
+		if( !( c = conv_findchannel( nick ) ) )
+		{
+			irc_reply( irc, 403, "%s :Channel does not exist", nick );
+			return( 0 );
+		}
+	}
+	else
+	{
+		u = user_find( irc, nick );
+		
+		if( !u )
+		{
+			if( irc->is_private )
+				irc_reply( irc, 401, "%s :Nick does not exist", nick );
+			else
+				irc_usermsg( irc, "Nick `%s' does not exist!", nick );
+			return( 0 );
+		}
+	}
+	
+	if( *s == 1 && s[strlen(s)-1] == 1 )
+	{
+		if( g_strncasecmp( s + 1, "ACTION", 6 ) == 0 )
+		{
+			if( s[7] == ' ' ) s ++;
+			s += 3;
+			*(s++) = '/';
+			*(s++) = 'm';
+			*(s++) = 'e';
+			*(s++) = ' ';
+			s -= 4;
+			s[strlen(s)-1] = 0;
+		}
+		else if( g_strncasecmp( s + 1, "VERSION", 7 ) == 0 )
+		{
+			u = user_find( irc, irc->mynick );
+			irc_privmsg( irc, u, "NOTICE", irc->nick, "", "\001VERSION BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\001" );
+			return( 1 );
+		}
+		else if( g_strncasecmp( s + 1, "PING", 4 ) == 0 )
+		{
+			u = user_find( irc, irc->mynick );
+			irc_privmsg( irc, u, "NOTICE", irc->nick, "", s );
+			return( 1 );
+		}
+		else if( g_strncasecmp( s + 1, "TYPING", 6 ) == 0 )
+		{
+			if( u && u->gc && u->gc->prpl->send_typing && strlen( s ) >= 10 )
+			{
+				time_t current_typing_notice = time( NULL );
+				
+				if( current_typing_notice - u->last_typing_notice >= 5 )
+				{
+					u->gc->prpl->send_typing( u->gc, u->handle, s[8] == '1' );
+					u->last_typing_notice = current_typing_notice;
 				}
-				changes[i++] = *t;
-			}
-			m[(int) *t] = st;
-		} else {
-			badflag = 1;
-		}
-	}
-	changes[i] = '\0';
-
-	/* Convert the m array back into an umode string. */
-	memset(irc->umode, 0, sizeof(irc->umode));
-	for (i = 'A'; i <= 'z' && strlen(irc->umode) < (sizeof(irc->umode) - 1); i++) {
-		if (m[i]) {
-			irc->umode[strlen(irc->umode)] = i;
-		}
-	}
-
-	if (badflag) {
-		irc_send_num(irc, 501, ":Unknown MODE flag");
-	}
-	if (*changes) {
-		irc_write(irc, ":%s!%s@%s MODE %s :%s", irc->user->nick,
-		          irc->user->user, irc->user->host, irc->user->nick,
-		          changes);
-	}
+			}
+			return( 1 );
+		}
+		else
+		{
+			irc_usermsg( irc, "Non-ACTION CTCP's aren't supported" );
+			return( 0 );
+		}
+	}
+	
+	if( u )
+	{
+		/* For the next message, we probably do have to send new notices... */
+		u->last_typing_notice = 0;
+		u->is_private = irc->is_private;
+		
+		if( u->is_private )
+		{
+			if( !u->online )
+				irc_reply( irc, 301, "%s :%s", u->nick, "User is offline" );
+			else if( u->away )
+				irc_reply( irc, 301, "%s :%s", u->nick, u->away );
+		}
+		
+		if( u->send_handler )
+			return( u->send_handler( irc, u, s, flags ) );
+	}
+	else if( c && c->gc && c->gc->prpl )
+	{
+		return( serv_send_chat( irc, c->gc, c->id, s ) );
+	}
+	
+	return( 0 );
+}
+
+gboolean buddy_send_handler_delayed( gpointer data )
+{
+	user_t *u = data;
+	
+	u->sendbuf[u->sendbuf_len-2] = 0; /* Cut off the last newline */
+	serv_send_im( u->gc->irc, u, u->sendbuf, u->sendbuf_flags );
+	
+	g_free( u->sendbuf );
+	u->sendbuf = NULL;
+	u->sendbuf_len = 0;
+	u->sendbuf_timer = 0;
+	u->sendbuf_flags = 0;
+	
+	return( FALSE );
+}
+
+int buddy_send_handler( irc_t *irc, user_t *u, char *msg, int flags )
+{
+	if( !u || !u->gc ) return( 0 );
+	
+	if( set_getint( irc, "buddy_sendbuffer" ) && set_getint( irc, "buddy_sendbuffer_delay" ) > 0 )
+	{
+		if( u->sendbuf_len > 0 && u->sendbuf_flags != flags)
+		{
+			//Flush the buffer
+			g_source_remove( u->sendbuf_timer );
+			buddy_send_handler_delayed( u );
+		}
+
+		if( u->sendbuf_len == 0 )
+		{
+			u->sendbuf_len = strlen( msg ) + 2;
+			u->sendbuf = g_new (char, u->sendbuf_len );
+			u->sendbuf[0] = 0;
+			u->sendbuf_flags = flags;
+		}
+		else
+		{
+			u->sendbuf_len += strlen( msg ) + 1;
+			u->sendbuf = g_renew ( char, u->sendbuf, u->sendbuf_len );
+		}
+		
+		strcat( u->sendbuf, msg );
+		strcat( u->sendbuf, "\n" );
+		
+		if( u->sendbuf_timer > 0 )
+			g_source_remove( u->sendbuf_timer );
+		u->sendbuf_timer = g_timeout_add( set_getint( irc, "buddy_sendbuffer_delay" ) * 1000,
+		                                  buddy_send_handler_delayed, u );
+		
+		return( 1 );
+	}
+	else
+	{
+		return( serv_send_im( irc, u, msg, flags ) );
+	}
+}
+
+int irc_privmsg( irc_t *irc, user_t *u, char *type, char *to, char *prefix, char *msg )
+{
+	char last = 0;
+	char *s = msg, *line = msg;
+	
+	/* The almighty linesplitter .. woohoo!! */
+	while( !last )
+	{
+		if( *s == '\r' && *(s+1) == '\n' )
+			*(s++) = 0;
+		if( *s == '\n' )
+		{
+			last = s[1] == 0;
+			*s = 0;
+		}
+		else
+		{
+			last = s[0] == 0;
+		}
+		if( *s == 0 )
+		{
+			if( g_strncasecmp( line, "/me ", 4 ) == 0 && ( !prefix || !*prefix ) && g_strcasecmp( type, "PRIVMSG" ) == 0 )
+			{
+				irc_write( irc, ":%s!%s@%s %s %s :\001ACTION %s\001", u->nick, u->user, u->host,
+				           type, to, line + 4 );
+			}
+			else
+			{
+				irc_write( irc, ":%s!%s@%s %s %s :%s%s", u->nick, u->user, u->host,
+				           type, to, prefix, line );
+			}
+			line = s + 1;
+		}
+		s ++;
+	}
+	
+	return( 1 );
+}
+
+int irc_msgfrom( irc_t *irc, char *nick, char *msg )
+{
+	user_t *u = user_find( irc, nick );
+	static char *prefix = NULL;
+	
+	if( !u ) return( 0 );
+	if( prefix && *prefix ) g_free( prefix );
+	
+	if( !u->is_private && nick_cmp( u->nick, irc->mynick ) != 0 )
+	{
+		int len = strlen( irc->nick) + 3;
+		prefix = g_new (char, len );
+		g_snprintf( prefix, len, "%s%s", irc->nick, set_getstr( irc, "to_char" ) );
+		prefix[len-1] = 0;
+	}
+	else
+	{
+		prefix = "";
+	}
+	
+	return( irc_privmsg( irc, u, "PRIVMSG", u->is_private ? irc->nick : irc->channel, prefix, msg ) );
+}
+
+int irc_noticefrom( irc_t *irc, char *nick, char *msg )
+{
+	user_t *u = user_find( irc, nick );
+	
+	if( u )
+		return( irc_privmsg( irc, u, "NOTICE", irc->nick, "", msg ) );
+	else
+		return( 0 );
 }
 
@@ -864,124 +1540,34 @@
    pongs from the user. When not connected yet, we don't ping but drop the
    connection when the user fails to connect in IRC_LOGIN_TIMEOUT secs. */
-static gboolean irc_userping(gpointer _irc, gint fd, b_input_condition cond)
-{
-	double now = gettime();
+static gboolean irc_userping( gpointer _irc )
+{
 	irc_t *irc = _irc;
-	int fail = 0;
-
-	if (!(irc->status & USTATUS_LOGGED_IN)) {
-		if (now > (irc->last_pong + IRC_LOGIN_TIMEOUT)) {
-			fail = now - irc->last_pong;
-		}
-	} else {
-		if (now > (irc->last_pong + global.conf->ping_timeout)) {
-			fail = now - irc->last_pong;
-		} else {
-			irc_write(irc, "PING :%s", IRC_PING_STRING);
-		}
-	}
-
-	if (fail > 0) {
-		irc_abort(irc, 0, "Ping Timeout: %d seconds", fail);
+	int rv = 0;
+	
+	if( irc->status < USTATUS_LOGGED_IN )
+	{
+		if( gettime() > ( irc->last_pong + IRC_LOGIN_TIMEOUT ) )
+			rv = gettime() - irc->last_pong;
+	}
+	else
+	{
+		if( ( gettime() > ( irc->last_pong + global.conf->ping_interval ) ) && !irc->pinging )
+		{
+			irc_write( irc, "PING :%s", IRC_PING_STRING );
+			irc->pinging = 1;
+		}
+		else if( gettime() > ( irc->last_pong + global.conf->ping_timeout ) )
+		{
+			rv = gettime() - irc->last_pong;
+		}
+	}
+	
+	if( rv > 0 )
+	{
+		irc_write( irc, "ERROR :Closing Link: Ping Timeout: %d seconds", rv );
+		irc_free( irc );
 		return FALSE;
 	}
-
+	
 	return TRUE;
 }
-
-static char *set_eval_charset(set_t *set, char *value)
-{
-	irc_t *irc = (irc_t *) set->data;
-	char *test;
-	gsize test_bytes = 0;
-	GIConv ic, oc;
-
-	if (g_strcasecmp(value, "none") == 0) {
-		value = g_strdup("utf-8");
-	}
-
-	if ((oc = g_iconv_open(value, "utf-8")) == (GIConv) - 1) {
-		return NULL;
-	}
-
-	/* Do a test iconv to see if the user picked an IRC-compatible
-	   charset (for example utf-16 goes *horribly* wrong). */
-	if ((test = g_convert_with_iconv(" ", 1, oc, NULL, &test_bytes, NULL)) == NULL ||
-	    test_bytes > 1) {
-		g_free(test);
-		g_iconv_close(oc);
-		irc_rootmsg(irc, "Unsupported character set: The IRC protocol "
-		            "only supports 8-bit character sets.");
-		return NULL;
-	}
-	g_free(test);
-
-	if ((ic = g_iconv_open("utf-8", value)) == (GIConv) - 1) {
-		g_iconv_close(oc);
-		return NULL;
-	}
-
-	if (irc->iconv != (GIConv) - 1) {
-		g_iconv_close(irc->iconv);
-	}
-	if (irc->oconv != (GIConv) - 1) {
-		g_iconv_close(irc->oconv);
-	}
-
-	irc->iconv = ic;
-	irc->oconv = oc;
-
-	return value;
-}
-
-/* Mostly meant for upgrades. If one of these is set to the non-default,
-   set show_users of all channels to something with the same effect. */
-static char *set_eval_bw_compat(set_t *set, char *value)
-{
-	irc_t *irc = set->data;
-	char *val;
-	GSList *l;
-
-	irc_rootmsg(irc, "Setting `%s' is obsolete, use the `show_users' "
-	            "channel setting instead.", set->key);
-
-	if (strcmp(set->key, "away_devoice") == 0 && !bool2int(value)) {
-		val = "online,special%,away";
-	} else if (strcmp(set->key, "show_offline") == 0 && bool2int(value)) {
-		val = "online@,special%,away+,offline";
-	} else {
-		val = "online+,special%,away";
-	}
-
-	for (l = irc->channels; l; l = l->next) {
-		irc_channel_t *ic = l->data;
-		/* No need to check channel type, if the setting doesn't exist it
-		   will just be ignored. */
-		set_setstr(&ic->set, "show_users", val);
-	}
-
-	return SET_INVALID;
-}
-
-static char *set_eval_utf8_nicks(set_t *set, char *value)
-{
-	irc_t *irc = set->data;
-	gboolean val = bool2int(value);
-
-	/* Do *NOT* unset this flag in the middle of a session. There will
-	   be UTF-8 nicks around already so if we suddenly disable support
-	   for them, various functions might behave strangely. */
-	if (val) {
-		irc->status |= IRC_UTF8_NICKS;
-	} else if (irc->status & IRC_UTF8_NICKS) {
-		irc_rootmsg(irc, "You need to reconnect to BitlBee for this "
-		            "change to take effect.");
-	}
-
-	return set_eval_bool(set, value);
-}
-
-void register_irc_plugin(const struct irc_plugin *p)
-{
-	irc_plugins = g_slist_prepend(irc_plugins, (gpointer) p);
-}
