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:
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
77
SharedLibrary/Helpers/Hashing.cs
Normal file
77
SharedLibrary/Helpers/Hashing.cs
Normal 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)
|
||||
};*/
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
@ -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();
|
||||
|
@ -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">
|
||||
|
Reference in New Issue
Block a user