/// <summary> /// Used to send a message to a specific dashboard API client. /// </summary> /// <param name="message">The text value of the BaseMessage.</param> /// <param name="name">The name of the system.</param> /// <param name="type">The message type.</param> /// <param name="client">The target.</param> /// /<param name="flags">Optional message data.</param> public void PushTo(string message, string name, string type, IWebSocketConnection client, float[] flags = null) { try { var connection = GetWith(client); if (connection == null) { return; //programmer error } var msg = new Structures.BaseMessage { Type = type, Name = name, Text = message, Date = GetTime(), Flags = flags }; connection.Send(JsonSerializer.Serialize(msg), QEData).ConfigureAwait(false); } catch (Fleck.ConnectionNotAvailableException) { RemoveWith(client); } catch (Exception ex) { //we'd like all the dashboards to know that they have been betrayed. Master.LogManager.LogError($"SRC : {client.ConnectionInfo.ClientIpAddress}: " + ex.ToString(), Shared.ErrorLocation.PushTo); Logger.LogError(ex.Message); } }
private void MessageReceived(Structures.BaseMessage message, IWebSocketConnection conn) { //the dashboard clients should not be sending something that does not start with '/'. //if it is not that, it might correspond to some plugin. if (message.Text.StartsWith("/")) { lock (Commands) { Parallel.ForEach(Commands, Options, (prefix, state) => { if (message.Text.StartsWith(prefix.Key)) { OnMessageReceived?.Invoke(message, conn); state.Break(); } }); } } }
/// <summary> /// This will host an API server, that can be accessed with the given API keys. /// </summary> /// <param name="port">The port to host the server on.</param> /// <param name="listenInterface">The interface to bind the socket to.</param> /// <param name="keys">The accepted API keys.</param> /// <param name="logger">The global logger.</param> public WebApiServer(ushort port, string listenInterface, string[] keys, ILogger <Class> logger, IGameManager manager, Class masterClass, QuiteEffectiveDetector detector, bool secure, X509Certificate2 certificate) { this.StartTime = DateTime.UtcNow; this.Counters = new PerformanceMonitors(); this.Running = true; this.Commands = new Dictionary <string, string>(); Options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; this.Logger = logger; this.GameManager = manager; this.QEDector = detector; this.QEData = new QuodEratDemonstrandum.QuiteEnigmaticData(keys); GlobalMessage = new Structures.BaseMessage(); ApiKeys = new List <string>(); GlobalMessage.Type = Structures.MessageFlag.ConsoleLogMessage; ApiKeys.AddRange(keys); if (!secure) { Server = new WebSocketServer($"ws://{listenInterface}:{port}"); Logger.LogInformation("! WebApi server: bound insecure listener."); } else { Server = new WebSocketServer($"wss://{listenInterface}:{port}") { Certificate = certificate }; Server.EnabledSslProtocols = SslProtocols.Tls12; Logger.LogInformation("! WebApi server: bound secure listener."); } //we start the listener. Server.Start(socket => { //a client connects. socket.OnOpen += () => OnOpen(socket); }); HeartbeatThread = new Thread(DoHeartbeat); HeartbeatThread.Start(); this.Master = masterClass; }
/// <summary> /// A client has connected to the websocket server. /// </summary> /// <param name="conn">The client to process.</param> private void OnOpen(IWebSocketConnection conn) { if (QEDector.IsAttacking(IPAddress.Parse((ReadOnlySpan <char>)conn.ConnectionInfo.ClientIpAddress))) { lock (AttackerAddresses) { if (!AttackerAddresses.Contains(conn.ConnectionInfo.ClientIpAddress)) { Push( $"Under denial of service attack from: {conn.ConnectionInfo.ClientIpAddress}! The address has been booted from accessing the Web API server, for 5 minutes. Please take action!", "-HIGHEST PRIORITY/CRITICAL-", Structures.MessageFlag.ConsoleLogMessage); AttackerAddresses.Add(conn.ConnectionInfo.ClientIpAddress); } } return; } conn.OnMessage = message => { //we will handle AUTH and commands here. try { BlackTeaConnection connection; if (!ContainsWith(conn)) { var cryptoResult = QEData.TryDecrypt(message); if (cryptoResult != null) { connection = new BlackTeaConnection(conn, cryptoResult.Password); } else { var reject = new Structures.BaseMessage { Name = "reject", Date = GetTime(), Type = Structures.MessageFlag.LoginApiRejected }; conn.Send(JsonSerializer.Serialize(reject)); Master.LogManager.LogDashboard(IPAddress.Parse(conn.ConnectionInfo.ClientIpAddress), "[CRITICAL WARN] Failed log-in attempt."); conn.Close(); //we log the issue. Logger.LogWarning($"Failed log-in attempt : {conn.ConnectionInfo.ClientIpAddress}."); return; } } else { connection = GetWith(conn); } var json = QEData.Decrypt(message, connection.Key); if (json == null) { return; } var msg = JsonSerializer.Deserialize <Structures.BaseMessage>(json); if (msg != null) { if (msg.Type.Equals(Structures.MessageFlag.LoginApiRequest)) { //the client has entered an invalid key. lock (ApiKeys) if (!ApiKeys.Contains(msg.Text)) { msg.Name = "reject"; msg.Date = GetTime(); msg.Type = Structures.MessageFlag.LoginApiRejected; conn.Send(JsonSerializer.Serialize(msg)); Master.LogManager.LogDashboard(IPAddress.Parse(conn.ConnectionInfo.ClientIpAddress), "[CRITICAL WARN] Failed log-in attempt."); conn.Close(); //we log the issue. Logger.LogWarning($"Failed log-in attempt : {conn.ConnectionInfo.ClientIpAddress} - key : {msg.Text}"); return; } lock (Clients) { Clients.Add(connection); Logger.LogWarning($"ImpostorHQ : New web admin client : {conn.ConnectionInfo.ClientIpAddress}"); msg.Text = "You have successfully connected to ImpostorHQ!"; msg.Type = Structures.MessageFlag.LoginApiAccepted; msg.Name = "welcome"; msg.Date = GetTime(); conn.Send(JsonSerializer.Serialize(msg)); conn.OnClose += () => { //we handle the client disconnecting. RemoveWith(conn); }; } } else if (msg.Type.Equals(Structures.MessageFlag.ConsoleCommand)) { if (!ContainsWith(conn)) { //we are being attacked. //the client is sending commands without being logged in. conn.Close(); RemoveWith(conn); Logger.LogWarning($"Break-in attempt from : {conn.ConnectionInfo.ClientIpAddress}"); return; } MessageReceived(msg, conn); } else { //invalid API call. //probably not a client. conn.Close(); } } } catch (Exception ex) { //not JSON. Console.WriteLine($"Fatal error occured : {ex}"); return; } }; }