summary refs log tree commit diff stats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/cfgfiles.c4
-rw-r--r--src/common/dcc.c9
-rw-r--r--src/common/hexchat.c1
-rw-r--r--src/common/hexchat.h8
-rw-r--r--src/common/inbound.c159
-rw-r--r--src/common/inbound.h2
-rw-r--r--src/common/outbound.c141
-rw-r--r--src/common/plugin.c5
-rw-r--r--src/common/proto-irc.c276
-rw-r--r--src/common/server.c4
-rw-r--r--src/common/servlist.c574
-rw-r--r--src/common/servlist.h64
-rw-r--r--src/common/textevents.in10
-rw-r--r--src/common/util.c94
-rw-r--r--src/common/util.h1
15 files changed, 867 insertions, 485 deletions
diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c
index a7949d3f..c3cad2aa 100644
--- a/src/common/cfgfiles.c
+++ b/src/common/cfgfiles.c
@@ -536,7 +536,9 @@ const struct prefs vars[] =
 	{"irc_whois_front", P_OFFINT (hex_irc_whois_front), TYPE_BOOL},
 
 	{"net_auto_reconnect", P_OFFINT (hex_net_auto_reconnect), TYPE_BOOL},
+#ifndef WIN32	/* FIXME fix reconnect crashes and remove this ifdef! */
 	{"net_auto_reconnectonfail", P_OFFINT (hex_net_auto_reconnectonfail), TYPE_BOOL},
+#endif
 	{"net_bind_host", P_OFFSET (hex_net_bind_host), TYPE_STR},
 	{"net_ping_timeout", P_OFFINT (hex_net_ping_timeout), TYPE_INT},
 	{"net_proxy_auth", P_OFFINT (hex_net_proxy_auth), TYPE_BOOL},
@@ -729,7 +731,7 @@ load_config (void)
 	prefs.hex_gui_win_width = 640;
 	prefs.hex_input_balloon_time = 20;
 	prefs.hex_irc_ban_type = 2;
-	prefs.hex_irc_join_delay = 3;
+	prefs.hex_irc_join_delay = 5;
 	prefs.hex_net_reconnect_delay = 10;
 	prefs.hex_notify_timeout = 15;
 	prefs.hex_text_max_indent = 256;
diff --git a/src/common/dcc.c b/src/common/dcc.c
index 9014296e..4980cabc 100644
--- a/src/common/dcc.c
+++ b/src/common/dcc.c
@@ -66,15 +66,6 @@
 #define BIG_STR_TO_INT(x) strtoul(x,NULL,10)
 #endif
 
-/* This is practically copy-paste from gstdio.h.
- * GStatBuf was added in 2.26. On Win32 we already use that,
- * so we only gotta check this on Unix */
-#ifndef WIN32
-#if !GLIB_CHECK_VERSION(2,26,0)
-typedef struct stat GStatBuf;
-#endif
-#endif
-
 static char *dcctypes[] = { "SEND", "RECV", "CHAT", "CHAT" };
 
 struct dccstat_info dccstat[] = {
diff --git a/src/common/hexchat.c b/src/common/hexchat.c
index 72b97e3a..c879af9d 100644
--- a/src/common/hexchat.c
+++ b/src/common/hexchat.c
@@ -706,6 +706,7 @@ static char defaultconf_commands[] =
 	"NAME KILL\n"			"CMD quote KILL %2 :&3\n\n"\
 	"NAME LEAVE\n"			"CMD part &2\n\n"\
 	"NAME M\n"				"CMD msg &2\n\n"\
+	"NAME OMSG\n"			"CMD msg @%c &2\n\n"\
 	"NAME ONOTICE\n"		"CMD notice @%c &2\n\n"\
 	"NAME RAW\n"			"CMD quote &2\n\n"\
 	"NAME SERVHELP\n"		"CMD quote HELP\n\n"\
diff --git a/src/common/hexchat.h b/src/common/hexchat.h
index dd30dce6..5fdbfc45 100644
--- a/src/common/hexchat.h
+++ b/src/common/hexchat.h
@@ -479,7 +479,7 @@ typedef struct server
 	void (*p_ns_identify)(struct server *, char *pass);
 	void (*p_ns_ghost)(struct server *, char *usname, char *pass);
 	void (*p_join)(struct server *, char *channel, char *key);
-	void (*p_join_list)(struct server *, GSList *channels, GSList *keys);
+	void (*p_join_list)(struct server *, GSList *favorites);
 	void (*p_login)(struct server *, char *user, char *realname);
 	void (*p_join_info)(struct server *, char *channel);
 	void (*p_mode)(struct server *, char *target, char *mode);
@@ -527,14 +527,12 @@ typedef struct server
 	char hostname[128];				/* real ip number */
 	char servername[128];			/* what the server says is its name */
 	char password[86];
-	char sasluser[32];				/* this is just a buffer for network->user */
-	char saslpassword[86];			/* we could reuse password but then we couldn't guarantee NickServ doesn't register first */
 	char nick[NICKLEN];
 	char linebuf[2048];				/* RFC says 512 chars including \r\n */
 	char *last_away_reason;
 	int pos;								/* current position in linebuf */
 	int nickcount;
-	int nickservtype;					/* 0=/MSG nickserv 1=/NICKSERV 2=/NS */
+	int loginmethod;					/* see login_types[] */
 
 	char *chantypes;					/* for 005 numeric - free me */
 	char *chanmodes;					/* for 005 numeric - free me */
@@ -568,7 +566,7 @@ typedef struct server
 	time_t away_time;					/* when we were marked away */
 
 	char *encoding;					/* NULL for system */
-	char *autojoin;			/* list of channels & keys to join */
+	GSList *favlist;			/* list of channels & keys to join */
 
 	unsigned int motd_skipped:1;
 	unsigned int connected:1;
diff --git a/src/common/inbound.c b/src/common/inbound.c
index 29eaf754..da2cb34c 100644
--- a/src/common/inbound.c
+++ b/src/common/inbound.c
@@ -1069,35 +1069,23 @@ inbound_nameslist_end (server *serv, char *chan)
 static gboolean
 check_autojoin_channels (server *serv)
 {
-	char *po;
+	int i = 0;
 	session *sess;
 	GSList *list = sess_list;
-	int i = 0;
-	GSList *channels, *keys;
+	GSList *sess_channels = NULL;			/* joined channels that are not in the favorites list */
+	favchannel *fav;
 
-	/* shouldnt really happen, the io tag is destroyed in server.c */
+	/* shouldn't really happen, the io tag is destroyed in server.c */
 	if (!is_server (serv))
-		return FALSE;
-
-	/* send auto join list */
-	if (serv->autojoin)
 	{
-		joinlist_split (serv->autojoin, &channels, &keys);
-		serv->p_join_list (serv, channels, keys);
-		joinlist_free (channels, keys);
-
-		free (serv->autojoin);
-		serv->autojoin = NULL;
-		i++;
+		return FALSE;
 	}
 
-	/* this is really only for re-connects when you
-    * join channels not in the auto-join list. */
-	channels = NULL;
-	keys = NULL;
+	/* If there's a session (i.e. this is a reconnect), autojoin to everything that was open previously. */
 	while (list)
 	{
 		sess = list->data;
+
 		if (sess->server == serv)
 		{
 			if (sess->willjoinchannel[0] != 0)
@@ -1105,34 +1093,50 @@ check_autojoin_channels (server *serv)
 				strcpy (sess->waitchannel, sess->willjoinchannel);
 				sess->willjoinchannel[0] = 0;
 
-				po = strchr (sess->waitchannel, ',');
-				if (po)
-					*po = 0;
-				po = strchr (sess->waitchannel, ' ');
-				if (po)
-					*po = 0;
+				fav = servlist_favchan_find (serv->network, sess->waitchannel, NULL);	/* Is this channel in our favorites? */
 
-				/* There can be no gap between keys, list keyed chans first. */
-				if (sess->channelkey[0] != 0)
+				/* session->channelkey is initially unset for channels joined from the favorites. You have to fill them up manually from favorites settings. */
+				if (fav)
 				{
-					channels = g_slist_prepend (channels, g_strdup (sess->waitchannel));
-					keys = g_slist_prepend (keys, g_strdup (sess->channelkey));
+					/* session->channelkey is set if there was a key change during the session. In that case, use the session key, not the one from favorites. */
+					if (fav->key && !strlen (sess->channelkey))
+					{
+						safe_strcpy (sess->channelkey, fav->key, sizeof (sess->channelkey));
+					}
+				}
+
+				/* for easier checks, ensure that favchannel->key is just NULL when session->channelkey is empty i.e. '' */
+				if (strlen (sess->channelkey))
+				{
+					sess_channels = servlist_favchan_listadd (sess_channels, sess->waitchannel, sess->channelkey);
 				}
 				else
 				{
-					channels = g_slist_append (channels, g_strdup (sess->waitchannel));
-					keys = g_slist_append (keys, g_strdup (sess->channelkey));
+					sess_channels = servlist_favchan_listadd (sess_channels, sess->waitchannel, NULL);
 				}
 				i++;
 			}
 		}
+
 		list = list->next;
 	}
 
-	if (channels)
+	if (sess_channels)
 	{
-		serv->p_join_list (serv, channels, keys);
-		joinlist_free (channels, keys);
+		serv->p_join_list (serv, sess_channels);
+		g_slist_free_full (sess_channels, (GDestroyNotify) servlist_favchan_free);
+	}
+	else
+	{
+		/* If there's no session, just autojoin to favorites. */
+		if (serv->favlist)
+		{
+			serv->p_join_list (serv, serv->favlist);
+			i++;
+
+			/* FIXME this is not going to work and is not needed either. server_free() does the job already. */
+			/* g_slist_free_full (serv->favlist, (GDestroyNotify) servlist_favchan_free); */
+		}
 	}
 
 	serv->joindelay_tag = 0;
@@ -1141,7 +1145,7 @@ check_autojoin_channels (server *serv)
 }
 
 void
