Add new features

- Add first (buggy and dirty) server implementation
- Some bugfixes
This commit is contained in:
Maurizio Porrato 2010-08-21 17:32:22 +02:00
parent d4a1a0da99
commit 5f0e7dd5fd
13 changed files with 386 additions and 125 deletions

18
docs/accounts.conf.sample Normal file
View File

@ -0,0 +1,18 @@
[DEFAULT]
; Settings in DEFAULT section are inherited by all other sections
city=City - LOCATOR
country=Country
transmission=PC Only
description=
[testaccount]
operator=CALLSIGN, Operator name
email=testaccount@example.com
password=TOPSECRET
[myaccount]
operator=CALLSIGN, Operator name
email=otheremail@example.com
transmission=Parrot
password=SUPERSECRET

10
docs/servers.conf.sample Normal file
View File

@ -0,0 +1,10 @@
[DEFAULT]
port=10024
backup_server=
backup_port=10024
[FRI]
server=master.freeradionetwork.it
[NL]
server=ham.freeradionetwork.nl

View File

@ -1,9 +0,0 @@
[accountname]
operator=callsign, name
email=me@example.com
city=City - Locator
country=Your country
transmission=PC Only
description=
password=SECRET
network=Default network

153
frn/clienttracker.py Normal file
View File

@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from twisted.python import log
class ClientTracker(object):
def __init__(self, cbClient, cbNet, cbMute, cbBlock, cbAdmin):
self._clientData = {}
self._net = {}
self._mute = {}
self._block = {}
self._admin = {}
self.cbClient = cbClient
self.cbNet = cbNet
self.cbMute = cbMute
self.cbBlock = cbBlock
self.cbAdmin = cbAdmin
def getClient(self, clientId):
return self._clientData[clientId].user
def getClientProtocol(self, clientId):
return self._clientData[clientId]
def getClientIndex(self, clientId):
net = self.getClient(clientId).NT
l = [x.ID for x in self.getClientList(net)]
return l.index(clientId)+1
def getNetworkList(self):
return self._net.keys()
def getClientList(self, network=[]):
if network:
allClients = self._net.get(network, [])
else:
allClients = self._clientData.keys()
return [self._clientData[x].user
for x in allClients]
def getMuteList(self, network=[]):
if network:
return [x for x in self._mute.values()
if self._mute[x.ID].NT in network]
else:
return [x for x in self._mute.values()]
def getBlockList(self):
return self._block.values()
def getAdminList(self):
return self._admin.values()
def isMute(self, clientId):
return clientId in self._mute
def isBlocked(self, clientId):
return clientId in self._block
def isAdmin(self, email):
return email in self._admin
def login(self, client, status=0):
clientId = client.user.ID
if clientId not in self._clientData:
client.user.S = status
self._clientData[clientId] = client
net = client.user.NT
nc = self._net.get(net, [])
nc.append(clientId)
self._net[net] = nc
if clientId in self._mute:
client.user.M = 1
a = self._mute[clientId].AI
self._mute[clientId].update(client.user.dict())
self._mute[clientId].AI = a
else:
client.user.M = 0
if clientId in self._block:
client.role = "BLOCK"
if client.user.EA in self._admin:
client.role = "ADMIN"
self._admin[client.user.EA].update(client.user.dict())
if len(nc) == 1:
self.cbNet(self.getNetworkList())
self.cbClient(net, self.getClientList(net))
def logout(self, client):
if client.user is None:
return
clientId = client.user.ID
log.msg("Client logout: %s" % (clientId,))
if clientId in self._clientData:
del self._clientData[clientId]
net = client.user.NT
self._net[net].remove(clientId)
if not self._net[net]:
del self._net[net]
self.cbNet(self.getNetworkList())
self.cbClient(net, self.getClientList(net))
def setStatus(self, clientId, status):
oldStatus = self._clientData[clientId].user.S
if oldStatus != str(status):
net = self._clientData[clientId].user.NT
self._clientData[clientId].user.S = status
self.cbClient(net, self.getClientList(net))
def mute(self, admin, clientId):
if clientId not in self._mute:
self._mute[clientId] = self.getClient(clientId).copy(AI=admin.ON)
self._clientData[clientId].user.M = 1
net = self.getClient(clientId).NT
self.cbClient(net, self.getClientList(net))
self.cbMute(self.getMuteList())
def unMute(self, clientId):
if clientId in self._mute:
del self._mute[clientId]
if clientId in self._clientData:
self._clientData[clientId].user.M = 0
net = self._clientData[clientId].user.NT
self.cbClient(net, self.getClientList(net))
self.cbMute(self.getMuteList())
def block(self, admin, clientId):
if clientId not in self._block:
self._block[clientId] = self.getClient(clientId).copy(AI=admin.ON)
if clientId in self._clientData:
net = self._clientData[clientId].user.NT
self.cbBlock(self.getBlockList())
def unBlock(self, clientId):
if clientId in self._block:
del self._block[clientId]
self.cbBlock(self.getBlockList())
def admin(self, clientId):
if clientId not in self._admin:
self._admin[clientId] = self.getClient(clientId).copy()
self.cbAdmin(self.getAdminList())
def unAdmin(self, clientId):
if clientId in self._admin:
del self._admin[clientId]
self.cbAdmin(self.getAdminList())
# vim: set et ai sw=4 ts=4 sts=4:

