Skip to content
Snippets Groups Projects
Commit fe72cd53 authored by Atanamo's avatar Atanamo
Browse files

Merge branch 'FloodingProtection'

parents 12adcd56 067e4883
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -148,6 +148,7 @@ class BotChannel extends Channel
 
# @override
_handleClientMessage: (clientSocket, messageText) =>
return unless clientSocket.rating.checkForFlooding(8)
log.debug "Client message to IRC (#{@ircChannelName}):", messageText
botID = clientSocket.identity.getGameID() or -1
targetBot = @botList[botID]
Loading
Loading
Loading
Loading
@@ -304,16 +304,21 @@ class Channel
# May be overridden
# @protected
_handleClientMessage: (clientSocket, messageText) ->
return unless clientSocket.rating.checkForFlooding(8)
log.debug "Client message to '#{@name}':", messageText
messageText = messageText?.trim() or ''
return if messageText is ''
@_sendMessageToRoom(clientSocket.identity, messageText)
 
_handleClientHistoryRequest: (clientSocket) =>
flagName = 'hasHistory_' + @name
return unless clientSocket.rating.checkForFlooding((if clientSocket[flagName] then 10 else 1))
log.debug "Client requests chat history for '#{@name}'"
clientSocket[flagName] = true # Flag socket to have requested history at least once
@_sendHistoryToSocket(clientSocket)
 
_handleClientLeave: (clientSocket, isClose=false) ->
return unless clientSocket.rating.checkForFlooding(3)
if not isClose and clientSocket.identity.getUserID() is @creatorID
# Disallow permanent leaving on channels created by the client
@_sendToSocket(clientSocket, 'leave_fail', 'Cannot leave own channels')
Loading
Loading
@@ -330,6 +335,7 @@ class Channel
delay_promise.done()
 
_handleClientDeleteRequest: (clientSocket) ->
return unless clientSocket.rating.checkForFlooding(4)
if clientSocket.identity.getUserID() isnt @creatorID
@_sendToSocket(clientSocket, 'delete_fail', 'Can only delete own channels')
else if @getNumberOfClients() > 1
Loading
Loading
## Include app modules
Config = require './config'
## Abstraction to store and recognize flooding by a client.
##
class ClientFloodingRating
floodingRecognizedCallback: null
ratingEntries: null # Array of objects: {'timestamp', 'weight'}*
TIME_INTERVAL = Config.FLOODRATE_TIME_INTERVAL # Interval in milliseconds
LIMIT_WEIGHT = Config.FLOODRATE_LIMIT_WEIGHT # Maximum total weight in interval
constructor: (@floodingRecognizedCallback) ->
@ratingEntries = []
_addRatingEntry: (newWeight) ->
@ratingEntries.push
timestamp: Date.now()
weight: newWeight
return
_calculateTotalWeightOfLatestEntries: ->
intervalEntries = []
nowTimestamp = Date.now()
totalWeight = 0
for currEntry in @ratingEntries by -1
if nowTimestamp - currEntry.timestamp <= TIME_INTERVAL # Must be younger than interval time
intervalEntries.unshift(currEntry) # Collect entries created within interval in chronological
totalWeight += currEntry.weight # Sum up weight of entries in interval
else
# Break at first entry outside interval
break
# Update ratings array
@ratingEntries = intervalEntries
return totalWeight
checkForFlooding: (eventWeight) ->
@_addRatingEntry(eventWeight)
# Collect entries in interval and sum up their weight
totalWeight = @_calculateTotalWeightOfLatestEntries()
# Check limit
if totalWeight > LIMIT_WEIGHT
@floodingRecognizedCallback?()
return false
return true
## Export class
module.exports = ClientFloodingRating
Loading
Loading
@@ -41,6 +41,9 @@ module.exports =
MAX_CHANNELS_PER_CLIENT: 3 # Maximum number of channels a client/user is allowed to create
MAX_BOTS: 5 # Maximum number of simultaneously existing bots - Use this, to obey connection limits to IRC server (Will also limit number of chats for game worlds)
 
FLOODRATE_TIME_INTERVAL: 3000 # Interval in milliseconds, to be used for flooding protection: To recognize flooding, only client requests not older than this value are totaled up
FLOODRATE_LIMIT_WEIGHT: 33 # Maximum total "weight" of client requests in time interval - A client is kicked, if he exceeds the limit
BOT_RECONNECT_DELAY: 61000 # Delay time in milliseconds, for reconnecting to server, when connection has been refused
BOT_NICK_PATTERN: "#{botNickPrefix}<id>" # The nick name of the Bot on IRC, with <id> as a placeholder for the game ID
BOT_USERNAME_PATTERN: "#{botName}_<id>" # The user name of the Bot on IRC, with <id> as a placeholder for the game ID
Loading
Loading
Loading
Loading
@@ -4,9 +4,10 @@ socketio = require 'socket.io'
 
## Include app modules
Config = require './config'
ClientIdentity = require './clientidentity'
Channel = require './channel'
BotChannel = require './botchannel'
ClientIdentity = require './clientidentity'
ClientFloodingRating = require './clientrating'
 
 
## Abstraction of a handler for common client request on a socket.io socket.
Loading
Loading
@@ -40,10 +41,16 @@ class SocketHandler
 