-inbound_next_nick (session *sess, char *nick)
+inbound_next_nick (session *sess, char *nick, int error)
 {
 	char *newnick;
 	server *serv = sess->server;
@@ -1156,14 +1160,30 @@ inbound_next_nick (session *sess, char *nick)
 		net = serv->network;
 		/* use network specific "Second choice"? */
 		if (net && !(net->flags & FLAG_USE_GLOBAL) && net->nick2)
+		{
 			newnick = net->nick2;
+		}
 		serv->p_change_nick (serv, newnick);
-		EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, newnick, NULL, NULL, 0);
+		if (error)
+		{
+			EMIT_SIGNAL (XP_TE_NICKERROR, sess, nick, newnick, NULL, NULL, 0);
+		}
+		else
+		{
+			EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, newnick, NULL, NULL, 0);
+		}
 		break;
 
 	case 3:
 		serv->p_change_nick (serv, prefs.hex_irc_nick3);
-		EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, prefs.hex_irc_nick3, NULL, NULL, 0);
+		if (error)
+		{
+			EMIT_SIGNAL (XP_TE_NICKERROR, sess, nick, prefs.hex_irc_nick3, NULL, NULL, 0);
+		}
+		else
+		{
+			EMIT_SIGNAL (XP_TE_NICKCLASH, sess, nick, prefs.hex_irc_nick3, NULL, NULL, 0);
+		}
 		break;
 
 	default:
@@ -1367,9 +1387,31 @@ inbound_exec_eom_cmd (char *str, void *sess)
 	return 1;
 }
 
+static int
+inbound_nickserv_login (server *serv)
+{
+	/* this could grow ugly, but let's hope there won't be new NickServ types */
+	switch (serv->loginmethod)
+	{
+		case LOGIN_MSG_NICKSERV:
+		case LOGIN_NICKSERV:
+		case LOGIN_CHALLENGEAUTH:
+#if 0
+		case LOGIN_NS:
+		case LOGIN_MSG_NS:
+		case LOGIN_AUTH:
+#endif
+			return 1;
+		default:
+			return 0;
+	}
+}
+
 void
 inbound_login_end (session *sess, char *text)
 {
+	GSList *cmdlist;
+	commandentry *cmd;
 	server *serv = sess->server;
 
 	if (!serv->end_of_motd)
@@ -1384,35 +1426,50 @@ inbound_login_end (session *sess, char *text)
 		if (serv->network)
 		{
 			/* there may be more than 1, separated by \n */
-			if (((ircnet *)serv->network)->command)
-				token_foreach (((ircnet *)serv->network)->command, '\n',
-									inbound_exec_eom_cmd, sess);
+
+			cmdlist = ((ircnet *)serv->network)->commandlist;
+			while (cmdlist)
+			{
+				cmd = cmdlist->data;
+				inbound_exec_eom_cmd (cmd->command, sess);
+				cmdlist = cmdlist->next;
+			}
 
 			/* send nickserv password */
-			if (((ircnet *)serv->network)->nickserv)
-				serv->p_ns_identify (serv, ((ircnet *)serv->network)->nickserv);
+			if (((ircnet *)serv->network)->pass && inbound_nickserv_login (serv))
+			{
+				serv->p_ns_identify (serv, ((ircnet *)serv->network)->pass);
+			}
 		}
 
-		/* send JOIN now or wait? */
-		if (serv->network && ((ircnet *)serv->network)->nickserv &&
-			 prefs.hex_irc_join_delay)
-			serv->joindelay_tag = fe_timeout_add (prefs.hex_irc_join_delay * 1000,
-															  check_autojoin_channels, serv);
+		/* wait for join if command or nickserv set */
+		if (serv->network && prefs.hex_irc_join_delay
+			&& ((((ircnet *)serv->network)->pass && inbound_nickserv_login (serv))
+				|| ((ircnet *)serv->network)->commandlist))
+		{
+			serv->joindelay_tag = fe_timeout_add (prefs.hex_irc_join_delay * 1000, check_autojoin_channels, serv);
+		}
 		else
+		{
 			check_autojoin_channels (serv);
+		}
+
 		if (serv->supports_watch || serv->supports_monitor)
+		{
 			notify_send_watches (serv);
+		}
+
 		serv->end_of_motd = TRUE;
 	}
+
 	if (prefs.hex_irc_skip_motd && !serv->motd_skipped)
 	{
 		serv->motd_skipped = TRUE;
-		EMIT_SIGNAL (XP_TE_MOTDSKIP, serv->server_session, NULL, NULL,
-						 NULL, NULL, 0);
+		EMIT_SIGNAL (XP_TE_MOTDSKIP, serv->server_session, NULL, NULL, NULL, NULL, 0);
 		return;
 	}
-	EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL,
-					 NULL, NULL, 0);
+
+	EMIT_SIGNAL (XP_TE_MOTD, serv->server_session, text, NULL, NULL, NULL, 0);
 }
 
 void
diff --git a/src/common/inbound.h b/src/common/inbound.h
index e90ef8c3..32368cc1 100644
--- a/src/common/inbound.h
+++ b/src/common/inbound.h
@@ -20,7 +20,7 @@
 #ifndef HEXCHAT_INBOUND_H
 #define HEXCHAT_INBOUND_H
 
-void inbound_next_nick (session *sess, char *nick);
+void inbound_next_nick (session *sess, char *nick, int error);
 void inbound_uback (server *serv);
 void inbound_uaway (server *serv);
 void inbound_account (server *serv, char *nick, char *account);
diff --git a/src/common/outbound.c b/src/common/outbound.c
index c8e0ff64..255e809c 100644
--- a/src/common/outbound.c
+++ b/src/common/outbound.c
@@ -4356,6 +4356,85 @@ xit:
 		free (newcmd);
 }
 
+char *
+command_insert_vars (session *sess, char *cmd)
+{
+	int pos;
+	GString *expanded;
+	ircnet *mynet = (ircnet *) sess->server->network;
+
+	if (!mynet)										/* shouldn't really happen */
+	{
+		return g_strdup (cmd);						/* the return value will be freed so we must srtdup() it */
+	}
+
+	expanded = g_string_new (NULL);
+
+	while (strchr (cmd, '%') != NULL)
+	{
+		pos = (int) (strchr (cmd, '%') - cmd);		/* offset to the first '%' */
+		g_string_append_len (expanded, cmd, pos);	/* copy contents till the '%' */
+		cmd += pos + 1;								/* jump to the char after the '%' */
+
+		switch (cmd[0])
+		{
+			case 'n':
+				if (mynet->nick)
+				{
+					g_string_append (expanded, mynet->nick);
+				}
+				else
+				{
+					g_string_append (expanded, prefs.hex_irc_nick1);
+				}
+				cmd++;
+				break;
+
+			case 'p':
+				if (mynet->pass)
+				{
+					g_string_append (expanded, mynet->pass);
+				}
+				cmd++;
+				break;
+
+			case 'r':
+				if (mynet->real)
+				{
+					g_string_append (expanded, mynet->real);
+				}
+				else
+				{
+					g_string_append (expanded, prefs.hex_irc_real_name);
+				}
+				cmd++;
+				break;
+
+			case 'u':
+				if (mynet->user)
+				{
+					g_string_append (expanded, mynet->user);
+				}
+				else
+				{
+					g_string_append (expanded, prefs.hex_irc_user_name);
+				}
+				cmd++;
+				break;
+
+			default:								/* unsupported character? copy it along with the '%'! */
+				cmd--;
+				g_string_append_len (expanded, cmd, 2);
+				cmd += 2;
+				break;
+		}
+	}
+
+	g_string_append (expanded, cmd);				/* copy any remaining string after the last '%' */
+
+	return g_string_free (expanded, FALSE);
+}
+
 /* handle a command, without the '/' prefix */
 
 int
@@ -4372,6 +4451,7 @@ handle_command (session *sess, char *cmd, int check_spch)
 	char tbuf_static[TBUFSIZE];
 	char *pdibuf;
 	char *tbuf;
+	char *cmd_vars;
 	int len;
 	int ret = TRUE;
 
@@ -4383,19 +4463,29 @@ handle_command (session *sess, char *cmd, int check_spch)
 	command_level++;
 	/* anything below MUST DEC command_level before returning */
 
-	len = strlen (cmd);
+	cmd_vars = command_insert_vars (sess, cmd);
+
+	len = strlen (cmd_vars);
 	if (len >= sizeof (pdibuf_static))
+	{
 		pdibuf = malloc (len + 1);
+	}
 	else
+	{
 		pdibuf = pdibuf_static;
+	}
 
 	if ((len * 2) >= sizeof (tbuf_static))
+	{
 		tbuf = malloc ((len * 2) + 1);
+	}
 	else
+	{
 		tbuf = tbuf_static;
+	}
 
 	/* split the text into words and word_eol */
-	process_data_init (pdibuf, cmd, word, word_eol, TRUE, TRUE);
+	process_data_init (pdibuf, cmd_vars, word, word_eol, TRUE, TRUE);
 
 	/* ensure an empty string at index 32 for cmd_deop etc */
 	/* (internal use only, plugins can still only read 1-31). */
@@ -4405,17 +4495,25 @@ handle_command (session *sess, char *cmd, int check_spch)
 	int_cmd = find_internal_command (word[1]);
 	/* redo it without quotes processing, for some commands like /JOIN */
 	if (int_cmd && !int_cmd->handle_quotes)
-		process_data_init (pdibuf, cmd, word, word_eol, FALSE, FALSE);
+	{
+		process_data_init (pdibuf, cmd_vars, word, word_eol, FALSE, FALSE);
+	}
 
 	if (check_spch && prefs.hex_input_perc_color)
-		check_special_chars (cmd, prefs.hex_input_perc_ascii);
+	{
+		check_special_chars (cmd_vars, prefs.hex_input_perc_ascii);
+	}
 
 	if (plugin_emit_command (sess, word[1], word, word_eol))
+	{
 		goto xit;
+	}
 
 	/* incase a plugin did /close */
 	if (!is_session (sess))
+	{
 		goto xit;
+	}
 
 	/* first see if it's a userCommand */
 	list = command_list;
@@ -4431,7 +4529,9 @@ handle_command (session *sess, char *cmd, int check_spch)
 	}
 
 	if (user_cmd)
+	{
 		goto xit;
+	}
 
 	/* now check internal commands */
 	int_cmd = find_internal_command (word[1]);
@@ -4441,38 +4541,51 @@ handle_command (session *sess, char *cmd, int check_spch)
 		if (int_cmd->needserver && !sess->server->connected)
 		{
 			notc_msg (sess);
-		} else if (int_cmd->needchannel && !sess->channel[0])
+		}
+		else if (int_cmd->needchannel && !sess->channel[0])
 		{
 			notj_msg (sess);
-		} else
+		}
+		else
 		{
 			switch (int_cmd->callback (sess, tbuf, word, word_eol))
 			{
-			case FALSE:
-				help (sess, tbuf, int_cmd->name, TRUE);
-				break;
-			case 2:
-				ret = FALSE;
-				goto xit;
+				case FALSE:
+					help (sess, tbuf, int_cmd->name, TRUE);
+					break;
+				case 2:
+					ret = FALSE;
+					goto xit;
 			}
 		}
-	} else
+	}
+	else
 	{
 		/* unknown command, just send it to the server and hope */
 		if (!sess->server->connected)
+		{
 			PrintText (sess, _("Unknown Command. Try /help\n"));
+		}
 		else
-			sess->server->p_raw (sess->server, cmd);
+		{
+			sess->server->p_raw (sess->server, cmd_vars);
+		}
 	}
 
 xit:
 	command_level--;
 
 	if (pdibuf != pdibuf_static)
+	{
 		free (pdibuf);
+	}
 
 	if (tbuf != tbuf_static)
+	{
 		free (tbuf);
+	}
+
+	g_free (cmd_vars);
 
 	return ret;
 }
diff --git a/src/common/plugin.c b/src/common/plugin.c
index 686f9749..61d5cb40 100644
--- a/src/common/plugin.c
+++ b/src/common/plugin.c
@@ -1111,11 +1111,6 @@ hexchat_get_info (hexchat_plugin *ph, const char *id)
 	case 0x339763: /* nick */
 		return sess->server->nick;
 
-	case 0x438fdf9: /* nickserv */
-		if (sess->server->network)
-			return ((ircnet *)sess->server->network)->nickserv;
-		return NULL;
-
 	case 0xca022f43: /* server */
 		if (!sess->server->connected)
 			return NULL;
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c
index 984f7f20..cb4db0cd 100644
--- a/src/common/proto-irc.c
+++ b/src/common/proto-irc.c
@@ -42,6 +42,7 @@
 #include "util.h"
 #include "hexchatc.h"
 #include "url.h"
+#include "servlist.h"
 
 
 static void
@@ -49,7 +50,7 @@ irc_login (server *serv, char *user, char *realname)
 {
 	tcp_sendf (serv, "CAP LS\r\n");		/* start with CAP LS as Charybdis sasl.txt suggests */
 
-	if (serv->password[0])
+	if (serv->password[0] && serv->loginmethod == LOGIN_PASS)
 	{
 		tcp_sendf (serv, "PASS %s\r\n", serv->password);
 	}
@@ -64,44 +65,54 @@ static void
 irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3)
 {
 	/* are all ircd authors idiots? */
-	switch (serv->nickservtype)
+	switch (serv->loginmethod)
 	{
-	case 0:
-		tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
-		break;
-	case 1:
-		tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
-		break;
-	case 2:
-		tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
-		break;
-	case 3:
-		tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
-		break;
-	case 4:
-		/* why couldn't QuakeNet implement one of the existing ones? */
-		tcp_sendf (serv, "AUTH %s %s\r\n", arg1, arg2);
+		case LOGIN_MSG_NICKSERV:
+			tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+			break;
+		case LOGIN_NICKSERV:
+			tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+			break;
+#if 0
+		case LOGIN_MSG_NS:
+			tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+			break;
+		case LOGIN_NS:
+			tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
+			break;
+		case LOGIN_AUTH:
+			/* why couldn't QuakeNet implement one of the existing ones? */
+			tcp_sendf (serv, "AUTH %s %s\r\n", arg1, arg2);
+			break;
+#endif
 	}
 }
 
 static void
 irc_ns_identify (server *serv, char *pass)
 {
-	if (serv->nickservtype == 4)	/* QuakeNet needs to do everything in its own ways... */
-	{
-		irc_nickserv (serv, "", serv->nick, pass, "");
-	}
-	else
+	switch (serv->loginmethod)
 	{
-		irc_nickserv (serv, "IDENTIFY", pass, "", "");
+		case LOGIN_CHALLENGEAUTH:
+			tcp_sendf (serv, "PRIVMSG %s :CHALLENGE\r\n", CHALLENGEAUTH_NICK);	/* request a challenge from Q */
+			break;
+#if 0
+		case LOGIN_AUTH:
+			irc_nickserv (serv, "", serv->nick, pass, "");
+			break;
+#endif
+		default:
+			irc_nickserv (serv, "IDENTIFY", pass, "", "");
 	}
 }
 
 static void
 irc_ns_ghost (server *serv, char *usname, char *pass)
 {
-	if (serv->nickservtype != 4)
+	if (serv->loginmethod != LOGIN_CHALLENGEAUTH)
+	{
 		irc_nickserv (serv, "GHOST", usname, " ", pass);
+	}
 }
 
 static void
@@ -114,118 +125,97 @@ irc_join (server *serv, char *channel, char *key)
 }
 
 static void
-irc_join_list_flush (server *serv, GString *c, GString *k)
+irc_join_list_flush (server *serv, GString *channels, GString *keys, int send_keys)
 {
-	char *chanstr, *keystr;
+	char *chanstr;
+	char *keystr;
+
+	chanstr = g_string_free (channels, FALSE);				/* convert our strings to char arrays */
+	keystr = g_string_free (keys, FALSE);
 
-	chanstr = g_string_free (c, FALSE);
-	keystr = g_string_free (k, FALSE);
-	if (chanstr[0])
+	if (send_keys)
 	{
-		if (keystr[0])
-			tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);
-		else
-			tcp_sendf (serv, "JOIN %s\r\n", chanstr);
+		tcp_sendf (serv, "JOIN %s %s\r\n", chanstr, keystr);	/* send the actual command */
+	}
+	else
+	{
+		tcp_sendf (serv, "JOIN %s\r\n", chanstr);	/* send the actual command */
 	}
