summary refs log tree commit diff stats
path: root/src/common/inbound.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/inbound.c')
-rw-r--r--src/common/inbound.c184
1 files changed, 162 insertions, 22 deletions
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");
 }