summary refs log tree commit diff stats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/cfgfiles.c2
-rw-r--r--src/common/common.vcxproj2
-rw-r--r--src/common/ctcp.c5
-rw-r--r--src/common/dbus/dbus-client.c2
-rw-r--r--src/common/dbus/meson.build2
-rw-r--r--src/common/fe.h12
-rw-r--r--src/common/hexchat.c20
-rw-r--r--src/common/hexchat.h12
-rw-r--r--src/common/inbound.c184
-rw-r--r--src/common/meson.build17
-rw-r--r--src/common/modes.c143
-rw-r--r--src/common/notify.c7
-rw-r--r--src/common/outbound.c129
-rw-r--r--src/common/plugin-timer.c2
-rw-r--r--src/common/plugin.c32
-rw-r--r--src/common/plugin.h2
-rw-r--r--src/common/proto-irc.c111
-rw-r--r--src/common/proto-irc.h2
-rw-r--r--src/common/scram.c333
-rw-r--r--src/common/scram.h51
-rw-r--r--src/common/server.c42
-rw-r--r--src/common/servlist.c150
-rw-r--r--src/common/servlist.h3
-rw-r--r--src/common/ssl.c21
-rw-r--r--src/common/ssl.h2
-rw-r--r--src/common/sysinfo/win32/backend.c8
-rw-r--r--src/common/text.c11
-rw-r--r--src/common/textevents.in36
-rw-r--r--src/common/url.c19
-rw-r--r--src/common/util.c8
30 files changed, 1027 insertions, 343 deletions
diff --git a/src/common/cfgfiles.c b/src/common/cfgfiles.c
index fdee9f2c..f0799de9 100644
--- a/src/common/cfgfiles.c
+++ b/src/common/cfgfiles.c
@@ -468,6 +468,7 @@ const struct prefs vars[] =
 	{"gui_win_fullscreen", P_OFFINT (hex_gui_win_fullscreen), TYPE_INT},
 	{"gui_win_left", P_OFFINT (hex_gui_win_left), TYPE_INT},
 	{"gui_win_modes", P_OFFINT (hex_gui_win_modes), TYPE_BOOL},
+	{"gui_win_nick", P_OFFINT (hex_gui_win_nick), TYPE_BOOL},
 	{"gui_win_save", P_OFFINT (hex_gui_win_save), TYPE_BOOL},
 	{"gui_win_state", P_OFFINT (hex_gui_win_state), TYPE_INT},
 	{"gui_win_swap", P_OFFINT (hex_gui_win_swap), TYPE_BOOL},
@@ -772,6 +773,7 @@ load_default_config(void)
 	prefs.hex_gui_ulist_count = 1;
 	prefs.hex_gui_ulist_icons = 1;
 	prefs.hex_gui_ulist_style = 1;
+	prefs.hex_gui_win_nick = 1;
 	prefs.hex_gui_win_save = 1;
 	prefs.hex_input_filter_beep = 1;
 	prefs.hex_input_flash_hilight = 1;
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index bc191f43..c91d8cbb 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -36,6 +36,7 @@
     <ClInclude Include="server.h" />

     <ClInclude Include="servlist.h" />

     <ClInclude Include="ssl.h" />

+    <ClInclude Include="scram.h" />

     <ClInclude Include="sysinfo\sysinfo.h" />

     <ClInclude Include="text.h" />

     <ClInclude Include="$(HexChatLib)textenums.h" />

@@ -69,6 +70,7 @@
     <ClCompile Include="server.c" />

     <ClCompile Include="servlist.c" />

     <ClCompile Include="ssl.c" />

+    <ClCompile Include="scram.c" />

     <ClCompile Include="sysinfo\win32\backend.c" />

     <ClCompile Include="text.c" />

     <ClCompile Include="tree.c" />

diff --git a/src/common/ctcp.c b/src/common/ctcp.c
index a8e1ea8d..f9c05440 100644
--- a/src/common/ctcp.c
+++ b/src/common/ctcp.c
@@ -94,9 +94,6 @@ ctcp_handle (session *sess, char *to, char *nick, char *ip,
 	char outbuf[1024];
 	int ctcp_offset = 2;
 
-	if (serv->have_idmsg && (word[4][1] == '+' || word[4][1] == '-') )
-			ctcp_offset = 3;
-
 	/* consider DCC to be different from other CTCPs */
 	if (!g_ascii_strncasecmp (msg, "DCC", 3))
 	{
@@ -129,7 +126,7 @@ ctcp_handle (session *sess, char *to, char *nick, char *ip,
 		if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
 			goto generic;
 
-		inbound_action (sess, to, nick, ip, msg + 7, FALSE, id, tags_data);
+		inbound_action (sess, to, nick, ip, msg + 7, FALSE, tags_data->identified, tags_data);
 		return;
 	}
 
diff --git a/src/common/dbus/dbus-client.c b/src/common/dbus/dbus-client.c
index 8b40dd24..e70a49a9 100644
--- a/src/common/dbus/dbus-client.c
+++ b/src/common/dbus/dbus-client.c
@@ -67,7 +67,7 @@ hexchat_remote (void)
 	gboolean hexchat_running;
 	GError *error = NULL;
 	char *command = NULL;
-	int i;
+	guint i;
 
 	/* if there is nothing to do, return now. */
 	if (!arg_existing || !(arg_url || arg_urls || arg_command)) {
diff --git a/src/common/dbus/meson.build b/src/common/dbus/meson.build
index 69066be0..856bbe55 100644
--- a/src/common/dbus/meson.build
+++ b/src/common/dbus/meson.build
@@ -1,5 +1,5 @@
 dbus_deps = [
-  dependency('dbus-glib-1')
+  dbus_glib_dep
 ]
 
 dbus_sources = [
diff --git a/src/common/fe.h b/src/common/fe.h
index 6614055b..b8a6279e 100644
--- a/src/common/fe.h
+++ b/src/common/fe.h
@@ -69,7 +69,16 @@ int fe_input_add (int sok, int flags, void *func, void *data);
 void fe_input_remove (int tag);
 void fe_idle_add (void *func, void *data);
 void fe_set_topic (struct session *sess, char *topic, char *stripped_topic);
-void fe_set_tab_color (struct session *sess, int col);
+typedef enum
+{
+	FE_COLOR_NONE = 0,
+	FE_COLOR_NEW_DATA = 1,
+	FE_COLOR_NEW_MSG = 2,
+	FE_COLOR_NEW_HILIGHT = 3,
+	FE_COLOR_FLAG_NOOVERRIDE = 8,
+} tabcolor;
+#define FE_COLOR_ALLFLAGS (FE_COLOR_FLAG_NOOVERRIDE)
+void fe_set_tab_color (struct session *sess, tabcolor col);
 void fe_flash_window (struct session *sess);
 void fe_update_mode_buttons (struct session *sess, char mode, char sign);
 void fe_update_channel_key (struct session *sess);
@@ -132,6 +141,7 @@ void fe_get_int (char *prompt, int def, void *callback, void *ud);
 #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 */
+#define FRF_MODAL 256           /* dialog should be modal to parent */
 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.c b/src/common/hexchat.c
index 9be2e56d..f74fe489 100644
--- a/src/common/hexchat.c
+++ b/src/common/hexchat.c
@@ -57,10 +57,6 @@
 #include <glib-object.h>			/* for g_type_init() */
 #endif
 
-#ifdef USE_LIBPROXY
-#include <proxy.h>
-#endif
-
 GSList *popup_list = 0;
 GSList *button_list = 0;
 GSList *dlgbutton_list = 0;
@@ -111,10 +107,6 @@ struct session *current_tab;
 struct session *current_sess = 0;
 struct hexchatprefs prefs;
 
-#ifdef USE_LIBPROXY
-pxProxyFactory *libproxy_factory;
-#endif
-
 /*
  * Update the priority queue of the "interesting sessions"
  * (sess_list_by_lastact).
@@ -560,7 +552,7 @@ new_ircwindow (server *serv, char *name, int type, int focus)
 		if (user && user->hostname)
 			set_topic (sess, user->hostname, user->hostname);
 	}
-	plugin_emit_dummy_print (sess, "Open Context");
+	plugin_emit_dummy_print (sess, "Open Context", -1);
 
 	return sess;
 }
@@ -637,7 +629,7 @@ session_free (session *killsess)
 	GSList *list;
 	int oldidx;
 
-	plugin_emit_dummy_print (killsess, "Close Context");
+	plugin_emit_dummy_print (killsess, "Close Context", 0);
 
 	if (current_tab == killsess)
 		current_tab = NULL;
@@ -1102,10 +1094,6 @@ main (int argc, char *argv[])
 	hexchat_remote ();
 #endif
 
-#ifdef USE_LIBPROXY
-	libproxy_factory = px_proxy_factory_new ();
-#endif
-
 #ifdef WIN32
 	coinit_result = CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
 	if (SUCCEEDED (coinit_result))
@@ -1148,10 +1136,6 @@ main (int argc, char *argv[])
 	}
 #endif
 
-#ifdef USE_LIBPROXY
-	px_proxy_factory_free (libproxy_factory);
-#endif
-
 #ifdef WIN32
 	WSACleanup ();
 #endif
diff --git a/src/common/hexchat.h b/src/common/hexchat.h
index d8effa1f..5ead96d1 100644
--- a/src/common/hexchat.h
+++ b/src/common/hexchat.h
@@ -41,6 +41,7 @@
 
 #ifdef USE_OPENSSL
 #include <openssl/ssl.h>		  /* SSL_() */
+#include "scram.h"
 #endif
 
 #ifdef __EMX__						  /* for o/s 2 */
@@ -150,6 +151,7 @@ struct hexchatprefs
 	unsigned int hex_gui_ulist_style;
 	unsigned int hex_gui_usermenu;
 	unsigned int hex_gui_win_modes;
+	unsigned int hex_gui_win_nick;
 	unsigned int hex_gui_win_save;
 	unsigned int hex_gui_win_swap;
 	unsigned int hex_gui_win_ucount;
@@ -429,6 +431,9 @@ typedef struct session
 /* SASL Mechanisms */
 #define MECH_PLAIN 0
 #define MECH_EXTERNAL 1
+#define MECH_SCRAM_SHA_1 2
+#define MECH_SCRAM_SHA_256 3
+#define MECH_SCRAM_SHA_512 4
 
 typedef struct server
 {
@@ -501,9 +506,9 @@ typedef struct server
 	int joindelay_tag;				/* waiting before we send JOIN */
 	char hostname[128];				/* real ip number */
 	char servername[128];			/* what the server says is its name */
-	char password[86];
+	char password[1024];
 	char nick[NICKLEN];
-	char linebuf[2048];				/* RFC says 512 chars including \r\n */
+	char linebuf[8704];				/* RFC says 512 chars including \r\n, IRCv3 message tags add 8191, plus the NUL byte */
 	char *last_away_reason;
 	int pos;								/* current position in linebuf */
 	int nickcount;
@@ -567,7 +572,7 @@ typedef struct server
 	unsigned int have_awaynotify:1;
 	unsigned int have_uhnames:1;
 	unsigned int have_whox:1;		/* have undernet's WHOX features */
-	unsigned int have_idmsg:1;		/* freenode's IDENTIFY-MSG */
+	unsigned int have_idmsg:1;		/* cap solanum.chat/identify-msg */
 	unsigned int have_accnotify:1; /* cap account-notify */
 	unsigned int have_extjoin:1;	/* cap extended-join */
 	unsigned int have_account_tag:1;	/* cap account-tag */
@@ -584,6 +589,7 @@ typedef struct server
 #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 */
+	scram_session *scram_session; /* session for SASL SCRAM authentication */
 #endif
 } server;
 
diff --git a/src/common/inbound.c b/src/common/inbound.c
index 7175b2ae..fdee2ecc 100644
--- a/src/common/inbound.c
+++ b/src/common/inbound.c
@@ -107,7 +107,8 @@ find_session_from_nick (char *nick, server *serv)
 
 	if (serv->front_session)
 	{
-		if (userlist_find (serv->front_session, nick))
+		// If we are here for ChanServ, then it is usually a reply for the user
+		if (!g_ascii_strcasecmp(nick, "ChanServ") || userlist_find (serv->front_session, nick))
 			return serv->front_session;
 	}
 
@@ -189,7 +190,7 @@ inbound_privmsg (server *serv, char *from, char *ip, char *text, int id,
 
 		if (ip && ip[0])
 			set_topic (sess, ip, ip);
-		inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, id, tags_data);
+		inbound_chanmsg (serv, NULL, NULL, from, text, FALSE, tags_data->identified, tags_data);
 		return;
 	}
 
@@ -1473,10 +1474,17 @@ inbound_user_info (session *sess, char *chan, char *user, char *host,
 		for (list = sess_list; list; list = list->next)
 		{
 			sess = list->data;
-			if (sess->type == SESS_CHANNEL && sess->server == serv)
+			if (sess->server != serv)
+				continue;
+
+			if (sess->type == SESS_CHANNEL)
 			{
 				userlist_add_hostname (sess, nick, uhost, realname, servname, account, away);
 			}
+			else if (sess->type == SESS_DIALOG && uhost && !serv->p_cmp (sess->channel, nick))
+			{
+				set_topic (sess, uhost, uhost);
+			}
 		}
 	}
 
@@ -1640,7 +1648,10 @@ inbound_identified (server *serv)	/* 'MODE +e MYSELF' on freenode */
 static const char *sasl_mechanisms[] =
 {
 	"PLAIN",
-	"EXTERNAL"
+	"EXTERNAL",
+	"SCRAM-SHA-1",
+	"SCRAM-SHA-256",
+	"SCRAM-SHA-512"
 };
 
 static void
@@ -1655,7 +1666,7 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable)
 	{
 		const char *extension = extensions[i];
 
-		if (!strcmp (extension, "identify-msg"))
+		if (!strcmp (extension, "solanum.chat/identify-msg"))
 			serv->have_idmsg = enable;
 		else if (!strcmp (extension, "multi-prefix"))
 			serv->have_namesx = enable;
@@ -1681,6 +1692,12 @@ inbound_toggle_caps (server *serv, const char *extensions_str, gboolean enable)
 #ifdef USE_OPENSSL
 				if (serv->loginmethod == LOGIN_SASLEXTERNAL)
 					serv->sasl_mech = MECH_EXTERNAL;
+				else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1)
+					serv->sasl_mech = MECH_SCRAM_SHA_1;
+				else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256)
+					serv->sasl_mech = MECH_SCRAM_SHA_256;
+				else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512)
+					serv->sasl_mech = MECH_SCRAM_SHA_512;
 #endif
 				/* Mechanism either defaulted to PLAIN or server gave us list */
 				tcp_sendf (serv, "AUTHENTICATE %s\r\n", sasl_mechanisms[serv->sasl_mech]);
@@ -1712,8 +1729,6 @@ inbound_cap_del (server *serv, char *nick, char *extensions,
 }
 
 static const char * const supported_caps[] = {
-	"identify-msg",
-
 	/* IRCv3.1 */
 	"multi-prefix",
 	"away-notify",
@@ -1729,6 +1744,7 @@ static const char * const supported_caps[] = {
 	"setname",
 	"invite-notify",
 	"account-tag",
+	"extended-monitor",
 
 	/* ZNC */
 	"znc.in/server-time-iso",
@@ -1736,6 +1752,9 @@ static const char * const supported_caps[] = {
 
 	/* Twitch */
 	"twitch.tv/membership",
+
+	/* Solanum */
+	"solanum.chat/identify-msg",
 };
 
 static int
@@ -1756,6 +1775,30 @@ get_supported_mech (server *serv, const char *list)
 				break;
 			}
 		}
+		else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1)
+		{
+			if (!strcmp(mechs[i], "SCRAM-SHA-1"))
+			{
+				ret = MECH_SCRAM_SHA_1;
+				break;
+			}
+		}
+		else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256)
+		{
+			if (!strcmp(mechs[i], "SCRAM-SHA-256"))
+			{
+				ret = MECH_SCRAM_SHA_256;
+				break;
+			}
+		}
+		else if (serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512)
+		{
+			if (!strcmp(mechs[i], "SCRAM-SHA-512"))
+			{
+				ret = MECH_SCRAM_SHA_512;
+				break;
+			}
+        }
 		else
 #endif
 		if (!strcmp (mechs[i], "PLAIN"))
@@ -1811,7 +1854,11 @@ 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 (!g_strcmp0 (extension, "sasl") &&
-			((serv->loginmethod == LOGIN_SASL && strlen (serv->password) != 0)
+			(((serv->loginmethod == LOGIN_SASL
+				|| serv->loginmethod == LOGIN_SASL_SCRAM_SHA_1
+				|| serv->loginmethod == LOGIN_SASL_SCRAM_SHA_256
+				|| serv->loginmethod == LOGIN_SASL_SCRAM_SHA_512)
+					&& strlen (serv->password) != 0)
 				|| serv->loginmethod == LOGIN_SASLEXTERNAL))
 		{
 			if (value)
@@ -1891,11 +1938,103 @@ inbound_cap_list (server *serv, char *nick, char *extensions,
 								  NULL, NULL, 0, tags_data->timestamp);
 }
 
+static void
+plain_authenticate (server *serv, char *user, char *password)
+{
+	char *pass = encode_sasl_pass_plain (user, password);
+
+	if (pass == NULL)
+	{
+		/* something went wrong abort */
+		tcp_sendf (serv, "AUTHENTICATE *\r\n");
+		return;
+	}
+
+	/* long SASL passwords must be split into 400-byte chunks
+	   https://ircv3.net/specs/extensions/sasl-3.1#the-authenticate-command */
+	size_t pass_len = strlen (pass);
+	if (pass_len <= 400)
+		tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass);
+	else
+	{
+		size_t sent = 0;
+		while (sent < pass_len)
+		{
+			char *pass_chunk = g_strndup (pass + sent, 400);
+			tcp_sendf (serv, "AUTHENTICATE %s\r\n", pass_chunk);
+			sent += 400;
+			g_free (pass_chunk);
+		}
+	}
+	if (pass_len % 400 == 0)
+		tcp_sendf (serv, "AUTHENTICATE +\r\n");
+}
+
+#ifdef USE_OPENSSL
+/*
+ * Sends AUTHENTICATE messages to log in via SCRAM.
+ */
+static void
+scram_authenticate (server *serv, const char *data, const char *digest,
+					const char *user, const char *password)
+{
+	char *encoded, *decoded, *output;
+	scram_status status;
+	size_t output_len;
+	gsize decoded_len;
+
+	if (serv->scram_session == NULL)
+	{
+		serv->scram_session = scram_session_create (digest, user, password);
+
+		if (serv->scram_session == NULL)
+		{
+			PrintTextf (serv->server_session, _("Could not create SCRAM session with digest %s"), digest);
+			g_warning ("Could not create SCRAM session with digest %s", digest);
+			tcp_sendf (serv, "AUTHENTICATE *\r\n");
+			return;
+		}
+	}
+
+	decoded = g_base64_decode (data, &decoded_len);
+	status = scram_process (serv->scram_session, decoded, &output, &output_len);
+	g_free (decoded);
+
+	if (status == SCRAM_IN_PROGRESS)
+	{
+		// Authentication is still in progress
+		encoded = g_base64_encode ((guchar *) output, output_len);
+		tcp_sendf (serv, "AUTHENTICATE %s\r\n", encoded);
+		g_free (encoded);
+		g_free (output);
+	}
+	else if (status == SCRAM_SUCCESS)
+	{
+		// Authentication succeeded
+		tcp_sendf (serv, "AUTHENTICATE +\r\n");
+		g_clear_pointer (&serv->scram_session, scram_session_free);
+	}
+	else if (status == SCRAM_ERROR)
+	{
+		// Authentication failed
+		tcp_sendf (serv, "AUTHENTICATE *\r\n");
+
+		if (serv->scram_session->error != NULL)
+		{
+			PrintTextf (serv->server_session, _("SASL SCRAM authentication failed: %s"), serv->scram_session->error);
+			g_info ("SASL SCRAM authentication failed: %s", serv->scram_session->error);
+		}
+
+		g_clear_pointer (&serv->scram_session, scram_session_free);
+	}
+}
+#endif
+
 void
 inbound_sasl_authenticate (server *serv, char *data)
 {
 		ircnet *net = (ircnet*)serv->network;
-		char *user, *pass = NULL;
+		char *user;
 		const char *mech = sasl_mechanisms[serv->sasl_mech];
 
 		/* Got a list of supported mechanisms from outdated inspircd
@@ -1911,26 +2050,24 @@ inbound_sasl_authenticate (server *serv, char *data)
 		switch (serv->sasl_mech)
 		{
 		case MECH_PLAIN:
-			pass = encode_sasl_pass_plain (user, serv->password);
+			plain_authenticate(serv, user, serv->password);
 			break;
 #ifdef USE_OPENSSL
 		case MECH_EXTERNAL:
-			pass = g_strdup ("+");
+			tcp_sendf (serv, "AUTHENTICATE +\r\n");
 			break;
-#endif
-		}
-
-		if (pass == NULL)
-		{
-			/* something went wrong abort */
-			tcp_sendf (serv, "AUTHENTICATE *\r\n");
+		case MECH_SCRAM_SHA_1:
+			scram_authenticate(serv, data, "SHA1", user, serv->password);
+			return;
+		case MECH_SCRAM_SHA_256:
+			scram_authenticate(serv, data, "SHA256", user, serv->password);
 			return;
+		case MECH_SCRAM_SHA_512:
+			scram_authenticate(serv, data, "SHA512", user, serv->password);
+			return;
+#endif
 		}
 
-		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);
 }
@@ -1938,6 +2075,9 @@ inbound_sasl_authenticate (server *serv, char *data)
 void
 inbound_sasl_error (server *serv)
 {
+#ifdef USE_OPENSSL
+    g_clear_pointer (&serv->scram_session, scram_session_free);
+#endif
 	/* Just abort, not much we can do */
 	tcp_sendf (serv, "AUTHENTICATE *\r\n");
 }
