From 0009ed69bb5e276f115f6cb4b51cd80658657117 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 9 May 2021 17:43:18 +0100 Subject: [PATCH 01/24] IRCD-59 - Refactor configuration to use IConfiguration and appsettings.json --- .gitignore | 3 + cmpctircd-tests | 2 +- cmpctircd/App.config | 91 ---- cmpctircd/Client/Client.cs | 7 +- cmpctircd/Configuration/AdvancedElement.cs | 36 -- cmpctircd/Configuration/CloakElement.cs | 29 -- .../CmpctConfigurationSection.cs | 95 ---- cmpctircd/Configuration/ListConverter.cs | 26 -- cmpctircd/Configuration/LoggerElement.cs | 47 +- .../Configuration/LoggerElementCollection.cs | 19 - cmpctircd/Configuration/ModeElement.cs | 25 +- .../Configuration/ModeElementCollection.cs | 19 - cmpctircd/Configuration/OperatorElement.cs | 46 +- .../OperatorElementCollection.cs | 28 -- cmpctircd/Configuration/ServerElement.cs | 75 +--- .../Configuration/ServerElementCollection.cs | 18 - cmpctircd/Configuration/SocketElement.cs | 80 ++-- .../Configuration/SocketElementCollection.cs | 19 - cmpctircd/Configuration/TlsElement.cs | 17 - cmpctircd/IRCd.cs | 419 ++++++++++-------- cmpctircd/IrcApplicationLifecycle.cs | 11 +- cmpctircd/PacketManager.cs | 3 +- cmpctircd/Program.cs | 12 +- cmpctircd/Server/Server.cs | 8 +- cmpctircd/SocketConnector.cs | 2 +- cmpctircd/SocketListener.cs | 4 +- cmpctircd/appsettings.json | 119 +++++ cmpctircd/cmpctircd.csproj | 6 +- 28 files changed, 477 insertions(+), 789 deletions(-) delete mode 100644 cmpctircd/App.config delete mode 100644 cmpctircd/Configuration/AdvancedElement.cs delete mode 100644 cmpctircd/Configuration/CloakElement.cs delete mode 100644 cmpctircd/Configuration/CmpctConfigurationSection.cs delete mode 100644 cmpctircd/Configuration/ListConverter.cs delete mode 100644 cmpctircd/Configuration/LoggerElementCollection.cs delete mode 100644 cmpctircd/Configuration/ModeElementCollection.cs delete mode 100644 cmpctircd/Configuration/OperatorElementCollection.cs delete mode 100644 cmpctircd/Configuration/ServerElementCollection.cs delete mode 100644 cmpctircd/Configuration/SocketElementCollection.cs delete mode 100644 cmpctircd/Configuration/TlsElement.cs create mode 100644 cmpctircd/appsettings.json diff --git a/.gitignore b/.gitignore index 3292aea..db8bee1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ obj/ bin/ .vs .vscode/* +/cmpctircd.sln.DotSettings.user +/cmpctircd.v3.ncrunchsolution +/_NCrunch_cmpctircd diff --git a/cmpctircd-tests b/cmpctircd-tests index 2e791c2..aef4a19 160000 --- a/cmpctircd-tests +++ b/cmpctircd-tests @@ -1 +1 @@ -Subproject commit 2e791c2df86ea152a478581a1567a6465dae8ac7 +Subproject commit aef4a1930fada6a3c93b5b38c6886ebf26c2f788 diff --git a/cmpctircd/App.config b/cmpctircd/App.config deleted file mode 100644 index 5bd659d..0000000 --- a/cmpctircd/App.config +++ /dev/null @@ -1,91 +0,0 @@ - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cmpctircd/Client/Client.cs b/cmpctircd/Client/Client.cs index 36a127a..a07e2e0 100755 --- a/cmpctircd/Client/Client.cs +++ b/cmpctircd/Client/Client.cs @@ -8,6 +8,7 @@ using cmpctircd.Modes; using System.Net; using System.IO; +using Microsoft.Extensions.Configuration; namespace cmpctircd { @@ -43,7 +44,7 @@ public IDictionary Modes { public string NickIfSet() => string.IsNullOrEmpty(Nick) ? "*" : Nick; public Client(IRCd ircd, TcpClient tc, SocketListener sl, Stream stream, string UID = null, Server OriginServer = null, bool RemoteClient = false) : base(ircd, tc, sl, stream) { - if(ircd.Config.Advanced.ResolveHostnames) + if(ircd.Config.GetValue("Advanced:ResolveHostnames")) ResolvingHost = true; this.UID = UID; @@ -94,7 +95,7 @@ public override void BeginTasks() { // Check for PING/PONG events due CheckTimeout(); - if(IRCd.Config.Advanced.ResolveHostnames) + if(IRCd.Config.GetValue("Advanced:ResolveHostnames")) Resolve(); } catch(Exception e) { IRCd.Log.Debug($"Failed to access client: {e.ToString()}"); @@ -169,7 +170,7 @@ public void Resolve() { IRCd.DNSCache[ip] = DNSHost; } - if (IRCd.Config.Advanced.ResolveHostnames) { + if (IRCd.Config.GetValue("Advanced:ResolveHostnames")) { // If the IRCd resolves all hostnames, then we will have // delayed calling SendWelcome until DNS resolution was complete SendWelcome(); diff --git a/cmpctircd/Configuration/AdvancedElement.cs b/cmpctircd/Configuration/AdvancedElement.cs deleted file mode 100644 index ef63a7d..0000000 --- a/cmpctircd/Configuration/AdvancedElement.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class AdvancedElement : ConfigurationElement { - [ConfigurationProperty("resolveHostnames", IsRequired = true)] - public bool ResolveHostnames { - get { return (bool) this["resolveHostnames"]; } - set { this["resolveHostnames"] = value; } - } - - [ConfigurationProperty("requirePongCookie", IsRequired = true)] - public bool RequirePongCookie { - get { return (bool) this["requirePongCookie"]; } - set { this["requirePongCookie"] = value; } - } - - [ConfigurationProperty("pingTimeout", IsRequired = true)] - public int PingTimeout { - get { return (int) this["pingTimeout"]; } - set { this["pingTimeout"] = value; } - } - - [ConfigurationProperty("maxTargets", IsRequired = true)] - public int MaxTargets { - get { return (int) this["maxTargets"]; } - set { this["maxTargets"] = value; } - } - - [ConfigurationProperty("cloak", IsRequired = true)] - public CloakElement Cloak { - get { - return this["cloak"] as CloakElement; - } - } - } -} diff --git a/cmpctircd/Configuration/CloakElement.cs b/cmpctircd/Configuration/CloakElement.cs deleted file mode 100644 index 48b06e9..0000000 --- a/cmpctircd/Configuration/CloakElement.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class CloakElement : ConfigurationElement { - [ConfigurationProperty("key", IsRequired = true)] - public string Key { - get { return (string) this["key"]; } - set { this["key"] = value; } - } - - [ConfigurationProperty("prefix", IsRequired = true)] - public string Prefix { - get { return (string)this["prefix"]; } - set { this["prefix"] = value; } - } - - [ConfigurationProperty("domainParts", IsRequired = true)] - public int DomainParts { - get { return (int) this["domainParts"]; } - set { this["domainParts"] = value; } - } - - [ConfigurationProperty("full", IsRequired = true)] - public bool Full { - get { return (bool) this["full"]; } - set { this["full"] = value; } - } - } -} diff --git a/cmpctircd/Configuration/CmpctConfigurationSection.cs b/cmpctircd/Configuration/CmpctConfigurationSection.cs deleted file mode 100644 index 0e56e39..0000000 --- a/cmpctircd/Configuration/CmpctConfigurationSection.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class CmpctConfigurationSection : ConfigurationSection { - public static CmpctConfigurationSection GetConfiguration() { - return (CmpctConfigurationSection) ConfigurationManager.GetSection("ircd") ?? new CmpctConfigurationSection(); - } - - [ConfigurationProperty("sid", DefaultValue = "auto", IsRequired = false)] - public string SID { - get { return (string) this["sid"]; } - set { this["sid"] = value; } - } - - [ConfigurationProperty("host", IsRequired = true)] - public string Host { - get { return (string) this["host"]; } - set { this["host"] = value; } - } - - [ConfigurationProperty("network", IsRequired = true)] - public string Network { - get { return (string) this["network"]; } - set { this["network"] = value; } - } - - [ConfigurationProperty("description", DefaultValue = "", IsRequired = false)] - public string Description { - get { return (string) this["description"]; } - set { this["description"] = value; } - } - - [ConfigurationProperty("sockets", IsRequired = true)] - [ConfigurationCollection(typeof(SocketElement), AddItemName = "socket")] - public SocketElementCollection Sockets { - get { - return this["sockets"] as SocketElementCollection; - } - } - - [ConfigurationProperty("tls", IsRequired = false, DefaultValue = null)] - public TlsElement Tls { - get { - return this["tls"] as TlsElement; - } - } - - [ConfigurationProperty("log", IsRequired = true)] - [ConfigurationCollection(typeof(LoggerElement), AddItemName = "logger")] - public LoggerElementCollection Loggers { - get { - return this["log"] as LoggerElementCollection; - } - } - - [ConfigurationProperty("advanced", IsRequired = true)] - public AdvancedElement Advanced { - get { - return this["advanced"] as AdvancedElement; - } - } - - [ConfigurationProperty("cmodes")] - [ConfigurationCollection(typeof(ModeElement), AddItemName = "mode")] - public ModeElementCollection AutomaticModes { - get { - return this["cmodes"] as ModeElementCollection; - } - } - - [ConfigurationProperty("umodes")] - [ConfigurationCollection(typeof(ModeElement), AddItemName = "mode")] - public ModeElementCollection AutomaticUserModes { - get { - return this["umodes"] as ModeElementCollection; - } - } - - [ConfigurationProperty("servers")] - [ConfigurationCollection(typeof(ModeElement), AddItemName = "server")] - public ServerElementCollection Servers { - get { - return this["servers"] as ServerElementCollection; - } - } - - [ConfigurationProperty("opers")] - [ConfigurationCollection(typeof(ModeElement), AddItemName = "oper")] - public OperatorElementCollection Operators { - get { - return this["opers"] as OperatorElementCollection; - } - } - } -} diff --git a/cmpctircd/Configuration/ListConverter.cs b/cmpctircd/Configuration/ListConverter.cs deleted file mode 100644 index cf4d89e..0000000 --- a/cmpctircd/Configuration/ListConverter.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; - -namespace cmpctircd.Configuration { - [TypeConverter(typeof(ListConverter))] - class ListConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { - return sourceType == typeof(string); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return value == null ? new List() : ((string) value).Split(' ').Where(s => !string.IsNullOrEmpty(s)).ToList(); - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { - return destinationType == typeof(string); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - return string.Join(" ", (IEnumerable) value); - } - } -} diff --git a/cmpctircd/Configuration/LoggerElement.cs b/cmpctircd/Configuration/LoggerElement.cs index 26dced2..6b54c53 100644 --- a/cmpctircd/Configuration/LoggerElement.cs +++ b/cmpctircd/Configuration/LoggerElement.cs @@ -1,36 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.Generic; using System.Configuration; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace cmpctircd.Configuration { - public class LoggerElement : ConfigurationElement { - private readonly Dictionary _attributes = new Dictionary(); +namespace cmpctircd.Configuration +{ + public class LoggerElement : ConfigurationElement + { + [JsonExtensionData] private Dictionary _attributes { get; set; } - // Guid, due to a lack of other unique properties for this element type. - public Guid InstanceGuid { - get; - } = Guid.NewGuid(); + public LoggerType Type { get; set; } + public LogType Level { get; set; } + public string Channel { get; set; } + public string Modes { get; set; } - public IReadOnlyDictionary Attributes { - get { return new ReadOnlyDictionary(_attributes); } - } - - [ConfigurationProperty("type", IsRequired = true)] - public LoggerType Type { - get { return (LoggerType) this["type"]; } - set { this["type"] = value; } - } - - [ConfigurationProperty("level", IsRequired = true)] - public LogType Level { - get { return (LogType) this["level"]; } - set { this["level"] = value; } - } - - protected override bool OnDeserializeUnrecognizedAttribute(string name, string value) { - _attributes.Add(name, value); - return true; + public Dictionary Attributes + { + get { return _attributes.ToDictionary(x => x.Key, x => x.Value.ToString()); } } } -} +} \ No newline at end of file diff --git a/cmpctircd/Configuration/LoggerElementCollection.cs b/cmpctircd/Configuration/LoggerElementCollection.cs deleted file mode 100644 index 626c147..0000000 --- a/cmpctircd/Configuration/LoggerElementCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class LoggerElementCollection : ConfigurationElementCollection { - public LoggerElement this[int i] { - get { - return (LoggerElement)BaseGet(i); - } - } - - protected override ConfigurationElement CreateNewElement() { - return new LoggerElement(); - } - - protected override object GetElementKey(ConfigurationElement element) { - return ((LoggerElement) element).InstanceGuid; - } - } -} diff --git a/cmpctircd/Configuration/ModeElement.cs b/cmpctircd/Configuration/ModeElement.cs index e36a6fe..899f8dc 100644 --- a/cmpctircd/Configuration/ModeElement.cs +++ b/cmpctircd/Configuration/ModeElement.cs @@ -1,17 +1,14 @@ -using System.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; -namespace cmpctircd.Configuration { - public class ModeElement : ConfigurationElement { - [ConfigurationProperty("name", IsRequired = true)] - public string Name { - get { return (string) this["name"]; } - set { this["name"] = value; } - } - - [ConfigurationProperty("param", IsRequired = false, DefaultValue = "")] - public string Param { - get { return (string) this["param"]; } - set { this["param"] = value; } - } +namespace cmpctircd.Configuration +{ + public class ModeElement + { + public string Name { get; set; } + public string Param { get; set; } } } diff --git a/cmpctircd/Configuration/ModeElementCollection.cs b/cmpctircd/Configuration/ModeElementCollection.cs deleted file mode 100644 index 0b49ccc..0000000 --- a/cmpctircd/Configuration/ModeElementCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class ModeElementCollection : ConfigurationElementCollection { - public ModeElement this[int i] { - get { - return (ModeElement) BaseGet(i); - } - } - - protected override ConfigurationElement CreateNewElement() { - return new ModeElement(); - } - - protected override object GetElementKey(ConfigurationElement element) { - return ((ModeElement) element).Name; - } - } -} diff --git a/cmpctircd/Configuration/OperatorElement.cs b/cmpctircd/Configuration/OperatorElement.cs index f5656c5..7330d4c 100644 --- a/cmpctircd/Configuration/OperatorElement.cs +++ b/cmpctircd/Configuration/OperatorElement.cs @@ -6,44 +6,26 @@ using System.ComponentModel; using System.Globalization; -namespace cmpctircd.Configuration { - public class OperatorElement : ConfigurationElement { - [ConfigurationProperty("name", IsRequired = true, IsKey = true)] - public string Name { - get { return (string)this["name"]; } - set { this["name"] = value; } - } +namespace cmpctircd.Configuration +{ + public class OperatorElement : ConfigurationElement + { + public string Name { get; set; } [TypeConverter(typeof(HexadecimalConverter))] - [ConfigurationProperty("password", IsRequired = true)] - public byte[] Password { - get { return (byte[]) this["password"]; } - set { this["password"] = value; } - } + public byte[] Password { get; set; } [TypeConverter(typeof(TypeNameConverter))] - [ConfigurationProperty("algorithm", IsRequired = true)] - public Type Algorithm { - get { return this["algorithm"] as Type; } - set { this["algorithm"] = value; } - } + public Type Algorithm { get; set; } - [ConfigurationProperty("tls", IsRequired = false, DefaultValue = false)] - public bool Tls { - get { return (bool) this["tls"]; } - set { this["tls"] = value; } - } + public bool Tls { get; set; } + public List Hosts { get; set; } - [TypeConverter(typeof(ListConverter))] - [ConfigurationProperty("hosts", IsRequired = true)] - public List Hosts { - get { return (List) this["hosts"]; } - set { this["hosts"] = value; } - } } [TypeConverter(typeof(HexadecimalConverter))] - class HexadecimalConverter : TypeConverter { + class HexadecimalConverter : TypeConverter + { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string); } @@ -51,7 +33,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { // From hex string to byte[] // https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array - var hex = (string) value; + var hex = (string)value; return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) @@ -64,7 +46,7 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { // From byte[] to hex string - return String.Concat(Array.ConvertAll((byte[]) value, x => x.ToString("X2"))); + return String.Concat(Array.ConvertAll((byte[])value, x => x.ToString("X2"))); } } -} +} \ No newline at end of file diff --git a/cmpctircd/Configuration/OperatorElementCollection.cs b/cmpctircd/Configuration/OperatorElementCollection.cs deleted file mode 100644 index eca4a59..0000000 --- a/cmpctircd/Configuration/OperatorElementCollection.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; - -namespace cmpctircd.Configuration { - public class OperatorElementCollection : ConfigurationElementCollection { - public OperatorElement this[int i] { - get { - return (OperatorElement) BaseGet(i); - } - } - - protected override ConfigurationElement CreateNewElement() { - return new OperatorElement(); - } - - protected override object GetElementKey(ConfigurationElement element) { - return ((OperatorElement) element).Name; - } - - [TypeConverter(typeof(ListConverter))] - [ConfigurationProperty("channels", IsRequired = false)] - public IList Channels { - get { return (List) this["channels"]; } - set { this["channels"] = value; } - } - } -} diff --git a/cmpctircd/Configuration/ServerElement.cs b/cmpctircd/Configuration/ServerElement.cs index 450b64d..cfa851b 100644 --- a/cmpctircd/Configuration/ServerElement.cs +++ b/cmpctircd/Configuration/ServerElement.cs @@ -1,64 +1,17 @@ using System.Collections.Generic; -using System.ComponentModel; -using System.Configuration; -namespace cmpctircd.Configuration { - public class ServerElement : ConfigurationElement { - [ConfigurationProperty("type", IsRequired = true)] - public ServerType Type { - get { return (ServerType) this["type"]; } - set { this["type"] = value; } - } - - [ConfigurationProperty("host", IsRequired = true)] - public string Host { - get { return (string) this["host"]; } - set { this["host"] = value; } - } - - [TypeConverter(typeof(ListConverter))] - [ConfigurationProperty("masks", IsRequired = true)] - public List Masks { - get { return ((List) this["masks"]); } - set { this["masks"] = value; } - } - - [ConfigurationProperty("port", IsRequired = true)] - [IntegerValidator(MinValue = 0, MaxValue = 65535, ExcludeRange = false)] - public int Port { - get { return (int) this["port"]; } - set { this["port"] = value; } - } - - [ConfigurationProperty("password", IsRequired = true)] - public string Password { - get { return (string) this["password"]; } - set { this["password"] = value; } - } - - [ConfigurationProperty("tls", IsRequired = false, DefaultValue = false)] - public bool IsTls { - get { return (bool) this["tls"]; } - set { this["tls"] = value; } - } - - // Outbound server - [ConfigurationProperty("outbound", IsRequired = false, DefaultValue = false)] - public bool IsOutbound { - get { return (bool) this["outbound"]; } - set { this["outbound"] = value; } - } - - [ConfigurationProperty("destination", IsRequired = false)] - public string Destination { - get { return (string) this["destination"]; } - set { this["destination"] = value; } - } - - [ConfigurationProperty("verify", IsRequired = false, DefaultValue = true)] - public bool VerifyTlsCert { - get { return (bool) this["verify"]; } - set { this["verify"] = value; } - } +namespace cmpctircd.Configuration +{ + public class ServerElement + { + public ServerType Type { get; set; } + public string Host { get; set; } + public List Masks { get; set; } + public int Port { get; set; } + public string Password { get; set; } + public bool Tls { get; set; } + public bool Outbound { get; set; } + public string Destination { get; set; } + public bool VerifyTlsCert { get; set; } } -} +} \ No newline at end of file diff --git a/cmpctircd/Configuration/ServerElementCollection.cs b/cmpctircd/Configuration/ServerElementCollection.cs deleted file mode 100644 index 6ff0ecf..0000000 --- a/cmpctircd/Configuration/ServerElementCollection.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class ServerElementCollection : ConfigurationElementCollection { - public ServerElement this[int i] { - get { return (ServerElement) BaseGet(i); } - } - - protected override ConfigurationElement CreateNewElement() { - return new ServerElement(); - } - - protected override object GetElementKey(ConfigurationElement element) { - ServerElement se = (ServerElement) element; - return se.Host + ":" + se.Port; - } - } -} diff --git a/cmpctircd/Configuration/SocketElement.cs b/cmpctircd/Configuration/SocketElement.cs index b86b16d..211d233 100644 --- a/cmpctircd/Configuration/SocketElement.cs +++ b/cmpctircd/Configuration/SocketElement.cs @@ -1,59 +1,33 @@ using System; using System.ComponentModel; -using System.Configuration; using System.Globalization; using System.Net; - -namespace cmpctircd.Configuration { - public class SocketElement : ConfigurationElement { - [ConfigurationProperty("type", IsRequired = true)] - public ListenerType Type { - get { return (ListenerType) this["type"]; } - set { this["type"] = value; } - } - - [TypeConverter(typeof(IPAddressConverter))] - [ConfigurationProperty("host", IsRequired = true)] - public IPAddress Host { - get { return (IPAddress) this["host"]; } - set { this["host"] = value; } - } - - [ConfigurationProperty("port", IsRequired = true)] - [IntegerValidator(MinValue = 0, MaxValue = 65535, ExcludeRange = false)] - public int Port { - get { return (int) this["port"]; } - set { this["port"] = value; } - } - - public IPEndPoint EndPoint { - get { return new IPEndPoint(Host, Port); } - } - - [ConfigurationProperty("tls", IsRequired = false, DefaultValue = false)] - public bool IsTls { - get { return (bool) this["tls"]; } - set { this["tls"] = value; } - } - - [ConfigurationProperty("protocol", IsRequired = false)] - public ServerType Protocol { - get { return (ServerType) this["protocol"]; } - set { this["protocol"] = value; } - } - - public static implicit operator SocketElement(ServerElement serverElement) { +using System.Reflection; + +namespace cmpctircd.Configuration +{ + public class SocketElement + { + public ListenerType Type { get; set; } + public IPAddress Host { get; set; } + public int Port { get; set; } + + public bool Tls { get; set; } + public ServerType Protocol { get; set; } + public IPEndPoint EndPoint => new IPEndPoint(Host, Port); + + public static implicit operator SocketElement(ServerElement serverElement) + { var se = new SocketElement(); // Check if this is a DNS name // If it is, resolve it - if (!IPAddress.TryParse(serverElement.Destination, out var IP)) { + if (!IPAddress.TryParse(serverElement.Destination, out var IP)) IP = Dns.GetHostEntry(serverElement.Destination).AddressList[0]; - } se.Host = IP; se.Port = serverElement.Port; - se.IsTls = serverElement.IsTls; + se.Tls = serverElement.Tls; se.Type = ListenerType.Server; return se; @@ -61,21 +35,27 @@ public static implicit operator SocketElement(ServerElement serverElement) { } [TypeConverter(typeof(IPAddressConverter))] - class IPAddressConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { + internal class IPAddressConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { return IPAddress.Parse((string) value); } - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { return destinationType == typeof(string); } - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, + Type destinationType) + { return ((IPAddress) value).ToString(); } } -} +} \ No newline at end of file diff --git a/cmpctircd/Configuration/SocketElementCollection.cs b/cmpctircd/Configuration/SocketElementCollection.cs deleted file mode 100644 index 88fbbac..0000000 --- a/cmpctircd/Configuration/SocketElementCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class SocketElementCollection : ConfigurationElementCollection { - public SocketElement this[int i] { - get { - return (SocketElement) BaseGet(i); - } - } - - protected override ConfigurationElement CreateNewElement() { - return new SocketElement(); - } - - protected override object GetElementKey(ConfigurationElement element) { - return ((SocketElement) element).EndPoint; - } - } -} diff --git a/cmpctircd/Configuration/TlsElement.cs b/cmpctircd/Configuration/TlsElement.cs deleted file mode 100644 index 72b8247..0000000 --- a/cmpctircd/Configuration/TlsElement.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Configuration; - -namespace cmpctircd.Configuration { - public class TlsElement : ConfigurationElement { - [ConfigurationProperty("file", IsRequired = true)] - public string File { - get { return (string) this["file"]; } - set { this["file"] = value; } - } - - [ConfigurationProperty("password", IsRequired = false, DefaultValue = "")] - public string Password { - get { return (string) this["password"]; } - set { this["password"] = value; } - } - } -} diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index 41f2bd6..bc4873a 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -1,16 +1,59 @@ -namespace cmpctircd { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Timers; +using cmpctircd.Configuration; +using cmpctircd.Modes; +using Microsoft.Extensions.Configuration; + +namespace cmpctircd +{ + public class IRCd + { + public const string Version = "0.2.1-dev"; + public static char[] lastUID = { }; + public readonly IList Connectors = new List(); + private readonly IList Listeners = new List(); - using cmpctircd.Configuration; - using cmpctircd.Modes; + public IRCd(Log log, IConfiguration config, IServiceProvider services) + { + Log = log; + Config = config; + + // Interpret the ConfigData + SID = config.GetValue("SID"); + Host = config.GetValue("Host"); + Desc = config.GetValue("Description"); + Network = config.GetValue("Network"); + + if (SID == "auto") SID = GenerateSID(Host, Desc); + + PingTimeout = config.GetValue("Advanced:PingTimeout"); + RequirePong = config.GetValue("Advanced:RequirePongCookie"); + + Loggers = config.GetSection("Logging:Loggers").Get>(); + + MaxTargets = config.GetValue("Advanced:MaxTargets"); + CloakKey = config.GetValue("Advanced:Cloak:Key"); + CloakFull = config.GetValue("Advanced:Cloak:Full"); + CloakPrefix = config.GetValue("Advanced:Cloak:Prefix"); + CloakDomainParts = config.GetValue("Advanced:Cloak:DomainParts"); + AutoModes = config.GetSection("Cmodes").Get>().ToDictionary(m => m.Name, m => m.Param); + AutoUModes = config.GetSection("Umodes").Get>().ToDictionary(m => m.Name, m => m.Param); + Opers = config.GetSection("Opers").Get>(); + OperChan = config.GetSection("OperChan").Get>(); + + PacketManager = new PacketManager(this, services); + ChannelManager = new ChannelManager(this); + + // Create certificate refresh + if (config.GetValue("Tls") != null) + Certificate = + new AutomaticCertificateCacheRefresh(new FileInfo(config.GetValue("Tls:File")), password: config.GetValue("Tls:Password")); + } - public class IRCd { - private readonly IList Listeners = new List(); - public readonly IList Connectors = new List(); public PacketManager PacketManager { get; } public ChannelManager ChannelManager { get; } public IList> ClientLists { get; } = new List>(); @@ -20,12 +63,11 @@ public class IRCd { private IList UserModes { get; set; } public Log Log { get; } - public CmpctConfigurationSection Config { get; } + public IConfiguration Config { get; } public string SID { get; } public string Host { get; } public string Desc { get; } public string Network { get; } - public const string Version = "0.2.1-dev"; public int MaxTargets { get; } public int MaxSeen { get; set; } = 0; public bool RequirePong { get; } @@ -42,7 +84,6 @@ public class IRCd { public IList Opers { get; } public IList OperChan { get; } public DateTime CreateTime { get; private set; } - public static char[] lastUID = new char[] { }; public AutomaticFileCacheRefresh MOTD { get; } = new AutomaticFileCacheRefresh(new FileInfo("ircd.motd")); public AutomaticFileCacheRefresh Rules { get; } = new AutomaticFileCacheRefresh(new FileInfo("ircd.rules")); @@ -50,243 +91,236 @@ public class IRCd { public List Clients => ClientLists.SelectMany(clientList => clientList).ToList(); public List Servers => ServerLists.SelectMany(serverList => serverList).ToList(); - public IRCd(Log log, CmpctConfigurationSection config, IServiceProvider services) { - this.Log = log; - this.Config = config; - - // Interpret the ConfigData - SID = config.SID; - Host = config.Host; - Desc = config.Description; - Network = config.Network; - - if (SID == "auto") { - SID = IRCd.GenerateSID(Host, Desc); - } - PingTimeout = config.Advanced.PingTimeout; - RequirePong = config.Advanced.RequirePongCookie; - - Loggers = config.Loggers.OfType().ToList(); - - MaxTargets = config.Advanced.MaxTargets; - CloakKey = config.Advanced.Cloak.Key; - CloakFull = config.Advanced.Cloak.Full; - CloakPrefix = config.Advanced.Cloak.Prefix; - CloakDomainParts = config.Advanced.Cloak.DomainParts; - AutoModes = config.AutomaticModes.OfType().ToDictionary(m => m.Name, m => m.Param); - AutoUModes = config.AutomaticUserModes.OfType().ToDictionary(m => m.Name, m => m.Param); - Opers = config.Operators.OfType().ToList(); - OperChan = config.Operators.Channels; - - PacketManager = new PacketManager(this, services); - ChannelManager = new ChannelManager(this); - - // Create certificate refresh - if(config.Tls != null) - Certificate = new AutomaticCertificateCacheRefresh(new FileInfo(config.Tls.File), password: Config.Tls.Password); - } - - public void Run() { + public void Run() + { Log.Info($"==> Starting cmpctircd-{Version}"); - if(Version.Contains("-dev")) { + if (Version.Contains("-dev")) + { Log.Info("===> You are running a development version of cmpctircd.NET."); Log.Info("===> If you are having problems, consider reverting to a stable version."); - Log.Info("===> Please report any bugs or feedback to the developers via the bugtracker at https://bugs.cmpct.info/"); + Log.Info( + "===> Please report any bugs or feedback to the developers via the bugtracker at https://bugs.cmpct.info/"); } + Log.Info($"==> Host: {Host}"); PacketManager.Load(); - foreach(var listener in Config.Sockets.OfType()) { - SocketListener sl = new SocketListener(this, listener); - Log.Info($"==> Listening on: {listener.Host}:{listener.Port} ({listener.Type}) ({(listener.IsTls ? "TLS" : "Plain" )})"); + foreach (var listener in Config.GetSection("Sockets").Get>()) + { + var sl = new SocketListener(this, listener); + Log.Info( + $"==> Listening on: {listener.Host}:{listener.Port} ({listener.Type}) ({(listener.Tls ? "TLS" : "Plain")})"); Listeners.Add(sl); sl.Bind(); } - foreach (var server in Config.Servers.OfType()) { - if (server.IsOutbound) { + foreach (var server in Config.GetSection("Servers").Get>()) + if (server.Outbound) + { // tag with outbound="true" // We want to connect out to this server, not have them connect to us var sc = new SocketConnector(this, server); - Log.Info($"==> Connecting to: {server.Destination}:{server.Port} ({server.Host}) ({(server.IsTls ? "TLS" : "Plain" )})"); + Log.Info( + $"==> Connecting to: {server.Destination}:{server.Port} ({server.Host}) ({(server.Tls ? "TLS" : "Plain")})"); Connectors.Add(sc); sc.Connect(); } - } // Set create time CreateTime = DateTime.UtcNow; - try { + try + { // HACK: You can't use await in async Listeners.ForEach(listener => listener.ListenToClients()); - } catch { + } + catch + { Log.Error("Got an exception: shutting down all listeners"); Listeners.ForEach(listener => listener.Stop()); } - if(Log.ShouldLogLevel(LogType.Debug)) { - System.Timers.Timer statTimer = new System.Timers.Timer(); + if (Log.ShouldLogLevel(LogType.Debug)) + { + var statTimer = new Timer(); // Run the timer every 5 minutes statTimer.Interval = TimeSpan.FromMinutes(5).TotalMilliseconds; - statTimer.Elapsed += delegate { + statTimer.Elapsed += delegate + { // Create a report on how each of the listeners is preforming (ratio of authenticated clients) // Should let us see if there's some problem with a specific listener - or in general with the handshake Log.Debug($"Since {CreateTime}, the following activity has occurred:"); - foreach(var listener in this.Listeners) { - if(listener.ClientCount == 0) continue; + foreach (var listener in Listeners) + { + if (listener.ClientCount == 0) continue; - var authRatio = decimal.Round(((decimal) listener.AuthClientCount / (decimal) listener.ClientCount) * 100); + var authRatio = decimal.Round(listener.AuthClientCount / (decimal) listener.ClientCount * 100); var unauthCount = listener.ClientCount - listener.AuthClientCount; - var unauthRatio = decimal.Round(((decimal) unauthCount / (decimal) listener.ClientCount) * 100); - var prefixLine = $"[{listener.Info.Host}:{listener.Info.Port} ({listener.Info.Type}) ({(listener.Info.IsTls ? "SSL/TLS" : "Plain" )})]"; + var unauthRatio = decimal.Round(unauthCount / (decimal) listener.ClientCount * 100); + var prefixLine = + $"[{listener.Info.Host}:{listener.Info.Port} ({listener.Info.Type}) ({(listener.Info.Tls ? "SSL/TLS" : "Plain")})]"; - Log.Debug($"==> {prefixLine} Authed: {listener.AuthClientCount} ({authRatio}%). Unauthed: {unauthCount} ({unauthRatio}%). Total: {listener.ClientCount}."); + Log.Debug( + $"==> {prefixLine} Authed: {listener.AuthClientCount} ({authRatio}%). Unauthed: {unauthCount} ({unauthRatio}%). Total: {listener.ClientCount}."); } }; statTimer.Start(); } } - public void Stop() { + public void Stop() + { // TODO: Do other things? Listeners.ForEach(listener => listener.Stop()); } - public void WriteToAllServers(string message, List except = null) { - foreach(List servers in ServerLists) { - foreach(Server server in servers) { - if(except != null && except.Contains(server)) { - // Skip a specified server - continue; - } - server.Write(message + "\r\n"); - } + public void WriteToAllServers(string message, List except = null) + { + foreach (List servers in ServerLists) + foreach (var server in servers) + { + if (except != null && except.Contains(server)) // Skip a specified server + continue; + server.Write(message + "\r\n"); } } - public Client GetClientByNick(String nick) { - foreach(var client in Clients) { + public Client GetClientByNick(string nick) + { + foreach (var client in Clients) + { // User may not have a nick yet - if (String.IsNullOrEmpty(client.Nick)) continue; + if (string.IsNullOrEmpty(client.Nick)) continue; // Check if user has the nick we're looking for - if (client.Nick.Equals(nick, StringComparison.OrdinalIgnoreCase)) { - return client; - } + if (client.Nick.Equals(nick, StringComparison.OrdinalIgnoreCase)) return client; } + throw new InvalidOperationException("No such user exists"); } - public Client GetClientByUUID(String UUID) { - try { + public Client GetClientByUUID(string UUID) + { + try + { return Clients.Single(client => client.UUID == UUID); - } catch(InvalidOperationException) { + } + catch (InvalidOperationException) + { throw new InvalidOperationException("No such user exists"); } } - public Server GetServerBySID(String SID) { - foreach (var serverList in ServerLists) { - try { + public Server GetServerBySID(string SID) + { + foreach (var serverList in ServerLists) + try + { return serverList.Single(server => server.SID == SID); - } catch (Exception) { } - } + } + catch (Exception) + { + } + throw new InvalidOperationException("No such server exists"); } - public IDictionary GetSupportedModes(bool requireSymbols) { - if(ModeDict != null && ModeDict.Any()) { - // Caching because this is still a relatively expensive operation to perform on each connection + public IDictionary GetSupportedModes(bool requireSymbols) + { + if (ModeDict != null && ModeDict.Any() + ) // Caching because this is still a relatively expensive operation to perform on each connection // (GetSupportedModesByType() is likely far more expensive given it uses reflection) // This is called by SendWelcome() to provide RPL_ISUPPORT return ModeDict; - } ModeDict = new Dictionary(); var chan = new Channel(ChannelManager, this); - foreach(var modeList in ModeTypes) { - foreach(var mode in modeList.Value) { - var modeObject = chan.Modes[mode]; - - // TODO: Are two different caches needed? - if(requireSymbols && String.IsNullOrEmpty(modeObject.Symbol)) continue; - ModeDict.Add(modeObject.Character, modeObject.Symbol); - } + foreach (var modeList in ModeTypes) + foreach (var mode in modeList.Value) + { + var modeObject = chan.Modes[mode]; + + // TODO: Are two different caches needed? + if (requireSymbols && string.IsNullOrEmpty(modeObject.Symbol)) continue; + ModeDict.Add(modeObject.Character, modeObject.Symbol); } - var modeCharacters = String.Join("", ModeDict.Select(p => p.Key)); - var modeSymbols = String.Join("", ModeDict.Select(p => p.Value)); + var modeCharacters = string.Join("", ModeDict.Select(p => p.Key)); + var modeSymbols = string.Join("", ModeDict.Select(p => p.Value)); ModeDict.Add("Characters", modeCharacters); ModeDict.Add("Symbols", modeSymbols); return ModeDict; } - public IList GetSupportedUModes(Client client) { - if(UserModes != null && UserModes.Any()) { - // Caching because reflection is an expensive operation to perform on each connection + public IList GetSupportedUModes(Client client) + { + if (UserModes != null && UserModes.Any() + ) // Caching because reflection is an expensive operation to perform on each connection // This is called by SendWelcome() to provide RPL_MYINFO return UserModes; - } UserModes = new List(); - string[] badClasses = { "Mode", "ModeType" }; + string[] badClasses = {"Mode", "ModeType"}; var classes = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(t => t.GetTypes()) - .Where( - t => t.IsClass && - t.Namespace == "cmpctircd.Modes" && - t.BaseType.Equals(typeof(UserMode)) && - !badClasses.Contains(t.Name) - ); - - foreach(Type className in classes) { - UserMode modeInstance = (UserMode) Activator.CreateInstance(Type.GetType(className.ToString()), client); + .SelectMany(t => t.GetTypes()) + .Where( + t => t.IsClass && + t.Namespace == "cmpctircd.Modes" && + t.BaseType.Equals(typeof(UserMode)) && + !badClasses.Contains(t.Name) + ); + + foreach (var className in classes) + { + var modeInstance = (UserMode) Activator.CreateInstance(Type.GetType(className.ToString()), client); UserModes.Add(modeInstance.Character); } return UserModes; } - public IDictionary> GetSupportedModesByType() { - if(ModeTypes != null && ModeTypes.Any()) { - // Caching to only generate this list once - reflection is expensive + public IDictionary> GetSupportedModesByType() + { + if (ModeTypes != null && ModeTypes.Any() + ) // Caching to only generate this list once - reflection is expensive return ModeTypes; - } ModeTypes = new Dictionary>(); // http://www.irc.org/tech_docs/005.html - List typeA = new List(); - List typeB = new List(); - List typeC = new List(); - List typeD = new List(); - List typeNone = new List(); + var typeA = new List(); + var typeB = new List(); + var typeC = new List(); + var typeD = new List(); + var typeNone = new List(); - string[] badClasses = { "Mode", "ModeType" }; + string[] badClasses = {"Mode", "ModeType"}; var classes = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(t => t.GetTypes()) - .Where( - t => t.IsClass && - t.Namespace == "cmpctircd.Modes" && - t.BaseType.Equals(typeof(ChannelMode)) && - !badClasses.Contains(t.Name) - ); - - foreach(Type className in classes) { - ChannelMode modeInstance = (ChannelMode) Activator.CreateInstance(Type.GetType(className.ToString()), new Channel(ChannelManager, this)); - ChannelModeType type = modeInstance.Type; - string modeChar = modeInstance.Character; - - switch(type) { + .SelectMany(t => t.GetTypes()) + .Where( + t => t.IsClass && + t.Namespace == "cmpctircd.Modes" && + t.BaseType.Equals(typeof(ChannelMode)) && + !badClasses.Contains(t.Name) + ); + + foreach (var className in classes) + { + var modeInstance = (ChannelMode) Activator.CreateInstance(Type.GetType(className.ToString()), + new Channel(ChannelManager, this)); + var type = modeInstance.Type; + var modeChar = modeInstance.Character; + + switch (type) + { case ChannelModeType.A: typeA.Add(modeChar); break; @@ -316,68 +350,76 @@ public IDictionary> GetSupportedModesByType() { // UID <-> Nick translation helpers - public string GenerateUID() { + public string GenerateUID() + { var UID = new char[6]; var highestUid = 6 * 90; // Sum of 6(Z) var aCharacter = Convert.ToChar(65); // A var zCharacter = Convert.ToChar(90); // Z - if (new String(lastUID) == "" || UID.Sum(character => Convert.ToInt32(character)) == highestUid) { - // We're at the start or we've hit the maximum possible ID (ZZZZZZ) + if (new string(lastUID) == "" || UID.Sum(character => Convert.ToInt32(character)) == highestUid + ) // We're at the start or we've hit the maximum possible ID (ZZZZZZ) // Start (again)... - UID = new char[] { 'A', 'A', 'A', 'A', 'A', 'A' }; - } else { - for (int i = UID.Length - 1; i >= 0; i--) { + UID = new[] {'A', 'A', 'A', 'A', 'A', 'A'}; + else + for (var i = UID.Length - 1; i >= 0; i--) + { // We need to increment every index, starting at UID[5] (6) until it reaches Z // Once it reaches Z (this will depend on subsequent UIDs), hop to the next column and repeat // If this column of the old UID was a Z, don't increment it // Copy it over and work on the next column - if (lastUID[i] == zCharacter) { + if (lastUID[i] == zCharacter) + { UID[i] = lastUID[i]; continue; } // Add one to the column if we're at the start - if (i == UID.Length - 1) { + if (i == UID.Length - 1) + { UID[i] = Convert.ToChar(lastUID[i] + 1); - } else if(lastUID[i + 1] == zCharacter) { + } + else if (lastUID[i + 1] == zCharacter) + { // Add one to the column if the previous column is Z UID[i] = Convert.ToChar(lastUID[i] + 1); // Once we've added one to THIS column, reset the one over to an A - if (lastUID[i + 1] == zCharacter) { - // If the next character over is a Z, change it to an A when we bump the next column + if (lastUID[i + 1] == zCharacter + ) // If the next character over is a Z, change it to an A when we bump the next column // e.g. if lastUID is AAAAAZ, make next AAAABA UID[i + 1] = Convert.ToChar(aCharacter); - } - } else { + } + else + { // Otherwise just copy that value // e.g. with AAAAAB -> AAAAAC, only the B -> C has changed, so rest can be copied UID[i] = lastUID[i]; } } - } // Don't allow this UID to be generated again... lastUID = UID; - var string_UID = new String(UID); + var string_UID = new string(UID); Log.Debug($"Generated a UID: {string_UID}"); return string_UID; - } - public bool IsUUID(string message) { + public bool IsUUID(string message) + { return Regex.IsMatch(message, "^[0-9][A-Z0-9][A-Z0-9][A-Z][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9]$"); } // Works on nick or (U)UID - public string ExtractIdentifierFromMessage(string message, bool split = false) { + public string ExtractIdentifierFromMessage(string message, bool split = false) + { var identifier = message; - if(split) { - var message_split = message.Split(new string[] { " " }, StringSplitOptions.None); + if (split) + { + var message_split = message.Split(new[] {" "}, StringSplitOptions.None); identifier = message_split[0]; } @@ -389,11 +431,14 @@ public string ExtractIdentifierFromMessage(string message, bool split = false) { } // todo: UID -> UUID rename - public string ReplaceUUIDWithNick(string message, int index = 0) { - var split_message = message.Split(new string[] { " " }, StringSplitOptions.None); - if(IsUUID(split_message[index].Replace(":", ""))) { + public string ReplaceUUIDWithNick(string message, int index = 0) + { + var split_message = message.Split(new[] {" "}, StringSplitOptions.None); + if (IsUUID(split_message[index].Replace(":", ""))) + { split_message[index] = ExtractIdentifierFromMessage(split_message[index]); - if (split_message[index] != Host) { + if (split_message[index] != Host) + { Log.Debug($"Looking for client with UUID (want their nick): {split_message[index]}"); var client = GetClientByUUID(split_message[index]); @@ -401,14 +446,18 @@ public string ReplaceUUIDWithNick(string message, int index = 0) { // TODO exception if non existent? } } - return String.Join(" ", split_message); + + return string.Join(" ", split_message); } - public string ReplaceNickWithUUID(string message, int index = 0) { - var split_message = message.Split(new string[] { " " }, StringSplitOptions.None); - if (!IsUUID(split_message[index])) { + public string ReplaceNickWithUUID(string message, int index = 0) + { + var split_message = message.Split(new[] {" "}, StringSplitOptions.None); + if (!IsUUID(split_message[index])) + { split_message[index] = ExtractIdentifierFromMessage(split_message[index]); - if (split_message[index] != Host) { + if (split_message[index] != Host) + { Log.Debug($"Looking for client with nick (want their UUID): {split_message[index]}"); var client = GetClientByNick(split_message[index]); @@ -416,25 +465,23 @@ public string ReplaceNickWithUUID(string message, int index = 0) { // TODO exception if non existent? } } - return String.Join(" ", split_message); + + return string.Join(" ", split_message); } // SID - public static string GenerateSID(string name, string description) { + public static string GenerateSID(string name, string description) + { // http://www.inspircd.org/wiki/Modules/spanningtree/UUIDs.html var SID = 0; - for(int i = 0; i < name.Length; i++) { - SID = (5 * SID) + Convert.ToInt32(name[i]); - } + for (var i = 0; i < name.Length; i++) SID = 5 * SID + Convert.ToInt32(name[i]); - for(int n = 0; n < description.Length; n++) { - SID = (5 * SID) + Convert.ToInt32(description[n]); - } + for (var n = 0; n < description.Length; n++) SID = 5 * SID + Convert.ToInt32(description[n]); SID = SID % 999; return SID.ToString("000"); } } -} +} \ No newline at end of file diff --git a/cmpctircd/IrcApplicationLifecycle.cs b/cmpctircd/IrcApplicationLifecycle.cs index b1387af..735854e 100644 --- a/cmpctircd/IrcApplicationLifecycle.cs +++ b/cmpctircd/IrcApplicationLifecycle.cs @@ -1,4 +1,7 @@ -namespace cmpctircd { +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; + +namespace cmpctircd { using System; using System.Linq; using System.Threading; @@ -10,11 +13,11 @@ public class IrcApplicationLifecycle : IHostedService { private readonly IRCd ircd; private readonly Log log; - private readonly CmpctConfigurationSection config; + private readonly IConfiguration config; private readonly IHostApplicationLifetime appLifetime; private QueuedSynchronizationContext synchronizationContext; - public IrcApplicationLifecycle(IRCd ircd, Log log, CmpctConfigurationSection config, IHostApplicationLifetime appLifetime) { + public IrcApplicationLifecycle(IRCd ircd, Log log, IConfiguration config, IHostApplicationLifetime appLifetime) { this.ircd = ircd ?? throw new ArgumentNullException(nameof(ircd)); this.log = log ?? throw new ArgumentNullException(nameof(log)); this.config = config ?? throw new ArgumentNullException(nameof(config)); @@ -36,7 +39,7 @@ public Task StopAsync(CancellationToken cancellationToken) { private void OnStarted() { synchronizationContext = new QueuedSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(synchronizationContext); - log.Initialise(ircd, config.Loggers.OfType().ToList()); + log.Initialise(ircd, config.GetSection("Logging:Loggers").Get>()); ircd.Run(); synchronizationContext.Run(); } diff --git a/cmpctircd/PacketManager.cs b/cmpctircd/PacketManager.cs index 902e176..02c84e0 100644 --- a/cmpctircd/PacketManager.cs +++ b/cmpctircd/PacketManager.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.Extensions.Configuration; namespace cmpctircd { public class PacketManager { @@ -65,7 +66,7 @@ public bool Handle(String packet, IRCd ircd, object sender, HandlerArgs args, Li try { // Restrict the commands which non-registered (i.e. pre PONG, pre USER/NICK) users can execute - if ((client.State.Equals(ClientState.PreAuth) || (ircd.Config.Advanced.ResolveHostnames && client.ResolvingHost)) && !registrationCommands.Contains(packet.ToUpper())) { + if ((client.State.Equals(ClientState.PreAuth) || (ircd.Config.GetValue("Advanced:ResolveHostnames") && client.ResolvingHost)) && !registrationCommands.Contains(packet.ToUpper())) { throw new IrcErrNotRegisteredException(client); } diff --git a/cmpctircd/Program.cs b/cmpctircd/Program.cs index 5d9c0af..3eb806b 100644 --- a/cmpctircd/Program.cs +++ b/cmpctircd/Program.cs @@ -1,4 +1,7 @@ -namespace cmpctircd { +using System.IO; +using Microsoft.Extensions.Configuration; + +namespace cmpctircd { using System; using System.Linq; using cmpctircd.Configuration; @@ -16,13 +19,16 @@ static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { var log = new Log(); - var config = CmpctConfigurationSection.GetConfiguration(); + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName) + .AddJsonFile("appsettings.json", false) + .Build(); foreach (var controllerType in AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes()).Where(t => !t.IsAbstract && typeof(ControllerBase).IsAssignableFrom(t))) { services.AddTransient(controllerType); } - services.AddSingleton(config); + services.AddSingleton(configuration); services.AddSingleton(log); services.AddSingleton(); services.AddScoped(); diff --git a/cmpctircd/Server/Server.cs b/cmpctircd/Server/Server.cs index 1480d6d..35abea1 100644 --- a/cmpctircd/Server/Server.cs +++ b/cmpctircd/Server/Server.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Sockets; using System.Text; using cmpctircd.Configuration; +using Microsoft.Extensions.Configuration; namespace cmpctircd { public class Server : SocketBase { @@ -136,14 +138,14 @@ public bool FindServerConfig(string hostname, string password) { link = ServerInfo; if (link.Host == hostname && link.Port == Listener.Info.Port - && link.IsTls == Listener.Info.IsTls && link.Password == password) { + && link.Tls == Listener.Info.Tls && link.Password == password) { foundMatch = true; } } else { // Find matching tag in config (or null) - link = IRCd.Config.Servers.Cast().Where(s => s.Host == hostname + link = IRCd.Config.GetSection("Servers").Get>().Where(s => s.Host == hostname && s.Port == Listener.Info.Port - && s.IsTls == Listener.Info.IsTls + && s.Tls == Listener.Info.Tls && s.Password == password).FirstOrDefault(); // IP address needed for the block diff --git a/cmpctircd/SocketConnector.cs b/cmpctircd/SocketConnector.cs index 7c252e9..b8e6e37 100644 --- a/cmpctircd/SocketConnector.cs +++ b/cmpctircd/SocketConnector.cs @@ -42,7 +42,7 @@ public async Task Connect() { return; } - if (ServerInfo.IsTls) { + if (ServerInfo.Tls) { // If we're TLS, we need to handshake immediately stream = await HandshakeTlsAsClient(tc, ServerInfo.Host, ServerInfo.VerifyTlsCert); } diff --git a/cmpctircd/SocketListener.cs b/cmpctircd/SocketListener.cs index 19bdfa6..61b355a 100644 --- a/cmpctircd/SocketListener.cs +++ b/cmpctircd/SocketListener.cs @@ -43,7 +43,7 @@ public virtual void Bind() { } public virtual void Stop() { if (_started) { - _ircd.Log.Debug($"Shutting down listener [IP: {Info.Host}, Port: {Info.Port}, TLS: {Info.IsTls}]"); + _ircd.Log.Debug($"Shutting down listener [IP: {Info.Host}, Port: {Info.Port}, TLS: {Info.Tls}]"); _listener.Stop(); _started = false; } @@ -68,7 +68,7 @@ public async Task ListenToClients() { protected async Task HandshakeIfNeededAsync(TcpClient tc, Stream stream) { // Handshake with TLS if they're from a TLS port - if (Info.IsTls) { + if (Info.Tls) { try { stream = await HandshakeTlsAsServerAsync(tc); } catch (Exception e) { diff --git a/cmpctircd/appsettings.json b/cmpctircd/appsettings.json new file mode 100644 index 0000000..437c25c --- /dev/null +++ b/cmpctircd/appsettings.json @@ -0,0 +1,119 @@ +{ + "startup": { + "supportedRuntime": { + "version": "v4.0", + "sku": ".NETFramework,Version=v4.6.1" + } + }, + "sid": "auto", + "host": "irc.cmpct.info", + "network": "cmpct", + "description": "The C# IRC Server", + "sockets": [ + { + "type": "Client", + "host": "127.0.0.1", + "port": 6667, + "tls": false + } + //{ + // "type": "Client", + // "host": "127.0.0.1", + // "port": "6697", + // "tls": true + //}, + //{ + // "protocol": "InspIRCd20", + // "type": "Server", + // "host": "127.0.0.1", + // "port": 9000, + // "tls": false + //}, + //{ + // "protocol": "InspIRCd20", + // "type": "Server", + // "host": "127.0.0.1", + // "port": 9001, + // "tls": true + //} + ], + "tls": { + "file": "server.pfx", + "password": "" + }, + "logging": { + "loggers": + [ + { + "type": "Stdout", + "level": "Debug" + }, + { + "type": "File", + "level": "Warn", + "path": "ircd.log" + } + //{ + // "type": "IRC", + // "level": "Debug", + // "channel": "#debug", + // "modes": "+nz" + //} + ], + "path": "ircd.log" + }, + "advanced": { + "resolveHostnames": true, + "requirePongCookie": true, + "pingTimeout": 120, + "maxTargets": 200, + "cloak": { + "key": "cmpct", + "prefix": "cmpct", + "domainParts": 3, + "full": false + } + }, + "cmodes": [ + { + "name": "n", + "param": "" + }, + { + "name": "t", + "param": "" + } + ], + "umodes": [ + { + "name": "x", + "param": "" + }, + { + "name": "i", + "param": "" + } + ], + "servers": [ + { + "type": "InspIRCd20", + "host": "services.cmpct.info", + "masks": "*@127.0.0.1", + "port": 9000, + "password": "mypassword", + "tls": false + } + ], + "opers": [ + { + "name": "josh", + "password": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", + "algorithm": "System.Security.Cryptography.SHA256Managed", + "tls": false, + "hosts": "*@127.0.0.1" + } + ], + "operChan": [ + "" + ] +} \ No newline at end of file diff --git a/cmpctircd/cmpctircd.csproj b/cmpctircd/cmpctircd.csproj index 22e4c72..9328e44 100644 --- a/cmpctircd/cmpctircd.csproj +++ b/cmpctircd/cmpctircd.csproj @@ -47,7 +47,6 @@ - PreserveNewest @@ -57,4 +56,9 @@ PreserveNewest + + + PreserveNewest + + From eeba837b78886ee67b3cd09849841b818b67e23a Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 9 May 2021 21:53:26 +0100 Subject: [PATCH 02/24] IRCD-59 - Properly parse socket IP address from config --- cmpctircd/Configuration/IPAddressConverter.cs | 32 ++++++++++++++ .../Configuration/IPEndPointConverter.cs | 33 +++++++++++++++ cmpctircd/Configuration/SocketElement.cs | 42 +++++-------------- cmpctircd/Configuration/SocketOptions.cs | 14 +++++++ cmpctircd/IRCd.cs | 8 +++- cmpctircd/Program.cs | 38 ++++++++++------- cmpctircd/SocketConnector.cs | 4 +- cmpctircd/SocketListener.cs | 3 +- cmpctircd/cmpctircd.csproj | 4 +- 9 files changed, 124 insertions(+), 54 deletions(-) create mode 100644 cmpctircd/Configuration/IPAddressConverter.cs create mode 100644 cmpctircd/Configuration/IPEndPointConverter.cs create mode 100644 cmpctircd/Configuration/SocketOptions.cs diff --git a/cmpctircd/Configuration/IPAddressConverter.cs b/cmpctircd/Configuration/IPAddressConverter.cs new file mode 100644 index 0000000..69a7170 --- /dev/null +++ b/cmpctircd/Configuration/IPAddressConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Net; + +namespace cmpctircd.Configuration +{ + [TypeConverter(typeof(IpAddressConverter))] + public class IpAddressConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, + Type sourceType) + { + if (sourceType == typeof(IPAddress)) return true; + return base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, + CultureInfo culture, object value) + { + if (value is string) return IPAddress.Parse((string) value); + return base.ConvertFrom(context, culture, value); + } + + public override object ConvertTo(ITypeDescriptorContext context, + CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) value.ToString(); + return base.ConvertTo(context, culture, value, destinationType); + } + } +} \ No newline at end of file diff --git a/cmpctircd/Configuration/IPEndPointConverter.cs b/cmpctircd/Configuration/IPEndPointConverter.cs new file mode 100644 index 0000000..6228b21 --- /dev/null +++ b/cmpctircd/Configuration/IPEndPointConverter.cs @@ -0,0 +1,33 @@ +using System; +using System.Net; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace cmpctircd.Configuration +{ + internal class IPEndPointConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IPEndPoint); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var ep = (IPEndPoint) value; + var jo = new JObject(); + jo.Add("Address", JToken.FromObject(ep.Address, serializer)); + jo.Add("Port", ep.Port); + jo.WriteTo(writer); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) + { + var jo = JObject.Load(reader); + var address = jo["Address"].ToObject(serializer); + var port = (int) jo["Port"]; + return new IPEndPoint(address, port); + } + } +} \ No newline at end of file diff --git a/cmpctircd/Configuration/SocketElement.cs b/cmpctircd/Configuration/SocketElement.cs index 211d233..e0aa619 100644 --- a/cmpctircd/Configuration/SocketElement.cs +++ b/cmpctircd/Configuration/SocketElement.cs @@ -1,20 +1,23 @@ -using System; -using System.ComponentModel; -using System.Globalization; +using System.ComponentModel; using System.Net; -using System.Reflection; +using System.Text.Json.Serialization; namespace cmpctircd.Configuration { public class SocketElement { public ListenerType Type { get; set; } - public IPAddress Host { get; set; } + + [TypeConverter(typeof(IpAddressConverter))] + public string Host { get; set; } + public int Port { get; set; } public bool Tls { get; set; } + public ServerType Protocol { get; set; } - public IPEndPoint EndPoint => new IPEndPoint(Host, Port); + + public IPEndPoint EndPoint => new IPEndPoint(IPAddress.Parse(Host), Port); public static implicit operator SocketElement(ServerElement serverElement) { @@ -25,7 +28,7 @@ public static implicit operator SocketElement(ServerElement serverElement) if (!IPAddress.TryParse(serverElement.Destination, out var IP)) IP = Dns.GetHostEntry(serverElement.Destination).AddressList[0]; - se.Host = IP; + se.Host = IP.ToString(); se.Port = serverElement.Port; se.Tls = serverElement.Tls; se.Type = ListenerType.Server; @@ -33,29 +36,4 @@ public static implicit operator SocketElement(ServerElement serverElement) return se; } } - - [TypeConverter(typeof(IPAddressConverter))] - internal class IPAddressConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof(string); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) - { - return IPAddress.Parse((string) value); - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - return destinationType == typeof(string); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, - Type destinationType) - { - return ((IPAddress) value).ToString(); - } - } } \ No newline at end of file diff --git a/cmpctircd/Configuration/SocketOptions.cs b/cmpctircd/Configuration/SocketOptions.cs new file mode 100644 index 0000000..b4cb18a --- /dev/null +++ b/cmpctircd/Configuration/SocketOptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace cmpctircd.Configuration +{ + public class SocketOptions + { + public SocketElement[] Sockets { get; set; } + } +} diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index bc4873a..f7e3450 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -7,6 +7,7 @@ using cmpctircd.Configuration; using cmpctircd.Modes; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace cmpctircd { @@ -17,10 +18,11 @@ public class IRCd public readonly IList Connectors = new List(); private readonly IList Listeners = new List(); - public IRCd(Log log, IConfiguration config, IServiceProvider services) + public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions socketOptions) { Log = log; Config = config; + SocketOptions = socketOptions; // Interpret the ConfigData SID = config.GetValue("SID"); @@ -64,6 +66,7 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services) public Log Log { get; } public IConfiguration Config { get; } + public IOptions SocketOptions { get; } public string SID { get; } public string Host { get; } public string Desc { get; } @@ -107,7 +110,8 @@ public void Run() PacketManager.Load(); - foreach (var listener in Config.GetSection("Sockets").Get>()) + var sockets = SocketOptions.Value; + foreach (var listener in sockets.Sockets) { var sl = new SocketListener(this, listener); Log.Info( diff --git a/cmpctircd/Program.cs b/cmpctircd/Program.cs index 3eb806b..eceed15 100644 --- a/cmpctircd/Program.cs +++ b/cmpctircd/Program.cs @@ -1,32 +1,36 @@ -using System.IO; +using System; +using System.IO; +using System.Linq; +using cmpctircd.Configuration; +using cmpctircd.Controllers; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; -namespace cmpctircd { - using System; - using System.Linq; - using cmpctircd.Configuration; - using cmpctircd.Controllers; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; - - class Program { - static void Main(string[] args) { +namespace cmpctircd +{ + internal class Program + { + private static void Main(string[] args) + { CreateHostBuilder(args) .Build().Run(); } - static IHostBuilder CreateHostBuilder(string[] args) { + private static IHostBuilder CreateHostBuilder(string[] args) + { return Host.CreateDefaultBuilder(args) - .ConfigureServices((hostContext, services) => { + .ConfigureServices((hostContext, services) => + { var log = new Log(); var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName) .AddJsonFile("appsettings.json", false) .Build(); - foreach (var controllerType in AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes()).Where(t => !t.IsAbstract && typeof(ControllerBase).IsAssignableFrom(t))) { + foreach (var controllerType in AppDomain.CurrentDomain.GetAssemblies().SelectMany(t => t.GetTypes()) + .Where(t => !t.IsAbstract && typeof(ControllerBase).IsAssignableFrom(t))) services.AddTransient(controllerType); - } services.AddSingleton(configuration); services.AddSingleton(log); @@ -35,7 +39,9 @@ static IHostBuilder CreateHostBuilder(string[] args) { services.AddScoped(sp => sp.GetRequiredService().Sender as Client); services.AddScoped(sp => sp.GetRequiredService().Sender as Server); services.AddHostedService(); + + services.AddOptions().Bind(configuration); }); } } - } +} \ No newline at end of file diff --git a/cmpctircd/SocketConnector.cs b/cmpctircd/SocketConnector.cs index b8e6e37..e12df42 100644 --- a/cmpctircd/SocketConnector.cs +++ b/cmpctircd/SocketConnector.cs @@ -35,10 +35,10 @@ public async Task Connect() { tc = new TcpClient(); try { - await tc.ConnectAsync(Info.Host.ToString(), Info.Port); + await tc.ConnectAsync(Info.Host, Info.Port); stream = tc.GetStream(); } catch (SocketException) { - _ircd.Log.Warn($"Unable to connect to server {Info.Host.ToString()}:{Info.Port}"); + _ircd.Log.Warn($"Unable to connect to server {Info.Host}:{Info.Port}"); return; } diff --git a/cmpctircd/SocketListener.cs b/cmpctircd/SocketListener.cs index 61b355a..3ee6b70 100644 --- a/cmpctircd/SocketListener.cs +++ b/cmpctircd/SocketListener.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using System.IO; +using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; @@ -28,7 +29,7 @@ public class SocketListener { public SocketListener(IRCd ircd, SocketElement info) { this._ircd = ircd; this.Info = info; - _listener = new TcpListener(info.Host, info.Port); + _listener = new TcpListener(info.EndPoint.Address, info.Port); _ircd.ClientLists.Add(Clients); _ircd.ServerLists.Add(_servers); } diff --git a/cmpctircd/cmpctircd.csproj b/cmpctircd/cmpctircd.csproj index 9328e44..6221f19 100644 --- a/cmpctircd/cmpctircd.csproj +++ b/cmpctircd/cmpctircd.csproj @@ -19,6 +19,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -27,6 +28,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -58,7 +60,7 @@ - PreserveNewest + Always From 8ac66ae9d8a80b74d349417a1833e50607361894 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 9 May 2021 22:03:10 +0100 Subject: [PATCH 03/24] IRCD-59 - Add config comments --- cmpctircd/appsettings.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/cmpctircd/appsettings.json b/cmpctircd/appsettings.json index 437c25c..5a4ee71 100644 --- a/cmpctircd/appsettings.json +++ b/cmpctircd/appsettings.json @@ -37,10 +37,16 @@ // "tls": true //} ], + // + // + // + // "tls": { "file": "server.pfx", - "password": "" + "password": "" }, + // + // "logging": { "loggers": [ @@ -53,6 +59,9 @@ "level": "Warn", "path": "ircd.log" } +// +// +// //{ // "type": "IRC", // "level": "Debug", @@ -67,6 +76,15 @@ "requirePongCookie": true, "pingTimeout": 120, "maxTargets": 200, +// "cloak": { "key": "cmpct", "prefix": "cmpct", @@ -94,6 +112,8 @@ "param": "" } ], +// +// "servers": [ { "type": "InspIRCd20", @@ -104,6 +124,8 @@ "tls": false } ], + // + // "opers": [ { "name": "josh", From 264bd4e98429a4db89bb2462cdc33a45b5cd41c5 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 9 May 2021 22:13:32 +0100 Subject: [PATCH 04/24] IRCD-59 - Remove defunct converters --- cmpctircd/Configuration/IPAddressConverter.cs | 32 ------------------ .../Configuration/IPEndPointConverter.cs | 33 ------------------- 2 files changed, 65 deletions(-) delete mode 100644 cmpctircd/Configuration/IPAddressConverter.cs delete mode 100644 cmpctircd/Configuration/IPEndPointConverter.cs diff --git a/cmpctircd/Configuration/IPAddressConverter.cs b/cmpctircd/Configuration/IPAddressConverter.cs deleted file mode 100644 index 69a7170..0000000 --- a/cmpctircd/Configuration/IPAddressConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using System.Net; - -namespace cmpctircd.Configuration -{ - [TypeConverter(typeof(IpAddressConverter))] - public class IpAddressConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, - Type sourceType) - { - if (sourceType == typeof(IPAddress)) return true; - return base.CanConvertFrom(context, sourceType); - } - - public override object ConvertFrom(ITypeDescriptorContext context, - CultureInfo culture, object value) - { - if (value is string) return IPAddress.Parse((string) value); - return base.ConvertFrom(context, culture, value); - } - - public override object ConvertTo(ITypeDescriptorContext context, - CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string)) value.ToString(); - return base.ConvertTo(context, culture, value, destinationType); - } - } -} \ No newline at end of file diff --git a/cmpctircd/Configuration/IPEndPointConverter.cs b/cmpctircd/Configuration/IPEndPointConverter.cs deleted file mode 100644 index 6228b21..0000000 --- a/cmpctircd/Configuration/IPEndPointConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Net; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace cmpctircd.Configuration -{ - internal class IPEndPointConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - { - return objectType == typeof(IPEndPoint); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var ep = (IPEndPoint) value; - var jo = new JObject(); - jo.Add("Address", JToken.FromObject(ep.Address, serializer)); - jo.Add("Port", ep.Port); - jo.WriteTo(writer); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, - JsonSerializer serializer) - { - var jo = JObject.Load(reader); - var address = jo["Address"].ToObject(serializer); - var port = (int) jo["Port"]; - return new IPEndPoint(address, port); - } - } -} \ No newline at end of file From 8897c3a48439773c5e173fbe0cf29b6d414563a3 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 9 May 2021 22:16:51 +0100 Subject: [PATCH 05/24] IRCD-59 - Remove references to converters --- cmpctircd/Configuration/SocketElement.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/cmpctircd/Configuration/SocketElement.cs b/cmpctircd/Configuration/SocketElement.cs index e0aa619..f8cd5ea 100644 --- a/cmpctircd/Configuration/SocketElement.cs +++ b/cmpctircd/Configuration/SocketElement.cs @@ -8,7 +8,6 @@ public class SocketElement { public ListenerType Type { get; set; } - [TypeConverter(typeof(IpAddressConverter))] public string Host { get; set; } public int Port { get; set; } From ef13af579be47756689055f3bdb22fb441a816ba Mon Sep 17 00:00:00 2001 From: joshoxe Date: Wed, 12 May 2021 13:11:49 +0100 Subject: [PATCH 06/24] IRCD-59 - Add LoggerElement validator and validate configuration in IRCd --- cmpctircd/IRCd.cs | 11 +++++ .../Validation/ConfigurationValidator.cs | 43 +++++++++++++++++++ .../Validation/LoggerElementValidator.cs | 11 +++++ cmpctircd/cmpctircd.csproj | 1 + 4 files changed, 66 insertions(+) create mode 100644 cmpctircd/Validation/ConfigurationValidator.cs create mode 100644 cmpctircd/Validation/LoggerElementValidator.cs diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index f7e3450..e55aebb 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -6,6 +6,7 @@ using System.Timers; using cmpctircd.Configuration; using cmpctircd.Modes; +using cmpctircd.Validation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; @@ -97,6 +98,16 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions< public void Run() { + // Validate configuration file + var configurationValidator = new ConfigurationValidator(Config); + var validationResult = configurationValidator.ValidateConfiguration(); + + if (!validationResult.IsValid) { + Log.Error("Configuration file is incorrectly set up"); + Log.Error($"{string.Join("\n", validationResult.Errors.Select(e => e.ErrorMessage))}"); + return; + } + Log.Info($"==> Starting cmpctircd-{Version}"); if (Version.Contains("-dev")) { diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs new file mode 100644 index 0000000..3f6ea7d --- /dev/null +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using cmpctircd.Configuration; +using FluentValidation.Results; +using Microsoft.Extensions.Configuration; + +namespace cmpctircd.Validation { + public class ConfigurationValidator { + private readonly IConfiguration _config; + + public ConfigurationValidator(IConfiguration config) { + _config = config; + } + + public ValidationResult ValidateConfiguration() { + var result = new ValidationResult(); + + var loggerValidationResult = ValidateLoggerElement(); + result.Errors.AddRange(loggerValidationResult.Errors); + + return result; + } + + private ValidationResult ValidateLoggerElement() { + var validationResult = new ValidationResult(); + var loggers = _config.GetSection("Logging:Loggers").Get>(); + + if (!loggers.Any()) { + validationResult.Errors.Add(new ValidationFailure("Log Section", + "There were no loggers found in the configuration")); + } + + var validator = new LoggerElementValidator(); + + foreach (var logger in loggers) { + var result = validator.Validate(logger); + validationResult.Errors.AddRange(result.Errors); + } + + return validationResult; + } + } +} \ No newline at end of file diff --git a/cmpctircd/Validation/LoggerElementValidator.cs b/cmpctircd/Validation/LoggerElementValidator.cs new file mode 100644 index 0000000..78eddb5 --- /dev/null +++ b/cmpctircd/Validation/LoggerElementValidator.cs @@ -0,0 +1,11 @@ +using cmpctircd.Configuration; +using FluentValidation; + +namespace cmpctircd.Validation { + public class LoggerElementValidator : AbstractValidator { + public LoggerElementValidator() { + RuleFor(logger => logger.Type).NotEmpty(); + RuleFor(logger => logger.Level).NotEmpty(); + } + } +} \ No newline at end of file diff --git a/cmpctircd/cmpctircd.csproj b/cmpctircd/cmpctircd.csproj index 6221f19..6163645 100644 --- a/cmpctircd/cmpctircd.csproj +++ b/cmpctircd/cmpctircd.csproj @@ -19,6 +19,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive From e1a6dd1475bd022c1dfd6032f2ada85d9aa96248 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 16 May 2021 12:31:10 +0100 Subject: [PATCH 07/24] IRCD-59 - Add mode configuration validation --- cmpctircd/IRCd.cs | 5 ++--- .../Validation/ConfigurationValidator.cs | 20 +++++++++++++++++++ cmpctircd/Validation/ModeElementValidator.cs | 17 ++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 cmpctircd/Validation/ModeElementValidator.cs diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index e55aebb..7a1d8a7 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -98,13 +98,12 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions< public void Run() { - // Validate configuration file + Log.Info("==> Validating appsettings.json"); var configurationValidator = new ConfigurationValidator(Config); var validationResult = configurationValidator.ValidateConfiguration(); if (!validationResult.IsValid) { - Log.Error("Configuration file is incorrectly set up"); - Log.Error($"{string.Join("\n", validationResult.Errors.Select(e => e.ErrorMessage))}"); + Log.Error($"==> {string.Join("\n", validationResult.Errors.Select(e => e.ErrorMessage))}"); return; } diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs index 3f6ea7d..90a4ed4 100644 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using cmpctircd.Configuration; +using FluentValidation; using FluentValidation.Results; using Microsoft.Extensions.Configuration; @@ -18,6 +19,9 @@ public ValidationResult ValidateConfiguration() { var loggerValidationResult = ValidateLoggerElement(); result.Errors.AddRange(loggerValidationResult.Errors); + var modeValidationResult = ValidateModeElement(); + result.Errors.AddRange(modeValidationResult.Errors); + return result; } @@ -39,5 +43,21 @@ private ValidationResult ValidateLoggerElement() { return validationResult; } + + private ValidationResult ValidateModeElement() { + var validationResult = new ValidationResult(); + + var modes = _config.GetSection("Cmodes").Get>(); + modes.AddRange(_config.GetSection("Umodes").Get>()); + + var validator = new ModeElementValidator(); + + foreach (var mode in modes) { + var result = validator.Validate(mode); + validationResult.Errors.AddRange(result.Errors); + } + + return validationResult; + } } } \ No newline at end of file diff --git a/cmpctircd/Validation/ModeElementValidator.cs b/cmpctircd/Validation/ModeElementValidator.cs new file mode 100644 index 0000000..981f4db --- /dev/null +++ b/cmpctircd/Validation/ModeElementValidator.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using cmpctircd.Configuration; +using FluentValidation; + +namespace cmpctircd.Validation +{ + public class ModeElementValidator : AbstractValidator + { + public ModeElementValidator() { + RuleFor(m => m.Name).NotEmpty(); + } + } +} From a3cc2f28d38156f48fe86f49ad04a5e90a6e6367 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 16 May 2021 16:03:30 +0100 Subject: [PATCH 08/24] IRCD-59 - Add operator configuration validation --- .../Validation/ConfigurationValidator.cs | 25 ++++++++++++++----- .../Validation/OperatorElementValidator.cs | 20 +++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 cmpctircd/Validation/OperatorElementValidator.cs diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs index 90a4ed4..6d74221 100644 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using cmpctircd.Configuration; -using FluentValidation; using FluentValidation.Results; using Microsoft.Extensions.Configuration; @@ -22,6 +21,9 @@ public ValidationResult ValidateConfiguration() { var modeValidationResult = ValidateModeElement(); result.Errors.AddRange(modeValidationResult.Errors); + var operatorValidationResult = ValidateOperatorElement(); + result.Errors.AddRange(operatorValidationResult.Errors); + return result; } @@ -29,11 +31,6 @@ private ValidationResult ValidateLoggerElement() { var validationResult = new ValidationResult(); var loggers = _config.GetSection("Logging:Loggers").Get>(); - if (!loggers.Any()) { - validationResult.Errors.Add(new ValidationFailure("Log Section", - "There were no loggers found in the configuration")); - } - var validator = new LoggerElementValidator(); foreach (var logger in loggers) { @@ -59,5 +56,21 @@ private ValidationResult ValidateModeElement() { return validationResult; } + + private ValidationResult ValidateOperatorElement() { + var validationResult = new ValidationResult(); + + var opers = _config.GetSection("Opers").Get>(); + + + var validator = new OperatorElementValidator(); + + foreach (var oper in opers) { + var result = validator.Validate(oper); + validationResult.Errors.AddRange(result.Errors); + } + + return validationResult; + } } } \ No newline at end of file diff --git a/cmpctircd/Validation/OperatorElementValidator.cs b/cmpctircd/Validation/OperatorElementValidator.cs new file mode 100644 index 0000000..4d645c0 --- /dev/null +++ b/cmpctircd/Validation/OperatorElementValidator.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using cmpctircd.Configuration; +using FluentValidation; + +namespace cmpctircd.Validation +{ + public class OperatorElementValidator : AbstractValidator + { + public OperatorElementValidator() { + RuleFor(o => o.Algorithm).NotEmpty(); + RuleFor(o => o.Name).NotEmpty(); + RuleFor(o => o.Password).NotEmpty(); + RuleFor(o => o.Hosts).NotEmpty(); + } + } +} From 2c7a163e5cd3998231548d2123f7d275becadeeb Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 16 May 2021 16:08:06 +0100 Subject: [PATCH 09/24] IRCD-59 - Add server configuration validation --- .../Validation/ConfigurationValidator.cs | 19 +++++++++++++++++ .../Validation/ServerElementValidator.cs | 21 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 cmpctircd/Validation/ServerElementValidator.cs diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs index 6d74221..13b1fe0 100644 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -24,6 +24,9 @@ public ValidationResult ValidateConfiguration() { var operatorValidationResult = ValidateOperatorElement(); result.Errors.AddRange(operatorValidationResult.Errors); + var serverValidationResult = ValidateServerElement(); + result.Errors.AddRange(serverValidationResult.Errors); + return result; } @@ -72,5 +75,21 @@ private ValidationResult ValidateOperatorElement() { return validationResult; } + + private ValidationResult ValidateServerElement() { + var validationResult = new ValidationResult(); + + var servers = _config.GetSection("Servers").Get>(); + + + var validator = new ServerElementValidator(); + + foreach(var server in servers) { + var result = validator.Validate(server); + validationResult.Errors.AddRange(result.Errors); + } + + return validationResult; + } } } \ No newline at end of file diff --git a/cmpctircd/Validation/ServerElementValidator.cs b/cmpctircd/Validation/ServerElementValidator.cs new file mode 100644 index 0000000..dfe86e5 --- /dev/null +++ b/cmpctircd/Validation/ServerElementValidator.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using cmpctircd.Configuration; +using FluentValidation; + +namespace cmpctircd.Validation +{ + public class ServerElementValidator : AbstractValidator + { + public ServerElementValidator() { + RuleFor(s => s.Host).NotEmpty(); + RuleFor(s => s.Type).NotEmpty(); + RuleFor(s => s.Masks).NotEmpty(); + RuleFor(s => s.Port).NotEmpty(); + RuleFor(s => s.Password).NotEmpty(); + } + } +} From f29a4bfc3197b97f44f0b190d3888859ad8f52b1 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 16 May 2021 16:18:23 +0100 Subject: [PATCH 10/24] IRCD-59 - Add socket configuration validator --- cmpctircd/IRCd.cs | 2 +- .../Validation/ConfigurationValidator.cs | 30 +++++++++++++++++-- .../Validation/SocketElementValidator.cs | 20 +++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 cmpctircd/Validation/SocketElementValidator.cs diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index 7a1d8a7..76b1424 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -99,7 +99,7 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions< public void Run() { Log.Info("==> Validating appsettings.json"); - var configurationValidator = new ConfigurationValidator(Config); + var configurationValidator = new ConfigurationValidator(Config, SocketOptions); var validationResult = configurationValidator.ValidateConfiguration(); if (!validationResult.IsValid) { diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs index 13b1fe0..1c5a0df 100644 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -3,13 +3,16 @@ using cmpctircd.Configuration; using FluentValidation.Results; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace cmpctircd.Validation { public class ConfigurationValidator { private readonly IConfiguration _config; + private readonly IOptions _socketOptions; - public ConfigurationValidator(IConfiguration config) { + public ConfigurationValidator(IConfiguration config, IOptions socketOptions) { _config = config; + _socketOptions = socketOptions; } public ValidationResult ValidateConfiguration() { @@ -27,6 +30,9 @@ public ValidationResult ValidateConfiguration() { var serverValidationResult = ValidateServerElement(); result.Errors.AddRange(serverValidationResult.Errors); + var socketValidationResult = ValidateSocketElement(); + result.Errors.AddRange(socketValidationResult.Errors); + return result; } @@ -84,12 +90,32 @@ private ValidationResult ValidateServerElement() { var validator = new ServerElementValidator(); - foreach(var server in servers) { + foreach (var server in servers) { var result = validator.Validate(server); validationResult.Errors.AddRange(result.Errors); } return validationResult; } + + private ValidationResult ValidateSocketElement() { + var validationResult = new ValidationResult(); + + var sockets = _socketOptions.Value; + + if (!sockets.Sockets.Any()) { + validationResult.Errors.Add(new ValidationFailure("Socket", "No socket configuration found")); + } + + + var validator = new SocketElementValidator(); + + foreach (var socket in sockets.Sockets) { + var result = validator.Validate(socket); + validationResult.Errors.AddRange(result.Errors); + } + + return validationResult; + } } } \ No newline at end of file diff --git a/cmpctircd/Validation/SocketElementValidator.cs b/cmpctircd/Validation/SocketElementValidator.cs new file mode 100644 index 0000000..b5c1645 --- /dev/null +++ b/cmpctircd/Validation/SocketElementValidator.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using cmpctircd.Configuration; +using FluentValidation; + +namespace cmpctircd.Validation +{ + public class SocketElementValidator : AbstractValidator + { + public SocketElementValidator() { + RuleFor(s => s.Tls).NotEmpty(); + RuleFor(s => s.Host).NotEmpty(); + RuleFor(s => s.Port).NotEmpty(); + RuleFor(s => s.Type).NotEmpty(); + } + } +} From d7b266365a9428fea3099664993fa8ff77a8625a Mon Sep 17 00:00:00 2001 From: joshoxe Date: Sun, 16 May 2021 22:39:49 +0100 Subject: [PATCH 11/24] IRCD-59 - Remove unused package --- cmpctircd/cmpctircd.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/cmpctircd/cmpctircd.csproj b/cmpctircd/cmpctircd.csproj index e989c47..4677ebb 100644 --- a/cmpctircd/cmpctircd.csproj +++ b/cmpctircd/cmpctircd.csproj @@ -20,7 +20,6 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive all From 7c48e2919f2c5014a6be264f73f7f88acc8b63d8 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Thu, 20 May 2021 21:06:33 +0100 Subject: [PATCH 12/24] IRCD-59 - Add port range validation --- cmpctircd/Validation/ServerElementValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmpctircd/Validation/ServerElementValidator.cs b/cmpctircd/Validation/ServerElementValidator.cs index dfe86e5..738b061 100644 --- a/cmpctircd/Validation/ServerElementValidator.cs +++ b/cmpctircd/Validation/ServerElementValidator.cs @@ -14,7 +14,7 @@ public ServerElementValidator() { RuleFor(s => s.Host).NotEmpty(); RuleFor(s => s.Type).NotEmpty(); RuleFor(s => s.Masks).NotEmpty(); - RuleFor(s => s.Port).NotEmpty(); + RuleFor(s => s.Port).NotEmpty().ExclusiveBetween(0, 65535); RuleFor(s => s.Password).NotEmpty(); } } From 98113d982adf7b4ab697282544f10244923a3a85 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Thu, 20 May 2021 21:08:45 +0100 Subject: [PATCH 13/24] IRCD-59 - Make max mode length name 1 --- cmpctircd/Validation/ModeElementValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmpctircd/Validation/ModeElementValidator.cs b/cmpctircd/Validation/ModeElementValidator.cs index 981f4db..98bdabf 100644 --- a/cmpctircd/Validation/ModeElementValidator.cs +++ b/cmpctircd/Validation/ModeElementValidator.cs @@ -11,7 +11,7 @@ namespace cmpctircd.Validation public class ModeElementValidator : AbstractValidator { public ModeElementValidator() { - RuleFor(m => m.Name).NotEmpty(); + RuleFor(m => m.Name).NotEmpty().MaximumLength(1); } } } From ff68313173f1466cad7aed5ec16ebd438c17bfb8 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Thu, 20 May 2021 21:25:25 +0100 Subject: [PATCH 14/24] IRCD-59 - Move validator function calls to list --- .../Validation/ConfigurationValidator.cs | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs index 1c5a0df..9e8a684 100644 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -16,22 +16,19 @@ public ConfigurationValidator(IConfiguration config, IOptions soc } public ValidationResult ValidateConfiguration() { - var result = new ValidationResult(); - - var loggerValidationResult = ValidateLoggerElement(); - result.Errors.AddRange(loggerValidationResult.Errors); - - var modeValidationResult = ValidateModeElement(); - result.Errors.AddRange(modeValidationResult.Errors); + var validators = new List { + ValidateServerElement(), + ValidateSocketElement(), + ValidateOperatorElement(), + ValidateLoggerElement(), + ValidateModeElement(), + }; - var operatorValidationResult = ValidateOperatorElement(); - result.Errors.AddRange(operatorValidationResult.Errors); - - var serverValidationResult = ValidateServerElement(); - result.Errors.AddRange(serverValidationResult.Errors); + var result = new ValidationResult(); - var socketValidationResult = ValidateSocketElement(); - result.Errors.AddRange(socketValidationResult.Errors); + foreach (var validationResult in validators) { + result.Errors.AddRange(validationResult.Errors); + } return result; } From c3476a879ac5020f84d4a3e4bd8079b439db2ca2 Mon Sep 17 00:00:00 2001 From: joshoxe Date: Thu, 20 May 2021 21:50:55 +0100 Subject: [PATCH 15/24] IRCD-59 - Change list to array --- cmpctircd/Validation/ConfigurationValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs index 9e8a684..1de134d 100644 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -16,7 +16,7 @@ public ConfigurationValidator(IConfiguration config, IOptions soc } public ValidationResult ValidateConfiguration() { - var validators = new List { + var validators = new[] { ValidateServerElement(), ValidateSocketElement(), ValidateOperatorElement(), From 7a61ddd2735fe8cefb11ebdbf434df3ae975aeb2 Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 13:21:17 +0100 Subject: [PATCH 16/24] Configuration: Move SocketOptions to Options/ subfolders task: #9 --- cmpctircd/Configuration/{ => Options}/SocketOptions.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmpctircd/Configuration/{ => Options}/SocketOptions.cs (100%) diff --git a/cmpctircd/Configuration/SocketOptions.cs b/cmpctircd/Configuration/Options/SocketOptions.cs similarity index 100% rename from cmpctircd/Configuration/SocketOptions.cs rename to cmpctircd/Configuration/Options/SocketOptions.cs From d8ce3af1288a0512beeb23ac3127f2fe1ddcbf62 Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 15:16:13 +0100 Subject: [PATCH 17/24] Configuration: Fix logger configuration initialisation * LoggerElements are now initialised using IOptions * Attributes now must be specified under a JSON section in the config Task: #9 --- cmpctircd/Configuration/LoggerElement.cs | 12 ++-------- .../Configuration/Options/LoggerOptions.cs | 13 +++++++++++ cmpctircd/IRCd.cs | 9 +++++--- cmpctircd/IrcApplicationLifecycle.cs | 8 +++++-- cmpctircd/Log/Log.cs | 1 + cmpctircd/Program.cs | 2 ++ .../Validation/ConfigurationValidator.cs | 7 ++++-- cmpctircd/appsettings.json | 23 ++++++++++--------- 8 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 cmpctircd/Configuration/Options/LoggerOptions.cs diff --git a/cmpctircd/Configuration/LoggerElement.cs b/cmpctircd/Configuration/LoggerElement.cs index 6b54c53..b6dfbb8 100644 --- a/cmpctircd/Configuration/LoggerElement.cs +++ b/cmpctircd/Configuration/LoggerElement.cs @@ -6,18 +6,10 @@ namespace cmpctircd.Configuration { - public class LoggerElement : ConfigurationElement + public class LoggerElement { - [JsonExtensionData] private Dictionary _attributes { get; set; } - public LoggerType Type { get; set; } public LogType Level { get; set; } - public string Channel { get; set; } - public string Modes { get; set; } - - public Dictionary Attributes - { - get { return _attributes.ToDictionary(x => x.Key, x => x.Value.ToString()); } - } + public Dictionary Attributes { get; set; } = new Dictionary(); } } \ No newline at end of file diff --git a/cmpctircd/Configuration/Options/LoggerOptions.cs b/cmpctircd/Configuration/Options/LoggerOptions.cs new file mode 100644 index 0000000..d2774f8 --- /dev/null +++ b/cmpctircd/Configuration/Options/LoggerOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace cmpctircd.Configuration.Options +{ + public class LoggerOptions + { + public LoggerElement[] Loggers { get; set; } + } +} diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index 76b1424..b9804b6 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using System.Timers; using cmpctircd.Configuration; +using cmpctircd.Configuration.Options; using cmpctircd.Modes; using cmpctircd.Validation; using Microsoft.Extensions.Configuration; @@ -18,9 +19,11 @@ public class IRCd public static char[] lastUID = { }; public readonly IList Connectors = new List(); private readonly IList Listeners = new List(); + private IOptions _loggerOptions; - public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions socketOptions) + public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions socketOptions, IOptions loggerOptions) { + _loggerOptions = loggerOptions; Log = log; Config = config; SocketOptions = socketOptions; @@ -36,7 +39,7 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions< PingTimeout = config.GetValue("Advanced:PingTimeout"); RequirePong = config.GetValue("Advanced:RequirePongCookie"); - Loggers = config.GetSection("Logging:Loggers").Get>(); + Loggers = _loggerOptions.Value.Loggers; MaxTargets = config.GetValue("Advanced:MaxTargets"); CloakKey = config.GetValue("Advanced:Cloak:Key"); @@ -99,7 +102,7 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions< public void Run() { Log.Info("==> Validating appsettings.json"); - var configurationValidator = new ConfigurationValidator(Config, SocketOptions); + var configurationValidator = new ConfigurationValidator(Config, SocketOptions, _loggerOptions); var validationResult = configurationValidator.ValidateConfiguration(); if (!validationResult.IsValid) { diff --git a/cmpctircd/IrcApplicationLifecycle.cs b/cmpctircd/IrcApplicationLifecycle.cs index 735854e..1fe1c44 100644 --- a/cmpctircd/IrcApplicationLifecycle.cs +++ b/cmpctircd/IrcApplicationLifecycle.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using cmpctircd.Configuration.Options; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; namespace cmpctircd { using System; @@ -16,8 +18,10 @@ public class IrcApplicationLifecycle : IHostedService { private readonly IConfiguration config; private readonly IHostApplicationLifetime appLifetime; private QueuedSynchronizationContext synchronizationContext; + private IOptions _loggerOptions; - public IrcApplicationLifecycle(IRCd ircd, Log log, IConfiguration config, IHostApplicationLifetime appLifetime) { + public IrcApplicationLifecycle(IRCd ircd, Log log, IConfiguration config, IHostApplicationLifetime appLifetime, IOptions loggerOptions) { + _loggerOptions = loggerOptions; this.ircd = ircd ?? throw new ArgumentNullException(nameof(ircd)); this.log = log ?? throw new ArgumentNullException(nameof(log)); this.config = config ?? throw new ArgumentNullException(nameof(config)); @@ -39,7 +43,7 @@ public Task StopAsync(CancellationToken cancellationToken) { private void OnStarted() { synchronizationContext = new QueuedSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(synchronizationContext); - log.Initialise(ircd, config.GetSection("Logging:Loggers").Get>()); + log.Initialise(ircd, _loggerOptions.Value.Loggers.ToList()); ircd.Run(); synchronizationContext.Run(); } diff --git a/cmpctircd/Log/Log.cs b/cmpctircd/Log/Log.cs index a995af0..39a412a 100644 --- a/cmpctircd/Log/Log.cs +++ b/cmpctircd/Log/Log.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace cmpctircd { public class Log { diff --git a/cmpctircd/Program.cs b/cmpctircd/Program.cs index eceed15..b7fdd54 100644 --- a/cmpctircd/Program.cs +++ b/cmpctircd/Program.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using cmpctircd.Configuration; +using cmpctircd.Configuration.Options; using cmpctircd.Controllers; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -41,6 +42,7 @@ private static IHostBuilder CreateHostBuilder(string[] args) services.AddHostedService(); services.AddOptions().Bind(configuration); + services.AddOptions().Bind(configuration); }); } } diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs index 1de134d..a81970d 100644 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ b/cmpctircd/Validation/ConfigurationValidator.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using cmpctircd.Configuration; +using cmpctircd.Configuration.Options; using FluentValidation.Results; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; @@ -9,8 +10,10 @@ namespace cmpctircd.Validation { public class ConfigurationValidator { private readonly IConfiguration _config; private readonly IOptions _socketOptions; + private IOptions _loggerOptions; - public ConfigurationValidator(IConfiguration config, IOptions socketOptions) { + public ConfigurationValidator(IConfiguration config, IOptions socketOptions, IOptions loggerOptions) { + _loggerOptions = loggerOptions; _config = config; _socketOptions = socketOptions; } @@ -35,7 +38,7 @@ public ValidationResult ValidateConfiguration() { private ValidationResult ValidateLoggerElement() { var validationResult = new ValidationResult(); - var loggers = _config.GetSection("Logging:Loggers").Get>(); + var loggers = _loggerOptions.Value.Loggers; var validator = new LoggerElementValidator(); diff --git a/cmpctircd/appsettings.json b/cmpctircd/appsettings.json index 5a4ee71..1fe874c 100644 --- a/cmpctircd/appsettings.json +++ b/cmpctircd/appsettings.json @@ -47,9 +47,7 @@ }, // // - "logging": { - "loggers": - [ + "loggers": [ { "type": "Stdout", "level": "Debug" @@ -57,20 +55,23 @@ { "type": "File", "level": "Warn", - "path": "ircd.log" + "attributes": { + "path": "ircd.log" + } } -// -// -// + // + // + // //{ // "type": "IRC", // "level": "Debug", - // "channel": "#debug", - // "modes": "+nz" + // "attributes": { + // "channel": "#debug", + // "modes": "+nz" + // } + //} ], - "path": "ircd.log" - }, "advanced": { "resolveHostnames": true, "requirePongCookie": true, From 1c0c41510f9d3546137825aa78ec5a9a1aee7cb1 Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 15:58:29 +0100 Subject: [PATCH 18/24] Configuration: Add ServerOptions task: #9 --- cmpctircd/Configuration/Options/ServerOptions.cs | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 cmpctircd/Configuration/Options/ServerOptions.cs diff --git a/cmpctircd/Configuration/Options/ServerOptions.cs b/cmpctircd/Configuration/Options/ServerOptions.cs new file mode 100644 index 0000000..539f260 --- /dev/null +++ b/cmpctircd/Configuration/Options/ServerOptions.cs @@ -0,0 +1,5 @@ +namespace cmpctircd.Configuration.Options { + public class ServerOptions { + public ServerElement[] Servers { get; set; } + } +} \ No newline at end of file From b6a55be0d9001f48264265361253af27758b18cf Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 15:59:02 +0100 Subject: [PATCH 19/24] Configuration: Clean up SocketOptions task: #9 --- cmpctircd/Configuration/Options/SocketOptions.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/cmpctircd/Configuration/Options/SocketOptions.cs b/cmpctircd/Configuration/Options/SocketOptions.cs index b4cb18a..5c87b70 100644 --- a/cmpctircd/Configuration/Options/SocketOptions.cs +++ b/cmpctircd/Configuration/Options/SocketOptions.cs @@ -1,14 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace cmpctircd.Configuration -{ - public class SocketOptions - { +namespace cmpctircd.Configuration { + public class SocketOptions { public SocketElement[] Sockets { get; set; } } -} +} \ No newline at end of file From e6b953efaa01fd1fe0eb321800f4188e9eb4542a Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 19:48:29 +0100 Subject: [PATCH 20/24] Configuration: Convert new IConfiguration to IOptions * Fixes a lot of the configuration not being deserialized correctly * Removes a lot of code duplication from validation Task: #9 --- cmpctircd/Channel/Channel.cs | 2 +- cmpctircd/Client/Client.cs | 10 +- cmpctircd/Configuration/OperatorElement.cs | 44 ++++--- .../Configuration/Options/AdvancedOptions.cs | 16 +++ .../Configuration/Options/CModeOptions.cs | 5 + .../Options/ConfigurationOptions.cs | 17 +++ .../Configuration/Options/OperatorOptions.cs | 5 + cmpctircd/Configuration/Options/TlsOptions.cs | 6 + .../Configuration/Options/UMoodeOptions.cs | 5 + cmpctircd/Controllers/OperController.cs | 6 +- cmpctircd/IRCd.cs | 57 ++++----- cmpctircd/PacketManager.cs | 2 +- cmpctircd/Program.cs | 3 + cmpctircd/Server/Server.cs | 2 +- .../ConfigurationOptionsValidator.cs | 36 ++++++ .../Validation/ConfigurationValidator.cs | 121 ------------------ .../Validation/SocketElementValidator.cs | 4 +- cmpctircd/appsettings.json | 4 +- 18 files changed, 160 insertions(+), 185 deletions(-) create mode 100644 cmpctircd/Configuration/Options/AdvancedOptions.cs create mode 100644 cmpctircd/Configuration/Options/CModeOptions.cs create mode 100644 cmpctircd/Configuration/Options/ConfigurationOptions.cs create mode 100644 cmpctircd/Configuration/Options/OperatorOptions.cs create mode 100644 cmpctircd/Configuration/Options/TlsOptions.cs create mode 100644 cmpctircd/Configuration/Options/UMoodeOptions.cs create mode 100644 cmpctircd/Validation/ConfigurationOptionsValidator.cs delete mode 100644 cmpctircd/Validation/ConfigurationValidator.cs diff --git a/cmpctircd/Channel/Channel.cs b/cmpctircd/Channel/Channel.cs index 81730f3..34d1b32 100644 --- a/cmpctircd/Channel/Channel.cs +++ b/cmpctircd/Channel/Channel.cs @@ -34,7 +34,7 @@ public Channel(ChannelManager manager, IRCd ircd) { .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.Modes" && + t.Namespace == "cmpctircd.UModes" && t.BaseType.Equals(typeof(ChannelMode)) && !badClasses.Contains(t.Name) ); diff --git a/cmpctircd/Client/Client.cs b/cmpctircd/Client/Client.cs index a07e2e0..2b74f52 100755 --- a/cmpctircd/Client/Client.cs +++ b/cmpctircd/Client/Client.cs @@ -44,7 +44,7 @@ public IDictionary Modes { public string NickIfSet() => string.IsNullOrEmpty(Nick) ? "*" : Nick; public Client(IRCd ircd, TcpClient tc, SocketListener sl, Stream stream, string UID = null, Server OriginServer = null, bool RemoteClient = false) : base(ircd, tc, sl, stream) { - if(ircd.Config.GetValue("Advanced:ResolveHostnames")) + if(ircd.Config.Value.Advanced.ResolveHostnames) ResolvingHost = true; this.UID = UID; @@ -65,7 +65,7 @@ public Client(IRCd ircd, TcpClient tc, SocketListener sl, Stream stream, string .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.Modes" && + t.Namespace == "cmpctircd.UModes" && t.BaseType.Equals(typeof(UserMode)) && !except.Contains(t) ); @@ -95,7 +95,7 @@ public override void BeginTasks() { // Check for PING/PONG events due CheckTimeout(); - if(IRCd.Config.GetValue("Advanced:ResolveHostnames")) + if(IRCd.Config.Value.Advanced.ResolveHostnames) Resolve(); } catch(Exception e) { IRCd.Log.Debug($"Failed to access client: {e.ToString()}"); @@ -170,7 +170,7 @@ public void Resolve() { IRCd.DNSCache[ip] = DNSHost; } - if (IRCd.Config.GetValue("Advanced:ResolveHostnames")) { + if (IRCd.Config.Value.Advanced.ResolveHostnames) { // If the IRCd resolves all hostnames, then we will have // delayed calling SendWelcome until DNS resolution was complete SendWelcome(); @@ -253,7 +253,7 @@ public void SendLusers() { users += list.Count(); // Count all of the users with the user mode +i (invisible) invisible += list.Where(client => client.Modes["i"].Enabled).Count(); - // Count all of the users with usermode +o (IRC Operators) + // Count all of the users with usermode +o (IRC Opers) ircops += list.Where(client => client.Modes["o"].Enabled).Count(); } users -= invisible; diff --git a/cmpctircd/Configuration/OperatorElement.cs b/cmpctircd/Configuration/OperatorElement.cs index 7330d4c..cc069ae 100644 --- a/cmpctircd/Configuration/OperatorElement.cs +++ b/cmpctircd/Configuration/OperatorElement.cs @@ -1,31 +1,36 @@ using System; -using System.Configuration; -using System.Xml; using System.Collections.Generic; -using System.Linq; using System.ComponentModel; +using System.Configuration; using System.Globalization; +using System.Linq; -namespace cmpctircd.Configuration -{ - public class OperatorElement : ConfigurationElement - { +namespace cmpctircd.Configuration { + public class OperatorElement : ConfigurationElement { public string Name { get; set; } - [TypeConverter(typeof(HexadecimalConverter))] - public byte[] Password { get; set; } + public string Password { get; set; } - [TypeConverter(typeof(TypeNameConverter))] - public Type Algorithm { get; set; } + public string Algorithm { get; set; } public bool Tls { get; set; } public List Hosts { get; set; } + public Type AlgorithmType => Type.GetType(Algorithm); + public byte[] PasswordArray => ConvertPassword(Password); + public byte[] ConvertPassword(string value) { + // From hex string to byte[] + // https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array + var hex = value; + return Enumerable.Range(0, hex.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); + } } [TypeConverter(typeof(HexadecimalConverter))] - class HexadecimalConverter : TypeConverter - { + internal class HexadecimalConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string); } @@ -33,20 +38,21 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { // From hex string to byte[] // https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array - var hex = (string)value; + var hex = (string) value; return Enumerable.Range(0, hex.Length) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) - .ToArray(); + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return destinationType == typeof(string); } - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, + Type destinationType) { // From byte[] to hex string - return String.Concat(Array.ConvertAll((byte[])value, x => x.ToString("X2"))); + return string.Concat(Array.ConvertAll((byte[]) value, x => x.ToString("X2"))); } } } \ No newline at end of file diff --git a/cmpctircd/Configuration/Options/AdvancedOptions.cs b/cmpctircd/Configuration/Options/AdvancedOptions.cs new file mode 100644 index 0000000..9f3f6cc --- /dev/null +++ b/cmpctircd/Configuration/Options/AdvancedOptions.cs @@ -0,0 +1,16 @@ +namespace cmpctircd.Configuration.Options { + public class AdvancedOptions { + public bool ResolveHostnames { get; set; } + public bool RequirePongCookie { get; set; } + public int PingTimeout { get; set; } + public int MaxTargets { get; set; } + public CloakOptions Cloak { get; set; } + } + + public class CloakOptions { + public string Key { get; set; } + public string Prefix { get; set; } + public int DomainParts { get; set; } + public bool Full { get; set; } + } +} \ No newline at end of file diff --git a/cmpctircd/Configuration/Options/CModeOptions.cs b/cmpctircd/Configuration/Options/CModeOptions.cs new file mode 100644 index 0000000..7a5df44 --- /dev/null +++ b/cmpctircd/Configuration/Options/CModeOptions.cs @@ -0,0 +1,5 @@ +namespace cmpctircd.Configuration.Options { + public class CModeOptions { + public ModeElement[] CModes { get; set; } + } +} \ No newline at end of file diff --git a/cmpctircd/Configuration/Options/ConfigurationOptions.cs b/cmpctircd/Configuration/Options/ConfigurationOptions.cs new file mode 100644 index 0000000..9d4ec21 --- /dev/null +++ b/cmpctircd/Configuration/Options/ConfigurationOptions.cs @@ -0,0 +1,17 @@ +namespace cmpctircd.Configuration.Options { + public class ConfigurationOptions { + public string Sid { get; set; } + public string Host { get; set; } + public string Network { get; set; } + public string Description { get; set; } + public LoggerElement[] Loggers { get; set; } + public ServerElement[] Servers { get; set; } + public SocketElement[] Sockets { get; set; } + public OperatorElement[] Opers { get; set; } + public ModeElement[] CModes { get; set; } + public ModeElement[] UModes { get; set; } + public TlsOptions Tls { get; set; } + public AdvancedOptions Advanced { get; set; } + public string[] OperChan { get; set; } + } +} \ No newline at end of file diff --git a/cmpctircd/Configuration/Options/OperatorOptions.cs b/cmpctircd/Configuration/Options/OperatorOptions.cs new file mode 100644 index 0000000..60cb749 --- /dev/null +++ b/cmpctircd/Configuration/Options/OperatorOptions.cs @@ -0,0 +1,5 @@ +namespace cmpctircd.Configuration.Options { + public class OperatorOptions { + public OperatorElement[] Opers { get; set; } + } +} \ No newline at end of file diff --git a/cmpctircd/Configuration/Options/TlsOptions.cs b/cmpctircd/Configuration/Options/TlsOptions.cs new file mode 100644 index 0000000..19de6b5 --- /dev/null +++ b/cmpctircd/Configuration/Options/TlsOptions.cs @@ -0,0 +1,6 @@ +namespace cmpctircd.Configuration.Options { + public class TlsOptions { + public string File { get; set; } + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/cmpctircd/Configuration/Options/UMoodeOptions.cs b/cmpctircd/Configuration/Options/UMoodeOptions.cs new file mode 100644 index 0000000..589af55 --- /dev/null +++ b/cmpctircd/Configuration/Options/UMoodeOptions.cs @@ -0,0 +1,5 @@ +namespace cmpctircd.Configuration.Options { + public class UModeOptions { + public ModeElement[] UModes { get; set; } + } +} \ No newline at end of file diff --git a/cmpctircd/Controllers/OperController.cs b/cmpctircd/Controllers/OperController.cs index 9cc404b..4454d80 100644 --- a/cmpctircd/Controllers/OperController.cs +++ b/cmpctircd/Controllers/OperController.cs @@ -49,12 +49,12 @@ public bool OperHandler(HandlerArgs args) { } // Instantiate the algorithm through reflection, if not already instantiated. HashAlgorithm algorithm; - if(!_algorithms.TryGetValue(ircop.Algorithm, out algorithm)) - algorithm = _algorithms[ircop.Algorithm] = (ircop.Algorithm.GetConstructor(Type.EmptyTypes)?.Invoke(new object[0]) as HashAlgorithm) ?? throw new IrcErrNoOperHostException(client); + if(!_algorithms.TryGetValue(ircop.AlgorithmType, out algorithm)) + algorithm = _algorithms[ircop.AlgorithmType] = (ircop.AlgorithmType.GetConstructor(Type.EmptyTypes)?.Invoke(new object[0]) as HashAlgorithm) ?? throw new IrcErrNoOperHostException(client); // Hash the user's input to match the stored hash password in the config byte[] password = Encoding.UTF8.GetBytes(args.SpacedArgs[2]); byte[] builtHash = algorithm.ComputeHash(password); - if(builtHash.SequenceEqual(ircop.Password)) { + if(builtHash.SequenceEqual(ircop.PasswordArray)) { Channel channel; Topic topic; client.Modes["o"].Grant("", true, true); diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index b9804b6..c81e517 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -19,45 +19,42 @@ public class IRCd public static char[] lastUID = { }; public readonly IList Connectors = new List(); private readonly IList Listeners = new List(); - private IOptions _loggerOptions; - public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions socketOptions, IOptions loggerOptions) + public IRCd(Log log, IServiceProvider services, IOptions config) { - _loggerOptions = loggerOptions; Log = log; Config = config; - SocketOptions = socketOptions; // Interpret the ConfigData - SID = config.GetValue("SID"); - Host = config.GetValue("Host"); - Desc = config.GetValue("Description"); - Network = config.GetValue("Network"); + SID = config.Value.Sid; + Host = config.Value.Host; + Desc = config.Value.Description; + Network = config.Value.Network; if (SID == "auto") SID = GenerateSID(Host, Desc); - PingTimeout = config.GetValue("Advanced:PingTimeout"); - RequirePong = config.GetValue("Advanced:RequirePongCookie"); + PingTimeout = config.Value.Advanced.PingTimeout; + RequirePong = config.Value.Advanced.RequirePongCookie; - Loggers = _loggerOptions.Value.Loggers; + Loggers = config.Value.Loggers; - MaxTargets = config.GetValue("Advanced:MaxTargets"); - CloakKey = config.GetValue("Advanced:Cloak:Key"); - CloakFull = config.GetValue("Advanced:Cloak:Full"); - CloakPrefix = config.GetValue("Advanced:Cloak:Prefix"); - CloakDomainParts = config.GetValue("Advanced:Cloak:DomainParts"); - AutoModes = config.GetSection("Cmodes").Get>().ToDictionary(m => m.Name, m => m.Param); - AutoUModes = config.GetSection("Umodes").Get>().ToDictionary(m => m.Name, m => m.Param); - Opers = config.GetSection("Opers").Get>(); - OperChan = config.GetSection("OperChan").Get>(); + MaxTargets = config.Value.Advanced.MaxTargets; + CloakKey = config.Value.Advanced.Cloak.Key; + CloakFull = config.Value.Advanced.Cloak.Full; + CloakPrefix = config.Value.Advanced.Cloak.Prefix; + CloakDomainParts = config.Value.Advanced.Cloak.DomainParts; + AutoModes = config.Value.CModes.ToDictionary(m => m.Name, m => m.Param); + AutoUModes = config.Value.UModes.ToDictionary(m => m.Name, m => m.Param); + Opers = config.Value.Opers; + OperChan = config.Value.OperChan; PacketManager = new PacketManager(this, services); ChannelManager = new ChannelManager(this); // Create certificate refresh - if (config.GetValue("Tls") != null) + if (config.Value.Tls != null) Certificate = - new AutomaticCertificateCacheRefresh(new FileInfo(config.GetValue("Tls:File")), password: config.GetValue("Tls:Password")); + new AutomaticCertificateCacheRefresh(new FileInfo(config.Value.Tls.File), password: config.Value.Tls.Password); } public PacketManager PacketManager { get; } @@ -69,7 +66,7 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions< private IList UserModes { get; set; } public Log Log { get; } - public IConfiguration Config { get; } + public IOptions Config { get; } public IOptions SocketOptions { get; } public string SID { get; } public string Host { get; } @@ -102,8 +99,8 @@ public IRCd(Log log, IConfiguration config, IServiceProvider services, IOptions< public void Run() { Log.Info("==> Validating appsettings.json"); - var configurationValidator = new ConfigurationValidator(Config, SocketOptions, _loggerOptions); - var validationResult = configurationValidator.ValidateConfiguration(); + var configurationValidator = new ConfigurationOptionsValidator(); + var validationResult = configurationValidator.Validate(Config); if (!validationResult.IsValid) { Log.Error($"==> {string.Join("\n", validationResult.Errors.Select(e => e.ErrorMessage))}"); @@ -123,8 +120,8 @@ public void Run() PacketManager.Load(); - var sockets = SocketOptions.Value; - foreach (var listener in sockets.Sockets) + var sockets = Config.Value.Sockets; + foreach (var listener in sockets) { var sl = new SocketListener(this, listener); Log.Info( @@ -134,7 +131,7 @@ public void Run() sl.Bind(); } - foreach (var server in Config.GetSection("Servers").Get>()) + foreach (var server in Config.Value.Servers) if (server.Outbound) { // tag with outbound="true" @@ -290,7 +287,7 @@ public IList GetSupportedUModes(Client client) .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.Modes" && + t.Namespace == "cmpctircd.UModes" && t.BaseType.Equals(typeof(UserMode)) && !badClasses.Contains(t.Name) ); @@ -324,7 +321,7 @@ public IDictionary> GetSupportedModesByType() .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.Modes" && + t.Namespace == "cmpctircd.UModes" && t.BaseType.Equals(typeof(ChannelMode)) && !badClasses.Contains(t.Name) ); diff --git a/cmpctircd/PacketManager.cs b/cmpctircd/PacketManager.cs index 02c84e0..400348e 100644 --- a/cmpctircd/PacketManager.cs +++ b/cmpctircd/PacketManager.cs @@ -66,7 +66,7 @@ public bool Handle(String packet, IRCd ircd, object sender, HandlerArgs args, Li try { // Restrict the commands which non-registered (i.e. pre PONG, pre USER/NICK) users can execute - if ((client.State.Equals(ClientState.PreAuth) || (ircd.Config.GetValue("Advanced:ResolveHostnames") && client.ResolvingHost)) && !registrationCommands.Contains(packet.ToUpper())) { + if ((client.State.Equals(ClientState.PreAuth) || (ircd.Config.Value.Advanced.ResolveHostnames && client.ResolvingHost)) && !registrationCommands.Contains(packet.ToUpper())) { throw new IrcErrNotRegisteredException(client); } diff --git a/cmpctircd/Program.cs b/cmpctircd/Program.cs index b7fdd54..fc1db22 100644 --- a/cmpctircd/Program.cs +++ b/cmpctircd/Program.cs @@ -42,7 +42,10 @@ private static IHostBuilder CreateHostBuilder(string[] args) services.AddHostedService(); services.AddOptions().Bind(configuration); + services.AddOptions().Bind(configuration); services.AddOptions().Bind(configuration); + //services.AddOptions().Bind(configuration); + services.Configure(configuration); }); } } diff --git a/cmpctircd/Server/Server.cs b/cmpctircd/Server/Server.cs index 35abea1..4d486d3 100644 --- a/cmpctircd/Server/Server.cs +++ b/cmpctircd/Server/Server.cs @@ -143,7 +143,7 @@ public bool FindServerConfig(string hostname, string password) { } } else { // Find matching tag in config (or null) - link = IRCd.Config.GetSection("Servers").Get>().Where(s => s.Host == hostname + link = IRCd.Config.Value.Servers.Where(s => s.Host == hostname && s.Port == Listener.Info.Port && s.Tls == Listener.Info.Tls && s.Password == password).FirstOrDefault(); diff --git a/cmpctircd/Validation/ConfigurationOptionsValidator.cs b/cmpctircd/Validation/ConfigurationOptionsValidator.cs new file mode 100644 index 0000000..7699005 --- /dev/null +++ b/cmpctircd/Validation/ConfigurationOptionsValidator.cs @@ -0,0 +1,36 @@ +using cmpctircd.Configuration.Options; +using FluentValidation; +using Microsoft.Extensions.Options; + +namespace cmpctircd.Validation { + public class ConfigurationOptionsValidator : AbstractValidator> { + public ConfigurationOptionsValidator() { + RuleFor(c => c.Value.Host).NotEmpty(); + RuleFor(c => c.Value.Sid).NotEmpty(); + RuleFor(c => c.Value.OperChan).NotEmpty(); + RuleFor(c => c.Value.Advanced).SetValidator(new AdvancedOptionsValidator()); + RuleForEach(c => c.Value.Opers).NotEmpty().SetValidator(new OperatorElementValidator()); + RuleForEach(c => c.Value.Sockets).NotEmpty().SetValidator(new SocketElementValidator()); + RuleForEach(c => c.Value.Loggers).SetValidator(new LoggerElementValidator()); + RuleForEach(c => c.Value.UModes).SetValidator(new ModeElementValidator()); + RuleForEach(c => c.Value.CModes).SetValidator(new ModeElementValidator()); + RuleForEach(c => c.Value.Servers).NotEmpty().SetValidator(new ServerElementValidator()); + RuleFor(c => c.Value.Tls).SetValidator(new TlsOptionsValidator()); + } + } + + public class TlsOptionsValidator : AbstractValidator { + public TlsOptionsValidator() { + RuleFor(t => t.File).NotEmpty(); + } + } + + public class AdvancedOptionsValidator : AbstractValidator { + public AdvancedOptionsValidator() { + RuleFor(a => a.MaxTargets).NotEmpty(); + RuleFor(a => a.PingTimeout).NotEmpty(); + RuleFor(a => a.RequirePongCookie).NotNull(); + RuleFor(a => a.ResolveHostnames).NotNull(); + } + } +} \ No newline at end of file diff --git a/cmpctircd/Validation/ConfigurationValidator.cs b/cmpctircd/Validation/ConfigurationValidator.cs deleted file mode 100644 index a81970d..0000000 --- a/cmpctircd/Validation/ConfigurationValidator.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using cmpctircd.Configuration; -using cmpctircd.Configuration.Options; -using FluentValidation.Results; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Options; - -namespace cmpctircd.Validation { - public class ConfigurationValidator { - private readonly IConfiguration _config; - private readonly IOptions _socketOptions; - private IOptions _loggerOptions; - - public ConfigurationValidator(IConfiguration config, IOptions socketOptions, IOptions loggerOptions) { - _loggerOptions = loggerOptions; - _config = config; - _socketOptions = socketOptions; - } - - public ValidationResult ValidateConfiguration() { - var validators = new[] { - ValidateServerElement(), - ValidateSocketElement(), - ValidateOperatorElement(), - ValidateLoggerElement(), - ValidateModeElement(), - }; - - var result = new ValidationResult(); - - foreach (var validationResult in validators) { - result.Errors.AddRange(validationResult.Errors); - } - - return result; - } - - private ValidationResult ValidateLoggerElement() { - var validationResult = new ValidationResult(); - var loggers = _loggerOptions.Value.Loggers; - - var validator = new LoggerElementValidator(); - - foreach (var logger in loggers) { - var result = validator.Validate(logger); - validationResult.Errors.AddRange(result.Errors); - } - - return validationResult; - } - - private ValidationResult ValidateModeElement() { - var validationResult = new ValidationResult(); - - var modes = _config.GetSection("Cmodes").Get>(); - modes.AddRange(_config.GetSection("Umodes").Get>()); - - var validator = new ModeElementValidator(); - - foreach (var mode in modes) { - var result = validator.Validate(mode); - validationResult.Errors.AddRange(result.Errors); - } - - return validationResult; - } - - private ValidationResult ValidateOperatorElement() { - var validationResult = new ValidationResult(); - - var opers = _config.GetSection("Opers").Get>(); - - - var validator = new OperatorElementValidator(); - - foreach (var oper in opers) { - var result = validator.Validate(oper); - validationResult.Errors.AddRange(result.Errors); - } - - return validationResult; - } - - private ValidationResult ValidateServerElement() { - var validationResult = new ValidationResult(); - - var servers = _config.GetSection("Servers").Get>(); - - - var validator = new ServerElementValidator(); - - foreach (var server in servers) { - var result = validator.Validate(server); - validationResult.Errors.AddRange(result.Errors); - } - - return validationResult; - } - - private ValidationResult ValidateSocketElement() { - var validationResult = new ValidationResult(); - - var sockets = _socketOptions.Value; - - if (!sockets.Sockets.Any()) { - validationResult.Errors.Add(new ValidationFailure("Socket", "No socket configuration found")); - } - - - var validator = new SocketElementValidator(); - - foreach (var socket in sockets.Sockets) { - var result = validator.Validate(socket); - validationResult.Errors.AddRange(result.Errors); - } - - return validationResult; - } - } -} \ No newline at end of file diff --git a/cmpctircd/Validation/SocketElementValidator.cs b/cmpctircd/Validation/SocketElementValidator.cs index b5c1645..d10982a 100644 --- a/cmpctircd/Validation/SocketElementValidator.cs +++ b/cmpctircd/Validation/SocketElementValidator.cs @@ -11,10 +11,10 @@ namespace cmpctircd.Validation public class SocketElementValidator : AbstractValidator { public SocketElementValidator() { - RuleFor(s => s.Tls).NotEmpty(); + RuleFor(s => s.Tls).NotNull(); RuleFor(s => s.Host).NotEmpty(); RuleFor(s => s.Port).NotEmpty(); - RuleFor(s => s.Type).NotEmpty(); + RuleFor(s => s.Type).NotNull(); } } } diff --git a/cmpctircd/appsettings.json b/cmpctircd/appsettings.json index 1fe874c..13bc601 100644 --- a/cmpctircd/appsettings.json +++ b/cmpctircd/appsettings.json @@ -119,7 +119,7 @@ { "type": "InspIRCd20", "host": "services.cmpct.info", - "masks": "*@127.0.0.1", + "masks": [ "*@127.0.0.1" ], "port": 9000, "password": "mypassword", "tls": false @@ -133,7 +133,7 @@ "password": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", "algorithm": "System.Security.Cryptography.SHA256Managed", "tls": false, - "hosts": "*@127.0.0.1" + "hosts": [ "*@127.0.0.1" ] } ], "operChan": [ From 318208d7935731ac8d5dc9e5dd96bd62022e2a91 Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 20:11:45 +0100 Subject: [PATCH 21/24] Configuration: Fix renamind bugs task: #9 --- cmpctircd/Channel/Channel.cs | 2 +- cmpctircd/Client/Client.cs | 2 +- cmpctircd/IRCd.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmpctircd/Channel/Channel.cs b/cmpctircd/Channel/Channel.cs index 34d1b32..81730f3 100644 --- a/cmpctircd/Channel/Channel.cs +++ b/cmpctircd/Channel/Channel.cs @@ -34,7 +34,7 @@ public Channel(ChannelManager manager, IRCd ircd) { .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.UModes" && + t.Namespace == "cmpctircd.Modes" && t.BaseType.Equals(typeof(ChannelMode)) && !badClasses.Contains(t.Name) ); diff --git a/cmpctircd/Client/Client.cs b/cmpctircd/Client/Client.cs index 2b74f52..d5bfbfe 100755 --- a/cmpctircd/Client/Client.cs +++ b/cmpctircd/Client/Client.cs @@ -65,7 +65,7 @@ public Client(IRCd ircd, TcpClient tc, SocketListener sl, Stream stream, string .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.UModes" && + t.Namespace == "cmpctircd.Modes" && t.BaseType.Equals(typeof(UserMode)) && !except.Contains(t) ); diff --git a/cmpctircd/IRCd.cs b/cmpctircd/IRCd.cs index c81e517..cd34d68 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -287,7 +287,7 @@ public IList GetSupportedUModes(Client client) .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.UModes" && + t.Namespace == "cmpctircd.Modes" && t.BaseType.Equals(typeof(UserMode)) && !badClasses.Contains(t.Name) ); @@ -321,7 +321,7 @@ public IDictionary> GetSupportedModesByType() .SelectMany(t => t.GetTypes()) .Where( t => t.IsClass && - t.Namespace == "cmpctircd.UModes" && + t.Namespace == "cmpctircd.Modes" && t.BaseType.Equals(typeof(ChannelMode)) && !badClasses.Contains(t.Name) ); From 02cfd345e41b4aca8d9fba8259d5c4242c1315df Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 20:33:40 +0100 Subject: [PATCH 22/24] Configuration: Remove unused code task: #9 --- cmpctircd/Configuration/OperatorElement.cs | 27 ---------------------- cmpctircd/Program.cs | 1 - 2 files changed, 28 deletions(-) diff --git a/cmpctircd/Configuration/OperatorElement.cs b/cmpctircd/Configuration/OperatorElement.cs index cc069ae..c46d2d8 100644 --- a/cmpctircd/Configuration/OperatorElement.cs +++ b/cmpctircd/Configuration/OperatorElement.cs @@ -28,31 +28,4 @@ public byte[] ConvertPassword(string value) { .ToArray(); } } - - [TypeConverter(typeof(HexadecimalConverter))] - internal class HexadecimalConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { - return sourceType == typeof(string); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - // From hex string to byte[] - // https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array - var hex = (string) value; - return Enumerable.Range(0, hex.Length) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) - .ToArray(); - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { - return destinationType == typeof(string); - } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, - Type destinationType) { - // From byte[] to hex string - return string.Concat(Array.ConvertAll((byte[]) value, x => x.ToString("X2"))); - } - } } \ No newline at end of file diff --git a/cmpctircd/Program.cs b/cmpctircd/Program.cs index fc1db22..913391b 100644 --- a/cmpctircd/Program.cs +++ b/cmpctircd/Program.cs @@ -44,7 +44,6 @@ private static IHostBuilder CreateHostBuilder(string[] args) services.AddOptions().Bind(configuration); services.AddOptions().Bind(configuration); services.AddOptions().Bind(configuration); - //services.AddOptions().Bind(configuration); services.Configure(configuration); }); } From 60b974d9d83b43b772ce041a53145866f64112cd Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Sun, 23 May 2021 21:11:23 +0100 Subject: [PATCH 23/24] Configuration: Add correct assembly for cryptography types task: #9 --- cmpctircd/Configuration/OperatorElement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmpctircd/Configuration/OperatorElement.cs b/cmpctircd/Configuration/OperatorElement.cs index c46d2d8..85376e3 100644 --- a/cmpctircd/Configuration/OperatorElement.cs +++ b/cmpctircd/Configuration/OperatorElement.cs @@ -15,7 +15,7 @@ public class OperatorElement : ConfigurationElement { public bool Tls { get; set; } public List Hosts { get; set; } - public Type AlgorithmType => Type.GetType(Algorithm); + public Type AlgorithmType => Type.GetType($"{Algorithm}, System.Security.Cryptography.Algorithms"); public byte[] PasswordArray => ConvertPassword(Password); public byte[] ConvertPassword(string value) { From 033dbc879be44fcd76a8a2fabbe53361b8803fc3 Mon Sep 17 00:00:00 2001 From: Josh Oxenham <18144362+joshoxe@users.noreply.github.com> Date: Wed, 8 Dec 2021 13:12:45 +0000 Subject: [PATCH 24/24] Configuration: Fix broken OPER command by requiring an operChan channel to be set --- cmpctircd/Validation/ConfigurationOptionsValidator.cs | 1 + cmpctircd/appsettings.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmpctircd/Validation/ConfigurationOptionsValidator.cs b/cmpctircd/Validation/ConfigurationOptionsValidator.cs index 7699005..1cad4b0 100644 --- a/cmpctircd/Validation/ConfigurationOptionsValidator.cs +++ b/cmpctircd/Validation/ConfigurationOptionsValidator.cs @@ -8,6 +8,7 @@ public ConfigurationOptionsValidator() { RuleFor(c => c.Value.Host).NotEmpty(); RuleFor(c => c.Value.Sid).NotEmpty(); RuleFor(c => c.Value.OperChan).NotEmpty(); + RuleForEach(c => c.Value.OperChan).NotEmpty(); RuleFor(c => c.Value.Advanced).SetValidator(new AdvancedOptionsValidator()); RuleForEach(c => c.Value.Opers).NotEmpty().SetValidator(new OperatorElementValidator()); RuleForEach(c => c.Value.Sockets).NotEmpty().SetValidator(new SocketElementValidator()); diff --git a/cmpctircd/appsettings.json b/cmpctircd/appsettings.json index 13bc601..3bd9ed7 100644 --- a/cmpctircd/appsettings.json +++ b/cmpctircd/appsettings.json @@ -137,6 +137,6 @@ } ], "operChan": [ - "" + "#opers" ] } \ No newline at end of file