mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
290: Applyied Rob's patch
This commit is contained in:
parent
c1ef2d05e8
commit
0e60036bb4
72
ipalib/conn.py
Normal file
72
ipalib/conn.py
Normal file
@ -0,0 +1,72 @@
|
||||
# Authors: Rob Crittenden <rcritten@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2008 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 only
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
import krbV
|
||||
import threading
|
||||
import ldap
|
||||
import ldap.dn
|
||||
from ipalib import ipaldap
|
||||
|
||||
context = threading.local()
|
||||
|
||||
class IPAConn:
|
||||
def __init__(self, host, port, krbccache, debug=None):
|
||||
self._conn = None
|
||||
|
||||
# Save the arguments
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._krbccache = krbccache
|
||||
self._debug = debug
|
||||
|
||||
self._ctx = krbV.default_context()
|
||||
|
||||
ccache = krbV.CCache(name=krbccache, context=self._ctx)
|
||||
cprinc = ccache.principal()
|
||||
|
||||
self._conn = ipaldap.IPAdmin(host,port,None,None,None,debug)
|
||||
|
||||
# This will bind the connection
|
||||
try:
|
||||
self._conn.set_krbccache(krbccache, cprinc.name)
|
||||
except ldap.UNWILLING_TO_PERFORM, e:
|
||||
raise e
|
||||
except Exception, e:
|
||||
raise e
|
||||
|
||||
def __del__(self):
|
||||
# take no chances on unreleased connections
|
||||
self.releaseConn()
|
||||
|
||||
def getConn(self):
|
||||
return self._conn
|
||||
|
||||
def releaseConn(self):
|
||||
if self._conn is None:
|
||||
return
|
||||
|
||||
self._conn.unbind_s()
|
||||
self._conn = None
|
||||
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
ipaconn = IPAConn("localhost", 389, "FILE:/tmp/krb5cc_500")
|
||||
x = ipaconn.getConn().getEntry("dc=example,dc=com", ldap.SCOPE_SUBTREE, "uid=admin", ["cn"])
|
||||
print "%s" % x
|
627
ipalib/ipaldap.py
Normal file
627
ipalib/ipaldap.py
Normal file
@ -0,0 +1,627 @@
|
||||
# Authors: Rich Megginson <richm@redhat.com>
|
||||
# Rob Crittenden <rcritten@redhat.com
|
||||
#
|
||||
# Copyright (C) 2007 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 only
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import base64
|
||||
import urllib
|
||||
import socket
|
||||
import ldif
|
||||
import re
|
||||
import string
|
||||
import ldap
|
||||
import cStringIO
|
||||
import struct
|
||||
import ldap.sasl
|
||||
from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples
|
||||
from ldap.ldapobject import SimpleLDAPObject
|
||||
import ipautil
|
||||
|
||||
# Global variable to define SASL auth
|
||||
sasl_auth = ldap.sasl.sasl({},'GSSAPI')
|
||||
|
||||
class Entry:
|
||||
"""This class represents an LDAP Entry object. An LDAP entry consists of a DN
|
||||
and a list of attributes. Each attribute consists of a name and a list of
|
||||
values. In python-ldap, entries are returned as a list of 2-tuples.
|
||||
Instance variables:
|
||||
dn - string - the string DN of the entry
|
||||
data - CIDict - case insensitive dict of the attributes and values"""
|
||||
|
||||
def __init__(self,entrydata):
|
||||
"""data is the raw data returned from the python-ldap result method, which is
|
||||
a search result entry or a reference or None.
|
||||
If creating a new empty entry, data is the string DN."""
|
||||
if entrydata:
|
||||
if isinstance(entrydata,tuple):
|
||||
self.dn = entrydata[0]
|
||||
self.data = ipautil.CIDict(entrydata[1])
|
||||
elif isinstance(entrydata,str) or isinstance(entrydata,unicode):
|
||||
self.dn = entrydata
|
||||
self.data = ipautil.CIDict()
|
||||
else:
|
||||
self.dn = ''
|
||||
self.data = ipautil.CIDict()
|
||||
|
||||
def __nonzero__(self):
|
||||
"""This allows us to do tests like if entry: returns false if there is no data,
|
||||
true otherwise"""
|
||||
return self.data != None and len(self.data) > 0
|
||||
|
||||
def hasAttr(self,name):
|
||||
"""Return True if this entry has an attribute named name, False otherwise"""
|
||||
return self.data and self.data.has_key(name)
|
||||
|
||||
def __getattr__(self,name):
|
||||
"""If name is the name of an LDAP attribute, return the first value for that
|
||||
attribute - equivalent to getValue - this allows the use of
|
||||
entry.cn
|
||||
instead of
|
||||
entry.getValue('cn')
|
||||
This also allows us to return None if an attribute is not found rather than
|
||||
throwing an exception"""
|
||||
return self.getValue(name)
|
||||
|
||||
def getValues(self,name):
|
||||
"""Get the list (array) of values for the attribute named name"""
|
||||
return self.data.get(name)
|
||||
|
||||
def getValue(self,name):
|
||||
"""Get the first value for the attribute named name"""
|
||||
return self.data.get(name,[None])[0]
|
||||
|
||||
def setValue(self,name,*value):
|
||||
"""Value passed in may be a single value, several values, or a single sequence.
|
||||
For example:
|
||||
ent.setValue('name', 'value')
|
||||
ent.setValue('name', 'value1', 'value2', ..., 'valueN')
|
||||
ent.setValue('name', ['value1', 'value2', ..., 'valueN'])
|
||||
ent.setValue('name', ('value1', 'value2', ..., 'valueN'))
|
||||
Since *value is a tuple, we may have to extract a list or tuple from that
|
||||
tuple as in the last two examples above"""
|
||||
if isinstance(value[0],list) or isinstance(value[0],tuple):
|
||||
self.data[name] = value[0]
|
||||
else:
|
||||
self.data[name] = value
|
||||
|
||||
setValues = setValue
|
||||
|
||||
def toTupleList(self):
|
||||
"""Convert the attrs and values to a list of 2-tuples. The first element
|
||||
of the tuple is the attribute name. The second element is either a
|
||||
single value or a list of values."""
|
||||
return self.data.items()
|
||||
|
||||
def __str__(self):
|
||||
"""Convert the Entry to its LDIF representation"""
|
||||
return self.__repr__()
|
||||
|
||||
# the ldif class base64 encodes some attrs which I would rather see in raw form - to
|
||||
# encode specific attrs as base64, add them to the list below
|
||||
ldif.safe_string_re = re.compile('^$')
|
||||
base64_attrs = ['nsstate', 'krbprincipalkey', 'krbExtraData']
|
||||
|
||||
def __repr__(self):
|
||||
"""Convert the Entry to its LDIF representation"""
|
||||
sio = cStringIO.StringIO()
|
||||
# what's all this then? the unparse method will currently only accept
|
||||
# a list or a dict, not a class derived from them. self.data is a
|
||||
# cidict, so unparse barfs on it. I've filed a bug against python-ldap,
|
||||
# but in the meantime, we have to convert to a plain old dict for printing
|
||||
# I also don't want to see wrapping, so set the line width really high (1000)
|
||||
newdata = {}
|
||||
newdata.update(self.data)
|
||||
ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(self.dn,newdata)
|
||||
return sio.getvalue()
|
||||
|
||||
def wrapper(f,name):
|
||||
"""This is the method that wraps all of the methods of the superclass. This seems
|
||||
to need to be an unbound method, that's why it's outside of IPAdmin. Perhaps there
|
||||
is some way to do this with the new classmethod or staticmethod of 2.4.
|
||||
Basically, we replace every call to a method in SimpleLDAPObject (the superclass
|
||||
of IPAdmin) with a call to inner. The f argument to wrapper is the bound method
|
||||
of IPAdmin (which is inherited from the superclass). Bound means that it will implicitly
|
||||
be called with the self argument, it is not in the args list. name is the name of
|
||||
the method to call. If name is a method that returns entry objects (e.g. result),
|
||||
we wrap the data returned by an Entry class. If name is a method that takes an entry
|
||||
argument, we extract the raw data from the entry object to pass in."""
|
||||
def inner(*args, **kargs):
|
||||
if name == 'result':
|
||||
objtype, data = f(*args, **kargs)
|
||||
# data is either a 2-tuple or a list of 2-tuples
|
||||
# print data
|
||||
if data:
|
||||
if isinstance(data,tuple):
|
||||
return objtype, Entry(data)
|
||||
elif isinstance(data,list):
|
||||
return objtype, [Entry(x) for x in data]
|
||||
else:
|
||||
raise TypeError, "unknown data type %s returned by result" % type(data)
|
||||
else:
|
||||
return objtype, data
|
||||
elif name.startswith('add'):
|
||||
# the first arg is self
|
||||
# the second and third arg are the dn and the data to send
|
||||
# We need to convert the Entry into the format used by
|
||||
# python-ldap
|
||||
ent = args[0]
|
||||
if isinstance(ent,Entry):
|
||||
return f(ent.dn, ent.toTupleList(), *args[2:])
|
||||
else:
|
||||
return f(*args, **kargs)
|
||||
else:
|
||||
return f(*args, **kargs)
|
||||
return inner
|
||||
|
||||
class LDIFConn(ldif.LDIFParser):
|
||||
def __init__(
|
||||
self,
|
||||
input_file,
|
||||
ignored_attr_types=None,max_entries=0,process_url_schemes=None
|
||||
):
|
||||
"""
|
||||
See LDIFParser.__init__()
|
||||
|
||||
Additional Parameters:
|
||||
all_records
|
||||
List instance for storing parsed records
|
||||
"""
|
||||
self.dndict = {} # maps dn to Entry
|
||||
self.dnlist = [] # contains entries in order read
|
||||
myfile = input_file
|
||||
if isinstance(input_file,str) or isinstance(input_file,unicode):
|
||||
myfile = open(input_file, "r")
|
||||
ldif.LDIFParser.__init__(self,myfile,ignored_attr_types,max_entries,process_url_schemes)
|
||||
self.parse()
|
||||
if isinstance(input_file,str) or isinstance(input_file,unicode):
|
||||
myfile.close()
|
||||
|
||||
def handle(self,dn,entry):
|
||||
"""
|
||||
Append single record to dictionary of all records.
|
||||
"""
|
||||
if not dn:
|
||||
dn = ''
|
||||
newentry = Entry((dn, entry))
|
||||
self.dndict[IPAdmin.normalizeDN(dn)] = newentry
|
||||
self.dnlist.append(newentry)
|
||||
|
||||
def get(self,dn):
|
||||
ndn = IPAdmin.normalizeDN(dn)
|
||||
return self.dndict.get(ndn, Entry(None))
|
||||
|
||||
class IPAdmin(SimpleLDAPObject):
|
||||
|
||||
def getDseAttr(self,attrname):
|
||||
conffile = self.confdir + '/dse.ldif'
|
||||
dseldif = LDIFConn(conffile)
|
||||
cnconfig = dseldif.get("cn=config")
|
||||
if cnconfig:
|
||||
return cnconfig.getValue(attrname)
|
||||
return None
|
||||
|
||||
def __initPart2(self):
|
||||
if self.binddn and len(self.binddn) and not hasattr(self,'sroot'):
|
||||
try:
|
||||
ent = self.getEntry('cn=config', ldap.SCOPE_BASE, '(objectclass=*)',
|
||||
[ 'nsslapd-instancedir', 'nsslapd-errorlog',
|
||||
'nsslapd-certdir', 'nsslapd-schemadir' ])
|
||||
self.errlog = ent.getValue('nsslapd-errorlog')
|
||||
self.confdir = ent.getValue('nsslapd-certdir')
|
||||
if not self.confdir:
|
||||
self.confdir = ent.getValue('nsslapd-schemadir')
|
||||
if self.confdir:
|
||||
self.confdir = os.path.dirname(self.confdir)
|
||||
ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config',
|
||||
ldap.SCOPE_BASE, '(objectclass=*)',
|
||||
[ 'nsslapd-directory' ])
|
||||
self.dbdir = os.path.dirname(ent.getValue('nsslapd-directory'))
|
||||
except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR):
|
||||
pass # usually means
|
||||
except ldap.LDAPError, e:
|
||||
print "caught exception ", e
|
||||
raise
|
||||
|
||||
def __localinit(self):
|
||||
"""If a CA certificate is provided then it is assumed that we are
|
||||
doing SSL client authentication with proxy auth.
|
||||
|
||||
If a CA certificate is not present then it is assumed that we are
|
||||
using a forwarded kerberos ticket for SASL auth. SASL provides
|
||||
its own encryption.
|
||||
"""
|
||||
if self.cacert is not None:
|
||||
SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port))
|
||||
else:
|
||||
SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port))
|
||||
|
||||
def __init__(self,host,port=389,cacert=None,bindcert=None,bindkey=None,proxydn=None,debug=None):
|
||||
"""We just set our instance variables and wrap the methods - the real
|
||||
work is done in __localinit and __initPart2 - these are separated
|
||||
out this way so that we can call them from places other than
|
||||
instance creation e.g. when we just need to reconnect, not create a
|
||||
new instance"""
|
||||
if debug and debug.lower() == "on":
|
||||
ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
|
||||
if cacert is not None:
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert)
|
||||
if bindcert is not None:
|
||||
ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert)
|
||||
if bindkey is not None:
|
||||
ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
|
||||
|
||||
self.__wrapmethods()
|
||||
self.port = port
|
||||
self.host = host
|
||||
self.cacert = cacert
|
||||
self.bindcert = bindcert
|
||||
self.bindkey = bindkey
|
||||
self.proxydn = proxydn
|
||||
self.suffixes = {}
|
||||
self.__localinit()
|
||||
|
||||
def __str__(self):
|
||||
return self.host + ":" + str(self.port)
|
||||
|
||||
def __get_server_controls(self):
|
||||
"""Create the proxy user server control. The control has the form
|
||||
0x04 = Octet String
|
||||
4|0x80 sets the length of the string length field at 4 bytes
|
||||
the struct() gets us the length in bytes of string self.proxydn
|
||||
self.proxydn is the proxy dn to send"""
|
||||
|
||||
if self.proxydn is not None:
|
||||
proxydn = chr(0x04) + chr(4|0x80) + struct.pack('l', socket.htonl(len(self.proxydn))) + self.proxydn;
|
||||
|
||||
# Create the proxy control
|
||||
sctrl=[]
|
||||
sctrl.append(LDAPControl('2.16.840.1.113730.3.4.18',True,proxydn))
|
||||
else:
|
||||
sctrl=None
|
||||
|
||||
return sctrl
|
||||
|
||||
def toLDAPURL(self):
|
||||
return "ldap://%s:%d/" % (self.host,self.port)
|
||||
|
||||
def set_proxydn(self, proxydn):
|
||||
self.proxydn = proxydn
|
||||
|
||||
def set_krbccache(self, krbccache, principal):
|
||||
if krbccache is not None:
|
||||
os.environ["KRB5CCNAME"] = krbccache
|
||||
self.sasl_interactive_bind_s("", sasl_auth)
|
||||
self.principal = principal
|
||||
self.proxydn = None
|
||||
|
||||
def do_simple_bind(self, binddn="cn=directory manager", bindpw=""):
|
||||
self.binddn = binddn
|
||||
self.bindpwd = bindpw
|
||||
self.simple_bind_s(binddn, bindpw)
|
||||
self.__initPart2()
|
||||
|
||||
def getEntry(self,*args):
|
||||
"""This wraps the search function. It is common to just get one entry"""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
|
||||
try:
|
||||
res = self.search(*args)
|
||||
objtype, obj = self.result(res)
|
||||
except ldap.NO_SUCH_OBJECT, e:
|
||||
raise e
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
|
||||
if not obj:
|
||||
raise ldap.NO_SUCH_OBJECT
|
||||
|
||||
elif isinstance(obj,Entry):
|
||||
return obj
|
||||
else: # assume list/tuple
|
||||
return obj[0]
|
||||
|
||||
def getList(self,*args):
|
||||
"""This wraps the search function to find multiple entries."""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
|
||||
try:
|
||||
res = self.search(*args)
|
||||
objtype, obj = self.result(res)
|
||||
except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED), e:
|
||||
# Too many results returned by search
|
||||
raise e
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
|
||||
if not obj:
|
||||
raise ldap.NO_SUCH_OBJECT
|
||||
|
||||
entries = []
|
||||
for s in obj:
|
||||
entries.append(s)
|
||||
|
||||
return entries
|
||||
|
||||
def getListAsync(self,*args):
|
||||
"""This version performs an asynchronous search, to allow
|
||||
results even if we hit a limit.
|
||||
|
||||
It returns a list: counter followed by the results.
|
||||
If the results are truncated, counter will be set to -1.
|
||||
"""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
|
||||
entries = []
|
||||
partial = 0
|
||||
|
||||
try:
|
||||
msgid = self.search_ext(*args)
|
||||
objtype, result_list = self.result(msgid, 0)
|
||||
while result_list:
|
||||
for result in result_list:
|
||||
entries.append(result)
|
||||
objtype, result_list = self.result(msgid, 0)
|
||||
except (ldap.ADMINLIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED,
|
||||
ldap.TIMELIMIT_EXCEEDED), e:
|
||||
partial = 1
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
|
||||
if not entries:
|
||||
raise ldap.NO_SUCH_OBJECT
|
||||
|
||||
if partial == 1:
|
||||
counter = -1
|
||||
else:
|
||||
counter = len(entries)
|
||||
|
||||
return [counter] + entries
|
||||
|
||||
def addEntry(self,*args):
|
||||
"""This wraps the add function. It assumes that the entry is already
|
||||
populated with all of the desired objectclasses and attributes"""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
|
||||
try:
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.add_s(*args)
|
||||
except ldap.ALREADY_EXISTS, e:
|
||||
# duplicate value
|
||||
raise e
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
return True
|
||||
|
||||
def updateRDN(self, dn, newrdn):
|
||||
"""Wrap the modrdn function."""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
|
||||
if dn == newrdn:
|
||||
# no need to report an error
|
||||
return True
|
||||
|
||||
try:
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.modrdn_s(dn, newrdn, delold=1)
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
return True
|
||||
|
||||
def updateEntry(self,dn,oldentry,newentry):
|
||||
"""This wraps the mod function. It assumes that the entry is already
|
||||
populated with all of the desired objectclasses and attributes"""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
|
||||
modlist = self.generateModList(oldentry, newentry)
|
||||
|
||||
if len(modlist) == 0:
|
||||
# FIXME: better error
|
||||
raise SyntaxError("empty modlist")
|
||||
|
||||
try:
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.modify_s(dn, modlist)
|
||||
# this is raised when a 'delete' attribute isn't found.
|
||||
# it indicates the previous attribute was removed by another
|
||||
# update, making the oldentry stale.
|
||||
except ldap.NO_SUCH_ATTRIBUTE:
|
||||
# FIXME: better error
|
||||
raise SyntaxError("mid-air collision")
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
return True
|
||||
|
||||
def generateModList(self, old_entry, new_entry):
|
||||
"""A mod list generator that computes more precise modification lists
|
||||
than the python-ldap version. This version purposely generates no
|
||||
REPLACE operations, to deal with multi-user updates more properly."""
|
||||
modlist = []
|
||||
|
||||
old_entry = ipautil.CIDict(old_entry)
|
||||
new_entry = ipautil.CIDict(new_entry)
|
||||
|
||||
keys = set(map(string.lower, old_entry.keys()))
|
||||
keys.update(map(string.lower, new_entry.keys()))
|
||||
|
||||
for key in keys:
|
||||
new_values = new_entry.get(key, [])
|
||||
if not(isinstance(new_values,list) or isinstance(new_values,tuple)):
|
||||
new_values = [new_values]
|
||||
new_values = filter(lambda value:value!=None, new_values)
|
||||
new_values = set(new_values)
|
||||
|
||||
old_values = old_entry.get(key, [])
|
||||
if not(isinstance(old_values,list) or isinstance(old_values,tuple)):
|
||||
old_values = [old_values]
|
||||
old_values = filter(lambda value:value!=None, old_values)
|
||||
old_values = set(old_values)
|
||||
|
||||
adds = list(new_values.difference(old_values))
|
||||
removes = list(old_values.difference(new_values))
|
||||
|
||||
if len(removes) > 0:
|
||||
modlist.append((ldap.MOD_DELETE, key, removes))
|
||||
if len(adds) > 0:
|
||||
modlist.append((ldap.MOD_ADD, key, adds))
|
||||
|
||||
return modlist
|
||||
|
||||
def inactivateEntry(self,dn,has_key):
|
||||
"""Rather than deleting entries we mark them as inactive.
|
||||
has_key defines whether the entry already has nsAccountlock
|
||||
set so we can determine which type of mod operation to run."""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
modlist=[]
|
||||
|
||||
if has_key:
|
||||
operation = ldap.MOD_REPLACE
|
||||
else:
|
||||
operation = ldap.MOD_ADD
|
||||
|
||||
modlist.append((operation, "nsAccountlock", "true"))
|
||||
|
||||
try:
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.modify_s(dn, modlist)
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
return True
|
||||
|
||||
def deleteEntry(self,*args):
|
||||
"""This wraps the delete function. Use with caution."""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
|
||||
try:
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.delete_s(*args)
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
return True
|
||||
|
||||
def modifyPassword(self,dn,oldpass,newpass):
|
||||
"""Set the user password using RFC 3062, LDAP Password Modify Extended
|
||||
Operation. This ends up calling the IPA password slapi plugin
|
||||
handler so the Kerberos password gets set properly.
|
||||
|
||||
oldpass is not mandatory
|
||||
"""
|
||||
|
||||
sctrl = self.__get_server_controls()
|
||||
|
||||
try:
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.passwd_s(dn, oldpass, newpass)
|
||||
except ldap.LDAPError, e:
|
||||
raise e
|
||||
return True
|
||||
|
||||
def __wrapmethods(self):
|
||||
"""This wraps all methods of SimpleLDAPObject, so that we can intercept
|
||||
the methods that deal with entries. Instead of using a raw list of tuples
|
||||
of lists of hashes of arrays as the entry object, we want to wrap entries
|
||||
in an Entry class that provides some useful methods"""
|
||||
for name in dir(self.__class__.__bases__[0]):
|
||||
attr = getattr(self, name)
|
||||
if callable(attr):
|
||||
setattr(self, name, wrapper(attr, name))
|
||||
|
||||
def addSchema(self, attr, val):
|
||||
dn = "cn=schema"
|
||||
self.modify_s(dn, [(ldap.MOD_ADD, attr, val)])
|
||||
|
||||
def addAttr(self, *args):
|
||||
return self.addSchema('attributeTypes', args)
|
||||
|
||||
def addObjClass(self, *args):
|
||||
return self.addSchema('objectClasses', args)
|
||||
|
||||
###########################
|
||||
# Static methods start here
|
||||
###########################
|
||||
def normalizeDN(dn):
|
||||
# not great, but will do until we use a newer version of python-ldap
|
||||
# that has DN utilities
|
||||
ary = ldap.explode_dn(dn.lower())
|
||||
return ",".join(ary)
|
||||
normalizeDN = staticmethod(normalizeDN)
|
||||
|
||||
def getfqdn(name=''):
|
||||
return socket.getfqdn(name)
|
||||
getfqdn = staticmethod(getfqdn)
|
||||
|
||||
def getdomainname(name=''):
|
||||
fqdn = IPAdmin.getfqdn(name)
|
||||
index = fqdn.find('.')
|
||||
if index >= 0:
|
||||
return fqdn[index+1:]
|
||||
else:
|
||||
return fqdn
|
||||
getdomainname = staticmethod(getdomainname)
|
||||
|
||||
def getdefaultsuffix(name=''):
|
||||
dm = IPAdmin.getdomainname(name)
|
||||
if dm:
|
||||
return "dc=" + dm.replace('.', ', dc=')
|
||||
else:
|
||||
return 'dc=localdomain'
|
||||
getdefaultsuffix = staticmethod(getdefaultsuffix)
|
||||
|
||||
def is_a_dn(dn):
|
||||
"""Returns True if the given string is a DN, False otherwise."""
|
||||
return (dn.find("=") > 0)
|
||||
is_a_dn = staticmethod(is_a_dn)
|
||||
|
||||
|
||||
def notfound(args):
|
||||
"""Return a string suitable for displaying as an error when a
|
||||
search returns no results.
|
||||
|
||||
This just returns whatever is after the equals sign"""
|
||||
if len(args) > 2:
|
||||
searchfilter = args[2]
|
||||
try:
|
||||
target = re.match(r'\(.*=(.*)\)', searchfilter).group(1)
|
||||
except:
|
||||
target = searchfilter
|
||||
return "%s not found" % str(target)
|
||||
else:
|
||||
return args[0]
|
190
ipalib/ipautil.py
Normal file
190
ipalib/ipautil.py
Normal file
@ -0,0 +1,190 @@
|
||||
# Authors: Simo Sorce <ssorce@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2007 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 only
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
import string
|
||||
import xmlrpclib
|
||||
import re
|
||||
|
||||
def realm_to_suffix(realm_name):
|
||||
s = realm_name.split(".")
|
||||
terms = ["dc=" + x.lower() for x in s]
|
||||
return ",".join(terms)
|
||||
|
||||
class CIDict(dict):
|
||||
"""
|
||||
Case-insensitive but case-respecting dictionary.
|
||||
|
||||
This code is derived from python-ldap's cidict.py module,
|
||||
written by stroeder: http://python-ldap.sourceforge.net/
|
||||
|
||||
This version extends 'dict' so it works properly with TurboGears.
|
||||
If you extend UserDict, isinstance(foo, dict) returns false.
|
||||
"""
|
||||
|
||||
def __init__(self,default=None):
|
||||
super(CIDict, self).__init__()
|
||||
self._keys = {}
|
||||
self.update(default or {})
|
||||
|
||||
def __getitem__(self,key):
|
||||
return super(CIDict,self).__getitem__(string.lower(key))
|
||||
|
||||
def __setitem__(self,key,value):
|
||||
lower_key = string.lower(key)
|
||||
self._keys[lower_key] = key
|
||||
return super(CIDict,self).__setitem__(string.lower(key),value)
|
||||
|
||||
def __delitem__(self,key):
|
||||
lower_key = string.lower(key)
|
||||
del self._keys[lower_key]
|
||||
return super(CIDict,self).__delitem__(string.lower(key))
|
||||
|
||||
def update(self,dict):
|
||||
for key in dict.keys():
|
||||
self[key] = dict[key]
|
||||
|
||||
def has_key(self,key):
|
||||
return super(CIDict, self).has_key(string.lower(key))
|
||||
|
||||
def get(self,key,failobj=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return failobj
|
||||
|
||||
def keys(self):
|
||||
return self._keys.values()
|
||||
|
||||
def items(self):
|
||||
result = []
|
||||
for k in self._keys.values():
|
||||
result.append((k,self[k]))
|
||||
return result
|
||||
|
||||
def copy(self):
|
||||
copy = {}
|
||||
for k in self._keys.values():
|
||||
copy[k] = self[k]
|
||||
return copy
|
||||
|
||||
def iteritems(self):
|
||||
return self.copy().iteritems()
|
||||
|
||||
def iterkeys(self):
|
||||
return self.copy().iterkeys()
|
||||
|
||||
def setdefault(self,key,value=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
self[key] = value
|
||||
return value
|
||||
|
||||
def pop(self, key, *args):
|
||||
try:
|
||||
value = self[key]
|
||||
del self[key]
|
||||
return value
|
||||
except KeyError:
|
||||
if len(args) == 1:
|
||||
return args[0]
|
||||
raise
|
||||
|
||||
def popitem(self):
|
||||
(lower_key,value) = super(CIDict,self).popitem()
|
||||
key = self._keys[lower_key]
|
||||
del self._keys[lower_key]
|
||||
|
||||
return (key,value)
|
||||
|
||||
|
||||
#
|
||||
# The safe_string_re regexp and needs_base64 function are extracted from the
|
||||
# python-ldap ldif module, which was
|
||||
# written by Michael Stroeder <michael@stroeder.com>
|
||||
# http://python-ldap.sourceforge.net
|
||||
#
|
||||
# It was extracted because ipaldap.py is naughtily reaching into the ldif
|
||||
# module and squashing this regexp.
|
||||
#
|
||||
SAFE_STRING_PATTERN = '(^(\000|\n|\r| |:|<)|[\000\n\r\200-\377]+|[ ]+$)'
|
||||
safe_string_re = re.compile(SAFE_STRING_PATTERN)
|
||||
|
||||
def needs_base64(s):
|
||||
"""
|
||||
returns 1 if s has to be base-64 encoded because of special chars
|
||||
"""
|
||||
return not safe_string_re.search(s) is None
|
||||
|
||||
|
||||
def wrap_binary_data(data):
|
||||
"""Converts all binary data strings into Binary objects for transport
|
||||
back over xmlrpc."""
|
||||
if isinstance(data, str):
|
||||
if needs_base64(data):
|
||||
return xmlrpclib.Binary(data)
|
||||
else:
|
||||
return data
|
||||
elif isinstance(data, list) or isinstance(data,tuple):
|
||||
retval = []
|
||||
for value in data:
|
||||
retval.append(wrap_binary_data(value))
|
||||
return retval
|
||||
elif isinstance(data, dict):
|
||||
retval = {}
|
||||
for (k,v) in data.iteritems():
|
||||
retval[k] = wrap_binary_data(v)
|
||||
return retval
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
def unwrap_binary_data(data):
|
||||
"""Converts all Binary objects back into strings."""
|
||||
if isinstance(data, xmlrpclib.Binary):
|
||||
# The data is decoded by the xmlproxy, but is stored
|
||||
# in a binary object for us.
|
||||
return str(data)
|
||||
elif isinstance(data, str):
|
||||
return data
|
||||
elif isinstance(data, list) or isinstance(data,tuple):
|
||||
retval = []
|
||||
for value in data:
|
||||
retval.append(unwrap_binary_data(value))
|
||||
return retval
|
||||
elif isinstance(data, dict):
|
||||
retval = {}
|
||||
for (k,v) in data.iteritems():
|
||||
retval[k] = unwrap_binary_data(v)
|
||||
return retval
|
||||
else:
|
||||
return data
|
||||
|
||||
def get_gsserror(e):
|
||||
"""A GSSError exception looks differently in python 2.4 than it does
|
||||
in python 2.5, deal with it."""
|
||||
|
||||
try:
|
||||
primary = e[0]
|
||||
secondary = e[1]
|
||||
except:
|
||||
primary = e[0][0]
|
||||
secondary = e[0][1]
|
||||
|
||||
return (primary[0], secondary[0])
|
@ -24,7 +24,8 @@ Some example plugins.
|
||||
|
||||
from ipalib import public
|
||||
from ipalib import api
|
||||
|
||||
from ipalib import servercore
|
||||
import ldap
|
||||
|
||||
# Hypothetical functional commands (not associated with any object):
|
||||
class krbtest(public.Command):
|
||||
@ -39,8 +40,11 @@ api.register(discover)
|
||||
# Register some methods for the 'user' object:
|
||||
class user_add(public.Method):
|
||||
'Add a new user.'
|
||||
def execute(self, **kw):
|
||||
return 1
|
||||
api.register(user_add)
|
||||
|
||||
|
||||
class user_del(public.Method):
|
||||
'Delete an existing user.'
|
||||
api.register(user_del)
|
||||
@ -51,6 +55,9 @@ api.register(user_mod)
|
||||
|
||||
class user_find(public.Method):
|
||||
'Search the users.'
|
||||
def execute(self, **kw):
|
||||
result = servercore.get_sub_entry(servercore.basedn, "uid=%s" % kw['uid'], ["*"])
|
||||
return result
|
||||
api.register(user_find)
|
||||
|
||||
|
||||
|
@ -338,7 +338,7 @@ class Command(plugable.Plugin):
|
||||
kw = self.normalize(**kw)
|
||||
kw.update(self.get_default(**kw))
|
||||
self.validate(**kw)
|
||||
self.execute(**kw)
|
||||
return self.execute(**kw)
|
||||
|
||||
def smart_option_order(self):
|
||||
def get_key(option):
|
||||
|
148
ipalib/servercore.py
Normal file
148
ipalib/servercore.py
Normal file
@ -0,0 +1,148 @@
|
||||
# Authors: Rob Crittenden <rcritten@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2007 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation; version 2 only
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
sys.path.insert(0, "..")
|
||||
import ldap
|
||||
from ipalib.conn import context
|
||||
from ipalib import ipautil
|
||||
|
||||
# temporary
|
||||
import krbV
|
||||
|
||||
krbctx = krbV.default_context()
|
||||
realm = krbctx.default_realm
|
||||
basedn = ipautil.realm_to_suffix(realm)
|
||||
|
||||
def convert_entry(ent):
|
||||
entry = dict(ent.data)
|
||||
entry['dn'] = ent.dn
|
||||
# For now convert single entry lists to a string for the ui.
|
||||
# TODO: we need to deal with multi-values better
|
||||
for key,value in entry.iteritems():
|
||||
if isinstance(value,list) or isinstance(value,tuple):
|
||||
if len(value) == 0:
|
||||
entry[key] = ''
|
||||
elif len(value) == 1:
|
||||
entry[key] = value[0]
|
||||
return entry
|
||||
|
||||
def convert_scalar_values(orig_dict):
|
||||
"""LDAP update dicts expect all values to be a list (except for dn).
|
||||
This method converts single entries to a list."""
|
||||
new_dict={}
|
||||
for (k,v) in orig_dict.iteritems():
|
||||
if not isinstance(v, list) and k != 'dn':
|
||||
v = [v]
|
||||
new_dict[k] = v
|
||||
|
||||
return new_dict
|
||||
|
||||
|
||||
# TODO: rethink the get_entry vs get_list API calls.
|
||||
# they currently restrict the data coming back without
|
||||
# restricting scope. For now adding a get_base/sub_entry()
|
||||
# calls, but the API isn't great.
|
||||
def get_entry (base, scope, searchfilter, sattrs=None):
|
||||
"""Get a specific entry (with a parametized scope).
|
||||
Return as a dict of values.
|
||||
Multi-valued fields are represented as lists.
|
||||
"""
|
||||
ent=""
|
||||
|
||||
ent = context.conn.getConn().getEntry(base, scope, searchfilter, sattrs)
|
||||
|
||||
return convert_entry(ent)
|
||||
|
||||
def get_base_entry (base, searchfilter, sattrs=None):
|
||||
"""Get a specific entry (with a scope of BASE).
|
||||
Return as a dict of values.
|
||||
Multi-valued fields are represented as lists.
|
||||
"""
|
||||
return get_entry(base, ldap.SCOPE_BASE, searchfilter, sattrs)
|
||||
|
||||
def get_sub_entry (base, searchfilter, sattrs=None):
|
||||
"""Get a specific entry (with a scope of SUB).
|
||||
Return as a dict of values.
|
||||
Multi-valued fields are represented as lists.
|
||||
"""
|
||||
return get_entry(base, ldap.SCOPE_SUBTREE, searchfilter, sattrs)
|
||||
|
||||
def get_list (base, searchfilter, sattrs=None):
|
||||
"""Gets a list of entries. Each is converted to a dict of values.
|
||||
Multi-valued fields are represented as lists.
|
||||
"""
|
||||
entries = []
|
||||
|
||||
entries = context.conn.getConn().getList(base, ldap.SCOPE_SUBTREE, searchfilter, sattrs)
|
||||
|
||||
return map(convert_entry, entries)
|
||||
|
||||
def update_entry (oldentry, newentry):
|
||||
"""Update an LDAP entry
|
||||
|
||||
oldentry is a dict
|
||||
newentry is a dict
|
||||
"""
|
||||
oldentry = convert_scalar_values(oldentry)
|
||||
newentry = convert_scalar_values(newentry)
|
||||
|
||||
# Should be able to get this from either the old or new entry
|
||||
# but just in case someone has decided to try changing it, use the
|
||||
# original
|
||||
try:
|
||||
moddn = oldentry['dn']
|
||||
except KeyError, e:
|
||||
# FIXME: return a missing DN error message
|
||||
raise e
|
||||
|
||||
res = context.conn.getConn().updateEntry(moddn, oldentry, newentry)
|
||||
return res
|
||||
|
||||
def uniq_list(x):
|
||||
"""Return a unique list, preserving order and ignoring case"""
|
||||
myset = {}
|
||||
return [set.setdefault(e.lower(),e) for e in x if e.lower() not in myset]
|
||||
|
||||
def get_schema():
|
||||
"""Retrieves the current LDAP schema from the LDAP server."""
|
||||
|
||||
schema_entry = get_base_entry("", "objectclass=*", ['dn','subschemasubentry'])
|
||||
schema_cn = schema_entry.get('subschemasubentry')
|
||||
schema = get_base_entry(schema_cn, "objectclass=*", ['*'])
|
||||
|
||||
return schema
|
||||
|
||||
def get_objectclasses():
|
||||
"""Returns a list of available objectclasses that the LDAP
|
||||
server supports. This parses out the syntax, attributes, etc
|
||||
and JUST returns a lower-case list of the names."""
|
||||
|
||||
schema = get_schema()
|
||||
|
||||
objectclasses = schema.get('objectclasses')
|
||||
|
||||
# Convert this list into something more readable
|
||||
result = []
|
||||
for i in range(len(objectclasses)):
|
||||
oc = objectclasses[i].lower().split(" ")
|
||||
result.append(oc[3].replace("'",""))
|
||||
|
||||
return result
|
16
server/test_client
Executable file
16
server/test_client
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import xmlrpclib
|
||||
|
||||
server = xmlrpclib.ServerProxy("http://localhost:8888/")
|
||||
|
||||
print server.system.listMethods()
|
||||
#print server.system.methodHelp("user_add")
|
||||
|
||||
user = {'givenname':'Joe', 'sn':'Smith'}
|
||||
result = server.user_add(user)
|
||||
print "returned %s" % result
|
||||
|
||||
user = {'givenname':'Joe', 'sn':'Smith', 'uid':'admin'}
|
||||
result = server.user_find(user)
|
||||
print "returned %s" % result
|
133
server/test_server
Executable file
133
server/test_server
Executable file
@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, "..")
|
||||
sys.path.insert(0, ".")
|
||||
import SimpleXMLRPCServer
|
||||
import logging
|
||||
import xmlrpclib
|
||||
import re
|
||||
import threading
|
||||
import commands
|
||||
from ipalib import api, conn
|
||||
from ipalib.conn import context
|
||||
import ipalib.load_plugins
|
||||
|
||||
PORT=8888
|
||||
|
||||
class StoppableXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
|
||||
"""Override of TIME_WAIT"""
|
||||
allow_reuse_address = True
|
||||
|
||||
def serve_forever(self):
|
||||
self.stop = False
|
||||
while not self.stop:
|
||||
self.handle_request()
|
||||
|
||||
class LoggingSimpleXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
|
||||
"""Overides the default SimpleXMLRPCRequestHander to support logging.
|
||||
Logs client IP and the XML request and response.
|
||||
"""
|
||||
|
||||
def parse(self, given):
|
||||
"""Convert the incoming arguments into the format IPA expects"""
|
||||
args = []
|
||||
kw = {}
|
||||
for g in given:
|
||||
kw[g] = unicode(given[g])
|
||||
return (args, kw)
|
||||
|
||||
def _dispatch(self, method, params):
|
||||
"""Dispatches the XML-RPC method.
|
||||
|
||||
Methods beginning with an '_' are considered private and will
|
||||
not be called.
|
||||
"""
|
||||
if (params):
|
||||
(args, kw) = self.parse(*params)
|
||||
else:
|
||||
args = []
|
||||
kw = {}
|
||||
|
||||
# this is fine for our test server
|
||||
uid = commands.getoutput('/usr/bin/id -u')
|
||||
krbccache = "FILE:/tmp/krb5cc_" + uid
|
||||
|
||||
func = None
|
||||
try:
|
||||
# FIXME: don't hardcode host and port
|
||||
context.conn = conn.IPAConn("localhost", 389, krbccache)
|
||||
try:
|
||||
# check to see if a matching function has been registered
|
||||
func = funcs[method]
|
||||
except KeyError:
|
||||
raise Exception('method "%s" is not supported' % method)
|
||||
return func(**kw)
|
||||
finally:
|
||||
# Clean up any per-request data and connections
|
||||
for k in context.__dict__.keys():
|
||||
del context.__dict__[k]
|
||||
|
||||
def do_POST(self):
|
||||
clientIP, port = self.client_address
|
||||
# Log client IP and Port
|
||||
logger.info('Client IP: %s - Port: %s' % (clientIP, port))
|
||||
try:
|
||||
# get arguments
|
||||
data = self.rfile.read(int(self.headers["content-length"]))
|
||||
|
||||
# unmarshal the XML data
|
||||
params, method = xmlrpclib.loads(data)
|
||||
|
||||
# Log client request
|
||||
logger.info('Client request: \n%s\n' % data)
|
||||
|
||||
response = self.server._marshaled_dispatch(
|
||||
data, getattr(self, '_dispatch', None))
|
||||
|
||||
# Log server response
|
||||
logger.info('Server response: \n%s\n' % response)
|
||||
except:
|
||||
# This should only happen if the module is buggy
|
||||
# internal error, report as HTTP server error
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
else:
|
||||
# got a valid XML-RPC response
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/xml")
|
||||
self.send_header("Content-length", str(len(response)))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
# shut down the connection
|
||||
self.wfile.flush()
|
||||
self.connection.shutdown(1)
|
||||
|
||||
# Set up our logger
|
||||
logger = logging.getLogger('xmlrpcserver')
|
||||
hdlr = logging.FileHandler('xmlrpcserver.log')
|
||||
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
||||
hdlr.setFormatter(formatter)
|
||||
logger.addHandler(hdlr)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
# Set up the server
|
||||
XMLRPCServer = StoppableXMLRPCServer(("",PORT), LoggingSimpleXMLRPCRequestHandler)
|
||||
|
||||
XMLRPCServer.register_introspection_functions()
|
||||
|
||||
# Get and register all the methods
|
||||
api.finalize()
|
||||
for cmd in api.Method:
|
||||
logger.info("registering %s" % cmd)
|
||||
XMLRPCServer.register_function(api.Method[cmd], cmd)
|
||||
|
||||
funcs = XMLRPCServer.funcs
|
||||
|
||||
print "Listening on port %d" % PORT
|
||||
try:
|
||||
XMLRPCServer.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
XMLRPCServer.server_close()
|
||||
print "Server shutdown."
|
Loading…
Reference in New Issue
Block a user