summary refs log tree commit diff stats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/dcc.c9
-rw-r--r--src/common/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
22 files changed, 735 insertions, 166 deletions
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