Removed NioTSO client and server

- NioTSO client isn't needed because we're using RayLib
- Added FreeSO's API server to handle most backend operations
This commit is contained in:
Tony Bark 2024-05-01 02:55:43 -04:00
parent f12ba1502b
commit 22191ce648
591 changed files with 53264 additions and 3362 deletions

View file

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace FSO.Server.Protocol.Utils
{
public class ChallengeResponse
{
private static int _salt_byte_length = 320;
private static int _nonce_byte_length = 32;
private static RNGCryptoServiceProvider _random_crypt_prov = new RNGCryptoServiceProvider();
private static Random _random_gen = new Random();
private static int _min_iteration_count = 4000;
private static int _max_iteration_count = 5000;
private const int _sha_output_length = 20;
private static byte[] ComputeHMACHash(byte[] data, string secret)
{
using (var _hmac_sha_1 = new HMACSHA1(data, true))
{
byte[] _hash_bytes = _hmac_sha_1.ComputeHash(Encoding.UTF8.GetBytes(secret));
return _hash_bytes;
}
}
public static string AnswerChallenge(string challenge, string secret)
{
var components = new Dictionary<string, string>();
var parts = challenge.Split(',');
foreach(var part in parts){
var subParts = part.Split(new char[] { '=' }, 2);
components.Add(subParts[0], subParts[1]);
}
var iterations = int.Parse(components["i"]);
var salt = components["s"];
var salted = Hi(secret, Convert.FromBase64String(salt), iterations);
return Convert.ToBase64String(ComputeHMACHash(salted, secret));
}
public static string GetChallenge()
{
var numIterations = GetRandomInteger();
return "i=" + numIterations + ",s=" + Convert.ToBase64String(GetRandomByteArray(_nonce_byte_length));
}
private static byte[] Hi(string password, byte[] salt, int iteration_count)
{
Rfc2898DeriveBytes _pdb = new Rfc2898DeriveBytes(password, salt, iteration_count);
return _pdb.GetBytes(_sha_output_length);
}
private static int GetRandomInteger()
{
if (_random_gen == null)
_random_gen = new Random();
int _random_int = _random_gen.Next(_min_iteration_count, _max_iteration_count);
if (_random_int < 0)
_random_int *= -1;
return _random_int;
}
private static byte[] GetRandomByteArray(int byte_array_length)
{
byte[] _random_byte_array = new byte[byte_array_length];
_random_crypt_prov.GetBytes(_random_byte_array);
return _random_byte_array;
}
}
}

View file

@ -0,0 +1,160 @@
using System;
using Mina.Core.Buffer;
using Mina.Core.Session;
using Mina.Filter.Codec;
namespace FSO.Server.Protocol.Utils
{
/// <summary>
/// A <see cref="IProtocolDecoder"/> that cumulates the content of received buffers to a
/// cumulative buffer to help users implement decoders.
/// </summary>
public abstract class CustomCumulativeProtocolDecoder : ProtocolDecoderAdapter
{
private readonly AttributeKey BUFFER = new AttributeKey(typeof(CustomCumulativeProtocolDecoder), "buffer");
/// <summary>
/// </summary>
protected CustomCumulativeProtocolDecoder()
{ }
/// <inheritdoc/>
public override void Decode(IoSession session, IoBuffer input, IProtocolDecoderOutput output)
{
if (!session.TransportMetadata.HasFragmentation)
{
while (input.HasRemaining)
{
if (!DoDecode(session, input, output))
break;
}
return;
}
Boolean usingSessionBuffer = true;
IoBuffer buf = session.GetAttribute<IoBuffer>(BUFFER);
// If we have a session buffer, append data to that; otherwise
// use the buffer read from the network directly.
if (buf == null)
{
buf = input;
usingSessionBuffer = false;
}
else
{
Boolean appended = false;
// Make sure that the buffer is auto-expanded.
if (buf.AutoExpand)
{
try
{
buf.Put(input);
appended = true;
}
catch (InvalidOperationException)
{
// A user called derivation method (e.g. slice()),
// which disables auto-expansion of the parent buffer.
}
catch (OverflowException)
{
// A user disabled auto-expansion.
}
}
if (appended)
{
buf.Flip();
}
else
{
// Reallocate the buffer if append operation failed due to
// derivation or disabled auto-expansion.
buf.Flip();
IoBuffer newBuf = IoBuffer.Allocate(buf.Remaining + input.Remaining);
newBuf.AutoExpand = true;
newBuf.Order = buf.Order;
newBuf.Put(buf);
newBuf.Put(input);
newBuf.Flip();
buf = newBuf;
// Update the session attribute.
session.SetAttribute(BUFFER, buf);
}
}
while (true)
{
Int32 oldPos = buf.Position;
Boolean decoded = DoDecode(session, buf, output);
if (decoded)
{
if (buf.Position == oldPos)
throw new InvalidOperationException("DoDecode() can't return true when buffer is not consumed.");
if (!buf.HasRemaining)
break;
}
else
{
break;
}
}
// if there is any data left that cannot be decoded, we store
// it in a buffer in the session and next time this decoder is
// invoked the session buffer gets appended to
if (buf.HasRemaining)
{
if (usingSessionBuffer && buf.AutoExpand)
{
buf.Compact();
}
else
{
StoreRemainingInSession(buf, session);
}
}
else
{
if (usingSessionBuffer)
{
RemoveSessionBuffer(session);
}
}
}
/// <summary>
/// Implement this method to consume the specified cumulative buffer and decode its content into message(s).
/// </summary>
/// <param name="session"></param>
/// <param name="input"></param>
/// <param name="output"></param>
/// <returns>
/// true if and only if there's more to decode in the buffer
/// and you want to have DoDecode method invoked again.
/// Return false if remaining data is not enough to decode,
/// then this method will be invoked again when more data is cumulated.
/// </returns>
protected abstract Boolean DoDecode(IoSession session, IoBuffer input, IProtocolDecoderOutput output);
private void RemoveSessionBuffer(IoSession session)
{
session.RemoveAttribute(BUFFER);
}
private void StoreRemainingInSession(IoBuffer buf, IoSession session)
{
IoBuffer remainingBuf = IoBuffer.Allocate(buf.Capacity);
remainingBuf.AutoExpand = true;
remainingBuf.Order = buf.Order;
remainingBuf.Put(buf);
session.SetAttribute(BUFFER, remainingBuf);
}
}
}

View file

@ -0,0 +1,618 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Common.Logging;
using Mina.Core.Buffer;
using Mina.Core.Filterchain;
using Mina.Core.Future;
using Mina.Core.Session;
using Mina.Core.Write;
namespace FSO.Server.Protocol.Utils
{
public class CustomSslFilter : IoFilterAdapter
{
static readonly ILog log = LogManager.GetLogger(typeof(CustomSslFilter));
private static readonly AttributeKey NEXT_FILTER = new AttributeKey(typeof(CustomSslFilter), "nextFilter");
private static readonly AttributeKey SSL_HANDLER = new AttributeKey(typeof(CustomSslFilter), "handler");
X509Certificate _serverCertificate = null;
SslProtocols _sslProtocol = SslProtocols.Default;
public CustomSslFilter(String certFile)
: this(X509Certificate.CreateFromCertFile(certFile))
{ }
public CustomSslFilter(X509Certificate cert)
{
_serverCertificate = cert;
}
public SslProtocols SslProtocol
{
get { return _sslProtocol; }
set { _sslProtocol = value; }
}
public X509Certificate Certificate
{
get { return _serverCertificate; }
}
/// <summary>
/// Returns <code>true</code> if and only if the specified session is
/// encrypted/decrypted over SSL/TLS currently.
/// </summary>
public Boolean IsSslStarted(IoSession session)
{
SslHandler handler = session.GetAttribute<SslHandler>(SSL_HANDLER);
return handler != null && handler.Authenticated;
}
/// <inheritdoc/>
public override void OnPreAdd(IoFilterChain parent, String name, INextFilter nextFilter)
{
if (parent.Contains<CustomSslFilter>())
throw new InvalidOperationException("Only one SSL filter is permitted in a chain.");
IoSession session = parent.Session;
session.SetAttribute(NEXT_FILTER, nextFilter);
// Create a SSL handler and start handshake.
SslHandler handler = new SslHandler(this, session);
session.SetAttribute(SSL_HANDLER, handler);
}
/// <inheritdoc/>
public override void OnPostAdd(IoFilterChain parent, String name, INextFilter nextFilter)
{
SslHandler handler = GetSslSessionHandler(parent.Session);
handler.Handshake(nextFilter);
}
/// <inheritdoc/>
public override void OnPreRemove(IoFilterChain parent, String name, INextFilter nextFilter)
{
IoSession session = parent.Session;
session.RemoveAttribute(NEXT_FILTER);
session.RemoveAttribute(SSL_HANDLER);
}
/// <inheritdoc/>
public override void SessionClosed(INextFilter nextFilter, IoSession session)
{
SslHandler handler = GetSslSessionHandler(session);
try
{
// release resources
handler.Destroy();
}
finally
{
// notify closed session
base.SessionClosed(nextFilter, session);
}
}
/// <inheritdoc/>
public override void MessageReceived(INextFilter nextFilter, IoSession session, Object message)
{
IoBuffer buf = (IoBuffer)message;
SslHandler handler = GetSslSessionHandler(session);
// forward read encrypted data to SSL handler
handler.MessageReceived(nextFilter, buf);
}
/// <inheritdoc/>
public override void MessageSent(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
EncryptedWriteRequest encryptedWriteRequest = writeRequest as EncryptedWriteRequest;
if (encryptedWriteRequest == null)
{
// ignore extra buffers used for handshaking
}
else
{
base.MessageSent(nextFilter, session, encryptedWriteRequest.InnerRequest);
}
}
/// <inheritdoc/>
public override void ExceptionCaught(INextFilter nextFilter, IoSession session, Exception cause)
{
base.ExceptionCaught(nextFilter, session, cause);
}
/// <inheritdoc/>
public override void FilterWrite(INextFilter nextFilter, IoSession session, IWriteRequest writeRequest)
{
SslHandler handler = GetSslSessionHandler(session);
handler.ScheduleFilterWrite(nextFilter, writeRequest);
}
/// <inheritdoc/>
public override void FilterClose(INextFilter nextFilter, IoSession session)
{
SslHandler handler = session.GetAttribute<SslHandler>(SSL_HANDLER);
if (handler == null)
{
// The connection might already have closed, or
// SSL might have not started yet.
base.FilterClose(nextFilter, session);
return;
}
IWriteFuture future = null;
try
{
future = InitiateClosure(handler, nextFilter, session);
future.Complete += (s, e) => base.FilterClose(nextFilter, session);
}
finally
{
if (future == null)
base.FilterClose(nextFilter, session);
}
}
private IWriteFuture InitiateClosure(SslHandler handler, INextFilter nextFilter, IoSession session)
{
IWriteFuture future = DefaultWriteFuture.NewWrittenFuture(session);
handler.Destroy();
return future;
}
private SslHandler GetSslSessionHandler(IoSession session)
{
SslHandler handler = session.GetAttribute<SslHandler>(SSL_HANDLER);
if (handler == null)
throw new InvalidOperationException();
if (handler.SslFilter != this)
throw new ArgumentException("Not managed by this filter.");
return handler;
}
public static void DisplaySecurityLevel(SslStream stream)
{
log.DebugFormat("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength);
log.DebugFormat("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength);
log.DebugFormat("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
log.DebugFormat("Protocol: {0}", stream.SslProtocol);
}
public static void DisplaySecurityServices(SslStream stream)
{
log.DebugFormat("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer);
log.DebugFormat("IsSigned: {0}", stream.IsSigned);
log.DebugFormat("Is Encrypted: {0}", stream.IsEncrypted);
}
public static void DisplayStreamProperties(SslStream stream)
{
log.DebugFormat("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite);
log.DebugFormat("Can timeout: {0}", stream.CanTimeout);
}
public static void DisplayCertificateInformation(SslStream stream)
{
log.DebugFormat("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus);
X509Certificate localCertificate = stream.LocalCertificate;
if (stream.LocalCertificate != null)
{
log.DebugFormat("Local cert was issued to {0} and is valid from {1} until {2}.",
localCertificate.Subject,
localCertificate.GetEffectiveDateString(),
localCertificate.GetExpirationDateString());
}
else
{
log.DebugFormat("Local certificate is null.");
}
// Display the properties of the client's certificate.
X509Certificate remoteCertificate = stream.RemoteCertificate;
if (stream.RemoteCertificate != null)
{
log.DebugFormat("Remote cert was issued to {0} and is valid from {1} until {2}.",
remoteCertificate.Subject,
remoteCertificate.GetEffectiveDateString(),
remoteCertificate.GetExpirationDateString());
}
else
{
log.DebugFormat("Remote certificate is null.");
}
}
internal class EncryptedWriteRequest : WriteRequestWrapper
{
private readonly IoBuffer _encryptedMessage;
public EncryptedWriteRequest(IWriteRequest writeRequest, IoBuffer encryptedMessage)
: base(writeRequest)
{
_encryptedMessage = encryptedMessage;
}
public override Object Message
{
get { return _encryptedMessage; }
}
}
}
class SslHandler : IDisposable
{
static readonly ILog log = LogManager.GetLogger(typeof(CustomSslFilter));
private readonly CustomSslFilter _sslFilter;
private readonly IoSession _session;
private readonly IoSessionStream _sessionStream;
private readonly SslStream _sslStream;
private volatile Boolean _authenticated;
private readonly ConcurrentQueue<IoFilterEvent> _preHandshakeEventQueue = new ConcurrentQueue<IoFilterEvent>();
private INextFilter _currentNextFilter;
private IWriteRequest _currentWriteRequest;
public SslHandler(CustomSslFilter sslFilter, IoSession session)
{
_sslFilter = sslFilter;
_session = session;
_sessionStream = new IoSessionStream(this);
_sslStream = new SslStream(_sessionStream, false);
}
public CustomSslFilter SslFilter
{
get { return _sslFilter; }
}
public Boolean Authenticated
{
get { return _authenticated; }
private set
{
_authenticated = value;
if (value)
FlushPreHandshakeEvents();
}
}
public void Dispose()
{
_sessionStream.Dispose();
_sslStream.Dispose();
}
public void Handshake(INextFilter nextFilter)
{
//lock (_sessionStream)
//{
_currentNextFilter = nextFilter;
_sslStream.BeginAuthenticateAsClient("auth.east.ea.com", null, _sslFilter.SslProtocol, false, AuthenticateCallback, null);
//}
}
private void AuthenticateCallback(IAsyncResult ar)
{
try
{
_sslStream.EndAuthenticateAsClient(ar);
}
catch (AuthenticationException e)
{
_sslFilter.ExceptionCaught(_currentNextFilter, _session, e);
return;
}
catch (IOException e)
{
_sslFilter.ExceptionCaught(_currentNextFilter, _session, e);
return;
}
Authenticated = true;
if (log.IsDebugEnabled)
{
// Display the properties and settings for the authenticated stream.
CustomSslFilter.DisplaySecurityLevel(_sslStream);
CustomSslFilter.DisplaySecurityServices(_sslStream);
CustomSslFilter.DisplayCertificateInformation(_sslStream);
CustomSslFilter.DisplayStreamProperties(_sslStream);
}
}
public void ScheduleFilterWrite(INextFilter nextFilter, IWriteRequest writeRequest)
{
if (!_authenticated)
{
if (_session.Connected)
{
// Handshake not complete yet.
_preHandshakeEventQueue.Enqueue(new IoFilterEvent(nextFilter, IoEventType.Write, _session, writeRequest));
}
return;
}
IoBuffer buf = (IoBuffer)writeRequest.Message;
if (buf.Remaining == 0) { return; }
//lock (_sessionStream)
//{
ArraySegment<Byte> array = buf.GetRemaining();
_currentNextFilter = nextFilter;
_currentWriteRequest = writeRequest;
// SSL encrypt
_sslStream.Write(array.Array, array.Offset, array.Count);
_sslStream.Flush();
//}
}
public void MessageReceived(INextFilter nextFilter, IoBuffer buf)
{
//lock (_sessionStream)
//{
_currentNextFilter = nextFilter;
_sessionStream.Write(buf);
if (_authenticated)
{
IoBuffer readBuffer = ReadBuffer();
nextFilter.MessageReceived(_session, readBuffer);
}
//}
}
public void Destroy()
{
_sslStream.Close();
IoFilterEvent scheduledWrite;
while (_preHandshakeEventQueue.TryDequeue(out scheduledWrite))
{ }
}
private void FlushPreHandshakeEvents()
{
lock (_sessionStream)
{
IoFilterEvent scheduledWrite;
while (_preHandshakeEventQueue.TryDequeue(out scheduledWrite))
{
_sslFilter.FilterWrite(scheduledWrite.NextFilter, scheduledWrite.Session, (IWriteRequest)scheduledWrite.Parameter);
}
}
}
private void WriteBuffer(IoBuffer buf)
{
IWriteRequest writeRequest;
if (_authenticated)
writeRequest = new CustomSslFilter.EncryptedWriteRequest(_currentWriteRequest, buf);
else
writeRequest = new DefaultWriteRequest(buf);
_currentNextFilter.FilterWrite(_session, writeRequest);
}
private IoBuffer ReadBuffer()
{
IoBuffer buf = IoBuffer.Allocate(_sessionStream.Remaining);
while (true)
{
ArraySegment<Byte> array = buf.GetRemaining();
Int32 bytesRead = _sslStream.Read(array.Array, array.Offset, array.Count);
if (bytesRead <= 0)
break;
buf.Position += bytesRead;
if (_sessionStream.Remaining == 0)
break;
else
{
// We have to grow the target buffer, it's too small.
buf.Capacity += _sessionStream.Remaining;
buf.Limit = buf.Capacity;
}
}
buf.Flip();
return buf;
}
class IoSessionStream : System.IO.Stream
{
readonly Object _syncRoot = new Byte[0];
readonly SslHandler _sslHandler;
readonly IoBuffer _buf;
volatile Boolean _closed;
volatile Boolean _released;
IOException _exception;
public IoSessionStream(SslHandler sslHandler)
{
_sslHandler = sslHandler;
_buf = IoBuffer.Allocate(16);
_buf.AutoExpand = true;
_buf.Limit = 0;
}
public override Int32 ReadByte()
{
lock (_syncRoot)
{
if (!WaitForData())
return 0;
return _buf.Get() & 0xff;
}
}
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
{
lock (_syncRoot)
{
if (!WaitForData())
return 0;
Int32 readBytes = Math.Min(count, _buf.Remaining);
_buf.Get(buffer, offset, readBytes);
return readBytes;
}
}
public override void Close()
{
base.Close();
if (_closed)
return;
lock (_syncRoot)
{
_closed = true;
ReleaseBuffer();
Monitor.PulseAll(_syncRoot);
}
}
public override void Write(Byte[] buffer, Int32 offset, Int32 count)
{
_sslHandler.WriteBuffer(IoBuffer.Wrap((Byte[])buffer.Clone(), offset, count));
}
public override void WriteByte(Byte value)
{
IoBuffer buf = IoBuffer.Allocate(1);
buf.Put(value);
buf.Flip();
_sslHandler.WriteBuffer(buf);
}
public override void Flush()
{ }
public void Write(IoBuffer buf)
{
if (_closed)
return;
lock (_syncRoot)
{
if (_buf.HasRemaining)
{
_buf.Compact().Put(buf).Flip();
}
else
{
_buf.Clear().Put(buf).Flip();
Monitor.PulseAll(_syncRoot);
}
}
}
private Boolean WaitForData()
{
if (_released)
return false;
lock (_syncRoot)
{
while (!_released && _buf.Remaining == 0 && _exception == null)
{
try
{
Monitor.Wait(_syncRoot);
}
catch (ThreadInterruptedException e)
{
throw new IOException("Interrupted while waiting for more data", e);
}
}
}
if (_exception != null)
{
ReleaseBuffer();
throw _exception;
}
if (_closed && _buf.Remaining == 0)
{
ReleaseBuffer();
return false;
}
return true;
}
private void ReleaseBuffer()
{
if (_released)
return;
_released = true;
}
public IOException Exception
{
set
{
if (_exception == null)
{
lock (_syncRoot)
{
_exception = value;
Monitor.PulseAll(_syncRoot);
}
}
}
}
public Int32 Remaining
{
get { return _buf.Remaining; }
}
public override Boolean CanRead
{
get { return true; }
}
public override Boolean CanSeek
{
get { return false; }
}
public override Boolean CanWrite
{
get { return true; }
}
public override Int64 Length
{
get { throw new NotSupportedException(); }
}
public override Int64 Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override Int64 Seek(Int64 offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(Int64 value)
{
throw new NotSupportedException();
}
}
}
}