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/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..d5bfbfe 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.Value.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.Value.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.Value.Advanced.ResolveHostnames) { // If the IRCd resolves all hostnames, then we will have // delayed calling SendWelcome until DNS resolution was complete SendWelcome(); @@ -252,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/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..b6dfbb8 100644 --- a/cmpctircd/Configuration/LoggerElement.cs +++ b/cmpctircd/Configuration/LoggerElement.cs @@ -1,36 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.Collections.Generic; using System.Configuration; - -namespace cmpctircd.Configuration { - public class LoggerElement : ConfigurationElement { - private readonly Dictionary _attributes = new Dictionary(); - - // Guid, due to a lack of other unique properties for this element type. - public Guid InstanceGuid { - get; - } = Guid.NewGuid(); - - 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; - } +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace cmpctircd.Configuration +{ + public class LoggerElement + { + public LoggerType Type { get; set; } + public LogType Level { get; set; } + public Dictionary Attributes { get; set; } = new Dictionary(); } -} +} \ 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..85376e3 100644 --- a/cmpctircd/Configuration/OperatorElement.cs +++ b/cmpctircd/Configuration/OperatorElement.cs @@ -1,70 +1,31 @@ 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 { - [ConfigurationProperty("name", IsRequired = true, IsKey = true)] - public string Name { - get { return (string)this["name"]; } - set { this["name"] = value; } - } + 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 string Password { get; set; } - [TypeConverter(typeof(TypeNameConverter))] - [ConfigurationProperty("algorithm", IsRequired = true)] - public Type Algorithm { - get { return this["algorithm"] as Type; } - set { this["algorithm"] = value; } - } + public string 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; } + public Type AlgorithmType => Type.GetType($"{Algorithm}, System.Security.Cryptography.Algorithms"); + public byte[] PasswordArray => ConvertPassword(Password); - [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 { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { - return sourceType == typeof(string); - } - - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { + 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 = (string) value; + var hex = 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"))); + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) + .ToArray(); } } -} +} \ 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/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/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/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/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 diff --git a/cmpctircd/Configuration/Options/SocketOptions.cs b/cmpctircd/Configuration/Options/SocketOptions.cs new file mode 100644 index 0000000..5c87b70 --- /dev/null +++ b/cmpctircd/Configuration/Options/SocketOptions.cs @@ -0,0 +1,5 @@ +namespace cmpctircd.Configuration { + public class SocketOptions { + public SocketElement[] Sockets { 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/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..f8cd5ea 100644 --- a/cmpctircd/Configuration/SocketElement.cs +++ b/cmpctircd/Configuration/SocketElement.cs @@ -1,81 +1,38 @@ -using System; -using System.ComponentModel; -using System.Configuration; -using System.Globalization; +using System.ComponentModel; using System.Net; +using System.Text.Json.Serialization; -namespace cmpctircd.Configuration { - public class SocketElement : ConfigurationElement { - [ConfigurationProperty("type", IsRequired = true)] - public ListenerType Type { - get { return (ListenerType) this["type"]; } - set { this["type"] = value; } - } +namespace cmpctircd.Configuration +{ + public class SocketElement + { + public ListenerType Type { get; set; } - [TypeConverter(typeof(IPAddressConverter))] - [ConfigurationProperty("host", IsRequired = true)] - public IPAddress Host { - get { return (IPAddress) this["host"]; } - set { this["host"] = value; } - } + public string Host { get; set; } - [ConfigurationProperty("port", IsRequired = true)] - [IntegerValidator(MinValue = 0, MaxValue = 65535, ExcludeRange = false)] - public int Port { - get { return (int) this["port"]; } - set { this["port"] = value; } - } + public int Port { get; set; } - public IPEndPoint EndPoint { - get { return new IPEndPoint(Host, Port); } - } + public bool Tls { get; set; } - [ConfigurationProperty("tls", IsRequired = false, DefaultValue = false)] - public bool IsTls { - get { return (bool) this["tls"]; } - set { this["tls"] = value; } - } + public ServerType Protocol { get; set; } - [ConfigurationProperty("protocol", IsRequired = false)] - public ServerType Protocol { - get { return (ServerType) this["protocol"]; } - set { this["protocol"] = value; } - } + public IPEndPoint EndPoint => new IPEndPoint(IPAddress.Parse(Host), Port); - public static implicit operator SocketElement(ServerElement serverElement) { + 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.Host = IP.ToString(); se.Port = serverElement.Port; - se.IsTls = serverElement.IsTls; + se.Tls = serverElement.Tls; se.Type = ListenerType.Server; return se; } } - - [TypeConverter(typeof(IPAddressConverter))] - 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/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/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 41f2bd6..cd34d68 100644 --- a/cmpctircd/IRCd.cs +++ b/cmpctircd/IRCd.cs @@ -1,16 +1,62 @@ -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.Configuration.Options; +using cmpctircd.Modes; +using cmpctircd.Validation; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +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, IServiceProvider services, IOptions config) + { + Log = log; + Config = config; + + // Interpret the ConfigData + 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.Value.Advanced.PingTimeout; + RequirePong = config.Value.Advanced.RequirePongCookie; + + Loggers = config.Value.Loggers; + + 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.Value.Tls != null) + Certificate = + new AutomaticCertificateCacheRefresh(new FileInfo(config.Value.Tls.File), password: config.Value.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 +66,12 @@ public class IRCd { private IList UserModes { get; set; } public Log Log { get; } - public CmpctConfigurationSection Config { get; } + public IOptions Config { get; } + public IOptions SocketOptions { 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 +88,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 +95,246 @@ 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; + public void Run() + { + Log.Info("==> Validating appsettings.json"); + var configurationValidator = new ConfigurationOptionsValidator(); + var validationResult = configurationValidator.Validate(Config); - if (SID == "auto") { - SID = IRCd.GenerateSID(Host, Desc); + if (!validationResult.IsValid) { + Log.Error($"==> {string.Join("\n", validationResult.Errors.Select(e => e.ErrorMessage))}"); + return; } - 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() { 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" )})"); + var sockets = Config.Value.Sockets; + foreach (var listener in sockets) + { + 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.Value.Servers) + 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 +364,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 +445,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 +460,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 +479,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..1fe1c44 100644 --- a/cmpctircd/IrcApplicationLifecycle.cs +++ b/cmpctircd/IrcApplicationLifecycle.cs @@ -1,4 +1,9 @@ -namespace cmpctircd { +using System.Collections.Generic; +using cmpctircd.Configuration.Options; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +namespace cmpctircd { using System; using System.Linq; using System.Threading; @@ -10,11 +15,13 @@ 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; + private IOptions _loggerOptions; - public IrcApplicationLifecycle(IRCd ircd, Log log, CmpctConfigurationSection 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)); @@ -36,7 +43,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, _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/PacketManager.cs b/cmpctircd/PacketManager.cs index 902e176..400348e 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.Value.Advanced.ResolveHostnames && client.ResolvingHost)) && !registrationCommands.Contains(packet.ToUpper())) { throw new IrcErrNotRegisteredException(client); } diff --git a/cmpctircd/Program.cs b/cmpctircd/Program.cs index 5d9c0af..913391b 100644 --- a/cmpctircd/Program.cs +++ b/cmpctircd/Program.cs @@ -1,35 +1,51 @@ -namespace cmpctircd { - using System; - using System.Linq; - using cmpctircd.Configuration; - using cmpctircd.Controllers; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; +using System; +using System.IO; +using System.Linq; +using cmpctircd.Configuration; +using cmpctircd.Configuration.Options; +using cmpctircd.Controllers; +using Microsoft.Extensions.Configuration; +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 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))) { + 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(); services.AddScoped(sp => sp.GetRequiredService().Sender as Client); services.AddScoped(sp => sp.GetRequiredService().Sender as Server); services.AddHostedService(); + + services.AddOptions().Bind(configuration); + services.AddOptions().Bind(configuration); + services.AddOptions().Bind(configuration); + services.Configure(configuration); }); } } - } +} \ No newline at end of file diff --git a/cmpctircd/Server/Server.cs b/cmpctircd/Server/Server.cs index 1480d6d..4d486d3 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.Value.Servers.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..e12df42 100644 --- a/cmpctircd/SocketConnector.cs +++ b/cmpctircd/SocketConnector.cs @@ -35,14 +35,14 @@ 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; } - 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..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); } @@ -43,7 +44,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 +69,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/Validation/ConfigurationOptionsValidator.cs b/cmpctircd/Validation/ConfigurationOptionsValidator.cs new file mode 100644 index 0000000..1cad4b0 --- /dev/null +++ b/cmpctircd/Validation/ConfigurationOptionsValidator.cs @@ -0,0 +1,37 @@ +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(); + 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()); + 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/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/Validation/ModeElementValidator.cs b/cmpctircd/Validation/ModeElementValidator.cs new file mode 100644 index 0000000..98bdabf --- /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().MaximumLength(1); + } + } +} 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(); + } + } +} diff --git a/cmpctircd/Validation/ServerElementValidator.cs b/cmpctircd/Validation/ServerElementValidator.cs new file mode 100644 index 0000000..738b061 --- /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().ExclusiveBetween(0, 65535); + RuleFor(s => s.Password).NotEmpty(); + } + } +} diff --git a/cmpctircd/Validation/SocketElementValidator.cs b/cmpctircd/Validation/SocketElementValidator.cs new file mode 100644 index 0000000..d10982a --- /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).NotNull(); + RuleFor(s => s.Host).NotEmpty(); + RuleFor(s => s.Port).NotEmpty(); + RuleFor(s => s.Type).NotNull(); + } + } +} diff --git a/cmpctircd/appsettings.json b/cmpctircd/appsettings.json new file mode 100644 index 0000000..3bd9ed7 --- /dev/null +++ b/cmpctircd/appsettings.json @@ -0,0 +1,142 @@ +{ + "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": "" + }, + // + // + "loggers": [ + { + "type": "Stdout", + "level": "Debug" + }, + { + "type": "File", + "level": "Warn", + "attributes": { + "path": "ircd.log" + } + } + // + // + // + //{ + // "type": "IRC", + // "level": "Debug", + // "attributes": { + // "channel": "#debug", + // "modes": "+nz" + // } + + //} + ], + "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": [ + "#opers" + ] +} \ No newline at end of file diff --git a/cmpctircd/cmpctircd.csproj b/cmpctircd/cmpctircd.csproj index c2d2f05..4677ebb 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 @@ -47,7 +49,6 @@ - PreserveNewest @@ -57,4 +58,9 @@ PreserveNewest + + + Always + +