Add DB-based system manager server. New simple XML parser.

Este commit está contenido en:
Maurizio Porrato 2011-01-30 02:29:31 +01:00
padre 02b1c0209c
commit 214cb07962
Se han modificado 10 ficheros con 207 adiciones y 191 borrados

Ver fichero

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

Ver fichero

@ -5,33 +5,146 @@
from zope.interface import implements
from frn.manager import IManager
from frn.userstore.database import DatabaseUserStore
from twisted.python import log
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):
implements(IManager)
def __init__(self, store):
self._store = store
def __init__(self, pool):
self._pool = pool
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, user):
pass
def serverLogin(self, server):
def checkauth(res):
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, user):
pass
def serverLogout(self, server):
self._pool.runOperation("UPDATE frn_users SET server=NULL, port=NULL, _nt=NULL WHERE server=? AND port=?",
(server.SN, server.PT))
self._pool.runOperation("DELETE FROM frn_servers WHERE _sn=? AND _pt=?",
(server.SN, server.PT))
def clientLogin(self, user):
pass
def clientLogin(self, server, user):
def userfound(data):
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, user):
pass
def clientLogout(self, server, user):
log.msg("Logging out client %s" % repr(user))
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):
pass
def buildlist(tr):
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):
pass
def fetchdata(is_new):
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))
with open('mailtemplate.txt','r') as tplfile:
tpl = tplfile.read()
mailbody = string.Template(tpl).safe_substitute(u)
sendmail('127.0.0.1',
'admin@gnuradionetwork.org',
[u['EA']],
mailbody, port=2525)
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:

Ver fichero

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

Ver fichero

@ -55,23 +55,23 @@ class RemoteManager(object):
def doConnect(self):
self.reactor.connectTCP(self.server, self.port, self.factory)
def serverLogin(self, user):
self.factory = CustomManagerClientFactory(user)
def serverLogin(self, server):
self.factory = CustomManagerClientFactory(server)
self.doConnect()
return self.factory.deferred
def serverLogout(self, user):
def serverLogout(self, server):
if self.factory.client is not None:
return self.factory.client.sendServerLogout(user)
return self.factory.client.sendServerLogout(server)
def clientLogin(self, user):
def clientLogin(self, server, user):
if self.maskParrot and user.BC == 'Parrot':
u = user.copy(BC='PC Only')
else:
u = user.copy()
return self.factory.client.sendClientLogin(u)
def clientLogout(self, user):
def clientLogout(self, server, user):
if self.maskParrot and user.BC == 'Parrot':
u = user.copy(BC='PC Only')
else:

Ver fichero

@ -112,12 +112,16 @@ class FRNManagerServer(LineOnlyReceiver):
def connectionMade(self):
log.msg("Manager client connected from %s" % self.transport.getPeer().host)
self._phase = "CONNECTED"
self.serverInfo = None
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
if self.serverInfo:
self.manager.serverLogout(self.serverInfo)
self.serverInfo = None
self._phase = "DISCONNECTED"
def _authOnly(self):
if self._phase != "AUTHENTICATED":
@ -129,6 +133,8 @@ class FRNManagerServer(LineOnlyReceiver):
if self._phase == "CHALLANGE":
if sline == responseToChallange(self.kp):
self._phase = "AUTHENTICATED"
self.serverInfo = self.tmpServerInfo
log.msg("Auth success: %s" % repr(self.serverInfo))
else:
self.transport.loseConnection()
else:
@ -146,8 +152,19 @@ class FRNManagerServer(LineOnlyReceiver):
handler(xbody)
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")
self.sendLine('0') # TODO
return self.manager.getClientList().addCallback(gotClientList)
def unimplemented(self, cmd, body):
log.err("Unimplemented command %s: %s" % (cmd, str(body)))
@ -156,13 +173,17 @@ class FRNManagerServer(LineOnlyReceiver):
def sendManagerInfo(res):
if versions.manager > 2009004:
self._phase = "CHALLANGE"
self.tmpServerInfo = FRNUser(**body)
else:
self._phase = "AUTHENTICATED"
self.serverInfo = FRNUser(**body)
log.msg("Auth success: %s" % repr(self.serverInfo))
self.sendLine(formatSimpleXML([
('SV', versions.server),
('CV', versions.client),
('MC', versions.manager),
('AL', res['al']),
# ('AL', res['al']),
('AL', res),
('KP', self.kp)
]))
log.msg("SC: %s" % str(body))
@ -172,13 +193,13 @@ class FRNManagerServer(LineOnlyReceiver):
def decodeCC(self, body): # Client login
log.msg("CC: %s" % str(body))
self._authOnly()
self.manager.clientLogin(FRNUser(**body)).addCallback(
self.manager.clientLogin(self.serverInfo, 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.manager.clientLogout(self.serverInfo, FRNUser(**body)).addCallback(
self.sendLine)
def decodeIG(self, body): # Account creation

Ver fichero

@ -32,20 +32,19 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
def connectionLost(self, reason):
log.msg("Client disconnected: %s" % self.clientAddress.host)
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)
self.disconnect()
BufferingLineReceiver.connectionLost(self, reason)
def timeoutConnection(self):
log.msg("Client dead: disconnecting %s" % self.user)
self.disconnect()
def lineReceived(self, line):
self.resetTimeout()
sline = line.strip()
if self.waitingKey:
if responseToChallange(self.kp) != sline:
self.transport.loseConnection()
self.disconnect()
return
else:
self.waitingKey = False
@ -59,7 +58,7 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
self.sendAccessFlags(ac,tx)
self.sendAccessList(self.factory.tracker.getAcl(self.user.NT))
if self.role not in ['OK', 'ADMIN', 'OWNER']:
self.transport.loseConnection()
self.disconnect()
return
self.startPinging()
self.setTimeout(10.0)
@ -72,7 +71,7 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
else:
command, body = sline.split(':', 1)
if command != 'CT' and self.user is None:
self.transport.loseConnection()
self.disconnect()
return
handler = getattr(self, 'decode'+command, None)
if body[0] == '<' and body[-1] == '>':
@ -110,7 +109,12 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
return succeed(("BLOCK", ""))
def disconnect(self):
self.transport.loseConnection()
self.stopPinging()
if self.user is not None:
log.msg("Logging out client %s" % self.user)
self.factory.manager.clientLogout(self.user)
self.factory.tracker.logout(self)
self.transport.loseConnection()
def decodeCT(self, body):
def authReturned(result):
@ -135,7 +139,7 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
self.sendLine(
self.factory.serverAuth.asXML('MT','SV','AL','BN','BP','KP'))
if self.role not in ['OK', 'OWNER', 'ADMIN']:
self.transport.loseConnection()
self.disconnect()
else:
self.sendNetworkList(self.factory.tracker.getNetworkList())
self.transport.setTcpNoDelay(True)
@ -307,14 +311,14 @@ class FRNServer(BufferingLineReceiver, TimeoutMixin):
log.msg("Sending ACL to %s: %s" % (self.user.ON, str(clients)))
self.transport.write(chr(7))
self.sendLine(str(len(clients)))
for c in clients: # FIXME
for c in clients:
self.sendLine(c.asXML('AI','NN','CT','BC','ON','ID'))
def sendAccessFlags(self, access, talk):
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.sendLine('2') # TODO
self.sendLine('2')
self.sendLine(FV[access])
self.sendLine(FV[talk])

Ver fichero

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

Ver fichero

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

Ver fichero

@ -3,7 +3,7 @@
# Copyright 2010 Maurizio Porrato <maurizio.porrato@gmail.com>
# See LICENSE.txt for copyright info
from HTMLParser import HTMLParser
import re
from random import choice
@ -19,45 +19,10 @@ def makeRandomChallange():
return ''.join([choice('0123456789') for i in range(6)])
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
re_tag = re.compile(r"<([A-Z]{2})>(.*)</\1>")
def parseSimpleXML(xml):
p = SimpleXMLParser()
p.feed(xml)
return p.get_fields()
return dict(re_tag.findall(xml))
def formatSimpleXML(elements):

Ver fichero

@ -9,25 +9,31 @@ 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.manager.database import DatabaseManager
from frn.user import FRNUser
from twisted.python import log
if __name__ == '__main__':
import sys
import sys
log.startLogging(sys.stderr)
log.startLogging(sys.stderr)
def standardManagerFactory():
log.msg("Building Manager")
return RemoteManager(reactor)
def dummyManagerFactory():
log.msg("Building DummyManager")
return DummyManager()
reactor.listenTCP(10025, FRNManagerServerFactory(
# DatabaseUserStore(
# ConnectionPool("sqlite3", "frn_users.sqlite3",
# check_same_thread=False)),
# DummyManager()
standardManagerFactory
))
reactor.run()
def remoteManagerFactory():
log.msg("Building RemoteManager")
return RemoteManager(reactor)
def databaseManagerFactory():
log.msg("Building DatabaseManager")
return DatabaseManager(
ConnectionPool("sqlite3", "/tmp/frnmanager.sqlite3", cp_noisy=True))
reactor.listenTCP(10025, FRNManagerServerFactory(
databaseManagerFactory
))
reactor.run()
# vim: set et ai sw=4 ts=4 sts=4: