summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am4
-rw-r--r--src/common/dcc.c9
-rw-r--r--src/common/dcc.h1
-rw-r--r--src/common/fe.h3
-rw-r--r--src/common/hexchat.h10
-rw-r--r--src/common/inbound.c114
-rw-r--r--src/common/inbound.h2
-rw-r--r--src/common/modes.c2
-rw-r--r--src/common/notify.c64
-rw-r--r--src/common/notify.h5
-rw-r--r--src/common/outbound.c72
-rw-r--r--src/common/outbound.h1
-rw-r--r--src/common/plugin.c42
-rw-r--r--src/common/plugin.h1
-rw-r--r--src/common/proto-irc.c43
-rw-r--r--src/common/server.c14
-rw-r--r--src/common/servlist.c12
-rw-r--r--src/common/servlist.h1
-rw-r--r--src/common/text.c8
-rw-r--r--src/common/textevents.in16
-rw-r--r--src/common/url.c238
-rw-r--r--src/common/util.c239
-rw-r--r--src/common/util.h4
-rw-r--r--src/fe-gtk/Makefile.am4
-rw-r--r--src/fe-gtk/about.c162
-rw-r--r--src/fe-gtk/about.h25
-rw-r--r--src/fe-gtk/chanlist.c1
-rw-r--r--src/fe-gtk/dccgui.c74
-rw-r--r--src/fe-gtk/fe-gtk.c23
-rw-r--r--src/fe-gtk/fe-gtk.vcxproj2
-rw-r--r--src/fe-gtk/fe-gtk.vcxproj.filters6
-rw-r--r--src/fe-gtk/fkeys.c32
-rw-r--r--src/fe-gtk/gtkutil.c123
-rw-r--r--src/fe-gtk/gtkutil.h8
-rw-r--r--src/fe-gtk/joind.c8
-rw-r--r--src/fe-gtk/maingui.c6
-rw-r--r--src/fe-gtk/menu.c57
-rw-r--r--src/fe-gtk/notifygui.c1
-rw-r--r--src/fe-gtk/plugingui.c53
-rw-r--r--src/fe-gtk/servlistgui.c18
-rw-r--r--src/fe-gtk/setup.c31
-rw-r--r--src/fe-gtk/urlgrab.c2
-rw-r--r--src/fe-gtk/xtext.c47
-rw-r--r--src/htm/Makefile.am12
-rw-r--r--src/htm/thememan.in3
45 files changed, 1096 insertions, 507 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 78856692..6cb77148 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -12,3 +12,7 @@ gtk_fe = fe-gtk
 endif
 
 SUBDIRS = pixmaps common $(gtk_fe) $(text_fe)
+
+if WITH_TM
+SUBDIRS += htm
+endif
diff --git a/src/common/dcc.c b/src/common/dcc.c
index 1137c444..5f4b1190 100644
--- a/src/common/dcc.c
+++ b/src/common/dcc.c
@@ -227,6 +227,15 @@ is_dcc (struct DCC *dcc)
 	return FALSE;
 }
 
+gboolean
+is_dcc_completed (struct DCC *dcc)
+{
+	if (dcc != NULL)
+		return (dcc->dccstat == STAT_FAILED || dcc->dccstat == STAT_DONE || dcc->dccstat == STAT_ABORTED);
+
+	return FALSE;
+}
+
 /* this is called from hexchat.c:hexchat_misc_checks() every 1 second. */
 
 void
diff --git a/src/common/dcc.h b/src/common/dcc.h
index e3163c8a..acb87f34 100644
--- a/src/common/dcc.h
+++ b/src/common/dcc.h
@@ -117,6 +117,7 @@ struct dccstat_info
 extern struct dccstat_info dccstat[];
 
 gboolean is_dcc (struct DCC *dcc);
+gboolean is_dcc_completed (struct DCC *dcc);
 void dcc_abort (session *sess, struct DCC *dcc);
 void dcc_get (struct DCC *dcc);
 int dcc_resume (struct DCC *dcc);
diff --git a/src/common/fe.h b/src/common/fe.h
index 22db38df..91bf0a5b 100644
--- a/src/common/fe.h
+++ b/src/common/fe.h
@@ -125,11 +125,12 @@ void fe_get_str (char *prompt, char *def, void *callback, void *ud);
 void fe_get_int (char *prompt, int def, void *callback, void *ud);
 #define FRF_WRITE 1				/* save file */
 #define FRF_MULTIPLE 2			/* multi-select */
-#define FRF_ADDFOLDER 4			/* add ~/.config/hexchat to favourites */
+#define FRF_RECENTLYUSED 4		/* let gtk decide start dir instead of our config */
 #define FRF_CHOOSEFOLDER 8		/* choosing a folder only */
 #define FRF_FILTERISINITIAL 16	/* filter is initial directory */
 #define FRF_NOASKOVERWRITE 32	/* don't ask to overwrite existing files */
 #define FRF_EXTENSIONS 64		/* specify file extensions to be displayed */
+#define FRF_MIMETYPES 128		/* specify file mimetypes to be displayed */
 void fe_get_file (const char *title, char *initial,
 				 void (*callback) (void *userdata, char *file), void *userdata,
 				 int flags);
diff --git a/src/common/hexchat.h b/src/common/hexchat.h
index 76d6c3c3..68da144d 100644
--- a/src/common/hexchat.h
+++ b/src/common/hexchat.h
@@ -461,6 +461,12 @@ struct msproxy_state_t
 	unsigned char		seq_sent;		/* seq number of last packet sent.	*/
 };
 
+/* SASL Mechanisms */
+#define MECH_PLAIN 0
+#define MECH_BLOWFISH 1
+#define MECH_AES 2
+#define MECH_EXTERNAL 3
+
 typedef struct server
 {
 	/*  server control operations (in server*.c) */
@@ -598,9 +604,13 @@ typedef struct server
 	unsigned int have_sasl:1;		/* SASL capability */
 	unsigned int have_except:1;	/* ban exemptions +e */
 	unsigned int have_invite:1;	/* invite exemptions +I */
+	unsigned int have_cert:1;	/* have loaded a cert */
 	unsigned int using_cp1255:1;	/* encoding is CP1255/WINDOWS-1255? */
 	unsigned int using_irc:1;		/* encoding is "IRC" (CP1252/UTF-8 hybrid)? */
 	unsigned int use_who:1;			/* whether to use WHO command to get dcc_ip */
+	unsigned int sasl_mech;			/* mechanism for sasl auth */
+	unsigned int sent_saslauth:1;	/* have sent AUTHENICATE yet */
+	unsigned int sent_capend:1;	/* have sent CAP END yet */
 #ifdef USE_OPENSSL
 	unsigned int use_ssl:1;				  /* is server SSL capable? */
 	unsigned int accept_invalid_cert:1;/* ignore result of server's cert. verify */
diff --git a/src/common/inbound.c b/src/common/inbound.c
index f57207d1..fca5f071 100644
--- a/src/common/inbound.c
+++ b/src/common/inbound.c
@@ -392,6 +392,8 @@ inbound_action (session *sess, char *chan, char *from, char *ip, char *text,
 		user->lasttalk = time (0);
 		if (user->account)
 			id = TRUE;
+		if (user->me)
+			fromme = TRUE;
 	}
 
 	inbound_make_idtext (serv, idtext, sizeof (idtext), id);
@@ -459,6 +461,8 @@ inbound_chanmsg (server *serv, session *sess, char *chan, char *from,
 			id = TRUE;
 		nickchar[0] = user->prefix[0];
 		user->lasttalk = time (0);
+		if (user->me)
+			fromme = TRUE;
 	}
 
 	if (fromme)
@@ -1450,7 +1454,12 @@ nowindow:
 static int
 inbound_exec_eom_cmd (char *str, void *sess)
 {
-	handle_command (sess, (str[0] == '/') ? str + 1 : str, TRUE);
+	char *cmd;
+
+	cmd = command_insert_vars ((session*)sess, (str[0] == '/') ? str + 1 : str);
+	handle_command ((session*)sess, cmd, TRUE);
+	g_free (cmd);
+
 	return 1;
 }
 
@@ -1557,8 +1566,6 @@ void
 inbound_cap_ack (server *serv, char *nick, char *extensions,
 					  const message_tags_data *tags_data)
 {
-	char *pass; /* buffer for SASL password */
-
 	EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
 								  NULL, NULL, 0, tags_data->timestamp);
 
@@ -1594,20 +1601,25 @@ inbound_cap_ack (server *serv, char *nick, char *extensions,
 
 	if (strstr (extensions, "sasl") != NULL)
 	{
-		char *user;
-
 		serv->have_sasl = TRUE;
+		serv->sent_saslauth = FALSE;
 
-		user = (((ircnet *)serv->network)->user) 
-			? (((ircnet *)serv->network)->user) : prefs.hex_irc_user_name;
-
-		EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, NULL,
-									  NULL,	NULL,	0,	tags_data->timestamp);
+#ifdef USE_OPENSSL
+		if (serv->loginmethod == LOGIN_SASLEXTERNAL)
+		{
+			serv->sasl_mech = MECH_EXTERNAL;
+			tcp_send_len (serv, "AUTHENTICATE EXTERNAL\r\n", 23);
+		}
+		else
+		{
+			/* default to most secure, it will fallback if not supported */
+			serv->sasl_mech = MECH_AES;
+			tcp_send_len (serv, "AUTHENTICATE DH-AES\r\n", 21);
+		}
+#else
+		serv->sasl_mech = MECH_PLAIN;
 		tcp_send_len (serv, "AUTHENTICATE PLAIN\r\n", 20);
-
-		pass = encode_sasl_pass (user, serv->password);
-		tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass);
-		free (pass);
+#endif
 	}
 }
 
@@ -1678,9 +1690,9 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
 		}
 		
 		/* if the SASL password is set AND auth mode is set to SASL, request SASL auth */
-		if (serv->loginmethod == LOGIN_SASL
-			 && strcmp (extension, "sasl") == 0
-			 && strlen (serv->password) != 0)
+		if (!strcmp (extension, "sasl")
+			&& ((serv->loginmethod == LOGIN_SASL && strlen (serv->password) != 0)
+			|| (serv->loginmethod == LOGIN_SASLEXTERNAL && serv->have_cert)))
 		{
 			strcat (buffer, "sasl ");
 			want_cap = 1;
@@ -1701,6 +1713,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
 	if (!want_sasl)
 	{
 		/* if we use SASL, CAP END is dealt via raw numerics */
+		serv->sent_capend = TRUE;
 		tcp_send_len (serv, "CAP END\r\n", 9);
 	}
 }
