Submitted By: Mario Fetka (mario-fetka at gmx dot at) Date: 2008-06-14 Initial Package Version: 2.2.0 Origin: Ticket #144 Upstream Status: Accepted Description: Kerberized accounts in ou=People diff -Naur mmc-agent-2.3.1.orig/conf/plugins/kerberos.ini mmc-agent-2.3.1/conf/plugins/kerberos.ini --- mmc-agent-2.3.1.orig/conf/plugins/kerberos.ini 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.1/conf/plugins/kerberos.ini 2008-05-17 13:04:35.000000000 +0000 @@ -0,0 +1,5 @@ +[main] +disable = 0 + +[kerberos] +realm = EXAMPLE.COM diff -Naur mmc-agent-2.3.1.orig/mmc/plugins/base/__init__.py mmc-agent-2.3.1/mmc/plugins/base/__init__.py --- mmc-agent-2.3.1.orig/mmc/plugins/base/__init__.py 2008-04-29 15:15:48.000000000 +0000 +++ mmc-agent-2.3.1/mmc/plugins/base/__init__.py 2008-06-14 09:22:59.000000000 +0000 @@ -1229,6 +1229,10 @@ attrs = [] attrib = self.l.search_s(dn, ldap.SCOPE_BASE) c, attrs = attrib[0] + # kerberos -> remove binary key from attrs + try: attrs.pop('krb5Key') + except: pass + # newattrs = copy.deepcopy(attrs) return newattrs @@ -1249,6 +1253,10 @@ attrib = self.l.search_s(cn, ldap.SCOPE_BASE) c,attrs=attrib[0] + # kerberos -> remove binary key from attrs + try: attrs.pop('krb5Key') + except: pass + # newattrs = copy.deepcopy(attrs) @@ -1402,8 +1410,13 @@ @rtype: list """ if not base: base = self.baseUsersDN - if (pattern==''): searchFilter = "uid=*" - else: searchFilter = pattern + # kerberos -> search only PosixAccount + if (pattern==''): searchFilter = "(&(objectClass=posixAccount)(uid=*))" + elif pattern[0] == '(': + searchFilter = "(&(objectClass=posixAccount)%s)" % (pattern) + else: + searchFilter = "(&(objectClass=posixAccount)(%s))" % (pattern) + # monoattrs = ["uid", "sn", "givenName", "mail"] result_set = self.search(searchFilter, base, monoattrs + ["telephoneNumber", "loginShell", "objectClass"], ldap.SCOPE_ONELEVEL) diff -Naur mmc-agent-2.3.1.orig/mmc/plugins/kerberos/__init__.py mmc-agent-2.3.1/mmc/plugins/kerberos/__init__.py --- mmc-agent-2.3.1.orig/mmc/plugins/kerberos/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.1/mmc/plugins/kerberos/__init__.py 2007-11-21 09:56:21.000000000 +0000 @@ -0,0 +1,180 @@ +# -*- coding: utf-8; -*- +# +# (c) 2004-2007 Linbox / Free&ALter Soft, http://linbox.com +# (c) 2007 Mandriva, http://www.mandriva.com/ +# (c) 2007 Kids-und-Co g.e.V http://www.kids-und-co.de +# +# $Id: __init__.py 108 2007-11-21 09:56:21Z iosifb $ +# +# This file is part of Mandriva Management Console (MMC). +# +# MMC 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 2 of the License, or +# (at your option) any later version. +# +# MMC 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 MMC; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging +import ldap.modlist +import copy +from mmc.plugins.base import ldapUserGroupControl +import xmlrpclib +from mmc.support.errorObj import errorMessage +from mmc.support.mmcException import * +from mmc.support import mmctools +import mmc.plugins.base +from mmc.support.config import * +from mmc.plugins.base import ldapUserGroupControl + +VERSION = "2.3.1" +APIVERSION = "4:2:0" +REVISION = int("$Rev: 108 $".split(':')[1].strip(' $')) + +def getVersion(): return VERSION +def getApiVersion(): return APIVERSION +def getRevision(): return REVISION + +def activate(): + """ + this function define if the module "base" can be activated. + @return: return True if this module can be activate + @rtype: boolean + """ + config = KerberosConfig("kerberos") + logger = logging.getLogger() + + if config.disabled: + logger.info("Kerberos plugin disabled by configuration.") + return False + + try: + ldapObj = ldapUserGroupControl() + except ldap.INVALID_CREDENTIALS: + logger.error("Can't bind to LDAP: invalid credentials.") + return False + + # Test if the Kerberos LDAP schema is available in the directory + try: + schema = ldapObj.getSchema("krb5KDCEntry") + if len(schema) <= 0: + logger.error("Kerberos schema is not included in LDAP directory"); + return False + except: + logger.exception("invalid schema") + return False + try: + schema = ldapObj.getSchema("krb5Principal") + if len(schema) <= 0: + logger.error("Kerberos schema is not included in LDAP directory"); + return False + except: + logger.exception("invalid schema") + return False + """ + TODO: Check kerberos database + """ + return True + +def isKrbUser(uid): + return kerberosLdapControl().isKerberosUser(uid) + +def addKrbAttr(uid,password): + return kerberosLdapControl().addKerberosAttr(uid,password) + +def delKrbAttr(uid,password): + return kerberosLdapControl().delKerberosAttr(uid,password) + +def changePassword(uid,password): + return kerberosLdapControl().changePassword(uid, password) + +class KerberosConfig(PluginConfig): + def __init__(self,name, conffile = None): + PluginConfig.__init__(self,name, conffile = None) + self.setDefault() + self.readConfig() + + def readConfig(self): + PluginConfig.readConf(self) + self.realm = self.get("kerberos", "realm") + +class kerberosLdapControl(mmc.plugins.base.ldapUserGroupControl): + + def __init__(self, conffile = None, conffilebase = None): + mmc.plugins.base.ldapUserGroupControl.__init__(self, conffilebase) + self.configKerberos = KerberosConfig("kerberos", conffile) + self.realm = self.configKerberos.realm + + def delKerberosAttr(self,uid,password): + # If the password has been encoded in the XML-RPC stream, decode it + if isinstance(password, xmlrpclib.Binary): + password = str(password) + dn = 'uid=' + uid + ',' + self.baseUsersDN + s = self.l.search_s(dn, ldap.SCOPE_BASE) + c, attr = s[0] + old = {} + new = {} + for key in attr.keys(): old[key.lower()] = attr[key] + new = copy.deepcopy(old) + # remove krb attributes + new.pop('krb5kdcflags') + new.pop('krb5principalname') + new.pop('krb5keyversionnumber') + try: new.pop('krb5key') + except KeyError: pass + newobjclasses = () + for s in new.pop('objectclass'): + if s[0:4] == 'krb5': pass + else: newobjclasses = newobjclasses + (s,) + new['objectclass'] = newobjclasses + modlist = ldap.modlist.modifyModlist(old, new) + #logger = logging.getLogger() + #logger.debug(newobjclasses) + #logger.debug(modlist) + self.l.modify_s(dn, modlist) + mmc.plugins.base.ldapUserGroupControl.changeUserPasswd(self,uid,password) + return True + + def addKerberosAttr(self,uid,password): + # If the password has been encoded in the XML-RPC stream, decode it + if isinstance(password, xmlrpclib.Binary): + password = str(password) + dn = 'uid=' + uid + ',' + self.baseUsersDN + s = self.l.search_s(dn, ldap.SCOPE_BASE) + c, attr = s[0] + old = {} + new = {} + for key in attr.keys(): old[key.lower()] = attr[key] + new = copy.deepcopy(old) + new['objectclass'] = new['objectclass'] + ['krb5Principal','krb5KDCEntry'] + new['userpassword'] = ['{K5Key}'] + new['krb5kdcflags'] = ['126'] + new['krb5keyversionnumber'] = ['0'] + new['krb5principalname'] = [uid+'@' +self.realm] + modlist = ldap.modlist.modifyModlist(old, new) + self.l.modify_s(dn, modlist) + self.l.passwd_s(dn, None, password) + return True + + def isKerberosUser(self,uid): + ret = False + if self.existUser(uid): ret = "krb5Principal" in self.getDetailedUser(uid)["objectClass"] + return ret + + def changePassword(self,uid,password): + # If the password has been encoded in the XML-RPC stream, decode it + if isinstance(password, xmlrpclib.Binary): + password = str(password) + dn = 'uid=' + uid + ',' + self.baseUsersDN + self.l.modify_s(dn, [(ldap.MOD_REPLACE,'userPassWord','{K5KEY}')]) + self.l.passwd_s(dn, None, password) + return True + + diff -Naur mmc-agent-2.3.1.orig/plugins_base.diff mmc-agent-2.3.1/plugins_base.diff --- mmc-agent-2.3.1.orig/plugins_base.diff 1970-01-01 00:00:00.000000000 +0000 +++ mmc-agent-2.3.1/plugins_base.diff 2008-05-17 13:04:04.000000000 +0000 @@ -0,0 +1,40 @@ +--- mds-orig/mmc-agent/mmc/plugins/base/__init__.py 2007-11-21 10:57:03.000000000 +0100 ++++ kerberos_plugin/trunk/mmc-agent/mmc/plugins/base/__init__.py 2007-11-21 11:34:36.000000000 +0100 +@@ -1207,6 +1207,10 @@ + attrs = [] + attrib = self.l.search_s(dn, ldap.SCOPE_BASE) + c, attrs = attrib[0] ++ # kerberos -> remove binary key from attrs ++ try: attrs.pop('krb5Key') ++ except: pass ++ # + newattrs = copy.deepcopy(attrs) + return newattrs + +@@ -1227,6 +1231,10 @@ + attrib = self.l.search_s(cn, ldap.SCOPE_BASE) + + c,attrs=attrib[0] ++ # kerberos -> remove binary key from attrs ++ try: attrs.pop('krb5Key') ++ except: pass ++ # + + newattrs = copy.deepcopy(attrs) + +@@ -1380,8 +1388,13 @@ + @rtype: list + """ + if not base: base = self.baseUsersDN +- if (pattern==''): searchFilter = "uid=*" +- else: searchFilter = pattern ++ # kerberos -> search only PosixAccount ++ if (pattern==''): searchFilter = "(&(objectClass=posixAccount)(uid=*))" ++ elif pattern[0] == '(': ++ searchFilter = "(&(objectClass=posixAccount)%s)" % (pattern) ++ else: ++ searchFilter = "(&(objectClass=posixAccount)(%s))" % (pattern) ++ # + monoattrs = ["uid", "sn", "givenName", "mail"] + result_set = self.search(searchFilter, base, monoattrs + ["telephoneNumber", "loginShell", "objectClass"], ldap.SCOPE_ONELEVEL) + diff -Naur mmc-agent-2.3.1.orig/setup.py mmc-agent-2.3.1/setup.py --- mmc-agent-2.3.1.orig/setup.py 2007-09-10 08:20:59.000000000 +0000 +++ mmc-agent-2.3.1/setup.py 2008-06-14 09:24:15.000000000 +0000 @@ -8,5 +8,5 @@ author_email = "cdelfosse@mandriva.com", maintainer = "Cedric Delfosse", maintainer_email = "cdelfosse@mandriva.com", - packages = ["mmc", "mmc.support", "mmc.plugins", "mmc.plugins.base", "mmc.plugins.samba", "mmc.plugins.proxy", "mmc.plugins.mail", "mmc.plugins.network"], + packages = ["mmc", "mmc.support", "mmc.plugins", "mmc.plugins.base", "mmc.plugins.samba", "mmc.plugins.proxy", "mmc.plugins.mail", "mmc.plugins.network", "mmc.plugins.kerberos"], ) diff -Naur mmc-agent-2.3.1.orig/contrib/ldap/krb5-kdc.schema mmc-agent-2.3.1/contrib/ldap/krb5-kdc.schema --- mmc-agent-2.3.1.orig/contrib/ldap/krb5-kdc.schema +++ mmc-agent-2.3.1/contrib/ldap/krb5-kdc.schema @@ -0,0 +1,136 @@ +# $Id: krb5-kdc.schema,v 1.1 2004-03-22 17:25:05 quanah Exp $ +# Definitions for a Kerberos V KDC schema + +# OID Base is iso(1) org(3) dod(6) internet(1) private(4) enterprise(1) padl(5322) kdcSchema(10) +# +# Syntaxes are under 1.3.6.1.4.1.5322.10.0 +# Attributes types are under 1.3.6.1.4.1.5322.10.1 +# Object classes are under 1.3.6.1.4.1.5322.10.2 + +# Syntax definitions + +#krb5KDCFlagsSyntax SYNTAX ::= { +# WITH SYNTAX INTEGER +#-- initial(0), -- require as-req +#-- forwardable(1), -- may issue forwardable +#-- proxiable(2), -- may issue proxiable +#-- renewable(3), -- may issue renewable +#-- postdate(4), -- may issue postdatable +#-- server(5), -- may be server +#-- client(6), -- may be client +#-- invalid(7), -- entry is invalid +#-- require-preauth(8), -- must use preauth +#-- change-pw(9), -- change password service +#-- require-hwauth(10), -- must use hwauth +#-- ok-as-delegate(11), -- as in TicketFlags +#-- user-to-user(12), -- may use user-to-user auth +#-- immutable(13) -- may not be deleted +# ID { 1.3.6.1.4.1.5322.10.0.1 } +#} + +#krb5PrincipalNameSyntax SYNTAX ::= { +# WITH SYNTAX OCTET STRING +#-- String representations of distinguished names as per RFC1510 +# ID { 1.3.6.1.4.1.5322.10.0.2 } +#} + +# Attribute type definitions + +attributetype ( 1.3.6.1.4.1.5322.10.1.1 + NAME 'krb5PrincipalName' + DESC 'The unparsed Kerberos principal name' + EQUALITY caseExactIA5Match + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.2 + NAME 'krb5KeyVersionNumber' + EQUALITY integerMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.3 + NAME 'krb5MaxLife' + EQUALITY integerMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.4 + NAME 'krb5MaxRenew' + EQUALITY integerMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.5 + NAME 'krb5KDCFlags' + EQUALITY integerMatch + SINGLE-VALUE + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.6 + NAME 'krb5EncryptionType' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.7 + NAME 'krb5ValidStart' + EQUALITY generalizedTimeMatch + ORDERING generalizedTimeOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.8 + NAME 'krb5ValidEnd' + EQUALITY generalizedTimeMatch + ORDERING generalizedTimeOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.9 + NAME 'krb5PasswordEnd' + EQUALITY generalizedTimeMatch + ORDERING generalizedTimeOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 + SINGLE-VALUE ) + +# this is temporary; keys will eventually +# be child entries or compound attributes. +attributetype ( 1.3.6.1.4.1.5322.10.1.10 + NAME 'krb5Key' + DESC 'Encoded ASN1 Key as an octet string' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.5 ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.11 + NAME 'krb5PrincipalRealm' + DESC 'Distinguished name of krb5Realm entry' + SUP distinguishedName ) + +attributetype ( 1.3.6.1.4.1.5322.10.1.12 + NAME 'krb5RealmName' + EQUALITY octetStringMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{128} ) + +# Object class definitions + +objectclass ( 1.3.6.1.4.1.5322.10.2.1 + NAME 'krb5Principal' + SUP top + AUXILIARY + MUST ( krb5PrincipalName ) + MAY ( cn $ krb5PrincipalRealm ) ) + +objectclass ( 1.3.6.1.4.1.5322.10.2.2 + NAME 'krb5KDCEntry' + SUP krb5Principal + AUXILIARY + MUST ( krb5KeyVersionNumber ) + MAY ( krb5ValidStart $ krb5ValidEnd $ krb5PasswordEnd $ + krb5MaxLife $ krb5MaxRenew $ krb5KDCFlags $ + krb5EncryptionType $ krb5Key ) ) + +objectclass ( 1.3.6.1.4.1.5322.10.2.3 + NAME 'krb5Realm' + SUP top + AUXILIARY + MUST ( krb5RealmName ) ) +