_handleClientConnect: (clientSocket) =>
log.debug 'Client connected...'
# Add flooding rating object
floodingCallback = =>
@_handleClientFlooding(clientSocket)
clientSocket.rating = new ClientFloodingRating(floodingCallback)
clientSocket.rating.checkForFlooding(2)
# Bind socket events to new client
@_bindSocketClientEvents(clientSocket)
 
_handleClientDisconnect: (clientSocket) =>
return if clientSocket.isDisconnected
log.debug 'Client disconnected...'
clientSocket.isDisconnected = true
# Deregister listeners
Loading
Loading
@@ -51,10 +58,22 @@ class SocketHandler
clientSocket.removeAllListeners 'auth'
clientSocket.removeAllListeners 'join'
 
_handleClientFlooding: (clientSocket) =>
return if clientSocket.isDisconnected
log.info "Disconnecting client '#{clientSocket.identity?.getName()}' due to flooding!"
clientSocket.emit 'forced_disconnect', 'Recognized flooding attack'
clientSocket.emit 'disconnect'
clientSocket.disconnect(false) # Disconnect without closing connection (avoids automatic reconnects)
_handleClientAuthRequest: (clientSocket, authData) =>
log.debug 'Client requests auth...'
return unless clientSocket.rating.checkForFlooding((if clientSocket.hasTriedAuth then 14 else 1))
return if clientSocket.identity?
log.debug 'Client requests auth...'
# Flag socket to have tried auth at least once
clientSocket.hasTriedAuth = true
 
# Set start values
userID = authData.userID
gameID = authData.gameID
securityToken = authData.token
Loading
Loading
@@ -112,6 +131,7 @@ class SocketHandler
 
_handleClientChannelJoin: (clientSocket, channelData) =>
return unless clientSocket.identity?
return unless clientSocket.rating.checkForFlooding(7)
 
gameID = clientSocket.identity.getGameID()
requestedChannelTitle = channelData?.title or null
Loading
Loading
 
# Controller class to handle communication with server
class this.ChatController
socketHandler: null
 
CHANNEL_NAME_MIN_LENGTH = 4
CHANNEL_NAME_MAX_LENGTH = 20
CHANNEL_PASSWORD_MIN_LENGTH = 5
serverIP: ''
serverPort: 0
instanceData: {}
Loading
Loading
Loading
Loading
@@ -10,6 +10,7 @@ class this.SocketClient
instanceData: null
identityData: null
lastMessageSentStamp: 0
isDisonnected: true
 
constructor: (@chatController, @serverIP, @serverPort, @instanceData) ->
 
Loading
Loading
@@ -21,6 +22,7 @@ class this.SocketClient
@socket.on 'connect', @_handleServerConnect # Build-in event
@socket.on 'disconnect', @_handleServerDisconnect # Build-in event
@socket.on 'error', @_handleServerDisconnect # Build-in event
@socket.on 'forced_disconnect', @_handleServerDisconnect
 
@socket.on 'auth_ack', @_handleServerAuthAck
@socket.on 'auth_fail', @_handleServerAuthFail
Loading
Loading
@@ -49,11 +51,14 @@ class this.SocketClient
#
 
_handleServerConnect: =>
@isDisonnected = false
@chatController.handleServerMessage(Translation.get('manage_msg.connect_success'))
@chatController.handleServerMessage(Translation.get('manage_msg.auth_start'))
@_sendAuthRequest()
 
_handleServerDisconnect: (errorMsg) =>
return if @isDisonnected
@isDisonnected = true
@identityData = null
if errorMsg?
serverText = Translation.getForServerMessage(errorMsg)
Loading
Loading
Loading
Loading
@@ -15,6 +15,7 @@ class this.Translation
'server_msg.cannot_leave_own_channel': 'Self-created channels can not be leaved!'
'server_msg.can_only_delete_own_channels': 'A channel can only be deleted, if it has been created by yourself!'
'server_msg.can_only_delete_empty_channels': 'A channel can only be deleted, if no other users are joined to it (even if offline)!'
'server_msg.recognized_flooding_attack': 'You were kicked from the server because of spamming suspicion!'
 
'manage_msg.loading_start': 'Loading...'
'manage_msg.connect_success': 'Connection established!'
Loading
Loading
@@ -79,6 +80,7 @@ class this.Translation
'server_msg.cannot_leave_own_channels': 'Selbst erstellte Channels k&ouml;nnen nicht verlassen werden!'
'server_msg.can_only_delete_own_channels': 'Channels k&ouml;nnen nur gel&ouml;scht werden, wenn sie selbst erstellt wurden!'
'server_msg.can_only_delete_empty_channels': 'Channels k&ouml;nnen nur gel&ouml;scht werden, wenn keine anderen User beigetreten sind (selbst wenn offline)!'
'server_msg.recognized_flooding_attack': 'Du wurdest wegen Spamming-Verdacht vom Server geworfen!'
 
'manage_msg.loading_start': 'Initialisierung l&auml;uft...'
'manage_msg.connect_success': 'Verbindung zum Server hergestellt!'
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment