# Copyright (c) Mathias Kaerlev 2011-2012.
# This file is part of pyspades.
# pyspades is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# pyspades is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with pyspades. If not, see <http://www.gnu.org/licenses/>.
import json
from twisted.internet.protocol import (ReconnectingClientFactory,
ServerFactory)
from twisted.internet import reactor
from twisted.internet.defer import Deferred, succeed
from twisted.protocols.basic import Int16StringReceiver
CONNECTION_TIMEOUT = 5
DEFAULT_PORT = 32880
[docs]def hash_password(value):
return value # hashlib.md5(value).hexdigest()
[docs]class StatsProtocol(Int16StringReceiver):
[docs] def stringReceived(self, data):
self.object_received(json.loads(data.decode()))
[docs] def send_object(self, obj):
if self.transport is not None:
self.sendString(json.dumps(obj).encode())
[docs] def object_received(self, obj):
pass
[docs]class StatsServer(StatsProtocol):
# TODO: add __init__ method for defined attributes and don't forget to call
# super
# pylint: disable=attribute-defined-outside-init
[docs] def connectionMade(self):
self.timeout_call = reactor.callLater(CONNECTION_TIMEOUT,
self.timed_out)
# TODO: check why the below
# pylint: disable=signature-differs
[docs] def connectionLost(self, reason):
if self in self.factory.connections:
self.factory.connections.remove(self)
[docs] def object_received(self, obj):
obj_type = obj.get('type', None)
if self.timeout_call is not None:
if obj_type != 'auth':
self.transport.loseConnection()
return
password = obj['password']
if password != self.factory.password:
self.transport.loseConnection()
else:
self.timeout_call.cancel()
self.timeout_call = None
self.send_object({'type': 'authed'})
self.name = obj['name']
self.factory.connections.append(self)
self.connection_accepted()
return
if obj_type == 'kill':
self.add_kill(obj['name'])
elif obj_type == 'death':
self.add_death(obj['name'])
elif obj_type == 'login':
# TODO: do we want to be returning the result?
# pylint: disable=unused-variable
result = self.check_user(obj['name'], obj['password']).addCallback(
self.send_login_result)
[docs] def send_login_result(self, result):
self.send_object({'type': 'login', 'result': result})
[docs] def add_kill(self, name):
pass
[docs] def add_death(self, name):
pass
[docs] def check_user(self, name, password):
pass
[docs] def connection_accepted(self):
pass
[docs] def timed_out(self):
self.timeout_call = None
self.transport.loseConnection()
[docs]class StatsFactory(ServerFactory):
protocol = StatsServer
def __init__(self, password):
self.password = hash_password(password)
self.connections = []
[docs]class StatsClient(StatsProtocol):
server = None
login_defers = None
[docs] def connectionMade(self):
self.login_defers = []
self.send_object({'type': 'auth', 'name': self.factory.name,
'password': self.factory.password})
[docs] def object_received(self, obj):
obj_type = obj['type']
if obj_type == 'authed':
self.factory.callback(self)
elif obj_type == 'login':
defer = self.login_defers.pop(0)
defer.callback(obj['result'])
[docs] def add_kill(self, name):
self.send_object({'type': 'kill', 'name': name})
[docs] def add_death(self, name):
self.send_object({'type': 'death', 'name': name})
[docs] def login_user(self, name, password):
defer = Deferred()
password = hash_password(password)
self.send_object({'type': 'login', 'name': name,
'password': password})
self.login_defers.append(defer)
return defer
[docs]class StatsClientFactory(ReconnectingClientFactory):
protocol = StatsClient
maxDelay = 20
def __init__(self, name, password, callback):
self.name = name
self.password = hash_password(password)
self.callback = callback
[docs]def connect_statistics(host, port, name, password, callback, interface=''):
reactor.connectTCP(host, port, StatsClientFactory(name, password, callback),
bindAddress=(interface, 0))
if __name__ == '__main__':
class TestServer(StatsServer):
def add_kill(self, name):
print('Adding kill to', name)
def add_death(self, name):
print('Adding death to', name)
def check_user(self, name, password):
print('Checking user name/pass ({}, {})'.format(name, password))
# TODO: pylint thinks this function should have an argument
# check it out
return succeed() # pylint: disable=no-value-for-parameter
class TestFactory(StatsFactory):
protocol = TestServer
reactor.listenTCP(DEFAULT_PORT, TestFactory('marmelade'))
reactor.run()