Compare commits

..

No commits in common. "master" and "wip" have entirely different histories.
master ... wip

19 changed files with 547 additions and 924 deletions

View File

@ -5,7 +5,6 @@
import shelve import shelve
from twisted.python import log from twisted.python import log
from frn.user import FRNUser
CLIENTS_ORDER = ['Crosslink', 'Parrot', 'Gateway', 'PC Only'] CLIENTS_ORDER = ['Crosslink', 'Parrot', 'Gateway', 'PC Only']
@ -40,7 +39,6 @@ class ClientTracker(object):
self._mute = {} self._mute = {}
self._block = {} self._block = {}
self._admin = {} self._admin = {}
self._acl = {}
self.cbClient = cbClient self.cbClient = cbClient
self.cbNet = cbNet self.cbNet = cbNet
self.cbMute = cbMute self.cbMute = cbMute
@ -55,7 +53,6 @@ class ClientTracker(object):
self._mute = s['mute'] self._mute = s['mute']
self._block = s['block'] self._block = s['block']
self._admin = s['admin'] self._admin = s['admin']
self._acl = s['acl']
except KeyError: pass except KeyError: pass
s.close() s.close()
@ -64,7 +61,6 @@ class ClientTracker(object):
s['mute'] = self._mute s['mute'] = self._mute
s['block'] = self._block s['block'] = self._block
s['admin'] = self._admin s['admin'] = self._admin
s['acl'] = self._acl
s.close() s.close()
def isLoggedIn(self, clientId): def isLoggedIn(self, clientId):
@ -82,7 +78,7 @@ class ClientTracker(object):
return l.index(clientId)+1 return l.index(clientId)+1
def getNetworkList(self): def getNetworkList(self):
return sorted(set(self._net.keys())|set(self._acl.keys())) return self._net.keys()
def getClientList(self, network=[]): def getClientList(self, network=[]):
if network: if network:
@ -117,10 +113,7 @@ class ClientTracker(object):
def login(self, client, status=0): def login(self, client, status=0):
clientId = client.user.ID clientId = client.user.ID
if clientId not in self._clientData: if clientId not in self._clientData:
if not self.aclTalkOk(client.user.NT, client.user.EA) and status < 1: client.user.S = status
client.user.S = 1
else:
client.user.S = status
self._clientData[clientId] = client self._clientData[clientId] = client
net = client.user.NT net = client.user.NT
nc = self._net.get(net, []) nc = self._net.get(net, [])
@ -140,10 +133,8 @@ class ClientTracker(object):
self._mute[clientId].AI = a self._mute[clientId].AI = a
else: else:
client.user.M = 0 client.user.M = 0
if not self.canLogin(client.user): if clientId in self._block:
client.role = "BLOCK" client.role = "BLOCK"
else:
self.aclUpdate(client.user)
if client.user.EA in self._admin: if client.user.EA in self._admin:
client.role = "ADMIN" client.role = "ADMIN"
self._admin[client.user.EA].update(client.user.dict()) self._admin[client.user.EA].update(client.user.dict())
@ -168,13 +159,11 @@ class ClientTracker(object):
self.cbClient(net, self.getClientList(net)) self.cbClient(net, self.getClientList(net))
def setStatus(self, clientId, status): def setStatus(self, clientId, status):
user = self.getClient(clientId) oldStatus = self._clientData[clientId].user.S
oldStatus = user.S
if not self.aclTalkOk(user.NT, user.EA) and status < 1:
status = 1
if oldStatus != str(status): if oldStatus != str(status):
net = self._clientData[clientId].user.NT
self._clientData[clientId].user.S = status self._clientData[clientId].user.S = status
self.cbClient(user.NT, self.getClientList(user.NT)) self.cbClient(net, self.getClientList(net))
def mute(self, admin, clientId): def mute(self, admin, clientId):
if clientId not in self._mute: if clientId not in self._mute:
@ -221,91 +210,5 @@ class ClientTracker(object):
self.save() self.save()
self.cbAdmin(self.getAdminList()) self.cbAdmin(self.getAdminList())
def _getAcl(self, network):
return self._acl.get(network, (False, False, {}))
def _setAcl(self, network, ac, tx, l):
log.msg("Changing ACL for %s" % network)
if ac == tx == False and len(l) == 0:
log.msg("Default settings detected")
if network in self._acl:
log.msg("Deleting")
del self._acl[network]
else:
log.msg("Setting ACL for %s to %s" % (network, str((ac,tx,l))))
self._acl[network] = (ac, tx, l)
self.save()
def getAcl(self, network):
ac, tx, l = self._getAcl(network)
return l.values()
def getAclFlags(self, network):
ac, tx, l = self._getAcl(network)
return (ac, tx)
def setAclFlags(self, network, ac, tx):
oac, otx, l = self._getAcl(network)
self._setAcl(network, ac, tx, l)
def addAcl(self, network, email):
log.msg("Adding %s to ACL for %s" % (email, network))
ac, tx, l = self._getAcl(network)
if email not in l:
knownUsers = dict([(x.EA, x) for x in [y.user for y in self._clientData.values()] + self._mute.values() + self._block.values()])
user = knownUsers.get(email, FRNUser(EA=email)).copy(ID=email, AI=1)
l[email] = user
self._setAcl(network, ac, tx, l)
def delAcl(self, network, email):
log.msg("Removing %s from ACL for %s" % (email, network))
ac, tx, l = self._getAcl(network)
if email in l:
del l[email]
self._setAcl(network, ac, tx, l)
def setAclTx(self, network, email, tx):
oac, otx, l = self._getAcl(network)
if email in l:
if tx:
l[email].AI = 0
else:
l[email].AI = 1
self._setAcl(network, oac, otx, l)
def aclUpdate(self, user):
network = user.NT
email = user.EA
ac, tx, l = self._getAcl(network)
if email in l:
old = l[email]
new = user.copy(ID=email, AI=old.AI)
l[email] = new
self._setAcl(network, ac, tx, l)
def aclLoginOk(self, network, email):
ac, tx, l = self._getAcl(network)
if not ac:
return True
return email in l
def aclTalkOk(self, network, email):
ac, tx, l = self._getAcl(network)
if not tx:
return True
if email not in l:
return False
return l[email].AI == '0'
def canLogin(self, user):
if self.isBlocked(user.ID):
return False
return self.aclLoginOk(user.NT, user.EA)
def canTalk(self, clientId):
if self.isMute(clientId):
return False
u = self.getClient(clientId)
return self.aclTalkOk(u.NT, u.EA)
# vim: set et ai sw=4 ts=4 sts=4: # vim: set et ai sw=4 ts=4 sts=4:

View File

@ -8,16 +8,16 @@ from zope.interface import Interface
class IManager(Interface): class IManager(Interface):
def serverLogin(server): def serverLogin(user):
"""Logs server on""" """Logs server on"""
def serverLogout(server): def serverLogout(user):
"""Logs server out""" """Logs server out"""
def clientLogin(server, user): def clientLogin(user):
"""Logs client in""" """Logs client in"""
def clientLogout(server, user): def clientLogout(user):
"""Logs client out""" """Logs client out"""
def getClientList(): def getClientList():

View File

