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