Fix bad commit

This commit is contained in:
Maurizio Porrato 2010-08-18 17:46:58 +02:00
parent 1a19977790
commit d4a1a0da99
15 changed files with 1077 additions and 0 deletions

23
docs/frn_users.sql Normal file
View File

@ -0,0 +1,23 @@
CREATE TABLE frn_users (
-- Fields required for FRN compatibility
"id" INTEGER PRIMARY KEY AUTOINCREMENT, -- User ID
"on" VARCHAR(30), -- Operator name (Callsign, Name)
"ea" VARCHAR(30), -- Email address
"pw" VARCHAR(30), -- Password
"bc" VARCHAR(30), -- Band/channel
"ds" VARCHAR(30), -- Description
"ct" VARCHAR(30), -- City - Locator
"nn" VARCHAR(30), -- Country
"nt" VARCHAR(30), -- Network
"ip" VARCHAR(15), -- Client IP
-- GRN specific fields
"rip" VARCHAR(20), -- IP the account was registered from
"cre" TIMESTAMP NOT NULL -- Date/time of account creation
DEFAULT CURRENT_TIMESTAMP,
"act" TIMESTAMP, -- Date/time of last account activity
"srv" VARCHAR(30) -- Server:port where usere is actually logged
);
CREATE UNIQUE INDEX idx_frn_users_on_ea ON frn_users("on","ea");
-- vim: set et ai sw=4 ts=4 sts=4:

118
docs/protocols.markdown Normal file
View File

@ -0,0 +1,118 @@
The FreeRadioNetwork protocol
=============================
This document describes the results of reverse-engeneering the official
set of [FreeRadioNetwork](http//freeradionetwork.eu) programs through
network traffic sniffing and software decompilation.
System components
-----------------
The FreeRadioNetwork system is composed of three programs:
* client
* server
* system manager
Client and server executables are freely available, while the system
manager is not. The role of the system manager is account management,
including password generation and credentials checking, keeping track
of active servers and connected users.
The client talks to the system manager only to get the list of active
servers and to request the creation of a new account/password: any other
communication from the client goes to the server.
Server listens for clients connection on TCP port 10024 (by default, but
can be changed through the server GUI), while the system manager listen
for server connections and client queries on TCP port 10025. In both the
client and the server, the system manager address is hard coded as
`frn.no-ip.info`.
Client - server protocol
------------------------
The client sends requests to the server in the form of CR+LF terminated
ascii text lines. Response to commands and unsolicited events from the
server (i.e. incoming text messages), excluding authentication procedure
and voice packets, starts with a byte indicating the message type
followed by one CR+LF terminated ascii line containing the decimal count
of lines following and then the lines composing the message body.
### Authentication ###
As soon as the TCP connection is established, the client sends a line
containing user's info and authentication credential formatted enclosing
fields between html-like tags, for example:
CT:<VX>2010002</VX><EA>user@example.com</EA><PW>XYZJKWXY</PW><ON>CALLSIGN, Name</ON><BC>PC Only</BC><DS></DS><NN>Country</NN><CT>City - Locator</CT><NT>Network</NT>
The line is, as usual, followed by the CR and LF chars (ascii 13 an 10).
The meaning of the fields is the following:
* **VX**: Client version (four digits for the year and three digits for
the release number)
* **EA**: User's email address
* **PW**: The password assigned by the system manager to the user
* **ON**: Operator's name, usually in the form CALLSIGN, RealName
* **BC**: Indicates the client type:
* `PC Only`
* `Parrot`
* `Crosslink`
* *BBB* `Ch`*CCC* *MM* `CTC`*TT* where **BBB** is the band (3 digits:
433, 446, 027,...), **CCC** is the channel number (3 digits), **MM**
is the modulation (AM or FM), **TT** is the subtone number (2 digits)
* **DS**: Description, only used for gateways, empty for other client
types
* **NN**: Country
* **CT**: Client's location in the form of City - Locator
* **NT**: Network (channel, room) to join on login
In response to the authentication request, the server sends two lines of
text (CR+LF terminated):
2010002
<MT></MT><SV>2009005</SV><AL>OK</AL><BN>backup.server.org</BN><BP>10024</BP><KP>327119</KP>
The first line contains the latest client version available.
The second one contains authentication results and server info:
* **MT**: Unknown meaning, always empty
* **SV**: Server version
* **AL**: Authentication result:
* `OK`: Success, unprivileged user
* `ADMIN`: Success, user has administration privileges
* `OWNER`: Success, user is the server owner
* `NOK`: Failed, invalid client version
* `WRONG`: Failed, invalid credentials
* `BLOCK`: Failed, user already logged in
* **BN**: Backup server
* **BP**: Backup server port
* **KP**: Six digits code used for second phase
At this point, if authentication succeeds, server versions greater than
2009004 needs one more line from the client before considering the
connection fully established. The line is a five digits number computed
as follows:
1. Split the KP code in two-digits numbers: `AABBCC`
2. Let `X` be the result of: `(AA+2)*(BB+1)+(CC+4)*(CC+7)`
3. Extend `X` to be 5 digits long, padding to the left with zeroes as
needed and let's call the resulting digits `DEFGH` respectively
4. The authentication code is composed by rearranging the digits as
`GDFHE`
For example, let's KP be `327119`. AA would be 32, BB=71 and CC=19.
X = (32+2)*(71+1)+(19+4)*(19+7) = 3046
Padding to five digits we would get `03046`, so D=0, E=3, F=0, G=4, H=6
so the auth code wold be `40063`.

30
frn/manager/__init__.py Normal file
View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import Interface
class IManager(Interface):
def serverLogin(user):
"""Logs server on"""
def serverLogout(user):
"""Logs server out"""
def clientLogin(user):
"""Logs client in"""
def clientLogout(user):
"""Logs client out"""
def getClientList():
"""Lists logged in clients"""
def registerUser(user):
"""Registers new user"""
# vim: set et ai sw=4 ts=4 sts=4:

37
frn/manager/database.py Normal file
View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import implements
from frn.manager import IManager
from frn.userstore.database import DatabaseUserStore
class DatabaseManager(object):
implements(IManager)
def __init__(self, store):
self._store = store
def serverLogin(self, user):
pass
def serverLogout(self, user):
pass
def clientLogin(self, user):
pass
def clientLogout(self, user):
pass
def getClientList(self):
pass
def registerUser(self, user):
pass
# vim: set et ai sw=4 ts=4 sts=4:

38
frn/manager/dummy.py Normal file
View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import implements
from frn.manager import IManager
from twisted.internet import defer
from random import randint
class DummyManager(object):
implements(IManager)
def _randId(self):
return '.'.join([str(randint(1,254)) for i in range(4)])
def serverLogin(self, user):
return defer.succeed(0)
def serverLogout(self, user):
return defer.succeed(None)
def clientLogin(self, user):
return defer.succeed(self._randId())
def clientLogout(self, user):
return defer.succeed('OK')
def getClientList(self):
return defer.succeed([])
def registerUser(self, user):
return defer.succeed('OK')
# vim: set et ai sw=4 ts=4 sts=4:

50
frn/manager/remote.py Normal file
View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from zope.interfaces import implements
from frn.manager import IManager
from twisted.python import log
from frn.protocol.manager import FRNManagerClient, FRNManagerClientFactory
class RemoteManager(object):
implements(IManager)
def __init__(self, reactor, server='frn.no-ip.info', port=10025):
self.reactor = reactor
self.server = server
self.port = port
self.factory = FRNManagerClientFactory()
self.factory.continueTrying = 0 # FIXME
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))
def serverLogout(self, user):
return self.managerConnection.sendServerLogout(user)
def clientLogin(self, user):
return self.managerConnection.sendClientLogin(user)
def clientLogout(self, user):
return self.managerConnection.sendClientLogout(user)
def getClientList(self):
return self.managerConnection.getClientList()
def registerUser(self, user):
return self.managerConnection.registerUser(user)
# vim: set et ai sw=4 ts=4 sts=4:

40
frn/protocol/common.py Normal file
View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from twisted.protocols.basic import LineReceiver
class BufferingLineReceiver(LineReceiver):
def connectionMade(self):
self.rawBuffer = ""
self.rawExpected = None
LineReceiver.connectionMade(self)
def expectRawData(self, howmany):
self.setRawMode()
self.rawExpected = howmany
self.rawDataReceived('')
def rawDataReceived(self, data):
if self.rawExpected is None:
raise NotImplementedError
self.rawBuffer += data
if len(self.rawBuffer) >= self.rawExpected:
expected = self.rawBuffer[:self.rawExpected]
rest = self.rawBuffer[self.rawExpected:]
self.rawBuffer = ""
self.rawExpected = None
self.expectedReceived(expected)
self.dataReceived(rest)
def setLineMode(self, extra=""):
LineReceiver.setLineMode(self, extra+self.rawBuffer)
def expectedReceived(self, data):
raise NotImplementedError
# vim: set et ai sw=4 ts=4 sts=4:

197
frn/protocol/manager.py Normal file
View File

@ -0,0 +1,197 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from twisted.internet.defer import Deferred, succeed
from twisted.internet.protocol import ReconnectingClientFactory, ServerFactory
from twisted.protocols.basic import LineOnlyReceiver
from twisted.internet.task import LoopingCall
from twisted.python import log
from frn.user import FRNUser
from frn.protocol import versions
from frn.utility import *
class FRNManagerClient(LineOnlyReceiver):
def connectionMade(self):
log.msg("Connected to manager [%s]" % self.transport.getPeer().host)
self.notifications = []
if not self.factory.managerConnection.called: # FIXME: Why???
self.factory.managerConnection.callback(self)
def connectionLost(self, reason):
log.msg("Manager disconnected")
try:
self.pingtimer.stop()
except: pass
for d in self.notifications:
d.errback(reason)
self.factory.managerConnection = Deferred()
def notifyFinish(self):
self.notifications.append(Deferred())
return self.notifications[-1]
def finish(self, result):
d = self.notifications[0]
del self.notifications[0]
d.callback(result)
def lineReceived(self, line):
log.msg("notifications: %s" % str(self.notifications))
if hasattr(self, 'serverlist'):
# TODO
pass
else:
self.finish(line.strip())
def sendServerLogin(self, user):
def loginDone(result):
self.managerdata = parseSimpleXML(result)
log.msg("Server login succeeded: %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()
return self.managerdata
log.msg("Sending server login")
user.VX = versions.server
self.sendLine("SC:"+user.asXML(
'VX','SN','PT','OW','PW'))
return self.notifyFinish().addCallback(loginDone)
def sendServerLogout(self, user):
self.transport.loseConnection()
return succeed(None)
def sendPing(self):
self.sendLine("P")
return self.notifyFinish()
def sendClientLogin(self, client):
self.sendLine("CC:"+client.asXML(
'EA','PW','ON','BC','NN','CT','NT','DS','IP'))
return self.notifyFinish()
def sendClientLogout(self, client):
self.sendLine("CD:"+client.asXML('ID'))
return self.notifyFinish()
def getClientList(self):
#self.sendLine('SM')
raise NotImplementedError # TODO
def sendRegisterUser(self, user):
self.sendLine("IG:"+user.asXML(
'ON','EA','BC','DS','NN','CT'))
return self.notifyFinish()
class FRNManagerClientFactory(ReconnectingClientFactory):
protocol = FRNManagerClient
def startFactory(self):
self.managerConnection = Deferred()
ReconnectingClientFactory.startFactory(self)
class FRNManagerServer(LineOnlyReceiver):
def connectionMade(self):
log.msg("Manager client connected from %s" % self.transport.getPeer().host)
self._phase = "CONNECTED"
self.kp = makeRandomChallange()
def connectionLost(self, reason):
log.msg("Manager client disconnected from %s: %s" %
(self.transport.getPeer().host, reason))
self.manager.serverLogout(None) # FIXME
def _authOnly(self):
if self._phase != "AUTHENTICATED":
log.msg("Unauthorized action!")
self.transport.loseConnection()
def lineReceived(self, line):
sline = line.strip()
if self._phase == "CHALLANGE":
if sline == responseToChallange(self.kp):
self._phase = "AUTHENTICATED"
else:
self.transport.loseConnection()
else:
if sline == 'P': # Ping
self.sendLine('F')
elif sline == 'SM': # Client list
self.sendClientList()
else:
cmd, body = sline.split(':', 1)
xbody = parseSimpleXML(body)
handler = getattr(self, 'decode'+cmd, None)
if handler is None:
self.unimplemented(cmd, xbody)
else:
handler(xbody)
def sendClientList(self):
log.msg("SM")
self.sendLine('0') # TODO
def unimplemented(self, cmd, body):
log.err("Unimplemented command %s: %s" % (cmd, str(body)))
def decodeSC(self, body): # Server login
def sendManagerInfo(res):
if versions.manager > 2009004:
self._phase = "CHALLANGE"
else:
self._phase = "AUTHENTICATED"
self.sendLine(formatSimpleXML([
('SV', versions.server),
('CV', versions.client),
('MC', versions.manager),
('AL', res['al']),
('KP', self.kp)
]))
log.msg("SC: %s" % str(body))
self.manager.serverLogin(FRNUser(**body)).addCallback(
sendManagerInfo) # TODO: second authentication phase
def decodeCC(self, body): # Client login
log.msg("CC: %s" % str(body))
self._authOnly()
self.manager.clientLogin(FRNUser(**body)).addCallback(
self.sendLine)
def decodeCD(self, body): # Client logout
log.msg("CD: %s" % str(body))
self._authOnly()
self.manager.clientLogout(FRNUser(**body)).addCallback(
self.sendLine)
def decodeIG(self, body): # Account creation
log.msg("IG: %s" % str(body))
self.manager.registerUser(FRNUser(**body)).addCallback(
self.sendLine)
class FRNManagerServerFactory(ServerFactory):
protocol = FRNManagerServer
def __init__(self, managerFactory):
self.managerFactory = managerFactory
def buildProtocol(self, addr):
p = ServerFactory.buildProtocol(self, addr)
p.manager = self.managerFactory()
return p
# vim: set et ai sw=4 ts=4 sts=4:

288
frn/protocol/server.py Normal file
View File

@ -0,0 +1,288 @@
# -*- 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:

10
frn/protocol/versions.py Normal file
View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
client = 2010002
server = 2009005
manager = 2009005
# vim: set et ai sw=4 ts=4 sts=4:

62
frn/user.py Normal file
View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from frn.utility import parseSimpleXML, formatSimpleXML
class FRNUser(object):
def __init__(self, **kw):
self._fields = {}
self.update(**kw)
def __getattr__(self, attr):
if attr.startswith('_'):
return super(FRNUser, self).__getattr__(attr)
else:
return self.get(attr)
def __setattr__(self, attr, value):
if attr.startswith('_'):
super(FRNUser, self).__setattr__(attr, value)
else:
self.set(attr, value)
def __str__(self):
return asXML(self)
def __repr__(self):
return "FRNUser(%s)" % \
', '.join(["%s='%s'" % (k,v) for k,v in self.items()])
def set(self, field, value):
self._fields[field.lower()] = str(value)
def get(self, field, default=''):
return self._fields[field.lower()]
def items(self, *fields):
if len(fields) == 0:
fields = self._fields.keys()
r = []
for field in fields:
r.append((field.upper(), self.get(field)))
return r
def dict(self, *fields):
return dict(self.items(*fields))
def update(self, **kw):
for field, value in kw.items():
self.set(field, value)
def updateXML(self, xml):
self.update(dict(parseSimpleXML(xml)))
def asXML(self, *fields):
return formatSimpleXML(self.items(*fields))
# vim: set et ai sw=4 ts=4 sts=4:

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:

33
manager.py Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from twisted.internet import reactor
from frn.protocol.manager import FRNManagerServer, FRNManagerServerFactory
from twisted.enterprise.adbapi import ConnectionPool
from frn.manager.dummy import DummyManager
from frn.manager.remote import RemoteManager
from frn.user import FRNUser
from twisted.python import log
if __name__ == '__main__':
import sys
log.startLogging(sys.stderr)
def standardManagerFactory():
log.msg("Building Manager")
return RemoteManager(reactor, '83.82.28.221')
reactor.listenTCP(10025, FRNManagerServerFactory(
# DatabaseUserStore(
# ConnectionPool("sqlite3", "frn_users.sqlite3",
# check_same_thread=False)),
# DummyManager()
standardManagerFactory
))
reactor.run()
# vim: set et ai sw=4 ts=4 sts=4:

58
server.py Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from twisted.internet import reactor
from frn.protocol.server import FRNServer, FRNServerFactory
from twisted.enterprise.adbapi import ConnectionPool
from frn.manager.remote import RemoteManager
from frn.manager.dummy import DummyManager
from frn.user import FRNUser
from twisted.python import log
if __name__ == '__main__':
import sys
from os.path import dirname, join as pjoin
from ConfigParser import ConfigParser
log.startLogging(sys.stderr)
basedir = dirname(__file__)
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'])
argc = len(sys.argv)
if argc >= 3:
account_name = sys.argv[1]
server_name = sys.argv[2]
server = scfg.get(server_name, 'server')
port = scfg.getint(server_name, 'port')
backup_server = scfg.get(server_name, 'backup_server')
backup_port = scfg.getint(server_name, 'backup_port')
owner = acfg.get(account_name, 'email')
password = acfg.get(account_name, 'password')
reactor.listenTCP(10024, FRNServerFactory(
# DatabaseUserStore(
# ConnectionPool("sqlite3", "frn_users.sqlite3",
# check_same_thread=False)),
# RemoteManager(reactor, '83.82.28.221'),
DummyManager(),
FRNUser(
SN=server,PT=port,
BN=backup_server, BP=backup_port,
OW=owner,PW=password)
))
reactor.run()
# vim: set et ai sw=4 ts=4 sts=4: