diff options
Diffstat (limited to 'ipaserver')
24 files changed, 1594 insertions, 1194 deletions
diff --git a/ipaserver/conn.py b/ipaserver/conn.py deleted file mode 100644 index 070a6adc8..000000000 --- a/ipaserver/conn.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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, either version 3 of the License, or -# (at your option) any later version. -# -# 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, see <http://www.gnu.org/licenses/>. -# - -import krbV -import ldap -import ldap.dn -import ipaldap - -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 diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py index 982e12b31..f7abf62e4 100644 --- a/ipaserver/dcerpc.py +++ b/ipaserver/dcerpc.py @@ -101,7 +101,7 @@ class DomainValidator(object): def is_configured(self): cn_trust_local = DN(('cn', self.api.env.domain), self.api.env.container_cifsdomains, self.api.env.basedn) try: - (dn, entry_attrs) = self.ldap.get_entry(unicode(cn_trust_local), [self.ATTR_FLATNAME, self.ATTR_SID]) + (dn, entry_attrs) = self.ldap.get_entry(cn_trust_local, [self.ATTR_FLATNAME, self.ATTR_SID]) self.flatname = entry_attrs[self.ATTR_FLATNAME][0] self.sid = entry_attrs[self.ATTR_SID][0] self.dn = dn @@ -115,7 +115,7 @@ class DomainValidator(object): try: search_kw = {'objectClass': 'ipaNTTrustedDomain'} filter = self.ldap.make_filter(search_kw, rules=self.ldap.MATCH_ALL) - (entries, truncated) = self.ldap.find_entries(filter=filter, base_dn=unicode(cn_trust), + (entries, truncated) = self.ldap.find_entries(filter=filter, base_dn=cn_trust, attrs_list=[self.ATTR_TRUSTED_SID, 'dn']) return entries @@ -447,5 +447,3 @@ class TrustDomainJoins(object): self.__populate_remote_domain(realm, realm_server, realm_passwd=None) self.local_domain.establish_trust(self.remote_domain, trustdom_passwd) return dict(local=self.local_domain, remote=self.remote_domain) - - diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py index 9dcbec2d6..078c54dbe 100644 --- a/ipaserver/install/adtrustinstance.py +++ b/ipaserver/install/adtrustinstance.py @@ -29,11 +29,12 @@ from ipaserver.install.dsinstance import realm_to_serverid from ipaserver.install.bindinstance import get_rr, add_rr, del_rr, \ dns_zone_exists from ipalib import errors, api -from ipalib.dn import DN +from ipapython.dn import DN from ipapython import sysrestore from ipapython import ipautil from ipapython.ipa_log_manager import * from ipapython import services as ipaservices +from ipapython.dn import DN import string import struct @@ -103,7 +104,7 @@ class ADTRUSTInstance(service.Service): self.netbios_name = None self.no_msdcs = None self.smbd_user = None - self.suffix = None + self.suffix = DN() self.ldapi_socket = None self.smb_conf = None self.smb_dn = None @@ -129,11 +130,10 @@ class ADTRUSTInstance(service.Service): return "S-1-5-21-%d-%d-%d" % (sub_ids[0], sub_ids[1], sub_ids[2]) def __add_admin_sids(self): - admin_dn = str(DN(('uid', 'admin'), api.env.container_user, - self.suffix)) - admin_group_dn = str(DN(('cn', 'admins'), api.env.container_group, - self.suffix)) - + admin_dn = DN(('uid', 'admin'), api.env.container_user, + self.suffix) + admin_group_dn = DN(('cn', 'admins'), api.env.container_group, + self.suffix) try: dom_entry = self.admin_conn.getEntry(self.smb_dom_dn, \ ldap.SCOPE_BASE) @@ -186,10 +186,9 @@ class ADTRUSTInstance(service.Service): """ try: - res = self.admin_conn.search_s(str(DN(api.env.container_ranges, - self.suffix)), - ldap.SCOPE_ONELEVEL, - "(objectclass=ipaDomainIDRange)") + res = self.admin_conn.getList(DN(api.env.container_ranges, self.suffix), + ldap.SCOPE_ONELEVEL, + "(objectclass=ipaDomainIDRange)") if len(res) != 1: root_logger.critical("Found more than one ID range for the " \ "local domain.") @@ -230,17 +229,18 @@ class ADTRUSTInstance(service.Service): pass for new_dn in (self.trust_dn, \ - str(DN(('cn', 'ad'), self.trust_dn)), \ - str(DN(api.env.container_cifsdomains, self.suffix))): + DN(('cn', 'ad'), self.trust_dn), \ + DN(api.env.container_cifsdomains, self.suffix)): try: self.admin_conn.getEntry(new_dn, ldap.SCOPE_BASE) except errors.NotFound: entry = ipaldap.Entry(new_dn) entry.setValues("objectclass", ["nsContainer"]) - name = new_dn.split('=')[1].split(',')[0] - if not name: - print "Cannot extract RDN attribute value from [%s]" % \ - new_dn + try: + name = new_dn[1].attr + except Exception, e: + print 'Cannot extract RDN attribute value from "%s": %s' % \ + (new_dn, e) return entry.setValues("cn", name) self.admin_conn.addEntry(entry) @@ -474,16 +474,16 @@ class ADTRUSTInstance(service.Service): self.smb_conf = "/etc/samba/smb.conf" - self.smb_dn = str(DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'), - ('cn', 'etc'), self.suffix)) + self.smb_dn = DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'), + ('cn', 'etc'), self.suffix) - self.trust_dn = str(DN(api.env.container_trusts, self.suffix)) - self.smb_dom_dn = str(DN(('cn', self.domain_name), - api.env.container_cifsdomains, self.suffix)) + self.trust_dn = DN(api.env.container_trusts, self.suffix) + self.smb_dom_dn = DN(('cn', self.domain_name), + api.env.container_cifsdomains, self.suffix) self.cifs_principal = "cifs/" + self.fqdn + "@" + self.realm - self.cifs_agent = str(DN(('krbprincipalname', self.cifs_principal.lower()), - api.env.container_service, - self.suffix)) + self.cifs_agent = DN(('krbprincipalname', self.cifs_principal.lower()), + api.env.container_service, + self.suffix) self.selinux_booleans = ["samba_portmapper"] self.__setup_sub_dict() @@ -491,16 +491,13 @@ class ADTRUSTInstance(service.Service): def find_local_id_range(self): self.ldap_connect() - if self.admin_conn.search_s(str(DN(api.env.container_ranges, - self.suffix)), + if self.admin_conn.search_s(DN(api.env.container_ranges, self.suffix), ldap.SCOPE_ONELEVEL, "objectclass=ipaDomainIDRange"): return try: - entry = self.admin_conn.getEntry(str(DN(('cn', 'admins'), - api.env.container_group, - self.suffix)), + entry = self.admin_conn.getEntry(DN(('cn', 'admins'), api.env.container_group, self.suffix), ldap.SCOPE_BASE) except errors.NotFound: raise ValueError("No local ID range and no admins group found.\n" \ @@ -523,9 +520,9 @@ class ADTRUSTInstance(service.Service): "range.\nAdd local ID range manually and try " \ "again!") - entry = ipaldap.Entry(str(DN(('cn', ('%s_id_range' % self.realm)), - api.env.container_ranges, - self.suffix))) + entry = ipaldap.Entry(DN(('cn', ('%s_id_range' % self.realm)), + api.env.container_ranges, + self.suffix)) entry.setValue('objectclass', 'ipaDomainIDRange') entry.setValue('cn', ('%s_id_range' % self.realm)) entry.setValue('ipaBaseID', str(base_id)) diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py index f320202ea..2e00f70b1 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -38,6 +38,7 @@ from ipapython.ipa_log_manager import * import ipalib from ipalib import api, util, errors +from ipapython.dn import DN NAMED_CONF = '/etc/named.conf' RESOLV_CONF = '/etc/resolv.conf' @@ -166,10 +167,11 @@ def dns_container_exists(fqdn, suffix, dm_password=None, ldapi=False, realm=None Test whether the dns container exists. """ - def object_exists(dn): + def object_exists(dn): # FIXME, this should be a IPAdmin/ldap2 method so it can be shared """ Test whether the given object exists in LDAP. """ + assert isinstance(dn, DN) try: conn.search_ext_s(dn, ldap.SCOPE_BASE) except ldap.NO_SUCH_OBJECT: @@ -177,6 +179,7 @@ def dns_container_exists(fqdn, suffix, dm_password=None, ldapi=False, realm=None else: return True + assert isinstance(suffix, DN) try: # At install time we may need to use LDAPI to avoid chicken/egg # issues with SSL certs and truting CAs @@ -192,7 +195,7 @@ def dns_container_exists(fqdn, suffix, dm_password=None, ldapi=False, realm=None except ldap.SERVER_DOWN: raise RuntimeError('LDAP server on %s is not responding. Is IPA installed?' % fqdn) - ret = object_exists("cn=dns,%s" % suffix) + ret = object_exists(DN(('cn', 'dns'), suffix)) conn.unbind_s() return ret @@ -288,11 +291,14 @@ def add_zone(name, zonemgr=None, dns_backup=None, ns_hostname=None, ns_ip_addres ns_main = ns_hostname ns_replicas = [] + if ns_ip_address is not None: + ns_ip_address = unicode(ns_ip_address) + try: api.Command.dnszone_add(unicode(name), idnssoamname=unicode(ns_main+'.'), idnssoarname=unicode(zonemgr), - ip_address=unicode(ns_ip_address), + ip_address=ns_ip_address, idnsallowdynupdate=True, idnsupdatepolicy=unicode(update_policy), idnsallowquery=u'any', @@ -329,11 +335,14 @@ def add_reverse_zone(zone, ns_hostname=None, ns_ip_address=None, ns_main = ns_hostname ns_replicas = [] + if ns_ip_address is not None: + ns_ip_address = unicode(ns_ip_address) + try: api.Command.dnszone_add(unicode(zone), idnssoamname=unicode(ns_main+'.'), idnsallowdynupdate=True, - ip_address=unicode(ns_ip_address), + ip_address=ns_ip_address, idnsupdatepolicy=unicode(update_policy), idnsallowquery=u'any', idnsallowtransfer=u'none',) @@ -465,6 +474,8 @@ class BindInstance(service.Service): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + suffix = ipautil.dn_attribute_property('_suffix') + def setup(self, fqdn, ip_address, realm_name, domain_name, forwarders, ntp, reverse_zone, named_user="named", zonemgr=None, zone_refresh=0, persistent_search=True, serial_autoincrement=True): @@ -574,7 +585,7 @@ class BindInstance(service.Service): if self.ntp: optional_ntp = "\n;ntp server\n" - optional_ntp += "_ntp._udp\t\tIN SRV 0 100 123\t%s""" % self.host_in_rr + optional_ntp += "_ntp._udp\t\tIN SRV 0 100 123\t%s" % self.host_in_rr else: optional_ntp = "" @@ -654,7 +665,8 @@ class BindInstance(service.Service): p = self.move_service(dns_principal) if p is None: # the service has already been moved, perhaps we're doing a DNS reinstall - dns_principal = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (dns_principal, self.suffix) + dns_principal = DN(('krbprincipalname', dns_principal), + ('cn', 'services'), ('cn', 'accounts'), self.suffix) else: dns_principal = p @@ -667,9 +679,7 @@ class BindInstance(service.Service): # it can host the memberof attribute, then also add it to the # dnsserver role group, this way the DNS is allowed to perform # DNS Updates - dns_group = "cn=DNS Servers,cn=privileges,cn=pbac,%s" % self.suffix - if isinstance(dns_principal, unicode): - dns_principal = dns_principal.encode('utf-8') + dns_group = DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), self.suffix) mod = [(ldap.MOD_ADD, 'member', dns_principal)] try: diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index dc4374cce..b00ceeaed 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -39,7 +39,7 @@ from ipapython import dogtag from ipapython.certdb import get_ca_nickname from ipapython import certmonger from ipalib import pkcs10, x509 -from ipalib.dn import DN +from ipapython.dn import DN import subprocess from nss.error import NSPRError @@ -243,7 +243,9 @@ class CADSInstance(service.Service): self.suffix = ipautil.realm_to_suffix(self.realm_name) self.__setup_sub_dict() else: - self.suffix = None + self.suffix = DN() + + subject_base = ipautil.dn_attribute_property('_subject_base') def create_instance(self, realm_name, host_name, domain_name, dm_password, pkcs12_info=None, ds_port=DEFAULT_DSPORT, @@ -268,7 +270,7 @@ class CADSInstance(service.Service): def __setup_sub_dict(self): server_root = dsinstance.find_server_root() self.sub_dict = dict(FQDN=self.fqdn, SERVERID=self.serverid, - PASSWORD=self.dm_password, SUFFIX=self.suffix.lower(), + PASSWORD=self.dm_password, SUFFIX=self.suffix, REALM=self.realm_name, USER=PKI_DS_USER, SERVER_ROOT=server_root, DOMAIN=self.domain, TIME=int(time.time()), DSPORT=self.ds_port, @@ -342,7 +344,7 @@ class CADSInstance(service.Service): def enable_ssl(self): conn = ipaldap.IPAdmin("127.0.0.1", port=DEFAULT_DSPORT) - conn.simple_bind_s("cn=directory manager", self.dm_password) + conn.simple_bind_s(DN(('cn', 'directory manager')), self.dm_password) mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"), (ldap.MOD_REPLACE, "nsSSL3Ciphers", @@ -350,13 +352,13 @@ class CADSInstance(service.Service): +rsa_des_sha,+rsa_fips_des_sha,+rsa_3des_sha,+rsa_fips_3des_sha,+fortezza,\ +fortezza_rc4_128_sha,+fortezza_null,+tls_rsa_export1024_with_rc4_56_sha,\ +tls_rsa_export1024_with_des_cbc_sha")] - conn.modify_s("cn=encryption,cn=config", mod) + conn.modify_s(DN(('cn', 'encryption'), ('cn', 'config')), mod) mod = [(ldap.MOD_ADD, "nsslapd-security", "on"), (ldap.MOD_ADD, "nsslapd-secureport", str(DEFAULT_DSPORT+1))] - conn.modify_s("cn=config", mod) + conn.modify_s(DN(('cn', 'config')), mod) - entry = ipaldap.Entry("cn=RSA,cn=encryption,cn=config") + entry = ipaldap.Entry(DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsEncryptionModule") entry.setValues("cn", "RSA") @@ -460,7 +462,7 @@ class CAInstance(service.Service): # will already have been initialized by Apache by the time # mod_python wants to do things. self.canickname = get_ca_nickname(realm) - self.basedn = "o=ipaca" + self.basedn = DN(('o', 'ipaca')) self.ca_agent_db = tempfile.mkdtemp(prefix = "tmp-") self.ra_agent_db = ra_db self.ra_agent_pwd = self.ra_agent_db + "/pwdfile.txt" @@ -506,7 +508,7 @@ class CAInstance(service.Service): self.clone = True self.master_host = master_host if subject_base is None: - self.subject_base = "O=%s" % self.realm + self.subject_base = DN(('O', self.realm)) else: self.subject_base = subject_base @@ -615,12 +617,12 @@ class CAInstance(service.Service): "-agent_name", "ipa-ca-agent", "-agent_key_size", "2048", "-agent_key_type", "rsa", - "-agent_cert_subject", "CN=ipa-ca-agent,%s" % self.subject_base, + "-agent_cert_subject", str(DN(('CN', 'ipa-ca-agent'), self.subject_base)), "-ldap_host", self.fqdn, "-ldap_port", str(self.ds_port), "-bind_dn", "cn=Directory Manager", "-bind_password", self.dm_password, - "-base_dn", self.basedn, + "-base_dn", str(self.basedn), "-db_name", "ipaca", "-key_size", "2048", "-key_type", "rsa", @@ -629,11 +631,12 @@ class CAInstance(service.Service): "-backup_pwd", self.admin_password, "-subsystem_name", self.service_name, "-token_name", "internal", - "-ca_subsystem_cert_subject_name", "CN=CA Subsystem,%s" % self.subject_base, - "-ca_ocsp_cert_subject_name", "CN=OCSP Subsystem,%s" % self.subject_base, - "-ca_server_cert_subject_name", "CN=%s,%s" % (self.fqdn, self.subject_base), - "-ca_audit_signing_cert_subject_name", "CN=CA Audit,%s" % self.subject_base, - "-ca_sign_cert_subject_name", "CN=Certificate Authority,%s" % self.subject_base ] + "-ca_subsystem_cert_subject_name", str(DN(('CN', 'CA Subsystem'), self.subject_base)), + "-ca_subsystem_cert_subject_name", str(DN(('CN', 'CA Subsystem'), self.subject_base)), + "-ca_ocsp_cert_subject_name", str(DN(('CN', 'OCSP Subsystem'), self.subject_base)), + "-ca_server_cert_subject_name", str(DN(('CN', self.fqdn), self.subject_base)), + "-ca_audit_signing_cert_subject_name", str(DN(('CN', 'CA Audit'), self.subject_base)), + "-ca_sign_cert_subject_name", str(DN(('CN', 'Certificate Authority'), self.subject_base)) ] if self.external == 1: args.append("-external") args.append("true") @@ -836,13 +839,12 @@ class CAInstance(service.Service): # Create an RA user in the CA LDAP server and add that user to # the appropriate groups so it can issue certificates without # manual intervention. - ld = ldap.initialize("ldap://%s" % ipautil.format_netloc(self.fqdn, self.ds_port)) - ld.protocol_version=ldap.VERSION3 - ld.simple_bind_s("cn=Directory Manager", self.dm_password) + conn = ipaldap.IPAdmin(self.fqdn, self.ds_port) + conn.simple_bind_s(DN(('cn', 'Directory Manager')), self.dm_password) decoded = base64.b64decode(self.ra_cert) - entry_dn = "uid=%s,ou=People,%s" % ("ipara", self.basedn) + entry_dn = DN(('uid', "ipara"), ('ou', 'People'), self.basedn) entry = [ ('objectClass', ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'cmsuser']), ('uid', "ipara"), @@ -851,19 +853,23 @@ class CAInstance(service.Service): ('usertype', "agentType"), ('userstate', "1"), ('userCertificate', decoded), - ('description', '2;%s;CN=Certificate Authority,%s;CN=IPA RA,%s' % (str(self.requestId), self.subject_base, self.subject_base)),] + ('description', '2;%s;%s;%s' % \ + (str(self.requestId), + DN(('CN', 'Certificate Authority'), self.subject_base), + DN(('CN', 'IPA RA'), self.subject_base))), + ] - ld.add_s(entry_dn, entry) + conn.add_s(entry_dn, entry) - dn = "cn=Certificate Manager Agents,ou=groups,%s" % self.basedn + dn = DN(('cn', 'Certificate Manager Agents'), ('ou', 'groups'), self.basedn) modlist = [(0, 'uniqueMember', '%s' % entry_dn)] - ld.modify_s(dn, modlist) + conn.modify_s(dn, modlist) - dn = "cn=Registration Manager Agents,ou=groups,%s" % self.basedn + dn = DN(('cn', 'Registration Manager Agents'), ('ou', 'groups'), self.basedn) modlist = [(0, 'uniqueMember', '%s' % entry_dn)] - ld.modify_s(dn, modlist) + conn.modify_s(dn, modlist) - ld.unbind_s() + conn.unbind_s() def __run_certutil(self, args, database=None, pwd_file=None,stdin=None): if not database: @@ -969,7 +975,7 @@ class CAInstance(service.Service): # Generate our CSR. The result gets put into stdout try: - (stdout, stderr, returncode) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", "CN=IPA RA,%s" % self.subject_base, "-z", noise_name, "-a"]) + (stdout, stderr, returncode) = self.__run_certutil(["-R", "-k", "rsa", "-g", "2048", "-s", str(DN(('CN', 'IPA RA'), self.subject_base)), "-z", noise_name, "-a"]) finally: os.remove(noise_name) @@ -1071,7 +1077,7 @@ class CAInstance(service.Service): def __set_subject_in_config(self): # dogtag ships with an IPA-specific profile that forces a subject # format. We need to update that template with our base subject - if installutils.update_file(IPA_SERVICE_PROFILE, 'OU=pki-ipa, O=IPA', self.subject_base): + if installutils.update_file(IPA_SERVICE_PROFILE, 'OU=pki-ipa, O=IPA', str(self.subject_base)): print "Updating subject_base in CA template failed" def uninstall(self): diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py index d25a471ea..eebaa48c4 100644 --- a/ipaserver/install/certs.py +++ b/ipaserver/install/certs.py @@ -39,7 +39,7 @@ from ipalib import pkcs10 from ConfigParser import RawConfigParser, MissingSectionHeaderError from ipapython import services as ipaservices from ipalib import x509 -from ipalib.dn import DN +from ipapython.dn import DN from ipalib.errors import CertificateOperationError from nss.error import NSPRError @@ -224,8 +224,7 @@ class CertDB(object): self.self_signed_ca = ipa_self_signed() if not subject_base: - self.subject_base = "O=IPA" - self.subject_format = "CN=%%s,%s" % self.subject_base + self.subject_base = DN(('O', 'IPA')) self.cacert_name = get_ca_nickname(self.realm) self.valid_months = "120" @@ -245,6 +244,8 @@ class CertDB(object): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + subject_base = ipautil.dn_attribute_property('_subject_base') + def __del__(self): if self.reqdir is not None: shutil.rmtree(self.reqdir, ignore_errors=True) @@ -381,11 +382,11 @@ class CertDB(object): def create_ca_cert(self): os.chdir(self.secdir) - subject = "cn=%s Certificate Authority" % self.realm + subject = DN(('cn', '%s Certificate Authority' % self.realm)) p = subprocess.Popen(["/usr/bin/certutil", "-d", self.secdir, "-S", "-n", self.cacert_name, - "-s", subject, + "-s", str(subject), "-x", "-t", "CT,,C", "-1", @@ -565,7 +566,7 @@ class CertDB(object): if not cdb: cdb = self if subject is None: - subject=self.subject_format % hostname + subject=DN(('CN', hostname), self.subject_base) self.request_cert(subject) cdb.issue_server_cert(self.certreq_fname, self.certder_fname) self.add_cert(self.certder_fname, nickname) @@ -583,7 +584,7 @@ class CertDB(object): if not cdb: cdb = self if subject is None: - subject=self.subject_format % hostname + subject=DN(('CN', hostname), self.subject_base) self.request_cert(subject) cdb.issue_signing_cert(self.certreq_fname, self.certder_fname) self.add_cert(self.certder_fname, nickname) @@ -591,9 +592,10 @@ class CertDB(object): os.unlink(self.certder_fname) def request_cert(self, subject, certtype="rsa", keysize="2048"): + assert isinstance(subject, DN) self.create_noise_file() self.setup_cert_request() - args = ["-R", "-s", subject, + args = ["-R", "-s", str(subject), "-o", self.certreq_fname, "-k", certtype, "-g", keysize, @@ -1046,19 +1048,19 @@ class CertDB(object): # Prepare a simple cert request req_dict = dict(PASSWORD=self.gen_password(), SUBJBASE=self.subject_base, - CERTNAME="CN="+nickname) + CERTNAME=DN(('CN', nickname))) req_template = ipautil.SHARE_DIR + reqcfg + ".template" conf = ipautil.template_file(req_template, req_dict) fd = open(reqcfg, "w+") fd.write(conf) fd.close() - base = self.subject_base.replace(",", "/") - esc_subject = "CN=%s/%s" % (nickname, base) + base = str(self.subject_base).replace(",", "/") + esc_subject = DN(('CN', '%s/%s' % (nickname, base))) ipautil.run(["/usr/bin/openssl", "req", "-new", "-config", reqcfg, - "-subj", esc_subject, + "-subj", str(esc_subject), "-key", key_fname, "-out", "kdc.req"]) diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 9f3ae7252..bf6677381 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -36,12 +36,12 @@ import service import installutils import certs import ldap -from ldap.dn import escape_dn_chars from ipaserver import ipaldap from ipaserver.install import ldapupdate from ipaserver.install import httpinstance from ipaserver.install import replication from ipalib import util, errors +from ipapython.dn import DN from ipaserver.plugins.ldap2 import ldap2 SERVER_ROOT_64 = "/usr/lib64/dirsrv" @@ -177,7 +177,7 @@ class DsInstance(service.Service): self.suffix = ipautil.realm_to_suffix(self.realm_name) self.__setup_sub_dict() else: - self.suffix = None + self.suffix = DN() if fstore: self.fstore = fstore @@ -185,6 +185,8 @@ class DsInstance(service.Service): self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + subject_base = ipautil.dn_attribute_property('_subject_base') + def __common_setup(self): self.step("creating directory server user", self.__create_ds_user) @@ -299,7 +301,7 @@ class DsInstance(service.Service): self.fqdn, self.dm_password) repl.setup_replication(self.master_fqdn, - r_binddn="cn=Directory Manager", + r_binddn=DN(('cn', 'Directory Manager')), r_bindpw=self.dm_password) self.run_init_memberof = repl.needs_memberof_fixup() @@ -314,12 +316,12 @@ class DsInstance(service.Service): self.sub_dict = dict(FQDN=self.fqdn, SERVERID=self.serverid, PASSWORD=self.dm_password, RANDOM_PASSWORD=self.generate_random(), - SUFFIX=self.suffix.lower(), + SUFFIX=self.suffix, REALM=self.realm_name, USER=DS_USER, SERVER_ROOT=server_root, DOMAIN=self.domain, TIME=int(time.time()), IDSTART=self.idstart, IDMAX=self.idmax, HOST=self.fqdn, - ESCAPED_SUFFIX= escape_dn_chars(self.suffix.lower()), + ESCAPED_SUFFIX=str(self.suffix), GROUP=DS_GROUP, IDRANGE_SIZE=self.idmax-self.idstart+1 ) @@ -445,11 +447,12 @@ class DsInstance(service.Service): self._ldap_mod("memberof-task.ldif", self.sub_dict) # Note, keep dn in sync with dn in install/share/memberof-task.ldif - dn = "cn=IPA install %s,cn=memberof task,cn=tasks,cn=config" % self.sub_dict["TIME"] + dn = DN(('cn', 'IPA install %s' % self.sub_dict["TIME"]), ('cn', 'memberof task'), + ('cn', 'tasks'), ('cn', 'config')) root_logger.debug("Waiting for memberof task to complete.") conn = ipaldap.IPAdmin("127.0.0.1") if self.dm_password: - conn.simple_bind_s("cn=directory manager", self.dm_password) + conn.simple_bind_s(DN(('cn', 'directory manager')), self.dm_password) else: conn.do_sasl_gssapi_bind() conn.checkTask(dn, dowait=True) @@ -543,7 +546,7 @@ class DsInstance(service.Service): dsdb.create_pin_file() conn = ipaldap.IPAdmin("127.0.0.1") - conn.simple_bind_s("cn=directory manager", self.dm_password) + conn.simple_bind_s(DN(('cn', 'directory manager')), self.dm_password) mod = [(ldap.MOD_REPLACE, "nsSSLClientAuth", "allowed"), (ldap.MOD_REPLACE, "nsSSL3Ciphers", @@ -551,12 +554,12 @@ class DsInstance(service.Service): +rsa_des_sha,+rsa_fips_des_sha,+rsa_3des_sha,+rsa_fips_3des_sha,+fortezza,\ +fortezza_rc4_128_sha,+fortezza_null,+tls_rsa_export1024_with_rc4_56_sha,\ +tls_rsa_export1024_with_des_cbc_sha")] - conn.modify_s("cn=encryption,cn=config", mod) + conn.modify_s(DN(('cn', 'encryption'), ('cn', 'config')), mod) mod = [(ldap.MOD_ADD, "nsslapd-security", "on")] - conn.modify_s("cn=config", mod) + conn.modify_s(DN(('cn', 'config')), mod) - entry = ipaldap.Entry("cn=RSA,cn=encryption,cn=config") + entry = ipaldap.Entry(DN(('cn', 'RSA'), ('cn', 'encryption'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsEncryptionModule") entry.setValues("cn", "RSA") @@ -612,9 +615,9 @@ class DsInstance(service.Service): os.close(admpwdfd) args = ["/usr/bin/ldappasswd", "-h", self.fqdn, - "-ZZ", "-x", "-D", "cn=Directory Manager", + "-ZZ", "-x", "-D", str(DN(('cn', 'Directory Manager'))), "-y", dmpwdfile, "-T", admpwdfile, - "uid=admin,cn=users,cn=accounts,"+self.suffix] + str(DN(('uid', 'admin'), ('cn', 'users'), ('cn', 'accounts'), self.suffix))] try: env = { 'LDAPTLS_CACERTDIR':os.path.dirname(CACERT), 'LDAPTLS_CACERT':CACERT } @@ -801,22 +804,19 @@ class DsInstance(service.Service): def replica_populate(self): self.ldap_connect() - dn = "cn=default,ou=profile,%s" % self.suffix + dn = DN(('cn', 'default'), ('ou', 'profile'), self.suffix) try: - ret = self.admin_conn.search_s(dn, ldap.SCOPE_BASE, - '(objectclass=*)')[0] - srvlist = ret.data.get('defaultServerList') - if len(srvlist) > 0: - srvlist = srvlist[0].split() + entry = self.admin_conn.getEntry(dn, ldap.SCOPE_BASE, '(objectclass=*)') + srvlist = entry.getValue('defaultServerList', '') + srvlist = srvlist.split() if not self.fqdn in srvlist: srvlist.append(self.fqdn) attr = ' '.join(srvlist) mod = [(ldap.MOD_REPLACE, 'defaultServerList', attr)] self.admin_conn.modify_s(dn, mod) - except ldap.NO_SUCH_OBJECT: + except errors.NotFound: pass except ldap.TYPE_OR_VALUE_EXISTS: pass self.ldap_disconnect() - diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index 601f76bb7..e5d9f080b 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -57,6 +57,8 @@ class HTTPInstance(service.Service): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + subject_base = ipautil.dn_attribute_property('_subject_base') + def create_instance(self, realm, fqdn, domain_name, dm_password=None, autoconfig=True, pkcs12_info=None, self_signed_ca=False, subject_base=None, auto_redirect=True): self.fqdn = fqdn self.realm = realm diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py index 388a11e26..048706523 100644 --- a/ipaserver/install/installutils.py +++ b/ipaserver/install/installutils.py @@ -44,6 +44,7 @@ from ipapython.ipa_log_manager import * from ipalib.util import validate_hostname from ipapython import config from ipalib import errors +from ipapython.dn import DN # Used to determine install status IPA_MODULES = ['httpd', 'kadmin', 'dirsrv', 'pki-cad', 'pkids', 'install', 'krb5kdc', 'ntpd', 'named', 'ipa_memcached'] @@ -71,9 +72,11 @@ class ReplicaConfig: self.dirman_password = "" self.host_name = "" self.dir = "" - self.subject_base = "" + self.subject_base = None self.setup_ca = False + subject_base = ipautil.dn_attribute_property('_subject_base') + def get_fqdn(): fqdn = "" try: diff --git a/ipaserver/install/ipa_ldap_updater.py b/ipaserver/install/ipa_ldap_updater.py index 794ea28b5..d9f680927 100644 --- a/ipaserver/install/ipa_ldap_updater.py +++ b/ipaserver/install/ipa_ldap_updater.py @@ -150,6 +150,8 @@ class LDAPUpdater_Upgrade(LDAPUpdater): class LDAPUpdater_NonUpgrade(LDAPUpdater): + log_file_name = '/var/log/ipaupgrade.log' + def validate_options(self): super(LDAPUpdater_NonUpgrade, self).validate_options() options = self.options diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index 8cc50fba4..9101e6fcc 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -33,6 +33,7 @@ from ipapython import services as ipaservices from ipalib import util from ipalib import errors from ipapython.ipa_log_manager import * +from ipapython.dn import DN from ipaserver import ipaldap from ipaserver.install import replication @@ -84,6 +85,7 @@ class KrbInstance(service.Service): self.admin_password = None self.master_password = None self.suffix = None + self.subject_base = None self.kdc_password = None self.sub_dict = None self.pkcs12_info = None @@ -94,8 +96,11 @@ class KrbInstance(service.Service): else: self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + suffix = ipautil.dn_attribute_property('_suffix') + subject_base = ipautil.dn_attribute_property('_subject_base') + def get_realm_suffix(self): - return "cn=%s,cn=kerberos,%s" % (self.realm, self.suffix) + return DN(('cn', self.realm), ('cn', 'kerberos'), self.suffix) def move_service_to_host(self, principal): """ @@ -103,12 +108,12 @@ class KrbInstance(service.Service): cn=kerberos to reside under the host entry. """ - service_dn = "krbprincipalname=%s,%s" % (principal, self.get_realm_suffix()) + service_dn = DN(('krbprincipalname', principal), self.get_realm_suffix()) service_entry = self.admin_conn.getEntry(service_dn, ldap.SCOPE_BASE) self.admin_conn.deleteEntry(service_dn) # Create a host entry for this master - host_dn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) + host_dn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) host_entry = ipaldap.Entry(host_dn) host_entry.setValues('objectclass', ['top', 'ipaobject', 'nshost', 'ipahost', 'ipaservice', 'pkiuser', 'krbprincipalaux', 'krbprincipal', 'krbticketpolicyaux', 'ipasshhost']) host_entry.setValues('krbextradata', service_entry.getValues('krbextradata')) @@ -251,7 +256,7 @@ class KrbInstance(service.Service): # they may conflict. try: - res = self.admin_conn.search_s("cn=mapping,cn=sasl,cn=config", + res = self.admin_conn.search_s(DN(('cn', 'mapping'), ('cn', 'sasl'), ('cn', 'config')), ldap.SCOPE_ONELEVEL, "(objectclass=nsSaslMapping)") for r in res: @@ -264,7 +269,7 @@ class KrbInstance(service.Service): root_logger.critical("Error while enumerating SASL mappings %s" % str(e)) raise e - entry = ipaldap.Entry("cn=Full Principal,cn=mapping,cn=sasl,cn=config") + entry = ipaldap.Entry(DN(('cn', 'Full Principal'), ('cn', 'mapping'), ('cn', 'sasl'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsSaslMapping") entry.setValues("cn", "Full Principal") entry.setValues("nsSaslMapRegexString", '\(.*\)@\(.*\)') @@ -277,7 +282,7 @@ class KrbInstance(service.Service): root_logger.critical("failed to add Full Principal Sasl mapping") raise e - entry = ipaldap.Entry("cn=Name Only,cn=mapping,cn=sasl,cn=config") + entry = ipaldap.Entry(DN(('cn', 'Name Only'), ('cn', 'mapping'), ('cn', 'sasl'), ('cn', 'config'))) entry.setValues("objectclass", "top", "nsSaslMapping") entry.setValues("cn", "Name Only") entry.setValues("nsSaslMapRegexString", '^[^:@]+$') @@ -358,7 +363,7 @@ class KrbInstance(service.Service): root_logger.critical("Could not find master key in DS") raise e - krbMKey = pyasn1.codec.ber.decoder.decode(entry.krbmkey) + krbMKey = pyasn1.codec.ber.decoder.decode(entry.getValue('krbmkey')) keytype = int(krbMKey[0][1][0]) keydata = str(krbMKey[0][1][1]) @@ -431,7 +436,7 @@ class KrbInstance(service.Service): # Create the special anonymous principal installutils.kadmin_addprinc(princ_realm) - dn = "krbprincipalname=%s,%s" % (princ_realm, self.get_realm_suffix()) + dn = DN(('krbprincipalname', princ_realm), self.get_realm_suffix()) self.admin_conn.inactivateEntry(dn, False) def __convert_to_gssapi_replication(self): @@ -439,7 +444,7 @@ class KrbInstance(service.Service): self.fqdn, self.dm_password) repl.convert_to_gssapi_replication(self.master_fqdn, - r_binddn="cn=Directory Manager", + r_binddn=DN(('cn', 'Directory Manager')), r_bindpw=self.dm_password) def uninstall(self): diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py index 949b86ad9..a51edff99 100644 --- a/ipaserver/install/ldapupdate.py +++ b/ipaserver/install/ldapupdate.py @@ -33,9 +33,8 @@ import uuid from ipalib import util from ipalib import errors from ipalib import api -from ipalib.dn import DN +from ipapython.dn import DN import ldap -from ldap.dn import escape_dn_chars from ipapython.ipa_log_manager import * import krbV import platform @@ -52,22 +51,62 @@ from ipaserver.install.plugins import FIRST, MIDDLE, LAST class BadSyntax(installutils.ScriptError): def __init__(self, value): self.value = value - self.msg = "There is a syntax error in this update file: \n %s" % value + self.msg = "LDAPUpdate: syntax error: \n %s" % value self.rval = 1 def __str__(self): return repr(self.value) class LDAPUpdate: + action_keywords = ["default", "add", "remove", "only", "deleteentry", "replace", "addifnew", "addifexist"] + def __init__(self, dm_password, sub_dict={}, live_run=True, online=True, ldapi=False, plugins=False): - """dm_password = Directory Manager password - sub_dict = substitution dictionary - live_run = Apply the changes or just test - online = do an online LDAP update or use an experimental LDIF updater - ldapi = bind using ldapi. This assumes autobind is enabled. - plugins = execute the pre/post update plugins - """ + ''' + :parameters: + dm_password + Directory Manager password + sub_dict + substitution dictionary + live_run + Apply the changes or just test + online + Do an online LDAP update or use an experimental LDIF updater + ldapi + Bind using ldapi. This assumes autobind is enabled. + plugins + execute the pre/post update plugins + + Data Structure Example: + ----------------------- + + dn_by_rdn_count = { + 3: 'cn=config,dc=example,dc=com': + 4: 'cn=bob,ou=people,dc=example,dc=com', + } + + all_updates = { + 'dn': 'cn=config,dc=example,dc=com': + { + 'dn': 'cn=config,dc=example,dc=com', + 'default': ['attr1':default1'], + 'updates': ['action:attr1:value1', + 'action:attr2:value2] + }, + 'dn': 'cn=bob,ou=people,dc=example,dc=com': + { + 'dn': 'cn=bob,ou=people,dc=example,dc=com', + 'default': ['attr3':default3'], + 'updates': ['action:attr3:value3', + 'action:attr4:value4], + } + } + + The default and update lists are "dispositions" + + + ''' + log_mgr.get_logger(self, True) self.sub_dict = sub_dict self.live_run = live_run self.dm_password = dm_password @@ -77,6 +116,8 @@ class LDAPUpdate: self.ldapi = ldapi self.plugins = plugins self.pw_name = pwd.getpwuid(os.geteuid()).pw_name + self.realm = None + suffix = None if sub_dict.get("REALM"): self.realm = sub_dict["REALM"] @@ -89,8 +130,10 @@ class LDAPUpdate: self.realm = None suffix = None + if suffix is not None: + assert isinstance(suffix, DN) domain = ipautil.get_domain_name() - libarch = self.__identify_arch() + libarch = self._identify_arch() fqdn = installutils.get_fqdn() if fqdn is None: @@ -110,7 +153,7 @@ class LDAPUpdate: if not self.sub_dict.get("SUFFIX") and suffix is not None: self.sub_dict["SUFFIX"] = suffix if not self.sub_dict.get("ESCAPED_SUFFIX"): - self.sub_dict["ESCAPED_SUFFIX"] = escape_dn_chars(suffix) + self.sub_dict["ESCAPED_SUFFIX"] = str(suffix) if not self.sub_dict.get("LIBARCH"): self.sub_dict["LIBARCH"] = libarch if not self.sub_dict.get("TIME"): @@ -123,7 +166,7 @@ class LDAPUpdate: try: conn = ipaldap.IPAdmin(fqdn, ldapi=self.ldapi, realm=self.realm) if self.dm_password: - conn.do_simple_bind(binddn="cn=directory manager", bindpw=self.dm_password) + conn.do_simple_bind(binddn=DN(('cn', 'directory manager')), bindpw=self.dm_password) elif os.getegid() == 0: try: # autobind @@ -145,13 +188,13 @@ class LDAPUpdate: # The following 2 functions were taken from the Python # documentation at http://docs.python.org/library/csv.html - def __utf_8_encoder(self, unicode_csv_data): + def _utf_8_encoder(self, unicode_csv_data): for line in unicode_csv_data: yield line.encode('utf-8') - def __unicode_csv_reader(self, unicode_csv_data, quote_char="'", dialect=csv.excel, **kwargs): + def _unicode_csv_reader(self, unicode_csv_data, quote_char="'", dialect=csv.excel, **kwargs): # csv.py doesn't do Unicode; encode temporarily as UTF-8: - csv_reader = csv.reader(self.__utf_8_encoder(unicode_csv_data), + csv_reader = csv.reader(self._utf_8_encoder(unicode_csv_data), dialect=dialect, delimiter=',', quotechar=quote_char, skipinitialspace=True, @@ -160,7 +203,7 @@ class LDAPUpdate: # decode UTF-8 back to Unicode, cell by cell: yield [unicode(cell, 'utf-8') for cell in row] - def __identify_arch(self): + def _identify_arch(self): """On multi-arch systems some libraries may be in /lib64, /usr/lib64, etc. Determine if a suffix is needed based on the current architecture. @@ -178,7 +221,7 @@ class LDAPUpdate: except KeyError, e: raise BadSyntax("Unknown template keyword %s" % e) - def __parse_values(self, line): + def _parse_values(self, line): """Parse a comma-separated string into separate values and convert them into a list. This should handle quoted-strings with embedded commas """ @@ -186,7 +229,7 @@ class LDAPUpdate: quote_char = "'" else: quote_char = '"' - reader = self.__unicode_csv_reader([line], quote_char) + reader = self._unicode_csv_reader([line], quote_char) value = [] for row in reader: value = value + row @@ -201,7 +244,7 @@ class LDAPUpdate: if fd != sys.stdin: fd.close() return text - def __entry_to_entity(self, ent): + def _entry_to_entity(self, ent): """Tne Entry class is a bare LDAP entry. The Entity class has a lot more helper functions that we need, so convert to dict and then to Entity. """ @@ -215,113 +258,158 @@ class LDAPUpdate: entry[key] = value[0] return entity.Entity(entry) - def __combine_updates(self, dn_list, all_updates, update): - """Combine a new update with the list of total updates - - Updates are stored in 2 lists: - dn_list: contains a unique list of DNs in the updates - all_updates: the actual updates that need to be applied - - We want to apply the updates from the shortest to the longest - path so if new child and parent entries are in different updates - we can be sure the parent gets written first. This also lets - us apply any schema first since it is in the very short cn=schema. - """ + def _combine_updates(self, all_updates, update): + 'Combine a new update with the list of total updates' dn = update.get('dn') - dns = ldap.explode_dn(dn.lower()) - l = len(dns) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] + assert isinstance(dn, DN) + if not all_updates.get(dn): all_updates[dn] = update return all_updates - e = all_updates[dn] + existing_update = all_updates[dn] if 'default' in update: - if 'default' in e: - e['default'] = e['default'] + update['default'] - else: - e['default'] = update['default'] + disposition_list = existing_update.setdefault('default', []) + disposition_list.extend(update['default']) elif 'updates' in update: - if 'updates' in e: - e['updates'] = e['updates'] + update['updates'] - else: - e['updates'] = update['updates'] + disposition_list = existing_update.setdefault('updates', []) + disposition_list.extend(update['updates']) else: - root_logger.debug("Unknown key in updates %s" % update.keys()) - - all_updates[dn] = e + self.debug("Unknown key in updates %s" % update.keys()) + + def merge_updates(self, all_updates, updates): + ''' + Add the new_update dict to the all_updates dict. If an entry + in the new_update already has an entry in all_updates merge + the two entries sensibly assuming the new entries take + precedence. Otherwise just add the new entry. + ''' + + for new_update in updates: + for new_dn, new_entry in new_update.iteritems(): + existing_entry = all_updates.get(new_dn) + if existing_entry: + # If the existing entry is marked for deletion but the + # new entry is not also a delete then clear the delete + # flag otherwise the newer update will be lost. + if existing_entry.has_key('deleteentry') and not new_entry.has_key('deleteentry'): + self.warning("ldapupdate: entry '%s' previously marked for deletion but" + + " this subsequent update reestablishes it: %s", new_dn, new_entry) + del existing_entry['deleteentry'] + existing_entry.update(new_entry) + else: + all_updates[new_dn] = new_entry - return all_updates - def parse_update_file(self, data, all_updates, dn_list): + def parse_update_file(self, data_source_name, source_data, all_updates): """Parse the update file into a dictonary of lists and apply the update for each DN in the file.""" - valid_keywords = ["default", "add", "remove", "only", "deleteentry", "replace", "addifnew", "addifexist"] update = {} - d = "" - index = "" + logical_line = "" + action = "" dn = None lcount = 0 - for line in data: - # Strip out \n and extra white space - lcount = lcount + 1 - # skip comments and empty lines - line = line.rstrip() - if line.startswith('#') or line == '': continue + def emit_item(logical_line): + ''' + Given a logical line containing an item to process perform the following: - if line.lower().startswith('dn:'): - if dn is not None: - all_updates = self.__combine_updates(dn_list, all_updates, update) + * Strip leading & trailing whitespace + * Substitute any variables + * Get the action, attribute, and value + * Each update has one list per disposition, append to specified disposition list + ''' - update = {} - dn = line[3:].strip() - update['dn'] = self._template_str(dn) - else: - if dn is None: - raise BadSyntax, "dn is not defined in the update" - - line = self._template_str(line) - if line.startswith(' '): - v = d[len(d) - 1] - v = v + line[1:] - d[len(d) - 1] = v - update[index] = d - continue - line = line.strip() - values = line.split(':', 2) - if len(values) != 3: - raise BadSyntax, "Bad formatting on line %d: %s" % (lcount,line) + logical_line = logical_line.strip() + if logical_line == '': + return + + # Perform variable substitution on constructued line + logical_line = self._template_str(logical_line) - index = values[0].strip().lower() + items = logical_line.split(':', 2) - if index not in valid_keywords: - raise BadSyntax, "Unknown keyword %s" % index + if len(items) == 0: + raise BadSyntax, "Bad formatting on line %s:%d: %s" % (data_source_name, lcount, logical_line) - attr = values[1].strip() - value = values[2].strip() + action = items[0].strip().lower() - new_value = "" - if index == "default": + if action not in self.action_keywords: + raise BadSyntax, "Unknown update action '%s', data source=%s" % (action, data_source_name) + + if action == 'deleteentry': + new_value = None + disposition = "deleteentry" + else: + if len(items) != 3: + raise BadSyntax, "Bad formatting on line %s:%d: %s" % (data_source_name, lcount, logical_line) + + attr = items[1].strip() + value = items[2].strip() + + if action == "default": new_value = attr + ":" + value + disposition = "default" else: - new_value = index + ":" + attr + ":" + value - index = "updates" + new_value = action + ":" + attr + ":" + value + disposition = "updates" + + disposition_list = update.setdefault(disposition, []) + disposition_list.append(new_value) + + def emit_update(update): + ''' + When processing a dn is completed emit the update by merging it into + the set of all updates. + ''' + + self._combine_updates(all_updates, update) + + # Iterate over source input lines + for source_line in source_data: + lcount += 1 + + # strip trailing whitespace and newline + source_line = source_line.rstrip() - d = update.get(index, []) + # skip comments and empty lines + if source_line.startswith('#') or source_line == '': + continue - d.append(new_value) + if source_line.lower().startswith('dn:'): + # Starting new dn + if dn is not None: + # Emit previous dn + emit_item(logical_line) + logical_line = '' + emit_update(update) + update = {} + + dn = source_line[3:].strip() + dn = DN(self._template_str(dn)) + update['dn'] = dn + else: + # Process items belonging to dn + if dn is None: + raise BadSyntax, "dn is not defined in the update, data source=%s" % (data_source_name) - update[index] = d + # If continuation line, append to existing logical line & continue, + # otherwise flush the previous item. + if source_line.startswith(' '): + logical_line += source_line[1:] + continue + else: + emit_item(logical_line) + logical_line = '' + logical_line = source_line if dn is not None: - all_updates = self.__combine_updates(dn_list, all_updates, update) + emit_item(logical_line) + logical_line = '' + emit_update(update) + update = {} - return (all_updates, dn_list) + return all_updates def create_index_task(self, attribute): """Create a task to update an index for an attribute""" @@ -337,15 +425,15 @@ class LDAPUpdate: cn = "indextask_%s_%s_%s" % (attribute, cn_uuid.time, cn_uuid.clock_seq) dn = DN(('cn', cn), ('cn', 'index'), ('cn', 'tasks'), ('cn', 'config')) - e = ipaldap.Entry(str(dn)) + e = ipaldap.Entry(dn) e.setValues('objectClass', ['top', 'extensibleObject']) e.setValue('cn', cn) e.setValue('nsInstance', 'userRoot') e.setValues('nsIndexAttribute', attribute) - root_logger.info("Creating task to index attribute: %s", attribute) - root_logger.debug("Task id: %s", dn) + self.info("Creating task to index attribute: %s", attribute) + self.debug("Task id: %s", dn) if self.live_run: self.conn.addEntry(e) @@ -356,6 +444,8 @@ class LDAPUpdate: """Give a task DN monitor it and wait until it has completed (or failed) """ + assert isinstance(dn, DN) + if not self.live_run: # If not doing this live there is nothing to monitor return @@ -370,10 +460,10 @@ class LDAPUpdate: try: entry = self.conn.getEntry(dn, ldap.SCOPE_BASE, "(objectclass=*)", attrlist) except errors.NotFound, e: - root_logger.error("Task not found: %s", dn) + self.error("Task not found: %s", dn) return except errors.DatabaseError, e: - root_logger.error("Task lookup failure %s", e) + self.error("Task lookup failure %s", e) return status = entry.getValue('nstaskstatus') @@ -383,200 +473,158 @@ class LDAPUpdate: continue if status.lower().find("finished") > -1: - root_logger.info("Indexing finished") + self.info("Indexing finished") break - root_logger.debug("Indexing in progress") + self.debug("Indexing in progress") time.sleep(1) return - def __create_default_entry(self, dn, default): + def _create_default_entry(self, dn, default): """Create the default entry from the values provided. The return type is entity.Entity """ + assert isinstance(dn, DN) entry = ipaldap.Entry(dn) if not default: # This means that the entire entry needs to be created with add - return self.__entry_to_entity(entry) + return self._entry_to_entity(entry) - for line in default: + for item in default: # We already do syntax-parsing so this is safe - (k, v) = line.split(':',1) - e = entry.getValues(k) + (attr, value) = item.split(':',1) + e = entry.getValues(attr) if e: # multi-valued attribute e = list(e) - e.append(v) + e.append(value) else: - e = v - entry.setValues(k, e) + e = value + entry.setValues(attr, e) - return self.__entry_to_entity(entry) + return self._entry_to_entity(entry) - def __get_entry(self, dn): + def _get_entry(self, dn): """Retrieve an object from LDAP. The return type is ipaldap.Entry """ + assert isinstance(dn, DN) searchfilter="objectclass=*" sattrs = ["*", "aci", "attributeTypes", "objectClasses"] scope = ldap.SCOPE_BASE return self.conn.getList(dn, scope, searchfilter, sattrs) - def __update_managed_entries(self): - """Update and move legacy Managed Entry Plugins.""" - - suffix = ipautil.realm_to_suffix(self.realm) - searchfilter = '(objectclass=*)' - definitions_managed_entries = [] - old_template_container = 'cn=etc,%s' % suffix - old_definition_container = 'cn=Managed Entries,cn=plugins,cn=config' - new = 'cn=Managed Entries,cn=etc,%s' % suffix - sub = ['cn=Definitions,', 'cn=Templates,'] - new_managed_entries = [] - old_templates = [] - template = None - try: - definitions_managed_entries = self.conn.getList(old_definition_container, ldap.SCOPE_ONELEVEL, searchfilter,[]) - except errors.NotFound, e: - return new_managed_entries - for entry in definitions_managed_entries: - new_definition = {} - definition_managed_entry_updates = {} - definitions_managed_entries - old_definition = {'dn': entry.dn, 'deleteentry': ['dn: %s' % entry.dn]} - old_template = entry.getValue('managedtemplate') - entry.setValues('managedtemplate', entry.getValue('managedtemplate').replace(old_template_container, sub[1] + new)) - new_definition['dn'] = entry.dn.replace(old_definition_container, sub[0] + new) - new_definition['default'] = str(entry).strip().replace(': ', ':').split('\n')[1:] - definition_managed_entry_updates[new_definition['dn']] = new_definition - definition_managed_entry_updates[old_definition['dn']] = old_definition - old_templates.append(old_template) - new_managed_entries.append(definition_managed_entry_updates) - for old_template in old_templates: - try: - template = self.conn.getEntry(old_template, ldap.SCOPE_BASE, searchfilter,[]) - new_template = {} - template_managed_entry_updates = {} - old_template = {'dn': template.dn, 'deleteentry': ['dn: %s' % template.dn]} - new_template['dn'] = template.dn.replace(old_template_container, sub[1] + new) - new_template['default'] = str(template).strip().replace(': ', ':').split('\n')[1:] - template_managed_entry_updates[new_template['dn']] = new_template - template_managed_entry_updates[old_template['dn']] = old_template - new_managed_entries.append(template_managed_entry_updates) - except errors.NotFound, e: - pass - if len(new_managed_entries) > 0: - new_managed_entries.sort(reverse=True) - - return new_managed_entries - - def __apply_updates(self, updates, entry): - """updates is a list of changes to apply - entry is the thing to apply them to + def _apply_update_disposition(self, updates, entry): + """ + updates is a list of changes to apply + entry is the thing to apply them to - Returns the modified entry + Returns the modified entry """ if not updates: return entry only = {} - for u in updates: + for update in updates: # We already do syntax-parsing so this is safe - (utype, k, values) = u.split(':',2) - values = self.__parse_values(values) - - e = entry.getValues(k) - if not isinstance(e, list): - if e is None: - e = [] + (action, attr, update_values) = update.split(':',2) + update_values = self._parse_values(update_values) + + # If the attribute is known to be a DN convert it to a DN object. + # This has to be done after _parse_values() due to quoting and comma separated lists. + if self.conn.has_dn_syntax(attr): + update_values = [DN(x) for x in update_values] + + entry_values = entry.getValues(attr) + if not isinstance(entry_values, list): + if entry_values is None: + entry_values = [] else: - e = [e] - for v in values: - if utype == 'remove': - root_logger.debug("remove: '%s' from %s, current value %s", v, k, e) + entry_values = [entry_values] + for update_value in update_values: + if action == 'remove': + self.debug("remove: '%s' from %s, current value %s", update_value, attr, entry_values) try: - e.remove(v) + entry_values.remove(update_value) except ValueError: - root_logger.warning("remove: '%s' not in %s", v, k) + self.warning("remove: '%s' not in %s", update_value, attr) pass - entry.setValues(k, e) - root_logger.debug('remove: updated value %s', e) - elif utype == 'add': - root_logger.debug("add: '%s' to %s, current value %s", v, k, e) + entry.setValues(attr, entry_values) + self.debug('remove: updated value %s', entry_values) + elif action == 'add': + self.debug("add: '%s' to %s, current value %s", update_value, attr, entry_values) # Remove it, ignoring errors so we can blindly add it later try: - e.remove(v) + entry_values.remove(update_value) except ValueError: pass - e.append(v) - root_logger.debug('add: updated value %s', e) - entry.setValues(k, e) - elif utype == 'addifnew': - root_logger.debug("addifnew: '%s' to %s, current value %s", v, k, e) + entry_values.append(update_value) + self.debug('add: updated value %s', entry_values) + entry.setValues(attr, entry_values) + elif action == 'addifnew': + self.debug("addifnew: '%s' to %s, current value %s", update_value, attr, entry_values) # Only add the attribute if it doesn't exist. Only works # with single-value attributes. - if len(e) == 0: - e.append(v) - root_logger.debug('addifnew: set %s to %s', k, e) - entry.setValues(k, e) - elif utype == 'addifexist': - root_logger.debug("addifexist: '%s' to %s, current value %s", v, k, e) + if len(entry_values) == 0: + entry_values.append(update_value) + self.debug('addifnew: set %s to %s', attr, entry_values) + entry.setValues(attr, entry_values) + elif action == 'addifexist': + self.debug("addifexist: '%s' to %s, current value %s", update_value, attr, entry_values) # Only add the attribute if the entry doesn't exist. We # determine this based on whether it has an objectclass if entry.getValues('objectclass'): - e.append(v) - root_logger.debug('addifexist: set %s to %s', k, e) - entry.setValues(k, e) - elif utype == 'only': - root_logger.debug("only: set %s to '%s', current value %s", k, v, e) - if only.get(k): - e.append(v) + entry_values.append(update_value) + self.debug('addifexist: set %s to %s', attr, entry_values) + entry.setValues(attr, entry_values) + elif action == 'only': + self.debug("only: set %s to '%s', current value %s", attr, update_value, entry_values) + if only.get(attr): + entry_values.append(update_value) else: - e = [v] - only[k] = True - entry.setValues(k, e) - root_logger.debug('only: updated value %s', e) - elif utype == 'deleteentry': + entry_values = [update_value] + only[attr] = True + entry.setValues(attr, entry_values) + self.debug('only: updated value %s', entry_values) + elif action == 'deleteentry': # skip this update type, it occurs in __delete_entries() return None - elif utype == 'replace': - # v has the format "old::new" + elif action == 'replace': + # value has the format "old::new" try: - (old, new) = v.split('::', 1) + (old, new) = update_value.split('::', 1) except ValueError: - raise BadSyntax, "bad syntax in replace, needs to be in the format old::new in %s" % v + raise BadSyntax, "bad syntax in replace, needs to be in the format old::new in %s" % update_value try: - e.remove(old) - e.append(new) - root_logger.debug('replace: updated value %s', e) - entry.setValues(k, e) + entry_values.remove(old) + entry_values.append(new) + self.debug('replace: updated value %s', entry_values) + entry.setValues(attr, entry_values) except ValueError: - root_logger.debug('replace: %s not found, skipping', old) - - self.print_entity(entry) + self.debug('replace: %s not found, skipping', old) return entry def print_entity(self, e, message=None): """The entity object currently lacks a str() method""" - root_logger.debug("---------------------------------------------") + self.debug("---------------------------------------------") if message: - root_logger.debug("%s", message) - root_logger.debug("dn: " + e.dn) + self.debug("%s", message) + self.debug("dn: %s", e.dn) attr = e.attrList() for a in attr: value = e.getValues(a) - if isinstance(value,str): - root_logger.debug(a + ": " + value) - else: - root_logger.debug(a + ": ") + if isinstance(value, (list, tuple)): + self.debug('%s:', a) for l in value: - root_logger.debug("\t" + l) + self.debug("\t%s", l) + else: + self.debug('%s: %s', a, value) def is_schema_updated(self, s): """Compare the schema in 's' with the current schema in the DS to @@ -598,48 +646,59 @@ class LDAPUpdate: s = s.ldap_entry() # Get a fresh copy and convert into a SubSchema - n = self.__get_entry("cn=schema")[0] - n = dict(n.data) - n = ldap.schema.SubSchema(n) + n = self._get_entry(DN(('cn', 'schema')))[0] + + # Convert IPA data types back to strings + d = dict() + for k,v in n.data.items(): + d[k] = [str(x) for x in v] + + # Convert to subschema dict + n = ldap.schema.SubSchema(d) n = n.ldap_entry() + # Are they equal? if s == n: return False else: return True - def __update_record(self, update): + def _update_record(self, update): found = False - new_entry = self.__create_default_entry(update.get('dn'), - update.get('default')) + # If the entry is going to be deleted no point in processing it. + if update.has_key('deleteentry'): + return + + new_entry = self._create_default_entry(update.get('dn'), + update.get('default')) try: - e = self.__get_entry(new_entry.dn) + e = self._get_entry(new_entry.dn) if len(e) > 1: # we should only ever get back one entry raise BadSyntax, "More than 1 entry returned on a dn search!? %s" % new_entry.dn - entry = self.__entry_to_entity(e[0]) + entry = self._entry_to_entity(e[0]) found = True - root_logger.info("Updating existing entry: %s", entry.dn) + self.info("Updating existing entry: %s", entry.dn) except errors.NotFound: # Doesn't exist, start with the default entry entry = new_entry - root_logger.info("New entry: %s", entry.dn) + self.info("New entry: %s", entry.dn) except errors.DatabaseError: # Doesn't exist, start with the default entry entry = new_entry - root_logger.info("New entry, using default value: %s", entry.dn) + self.info("New entry, using default value: %s", entry.dn) - self.print_entity(entry) + self.print_entity(entry, "Initial value") # Bring this entry up to date - entry = self.__apply_updates(update.get('updates'), entry) + entry = self._apply_update_disposition(update.get('updates'), entry) if entry is None: # It might be None if it is just deleting an entry return - self.print_entity(entry, "Final value") + self.print_entity(entry, "Final value after applying updates") if not found: # New entries get their orig_data set to the entry itself. We want to @@ -657,47 +716,47 @@ class LDAPUpdate: except errors.NotFound: # parent entry of the added entry does not exist # this may not be an error (e.g. entries in NIS container) - root_logger.info("Parent DN of %s may not exist, cannot create the entry", + self.info("Parent DN of %s may not exist, cannot create the entry", entry.dn) return self.modified = True except Exception, e: - root_logger.error("Add failure %s", e) + self.error("Add failure %s", e) else: # Update LDAP try: updated = False changes = self.conn.generateModList(entry.origDataDict(), entry.toDict()) - if (entry.dn == "cn=schema"): + if (entry.dn == DN(('cn', 'schema'))): updated = self.is_schema_updated(entry.toDict()) else: if len(changes) >= 1: updated = True - root_logger.debug("%s" % changes) - root_logger.debug("Live %d, updated %d" % (self.live_run, updated)) + self.debug("%s" % changes) + self.debug("Live %d, updated %d" % (self.live_run, updated)) if self.live_run and updated: self.conn.updateEntry(entry.dn, entry.origDataDict(), entry.toDict()) - root_logger.info("Done") + self.info("Done") except errors.EmptyModlist: - root_logger.info("Entry already up-to-date") + self.info("Entry already up-to-date") updated = False except errors.DatabaseError, e: - root_logger.error("Update failed: %s", e) + self.error("Update failed: %s", e) updated = False except errors.ACIError, e: - root_logger.error("Update failed: %s", e) + self.error("Update failed: %s", e) updated = False - if ("cn=index" in entry.dn and - "cn=userRoot" in entry.dn): - taskid = self.create_index_task(entry.cn) + if (DN(('cn', 'index')) in entry.dn and + DN(('cn', 'userRoot')) in entry.dn): + taskid = self.create_index_task(entry.getValue('cn')) self.monitor_index_task(taskid) if updated: self.modified = True return - def __delete_record(self, updates): + def _delete_record(self, updates): """ Run through all the updates again looking for any that should be deleted. @@ -706,38 +765,21 @@ class LDAPUpdate: considered first so we don't end up trying to delete a parent and child in the wrong order. """ - dn = updates['dn'] - deletes = updates.get('deleteentry', []) - for d in deletes: - try: - root_logger.info("Deleting entry %s", dn) - if self.live_run: - self.conn.deleteEntry(dn) - self.modified = True - except errors.NotFound, e: - root_logger.info("%s did not exist:%s", dn, e) - self.modified = True - except errors.DatabaseError, e: - root_logger.error("Delete failed: %s", e) - - updates = updates.get('updates', []) - for u in updates: - # We already do syntax-parsing so this is safe - (utype, k, values) = u.split(':',2) - if utype == 'deleteentry': - try: - root_logger.info("Deleting entry %s", dn) - if self.live_run: - self.conn.deleteEntry(dn) - self.modified = True - except errors.NotFound, e: - root_logger.info("%s did not exist:%s", dn, e) - self.modified = True - except errors.DatabaseError, e: - root_logger.error("Delete failed: %s", e) + if not updates.has_key('deleteentry'): + return - return + dn = updates['dn'] + try: + self.info("Deleting entry %s", dn) + if self.live_run: + self.conn.deleteEntry(dn) + self.modified = True + except errors.NotFound, e: + self.info("%s did not exist:%s", dn, e) + self.modified = True + except errors.DatabaseError, e: + self.error("Delete failed: %s", e) def get_all_files(self, root, recursive=False): """Get all update files""" @@ -761,7 +803,7 @@ class LDAPUpdate: realm=self.realm) try: if self.dm_password: - self.conn.do_simple_bind(binddn="cn=directory manager", bindpw=self.dm_password) + self.conn.do_simple_bind(binddn=DN(('cn', 'directory manager')), bindpw=self.dm_password) elif os.getegid() == 0: try: # autobind @@ -776,19 +818,28 @@ class LDAPUpdate: else: raise RuntimeError("Offline updates are not supported.") - def __run_updates(self, dn_list, all_updates): + def _run_updates(self, all_updates): # For adds and updates we want to apply updates from shortest # to greatest length of the DN. For deletes we want the reverse. - sortedkeys = dn_list.keys() + + dn_by_rdn_count = {} + for dn in all_updates.keys(): + assert isinstance(dn, DN) + rdn_count = len(dn) + rdn_count_list = dn_by_rdn_count.setdefault(rdn_count, []) + if dn not in rdn_count_list: + rdn_count_list.append(dn) + + sortedkeys = dn_by_rdn_count.keys() sortedkeys.sort() - for k in sortedkeys: - for dn in dn_list[k]: - self.__update_record(all_updates[dn]) + for rdn_count in sortedkeys: + for dn in dn_by_rdn_count[rdn_count]: + self._update_record(all_updates[dn]) sortedkeys.reverse() - for k in sortedkeys: - for dn in dn_list[k]: - self.__delete_record(all_updates[dn]) + for rdn_count in sortedkeys: + for dn in dn_by_rdn_count[rdn_count]: + self._delete_record(all_updates[dn]) def update(self, files): """Execute the update. files is a list of the update files to use. @@ -796,71 +847,46 @@ class LDAPUpdate: returns True if anything was changed, otherwise False """ - updates = None + all_updates = {} if self.plugins: - root_logger.info('PRE_UPDATE') + self.info('PRE_UPDATE') updates = api.Backend.updateclient.update(PRE_UPDATE, self.dm_password, self.ldapi, self.live_run) - + self.merge_updates(all_updates, updates) try: self.create_connection() - all_updates = {} - dn_list = {} - # Start with any updates passed in from pre-update plugins - if updates: - for entry in updates: - all_updates.update(entry) - for upd in updates: - for dn in upd: - dn_explode = ldap.explode_dn(dn.lower()) - l = len(dn_explode) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] for f in files: try: - root_logger.info("Parsing file %s" % f) + self.info("Parsing update file '%s'" % f) data = self.read_file(f) except Exception, e: - print e + self.error("error reading update file '%s'", f) sys.exit(e) - (all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list) + self.parse_update_file(f, data, all_updates) - self.__run_updates(dn_list, all_updates) + self._run_updates(all_updates) finally: if self.conn: self.conn.unbind() if self.plugins: - root_logger.info('POST_UPDATE') + self.info('POST_UPDATE') + all_updates = {} updates = api.Backend.updateclient.update(POST_UPDATE, self.dm_password, self.ldapi, self.live_run) - dn_list = {} - for upd in updates: - for dn in upd: - dn_explode = ldap.explode_dn(dn.lower()) - l = len(dn_explode) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] - self.__run_updates(dn_list, updates) + self.merge_updates(all_updates, updates) + self._run_updates(all_updates) return self.modified - def update_from_dict(self, dn_list, updates): + def update_from_dict(self, updates): """ Apply updates internally as opposed to from a file. - - dn_list is a list of dns to be updated updates is a dictionary containing the updates """ if not self.conn: self.create_connection() - self.__run_updates(dn_list, updates) + self._run_updates(updates) return self.modified diff --git a/ipaserver/install/plugins/adtrust.py b/ipaserver/install/plugins/adtrust.py index c32d82e3a..555a28b8f 100644 --- a/ipaserver/install/plugins/adtrust.py +++ b/ipaserver/install/plugins/adtrust.py @@ -20,7 +20,7 @@ from ipaserver.install.plugins import MIDDLE from ipaserver.install.plugins.baseupdate import PostUpdate from ipalib import api, errors -from ipalib.dn import DN +from ipapython.dn import DN from ipapython.ipa_log_manager import * DEFAULT_ID_RANGE_SIZE = 200000 @@ -34,7 +34,7 @@ class update_default_range(PostUpdate): def execute(self, **options): ldap = self.obj.backend - dn = str(DN(api.env.container_ranges, api.env.basedn)) + dn = DN(api.env.container_ranges, api.env.basedn) search_filter = "objectclass=ipaDomainIDRange" try: (entries, truncated) = ldap.find_entries(search_filter, [], dn) @@ -44,7 +44,7 @@ class update_default_range(PostUpdate): root_logger.debug("default_range: ipaDomainIDRange entry found, skip plugin") return (False, False, []) - dn = str(DN(('cn', 'admins'), api.env.container_group, api.env.basedn)) + dn = DN(('cn', 'admins'), api.env.container_group, api.env.basedn) try: (dn, admins_entry) = ldap.get_entry(dn, ['gidnumber']) except errors.NotFound: @@ -65,12 +65,10 @@ class update_default_range(PostUpdate): ] updates = {} - dn = str(DN(('cn', '%s_id_range' % api.env.realm), - api.env.container_ranges, api.env.basedn)) + dn = DN(('cn', '%s_id_range' % api.env.realm), + api.env.container_ranges, api.env.basedn) - # make sure everything is str or otherwise python-ldap would complain - range_entry = map(str, range_entry) - updates[dn] = {'dn' : dn, 'default' : range_entry} + updates[dn] = {'dn': dn, 'default': range_entry} # Default range entry has a hard-coded range size to 200000 which is # a default range size in ipa-server-install. This could cause issues @@ -78,7 +76,7 @@ class update_default_range(PostUpdate): # bigger range (option --idmax). # We should make our best to check if this is the case and provide # user with an information how to fix it. - dn = str(DN(api.env.container_dna_posix_ids, api.env.basedn)) + dn = DN(api.env.container_dna_posix_ids, api.env.basedn) search_filter = "objectclass=dnaSharedConfig" attrs = ['dnaHostname', 'dnaRemainingValues'] try: diff --git a/ipaserver/install/plugins/dns.py b/ipaserver/install/plugins/dns.py index e11c331a4..d55596704 100644 --- a/ipaserver/install/plugins/dns.py +++ b/ipaserver/install/plugins/dns.py @@ -21,7 +21,7 @@ from ipaserver.install.plugins import MIDDLE from ipaserver.install.plugins.baseupdate import PostUpdate from ipaserver.install.plugins import baseupdate from ipalib import api, errors, util -from ipalib.dn import DN +from ipapython.dn import DN from ipalib.plugins.dns import dns_container_exists from ipapython.ipa_log_manager import * @@ -89,31 +89,29 @@ class update_dns_permissions(PostUpdate): entries otherwise. """ - _write_dns_perm_dn = DN('cn=Write DNS Configuration', - api.env.container_permission, - api.env.basedn) + _write_dns_perm_dn = DN(('cn', 'Write DNS Configuration'), + api.env.container_permission, api.env.basedn) _write_dns_perm_entry = ['objectClass:groupofnames', 'objectClass:top', 'cn:Write DNS Configuration', 'description:Write DNS Configuration', - 'member:cn=DNS Administrators,cn=privileges,cn=pbac,%s' \ - % api.env.basedn, - 'member:cn=DNS Servers,cn=privileges,cn=pbac,%s' \ - % api.env.basedn] - - _read_dns_perm_dn = DN('cn=Read DNS Entries', - api.env.container_permission, - api.env.basedn) + 'member:%s' % DN(('cn', 'DNS Administrators'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn), + 'member:%s' % DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn)] + + _read_dns_perm_dn = DN(('cn', 'Read DNS Entries'), + api.env.container_permission, api.env.basedn) _read_dns_perm_entry = ['objectClass:top', 'objectClass:groupofnames', 'objectClass:ipapermission', 'cn:Read DNS Entries', 'description:Read DNS entries', 'ipapermissiontype:SYSTEM', - 'member:cn=DNS Administrators,cn=privileges,cn=pbac,%s' \ - % api.env.basedn, - 'member:cn=DNS Servers,cn=privileges,cn=pbac,%s' \ - % api.env.basedn,] + 'member:%s' % DN(('cn', 'DNS Administrators'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn), + 'member:%s' % DN(('cn', 'DNS Servers'), ('cn', 'privileges'), ('cn', 'pbac'), + api.env.basedn),] _write_dns_aci_dn = DN(api.env.basedn) _write_dns_aci_entry = ['add:aci:\'(targetattr = "idnsforwardpolicy || idnsforwarders || idnsallowsyncptr || idnszonerefresh || idnspersistentsearch")(target = "ldap:///cn=dns,%(realm)s")(version 3.0;acl "permission:Write DNS Configuration";allow (write) groupdn = "ldap:///cn=Write DNS Configuration,cn=permissions,cn=pbac,%(realm)s";)\'' % dict(realm=api.env.basedn)] @@ -135,10 +133,7 @@ class update_dns_permissions(PostUpdate): (self._write_dns_aci_dn, 'updates', self._write_dns_aci_entry), (self._read_dns_aci_dn, 'updates', self._read_dns_aci_entry)): - dn = str(dn) - # make sure everything is str or otherwise python-ldap would complain - entry = map(str, entry) - dnsupdates[dn] = {'dn' : dn, container : entry} + dnsupdates[dn] = {'dn': dn, container: entry} return (False, True, [dnsupdates]) @@ -161,9 +156,9 @@ class update_dns_limits(PostUpdate): return (False, False, []) dns_principal = 'DNS/%s@%s' % (self.env.host, self.env.realm) - dns_service_dn = str(DN(('krbprincipalname', dns_principal), - self.env.container_service, - self.env.basedn)) + dns_service_dn = DN(('krbprincipalname', dns_principal), + self.env.container_service, + self.env.basedn) try: (dn, entry) = ldap.get_entry(dns_service_dn, self.limit_attributes) diff --git a/ipaserver/install/plugins/fix_replica_memberof.py b/ipaserver/install/plugins/fix_replica_memberof.py index 23bde0c9f..d4ab75348 100644 --- a/ipaserver/install/plugins/fix_replica_memberof.py +++ b/ipaserver/install/plugins/fix_replica_memberof.py @@ -44,7 +44,7 @@ class update_replica_exclude_attribute_list(PreUpdate): entries = repl.find_replication_agreements() self.log.debug("Found %d agreement(s)", len(entries)) for replica in entries: - self.log.debug(replica.description) + self.log.debug(replica.getValue('description')) attrlist = replica.getValue('nsDS5ReplicatedAttributeList') if attrlist is None: self.log.debug("Adding nsDS5ReplicatedAttributeList and nsDS5ReplicatedAttributeListTotal") @@ -68,7 +68,7 @@ class update_replica_exclude_attribute_list(PreUpdate): self.log.debug("Attribute list needs updating") current = replica.toDict() replica.setValue('nsDS5ReplicatedAttributeList', - replica.nsDS5ReplicatedAttributeList + ' %s' % ' '.join(missing)) + replica.getValue('nsDS5ReplicatedAttributeList') + ' %s' % ' '.join(missing)) try: repl.conn.updateEntry(replica.dn, current, replica.toDict()) self.log.debug("Updated") diff --git a/ipaserver/install/plugins/rename_managed.py b/ipaserver/install/plugins/rename_managed.py index a9eed0be3..99dac8148 100644 --- a/ipaserver/install/plugins/rename_managed.py +++ b/ipaserver/install/plugins/rename_managed.py @@ -24,6 +24,7 @@ from ipalib.frontend import Updater from ipaserver.install.plugins import baseupdate from ipalib import api, errors from ipapython import ipautil +from ipapython.dn import DN, EditableDN import ldap as _ldap def entry_to_update(entry): @@ -44,67 +45,99 @@ def entry_to_update(entry): return update -def generate_update(ldap, deletes=False): - """ - We need to separate the deletes that need to happen from the - new entries that need to be added. - """ - suffix = ipautil.realm_to_suffix(api.env.realm) - searchfilter = '(objectclass=*)' - definitions_managed_entries = [] - old_template_container = 'cn=etc,%s' % suffix - old_definition_container = 'cn=managed entries,cn=plugins,cn=config' - new = 'cn=Managed Entries,cn=etc,%s' % suffix - sub = ['cn=Definitions,', 'cn=Templates,'] - new_managed_entries = [] - old_templates = [] - template = None - restart = False - - # If the old entries don't exist the server has already been updated. - try: - (definitions_managed_entries, truncated) = ldap.find_entries( - searchfilter, ['*'], old_definition_container, _ldap.SCOPE_ONELEVEL, normalize=False - ) - except errors.NotFound, e: - return (False, new_managed_entries) - - for entry in definitions_managed_entries: - new_definition = {} - definition_managed_entry_updates = {} - if deletes: - old_definition = {'dn': str(entry[0]), 'deleteentry': ['dn: %s' % str(entry[0])]} - old_template = str(entry[1]['managedtemplate'][0]) - definition_managed_entry_updates[old_definition['dn']] = old_definition - old_templates.append(old_template) - else: - entry[1]['managedtemplate'] = str(entry[1]['managedtemplate'][0].replace(old_template_container, sub[1] + new)) - new_definition['dn'] = str(entry[0].replace(old_definition_container, sub[0] + new)) - new_definition['default'] = entry_to_update(entry[1]) - definition_managed_entry_updates[new_definition['dn']] = new_definition - new_managed_entries.append(definition_managed_entry_updates) - for old_template in old_templates: # Only happens when deletes is True - try: - (dn, template) = ldap.get_entry(old_template, ['*'], normalize=False) - dn = str(dn) - new_template = {} - template_managed_entry_updates = {} - old_template = {'dn': dn, 'deleteentry': ['dn: %s' % dn]} - new_template['dn'] = str(dn.replace(old_template_container, sub[1] + new)) - new_template['default'] = entry_to_update(template) - template_managed_entry_updates[new_template['dn']] = new_template - template_managed_entry_updates[old_template['dn']] = old_template - new_managed_entries.append(template_managed_entry_updates) - except errors.NotFound, e: - pass +class GenerateUpdateMixin(object): + def generate_update(self, deletes=False): + """ + We need to separate the deletes that need to happen from the + new entries that need to be added. + """ + ldap = self.obj.backend - if len(new_managed_entries) > 0: - restart = True - new_managed_entries.sort(reverse=True) + suffix = ipautil.realm_to_suffix(api.env.realm) + searchfilter = '(objectclass=*)' + definitions_managed_entries = [] - return (restart, new_managed_entries) + old_template_container = DN(('cn', 'etc'), suffix) + new_template_container = DN(('cn', 'Templates'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix) -class update_managed_post_first(PreUpdate): + old_definition_container = DN(('cn', 'managed entries'), ('cn', 'plugins'), ('cn', 'config'), suffix) + new_definition_container = DN(('cn', 'Definitions'), ('cn', 'Managed Entries'), ('cn', 'etc'), suffix) + + definitions_dn = DN(('cn', 'Definitions')) + update_list = [] + restart = False + + # If the old entries don't exist the server has already been updated. + try: + (definitions_managed_entries, truncated) = ldap.find_entries( + searchfilter, ['*'], old_definition_container, _ldap.SCOPE_ONELEVEL, normalize=False + ) + except errors.NotFound, e: + return (False, update_list) + + for entry in definitions_managed_entries: + assert isinstance(entry.dn, DN) + if deletes: + old_dn = entry.data['managedtemplate'][0] + assert isinstance(old_dn, DN) + try: + (old_dn, entry) = ldap.get_entry(old_dn, ['*'], normalize=False) + except errors.NotFound, e: + pass + else: + # Compute the new dn by replacing the old container with the new container + new_dn = EditableDN(old_dn) + if new_dn.replace(old_template_container, new_template_container) != 1: + self.error("unable to replace '%s' with '%s' in '%s'", + old_template_container, new_template_container, old_dn) + continue + + new_dn = DN(new_dn) + + # The old attributes become defaults for the new entry + new_update = {'dn': new_dn, + 'default': entry_to_update(entry)} + + # Delete the old entry + old_update = {'dn': old_dn, 'deleteentry': None} + + # Add the delete and replacement updates to the list of all updates + update_list.append({old_dn: old_update, new_dn: new_update}) + + else: + # Update the template dn by replacing the old containter with the new container + old_dn = entry.data['managedtemplate'][0] + new_dn = EditableDN(old_dn) + if new_dn.replace(old_template_container, new_template_container) != 1: + self.error("unable to replace '%s' with '%s' in '%s'", + old_template_container, new_template_container, old_dn) + continue + new_dn = DN(new_dn) + entry.data['managedtemplate'] = new_dn + + # Edit the dn, then convert it back to an immutable DN + old_dn = entry.dn + new_dn = EditableDN(old_dn) + if new_dn.replace(old_definition_container, new_definition_container) != 1: + self.error("unable to replace '%s' with '%s' in '%s'", + old_definition_container, new_definition_container, old_dn) + continue + new_dn = DN(new_dn) + + # The old attributes become defaults for the new entry + new_update = {'dn': new_dn, + 'default': entry_to_update(entry.data)} + + # Add the replacement update to the collection of all updates + update_list.append({new_dn: new_update}) + + if len(update_list) > 0: + restart = True + update_list.sort(reverse=True) + + return (restart, update_list) + +class update_managed_post_first(PreUpdate, GenerateUpdateMixin): """ Update managed entries """ @@ -112,21 +145,21 @@ class update_managed_post_first(PreUpdate): def execute(self, **options): # Never need to restart with the pre-update changes - (ignore, new_managed_entries) = generate_update(self.obj.backend, False) + (ignore, update_list) = self.generate_update(False) - return (False, True, new_managed_entries) + return (False, True, update_list) api.register(update_managed_post_first) -class update_managed_post(PostUpdate): +class update_managed_post(PostUpdate, GenerateUpdateMixin): """ Update managed entries """ order=LAST def execute(self, **options): - (restart, new_managed_entries) = generate_update(self.obj.backend, True) + (restart, update_list) = self.generate_update(True) - return (restart, True, new_managed_entries) + return (restart, True, update_list) api.register(update_managed_post) diff --git a/ipaserver/install/plugins/updateclient.py b/ipaserver/install/plugins/updateclient.py index e23769471..dca2c75dd 100644 --- a/ipaserver/install/plugins/updateclient.py +++ b/ipaserver/install/plugins/updateclient.py @@ -25,6 +25,7 @@ from ipaserver.install.ldapupdate import LDAPUpdate from ipapython.ipautil import wait_for_open_socket from ipalib import api from ipalib import backend +from ipapython.dn import DN import ldap as _ldap class updateclient(backend.Executioner): @@ -44,7 +45,7 @@ class updateclient(backend.Executioner): updates is a dictionary keyed on dn. The value of an update is a dictionary with the following possible values: - - dn: str, duplicate of the key + - dn: DN, equal to the dn attribute - updates: list of updates against the dn - default: list of the default entry to be added if it doesn't exist @@ -103,7 +104,7 @@ class updateclient(backend.Executioner): autobind = False else: autobind = True - self.Backend.ldap2.connect(bind_dn='cn=Directory Manager', bind_pw=dm_password, autobind=autobind) + self.Backend.ldap2.connect(bind_dn=DN(('cn', 'Directory Manager')), bind_pw=dm_password, autobind=autobind) def order(self, updatetype): """Return plugins of the given updatetype in sorted order. @@ -125,22 +126,12 @@ class updateclient(backend.Executioner): (restart, apply_now, res) = self.run(update.name, **kw) if restart: self.restart(dm_password, live_run) - dn_list = {} - for upd in res: - for dn in upd: - dn_explode = _ldap.explode_dn(dn.lower()) - l = len(dn_explode) - if dn_list.get(l): - if dn not in dn_list[l]: - dn_list[l].append(dn) - else: - dn_list[l] = [dn] - updates = {} - for entry in res: - updates.update(entry) if apply_now: - ld.update_from_dict(dn_list, updates) + updates = {} + for entry in res: + updates.update(entry) + ld.update_from_dict(updates) elif res: result.extend(res) diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py index 38abfe210..8fe73ca77 100644 --- a/ipaserver/install/replication.py +++ b/ipaserver/install/replication.py @@ -28,17 +28,16 @@ from ipapython import services as ipaservices from ldap import modlist from ipalib import api, util, errors from ipapython import ipautil -from ipalib.dn import DN +from ipapython.dn import DN -DIRMAN_CN = "cn=directory manager" CACERT = "/etc/ipa/ca.crt" # the default container used by AD for user entries -WIN_USER_CONTAINER = "cn=Users" +WIN_USER_CONTAINER = DN(('cn', 'Users')) # the default container used by IPA for user entries -IPA_USER_CONTAINER = "cn=users,cn=accounts" +IPA_USER_CONTAINER = DN(('cn', 'users'), ('cn', 'accounts')) PORT = 636 TIMEOUT = 120 -REPL_MAN_DN = "cn=replication manager,cn=config" +REPL_MAN_DN = DN(('cn', 'replication manager'), ('cn', 'config')) IPA_REPLICA = 1 WINSYNC = 2 @@ -94,9 +93,10 @@ def enable_replication_version_checking(hostname, realm, dirman_passwd): conn.do_simple_bind(bindpw=dirman_passwd) else: conn.do_sasl_gssapi_bind() - entry = conn.search_s('cn=IPA Version Replication,cn=plugins,cn=config', ldap.SCOPE_BASE, 'objectclass=*') - if entry[0].getValue('nsslapd-pluginenabled') == 'off': - conn.modify_s(entry[0].dn, [(ldap.MOD_REPLACE, 'nsslapd-pluginenabled', 'on')]) + entry = conn.getEntry(DN(('cn', 'IPA Version Replication'), ('cn', 'plugins'), ('cn', 'config')), + ldap.SCOPE_BASE, 'objectclass=*') + if entry.getValue('nsslapd-pluginenabled') == 'off': + conn.modify_s(entry.dn, [(ldap.MOD_REPLACE, 'nsslapd-pluginenabled', 'on')]) conn.unbind() serverid = "-".join(realm.split(".")) ipaservices.knownservices.dirsrv.restart(instance_name=serverid) @@ -112,8 +112,7 @@ class ReplicationManager(object): self.dirman_passwd = dirman_passwd self.realm = realm self.starttls = starttls - tmp = ipautil.realm_to_suffix(realm) - self.suffix = str(DN(tmp)).lower() + self.suffix = ipautil.realm_to_suffix(realm) self.need_memberof_fixup = False # The caller is allowed to pass in an existing IPAdmin connection. @@ -150,25 +149,28 @@ class ReplicationManager(object): """ # First see if there is already one set dn = self.replica_dn() + assert isinstance(dn, DN) try: - replica = conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] + replica = conn.getEntry(dn, ldap.SCOPE_BASE, "objectclass=*") + except errors.NotFound: + pass + else: if replica.getValue('nsDS5ReplicaId'): return int(replica.getValue('nsDS5ReplicaId')) - except ldap.NO_SUCH_OBJECT: - pass # Ok, either the entry doesn't exist or the attribute isn't set # so get it from the other master retval = -1 - dn = str(DN(('cn','replication'),('cn','etc'), self.suffix)) + dn = DN(('cn','replication'),('cn','etc'), self.suffix) try: - replica = master_conn.search_s(dn, ldap.SCOPE_BASE, "objectclass=*")[0] - if not replica.getValue('nsDS5ReplicaId'): - root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") - raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") - except ldap.NO_SUCH_OBJECT: + replica = master_conn.getEntry(dn, ldap.SCOPE_BASE, "objectclass=*") + except errors.NotFound: root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") raise + else: + if replica.getValue('nsDS5ReplicaId') is None: + root_logger.debug("Unable to retrieve nsDS5ReplicaId from remote server") + raise RuntimeError("Unable to retrieve nsDS5ReplicaId from remote server") # Now update the value on the master retval = int(replica.getValue('nsDS5ReplicaId')) @@ -195,8 +197,9 @@ class ReplicationManager(object): """ filt = "(|(objectclass=nsDSWindowsReplicationAgreement)(objectclass=nsds5ReplicationAgreement))" try: - ents = self.conn.search_s("cn=mapping tree,cn=config", ldap.SCOPE_SUBTREE, filt) - except ldap.NO_SUCH_OBJECT: + ents = self.conn.getList(DN(('cn', 'mapping tree'), ('cn', 'config')), + ldap.SCOPE_SUBTREE, filt) + except errors.NotFound: ents = [] return ents @@ -212,13 +215,13 @@ class ReplicationManager(object): filt = "(objectclass=nsds5ReplicationAgreement)" try: - ents = self.conn.search_s("cn=mapping tree,cn=config", - ldap.SCOPE_SUBTREE, filt) - except ldap.NO_SUCH_OBJECT: + ents = self.conn.getList(DN(('cn', 'mapping tree'), ('cn', 'config')), + ldap.SCOPE_SUBTREE, filt) + except errors.NotFound: return res for ent in ents: - res.append(ent.nsds5replicahost) + res.append(ent.getValue('nsds5replicahost')) return res @@ -234,24 +237,23 @@ class ReplicationManager(object): filt = "(&(|(objectclass=nsds5ReplicationAgreement)(objectclass=nsDSWindowsReplicationAgreement))(nsDS5ReplicaHost=%s))" % hostname try: - entry = self.conn.search_s("cn=mapping tree,cn=config", - ldap.SCOPE_SUBTREE, filt) - except ldap.NO_SUCH_OBJECT: + entries = self.conn.getList(DN(('cn', 'mapping tree'), ('cn', 'config')), + ldap.SCOPE_SUBTREE, filt) + except errors.NotFound: return None - if len(entry) == 0: + if len(entries) == 0: return None else: - return entry[0] # There can be only one + return entries[0] # There can be only one def add_replication_manager(self, conn, dn, pw): """ Create a pseudo user to use for replication. """ - - edn = ldap.dn.str2dn(dn) - rdn_attr = edn[0][0][0] - rdn_val = edn[0][0][1] + assert isinstance(dn, DN) + rdn_attr = dn[0].attr + rdn_val = dn[0].value ent = ipaldap.Entry(dn) ent.setValues("objectclass", "top", "person") @@ -266,6 +268,7 @@ class ReplicationManager(object): pass def delete_replication_manager(self, conn, dn=REPL_MAN_DN): + assert isinstance(dn, DN) try: conn.delete_s(dn) except ldap.NO_SUCH_OBJECT: @@ -278,16 +281,18 @@ class ReplicationManager(object): return "2" def replica_dn(self): - return str(DN(('cn','replica'),('cn',self.suffix),('cn','mapping tree'),('cn','config'))) + return DN(('cn','replica'),('cn',self.suffix),('cn','mapping tree'),('cn','config')) def replica_config(self, conn, replica_id, replica_binddn): + assert isinstance(replica_binddn, DN) dn = self.replica_dn() + assert isinstance(dn, DN) try: entry = conn.getEntry(dn, ldap.SCOPE_BASE) managers = entry.getValues('nsDS5ReplicaBindDN') for m in managers: - if DN(replica_binddn) == DN(m): + if replica_binddn == DN(m): return # Add the new replication manager mod = [(ldap.MOD_ADD, 'nsDS5ReplicaBindDN', replica_binddn)] @@ -303,7 +308,7 @@ class ReplicationManager(object): entry = ipaldap.Entry(dn) entry.setValues('objectclass', "top", "nsds5replica", "extensibleobject") entry.setValues('cn', "replica") - entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaroot', str(self.suffix)) entry.setValues('nsds5replicaid', str(replica_id)) entry.setValues('nsds5replicatype', replica_type) entry.setValues('nsds5flags', "1") @@ -313,7 +318,7 @@ class ReplicationManager(object): conn.addEntry(entry) def setup_changelog(self, conn): - dn = "cn=changelog5, cn=config" + dn = DN(('cn', 'changelog5'), ('cn', 'config')) dirpath = conn.dbdir + "/cldb" entry = ipaldap.Entry(dn) entry.setValues('objectclass', "top", "extensibleobject") @@ -325,7 +330,7 @@ class ReplicationManager(object): return def setup_chaining_backend(self, conn): - chaindn = "cn=chaining database, cn=plugins, cn=config" + chaindn = DN(('cn', 'chaining database'), ('cn', 'plugins'), ('cn', 'config')) benamebase = "chaindb" urls = [self.to_ldap_url(conn)] cn = "" @@ -334,11 +339,11 @@ class ReplicationManager(object): while not done: try: cn = benamebase + str(benum) # e.g. localdb1 - dn = "cn=" + cn + ", " + chaindn + dn = DN(('cn', cn), chaindn) entry = ipaldap.Entry(dn) entry.setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance') entry.setValues('cn', cn) - entry.setValues('nsslapd-suffix', self.suffix) + entry.setValues('nsslapd-suffix', str(self.suffix)) entry.setValues('nsfarmserverurl', urls) entry.setValues('nsmultiplexorbinddn', self.repl_man_dn) entry.setValues('nsmultiplexorcredentials', self.repl_man_passwd) @@ -365,7 +370,7 @@ class ReplicationManager(object): def get_mapping_tree_entry(self): try: - entry = self.conn.getEntry("cn=mapping tree,cn=config", ldap.SCOPE_ONELEVEL, + entry = self.conn.getEntry(DN(('cn', 'mapping tree'), ('cn', 'config')), ldap.SCOPE_ONELEVEL, "(cn=\"%s\")" % (self.suffix)) except errors.NotFound, e: root_logger.debug("failed to find mappting tree entry for %s" % self.suffix) @@ -378,7 +383,7 @@ class ReplicationManager(object): mtent = self.get_mapping_tree_entry() dn = mtent.dn - plgent = self.conn.getEntry("cn=Multimaster Replication Plugin,cn=plugins,cn=config", + plgent = self.conn.getEntry(DN(('cn', 'Multimaster Replication Plugin'), ('cn', 'plugins'), ('cn', 'config')), ldap.SCOPE_BASE, "(objectclass=*)", ['nsslapd-pluginPath']) path = plgent.getValue('nsslapd-pluginPath') @@ -397,7 +402,7 @@ class ReplicationManager(object): self.enable_chain_on_update(chainbe) def add_passsync_user(self, conn, password): - pass_dn = "uid=passsync,cn=sysaccounts,cn=etc,%s" % self.suffix + pass_dn = DN(('uid', 'passsync'), ('cn', 'sysaccounts'), ('cn', 'etc'), self.suffix) print "The user for the Windows PassSync service is %s" % pass_dn try: conn.getEntry(pass_dn, ldap.SCOPE_BASE) @@ -414,7 +419,7 @@ class ReplicationManager(object): conn.addEntry(entry) # Add it to the list of users allowed to bypass password policy - extop_dn = "cn=ipa_pwd_extop,cn=plugins,cn=config" + extop_dn = DN(('cn', 'ipa_pwd_extop'), ('cn', 'plugins'), ('cn', 'config')) entry = conn.getEntry(extop_dn, ldap.SCOPE_BASE) pass_mgrs = entry.getValues('passSyncManagersDNs') if not pass_mgrs: @@ -435,9 +440,9 @@ class ReplicationManager(object): def setup_winsync_agmt(self, entry, win_subtree=None): if win_subtree is None: - win_subtree = WIN_USER_CONTAINER + "," + self.ad_suffix - ds_subtree = IPA_USER_CONTAINER + "," + self.suffix - windomain = '.'.join(ldap.explode_dn(self.suffix, 1)) + win_subtree = DN(WIN_USER_CONTAINER, self.ad_suffix) + ds_subtree = DN(IPA_USER_CONTAINER, self.suffix) + windomain = ipautil.suffix_to_realm(self.suffix) entry.setValues("objectclass", "nsDSWindowsReplicationAgreement") entry.setValues("nsds7WindowsReplicaSubtree", win_subtree) @@ -454,7 +459,7 @@ class ReplicationManager(object): tell which side we want. """ cn = "meTo%s" % (hostname) - dn = "cn=%s, %s" % (cn, self.replica_dn()) + dn = DN(('cn', cn), self.replica_dn()) return (cn, dn) @@ -469,6 +474,9 @@ class ReplicationManager(object): isn't a dogtag replication agreement. """ + if repl_man_dn is not None: + assert isinstance(repl_man_dn, DN) + cn, dn = self.agreement_dn(b_hostname, master=master) try: a_conn.getEntry(dn, ldap.SCOPE_BASE) @@ -482,7 +490,7 @@ class ReplicationManager(object): entry.setValues('nsds5replicahost', b_hostname) entry.setValues('nsds5replicaport', str(port)) entry.setValues('nsds5replicatimeout', str(TIMEOUT)) - entry.setValues('nsds5replicaroot', self.suffix) + entry.setValues('nsds5replicaroot', str(self.suffix)) if master is None: entry.setValues('nsDS5ReplicatedAttributeList', '(objectclass=*) $ EXCLUDE %s' % " ".join(EXCLUDES)) @@ -538,10 +546,15 @@ class ReplicationManager(object): while (retries > 0 ): root_logger.info('Getting ldap service principals for conversion: %s and %s' % (filter_a, filter_b)) - a_entry = b.search_s(self.suffix, ldap.SCOPE_SUBTREE, - filterstr=filter_a) - b_entry = a.search_s(self.suffix, ldap.SCOPE_SUBTREE, - filterstr=filter_b) + try: + a_entry = b.search_s(self.suffix, ldap.SCOPE_SUBTREE, filterstr=filter_a) + except errors.NotFound: + pass + + try: + b_entry = a.search_s(self.suffix, ldap.SCOPE_SUBTREE, filterstr=filter_b) + except errors.NotFound: + pass if a_entry and b_entry: root_logger.debug('Found both principals.') @@ -578,7 +591,10 @@ class ReplicationManager(object): """ rep_dn = self.replica_dn() + assert isinstance(rep_dn, DN) (a_dn, b_dn) = self.get_replica_principal_dns(a, b, retries=10) + assert isinstance(a_dn, DN) + assert isinstance(b_dn, DN) # Add kerberos principal DNs as valid bindDNs for replication try: @@ -623,12 +639,10 @@ class ReplicationManager(object): return self.conn.deleteEntry(dn) def delete_referral(self, hostname): - esc1_suffix = self.suffix.replace('=', '\\3D').replace(',', '\\2C') - esc2_suffix = self.suffix.replace('=', '%3D').replace(',', '%2C') - dn = 'cn=%s,cn=mapping tree,cn=config' % esc1_suffix + dn = DN(('cn', self.suffix), ('cn', 'mapping tree'), ('cn', 'config')) # TODO: should we detect proto/port somehow ? mod = [(ldap.MOD_DELETE, 'nsslapd-referral', - 'ldap://%s/%s' % (ipautil.format_netloc(hostname, 389), esc2_suffix))] + 'ldap://%s/%s' % (ipautil.format_netloc(hostname, 389), self.suffix))] try: self.conn.modify_s(dn, mod) @@ -648,9 +662,9 @@ class ReplicationManager(object): print "Error reading status from agreement", agmtdn hasError = 1 else: - refresh = entry.nsds5BeginReplicaRefresh - inprogress = entry.nsds5replicaUpdateInProgress - status = entry.nsds5ReplicaLastInitStatus + refresh = entry.getValue('nsds5BeginReplicaRefresh') + inprogress = entry.getValue('nsds5replicaUpdateInProgress') + status = entry.getValue('nsds5ReplicaLastInitStatus') if not refresh: # done - check status if not status: print "No status yet" @@ -683,10 +697,10 @@ class ReplicationManager(object): print "Error reading status from agreement", agmtdn hasError = 1 else: - inprogress = entry.nsds5replicaUpdateInProgress - status = entry.nsds5ReplicaLastUpdateStatus - start = entry.nsds5ReplicaLastUpdateStart - end = entry.nsds5ReplicaLastUpdateEnd + inprogress = entry.getValue('nsds5replicaUpdateInProgress') + status = entry.getValue('nsds5ReplicaLastUpdateStatus') + start = entry.getValue('nsds5ReplicaLastUpdateStart') + end = entry.getValue('nsds5ReplicaLastUpdateEnd') # incremental update is done if inprogress is false and end >= start done = inprogress and inprogress.lower() == 'false' and start and end and (start <= end) root_logger.info("Replication Update in progress: %s: status: %s: start: %s: end: %s" % @@ -733,6 +747,7 @@ class ReplicationManager(object): return self.wait_for_repl_init(conn, dn) def basic_replication_setup(self, conn, replica_id, repldn, replpw): + assert isinstance(repldn, DN) if replpw is not None: self.add_replication_manager(conn, repldn, replpw) self.replica_config(conn, replica_id, repldn) @@ -741,6 +756,7 @@ class ReplicationManager(object): def setup_replication(self, r_hostname, r_port=389, r_sslport=636, r_binddn=None, r_bindpw=None, starttls=False, is_cs_replica=False): + assert isinstance(r_binddn, DN) # note - there appears to be a bug in python-ldap - it does not # allow connections using two different CA certs if starttls: @@ -836,7 +852,7 @@ class ReplicationManager(object): root_logger.info("Agreement is ready, starting replication . . .") # Add winsync replica to the public DIT - dn = str(DN(('cn',ad_dc_name),('cn','replicas'),('cn','ipa'),('cn','etc'), self.suffix)) + dn = DN(('cn',ad_dc_name),('cn','replicas'),('cn','ipa'),('cn','etc'), self.suffix) entry = ipaldap.Entry(dn) entry.setValues("objectclass", ["nsContainer", "ipaConfigObject"]) entry.setValues("cn", ad_dc_name) @@ -910,17 +926,17 @@ class ReplicationManager(object): filter = '(&(nsDS5ReplicaHost=%s)' \ '(|(objectclass=nsDSWindowsReplicationAgreement)' \ '(objectclass=nsds5ReplicationAgreement)))' % hostname - entry = conn.search_s("cn=config", ldap.SCOPE_SUBTREE, filter) - if len(entry) == 0: + entries = conn.getList(DN(('cn', 'config')), ldap.SCOPE_SUBTREE, filter) + if len(entries) == 0: root_logger.error("Unable to find replication agreement for %s" % (hostname)) raise RuntimeError("Unable to proceed") - if len(entry) > 1: + if len(entries) > 1: root_logger.error("Found multiple agreements for %s" % hostname) - root_logger.error("Using the first one only (%s)" % entry[0].dn) + root_logger.error("Using the first one only (%s)" % entries[0].dn) - dn = entry[0].dn - schedule = entry[0].nsds5replicaupdateschedule + dn = entries[0].dn + schedule = entries[0].getValue('nsds5replicaupdateschedule') # On the remote chance of a match. We force a synch to happen right # now by setting the schedule to something and quickly removing it. @@ -965,16 +981,14 @@ class ReplicationManager(object): # delete master kerberos key and all its svc principals try: filter='(krbprincipalname=*/%s@%s)' % (replica, realm) - entries = self.conn.search_s(self.suffix, ldap.SCOPE_SUBTREE, - filterstr=filter) + entries = self.conn.getList(self.suffix, ldap.SCOPE_SUBTREE, + filterstr=filter) if len(entries) != 0: dnset = self.conn.get_dns_sorted_by_length(entries, reverse=True) for dns in dnset: for dn in dns: self.conn.deleteEntry(dn) - except ldap.NO_SUCH_OBJECT: - pass except errors.NotFound: pass except Exception, e: @@ -984,18 +998,18 @@ class ReplicationManager(object): err = e # remove replica memberPrincipal from s4u2proxy configuration - dn1 = DN(u'cn=ipa-http-delegation', api.env.container_s4u2proxy, self.suffix) + dn1 = DN(('cn', 'ipa-http-delegation'), api.env.container_s4u2proxy, self.suffix) member_principal1 = "HTTP/%(fqdn)s@%(realm)s" % dict(fqdn=replica, realm=realm) - dn2 = DN(u'cn=ipa-ldap-delegation-targets', api.env.container_s4u2proxy, self.suffix) + dn2 = DN(('cn', 'ipa-ldap-delegation-targets'), api.env.container_s4u2proxy, self.suffix) member_principal2 = "ldap/%(fqdn)s@%(realm)s" % dict(fqdn=replica, realm=realm) - dn3 = DN(u'cn=ipa-cifs-delegation-targets', api.env.container_s4u2proxy, self.suffix) + dn3 = DN(('cn', 'ipa-cifs-delegation-targets'), api.env.container_s4u2proxy, self.suffix) member_principal3 = "cifs/%(fqdn)s@%(realm)s" % dict(fqdn=replica, realm=realm) - for (dn, member_principal) in ((str(dn1), member_principal1), - (str(dn2), member_principal2), - (str(dn3), member_principal3)): + for (dn, member_principal) in ((dn1, member_principal1), + (dn2, member_principal2), + (dn3, member_principal3)): try: mod = [(ldap.MOD_DELETE, 'memberPrincipal', member_principal)] self.conn.modify_s(dn, mod) @@ -1010,16 +1024,14 @@ class ReplicationManager(object): # delete master entry with all active services try: - dn = 'cn=%s,cn=masters,cn=ipa,cn=etc,%s' % (replica, self.suffix) - entries = self.conn.search_s(dn, ldap.SCOPE_SUBTREE) + dn = DN(('cn', replica), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), self.suffix) + entries = self.conn.getList(dn, ldap.SCOPE_SUBTREE) if len(entries) != 0: dnset = self.conn.get_dns_sorted_by_length(entries, reverse=True) for dns in dnset: for dn in dns: self.conn.deleteEntry(dn) - except ldap.NO_SUCH_OBJECT: - pass except errors.NotFound: pass except Exception, e: @@ -1029,15 +1041,13 @@ class ReplicationManager(object): err = e try: - basedn = 'cn=etc,%s' % self.suffix + basedn = DN(('cn', 'etc'), self.suffix) filter = '(dnaHostname=%s)' % replica - entries = self.conn.search_s(basedn, ldap.SCOPE_SUBTREE, - filterstr=filter) + entries = self.conn.getList(basedn, ldap.SCOPE_SUBTREE, + filterstr=filter) if len(entries) != 0: for e in entries: self.conn.deleteEntry(e.dn) - except ldap.NO_SUCH_OBJECT: - pass except errors.NotFound: pass except Exception, e: @@ -1047,18 +1057,16 @@ class ReplicationManager(object): err = e try: - dn = 'cn=default,ou=profile,%s' % self.suffix - ret = self.conn.search_s(dn, ldap.SCOPE_BASE, - '(objectclass=*)')[0] - srvlist = ret.data.get('defaultServerList') - if len(srvlist) > 0: - srvlist = srvlist[0].split() + dn = DN(('cn', 'default'), ('ou', 'profile'), self.suffix) + ret = self.conn.getEntry(dn, ldap.SCOPE_BASE, '(objectclass=*)') + srvlist = ret.getValue('defaultServerList', '') + srvlist = srvlist[0].split() if replica in srvlist: srvlist.remove(replica) attr = ' '.join(srvlist) mod = [(ldap.MOD_REPLACE, 'defaultServerList', attr)] self.conn.modify_s(dn, mod) - except ldap.NO_SUCH_OBJECT: + except errors.NotFound: pass except ldap.NO_SUCH_ATTRIBUTE: pass diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index 198cb3870..dcfcbc27f 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -25,6 +25,7 @@ from ipapython import sysrestore from ipapython import ipautil from ipapython import services as ipaservices from ipalib import errors +from ipapython.dn import DN import ldap from ipaserver import ipaldap import base64 @@ -78,7 +79,7 @@ class Service(object): self.sstore = sysrestore.StateFile('/var/lib/ipa/sysrestore') self.realm = None - self.suffix = None + self.suffix = DN() self.principal = None self.dercert = None @@ -150,7 +151,7 @@ class Service(object): # use URI of admin connection if not self.admin_conn: self.ldap_connect() - args += ["-H", self.admin_conn._uri] + args += ["-H", self.admin_conn.uri] auth_parms = [] if self.dm_password: @@ -183,15 +184,15 @@ class Service(object): cn=kerberos to cn=services """ - dn = "krbprincipalname=%s,cn=%s,cn=kerberos,%s" % (principal, self.realm, self.suffix) + dn = DN(('krbprincipalname', principal), ('cn', self.realm), ('cn', 'kerberos'), self.suffix) try: entry = self.admin_conn.getEntry(dn, ldap.SCOPE_BASE) except errors.NotFound: # There is no service in the wrong location, nothing to do. # This can happen when installing a replica - return - newdn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (principal, self.suffix) - hostdn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) + return None + newdn = DN(('krbprincipalname', principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) + hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) self.admin_conn.deleteEntry(dn) entry.dn = newdn classes = entry.getValues("objectclass") @@ -211,8 +212,8 @@ class Service(object): if not self.admin_conn: self.ldap_connect() - dn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (principal, self.suffix) - hostdn = "fqdn=%s,cn=computers,cn=accounts,%s" % (self.fqdn, self.suffix) + dn = DN(('krbprincipalname', principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) + hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) entry = ipaldap.Entry(dn) entry.setValues("objectclass", ["krbprincipal", "krbprincipalaux", "krbticketpolicyaux", "ipaobject", "ipaservice", "pkiuser"]) entry.setValue("krbprincipalname", principal) @@ -242,7 +243,7 @@ class Service(object): self.ldap_disconnect() self.ldap_connect() - dn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (self.principal, self.suffix) + dn = DN(('krbprincipalname', self.principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) mod = [(ldap.MOD_ADD, 'userCertificate', self.dercert)] try: self.admin_conn.modify_s(dn, mod) @@ -327,13 +328,12 @@ class Service(object): self.steps = [] def ldap_enable(self, name, fqdn, dm_password, ldap_suffix): + assert isinstance(ldap_suffix, DN) self.disable() if not self.admin_conn: self.ldap_connect() - entry_name = "cn=%s,cn=%s,%s,%s" % (name, fqdn, - "cn=masters,cn=ipa,cn=etc", - ldap_suffix) + entry_name = DN(('cn', name), ('cn', fqdn), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), ldap_suffix) order = SERVICE_LIST[name][1] entry = ipaldap.Entry(entry_name) entry.setValues("objectclass", @@ -362,6 +362,8 @@ class SimpleServiceInstance(Service): self.step("configuring %s to start on boot" % self.service_name, self.__enable) self.start_creation("Configuring %s" % self.service_name) + suffix = ipautil.dn_attribute_property('_ldap_suffix') + def __start(self): self.backup_state("running", self.is_running()) self.restart() diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py index e4fa2c644..2e1b91a56 100644 --- a/ipaserver/ipaldap.py +++ b/ipaserver/ipaldap.py @@ -34,9 +34,11 @@ import ldap.sasl import ldapurl from ldap.controls import LDAPControl from ldap.ldapobject import SimpleLDAPObject +from ipapython.ipa_log_manager import * from ipapython import ipautil from ipalib import errors from ipapython.ipautil import format_netloc, wait_for_open_socket, wait_for_open_ports +from ipapython.dn import DN from ipapython.entity import Entity from ipaserver.plugins.ldap2 import IPASimpleLDAPObject @@ -92,7 +94,7 @@ class Entry: 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 + * dn - DN object - the DN of the entry * data - CIDict - case insensitive dict of the attributes and values """ def __init__(self,entrydata): @@ -103,13 +105,22 @@ class Entry: if isinstance(entrydata,tuple): self.dn = entrydata[0] self.data = ipautil.CIDict(entrydata[1]) - elif isinstance(entrydata,str) or isinstance(entrydata,unicode): + elif isinstance(entrydata,DN): self.dn = entrydata self.data = ipautil.CIDict() + elif isinstance(entrydata, basestring): + self.dn = DN(entrydata) + self.data = ipautil.CIDict() + else: + raise TypeError("entrydata must be 2-tuple, DN, or basestring, got %s" % type(entrydata)) else: - self.dn = '' + self.dn = DN() self.data = ipautil.CIDict() + assert isinstance(self.dn, DN) + + dn = ipautil.dn_attribute_property('_dn') + def __nonzero__(self): """This allows us to do tests like if entry: returns false if there is no data, true otherwise""" @@ -119,23 +130,16 @@ class Entry: """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): + def getValue(self,name, default=None): """Get the first value for the attribute named name""" - return self.data.get(name,[None])[0] + value = self.data.get(name, default) + if isinstance(value, (list, tuple)): + return value[0] + return value def setValue(self, name, *value): """ @@ -179,6 +183,7 @@ class Entry: def toDict(self): """Convert the attrs and values to a dict. The dict is keyed on the attribute name. The value is either single value or a list of values.""" + assert isinstance(self.dn, DN) result = ipautil.CIDict(self.data) for i in result.keys(): result[i] = ipautil.utf8_encode_values(result[i]) @@ -206,7 +211,7 @@ class Entry: # (1000) newdata = {} newdata.update(self.data) - ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(self.dn,newdata) + ldif.LDIFWriter(sio,Entry.base64_attrs,1000).unparse(str(self.dn),newdata) return sio.getvalue() class IPAdmin(IPAEntryLDAPObject): @@ -233,6 +238,7 @@ class IPAdmin(IPAEntryLDAPObject): that we can call it from places other than instance creation e.g. when we just need to reconnect """ + log_mgr.get_logger(self, True) if debug and debug.lower() == "on": ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) if cacert is not None: @@ -251,7 +257,6 @@ class IPAdmin(IPAEntryLDAPObject): self.ldapi = ldapi self.realm = realm self.suffixes = {} - self.schema = None self.__localinit() def __lateinit(self): @@ -260,7 +265,7 @@ class IPAdmin(IPAEntryLDAPObject): values. """ try: - ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config', + ent = self.getEntry(DN(('cn', 'config'), ('cn', 'ldbm database'), ('cn', 'plugins'), ('cn', 'config')), ldap.SCOPE_BASE, '(objectclass=*)', [ 'nsslapd-directory' ]) @@ -299,6 +304,9 @@ class IPAdmin(IPAEntryLDAPObject): if not isinstance(e,ldap.TIMEOUT): desc = e.args[0]['desc'].strip() info = e.args[0].get('info','').strip() + arg_desc = kw.get('arg_desc') + if arg_desc is not None: + info += " arguments: %s" % arg_desc else: desc = '' info = '' @@ -334,7 +342,7 @@ class IPAdmin(IPAEntryLDAPObject): raise errors.DatabaseError(desc=desc,info=info) def __wait_for_connection(self, timeout): - lurl = ldapurl.LDAPUrl(self._uri) + lurl = ldapurl.LDAPUrl(self.uri) if lurl.urlscheme == 'ldapi': wait_for_open_socket(lurl.hostport, timeout) else: @@ -365,25 +373,25 @@ class IPAdmin(IPAEntryLDAPObject): try: if krbccache is not None: os.environ["KRB5CCNAME"] = krbccache - self.sasl_interactive_bind_s("", SASL_AUTH) + self.sasl_interactive_bind_s(None, SASL_AUTH) self.principal = principal self.proxydn = None except ldap.LDAPError, e: self.__handle_errors(e) - def do_simple_bind(self, binddn="cn=directory manager", bindpw="", timeout=DEFAULT_TIMEOUT): - self.binddn = binddn + def do_simple_bind(self, binddn=DN(('cn', 'directory manager')), bindpw="", timeout=DEFAULT_TIMEOUT): + self.binddn = binddn # FIXME, self.binddn & self.bindpwd never referenced. self.bindpwd = bindpw self.__bind_with_wait(self.simple_bind_s, timeout, binddn, bindpw) self.__lateinit() def do_sasl_gssapi_bind(self, timeout=DEFAULT_TIMEOUT): - self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, '', SASL_AUTH) + self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, None, SASL_AUTH) self.__lateinit() def do_external_bind(self, user_name=None, timeout=DEFAULT_TIMEOUT): auth_tokens = ldap.sasl.external(user_name) - self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, '', auth_tokens) + self.__bind_with_wait(self.sasl_interactive_bind_s, timeout, None, auth_tokens) self.__lateinit() def getEntry(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): @@ -491,13 +499,15 @@ class IPAdmin(IPAEntryLDAPObject): self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl) self.add_s(entry.dn, entry.toTupleList()) except ldap.LDAPError, e: - arg_desc = 'entry=%s' % (entry.toTupleList()) + arg_desc = 'entry=%s: %s' % (entry.dn, entry.toTupleList()) self.__handle_errors(e, arg_desc=arg_desc) return True def updateRDN(self, dn, newrdn): """Wrap the modrdn function.""" + assert isinstance(dn, DN) + assert isinstance(newrdn, DN) sctrl = self.__get_server_controls() if dn == newrdn: @@ -516,6 +526,7 @@ class IPAdmin(IPAEntryLDAPObject): """This wraps the mod function. It assumes that the entry is already populated with all of the desired objectclasses and attributes""" + assert isinstance(dn, DN) sctrl = self.__get_server_controls() modlist = self.generateModList(oldentry, newentry) @@ -575,7 +586,7 @@ class IPAdmin(IPAEntryLDAPObject): # You can't remove schema online. An add will automatically # replace any existing schema. - if old_entry.get('dn') == 'cn=schema': + if old_entry.get('dn', DN()) == DN(('cn', 'schema')): if len(adds) > 0: modlist.append((ldap.MOD_ADD, key, adds)) else: @@ -590,11 +601,12 @@ class IPAdmin(IPAEntryLDAPObject): return modlist - def inactivateEntry(self,dn,has_key): + 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.""" + assert isinstance(dn, DN) sctrl = self.__get_server_controls() modlist=[] @@ -616,6 +628,7 @@ class IPAdmin(IPAEntryLDAPObject): def deleteEntry(self, dn): """This wraps the delete function. Use with caution.""" + assert isinstance(dn, DN) sctrl = self.__get_server_controls() try: @@ -627,7 +640,7 @@ class IPAdmin(IPAEntryLDAPObject): self.__handle_errors(e, arg_desc=arg_desc) return True - def modifyPassword(self,dn,oldpass,newpass): + 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. @@ -635,6 +648,7 @@ class IPAdmin(IPAEntryLDAPObject): oldpass is not mandatory """ + assert isinstance(dn, DN) sctrl = self.__get_server_controls() try: @@ -656,6 +670,7 @@ class IPAdmin(IPAEntryLDAPObject): if isinstance(dn,Entry): dn = dn.dn + assert isinstance(dn, DN) # wait for entry and/or attr to show up if not quiet: @@ -691,6 +706,7 @@ class IPAdmin(IPAEntryLDAPObject): running, true if done - if true, second is the exit code - if dowait is True, this function will block until the task is complete """ + assert isinstance(dn, DN) attrlist = ['nsTaskLog', 'nsTaskStatus', 'nsTaskExitCode', 'nsTaskCurrentItem', 'nsTaskTotalItems'] done = False exitCode = 0 @@ -701,26 +717,13 @@ class IPAdmin(IPAEntryLDAPObject): break if verbose: print entry - if entry.nsTaskExitCode: - exitCode = int(entry.nsTaskExitCode) + if entry.getValue('nsTaskExitCode'): + exitCode = int(entry.getValue('nsTaskExitCode')) done = True if dowait: time.sleep(1) else: break return (done, exitCode) - def get_schema(self): - """ - Retrieve cn=schema and convert it into a python-ldap schema - object. - """ - if self.schema: - return self.schema - schema = self.getEntry('cn=schema', ldap.SCOPE_BASE, - '(objectclass=*)', ['attributetypes', 'objectclasses']) - schema = schema.toDict() - self.schema = ldap.schema.SubSchema(schema) - return self.schema - def get_single_value(self, attr): """ Check the schema to see if the attribute is single-valued. @@ -730,8 +733,6 @@ class IPAdmin(IPAEntryLDAPObject): If there is a problem loading the schema or the attribute is not in the schema return None """ - if not self.schema: - self.get_schema() obj = self.schema.get_obj(ldap.schema.AttributeType, attr) return obj and obj.single_value @@ -744,19 +745,27 @@ class IPAdmin(IPAEntryLDAPObject): starting from the leafs and go up to delete nodes only when all its leafs are removed. - Returns a "sorted" dict keyed by dn lengths and corresponding list - of DNs. - {'1': [dn1, dn2, dn3], '2': [dn4, dn5], ..} + Returns a list of list of dn's. Every dn in the dn list has + the same number of RDN's. The outer list is sorted according + to the number of RDN's in each inner list. + + Example: + + [['cn=bob', cn=tom], ['cn=bob,ou=people', cn=tom,ou=people]] + + dn's in list[0] have 1 RDN + dn's in list[1] have 2 RDN's """ res = dict() for e in entries: - sdn = ldap.dn.str2dn(e.dn) - l = len(sdn) - if not l in res: - res[l] = [] - res[l].append(e.dn) + dn = e.dn + assert isinstance(dn, DN) + rdn_count = len(dn) + rdn_count_list = res.setdefault(rdn_count, []) + if dn not in rdn_count_list: + rdn_count_list.append(dn) keys = res.keys() keys.sort(reverse=reverse) diff --git a/ipaserver/plugins/dogtag.py b/ipaserver/plugins/dogtag.py index 8accb56d2..baa41ad3c 100644 --- a/ipaserver/plugins/dogtag.py +++ b/ipaserver/plugins/dogtag.py @@ -238,6 +238,8 @@ digits and nothing else follows. from lxml import etree import datetime +from ipapython.dn import DN +from ldap.filter import escape_filter_chars # These are general status return values used when # CMSServlet.outputError() is invoked. @@ -1239,8 +1241,8 @@ class ra(rabase.rabase): Check if a specified host is a master for a specified service. """ - base_dn = 'cn=%s,cn=masters,cn=ipa,cn=etc,%s' % (host, api.env.basedn) - filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % service + base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) + filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % escape_filter_chars(service) try: ldap2 = self.api.Backend.ldap2 ent,trunc = ldap2.find_entries(filter=filter, base_dn=base_dn) @@ -1258,14 +1260,16 @@ class ra(rabase.rabase): Select any host which is a master for a specified service. """ - base_dn = 'cn=masters,cn=ipa,cn=etc,%s' % api.env.basedn - filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % service + base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn) + filter = '(&(objectClass=ipaConfigObject)(cn=%s)(ipaConfigString=enabledService))' % escape_filter_chars(service) try: ldap2 = self.api.Backend.ldap2 ent,trunc = ldap2.find_entries(filter=filter, base_dn=base_dn) if len(ent): entry = random.choice(ent) - return ldap.explode_dn(dn=entry[0],notypes=True)[1] + dn = entry[0] + assert isinstance(dn, DN) + return dn[1].value except Exception, e: pass return None diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index 6a3d2164e..a0b91fd5d 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -1,5 +1,6 @@ # Authors: # Pavel Zuna <pzuna@redhat.com> +# John Dennis <jdennis@redhat.com> # # Copyright (C) 2009 Red Hat # see file 'COPYING' for use and warranty information @@ -35,6 +36,8 @@ import tempfile import time import re import pwd +import sys +from decimal import Decimal import krbV from ipapython.ipa_log_manager import * @@ -42,6 +45,12 @@ import ldap as _ldap from ldap.ldapobject import SimpleLDAPObject import ldap.filter as _ldap_filter import ldap.sasl as _ldap_sasl +from ipapython.dn import DN, RDN +from ipapython.ipautil import CIDict +from collections import namedtuple +from ipalib.errors import NetworkError, DatabaseError + + try: from ldap.controls.simple import GetEffectiveRightsControl #pylint: disable=F0401,E0611 except ImportError: @@ -56,17 +65,26 @@ except ImportError: def __init__(self, criticality, authzId=None): LDAPControl.__init__(self, '1.3.6.1.4.1.42.2.27.9.5.2', criticality, authzId) # for backward compatibility -from ldap.functions import explode_dn -from ipalib.dn import DN from ipalib import _ import krbV from ipalib import api, errors from ipalib.crud import CrudBackend -from ipalib.encoder import Encoder, encode_args, decode_retval from ipalib.request import context +_debug_log_ldap = False + +# Make python-ldap tuple style result compatible with Entry and Entity +# objects by allowing access to the dn (tuple index 0) via the 'dn' +# attribute name and the attr dict (tuple index 1) via the 'data' +# attribute name. Thus: +# r = result[0] +# r[0] == r.dn +# r[1] == r.data +LDAPEntry = namedtuple('LDAPEntry', ['dn', 'data']) + + # Group Member types MEMBERS_ALL = 0 MEMBERS_DIRECT = 1 @@ -75,264 +93,541 @@ MEMBERS_INDIRECT = 2 # SASL authentication mechanism SASL_AUTH = _ldap_sasl.sasl({}, 'GSSAPI') -class IPASimpleLDAPObject(SimpleLDAPObject): +DN_SYNTAX_OID = '1.3.6.1.4.1.1466.115.121.1.12' + +def unicode_from_utf8(val): ''' - This is a thin layer over SimpleLDAPObject which allows us to utilize - IPA specific types with the python-ldap API without the IPA caller needing - to perform the type translation, consider this a convenience layer for the - IPA programmer. + val is a UTF-8 encoded string, return a unicode object. + ''' + return val.decode('utf-8') - This subclass performs the following translations: +def value_to_utf8(val): + ''' + Coerce the val parameter to a UTF-8 encoded string representation + of the val. + ''' + + # If val is not a string we need to convert it to a string + # (specifically a unicode string). Naively we might think we need to + # call str(val) to convert to a string. This is incorrect because if + # val is already a unicode object then str() will call + # encode(default_encoding) returning a str object encoded with + # default_encoding. But we don't want to apply the default_encoding! + # Rather we want to guarantee the val object has been converted to a + # unicode string because from a unicode string we want to explicitly + # encode to a str using our desired encoding (utf-8 in this case). + # + # Note: calling unicode on a unicode object simply returns the exact + # same object (with it's ref count incremented). This means calling + # unicode on a unicode object is effectively a no-op, thus it's not + # inefficient. - * DN objects may be passed into any ldap function expecting a dn. The DN - object will be converted to a string before being passed to the python-ldap - function. This allows us to maintain DN objects as DN objects in the rest - of the code (useful for DN manipulation and DN information) and not have - to worry about conversion to a string prior to passing it ldap. + return unicode(val).encode('utf-8') +class _ServerSchema(object): + ''' + Properties of a schema retrieved from an LDAP server. ''' - def __init__(self, *args, **kwds): - SimpleLDAPObject.__init__(self, *args, **kwds) - def add(self, dn, modlist): - return SimpleLDAPObject.add(self, str(dn), modlist) + def __init__(self, server, schema): + self.server = server + self.schema = schema + self.retrieve_timestamp = time.time() - def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.add_ext(self, str(dn), modlist, serverctrls, clientctrls) +class SchemaCache(object): + ''' + Cache the schema's from individual LDAP servers. + ''' - def add_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.add_ext_s(self, str(dn), modlist, serverctrls, clientctrls) + def __init__(self): + log_mgr.get_logger(self, True) + self.servers = {} - def add_s(self, dn, modlist): - return SimpleLDAPObject.add_s(self, str(dn), modlist) + def get_schema(self, url, conn=None, force_update=False): + ''' + Return schema belonging to a specific LDAP server. - def compare(self, dn, attr, value): - return SimpleLDAPObject.compare(self, str(dn), attr, value) + For performance reasons the schema is retrieved once and + cached unless force_update is True. force_update flushes the + existing schema for the server from the cache and reacquires + it. + ''' - def compare_ext(self, dn, attr, value, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.compare_ext(self, str(dn), attr, value, serverctrls, clientctrls) + if force_update: + self.flush(url) - def compare_ext_s(self, dn, attr, value, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.compare_ext_s(self, str(dn), attr, value, serverctrls, clientctrls) + server_schema = self.servers.get(url) + if server_schema is None: + schema = self._retrieve_schema_from_server(url, conn) + server_schema = _ServerSchema(url, schema) + self.servers[url] = server_schema + return server_schema.schema - def compare_s(self, dn, attr, value): - return SimpleLDAPObject.compare_s(self, str(dn), attr, value) + def flush(self, url): + self.debug('flushing %s from SchemaCache', url) + try: + del self.servers[url] + except KeyError: + pass - def delete(self, dn): - return SimpleLDAPObject.delete(self, str(dn)) + def _retrieve_schema_from_server(self, url, conn=None): + """ + Retrieve the LDAP schema from the provided url and determine if + User-Private Groups (upg) are configured. - def delete_ext(self, dn, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.delete_ext(self, str(dn), serverctrls, clientctrls) + Bind using kerberos credentials. If in the context of the + in-tree "lite" server then use the current ccache. If in the context of + Apache then create a new ccache and bind using the Apache HTTP service + principal. - def delete_ext_s(self, dn, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.delete_ext_s(self, str(dn), serverctrls, clientctrls) + If a connection is provided then it the credentials bound to it are + used. The connection is not closed when the request is done. + """ + tmpdir = None + has_conn = conn is not None - def delete_s(self, dn): - return SimpleLDAPObject.delete_s(self, str(dn)) + self.debug('retrieving schema for SchemaCache url=%s conn=%s', url, conn) - def modify(self, dn, modlist): - return SimpleLDAPObject.modify(self, str(dn), modlist) + try: + if api.env.context == 'server' and conn is None: + # FIXME: is this really what we want to do? + # This seems like this logic is in the wrong place and may conflict with other state. + try: + # Create a new credentials cache for this Apache process + tmpdir = tempfile.mkdtemp(prefix = "tmp-") + ccache_file = 'FILE:%s/ccache' % tmpdir + krbcontext = krbV.default_context() + principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm)) + keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext) + principal = krbV.Principal(name=principal, context=krbcontext) + prev_ccache = os.environ.get('KRB5CCNAME') + os.environ['KRB5CCNAME'] = ccache_file + ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal) + ccache.init(principal) + ccache.init_creds_keytab(keytab=keytab, principal=principal) + except krbV.Krb5Error, e: + raise StandardError('Unable to retrieve LDAP schema. Error initializing principal %s in %s: %s' % (principal.name, '/etc/httpd/conf/ipa.keytab', str(e))) + finally: + if prev_ccache is not None: + os.environ['KRB5CCNAME'] = prev_ccache + + + if conn is None: + conn = IPASimpleLDAPObject(url) + if url.startswith('ldapi://'): + conn.set_option(_ldap.OPT_HOST_NAME, api.env.host) + conn.sasl_interactive_bind_s(None, SASL_AUTH) + + schema_entry = conn.search_s('cn=schema', _ldap.SCOPE_BASE, + attrlist=['attributetypes', 'objectclasses'])[0] + if not has_conn: + conn.unbind_s() + except _ldap.SERVER_DOWN: + raise NetworkError(uri=url, + error=u'LDAP Server Down, unable to retrieve LDAP schema') + except _ldap.LDAPError, e: + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info', '').strip() + raise DatabaseError(desc = u'uri=%s' % url, + info = u'Unable to retrieve LDAP schema: %s: %s' % (desc, info)) + except IndexError: + # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema' + # TODO: DS uses 'cn=schema', support for other server? + # raise a more appropriate exception + raise + finally: + if tmpdir: + shutil.rmtree(tmpdir) + + return _ldap.schema.SubSchema(schema_entry[1]) + +schema_cache = SchemaCache() + +class IPASimpleLDAPObject(object): + ''' + The purpose of this class is to provide a boundary between IPA and + python-ldap. In IPA we use IPA defined types because they are + richer and are designed to meet our needs. We also require that we + consistently use those types without exception. On the other hand + python-ldap uses different types. The goal is to be able to have + IPA code call python-ldap methods using the types native to + IPA. This class accomplishes that goal by exposing python-ldap + methods which take IPA types, convert them to python-ldap types, + call python-ldap, and then convert the results returned by + python-ldap into IPA types. + + IPA code should never call python-ldap directly, it should only + call python-ldap methods in this class. + ''' - def modify_ext(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.modify_ext(self, str(dn), modlist, serverctrls, clientctrls) + # Note: the oid for dn syntax is: 1.3.6.1.4.1.1466.115.121.1.12 - def modify_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.modify_ext_s(self, str(dn), modlist, serverctrls, clientctrls) + _SYNTAX_MAPPING = { + '1.3.6.1.4.1.1466.115.121.1.1' : str, # ACI item + '1.3.6.1.4.1.1466.115.121.1.4' : str, # Audio + '1.3.6.1.4.1.1466.115.121.1.5' : str, # Binary + '1.3.6.1.4.1.1466.115.121.1.8' : str, # Certificate + '1.3.6.1.4.1.1466.115.121.1.9' : str, # Certificate List + '1.3.6.1.4.1.1466.115.121.1.10' : str, # Certificate Pair + '1.3.6.1.4.1.1466.115.121.1.23' : str, # Fax + '1.3.6.1.4.1.1466.115.121.1.28' : str, # JPEG + '1.3.6.1.4.1.1466.115.121.1.40' : str, # OctetString (same as Binary) + '1.3.6.1.4.1.1466.115.121.1.49' : str, # Supported Algorithm + '1.3.6.1.4.1.1466.115.121.1.51' : str, # Teletext Terminal Identifier + + DN_SYNTAX_OID : DN, # DN, member, memberof + '2.16.840.1.113730.3.8.3.3' : DN, # enrolledBy + '2.16.840.1.113730.3.8.3.18' : DN, # managedBy + '2.16.840.1.113730.3.8.3.5' : DN, # memberUser + '2.16.840.1.113730.3.8.3.7' : DN, # memberHost + '2.16.840.1.113730.3.8.3.20' : DN, # memberService + '2.16.840.1.113730.3.8.11.4' : DN, # ipaNTFallbackPrimaryGroup + '2.16.840.1.113730.3.8.11.21' : DN, # ipaAllowToImpersonate + '2.16.840.1.113730.3.8.11.22' : DN, # ipaAllowedTarget + '2.16.840.1.113730.3.8.7.1' : DN, # memberAllowCmd + '2.16.840.1.113730.3.8.7.2' : DN, # memberDenyCmd + + '2.16.840.1.113719.1.301.4.14.1' : DN, # krbRealmReferences + '2.16.840.1.113719.1.301.4.17.1' : DN, # krbKdcServers + '2.16.840.1.113719.1.301.4.18.1' : DN, # krbPwdServers + '2.16.840.1.113719.1.301.4.26.1' : DN, # krbPrincipalReferences + '2.16.840.1.113719.1.301.4.29.1' : DN, # krbAdmServers + '2.16.840.1.113719.1.301.4.36.1' : DN, # krbPwdPolicyReference + '2.16.840.1.113719.1.301.4.40.1' : DN, # krbTicketPolicyReference + '2.16.840.1.113719.1.301.4.41.1' : DN, # krbSubTrees + '2.16.840.1.113719.1.301.4.52.1' : DN, # krbObjectReferences + '2.16.840.1.113719.1.301.4.53.1' : DN, # krbPrincContainerRef + } - def modify_s(self, dn, modlist): - return SimpleLDAPObject.modify_s(self, str(dn), modlist) + # In most cases we lookup the syntax from the schema returned by + # the server. However, sometimes attributes may not be defined in + # the schema (e.g. extensibleObject which permits undefined + # attributes), or the schema was incorrectly defined (i.e. giving + # an attribute the syntax DirectoryString when in fact it's really + # a DN). This (hopefully sparse) table allows us to trap these + # anomalies and force them to be the syntax we know to be in use. + # + # FWIW, many entries under cn=config are undefined :-( + + _SCHEMA_OVERRIDE = CIDict({ + 'managedtemplate': DN_SYNTAX_OID, # DN + 'managedbase': DN_SYNTAX_OID, # DN + 'originscope': DN_SYNTAX_OID, # DN + }) + + def __init__(self, uri): + log_mgr.get_logger(self, True) + self.uri = uri + self.conn = SimpleLDAPObject(uri) + self._schema = None + + def _get_schema(self): + if self._schema is None: + # The schema may be updated during install or during + # updates, make sure we have a current version of the + # schema, not an out of date cached version. + force_update = api.env.context in ('installer', 'updates') + self._schema = schema_cache.get_schema(self.uri, self.conn, force_update=force_update) + return self._schema + + schema = property(_get_schema, None, None, 'schema associated with this LDAP server') + + + def flush_cached_schema(self): + ''' + Force this instance to forget it's cached schema and reacquire + it from the schema cache. + ''' + + # Currently this is called during bind operations to assure + # we're working with valid schema for a specific + # connection. This causes self._get_schema() to query the + # schema cache for the server's schema passing along a flag + # indicating if we're in a context that requires freshly + # loading the schema vs. returning the last cached version of + # the schema. If we're in a mode that permits use of + # previously cached schema the flush and reacquire is a very + # low cost operation. + # + # The schema is reacquired whenever this object is + # instantiated or when binding occurs. The schema is not + # reacquired for operations during a bound connection, it is + # presumed schema cannot change during this interval. This + # provides for maximum efficiency in contexts which do need + # schema refreshing by only peforming the refresh inbetween + # logical operations that have the potential to cause a schema + # change. + + self._schema = None + + def get_syntax(self, attr): + # Is this a special case attribute? + syntax = self._SCHEMA_OVERRIDE.get(attr) + if syntax is not None: + return syntax + + # Try to lookup the syntax in the schema returned by the server + obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) + if obj is not None: + return obj.syntax + else: + return None - def modrdn(self, dn, newrdn, delold=1): - return SimpleLDAPObject.modrdn(self, str(dn), str(newrdn), delold) + def has_dn_syntax(self, attr): + """ + Check the schema to see if the attribute uses DN syntax. - def modrdn_s(self, dn, newrdn, delold=1): - return SimpleLDAPObject.modrdn_s(self, str(dn), str(newrdn), delold) + Returns True/False + """ + syntax = self.get_syntax(attr) + return syntax == DN_SYNTAX_OID + + + def encode(self, val): + ''' + ''' + # Booleans are both an instance of bool and int, therefore + # test for bool before int otherwise the int clause will be + # entered for a boolean value instead of the boolean clause. + if isinstance(val, bool): + if val: + return 'TRUE' + else: + return 'FALSE' + elif isinstance(val, (unicode, float, int, long, Decimal, DN)): + return value_to_utf8(val) + elif isinstance(val, str): + return val + elif isinstance(val, list): + return [self.encode(m) for m in val] + elif isinstance(val, tuple): + return tuple(self.encode(m) for m in val) + elif isinstance(val, dict): + dct = dict((self.encode(k), self.encode(v)) for k, v in val.iteritems()) + return dct + elif val is None: + return None + else: + raise TypeError("attempt to pass unsupported type to ldap, value=%s type=%s" %(val, type(val))) - def read_subschemasubentry_s(self, subschemasubentry_dn, attrs=None): - return SimpleLDAPObject.read_subschemasubentry_s(self, str(subschemasubentry_dn), attrs) + def convert_value_list(self, attr, target_type, values): + ''' + ''' - def rename(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.rename(self, str(dn), str(newrdn), newsuperior, delold, serverctrls, clientctrls) + ipa_values = [] - def rename_s(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None): - return SimpleLDAPObject.rename_s(self, str(dn), str(newrdn), newsuperior, delold, serverctrls, clientctrls) + for original_value in values: + if isinstance(target_type, type) and isinstance(original_value, target_type): + ipa_value = original_value + else: + try: + ipa_value = target_type(original_value) + except Exception, e: + msg = 'unable to convert the attribute "%s" value "%s" to type %s' % (attr, original_value, target_type) + self.error(msg) + raise ValueError(msg) - def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): - return SimpleLDAPObject.search(self, str(base), scope, filterstr, attrlist, attrsonly) + ipa_values.append(ipa_value) - def search_ext(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, - serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): - return SimpleLDAPObject.search_ext(self, str(base), scope, filterstr, attrlist, attrsonly, - serverctrls, clientctrls, timeout, sizelimit) + return ipa_values - def search_ext_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, - serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): - return SimpleLDAPObject.search_ext_s(self, str(base), scope, filterstr, attrlist, attrsonly, - serverctrls, clientctrls, timeout, sizelimit) + def convert_result(self, result): + ''' + result is a python-ldap result tuple of the form (dn, attrs), + where dn is a string containing the dn (distinguished name) of + the entry, and attrs is a dictionary containing the attributes + associated with the entry. The keys of attrs are strings, and + the associated values are lists of strings. - def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): - return SimpleLDAPObject.search_s(self, str(base), scope, filterstr, attrlist, attrsonly) + We convert the dn to a DN object. - def search_st(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, timeout=-1): - return SimpleLDAPObject.search_st(self, str(base), scope, filterstr, attrlist, attrsonly, timeout) + We convert every value associated with an attribute according + to it's syntax into the desired Python type. - def search_subschemasubentry_s(self, dn=''): - return SimpleLDAPObject.search_subschemasubentry_s(self, str(dn)) + returns a IPA result tuple of the same form as a python-ldap + result tuple except everything inside of the result tuple has + been converted to it's preferred IPA python type. + ''' -# universal LDAPError handler -def _handle_errors(e, **kw): - """ - Centralize error handling in one place. + ipa_result = [] + for dn_tuple in result: + original_dn = dn_tuple[0] + original_attrs = dn_tuple[1] - e is the error to be raised - **kw is an exception-specific list of options - """ - if not isinstance(e, _ldap.TIMEOUT): - desc = e.args[0]['desc'].strip() - info = e.args[0].get('info', '').strip() - else: - desc = '' - info = '' - - try: - # re-raise the error so we can handle it - raise e - except _ldap.NO_SUCH_OBJECT: - # args = kw.get('args', '') - # raise errors.NotFound(msg=notfound(args)) - raise errors.NotFound(reason='no such entry') - except _ldap.ALREADY_EXISTS: - raise errors.DuplicateEntry() - except _ldap.CONSTRAINT_VIOLATION: - # This error gets thrown by the uniqueness plugin - if info.startswith('Another entry with the same attribute value already exists'): - raise errors.DuplicateEntry() - else: - raise errors.DatabaseError(desc=desc, info=info) - except _ldap.INSUFFICIENT_ACCESS: - raise errors.ACIError(info=info) - except _ldap.INVALID_CREDENTIALS: - raise errors.ACIError(info="%s %s" % (info, desc)) - except _ldap.NO_SUCH_ATTRIBUTE: - # this is raised when a 'delete' attribute isn't found. - # it indicates the previous attribute was removed by another - # update, making the oldentry stale. - raise errors.MidairCollision() - except _ldap.INVALID_SYNTAX: - raise errors.InvalidSyntax(attr=info) - except _ldap.OBJECT_CLASS_VIOLATION: - raise errors.ObjectclassViolation(info=info) - except _ldap.ADMINLIMIT_EXCEEDED: - raise errors.LimitsExceeded() - except _ldap.SIZELIMIT_EXCEEDED: - raise errors.LimitsExceeded() - except _ldap.TIMELIMIT_EXCEEDED: - raise errors.LimitsExceeded() - except _ldap.NOT_ALLOWED_ON_RDN: - raise errors.NotAllowedOnRDN(attr=info) - except _ldap.FILTER_ERROR: - raise errors.BadSearchFilter(info=info) - except _ldap.SUCCESS: - pass - except _ldap.LDAPError, e: - if 'NOT_ALLOWED_TO_DELEGATE' in info: - raise errors.ACIError(info="KDC returned NOT_ALLOWED_TO_DELEGATE") - root_logger.info('Unhandled LDAPError: %s' % str(e)) - raise errors.DatabaseError(desc=desc, info=info) - - -def get_schema(url, conn=None): - """ - Perform global initialization when the module is loaded. + ipa_dn = DN(original_dn) + ipa_attrs = dict() - Retrieve the LDAP schema from the provided url and determine if - User-Private Groups (upg) are configured. + for attr, original_values in original_attrs.items(): + target_type = self._SYNTAX_MAPPING.get(self.get_syntax(attr), unicode_from_utf8) + ipa_attrs[attr.lower()] = self.convert_value_list(attr, target_type, original_values) - Bind using kerberos credentials. If in the context of the - in-tree "lite" server then use the current ccache. If in the context of - Apache then create a new ccache and bind using the Apache HTTP service - principal. + ipa_result.append(LDAPEntry(ipa_dn, ipa_attrs)) - If a connection is provided then it the credentials bound to it are - used. The connection is not closed when the request is done. - """ - tmpdir = None - has_conn = conn is not None + if _debug_log_ldap: + self.debug('ldap.result: %s', ipa_result) + return ipa_result - if ((not api.env.in_server or api.env.context not in ['lite', 'server']) - and conn is None): - # The schema is only needed on the server side - return None + #---------- python-ldap emulations ---------- - try: - if api.env.context == 'server' and conn is None: - try: - # Create a new credentials cache for this Apache process - tmpdir = tempfile.mkdtemp(prefix = "tmp-") - ccache_file = 'FILE:%s/ccache' % tmpdir - krbcontext = krbV.default_context() - principal = str('HTTP/%s@%s' % (api.env.host, api.env.realm)) - keytab = krbV.Keytab(name='/etc/httpd/conf/ipa.keytab', context=krbcontext) - principal = krbV.Principal(name=principal, context=krbcontext) - os.environ['KRB5CCNAME'] = ccache_file - ccache = krbV.CCache(name=ccache_file, context=krbcontext, primary_principal=principal) - ccache.init(principal) - ccache.init_creds_keytab(keytab=keytab, principal=principal) - except krbV.Krb5Error, e: - raise StandardError('Unable to retrieve LDAP schema. Error initializing principal %s in %s: %s' % (principal.name, '/etc/httpd/conf/ipa.keytab', str(e))) - - if conn is None: - conn = IPASimpleLDAPObject(url) - if url.startswith('ldapi://'): - conn.set_option(_ldap.OPT_HOST_NAME, api.env.host) - conn.sasl_interactive_bind_s('', SASL_AUTH) - - schema_entry = conn.search_s( - 'cn=schema', _ldap.SCOPE_BASE, - attrlist=['attributetypes', 'objectclasses'] - )[0] - if not has_conn: - conn.unbind_s() - except _ldap.SERVER_DOWN: - return None - except _ldap.LDAPError, e: - desc = e.args[0]['desc'].strip() - info = e.args[0].get('info', '').strip() - raise StandardError('Unable to retrieve LDAP schema: %s: %s' % (desc, info)) - except IndexError: - # no 'cn=schema' entry in LDAP? some servers use 'cn=subschema' - # TODO: DS uses 'cn=schema', support for other server? - # raise a more appropriate exception - raise - finally: - if tmpdir: - shutil.rmtree(tmpdir) - - return _ldap.schema.SubSchema(schema_entry[1]) - -# Global schema -_schema = None - -class ldap2(CrudBackend, Encoder): + def add(self, dn, modlist): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add(dn, modlist) + + def add_ext(self, dn, modlist, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add_ext(dn, modlist, serverctrls, clientctrls) + + def add_ext_s(self, dn, modlist, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add_ext_s(dn, modlist, serverctrls, clientctrls) + + def add_s(self, dn, modlist): + assert isinstance(dn, DN) + dn = str(dn) + modlist = self.encode(modlist) + return self.conn.add_s(dn, modlist) + + def bind(self, who, cred, method=_ldap.AUTH_SIMPLE): + self.flush_cached_schema() + if who is None: + who = DN() + assert isinstance(who, DN) + who = str(who) + cred = self.encode(cred) + return self.conn.bind(who, cred, method) + + def delete(self, dn): + assert isinstance(dn, DN) + dn = str(dn) + return self.conn.delete(dn) + + def delete_s(self, dn): + assert isinstance(dn, DN) + dn = str(dn) + return self.conn.delete_s(dn) + + def get_option(self, option): + return self.conn.get_option(option) + + def modify_s(self, dn, modlist): + assert isinstance(dn, DN) + dn = str(dn) + modlist = [(x[0], self.encode(x[1]), self.encode(x[2])) for x in modlist] + return self.conn.modify_s(dn, modlist) + + def modrdn_s(self, dn, newrdn, delold=1): + assert isinstance(dn, DN) + dn = str(dn) + assert isinstance(newrdn, (DN, RDN)) + newrdn = str(newrdn) + return self.conn.modrdn_s(dn, newrdn, delold) + + def passwd_s(self, dn, oldpw, newpw, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + oldpw = self.encode(oldpw) + newpw = self.encode(newpw) + return self.conn.passwd_s(dn, oldpw, newpw, serverctrls, clientctrls) + + def rename_s(self, dn, newrdn, newsuperior=None, delold=1, serverctrls=None, clientctrls=None): + assert isinstance(dn, DN) + dn = str(dn) + assert isinstance(newrdn, (DN, RDN)) + newrdn = str(newrdn) + return self.conn.rename_s(dn, newrdn, newsuperior, delold, serverctrls, clientctrls) + + def result(self, msgid=_ldap.RES_ANY, all=1, timeout=None): + resp_type, resp_data = self.conn.result(msgid, all, timeout) + resp_data = self.convert_result(resp_data) + return resp_type, resp_data + + def sasl_interactive_bind_s(self, who, auth, serverctrls=None, clientctrls=None, sasl_flags=_ldap.SASL_QUIET): + self.flush_cached_schema() + if who is None: + who = DN() + assert isinstance(who, DN) + who = str(who) + return self.conn.sasl_interactive_bind_s(who, auth, serverctrls, clientctrls, sasl_flags) + + def search(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + return self.conn.search(base, scope, filterstr, attrlist, attrsonly) + + def search_ext(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + + if _debug_log_ldap: + self.debug("ldap.search_ext: dn: %s\nfilter: %s\nattrs_list: %s", base, filterstr, attrlist) + + + return self.conn.search_ext(base, scope, filterstr, attrlist, attrsonly, serverctrls, clientctrls, timeout, sizelimit) + + def search_ext_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + ldap_result = self.conn.search_ext_s(base, scope, filterstr, attrlist, attrsonly, serverctrls, clientctrls, timeout, sizelimit) + ipa_result = self.convert_result(ldap_result) + return ipa_result + + def search_s(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + ldap_result = self.conn.search_s(base, scope, filterstr, attrlist, attrsonly) + ipa_result = self.convert_result(ldap_result) + return ipa_result + + def search_st(self, base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, timeout=-1): + assert isinstance(base, DN) + base = str(base) + filterstr = self.encode(filterstr) + attrlist = self.encode(attrlist) + ldap_result = self.conn.search_st(base, scope, filterstr, attrlist, attrsonly, timeout) + ipa_result = self.convert_result(ldap_result) + return ipa_result + + def set_option(self, option, invalue): + return self.conn.set_option(option, invalue) + + def simple_bind_s(self, who=None, cred='', serverctrls=None, clientctrls=None): + self.flush_cached_schema() + if who is None: + who = DN() + assert isinstance(who, DN) + who = str(who) + cred = self.encode(cred) + return self.conn.simple_bind_s(who, cred, serverctrls, clientctrls) + + def start_tls_s(self): + return self.conn.start_tls_s() + + def unbind(self): + self.flush_cached_schema() + return self.conn.unbind() + + def unbind_s(self): + self.flush_cached_schema() + return self.conn.unbind_s() + +class ldap2(CrudBackend): """ LDAP Backend Take 2. """ - # attribute syntax to python type mapping, 'SYNTAX OID': type - # everything not in this dict is considered human readable unicode - _SYNTAX_MAPPING = { - '1.3.6.1.4.1.1466.115.121.1.1': str, # ACI item - '1.3.6.1.4.1.1466.115.121.1.4': str, # Audio - '1.3.6.1.4.1.1466.115.121.1.5': str, # Binary - '1.3.6.1.4.1.1466.115.121.1.8': str, # Certificate - '1.3.6.1.4.1.1466.115.121.1.9': str, # Certificate List - '1.3.6.1.4.1.1466.115.121.1.10': str, # Certificate Pair - '1.3.6.1.4.1.1466.115.121.1.23': str, # Fax - '1.3.6.1.4.1.1466.115.121.1.28': str, # JPEG - '1.3.6.1.4.1.1466.115.121.1.40': str, # OctetString (same as Binary) - '1.3.6.1.4.1.1466.115.121.1.49': str, # Supported Algorithm - '1.3.6.1.4.1.1466.115.121.1.51': str, # Teletext Terminal Identifier - } - # attributes in this list cannot be deleted by update_entry # only MOD_REPLACE operations are generated for them _FORCE_REPLACE_ON_UPDATE_ATTRS = [] @@ -349,27 +644,19 @@ class ldap2(CrudBackend, Encoder): def __init__(self, shared_instance=True, ldap_uri=None, base_dn=None, schema=None): - global _schema + log_mgr.get_logger(self, True) CrudBackend.__init__(self, shared_instance=shared_instance) - Encoder.__init__(self) - self.encoder_settings.encode_dict_keys = True - self.encoder_settings.decode_dict_keys = True - self.encoder_settings.decode_dict_vals_postprocess = False - self.encoder_settings.decode_dict_vals_table = self._SYNTAX_MAPPING - self.encoder_settings.decode_dict_vals_table_keygen = self.get_syntax - self.encoder_settings.decode_postprocessor = lambda x: string.lower(x) try: self.ldap_uri = ldap_uri or api.env.ldap_uri except AttributeError: self.ldap_uri = 'ldap://example.com' try: if base_dn is not None: - self.base_dn = base_dn + self.base_dn = DN(base_dn) else: - self.base_dn = api.env.basedn + self.base_dn = DN(api.env.basedn) except AttributeError: - self.base_dn = '' - self.schema = schema or _schema + self.base_dn = DN() def __del__(self): if self.isconnected(): @@ -378,18 +665,83 @@ class ldap2(CrudBackend, Encoder): def __str__(self): return self.ldap_uri + def _get_schema(self): + return self.conn.schema + schema = property(_get_schema, None, None, 'schema associated with this LDAP server') + + # universal LDAPError handler + def handle_errors(self, e): + """ + Centralize error handling in one place. + + e is the error to be raised + """ + if not isinstance(e, _ldap.TIMEOUT): + desc = e.args[0]['desc'].strip() + info = e.args[0].get('info', '').strip() + else: + desc = '' + info = '' + + try: + # re-raise the error so we can handle it + raise e + except _ldap.NO_SUCH_OBJECT: + raise errors.NotFound(reason='no such entry') + except _ldap.ALREADY_EXISTS: + raise errors.DuplicateEntry() + except _ldap.CONSTRAINT_VIOLATION: + # This error gets thrown by the uniqueness plugin + if info.startswith('Another entry with the same attribute value already exists'): + raise errors.DuplicateEntry() + else: + raise errors.DatabaseError(desc=desc, info=info) + except _ldap.INSUFFICIENT_ACCESS: + raise errors.ACIError(info=info) + except _ldap.INVALID_CREDENTIALS: + raise errors.ACIError(info="%s %s" % (info, desc)) + except _ldap.NO_SUCH_ATTRIBUTE: + # this is raised when a 'delete' attribute isn't found. + # it indicates the previous attribute was removed by another + # update, making the oldentry stale. + raise errors.MidairCollision() + except _ldap.INVALID_SYNTAX: + raise errors.InvalidSyntax(attr=info) + except _ldap.OBJECT_CLASS_VIOLATION: + raise errors.ObjectclassViolation(info=info) + except _ldap.ADMINLIMIT_EXCEEDED: + raise errors.LimitsExceeded() + except _ldap.SIZELIMIT_EXCEEDED: + raise errors.LimitsExceeded() + except _ldap.TIMELIMIT_EXCEEDED: + raise errors.LimitsExceeded() + except _ldap.NOT_ALLOWED_ON_RDN: + raise errors.NotAllowedOnRDN(attr=info) + except _ldap.FILTER_ERROR: + raise errors.BadSearchFilter(info=info) + except _ldap.SUCCESS: + pass + except _ldap.LDAPError, e: + if 'NOT_ALLOWED_TO_DELEGATE' in info: + raise errors.ACIError(info="KDC returned NOT_ALLOWED_TO_DELEGATE") + self.info('Unhandled LDAPError: %s' % str(e)) + raise errors.DatabaseError(desc=desc, info=info) + def get_syntax(self, attr, value): - if not self.schema: - self.get_schema() + if self.schema is None: + return None obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) if obj is not None: return obj.syntax else: return None + def has_dn_syntax(self, attr): + return self.conn.has_dn_syntax(attr) + def get_allowed_attributes(self, objectclasses, raise_on_unknown=False): - if not self.schema: - self.get_schema() + if self.schema is None: + return None allowed_attributes = [] for oc in objectclasses: obj = self.schema.get_obj(_ldap.schema.ObjectClass, oc) @@ -408,13 +760,12 @@ class ldap2(CrudBackend, Encoder): If there is a problem loading the schema or the attribute is not in the schema return None """ - if not self.schema: - self.get_schema() + if self.schema is None: + return None obj = self.schema.get_obj(_ldap.schema.AttributeType, attr) return obj and obj.single_value - @encode_args(2, 3, 'bind_dn', 'bind_pw') - def create_connection(self, ccache=None, bind_dn='', bind_pw='', + def create_connection(self, ccache=None, bind_dn=None, bind_pw='', tls_cacertfile=None, tls_certfile=None, tls_keyfile=None, debug_level=0, autobind=False): """ @@ -433,7 +784,9 @@ class ldap2(CrudBackend, Encoder): Extends backend.Connectible.create_connection. """ - global _schema + if bind_dn is None: + bind_dn = DN() + assert isinstance(bind_dn, DN) if tls_cacertfile is not None: _ldap.set_option(_ldap.OPT_X_TLS_CACERTFILE, tls_cacertfile) if tls_certfile is not None: @@ -445,7 +798,7 @@ class ldap2(CrudBackend, Encoder): _ldap.set_option(_ldap.OPT_DEBUG_LEVEL, debug_level) try: - conn = _ldap.initialize(self.ldap_uri) + conn = IPASimpleLDAPObject(self.ldap_uri) if self.ldap_uri.startswith('ldapi://') and ccache: conn.set_option(_ldap.OPT_HOST_NAME, api.env.host) minssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MIN) @@ -459,7 +812,7 @@ class ldap2(CrudBackend, Encoder): conn.set_option(_ldap.OPT_X_SASL_SSF_MAX, minssf) if ccache is not None: os.environ['KRB5CCNAME'] = ccache - conn.sasl_interactive_bind_s('', SASL_AUTH) + conn.sasl_interactive_bind_s(None, SASL_AUTH) principal = krbV.CCache(name=ccache, context=krbV.default_context()).principal().name setattr(context, 'principal', principal) @@ -468,15 +821,13 @@ class ldap2(CrudBackend, Encoder): if autobind: pent = pwd.getpwuid(os.geteuid()) auth_tokens = _ldap.sasl.external(pent.pw_name) - conn.sasl_interactive_bind_s("", auth_tokens) + conn.sasl_interactive_bind_s(None, auth_tokens) else: conn.simple_bind_s(bind_dn, bind_pw) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) - if _schema: - object.__setattr__(self, 'schema', _schema) return conn def destroy_connection(self): @@ -489,55 +840,41 @@ class ldap2(CrudBackend, Encoder): def normalize_dn(self, dn): """ - Normalize distinguished name. + Normalize distinguished name by assuring it ends with + the base_dn. Note: You don't have to normalize DN's before passing them to ldap2 methods. It's done internally for you. """ - rdns = explode_dn(dn) - if rdns: - dn = ','.join(rdns) - if not dn.endswith(self.base_dn): - dn = '%s,%s' % (dn, self.base_dn) - return dn - return self.base_dn - - def get_container_rdn(self, name): - """Get relative distinguished name of cotainer.""" - env_container = 'container_%s' % name - if env_container in self.api.env: - return self.api.env[env_container] - return '' - def make_rdn_from_attr(self, attr, value): - """Make relative distinguished name from attribute.""" - if isinstance(value, (list, tuple)): - value = value[0] - attr = _ldap.dn.escape_dn_chars(attr) - value = _ldap.dn.escape_dn_chars(value) - return '%s=%s' % (attr, value) + assert isinstance(dn, DN) - def make_dn_from_rdn(self, rdn, parent_dn=''): - """ - Make distinguished name from relative distinguished name. + if not dn.endswith(self.base_dn): + # DN's are mutable, don't use in-place addtion (+=) which would + # modify the dn passed in with unintended side-effects. Addition + # returns a new DN object which is the concatenation of the two. + dn = dn + self.base_dn - Keyword arguments: - parent_dn -- DN of the parent entry (default '') - """ - parent_dn = self.normalize_dn(parent_dn) - return '%s,%s' % (rdn, parent_dn) + return dn - def make_dn_from_attr(self, attr, value, parent_dn=''): + def make_dn_from_attr(self, attr, value, parent_dn=None): """ Make distinguished name from attribute. Keyword arguments: parent_dn -- DN of the parent entry (default '') """ - rdn = self.make_rdn_from_attr(attr, value) - return self.make_dn_from_rdn(rdn, parent_dn) + if parent_dn is None: + parent_dn = DN() + assert isinstance(parent_dn, DN) + parent_dn = self.normalize_dn(parent_dn) + + if isinstance(value, (list, tuple)): + value = value[0] + + return DN((attr, value), parent_dn) - def make_dn(self, entry_attrs, primary_key='cn', parent_dn=''): + def make_dn(self, entry_attrs, primary_key='cn', parent_dn=None): """ Make distinguished name from entry attributes. @@ -545,23 +882,32 @@ class ldap2(CrudBackend, Encoder): primary_key -- attribute from which to make RDN (default 'cn') parent_dn -- DN of the parent entry (default '') """ + assert primary_key in entry_attrs - rdn = self.make_rdn_from_attr(primary_key, entry_attrs[primary_key]) - return self.make_dn_from_rdn(rdn, parent_dn) - @encode_args(1, 2) + if parent_dn is None: + parent_dn = DN() + + parent_dn = self.normalize_dn(parent_dn) + return DN((primary_key, entry_attrs[primary_key]), parent_dn) + def add_entry(self, dn, entry_attrs, normalize=True): """Create a new entry.""" + + assert isinstance(dn, DN) + if normalize: dn = self.normalize_dn(dn) - # remove all None values, python-ldap hates'em + # remove all None or [] values, python-ldap hates'em entry_attrs = dict( - (k, v) for (k, v) in entry_attrs.iteritems() if v + # FIXME, shouldn't these values be an error? + (k, v) for (k, v) in entry_attrs.iteritems() + if v is not None and v != [] ) try: self.conn.add_s(dn, list(entry_attrs.iteritems())) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) # generating filters for find_entry # some examples: @@ -580,7 +926,9 @@ class ldap2(CrudBackend, Encoder): Keyword arguments: rules -- see ldap2.make_filter """ + assert isinstance(filters, (list, tuple)) + filters = [f for f in filters if f] if filters and rules == self.MATCH_NONE: # unary operator return '(%s%s)' % (self.MATCH_NONE, @@ -598,7 +946,6 @@ class ldap2(CrudBackend, Encoder): flt = '%s)' % flt return flt - @encode_args(1, 2) def make_filter_from_attr(self, attr, value, rules='|', exact=True, leading_wildcard=True, trailing_wildcard=True): """ @@ -620,7 +967,7 @@ class ldap2(CrudBackend, Encoder): trailing_wildcard=trailing_wildcard) for v in value ] return self.combine_filters(flts, rules) elif value is not None: - value = _ldap_filter.escape_filter_chars(value) + value = _ldap_filter.escape_filter_chars(value_to_utf8(value)) if not exact: template = '%s' if leading_wildcard: @@ -671,9 +1018,7 @@ class ldap2(CrudBackend, Encoder): ) return self.combine_filters(flts, rules) - @encode_args(1, 2, 3) - @decode_retval() - def find_entries(self, filter=None, attrs_list=None, base_dn='', + def find_entries(self, filter=None, attrs_list=None, base_dn=None, scope=_ldap.SCOPE_SUBTREE, time_limit=None, size_limit=None, normalize=True, search_refs=False): """ @@ -691,6 +1036,9 @@ class ldap2(CrudBackend, Encoder): normalize -- normalize the DN (default True) search_refs -- allow search references to be returned (default skips these entries) """ + if base_dn is None: + base_dn = DN() + assert isinstance(base_dn, DN) if normalize: base_dn = self.normalize_dn(base_dn) if not filter: @@ -731,7 +1079,7 @@ class ldap2(CrudBackend, Encoder): _ldap.SIZELIMIT_EXCEEDED), e: truncated = True except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) if not res and not truncated: raise errors.NotFound(reason='no such entry') @@ -756,7 +1104,8 @@ class ldap2(CrudBackend, Encoder): del r[1]['memberOf'] else: continue - (direct, indirect) = self.get_memberof(r[0], memberof, time_limit=time_limit, size_limit=size_limit, normalize=normalize) + (direct, indirect) = self.get_memberof(r[0], memberof, time_limit=time_limit, + size_limit=size_limit, normalize=normalize) if len(direct) > 0: r[1]['memberof'] = direct if len(indirect) > 0: @@ -764,8 +1113,7 @@ class ldap2(CrudBackend, Encoder): return (res, truncated) - def find_entry_by_attr(self, attr, value, object_class, attrs_list=None, - base_dn=''): + def find_entry_by_attr(self, attr, value, object_class, attrs_list=None, base_dn=None): """ Find entry (dn, entry_attrs) by attribute and object class. @@ -773,6 +1121,11 @@ class ldap2(CrudBackend, Encoder): attrs_list - list of attributes to return, all if None (default None) base_dn - dn of the entry at which to start the search (default '') """ + + if base_dn is None: + base_dn = DN() + assert isinstance(base_dn, DN) + search_kw = {attr: value, 'objectClass': object_class} filter = self.make_filter(search_kw, rules=self.MATCH_ALL) (entries, truncated) = self.find_entries(filter, attrs_list, base_dn) @@ -793,6 +1146,9 @@ class ldap2(CrudBackend, Encoder): Keyword arguments: attrs_list - list of attributes to return, all if None (default None) """ + + assert isinstance(dn, DN) + (entry, truncated) = self.find_entries( None, attrs_list, dn, self.SCOPE_BASE, time_limit=time_limit, size_limit=size_limit, normalize=normalize @@ -805,7 +1161,12 @@ class ldap2(CrudBackend, Encoder): config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]} def get_ipa_config(self, attrs_list=None): """Returns the IPA configuration entry (dn, entry_attrs).""" - cdn = "%s,%s" % (api.Object.config.get_dn(), api.env.basedn) + + odn = api.Object.config.get_dn() + assert isinstance(odn, DN) + assert isinstance(api.env.basedn, DN) + cdn = DN(odn, api.env.basedn) + try: config_entry = getattr(context, 'config_entry') return (cdn, copy.deepcopy(config_entry)) @@ -828,35 +1189,17 @@ class ldap2(CrudBackend, Encoder): setattr(context, 'config_entry', copy.deepcopy(config_entry)) return (cdn, config_entry) - def get_schema(self, deepcopy=False): - """Returns either a reference to current schema or its deep copy""" - global _schema - if not _schema: - _schema = get_schema(self.ldap_uri, self.conn) - if not _schema: - raise errors.DatabaseError(desc='Unable to retrieve LDAP schema', info='Unable to proceed with request') - # explicitly use setattr here so the schema can be set after - # the object is finalized. - object.__setattr__(self, 'schema', _schema) - - if (deepcopy): - return copy.deepcopy(self.schema) - else: - return self.schema - def has_upg(self): """Returns True/False whether User-Private Groups are enabled. This is determined based on whether the UPG Template exists. """ - upg_dn = str(DN('cn=UPG Definition,cn=Definitions,cn=Managed Entries,cn=etc', api.env.basedn)) + upg_dn = DN(('cn', 'UPG Definition'), ('cn', 'Definitions'), ('cn', 'Managed Entries'), + ('cn', 'etc'), api.env.basedn) try: - upg_entry = self.conn.search_s( - upg_dn, - _ldap.SCOPE_BASE, - attrlist=['*'] - )[0] + upg_entry = self.conn.search_s(upg_dn, _ldap.SCOPE_BASE, + attrlist=['*'])[0] disable_attr = '(objectclass=disable)' if 'originfilter' in upg_entry[1]: org_filter = upg_entry[1]['originfilter'] @@ -866,27 +1209,32 @@ class ldap2(CrudBackend, Encoder): except _ldap.NO_SUCH_OBJECT, e: return False - @encode_args(1, 2) def get_effective_rights(self, dn, entry_attrs): """Returns the rights the currently bound user has for the given DN. Returns 2 attributes, the attributeLevelRights for the given list of attributes and the entryLevelRights for the entry itself. """ + + assert isinstance(dn, DN) + principal = getattr(context, 'principal') (binddn, attrs) = self.find_entry_by_attr("krbprincipalname", principal, "krbPrincipalAux") - sctrl = [GetEffectiveRightsControl(True, "dn: " + binddn.encode('UTF-8'))] + assert isinstance(binddn, DN) + sctrl = [GetEffectiveRightsControl(True, "dn: " + str(binddn))] self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, sctrl) (dn, attrs) = self.get_entry(dn, entry_attrs) # remove the control so subsequent operations don't include GER self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, []) return (dn, attrs) - @encode_args(1, 2) def can_write(self, dn, attr): """Returns True/False if the currently bound user has write permissions on the attribute. This only operates on a single attribute at a time. """ + + assert isinstance(dn, DN) + (dn, attrs) = self.get_effective_rights(dn, [attr]) if 'attributelevelrights' in attrs: attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8') @@ -896,11 +1244,12 @@ class ldap2(CrudBackend, Encoder): return False - @encode_args(1, 2) def can_read(self, dn, attr): """Returns True/False if the currently bound user has read permissions on the attribute. This only operates on a single attribute at a time. """ + assert isinstance(dn, DN) + (dn, attrs) = self.get_effective_rights(dn, [attr]) if 'attributelevelrights' in attrs: attr_rights = attrs.get('attributelevelrights')[0].decode('UTF-8') @@ -919,11 +1268,13 @@ class ldap2(CrudBackend, Encoder): # v - View the entry # - @encode_args(1) def can_delete(self, dn): """Returns True/False if the currently bound user has delete permissions on the entry. """ + + assert isinstance(dn, DN) + (dn, attrs) = self.get_effective_rights(dn, ["*"]) if 'entrylevelrights' in attrs: entry_rights = attrs['entrylevelrights'][0].decode('UTF-8') @@ -932,11 +1283,11 @@ class ldap2(CrudBackend, Encoder): return False - @encode_args(1) def can_add(self, dn): """Returns True/False if the currently bound user has add permissions on the entry. """ + assert isinstance(dn, DN) (dn, attrs) = self.get_effective_rights(dn, ["*"]) if 'entrylevelrights' in attrs: entry_rights = attrs['entrylevelrights'][0].decode('UTF-8') @@ -945,7 +1296,6 @@ class ldap2(CrudBackend, Encoder): return False - @encode_args(1, 2) def update_entry_rdn(self, dn, new_rdn, del_old=True): """ Update entry's relative distinguished name. @@ -953,22 +1303,25 @@ class ldap2(CrudBackend, Encoder): Keyword arguments: del_old -- delete old RDN value (default True) """ + + assert isinstance(dn, DN) + assert isinstance(new_rdn, RDN) + dn = self.normalize_dn(dn) - if dn.startswith(new_rdn + ","): + if dn[0] == new_rdn: raise errors.EmptyModlist() try: self.conn.rename_s(dn, new_rdn, delold=int(del_old)) time.sleep(.3) # Give memberOf plugin a chance to work except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) def _generate_modlist(self, dn, entry_attrs, normalize): + assert isinstance(dn, DN) + # get original entry (dn, entry_attrs_old) = self.get_entry(dn, entry_attrs.keys(), normalize=normalize) - # get_entry returns a decoded entry, encode it back - # we could call search_s directly, but this saves a lot of code at - # the expense of a little bit of performace - entry_attrs_old = self.encode(entry_attrs_old) + # generate modlist # for multi value attributes: no MOD_REPLACE to handle simultaneous # updates better @@ -1009,13 +1362,14 @@ class ldap2(CrudBackend, Encoder): return modlist - @encode_args(1, 2) def update_entry(self, dn, entry_attrs, normalize=True): """ Update entry's attributes. An attribute value set to None deletes all current values. """ + + assert isinstance(dn, DN) if normalize: dn = self.normalize_dn(dn) @@ -1028,37 +1382,40 @@ class ldap2(CrudBackend, Encoder): try: self.conn.modify_s(dn, modlist) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) - @encode_args(1) def delete_entry(self, dn, normalize=True): """Delete entry.""" + + assert isinstance(dn, DN) if normalize: dn = self.normalize_dn(dn) + try: self.conn.delete_s(dn) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) - @encode_args(1, 2, 3) def modify_password(self, dn, new_pass, old_pass=''): """Set user password.""" + + assert isinstance(dn, DN) dn = self.normalize_dn(dn) # The python-ldap passwd command doesn't verify the old password # so we'll do a simple bind to validate it. if old_pass != '': try: - conn = _ldap.initialize(self.ldap_uri) + conn = IPASimpleLDAPObject(self.ldap_uri) conn.simple_bind_s(dn, old_pass) conn.unbind() except _ldap.LDAPError, e: - _handle_errors(e, **{}) + self.handle_errors(e) try: self.conn.passwd_s(dn, old_pass, new_pass) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) def add_entry_to_group(self, dn, group_dn, member_attr='member', allow_same=False): """ @@ -1068,12 +1425,18 @@ class ldap2(CrudBackend, Encoder): Adding a group as a member of itself is not allowed unless allow_same is True. """ + + assert isinstance(dn, DN) + assert isinstance(group_dn, DN) + + self.debug("add_entry_to_group: dn=%s group_dn=%s member_attr=%s", dn, group_dn, member_attr) # check if the entry exists (dn, entry_attrs) = self.get_entry(dn, ['objectclass']) # get group entry (group_dn, group_entry_attrs) = self.get_entry(group_dn, [member_attr]) + self.debug("add_entry_to_group: group_entry_attrs=%s", group_entry_attrs) # check if we're not trying to add group into itself if dn == group_dn and not allow_same: raise errors.SameGroupError() @@ -1091,16 +1454,23 @@ class ldap2(CrudBackend, Encoder): def remove_entry_from_group(self, dn, group_dn, member_attr='member'): """Remove entry from group.""" + + assert isinstance(dn, DN) + assert isinstance(group_dn, DN) + + self.debug("remove_entry_from_group: dn=%s group_dn=%s member_attr=%s", dn, group_dn, member_attr) # get group entry (group_dn, group_entry_attrs) = self.get_entry(group_dn, [member_attr]) + self.debug("remove_entry_from_group: group_entry_attrs=%s", group_entry_attrs) # remove dn from group entry's `member_attr` attribute - members = [DN(m) for m in group_entry_attrs.get(member_attr, [])] + members = group_entry_attrs.get(member_attr, []) + assert all([isinstance(x, DN) for x in members]) try: - members.remove(DN(dn)) + members.remove(dn) except ValueError: raise errors.NotGroupMember() - group_entry_attrs[member_attr] = [str(m) for m in members] + group_entry_attrs[member_attr] = members # update group entry self.update_entry(group_dn, group_entry_attrs) @@ -1118,10 +1488,14 @@ class ldap2(CrudBackend, Encoder): Returns a list of DNs. """ + + assert isinstance(group_dn, DN) + if membertype not in [MEMBERS_ALL, MEMBERS_DIRECT, MEMBERS_INDIRECT]: return None - search_group_dn = _ldap_filter.escape_filter_chars(group_dn) + self.debug("get_members: group_dn=%s members=%s membertype=%s", group_dn, members, membertype) + search_group_dn = _ldap_filter.escape_filter_chars(str(group_dn)) searchfilter = "(memberof=%s)" % search_group_dn attr_list.append("member") @@ -1130,18 +1504,23 @@ class ldap2(CrudBackend, Encoder): results = [] if membertype == MEMBERS_ALL or membertype == MEMBERS_INDIRECT: - checkmembers = copy.deepcopy(members) - for member in checkmembers: + user_container_dn = DN(api.env.container_user, api.env.basedn) # FIXME, initialize once + host_container_dn = DN(api.env.container_host, api.env.basedn) + checkmembers = set(DN(x) for x in members) + checked = set() + while checkmembers: + member_dn = checkmembers.pop() + checked.add(member_dn) + # No need to check entry types that are not nested for # additional members - dn = DN(member) - if dn.endswith(DN(api.env.container_user, api.env.basedn)) or \ - dn.endswith(DN(api.env.container_host, api.env.basedn)): - results.append([member, {}]) + if member_dn.endswith(user_container_dn) or \ + member_dn.endswith(host_container_dn): + results.append([member_dn, {}]) continue try: (result, truncated) = self.find_entries(searchfilter, - attr_list, member, time_limit=time_limit, + attr_list, member_dn, time_limit=time_limit, size_limit=size_limit, scope=_ldap.SCOPE_BASE, normalize=normalize) if truncated: @@ -1150,8 +1529,8 @@ class ldap2(CrudBackend, Encoder): for m in result[0][1].get('member', []): # This member may contain other members, add it to our # candidate list - if m not in checkmembers: - checkmembers.append(m) + if m not in checked: + checkmembers.add(m) except errors.NotFound: pass @@ -1164,21 +1543,18 @@ class ldap2(CrudBackend, Encoder): (dn, group) = self.get_entry(group_dn, ['dn', 'member'], size_limit=size_limit, time_limit=time_limit) - real_members = group.get('member') - if isinstance(real_members, basestring): - real_members = [real_members] - if real_members is None: - real_members = [] + real_members = group.get('member', []) entries = [] for e in results: - if unicode(e[0]) not in real_members and unicode(e[0]) not in entries: + if e[0] not in real_members and e[0] not in entries: if membertype == MEMBERS_INDIRECT: entries.append(e[0]) else: if membertype == MEMBERS_DIRECT: entries.append(e[0]) + self.debug("get_members: result=%s", entries) return entries def get_memberof(self, entry_dn, memberof, time_limit=None, size_limit=None, normalize=True): @@ -1192,12 +1568,15 @@ class ldap2(CrudBackend, Encoder): Returns two memberof lists: (direct, indirect) """ + assert isinstance(entry_dn, DN) + + self.debug("get_memberof: entry_dn=%s memberof=%s", entry_dn, memberof) if not type(memberof) in (list, tuple): return ([], []) if len(memberof) == 0: return ([], []) - search_entry_dn = _ldap_filter.escape_filter_chars(entry_dn) + search_entry_dn = _ldap_filter.escape_filter_chars(str(entry_dn)) attr_list = ["dn", "memberof"] searchfilter = "(|(member=%s)(memberhost=%s)(memberuser=%s))" % ( search_entry_dn, search_entry_dn, search_entry_dn) @@ -1207,6 +1586,7 @@ class ldap2(CrudBackend, Encoder): results = [] for group in memberof: + assert isinstance(group, DN) try: (result, truncated) = self.find_entries(searchfilter, attr_list, group, time_limit=time_limit,size_limit=size_limit, @@ -1219,20 +1599,24 @@ class ldap2(CrudBackend, Encoder): # If there is an exception here, it is likely due to a failure in # referential integrity. All members should have corresponding # memberOf entries. - indirect = [ m.lower() for m in memberof ] + indirect = list(memberof) for r in results: direct.append(r[0]) try: - indirect.remove(r[0].lower()) + indirect.remove(r[0]) except ValueError, e: - root_logger.info('Failed to remove indirect entry %s from %s' % r[0], entry_dn) + self.info('Failed to remove indirect entry %s from %s' % r[0], entry_dn) raise e + self.debug("get_memberof: result direct=%s indirect=%s", direct, indirect) return (direct, indirect) def set_entry_active(self, dn, active): """Mark entry active/inactive.""" + + assert isinstance(dn, DN) assert isinstance(active, bool) + # get the entry in question (dn, entry_attrs) = self.get_entry(dn, ['nsaccountlock']) @@ -1255,15 +1639,20 @@ class ldap2(CrudBackend, Encoder): def activate_entry(self, dn): """Mark entry active.""" + + assert isinstance(dn, DN) self.set_entry_active(dn, True) def deactivate_entry(self, dn): """Mark entry inactive.""" + + assert isinstance(dn, DN) self.set_entry_active(dn, False) def remove_principal_key(self, dn): """Remove a kerberos principal key.""" + assert isinstance(dn, DN) dn = self.normalize_dn(dn) # We need to do this directly using the LDAP library because we @@ -1275,11 +1664,14 @@ class ldap2(CrudBackend, Encoder): try: self.conn.modify_s(dn, mod) except _ldap.LDAPError, e: - _handle_errors(e) + self.handle_errors(e) # CrudBackend methods def _get_normalized_entry_for_crud(self, dn, attrs_list=None): + + assert isinstance(dn, DN) + (dn, entry_attrs) = self.get_entry(dn, attrs_list) entry_attrs['dn'] = dn return entry_attrs @@ -1292,6 +1684,7 @@ class ldap2(CrudBackend, Encoder): """ assert 'dn' in kw dn = kw['dn'] + assert isinstance(dn, DN) del kw['dn'] self.add_entry(dn, kw) return self._get_normalized_entry_for_crud(dn) @@ -1337,7 +1730,8 @@ class ldap2(CrudBackend, Encoder): # get keyword arguments filter = kw.pop('filter', None) attrs_list = kw.pop('attrs_list', None) - base_dn = kw.pop('base_dn', '') + base_dn = kw.pop('base_dn', DN()) + assert isinstance(base_dn, DN) scope = kw.pop('scope', self.SCOPE_SUBTREE) # generate filter @@ -1363,4 +1757,3 @@ class ldap2(CrudBackend, Encoder): return (len(output), output) api.register(ldap2) - diff --git a/ipaserver/plugins/selfsign.py b/ipaserver/plugins/selfsign.py index bbf8fa78a..09ed04f49 100644 --- a/ipaserver/plugins/selfsign.py +++ b/ipaserver/plugins/selfsign.py @@ -39,6 +39,7 @@ from ipalib import Backend from ipalib import errors from ipalib import x509 from ipalib import pkcs10 +from ipapython.dn import DN, EditableDN, RDN from ipapython.certdb import get_ca_nickname import subprocess import os @@ -86,16 +87,14 @@ class ra(rabase.rabase): """ try: config = api.Command['config_show']()['result'] - subject_base = config.get('ipacertificatesubjectbase')[0] + subject_base = EditableDN(config.get('ipacertificatesubjectbase')[0]) hostname = get_csr_hostname(csr) - base = re.split(',\s*(?=\w+=)', subject_base) - base.insert(0,'CN=%s' % hostname) - subject_base = ",".join(base) + subject_base.insert(0, RDN(('CN', hostname))) request = pkcs10.load_certificate_request(csr) # python-nss normalizes the request subject - request_subject = str(pkcs10.get_subject(request)) + request_subject = DN(pkcs10.get_subject(request)) - if str(subject_base).lower() != request_subject.lower(): + if subject_base != request_subject: raise errors.CertificateOperationError(error=_('Request subject "%(request_subject)s" does not match the form "%(subject_base)s"') % \ {'request_subject' : request_subject, 'subject_base' : subject_base}) except errors.CertificateOperationError, e: diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index c770290f1..f7b71b32f 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -32,7 +32,7 @@ from ipalib.errors import PublicError, InternalError, CommandError, JSONError, C from ipalib.request import context, Connection, destroy_context from ipalib.rpc import xml_dumps, xml_loads from ipalib.util import parse_time_duration -from ipalib.dn import DN +from ipapython.dn import DN from ipaserver.plugins.ldap2 import ldap2 from ipapython.compat import json from ipalib.session import session_mgr, AuthManager, get_ipa_ccache_name, load_ccache_data, bind_ipa_ccache, release_ipa_ccache, fmt_time, default_max_session_duration @@ -418,29 +418,17 @@ def json_encode_binary(val): if isinstance(val, dict): new_dict = {} for k,v in val.items(): - if isinstance(v, str): - new_dict[k] = {'__base64__' : base64.b64encode(v)} - else: - new_dict[k] = json_encode_binary(v) - del val + new_dict[k] = json_encode_binary(v) return new_dict elif isinstance(val, (list, tuple)): - new_list = [] - n = len(val) - i = 0 - while i < n: - v = val[i] - if isinstance(v, str): - new_list.append({'__base64__' : base64.b64encode(v)}) - else: - new_list.append(json_encode_binary(v)) - i += 1 - del val + new_list = [json_encode_binary(v) for v in val] return new_list elif isinstance(val, str): return {'__base64__' : base64.b64encode(val)} elif isinstance(val, Decimal): return {'__base64__' : base64.b64encode(str(val))} + elif isinstance(val, DN): + return str(val) else: return val @@ -474,7 +462,6 @@ def json_decode_binary(val): new_dict[k] = base64.b64decode(v['__base64__']) else: new_dict[k] = json_decode_binary(v) - del val return new_dict elif isinstance(val, list): new_list = [] @@ -488,7 +475,6 @@ def json_decode_binary(val): else: new_list.append(json_decode_binary(v)) i += 1 - del val return new_list else: if isinstance(val, basestring): @@ -963,9 +949,9 @@ class login_password(Backend, KerberosSession, HTTP_Status): # Ok, now why is this bad. Is the password simply bad or is the # password expired? try: - dn = str(DN(('uid', user), - self.api.env.container_user, - self.api.env.basedn)) + dn = DN(('uid', user), + self.api.env.container_user, + self.api.env.basedn) conn = ldap2(shared_instance=False, ldap_uri=self.api.env.ldap_uri) conn.connect(bind_dn=dn, bind_pw=password) @@ -1059,8 +1045,8 @@ class change_password(Backend, HTTP_Status): result = 'error' policy_error = None - bind_dn = str(DN((self.api.Object.user.primary_key.name, data['user']), - self.api.env.container_user, self.api.env.basedn)) + bind_dn = DN((self.api.Object.user.primary_key.name, data['user']), + self.api.env.container_user, self.api.env.basedn) try: conn = ldap2(shared_instance=False, |