gnuradionetwork/frn/protocol/server.py

289 lines
9.5 KiB
Python

# -*- coding: utf-8 -*-
#
# 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.protocol import versions
from frn.protocol.common import BufferingLineReceiver
from frn.utility import *
class FRNServer(BufferingLineReceiver, TimeoutMixin):
def connectionMade(self):
BufferingLineReceiver.connectionMade(self)
log.msg("Connection from %s" % self.transport.getPeer().host)
self.clientAddress = self.transport.getPeer()
self.user = None
self.role = None
self.kp = makeRandomChallange()
self.waitingKey = False
self.pingTimer = LoopingCall(self.sendPing)
self.pingCount = 0
self.setTimeout(25.0)
def connectionLost(self, reason):
log.msg("Client disconnected: %s" % self.clientAddress.host)
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
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()
if self.waitingKey:
if responseToChallange(self.kp) != sline:
self.transport.loseConnection()
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()
if self.role in ['OWNER', 'ADMIN']:
self.sendMuteList()
self.sendBlockList()
if self.role == 'OWNER':
self.sendAccessFlags()
self.sendAccessList()
self.pingCount += 1
self.pingTimer.start(0.5)
self.setTimeout(10.0)
return
if sline == 'P': # Pong
#log.msg('Pong')
self.pingCount -= 1
return
if sline in ['RX0', 'TX0', 'TX1']:
command, body = sline[:2], sline[2:]
else:
command, body = sline.split(':', 1)
if command != 'CT' and self.user is None:
self.transport.loseConnection()
return
handler = getattr(self, 'decode'+command, None)
if body[0] == '<' and body[-1] == '>':
body = parseSimpleXML(body)
if handler is not None:
handler(body)
else:
self.unimplemented(command, body)
self.transport.loseConnection() # ???
def expectedReceived(self, data):
self.resetTimeout()
self.setLineMode()
self.audioFrameReceived(data)
def unimplemented(self, command, body):
log.err("Unimplemented message: %s: %s" % (command, body))
def authenticate(self, user):
def loginReturned(userId):
if userId != 'WRONG':
return ("OK", userId) # FIXME: return OWNER or ADMIN eventually
else:
return ("WRONG", "")
user.IP = self.clientAddress.host
return self.factory.manager.clientLogin(user).addCallback(loginReturned)
def decodeCT(self, body):
def authReturned(result):
self.role, clientId = result
log.msg("AUTH result: %s %s" % (self.role, clientId))
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:
self.role = 'ADMIN'
# TODO: Blocklist
if versions.server > 2009004:
self.waitingKey = True
self.sendLine(str(versions.client))
self.factory.serverAuth.update(MT='', SV=versions.server,
AL=self.role, KP=self.kp)
self.sendLine(
self.factory.serverAuth.asXML('MT','SV','AL','BN','BP','KP'))
if self.role not in ['OK', 'OWNER', 'ADMIN']:
self.transport.loseConnection()
return self.authenticate(FRNUser(**body)).addCallback(
authReturned)
def decodeRX(self, body):
log.msg("RX%d" % int(body))
if not self.pingTimer.running:
self.pingTimer.start(0.5)
def decodeST(self, body):
log.msg("Set status = %d" % int(body))
self.updateClient(S=int(body))
def decodeTM(self, body):
log.msg("TM: %s" % str(body))
if body['id'] == '':
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)
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))
elif body == '1':
log.msg("TX1")
self.expectRawData(325)
if self.pingTimer.running:
self.pingTimer.stop()
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)
def sendPing(self):
if self.pingCount > 20:
log.msg("Client %s is dead: disconnecting" %
self.clientAddress.host)
self.transport.loseConnection()
# log.msg(self.pingCount)
self.pingCount += 1
self.transport.write(chr(0))
def sendClientList(self, clients):
self.transport.write(chr(3)+chr(0)+chr(0))
self.sendLine(str(len(clients)))
for client in clients:
self.sendLine(client.asXML(
'S','M','NN','CT','BC','ON','ID','DS'
))
self.pingCount += 1
def sendNetworkList(self):
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(net)
self.pingCount += 1
def sendMuteList(self):
self.transport.write(chr(9))
self.sendLine('0') # TODO
def sendBlockList(self):
self.transport.write(chr(8))
self.sendLine('0') # TODO
def sendAdminList(self):
self.transport.write(chr(6))
self.sendLine('0') # TODO
def sendAccessList(self):
self.transport.write(chr(7))
self.sendLine('0') # TODO
def sendAccessFlags(self):
self.transport.write(chr(10))
self.sendLine('2') # TODO
self.sendLine('o')
self.sendLine('o')
def sendTextMessage(self, clientId, message, target):
self.transport.write(chr(4))
self.sendLine('3')
self.sendLine(clientId)
self.sendLine(message)
self.sendLine(target)
self.pingCount += 1
def sendAudioFrame(self, clientIdx, frame):
self.transport.write(chr(2))
ih,il = divmod(clientIdx, 256)
self.transport.write(chr(ih)+chr(il))
self.transport.write(frame)
class FRNServerFactory(ServerFactory):
protocol = FRNServer
def __init__(self, manager, serverAuth):
self.manager = manager
self.serverAuth = serverAuth
self.talking = None
self.clientList = []
self.adminList = []
self.muteList = []
self.blockList = []
self.officialNets = []
def startFactory(self):
ServerFactory.startFactory(self)
self.manager.serverLogin(self.serverAuth)
def stopFactory(self):
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):
n = self.getNetworkList()
for c in self.clientList:
c.sendNetworkList(n)
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: