1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-11 15:52:25 -05:00

RCon error handling is clearer

Show chat on mobile view of server overview
basic authentication
switched to extreme-ip-lookup for ip lookups (SSL)
This commit is contained in:
RaidMax
2018-04-04 14:38:34 -05:00
parent 071cab1ecf
commit 6ab37a6b6e
27 changed files with 476 additions and 123 deletions

View File

@ -975,6 +975,29 @@ namespace SharedLibrary.Commands
}
}
public class CSetPassword : Command
{
public CSetPassword() : base("setpassword", "set your authentication password", "sp", Player.Permission.Moderator, false, new CommandArgument[]
{
new CommandArgument()
{
Name = "password",
Required = true
}
})
{ }
public override async Task ExecuteAsync(Event E)
{
string[] hashedPassword = Helpers.Hashing.Hash(E.Data);
E.Origin.Password = hashedPassword[0];
E.Origin.PasswordSalt = hashedPassword[1];
await E.Owner.Manager.GetClientService().Update(E.Origin);
}
}
public class CKillServer : Command
{
public CKillServer() : base("killserver", "kill the game server", "kill", Player.Permission.Administrator, false)

View File

@ -36,6 +36,9 @@ namespace SharedLibrary.Database.Models
[ForeignKey("CurrentAliasId")]
public virtual EFAlias CurrentAlias { get; set; }
public string Password { get; set; }
public string PasswordSalt { get; set; }
[NotMapped]
public virtual string Name
{

View File

@ -0,0 +1,77 @@
using System;
using SimpleCrypto;
namespace SharedLibrary.Helpers
{
public class Hashing
{
/// <summary>
/// Generate password hash and salt
/// </summary>
/// <param name="password">plaintext password</param>
/// <returns></returns>
public static string[] Hash(string password, string saltStr = null)
{
string hash;
string salt;
var CryptoSvc = new PBKDF2();
// generate new hash
if (saltStr == null)
{
hash = CryptoSvc.Compute(password);
salt = CryptoSvc.Salt;
return new string[]
{
hash,
salt
};
}
else
{
hash = CryptoSvc.Compute(password, saltStr);
return new string[]
{
hash,
""
};
}
/*//https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/password-hashing
byte[] salt;
if (saltStr == null)
{
salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
}
else
{
salt = Convert.FromBase64String(saltStr);
}
// derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
return new string[]
{
hashed,
Convert.ToBase64String(salt)
};*/
}
}
}

View File

@ -11,10 +11,24 @@ namespace SharedLibrary.RCon
{
class ConnectionState
{
public Socket Client { get; set; }
public const int BufferSize = 8192;
public byte[] Buffer = new byte[BufferSize];
public readonly StringBuilder ResponseString = new StringBuilder();
public Socket Client { get; private set; }
public int BufferSize { get; private set; }
public byte[] Buffer { get; private set; }
private readonly StringBuilder sb;
public StringBuilder ResponseString
{
get => sb;
}
public ConnectionState(Socket cl)
{
BufferSize = 8192;
Buffer = new byte[BufferSize];
Client = cl;
sb = new StringBuilder();
}
}
public class Connection
@ -26,6 +40,7 @@ namespace SharedLibrary.RCon
int FailedSends;
int FailedReceives;
DateTime LastQuery;
string response;
ManualResetEvent OnConnected;
ManualResetEvent OnSent;
@ -92,14 +107,11 @@ namespace SharedLibrary.RCon
#if DEBUG
Log.WriteDebug($"Sent {sentByteNum} bytes to {ServerConnection.RemoteEndPoint}");
#endif
FailedSends = 0;
OnSent.Set();
}
catch (Exception e)
catch (SocketException)
{
FailedSends++;
Log.WriteWarning($"Could not send RCon data to server - {e.Message}");
}
}
@ -126,31 +138,33 @@ namespace SharedLibrary.RCon
}
else
{
FailedReceives = 0;
response = connectionState.ResponseString.ToString();
OnReceived.Set();
}
}
else
{
response = connectionState.ResponseString.ToString();
OnReceived.Set();
}
}
catch (Exception)
catch (SocketException)
{
FailedReceives++;
}
}
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
{
// will this really prevent flooding?
if ((DateTime.Now - LastQuery).TotalMilliseconds < 300)
if ((DateTime.Now - LastQuery).TotalMilliseconds < 150)
{
await Task.Delay(300);
LastQuery = DateTime.Now;
await Task.Delay(150);
}
LastQuery = DateTime.Now;
OnSent.Reset();
OnReceived.Reset();
string queryString = "";
@ -168,66 +182,103 @@ namespace SharedLibrary.RCon
byte[] payload = Encoding.Default.GetBytes(queryString);
retrySend:
try
{
retrySend:
ServerConnection.BeginSend(payload, 0, payload.Length, 0, new AsyncCallback(OnSentCallback), ServerConnection);
bool success = await Task.FromResult(OnSent.WaitOne(StaticHelpers.SocketTimeout));
if (!success)
{
FailedSends++;
#if DEBUG
Log.WriteDebug($"{FailedSends} failed sends to {ServerConnection.RemoteEndPoint.ToString()}");
#endif
if (FailedSends < 4)
goto retrySend;
else
throw new NetworkException($"Could not send data to server - {new SocketException((int)SocketError.TimedOut).Message}");
else if (FailedSends == 4)
Log.WriteError($"Failed to send data to {ServerConnection.RemoteEndPoint}");
}
else
{
if (FailedSends >= 4)
{
Log.WriteVerbose($"Resumed send RCon connection with {ServerConnection.RemoteEndPoint}");
FailedSends = 0;
}
}
}
catch (SocketException e)
{
// this result is normal if the server is not listening
if (e.HResult != (int)SocketError.ConnectionReset)
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
e.NativeErrorCode != (int)SocketError.TimedOut)
throw new NetworkException($"Unexpected error while sending data to server - {e.Message}");
}
var connectionState = new ConnectionState
{
Client = ServerConnection
};
var connectionState = new ConnectionState(ServerConnection);
retryReceive:
try
{
retryReceive:
ServerConnection.BeginReceive(connectionState.Buffer, 0, connectionState.Buffer.Length, 0,
new AsyncCallback(OnReceivedCallback), connectionState);
bool success = await Task.FromResult(OnReceived.WaitOne(StaticHelpers.SocketTimeout));
if (!success)
{
FailedReceives++;
#if DEBUG
Log.WriteDebug($"{FailedReceives} failed receives from {ServerConnection.RemoteEndPoint.ToString()}");
#endif
FailedReceives++;
if (FailedReceives < 4)
goto retryReceive;
goto retrySend;
else if (FailedReceives == 4)
Log.WriteError($"Failed to receive data from {ServerConnection.RemoteEndPoint}");
else
throw new NetworkException($"Could not receive data from the server - {new SocketException((int)SocketError.TimedOut).Message}");
{
Log.WriteError($"Failed to receive data from {ServerConnection.RemoteEndPoint} after {FailedReceives} tries");
}
if (FailedReceives >= 4)
{
throw new NetworkException($"Could not receive data from the {ServerConnection.RemoteEndPoint}");
}
}
else
{
if (FailedReceives >= 4)
{
Log.WriteVerbose($"Resumed receive RCon connection from {ServerConnection.RemoteEndPoint}");
FailedReceives = 0;
}
}
}
catch (SocketException e)
{
// this result is normal if the server is not listening
if (e.HResult != (int)SocketError.ConnectionReset)
if (e.NativeErrorCode != (int)SocketError.ConnectionReset &&
e.NativeErrorCode != (int)SocketError.TimedOut)
throw new NetworkException($"Unexpected error while receiving data from server - {e.Message}");
else if (FailedReceives < 4)
{
goto retryReceive;
}
else if (FailedReceives == 4)
{
Log.WriteError($"Failed to receive data from {ServerConnection.RemoteEndPoint} after {FailedReceives} tries");
}
if (FailedReceives >= 4)
{
throw new NetworkException(e.Message);
}
}
string queryResponse = connectionState.ResponseString.ToString();
string queryResponse = response;
if (queryResponse.Contains("Invalid password"))
throw new NetworkException("RCON password is invalid");

