diff options
Diffstat (limited to 'ipaserver/install')
-rw-r--r-- | ipaserver/install/adtrustinstance.py | 63 | ||||
-rw-r--r-- | ipaserver/install/bindinstance.py | 28 | ||||
-rw-r--r-- | ipaserver/install/cainstance.py | 64 | ||||
-rw-r--r-- | ipaserver/install/certs.py | 26 | ||||
-rw-r--r-- | ipaserver/install/dsinstance.py | 42 | ||||
-rw-r--r-- | ipaserver/install/httpinstance.py | 2 | ||||
-rw-r--r-- | ipaserver/install/installutils.py | 5 | ||||
-rw-r--r-- | ipaserver/install/ipa_ldap_updater.py | 2 | ||||
-rw-r--r-- | ipaserver/install/krbinstance.py | 23 | ||||
-rw-r--r-- | ipaserver/install/ldapupdate.py | 688 | ||||
-rw-r--r-- | ipaserver/install/plugins/adtrust.py | 16 | ||||
-rw-r--r-- | ipaserver/install/plugins/dns.py | 41 | ||||
-rw-r--r-- | ipaserver/install/plugins/fix_replica_memberof.py | 4 | ||||
-rw-r--r-- | ipaserver/install/plugins/rename_managed.py | 159 | ||||
-rw-r--r-- | ipaserver/install/plugins/updateclient.py | 23 | ||||
-rw-r--r-- | ipaserver/install/replication.py | 204 | ||||
-rw-r--r-- | ipaserver/install/service.py | 26 |
17 files changed, 748 insertions, 668 deletions
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() |