using System; using System.Text; using System.Collections; using Server.Accounting; using Server.Network; namespace Server.RemoteAdmin { public class AdminNetwork { private const string ProtocolVersion = "2"; private static ArrayList m_Auth = new ArrayList(); private static bool m_NewLine = true; private static StringBuilder m_ConsoleData = new StringBuilder(); private const string DateFormat = "MMMM dd hh:mm:ss.f tt"; public static void Configure() { PacketHandlers.Register( 0xF1, 0, false, OnReceive ); #if !MONO Core.MultiConsoleOut.Add( new EventTextWriter( OnConsoleChar, OnConsoleLine, OnConsoleString ) ); #endif Timer.DelayCall( TimeSpan.FromMinutes( 2.5 ), TimeSpan.FromMinutes( 2.5 ), CleanUp ); } public static void OnConsoleString( string str ) { string outStr; if ( m_NewLine ) { outStr = $"[{DateTime.UtcNow.ToString(DateFormat)}]: {str}"; m_NewLine = false; } else { outStr = str; } m_ConsoleData.Append( outStr ); RoughTrimConsoleData(); SendToAll( outStr ); } public static void OnConsoleChar( char ch ) { if ( m_NewLine ) { string outStr; outStr = $"[{DateTime.UtcNow.ToString(DateFormat)}]: {ch}"; m_ConsoleData.Append( outStr ); SendToAll( outStr ); m_NewLine = false; } else { m_ConsoleData.Append( ch ); SendToAll( ch ); } RoughTrimConsoleData(); } public static void OnConsoleLine( string line ) { string outStr; if ( m_NewLine ) outStr = $"[{DateTime.UtcNow.ToString(DateFormat)}]: {line}{Console.Out.NewLine}"; else outStr = $"{line}{Console.Out.NewLine}"; m_ConsoleData.Append( outStr ); RoughTrimConsoleData(); SendToAll( outStr ); m_NewLine = true; } static void SendToAll( string outStr ) { SendToAll( new ConsoleData( outStr ) ); } static void SendToAll( char ch ) { SendToAll( new ConsoleData( ch ) ); } static void SendToAll( ConsoleData packet ) { packet.Acquire(); for ( int i = 0; i < m_Auth.Count; i++ ) ((NetState)m_Auth[i]).Send( packet ); packet.Release(); } static void RoughTrimConsoleData() { if ( m_ConsoleData.Length >= 4096 ) m_ConsoleData.Remove( 0, 2048 ); } static void TightTrimConsoleData() { if ( m_ConsoleData.Length > 1024 ) m_ConsoleData.Remove( 0, m_ConsoleData.Length - 1024 ); } public static void OnReceive( NetState state, PacketReader pvSrc ) { byte cmd = pvSrc.ReadByte(); if ( cmd == 0x02 ) { Authenticate( state, pvSrc ); } else if ( cmd == 0xFE ) { state.Send( new CompactServerInfo() ); state.Dispose(); } else if ( cmd == 0xFF ) { string statStr = $", Name={Misc.ServerList.ServerName}, Age={(int) (DateTime.UtcNow - Items.Clock.ServerStart).TotalHours}, Clients={NetState.Instances.Count}, Items={World.Items.Count}, Chars={World.Mobiles.Count}, Mem={(int) (GC.GetTotalMemory(false) / 1024)}K, Ver={ProtocolVersion}"; state.Send( new UOGInfo( statStr ) ); state.Dispose(); } else if ( !IsAuth( state ) ) { Console.WriteLine( "ADMIN: Unauthorized packet from {0}, disconnecting", state ); Disconnect( state ); } else { if ( !RemoteAdminHandlers.Handle( cmd, state, pvSrc ) ) Disconnect( state ); } } private static void DelayedDisconnect( NetState state ) { Timer.DelayCall( TimeSpan.FromSeconds( 15.0 ), new TimerStateCallback( Disconnect ), state ); } private static void Disconnect( object state ) { m_Auth.Remove( state ); ((NetState)state).Dispose(); } public static void Authenticate( NetState state, PacketReader pvSrc ) { string user = pvSrc.ReadString( 30 ); string pw = pvSrc.ReadString( 30 ); if ( !(Accounts.GetAccount( user ) is Account a) ) { state.Send( new Login( LoginResponse.NoUser ) ); Console.WriteLine( "ADMIN: Invalid username '{0}' from {1}", user, state ); DelayedDisconnect( state ); } else if ( !a.HasAccess( state ) ) { state.Send( new Login( LoginResponse.BadIP ) ); Console.WriteLine( "ADMIN: Access to '{0}' from {1} denied.", user, state ); DelayedDisconnect( state ); } else if ( !a.CheckPassword( pw ) ) { state.Send( new Login( LoginResponse.BadPass ) ); Console.WriteLine( "ADMIN: Invalid password for user '{0}' from {1}", user, state ); DelayedDisconnect( state ); } else if ( a.AccessLevel < AccessLevel.Administrator || a.Banned ) { Console.WriteLine( "ADMIN: Account '{0}' does not have admin access. Connection Denied.", user ); state.Send( new Login( LoginResponse.NoAccess ) ); DelayedDisconnect( state ); } else { Console.WriteLine( "ADMIN: Access granted to '{0}' from {1}", user, state ); state.Account = a; a.LogAccess( state ); a.LastLogin = DateTime.UtcNow; state.Send( new Login( LoginResponse.OK ) ); TightTrimConsoleData(); state.Send( Compress( new ConsoleData( m_ConsoleData.ToString() ) ) ); m_Auth.Add( state ); } } public static bool IsAuth( NetState state ) { return m_Auth.Contains( state ); } private static void CleanUp() {//remove dead instances from m_Auth ArrayList list = new ArrayList(); for (int i=0;i 100 && length < 60000 ) { byte[] dest = new byte[(int)(length * 1.001) + 10]; int destSize = dest.Length; ZLibError error = Compression.Pack( dest, ref destSize, source, length, ZLibQuality.Default ); if ( error != ZLibError.Okay ) { Console.WriteLine( "WARNING: Unable to compress admin packet, zlib error: {0}", error ); return p; } return new AdminCompressedPacket( dest, destSize, length ); } return p; } } public class EventTextWriter : System.IO.TextWriter { public delegate void OnConsoleChar( char ch ); public delegate void OnConsoleLine( string line ); public delegate void OnConsoleStr( string str ); private OnConsoleChar m_OnChar; private OnConsoleLine m_OnLine; private OnConsoleStr m_OnStr; public EventTextWriter( OnConsoleChar onChar, OnConsoleLine onLine, OnConsoleStr onStr ) { m_OnChar = onChar; m_OnLine = onLine; m_OnStr = onStr; } public override void Write( char ch ) { m_OnChar?.Invoke( ch ); } public override void Write( string str ) { m_OnStr?.Invoke( str ); } public override void WriteLine( string line ) { m_OnLine?.Invoke( line ); } public override Encoding Encoding => Encoding.ASCII; } }