@@ -1708,6 +1721,7 @@ inbound_cap_ls (server *serv, char *nick, char *extensions_str,
 void
 inbound_cap_nak (server *serv, const message_tags_data *tags_data)
 {
+	serv->sent_capend = TRUE;
 	tcp_send_len (serv, "CAP END\r\n", 9);
 }
 
@@ -1718,3 +1732,69 @@ inbound_cap_list (server *serv, char *nick, char *extensions,
 	EMIT_SIGNAL_TIMESTAMP (XP_TE_CAPACK, serv->server_session, nick, extensions,
 								  NULL, NULL, 0, tags_data->timestamp);
 }
+
+static const char *sasl_mechanisms[] =
+{
+	"PLAIN",
+	"DH-BLOWFISH",
+	"DH-AES",
+	"EXTERNAL"
+};
+
+void
+inbound_sasl_authenticate (server *serv, char *data)
+{
+		char *user, *pass = NULL;
+		const char *mech = sasl_mechanisms[serv->sasl_mech];
+
+		user = (((ircnet*)serv->network)->user)
+				? (((ircnet*)serv->network)->user) : prefs.hex_irc_user_name;
+
+		switch (serv->sasl_mech)
+		{
+		case MECH_PLAIN:
+			pass = encode_sasl_pass_plain (user, serv->password);
+			break;
+#ifdef USE_OPENSSL
+		case MECH_BLOWFISH:
+			pass = encode_sasl_pass_blowfish (user, serv->password, data);
+			break;
+		case MECH_AES:
+			pass = encode_sasl_pass_aes (user, serv->password, data);
+			break;
+		case MECH_EXTERNAL:
+			pass = g_strdup ("+");
+			break;
+#endif
+		}
+
+		if (pass == NULL)
+		{
+			/* something went wrong abort */
+			serv->sent_saslauth = TRUE; /* prevent trying PLAIN */
+			tcp_sendf (serv, "AUTHENTICATE *\r\n");
+			return;
+		}
+
+		serv->sent_saslauth = TRUE;
+		tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass);
+		g_free (pass);
+
+		
+		EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLAUTH, serv->server_session, user, (char*)mech,
+								NULL,	NULL,	0,	0);
+}
+
+int
+inbound_sasl_error (server *serv)
+{
+	/* If server sent 904 before we sent password,
+		* mech not support so fallback to next mech */
+	if (!serv->sent_saslauth && serv->sasl_mech != MECH_EXTERNAL && serv->sasl_mech != MECH_PLAIN)
+	{
+		serv->sasl_mech -= 1;
+		tcp_sendf (serv, "AUTHENTICATE %s\r\n", sasl_mechanisms[serv->sasl_mech]);
+		return 1;
+	}
+	return 0;
+}
diff --git a/src/common/inbound.h b/src/common/inbound.h
index cbb04890..40eeb8f6 100644
--- a/src/common/inbound.h
+++ b/src/common/inbound.h
@@ -95,6 +95,8 @@ void inbound_cap_ls (server *serv, char *nick, char *extensions,
 void inbound_cap_nak (server *serv, const message_tags_data *tags_data);
 void inbound_cap_list (server *serv, char *nick, char *extensions,
 							  const message_tags_data *tags_data);
+void inbound_sasl_authenticate (server *serv, char *data);
+int inbound_sasl_error (server *serv);
 void do_dns (session *sess, char *nick, char *host,
 				 const message_tags_data *tags_data);
 gboolean alert_match_word (char *word, char *masks);
diff --git a/src/common/modes.c b/src/common/modes.c
index 420f3e8c..416805c3 100644
--- a/src/common/modes.c
+++ b/src/common/modes.c
@@ -848,7 +848,7 @@ inbound_005 (server * serv, char *word[], const message_tags_data *tags_data)
 				serv->p_cmp = (void *)g_ascii_strcasecmp;
 		} else if (strncmp (word[w], "CHARSET=", 8) == 0)
 		{
-			if (g_ascii_strcasecmp (word[w] + 8, "UTF-8") == 0)
+			if (g_ascii_strncasecmp (word[w] + 8, "UTF-8", 5) == 0)
 			{
 				server_set_encoding (serv, "UTF-8");
 			}
diff --git a/src/common/notify.c b/src/common/notify.c
index 944d826c..f1439140 100644
--- a/src/common/notify.c
+++ b/src/common/notify.c
@@ -284,6 +284,70 @@ notify_set_online (server * serv, char *nick,
 	notify_announce_online (serv, servnot, nick, tags_data);
 }
 
+/* monitor can send lists for numeric 730/731 */
+
+void
+notify_set_offline_list (server * serv, char *users, int quiet,
+						  const message_tags_data *tags_data)
+{
+	struct notify_per_server *servnot;
+	char nick[NICKLEN];
+	char *token, *chr;
+	int pos;
+
+	token = strtok (users, ",");
+	while (token != NULL)
+	{
+		chr = strchr (token, '!');
+		if (!chr)
+			goto end;
+
+		pos = chr - token;
+		if (pos + 1 >= sizeof(nick))
+			goto end;
+
+		memset (nick, 0, sizeof(nick));
+		strncpy (nick, token, pos);
+
+		servnot = notify_find (serv, nick);
+		if (servnot)
+			notify_announce_offline (serv, servnot, nick, quiet, tags_data);
+end:
+		token = strtok (NULL, ",");
+	}
+}
+
+void
+notify_set_online_list (server * serv, char *users,
+						 const message_tags_data *tags_data)
+{
+	struct notify_per_server *servnot;
+	char nick[NICKLEN];
+	char *token, *chr;
+	int pos;
+
+	token = strtok (users, ",");
+	while (token != NULL)
+	{
+		chr = strchr (token, '!');
+		if (!chr)
+			goto end;
+
+		pos = chr - token;
+		if (pos + 1 >= sizeof(nick))
+			goto end;
+
+		memset (nick, 0, sizeof(nick));
+		strncpy (nick, token, pos);
+
+		servnot = notify_find (serv, nick);
+		if (servnot)
+			notify_announce_online (serv, servnot, nick, tags_data);
+end:
+		token = strtok (NULL, ",");
+	}
+}
+
 static void
 notify_watch (server * serv, char *nick, int add)
 {
diff --git a/src/common/notify.h b/src/common/notify.h
index 4a6ffb35..5bf43410 100644
--- a/src/common/notify.h
+++ b/src/common/notify.h
@@ -47,6 +47,11 @@ void notify_set_online (server * serv, char *nick,
 								const message_tags_data *tags_data);
 void notify_set_offline (server * serv, char *nick, int quiet,
 								 const message_tags_data *tags_data);
+/* the MONITOR stuff */
+void notify_set_online_list (server * serv, char *users,
+								const message_tags_data *tags_data);
+void notify_set_offline_list (server * serv, char *users, int quiet,
+								 const message_tags_data *tags_data);
 void notify_send_watches (server * serv);
 
 /* the general stuff */
diff --git a/src/common/outbound.c b/src/common/outbound.c
index 9ddacb75..a35ba429 100644
--- a/src/common/outbound.c
+++ b/src/common/outbound.c
@@ -487,19 +487,19 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop)
 			switch (type)
 			{
 			case 0:
-				snprintf (buf, sizeof (buf), "%s%s *!*@%s.*", mode, p2, domain);
+				snprintf (buf, sizeof (buf), "%s %s *!*@%s.*", mode, p2, domain);
 				break;
 
 			case 1:
-				snprintf (buf, sizeof (buf), "%s%s *!*@%s", mode, p2, fullhost);
+				snprintf (buf, sizeof (buf), "%s %s *!*@%s", mode, p2, fullhost);
 				break;
 
 			case 2:
-				snprintf (buf, sizeof (buf), "%s%s *!%s@%s.*", mode, p2, username, domain);
+				snprintf (buf, sizeof (buf), "%s %s *!%s@%s.*", mode, p2, username, domain);
 				break;
 
 			case 3:
-				snprintf (buf, sizeof (buf), "%s%s *!%s@%s", mode, p2, username, fullhost);
+				snprintf (buf, sizeof (buf), "%s %s *!%s@%s", mode, p2, username, fullhost);
 				break;
 			}
 		} else
@@ -507,19 +507,19 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop)
 			switch (type)
 			{
 			case 0:
-				snprintf (buf, sizeof (buf), "%s%s *!*@*%s", mode, p2, domain);
+				snprintf (buf, sizeof (buf), "%s %s *!*@*%s", mode, p2, domain);
 				break;
 
 			case 1:
-				snprintf (buf, sizeof (buf), "%s%s *!*@%s", mode, p2, fullhost);
+				snprintf (buf, sizeof (buf), "%s %s *!*@%s", mode, p2, fullhost);
 				break;
 
 			case 2:
-				snprintf (buf, sizeof (buf), "%s%s *!%s@*%s", mode, p2, username, domain);
+				snprintf (buf, sizeof (buf), "%s %s *!%s@*%s", mode, p2, username, domain);
 				break;
 
 			case 3:
-				snprintf (buf, sizeof (buf), "%s%s *!%s@%s", mode, p2, username, fullhost);
+				snprintf (buf, sizeof (buf), "%s %s *!%s@%s", mode, p2, username, fullhost);
 				break;
 			}
 		}
@@ -3498,6 +3498,39 @@ cmd_unload (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 	return FALSE;
 }
 
+static int
+cmd_reload (struct session *sess, char *tbuf, char *word[], char *word_eol[])
+{
+#ifdef USE_PLUGIN
+	int len, by_file = FALSE;
+
+	len = strlen (word[2]);
+#ifdef WIN32
+	if (len > 4 && g_ascii_strcasecmp (word[2] + len - 4, ".dll") == 0)
+#else
+#if defined(__hpux)
+	if (len > 3 && g_ascii_strcasecmp (word[2] + len - 3, ".sl") == 0)
+#else
+	if (len > 3 && g_ascii_strcasecmp (word[2] + len - 3, ".so") == 0)
+#endif
+#endif
+		by_file = TRUE;
+
+	switch (plugin_reload (sess, word[2], by_file))
+	{
+	case 0: /* error */
+			PrintText (sess, _("No such plugin found.\n"));
+			break;
+	case 1: /* success */
+			return TRUE;
+	case 2: /* fake plugin, we know it exists but scripts should handle it. */
+			return TRUE;
+	}
+#endif
+
+	return FALSE;
+}
+
 static server *
 find_server_from_hostname (char *hostname)
 {
@@ -3782,8 +3815,8 @@ const struct commands xc_cmds[] = {
 	 N_("BAN <mask> [<bantype>], bans everyone matching the mask from the current channel. If they are already on the channel this doesn't kick them (needs chanop)")},
 	{"CHANOPT", cmd_chanopt, 0, 0, 1, N_("CHANOPT [-quiet] <variable> [<value>]")},
 	{"CHARSET", cmd_charset, 0, 0, 1, N_("CHARSET [<encoding>], get or set the encoding used for the current connection")},
-	{"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY], Clears the current text window or command history")},
-	{"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE, Closes the current window/tab")},
+	{"CLEAR", cmd_clear, 0, 0, 1, N_("CLEAR [ALL|HISTORY|[-]<amount>], Clears the current text window or command history")},
+	{"CLOSE", cmd_close, 0, 0, 1, N_("CLOSE [-m], Closes the current window/tab or all queries")},
 
 	{"COUNTRY", cmd_country, 0, 0, 1,
 	 N_("COUNTRY [-s] <code|wildcard>, finds a country code, eg: au = australia")},
@@ -3883,7 +3916,7 @@ const struct commands xc_cmds[] = {
 	{"MODE", cmd_mode, 1, 0, 1, 0},
 	{"MOP", cmd_mop, 1, 1, 1,
 	 N_("MOP, Mass op's all users in the current channel (needs chanop)")},
-	{"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message")},
+	{"MSG", cmd_msg, 0, 0, 1, N_("MSG <nick> <message>, sends a private message, message \".\" to send to last nick or prefix with \"=\" for dcc chat")},
 
 	{"NAMES", cmd_names, 1, 0, 1,
 	 N_("NAMES, Lists the nicks on the current channel")},
@@ -3918,7 +3951,7 @@ const struct commands xc_cmds[] = {
 	 N_("RECONNECT [<host>] [<port>] [<password>], Can be called just as /RECONNECT to reconnect to the current server or with /RECONNECT ALL to reconnect to all the open servers")},
 #endif
 	{"RECV", cmd_recv, 1, 0, 1, N_("RECV <text>, send raw data to HexChat, as if it was received from the IRC server")},
-
+	{"RELOAD", cmd_reload, 0, 0, 1, N_("RELOAD <name>, reloads a plugin or script")},
 	{"SAY", cmd_say, 0, 0, 1,
 	 N_("SAY <text>, sends the text to the object in the current window")},
 	{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
@@ -4551,7 +4584,6 @@ 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;
 
@@ -4563,9 +4595,7 @@ handle_command (session *sess, char *cmd, int check_spch)
 	command_level++;
 	/* anything below MUST DEC command_level before returning */
 
-	cmd_vars = command_insert_vars (sess, cmd);
-
-	len = strlen (cmd_vars);
+	len = strlen (cmd);
 	if (len >= sizeof (pdibuf_static))
 	{
 		pdibuf = malloc (len + 1);
@@ -4585,7 +4615,7 @@ handle_command (session *sess, char *cmd, int check_spch)
 	}
 
 	/* split the text into words and word_eol */
-	process_data_init (pdibuf, cmd_vars, word, word_eol, TRUE, TRUE);
+	process_data_init (pdibuf, cmd, 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). */
@@ -4596,12 +4626,12 @@ handle_command (session *sess, char *cmd, int check_spch)
 	/* redo it without quotes processing, for some commands like /JOIN */
 	if (int_cmd && !int_cmd->handle_quotes)
 	{
-		process_data_init (pdibuf, cmd_vars, word, word_eol, FALSE, FALSE);
+		process_data_init (pdibuf, cmd, word, word_eol, FALSE, FALSE);
 	}
 
 	if (check_spch && prefs.hex_input_perc_color)
 	{
-		check_special_chars (cmd_vars, prefs.hex_input_perc_ascii);
+		check_special_chars (cmd, prefs.hex_input_perc_ascii);
 	}
 
 	if (plugin_emit_command (sess, word[1], word, word_eol))
@@ -4668,7 +4698,7 @@ handle_command (session *sess, char *cmd, int check_spch)
 		}
 		else
 		{
-			sess->server->p_raw (sess->server, cmd_vars);
+			sess->server->p_raw (sess->server, cmd);
 		}
 	}
 
@@ -4685,8 +4715,6 @@ xit:
 		free (tbuf);
 	}
 
-	g_free (cmd_vars);
-
 	return ret;
 }
 
diff --git a/src/common/outbound.h b/src/common/outbound.h
index 7dc1ba97..81a98666 100644
--- a/src/common/outbound.h
+++ b/src/common/outbound.h
@@ -25,6 +25,7 @@ extern GSList *menu_list;
 
 int auto_insert (char *dest, int destlen, unsigned char *src, char *word[], char *word_eol[],
 				 char *a, char *c, char *d, char *e, char *h, char *n, char *s, char *u);
+char *command_insert_vars (session *sess, char *cmd);
 int handle_command (session *sess, char *cmd, int check_spch);
 void process_data_init (char *buf, char *cmd, char *word[], char *word_eol[], gboolean handle_quotes, gboolean allow_escape_quotes);
 void handle_multiline (session *sess, char *cmd, int history, int nocommand);
diff --git a/src/common/plugin.c b/src/common/plugin.c
index 50157ea1..d186679a 100644
--- a/src/common/plugin.c
+++ b/src/common/plugin.c
@@ -512,6 +512,44 @@ plugin_auto_load (session *sess)
 	g_free (sub_dir);
 }
 