@ -5,149 +5,33 @@
from zope.interface import implements from zope.interface import implements
from frn.manager import IManager from frn.manager import IManager
from twisted.python import log from frn.userstore.database import DatabaseUserStore
from twisted.python.failure import Failure
from twisted.mail.smtp import sendmail
import random, string, uuid
from frn.user import FRNUser
def rndpasswd():
return ''.join([random.choice(string.ascii_uppercase) for i in range(8)])
class DatabaseManager(object): class DatabaseManager(object):
implements(IManager) implements(IManager)
def __init__(self, pool): def __init__(self, store):
self._pool = pool self._store = store
self._pool.runOperation("""
CREATE TABLE IF NOT EXISTS frn_users (
_id VARCHAR(32) NOT NULL PRIMARY KEY,
_ea VARCHAR(30) UNIQUE NOT NULL,
_pw VARCHAR(20) NOT NULL,
_on VARCHAR(20) NOT NULL,
_bc VARCHAR(20) NOT NULL,
_nn VARCHAR(20) NOT NULL,
_ct VARCHAR(20) NOT NULL,
_nt VARCHAR(20),
_ds VARCHAR(20) NOT NULL,
_ip VARCHAR(20),
registration TIMESTAMP NOT NULL DEFAULT current_timestamp,
lastlogin TIMESTAMP,
server VARCHAR(20),
port INTEGER
);
""")
self._pool.runOperation("""
CREATE TABLE IF NOT EXISTS frn_servers (
_vx VARCHAR(7) NOT NULL,
_sn VARCHAR(20) NOT NULL,
_pt INTEGER NOT NULL,
_bn VARCHAR(20),
_bp integer,
_ow VARCHAR(30) NOT NULL,
registration TIMESTAMP NOT NULL DEFAULT current_timestamp,
PRIMARY KEY(_sn, _pt)
);
""")
def serverLogin(self, server): def serverLogin(self, user):
def checkauth(res): pass
if not isinstance(res, Failure):
if res[0] > 0:
return self._pool.runOperation("""
INSERT INTO frn_servers
(_vx, _sn, _pt, _bn, _bp, _ow) VALUES
(?,?,?,?,?,?)
""", (server.VX, server.SN, server.PT,
server.BN, server.BP, server.OW)).addCallbacks(
lambda x: 0, lambda x: -1)
else:
return -1
return self._pool.runQuery("SELECT count(*) FROM frn_users WHERE _ea=? AND _pw=?",
(server.OW, server.PW)).addBoth(checkauth)
def serverLogout(self, server): def serverLogout(self, user):
self._pool.runOperation("UPDATE frn_users SET server=NULL, port=NULL, _nt=NULL WHERE server=? AND port=?", pass
(server.SN, server.PT))
self._pool.runOperation("DELETE FROM frn_servers WHERE _sn=? AND _pt=?",
(server.SN, server.PT))
def clientLogin(self, server, user): def clientLogin(self, user):
def userfound(data): pass
log.msg("Client login: %s" % repr(data))
if data:
u = dict(zip(
('ID', 'EA', 'PW', 'ON', 'BC', 'NN', 'CT', 'NT', 'DS', 'IP', 'registration', 'lastlogin', 'server', 'port'),
data[0]))
if u['server']:
log.msg("Duplicate client %s" % repr(user))
return "DUPL"
self._pool.runQuery("""
UPDATE frn_users SET
_bc=?, _nn=?, _ct=?, _nt=?, _ds=?, _ip=?, server=?, port=?,
lastlogin=current_timestamp
WHERE _id=?
""", (user.BC, user.NN, user.CT, user.NT, user.DS, user.IP, server.SN, server.PT, u['ID']))
log.msg("Authenticated client %s" % repr(user))
return str(u['ID'])
else:
log.msg("Wrong client %s" % repr(user))
return "WRONG"
return self._pool.runQuery("SELECT * FROM frn_users WHERE _ea=? AND _on=? AND _pw=?",
(user.EA, user.ON, user.PW)).addCallbacks(userfound, lambda x: "WRONG")
def clientLogout(self, server, user): def clientLogout(self, user):
log.msg("Logging out client %s" % repr(user)) pass
return self._pool.runOperation("UPDATE frn_users SET server=NULL, port=NULL, _nt=NULL WHERE _id=?",
(user.ID,)).addBoth(lambda x: "OK")
def getClientList(self): def getClientList(self):
def buildlist(tr): pass
tr.execute("SELECT _sn, _pt FROM frn_servers")
servers = tr.fetchall()
r = {}
for sn, sp in servers:
r[(sn,sp)] = {}
tr.execute("SELECT DISTINCT _nt FROM frn_users WHERE server=? AND port=?", (sn,sp))
networks = tr.fetchall()
for (n,) in networks:
r[(sn,sp)][n] = []
tr.execute("SELECT _id, _on, _bc, _ds, _nn, _ct FROM frn_users WHERE server=? AND port=? AND _nt=?",
(sn, sp, n))
clients = tr.fetchall()
for c in clients:
cu = FRNUser(**dict(zip(['EA','ON','BC','DS','NN','CT'],c)))
r[(sn,sp)][n].append(cu)
return r
return self._pool.runInteraction(buildlist)
def registerUser(self, user): def registerUser(self, user):
def fetchdata(is_new): pass
def maildata(data):
u = dict(zip(
('ID', 'EA', 'PW', 'ON', 'BC', 'NN', 'CT', 'NT', 'DS', 'IP', 'registration', 'lastlogin', 'server', 'port'),
data[0]))
log.msg("Mailing password to user %s" % str(u))
tplfile = open('mailtemplate.txt','r')
try:
tpl = tplfile.read()
mailbody = string.Template(tpl).safe_substitute(u)
sendmail('127.0.0.1',
'admin@gnuradionetwork.org',
[u['EA']],
mailbody, port=25)
finally:
tplfile.close()
return "OK"
return self._pool.runQuery(
"SELECT * FROM frn_users WHERE _ea=?", (user.EA,)
).addCallback(maildata).addErrback(lambda x: "ERROR")
return self._pool.runOperation("""
INSERT INTO frn_users (_id, _ea, _pw, _on, _bc, _ds, _nn, _ct)
VALUES (?,?,?,?,?,?,?,?)
""", (uuid.uuid4().get_hex()[:20], user.EA, rndpasswd(),
user.ON, user.BC, user.DS, user.NN, user.CT)
).addBoth(fetchdata)
# vim: set et ai sw=4 ts=4 sts=4: # vim: set et ai sw=4 ts=4 sts=4:

View File

@ -16,20 +16,20 @@ class DummyManager(object):
def _randId(self): def _randId(self):
return '.'.join([str(randint(1,254)) for i in range(4)]) return '.'.join([str(randint(1,254)) for i in range(4)])
def serverLogin(self, server): def serverLogin(self, user):
return defer.succeed(0) return defer.succeed(0)
def serverLogout(self, server): def serverLogout(self, user):
return defer.succeed(None) return defer.succeed(None)
def clientLogin(self, server, user): def clientLogin(self, user):
return defer.succeed(self._randId()) return defer.succeed(self._randId())
def clientLogout(self, server, user): def clientLogout(self, user):
return defer.succeed('OK') return defer.succeed('OK')
def getClientList(self): def getClientList(self):
return defer.succeed({}) return defer.succeed([])
def registerUser(self, user): def registerUser(self, user):
return defer.succeed('OK') return defer.succeed('OK')

View File

@ -23,7 +23,7 @@ class CustomManagerClientFactory(FRNManagerClientFactory):
def authReply(auth): def authReply(auth):
self.resetDelay() self.resetDelay()
self.authResult = auth self.authResult = auth
self.authDone = (auth['AL'] == '0') self.authDone = (auth['al'] == '0')
if self.authDone: if self.authDone:
self.deferred.callback(auth) self.deferred.callback(auth)
connection.sendServerLogin(self.user).addCallback(authReply) connection.sendServerLogin(self.user).addCallback(authReply)
@ -55,23 +55,23 @@ class RemoteManager(object):
def doConnect(self): def doConnect(self):
self.reactor.connectTCP(self.server, self.port, self.factory) self.reactor.connectTCP(self.server, self.port, self.factory)
def serverLogin(self, server): def serverLogin(self, user):
self.factory = CustomManagerClientFactory(server) self.factory = CustomManagerClientFactory(user)
self.doConnect() self.doConnect()
return self.factory.deferred return self.factory.deferred
def serverLogout(self, server): def serverLogout(self, user):
if self.factory.client is not None: if self.factory.client is not None:
return self.factory.client.sendServerLogout(server) return self.factory.client.sendServerLogout(user)
def clientLogin(self, server, user): def clientLogin(self, user):
if self.maskParrot and user.BC == 'Parrot': if self.maskParrot and user.BC == 'Parrot':
u = user.copy(BC='PC Only') u = user.copy(BC='PC Only')
else: else:
u = user.copy() u = user.copy()
return self.factory.client.sendClientLogin(u) return self.factory.client.sendClientLogin(u)
def clientLogout(self, server, user): def clientLogout(self, user):
if self.maskParrot and user.BC == 'Parrot': if self.maskParrot and user.BC == 'Parrot':
u = user.copy(BC='PC Only') u = user.copy(BC='PC Only')
else: else:

View File

