/// <summary> /// This is used internally to remove a WebSocket connection from the authenticated clients (if present). /// </summary> /// <param name="conn">The connection to remove.</param> private void RemoveWith(IWebSocketConnection conn) { BlackTeaConnection client = null; lock (Clients) { foreach (var blackTeaConnection in Clients.ToList()) { if (blackTeaConnection.Connection.Equals(conn)) { client = blackTeaConnection; break; } } } if (client != null) { //very unlikely that the collection has changed lock (Clients) if (Clients.Contains(client)) { Clients.Remove(client); } } }
/// <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; } }; }