+int
+plugin_reload (session *sess, char *name, int by_filename)
+{
+	GSList *list;
+	char *filename;
+	char *ret;
+	hexchat_plugin *pl;
+
+	list = plugin_list;
+	while (list)
+	{
+		pl = list->data;
+		/* static-plugins (plugin-timer.c) have a NULL filename */
+		if ((by_filename && pl->filename && g_ascii_strcasecmp (name, pl->filename) == 0) ||
+			 (by_filename && pl->filename && g_ascii_strcasecmp (name, file_part (pl->filename)) == 0) ||
+			(!by_filename && g_ascii_strcasecmp (name, pl->name) == 0))
+		{
+			/* statically linked plugins have a NULL filename */
+			if (pl->filename != NULL && !pl->fake)
+			{
+				filename = g_strdup (pl->filename);
+				plugin_free (pl, TRUE, FALSE);
+				ret = plugin_load (sess, filename, NULL);
+				g_free (filename);
+				if (ret == NULL)
+					return 1;
+				else
+					return 0;
+			}
+			else
+				return 2;
+		}
+		list = list->next;
+	}
+
+	return 0;
+}
+
 #endif
 
 static GSList *
@@ -1343,7 +1381,7 @@ hexchat_list_fields (hexchat_plugin *ph, const char *name)
 	};
 	static const char * const channels_fields[] =
 	{
-		"schannel",	"schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes",
+		"schannel",	"schannelkey", "schantypes", "pcontext", "iflags", "iid", "ilag", "imaxmodes",
 		"snetwork", "snickmodes", "snickprefixes", "iqueue", "sserver", "itype", "iusers",
 		NULL
 	};
@@ -1438,6 +1476,8 @@ hexchat_list_str (hexchat_plugin *ph, hexchat_list *xlist, const char *name)
 		{
 		case 0x2c0b7d03: /* channel */
 			return ((session *)data)->channel;
+		case 0x8cea5e7c: /* channelkey */
+			return ((session *)data)->channelkey;
 		case 0x577e0867: /* chantypes */
 			return ((session *)data)->server->chantypes;
 		case 0x38b735af: /* context */
diff --git a/src/common/plugin.h b/src/common/plugin.h
index f75639e9..ee9da8c1 100644
--- a/src/common/plugin.h
+++ b/src/common/plugin.h
@@ -164,6 +164,7 @@ struct _hexchat_plugin
 #endif
 
 char *plugin_load (session *sess, char *filename, char *arg);
+int plugin_reload (session *sess, char *name, int by_filename);
 void plugin_add (session *sess, char *filename, void *handle, void *init_func, void *deinit_func, char *arg, int fake);
 int plugin_kill (char *name, int by_filename);
 void plugin_kill_all (void);
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c
index 128c0c85..250a2937 100644
--- a/src/common/proto-irc.c
+++ b/src/common/proto-irc.c
@@ -45,11 +45,11 @@
 #include "url.h"
 #include "servlist.h"
 
-
 static void
 irc_login (server *serv, char *user, char *realname)
 {
 	tcp_sendf (serv, "CAP LS\r\n");		/* start with CAP LS as Charybdis sasl.txt suggests */
+	serv->sent_capend = FALSE;	/* track if we have finished */
 
 	if (serv->password[0] && serv->loginmethod == LOGIN_PASS)
 	{
@@ -465,8 +465,6 @@ process_numeric (session * sess, int n,
 	server *serv = sess->server;
 	/* show whois is the server tab */
 	session *whois_sess = serv->server_session;
-
-	char *ex;
 	
 	/* unless this setting is on */
 	if (prefs.hex_irc_whois_front)
@@ -678,6 +676,15 @@ process_numeric (session * sess, int n,
 		handle_mode (serv, word, word_eol, "", TRUE, tags_data);
 		break;
 
+	case 328: /* channel url */
+		sess = find_channel (serv, word[4]);
+		if (sess)
+		{
+			EMIT_SIGNAL_TIMESTAMP (XP_TE_CHANURL, sess, word[4], word[5] + 1,
+									NULL, NULL, 0, tags_data->timestamp); 
+		}
+		break;
+
 	case 329:
 		sess = find_channel (serv, word[4]);
 		if (sess)
@@ -933,17 +940,11 @@ process_numeric (session * sess, int n,
 		break;
 
 	case 730: /* RPL_MONONLINE */
-		ex = strchr (word[4], '!'); /* only send the nick */
-		if (ex)
-			ex[0] = 0;
-		notify_set_online (serv, word[4] + 1, tags_data);
+		notify_set_online_list (serv, word[4] + 1, tags_data);
 		break;
 
 	case 731: /* RPL_MONOFFLINE */
-		ex = strchr (word[4], '!'); /* only send the nick */
-		if (ex)
-			ex[0] = 0;
-		notify_set_offline (serv, word[4] + 1, FALSE, tags_data);
+		notify_set_offline_list (serv, word[4] + 1, FALSE, tags_data);
 		break;
 
 	case 900:	/* successful SASL 'logged in as ' */
@@ -952,14 +953,20 @@ process_numeric (session * sess, int n,
 									  tags_data->timestamp);
 		break;
 	case 903:	/* successful SASL auth */
-	case 904:	/* aborted SASL auth */
+	case 904:	/* failed SASL auth */
+		if (inbound_sasl_error (serv))
+			break; /* might retry */
 	case 905:	/* failed SASL auth */
-	case 906:	/* registration completes before SASL auth */
+	case 906:	/* aborted */
 	case 907:	/* attempting to re-auth after a successful auth */
 		EMIT_SIGNAL_TIMESTAMP (XP_TE_SASLRESPONSE, serv->server_session, word[1],
 									  word[2], word[3], ++word_eol[4], 0,
 									  tags_data->timestamp);
-		tcp_send_len (serv, "CAP END\r\n", 9);
+		if (!serv->sent_capend)
+		{
+			serv->sent_capend = TRUE;
+			tcp_send_len (serv, "CAP END\r\n", 9);
+		}
 		break;
 
 	default:
@@ -1144,7 +1151,6 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 		case WORDL('N','O','T','I'):
 			{
 				int id = FALSE;								/* identified */
-				char *response;
 
 				text = word_eol[4];
 				if (*text == ':')
@@ -1152,9 +1158,10 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 					text++;
 				}
 
+#ifdef USE_OPENSSL
 				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]);
+					char *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,
@@ -1165,6 +1172,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 					g_free (response);
 					return;									/* omit the CHALLENGE <hash> ALGOS message */
 				}
+#endif
 
 				if (serv->have_idmsg)
 				{
@@ -1320,8 +1328,9 @@ process_named_servermsg (session *sess, char *buf, char *rawname, char *word_eol
 									  tags_data->timestamp);
 		return;
 	}
-	if (!strncmp (buf, "AUTHENTICATE +", 14))	/* omit SASL "empty" responses */
+	if (!strncmp (buf, "AUTHENTICATE", 12))
 	{
+		inbound_sasl_authenticate (sess->server, word_eol[2]);
 		return;
 	}
 
diff --git a/src/common/server.c b/src/common/server.c
index e59a7ee3..eea7ce08 100644
--- a/src/common/server.c
+++ b/src/common/server.c
@@ -1049,7 +1049,8 @@ server_cleanup (server * serv)
 #ifdef USE_OPENSSL
 	if (serv->ssl)
 	{
-		_SSL_close (serv->ssl);
+		SSL_shutdown (serv->ssl);
+		SSL_free (serv->ssl);
 		serv->ssl = NULL;
 	}
 #endif
@@ -1705,18 +1706,25 @@ server_connect (server *serv, char *hostname, int port, int no_login)
 	if (serv->use_ssl)
 	{
 		char *cert_file;
+		serv->have_cert = FALSE;
 
 		/* first try network specific cert/key */
 		cert_file = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "certs" G_DIR_SEPARATOR_S "%s.pem",
 					 get_xdir (), server_get_network (serv, TRUE));
 		if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
-			SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+		{
+			if (SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+				serv->have_cert = TRUE;
+		}
 		else
 		{
 			/* if that doesn't exist, try <config>/certs/client.pem */
 			cert_file = g_build_filename (get_xdir (), "certs", "client.pem", NULL);
 			if (SSL_CTX_use_certificate_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
-				SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM);
+			{
+				if (SSL_CTX_use_PrivateKey_file (ctx, cert_file, SSL_FILETYPE_PEM) == 1)
+					serv->have_cert = TRUE;
+			}
 		}
 		g_free (cert_file);
 	}
diff --git a/src/common/servlist.c b/src/common/servlist.c
index ceee3455..3ccd81a6 100644
--- a/src/common/servlist.c
+++ b/src/common/servlist.c
@@ -251,11 +251,13 @@ static const struct defaultserver def[] =
 	{0,			"irc.ggn.net"},
 	{0,			"irc.vendetta.com"},
 
-	{"freenode", 0, "#hexchat", 0, LOGIN_SASL},
+	{"freenode", 0, 0, 0, LOGIN_SASL},
 #ifdef USE_OPENSSL
 	{0,				"chat.freenode.net/+6697"},
 #endif
 	{0,				"chat.freenode.net"},
+	/* irc. points to chat. but many users and urls still reference it */
+	{0,				"irc.freenode.net"},
 
 /*	{"Freeworld",	0},
 	{0,			"kabel.freeworld.nu"},
@@ -418,6 +420,9 @@ static const struct defaultserver def[] =
 
 /*	{"NullusNet",	0},
 	{0,			"irc.nullus.net"},*/
+	
+	{"ObsidianIRC",  0},
+	{0,      "irc.obsidianirc.net"}, 
 
 	{"Oceanius", 0},
 	{0,			"irc.oceanius.com"},
@@ -535,7 +540,7 @@ static const struct defaultserver def[] =
 	{0,			"eu.spidernet.org"},
 	{0,			"irc.spidernet.org"},*/
 	
-	{"SpotChat", 0},
+	{"SpotChat", 0, 0, 0, LOGIN_SASL},
 #ifdef USE_OPENSSL
 	{0,			"irc.spotchat.org/+6697"},
 #endif
@@ -565,6 +570,9 @@ static const struct defaultserver def[] =
 #endif
 	{0,			"irc.swiftirc.net/6667"},
 
+	{"TinyCrab",			0},
+	{0,			"irc.tinycrab.net"},
+
 /*	{"TNI3",			0},
 	{0,			"irc.tni3.com"},*/
 
diff --git a/src/common/servlist.h b/src/common/servlist.h
index 45b6dad6..6d6f1bd3 100644
--- a/src/common/servlist.h
+++ b/src/common/servlist.h
@@ -79,6 +79,7 @@ extern GSList *network_list;
 #define LOGIN_PASS				7
 #define LOGIN_CHALLENGEAUTH		8
 #define LOGIN_CUSTOM			9
+#define LOGIN_SASLEXTERNAL		10
 
 #define CHALLENGEAUTH_ALGO		"HMAC-SHA-256"
 #define CHALLENGEAUTH_NICK		"Q@CServe.quakenet.org"
diff --git a/src/common/text.c b/src/common/text.c
index b825faba..e0cdb5ee 100644
--- a/src/common/text.c
+++ b/src/common/text.c
@@ -1297,7 +1297,8 @@ static char * const pevt_generic_channel_help[] = {
 };
 
 static char * const pevt_saslauth_help[] = {
-	N_("Username")
+	N_("Username"),
+	N_("Mechanism")
 };
 
 static char * const pevt_saslresponse_help[] = {
@@ -1359,6 +1360,11 @@ static char * const pevt_chanmodes_help[] = {
 	N_("Modes string"),
 };
 
+static char * const pevt_chanurl_help[] = {
+	N_("Channel Name"),
+	N_("URL"),
+};
+
 static char * const pevt_rawmodes_help[] = {
 	N_("Nickname"),
 	N_("Modes string"),
diff --git a/src/common/textevents.in b/src/common/textevents.in
index 1f86d90b..3631e363 100644
--- a/src/common/textevents.in
+++ b/src/common/textevents.in
@@ -202,6 +202,12 @@ pevt_chanunquiet_help
 %C22*%O$t%C26$1%O removes quiet on %C18$2%O
 2
 
+Channel Url
+XP_TE_CHANURL
+pevt_chanurl_help
+%C22*%O$tChannel %C22$1%O url: %C24$2
+2
+
 Channel Voice
 XP_TE_CHANVOICE
 pevt_chanvoice_help
@@ -487,7 +493,7 @@ pevt_invited_help
 Join
 XP_TE_JOIN
 pevt_join_help
-%C23*$t$1 ($3) has joined
+%C23*$t$1 ($3%C23) has joined
 3
 
 Keyword
@@ -619,13 +625,13 @@ n0
 Part
 XP_TE_PART
 pevt_part_help
-%C24*$t$1 ($2) has left
+%C24*$t$1 ($2%C24) has left
 3
 
 Part with Reason
 XP_TE_PARTREASON
 pevt_partreason_help
-%C24*$t$1 ($2) has left ($4)
+%C24*$t$1 ($2%C24) has left ($4)
 4
 
 Ping Reply
@@ -697,8 +703,8 @@ pevt_resolvinguser_help
 SASL Authenticating
 XP_TE_SASLAUTH
 pevt_saslauth_help
-%C23*%O$tAuthenticating via SASL as %C18$1%O
-1
+%C23*%O$tAuthenticating via SASL as %C18$1%O (%C24$2%O)
+2
 
 SASL Response
 XP_TE_SASLRESPONSE
diff --git a/src/common/url.c b/src/common/url.c
index 892441c8..57d8d88b 100644
--- a/src/common/url.c
+++ b/src/common/url.c
@@ -32,15 +32,22 @@
 
 void *url_tree = NULL;
 GTree *url_btree = NULL;
-static int do_an_re (const char *word, int *start, int *end, int *type);
-static GRegex *re_url (void);
-static GRegex *re_host (void);
-static GRegex *re_host6 (void);
-static GRegex *re_email (void);
-static GRegex *re_nick (void);
-static GRegex *re_channel (void);
-static GRegex *re_path (void);
-
+static gboolean regex_match (const GRegex *re, const char *word,
+							 int *start, int *end);
+static const GRegex *re_url (void);
+static const GRegex *re_host (void);
+static const GRegex *re_host6 (void);
+static const GRegex *re_email (void);
+static const GRegex *re_nick (void);
+static const GRegex *re_channel (void);
+static const GRegex *re_path (void);
+static gboolean match_nick (const char *word, int *start, int *end);
+static gboolean match_channel (const char *word, int *start, int *end);
+static gboolean match_email (const char *word, int *start, int *end);
+static gboolean match_url (const char *word, int *start, int *end);
+static gboolean match_host (const char *word, int *start, int *end);
+static gboolean match_host6 (const char *word, int *start, int *end);
+static gboolean match_path (const char *word, int *start, int *end);
 
 static int
 url_free (char *url, void *data)
@@ -189,50 +196,111 @@ static int laststart = 0;
 static int lastend = 0;
 static int lasttype = 0;
 
-static int
-strchrs (char c, char *s)
-{
-	while (*s)
-		if (c == *s++)
-			return TRUE;
-	return FALSE;
-}
+#define NICKPRE "~+!@%&"
+#define CHANPRE "#&!+"
 
-#define NICKPRE "~+!@%%&"
 int
 url_check_word (const char *word)
 {
+	struct {
+		gboolean (*match) (const char *word, int *start, int *end);
+		int type;
+	} m[] = {
+	   { match_url,     WORD_URL },
+	   { match_email,   WORD_EMAIL },
+	   { match_nick,    WORD_NICK },
+	   { match_channel, WORD_CHANNEL },
+	   { match_host6,   WORD_HOST6 },
+	   { match_host,    WORD_HOST },
+	   { match_path,    WORD_PATH },
+	   { NULL,          0}
+	};
+	int i;
+
 	laststart = lastend = lasttype = 0;
-	if (do_an_re (word, &laststart, &lastend, &lasttype))
-	{
-		switch (lasttype)
+
+	for (i = 0; m[i].match; i++)
+		if (m[i].match (word, &laststart, &lastend))
 		{
-			char *str;
-
-			case WORD_NICK:
-				if (strchrs (word[laststart], NICKPRE))
-					laststart++;
-				str = g_strndup (&word[laststart], lastend - laststart);
-				if (!userlist_find (current_sess, str))
-					lasttype = 0;
-				g_free (str);
-				return lasttype;
-			case WORD_EMAIL:
-				if (!isalnum (word[laststart]))
-					laststart++;
-				/* Fall through */
-			case WORD_URL:
-			case WORD_HOST:
-			case WORD_HOST6:
-			case WORD_CHANNEL:
-			case WORD_PATH:
-				return lasttype;
-			default:
-				return 0;	/* Should not occur */
+			lasttype = m[i].type;
+			return lasttype;
 		}
+
+	return 0;
+}
+
+static gboolean
+match_nick (const char *word, int *start, int *end)
+{
+	const server *serv = current_sess->server;
+	const char *nick_prefixes = serv ? serv->nick_prefixes : NICKPRE;
+	char *str;
+
+	if (!regex_match (re_nick (), word, start, end))
+		return FALSE;
+
+	/* ignore matches with prefixes that the server doesn't use */
+	if (strchr (NICKPRE, word[*start])
+		&& !strchr (nick_prefixes, word[*start]))
+		return FALSE;
+	
+	/* nick prefix is not part of the matched word */
+	if (strchr (nick_prefixes, word[*start]))
+		(*start)++;
+
+	str = g_strndup (&word[*start], *end - *start);
+
+	if (!userlist_find (current_sess, str))
+	{
+		g_free (str);
+		return FALSE;
 	}
-	else
-		return 0;
+
+	g_free (str);
+
+	return TRUE;
+}
+
+static gboolean
+match_channel (const char *word, int *start, int *end)
+{
+	const server *serv = current_sess->server;
+	const char *chan_prefixes = serv ? serv->chantypes : CHANPRE;
+
+	if (!regex_match (re_channel (), word, start, end))
+		return FALSE;
+
+	return strchr (chan_prefixes, word[*start]) != NULL;
+}
+
+static gboolean
+match_email (const char *word, int *start, int *end)
+{
+	return regex_match (re_email (), word, start, end);
+}
+
+static gboolean
+match_url (const char *word, int *start, int *end)
+{
+	return regex_match (re_url (), word, start, end);
+}
+
+static gboolean
+match_host (const char *word, int *start, int *end)
+{
+	return regex_match (re_host (), word, start, end);
+}
+
+static gboolean
+match_host6 (const char *word, int *start, int *end)
+{
+	return regex_match (re_host6 (), word, start, end);
+}
+
+static gboolean
+match_path (const char *word, int *start, int *end)
+{
+	return regex_match (re_path (), word, start, end);
 }
 
 /* List of IRC commands for which contents (and thus possible URLs)
@@ -307,46 +375,28 @@ url_last (int *lstart, int *lend)
 	return lasttype;
 }
 
-static int
-do_an_re(const char *word, int *start, int *end, int *type)
+static gboolean
+regex_match (const GRegex *re, const char *word, int *start, int *end)
 {
-	typedef struct func_s {
-		GRegex *(*fn)(void);
-		int type;
-	} func_t;
-	func_t funcs[] =
-	{
-		{ re_url, WORD_URL },
-		{ re_email, WORD_EMAIL },
-		{ re_channel, WORD_CHANNEL },
-		{ re_host6, WORD_HOST6 },
-		{ re_host, WORD_HOST },
-		{ re_path, WORD_PATH },
-		{ re_nick, WORD_NICK }
-	};
-
 	GMatchInfo *gmi;
-	int k;
 
-	for (k = 0; k < sizeof funcs / sizeof (func_t); k++)
+	g_regex_match (re, word, 0, &gmi);
+	
+	if (!g_match_info_matches (gmi))
 	{
-		g_regex_match (funcs[k].fn(), word, 0, &gmi);
-		if (!g_match_info_matches (gmi))
-		{
-			g_match_info_free (gmi);
-			continue;
-		}
-		while (g_match_info_matches (gmi))
-		{
-			g_match_info_fetch_pos (gmi, 0, start, end);
-			g_match_info_next (gmi, NULL);
-		}
 		g_match_info_free (gmi);
-		*type = funcs[k].type;
-		return TRUE;
+		return FALSE;
 	}
-
-	return FALSE;
+	
+	while (g_match_info_matches (gmi))
+	{
+		g_match_info_fetch_pos (gmi, 0, start, end);
+		g_match_info_next (gmi, NULL);
+	}
+	
+	g_match_info_free (gmi);
+	
+	return TRUE;
 }
 
 /*	Miscellaneous description --- */
@@ -376,7 +426,7 @@ make_re (char *grist)
 
 /*	HOST description --- */
 /* (see miscellaneous above) */
-static GRegex *
+static const GRegex *
 re_host (void)
 {
 	static GRegex *host_ret;
@@ -384,7 +434,7 @@ re_host (void)
 
 	if (host_ret) return host_ret;
 
-	grist = g_strdup_printf (
+	grist = g_strdup (
 		"("
 			"(" HOST_URL PORT ")|(" HOST ")"
 		")"
@@ -393,7 +443,7 @@ re_host (void)
 	return host_ret;
 }
 
-static GRegex *
+static const GRegex *
 re_host6 (void)
 {
 	static GRegex *host6_ret;
@@ -401,7 +451,7 @@ re_host6 (void)
 
 	if (host6_ret) return host6_ret;
 
-	grist = g_strdup_printf (
+	grist = g_strdup (
 		"("
 			"(" IPV6ADDR ")|(" "\\[" IPV6ADDR "\\]" PORT ")"
 		")"
@@ -489,7 +539,7 @@ struct
 	{ NULL,        "",  0}
 };
 
-static GRegex *
+static const GRegex *
 re_url (void)
 {
 	static GRegex *url_ret = NULL;
@@ -546,7 +596,7 @@ re_url (void)
 /*	EMAIL description --- */
 #define EMAIL "[a-z][-_a-z0-9]+@" "(" HOST_URL ")"
 
-static GRegex *
+static const GRegex *
 re_email (void)
 {
 	static GRegex *email_ret;
@@ -554,7 +604,7 @@ re_email (void)
 
 	if (email_ret) return email_ret;
 
-	grist = g_strdup_printf (
+	grist = g_strdup (
 		"("
 			EMAIL
 		")"
@@ -577,12 +627,12 @@ re_email (void)
 /* Rationale is that do_an_re() above will anyway look up what */
 /* we find, and that WORD_NICK is the last item in the array */
 /* that do_an_re() runs through. */
-#define NICK0 "[" NICKPRE "]?[" NICKLET NICKDIG NICKSPE "]"
+#define NICK0 "^[" NICKPRE "]?[" NICKLET NICKDIG NICKSPE "]"
 #endif
 #define NICK1 "[" NICKHYP NICKLET NICKDIG NICKSPE "]*"
 #define NICK	NICK0 NICK1
 
-static GRegex *
+static const GRegex *
 re_nick (void)
 {
 	static GRegex *nick_ret;
@@ -590,7 +640,7 @@ re_nick (void)
 
 	if (nick_ret) return nick_ret;
 
-	grist = g_strdup_printf (
+	grist = g_strdup (
 		"("
 			NICK
 		")"
@@ -600,9 +650,9 @@ re_nick (void)
 }
 
 /*	CHANNEL description --- */
-#define CHANNEL "#[^ \t\a,:]+"
+#define CHANNEL "[" CHANPRE "][^ \t\a,]+(?:,[" CHANPRE "][^ \t\a,]+)*"
 
-static GRegex *
+static const GRegex *
 re_channel (void)
 {
 	static GRegex *channel_ret;
@@ -610,7 +660,7 @@ re_channel (void)
 
 	if (channel_ret) return channel_ret;
 
-	grist = g_strdup_printf (
+	grist = g_strdup (
 		"("
 			CHANNEL
 		")"
@@ -628,7 +678,7 @@ re_channel (void)
 #define FS_PATH "^(/|\\./|\\.\\./).*"
 #endif
 
-static GRegex *
+static const GRegex *
 re_path (void)
 {
 	static GRegex *path_ret;
@@ -636,7 +686,7 @@ re_path (void)
 
 	if (path_ret) return path_ret;
 
-	grist = g_strdup_printf (
+	grist = g_strdup (
 		"("
 			FS_PATH
 		")"
diff --git a/src/common/util.c b/src/common/util.c
index 52464621..9771b1f6 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -58,6 +58,17 @@
 #include <socks.h>
 #endif
 
+/* SASL mechanisms */
+#ifdef USE_OPENSSL
+#include <openssl/bn.h>
+#include <openssl/rand.h>
+#include <openssl/blowfish.h>
+#include <openssl/aes.h>
+#ifndef WIN32
+#include <netinet/in.h>
+#endif
+#endif
+
 #ifndef HAVE_SNPRINTF
 #define snprintf g_snprintf
 #endif
@@ -1929,7 +1940,7 @@ get_subdirs (const char *path)
 }
 
 char *
-encode_sasl_pass (char *user, char *pass)
+encode_sasl_pass_plain (char *user, char *pass)
 {
 	int authlen;
 	char *buffer;
@@ -1944,6 +1955,230 @@ encode_sasl_pass (char *user, char *pass)
 	return encoded;
 }
 
+#ifdef USE_OPENSSL
+/* Adapted from ZNC's SASL module */
+
+static int
+parse_dh (char *str, DH **dh_out, unsigned char **secret_out, int *keysize_out)
+{
+	DH *dh;
+	guchar *data, *decoded_data;
+	guchar *secret;
+	gsize data_len;
+	guint size;
+	guint16 size16;
+	BIGNUM *pubkey;
+	gint key_size;
+
+	dh = DH_new();
+	data = decoded_data = g_base64_decode (str, &data_len);
+	if (data_len < 2)
+		goto fail;
+
+	/* prime number */
+	memcpy (&size16, data, sizeof(size16));
+	size = ntohs (size16);
+	data += 2;
+	data_len -= 2;
+
+	if (size > data_len)
+		goto fail;
+
+	dh->p = BN_bin2bn (data, size, NULL);
+	data += size;
+
+	/* Generator */
+	if (data_len < 2)
+		goto fail;
+
+	memcpy (&size16, data, sizeof(size16));
+	size = ntohs (size16);
+	data += 2;
+	data_len -= 2;
+	
+	if (size > data_len)
+		goto fail;
+
+	dh->g = BN_bin2bn (data, size, NULL);
+	data += size;
+
+	/* pub key */
+	if (data_len < 2)
+		goto fail;
+
+	memcpy (&size16, data, sizeof(size16));
+	size = ntohs(size16);
+	data += 2;
+	data_len -= 2;
+
+	pubkey = BN_bin2bn (data, size, NULL);
+	if (!(DH_generate_key (dh)))
+		goto fail;
+
+	secret = (unsigned char*)malloc (DH_size(dh));
+	key_size = DH_compute_key (secret, pubkey, dh);
+	if (key_size == -1)
+		goto fail;
+
+	g_free (decoded_data);
+
+	*dh_out = dh;
+	*secret_out = secret;
+	*keysize_out = key_size;
+	return 1;
+
+fail:
+	if (decoded_data)
+		g_free (decoded_data);
+	return 0;
+}
+
+char *
+encode_sasl_pass_blowfish (char *user, char *pass, char *data)
+{
+	DH *dh;
+	char *response, *ret;
+	unsigned char *secret;
+	unsigned char *encrypted_pass;
+	char *plain_pass;
+	BF_KEY key;
+	int key_size, length;
+	int pass_len = strlen (pass) + (8 - (strlen (pass) % 8));
+	int user_len = strlen (user);
+	guint16 size16;
+	char *in_ptr, *out_ptr;
+
+	if (!parse_dh (data, &dh, &secret, &key_size))
+		return NULL;
+	BF_set_key (&key, key_size, secret);
+
+	encrypted_pass = (guchar*)malloc (pass_len);
+	memset (encrypted_pass, 0, pass_len);
+	plain_pass = (char*)malloc (pass_len);
+	memset (plain_pass, 0, pass_len);
+	memcpy (plain_pass, pass, pass_len);
+	out_ptr = (char*)encrypted_pass;
+	in_ptr = (char*)plain_pass;
+
+	for (length = pass_len; length; length -= 8, in_ptr += 8, out_ptr += 8)
+		BF_ecb_encrypt ((unsigned char*)in_ptr, (unsigned char*)out_ptr, &key, BF_ENCRYPT);
+
+	/* Create response */
+	length = 2 + BN_num_bytes (dh->pub_key) + pass_len + user_len + 1;
+	response = (char*)malloc (length);
+	out_ptr = response;
+
+	/* our key */
+	size16 = htons ((guint16)BN_num_bytes (dh->pub_key));
+	memcpy (out_ptr, &size16, sizeof(size16));
+	out_ptr += 2;
+	BN_bn2bin (dh->pub_key, (guchar*)out_ptr);
+	out_ptr += BN_num_bytes (dh->pub_key);
+
+	/* username */
+	memcpy (out_ptr, user, user_len + 1);
+	out_ptr += user_len + 1;
+
+	/* pass */
+	memcpy (out_ptr, encrypted_pass, pass_len);
+	
+	ret = g_base64_encode ((const guchar*)response, length);
+
+	DH_free (dh);
+	free (plain_pass);
+	free (encrypted_pass);
+	free (secret);
+	free (response);
+
+	return ret;
+}
+
+char *
+encode_sasl_pass_aes (char *user, char *pass, char *data)
+{
+	DH *dh;
+	AES_KEY key;
+	char *response = NULL;
+	char *out_ptr, *ret = NULL;
+	unsigned char *secret, *ptr;
+	unsigned char *encrypted_userpass, *plain_userpass;
+	int key_size, length;
+	guint16 size16;
+	unsigned char iv[16], iv_copy[16];
+	int user_len = strlen (user) + 1;
+	int pass_len = strlen (pass) + 1;
+	int len = user_len + pass_len;
+	int padlen = 16 - (len % 16);
+	int userpass_len = len + padlen;
+
+	if (!parse_dh (data, &dh, &secret, &key_size))
+		return NULL;
+
+	encrypted_userpass = (guchar*)malloc (userpass_len);
+	memset (encrypted_userpass, 0, userpass_len);
+	plain_userpass = (guchar*)malloc (userpass_len);
+	memset (plain_userpass, 0, userpass_len);
+
+	/* create message */
+	/* format of: <username>\0<password>\0<padding> */
+	ptr = plain_userpass;
+	memcpy (ptr, user, user_len);
+	ptr += user_len;
+	memcpy (ptr, pass, pass_len);
+	ptr += pass_len;
+	if (padlen)
+	{
+		/* Padding */
+		unsigned char randbytes[16];
+		if (!RAND_bytes (randbytes, padlen))
+			goto end;
+
+		memcpy (ptr, randbytes, padlen);
+	}
+
+	if (!RAND_bytes (iv, sizeof (iv)))
+		goto end;
+
+	memcpy (iv_copy, iv, sizeof(iv));
+
+	/* Encrypt */
+	AES_set_encrypt_key (secret, key_size * 8, &key);
+	AES_cbc_encrypt(plain_userpass, encrypted_userpass, userpass_len, &key, iv_copy, AES_ENCRYPT);
+
+	/* Create response */
+	/* format of:  <size pubkey><pubkey><iv (always 16 bytes)><ciphertext> */
+	length = 2 + key_size + sizeof(iv) + userpass_len;
+	response = (char*)malloc (length);
+	out_ptr = response;
+
+	/* our key */
+	size16 = htons ((guint16)key_size);
+	memcpy (out_ptr, &size16, sizeof(size16));
+	out_ptr += 2;
+	BN_bn2bin (dh->pub_key, (guchar*)out_ptr);
+	out_ptr += key_size;
+
+	/* iv */
+	memcpy (out_ptr, iv, sizeof(iv));
+	out_ptr += sizeof(iv);
+
+	/* userpass */
+	memcpy (out_ptr, encrypted_userpass, userpass_len);
+	
+	ret = g_base64_encode ((const guchar*)response, length);
+
+end:
+	DH_free (dh);
+	free (plain_userpass);
+	free (encrypted_userpass);
+	free (secret);
+	if (response)
+		free (response);
+
+	return ret;
+}
+#endif
+
 #ifdef WIN32
 int
 find_font (const char *fontname)
@@ -1975,6 +2210,7 @@ find_font (const char *fontname)
 }
 #endif
 
+#ifdef USE_OPENSSL
 static char *
 str_sha256hash (char *string)
 {
@@ -2050,3 +2286,4 @@ challengeauth_response (char *username, char *password, char *challenge)
 
 	return (char *) digest;
 }
+#endif
diff --git a/src/common/util.h b/src/common/util.h
index 0ebd89d4..6b8d359c 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -79,7 +79,9 @@ void canonalize_key (char *key);
 int portable_mode ();
 int unity_mode ();
 GSList *get_subdirs (const char *path);
-char *encode_sasl_pass (char *user, char *pass);
+char *encode_sasl_pass_plain (char *user, char *pass);
+char *encode_sasl_pass_blowfish (char *user, char *pass, char *data);
+char *encode_sasl_pass_aes (char *user, char *pass, char *data);
 char *challengeauth_response (char *username, char *password, char *challenge);
 
 #endif
diff --git a/src/fe-gtk/Makefile.am b/src/fe-gtk/Makefile.am
index 1890edbf..24098ace 100644
--- a/src/fe-gtk/Makefile.am
+++ b/src/fe-gtk/Makefile.am
@@ -7,7 +7,7 @@ AM_CPPFLAGS = $(GUI_CFLAGS) -DLOCALEDIR=\"$(localedir)\"
 hexchat_LDADD = ../common/libhexchatcommon.a $(GUI_LIBS)
 
 EXTRA_DIST = \
-	about.h ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \
+	ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \
 	chanview-tree.c custom-list.h editlist.h fe-gtk.h fkeys.h gtkutil.h joind.h \
 	maingui.h menu.h mmx_cmod.S mmx_cmod.h notifygui.h palette.h pixmaps.h \
 	plugin-tray.h plugingui.c plugingui.h rawlog.h sexy-iso-codes.h \
@@ -26,7 +26,7 @@ sexy_spell = \
 	sexy-iso-codes.c sexy-marshal.c sexy-spell-entry.c
 endif
 
-hexchat_SOURCES = about.c ascii.c banlist.c chanlist.c chanview.c custom-list.c \
+hexchat_SOURCES = ascii.c banlist.c chanlist.c chanview.c custom-list.c \
 	dccgui.c editlist.c fe-gtk.c fkeys.c gtkutil.c ignoregui.c joind.c menu.c \
 	maingui.c $(mmx_cmod_S) notifygui.c palette.c pixmaps.c plugin-tray.c $(plugingui_c) \
 	rawlog.c servlistgui.c setup.c $(sexy_spell) textgui.c \
diff --git a/src/fe-gtk/about.c b/src/fe-gtk/about.c
deleted file mode 100644
index c47fba4f..00000000
--- a/src/fe-gtk/about.c
+++ /dev/null
@@ -1,162 +0,0 @@
-/* X-Chat
- * Copyright (C) 1998 Peter Zelezny.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "fe-gtk.h"
-
-#ifdef USE_XLIB
-#include <gdk/gdkx.h>
-#endif
-
-#include "../common/hexchat.h"
-#include "../common/util.h"
-#include "../common/hexchatc.h"
-#include "palette.h"
-#include "pixmaps.h"
-#include "gtkutil.h"
-#include "about.h"
-
-static GtkWidget *about = 0;
-
-static int
-about_close (void)
-{
-	about = 0;
-	return 0;
-}
-
-void
-menu_about (GtkWidget * wid, gpointer sess)
-{
-	GtkWidget *vbox;									/* the main vertical box inside the about dialog */
-	GtkWidget *hbox_main;								/* horizontal box for containing text on the left and logo on the right */
-	GtkWidget *vbox_logo;								/* vertical box for our logo */
-	GtkWidget *vbox_text;								/* vertical box for text */
-	GtkWidget *label_title;								/* label for the main title */
-	GtkWidget *label_subtitle;							/* label for the subtitle */
-	GtkWidget *label_info;								/* for the informative text */
-	GtkWidget *label_copyright;							/* for copyright notices */
-	GdkColor color;										/* color buffer for our nice paintings */
-	char buf[512];										/* text buffer for the labels */
-	const char *locale = NULL;
-	extern GtkWindow *parent_window;					/* maingui.c */
-
-	if (about)
-	{
-		gtk_window_present (GTK_WINDOW (about));
-		return;
-	}
-
-	/* general about dialog initialization */
-	about = gtk_dialog_new ();
-	gtk_window_set_position (GTK_WINDOW (about), GTK_WIN_POS_CENTER_ON_PARENT);
-	gtk_window_set_resizable (GTK_WINDOW (about), FALSE);
-	gtk_window_set_title (GTK_WINDOW (about), _("About "DISPLAY_NAME));
-	if (parent_window)
-	{
-		gtk_window_set_transient_for (GTK_WINDOW (about), parent_window);
-	}
-	g_signal_connect (G_OBJECT (about), "destroy", G_CALLBACK (about_close), 0);
-	vbox = GTK_DIALOG (about)->vbox;
-
-	/* main horizontal box, text on the left, logo on the right */
-	hbox_main = gtk_hbox_new (FALSE, 0);
-	gtk_container_add (GTK_CONTAINER (vbox), GTK_WIDGET (hbox_main));
-
-	/* textbox on the left */
-	vbox_text = gtk_vbox_new (FALSE, 0);
-	gtk_box_pack_start (GTK_BOX (hbox_main), vbox_text, 0, 0, 5);
-
-	/* label for title */
-	snprintf (buf, sizeof (buf), "<span size=\"x-large\"><b>"DISPLAY_NAME"</b></span>");
-	label_title = gtk_label_new (NULL);
-	gtk_misc_set_alignment (GTK_MISC (label_title), 0, 0);
-	gtk_box_pack_start (GTK_BOX (vbox_text), label_title, 0, 0, 10);
-	color.red   = 0xd7d7;
-	color.green = 0x4343;
-	color.blue  = 0x0404;
-	gtk_widget_modify_fg (label_title, GTK_STATE_NORMAL, &color);
-	gtk_label_set_markup (GTK_LABEL (label_title), buf);
-
-	/* label for subtitle */
-	snprintf (buf, sizeof (buf), "%s", _("<b>A multiplatform IRC Client</b>"));
-	label_subtitle = gtk_label_new (NULL);
-	gtk_misc_set_alignment (GTK_MISC (label_subtitle), 0, 0);
-	gtk_box_pack_start (GTK_BOX (vbox_text), label_subtitle, 0, 0, 10);
-	color.red   = 0x5555;
-	color.green = 0x5555;
-	color.blue  = 0x5555;
-	gtk_widget_modify_fg (label_subtitle, GTK_STATE_NORMAL, &color);
-	gtk_label_set_markup (GTK_LABEL (label_subtitle), buf);
-
-	/* label for additional info */
-	g_get_charset (&locale);
-	(snprintf) (buf, sizeof (buf),
-				"<b>Version:</b> "PACKAGE_VERSION"\n"
-				"<b>Compiled:</b> "__DATE__"\n"
-#ifdef WIN32
-				"<b>Portable Mode:</b> %s\n"
-				"<b>Build Type:</b> x%d\n"
-#endif
-				"<b>OS:</b> %s\n"
-				"<b>Charset:</b> %s",
-#ifdef WIN32
-				(portable_mode () ? "Yes" : "No"),
-				get_cpu_arch (),
-#endif
-				get_sys_str (0),
-				locale);
-
-	label_info = gtk_label_new (NULL);
-	gtk_misc_set_alignment (GTK_MISC (label_info), 0, 0);
-	gtk_box_pack_start (GTK_BOX (vbox_text), label_info, 0, 0, 10);
-	gtk_label_set_selectable (GTK_LABEL (label_info), TRUE);
-	gtk_label_set_markup (GTK_LABEL (label_info), buf);
-
-	/* label for copyright notices */
-	snprintf (buf, sizeof (buf), "<small>\302\251 1998-2010 Peter \305\275elezn\303\275\n\302\251 2009-2013 Berke Viktor</small>");
-	label_copyright = gtk_label_new (NULL);
-	gtk_misc_set_alignment (GTK_MISC (label_copyright), 0, 0);
-	gtk_box_pack_start (GTK_BOX (vbox_text), label_copyright, 0, 0, 10);
-	gtk_label_set_markup (GTK_LABEL (label_copyright), buf);
-
-	/* imagebox on the right */
-	vbox_logo = gtk_vbox_new (FALSE, 0);
-	gtk_box_pack_start (GTK_BOX (hbox_main), vbox_logo, 0, 0, 10);
-
-	/* the actual image */
-	wid = gtk_image_new_from_pixbuf (pix_hexchat);
-	gtk_box_pack_start (GTK_BOX (vbox_logo), wid, 0, 0, 10);
-
-	/* our close button on the bottom right */
-	wid = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
-	GTK_WIDGET_SET_FLAGS (GTK_WIDGET (wid), GTK_CAN_DEFAULT);
-	gtk_box_pack_end (GTK_BOX (GTK_DIALOG (about)->action_area), wid, 0, 0, 0);
-	gtk_widget_grab_default (wid);
-	g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (gtkutil_destroy), about);
-
-	/* pure white background for the whole about widget*/
-	color.red = color.green = color.blue = 0xffff;
-	gtk_widget_modify_bg (about, GTK_STATE_NORMAL, &color);
-
-	/* show off! */
-	gtk_widget_show_all (about);
-}
diff --git a/src/fe-gtk/about.h b/src/fe-gtk/about.h
deleted file mode 100644
index b4d5cb34..00000000
--- a/src/fe-gtk/about.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/* HexChat
- * Copyright (C) 1998-2010 Peter Zelezny.
- * Copyright (C) 2009-2013 Berke Viktor.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-#ifndef HEXCHAT_ABOUT_H
-#define HEXCHAT_ABOUT_H
-
-void menu_about (GtkWidget * wid, gpointer sess);
-
-#endif
diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c
index 0f0307b7..d0e0ea40 100644
--- a/src/fe-gtk/chanlist.c
+++ b/src/fe-gtk/chanlist.c
@@ -747,6 +747,7 @@ chanlist_opengui (server *serv, int do_refresh)
 	serv->gui->chanlist_window =
 		mg_create_generic_tab ("ChanList", tbuf, FALSE, TRUE, chanlist_closegui,
 								serv, 640, 480, &vbox, serv);
+	gtkutil_destroy_on_esc (serv->gui->chanlist_window);
 
 	gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
 	gtk_box_set_spacing (GTK_BOX (vbox), 12);
diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c
index 24d3bcbf..a3c2619a 100644
--- a/src/fe-gtk/dccgui.c
+++ b/src/fe-gtk/dccgui.c
@@ -78,6 +78,7 @@ struct dccwindow
 	GtkWidget *accept_button;
 	GtkWidget *resume_button;
 	GtkWidget *open_button;
+	GtkWidget *clear_button; /* clears aborted and completed requests */	
 
 	GtkWidget *file_label;
 	GtkWidget *address_label;
@@ -380,6 +381,50 @@ dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
 		dcc_prepare_row_send (dcc, store, &iter, FALSE);
 }
 
+/* Returns aborted and completed transfers. */
+static GSList *
+dcc_get_completed (void)
+{
+	struct DCC *dcc;
+	GtkTreeIter iter;
+	GtkTreeModel *model;	
+	GSList *completed = NULL;
+
+	model = GTK_TREE_MODEL (dccfwin.store);
+	if (gtk_tree_model_get_iter_first (model, &iter))
+	{
+		do
+		{
+			gtk_tree_model_get (model, &iter, COL_DCC, &dcc, -1);
+			if (is_dcc_completed (dcc))
+				completed = g_slist_prepend (completed, dcc);
+				
+		} while (gtk_tree_model_iter_next (model, &iter));
+	}
+
+	return completed;
+}
+
+static gboolean
+dcc_completed_transfer_exists (void)
+{
+	gboolean exist;
+	GSList *comp_list;
+	
+	comp_list = dcc_get_completed (); 
+	exist = comp_list != NULL;
+	
+	g_slist_free (comp_list);	
+	return exist;
+}
+
+static void
+update_clear_button_sensitivity (void)
+{
+	gboolean sensitive = dcc_completed_transfer_exists ();
+	gtk_widget_set_sensitive (dccfwin.clear_button, sensitive);
+}
+
 static void
 dcc_fill_window (int flags)
 {
@@ -426,6 +471,8 @@ dcc_fill_window (int flags)
 		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter);
 		gtk_tree_selection_select_iter (dccfwin.sel, &iter);
 	}
+	
+	update_clear_button_sensitivity ();
 }
 
 /* return list of selected DCCs */
@@ -511,6 +558,9 @@ abort_clicked (GtkWidget * wid, gpointer none)
 		dcc_abort (dcc->serv->front_session, dcc);
 	}
 	g_slist_free (start);
+	
+	/* Enable the clear button if it wasn't already enabled */
+	update_clear_button_sensitivity ();
 }
 
 static void
@@ -530,6 +580,27 @@ accept_clicked (GtkWidget * wid, gpointer none)
 }
 
 static void
+clear_completed (GtkWidget * wid, gpointer none)
+{
+	struct DCC *dcc;
+	GSList *completed;
+
+	/* Make a new list of only the completed items and abort each item.
+	 * A new list is made because calling dcc_abort removes items from the original list,
+	 * making it impossible to iterate over that list directly.
+	*/
+	for (completed = dcc_get_completed (); completed; completed = completed->next)
+	{
+		dcc = completed->data;
+		dcc_abort (dcc->serv->front_session, dcc);
+	}
+
+	/* The data was freed by dcc_close */
+	g_slist_free (completed);
+	update_clear_button_sensitivity ();
+}
+
+static void
 browse_folder (char *dir)
 {
 #ifdef WIN32
@@ -812,6 +883,7 @@ fe_dcc_open_recv_win (int passive)
 	dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort"));
 	dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept"));
 	dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume"));
+	dccfwin.clear_button = gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, clear_completed, 0, _("Clear"));
 	dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder..."));
 	gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
 	gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
@@ -1055,6 +1127,8 @@ fe_dcc_update (struct DCC *dcc)
 	default:
 		dcc_update_chat (dcc);
 	}
+
+	update_clear_button_sensitivity ();
 }
 
 void
diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c
index c8a80369..3469569e 100644
--- a/src/fe-gtk/fe-gtk.c
+++ b/src/fe-gtk/fe-gtk.c
@@ -220,26 +220,26 @@ fe_args (int argc, char *argv[])
 
 	if (arg_show_version)
 	{
+		buffer = g_strdup_printf ("%s %s", PACKAGE_NAME, PACKAGE_VERSION);
 #ifdef WIN32
-		buffer = g_strdup_printf (DISPLAY_NAME " " PACKAGE_VERSION "\n");
 		gtk_init (&argc, &argv);
 		create_msg_dialog ("Version Information", buffer);
-		g_free (buffer);
 #else
-		 printf (PACKAGE_NAME" "PACKAGE_VERSION"\n");
+		puts (buffer);
 #endif
+		g_free (buffer);
 
 		return 0;
 	}
 
 	if (arg_show_autoload)
 	{
-		buffer = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "addons\n", get_xdir ());
+		buffer = g_strdup_printf ("%s%caddons%c", get_xdir(), G_DIR_SEPARATOR, G_DIR_SEPARATOR);
 #ifdef WIN32
 		gtk_init (&argc, &argv);
 		create_msg_dialog ("Plugin/Script Auto-load Directory", buffer);
 #else
-		printf (buffer);
+		puts (buffer);
 #endif
 		g_free (buffer);
 
@@ -248,12 +248,12 @@ fe_args (int argc, char *argv[])
 
 	if (arg_show_config)
 	{
-		buffer = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "\n", get_xdir ());
+		buffer = g_strdup_printf ("%s%c", get_xdir(), G_DIR_SEPARATOR);
 #ifdef WIN32
 		gtk_init (&argc, &argv);
 		create_msg_dialog ("User Config Directory", buffer);
 #else
-		printf (buffer);
+		puts (buffer);
 #endif
 		g_free (buffer);
 
@@ -908,10 +908,15 @@ fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *)
 {
 	/* warning, assuming fe_confirm is used by DCC only! */
 	struct DCC *dcc = ud;
+	char *filepath;
 
 	if (dcc->file)
-		gtkutil_file_req (message, dcc_saveas_cb, ud, dcc->file, NULL,
-								FRF_WRITE|FRF_NOASKOVERWRITE);
+	{
+		filepath = g_build_filename (prefs.hex_dcc_dir, dcc->file, NULL);
+		gtkutil_file_req (message, dcc_saveas_cb, ud, filepath, NULL,
+								FRF_WRITE|FRF_NOASKOVERWRITE|FRF_FILTERISINITIAL);
+		g_free (filepath);
+	}
 }
 
 int
diff --git a/src/fe-gtk/fe-gtk.vcxproj b/src/fe-gtk/fe-gtk.vcxproj
index 405b5207..1bdc7fef 100644
--- a/src/fe-gtk/fe-gtk.vcxproj
+++ b/src/fe-gtk/fe-gtk.vcxproj
@@ -98,7 +98,6 @@
     </Link>

   </ItemDefinitionGroup>

   <ItemGroup>

-    <ClInclude Include="about.h" />

     <ClInclude Include="ascii.h" />

     <ClInclude Include="banlist.h" />

     <ClInclude Include="chanlist.h" />

@@ -129,7 +128,6 @@
     <ClInclude Include="xtext.h" />

   </ItemGroup>

   <ItemGroup>

-    <ClCompile Include="about.c" />

     <ClCompile Include="ascii.c" />

     <ClCompile Include="banlist.c" />

     <ClCompile Include="chanlist.c" />

diff --git a/src/fe-gtk/fe-gtk.vcxproj.filters b/src/fe-gtk/fe-gtk.vcxproj.filters
index 0f1f8203..424f7564 100644
--- a/src/fe-gtk/fe-gtk.vcxproj.filters
+++ b/src/fe-gtk/fe-gtk.vcxproj.filters
@@ -15,9 +15,6 @@
     </Filter>

   </ItemGroup>

   <ItemGroup>

-    <ClInclude Include="about.h">

-      <Filter>Header Files</Filter>

-    </ClInclude>

     <ClInclude Include="ascii.h">

       <Filter>Header Files</Filter>

     </ClInclude>

@@ -104,9 +101,6 @@
     </ClInclude>

   </ItemGroup>

   <ItemGroup>

-    <ClCompile Include="about.c">

-      <Filter>Source Files</Filter>

-    </ClCompile>

     <ClCompile Include="ascii.c">

       <Filter>Source Files</Filter>

     </ClCompile>

diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c
index 8a9a13c2..ba9599e1 100644
--- a/src/fe-gtk/fkeys.c
+++ b/src/fe-gtk/fkeys.c
@@ -148,7 +148,7 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
 	{key_action_insert, "Insert in Buffer",
 	 N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")},
 	{key_action_scroll_page, "Scroll Page",
-	 N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Up, Down, +1 or -1.")},
+	 N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Top, Bottom, Up, Down, +1 or -1.")},
 	{key_action_set_buffer, "Set Buffer",
 	 N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")},
 	{key_action_history_up, "Last Command",
@@ -395,6 +395,8 @@ key_load_defaults ()
 		"S\nNext\nChange Selected Nick\nD1!\nD2!\n\n"\
 		"S\nPrior\nChange Selected Nick\nD1:Up\nD2!\n\n"\
 		"None\nNext\nScroll Page\nD1:Down\nD2!\n\n"\
+		"C\nHome\nScroll Page\nD1:Top\nD2!\n\n"\
+		"C\nEnd\nScroll Page\nD1:Bottom\nD2!\n\n"\
 		"None\nPrior\nScroll Page\nD1:Up\nD2!\n\n"\
 		"S\nDown\nScroll Page\nD1:+1\nD2!\n\n"\
 		"S\nUp\nScroll Page\nD1:-1\nD2!\n\n"\
@@ -1279,13 +1281,19 @@ key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1,
 {
 	int value, end;
 	GtkAdjustment *adj;
-	enum scroll_type { PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN };
+	enum scroll_type { PAGE_TOP, PAGE_BOTTOM, PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN };
 	int type = PAGE_DOWN;
 
 	if (d1)
 	{
-		if (!g_ascii_strcasecmp (d1, "up"))
+		if (!g_ascii_strcasecmp (d1, "top"))
+			type = PAGE_TOP;
+		else if (!g_ascii_strcasecmp (d1, "bottom"))
+			type = PAGE_BOTTOM;
+		else if (!g_ascii_strcasecmp (d1, "up"))
 			type = PAGE_UP;
+		else if (!g_ascii_strcasecmp (d1, "down"))
+			type = PAGE_DOWN;
 		else if (!g_ascii_strcasecmp (d1, "+1"))
 			type = LINE_DOWN;
 		else if (!g_ascii_strcasecmp (d1, "-1"))
@@ -1300,21 +1308,29 @@ key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1,
 
 	switch (type)
 	{
-	case LINE_UP:
-		value = adj->value - 1.0;
+	case PAGE_TOP:
+		value = 0;
 		break;
 
-	case LINE_DOWN:
-		value = adj->value + 1.0;
+	case PAGE_BOTTOM:
+		value = end;
 		break;
 
 	case PAGE_UP:
 		value = adj->value - (adj->page_size - 1);
 		break;
 
-	default:	/* PAGE_DOWN */
+	case PAGE_DOWN:
 		value = adj->value + (adj->page_size - 1);
 		break;
+
+	case LINE_UP:
+		value = adj->value - 1.0;
+		break;
+
+	case LINE_DOWN:
+		value = adj->value + 1.0;
+		break;
 	}
 
 	if (value < 0)
diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c
index 11206e02..985a2f78 100644
--- a/src/fe-gtk/gtkutil.c
+++ b/src/fe-gtk/gtkutil.c
@@ -51,7 +51,6 @@
 
 extern void path_part (char *file, char *path, int pathlen);
 
-
 struct file_req
 {
 	GtkWidget *dialog;
@@ -69,9 +68,6 @@ struct file_req
 #endif
 };
 
-static char last_dir[256] = "";
-
-
 static void
 gtkutil_file_req_destroy (GtkWidget * wid, struct file_req *freq)
 {
@@ -84,13 +80,14 @@ gtkutil_check_file (char *file, struct file_req *freq)
 {
 	struct stat st;
 	int axs = FALSE;
+	char temp[256];
 
-	path_part (file, last_dir, sizeof (last_dir));
+	path_part (file, temp, sizeof (temp));
 
 	/* check if the file is readable or writable */
 	if (freq->flags & FRF_WRITE)
 	{
-		if (access (last_dir, W_OK) == 0)
+		if (access (temp, W_OK) == 0)
 			axs = TRUE;
 	} else
 	{
@@ -179,60 +176,6 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte
 	char *token;
 	char *tokenbuffer;
 
-#if 0	/* native file dialogs */
-#ifdef WIN32
-	if (!(flags & FRF_WRITE))
-	{
-		freq = malloc (sizeof (struct file_req));
-		freq->th = thread_new ();
-		freq->flags = 0;
-		freq->multiple = (flags & FRF_MULTIPLE);
-		freq->callback = callback;
-		freq->userdata = userdata;
-		freq->title = g_locale_from_utf8 (title, -1, 0, 0, 0);
-		if (!filter)
-		{
-			freq->filter =	"All files\0*.*\0"
-							"Executables\0*.exe\0"
-							"ZIP files\0*.zip\0\0";
-		}
-		else
-		{
-			freq->filter = filter;
-		}
-
-		thread_start (freq->th, win32_thread, freq);
-		fe_input_add (freq->th->pipe_fd[0], FIA_FD|FIA_READ, win32_read_thread, freq);
-
-		return;
-
-	}
-	
-	else {
-		freq = malloc (sizeof (struct file_req));
-		freq->th = thread_new ();
-		freq->flags = 0;
-		freq->multiple = (flags & FRF_MULTIPLE);
-		freq->callback = callback;
-		freq->userdata = userdata;
-		freq->title = g_locale_from_utf8 (title, -1, 0, 0, 0);
-		if (!filter)
-		{
-			freq->filter = "All files\0*.*\0\0";
-		}
-		else
-		{
-			freq->filter = filter;
-		}
-
-		thread_start (freq->th, win32_thread2, freq);
-		fe_input_add (freq->th->pipe_fd[0], FIA_FD|FIA_READ, win32_read_thread, freq);
-
-	return;
-	}
-#endif
-#endif
-
 	if (flags & FRF_WRITE)
 	{
 		dialog = gtk_file_chooser_dialog_new (title, NULL,
@@ -240,13 +183,6 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte
 												GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 												GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
 												NULL);
-		if (filter && filter[0])	/* filter becomes initial name when saving */
-		{
-			char temp[1024];
-			path_part (filter, temp, sizeof (temp));
-			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp);
-			gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter));
-		}
 
 		if (!(flags & FRF_NOASKOVERWRITE))
 			gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
@@ -257,41 +193,28 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte
 												GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
 												GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
 												NULL);
-	if (flags & FRF_MULTIPLE)
-		gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
-	if (last_dir[0])
-		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), last_dir);
-	if (flags & FRF_ADDFOLDER)
-		gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog),
-														  get_xdir (), NULL);
-	if (flags & FRF_CHOOSEFOLDER)
-	{
-		gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
-		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
-	}
-	else
+
+	if (filter && filter[0] && (flags & FRF_FILTERISINITIAL))
 	{
-		if (filter && (flags & FRF_FILTERISINITIAL))
-		{
-			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
-		}
-		/* With DCC, we can't rely on filter as initial folder since filter already contains
-		 * the filename upon DCC RECV. Thus we have no better option than to check for the message
-		 * which will be the title of the window. For DCC it always contains the "offering" word.
-		 * This method is really ugly but it works so we'll stick with it for now.
-		 */
-		else if (strstr (title, "offering") != NULL)
+		if (flags & FRF_WRITE)
 		{
-			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), prefs.hex_dcc_dir);
+			char temp[1024];
+			path_part (filter, temp, sizeof (temp));
+			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp);
+			gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter));
 		}
-		/* by default, open the config folder */
 		else
-		{
-			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), get_xdir ());
-		}
+			gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter);
 	}
+	else if (!(flags & FRF_RECENTLYUSED))
+		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), get_xdir ());
 
-	if (flags & FRF_EXTENSIONS && extensions != NULL)
+	if (flags & FRF_MULTIPLE)
+		gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE);
+	if (flags & FRF_CHOOSEFOLDER)
+		gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
+
+	if ((flags & FRF_EXTENSIONS || flags & FRF_MIMETYPES) && extensions != NULL)
 	{
 		filefilter = gtk_file_filter_new ();
 		tokenbuffer = g_strdup (extensions);
@@ -299,7 +222,10 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte
 
 		while (token != NULL)
 		{
-			gtk_file_filter_add_pattern (filefilter, token);
+			if (flags & FRF_EXTENSIONS)
+				gtk_file_filter_add_pattern (filefilter, token);
+			else
+				gtk_file_filter_add_mime_type (filefilter, token);
 			token = strtok (NULL, ";");
 		}
 
@@ -307,6 +233,8 @@ gtkutil_file_req (const char *title, void *callback, void *userdata, char *filte
 		gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filefilter);
 	}
 
+	gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), get_xdir (), NULL);
+
 	freq = malloc (sizeof (struct file_req));
 	freq->dialog = dialog;
 	freq->flags = flags;
@@ -678,6 +606,7 @@ gtkutil_window_new (char *title, char *role, int width, int height, int flags)
 	{
 		gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG);
 		gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent_window));
+		gtk_window_set_destroy_with_parent (GTK_WINDOW (win), TRUE);
 	}
 
 	return win;
diff --git a/src/fe-gtk/gtkutil.h b/src/fe-gtk/gtkutil.h
index e3f59a29..2ecbe3c2 100644
--- a/src/fe-gtk/gtkutil.h
+++ b/src/fe-gtk/gtkutil.h
@@ -21,16 +21,10 @@
 #define HEXCHAT_GTKUTIL_H
 
 #include <gtk/gtk.h>
+#include "../common/fe.h"
 
 typedef void (*filereqcallback) (void *, char *file);
 
-#define FRF_WRITE 1
-#define FRF_MULTIPLE 2
-#define FRF_ADDFOLDER 4
-#define FRF_CHOOSEFOLDER 8
-#define FRF_FILTERISINITIAL 16
-#define FRF_NOASKOVERWRITE 32
-
 void gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, char *extensions, int flags);
 void gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad);
 void gtkutil_destroy_on_esc (GtkWidget *win);
diff --git a/src/fe-gtk/joind.c b/src/fe-gtk/joind.c
index b58d662a..55d62e3b 100644
--- a/src/fe-gtk/joind.c
+++ b/src/fe-gtk/joind.c
@@ -34,6 +34,7 @@
 #include "../common/hexchat.h"
 #include "../common/hexchatc.h"
 #include "../common/server.h"
+#include "../common/servlist.h"
 #include "../common/fe.h"
 #include "fe-gtk.h"
 #include "chanlist.h"
@@ -241,6 +242,13 @@ joind_show_dialog (server *serv)
 							G_CALLBACK (joind_radio2_cb), serv);
 	g_signal_connect (G_OBJECT (okbutton1), "clicked",
 							G_CALLBACK (joind_ok_cb), serv);
+							
+	if (serv->network)
+		if (g_ascii_strcasecmp(((ircnet*)serv->network)->name, "freenode") == 0)
+		{
+			gtk_entry_set_text (GTK_ENTRY (entry1), "#hexchat");
+			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(radiobutton2), TRUE);
+		}
 
 	gtk_widget_grab_focus (okbutton1);
 	gtk_widget_show_all (dialog1);
diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c
index 91bc9d6f..fd0ee6f1 100644
--- a/src/fe-gtk/maingui.c
+++ b/src/fe-gtk/maingui.c
@@ -2106,8 +2106,8 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
 	gui->flag_k = mg_create_flagbutton (_("Keyword"), box, "K");
 	gui->key_entry = gtk_entry_new ();
 	gtk_widget_set_name (gui->key_entry, "hexchat-inputbox");
