ModernUO/Projects/UOContent/Accounting/Firewall.cs
Kamron Batman 92aae8d482
fix(core): Adds IPv6 support (#348)
## Breaking Change
* IP Matching no longer supports `?` (e.g. you cannot do: "192.16?.0.1")
* IP Matching no longer supports `*` and other values in the same section (e.g. you cannot do "192.1\*.0.1")
  * To do "192.1\*.0.1", you should use the range option with three separate entries: "192.100-199.0.1", "192.1.0.1", "192.10-19.0.1"

## Non-Breaking Changes
- [X] Adds IPv6 support (not for servers though, just clients)
- [X] Adds interning support for IPv4 mapped to IPv6
- [X] Updates IPv4ToAddress (don't use this unless you know it is IPv4 or IPv4 mapped to IPv6)

## Note:
UO Does not support IPv6 for the server. To support an IPv6 server IP you will need to use CUO or some kind of custom client and probably modify it accordingly.

Bumps release version
2020-12-19 00:27:39 -08:00

293 lines
7.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
namespace Server
{
public static class Firewall
{
static Firewall()
{
List = new List<IFirewallEntry>();
var path = "firewall.cfg";
if (File.Exists(path))
{
using var ip = new StreamReader(path);
string line;
while ((line = ip.ReadLine()) != null)
{
line = line.Trim();
if (line.Length == 0)
{
continue;
}
List.Add(ToFirewallEntry(line));
/*
object toAdd;
IPAddress addr;
if (IPAddress.TryParse( line, out addr ))
toAdd = addr;
else
toAdd = line;
m_Blocked.Add( toAdd.ToString() );
* */
}
}
}
public static List<IFirewallEntry> List { get; }
public static IFirewallEntry ToFirewallEntry(object entry)
{
return entry switch
{
IFirewallEntry firewallEntry => firewallEntry,
IPAddress address => new IPFirewallEntry(address),
string s => ToFirewallEntry(s),
_ => null
};
}
public static IFirewallEntry ToFirewallEntry(string entry)
{
if (IPAddress.TryParse(entry, out var addr))
{
return new IPFirewallEntry(addr);
}
// Try CIDR parse
var str = entry?.Split('/');
if (str?.Length == 2)
{
if (IPAddress.TryParse(str[0], out var cidrPrefix))
{
if (int.TryParse(str[1], out var cidrLength))
{
return new CIDRFirewallEntry(cidrPrefix, cidrLength);
}
}
}
return new WildcardIPFirewallEntry(entry);
}
public static void RemoveAt(int index)
{
List.RemoveAt(index);
Save();
}
public static void Remove(object obj)
{
var entry = ToFirewallEntry(obj);
if (entry != null)
{
List.Remove(entry);
Save();
}
}
public static void Add(object obj)
{
if (obj is IPAddress address)
{
Add(address);
}
else if (obj is string s)
{
Add(s);
}
else if (obj is IFirewallEntry entry)
{
Add(entry);
}
}
public static void Add(IFirewallEntry entry)
{
if (!List.Contains(entry))
{
List.Add(entry);
}
Save();
}
public static void Add(string pattern)
{
var entry = ToFirewallEntry(pattern);
if (!List.Contains(entry))
{
List.Add(entry);
}
Save();
}
public static void Add(IPAddress ip)
{
IFirewallEntry entry = new IPFirewallEntry(ip);
if (!List.Contains(entry))
{
List.Add(entry);
}
Save();
}
public static void Save()
{
var path = "firewall.cfg";
using var op = new StreamWriter(path);
for (var i = 0; i < List.Count; ++i)
{
op.WriteLine(List[i]);
}
}
public static bool IsBlocked(IPAddress ip)
{
for (var i = 0; i < List.Count; i++)
{
if (List[i].IsBlocked(ip))
{
return true;
}
}
return false;
}
public interface IFirewallEntry
{
bool IsBlocked(IPAddress address);
}
public class IPFirewallEntry : IFirewallEntry
{
private readonly IPAddress m_Address;
public IPFirewallEntry(IPAddress address) => m_Address = address;
public bool IsBlocked(IPAddress address) => m_Address.Equals(address);
public override string ToString() => m_Address.ToString();
public override bool Equals(object obj)
{
if (obj is IPAddress)
{
return obj.Equals(m_Address);
}
if (obj is string s)
{
if (IPAddress.TryParse(s, out var otherAddress))
{
return otherAddress.Equals(m_Address);
}
}
else if (obj is IPFirewallEntry entry)
{
return m_Address.Equals(entry.m_Address);
}
return false;
}
public override int GetHashCode() => m_Address.GetHashCode();
}
public class CIDRFirewallEntry : IFirewallEntry
{
private readonly int m_CIDRLength;
private readonly IPAddress m_CIDRPrefix;
public CIDRFirewallEntry(IPAddress cidrPrefix, int cidrLength)
{
m_CIDRPrefix = cidrPrefix;
m_CIDRLength = cidrLength;
}
public bool IsBlocked(IPAddress address) => Utility.IPMatchCIDR(m_CIDRPrefix, address, m_CIDRLength);
public override string ToString() => $"{m_CIDRPrefix}/{m_CIDRLength}";
public override bool Equals(object obj)
{
if (obj is string entry)
{
var str = entry.Split('/');
if (str.Length == 2)
{
if (IPAddress.TryParse(str[0], out var cidrPrefix))
{
if (int.TryParse(str[1], out var cidrLength))
{
return m_CIDRPrefix.Equals(cidrPrefix) && m_CIDRLength.Equals(cidrLength);
}
}
}
}
else if (obj is CIDRFirewallEntry cidrEntry)
{
return m_CIDRPrefix.Equals(cidrEntry.m_CIDRPrefix) && m_CIDRLength.Equals(cidrEntry.m_CIDRLength);
}
return false;
}
public override int GetHashCode() => m_CIDRPrefix.GetHashCode() ^ m_CIDRLength.GetHashCode();
}
public class WildcardIPFirewallEntry : IFirewallEntry
{
private readonly string m_Entry;
private bool m_Valid;
public WildcardIPFirewallEntry(string entry) => m_Entry = entry;
public bool IsBlocked(IPAddress address)
{
if (!m_Valid)
{
return false; // Why process if it's invalid? it'll return false anyway after processing it.
}
var matched = Utility.IPMatch(m_Entry, address, out var valid);
m_Valid = valid;
return matched;
}
public override string ToString() => m_Entry;
public override bool Equals(object obj)
{
if (obj is string)
{
return obj.Equals(m_Entry);
}
return obj is WildcardIPFirewallEntry entry && m_Entry == entry.m_Entry;
}
public override int GetHashCode() => m_Entry.GetHashCode(StringComparison.Ordinal);
}
}
}