@ -1,311 +1,311 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com> # Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info # See LICENSE.txt for copyright info
from Queue import Queue from Queue import Queue
from twisted.internet.protocol import ReconnectingClientFactory from twisted.internet.protocol import ReconnectingClientFactory
from twisted.protocols.policies import TimeoutMixin from twisted.protocols.policies import TimeoutMixin
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from twisted.python import log from twisted.python import log
from frn.protocol import versions from frn.protocol import versions
from frn.user import FRNUser from frn.user import FRNUser
from frn.protocol.common import BufferingLineReceiver from frn.protocol.common import BufferingLineReceiver
from frn.utility import * from frn.utility import *
class FRNClient(BufferingLineReceiver, TimeoutMixin): class FRNClient(BufferingLineReceiver, TimeoutMixin):
def connectionMade(self): def connectionMade(self):
BufferingLineReceiver.connectionMade(self) BufferingLineReceiver.connectionMade(self)
self.user.VX = versions.client self.user.VX = versions.client
self.serverdata = {} self.serverdata = {}
self.txq = Queue() self.txq = Queue()
self.setTimeout(25.0) self.setTimeout(25.0)
self.login() self.login()
def connectionLost(self, reason): def connectionLost(self, reason):
self.serverdata = {} self.serverdata = {}
BufferingLineReceiver.connectionLost(self, reason) BufferingLineReceiver.connectionLost(self, reason)
def ready(self): def ready(self):
self.status = 'READY' self.status = 'READY'
self.msgbuffer = [] self.msgbuffer = []
self.phase = 0 self.phase = 0
self.expectRawData(1) self.expectRawData(1)
def startMultiLineMessage(self, msgtype): def startMultiLineMessage(self, msgtype):
self.status = msgtype self.status = msgtype
self.phase = None self.phase = None
self.setLineMode() self.setLineMode()
def stopMultiLineMessage(self): def stopMultiLineMessage(self):
handler = getattr(self, 'decode'+self.status, None) handler = getattr(self, 'decode'+self.status, None)
status = self.status status = self.status
message = self.msgbuffer message = self.msgbuffer
self.ready() self.ready()
if handler is not None: if handler is not None:
handler(message) handler(message)
else: else:
self.unimplemented(status, message) self.unimplemented(status, message)
def collectMultiLineMessage(self, line): def collectMultiLineMessage(self, line):
if self.phase is None: if self.phase is None:
self.expected_lines = int(line.strip()) self.expected_lines = int(line.strip())
self.msgbuffer = [] self.msgbuffer = []
self.phase = 0 self.phase = 0
else: else:
self.msgbuffer.append(line) self.msgbuffer.append(line)
self.phase += 1 self.phase += 1
if self.phase >= self.expected_lines: if self.phase >= self.expected_lines:
self.stopMultiLineMessage() self.stopMultiLineMessage()
def lineReceived(self, line): def lineReceived(self, line):
if self.status == 'AUTH': if self.status == 'AUTH':
if self.phase == 0: if self.phase == 0:
self.latest_client_version = int(line.strip()) self.latest_client_version = int(line.strip())
self.phase = 1 self.phase = 1
else: else:
self.serverdata = parseSimpleXML(line.strip()) self.serverdata = parseSimpleXML(line.strip())
if int(self.serverdata['SV']) > 2009004: if int(self.serverdata['sv']) > 2009004:
self.sendLine(responseToChallange( self.sendLine(responseToChallange(
self.serverdata['KP'])) self.serverdata['kp']))
self.ready() self.ready()
self.setTimeout(10.0) self.setTimeout(10.0)
self.factory.resetDelay() self.factory.resetDelay()
self.loginResponse(self.serverdata) self.loginResponse(self.serverdata)
else: else:
self.collectMultiLineMessage(line) self.collectMultiLineMessage(line)
def expectedReceived(self, data): def expectedReceived(self, data):
self.resetTimeout() self.resetTimeout()
if self.status == 'READY': if self.status == 'READY':
packet_type = ord(data[0]) packet_type = ord(data[0])
if packet_type == 0: # Keepalive if packet_type == 0: # Keepalive
self.ready() self.ready()
self.pong() self.pong()
elif packet_type == 1: # TX ack elif packet_type == 1: # TX ack
self.status = 'TX' self.status = 'TX'
self.expectRawData(2) self.expectRawData(2)
elif packet_type == 2: # Audio elif packet_type == 2: # Audio
self.status = 'AUDIO' self.status = 'AUDIO'
self.expectRawData(327) # Two ID bytes + 10 GSM frames self.expectRawData(327) # Two ID bytes + 10 GSM frames
elif packet_type == 3: # Client list elif packet_type == 3: # Client list
self.status = 'CLIENTS' self.status = 'CLIENTS'
self.expectRawData(2) # Discard two null bytes self.expectRawData(2) # Discard two null bytes
elif packet_type == 4: # Text elif packet_type == 4: # Text
self.startMultiLineMessage('TEXT') self.startMultiLineMessage('TEXT')
elif packet_type == 5: # Channel list elif packet_type == 5: # Channel list
self.startMultiLineMessage('NETWORKS') self.startMultiLineMessage('NETWORKS')
elif packet_type == 6: # Admin list elif packet_type == 6: # Admin list
self.startMultiLineMessage('ADMIN') self.startMultiLineMessage('ADMIN')
elif packet_type == 7: # Access list elif packet_type == 7: # Access list
self.startMultiLineMessage('ACCESS') self.startMultiLineMessage('ACCESS')
elif packet_type == 8: # Block list elif packet_type == 8: # Block list
self.startMultiLineMessage('BLOCK') self.startMultiLineMessage('BLOCK')
elif packet_type == 9: # Mute list elif packet_type == 9: # Mute list
self.startMultiLineMessage('MUTE') self.startMultiLineMessage('MUTE')
elif packet_type == 10: # Access list flags elif packet_type == 10: # Access list flags
self.startMultiLineMessage('ACCESSFLAGS') self.startMultiLineMessage('ACCESSFLAGS')
else: else:
log.err("Unknown packet type %d" % packet_type) log.err("Unknown packet type %d" % packet_type)
elif self.status == 'CLIENTS': elif self.status == 'CLIENTS':
self.startMultiLineMessage('CLIENTS') self.startMultiLineMessage('CLIENTS')
elif self.status == 'AUDIO': elif self.status == 'AUDIO':
self.ready() self.ready()
self.decodeAUDIO(ord(data[0])*256+ord(data[1]), data[2:]) self.decodeAUDIO(ord(data[0])*256+ord(data[1]), data[2:])
elif self.status == 'TX': elif self.status == 'TX':
self.ready() self.ready()
self.decodeTX(ord(data[0])*256+ord(data[1])) self.decodeTX(ord(data[0])*256+ord(data[1]))
def login(self): def login(self):
ap = "CT:"+self.user.asXML( ap = "CT:"+self.user.asXML(
'VX','EA','PW','ON','BC','DS','NN','CT','NT') 'VX','EA','PW','ON','BC','DS','NN','CT','NT')
self.status = 'AUTH' self.status = 'AUTH'
self.phase = 0 self.phase = 0
self.sendLine(ap) self.sendLine(ap)
def pong(self): def pong(self):
self.sendLine('P') self.sendLine('P')
def setStatus(self, status): def setStatus(self, status):
self.sendLine('ST:%s' % str(status)) self.sendLine('ST:%s' % str(status))
def stopTransmission(self): def stopTransmission(self):
self.sendLine('RX0') self.sendLine('RX0')
def startTransmission(self): def startTransmission(self):
self.sendLine('TX0') self.sendLine('TX0')
def sendAudioFrame(self, frame): def sendAudioFrame(self, frame):
self.resetTimeout() self.resetTimeout()
self.sendLine('TX1') self.sendLine('TX1')
self.transport.write(frame) self.transport.write(frame)
def streamStep(self, count): def streamStep(self, count):
if count > 1: if count > 1:
log.msg("WARNING: lost %d ticks" % (count-1)) log.msg("WARNING: lost %d ticks" % (count-1))
for i in range(count): for i in range(count):
self.sendAudioFrame(self.txq.get_nowait()) self.sendAudioFrame(self.txq.get_nowait())
def stopStreaming(self): def stopStreaming(self):
self.txtimer.stop() self.txtimer.stop()
def _streamAck(self): def _streamAck(self):
self.txtimer = LoopingCall.withCount(self.streamStep) self.txtimer = LoopingCall.withCount(self.streamStep)
self.txtimer.start(0.20).addCallback( self.txtimer.start(0.20).addCallback(
lambda _: self.stopTransmission()).addErrback( lambda _: self.stopTransmission()).addErrback(
lambda _: self.stopTransmission()) lambda _: self.stopTransmission())
def feedStreaming(self, frames): def feedStreaming(self, frames):
if type(frames) == list: if type(frames) == list:
for frame in frames: for frame in frames:
self.txq.put_nowait(frame) self.txq.put_nowait(frame)
else: else:
self.txq.put_nowait(frames) self.txq.put_nowait(frames)
def startStreaming(self): def startStreaming(self):
self.startTransmission() self.startTransmission()
def sendTextMessage(self, dest, text): def sendTextMessage(self, dest, text):
self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text))) self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text)))
def addAdmin(self, client_ip): def addAdmin(self, client_ip):
self.sendLine("AA:"+formatSimpleXML(dict(IP=client_ip))) self.sendLine("AA:"+formatSimpleXML(dict(IP=client_ip)))
def removeAdmin(self, client_ip): def removeAdmin(self, client_ip):
self.sendLine("DA:"+formatSimpleXML(dict(IP=client_ip))) self.sendLine("DA:"+formatSimpleXML(dict(IP=client_ip)))
def addMute(self, client_ip): def addMute(self, client_ip):
self.sendLine("MC:"+formatSimpleXML(dict(IP=client_ip))) self.sendLine("MC:"+formatSimpleXML(dict(IP=client_ip)))
def removeMute(self, client_ip): def removeMute(self, client_ip):
self.sendLine("UM:"+formatSimpleXML(dict(IP=client_ip))) self.sendLine("UM:"+formatSimpleXML(dict(IP=client_ip)))
def addBlock(self, client_ip): def addBlock(self, client_ip):
self.sendLine("BC:"+formatSimpleXML(dict(IP=client_ip))) self.sendLine("BC:"+formatSimpleXML(dict(IP=client_ip)))
def removeBlock(self, client_ip): def removeBlock(self, client_ip):
self.sendLine("UC:"+formatSimpleXML(dict(IP=client_ip))) self.sendLine("UC:"+formatSimpleXML(dict(IP=client_ip)))
def addAccess(self, email): def addAccess(self, email):
self.sendLine("AT:"+formatSimpleXML(dict(EA=email))) self.sendLine("AT:"+formatSimpleXML(dict(EA=email)))
def removeAccess(self, email): def removeAccess(self, email):
self.sendLine("DT:"+formatSimpleXML(dict(EA=email))) self.sendLine("DT:"+formatSimpleXML(dict(EA=email)))
def addTalk(self, email): def addTalk(self, email):
self.sendLine("ETX:"+formatSimpleXML(dict(EA=email))) self.sendLine("ETX:"+formatSimpleXML(dict(EA=email)))
def removeTalk(self, email): def removeTalk(self, email):
self.sendLine("RTX:"+formatSimpleXML(dict(EA=email))) self.sendLine("RTX:"+formatSimpleXML(dict(EA=email)))
def accessFlagEnable(self, enable): def accessFlagEnable(self, enable):
if enable: if enable:
v = 1 v = 1
else: else:
v = 0 v = 0
self.sendLine("ENA:%d" % v) self.sendLine("ENA:%d" % v)
def accessFlagTalk(self, enable): def accessFlagTalk(self, enable):
if enable: if enable:
v = 1 v = 1
else: else:
v = 0 v = 0
self.sendLine("TXR:%d" % v) self.sendLine("TXR:%d" % v)
def unimplemented(self, status, msg): def unimplemented(self, status, msg):
log.msg("Unimplemented: %s: %s" % (status, msg)) log.msg("Unimplemented: %s: %s" % (status, msg))
def decodeAUDIO(self, from_id, frames): def decodeAUDIO(self, from_id, frames):
self.audioFrameReceived(from_id, frames) self.audioFrameReceived(from_id, frames)
def decodeTX(self, my_id): def decodeTX(self, my_id):
self._streamAck() self._streamAck()
def decodeTEXT(self, msg): def decodeTEXT(self, msg):
self.textMessageReceived(msg[0], msg[1], msg[2]) self.textMessageReceived(msg[0], msg[1], msg[2])
def decodeCLIENTS(self, msg): def decodeCLIENTS(self, msg):
self.clientsListUpdated([parseSimpleXML(x) for x in msg]) self.clientsListUpdated([parseSimpleXML(x) for x in msg])
def decodeNETWORKS(self, msg): def decodeNETWORKS(self, msg):
self.networksListUpdated(msg) self.networksListUpdated(msg)
def decodeADMIN(self, msg): def decodeADMIN(self, msg):
self.adminListUpdated([parseSimpleXML(x) for x in msg]) self.adminListUpdated([parseSimpleXML(x) for x in msg])
def decodeACCESS(self, msg): def decodeACCESS(self, msg):
self.accessListUpdated([parseSimpleXML(x) for x in msg]) self.accessListUpdated([parseSimpleXML(x) for x in msg])
def decodeBLOCK(self, msg): def decodeBLOCK(self, msg):
self.blockListUpdated([parseSimpleXML(x) for x in msg]) self.blockListUpdated([parseSimpleXML(x) for x in msg])
def decodeMUTE(self, msg): def decodeMUTE(self, msg):
self.muteListUpdated([parseSimpleXML(x) for x in msg]) self.muteListUpdated([parseSimpleXML(x) for x in msg])
def decodeACCESSFLAGS(self, msg): def decodeACCESSFLAGS(self, msg):
self.accessFlagsUpdated(msg[0], msg[1]) self.accessFlagsUpdated(msg[0], msg[1])
def decodeUNKNOWN(self, code, msg): def decodeUNKNOWN(self, code, msg):
log.msg("%s: %s" % (code, msg)) log.msg("%s: %s" % (code, msg))
def loginResponse(self, info): def loginResponse(self, info):
pass pass
def audioFrameReceived(self, from_id, frame): def audioFrameReceived(self, from_id, frame):
pass pass
def textMessageReceived(self, client, message, target): def textMessageReceived(self, client, message, target):
pass pass
def clientsListUpdated(self, clients): def clientsListUpdated(self, clients):
pass pass
def networksListUpdated(self, networks): def networksListUpdated(self, networks):
pass pass
def adminListUpdated(self, admins): def adminListUpdated(self, admins):
pass pass
def accessListUpdated(self, access): def accessListUpdated(self, access):
pass pass
def blockListUpdated(self, blocks): def blockListUpdated(self, blocks):
pass pass
def muteListUpdated(self, mutes): def muteListUpdated(self, mutes):
pass pass
def accessFlagsUpdated(self, access, talk): def accessFlagsUpdated(self, access, talk):
pass pass
class FRNClientFactory(ReconnectingClientFactory): class FRNClientFactory(ReconnectingClientFactory):
protocol = FRNClient protocol = FRNClient
maxRetries = 10 maxRetries = 10
def __init__(self, user): def __init__(self, user):
self.user = user self.user = user
def startedConnecting(self, connector): def startedConnecting(self, connector):
log.msg('Started to connect') log.msg('Started to connect')
def buildProtocol(self, addr): def buildProtocol(self, addr):
log.msg('Connected') log.msg('Connected')
p = ReconnectingClientFactory.buildProtocol(self, addr) p = ReconnectingClientFactory.buildProtocol(self, addr)
p.user = self.user p.user = self.user
return p return p
def clientConnectionLost(self, connector, reason): def clientConnectionLost(self, connector, reason):
log.msg('Lost connection. Reason: %s' % reason) log.msg('Lost connection. Reason: %s' % reason)
ReconnectingClientFactory.clientConnectionLost( ReconnectingClientFactory.clientConnectionLost(
self, connector, reason) self, connector, reason)
def clientConnectionFailed(self, connector, reason): def clientConnectionFailed(self, connector, reason):
log.err('Connection failed. Reason: %s' % reason) log.err('Connection failed. Reason: %s' % reason)
ReconnectingClientFactory.clientConnectionFailed( ReconnectingClientFactory.clientConnectionFailed(
self, connector, reason) self, connector, reason)
# vim: set et ai sw=4 ts=4 sts=4: # vim: set et ai sw=4 ts=4 sts=4:

View File

@ -6,7 +6,6 @@
from twisted.internet.defer import Deferred, succeed from twisted.internet.defer import Deferred, succeed
from twisted.internet.protocol import ReconnectingClientFactory, ServerFactory from twisted.internet.protocol import ReconnectingClientFactory, ServerFactory
from twisted.protocols.basic import LineOnlyReceiver from twisted.protocols.basic import LineOnlyReceiver
from twisted.protocols.policies import TimeoutMixin
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from twisted.python import log from twisted.python import log
from frn.user import FRNUser from frn.user import FRNUser
@ -53,10 +52,10 @@ class FRNManagerClient(LineOnlyReceiver):
def loginDone(result): def loginDone(result):
self.managerdata = parseSimpleXML(result) self.managerdata = parseSimpleXML(result)
log.msg("Server login results: %s" % str(self.managerdata)) log.msg("Server login results: %s" % str(self.managerdata))
if int(self.managerdata['MC']) > 2009004: if int(self.managerdata['mc']) > 2009004:
self.sendLine(responseToChallange( self.sendLine(responseToChallange(
self.managerdata['KP'])) self.managerdata['kp']))
if self.managerdata['AL'] != '0': if self.managerdata['al'] != '0':
self.transport.loseConnection() self.transport.loseConnection()
else: else:
self.pingtimer = LoopingCall(self.sendPing) self.pingtimer = LoopingCall(self.sendPing)
@ -108,22 +107,17 @@ class FRNManagerClientFactory(ReconnectingClientFactory):
pass pass
class FRNManagerServer(LineOnlyReceiver, TimeoutMixin): class FRNManagerServer(LineOnlyReceiver):
def connectionMade(self): def connectionMade(self):
log.msg("Manager client connected from %s" % self.transport.getPeer().host) log.msg("Manager client connected from %s" % self.transport.getPeer().host)
self._phase = "CONNECTED" self._phase = "CONNECTED"
self.serverInfo = None
self.kp = makeRandomChallange() self.kp = makeRandomChallange()
self.setTimeout(30.0)
def connectionLost(self, reason): def connectionLost(self, reason):
log.msg("Manager client disconnected from %s: %s" % log.msg("Manager client disconnected from %s: %s" %
(self.transport.getPeer().host, reason)) (self.transport.getPeer().host, reason))
if self.serverInfo: self.manager.serverLogout(None) # FIXME
self.manager.serverLogout(self.serverInfo)
self.serverInfo = None
self._phase = "DISCONNECTED"
def _authOnly(self): def _authOnly(self):
if self._phase != "AUTHENTICATED": if self._phase != "AUTHENTICATED":
@ -131,13 +125,10 @@ class FRNManagerServer(LineOnlyReceiver, TimeoutMixin):
self.transport.loseConnection() self.transport.loseConnection()
def lineReceived(self, line): def lineReceived(self, line):
self.resetTimeout()
sline = line.strip() sline = line.strip()
if self._phase == "CHALLANGE": if self._phase == "CHALLANGE":
if sline == responseToChallange(self.kp): if sline == responseToChallange(self.kp):
self._phase = "AUTHENTICATED" self._phase = "AUTHENTICATED"
self.serverInfo = self.tmpServerInfo
log.msg("Auth success: %s" % repr(self.serverInfo))
else: else:
self.transport.loseConnection() self.transport.loseConnection()
else: else:
@ -155,19 +146,8 @@ class FRNManagerServer(LineOnlyReceiver, TimeoutMixin):
handler(xbody) handler(xbody)
def sendClientList(self): def sendClientList(self):
def gotClientList(cl):
self.sendLine(str(len(cl)))
for sn, sp in cl:
self.sendLine("%s - Port: %d" % (sn, sp))
self.sendLine(str(len(cl[(sn,sp)])))
for nt in cl[(sn,sp)]:
self.sendLine(nt)
self.sendLine(str(len(cl[(sn,sp)][nt])))
for u in cl[(sn,sp)][nt]:
self.sendLine(u.asXML('EA','ON','BC','DS','NN','CT'))
log.msg("SM") log.msg("SM")
return self.manager.getClientList().addCallback(gotClientList) self.sendLine('0') # TODO
def unimplemented(self, cmd, body): def unimplemented(self, cmd, body):
log.err("Unimplemented command %s: %s" % (cmd, str(body))) log.err("Unimplemented command %s: %s" % (cmd, str(body)))
@ -176,16 +156,13 @@ class FRNManagerServer(LineOnlyReceiver, TimeoutMixin):
def sendManagerInfo(res): def sendManagerInfo(res):
if versions.manager > 2009004: if versions.manager > 2009004:
self._phase = "CHALLANGE" self._phase = "CHALLANGE"
self.tmpServerInfo = FRNUser(**body)
else: else:
self._phase = "AUTHENTICATED" self._phase = "AUTHENTICATED"
self.serverInfo = FRNUser(**body)
log.msg("Auth success: %s" % repr(self.serverInfo))
self.sendLine(formatSimpleXML([ self.sendLine(formatSimpleXML([
('SV', versions.server), ('SV', versions.server),
('CV', versions.client), ('CV', versions.client),
('MC', versions.manager), ('MC', versions.manager),
('AL', res), ('AL', res['al']),
('KP', self.kp) ('KP', self.kp)
])) ]))
log.msg("SC: %s" % str(body)) log.msg("SC: %s" % str(body))
@ -195,13 +172,13 @@ class FRNManagerServer(LineOnlyReceiver, TimeoutMixin):
def decodeCC(self, body): # Client login def decodeCC(self, body): # Client login
log.msg("CC: %s" % str(body)) log.msg("CC: %s" % str(body))
self._authOnly() self._authOnly()
self.manager.clientLogin(self.serverInfo, FRNUser(**body)).addCallback( self.manager.clientLogin(FRNUser(**body)).addCallback(
self.sendLine) self.sendLine)
def decodeCD(self, body): # Client logout def decodeCD(self, body): # Client logout
log.msg("CD: %s" % str(body)) log.msg("CD: %s" % str(body))
self._authOnly() self._authOnly()
self.manager.clientLogout(self.serverInfo, FRNUser(**body)).addCallback( self.manager.clientLogout(FRNUser(**body)).addCallback(
self.sendLine) self.sendLine)
def decodeIG(self, body): # Account creation def decodeIG(self, body): # Account creation

View File

@ -3,7 +3,7 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com> # Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info # See LICENSE.txt for copyright info
from twisted.internet.defer import Deferred, succeed from twisted.internet.defer import Deferred
from twisted.internet.protocol import ServerFactory from twisted.internet.protocol import ServerFactory
from twisted.protocols.policies import TimeoutMixin from twisted.protocols.policies import TimeoutMixin
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
@ -32,19 +32,20 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
def connectionLost(self, reason): def connectionLost(self, reason):
log.msg("Client disconnected: %s" % self.clientAddress.host) log.msg("Client disconnected: %s" % self.clientAddress.host)
self.disconnect() self.stopPinging()
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) BufferingLineReceiver.connectionLost(self, reason)
def timeoutConnection(self):
log.msg("Client dead: disconnecting %s" % self.user)
self.disconnect()
def lineReceived(self, line): def lineReceived(self, line):
self.resetTimeout() self.resetTimeout()
sline = line.strip() sline = line.strip()
if self.waitingKey: if self.waitingKey:
if responseToChallange(self.kp) != sline: if responseToChallange(self.kp) != sline:
self.disconnect() self.transport.loseConnection()
return return
else: else:
self.waitingKey = False self.waitingKey = False
@ -53,13 +54,8 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
self.sendMuteList(self.factory.tracker.getMuteList()) self.sendMuteList(self.factory.tracker.getMuteList())
self.sendBlockList(self.factory.tracker.getBlockList()) self.sendBlockList(self.factory.tracker.getBlockList())
if self.role == 'OWNER': if self.role == 'OWNER':
self.sendAdminList(self.factory.tracker.getAdminList()) self.sendAccessFlags(None)
ac, tx = self.factory.tracker.getAclFlags(self.user.NT) self.sendAccessList([])
self.sendAccessFlags(ac,tx)
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
if self.role not in ['OK', 'ADMIN', 'OWNER']:
self.disconnect()
return
self.startPinging() self.startPinging()
self.setTimeout(10.0) self.setTimeout(10.0)
return return
@ -71,7 +67,7 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
else: else:
command, body = sline.split(':', 1) command, body = sline.split(':', 1)
if command != 'CT' and self.user is None: if command != 'CT' and self.user is None:
self.disconnect() self.transport.loseConnection()
return return
handler = getattr(self, 'decode'+command, None) handler = getattr(self, 'decode'+command, None)
if body[0] == '<' and body[-1] == '>': if body[0] == '<' and body[-1] == '>':
@ -102,20 +98,9 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
else: else:
return (userId, "") return (userId, "")
user.IP = self.clientAddress.host user.IP = self.clientAddress.host
if self.factory.tracker.canLogin(user): return self.factory.manager.clientLogin(user).addCallback(loginReturned)
return self.factory.manager.clientLogin(self.factory.serverAuth, user).addCallback(loginReturned)
else:
log.msg("BLOCK %s" % repr(user))
return succeed(("BLOCK", ""))
def disconnect(self): def disconnect(self):
self.stopPinging()
if self.user is not None:
log.msg("Logging out client %s" % self.user)
if self.factory.talking.get(self.user.NT) == self:
del self.factory.talking[self.user.NT]
self.factory.manager.clientLogout(self.factory.serverAuth, self.user)
self.factory.tracker.logout(self)
self.transport.loseConnection() self.transport.loseConnection()
def decodeCT(self, body): def decodeCT(self, body):
@ -127,10 +112,8 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
if self.role == 'OK': if self.role == 'OK':
if self.user.EA == self.factory.serverAuth.OW: if self.user.EA == self.factory.serverAuth.OW:
self.role = 'OWNER' self.role = 'OWNER'
log.msg("%s promoted to OWNER" % clientId)
elif self.factory.tracker.isAdmin(self.user.ID): elif self.factory.tracker.isAdmin(self.user.ID):
self.role = 'ADMIN' self.role = 'ADMIN'
log.msg("%s promoted to ADMIN" % clientId)
if versions.server > 2009004: if versions.server > 2009004:
self.waitingKey = True self.waitingKey = True
self.sendLine(str(versions.client)) self.sendLine(str(versions.client))
@ -141,7 +124,7 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
self.sendLine( self.sendLine(
self.factory.serverAuth.asXML('MT','SV','AL','BN','BP','KP')) self.factory.serverAuth.asXML('MT','SV','AL','BN','BP','KP'))
if self.role not in ['OK', 'OWNER', 'ADMIN']: if self.role not in ['OK', 'OWNER', 'ADMIN']:
self.disconnect() self.transport.loseConnection()
else: else:
self.sendNetworkList(self.factory.tracker.getNetworkList()) self.sendNetworkList(self.factory.tracker.getNetworkList())
self.transport.setTcpNoDelay(True) self.transport.setTcpNoDelay(True)
@ -160,8 +143,6 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
def decodeRX(self, body): def decodeRX(self, body):
log.msg("RX%d" % int(body)) log.msg("RX%d" % int(body))
self.startPinging() self.startPinging()
if self.factory.talking.get(self.user.NT) == self:
del self.factory.talking[self.user.NT]
for c in self.factory.tracker.getClientList(self.user.NT): for c in self.factory.tracker.getClientList(self.user.NT):
cp = self.factory.tracker.getClientProtocol(c.ID).startPinging() cp = self.factory.tracker.getClientProtocol(c.ID).startPinging()
@ -173,101 +154,55 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
def decodeTM(self, body): def decodeTM(self, body):
log.msg("TM: %s" % str(body)) log.msg("TM: %s" % str(body))
if body['ID'] == '': if body['id'] == '':
msgtype = 'A' msgtype = 'A'
else: else:
msgtype = 'P' msgtype = 'P'
for c in self.factory.tracker.getClientList(self.user.NT): for c in self.factory.tracker.getClientList(self.user.NT):
if msgtype == 'A' or c.ID == body['ID']: if msgtype == 'A' or c.ID == body['id']:
if c.ID != self.user.ID or msgtype == 'A': if c.ID != self.user.ID or msgtype == 'A':
client = self.factory.tracker.getClientProtocol(c.ID) client = self.factory.tracker.getClientProtocol(c.ID)
client.sendTextMessage(self.user.ID, body['MS'], msgtype) client.sendTextMessage(self.user.ID, body['ms'], msgtype)
def decodeTX(self, body): def decodeTX(self, body):
if body == '0': if body == '0':
if not self.factory.tracker.isMute(self.user.ID): if not self.factory.tracker.isMute(self.user.ID):
if not self.factory.talking.get(self.user.NT): ih,il = divmod(self.getIndex(), 256)
self.factory.talking[self.user.NT] = self self.transport.write(chr(1)+chr(ih)+chr(il))
ih,il = divmod(self.getIndex(), 256)
self.transport.write(chr(1)+chr(ih)+chr(il))
elif body == '1': elif body == '1':
self.stopPinging() self.stopPinging()
self.expectRawData(325) self.expectRawData(325)
def decodeMC(self, body): def decodeMC(self, body):
if self.role in ["OWNER","ADMIN"]: if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.mute(self.user, body['IP']) self.factory.tracker.mute(self.user, body['ip'])
if self.factory.tracker.isLoggedIn(body['IP']): if self.factory.tracker.isLoggedIn(body['ip']):
self.factory.tracker.getClientProtocol(body['IP']).decodeRX('0') self.factory.tracker.getClientProtocol(body['ip']).decodeRX('0')
def decodeUM(self, body): def decodeUM(self, body):
if self.role in ["OWNER","ADMIN"]: if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.unMute(body['IP']) self.factory.tracker.unMute(body['ip'])
def decodeBC(self, body): def decodeBC(self, body):
if self.role in ["OWNER","ADMIN"]: if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.block(self.user, body['IP']) self.factory.tracker.block(self.user, body['ip'])
self.factory.tracker.getClientProtocol(body['IP']).disconnect() self.factory.tracker.getClientProtocol(body['ip']).disconnect()
def decodeUC(self, body): def decodeUC(self, body):
if self.role in ["OWNER","ADMIN"]: if self.role in ["OWNER","ADMIN"]:
self.factory.tracker.unBlock(body['IP']) self.factory.tracker.unBlock(body['ip'])
def decodeAA(self, body): def decodeAA(self, body):
if self.role == "OWNER": if self.role == "OWNER":
self.factory.tracker.admin(body['IP']) self.factory.tracker.admin(body['ip'])
def decodeDA(self, body): def decodeDA(self, body):
if self.role == "OWNER": if self.role == "OWNER":
self.factory.tracker.unAdmin(body['IP']) self.factory.tracker.unAdmin(body['ip'])
def decodeETX(self, body):
log.msg("decodeETX(%s)" % str(body))
if self.role == "OWNER":
self.factory.tracker.setAclTx(self.user.NT, body['EA'], True)
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
def decodeRTX(self, body):
log.msg("decodeRTX(%s)" % str(body))
if self.role == "OWNER":
self.factory.tracker.setAclTx(self.user.NT, body['EA'], False)
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
def decodeDT(self, body):
log.msg("decodeDT(%s)" % str(body))
if self.role == "OWNER":
self.factory.tracker.delAcl(self.user.NT, body['EA'])
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
def decodeAT(self, body):
log.msg("decodeAT(%s)" % str(body))
if self.role == "OWNER":
self.factory.tracker.addAcl(self.user.NT, body['EA'])
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
def decodeTXR(self, body):
log.msg("decodeTXR(%s)" % str(body))
if self.role == "OWNER":
en, _ = self.factory.tracker.getAclFlags(self.user.NT)
tx = body == '1'
self.factory.tracker.setAclFlags(self.user.NT, en, tx)
en,tx = self.factory.tracker.getAclFlags(self.user.NT)
self.sendAccessFlags(en, tx)
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
def decodeENA(self, body):
log.msg("decodeENA(%s)" % str(body))
if self.role == "OWNER":
_, tx = self.factory.tracker.getAclFlags(self.user.NT)
en = body == '1'
self.factory.tracker.setAclFlags(self.user.NT, en, tx)
en,tx = self.factory.tracker.getAclFlags(self.user.NT)
self.sendAccessFlags(en, tx)
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
def audioFrameReceived(self, frame): def audioFrameReceived(self, frame):
#log.msg("audioFrameReceived") #log.msg("audioFrameReceived")
if self.factory.tracker.canTalk(self.user.ID) and self.factory.talking.get(self.user.NT) == self: if not self.factory.tracker.isMute(self.user.ID):
clientIdx = self.getIndex() clientIdx = self.getIndex()
for c in self.factory.tracker.getClientList(self.user.NT): for c in self.factory.tracker.getClientList(self.user.NT):
if int(c.S) < 2 and c.ID != self.user.ID: if int(c.S) < 2 and c.ID != self.user.ID:
@ -314,19 +249,16 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
self.sendLine(c.asXML('NN','CT','BC','ON','ID')) self.sendLine(c.asXML('NN','CT','BC','ON','ID'))
def sendAccessList(self, clients): def sendAccessList(self, clients):
log.msg("Sending ACL to %s: %s" % (self.user.ON, str(clients)))
self.transport.write(chr(7)) self.transport.write(chr(7))
self.sendLine(str(len(clients))) self.sendLine(str(len(clients)))
for c in clients: for c in clients: # FIXME
self.sendLine(c.asXML('AI','NN','CT','BC','ON','ID')) self.sendLine(c.asXML('AI','NN','CT','BC','ON','ID'))
def sendAccessFlags(self, access, talk): def sendAccessFlags(self, flags):
log.msg("Sending ACL flags to %s: %s" % (self.user.ON, str((access, talk))))
FV = {True: '1', False: 'o'}
self.transport.write(chr(10)) self.transport.write(chr(10))
self.sendLine('2') self.sendLine('2') # TODO
self.sendLine(FV[access]) self.sendLine('o')
self.sendLine(FV[talk]) self.sendLine('o')
def sendTextMessage(self, clientId, message, target): def sendTextMessage(self, clientId, message, target):
self.transport.write(chr(4)) self.transport.write(chr(4))
@ -350,7 +282,7 @@ class FRNServerFactory(ServerFactory):
def __init__(self, trackerfile, manager, serverAuth): def __init__(self, trackerfile, manager, serverAuth):
self.manager = manager self.manager = manager
self.serverAuth = serverAuth self.serverAuth = serverAuth
self.talking = {} self.talking = None
self.officialNets = [] self.officialNets = []
self.tracker = ClientTracker( self.tracker = ClientTracker(
trackerfile, trackerfile,

View File

@ -35,7 +35,7 @@ class FRNUser(object):
self._fields[field.lower()] = str(value) self._fields[field.lower()] = str(value)
def get(self, field, default=''): def get(self, field, default=''):
return self._fields.get(field.lower(),'') return self._fields[field.lower()]
def items(self, *fields): def items(self, *fields):
if len(fields) == 0: if len(fields) == 0:

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
class FRNUserNotFound(StandardError): pass
# vim: set et ai sw=4 ts=4 sts=4:

84
frn/userstore/database.py Normal file
View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from twisted.python import log
from twisted.enterprise.adbapi import safe
from frn.userstore import FRNUserNotFound
from frn.user import FRNUser
def _asDict(curs, *args, **kw):
log.msg("Running query %s %s" % (str(args), str(kw)))
curs.execute(*args, **kw)
result = curs.fetchall()
columns = [d[0] for d in curs.description]
d = [dict(zip(columns, r)) for r in result]
log.msg("Query returned: %s" % str(d))
return d
def _returnId(curs, *args, **kw):
log.msg("Running query %s %s" % (str(args), str(kw)))
curs.execute(*args, **kw)
rowId = curs.lastrowid
log.msg("Query returned: %s" % str(rowId))
return rowId
class DatabaseUserStore(object):
def __init__(self, pool):
self._pool = pool
def _query(self, *args, **kw):
return self._pool.runInteraction(_asDict, *args, **kw)
def getByName(self, name, email):
try:
return self._query("""
SELECT * FROM frn_users
WHERE "on"=? AND "ea"=?""",
(name, email)).addCallback(
lambda x: FRNUser(**x[0]))
except IndexError:
raise FRNUserNotFound
def getById(self, userId):
return self._query("""
SELECT * FROM frn_users
WHERE "id"=?""",
(int(userId), )).addCallback(
lambda x: FRNUser(x[0]))
def update(self, userId, **kw):
assignments = ','.join(["\"%s\"='%s'" % (k,safe(v))
for k,v in kw.items()])
op = "UPDATE frn_users SET "+assignments+" WHERE \"id\"=?"
self._pool.runOperation(op, (int(userId),))
def create(self, user):
return self._query(
"""INSERT INTO frn_users ("on","ea") VALUES (?,?)""",
(user.ON, user.EA)).addCallback(lambda x: "%015s" % x)
def list(self):
return self._query("SELECT * FROM frn_users").addCallback(
lambda x: [FRNUser(y) for y in x])
def login(self, user):
def gotUser(u):
log.msg("Got user %s" % u.ID)
if u.PW == user.PW:
return u.ID
return "WRONG"
log.msg("Authenticating %s (%s)" % (user.ON, user.EA))
return self.getByName(user.ON, user.EA).addCallbacks(
gotUser, lambda x: "WRONG")
def logout(self, userId):
pass
# vim: set et ai sw=4 ts=4 sts=4:

View File

@ -3,7 +3,7 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com> # Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info # See LICENSE.txt for copyright info
import re from HTMLParser import HTMLParser
from random import choice from random import choice
@ -19,10 +19,45 @@ def makeRandomChallange():
return ''.join([choice('0123456789') for i in range(6)]) return ''.join([choice('0123456789') for i in range(6)])
re_tag = re.compile(r"<([A-Z]{1,2})>(.*)</\1>") class SimpleXMLParser(HTMLParser):
"""Dirty FRN-specific hack to handle bogus one-level nesting only
XML-like syntax"""
def handle_starttag(self, tag, attrs):
if not hasattr(self, 'fields'):
self.fields = {}
self.next_field = None
self.next_data = ''
if self.next_field is None:
self.next_field = tag
self.next_data = ''
else:
if attrs:
a = ' '+' '.join(['%s="%s"' % (k,v) for k,v in attrs])
else:
a = ''
self.next_data += "<%s%s>" % (tag,a)
def handle_data(self, data):
if self.next_field is not None:
self.next_data += data
def handle_endtag(self, tag):
if tag == self.next_field:
self.fields[self.next_field] = self.next_data
self.next_field = None
self.next_data = ''
else:
self.next_data += "</%s>" % tag
def get_fields(self):
return self.fields
def parseSimpleXML(xml): def parseSimpleXML(xml):
return dict(re_tag.findall(xml)) p = SimpleXMLParser()
p.feed(xml)
return p.get_fields()
def formatSimpleXML(elements): def formatSimpleXML(elements):
@ -30,6 +65,6 @@ def formatSimpleXML(elements):
items = elements.items() items = elements.items()
else: else:
items = elements items = elements
return ''.join(["<%s>%s</%s>" % (k.upper().encode('utf8'),unicode(v).encode('utf8'),k.upper().encode('utf8')) for k,v in items]) return ''.join(["<%s>%s</%s>" % (k.upper(),v,k.upper()) for k,v in items])
# vim: set et ai sw=4 ts=4 sts=4: # vim: set et ai sw=4 ts=4 sts=4:

View File

@ -1,13 +0,0 @@
From: GNURadioNetwork System Manager <admin@gnuradionetwork.org>
To: $EA
Subject: [GRN] $ON account data
Callsign and operator name: $ON
Password: $PW
Operating mode: $BC
Description: $DS
City and locator: $CT
Country: $NN
Registration date: $registration
Last login: $lastlogin

View File

@ -9,7 +9,6 @@ from frn.protocol.manager import FRNManagerServer, FRNManagerServerFactory
from twisted.enterprise.adbapi import ConnectionPool from twisted.enterprise.adbapi import ConnectionPool
from frn.manager.dummy import DummyManager from frn.manager.dummy import DummyManager
from frn.manager.remote import RemoteManager from frn.manager.remote import RemoteManager
from frn.manager.database import DatabaseManager
from frn.user import FRNUser from frn.user import FRNUser
from twisted.python import log from twisted.python import log
@ -18,22 +17,16 @@ if __name__ == '__main__':
log.startLogging(sys.stderr) log.startLogging(sys.stderr)
def dummyManagerFactory(): def standardManagerFactory():
log.msg("Building DummyManager") log.msg("Building Manager")
return DummyManager()
def remoteManagerFactory():
log.msg("Building RemoteManager")
return RemoteManager(reactor) return RemoteManager(reactor)
pool = ConnectionPool("sqlite3", "/dev/shm/frnmanager.sqlite3", check_same_thread=False, cp_noisy=True)
def databaseManagerFactory():
log.msg("Building DatabaseManager")
return DatabaseManager(pool)
reactor.listenTCP(10025, FRNManagerServerFactory( reactor.listenTCP(10025, FRNManagerServerFactory(
databaseManagerFactory # DatabaseUserStore(
# ConnectionPool("sqlite3", "frn_users.sqlite3",
# check_same_thread=False)),
# DummyManager()
standardManagerFactory
)) ))
reactor.run() reactor.run()

View File

@ -27,7 +27,7 @@ class FRNParrot(FRNClient):
def getClientName(self, client_id): def getClientName(self, client_id):
if self.clientsById.has_key(client_id): if self.clientsById.has_key(client_id):
return self.clientsById[client_id]['ON'] return self.clientsById[client_id]['on']
else: else:
return client_id return client_id
@ -64,11 +64,11 @@ class FRNParrot(FRNClient):
def startRepeating(self, from_id): def startRepeating(self, from_id):
log.msg("%s stopped talking: starting playback." % log.msg("%s stopped talking: starting playback." %
self.clients[from_id-1]['ON']) self.clients[from_id-1]['on'])
self.startStreaming() self.startStreaming()
def audioFrameReceived(self, from_id, frames): def audioFrameReceived(self, from_id, frames):
recname = sanitizeFilename(self.clients[from_id-1]['ON']) recname = sanitizeFilename(self.clients[from_id-1]['on'])
with file('recordings/%s.gsm' % recname, 'ab') as f: with file('recordings/%s.gsm' % recname, 'ab') as f:
f.write(frames) f.write(frames)
self.feedStreaming(frames) self.feedStreaming(frames)
@ -76,17 +76,17 @@ class FRNParrot(FRNClient):
self.parrot_timer.reset(PARROT_AUDIO_DELAY) self.parrot_timer.reset(PARROT_AUDIO_DELAY)
except: except:
log.msg("%s started talking" % log.msg("%s started talking" %
self.clients[from_id-1]['ON']) self.clients[from_id-1]['on'])
self.parrot_timer = self.factory.reactor.callLater( self.parrot_timer = self.factory.reactor.callLater(
PARROT_AUDIO_DELAY, self.startRepeating, from_id) PARROT_AUDIO_DELAY, self.startRepeating, from_id)
self.pong() self.pong()
def loginResponse(self, info): def loginResponse(self, info):
log.msg("Login: %s" % info['AL']) log.msg("Login: %s" % info['al'])
def clientsListUpdated(self, clients): def clientsListUpdated(self, clients):
self.clients = clients self.clients = clients
self.clientsById = dict([(i['ID'], i) for i in clients]) self.clientsById = dict([(i['id'], i) for i in clients])
class FRNParrotFactory(FRNClientFactory): class FRNParrotFactory(FRNClientFactory):

View File

@ -68,7 +68,7 @@ class FRNStream(FRNClient):
def getClientName(self, client_id): def getClientName(self, client_id):
if self.clientsById.has_key(client_id): if self.clientsById.has_key(client_id):
return self.clientsById[client_id]['ON'] return self.clientsById[client_id]['on']
else: else:
return client_id return client_id
@ -77,11 +77,11 @@ class FRNStream(FRNClient):
self.pong() self.pong()
def loginResponse(self, info): def loginResponse(self, info):
log.msg("Login: %s" % info['AL']) log.msg("Login: %s" % info['al'])
def clientsListUpdated(self, clients): def clientsListUpdated(self, clients):
self.clients = clients self.clients = clients
self.clientsById = dict([(i['ID'], i) for i in clients]) self.clientsById = dict([(i['id'], i) for i in clients])
class FRNStreamFactory(FRNClientFactory): class FRNStreamFactory(FRNClientFactory):

View File

@ -34,7 +34,7 @@ class FRNCrosslinkClient(FRNClient):
def getClientName(self, client_id): def getClientName(self, client_id):
if self.clientsById.has_key(client_id): if self.clientsById.has_key(client_id):
return self.clientsById[client_id]['ON'] return self.clientsById[client_id]['on']
else: else:
return client_id return client_id
@ -60,16 +60,16 @@ class FRNCrosslinkClient(FRNClient):
for server, port, u, factory in clients: for server, port, u, factory in clients:
cl = [] cl = []
cl.append("<u>%s@%s:%d</u>" % (u.NT,server,port)) cl.append("<u>%s@%s:%d</u>" % (u.NT,server,port))
role = factory.connection.serverdata.get('AL', None) role = factory.connection.serverdata.get('al', None)
if role in ['OK', 'ADMIN', 'OWNER']: if role in ['OK', 'ADMIN', 'OWNER']:
for c in factory.connection.clients: for c in factory.connection.clients:
ss = cStatus.get(c['S'], '?') ss = cStatus.get(c['s'], '?')
if c['M'] != '0': if c['m'] != '0':
ss = ss.lower() ss = ss.lower()
cl.append(" %s%s %s " % ( cl.append(" %s%s %s " % (
cType.get(c['BC'], 'G'), cType.get(c['bc'], 'G'),
ss, ss,
c['ON'].replace('<', '&lt;'), c['on'].replace('<', '&lt;'),
)) ))
else: else:
cl.append(" :!: DISCONNECTED :!: ") cl.append(" :!: DISCONNECTED :!: ")
@ -100,7 +100,7 @@ class FRNCrosslinkClient(FRNClient):
global clients, talking, lastMessages global clients, talking, lastMessages
if talking is None or talking == self: if talking is None or talking == self:
talking = self talking = self
talkingUser = self.clients[from_id-1]['ON'] talkingUser = self.clients[from_id-1]['on']
if len(lastMessages) > 0: if len(lastMessages) > 0:
if lastMessages[0] == talkingUser: if lastMessages[0] == talkingUser:
talkingUser = None talkingUser = None
@ -110,7 +110,7 @@ class FRNCrosslinkClient(FRNClient):
for _, _, _, factory in clients: for _, _, _, factory in clients:
conn = factory.connection conn = factory.connection
if conn != self: if conn != self:
role = conn.serverdata.get('AL', None) role = conn.serverdata.get('al', None)
if role in ['OK', 'ADMIN', 'OWNER']: if role in ['OK', 'ADMIN', 'OWNER']:
if conn.txOk: if conn.txOk:
conn.txReq = False conn.txReq = False
@ -126,15 +126,15 @@ class FRNCrosslinkClient(FRNClient):
self.pong() self.pong()
def loginResponse(self, info): def loginResponse(self, info):
log.msg("%s login: %s" % (self.user.ON, info['AL'])) log.msg("%s login: %s" % (self.user.ON, info['al']))
def clientsListUpdated(self, clients): def clientsListUpdated(self, clients):
self.clients = clients self.clients = clients
self.clientsById = dict([(i['ID'], i) for i in clients]) self.clientsById = dict([(i['id'], i) for i in clients])
if self.clientId is None: if self.clientId is None:
for c in clients: for c in clients:
if c['ON'] == self.user.ON and c['BC'] == self.user.BC: if c['on'] == self.user.ON and c['bc'] == self.user.BC:
self.clientId = c['ID'] self.clientId = c['id']
class FRNCrosslinkClientFactory(FRNClientFactory): class FRNCrosslinkClientFactory(FRNClientFactory):
protocol = FRNCrosslinkClient protocol = FRNCrosslinkClient

View File

@ -1,180 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from __future__ import with_statement
from zope.interface import implements
from twisted.plugin import IPlugin
from twisted.application.service import IServiceMaker
from frn.protocol.client import FRNClient, FRNClientFactory
from frn.user import FRNUser
from twisted.application import internet
from twisted.internet import reactor, task
from twisted.internet.defer import DeferredList
from twisted.python import log, usage
import os, string, time, struct
from os.path import curdir, join as pjoin
from ConfigParser import ConfigParser
safe_chars = string.ascii_letters+string.digits+' :;.,+-=$@'
WAV_HEADER = "524946460c06000057415645666d74201400000031000100401f00005906000041000000020040016661637404000000000000006461746100000000".decode('hex')
AUDIO_TIMEOUT = 5.0
def sanitizeFilename(name):
r = ''
for c in name:
if c in safe_chars:
r += c
return r
class FRNRecorder(FRNClient):
def getClientName(self, client_id):
if self.clientsById.has_key(client_id):
return self.clientsById[client_id]['ON']
else:
return client_id
def buildRecordingName(self, client_id):
ts = time.localtime()
opname = sanitizeFilename(self.clients[client_id-1]['ON'])
dirname = self.factory.recdir+"/"+time.strftime("%Y%m/%d/%H", ts)
filename = time.strftime("%Y%m%d-%H%M%S-", ts) + opname + '.wav'
try:
os.makedirs(dirname)
except OSError: pass
return dirname+'/'+filename
def buildMessageLogName(self):
ts = time.localtime()
dirname = self.factory.recdir+"/"+time.strftime("%Y%m/%d/%H", ts)
filename = time.strftime("%Y%m%d-%H-", ts) + 'messages.txt'
try:
os.makedirs(dirname)
except OSError: pass
return dirname+'/'+filename
def textMessageReceived(self, client, message, target):
if target == 'A':
f = open(self.buildMessageLogName(), 'a')
ts = time.strftime("%Y-%m-%d %H:%M:%S")
f.write("[%s] [%s] %s\r\n" %
(ts, self.getClientName(client), message))
f.close()
def closeFile(self):
size = self.recordingFile.tell()-len(WAV_HEADER)
time = size / 1625.0
if time > self.factory.mintime:
self.recordingFile.seek(0x30) # fact chunk value
self.recordingFile.write(struct.pack("<L", int(size/32.5)))
self.recordingFile.seek(0x38) # data chunk size
self.recordingFile.write(struct.pack("<L", size))
self.recordingFile.close()
else:
name = self.recordingFile.name
log.msg("Deleting short recording: %s" % (name,))
self.recordingFile.close()
os.remove(name)
def archiveRecording(self, from_id):
log.msg("%s stopped talking" %
self.clients[from_id-1]['ON'])
self.closeFile()
self.recordingFile = None
self.recordingOperator = None
def audioFrameReceived(self, from_id, frames):
if self.recordingOperator != self.clients[from_id-1]['ON']:
if self.recordingOperator is not None:
self.closeFile()
self.recordingOperator = self.clients[from_id-1]['ON']
self.recordingFile = file(self.buildRecordingName(from_id), 'wb')
self.recordingFile.write(WAV_HEADER)
self.recordingFile.write(frames)
try:
self.parrot_timer.reset(AUDIO_TIMEOUT)
except:
log.msg("%s started talking" %
self.clients[from_id-1]['ON'])
self.parrot_timer = self.factory.reactor.callLater(
AUDIO_TIMEOUT, self.archiveRecording, from_id)
self.pong()
def loginResponse(self, info):
log.msg("Login: %s" % info['AL'])
self.setStatus(1)
self.recordingOperator = None
self.recordingFile = None
def clientsListUpdated(self, clients):
self.clients = clients
self.clientsById = dict([(i['ID'], i) for i in clients])
class FRNRecorderFactory(FRNClientFactory):
protocol = FRNRecorder
reactor = reactor
def __init__(self, user, recdir="/var/spool/grn/recordings", mintime=2.0):
self.recdir = recdir
self.mintime = mintime
FRNClientFactory.__init__(self, user)
class Options(usage.Options):
optParameters = [
["confdir", "c", curdir, "Directory containing config files"],
["recdir", "d", "/var/spool/grn/recordings", "Directory where to save recordings"],
["mintime", "m", 2.0, "Minimun recording duration"]
]
def parseArgs(self, serverdef):
basedir = self['confdir']
acfg = ConfigParser()
acfg.read(['/etc/grn/accounts.conf',
pjoin(basedir,'accounts.conf'), 'accounts.conf'])
scfg = ConfigParser()
scfg.read(['/etc/grn/servers.conf',
pjoin(basedir,'servers.conf'), 'servers.conf'])
account_name, server_name, network_name = serverdef.split(':', 2)
self['server'] = scfg.get(server_name, 'server')
self['port'] = scfg.getint(server_name, 'port')
self['backup_server'] = scfg.get(server_name, 'backup_server')
self['backup_port'] = scfg.getint(server_name, 'backup_port')
self['operator'] = acfg.get(account_name, 'operator')
self['email'] = acfg.get(account_name, 'email')
self['password'] = acfg.get(account_name, 'password')
self['description'] = acfg.get(account_name, 'description')
self['country'] = acfg.get(account_name, 'country')
self['city'] = acfg.get(account_name, 'city')
self['network'] = network_name
class FRNRecorderServiceMaker(object):
implements(IServiceMaker, IPlugin)
tapname = "frnrecorder"
description = "Freeradionetwork recorder"
options = Options
def makeService(self, options):
return internet.TCPClient(options['server'], options['port'],
FRNRecorderFactory(
FRNUser(
EA=options['email'],
PW=options['password'], ON=options['operator'],
BC="Crosslink", DS=options['description'],
NN=options['country'], CT=options['city'], NT=options['network']),
options['recdir'], options['mintime']))
serviceMaker = FRNRecorderServiceMaker()
# vim: set et ai sw=4 ts=4 sts=4:

View File

@ -52,8 +52,7 @@ class FRNServerServiceMaker(object):
return internet.TCPServer(options['port'], return internet.TCPServer(options['port'],
FRNServerFactory( FRNServerFactory(
pjoin(options['confdir'], 'tracker.shelve'), pjoin(options['confdir'], 'tracker.shelve'),
# RemoteManager(), # Authenticate on frn.no-ip.info RemoteManager(),
RemoteManager(server='fri.no-ip.info', maskParrot=False),
FRNUser( FRNUser(
SN=options['server'],PT=options['port'], SN=options['server'],PT=options['port'],
BN=options['backup_server'], BP=options['backup_port'], BN=options['backup_server'], BP=options['backup_port'],