+
 	g_free (chanstr);
 	g_free (keystr);
 }
 
-/* join a whole list of channels & keys, split to multiple lines
- * to get around 512 limit */
+/* Join a whole list of channels & keys, split to multiple lines
+ * to get around the 512 limit.
+ */
 
 static void
-irc_join_list (server *serv, GSList *channels, GSList *keys)
+irc_join_list (server *serv, GSList *favorites)
 {
-	GSList *clist;
-	GSList *klist;
-	GString *c = g_string_new (NULL);
-	GString *k = g_string_new (NULL);
-	int len;
-	int add;
-	int i, j;
+	int first_item = 1;										/* determine whether we add commas or not */
+	int send_keys = 0;										/* if none of our channels have keys, we can omit the 'x' fillers altogether */
+	int len = 9;											/* JOIN<space>channels<space>keys\r\n\0 */
+	favchannel *fav;
+	GString *chanlist = g_string_new (NULL);
+	GString *keylist = g_string_new (NULL);
+	GSList *favlist;
 
-	i = j = 0;
-	len = 9; /* "JOIN<space><space>\r\n" */
-	clist = channels;
-	klist = keys;
+	favlist = favorites;
 
-	while (clist)
+	while (favlist)
 	{
-		/* measure how many bytes this channel would add... */
-		if (1)
-		{
-			add = strlen (clist->data);
-			if (i != 0)
-				add++;	/* comma */
-		}
+		fav = favlist->data;
 
-		if (klist->data)
-		{
-			add += strlen (klist->data);
-		}
-		else
+		len += strlen (fav->name);
+		if (fav->key)
 		{
-			add++;	/* 'x' filler */
+			len += strlen (fav->key);
 		}
 
-		if (j != 0)
-			add++;	/* comma */
-
-		/* too big? dump buffer and start a fresh one */
-		if (len + add > 512)
+		if (len >= 512)										/* command length exceeds the IRC hard limit, flush it and start from scratch */
 		{
-			irc_join_list_flush (serv, c, k);
+			irc_join_list_flush (serv, chanlist, keylist, send_keys);
+
+			chanlist = g_string_new (NULL);
+			keylist = g_string_new (NULL);
 
-			c = g_string_new (NULL);
-			k = g_string_new (NULL);
-			i = j = 0;
 			len = 9;
+			first_item = 1;									/* list dumped, omit commas once again */
+			send_keys = 0;									/* also omit keys until we actually find one */
 		}
 
-		/* now actually add it to our GStrings */
-		if (1)
+		if (!first_item)
 		{
-			add = strlen (clist->data);
-			if (i != 0)
-			{
-				add++;
-				g_string_append_c (c, ',');
-			}
-			g_string_append (c, clist->data);
-			i++;
+			/* This should be done before the length check, but channel names
+			 * are already at least 2 characters long so it would trigger the
+			 * flush anyway.
+			 */
+			len += 2;
+
+			/* add separators but only if it's not the 1st element */
+			g_string_append_c (chanlist, ',');
+			g_string_append_c (keylist, ',');
 		}
 
-		if (klist->data)
+		g_string_append (chanlist, fav->name);
+
+		if (fav->key)
 		{
-			add += strlen (klist->data);
-			if (j != 0)
-			{
-				add++;
-				g_string_append_c (k, ',');
-			}
-			g_string_append (k, klist->data);
-			j++;
+			g_string_append (keylist, fav->key);
+			send_keys = 1;
 		}
 		else
 		{
-			add++;
-			if (j != 0)
-			{
-				add++;
-				g_string_append_c (k, ',');
-			}
-			g_string_append_c (k, 'x');
-			j++;
+			g_string_append_c (keylist, 'x');				/* 'x' filler for keyless channels so that our JOIN command is always well-formatted */
 		}
 
-		len += add;
-
-		klist = klist->next;
-		clist = clist->next;
+		first_item = 0;
+		favlist = favlist->next;
 	}
 
-	irc_join_list_flush (serv, c, k);
+	irc_join_list_flush (serv, chanlist, keylist, send_keys);
+	g_slist_free (favlist);
 }
 
 static void
@@ -845,17 +835,26 @@ process_numeric (session * sess, int n,
 		inbound_login_end (sess, text);
 		break;
 
-	case 433:	/* nickname in use */
 	case 432:	/* erroneous nickname */
 		if (serv->end_of_motd)
+		{
+			goto def;
+		}
+		inbound_next_nick (sess,  word[4], 1);
+		break;
+
+	case 433:	/* nickname in use */
+		if (serv->end_of_motd)
+		{
 			goto def;
-		inbound_next_nick (sess,  word[4]);
+		}
+		inbound_next_nick (sess,  word[4], 0);
 		break;
 
 	case 437:
 		if (serv->end_of_motd || is_channel (serv, word[4]))
 			goto def;
-		inbound_next_nick (sess, word[4]);
+		inbound_next_nick (sess, word[4], 0);
 		break;
 
 	case 471:
@@ -938,16 +937,21 @@ process_numeric (session * sess, int n,
 		}
 
 	def:
-		if (is_channel (serv, word[4]))
-		{
-			session *realsess = find_channel (serv, word[4]);
-			if (!realsess)
-				realsess = serv->server_session;
-			EMIT_SIGNAL (XP_TE_SERVTEXT, realsess, text, word[1], word[2], NULL, 0);
-		} else
 		{
-			EMIT_SIGNAL (XP_TE_SERVTEXT, serv->server_session, text, word[1],
-							 word[2], NULL, 0);
+			session *sess;
+		
+			if (is_channel (serv, word[4]))
+			{
+				sess = find_channel (serv, word[4]);
+				if (!sess)
+					sess = serv->server_session;
+			}
+			else if ((sess=find_dialog (serv,word[4]))) /* user with an open dialog */
+				;
+			else
+				sess=serv->server_session;
+			
+			EMIT_SIGNAL (XP_TE_SERVTEXT, sess, text, word[1], word[2], NULL, 0);
 		}
 	}
 }
@@ -1091,11 +1095,28 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
 
 		case WORDL('N','O','T','I'):
 			{
-				int id = FALSE;	/* identified */
+				int id = FALSE;								/* identified */
+				char *response;
 
 				text = word_eol[4];
 				if (*text == ':')
+				{
 					text++;
+				}
+
+				if (!strncmp (text, "CHALLENGE ", 10))		/* QuakeNet CHALLENGE upon our request */
+				{
+					response = challengeauth_response (((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.hex_irc_user_name, serv->password, word[5]);
+
+					tcp_sendf (serv, "PRIVMSG %s :CHALLENGEAUTH %s %s %s\r\n",
+						CHALLENGEAUTH_NICK,
+						((ircnet *)serv->network)->user ? ((ircnet *)serv->network)->user : prefs.hex_irc_user_name,
+						response,
+						CHALLENGEAUTH_ALGO);
+
+					g_free (response);
+					return;									/* omit the CHALLENGE <hash> ALGOS message */
+				}
 
 				if (serv->have_idmsg)
 				{
@@ -1119,6 +1140,10 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
 				int id = FALSE;	/* identified */
 				if (*to)
 				{
+					/* Handle limited channel messages, for now no special event */
+					if (strchr (serv->nick_prefixes, to[0]) != NULL)
+						to++;
+						
 					text = word_eol[4];
 					if (*text == ':')
 						text++;
@@ -1218,10 +1243,23 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
 					if (strstr (word_eol[5], "sasl") != 0)
 					{
 						serv->have_sasl = TRUE;
-						EMIT_SIGNAL (XP_TE_SASLAUTH, serv->server_session, sess->server->sasluser, NULL, NULL, NULL, 0);
+						EMIT_SIGNAL
+						(
+							XP_TE_SASLAUTH,
+							serv->server_session,
+							(((ircnet *)sess->server->network)->user) ? (((ircnet *)sess->server->network)->user) : prefs.hex_irc_user_name,
+							NULL,
+							NULL,
+							NULL,
+							0
+						);
 						tcp_send_len (serv, "AUTHENTICATE PLAIN\r\n", 20);
 
-						pass = encode_sasl_pass (sess->server->sasluser, sess->server->saslpassword);
+						pass = encode_sasl_pass
+						(
+							(((ircnet *)sess->server->network)->user) ? (((ircnet *)sess->server->network)->user) : prefs.hex_irc_user_name,
+							sess->server->password
+						);
 						tcp_sendf (sess->server, "AUTHENTICATE %s\r\n", pass);
 						free (pass);
 					}
@@ -1259,8 +1297,8 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[])
 						strcat (buffer, "extended-join ");
 						want_cap = 1;
 					}
-					/* if the SASL password is set, request SASL auth */
-					if (strstr (word_eol[5], "sasl") != 0 && strlen (sess->server->saslpassword) != 0)
+					/* if the SASL password is set AND auth mode is set to SASL, request SASL auth */
+					if (strstr (word_eol[5], "sasl") != 0 && strlen (sess->server->password) != 0 && serv->loginmethod == LOGIN_SASL)
 					{
 						strcat (buffer, "sasl ");
 						want_cap = 1;
diff --git a/src/common/server.c b/src/common/server.c
index 45472e05..3f7027e2 100644
--- a/src/common/server.c
+++ b/src/common/server.c
@@ -2031,8 +2031,8 @@ server_free (server *serv)
 		free (serv->last_away_reason);
 	if (serv->encoding)
 		free (serv->encoding);
-	if (serv->autojoin)
-		free (serv->autojoin);
+	if (serv->favlist)
+		g_slist_free_full (serv->favlist, (GDestroyNotify) servlist_favchan_free);
 
 	fe_server_callback (serv);
 
diff --git a/src/common/servlist.c b/src/common/servlist.c
index 4b04820b..d121dd47 100644
--- a/src/common/servlist.c
+++ b/src/common/servlist.c
@@ -43,7 +43,8 @@ struct defaultserver
 	char *host;
 	char *channel;
 	char *charset;
-	int nsmode;		/* default NickServ type */
+	int loginmode;		/* default authentication type */
+	char *connectcmd;	/* default connect command - should only be used for rare login types, paired with LOGIN_CUSTOM */
 };
 
 static const struct defaultserver def[] =
@@ -167,7 +168,7 @@ static const struct defaultserver def[] =
 	{0,			"irc.criten.net"},
 	{0,			"irc.eu.criten.net"},
 
-	{"DALnet", 0, 0, 0, 2},
+	{"DALnet", 0},
 	{0,			"irc.dal.net"},
 	{0,			"irc.eu.dal.net"},
 
@@ -245,7 +246,7 @@ static const struct defaultserver def[] =
 	{0,			"irc.ggn.net"},
 	{0,			"irc.vendetta.com"},
 
-	{"freenode",	0,	"#hexchat"},
+	{"freenode", 0, "#hexchat", 0, LOGIN_SASL},
 #ifdef USE_OPENSSL
 	{0,				"irc.freenode.net/+6697"},
 #endif
@@ -255,6 +256,12 @@ static const struct defaultserver def[] =
 	{0,			"kabel.freeworld.nu"},
 	{0,			"irc.freeworld.nu"},*/
 
+	{"FurryLand",	0},
+#ifdef USE_OPENSSL
+	{0,				"irc.furryland.net/+6697"},
+#endif
+	{0,				"irc.furryland.net"},	
+
 	{"Fusion Latina",	0},
 	{0,					"irc.fusionlatina.org/2012"},
 
@@ -263,7 +270,7 @@ static const struct defaultserver def[] =
 /*	{0,			"sprynet.us.galaxynet.org"},
 	{0,			"atlanta.ga.us.galaxynet.org"},*/
 
-	{"GameSurge", 0, 0, 0, 2},
+	{"GameSurge", 0},
 	{0,			"irc.gamesurge.net"},
 	
 /*	{"GamesNET",	0},
@@ -426,7 +433,7 @@ static const struct defaultserver def[] =
 	{0,			"nfsi.ptnet.org"},
 	{0,			"fctunl.ptnet.org"},
 
-	{"QuakeNet", 0, 0, 0, 5},
+	{"QuakeNet", 0, 0, 0, LOGIN_CHALLENGEAUTH},
 	{0,			"irc.quakenet.org"},
 	{0,			"irc.se.quakenet.org"},
 	{0,			"irc.dk.quakenet.org"},
@@ -460,7 +467,7 @@ static const struct defaultserver def[] =
 	{"Rizon", 0},
 	{0,			"irc.rizon.net"},
 
-	{"RusNet", 0, 0, "KOI8-R (Cyrillic)", 2},
+	{"RusNet", 0, 0, "KOI8-R (Cyrillic)"},
 	{0,			"irc.tomsk.net"},
 	{0,			"irc.rinet.ru"},
 	{0,			"irc.run.net"},
@@ -541,11 +548,11 @@ static const struct defaultserver def[] =
 	{0,			"irc.servx.ru"},
 	{0,			"irc.gavnos.ru"},
 
-	{"UnderNet",	0},
+	{"UnderNet", 0, 0, 0, LOGIN_CUSTOM, "MSG x@channels.undernet.org login %u %p"},
 	{0,			"us.undernet.org"},
 	{0,			"eu.undernet.org"},
 
-	{"UniBG", 0, 0, 0, 4},
+	{"UniBG", 0, 0, 0, LOGIN_CUSTOM, "MSG NS IDENTIFY %p"},
 	{0,			"irc.lirex.com"},
 	{0,			"irc.naturella.com"},
 	{0,			"irc.spnet.net"},
@@ -581,6 +588,55 @@ static const struct defaultserver def[] =
 
 GSList *network_list = 0;
 
+#if !GLIB_CHECK_VERSION(2,34,0)
+#define g_slist_copy_deep servlist_slist_copy_deep
+/* FIXME copy-paste from gslist.c, should be dumped sometime */
+static GSList*
+servlist_slist_copy_deep (GSList *list, GCopyFunc func, gpointer user_data)
+{
+  GSList *new_list = NULL;
+
+  if (list)
+    {
+      GSList *last;
+
+      new_list = g_slice_new (GSList);
+      if (func)
+        new_list->data = func (list->data, user_data);
+      else
+        new_list->data = list->data;
+      last = new_list;
+      list = list->next;
+      while (list)
+        {
+          last->next = g_slice_new (GSList);
+          last = last->next;
+          if (func)
+            last->data = func (list->data, user_data);
+          else
+            last->data = list->data;
+          list = list->next;
+        }
+      last->next = NULL;
+    }
+
+  return new_list;
+}
+#endif
+
+favchannel *
+servlist_favchan_copy (favchannel *fav)
+{
+	favchannel *newfav;
+
+	newfav = malloc (sizeof (favchannel));
+	memset (newfav, 0, sizeof (favchannel));
+
+	newfav->name = g_strdup (fav->name);
+	newfav->key = g_strdup (fav->key);		/* g_strdup() can handle NULLs so no need to check it */
+
+	return newfav;
+}
 
 void
 servlist_connect (session *sess, ircnet *net, gboolean join)
@@ -603,53 +659,39 @@ servlist_connect (session *sess, ircnet *net, gboolean join)
 		return;
 	ircserv = list->data;
 
-	/* incase a protocol switch is added to the servlist gui */
+	/* in case a protocol switch is added to the servlist gui */
 	server_fill_her_up (sess->server);
 
 	if (join)
 	{
 		sess->willjoinchannel[0] = 0;
 
-		if (net->autojoin)
+		if (net->favchanlist)
 		{
-			if (serv->autojoin)
-				free (serv->autojoin);
-			serv->autojoin = strdup (net->autojoin);
+			if (serv->favlist)
+			{
+				g_slist_free_full (serv->favlist, (GDestroyNotify) servlist_favchan_free);
+			}
+			serv->favlist = g_slist_copy_deep (net->favchanlist, (GCopyFunc) servlist_favchan_copy, NULL);
 		}
 	}
 
-	if (net->nstype >= 1)	/* once again, make sure gtk_combo_box_get_active() is not bugging us, just in case */
+	if (net->logintype)
 	{
-		serv->nickservtype = net->nstype - 1;	/* ircnet->nstype starts at 1, server->nickservtype starts at 0! */
+		serv->loginmethod = net->logintype;
 	}
 	else
 	{
-		serv->nickservtype = 1;					/* use /NickServ by default */
+		serv->loginmethod = LOGIN_DEFAULT_REAL;
 	}
 
 	serv->password[0] = 0;
-	serv->sasluser[0] = 0;
-	serv->saslpassword[0] = 0;
 
 	if (net->pass)
 	{
 		safe_strcpy (serv->password, net->pass, sizeof (serv->password));
 	}
 
-	if (net->flags & FLAG_USE_GLOBAL || net->user == NULL)
-	{
-		strcpy (serv->sasluser, prefs.hex_irc_user_name);
-	}
-	else
-	{
-		safe_strcpy (serv->sasluser, net->user, sizeof (serv->sasluser));
-	}
-
-	if (net->saslpass)
-	{
-		safe_strcpy (serv->saslpassword, net->saslpass, sizeof (serv->saslpassword));
-	}
-
 	if (net->flags & FLAG_USE_GLOBAL)
 	{
 		strcpy (serv->nick, prefs.hex_irc_nick1);
@@ -820,7 +862,9 @@ servlist_server_find (ircnet *net, char *name, int *pos)
 		if (strcmp (serv->hostname, name) == 0)
 		{
 			if (pos)
+			{
 				*pos = i;
+			}
 			return serv;
 		}
 		i++;
@@ -830,6 +874,56 @@ servlist_server_find (ircnet *net, char *name, int *pos)
 	return NULL;
 }
 
+favchannel *
+servlist_favchan_find (ircnet *net, char *channel, int *pos)
+{
+	GSList *list = net->favchanlist;
+	favchannel *favchan;
+	int i = 0;
+
+	while (list)
+	{
+		favchan = list->data;
+		if (strcmp (favchan->name, channel) == 0)
+		{
+			if (pos)
+			{
+				*pos = i;
+			}
+			return favchan;
+		}
+		i++;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
+commandentry *
+servlist_command_find (ircnet *net, char *cmd, int *pos)
+{
+	GSList *list = net->commandlist;
+	commandentry *entry;
+	int i = 0;
+
+	while (list)
+	{
+		entry = list->data;
+		if (strcmp (entry->command, cmd) == 0)
+		{
+			if (pos)
+			{
+				*pos = i;
+			}
+			return entry;
+		}
+		i++;
+		list = list->next;
+	}
+
+	return NULL;
+}
+
 /* find a network (e.g. (ircnet *) to "FreeNode") from a hostname
    (e.g. "irc.eu.freenode.net") */
 
@@ -897,6 +991,60 @@ servlist_server_add (ircnet *net, char *name)
 	return serv;
 }
 
+commandentry *
+servlist_command_add (ircnet *net, char *cmd)
+{
+	commandentry *entry;
+
+	entry = malloc (sizeof (commandentry));
+	memset (entry, 0, sizeof (commandentry));
+	entry->command = strdup (cmd);
+
+	net->commandlist = g_slist_append (net->commandlist, entry);
+
+	return entry;
+}
+
+GSList *
+servlist_favchan_listadd (GSList *chanlist, char *channel, char *key)
+{
+	favchannel *chan;
+
+	chan = malloc (sizeof (favchannel));
+	memset (chan, 0, sizeof (favchannel));
+
+	chan->name = g_strdup (channel);
+	chan->key = g_strdup (key);
+	chanlist = g_slist_append (chanlist, chan);
+
+	return chanlist;
+}
+
+void
+servlist_favchan_add (ircnet *net, char *channel)
+{
+	int pos;
+	char *name;
+	char *key;
+
+	if (strchr (channel, ',') != NULL)
+	{
+		pos = (int) (strchr (channel, ',') - channel);
+		name = g_strndup (channel, pos);
+		key = g_strdup (channel + pos + 1);
+	}
+	else
+	{
+		name = g_strdup (channel);
+		key = NULL;
+	}
+
+	net->favchanlist = servlist_favchan_listadd (net->favchanlist, name, key);
+
+	g_free (name);
+	g_free (key);
+}
+
 void
 servlist_server_remove (ircnet *net, ircserver *serv)
 {
@@ -917,6 +1065,35 @@ servlist_server_remove_all (ircnet *net)
 	}
 }
 
+void
+servlist_command_free (commandentry *entry)
+{
+	g_free (entry->command);
+	g_free (entry);
+}
+
+void
+servlist_command_remove (ircnet *net, commandentry *entry)
+{
+	servlist_command_free (entry);
+	net->commandlist = g_slist_remove (net->commandlist, entry);
+}
+
+void
+servlist_favchan_free (favchannel *channel)
+{
+	g_free (channel->name);
+	g_free (channel->key);
+	g_free (channel);
+}
+
+void
+servlist_favchan_remove (ircnet *net, favchannel *channel)
+{
+	servlist_favchan_free (channel);
+	net->favchanlist = g_slist_remove (net->favchanlist, channel);
+}
+
 static void
 free_and_clear (char *str)
 {
@@ -941,8 +1118,6 @@ servlist_cleanup (void)
 	{
 		net = list->data;
 		free_and_clear (net->pass);
-		free_and_clear (net->saslpass);
-		free_and_clear (net->nickserv);
 	}
 }
 
@@ -964,12 +1139,10 @@ servlist_net_remove (ircnet *net)
 	if (net->real)
 		free (net->real);
 	free_and_clear (net->pass);
-	free_and_clear (net->saslpass);
-	if (net->autojoin)
-		free (net->autojoin);
-	if (net->command)
-		free (net->command);
-	free_and_clear (net->nickserv);
+	if (net->favchanlist)
+		g_slist_free_full (net->favchanlist, (GDestroyNotify) servlist_favchan_free);
+	if (net->commandlist)
+		g_slist_free_full (net->commandlist, (GDestroyNotify) servlist_command_free);
 	if (net->comment)
 		free (net->comment);
 	if (net->encoding)
@@ -983,7 +1156,9 @@ servlist_net_remove (ircnet *net)
 	{
 		serv = list->data;
 		if (serv->network == net)
+		{
 			serv->network = NULL;
+		}
 		list = list->next;
 	}
 }
@@ -1019,24 +1194,32 @@ servlist_load_defaults (void)
 		if (def[i].network)
 		{
 			net = servlist_net_add (def[i].network, def[i].host, FALSE);
-			net->encoding = strdup (IRC_DEFAULT_CHARSET);
 			if (def[i].channel)
 			{
-				net->autojoin = strdup (def[i].channel);
+				servlist_favchan_add (net, def[i].channel);
 			}
 			if (def[i].charset)
 			{
-				free (net->encoding);
-				net->encoding = strdup (def[i].charset);
+				net->encoding = g_strdup (def[i].charset);
 			}
-			if (def[i].nsmode)
+			else
 			{
-				net->nstype = def[i].nsmode;
+				net->encoding = g_strdup (IRC_DEFAULT_CHARSET);
 			}
+			if (def[i].loginmode)
+			{
+				net->logintype = def[i].loginmode;
+			}
+			if (def[i].connectcmd)
+			{
+				servlist_command_add (net, def[i].connectcmd);
+			}
+
 			if (g_str_hash (def[i].network) == def_hash)
 			{
 				prefs.hex_gui_slist_select = j;
 			}
+
 			j++;
 		}
 		else
@@ -1057,7 +1240,6 @@ servlist_load (void)
 	FILE *fp;
 	char buf[2048];
 	int len;
-	char *tmp;
 	ircnet *net = NULL;
 
 	/* simple migration we will keep for a short while */
@@ -1100,43 +1282,55 @@ servlist_load (void)
 			case 'P':
 				net->pass = strdup (buf + 2);
 				break;
-			case 'A':
-				net->saslpass = strdup (buf + 2);
-				break;
-			case 'J':
-				net->autojoin = strdup (buf + 2);
+			case 'L':
+				net->logintype = atoi (buf + 2);
 				break;
-			case 'C':
-				if (net->command)
-				{
-					/* concat extra commands with a \n separator */
-					tmp = net->command;
-					net->command = malloc (strlen (tmp) + strlen (buf + 2) + 2);
-					strcpy (net->command, tmp);
-					strcat (net->command, "\n");
-					strcat (net->command, buf + 2);
-					free (tmp);
-				} else
-					net->command = strdup (buf + 2);
+			case 'E':
+				net->encoding = strdup (buf + 2);
 				break;
 			case 'F':
 				net->flags = atoi (buf + 2);
 				break;
-			case 'D':
-				net->selected = atoi (buf + 2);
-				break;
-			case 'E':
-				net->encoding = strdup (buf + 2);
-				break;
 			case 'S':	/* new server/hostname for this network */
 				servlist_server_add (net, buf + 2);
 				break;
-			case 'B':
-				net->nickserv = strdup (buf + 2);
+			case 'C':
+				servlist_command_add (net, buf + 2);
 				break;
-			case 'T':
-				net->nstype = atoi (buf + 2);
+			case 'J':
+				servlist_favchan_add (net, buf + 2);
 				break;
+			case 'D':
+				net->selected = atoi (buf + 2);
+				break;
+			/* FIXME Migration code. In 2.9.5 the order was:
+			 *
+			 * P=serverpass, A=saslpass, B=nickservpass
+			 *
+			 * So if server password was unset, we can safely use SASL
+			 * password for our new universal password, or if that's also
+			 * unset, use NickServ password.
+			 *
+			 * Should be removed at some point.
+			 */
+			case 'A':
+				if (!net->pass)
+				{
+					net->pass = strdup (buf + 2);
+					if (!net->logintype)
+					{
+						net->logintype = LOGIN_SASL;
+					}
+				}
+			case 'B':
+				if (!net->pass)
+				{
+					net->pass = strdup (buf + 2);
+					if (!net->logintype)
+					{
+						net->logintype = LOGIN_NICKSERV;
+					}
+				}
 			}
 		}
 		if (buf[0] == 'N')
@@ -1187,13 +1381,6 @@ servlist_check_encoding (char *charset)
 	return FALSE;
 }
 
-static int
-servlist_write_ccmd (char *str, void *fp)
-{
-	return fprintf (fp, "C=%s\n", (str[0] == '/') ? str + 1 : str);
-}
-
-
 int
 servlist_save (void)
 {
@@ -1201,8 +1388,12 @@ servlist_save (void)
 	char *buf;
 	ircnet *net;
 	ircserver *serv;
+	commandentry *cmd;
+	favchannel *favchan;
 	GSList *list;
-	GSList *hlist;
+	GSList *netlist;
+	GSList *cmdlist;
+	GSList *favlist;
 #ifndef WIN32
 	int first = FALSE;
 
@@ -1244,26 +1435,8 @@ servlist_save (void)
 			fprintf (fp, "R=%s\n", net->real);
 		if (net->pass)
 			fprintf (fp, "P=%s\n", net->pass);
-		if (net->saslpass)
-			fprintf (fp, "A=%s\n", net->saslpass);
-		if (net->autojoin)
-			fprintf (fp, "J=%s\n", net->autojoin);
-		if (net->nickserv)
-			fprintf (fp, "B=%s\n", net->nickserv);
-		if (net->nstype)
-		{
-			if (net->nstype == -1)		/* gtk_combo_box_get_active() returns -1 for invalid indices */
-			{
-				net->nstype = 0; 		/* avoid further crashes for the current session */
-				buf = g_strdup_printf (_("Warning: invalid NickServ type. Falling back to default type for network %s."), net->name);
-				fe_message (buf, FE_MSG_WARN);
-				g_free (buf);
-			}
-			else						/* the selection was fine, save it */
-			{
-				fprintf (fp, "T=%d\n", net->nstype);
-			}
-		}
+		if (net->logintype)
+			fprintf (fp, "L=%d\n", net->logintype);
 		if (net->encoding && g_ascii_strcasecmp (net->encoding, "System") &&
 			 g_ascii_strcasecmp (net->encoding, "System default"))
 		{
@@ -1277,17 +1450,39 @@ servlist_save (void)
 			}
 		}
 
