diff --git a/LdapService.py b/LdapService.py index 9e3bd85..ab6ce82 100755 --- a/LdapService.py +++ b/LdapService.py @@ -8,7 +8,6 @@ from struct import pack from collections import deque from os.path import dirname, realpath -from sys import argv, path path.append(dirname(realpath(__file__)) + '/lib') from Server import Server @@ -20,10 +19,12 @@ from Communication.EndPoint import CommunicationEndPoint from Protocol.Http.Http import Http from Protocol.Websocket.Websocket import Websocket +from jinja2 import Environment, FileSystemLoader + from LdapTree import LdapTree class Application(EventHandler): - def __init__(self, hosturi, binddn, basedn, password): + def __init__(self, ip, port, hosturi, binddn, basedn, password): super(Application, self).__init__() self._event_methods = { @@ -35,17 +36,16 @@ class Application(EventHandler): self._websockets = [] - self._wstest = open('websocket.html', 'r+b') - self._wstestmm = mmap.mmap(self._wstest.fileno(), 0) + env = Environment(loader=FileSystemLoader( + dirname(realpath(__file__)) + '/templates')) + template = env.get_template('websocket.html.j2') + # TODO get ip and port or better our complete base uri here. + self._page = template.render(ip=ip, port=port) random.seed() self.ldaptree = LdapTree(hosturi, binddn, basedn, password, False) - def __del__(self): - self._wstestmm.close() - self._wstest.close() - def _upgrade(self, event): self._websockets.append(event.subject) # let other also handle the upgrade .. no return True @@ -64,13 +64,14 @@ class Application(EventHandler): if event.data.isRequest(): if event.data.getUri() == '/': resp = protocol.createResponse(event.data, 200, 'OK') - resp.setBody(self._wstestmm[0:]) + resp.setBody(self._page) elif event.data.getUri() == '/ldap': resp = protocol.createResponse(event.data, 200, 'OK') resp.setHeader('Content-Type', 'image/svg+xml') resp.setBody(self.ldaptree.graph()) else: - resp = protocol.createResponse(event.data, 404, 'Not Found') + resp = protocol.createResponse( + event.data, 404, 'Not Found') resp.setBody('

404 - Not Found

') self.issueEvent(event.subject, 'send_msg', resp) @@ -133,7 +134,9 @@ def main(): usage() sys.exit(2) - server = Server(Application(hosturi, binddn, basedn, password)) + server = Server( + Application( + args[0], int(args[1], hosturi, binddn, basedn, password)) server.bindTcp(args[0], int(args[1]), Http()) server.start(1.0) diff --git a/LdapService2.py b/LdapService2.py new file mode 100755 index 0000000..bc46da0 --- /dev/null +++ b/LdapService2.py @@ -0,0 +1,123 @@ +#!/usr/bin/python + +import time +import random +import mmap +from struct import pack +from collections import deque + +from os.path import dirname, realpath +import sys +reload(sys) +from sys import argv, path, setdefaultencoding +path.append(dirname(realpath(__file__)) + '/lib') +setdefaultencoding('utf-8') +import re + +from Server import Server + +from Event.EventHandler import EventHandler +from Event.EventDispatcher import EventDispatcher +from Communication.EndPoint import CommunicationEndPoint + +from Protocol.Http.Http import Http +from Protocol.Websocket.Websocket import Websocket +from jinja2 import Environment, FileSystemLoader + +from LdapTree import LdapTree + +class Application(EventHandler): + def __init__(self, ip, port, hosturi, binddn, basedn, password): + super(Application, self).__init__() + + self._event_methods = { + CommunicationEndPoint.eventId('new_msg') : self._handle_data, + } + + self._ldaptree = LdapTree(hosturi, binddn, basedn, password, False) + + env = Environment(loader=FileSystemLoader( + dirname(realpath(__file__)) + '/templates')) + self._template = env.get_template('simple.html.j2') + + random.seed() + + @property + def _body(self): + return self._template.render(ldaptree=self._ldaptree).encode('utf8') + + def _handle_data(self, event): + protocol = event.subject.getProtocol() + + if event.subject.hasProtocol(Http): + if event.data.isRequest(): + if event.data.getUri() == '/': + resp = protocol.createResponse(event.data, 200, 'OK') + resp.setBody(self._body) + else: + resp = protocol.createResponse(event.data, 404, 'Not Found') + resp.setBody('

404 - Not Found

') + + self.issueEvent(event.subject, 'send_msg', resp) + + return True + + +def usage(): + print "Usage: " + sys.argv[0] + " ARGUMENT... [OPTIONS]... bindip bindport\n" + print "Start a webserver on the given bindip and bindport. On the page a" + print "tree representation of all DNs starting with a given base DN is" + print "visualized." + print "Only simple binds to the directory with DN and password are supported.\n" + print "ARGUMENTS:\n" + print " {:30s} : {:s}".format('-H, --hosturi=URI', 'The URI to the ldap server to query in the form:') + print " {:30s} {:s}".format('', 'ldap[s]://host.uri[:port]') + print " {:30s} : {:s}".format('-D, --binddn=DN', 'The DN to use for the LDAP bind.') + print " {:30s} : {:s}".format('-p, --password=PASSWORD', 'The password to use for the LDAP bind.') + print " {:30s} : {:s}\n".format('-b, --basedn=DN', 'The DN to start the tree with.') + print "OPTIONS:\n" + print " {:30s} : {:s}".format('-h, --help', 'Show this help page') + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'hH:D:b:p:', + ['help', 'hosturi=', 'binddn=', 'basedn=', 'password=']) + except getopt.GetoptError as err: + print str(err) + usage() + sys.exit(2) + + hosturi = binddn = basedn = password = None + + for o, a in opts: + if o in ["-h", "--help"]: + usage() + sys.exit(0) + elif o in ["-H", "--hosturi"]: + hosturi = a + elif o in ["-D", "--binddn"]: + binddn = a + elif o in ["-b", "--basedn"]: + basedn = a + elif o in ["-p", "--password"]: + password = a + else: + print "unknown parameter: " + a + usage() + sys.exit(2) + + if not hosturi or not binddn or not basedn or not password: + usage() + sys.exit(2) + + server = Server( + Application( + args[0], int(args[1], hosturi, binddn, basedn, password)) + server.bindTcp(args[0], int(args[1]), Http()) + server.start(1.0) + +if __name__ == '__main__': + main() +# vim: set ft=python et ts=8 sw=4 sts=4: diff --git a/ldaptree.py b/ldaptree.py index 57bcb80..66cfabf 100755 --- a/ldaptree.py +++ b/ldaptree.py @@ -1,7 +1,9 @@ #!/usr/bin/python from os.path import dirname, realpath import getopt, sys +reload(sys) sys.path.append(dirname(realpath(__file__)) + '/lib') +sys.setdefaultencoding('utf-8') import getpass from LdapTree import LdapTree diff --git a/lib/Communication/Connection.py b/lib/Communication/Connection.py index 872bc05..7eb0991 100644 --- a/lib/Communication/Connection.py +++ b/lib/Communication/Connection.py @@ -31,7 +31,7 @@ class Connection(CommunicationEndPoint): if not self._current_msg or self._current_msg.ready(): self._current_msg = self._protocol.createMessage( self.getTransport().remote) - + end = self._protocol.getParser().parse( self._current_msg, self._read_buffer) diff --git a/lib/LdapTree.py b/lib/LdapTree.py index 7c59f7b..bd44136 100644 --- a/lib/LdapTree.py +++ b/lib/LdapTree.py @@ -1,6 +1,10 @@ +from os.path import dirname, realpath + import ldap import pygraphviz as pgv +from jinja2 import Environment, FileSystemLoader + class LdapTree(object): def __init__(self, hosturi, binddn, basedn, password, use_gssapi): #ldap.set_option(ldap.OPT_DEBUG_LEVEL, 1) @@ -19,16 +23,23 @@ class LdapTree(object): self._basedn = basedn self._ldap_result = [] + self._data = None + def text(self, filename = None): """ Returns a text representing the directory. If filename is given it will be written in that file. """ + env = Environment(loader=FileSystemLoader( + dirname(dirname(realpath(__file__))) + '/templates')) + template = env.get_template('simple.txt.j2') + text = template.render(ldaptree=self).encode('utf8') + if filename: with open(filename, "w") as text_file: - text_file.write(self._text(self._basedn, 0)) + text_file.write(text) else: - return self._text(self._basedn, 0) + return text def graph(self, filename = None): """ @@ -54,26 +65,11 @@ class LdapTree(object): else: return graph.draw(format='svg') - def _text(self, dn, level): - """ - Recursive function that returns a string representation of the - directory where each depth is indicated by a dash. - """ - result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL) - indent = '-' * level - text = indent + dn + "\n" - - for entry in (entry[0] for entry in result): - if entry: - text += self._text(entry, level + 1) - - return text - def _graph(self, graph, dn): """ Recursive function creating a graphviz graph from the directory. """ - result = self._ldap.search_s(dn, ldap.SCOPE_ONELEVEL) + result = self.node(dn) minlen = thislen = 1 edge_start = dn @@ -85,6 +81,7 @@ class LdapTree(object): sub.add_node( point, shape='circle', fixedsize='true', width='0.04', label='', fillcolor='transparent') + #sub.add_node(entry, URL='https://www.google.de/') sub.add_node(entry) graph.add_edge(edge_start, point, arrowhead='none', minlen=str(minlen)) @@ -94,3 +91,62 @@ class LdapTree(object): thislen += minlen return thislen + + @property + def all(self): + if self._data == None: + self._data = {} + result = self._ldap.search_s(self._basedn, ldap.SCOPE_SUBTREE) + for entry in result: + self._data[entry[0]] = entry[1:][0] + + return self._data + + @property + def dn_tree(self): + retval = {} + for d in self.all.keys(): + current = retval + for k in reversed(d.split(',')): + try: + current = current[k] + except: + current[k] = {} + current = current[k] + return retval + + @property + def hirarchy(self): + return self._hirarchy(self.dn_tree) + + def _hirarchy(self, dn, base=[], depth=0): + """ + Hirarchie generates a flat list where each parent is + followed by all its childs, so that in a template one + can simple iterate over it to display the complete tree. + Recently I learned that "recursive loops" are possible + within jinja2. So we can alter the template that it + ensures child displays correctly + """ + retval = [] + for d in dn.keys(): + base_name = ','.join(reversed(base)) + name = ','.join(reversed(base + [d])) + retval.append((depth, base_name, name)) + retval += self._hirarchy(dn[d], base + [d], depth+1) + return retval + + def childs(self, dn): + """ + Recently I learned that "recursive loops" are possible + within jinja2. So we can alter the template that it + ensures child displays correctly. So this function should + return all child dn's to a given dn. + """ + return [d for d in self.hirarchy if d[1] == dn] + + def node(self, dn): + if dn in self.all: + return self.all[dn] + else: + return {} diff --git a/lib/Transport/TcpSocket.py b/lib/Transport/TcpSocket.py index 4e850a6..6985d37 100644 --- a/lib/Transport/TcpSocket.py +++ b/lib/Transport/TcpSocket.py @@ -62,13 +62,13 @@ class TcpSocket(Socket): def recv(self, size): data = '' - try: + try: data = self.socket.recv(size) except socket.error as error: if error.errno not in CONTINUE: raise Transport.Error(Transport.Error.ERR_FAILED) return None - + if not data: raise Transport.Error(Transport.Error.ERR_REMOTE_CLOSE) @@ -76,7 +76,7 @@ class TcpSocket(Socket): def send(self, data, remote=None): send = 0 - try: + try: if self.socket: send = self.socket.send(data) except socket.error as error: diff --git a/templates/simple.html.j2 b/templates/simple.html.j2 new file mode 100644 index 0000000..cdc8f04 --- /dev/null +++ b/templates/simple.html.j2 @@ -0,0 +1,89 @@ + + + + + Ldap + + + + +

Ldap

+ + + + + + + diff --git a/templates/simple.txt.j2 b/templates/simple.txt.j2 new file mode 100644 index 0000000..6c3f9bd --- /dev/null +++ b/templates/simple.txt.j2 @@ -0,0 +1,12 @@ +{% for d in ldaptree.hirarchy -%} +{{ '--'*d[0] }} dn: {{ d[2] }} +{% for k in ldaptree.node(d[2]).keys() -%} +{% if ldaptree.node(d[2]) is string -%} +{{ ' '*d[0] }} {{ k }}: {{ ldaptree.node(d[2])[k] }} +{% else -%} +{% for v in ldaptree.node(d[2])[k] -%} +{{ ' '*d[0] }} {{ k }}: {{ v }} +{% endfor -%} +{% endif -%} +{% endfor -%} +{% endfor -%} diff --git a/websocket.html b/templates/websocket.html.j2 similarity index 94% rename from websocket.html rename to templates/websocket.html.j2 index 019d3ff..0162b35 100644 --- a/websocket.html +++ b/templates/websocket.html.j2 @@ -80,7 +80,7 @@ var interval = null wstest.data = 'Websockets are supported'; - connection = new WebSocket('ws://127.0.0.1:8080/'); + connection = new WebSocket('ws://172.27.1.139:8080/'); console.log('Websockets test'); connection.onopen = function() { @@ -114,7 +114,8 @@ } }/* ]]> */ - + + diff --git a/tests/.TestAll.py.swp b/tests/.TestAll.py.swp new file mode 100644 index 0000000..420ba2c Binary files /dev/null and b/tests/.TestAll.py.swp differ