View File

@ -65,7 +65,7 @@ namespace SharedLibrary.Services
Masked = false,
NetworkId = entity.NetworkId,
AliasLink = aliasLink,
CurrentAlias = existingAlias
CurrentAlias = existingAlias,
};
context.Clients.Add(client);
@ -179,7 +179,9 @@ namespace SharedLibrary.Services
client.Connections = entity.Connections;
client.FirstConnection = entity.FirstConnection;
client.Masked = entity.Masked;
client.TotalConnectionTime = entity.TotalConnectionTime;
client.TotalConnectionTime = entity.TotalConnectionTime;
client.Password = entity.Password;
client.PasswordSalt = entity.PasswordSalt;
// update in database
await context.SaveChangesAsync();

View File

@ -173,6 +173,7 @@
<Compile Include="Exceptions\SerializationException.cs" />
<Compile Include="Exceptions\ServerException.cs" />
<Compile Include="Helpers\BaseConfigurationHandler.cs" />
<Compile Include="Helpers\Hashing.cs" />
<Compile Include="Helpers\ParseEnum.cs" />
<Compile Include="Helpers\Vector3.cs" />
<Compile Include="Interfaces\IBaseConfiguration.cs" />
@ -242,6 +243,9 @@
<PackageReference Include="Newtonsoft.Json">
<Version>11.0.1</Version>
</PackageReference>
<PackageReference Include="SimpleCrypto">
<Version>0.3.30.26</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">