This commit is contained in:
mark 2006-06-15 04:14:30 +00:00
commit 47711d616e
2644 changed files with 479454 additions and 0 deletions

View file

@ -0,0 +1,46 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using Server;
using Server.Misc;
namespace Server
{
public class AccessRestrictions
{
public static void Initialize()
{
EventSink.SocketConnect += new SocketConnectEventHandler( EventSink_SocketConnect );
}
private static void EventSink_SocketConnect( SocketConnectEventArgs e )
{
try
{
IPAddress ip = ((IPEndPoint)e.Socket.RemoteEndPoint).Address;
if ( Firewall.IsBlocked( ip ) )
{
Console.WriteLine( "Client: {0}: Firewall blocked connection attempt.", ip );
e.AllowConnection = false;
return;
}
else if ( IPLimiter.SocketBlock && !IPLimiter.Verify( ip ) )
{
Console.WriteLine( "Client: {0}: Past IP limit threshold", ip );
using ( StreamWriter op = new StreamWriter( "ipLimits.log", true ) )
op.WriteLine( "{0}\tPast IP limit threshold\t{1}", ip, DateTime.Now );
e.AllowConnection = false;
return;
}
}
catch
{
e.AllowConnection = false;
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Server;
using Server.Network;
namespace Server.Accounting
{
public class AccountAttackLimiter
{
public static bool Enabled = true;
public static void Initialize()
{
if ( !Enabled )
return;
RegisterThrottler( 0x80 );
RegisterThrottler( 0x91 );
RegisterThrottler( 0xCF );
}
public static void RegisterThrottler( int packetID )
{
PacketHandler ph = PacketHandlers.GetHandler( packetID );
if ( ph == null )
return;
ph.ThrottleCallback = new ThrottlePacketCallback( Throttle_Callback );
}
public static bool Throttle_Callback( NetState ns )
{
InvalidAccountAccessLog accessLog = FindAccessLog( ns );
if ( accessLog == null )
return true;
return ( DateTime.Now >= (accessLog.LastAccessTime + ComputeThrottle( accessLog.Counts )) );
}
private static List<InvalidAccountAccessLog> m_List = new List<InvalidAccountAccessLog>();
public static InvalidAccountAccessLog FindAccessLog( NetState ns )
{
if ( ns == null )
return null;
IPAddress ipAddress = ns.Address;
for ( int i = 0; i < m_List.Count; ++i )
{
InvalidAccountAccessLog accessLog = m_List[i];
if ( accessLog.HasExpired )
m_List.RemoveAt( i-- );
else if ( accessLog.Address.Equals( ipAddress ) )
return accessLog;
}
return null;
}
public static void RegisterInvalidAccess( NetState ns )
{
if ( ns == null || !Enabled )
return;
InvalidAccountAccessLog accessLog = FindAccessLog( ns );
if ( accessLog == null )
m_List.Add( accessLog = new InvalidAccountAccessLog( ns.Address ) );
accessLog.Counts += 1;
accessLog.RefreshAccessTime();
}
public static TimeSpan ComputeThrottle( int counts )
{
if ( counts >= 15 )
return TimeSpan.FromMinutes( 5.0 );
if ( counts >= 10 )
return TimeSpan.FromMinutes( 1.0 );
if ( counts >= 5 )
return TimeSpan.FromSeconds( 20.0 );
if ( counts >= 3 )
return TimeSpan.FromSeconds( 10.0 );
if ( counts >= 1 )
return TimeSpan.FromSeconds( 2.0 );
return TimeSpan.Zero;
}
}
public class InvalidAccountAccessLog
{
private IPAddress m_Address;
private DateTime m_LastAccessTime;
private int m_Counts;
public IPAddress Address
{
get{ return m_Address; }
set{ m_Address = value; }
}
public DateTime LastAccessTime
{
get{ return m_LastAccessTime; }
set{ m_LastAccessTime = value; }
}
public bool HasExpired
{
get{ return ( DateTime.Now >= ( m_LastAccessTime + TimeSpan.FromHours( 1.0 ) ) ); }
}
public int Counts
{
get{ return m_Counts; }
set{ m_Counts = value; }
}
public void RefreshAccessTime()
{
m_LastAccessTime = DateTime.Now;
}
public InvalidAccountAccessLog( IPAddress address )
{
m_Address = address;
RefreshAccessTime();
}
}
}

View file

@ -0,0 +1,77 @@
using System;
using System.Xml;
namespace Server.Accounting
{
public class AccountComment
{
private string m_AddedBy;
private string m_Content;
private DateTime m_LastModified;
/// <summary>
/// A string representing who added this comment.
/// </summary>
public string AddedBy
{
get{ return m_AddedBy; }
}
/// <summary>
/// Gets or sets the body of this comment. Setting this value will reset LastModified.
/// </summary>
public string Content
{
get{ return m_Content; }
set{ m_Content = value; m_LastModified = DateTime.Now; }
}
/// <summary>
/// The date and time when this account was last modified -or- the comment creation time, if never modified.
/// </summary>
public DateTime LastModified
{
get{ return m_LastModified; }
}
/// <summary>
/// Constructs a new AccountComment instance.
/// </summary>
/// <param name="addedBy">Initial AddedBy value.</param>
/// <param name="content">Initial Content value.</param>
public AccountComment( string addedBy, string content )
{
m_AddedBy = addedBy;
m_Content = content;
m_LastModified = DateTime.Now;
}
/// <summary>
/// Deserializes an AccountComment instance from an xml element.
/// </summary>
/// <param name="node">The XmlElement instance from which to deserialize.</param>
public AccountComment( XmlElement node )
{
m_AddedBy = Utility.GetAttribute( node, "addedBy", "empty" );
m_LastModified = Utility.GetDateTime( Utility.GetAttribute( node, "lastModified" ), DateTime.Now );
m_Content = Utility.GetText( node, "" );
}
/// <summary>
/// Serializes this AccountComment instance to an XmlTextWriter.
/// </summary>
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
public void Save( XmlTextWriter xml )
{
xml.WriteStartElement( "comment" );
xml.WriteAttributeString( "addedBy", m_AddedBy );
xml.WriteAttributeString( "lastModified", XmlConvert.ToString( m_LastModified, XmlDateTimeSerializationMode.Local ) );
xml.WriteString( m_Content );
xml.WriteEndElement();
}
}
}

View file

@ -0,0 +1,396 @@
using System;
using System.IO;
using System.Net;
using Server;
using Server.Network;
using Server.Accounting;
using Server.Engines.Help;
using System.Collections;
using Server.Commands;
namespace Server.Misc
{
public enum PasswordProtection
{
None,
Crypt,
NewCrypt
}
public class AccountHandler
{
private static int MaxAccountsPerIP = 1;
private static bool AutoAccountCreation = true;
private static bool RestrictDeletion = !TestCenter.Enabled;
private static TimeSpan DeleteDelay = TimeSpan.FromDays( 7.0 );
public static PasswordProtection ProtectPasswords = PasswordProtection.NewCrypt;
private static AccessLevel m_LockdownLevel;
public static AccessLevel LockdownLevel
{
get{ return m_LockdownLevel; }
set{ m_LockdownLevel = value; }
}
private static CityInfo[] StartingCities = new CityInfo[]
{
new CityInfo( "Yew", "The Empath Abbey", 633, 858, 0 ),
new CityInfo( "Minoc", "The Barnacle", 2476, 413, 15 ),
new CityInfo( "Britain", "Sweet Dreams Inn", 1496, 1628, 10 ),
new CityInfo( "Moonglow", "The Scholars Inn", 4408, 1168, 0 ),
new CityInfo( "Trinsic", "The Traveler's Inn", 1845, 2745, 0 ),
new CityInfo( "Magincia", "The Great Horns Tavern", 3734, 2222, 20 ),
new CityInfo( "Jhelom", "The Mercenary Inn", 1374, 3826, 0 ),
new CityInfo( "Skara Brae", "The Falconer's Inn", 618, 2234, 0 ),
new CityInfo( "Vesper", "The Ironwood Inn", 2771, 976, 0 ),
new CityInfo( "Haven", "Buckler's Hideaway", 3667, 2625, 0 )
};
private static bool PasswordCommandEnabled = false;
public static void Initialize()
{
EventSink.DeleteRequest += new DeleteRequestEventHandler( EventSink_DeleteRequest );
EventSink.AccountLogin += new AccountLoginEventHandler( EventSink_AccountLogin );
EventSink.GameLogin += new GameLoginEventHandler( EventSink_GameLogin );
if ( PasswordCommandEnabled )
CommandSystem.Register( "Password", AccessLevel.Player, new CommandEventHandler( Password_OnCommand ) );
if ( Core.AOS )
{
CityInfo haven = new CityInfo( "Haven", "Uzeraan's Mansion", 3618, 2591, 0 );
StartingCities[StartingCities.Length - 1] = haven;
}
}
[Usage( "Password <newPassword> <repeatPassword>" )]
[Description( "Changes the password of the commanding players account. Requires the same C-class IP address as the account's creator." )]
public static void Password_OnCommand( CommandEventArgs e )
{
Mobile from = e.Mobile;
Account acct = from.Account as Account;
if ( acct == null )
return;
IPAddress[] accessList = acct.LoginIPs;
if ( accessList.Length == 0 )
return;
NetState ns = from.NetState;
if ( ns == null )
return;
if ( e.Length == 0 )
{
from.SendMessage( "You must specify the new password." );
return;
}
else if ( e.Length == 1 )
{
from.SendMessage( "To prevent potential typing mistakes, you must type the password twice. Use the format:" );
from.SendMessage( "Password \"(newPassword)\" \"(repeated)\"" );
return;
}
string pass = e.GetString( 0 );
string pass2 = e.GetString( 1 );
if ( pass != pass2 )
{
from.SendMessage( "The passwords do not match." );
return;
}
bool isSafe = true;
for ( int i = 0; isSafe && i < pass.Length; ++i )
isSafe = ( pass[i] >= 0x20 && pass[i] < 0x80 );
if ( !isSafe )
{
from.SendMessage( "That is not a valid password." );
return;
}
try
{
IPAddress ipAddress = ((IPEndPoint)ns.Socket.RemoteEndPoint).Address;
if ( Utility.IPMatchClassC( accessList[0], ipAddress ) )
{
acct.SetPassword( pass );
from.SendMessage( "The password to your account has changed." );
}
else
{
PageEntry entry = PageQueue.GetEntry( from );
if ( entry != null )
{
if ( entry.Message.StartsWith( "[Automated: Change Password]" ) )
from.SendMessage( "You already have a password change request in the help system queue." );
else
from.SendMessage( "Your IP address does not match that which created this account." );
}
else if ( PageQueue.CheckAllowedToPage( from ) )
{
from.SendMessage( "Your IP address does not match that which created this account. A page has been entered into the help system on your behalf." );
from.SendLocalizedMessage( 501234, "", 0x35 ); /* The next available Counselor/Game Master will respond as soon as possible.
* Please check your Journal for messages every few minutes.
*/
PageQueue.Enqueue( new PageEntry( from, String.Format( "[Automated: Change Password]<br>Desired password: {0}<br>Current IP address: {1}<br>Account IP address: {2}", pass, ipAddress, accessList[0] ), PageType.Account ) );
}
}
}
catch
{
}
}
private static void EventSink_DeleteRequest( DeleteRequestEventArgs e )
{
NetState state = e.State;
int index = e.Index;
Account acct = state.Account as Account;
if ( acct == null )
{
state.Dispose();
}
else if ( index < 0 || index >= acct.Length )
{
state.Send( new DeleteResult( DeleteResultType.BadRequest ) );
state.Send( new CharacterListUpdate( acct ) );
}
else
{
Mobile m = acct[index];
if ( m == null )
{
state.Send( new DeleteResult( DeleteResultType.CharNotExist ) );
state.Send( new CharacterListUpdate( acct ) );
}
else if ( m.NetState != null )
{
state.Send( new DeleteResult( DeleteResultType.CharBeingPlayed ) );
state.Send( new CharacterListUpdate( acct ) );
}
else if ( RestrictDeletion && DateTime.Now < (m.CreationTime + DeleteDelay) )
{
state.Send( new DeleteResult( DeleteResultType.CharTooYoung ) );
state.Send( new CharacterListUpdate( acct ) );
}
else
{
Console.WriteLine( "Client: {0}: Deleting character {1} (0x{2:X})", state, index, m.Serial.Value );
acct.Comments.Add( new AccountComment( "System", String.Format( "Character #{0} {1} deleted by {2}", index+1, m, state ) ) );
m.Delete();
state.Send( new CharacterListUpdate( acct ) );
}
}
}
public static bool CanCreate( IPAddress ip )
{
if ( IPTables[ip] == null ) //Sanity
return true;
return ((int)IPTables[ip] < MaxAccountsPerIP);
/*
if ( (int)IPTables[ip] >= MaxAccountsPerIP )
return false;
else
return true;
*/
}
private static Hashtable m_IPTables;
public static Hashtable IPTables
{
get
{
if ( m_IPTables == null )
{
m_IPTables = new Hashtable();
foreach ( Account a in Accounts.GetAccounts() )
{
if ( a.LoginIPs.Length > 0 )
{
IPAddress ip = a.LoginIPs[0];
if ( m_IPTables[ip] == null )
m_IPTables[ip] = 1;
else
m_IPTables[ip] = (int)m_IPTables[ip] + 1;
}
}
}
return m_IPTables;
}
}
private static Account CreateAccount( NetState state, string un, string pw )
{
if ( un.Length == 0 || pw.Length == 0 )
return null;
bool isSafe = true;
for ( int i = 0; isSafe && i < un.Length; ++i )
isSafe = ( un[i] >= 0x20 && un[i] < 0x80 );
for ( int i = 0; isSafe && i < pw.Length; ++i )
isSafe = ( pw[i] >= 0x20 && pw[i] < 0x80 );
if ( !isSafe )
return null;
if ( !CanCreate( state.Address ) )
{
Console.WriteLine( "Login: {0}: Account '{1}' not created, ip already has {2} account{3}.", state, un, MaxAccountsPerIP, MaxAccountsPerIP == 1 ? "" : "s" );
return null;
}
Console.WriteLine( "Login: {0}: Creating new account '{1}'", state, un );
Account a = new Account( un, pw );
return a;
}
public static void EventSink_AccountLogin( AccountLoginEventArgs e )
{
if ( !IPLimiter.SocketBlock && !IPLimiter.Verify( e.State.Address ) )
{
e.Accepted = false;
e.RejectReason = ALRReason.InUse;
Console.WriteLine( "Login: {0}: Past IP limit threshold", e.State );
using ( StreamWriter op = new StreamWriter( "ipLimits.log", true ) )
op.WriteLine( "{0}\tPast IP limit threshold\t{1}", e.State, DateTime.Now );
return;
}
string un = e.Username;
string pw = e.Password;
e.Accepted = false;
Account acct = Accounts.GetAccount( un ) as Account;
if ( acct == null )
{
if ( AutoAccountCreation && un.Trim().Length > 0 ) //To prevent someone from mkaing an account of just '' or a bunch of meaningless spaces
{
e.State.Account = acct = CreateAccount( e.State, un, pw );
e.Accepted = acct == null ? false : acct.CheckAccess( e.State );
if ( !e.Accepted )
e.RejectReason = ALRReason.BadComm;
}
else
{
Console.WriteLine( "Login: {0}: Invalid username '{1}'", e.State, un );
e.RejectReason = ALRReason.Invalid;
}
}
else if ( !acct.HasAccess( e.State ) )
{
Console.WriteLine( "Login: {0}: Access denied for '{1}'", e.State, un );
e.RejectReason = ( m_LockdownLevel > AccessLevel.Player ? ALRReason.BadComm : ALRReason.BadPass );
}
else if ( !acct.CheckPassword( pw ) )
{
Console.WriteLine( "Login: {0}: Invalid password for '{1}'", e.State, un );
e.RejectReason = ALRReason.BadPass;
}
else if ( acct.Banned )
{
Console.WriteLine( "Login: {0}: Banned account '{1}'", e.State, un );
e.RejectReason = ALRReason.Blocked;
}
else
{
Console.WriteLine( "Login: {0}: Valid credentials for '{1}'", e.State, un );
e.State.Account = acct;
e.Accepted = true;
acct.LogAccess( e.State );
}
if ( !e.Accepted )
AccountAttackLimiter.RegisterInvalidAccess( e.State );
}
public static void EventSink_GameLogin( GameLoginEventArgs e )
{
if ( !IPLimiter.SocketBlock && !IPLimiter.Verify( e.State.Address ) )
{
e.Accepted = false;
Console.WriteLine( "Login: {0}: Past IP limit threshold", e.State );
using ( StreamWriter op = new StreamWriter( "ipLimits.log", true ) )
op.WriteLine( "{0}\tPast IP limit threshold\t{1}", e.State, DateTime.Now );
return;
}
string un = e.Username;
string pw = e.Password;
Account acct = Accounts.GetAccount( un ) as Account;
if ( acct == null )
{
e.Accepted = false;
}
else if ( !acct.HasAccess( e.State ) )
{
Console.WriteLine( "Login: {0}: Access denied for '{1}'", e.State, un );
e.Accepted = false;
}
else if ( !acct.CheckPassword( pw ) )
{
Console.WriteLine( "Login: {0}: Invalid password for '{1}'", e.State, un );
e.Accepted = false;
}
else if ( acct.Banned )
{
Console.WriteLine( "Login: {0}: Banned account '{1}'", e.State, un );
e.Accepted = false;
}
else
{
acct.LogAccess( e.State );
Console.WriteLine( "Login: {0}: Account '{1}' at character list", e.State, un );
e.State.Account = acct;
e.Accepted = true;
e.CityInfo = StartingCities;
}
if ( !e.Accepted )
AccountAttackLimiter.RegisterInvalidAccess( e.State );
}
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Xml;
namespace Server.Accounting
{
public class AccountTag
{
private string m_Name, m_Value;
/// <summary>
/// Gets or sets the name of this tag.
/// </summary>
public string Name
{
get{ return m_Name; }
set{ m_Name = value; }
}
/// <summary>
/// Gets or sets the value of this tag.
/// </summary>
public string Value
{
get{ return m_Value; }
set{ m_Value = value; }
}
/// <summary>
/// Constructs a new AccountTag instance with a specific name and value.
/// </summary>
/// <param name="name">Initial name.</param>
/// <param name="value">Initial value.</param>
public AccountTag( string name, string value )
{
m_Name = name;
m_Value = value;
}
/// <summary>
/// Deserializes an AccountTag instance from an xml element.
/// </summary>
/// <param name="node">The XmlElement instance from which to deserialize.</param>
public AccountTag( XmlElement node )
{
m_Name = Utility.GetAttribute( node, "name", "empty" );
m_Value = Utility.GetText( node, "" );
}
/// <summary>
/// Serializes this AccountTag instance to an XmlTextWriter.
/// </summary>
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
public void Save( XmlTextWriter xml )
{
xml.WriteStartElement( "tag" );
xml.WriteAttributeString( "name", m_Name );
xml.WriteString( m_Value );
xml.WriteEndElement();
}
}
}

View file

@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml;
namespace Server.Accounting
{
public class Accounts
{
private static Dictionary<string, IAccount> m_Accounts = new Dictionary<string, IAccount>();
public static void Configure()
{
EventSink.WorldLoad += new WorldLoadEventHandler( Load );
EventSink.WorldSave += new WorldSaveEventHandler( Save );
}
static Accounts()
{
}
public static int Count { get { return m_Accounts.Count; } }
public static ICollection<IAccount> GetAccounts()
{
return m_Accounts.Values;
}
public static IAccount GetAccount( string username )
{
IAccount a;
m_Accounts.TryGetValue( username, out a );
return a;
}
public static void Add( IAccount a )
{
m_Accounts[a.Username] = a;
}
public static void Remove( string username )
{
m_Accounts.Remove( username );
}
public static void Load()
{
m_Accounts = new Dictionary<string, IAccount>( 32, StringComparer.OrdinalIgnoreCase );
string filePath = Path.Combine( "Saves/Accounts", "accounts.xml" );
if ( !File.Exists( filePath ) )
return;
XmlDocument doc = new XmlDocument();
doc.Load( filePath );
XmlElement root = doc["accounts"];
foreach ( XmlElement account in root.GetElementsByTagName( "account" ) )
{
try
{
Account acct = new Account( account );
}
catch
{
Console.WriteLine( "Warning: Account instance load failed" );
}
}
}
public static void Save( WorldSaveEventArgs e )
{
if ( !Directory.Exists( "Saves/Accounts" ) )
Directory.CreateDirectory( "Saves/Accounts" );
string filePath = Path.Combine( "Saves/Accounts", "accounts.xml" );
using ( StreamWriter op = new StreamWriter( filePath ) )
{
XmlTextWriter xml = new XmlTextWriter( op );
xml.Formatting = Formatting.Indented;
xml.IndentChar = '\t';
xml.Indentation = 1;
xml.WriteStartDocument( true );
xml.WriteStartElement( "accounts" );
xml.WriteAttributeString( "count", m_Accounts.Count.ToString() );
foreach ( Account a in GetAccounts() )
a.Save( xml );
xml.WriteEndElement();
xml.Close();
}
}
}
}

View file

@ -0,0 +1,131 @@
using System;
using System.Collections;
using System.IO;
using System.Net;
namespace Server
{
public class Firewall
{
private static ArrayList m_Blocked;
static Firewall()
{
m_Blocked = new ArrayList();
string path = "firewall.cfg";
if ( File.Exists( path ) )
{
using ( StreamReader ip = new StreamReader( path ) )
{
string line;
while ( (line = ip.ReadLine()) != null )
{
line = line.Trim();
if ( line.Length == 0 )
continue;
object toAdd;
IPAddress addr;
if( IPAddress.TryParse( line, out addr ) )
toAdd = addr;
else
toAdd = line;
m_Blocked.Add( toAdd.ToString() );
}
}
}
}
public static ArrayList List
{
get
{
return m_Blocked;
}
}
public static void RemoveAt( int index )
{
m_Blocked.RemoveAt( index );
Save();
}
public static void Remove( string pattern )
{
m_Blocked.Remove( pattern );
Save();
}
public static void Remove( IPAddress ip )
{
m_Blocked.Remove( ip );
Save();
}
public static void Add( object obj )
{
if ( !(obj is IPAddress) && !(obj is String) )
return;
if ( !m_Blocked.Contains( obj ) )
m_Blocked.Add( obj );
Save();
}
public static void Add( string pattern )
{
if ( !m_Blocked.Contains( pattern ) )
m_Blocked.Add( pattern );
Save();
}
public static void Add( IPAddress ip )
{
if ( !m_Blocked.Contains( ip ) )
m_Blocked.Add( ip );
Save();
}
public static void Save()
{
string path = "firewall.cfg";
using ( StreamWriter op = new StreamWriter( path ) )
{
for ( int i = 0; i < m_Blocked.Count; ++i )
op.WriteLine( m_Blocked[i] );
}
}
public static bool IsBlocked( IPAddress ip )
{
bool contains = false;
for ( int i = 0; !contains && i < m_Blocked.Count; ++i )
{
if ( m_Blocked[i] is IPAddress )
contains = ip.Equals( m_Blocked[i] );
else if ( m_Blocked[i] is String )
{
string s = (string)m_Blocked[i];
contains = Utility.IPMatchCIDR( s, ip );
if( !contains )
contains = Utility.IPMatch( s, ip );
}
}
return contains;
}
}
}

View file

@ -0,0 +1,59 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using Server;
using Server.Network;
namespace Server.Misc
{
public class IPLimiter
{
public static bool Enabled = true;
public static bool SocketBlock = true; // true to block at connection, false to block at login request
public const int MaxAddresses = 10;
public static IPAddress[] Exemptions = new IPAddress[] //For hosting services where there are cases where IPs can be proxied
{
//IPAddress.Parse( "127.0.0.1" ),
};
public static bool IsExempt( IPAddress ip )
{
for ( int i = 0; i < Exemptions.Length; i++ )
{
if ( ip.Equals( Exemptions[i] ) )
return true;
}
return false;
}
public static bool Verify( IPAddress ourAddress )
{
if ( !Enabled || IsExempt( ourAddress ) )
return true;
List<NetState> netStates = NetState.Instances;
int count = 0;
for ( int i = 0; i < netStates.Count; ++i )
{
NetState compState = (NetState)netStates[i];
if ( ourAddress.Equals( compState.Address ) )
{
++count;
if ( count > MaxAddresses )
return false;
}
}
return true;
}
}
}