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/dcc.c9
-rw-r--r--src/common/hexchat.h8
-rw-r--r--src/common/inbound.c133
-rw-r--r--src/common/plugin.c5
-rw-r--r--src/common/proto-irc.c202
-rw-r--r--src/common/server.c4
-rw-r--r--src/common/servlist.c551
-rw-r--r--src/common/servlist.h57
-rw-r--r--src/common/util.c2
9 files changed, 539 insertions, 432 deletions
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.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..07539a63 100644
--- a/src/common/inbound.c
+++ b/src/common/inbound.c
@@ -1066,38 +1066,26 @@ inbound_nameslist_end (server *serv, char *chan)
 	return FALSE;
 }
 
-static gboolean
+static void
 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;
 	}
 
-	/* 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,39 +1093,54 @@ 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? */
+
+				/* session->channelkey is initially unset for channels joined from the favorites. You have to fill them up manually from favorites settings. */
+				if (fav)
+				{
+					/* 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));
+					}
+				}
 
-				/* There can be no gap between keys, list keyed chans first. */
-				if (sess->channelkey[0] != 0)
+				/* for easier checks, ensure that favchannel->key is just NULL when session->channelkey is empty i.e. '' */
+				if (strlen (sess->channelkey))
 				{
-					channels = g_slist_prepend (channels, g_strdup (sess->waitchannel));
-					keys = g_slist_prepend (keys, g_strdup (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;
 	fe_server_event (serv, FE_SE_LOGGEDIN, i);
-	return FALSE;
 }
 
 void
@@ -1367,9 +1370,28 @@ 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_NS:
+		case LOGIN_MSG_NS:
+		case LOGIN_AUTH:
+			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 +1406,48 @@ 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);
+		if (serv->network && ((ircnet *)serv->network)->pass && prefs.hex_irc_join_delay && inbound_nickserv_login (serv))
+		{
+			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/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..8556f9e9 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,21 +65,23 @@ 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:
+	case LOGIN_MSG_NICKSERV:
 		tcp_sendf (serv, "PRIVMSG NICKSERV :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
 		break;
-	case 1:
+	case LOGIN_NICKSERV:
 		tcp_sendf (serv, "NICKSERV %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
 		break;
-	case 2:
+#if 0
+	case LOGIN_NS:
 		tcp_sendf (serv, "NS %s %s%s%s\r\n", cmd, arg1, arg2, arg3);
 		break;
-	case 3:
+#endif
+	case LOGIN_MSG_NS:
 		tcp_sendf (serv, "PRIVMSG NS :%s %s%s%s\r\n", cmd, arg1, arg2, arg3);
 		break;
-	case 4:
+	case LOGIN_AUTH:
 		/* why couldn't QuakeNet implement one of the existing ones? */
 		tcp_sendf (serv, "AUTH %s %s\r\n", arg1, arg2);
 	}
@@ -87,7 +90,7 @@ irc_nickserv (server *serv, char *cmd, char *arg1, char *arg2, char *arg3)
 static void
 irc_ns_identify (server *serv, char *pass)
 {
-	if (serv->nickservtype == 4)	/* QuakeNet needs to do everything in its own ways... */
+	if (serv->loginmethod == LOGIN_AUTH)	/* QuakeNet needs to do everything in its own ways... */
 	{
 		irc_nickserv (serv, "", serv->nick, pass, "");
 	}
@@ -100,8 +103,10 @@ irc_ns_identify (server *serv, char *pass)
 static void
 irc_ns_ghost (server *serv, char *usname, char *pass)
 {
-	if (serv->nickservtype != 4)
+	if (serv->loginmethod != LOGIN_AUTH)
+	{
 		irc_nickserv (serv, "GHOST", usname, " ", pass);
+	}
 }
 
 static void
@@ -114,118 +119,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 (c, FALSE);
-	keystr = g_string_free (k, FALSE);
-	if (chanstr[0])
+	chanstr = g_string_free (channels, FALSE);				/* convert our strings to char arrays */
+	keystr = g_string_free (keys, FALSE);
+
+	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
@@ -938,16 +922,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);
 		}
 	}
 }
@@ -1218,10 +1207,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 +1261,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 26d9a7cb..9b71d53d 100644
--- a/src/common/server.c
+++ b/src/common/server.c
@@ -2030,8 +2030,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..da0b746d 100644
--- a/src/common/servlist.c
+++ b/src/common/servlist.c
@@ -43,7 +43,7 @@ struct defaultserver
 	char *host;
 	char *channel;
 	char *charset;
-	int nsmode;		/* default NickServ type */
+	int loginmode;		/* default authentication type */
 };
 
 static const struct defaultserver def[] =
@@ -167,7 +167,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 +245,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
@@ -263,7 +263,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 +426,7 @@ static const struct defaultserver def[] =
 	{0,			"nfsi.ptnet.org"},
 	{0,			"fctunl.ptnet.org"},
 
-	{"QuakeNet", 0, 0, 0, 5},
+	{"QuakeNet", 0, 0, 0, LOGIN_AUTH},
 	{0,			"irc.quakenet.org"},
 	{0,			"irc.se.quakenet.org"},
 	{0,			"irc.dk.quakenet.org"},
@@ -460,7 +460,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"},
@@ -545,7 +545,7 @@ static const struct defaultserver def[] =
 	{0,			"us.undernet.org"},
 	{0,			"eu.undernet.org"},
 
-	{"UniBG", 0, 0, 0, 4},
+	{"UniBG", 0, 0, 0, LOGIN_MSG_NS},
 	{0,			"irc.lirex.com"},
 	{0,			"irc.naturella.com"},
 	{0,			"irc.spnet.net"},
@@ -581,6 +581,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 +652,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 +855,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 +867,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 +984,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 +1058,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 +1111,6 @@ servlist_cleanup (void)
 	{
 		net = list->data;
 		free_and_clear (net->pass);
-		free_and_clear (net->saslpass);
-		free_and_clear (net->nickserv);
 	}
 }
 
@@ -964,12 +1132,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 +1149,9 @@ servlist_net_remove (ircnet *net)
 	{
 		serv = list->data;
 		if (serv->network == net)
+		{
 			serv->network = NULL;
+		}
 		list = list->next;
 	}
 }
@@ -1022,16 +1190,16 @@ servlist_load_defaults (void)
 			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);
 			}
-			if (def[i].nsmode)
+			if (def[i].loginmode)
 			{
-				net->nstype = def[i].nsmode;
+				net->logintype = def[i].loginmode;
 			}
 			if (g_str_hash (def[i].network) == def_hash)
 			{
@@ -1057,7 +1225,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 +1267,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 +1366,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 +1373,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 +1420,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 +1435,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 +1483,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..92100b9b 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,24 @@ 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
+#define LOGIN_NS				3
+#define LOGIN_MSG_NS			4
+#define LOGIN_AUTH				5
+#define LOGIN_SASL				6
+#define LOGIN_PASS				7
+
 /* DEFAULT_CHARSET is already defined in wingdi.h */
 #define IRC_DEFAULT_CHARSET		"UTF-8 (Unicode)"
 
@@ -74,13 +94,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/util.c b/src/common/util.c
index 29a0f3ed..a6c9fe21 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);