-	gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 16);
-	gtk_widget_set_size_request (gui->key_entry, 30, -1);
+	gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23);
+	gtk_widget_set_size_request (gui->key_entry, 115, -1);
 	gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
 	g_signal_connect (G_OBJECT (gui->key_entry), "activate",
 							G_CALLBACK (mg_key_entry_cb), NULL);
@@ -2310,6 +2310,8 @@ mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even)
 		menu_nickmenu (sess, even, word, FALSE);
 		break;
 	case WORD_CHANNEL:
+		word[end] = 0;
+		word += start;
 		menu_chanmenu (sess, even, word);
 		break;
 	case WORD_EMAIL:
diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c
index dd3223a1..8d87f84e 100644
--- a/src/fe-gtk/menu.c
+++ b/src/fe-gtk/menu.c
@@ -43,7 +43,6 @@
 #include "../common/notify.h"
 #include "../common/util.h"
 #include "xtext.h"
-#include "about.h"
 #include "ascii.h"
 #include "banlist.h"
 #include "chanlist.h"
@@ -1621,6 +1620,62 @@ menu_metres_both (GtkWidget *item, gpointer none)
 	}
 }
 
+static void
+about_dialog_close (GtkDialog *dialog, int response, gpointer data)
+{
+	gtk_widget_destroy (GTK_WIDGET(dialog));
+}
+
+static gboolean
+about_dialog_openurl (GtkAboutDialog *dialog, char *uri, gpointer data)
+{
+	fe_open_url (uri);
+	return TRUE;
+}
+
+static void
+menu_about (GtkWidget *wid, gpointer sess)
+{
+	GtkAboutDialog *dialog = GTK_ABOUT_DIALOG(gtk_about_dialog_new());
+	char comment[512];
+	char *license = "This program is free software; you can redistribute it and/or modify\n" \
+					"it under the terms of the GNU General Public License as published by\n" \
+					"the Free Software Foundation; version 2.\n\n" \
+					"This program is distributed in the hope that it will be useful,\n" \
+					"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \
+					"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \
+					"GNU General Public License for more details.\n\n" \
+					"You should have received a copy of the GNU General Public License\n" \
+					"along with this program. If not, see <http://www.gnu.org/licenses/>";
+
+	g_snprintf  (comment, sizeof(comment), "Compiled: "__DATE__"\n"
+#ifdef WIN32
+				"Portable Mode: %s\n"
+				"Build Type: x%d\n"
+#endif
+				"OS: %s",
+#ifdef WIN32
+				(portable_mode () ? "Yes" : "No"),
+				get_cpu_arch (),
+#endif
+				get_sys_str (0));
+
+	gtk_about_dialog_set_name (dialog, DISPLAY_NAME);
+	gtk_about_dialog_set_version (dialog, PACKAGE_VERSION);
+	gtk_about_dialog_set_license (dialog, license); /* gtk3 can use GTK_LICENSE_GPL_2_0 */
+	gtk_about_dialog_set_website (dialog, "http://hexchat.github.io");
+	gtk_about_dialog_set_website_label (dialog, "Website");
+	gtk_about_dialog_set_logo (dialog, pix_hexchat);
+	gtk_about_dialog_set_copyright (dialog, "\302\251 1998-2010 Peter \305\275elezn\303\275\n\302\251 2009-2013 Berke Viktor");
+	gtk_about_dialog_set_comments (dialog, comment);
+
+	gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(parent_window));
+	g_signal_connect (G_OBJECT(dialog), "response", G_CALLBACK(about_dialog_close), NULL);
+	g_signal_connect (G_OBJECT(dialog), "activate-link", G_CALLBACK(about_dialog_openurl), NULL);
+	
+	gtk_widget_show_all (GTK_WIDGET(dialog));
+}
+
 static struct mymenu mymenu[] = {
 	{N_("He_xChat"), 0, 0, M_NEWMENU, 0, 0, 1},
 	{N_("Network Li_st..."), menu_open_server_list, (char *)&pix_book, M_MENUPIX, 0, 0, 1, GDK_s},
diff --git a/src/fe-gtk/notifygui.c b/src/fe-gtk/notifygui.c
index 872fb7ed..c0282ade 100644
--- a/src/fe-gtk/notifygui.c
+++ b/src/fe-gtk/notifygui.c
@@ -60,6 +60,7 @@ static void
 notify_closegui (void)
 {
 	notify_window = 0;
+	notify_save ();
 }
 
 /* Need this to be able to set the foreground colour property of a row
diff --git a/src/fe-gtk/plugingui.c b/src/fe-gtk/plugingui.c
index c7193837..c6d90e2f 100644
--- a/src/fe-gtk/plugingui.c
+++ b/src/fe-gtk/plugingui.c
@@ -72,6 +72,31 @@ plugingui_treeview_new (GtkWidget *box)
 	return view;
 }
 
+static char *
+plugingui_getfilename (GtkTreeView *view)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *sel;
+	GtkTreeIter iter;
+	GValue file;
+	char *str;
+
+	memset (&file, 0, sizeof (file));
+
+	sel = gtk_tree_view_get_selection (view);
+	if (gtk_tree_selection_get_selected (sel, &model, &iter))
+	{
+		gtk_tree_model_get_value (model, &iter, FILE_COLUMN, &file);
+
+		str = g_value_dup_string (&file);
+		g_value_unset (&file);
+
+		return str;
+	}
+
+	return NULL;
+}
+
 static void
 plugingui_close (GtkWidget * wid, gpointer a)
 {
@@ -137,9 +162,9 @@ plugingui_load (void)
 
 	gtkutil_file_req (_("Select a Plugin or Script to load"), plugingui_load_cb, current_sess,
 #ifdef WIN32
-							sub_dir, "*.dll;*.lua;*.pl;*.py;*.tcl;*.js", FRF_ADDFOLDER|FRF_FILTERISINITIAL|FRF_EXTENSIONS);
+							sub_dir, "*.dll;*.lua;*.pl;*.py;*.tcl;*.js", FRF_FILTERISINITIAL|FRF_EXTENSIONS);
 #else
-							sub_dir, "*.so;*.lua;*.pl;*.py;*.tcl;*.js", FRF_ADDFOLDER|FRF_FILTERISINITIAL|FRF_EXTENSIONS);
+							sub_dir, "*.so;*.lua;*.pl;*.py;*.tcl;*.js", FRF_FILTERISINITIAL|FRF_EXTENSIONS);
 #endif
 
 	g_free (sub_dir);
@@ -193,6 +218,25 @@ plugingui_unload (GtkWidget * wid, gpointer unused)
 	g_free (file);
 }
 
+static void
+plugingui_reloadbutton_cb (GtkWidget *wid, GtkTreeView *view)
+{
+	char *file = plugingui_getfilename(view);
+
+	if (file)
+	{
+		char *buf = malloc (strlen (file) + 9);
+
+		if (strchr (file, ' '))
+			sprintf (buf, "RELOAD \"%s\"", file);
+		else
+			sprintf (buf, "RELOAD %s", file);
+		handle_command (current_sess, buf, FALSE);
+		free (buf);
+		g_free (file);
+	}
+}
+
 void
 plugingui_open (void)
 {
@@ -223,7 +267,10 @@ plugingui_open (void)
 	                plugingui_loadbutton_cb, NULL, _("_Load..."));
 
 	gtkutil_button (hbox, GTK_STOCK_DELETE, NULL,
-	                plugingui_unload, NULL, _("_UnLoad"));
+	                plugingui_unload, NULL, _("_Unload"));
+
+	gtkutil_button (hbox, GTK_STOCK_REFRESH, NULL,
+	                plugingui_reloadbutton_cb, view, _("_Reload"));
 
 	fe_pluginlist_update ();
 
diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c
index 94cb209f..ef5b57cd 100644
--- a/src/fe-gtk/servlistgui.c
+++ b/src/fe-gtk/servlistgui.c
@@ -120,10 +120,15 @@ static int login_types_conf[] =
 {
 	LOGIN_DEFAULT,			/* default entry - we don't use this but it makes indexing consistent with login_types[] so it's nice */
 	LOGIN_SASL,
+#ifdef USE_OPENSSL
+	LOGIN_SASLEXTERNAL,
+#endif
 	LOGIN_PASS,
 	LOGIN_MSG_NICKSERV,
 	LOGIN_NICKSERV,
+#ifdef USE_OPENSSL
 	LOGIN_CHALLENGEAUTH,
+#endif
 	LOGIN_CUSTOM
 #if 0
 	LOGIN_NS,
@@ -136,10 +141,15 @@ static const char *login_types[]=
 {
 	"Default",
 	"SASL (username + password)",
+#ifdef USE_OPENSSL
+	"SASL EXTERNAL (cert)",
+#endif
 	"Server Password (/PASS password)",
 	"NickServ (/MSG NickServ + password)",
 	"NickServ (/NICKSERV + password)",
+#ifdef USE_OPENSSL
 	"Challenge Auth (username + password)",
+#endif
 	"Custom... (connect commands)",
 #if 0
 	"NickServ (/NS + password)",
@@ -1513,6 +1523,12 @@ servlist_logintypecombo_cb (GtkComboBox *cb, gpointer *userdata)
 	{
 		gtk_notebook_set_current_page (GTK_NOTEBOOK (userdata), 2);		/* FIXME avoid hardcoding? */
 	}
+	
+	/* EXTERNAL uses a cert, not a pass */
+	if (login_types_conf[index] == LOGIN_SASLEXTERNAL)
+		gtk_widget_set_sensitive (edit_entry_pass, FALSE);
+	else
+		gtk_widget_set_sensitive (edit_entry_pass, TRUE);
 }
 
 
@@ -1816,6 +1832,8 @@ servlist_open_edit (GtkWidget *parent, ircnet *net)
 
 	edit_entry_pass = servlist_create_entry (table3, _("Password:"), 11, net->pass, 0, _("Password used for login. If in doubt, leave blank."));
 	gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE);