-		if (net->command)
-			token_foreach (net->command, '\n', servlist_write_ccmd, fp);
-
 		fprintf (fp, "F=%d\nD=%d\n", net->flags, net->selected);
 
-		hlist = net->servlist;
-		while (hlist)
+		netlist = net->servlist;
+		while (netlist)
 		{
-			serv = hlist->data;
+			serv = netlist->data;
 			fprintf (fp, "S=%s\n", serv->hostname);
-			hlist = hlist->next;
+			netlist = netlist->next;
+		}
+
+		cmdlist = net->commandlist;
+		while (cmdlist)
+		{
+			cmd = cmdlist->data;
+			fprintf (fp, "C=%s\n", cmd->command);
+			cmdlist = cmdlist->next;
+		}
+
+		favlist = net->favchanlist;
+		while (favlist)
+		{
+			favchan = favlist->data;
+
+			if (favchan->key)
+			{
+				fprintf (fp, "J=%s,%s\n", favchan->name, favchan->key);
+			}
+			else
+			{
+				fprintf (fp, "J=%s\n", favchan->name);
+			}
+
+			favlist = favlist->next;
 		}
 
 		if (fprintf (fp, "\n") < 1)
@@ -1303,162 +1498,33 @@ servlist_save (void)
 	return TRUE;
 }
 
-static void
-joinlist_free1 (GSList *list)
-{
-	GSList *head = list;
-
-	for (; list; list = list->next)
-		g_free (list->data);
-	g_slist_free (head);
-}
-
-void
-joinlist_free (GSList *channels, GSList *keys)
-{
-	joinlist_free1 (channels);
-	joinlist_free1 (keys);
-}
-
-gboolean
-joinlist_is_in_list (server *serv, char *channel)
-{
-	GSList *channels, *keys;
-	GSList *list;
-
-	if (!serv->network || !((ircnet *)serv->network)->autojoin)
-		return FALSE;
-
-	joinlist_split (((ircnet *)serv->network)->autojoin, &channels, &keys);
-
-	for (list = channels; list; list = list->next)
-	{
-		if (serv->p_cmp (list->data, channel) == 0)
-			return TRUE;
-	}
-
-	joinlist_free (channels, keys);
-
-	return FALSE;
-}
-
-gchar *
-joinlist_merge (GSList *channels, GSList *keys)
+static int
+joinlist_find_chan (favchannel *curr_item, const char *channel)
 {
-	GString *out = g_string_new (NULL);
-	GSList *list;
-	int i, j;
-
-	for (; channels; channels = channels->next)
+	if (!g_ascii_strcasecmp (curr_item->name, channel))
 	{
-		g_string_append (out, channels->data);
-
-		if (channels->next)
-			g_string_append_c (out, ',');
+		return 0;
 	}
-
-	/* count number of REAL keys */
-	for (i = 0, list = keys; list; list = list->next)
-		if (list->data)
-			i++;
-
-	if (i > 0)
+	else
 	{
-		g_string_append_c (out, ' ');
-
-		for (j = 0; keys; keys = keys->next)
-		{
-			if (keys->data)
-			{
-				g_string_append (out, keys->data);
-				j++;
-				if (j == i)
-					break;
-			}
-
-			if (keys->next)
-				g_string_append_c (out, ',');
-		}
+		return 1;
 	}
-
-	return g_string_free (out, FALSE);
 }
 
-void
-joinlist_split (char *autojoin, GSList **channels, GSList **keys)
+gboolean
+joinlist_is_in_list (server *serv, char *channel)
 {
-	char *parta, *partb;
-	char *chan, *key;
-	int len;
-
-	*channels = NULL;
-	*keys = NULL;
-
-	/* after the first space, the keys begin */
-	parta = autojoin;
-	partb = strchr (autojoin, ' ');
-	if (partb)
-		partb++;
-
-	while (1)
+	if (!serv->network || !((ircnet *)serv->network)->favchanlist)
 	{
-		chan = parta;
-		key = partb;
-
-		if (1)
-		{
-			while (parta[0] != 0 && parta[0] != ',' && parta[0] != ' ')
-			{
-				parta++;
-			}
-		}
-
-		if (partb)
-		{
-			while (partb[0] != 0 && partb[0] != ',' && partb[0] != ' ')
-			{
-				partb++;
-			}
-		}
-
-		len = parta - chan;
-		if (len < 1)
-			break;
-		*channels = g_slist_append (*channels, g_strndup (chan, len));
-
-		len = partb - key;
-		*keys = g_slist_append (*keys, len ? g_strndup (key, len) : NULL);
-
-		if (parta[0] == ' ' || parta[0] == 0)
-			break;
-		parta++;
-
-		if (partb)
-		{
-			if (partb[0] == 0 || partb[0] == ' ')
-				partb = NULL;	/* no more keys, but maybe more channels? */
-			else
-				partb++;
-		}
+		return FALSE;
 	}
 
-#if 0
-	GSList *lista, *listb;
-	int i;
-
-	printf("-----\n");
-	i = 0;
-	lista = *channels;
-	listb = *keys;
-	while (lista)
+	if (g_slist_find_custom (((ircnet *)serv->network)->favchanlist, channel, (GCompareFunc) joinlist_find_chan))
 	{
-		printf("%d. |%s| |%s|\n", i, lista->data, listb->data);
-		i++;
-		lista = lista->next;
-		listb = listb->next;
+		return TRUE;
+	}
+	else
+	{
+		return FALSE;
 	}
-	printf("-----\n\n");
-#endif
 }
-
-
diff --git a/src/common/servlist.h b/src/common/servlist.h
index b652f463..45b6dad6 100644
--- a/src/common/servlist.h
+++ b/src/common/servlist.h
@@ -25,6 +25,17 @@ typedef struct ircserver
 	char *hostname;
 } ircserver;
 
+typedef struct commandentry
+{
+	char *command;
+} commandentry;
+
+typedef struct favchannel
+{
+	char *name;
+	char *key;
+} favchannel;
+
 typedef struct ircnet
 {
 	char *name;
@@ -33,14 +44,12 @@ typedef struct ircnet
 	char *user;
 	char *real;
 	char *pass;
-	char *saslpass;
-	char *autojoin;
-	char *command;
-	char *nickserv;
-	int nstype;
+	int logintype;
 	char *comment;
 	char *encoding;
 	GSList *servlist;
+	GSList *commandlist;
+	GSList *favchanlist;
 	int selected;
 	guint32 flags;
 } ircnet;
@@ -49,13 +58,31 @@ extern GSList *network_list;
 
 #define FLAG_CYCLE				1
 #define FLAG_USE_GLOBAL			2
-#define FLAG_USE_SSL				4
+#define FLAG_USE_SSL			4
 #define FLAG_AUTO_CONNECT		8
 #define FLAG_USE_PROXY			16
 #define FLAG_ALLOW_INVALID		32
 #define FLAG_FAVORITE			64
 #define FLAG_COUNT				7
 
+/* Login methods. Use server password by default - if we had a NickServ password, it'd be set to 2 already by servlist_load() */
+#define LOGIN_DEFAULT_REAL		LOGIN_PASS		/* this is to set the default login method for unknown servers */
+#define LOGIN_DEFAULT			0				/* this is for the login type dropdown, doesn't serve any other purpose */
+#define LOGIN_MSG_NICKSERV		1
+#define LOGIN_NICKSERV			2
+#if 0
+#define LOGIN_NS				3
+#define LOGIN_MSG_NS			4
+#define LOGIN_AUTH				5
+#endif
+#define LOGIN_SASL				6
+#define LOGIN_PASS				7
+#define LOGIN_CHALLENGEAUTH		8
+#define LOGIN_CUSTOM			9
+
+#define CHALLENGEAUTH_ALGO		"HMAC-SHA-256"
+#define CHALLENGEAUTH_NICK		"Q@CServe.quakenet.org"
+
 /* DEFAULT_CHARSET is already defined in wingdi.h */
 #define IRC_DEFAULT_CHARSET		"UTF-8 (Unicode)"
 
@@ -74,13 +101,28 @@ void servlist_net_remove (ircnet *net);
 ircnet *servlist_net_find (char *name, int *pos, int (*cmpfunc) (const char *, const char *));
 ircnet *servlist_net_find_from_server (char *server_name);
 
-void servlist_server_remove (ircnet *net, ircserver *serv);
-ircserver *servlist_server_add (ircnet *net, char *name);
 ircserver *servlist_server_find (ircnet *net, char *name, int *pos);
+commandentry *servlist_command_find (ircnet *net, char *cmd, int *pos);
+favchannel *servlist_favchan_find (ircnet *net, char *channel, int *pos);
+
+ircserver *servlist_server_add (ircnet *net, char *name);
+commandentry *servlist_command_add (ircnet *net, char *command);
+void servlist_favchan_add (ircnet *net, char *channel);
+
+void servlist_command_free (commandentry *entry);
+void servlist_favchan_free (favchannel *channel);
+
+void servlist_server_remove (ircnet *net, ircserver *serv);
+void servlist_command_remove (ircnet *net, commandentry *entry);
+void servlist_favchan_remove (ircnet *net, favchannel *channel);
+
+favchannel *servlist_favchan_copy (favchannel *fav);
+GSList *servlist_favchan_listadd (GSList *chanlist, char *channel, char *key);
 
-void joinlist_split (char *autojoin, GSList **channels, GSList **keys);
 gboolean joinlist_is_in_list (server *serv, char *channel);
-void joinlist_free (GSList *channels, GSList *keys);
-gchar *joinlist_merge (GSList *channels, GSList *keys);
 
+/* FIXME
+void joinlist_split (char *autojoin, GSList **channels, GSList **keys);
+void joinlist_free (GSList *channels, GSList *keys);
+*/
 #endif
diff --git a/src/common/textevents.in b/src/common/textevents.in
index 3b0b676a..c86af2bc 100644
--- a/src/common/textevents.in
+++ b/src/common/textevents.in
@@ -529,13 +529,19 @@ pevt_generic_none_help
 Nick Clash
 XP_TE_NICKCLASH
 pevt_nickclash_help
-%C23*%O$t%C28$1%C already in use. Retrying with %C18$2%O...
+%C23*%O$t%C28$1%C is already in use. Retrying with %C18$2%O...
+2
+
+Nick Erroneous
+XP_TE_NICKERROR
+pevt_nickclash_help
+%C23*%O$t%C28$1%C is erroneous. Retrying with %C18$2%O...
 2
 
 Nick Failed
 XP_TE_NICKFAIL
 pevt_generic_none_help
-%C20*%O$tNickname already in use. Use /NICK to try another.
+%C20*%O$tNickname is erroneous or already in use. Use /NICK to try another.
 0
 
 No DCC
diff --git a/src/common/util.c b/src/common/util.c
index 29a0f3ed..52464621 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -1886,7 +1886,7 @@ int main (int argc, char *argv[])
 	list = get_subdirs ("foo");
 	display_list (list);
 #if GLIB_CHECK_VERSION(2,28,0)
-	g_slist_free_full (list, (GFunc) g_free);
+	g_slist_free_full (list, (GDestroyNotify) g_free);
 #else
 	g_slist_foreach (list, (GFunc) g_free, NULL);
 	g_slist_free (list);
@@ -1931,19 +1931,15 @@ get_subdirs (const char *path)
 char *
 encode_sasl_pass (char *user, char *pass)
 {
-	int passlen;
+	int authlen;
 	char *buffer;
 	char *encoded;
 
-	/* passphrase generation, nicely copy-pasted from the CAP-SASL plugin */
-	passlen = strlen (user) * 2 + 2 + strlen (pass);
-	buffer = (char*) malloc (passlen + 1);
-	strcpy (buffer, user);
-	strcpy (buffer + strlen (user) + 1, user);
-	strcpy (buffer + strlen (user) * 2 + 2, pass);
-	encoded = g_base64_encode ((unsigned char*) buffer, passlen);
-
-	free (buffer);
+	/* we can't call strlen() directly on buffer thanks to the atrocious \0 characters it requires */
+	authlen = strlen (user) * 2 + 2 + strlen (pass);
+	buffer = g_strdup_printf ("%s%c%s%c%s", user, '\0', user, '\0', pass);
+	encoded = g_base64_encode ((unsigned char*) buffer, authlen);
+	g_free (buffer);
 
 	return encoded;
 }
@@ -1978,3 +1974,79 @@ find_font (const char *fontname)
 	return 0;
 }
 #endif
+
+static char *
+str_sha256hash (char *string)
+{
+	int i;
+	unsigned char hash[SHA256_DIGEST_LENGTH];
+	char buf[SHA256_DIGEST_LENGTH * 2 + 1];		/* 64 digit hash + '\0' */
+	SHA256_CTX sha256;
+
+	SHA256_Init (&sha256);
+	SHA256_Update (&sha256, string, strlen (string));
+	SHA256_Final (hash, &sha256);
+
+	for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
+	{
+		sprintf (buf + (i * 2), "%02x", hash[i]);
+	}
+
+	buf[SHA256_DIGEST_LENGTH * 2] = 0;
+
+	return g_strdup (buf);
+}
+
+/**
+ * \brief Generate CHALLENGEAUTH response for QuakeNet login.
+ *
+ * \param username QuakeNet user name
+ * \param password password for the user
+ * \param challenge the CHALLENGE response we got from Q
+ *
+ * After a successful connection to QuakeNet a CHALLENGE is requested from Q.
+ * Generate the CHALLENGEAUTH response from this CHALLENGE and our user
+ * credentials as per the
+ * <a href="http://www.quakenet.org/development/challengeauth">CHALLENGEAUTH</a>
+ * docs. As for using OpenSSL HMAC, see
+ * <a href="http://www.askyb.com/cpp/openssl-hmac-hasing-example-in-cpp/">example 1</a>,
+ * <a href="http://stackoverflow.com/questions/242665/understanding-engine-initialization-in-openssl">example 2</a>.
+ */
+char *
+challengeauth_response (char *username, char *password, char *challenge)
+{
+	int i;
+	char *user;
+	char *pass;
+	char *passhash;
+	char *key;
+	char *keyhash;
+	unsigned char *digest;
+	GString *buf = g_string_new_len (NULL, SHA256_DIGEST_LENGTH * 2);
+
+	user = g_strdup (username);
+	*user = rfc_tolower (*username);			/* convert username to lowercase as per the RFC */
+
+	pass = g_strndup (password, 10);			/* truncate to 10 characters */
+	passhash = str_sha256hash (pass);
+	g_free (pass);
+
+	key = g_strdup_printf ("%s:%s", user, passhash);
+	g_free (user);
+	g_free (passhash);
+
+	keyhash = str_sha256hash (key);
+	g_free (key);
+
+	digest = HMAC (EVP_sha256 (), keyhash, strlen (keyhash), (unsigned char *) challenge, strlen (challenge), NULL, NULL);
+	g_free (keyhash);
+
+	for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
+	{
+		g_string_append_printf (buf, "%02x", (unsigned int) digest[i]);
+	}
+
+	digest = (unsigned char *) g_string_free (buf, FALSE);
+
+	return (char *) digest;
+}
diff --git a/src/common/util.h b/src/common/util.h
index 9e2d9f52..0ebd89d4 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -80,5 +80,6 @@ int portable_mode ();
 int unity_mode ();
 GSList *get_subdirs (const char *path);
 char *encode_sasl_pass (char *user, char *pass);
+char *challengeauth_response (char *username, char *password, char *challenge);
 
 #endif