From bfddb78217ee191eac8874063e5e2bc89e1b17f9 Mon Sep 17 00:00:00 2001 From: Alexandre Oliveira Date: Wed, 20 Dec 2017 22:00:42 -0200 Subject: [PATCH] Add support for SASL PLAIN authentication --- ChatSharp/Handlers/CapabilityHandlers.cs | 10 +++++- ChatSharp/Handlers/MessageHandlers.cs | 9 +++++ ChatSharp/Handlers/SaslHandlers.cs | 45 ++++++++++++++++++++++++ ChatSharp/Handlers/UserHandlers.cs | 5 +++ ChatSharp/IrcClient.cs | 8 ++++- 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 ChatSharp/Handlers/SaslHandlers.cs diff --git a/ChatSharp/Handlers/CapabilityHandlers.cs b/ChatSharp/Handlers/CapabilityHandlers.cs index 54a7288..b24a2a1 100644 --- a/ChatSharp/Handlers/CapabilityHandlers.cs +++ b/ChatSharp/Handlers/CapabilityHandlers.cs @@ -42,11 +42,19 @@ namespace ChatSharp.Handlers // Get the accepted capabilities var acceptedCaps = message.Parameters[2].Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string acceptedCap in acceptedCaps) + { client.Capabilities.Enable(acceptedCap); + // Begin SASL authentication + if (acceptedCap == "sasl") + { + client.SendRawMessage("AUTHENTICATE PLAIN"); + client.IsAuthenticatingSasl = true; + } + } // Check if the enabled capabilities count is the same as the ones // acknowledged by the server. - if (client.IsNegotiatingCapabilities && client.Capabilities.Enabled.Count() == acceptedCaps.Count()) + if (client.IsNegotiatingCapabilities && client.Capabilities.Enabled.Count() == acceptedCaps.Count() && !client.IsAuthenticatingSasl) { client.SendRawMessage("CAP END"); client.IsNegotiatingCapabilities = false; diff --git a/ChatSharp/Handlers/MessageHandlers.cs b/ChatSharp/Handlers/MessageHandlers.cs index 0b08c0f..6f07f83 100644 --- a/ChatSharp/Handlers/MessageHandlers.cs +++ b/ChatSharp/Handlers/MessageHandlers.cs @@ -48,6 +48,7 @@ namespace ChatSharp.Handlers client.SetHandler("330", UserHandlers.HandleWhoIsLoggedInAs); client.SetHandler("352", UserHandlers.HandleWho); client.SetHandler("354", UserHandlers.HandleWhox); + client.SetHandler("900", UserHandlers.HandleLoggedIn); // ERR_LOGGEDIN client.SetHandler("ACCOUNT", UserHandlers.HandleAccount); client.SetHandler("CHGHOST", UserHandlers.HandleChangeHost); @@ -76,6 +77,14 @@ namespace ChatSharp.Handlers // Capability handlers client.SetHandler("CAP", CapabilityHandlers.HandleCapability); + + // SASL handlers + client.SetHandler("AUTHENTICATE", SaslHandlers.HandleAuthentication); + client.SetHandler("903", SaslHandlers.HandleError); // ERR_SASLSUCCESS + client.SetHandler("904", SaslHandlers.HandleError); // ERR_SASLFAIL + client.SetHandler("905", SaslHandlers.HandleError); // ERR_SASLTOOLONG + client.SetHandler("906", SaslHandlers.HandleError); // ERR_SASLABORTED + client.SetHandler("907", SaslHandlers.HandleError); // ERR_SASLALREADY } public static void HandleNick(IrcClient client, IrcMessage message) diff --git a/ChatSharp/Handlers/SaslHandlers.cs b/ChatSharp/Handlers/SaslHandlers.cs new file mode 100644 index 0000000..836f59f --- /dev/null +++ b/ChatSharp/Handlers/SaslHandlers.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; + +namespace ChatSharp.Handlers +{ + internal static class SaslHandlers + { + public static void HandleAuthentication(IrcClient client, IrcMessage message) + { + if (client.IsAuthenticatingSasl) + { + if (message.Parameters[0] == "+") + { + // Based off irc-framework implementation + var plainString = string.Format("{0}\0{0}\0{1}", client.User.Nick, client.User.Password); + var b64Bytes = Encoding.UTF8.GetBytes(Convert.ToBase64String(Encoding.UTF8.GetBytes(plainString))); + + while (b64Bytes.Length >= 400) + { + var chunk = b64Bytes.Take(400).ToArray(); + b64Bytes = b64Bytes.Skip(400).ToArray(); + client.SendRawMessage(string.Format("AUTHENTICATE {0}", Encoding.UTF8.GetString(chunk))); + } + if (b64Bytes.Length > 0) + client.SendRawMessage(string.Format("AUTHENTICATE {0}", Encoding.UTF8.GetString(b64Bytes))); + else + client.SendRawMessage("AUTHENTICATE +"); + + client.IsAuthenticatingSasl = false; + } + } + } + + public static void HandleError(IrcClient client, IrcMessage message) + { + if (client.IsNegotiatingCapabilities && !client.IsAuthenticatingSasl) + { + client.SendRawMessage("CAP END"); + client.IsNegotiatingCapabilities = false; + } + } + } +} diff --git a/ChatSharp/Handlers/UserHandlers.cs b/ChatSharp/Handlers/UserHandlers.cs index b79539c..84f9173 100644 --- a/ChatSharp/Handlers/UserHandlers.cs +++ b/ChatSharp/Handlers/UserHandlers.cs @@ -217,6 +217,11 @@ namespace ChatSharp.Handlers } } + public static void HandleLoggedIn(IrcClient client, IrcMessage message) + { + client.User.Account = message.Parameters[2]; + } + public static void HandleWhoEnd(IrcClient client, IrcMessage message) { if (client.ServerInfo.ExtendedWho) diff --git a/ChatSharp/IrcClient.cs b/ChatSharp/IrcClient.cs index 9bf4ab4..25bb954 100644 --- a/ChatSharp/IrcClient.cs +++ b/ChatSharp/IrcClient.cs @@ -138,6 +138,11 @@ namespace ChatSharp /// If set to False, capability negotiation is finished. /// public bool IsNegotiatingCapabilities { get; internal set; } + /// + /// Set to True when the client is authenticating with SASL. + /// If set to False, SASL authentication is finished. + /// + public bool IsAuthenticatingSasl { get; internal set; } /// /// Creates a new IRC client, but will not connect until ConnectAsync is called. @@ -169,10 +174,11 @@ namespace ChatSharp // List of supported capabilities Capabilities.AddRange(new string[] { "server-time", "multi-prefix", "cap-notify", "znc.in/server-time", "znc.in/server-time-iso", - "account-notify", "chghost", "userhost-in-names" + "account-notify", "chghost", "userhost-in-names", "sasl" }); IsNegotiatingCapabilities = false; + IsAuthenticatingSasl = false; RandomNumber = new Random(); }