diff --git a/src/common/meson.build b/src/common/meson.build
index 492227b2..35db2c27 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -15,6 +15,7 @@ common_sources = [
   'plugin-identd.c',
   'plugin-timer.c',
   'proto-irc.c',
+  'scram.c',
   'server.c',
   'servlist.c',
 	'text.c',
@@ -28,6 +29,7 @@ common_sysinfo_deps = []
 
 common_deps = [
   libgio_dep,
+  libcanberra_dep,
 ] + global_deps
 
 common_includes = [
@@ -46,7 +48,6 @@ if host_machine.system() == 'windows'
   ]
   common_sysinfo_deps += [
     cc.find_library('wbemuuid'), # sysinfo
-    cc.find_library('wbemcore'),
   ]
 
   common_sources += 'sysinfo/win32/backend.c'
@@ -72,26 +73,18 @@ textevents = custom_target('textevents',
 #   SIGACTION
 #   HAVE_GTK_MAC
 
-if get_option('with-ssl')
+if libssl_dep.found()
   common_sources += 'ssl.c'
   common_deps += libssl_dep
 endif
 
-if get_option('with-libproxy')
-  common_deps += dependency('libproxy-1.0')
-endif
-
-if get_option('with-libcanberra')
-  common_deps += dependency('libcanberra', version: '>= 0.22')
-endif
-
-if get_option('with-dbus')
+if dbus_glib_dep.found()
   subdir('dbus')
   common_deps += hexchat_dbus_dep
   common_includes += include_directories('dbus')
 endif
 
-if get_option('with-plugin')
+if get_option('plugin')
   common_deps += libgmodule_dep
   install_headers('hexchat-plugin.h')
 endif
diff --git a/src/common/modes.c b/src/common/modes.c
index 3c0ac8ab..1ff309bd 100644
--- a/src/common/modes.c
+++ b/src/common/modes.c
@@ -67,8 +67,8 @@ send_channel_modes (session *sess, char *tbuf, char *word[], int wpos,
 	int usable_modes, orig_len, len, wlen, i, max;
 	server *serv = sess->server;
 
-	/* sanity check. IRC RFC says three per line. */
-	if (serv->modes_per_line < 3)
+	/* sanity check. IRC RFC says three per line but some servers may support less. */
+	if (serv->modes_per_line < 1)
 		serv->modes_per_line = 3;
 	if (modes_per_line < 1)
 		modes_per_line = serv->modes_per_line;
@@ -680,7 +680,7 @@ handle_mode (server * serv, char *word[], char *word_eol[],
 	int len;
 	size_t arg;
 	size_t i, num_args;
-	int num_modes;
+	size_t num_modes;
 	size_t offset = 3;
 	int all_modes_have_args = FALSE;
 	int using_front_tab = FALSE;
@@ -785,6 +785,64 @@ handle_mode (server * serv, char *word[], char *word_eol[],
 	mode_print_grouped (sess, nick, &mr, tags_data);
 }
 
+static char
+hex_to_chr(char chr)
+{
+	return g_ascii_isdigit (chr) ? chr - '0' : g_ascii_tolower (chr) - 'a' + 10;
+}
+
+static void
+parse_005_token (const char *token, char **name, char **value, gboolean *adding)
+{
+	char *toksplit, *valuecurr;
+	size_t idx;
+
+	if (token[0] == '-')
+	{
+		*adding = FALSE;
+		token++;
+	} else
+	{
+		*adding = TRUE;
+	}
+
+	toksplit = strchr (token, '=');
+	if (toksplit && *toksplit++)
+	{
+		/* The token has a value; parse any escape codes. */
+		*name = g_strndup (token, toksplit - token - 1);
+		*value = g_malloc (strlen (toksplit) + 1);
+		valuecurr = *value;
+
+		while (*toksplit)
+		{
+			if (toksplit[0] == '\\')
+			{
+				/** If it's a malformed escape then just skip it. */
+				if (toksplit[1] == 'x' && g_ascii_isxdigit (toksplit[2]) && g_ascii_isxdigit (toksplit[3]))
+					*valuecurr++ = hex_to_chr (toksplit[2]) << 4 | hex_to_chr (toksplit[3]);
+
+				for (idx = 0; idx < 4; ++idx)
+				{
+					/* We need to do this to avoid jumping past the end of the array. */
+					if (*toksplit)
+						toksplit++;
+				}
+			} else
+			{
+				/** Non-escape characters can be copied as is. */
+				*valuecurr++ = *toksplit++;
+			}
+		}
+		*valuecurr++ = 0;
+	} else
+	{
+		/* The token has no value; store a dummy value instead. */
+		*name = g_strdup (token);
+		*value = g_strdup ("");
+	}
+}
+
 /* handle the 005 numeric */
 
 void
@@ -792,84 +850,99 @@ inbound_005 (server * serv, char *word[], const message_tags_data *tags_data)
 {
 	int w;
 	char *pre;
+	char *tokname, *tokvalue;
+	gboolean tokadding;
 
 	w = 4;							  /* start at the 4th word */
 	while (w < PDIWORDS && *word[w])
 	{
-		if (strncmp (word[w], "MODES=", 6) == 0)
+		if (word[w][0] == ':')
+			break; // :are supported by this server
+
+		parse_005_token(word[w], &tokname, &tokvalue, &tokadding);
+		if (g_strcmp0 (tokname, "MODES") == 0)
 		{
-			serv->modes_per_line = atoi (word[w] + 6);
-		} else if (strncmp (word[w], "CHANTYPES=", 10) == 0)
+			serv->modes_per_line = atoi (tokvalue);
+		} else if (g_strcmp0 (tokname, "CHANTYPES") == 0)
 		{
 			g_free (serv->chantypes);
-			serv->chantypes = g_strdup (word[w] + 10);
-		} else if (strncmp (word[w], "CHANMODES=", 10) == 0)
+			serv->chantypes = g_strdup (tokvalue);
+		} else if (g_strcmp0 (tokname, "CHANMODES") == 0)
 		{
 			g_free (serv->chanmodes);
-			serv->chanmodes = g_strdup (word[w] + 10);
-		} else if (strncmp (word[w], "PREFIX=", 7) == 0)
+			serv->chanmodes = g_strdup (tokvalue);
+		} else if (g_strcmp0 (tokname, "PREFIX") == 0)
 		{
-			pre = strchr (word[w] + 7, ')');
+			pre = strchr (tokvalue, ')');
 			if (pre)
 			{
 				pre[0] = 0;			  /* NULL out the ')' */
 				g_free (serv->nick_prefixes);
 				g_free (serv->nick_modes);
 				serv->nick_prefixes = g_strdup (pre + 1);
-				serv->nick_modes = g_strdup (word[w] + 8);
+				serv->nick_modes = g_strdup (tokvalue + 1);
 			} else
 			{
 				/* bad! some ircds don't give us the modes. */
 				/* in this case, we use it only to strip /NAMES */
 				serv->bad_prefix = TRUE;
 				g_free (serv->bad_nick_prefixes);
-				serv->bad_nick_prefixes = g_strdup (word[w] + 7);
+				serv->bad_nick_prefixes = g_strdup (tokvalue);
 			}
-		} else if (strncmp (word[w], "WATCH=", 6) == 0)
+		} else if (g_strcmp0 (tokname, "WATCH") == 0)
 		{
-			serv->supports_watch = TRUE;
-		} else if (strncmp (word[w], "MONITOR=", 8) == 0)
+			serv->supports_watch = tokadding;
+		} else if (g_strcmp0 (tokname, "MONITOR") == 0)
 		{
-			serv->supports_monitor = TRUE;
-		} else if (strncmp (word[w], "NETWORK=", 8) == 0)
+			serv->supports_monitor = tokadding;
+		} else if (g_strcmp0 (tokname, "NETWORK") == 0)
 		{
-			if (serv->server_session->type == SESS_SERVER)
+			if (serv->server_session->type == SESS_SERVER && strlen (tokvalue))
 			{
-				safe_strcpy (serv->server_session->channel, word[w] + 8, CHANLEN);
+				safe_strcpy (serv->server_session->channel, tokvalue, CHANLEN);
 				fe_set_channel (serv->server_session);
 			}
 
-		} else if (strncmp (word[w], "CASEMAPPING=", 12) == 0)
+		} else if (g_strcmp0 (tokname, "CASEMAPPING") == 0)
 		{
-			if (strcmp (word[w] + 12, "ascii") == 0)	/* bahamut */
+			if (g_strcmp0 (tokvalue, "ascii") == 0)
 				serv->p_cmp = (void *)g_ascii_strcasecmp;
-		} else if (strncmp (word[w], "CHARSET=", 8) == 0)
+		} else if (g_strcmp0 (tokname, "CHARSET") == 0)
 		{
-			if (g_ascii_strncasecmp (word[w] + 8, "UTF-8", 5) == 0)
+			if (g_ascii_strcasecmp (tokvalue, "UTF-8") == 0)
 			{
 				server_set_encoding (serv, "UTF-8");
 			}
-		} else if (strcmp (word[w], "NAMESX") == 0)
+		} else if (g_strcmp0 (tokname, "UTF8ONLY") == 0)
 		{
-									/* 12345678901234567 */
-			tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17);
-		} else if (strcmp (word[w], "WHOX") == 0)
+			server_set_encoding (serv, "UTF-8");
+		} else if (g_strcmp0 (tokname, "NAMESX") == 0)
+		{
+			if (tokadding && !serv->have_namesx)
+			{
+				/* only use protoctl if the server doesn't have the equivalent cap */
+				tcp_send_len (serv, "PROTOCTL NAMESX\r\n", 17);
+				serv->have_namesx = TRUE;
+			}
+		} else if (g_strcmp0 (tokname, "WHOX") == 0)
 		{
-			serv->have_whox = TRUE;
-		} else if (strcmp (word[w], "EXCEPTS") == 0)
+			serv->have_whox = tokadding;
+		} else if (g_strcmp0 (tokname, "EXCEPTS") == 0)
 		{
-			serv->have_except = TRUE;
-		} else if (strcmp (word[w], "INVEX") == 0)
+			serv->have_except = tokadding;
+		} else if (g_strcmp0 (tokname, "INVEX") == 0)
 		{
 			/* supports mode letter +I, default channel invite */
-			serv->have_invite = TRUE;
-		} else if (strncmp (word[w], "ELIST=", 6) == 0)
+			serv->have_invite = tokadding;
+		} else if (g_strcmp0 (tokname, "ELIST") == 0)
 		{
 			/* supports LIST >< min/max user counts? */
-			if (strchr (word[w] + 6, 'U') || strchr (word[w] + 6, 'u'))
+			if (strchr (tokvalue, 'U') || strchr (tokvalue, 'u'))
 				serv->use_listargs = TRUE;
 		}
 
+		g_free (tokname);
+		g_free (tokvalue);
 		w++;
 	}
 }
diff --git a/src/common/notify.c b/src/common/notify.c
index b5316c36..ef52889b 100644
--- a/src/common/notify.c
+++ b/src/common/notify.c
@@ -123,7 +123,11 @@ notify_save (void)
 {
 	int fh;
 	struct notify *notify;
-	GSList *list = notify_list;
+        // while reading the notify.conf file, elements are added by prepending to the
+        // list. reverse the list before writing to disk to keep the original
+        // order of the list
+        GSList *list = g_slist_copy(notify_list);
+        list = g_slist_reverse(list);
 
 	fh = hexchat_open_file ("notify.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE);
 	if (fh != -1)
@@ -142,6 +146,7 @@ notify_save (void)
 		}
 		close (fh);
 	}
+        g_slist_free(list);
 }
 
 void
diff --git a/src/common/outbound.c b/src/common/outbound.c
index 614aad38..b8153502 100644
--- a/src/common/outbound.c
+++ b/src/common/outbound.c
@@ -468,7 +468,7 @@ create_mask (session * sess, char *mask, char *mode, char *typestr, int deop)
 			type = prefs.hex_irc_ban_type;
 
 		buf[0] = 0;
-		if (inet_addr (fullhost) != -1)	/* "fullhost" is really a IP number */
+		if (inet_addr (fullhost) != (guint32) -1)	/* "fullhost" is really a IP number */
 		{
 			lastdot = strrchr (fullhost, '.');
 			if (!lastdot)
@@ -1579,9 +1579,26 @@ cmd_execw (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 		EMIT_SIGNAL (XP_TE_NOCHILD, sess, NULL, NULL, NULL, NULL, 0);
 		return FALSE;
 	}
-	len = strlen(word_eol[2]);
-	temp = g_strconcat (word_eol[2], "\n", NULL);
-	PrintText(sess, temp);
+	if (strcmp (word[2], "--") == 0)
+	{
+		len = strlen(word_eol[3]);
+		temp = g_strconcat (word_eol[3], "\n", NULL);
+		PrintText(sess, temp);
+	}
+	else
+	{
+		if (strcmp (word[2], "-q") == 0)
+		{
+			len = strlen(word_eol[3]);
+			temp = g_strconcat (word_eol[3], "\n", NULL);
+		}
+		else
+		{
+			len = strlen(word_eol[2]);
+			temp = g_strconcat (word_eol[2], "\n", NULL);
+			PrintText(sess, temp);
+		}
+	}
 	write(sess->running_exec->myfd, temp, len + 1);
 	g_free(temp);
 
@@ -2152,7 +2169,6 @@ cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 	{
 	case 0x058b836e: fe_ctrl_gui (sess, 8, 0); break; /* APPLY */
 	case 0xac1eee45: fe_ctrl_gui (sess, 7, 2); break; /* ATTACH */
-	case 0x05a72f63: fe_ctrl_gui (sess, 4, atoi (word[3])); break; /* COLOR */
 	case 0xb06a1793: fe_ctrl_gui (sess, 7, 1); break; /* DETACH */
 	case 0x05cfeff0: fe_ctrl_gui (sess, 3, 0); break; /* FLASH */
 	case 0x05d154d8: fe_ctrl_gui (sess, 2, 0); break; /* FOCUS */
@@ -2166,6 +2182,12 @@ cmd_gui (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 		else
 			return FALSE;
 		break;
+	case 0x05a72f63: /* COLOR */
+		if (!g_ascii_strcasecmp (word[4], "-NOOVERRIDE"))
+			fe_ctrl_gui (sess, 4, FE_COLOR_FLAG_NOOVERRIDE | atoi (word[3]));
+		else
+			fe_ctrl_gui (sess, 4, atoi (word[3]));
+		break;
 	default:
 		return FALSE;
 	}
@@ -3225,16 +3247,28 @@ cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 	else if (*word[2])
 	{
 		int offset = 0;
-#ifdef USE_OPENSSL
-		int use_ssl = FALSE;
 
-		if (strcmp (word[2], "-ssl") == 0)
+#ifdef USE_OPENSSL
+		int use_ssl = TRUE;
+		int use_ssl_noverify = FALSE;
+		if (g_strcmp0 (word[2], "-ssl") == 0)
 		{
 			use_ssl = TRUE;
+			use_ssl_noverify = FALSE;
+			offset++;	/* args move up by 1 word */
+		} else if (g_strcmp0 (word[2], "-ssl-noverify") == 0)
+		{
+			use_ssl = TRUE;
+			use_ssl_noverify = TRUE;
+			offset++;	/* args move up by 1 word */
+		} else if (g_strcmp0 (word[2], "-insecure") == 0)
+		{
+			use_ssl = FALSE;
+			use_ssl_noverify = FALSE;
 			offset++;	/* args move up by 1 word */
 		}
 		serv->use_ssl = use_ssl;
-		serv->accept_invalid_cert = TRUE;
+		serv->accept_invalid_cert = use_ssl_noverify;
 #endif
 
 		if (*word[4+offset])
@@ -3421,18 +3455,34 @@ cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 	char *pass = NULL;
 	char *channel = NULL;
 	char *key = NULL;
+#ifdef USE_OPENSSL
+	int use_ssl = TRUE;
+	int use_ssl_noverify = FALSE;
+#else
 	int use_ssl = FALSE;
+#endif
 	int is_url = TRUE;
 	server *serv = sess->server;
 	ircnet *net = NULL;
 
 #ifdef USE_OPENSSL
 	/* BitchX uses -ssl, mIRC uses -e, let's support both */
-	if (strcmp (word[2], "-ssl") == 0 || strcmp (word[2], "-e") == 0)
+	if (g_strcmp0 (word[2], "-ssl") == 0 || g_strcmp0 (word[2], "-e") == 0)
 	{
 		use_ssl = TRUE;
 		offset++;	/* args move up by 1 word */
 	}
+	else if (g_strcmp0 (word[2], "-ssl-noverify") == 0)
+	{
+		use_ssl = TRUE;
+		use_ssl_noverify = TRUE;
+		offset++;	/* args move up by 1 word */
+	}
+	else if (g_strcmp0 (word[2], "-insecure") == 0)
+	{
+		use_ssl = FALSE;
+		offset++;	/* args move up by 1 word */
+	}
 #endif
 
 	if (!parse_irc_url (word[2 + offset], &server_name, &port, &channel, &key, &use_ssl))
@@ -3473,6 +3523,13 @@ cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 		use_ssl = TRUE;
 #endif
 	}
+	else if (port[0] == '-')
+	{
+		port++;
+#ifdef USE_OPENSSL
+		use_ssl = FALSE;
+#endif
+	}
 
 	if (*pass)
 	{
@@ -3497,7 +3554,7 @@ cmd_server (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 
 #ifdef USE_OPENSSL
 	serv->use_ssl = use_ssl;
-	serv->accept_invalid_cert = TRUE;
+	serv->accept_invalid_cert = use_ssl_noverify;
 #endif
 
 	/* try to connect by Network name */
@@ -3528,7 +3585,7 @@ cmd_servchan (struct session *sess, char *tbuf, char *word[],
 	int offset = 0;
 
 #ifdef USE_OPENSSL
-	if (strcmp (word[2], "-ssl") == 0)
+	if (g_strcmp0 (word[2], "-ssl") == 0 || g_strcmp0 (word[2], "-ssl-noverify") == 0 || g_strcmp0 (word[2], "-insecure") == 0)
 		offset++;
 #endif
 
@@ -3863,34 +3920,6 @@ cmd_wallchop (struct session *sess, char *tbuf, char *word[],
 }
 
 static int
-cmd_wallchan (struct session *sess, char *tbuf, char *word[],
-				  char *word_eol[])
-{
-	GSList *list;
-
-	if (*word_eol[2])
-	{
-		list = sess_list;
-		while (list)
-		{
-			sess = list->data;
-			if (sess->type == SESS_CHANNEL)
-			{
-				message_tags_data no_tags = MESSAGE_TAGS_DATA_INIT;
-
-				inbound_chanmsg (sess->server, NULL, sess->channel,
-									  sess->server->nick, word_eol[2], TRUE, FALSE, 
-									  &no_tags);
-				sess->server->p_message (sess->server, sess->channel, word_eol[2]);
-			}
-			list = list->next;
-		}
-		return TRUE;
-	}
-	return FALSE;
-}
-
-static int
 cmd_hop (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 {
 	int i = 2;
@@ -3930,7 +3959,7 @@ cmd_voice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
 const struct commands xc_cmds[] = {
 	{"ADDBUTTON", cmd_addbutton, 0, 0, 1,
 	 N_("ADDBUTTON <name> <action>, adds a button under the user-list")},
-	{"ADDSERVER", cmd_addserver, 0, 0, 1, N_("ADDSERVER <NewNetwork> <newserver/6667>, adds a new network with a new server to the network list")},
+	{"ADDSERVER", cmd_addserver, 0, 0, 1, N_("ADDSERVER <NewNetwork> <hostname/port>, adds a new network with a new server to the network list")},
 	{"ALLCHAN", cmd_allchannels, 0, 0, 1,
 	 N_("ALLCHAN <cmd>, sends a command to all channels you're in")},
 	{"ALLCHANL", cmd_allchannelslocal, 0, 0, 1,
@@ -3986,7 +4015,7 @@ const struct commands xc_cmds[] = {
 	 N_("EXECKILL [-9], kills a running exec in the current session. If -9 is given the process is SIGKILL'ed")},
 #ifndef __EMX__
 	{"EXECSTOP", cmd_execs, 0, 0, 1, N_("EXECSTOP, sends the process SIGSTOP")},
-	{"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE, sends data to the processes stdin")},
+	{"EXECWRITE", cmd_execw, 0, 0, 1, N_("EXECWRITE [-q|--], sends data to the processes stdin; use -q flag to quiet/suppress output at text box; use -- flag to stop interpreting arguments as flags, needed if -q itself would occur as data")},
 #endif
 #endif
 #if 0
@@ -4001,8 +4030,9 @@ const struct commands xc_cmds[] = {
 	{"GETINT", cmd_getint, 0, 0, 1, "GETINT <default> <command> <prompt>"},
 	{"GETSTR", cmd_getstr, 0, 0, 1, "GETSTR <default> <command> <prompt>"},
 	{"GHOST", cmd_ghost, 1, 0, 1, N_("GHOST <nick> [password], Kills a ghosted nickname")},
-	{"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY|COLOR <n>]\n"
-									  "       GUI [MSGBOX <text>|MENU TOGGLE]"},
+	{"GUI", cmd_gui, 0, 0, 1, "GUI [APPLY|ATTACH|DETACH|SHOW|HIDE|FOCUS|FLASH|ICONIFY]\n"
+									  "       GUI [MSGBOX <text>|MENU TOGGLE]\n"
+									  "       GUI COLOR <n> [-NOOVERRIDE]"},
 	{"HELP", cmd_help, 0, 0, 1, 0},
 	{"HOP", cmd_hop, 1, 1, 1,
 	 N_("HOP <nick>, gives chanhalf-op status to the nick (needs chanop)")},
@@ -4077,7 +4107,7 @@ const struct commands xc_cmds[] = {
 	 N_("QUOTE <text>, sends the text in raw form to the server")},
 #ifdef USE_OPENSSL
 	{"RECONNECT", cmd_reconnect, 0, 0, 1,
-	 N_("RECONNECT [-ssl] [<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")},
+	 N_("RECONNECT [-ssl|-ssl-noverify] [<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")},
 #else
 	{"RECONNECT", cmd_reconnect, 0, 0, 1,
 	 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")},
@@ -4089,14 +4119,14 @@ const struct commands xc_cmds[] = {
 	{"SEND", cmd_send, 0, 0, 1, N_("SEND <nick> [<file>]")},
 #ifdef USE_OPENSSL
 	{"SERVCHAN", cmd_servchan, 0, 0, 1,
-	 N_("SERVCHAN [-ssl] <host> <port> <channel>, connects and joins a channel")},
+	 N_("SERVCHAN [-insecure|-ssl|-ssl-noverify] <host> <port> <channel>, connects and joins a channel using ssl unless otherwise specified")},
 #else
 	{"SERVCHAN", cmd_servchan, 0, 0, 1,
 	 N_("SERVCHAN <host> <port> <channel>, connects and joins a channel")},
 #endif
 #ifdef USE_OPENSSL
 	{"SERVER", cmd_server, 0, 0, 1,
-	 N_("SERVER [-ssl] <host> [<port>] [<password>], connects to a server, the default port is 6667 for normal connections, and 6697 for ssl connections")},
+	 N_("SERVER [-insecure|-ssl|-ssl-noverify] <host> [<port>] [<password>], connects to a server using ssl unless otherwise specified, the default port is 6697 for ssl connections and 6667 for insecure connections")},
 #else
 	{"SERVER", cmd_server, 0, 0, 1,
 	 N_("SERVER <host> [<port>] [<password>], connects to a server, the default port is 6667")},
@@ -4127,8 +4157,6 @@ const struct commands xc_cmds[] = {
 	{"USERLIST", cmd_userlist, 1, 1, 1, 0},
 	{"VOICE", cmd_voice, 1, 1, 1,
 	 N_("VOICE <nick>, gives voice status to someone (needs chanop)")},
-	{"WALLCHAN", cmd_wallchan, 1, 1, 1,
-	 N_("WALLCHAN <message>, writes the message to all channels")},
 	{"WALLCHOP", cmd_wallchop, 1, 1, 1,
 	 N_("WALLCHOP <message>, sends the message to all chanops on the current channel")},
 	{0, 0, 0, 0, 0, 0}
@@ -4420,6 +4448,9 @@ check_special_chars (char *cmd, int do_ascii) /* check for %X */
 				case 'I':
 					buf[i] = '\035';
 					break;
+				case 'S':
+					buf[i] = '\036';
+					break;
 				case 'C':
 					buf[i] = '\003';
 					break;
diff --git a/src/common/plugin-timer.c b/src/common/plugin-timer.c
index d0c82c28..17f11ce3 100644
--- a/src/common/plugin-timer.c
+++ b/src/common/plugin-timer.c
@@ -198,7 +198,7 @@ timer_cb (char *word[], char *word_eol[], void *userdata)
 		offset += 2;
 	}
 
-	timeout = atof (word[2 + offset]);
+	timeout = g_ascii_strtod (word[2 + offset], NULL);
 	command = word_eol[3 + offset];
 
 	if (timeout < 0.1 || timeout * 1000 > INT_MAX || !command[0])
diff --git a/src/common/plugin.c b/src/common/plugin.c
index d3f3b7ca..f4691d73 100644
--- a/src/common/plugin.c
+++ b/src/common/plugin.c
@@ -491,7 +491,6 @@ plugin_auto_load (session *sess)
 	for_files (lib_dir, "hcfishlim.dll", plugin_auto_load_cb);
 	for_files(lib_dir, "hclua.dll", plugin_auto_load_cb);
 	for_files (lib_dir, "hcperl.dll", plugin_auto_load_cb);
-	for_files (lib_dir, "hcpython2.dll", plugin_auto_load_cb);
 	for_files (lib_dir, "hcpython3.dll", plugin_auto_load_cb);
 	for_files (lib_dir, "hcupd.dll", plugin_auto_load_cb);
 	for_files (lib_dir, "hcwinamp.dll", plugin_auto_load_cb);
@@ -572,7 +571,7 @@ plugin_hook_find (GSList *list, int type, char *name)
 
 static int
 plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
-				 hexchat_event_attrs *attrs, int type)
+				 hexchat_event_attrs *attrs, int type, int mask)
 {
 	/* fix segfault https://github.com/hexchat/hexchat/issues/2265 */
 	static int depth = 0;
@@ -612,6 +611,11 @@ plugin_hook_run (session *sess, char *name, char *word[], char *word_eol[],
 			break;
 		}
 
+		if ((ret & mask) != ret) {
+			g_critical("plugin tried to eat cleanup hooks");
+		}
+		ret &= mask;
+
 		if ((ret & HEXCHAT_EAT_HEXCHAT) && (ret & HEXCHAT_EAT_PLUGIN))
 		{
 			eat = 1;
@@ -652,7 +656,7 @@ xit:
 int
 plugin_emit_command (session *sess, char *name, char *word[], char *word_eol[])
 {
-	return plugin_hook_run (sess, name, word, word_eol, NULL, HOOK_COMMAND);
+	return plugin_hook_run (sess, name, word, word_eol, NULL, HOOK_COMMAND, -1);
 }
 
 hexchat_event_attrs *
@@ -678,7 +682,7 @@ plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[],
 	attrs.server_time_utc = server_time;
 
 	return plugin_hook_run (sess, name, word, word_eol, &attrs, 
-							HOOK_SERVER | HOOK_SERVER_ATTRS);
+							HOOK_SERVER | HOOK_SERVER_ATTRS, -1);
 }
 
 /* see if any plugins are interested in this print event */
@@ -691,7 +695,7 @@ plugin_emit_print (session *sess, char *word[], time_t server_time)
 	attrs.server_time_utc = server_time;
 
 	return plugin_hook_run (sess, word[0], word, NULL, &attrs,
-							HOOK_PRINT | HOOK_PRINT_ATTRS);
+							HOOK_PRINT | HOOK_PRINT_ATTRS, -1);
 }
 
 /* used by plugin_emit_dummy_print to fix some UB */
@@ -707,7 +711,7 @@ check_and_invalidate(void *plug_, void *killsess_)
 }
 
 int
-plugin_emit_dummy_print (session *sess, char *name)
+plugin_emit_dummy_print (session *sess, char *name, int mask)
 {
 	char *word[PDIWORDS];
 	int i;
@@ -716,7 +720,7 @@ plugin_emit_dummy_print (session *sess, char *name)
 	for (i = 1; i < PDIWORDS; i++)
 		word[i] = "\000";
 
-	i = plugin_hook_run (sess, name, word, NULL, NULL, HOOK_PRINT);
+	i = plugin_hook_run (sess, name, word, NULL, NULL, HOOK_PRINT, mask);
 
 	/* shoehorned fix for Undefined Behaviour */
 	/* see https://stackoverflow.com/q/52628773/3691554 */
@@ -758,7 +762,7 @@ plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gu
 	for (i = 5; i < PDIWORDS; i++)
 		word[i] = "\000";
 
-	return plugin_hook_run (sess, word[0], word, NULL, NULL, HOOK_PRINT);
+	return plugin_hook_run (sess, word[0], word, NULL, NULL, HOOK_PRINT, -1);
 }
 
 static int
@@ -1149,12 +1153,16 @@ hexchat_get_context (hexchat_plugin *ph)
 int
 hexchat_set_context (hexchat_plugin *ph, hexchat_context *context)
 {
-	if (is_session (context))
+	if (context == NULL) {
+		return 0;
+	}
+	if (!is_session (context))
 	{
-		ph->context = context;
-		return 1;
+		g_critical("plugin tried to set an invalid context");
+		return 0;
 	}
-	return 0;
+	ph->context = context;
+	return 1;
 }
 
 hexchat_context *
diff --git a/src/common/plugin.h b/src/common/plugin.h
index fb7da831..051d1f5a 100644
--- a/src/common/plugin.h
+++ b/src/common/plugin.h
@@ -174,7 +174,7 @@ int plugin_emit_command (session *sess, char *name, char *word[], char *word_eol
 int plugin_emit_server (session *sess, char *name, char *word[], char *word_eol[],
 						time_t server_time);
 int plugin_emit_print (session *sess, char *word[], time_t server_time);
-int plugin_emit_dummy_print (session *sess, char *name);
+int plugin_emit_dummy_print (session *sess, char *name, int mask);
 int plugin_emit_keypress (session *sess, unsigned int state, unsigned int keyval, gunichar key);
 GList* plugin_command_list(GList *tmp_list);
 int plugin_show_help (session *sess, char *cmd);
diff --git a/src/common/proto-irc.c b/src/common/proto-irc.c
index c8e44b62..5b8e02c4 100644
--- a/src/common/proto-irc.c
+++ b/src/common/proto-irc.c
@@ -460,6 +460,18 @@ channel_date (session *sess, char *chan, char *timestr,
 								  tags_data->timestamp);
 }
 
+static int
+trailing_index(char *word_eol[])
+{
+	int param_index;
+	for (param_index = 3; param_index < PDIWORDS; ++param_index)
+	{
+		if (word_eol[param_index][0] == ':')
+			break;
+	}
+	return param_index;
+}
+
 static void
 process_numeric (session * sess, int n,
 					  char *word[], char *word_eol[], char *text,
@@ -491,22 +503,6 @@ process_numeric (session * sess, int n,
 
 		goto def;
 
-	case 4:	/* check the ircd type */
-		serv->use_listargs = FALSE;
-		serv->modes_per_line = 3;		/* default to IRC RFC */
-		if (strncmp (word[5], "bahamut", 7) == 0)				/* DALNet */
-		{
-			serv->use_listargs = TRUE;		/* use the /list args */
-		} else if (strncmp (word[5], "u2.10.", 6) == 0)		/* Undernet */
-		{
-			serv->use_listargs = TRUE;		/* use the /list args */
-			serv->modes_per_line = 6;		/* allow 6 modes per line */
-		} else if (strncmp (word[5], "glx2", 4) == 0)
-		{
-			serv->use_listargs = TRUE;		/* use the /list args */
-		}
-		goto def;
-
 	case 5:
 		inbound_005 (serv, word, tags_data);
 		goto def;
@@ -631,7 +627,7 @@ process_numeric (session * sess, int n,
 	case 320:	/* :is an identified user */
 		if (!serv->skip_next_whois)
 			EMIT_SIGNAL_TIMESTAMP (XP_TE_WHOIS_ID, whois_sess, word[4],
-										  word_eol[5] + 1, NULL, NULL, 0,
+										  word_eol[5][0] == ':' ? word_eol[5] + 1 : word_eol[5], NULL, NULL, 0,
 										  tags_data->timestamp);
 		break;
 
@@ -801,7 +797,7 @@ process_numeric (session * sess, int n,
 		break;
 
 	case 346:	/* +I-list entry */
-		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 346,
+		if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 346,
 									 tags_data))
 			goto def;
 		break;
@@ -812,7 +808,7 @@ process_numeric (session * sess, int n,
 		break;
 
 	case 348:	/* +e-list entry */
-		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 348,
+		if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 348,
 									 tags_data))
 			goto def;
 		break;
@@ -837,7 +833,7 @@ process_numeric (session * sess, int n,
 		break;
 
 	case 367: /* banlist entry */
-		if (!inbound_banlist (sess, atol (word[7]), word[4], word[5], word[6], 367,
+		if (!inbound_banlist (sess, atol (STRIP_COLON (word, word_eol, 7)), word[4], word[5], word[6], 367,
 									 tags_data))
 			goto def;
 		break;
@@ -924,6 +920,14 @@ process_numeric (session * sess, int n,
 		notify_set_online (serv, word[4], tags_data);
 		break;
 
+	case 524: // ERR_HELPNOTFOUND
+	case 704: // RPL_HELPSTART
+	case 705: // RPL_HELPTXT
+	case 706: // RPL_ENDOFHELP
+		EMIT_SIGNAL_TIMESTAMP (XP_TE_SERVTEXT, sess, STRIP_COLON(word, word_eol, 5), NULL, NULL, NULL,
+									  0, tags_data->timestamp);
+		break;
+
 	case 728:	/* +q-list entry */
 		/* NOTE:  FREENODE returns these results inconsistent with e.g. +b */
 		/* Who else has imlemented MODE_QUIET, I wonder? */
@@ -1139,6 +1143,39 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 										(word_eol[3][0] == ':') ? word_eol[3] + 1 : NULL,
 										tags_data);
 			return;
+
+		case WORDL('F','A','I','L'):
+			text = STRIP_COLON(word, word_eol, trailing_index(word_eol));
+			if (g_strcmp0(word[3], "*") == 0)
+			{
+				EMIT_SIGNAL_TIMESTAMP (XP_TE_FAIL, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp);
+			} else
+			{
+				EMIT_SIGNAL_TIMESTAMP (XP_TE_FAILCMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp);
+			}
+			return;
+
+		case WORDL('W','A','R','N'):
+			text = STRIP_COLON(word, word_eol, trailing_index(word_eol));
+			if (g_strcmp0(word[3], "*") == 0)
+			{
+				EMIT_SIGNAL_TIMESTAMP (XP_TE_WARN, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp);
+			} else
+			{
+				EMIT_SIGNAL_TIMESTAMP (XP_TE_WARNCMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp);
+			}
+			return;
+
+		case WORDL('N','O','T','E'):
+			text = STRIP_COLON(word, word_eol, trailing_index(word_eol));
+			if (g_strcmp0(word[3], "*") == 0)
+			{
+				EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTE, sess, word[4], text, NULL, NULL, NULL, tags_data->timestamp);
+			} else
+			{
+				EMIT_SIGNAL_TIMESTAMP (XP_TE_NOTECMD, sess, word[3], word[4], text, NULL, NULL, tags_data->timestamp);
+			}
+			return;
 		}
 
 		goto garbage;
@@ -1189,8 +1226,6 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 
 		case WORDL('N','O','T','I'):
 			{
-				int id = FALSE;								/* identified */
-
 				text = word_eol[4];
 				if (*text == ':')
 				{
@@ -1219,18 +1254,8 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 				}
 #endif
 
-				if (serv->have_idmsg)
-				{
-					if (*text == '+')
-					{
-						id = TRUE;
-						text++;
-					} else if (*text == '-')
-						text++;
-				}
-
 				if (!ignore_check (word[1], IG_NOTI))
-					inbound_notice (serv, word[3], nick, text, ip, id, tags_data);
+					inbound_notice (serv, word[3], nick, text, ip, tags_data->identified, tags_data);
 			}
 			return;
 
@@ -1238,7 +1263,6 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 			{
 				char *to = word[3];
 				int len;
-				int id = FALSE;	/* identified */
 				if (*to)
 				{
 					/* Handle limited channel messages, for now no special event */
@@ -1249,15 +1273,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 					text = word_eol[4];
 					if (*text == ':')
 						text++;
-					if (serv->have_idmsg)
-					{
-						if (*text == '+')
-						{
-							id = TRUE;
-							text++;
-						} else if (*text == '-')
-							text++;
-					}
+
 					len = strlen (text);
 					if (text[0] == 1)	/* ctcp */
 					{
@@ -1289,7 +1305,7 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 							}
 						}
 
-						ctcp_handle (sess, to, nick, ip, text, word, word_eol, id,
+						ctcp_handle (sess, to, nick, ip, text, word, word_eol, tags_data->identified,
 										 tags_data);
 
 						/* Note word will be invalid beyond this scope */
@@ -1300,13 +1316,13 @@ process_named_msg (session *sess, char *type, char *word[], char *word_eol[],
 						{
 							if (ignore_check (word[1], IG_CHAN))
 								return;
-							inbound_chanmsg (serv, NULL, to, nick, text, FALSE, id,
+							inbound_chanmsg (serv, NULL, to, nick, text, FALSE, tags_data->identified,
 												  tags_data);
 						} else
 						{
 							if (ignore_check (word[1], IG_PRIV))
 								return;
-							inbound_privmsg (serv, nick, ip, text, id, tags_data);
+							inbound_privmsg (serv, nick, ip, text, tags_data->identified, tags_data);
 						}
 					}
 				}
@@ -1537,6 +1553,9 @@ handle_message_tags (server *serv, const char *tags_str,
 		if (serv->have_account_tag && !strcmp (key, "account"))
 			tags_data->account = g_strdup (value);
 
+		if (serv->have_idmsg && strcmp (key, "solanum.chat/identified"))
+			tags_data->identified = TRUE;
+
 		if (serv->have_server_time && !strcmp (key, "time"))
 			handle_message_tag_time (value, tags_data);
 	}
diff --git a/src/common/proto-irc.h b/src/common/proto-irc.h
index 0f72c644..6f52f1bc 100644
--- a/src/common/proto-irc.h
+++ b/src/common/proto-irc.h
@@ -26,6 +26,7 @@
 #define MESSAGE_TAGS_DATA_INIT			\
 	{									\
 		NULL, /* account name */		\
+		FALSE, /* identified to nick */ \
 		(time_t)0, /* timestamp */		\
 	}
 
@@ -38,6 +39,7 @@
 typedef struct 
 {
 	char *account;
+	gboolean identified;
 	time_t timestamp;
 } message_tags_data;
 
diff --git a/src/common/scram.c b/src/common/scram.c
new file mode 100644
index 00000000..b39199de
--- /dev/null
+++ b/src/common/scram.c
@@ -0,0 +1,333 @@
+/* HexChat
+ * Copyright (C) 2023 Patrick Okraku
+ *
+ * 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 "hexchat.h"
+
+#ifdef USE_OPENSSL
+
+#include "scram.h"
+#include <openssl/hmac.h>
+#include <openssl/rand.h>
+
+#define NONCE_LENGTH 18
+#define CLIENT_KEY "Client Key"
+#define SERVER_KEY "Server Key"
+
+// EVP_MD_CTX_create() and EVP_MD_CTX_destroy() were renamed in OpenSSL 1.1.0
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#define EVP_MD_CTX_new(ctx) EVP_MD_CTX_create(ctx)
+#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx)
+#endif
+
+scram_session
+*scram_session_create (const char *digest, const char *username, const char *password)
+{
+	scram_session *session;
+	const EVP_MD *md;
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
+	OpenSSL_add_all_algorithms ();
+#endif
+	md = EVP_get_digestbyname (digest);
+
+	if (md == NULL)
+	{
+		// Unknown message digest
+		return NULL;
+	}
+
+	session = g_new0 (scram_session, 1);
+	session->digest = md;
+	session->digest_size = EVP_MD_size (md);
+	session->username = g_strdup (username);
+	session->password = g_strdup (password);
+	return session;
+}
+
+void
+scram_session_free (scram_session *session)
+{
+	if (session == NULL)
+	{
+		return;
+	}
+
+	g_free (session->username);
+	g_free (session->password);
+	g_free (session->client_nonce_b64);
+	g_free (session->client_first_message_bare);
+	g_free (session->salted_password);
+	g_free (session->auth_message);
+	g_free (session->error);
+
+	g_free (session);
+}
+
+static int
+create_nonce (void *buffer, size_t length)
+{
+	return RAND_bytes (buffer, length);
+}
+
+static int
+create_SHA (scram_session *session, const unsigned char *input, size_t input_len,
+			unsigned char *output, unsigned int *output_len)
+{
+	EVP_MD_CTX *md_ctx = EVP_MD_CTX_new ();
+
+	if (!EVP_DigestInit_ex (md_ctx, session->digest, NULL))
+	{
+		session->error = g_strdup ("Message digest initialization failed");
+		EVP_MD_CTX_free (md_ctx);
+		return SCRAM_ERROR;
+	}
+
+	if (!EVP_DigestUpdate (md_ctx, input, input_len))
+	{
+		session->error = g_strdup ("Message digest update failed");
+		EVP_MD_CTX_free (md_ctx);
+		return SCRAM_ERROR;
+	}
+
+	if (!EVP_DigestFinal_ex (md_ctx, output, output_len))
+	{
+		session->error = g_strdup ("Message digest finalization failed");
+		EVP_MD_CTX_free (md_ctx);
+		return SCRAM_ERROR;
+	}
+
+	EVP_MD_CTX_free (md_ctx);
+	return SCRAM_IN_PROGRESS;
+}
+
+static scram_status
+process_client_first (scram_session *session, char **output, size_t *output_len)
+{
+	char nonce[NONCE_LENGTH];
+
+	if (!create_nonce (nonce, NONCE_LENGTH))
+	{
+		session->error = g_strdup ("Could not create client nonce");
+		return SCRAM_ERROR;
+	}
+
+	session->client_nonce_b64 = g_base64_encode ((guchar *) nonce, NONCE_LENGTH);
+	*output = g_strdup_printf ("n,,n=%s,r=%s", session->username, session->client_nonce_b64);
+	*output_len = strlen (*output);
+	session->client_first_message_bare = g_strdup (*output + 3);
+	session->step++;
+	return SCRAM_IN_PROGRESS;
+}
+
+static scram_status
+process_server_first (scram_session *session, const char *data, char **output,
+					  size_t *output_len)
+{
+	char **params, *client_final_message_without_proof, *salt, *server_nonce_b64,
+			*client_proof_b64;
+	unsigned char *client_key, stored_key[EVP_MAX_MD_SIZE], *client_signature, *client_proof;
+	unsigned int i, param_count, iteration_count, client_key_len, stored_key_len;
+	gsize salt_len = 0;
+	size_t client_nonce_len;
+
+	params = g_strsplit (data, ",", -1);
+	param_count = g_strv_length (params);
+
+	if (param_count < 3)
+	{
+		session->error = g_strdup_printf ("Invalid server-first-message: %s", data);
+		g_strfreev (params);
+		return SCRAM_ERROR;
+	}
+
+	server_nonce_b64 = NULL;
+	salt = NULL;
+	iteration_count = 0;
+
+	for (i = 0; i < param_count; i++)
+	{
+		if (!strncmp (params[i], "r=", 2))
+		{
+			g_free (server_nonce_b64);
+			server_nonce_b64 = g_strdup (params[i] + 2);
+		}
+		else if (!strncmp (params[i], "s=", 2))
+		{
+			g_free (salt);
+			salt = g_strdup (params[i] + 2);
+		}
+		else if (!strncmp (params[i], "i=", 2))
+		{
+			iteration_count = strtoul (params[i] + 2, NULL, 10);
+		}
+	}
+
+	g_strfreev (params);
+
+	if (server_nonce_b64 == NULL || *server_nonce_b64 == '\0' || salt == NULL ||
+		*salt == '\0' || iteration_count == 0)
+	{
+		session->error = g_strdup_printf ("Invalid server-first-message: %s", data);
+		g_free (server_nonce_b64);
+		g_free (salt);
+		return SCRAM_ERROR;
+	}
+
+	client_nonce_len = strlen (session->client_nonce_b64);
+
+	// The server can append his nonce to the client's nonce
+	if (strlen (server_nonce_b64) < client_nonce_len ||
+		strncmp (server_nonce_b64, session->client_nonce_b64, client_nonce_len))
+	{
+		session->error = g_strdup_printf ("Invalid server nonce: %s", server_nonce_b64);
+		return SCRAM_ERROR;
+	}
+
+	g_base64_decode_inplace ((gchar *) salt, &salt_len);
+
+	// SaltedPassword := Hi(Normalize(password), salt, i)
+	session->salted_password = g_malloc (session->digest_size);
+
+	PKCS5_PBKDF2_HMAC (session->password, strlen (session->password), (unsigned char *) salt,
+					   salt_len, iteration_count, session->digest, session->digest_size,
+					   session->salted_password);
+
+	// AuthMessage := client-first-message-bare + "," +
+	//                server-first-message + "," +
+	//                client-final-message-without-proof
+	client_final_message_without_proof = g_strdup_printf ("c=biws,r=%s", server_nonce_b64);
+
+	session->auth_message = g_strdup_printf ("%s,%s,%s", session->client_first_message_bare,
+											 data, client_final_message_without_proof);
+
+	// ClientKey := HMAC(SaltedPassword, "Client Key")
+	client_key = g_malloc0 (session->digest_size);
+
+	HMAC (session->digest, session->salted_password, session->digest_size,
+		  (unsigned char *) CLIENT_KEY, strlen (CLIENT_KEY), client_key, &client_key_len);
+
+	// StoredKey := H(ClientKey)
+	if (!create_SHA (session, client_key, session->digest_size, stored_key, &stored_key_len))
+	{
+		g_free (client_final_message_without_proof);
+		g_free (server_nonce_b64);
+		g_free (salt);
+		g_free (client_key);
+		return SCRAM_ERROR;
+	}
+
+	// ClientSignature := HMAC(StoredKey, AuthMessage)
+	client_signature = g_malloc0 (session->digest_size);
+	HMAC (session->digest, stored_key, stored_key_len, (unsigned char *) session->auth_message,
+		  strlen ((char *) session->auth_message), client_signature, NULL);
+
+	// ClientProof := ClientKey XOR ClientSignature
+	client_proof = g_malloc0 (client_key_len);
+
+	for (i = 0; i < client_key_len; i++)
+	{
+		client_proof[i] = client_key[i] ^ client_signature[i];
+	}
+
+	client_proof_b64 = g_base64_encode ((guchar *) client_proof, client_key_len);
+
+	*output = g_strdup_printf ("%s,p=%s", client_final_message_without_proof, client_proof_b64);
+	*output_len = strlen (*output);
+
+	g_free (server_nonce_b64);
+	g_free (salt);
+	g_free (client_final_message_without_proof);
+	g_free (client_key);
+	g_free (client_signature);
+	g_free (client_proof);
+	g_free (client_proof_b64);
+
+	session->step++;
+	return SCRAM_IN_PROGRESS;
+}
+
+static scram_status
+process_server_final (scram_session *session, const char *data)
+{
+	char *verifier;
+	unsigned char *server_key, *server_signature;
+	unsigned int server_key_len = 0, server_signature_len = 0;
+	gsize verifier_len = 0;
+
+	if (strlen (data) < 3 || (data[0] != 'v' && data[1] != '='))
+	{
+		return SCRAM_ERROR;
+	}
+
+	verifier = g_strdup (data + 2);
+	g_base64_decode_inplace (verifier, &verifier_len);
+
+	// ServerKey := HMAC(SaltedPassword, "Server Key")
+	server_key = g_malloc0 (session->digest_size);
+	HMAC (session->digest, session->salted_password, session->digest_size,
+		  (unsigned char *) SERVER_KEY, strlen (SERVER_KEY), server_key, &server_key_len);
+
+	// ServerSignature := HMAC(ServerKey, AuthMessage)
+	server_signature = g_malloc0 (session->digest_size);
+	HMAC (session->digest, server_key, session->digest_size,
+		  (unsigned char *) session->auth_message, strlen ((char *) session->auth_message),
+		  server_signature, &server_signature_len);
+
+	if (verifier_len == server_signature_len &&
+		memcmp (verifier, server_signature, verifier_len) == 0)
+	{
+		g_free (verifier);
+		g_free (server_key);
+		g_free (server_signature);
+		return SCRAM_SUCCESS;
+	}
+	else
+	{
+		g_free (verifier);
+		g_free (server_key);
+		g_free (server_signature);
+		return SCRAM_ERROR;
+	}
+}
+
+scram_status
+scram_process (scram_session *session, const char *input, char **output, size_t *output_len)
+{
+	scram_status status;
+
+	switch (session->step)
+	{
+		case 0:
+			status = process_client_first (session, output, output_len);
+			break;
+		case 1:
+			status = process_server_first (session, input, output, output_len);
+			break;
+		case 2:
+			status = process_server_final (session, input);
+			break;
+		default:
+			*output = NULL;
+			*output_len = 0;
+			status = SCRAM_ERROR;
+			break;
+	}
+
+	return status;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/common/scram.h b/src/common/scram.h
new file mode 100644
index 00000000..ffe22037
--- /dev/null
+++ b/src/common/scram.h
@@ -0,0 +1,51 @@
+/* HexChat
+ * Copyright (C) 2023 Patrick Okraku
+ *
+ * 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_SCRAM_H
+#define HEXCHAT_SCRAM_H
+
+#include "config.h"
+#ifdef USE_OPENSSL
+#include <openssl/evp.h>
+
+typedef struct
+{
+	const EVP_MD *digest;
+	size_t digest_size;
+	char *username;
+	char *password;
+	char *client_nonce_b64;
+	char *client_first_message_bare;
+	unsigned char *salted_password;
+	char *auth_message;
+	char *error;
+	int step;
+} scram_session;
+
+typedef enum
+{
+	SCRAM_ERROR = 0,
+	SCRAM_IN_PROGRESS,
+	SCRAM_SUCCESS
+} scram_status;
+
+scram_session *scram_session_create (const char *digset, const char *username, const char *password);
+void scram_session_free (scram_session *session);
+scram_status scram_process (scram_session *session, const char *input, char **output, size_t *output_len);
+
+#endif
+#endif
\ No newline at end of file
diff --git a/src/common/server.c b/src/common/server.c
index 5c645eb5..c78ce900 100644
--- a/src/common/server.c
+++ b/src/common/server.c
@@ -61,10 +61,6 @@
 #include "ssl.h"
 #endif
 
-#ifdef USE_LIBPROXY
-#include <proxy.h>
-#endif
-
 #ifdef USE_OPENSSL
 /* local variables */
 static struct session *g_sess = NULL;
@@ -78,9 +74,15 @@ static void server_disconnect (session * sess, int sendquit, int err);
 static int server_cleanup (server * serv);
 static void server_connect (server *serv, char *hostname, int port, int no_login);
 
-#ifdef USE_LIBPROXY
-extern pxProxyFactory *libproxy_factory;
-#endif
+static void
+write_error (char *message, GError **error)
+{
+	if (error == NULL || *error == NULL) {
+		return;
+	}
+	g_printerr ("%s: %s\n", message, (*error)->message);
+	g_clear_error (error);
+}
 
 /* actually send to the socket. This might do a character translation or
    send via SSL. server/dcc both use this function. */
@@ -360,7 +362,7 @@ server_read (GIOChannel *source, GIOCondition condition, server *serv)
 				serv->linebuf[serv->pos] = lbuf[i];
 				if (serv->pos >= (sizeof (serv->linebuf) - 1))
 					fprintf (stderr,
-								"*** HEXCHAT WARNING: Buffer overflow - shit server!\n");
+								"*** HEXCHAT WARNING: Buffer overflow - non-compliant server!\n");
 				else
 					serv->pos++;
 			}
@@ -770,7 +772,7 @@ server_connect_success (server *serv)
 
 		/* it'll be a memory leak, if connection isn't terminated by
 		   server_cleanup() */
-		if ((err = _SSL_set_verify (serv->ctx, ssl_cb_verify, NULL)))
+		if ((err = _SSL_set_verify (serv->ctx, ssl_cb_verify)))
 		{
 			EMIT_SIGNAL (XP_TE_CONNFAIL, serv->server_session, err, NULL,
 							 NULL, NULL, 0);
@@ -1392,14 +1394,16 @@ server_child (server * serv)
 
 	if (!serv->dont_use_proxy) /* blocked in serverlist? */
 	{
-#ifdef USE_LIBPROXY
 		if (prefs.hex_net_proxy_type == 5)
 		{
 			char **proxy_list;
 			char *url, *proxy;
+			GProxyResolver *resolver;
+			GError *error = NULL;
 
+			resolver = g_proxy_resolver_get_default ();
 			url = g_strdup_printf ("irc://%s:%d", hostname, port);
-			proxy_list = px_proxy_factory_get_proxies (libproxy_factory, url);
+			proxy_list = g_proxy_resolver_lookup (resolver, url, NULL, &error);
 
 			if (proxy_list) {
 				/* can use only one */
@@ -1412,6 +1416,8 @@ server_child (server * serv)
 					proxy_type = 3;
 				else if (!strncmp (proxy, "socks", 5))
 					proxy_type = 2;
+			} else {
+				write_error ("Failed to lookup proxy", &error);
 			}
 
 			if (proxy_type) {
@@ -1426,7 +1432,7 @@ server_child (server * serv)
 			g_strfreev (proxy_list);
 			g_free (url);
 		}
-#endif
+
 		if (prefs.hex_net_proxy_host[0] &&
 			   prefs.hex_net_proxy_type > 0 &&
 			   prefs.hex_net_proxy_use != 2) /* proxy is NOT dcc-only */
@@ -1553,7 +1559,7 @@ server_connect (server *serv, char *hostname, int port, int no_login)
 	if (!hostname[0])
 		return;
 
-	if (port < 0)
+	if (port < 1 || port > 65535)
 	{
 		/* use default port for this server type */
 		port = 6667;
@@ -1561,8 +1567,8 @@ server_connect (server *serv, char *hostname, int port, int no_login)
 		if (serv->use_ssl)
 			port = 6697;
 #endif
+		g_debug ("Attempted to connect to invalid port, assuming default port %d", port);
 	}
-	port &= 0xffff;	/* wrap around */
 
 	if (serv->connected || serv->connecting || serv->recondelay_tag)
 		server_disconnect (sess, TRUE, -1);
@@ -1759,11 +1765,14 @@ server_set_defaults (server *serv)
 	g_free (serv->chanmodes);
 	g_free (serv->nick_prefixes);
 	g_free (serv->nick_modes);
-
+#ifdef USE_OPENSSL
+        g_clear_pointer (&serv->scram_session, scram_session_free);
+#endif
 	serv->chantypes = g_strdup ("#&!+");
 	serv->chanmodes = g_strdup ("beI,k,l");
 	serv->nick_prefixes = g_strdup ("@%+");
 	serv->nick_modes = g_strdup ("ohv");
+	serv->modes_per_line = 3; /* https://datatracker.ietf.org/doc/html/rfc1459#section-4.2.3.1 */
 	serv->sasl_mech = MECH_PLAIN;
 
 	if (!serv->encoding)
@@ -1772,6 +1781,7 @@ server_set_defaults (server *serv)
 	serv->nickcount = 1;
 	serv->end_of_motd = FALSE;
 	serv->sent_capend = FALSE;
+	serv->use_listargs = FALSE;
 	serv->is_away = FALSE;
 	serv->supports_watch = FALSE;
 	serv->supports_monitor = FALSE;
@@ -1929,6 +1939,8 @@ server_free (server *serv)
 #ifdef USE_OPENSSL
 	if (serv->ctx)
 		_SSL_context_free (serv->ctx);
+
+        g_clear_pointer (&serv->scram_session, scram_session_free);
 #endif
 
 	fe_server_callback (serv);
diff --git a/src/common/servlist.c b/src/common/servlist.c
index 79a5694b..1f29bb32 100644
--- a/src/common/servlist.c
+++ b/src/common/servlist.c
@@ -54,12 +54,6 @@ static const struct defaultserver def[] =
 	/* Invalid hostname in cert */
 	{0,			"irc.2600.net"},
 
-	{"AccessIRC",	0},
-	/* Self signed */
-	{0,			"irc.accessirc.net"},
-
-	{"ACN", 0, 0, 0, LOGIN_SASL, 0, TRUE},
-	{0,			"global.acn.gr"},
 
 	{"AfterNET", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.afternet.org"},
@@ -67,15 +61,11 @@ static const struct defaultserver def[] =
 	{"Aitvaras",	0},
 #ifdef USE_OPENSSL
 	{0,			"irc.data.lt/+6668"},
-	{0,			"irc.omnitel.net/+6668"},
-	{0,			"irc.ktu.lt/+6668"},
-	{0,			"irc.kis.lt/+6668"},
+	{0,			"irc.omicron.lt/+6668"},
 	{0,			"irc.vub.lt/+6668"},
 #endif
 	{0,			"irc.data.lt"},
-	{0,			"irc.omnitel.net"},
-	{0,			"irc.ktu.lt"},
-	{0,			"irc.kis.lt"},
+	{0,			"irc.omicron.lt"},
 	{0,			"irc.vub.lt"},
 
 	{"Anthrochat", 0, 0, 0, 0, 0, TRUE},
@@ -90,10 +80,6 @@ static const struct defaultserver def[] =
 	{"AzzurraNet",	0},
 	{0,			"irc.azzurra.org"},
 
-	{"BetaChat", 0, 0, 0, LOGIN_SASL},
-	{0,			"irc.betachat.net"},
-	{"BuddyIM", 0, 0, 0, LOGIN_SASL, 0, TRUE},
-	{0,			"irc.buddy.im"},
 	{"Canternet", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.canternet.org"},
 
@@ -103,19 +89,16 @@ static const struct defaultserver def[] =
 	{"ChatJunkies",	0},
 	{0,			"irc.chatjunkies.org"},
 
-	{"ChatNet",	0},
-	{0,			"irc.chatnet.org"},
+	{"chatpat", 0, 0, "CP1251", LOGIN_CUSTOM, "MSG NS IDENTIFY %p"},
+	{0,			"irc.unibg.net"},
+	{0,			"irc.chatpat.bg"},
 
 	{"ChatSpike", 0, 0, 0, LOGIN_SASL},
 	{0,			"irc.chatspike.net"},
 
-	{"ChattingAway", 0},
-	{0,			"irc.chattingaway.com"},
-
-	{"Criten", 0},
-	/* Self signed */
-	{0,			"irc.criten.net"},
-
+	{"DaIRC", 0},
+	{0,			"irc.dairc.net"},
+	
 	{"DALnet", 0, 0, 0, LOGIN_NICKSERV},
 	/* Self signed */
 	{0,			"us.dal.net"},
@@ -132,19 +115,20 @@ static const struct defaultserver def[] =
 
 	{"Dark-Tou-Net",	0},
 	{0,			"irc.d-t-net.de"},
-
-	{"DeltaAnime", 0},
-	{0,			"irc.deltaanime.net"},
+	
+	{"DigitalIRC", 0, 0, 0, LOGIN_SASL, 0, TRUE},
+	{0,			"irc.digitalirc.org"},
+	
+#ifdef USE_OPENSSL
+	{"DosersNET", 0, 0, 0, LOGIN_SASL, 0, TRUE},
+	{0,			"irc.dosers.net/+6697"},
+#endif
 
 	{"EFnet",	0},
 	{0,			"irc.choopa.net"},
-	{0,			"irc.paraphysics.net"},
 	{0,			"efnet.port80.se"},
 	{0,			"irc.underworld.no"},
-	{0,			"irc.inet.tele.dk"},
-
-	{"ElectroCode", 0, 0, 0, LOGIN_SASL, 0, TRUE},
-	{0,			"irc.electrocode.net"},
+	{0,			"efnet.deic.eu"},
 
 	{"EnterTheGame",	0},
 	{0,			"irc.enterthegame.com"},
@@ -166,20 +150,8 @@ static const struct defaultserver def[] =
 	/* Self signed */
 	{0,			"irc.fdfnet.net"},
 
-	{"freenode", 0, 0, 0, LOGIN_SASL, 0, TRUE},
-	{0,				"chat.freenode.net"},
-	/* irc. points to chat. but many users and urls still reference it */
-	{0,				"irc.freenode.net"},
-
-	{"GalaxyNet",	0},
-	{0,			"irc.galaxynet.org"},
-
 	{"GameSurge", 0},
 	{0,			"irc.gamesurge.net"},
-	
-	{"GeeksIRC", 0, 0, 0, LOGIN_SASL},
-	/* Self signed */
-	{0,			"irc.geeksirc.net"},
 
 	{"GeekShed", 0, 0, 0, 0, 0, TRUE},
 	{0,			"irc.geekshed.net"},
@@ -207,38 +179,31 @@ static const struct defaultserver def[] =
 	{"Hashmark",	0},
 	{0,			"irc.hashmark.net"},
 
-	{"IdleMonkeys", 0},
-	{0,			"irc.idlemonkeys.net"},
+	{"ICQ-Chat", 0, 0, 0, LOGIN_SASL, 0, TRUE},
+	{0,			"irc.icq-chat.com"},
 
-	{"IndirectIRC", 0, 0, 0, LOGIN_SASL},
-	/* Self signed */
-	{0,			"irc.indirectirc.com"},
-	
 	{"Interlinked", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.interlinked.me"},
 
+	{"Irc-Nerds", 0, 0, 0, LOGIN_SASL, 0, TRUE},
+	{0,			"irc.irc-nerds.net"},
+	
 	{"IRC4Fun", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,				"irc.irc4fun.net"},
 
-	{"IRCHighWay", 0, 0, 0, 0, 0, TRUE},
-	{0,				"irc.irchighway.net"},
-
 	{"IRCNet",		0},
 	{0,				"open.ircnet.net"},
 
-	{"Irctoo.net",	0},
+	{"IRCtoo",	0},
 	{0,			"irc.irctoo.net"},
 
-	{"iZ-smart.net", 0, 0, "CP1252"},
-	{0,			"irc.iz-smart.net"},
-
-	{"KBFail", 0},
+	{"Keyboard-Failure", 0},
 	/* SSL is self-signed */
 	{0,			"irc.kbfail.net"},
 
-	{"Krstarica", 0},
-	{0,			"irc.krstarica.com"},
-
+	{"Libera.Chat", 0, 0, 0, LOGIN_SASL, 0, TRUE},
+	{0,			"irc.libera.chat"},
+	
 #ifdef USE_OPENSSL
 	{"LibertaCasa", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.liberta.casa"},
@@ -248,9 +213,6 @@ static const struct defaultserver def[] =
 	/* Self signed */
 	{0,			"irc.librairc.net"},
 
-	{"Libera.Chat", 0, 0, 0, LOGIN_SASL, 0, TRUE},
-	{0,			"irc.libera.chat"},
-
 #ifdef USE_OPENSSL
 	{"LinkNet",	0},
 	{0,			"irc.link-net.org/+7000"},
@@ -262,10 +224,6 @@ static const struct defaultserver def[] =
 	{"MIXXnet",		0},
 	{0,			"irc.mixxnet.net"},
 
-	{"ObsidianIRC",  0},
-	/* Self signed */
-	{0,      "irc.obsidianirc.net"}, 
-
 	{"Oceanius", 0, 0, 0, LOGIN_SASL},
 	/* Self signed */
 	{0,			"irc.oceanius.com"},
@@ -276,16 +234,16 @@ static const struct defaultserver def[] =
 	{"OtherNet",	0},
 	{0,			"irc.othernet.org"},
 
-	{"OzNet",	0},
+	{"OzOrg",	0},
 	{0,			"irc.oz.org"},
 
-	{"PIRC.PL",	0, 0, 0, 0, 0, TRUE},
+	{"PIK", 0},
+	{0,			"irc.krstarica.com"},
+
+	{"pirc.pl",	0, 0, 0, 0, 0, TRUE},
 	{0,			"irc.pirc.pl"},
-	
-	{"PonyChat", 0, 0, 0, LOGIN_SASL, 0, TRUE},
-	{0,			"irc.ponychat.net"},
 
-	{"PTNet.org",	0},
+	{"PTNet",	0},
 	{0,			"irc.ptnet.org"},
 	{0,			"uevora.ptnet.org"},
 	{0,			"claranet.ptnet.org"},
@@ -306,12 +264,6 @@ static const struct defaultserver def[] =
 	{0,			"irc.ru"},
 	{0,			"irc.lucky.net"},
 
-	{"SceneNet",	0},
-	{0,			"irc.scene.org"},
-
-	{"SeilEn.de", 0, 0, "CP1252"},
-	{0,			"irc.seilen.de"},
-
 	{"Serenity-IRC",	0},
 	{0,			"irc.serenity-irc.net"},
 
@@ -328,10 +280,6 @@ static const struct defaultserver def[] =
 	{"Sohbet.Net", 0, 0, "CP1254"},
 	{0,			"irc.sohbet.net"},
 
-	{"SolidIRC", 0},
-	/* Self signed */
-	{0,			"irc.solidirc.com"},
-
 	{"SorceryNet", 0, 0, 0, LOGIN_SASL},
 	/* Self signed */
 	{0,			"irc.sorcery.net"},
@@ -339,9 +287,6 @@ static const struct defaultserver def[] =
 	{"SpotChat", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.spotchat.org"},
 
-	{"StarChat", 0},
-	{0,			"irc.starchat.net"},
-
 	{"Station51", 0},
 	/* Self signed */
 	{0,			"irc.station51.net"},
@@ -360,9 +305,18 @@ static const struct defaultserver def[] =
 	{"Techtronix",	0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.techtronix.net"},
 	
+	{"TechNet", 0, 0, 0, LOGIN_SASL, 0, TRUE},
+	{0,			"irc.technet.chat"},
+	
 	{"tilde.chat", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.tilde.chat"},
 
+	{"TURLINet", 0, 0, 0, 0, 0, TRUE},
+	/* all servers use UTF-8 and valid certs */
+	{0,			"irc.servx.org"},
+	{0,			"i.valware.uk"},
+	
+	
 #ifdef USE_OPENSSL
 	{"TripSit", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.tripsit.me"},
@@ -370,27 +324,12 @@ static const struct defaultserver def[] =
 	{0,			"coconut.tripsit.me"},
 	{0,			"innsbruck.tripsit.me"},
 #endif	
-	
-	{"TURLINet", 0, 0, 0, 0, 0, TRUE},
-	/* Other servers use CP1251 and invalid certs */
-	{0,			"irc.servx.ru"},
 
 	{"UnderNet", 0, 0, 0, LOGIN_CUSTOM, "MSG x@channels.undernet.org login %u %p"},
-	{0,			"us.undernet.org"},
-
-	{"UniBG", 0, 0, "CP1251", LOGIN_CUSTOM, "MSG NS IDENTIFY %p"},
-	{0,			"irc.lirex.com"},
-	{0,			"irc.naturella.com"},
-	{0,			"irc.techno-link.com"},
-
-	{"Worldnet",		0},
-	{0,			"irc.worldnet.net"},
+	{0,			"irc.undernet.org"},
 
 	{"Xertion", 0, 0, 0, LOGIN_SASL, 0, TRUE},
 	{0,			"irc.xertion.org"},
-	
-	{"DeltaPool", 0, 0, 0, LOGIN_SASL, 0, TRUE},
-	{0,			"irc.deltapool.net"},
 
 	{0,0}
 };
@@ -947,6 +886,9 @@ servlist_net_add (char *name, char *comment, int prepend)
 	net = g_new0 (ircnet, 1);
 	net->name = g_strdup (name);
 	net->flags = FLAG_CYCLE | FLAG_USE_GLOBAL | FLAG_USE_PROXY;
+#ifdef USE_OPENSSL
+	net->flags |= FLAG_USE_SSL;
+#endif
 
 	if (prepend)
 		network_list = g_slist_prepend (network_list, net);
diff --git a/src/common/servlist.h b/src/common/servlist.h
index ec885fef..c3d158b2 100644
--- a/src/common/servlist.h
+++ b/src/common/servlist.h
@@ -79,6 +79,9 @@ extern GSList *network_list;
 #define LOGIN_CHALLENGEAUTH		8
 #define LOGIN_CUSTOM			9
 #define LOGIN_SASLEXTERNAL		10
+#define LOGIN_SASL_SCRAM_SHA_1	11
+#define LOGIN_SASL_SCRAM_SHA_256	12
+#define LOGIN_SASL_SCRAM_SHA_512	13
 
 #define CHALLENGEAUTH_ALGO		"HMAC-SHA-256"
 #define CHALLENGEAUTH_NICK		"Q@CServe.quakenet.org"
diff --git a/src/common/ssl.c b/src/common/ssl.c
index 0eb78bd7..e7f7e0a8 100644
--- a/src/common/ssl.c
+++ b/src/common/ssl.c
@@ -321,23 +321,22 @@ _SSL_socket (SSL_CTX *ctx, int sd)
 
 
 char *
-_SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert)
+_SSL_set_verify (SSL_CTX *ctx, void *verify_callback)
 {
-	if (!SSL_CTX_set_default_verify_paths (ctx))
+#ifdef DEFAULT_CERT_FILE
+	if (!SSL_CTX_load_verify_locations (ctx, DEFAULT_CERT_FILE, NULL))
 	{
-		__SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths");
+		__SSL_fill_err_buf ("SSL_CTX_load_verify_locations");
 		return (err_buf);
 	}
-/*
-	if (cacert)
+#else
+	if (!SSL_CTX_set_default_verify_paths (ctx))
 	{
-		if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL))
-		{
-			__SSL_fill_err_buf ("SSL_CTX_load_verify_locations");
-			return (err_buf);
-		}
+		__SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths");
+		return (err_buf);
 	}
-*/
+#endif
+
 	SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback);
 
 	return (NULL);
diff --git a/src/common/ssl.h b/src/common/ssl.h
index e722f831..bea2f440 100644
--- a/src/common/ssl.h
+++ b/src/common/ssl.h
@@ -45,7 +45,7 @@ SSL_CTX *_SSL_context_init (void (*info_cb_func));
 #define _SSL_context_free(a)	SSL_CTX_free(a);
 
 SSL *_SSL_socket (SSL_CTX *ctx, int sd);
-char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert);
+char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback));
 /*
     int SSL_connect(SSL *);
     int SSL_accept(SSL *);
diff --git a/src/common/sysinfo/win32/backend.c b/src/common/sysinfo/win32/backend.c
index 1d88b139..e2ae83ed 100644
--- a/src/common/sysinfo/win32/backend.c
+++ b/src/common/sysinfo/win32/backend.c
@@ -84,7 +84,8 @@ sysinfo_get_cpu_arch (void)
 
 	GetNativeSystemInfo (&si);
 
-	if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
+	if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
+		si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64)
 	{
 		return cpu_arch = 64;
 	}
@@ -106,7 +107,8 @@ sysinfo_get_build_arch (void)
 
 	GetSystemInfo (&si);
 
-	if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)
+	if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
+		si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64)
 	{
 		return build_arch = 64;
 	}
@@ -354,6 +356,8 @@ static char *read_cpu_info (IWbemClassObject *object)
 
 	VariantClear (&max_clock_speed_variant);
 
+	g_strchomp (name_utf8);
+
 	if (cpu_freq_mhz > 1000)
 	{
 		result = g_strdup_printf ("%s (%.2fGHz)", name_utf8, cpu_freq_mhz / 1000.f);
diff --git a/src/common/text.c b/src/common/text.c
index b0a90e03..a77700fa 100644
--- a/src/common/text.c
+++ b/src/common/text.c
@@ -1512,6 +1512,17 @@ static char * const pevt_discon_help[] = {
 	N_("Error"),
 };
 
+static char * const pevt_stdrpl_help[] = {
+	N_("Error Code"),
+	N_("Error Message"),
+};
+
+static char * const pevt_stdrplcmd_help[] = {
+	N_("Command"),
+	N_("Error Code"),
+	N_("Error Message"),
+};
+
 #include "textevents.h"
 
 static void
diff --git a/src/common/textevents.in b/src/common/textevents.in
index 14bd4b06..19b0d497 100644
--- a/src/common/textevents.in
+++ b/src/common/textevents.in
@@ -436,6 +436,18 @@ pevt_discon_help
 %C20*%O$tDisconnected (%C20$1%O)
 1
 
+Fail
+XP_TE_FAIL
+pevt_stdrpl_help
+%C20*%O$t$2%O
+2
+
+Fail Command
+XP_TE_FAILCMD
+pevt_stdrplcmd_help
+%C20*%O$t$1: $3%O
+3
+
 Found IP
 XP_TE_FOUNDIP
 pevt_foundip_help
@@ -574,6 +586,18 @@ pevt_generic_none_help
 %C23*%O$tNo process is currently running
 0
 
+Note
+XP_TE_NOTE
+pevt_stdrpl_help
+%C22*%O$t$2%O
+2
+
+Note Command
+XP_TE_NOTECMD
+pevt_stdrplcmd_help
+%C22*%O$t$1: $3%O
+3
+
 Notice
 XP_TE_NOTICE
 pevt_notice_help
@@ -802,6 +826,18 @@ pevt_usersonchan_help
 %C22*%O$tUsers on %C22$1%C: %C24$2%O
 2
 
+Warn
+XP_TE_WARN
+pevt_stdrpl_help
+%C23*%O$t$2%O
+2
+
+Warn Command
+XP_TE_WARNCMD
+pevt_stdrplcmd_help
+%C23*%O$t$1: $3%O
+3
+
 WhoIs Authenticated
 XP_TE_WHOIS_AUTH
 pevt_whoisauth_help
diff --git a/src/common/url.c b/src/common/url.c
index 6a1d09e8..162c5706 100644
--- a/src/common/url.c
+++ b/src/common/url.c
@@ -331,7 +331,7 @@ url_check_line (char *buf)
 	GRegex *re(void);
 	GMatchInfo *gmi;
 	char *po = buf;
-	int i;
+	size_t i;
 
 	/* Skip over message prefix */
 	if (*po == ':')
@@ -536,6 +536,7 @@ struct
 	{ "lastfm",    "/", URI_PATH },
 	{ "xfire",     "",  URI_PATH },
 	{ "ts3server", "",  URI_PATH },
+	{ "web+*",      "", URI_AUTHORITY | URI_OPT_USERINFO | URI_PATH },
 	{ NULL,        "",  0}
 };
 
@@ -569,7 +570,21 @@ re_url (void)
 			g_string_append (grist_gstr, "|");
 
 		g_string_append (grist_gstr, "(");
-		g_string_append_printf (grist_gstr, "%s:", uri[i].scheme);
+
+		if (!strcmp(uri[i].scheme, "web+*"))
+		{
+			g_string_append(grist_gstr, "web\\+[a-z]+");
+		}
+		else
+		{
+			char *scheme_escaped = g_regex_escape_string (uri[i].scheme, strlen(uri[i].scheme));
+
+			g_string_append_printf (grist_gstr, "%s", scheme_escaped);
+
+			g_free (scheme_escaped);
+		}
+
+		g_string_append(grist_gstr, ":");
 
 		if (uri[i].flags & URI_AUTHORITY)
 			g_string_append (grist_gstr, "//");
diff --git a/src/common/util.c b/src/common/util.c
index 5b5fb23f..bd920cae 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -329,6 +329,7 @@ strip_color2 (const char *src, int len, char *dst, int flags)
 			case '\026':			  /*ATTR_REVERSE: */
 			case '\002':			  /*ATTR_BOLD: */
 			case '\037':			  /*ATTR_UNDERLINE: */
+			case '\036':			  /*ATTR_STRIKETHROUGH: */
 			case '\035':			  /*ATTR_ITALICS: */
 				if (!(flags & STRIP_ATTRIB)) goto pass_char;
 				break;
@@ -987,7 +988,7 @@ void
 country_search (char *pattern, void *ud, void (*print)(void *, char *, ...))
 {
 	const domain_t *dom;
-	int i;
+	size_t i;
 
 	for (i = 0; i < sizeof (domain) / sizeof (domain_t); i++)
 	{
@@ -1374,11 +1375,16 @@ str_sha256hash (char *string)
 	int i;
 	unsigned char hash[SHA256_DIGEST_LENGTH];
 	char buf[SHA256_DIGEST_LENGTH * 2 + 1];		/* 64 digit hash + '\0' */
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+	SHA256 (string, strlen (string), hash);
+#else
 	SHA256_CTX sha256;
 
 	SHA256_Init (&sha256);
 	SHA256_Update (&sha256, string, strlen (string));
 	SHA256_Final (hash, &sha256);
+#endif
 
 	for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
 	{