+	if (selected_net && selected_net->logintype == LOGIN_SASLEXTERNAL)
+		gtk_widget_set_sensitive (edit_entry_pass, FALSE);
 
 	label34 = gtk_label_new (_("Character set:"));
 	gtk_table_attach (GTK_TABLE (table3), label34, 0, 1, 12, 13, (GtkAttachOptions) (GTK_FILL), (GtkAttachOptions) (0), SERVLIST_X_PADDING, SERVLIST_Y_PADDING);
diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c
index 2d46b95f..8b3a43e5 100644
--- a/src/fe-gtk/setup.c
+++ b/src/fe-gtk/setup.c
@@ -426,7 +426,11 @@ static const setting alert_settings[] =
 #ifdef WIN32
 	{ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play the \"Instant Message Notification\" system sound upon the selected events"), (void *)beeplist, 0},
 #else
+#ifdef USE_LIBCANBERRA
 	{ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play \"message-new-instant\" from the freedesktop.org sound theme upon the selected events"), (void *)beeplist, 0},
+#else
+	{ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play a GTK beep upon the selected events"), (void *)beeplist, 0},
+#endif
 #endif
 
 	{ST_TOGGLE,	N_("Omit alerts when marked as being away"), P_OFFINTNL(hex_away_omit_alerts), 0, 0, 0},
@@ -1059,7 +1063,19 @@ setup_filereq_cb (GtkWidget *entry, char *file)
 static void
 setup_browsefile_cb (GtkWidget *button, GtkWidget *entry)
 {
-	gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, entry, NULL, NULL, 0);
+	/* used for background image only */
+	char *filter;
+	int filter_type;
+
+#ifdef WIN32
+	filter = "*png;*.tiff;*.gif;*.jpeg;*.jpg";
+	filter_type = FRF_EXTENSIONS;
+#else
+	filter = "image/*";
+	filter_type = FRF_MIMETYPES;
+#endif
+	gtkutil_file_req (_("Select an Image File"), setup_filereq_cb,
+					entry, NULL, filter, filter_type|FRF_RECENTLYUSED);
 }
 
 static void
@@ -1659,7 +1675,18 @@ static void
 setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry)
 {
 	char *sounds_dir = g_build_filename (get_xdir (), HEXCHAT_SOUND_DIR, NULL);
-	gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, sounds_dir, NULL, FRF_FILTERISINITIAL);
+	char *filter = NULL;
+	int filter_type;
+#ifdef WIN32 /* win32 only supports wav, others could support anything */
+	filter = "*.wav";
+	filter_type = FRF_EXTENSIONS;
+#else
+	filter = "audio/*";
+	filter_type = FRF_MIMETYPES;
+#endif
+
+	gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry,
+						sounds_dir, filter, FRF_FILTERISINITIAL|filter_type);
 	g_free (sounds_dir);
 }
 
diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c
index cefa51c8..79a6d5f5 100644
--- a/src/fe-gtk/urlgrab.c
+++ b/src/fe-gtk/urlgrab.c
@@ -146,7 +146,7 @@ static void
 url_button_save (void)
 {
 	gtkutil_file_req (_("Select an output filename"),
-							url_save_callback, NULL, get_xdir (), NULL, FRF_WRITE|FRF_FILTERISINITIAL);
+							url_save_callback, NULL, NULL, NULL, FRF_WRITE);
 }
 
 void
diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c
index 7582f82c..3d2ae7ae 100644
--- a/src/fe-gtk/xtext.c
+++ b/src/fe-gtk/xtext.c
@@ -1901,10 +1901,10 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
 {
 	textentry *ent;
 	int offset;
-	unsigned char *str;
 	unsigned char *word;
 	int len;
 	int out_of_bounds = 0;
+	int len_to_offset = 0;
 
 	ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds);
 	if (!ent)
@@ -1921,25 +1921,25 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
 
 	/*offset--;*/	/* FIXME: not all chars are 1 byte */
 
-	str = ent->str + offset;
+	word = ent->str + offset;
 
-	while (!is_del (*str) && str != ent->str)
-		str--;
-	word = str + 1;
+	while (!is_del (*word) && word != ent->str)
+	{
+		word--;
+		len_to_offset++;
+	}
+	word++;
+	len_to_offset--;
+
+	/* remove color characters from the length */
+	gtk_xtext_strip_color (word, len_to_offset, xtext->scratch_buffer, &len_to_offset, NULL, slp, FALSE);
 
 	len = 0;
-	str = word;
-	while (!is_del (*str) && len != ent->str_len)
-	{
-		str++;
+	while (!is_del (word[len]) && len != ent->str_len)
 		len++;
-	}
 
 	if (len > 0 && word[len-1]=='.')
-	{
 		len--;
-		str--;
-	}
 
 	if (ret_ent)
 		*ret_ent = ent;
@@ -1948,7 +1948,24 @@ gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent,
 	if (ret_len)
 		*ret_len = len;		/* Length before stripping */
 
-	return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, slp, FALSE);
+	word = gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, slp, FALSE);
+
+	/* avoid turning the cursor into a hand for non-url part of the word */
+	if (xtext->urlcheck_function (GTK_WIDGET (xtext), word))
+	{
+		int start, end;
+		url_last (&start, &end);
+
+		/* make sure we're not before the start of the match */
+		if (len_to_offset < start)
+			return 0;
+
+		/* and not after it */
+		if (len_to_offset - start >= end - start)
+			return 0;
+	}
+
+	return word;
 }
 
 #ifdef MOTION_MONITOR
@@ -2139,6 +2156,7 @@ gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event)
 			{
 				gdk_window_set_cursor (GTK_WIDGET (xtext)->window,
 										  		xtext->resize_cursor);
+				xtext->cursor_hand = FALSE;
 				xtext->cursor_resize = TRUE;
 			}
 			return FALSE;
@@ -2161,6 +2179,7 @@ gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event)
 				gdk_window_set_cursor (GTK_WIDGET (xtext)->window,
 										  		xtext->hand_cursor);
 				xtext->cursor_hand = TRUE;
+				xtext->cursor_resize = FALSE;
 			}
 
 			/* un-render the old hilight */
diff --git a/src/htm/Makefile.am b/src/htm/Makefile.am
new file mode 100644
index 00000000..85480402
--- /dev/null
+++ b/src/htm/Makefile.am
@@ -0,0 +1,12 @@
+MDTOOL_OPTS = --verbose
+
+theme_SCRIPTS = thememan.exe thememan
+themedir = $(bindir)
+
+thememan.exe: htm-mono.csproj
+	$(MDTOOL) $(MDTOOL_OPTS) build $<
+
+clean-local:
+	rm -f thememan.exe thememan.exe.config thememan.exe.mdb thememan Main.resources
+
+EXTRA_DIST = thememan.in
diff --git a/src/htm/thememan.in b/src/htm/thememan.in
new file mode 100644
index 00000000..f6f80df6
--- /dev/null
+++ b/src/htm/thememan.in
@@ -0,0 +1,3 @@
+#!/bin/sh
+exec_prefix="@exec_prefix@"
+exec mono "@bindir@/thememan.exe" "$@"