289 lines
9.5 KiB
Python
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:
|