View File

@ -3,7 +3,7 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import Interface
from zope.interface import Interface
class IManager(Interface):

View File

@ -3,7 +3,7 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import implements
from zope.interface import implements
from frn.manager import IManager
from frn.userstore.database import DatabaseUserStore

View File

@ -3,7 +3,7 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import implements
from zope.interface import implements
from frn.manager import IManager
from twisted.internet import defer
from random import randint

View File

@ -3,12 +3,56 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import implements
from zope.interface import implements
from twisted.internet.defer import Deferred, succeed
from frn.manager import IManager
from twisted.python import log
from frn.protocol.manager import FRNManagerClient, FRNManagerClientFactory
class CustomManagerClientFactory(FRNManagerClientFactory):
def __init__(self, user, onConnect, onDisconnect):
self.user = user
self.onConnect = onConnect
self.onDisconnect = onDisconnect
self.authResult = False
self.client = None
self.managerReady = Deferred()
self.client = None
def clientConnectionFailed(self, connector, reason):
self.authResult = False
self.client = None
self.managerReady = Deferred()
self.onDisconnect(self.protocol)
FRNManagerClientFactory.clientConnectionFailed(
self, connector, reason)
self.managerConnection.addCallback(self.connectionReady)
def clientConnectionLost(self, connector, reason):
self.authResult = False
self.client = None
self.managerReady = Deferred()
self.onDisconnect(self.protocol)
FRNManagerClientFactory.clientConnectionLost(
self, connector, reason)
self.managerConnection.addCallback(self.connectionReady)
def connectionReady(self, client):
def authDone(res):
log.msg("Auth: %s" % str(res))
if res['al'] == '0':
self.authResult = res
self.onConnect(client)
self.managerReady.callback(res)
else:
client.transport.loseConnection()
return res
self.client = client
self.client.sendServerLogin(self.user).addCallback(authDone)
class RemoteManager(object):
implements(IManager)
@ -17,34 +61,39 @@ class RemoteManager(object):
self.reactor = reactor
self.server = server
self.port = port
self.factory = FRNManagerClientFactory()
self.factory.continueTrying = 0 # FIXME
self.factory = None
def onManagerConnect(self, protocol):
pass
def onManagerDisconnect(self, protocol):
pass
def doConnect(self):
self.reactor.connectTCP(self.server, self.port, self.factory)
self.factory.managerConnection.addCallback(self.factory.connectionReady)
return self.factory.managerReady
def serverLogin(self, user):
def connectionDone(conn):
log.msg("%s connected" % self.server)
self.managerConnection = conn
return conn
self.reactor.connectTCP(self.server, self.port, self.factory)
log.msg("RemoteManager started connecting %s" % self.server)
return self.factory.managerConnection.addCallback(
connectionDone).addCallback(
lambda _: self.managerConnection.sendServerLogin(user))
self.factory = CustomManagerClientFactory(
user, self.onManagerConnect, self.onManagerDisconnect)
return self.doConnect()
def serverLogout(self, user):
return self.managerConnection.sendServerLogout(user)
if self.factory.client is not None:
return self.factory.client.sendServerLogout(user)
def clientLogin(self, user):
return self.managerConnection.sendClientLogin(user)
return self.factory.client.sendClientLogin(user)
def clientLogout(self, user):
return self.managerConnection.sendClientLogout(user)
return self.factory.client.sendClientLogout(user)
def getClientList(self):
return self.managerConnection.getClientList()
return self.factory.client.getClientList()
def registerUser(self, user):
return self.managerConnection.registerUser(user)
return self.factory.client.registerUser(user)
# vim: set et ai sw=4 ts=4 sts=4:

View File

@ -15,10 +15,13 @@ from frn.utility import *
class FRNManagerClient(LineOnlyReceiver):
PING_PERIOD = 3.0 # Seconds between ping requests
def connectionMade(self):
log.msg("Connected to manager [%s]" % self.transport.getPeer().host)
self.notifications = []
if not self.factory.managerConnection.called: # FIXME: Why???
log.msg("Firing manager connection callback!")
self.factory.managerConnection.callback(self)
def connectionLost(self, reason):
@ -40,7 +43,7 @@ class FRNManagerClient(LineOnlyReceiver):
d.callback(result)
def lineReceived(self, line):
log.msg("notifications: %s" % str(self.notifications))
#log.msg("notifications: %s" % str(self.notifications))
if hasattr(self, 'serverlist'):
# TODO
pass
@ -50,13 +53,16 @@ class FRNManagerClient(LineOnlyReceiver):
def sendServerLogin(self, user):
def loginDone(result):
self.managerdata = parseSimpleXML(result)
log.msg("Server login succeeded: %s" % str(self.managerdata))
log.msg("Server login results: %s" % str(self.managerdata))
if int(self.managerdata['mc']) > 2009004:
self.sendLine(responseToChallange(
self.managerdata['kp']))
self.pingtimer = LoopingCall(self.sendPing)
self.pingtimer.start(3.0, False)
self.factory.resetDelay()
if self.managerdata['al'] != '0':
self.transport.loseConnection()
else:
self.pingtimer = LoopingCall(self.sendPing)
self.pingtimer.start(self.PING_PERIOD, False)
self.factory.resetDelay()
return self.managerdata
log.msg("Sending server login")
@ -98,7 +104,6 @@ class FRNManagerClientFactory(ReconnectingClientFactory):
def startFactory(self):
self.managerConnection = Deferred()
ReconnectingClientFactory.startFactory(self)
class FRNManagerServer(LineOnlyReceiver):

View File

@ -3,13 +3,13 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from random import choice
from twisted.internet.defer import Deferred
from twisted.internet.protocol import ServerFactory
from twisted.protocols.policies import TimeoutMixin
from twisted.internet.task import LoopingCall
from twisted.python import log
from frn.user import FRNUser
from frn.clienttracker import ClientTracker
from frn.protocol import versions
from frn.protocol.common import BufferingLineReceiver
from frn.utility import *
@ -34,22 +34,13 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
try:
self.pingTimer.stop()
except AssertionError: pass
try:
self.factory.clientList.remove(self)
self.factory.manager.clientLogout(self.user)
self.factory.sendClientList([self.user.NT])
except ValueError: pass
if self.user is not None:
if self.user.ID:
log.msg("Logging out client %s" % self.user.ID)
self.factory.manager.clientLogout(self.user)
self.factory.tracker.logout(self)
BufferingLineReceiver.connectionLost(self, reason)
def updateClient(self, **kw):
d = {}
for k, v in [(x.lower(), str(y)) for x,y in kw.items()]:
if self.user.get(k) != v:
d[k] = v
if len(d) > 0:
self.user.update(**d)
self.factory.sendClientList([self.user.NT])
def lineReceived(self, line):
self.resetTimeout()
sline = line.strip()
@ -59,20 +50,13 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
return
else:
self.waitingKey = False
if self.user.ID in self.factory.muteList:
self.user.M = 1
else:
self.user.M = 0
self.user.S = 0
self.factory.clientList.append(self)
self.factory.sendClientList([self.user.NT]) # FIXME: older servers can't get here
self.sendNetworkList()
self.factory.tracker.login(self)
if self.role in ['OWNER', 'ADMIN']:
self.sendMuteList()
self.sendBlockList()
self.sendMuteList(self.factory.tracker.getMuteList())
self.sendBlockList(self.factory.tracker.getBlockList())
if self.role == 'OWNER':
self.sendAccessFlags()
self.sendAccessList()
self.sendAccessFlags(None)
self.sendAccessList([])
self.pingCount += 1
self.pingTimer.start(0.5)
self.setTimeout(10.0)
@ -95,7 +79,7 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
handler(body)
else:
self.unimplemented(command, body)
self.transport.loseConnection() # ???
#self.transport.loseConnection() # ???
def expectedReceived(self, data):
self.resetTimeout()
@ -105,15 +89,23 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
def unimplemented(self, command, body):
log.err("Unimplemented message: %s: %s" % (command, body))
def getIndex(self):
return self.factory.tracker.getClientIndex(self.user.ID)+1
def authenticate(self, user):
def loginReturned(userId):
if userId != 'WRONG':
if userId not in ['WRONG', 'DUPL']:
if self.factory.tracker.isBlocked(userId):
return ("BLOCK", userId)
return ("OK", userId) # FIXME: return OWNER or ADMIN eventually
else:
return ("WRONG", "")
return (userId, "")
user.IP = self.clientAddress.host
return self.factory.manager.clientLogin(user).addCallback(loginReturned)
def disconnect(self):
self.transport.loseConnection()
def decodeCT(self, body):
def authReturned(result):
self.role, clientId = result
@ -121,12 +113,10 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
if self.role == 'OK':
self.user = FRNUser(**body)
self.user.ID = clientId
if self.role == 'OK':
if self.user.EA == self.factory.serverAuth.OW:
self.role = 'OWNER'
elif self.user.EA in self.factory.adminList:
elif self.factory.tracker.isAdmin(self.user.EA):
self.role = 'ADMIN'
# TODO: Blocklist
if versions.server > 2009004:
self.waitingKey = True
self.sendLine(str(versions.client))
@ -146,7 +136,7 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
def decodeST(self, body):
log.msg("Set status = %d" % int(body))
self.updateClient(S=int(body))
self.factory.tracker.setStatus(self.user.ID, int(body))
def decodeTM(self, body):
log.msg("TM: %s" % str(body))
@ -154,30 +144,55 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
msgtype = 'A'
else:
msgtype = 'P'
for c in self.factory.clientList:
if msgtype == 'A' or c.user.ID == body['id']:
if c != self:
c.sendTextMessage(self.user.ID, body['ms'], msgtype)
for c in self.factory.tracker.getClientList(self.user.NT):
if msgtype == 'A' or c.ID == body['id']:
if c.ID != self.user.ID:
client = self.factory.tracker.getClientProtocol(c.ID)
client.sendTextMessage(self.user.ID, body['ms'], msgtype)
def decodeTX(self, body):
if body == '0': # FIXME: Mute?
log.msg("TX0")
clientIdx = self.factory.clientList.index(self)+1
ih,il = divmod(clientIdx, 256)
self.transport.write(chr(1)+chr(ih)+chr(il))
if body == '0':
if not self.factory.tracker.isMute(self.user.ID):
ih,il = divmod(self.getIndex(), 256)
self.transport.write(chr(1)+chr(ih)+chr(il))
elif body == '1':
log.msg("TX1")
if self.pingTimer.running:
self.pingTimer.stop()
self.expectRawData(325)
if self.pingTimer.running:
self.pingTimer.stop()
def decodeMC(self, body):
if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.mute(self.user, body['ip'])
def decodeUM(self, body):
if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.unMute(body['ip'])
def decodeBC(self, body):
if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.block(self.user, body['ip'])
self.factory.tracker.getClientProtocol(body['ip']).disconnect()
def decodeUC(self, body):
if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.unBlock(body['ip'])
def decodeAA(self, body):
if self.role == "OWNER":
self.factory.tracker.admin(body['ip'])
def decodeDA(self, body):
if self.role == "OWNER":
self.factory.tracker.unAdmin(body['ip'])
def audioFrameReceived(self, frame):
log.msg("audioFrameReceived")
clientIdx = self.factory.clientList.index(self)+1
for c in self.factory.clientList:
if int(c.user.S) < 2 and c != self:
log.msg("Sending to %s" % c.user.ON)
c.sendAudioFrame(clientIdx, frame)
#log.msg("audioFrameReceived")
if not self.factory.tracker.isMute(self.user.ID):
clientIdx = self.getIndex()
for c in self.factory.tracker.getClientList(self.user.NT):
if int(c.S) < 2 and c.ID != self.user.ID:
#log.msg("Sending to %s" % c.ON)
self.factory.tracker.getClientProtocol(c.ID).sendAudioFrame(clientIdx, frame)
def sendPing(self):
if self.pingCount > 20:
@ -197,32 +212,42 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
))
self.pingCount += 1
def sendNetworkList(self):
def sendNetworkList(self, networks):
log.msg("Send network list")
self.transport.write(chr(5))
nets = self.factory.getNetworkList()
self.sendLine(str(len(nets)))
for net in nets:
self.sendLine(str(len(networks)))
for net in networks:
self.sendLine(net)
self.pingCount += 1
def sendMuteList(self):
def sendMuteList(self, clients):
log.msg("Sending mute list to %s: %s" % (self.user.ON, str(clients)))
self.transport.write(chr(9))
self.sendLine('0') # TODO
self.sendLine(str(len(clients)))
for c in clients:
self.sendLine(c.asXML('AI','NN','CT','BC','ON','ID'))
def sendBlockList(self):
def sendBlockList(self, clients):
log.msg("Sending block list to %s: %s" % (self.user.ON, str(clients)))
self.transport.write(chr(8))
self.sendLine('0') # TODO
self.sendLine(str(len(clients)))
for c in clients:
self.sendLine(c.asXML('AI','NN','CT','BC','ON','ID'))
def sendAdminList(self):
def sendAdminList(self, clients):
log.msg("Sending admin list to %s: %s" % (self.user.ON, str(clients)))
self.transport.write(chr(6))
self.sendLine('0') # TODO
self.sendLine(str(len(clients)))
for c in clients:
self.sendLine(c.asXML('NN','CT','BC','ON','ID'))
def sendAccessList(self):
def sendAccessList(self, clients):
self.transport.write(chr(7))
self.sendLine('0') # TODO
self.sendLine(str(len(clients)))
for c in clients: # FIXME
self.sendLine(c.asXML('AI','NN','CT','BC','ON','ID'))
def sendAccessFlags(self):
def sendAccessFlags(self, flags):
self.transport.write(chr(10))
self.sendLine('2') # TODO
self.sendLine('o')
@ -251,11 +276,11 @@ class FRNServerFactory(ServerFactory):
self.manager = manager
self.serverAuth = serverAuth
self.talking = None
self.clientList = []
self.adminList = []
self.muteList = []
self.blockList = []
self.officialNets = []
self.tracker = ClientTracker(
self.sendClientList, self.sendNetworkList,
self.sendMuteList, self.sendBlockList,
self.sendAdminList)
def startFactory(self):
ServerFactory.startFactory(self)
@ -265,24 +290,29 @@ class FRNServerFactory(ServerFactory):
self.manager.serverLogout(self.serverAuth)
ServerFactory.stopFactory(self)
def getNetworkList(self):
n = set([c.user.NT for c in self.clientList])
o = set(self.officialNets)
return self.officialNets+list(n-o)
def sendNetworkList(self, networks):
nets = self.officialNets+list(set(networks) - set(self.officialNets))
for c in self.tracker.getClientList():
self.tracker.getClientProtocol(c.ID).sendNetworkList(nets)
def sendNetworkList(self):
n = self.getNetworkList()
for c in self.clientList:
c.sendNetworkList(n)
def sendClientList(self, network, clients):
for c in clients:
self.tracker.getClientProtocol(c.ID).sendClientList(clients)
def sendMuteList(self, clients):
for c in self.tracker.getClientList():
if self.tracker.isAdmin(c.ID) or c.EA == self.serverAuth.OW:
self.tracker.getClientProtocol(c.ID).sendMuteList(clients)
def sendBlockList(self, clients):
for c in self.tracker.getClientList():
if self.tracker.isAdmin(c.ID) or c.EA == self.serverAuth.OW:
self.tracker.getClientProtocol(c.ID).sendBlockList(clients)
def sendAdminList(self, clients):
for c in self.tracker.getClientList():
if c.EA == self.serverAuth.OW:
self.tracker.getClientProtocol(c.ID).sendAdminList(clients)
def sendClientList(self, networks=[]):
n = {}
for c in self.clientList:
l = n.get(c.user.NT, [])
l.append(c.user)
n[c.user.NT] = l
for c in self.clientList:
if (not networks) or (c.user.NT in networks):
c.sendClientList(n[c.user.NT])
# vim: set et ai sw=4 ts=4 sts=4:

View File

@ -25,7 +25,7 @@ class FRNUser(object):
self.set(attr, value)
def __str__(self):
return asXML(self)
return self.asXML()
def __repr__(self):
return "FRNUser(%s)" % \
@ -52,6 +52,11 @@ class FRNUser(object):
for field, value in kw.items():
self.set(field, value)
def copy(self, **kw):
n = FRNUser(**self._fields)
n.update(**kw)
return n
def updateXML(self, xml):
self.update(dict(parseSimpleXML(xml)))

View File

@ -19,7 +19,7 @@ if __name__ == '__main__':
def standardManagerFactory():
log.msg("Building Manager")
return RemoteManager(reactor, '83.82.28.221')
return RemoteManager(reactor)
reactor.listenTCP(10025, FRNManagerServerFactory(
# DatabaseUserStore(

View File

@ -46,8 +46,8 @@ if __name__ == '__main__':
# DatabaseUserStore(
# ConnectionPool("sqlite3", "frn_users.sqlite3",
# check_same_thread=False)),
# RemoteManager(reactor, '83.82.28.221'),
DummyManager(),
RemoteManager(reactor),
# DummyManager(),
FRNUser(
SN=server,PT=port,
BN=backup_server, BP=backup_port,