diff -rupN a/source4/scripting/python/samba/netcmd/domain.py b/source4/scripting/python/samba/netcmd/domain.py --- a/source4/scripting/python/samba/netcmd/domain.py 2012-11-13 09:03:39.000000000 +0100 +++ b/source4/scripting/python/samba/netcmd/domain.py 2012-12-10 09:48:11.660203662 +0100 @@ -1218,6 +1218,8 @@ class cmd_domain_classicupgrade(Command) help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default="auto"), Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)", action="store_true"), + Option("--no-upn", + help="Define if we shouldn't add attribute userPrincipalName to the user accounts", action="store_true"), Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND", choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"], help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " @@ -1231,7 +1233,7 @@ class cmd_domain_classicupgrade(Command) def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None, quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None, - dns_backend=None, use_ntvfs=False): + dns_backend=None, use_ntvfs=False, no_upn=False): if not os.path.exists(smbconf): raise CommandError("File %s does not exist" % smbconf) @@ -1268,6 +1270,8 @@ class cmd_domain_classicupgrade(Command) if not os.path.isdir(targetdir): os.mkdir(targetdir) + logger.warning("UPN: %s" % no_upn) + eadb = True if use_xattrs == "yes": eadb = False @@ -1315,7 +1319,7 @@ class cmd_domain_classicupgrade(Command) logger.info("Provisioning") upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(), - useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs) + useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs, no_upn=no_upn) class cmd_domain_samba3upgrade(cmd_domain_classicupgrade): diff -rupN a/source4/scripting/python/samba/netcmd/upn.py b/source4/scripting/python/samba/netcmd/upn.py --- a/source4/scripting/python/samba/netcmd/upn.py 1970-01-01 01:00:00.000000000 +0100 +++ b/source4/scripting/python/samba/netcmd/upn.py 2012-12-10 10:56:50.339078809 +0100 @@ -0,0 +1,208 @@ +# userPrincipal management for accounts +# Copyright Alejandro Escanero Blanco aescanero@gmail.com 2012 +# +# 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 . +# + +import samba.getopt as options +import ldb +from samba import provision +from samba.samdb import SamDB +from samba.auth import system_session +from samba.netcmd.common import _get_user_realm_domain +from samba.netcmd import ( + Command, + CommandError, + SuperCommand, + Option + ) + + +class cmd_upn_show(Command): + """List userPrincipalName of a user account. + +The user can either be specified by their sAMAccountName or using the --filter option. + +The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command on a remote server. + +Example1: +samba-tool user upn show User1 + +Example1 shows how to show the userPrincipalname attributes of an account. The username or sAMAccountName is specified using the --filter= paramter and the username in this example is User1. + +""" + + synopsis = "%prog [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + Option("--filter", help="LDAP Filter to set upn on", type=str), + ] + + takes_args = ["username"] + + def run(self, username=None, sambaopts=None, credopts=None, + versionopts=None, H=None, filter=None): + if username is None and filter is None: + raise CommandError("Either the username or '--filter' must be specified!") + + if filter is None: + filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username)) + + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + domain_dn = samdb.domain_dn() + res = samdb.search(domain_dn, scope=ldb.SCOPE_SUBTREE, + expression=filter, + attrs=["userPrincipalName"]) + + if (len(res) == 0): + raise CommandError("Failed to find user '%s': %s" % (username or filter, msg)) + + if "userPrincipalName" in res[0]: + self.outf.write("%s\n" % res[0]["userPrincipalName"]) + +class cmd_upn_set(Command): + """Add the userPrincipalName to a user account. + +The user can either be specified by their sAMAccountName or using the --filter option. + +The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command on a remote server. + +Example1: +samba-tool user upn set User1 LOGIN@DOMAINFQDN --URL=ldap://samba.samdom.example.com --username=administrator --password=passw1rd + +Example1 shows how to set a userPrincipalname of an account in a remote LDAP server. The --URL parameter is used to specify the remote target server. The --username= and --password= options are used to pass the username and password of a user that exists on the remote server and is authorized to update that server. + +Example2: +samba-tool user upn set User1 LOGIN@DOMAINFQDN + +Example2 shows how to set the userPrincipalname of an account. The username or sAMAccountName is specified using the --filter= paramter and the username in this example is User1. +""" + + + synopsis = "%prog [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + Option("--filter", help="LDAP Filter to set upn on", type=str), + ] + + takes_args = ["username", "upn"] + def run(self, username=None, upn=None, sambaopts=None, credopts=None, + versionopts=None, H=None, filter=None): + if username is None and filter is None: + raise CommandError("Either the username or '--filter' must be specified!") + + if filter is None: + filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username)) + + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + if upn is None: + raise CommandError("Param UserPrincipalName is not defined") + + try: + samdb.setupn(filter, upn) + except Exception, msg: + raise CommandError("Failed to set userPrincipalName for user '%s': %s" % ( + username or filter, msg)) + self.outf.write("Set UserPrincipalName %s for user '%s' .\n" % ( + upn, username or filter)) + + +class cmd_upn_delete(Command): + """Remove userPrincipalName of a user account. + +The user can either be specified by their sAMAccountName or using the --filter option. + +The command may be run from the root userid or another authorized userid. The -H or --URL= option can be used to execute the command on a remote server. + +Example1: +samba-tool user upn delete User1 LOGIN@DOMAINFQDN + +Example1 shows how to remove userPrincipalname of an account. The username or sAMAccountName is specified using the --filter= paramter and the username in this example is User1. + +""" + + synopsis = "%prog [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "credopts": options.CredentialsOptions, + "versionopts": options.VersionOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + Option("--filter", help="LDAP Filter to set upn on", type=str), + ] + + takes_args = ["username"] + + def run(self, username=None, sambaopts=None, credopts=None, + versionopts=None, H=None, filter=None): + if username is None and filter is None: + raise CommandError("Either the username or '--filter' must be specified!") + + if filter is None: + filter = "(&(objectClass=user)(sAMAccountName=%s))" % (ldb.binary_encode(username)) + + lp = sambaopts.get_loadparm() + creds = credopts.get_credentials(lp) + + samdb = SamDB(url=H, session_info=system_session(), + credentials=creds, lp=lp) + + try: + samdb.delupn(filter) + except Exception, msg: + raise CommandError("Failed to remove userPrincipalName for user '%s': %s" % ( + username or filter, msg)) + + self.outf.write("Removed UserPrincipalName for user '%s' .\n" % ( + username or filter)) + + + +class cmd_upn(SuperCommand): + """User Principal Name (UPN) management.""" + + subcommands = {} + subcommands["set"] = cmd_upn_set() + subcommands["show"] = cmd_upn_show() + subcommands["delete"] = cmd_upn_delete() diff -rupN a/source4/scripting/python/samba/netcmd/user.py b/source4/scripting/python/samba/netcmd/user.py --- a/source4/scripting/python/samba/netcmd/user.py 2012-10-30 10:01:47.000000000 +0100 +++ b/source4/scripting/python/samba/netcmd/user.py 2012-12-10 08:34:19.631067771 +0100 @@ -36,6 +36,8 @@ from samba.netcmd import ( Option, ) +from samba.netcmd.upn import cmd_upn + class cmd_user_create(Command): """Create a new user. @@ -83,6 +85,8 @@ Example3 shows how to create a new user type=str), Option("--surname", help="User's surname", type=str), Option("--given-name", help="User's given name", type=str), + Option("--upn", help="User Principal Name in place of username@REALM", type=str), + Option("--no-upn", help="No User Principal Name", action="store_true"), Option("--initials", help="User's initials", type=str), Option("--profile-path", help="User's profile path", type=str), Option("--script-path", help="User's logon script path", type=str), @@ -109,12 +113,15 @@ Example3 shows how to create a new user def run(self, username, password=None, credopts=None, sambaopts=None, versionopts=None, H=None, must_change_at_next_login=False, random_password=False, use_username_as_cn=False, userou=None, - surname=None, given_name=None, initials=None, profile_path=None, - script_path=None, home_drive=None, home_directory=None, + surname=None, given_name=None, upn=None, initials=None, profile_path=None, + script_path=None, home_drive=None, home_directory=None, no_upn=False, job_title=None, department=None, company=None, description=None, mail_address=None, internet_address=None, telephone_number=None, physical_delivery_office=None): + if upn is not None and no-upn: + raise CommandError("Can't use Options --upn and --no-upn at the same time") + if random_password: password = generate_random_password(128, 255) @@ -134,10 +141,10 @@ Example3 shows how to create a new user samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp) samdb.newuser(username, password, force_password_change_at_next_login_req=must_change_at_next_login, - useusernameascn=use_username_as_cn, userou=userou, surname=surname, givenname=given_name, initials=initials, + useusernameascn=use_username_as_cn, userou=userou, surname=surname, givenname=given_name, upn=upn, initials=initials, profilepath=profile_path, homedrive=home_drive, scriptpath=script_path, homedirectory=home_directory, jobtitle=job_title, department=department, company=company, description=description, - mailaddress=mail_address, internetaddress=internet_address, + mailaddress=mail_address, internetaddress=internet_address, no_upn=no_upn, telephonenumber=telephone_number, physicaldeliveryoffice=physical_delivery_office) except Exception, e: raise CommandError("Failed to add user '%s': " % username, e) @@ -422,7 +429,6 @@ Example4 shows how to set the account ex self.outf.write("Expiry for user '%s' disabled.\n" % ( username or filter)) - class cmd_user_password(Command): """Change password for a user account (the one provided in authentication). """ @@ -566,5 +572,6 @@ class cmd_user(SuperCommand): subcommands["enable"] = cmd_user_enable() subcommands["list"] = cmd_user_list() subcommands["setexpiry"] = cmd_user_setexpiry() + subcommands["upn"] = cmd_upn() subcommands["password"] = cmd_user_password() subcommands["setpassword"] = cmd_user_setpassword() diff -rupN a/source4/scripting/python/samba/samdb.py b/source4/scripting/python/samba/samdb.py --- a/source4/scripting/python/samba/samdb.py 2012-10-16 09:33:06.000000000 +0200 +++ b/source4/scripting/python/samba/samdb.py 2012-12-10 08:34:19.632067071 +0100 @@ -285,10 +285,10 @@ member: %s def newuser(self, username, password, force_password_change_at_next_login_req=False, - useusernameascn=False, userou=None, surname=None, givenname=None, + useusernameascn=False, userou=None, surname=None, givenname=None, upn=None, initials=None, profilepath=None, scriptpath=None, homedrive=None, homedirectory=None, jobtitle=None, department=None, company=None, - description=None, mailaddress=None, internetaddress=None, + description=None, mailaddress=None, internetaddress=None, no_upn=False, telephonenumber=None, physicaldeliveryoffice=None, sd=None, setpassword=True): """Adds a new user with additional parameters @@ -301,6 +301,8 @@ member: %s :param userou: Object container (without domainDN postfix) for new user :param surname: Surname of the new user :param givenname: First name of the new user + :param upn: UserPrincipalName of the new user + :param no-upn: The user has no UserPrincipalName :param initials: Initials of the new user :param profilepath: Profile path of the new user :param scriptpath: Logon script path of the new user @@ -335,12 +337,11 @@ member: %s user_dn = "CN=%s,%s,%s" % (cn, (userou or "CN=Users"), self.domain_dn()) dnsdomain = ldb.Dn(self, self.domain_dn()).canonical_str().replace("/", "") - user_principal_name = "%s@%s" % (username, dnsdomain) + user_principal_name = "%s@%s" % (username, dnsdomain.upper()) # The new user record. Note the reliance on the SAMLDB module which # fills in the default informations ldbmessage = {"dn": user_dn, "sAMAccountName": username, - "userPrincipalName": user_principal_name, "objectClass": "user"} if surname is not None: @@ -348,6 +349,11 @@ member: %s if givenname is not None: ldbmessage["givenName"] = givenname + + if upn is not None: + ldbmessage["userPrincipalName"] = upn + elif no_upn is False or no_upn is None: + ldbmessage["userPrincipalName"] = user_principal_name if displayname is not "": ldbmessage["displayName"] = displayname @@ -513,6 +519,67 @@ accountExpires: %u else: self.transaction_commit() + def setupn(self, search_filter, userPrincipalName): + """Set a userPrincipalName for a user + + :param search_filter: LDAP filter to find the user (eg + samaccountname=name) + :param upn: userPrincipalName in form LOGIN@DOMAINFQDN + """ + self.transaction_start() + try: + res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, + expression=search_filter, + attrs=["userPrincipalName"]) + + if len(res) == 0: + raise Exception('Unable to find user "%s"' % search_filter) + + user_dn = res[0].dn + + setexp = """ +dn: %s +changetype: modify +replace: userPrincipalName +userPrincipalName: %s +""" % (user_dn, userPrincipalName) + + self.modify_ldif(setexp) + except: + self.transaction_cancel() + raise + else: + self.transaction_commit() + + def delupn(self, search_filter): + """Delete a userPrincipalName from a user + + :param search_filter: LDAP filter to find the user (eg + samaccountname=name) + """ + self.transaction_start() + try: + res = self.search(base=self.domain_dn(), scope=ldb.SCOPE_SUBTREE, + expression=search_filter, + attrs=["userPrincipalName"]) + + if len(res) == 0: + raise Exception('Unable to find user "%s"' % search_filter) + + user_dn = res[0].dn + setexp = """ +dn: %s +changetype: modify +delete: userPrincipalName +""" % (user_dn) + + self.modify_ldif(setexp) + except: + self.transaction_cancel() + raise + else: + self.transaction_commit() + def set_domain_sid(self, sid): """Change the domain SID used by this LDB. diff -rupN a/source4/scripting/python/samba/tests/samba_tool/user.py b/source4/scripting/python/samba/tests/samba_tool/user.py --- a/source4/scripting/python/samba/tests/samba_tool/user.py 2012-10-30 10:01:47.000000000 +0100 +++ b/source4/scripting/python/samba/tests/samba_tool/user.py 2012-12-10 08:34:19.632067071 +0100 @@ -18,6 +18,7 @@ import os import time import ldb +from samba.tests import env_loadparm from samba.tests.samba_tool.base import SambaToolCmdTest from samba import ( nttime2unix, @@ -98,7 +99,12 @@ class UserCmdTestCase(SambaToolCmdTest): self.assertEquals("%s" % found.get("cn"), "%(name)s" % user) self.assertEquals("%s" % found.get("name"), "%(name)s" % user) - + + lp = env_loadparm() + realm = lp.get("realm") + upn = "%s@%s" % (user["name"], realm) + + self.assertEquals("%s" % found.get("userPrincipalName"), upn) def test_setpassword(self): @@ -200,6 +206,68 @@ class UserCmdTestCase(SambaToolCmdTest): "user '%s' not found" % name) + def test_setupn(self): + newupn = "TEST@TEST.TEST" + upnlist = {} + for user in self.users: + found = self._find_user(user["name"]) + upnlist[user["name"]] = found.get("userPrincipalName") + + (result, out, err) = self.runsubcmd("user", "upn", "set", + user["name"], + "%s" % newupn, + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + + + found = self._find_user(user["name"]) + + self.assertEquals("%s" % found.get("userPrincipalName"), newupn) + + for user in self.users: + (result, out, err) = self.runsubcmd("user", "upn", "delete", + user["name"], + "-H", "ldap://%s" % os.environ["DC_SERVER"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + found = self._find_user(user["name"]) + + self.assertEquals(found.get("userPrincipalName"), None) + + for user in self.users: + upn = upnlist[user["name"]] + if upn is None: + self.assertIn("UPN '%s' not created successfully" % user["name"], out) + + (result, out, err) = self.runsubcmd("user", "upn", "set", + user["name"], + "%s" % upn, + "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + + found = self._find_user(user["name"]) + + self.assertEquals("%s" % found.get("userPrincipalName"), "%s" % upn) + + for user in self.users: + (result, out, err) = self.runsubcmd("user", "upn", "delete", + user["name"], + "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + + found = self._find_user(user["name"]) + + self.assertEquals(found.get("userPrincipalName"), None) + + for user in self.users: + upn = upnlist[user["name"]] + (result, out, err) = self.runsubcmd("user", "upn", "set", + user["name"], + "%s" % upn, + "-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"])) + + found = self._find_user(user["name"]) + + self.assertEquals("%s" % found.get("userPrincipalName"), "%s" % upn) + + def _randomUser(self, base={}): """create a user with random attribute values, you can specify base attributes""" user = { @@ -235,3 +303,4 @@ class UserCmdTestCase(SambaToolCmdTest): return userlist[0] else: return None + diff -rupN a/source4/scripting/python/samba/upgrade.py b/source4/scripting/python/samba/upgrade.py --- a/source4/scripting/python/samba/upgrade.py 2012-12-04 11:07:44.000000000 +0100 +++ b/source4/scripting/python/samba/upgrade.py 2012-12-10 09:54:29.986071092 +0100 @@ -127,6 +127,30 @@ def add_posix_attrs(logger, samdb, sid, 'Could not add posix attrs for AD entry for sid=%s, (%s)', str(sid), str(e)) +def add_userPrincipalName(logger, samdb, sid, name, realm, xid_type): + """Add userPrincipalName for the user + + :param samdb: Samba4 sam.ldb database + :param sid: user/group sid + :param name: user/group name + :param realm: fqdn domain name + :param xid_type: type of id (ID_TYPE_UID/ID_TYPE_GID) + """ + user_principal_name = "%s@%s" % (name, realm) + + try: + m = ldb.Message() + m.dn = ldb.Dn(samdb, "" % str(sid)) + if xid_type == "ID_TYPE_UID": + m['userPrincipalName'] = ldb.MessageElement( + str(user_principal_name), ldb.FLAG_MOD_REPLACE, 'userPrincipalName') + + samdb.modify(m) + except ldb.LdbError, e: + logger.warn( + 'Could not add userPrincipalName attr for AD entry for sid=%s, (%s)', + str(sid), str(e)) + def add_ad_posix_idmap_entry(samdb, sid, xid, xid_type, logger): """Create idmap entry @@ -157,7 +181,6 @@ def add_ad_posix_idmap_entry(samdb, sid, 'Could not modify AD idmap entry for sid=%s, id=%s, type=%s (%s)', str(sid), str(xid), xid_type, str(e)) - def add_idmap_entry(idmapdb, sid, xid, xid_type, logger): """Create idmap entry @@ -550,7 +573,7 @@ def get_posix_attr_from_ldap_backend(log def upgrade_from_samba3(samba3, logger, targetdir, session_info=None, - useeadb=False, dns_backend=None, use_ntvfs=False): + useeadb=False, dns_backend=None, use_ntvfs=False, no_upn=False): """Upgrade from samba3 database to samba4 AD database :param samba3: samba3 object @@ -736,13 +759,13 @@ Please fix this account before attemptin admin_user = username try: - group_memberships = s3db.enum_group_memberships(user); + group_memberships = s3db.enum_group_memberships(user) for group in group_memberships: if str(group) in groupmembers: if user.user_sid not in groupmembers[str(group)]: groupmembers[str(group)].append(user.user_sid) else: - groupmembers[str(group)] = [user.user_sid]; + groupmembers[str(group)] = [user.user_sid] except passdb.error, e: logger.warn("Ignoring group memberships of '%s' %s: %s", username, user.user_sid, e) @@ -894,6 +917,9 @@ Please fix this account before attemptin (username in shells) and (shells[username] is not None) and \ (username in pgids) and (pgids[username] is not None): add_posix_attrs(samdb=result.samdb, sid=userdata[username].user_sid, name=username, nisdomain=domainname.lower(), xid_type="ID_TYPE_UID", home=homes[username], shell=shells[username], pgid=pgids[username], logger=logger) + if ( no_upn is not True ) and ( userdata[username].acct_ctrl & 16 ): + logger.warn('User UPN: %s' % username) + add_userPrincipalName(samdb=result.samdb, sid=userdata[username].user_sid, name=username, realm=realm.upper(), xid_type="ID_TYPE_UID", logger=logger) logger.info("Adding users to groups") for g in grouplist: