Browse Source

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

master
Maurizio Porrato 8 years ago
parent
commit
214cb07962

+ 4
- 4
frn/manager/__init__.py View File

@@ -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():

+ 127
- 14
frn/manager/database.py View File

@@ -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:

+ 5
- 5
frn/manager/dummy.py View File

@@ -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')

+ 6
- 6
frn/manager/remote.py View File

@@ -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:

+ 26
- 5
frn/protocol/manager.py View File

@@ -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

+ 17
- 13
frn/protocol/server.py View File

@@ -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])


+ 0
- 9
frn/userstore/__init__.py View File

@@ -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:

+ 0
- 84
frn/userstore/database.py View File

@@ -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:

+ 3
- 38
frn/utility.py View File

@@ -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):

+ 22
- 16
manager.py View File

@@ -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

log.startLogging(sys.stderr)

def standardManagerFactory():
log.msg("Building Manager")
return RemoteManager(reactor)

reactor.listenTCP(10025, FRNManagerServerFactory(
# DatabaseUserStore(
# ConnectionPool("sqlite3", "frn_users.sqlite3",
# check_same_thread=False)),
# DummyManager()
standardManagerFactory
))
reactor.run()
import sys

log.startLogging(sys.stderr)

def dummyManagerFactory():
log.msg("Building DummyManager")
return DummyManager()

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:

Loading…
Cancel
Save