[PATCH] samba-tool: make 'samba-tool user create' work like ADUC

Rowland Penny repenny241155 at gmail.com
Sat Jun 20 08:36:42 MDT 2015


Hi, The basis behind this patch is to make creating a NIS user or group 
with samba-tool work more like ADUC

With ADUC, you create the user or group and then add NIS attributes via 
the Unix Attributes tab.
The user or group objects ID number is obtained from msSFU30MaxUidNumber 
or msSFU30MaxGidNumber, or the start number '10000' is used.

There are problems with Samba 4, neither of the msSFU30MaxUidNumber or 
msSFU30MaxGidNumber attributes are normally created and the domain may 
have been provisioned via classic-upgrade.
This patch should work around these problems.

If this is a new provision, msSFU30MaxUidNumber and msSFU30MaxGidNumber 
will not exist, there will be no uidNumber & gidNumber attributes in AD, 
so the first user and group to be created with samba-tool will have 
their ID number set to 10000 (as with ADUC) and the relevant 
msSFU30Max*idNumber attribute will be created with the next id number.

If users or groups are upgraded to have NIS attributes via ADUC, this 
will create the required msSFU30Max*idNumber attribute and samba-tool 
will use this when creating users or groups.

If the domain is provisioned via classic-upgrade, there will be 
uidNumbers & gidNumbers in AD, the first time a user or group is created 
with samba-tool, the highest ID number will be found in AD, one will be 
added to this and then used for the ID number, the relevant 
msSFU30Max*idNumber attribute will then be created with the next id number.

Users will no longer have to keep track of uidNumber & gidNumber 
attributes, they will not have to supply them when they create a new 
user or group and they will not have to remember the gidNumber for a group.

You can also upgrade a windows user or group to NIS, via 'samba-tool 
XXXX nisadd' , this works as above.

Rowland

-------------- next part --------------
From c96b15ee46e6e40a06244f89c24408d99eb1a9d2 Mon Sep 17 00:00:00 2001
From: Rowland Penny <repenny241155 at gmail.com>
Date: Sat, 20 Jun 2015 15:16:19 +0100
Subject: [PATCH] samba-tool: make 'samba-tool user create' work like ADUC

Signed-off-by: Rowland Penny <repenny241155 at gmail.com>
---
 python/samba/netcmd/group.py | 220 +++++++++++++++++++++----
 python/samba/netcmd/user.py  | 377 +++++++++++++++++++++++++++++++++++++------
 python/samba/samdb.py        |  86 ++++++++++
 3 files changed, 605 insertions(+), 78 deletions(-)

diff --git a/python/samba/netcmd/group.py b/python/samba/netcmd/group.py
index 722bcc4..225b8f0 100644
--- a/python/samba/netcmd/group.py
+++ b/python/samba/netcmd/group.py
@@ -47,33 +47,57 @@ distribution_group = dict({"Domain": GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
 class cmd_group_add(Command):
     """Creates a new AD group.
 
-This command creates a new Active Directory group.  The groupname specified on the command is a unique sAMAccountName.
+This command creates a new Active Directory group.  
+The groupname specified on the command is a unique sAMAccountName.
 
-An Active Directory group may contain user and computer accounts as well as other groups.  An administrator creates a group and adds members to that group so they can be managed as a single entity.  This helps to simplify security and system administration.
+An Active Directory group may contain user and computer accounts as well as 
+other groups.  
+An administrator creates a group and adds members to that group so they can be 
+managed as a single entity.  
+This helps to simplify security and system administration.
 
-Groups may also be used to establish email distribution lists, using --group-type=Distribution.
+Groups may also be used to establish email distribution lists, using 
+--group-type=Distribution.
 
-Groups are located in domains in organizational units (OUs).  The group's scope is a characteristic of the group that designates the extent to which the group is applied within the domain tree or forest.
+Groups are located in domains in organizational units (OUs).  
+The group's scope is a characteristic of the group that designates the extent 
+to which the group is applied within the domain tree or forest.
 
-The group location (OU), type (security or distribution) and scope may all be specified on the samba-tool command when the group is created.
+The group location (OU), type (security or distribution) and scope may all be 
+specified on the samba-tool command when the group is created.
 
-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.
+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 group add Group1 -H ldap://samba.samdom.example.com --description='Simple group'
+samba-tool group add Group1 -H ldap://samba.samdom.example.com \
+--description='Simple group'
 
-Example1 adds a new group with the name Group1 added to the Users container on a remote LDAP server.  The -U parameter is used to pass the userid and password of a user that exists on the remote server and is authorized to issue the command on that server.  It defaults to the security type and global scope.
+Example1 adds a new group with the name Group1 to the Users container on a 
+remote LDAP server.  
+The -U parameter is used to pass the userid and password of a user that exists 
+on the remote server and is authorized to issue the command on that server.  
+It defaults to the security type and global scope.
 
 Example2:
 sudo samba-tool group add Group2 --group-type=Distribution
 
-Example2 adds a new distribution group to the local server.  The command is run under root using the sudo command.
+Example2 adds a new distribution group to the local server.  
+The command is run under root using the sudo command.
 
 Example3:
-samba-tool group add Group3 --nis-domain=samdom --gid-number=12345
+samba-tool group add Group3 --nis-domain=samdom
+
+Example3 adds a new RFC2307 enabled group for NIS domain samdom 
+
+The groups gidNumber will be set automatically from either: 
+The 'msSFU30MaxGidNumber' attribute (if set),
+The last 'gidNumber' attribute found in AD,
+Or '10000' (as ADUC).
+
+When a NIS group is created with samba-tool, the 'msSFU30MaxGidNumber' 
+attribute will be created/updated with the next 'gidNumber'.
 
-Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (both options are required to enable this feature).
 """
 
     synopsis = "%prog <groupname> [options]"
@@ -85,19 +109,18 @@ Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (b
     }
 
     takes_options = [
-        Option("-H", "--URL", help="LDB URL for database or target server", type=str,
-               metavar="URL", dest="H"),
+        Option("-H", "--URL", help="LDB URL for database or target server", 
+                type=str, metavar="URL", dest="H"),
         Option("--groupou",
-           help="Alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created",
-           type=str),
-        Option("--group-scope", type="choice", choices=["Domain", "Global", "Universal"],
-            help="Group scope (Domain | Global | Universal)"),
-        Option("--group-type", type="choice", choices=["Security", "Distribution"],
-            help="Group type (Security | Distribution)"),
+           help="Alternative location (without domainDN counterpart) \
+to default CN=Users in which new user object will be created", type=str),
+        Option("--group-scope", type="choice", choices=["Domain", "Global", 
+               "Universal"], help="Group scope (Domain | Global | Universal)"),
+        Option("--group-type", type="choice", choices=["Security", 
+               "Distribution"], help="Group type (Security | Distribution)"),
         Option("--description", help="Group's description", type=str),
         Option("--mail-address", help="Group's email address", type=str),
         Option("--notes", help="Groups's notes", type=str),
-        Option("--gid-number", help="Group's Unix/RFC2307 GID number", type=int),
         Option("--nis-domain", help="SFU30 NIS Domain", type=str),
     ]
 
@@ -105,28 +128,49 @@ Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (b
 
     def run(self, groupname, credopts=None, sambaopts=None,
             versionopts=None, H=None, groupou=None, group_scope=None,
-            group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None):
+            group_type=None, description=None, mail_address=None, notes=None, 
+            gid_number=None, nis_domain=None):
 
         if (group_type or "Security") == "Security":
             gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
         else:
-            gtype = distribution_group.get(group_scope, GTYPE_DISTRIBUTION_GLOBAL_GROUP)
-
-        if (gid_number is None and nis_domain is not None) or (gid_number is not None and nis_domain is None):
-            raise CommandError('Both --gid-number and --nis-domain have to be set for a RFC2307-enabled group. Operation cancelled.')
+            gtype = (distribution_group.get(group_scope, 
+                     GTYPE_DISTRIBUTION_GLOBAL_GROUP))
 
         lp = sambaopts.get_loadparm()
         creds = credopts.get_credentials(lp, fallback_machine=True)
+        samdb = SamDB(url=H, session_info=system_session(), credentials=creds, 
+                      lp=lp)
+        domain_dn = samdb.domain_dn()
+
+        if nis_domain is not None:
+            maxattr = "msSFU30MaxGidNumber"
+            idattr = "gidNumber"
+            idexpr = ("(objectClass=group)")
+            gid_number = (samdb.get_next_idnumber(nis_domain, maxattr, idattr, 
+                                                  idexpr))
+
+            if not lp.get("idmap_ldb:use rfc2307"):
+                self.outf.write("You are setting a Unix/RFC2307 GID. \
+You may want to set 'idmap_ldb:use rfc2307 = Yes' in smb.conf to use \
+this attribute for XID/SID-mapping.\n")
 
         try:
-            samdb = SamDB(url=H, session_info=system_session(),
-                          credentials=creds, lp=lp)
             samdb.newgroup(groupname, groupou=groupou, grouptype = gtype,
-                          description=description, mailaddress=mail_address, notes=notes,
-                          gidnumber=gid_number, nisdomain=nis_domain)
+                          description=description, mailaddress=mail_address, 
+                          notes=notes,gidnumber=gid_number, 
+                          nisdomain=nis_domain)
         except Exception, e:
-            # FIXME: catch more specific exception
             raise CommandError('Failed to create group "%s"' % groupname, e)
+
+        if nis_domain is not None:
+            try:
+                nextmax = int(gid_number) + 1
+                maxattr = "msSFU30MaxGidNumber"
+                samdb.update_max_idnumber(nis_domain, nextmax, maxattr)
+            except Exception, e:
+                raise CommandError("msSFU30MaxGidNumber update failed: " , e)
+
         self.outf.write("Added group %s\n" % groupname)
 
 
@@ -408,6 +452,119 @@ samba-tool group listmembers \"Domain Users\" -H ldap://samba.samdom.example.com
             raise CommandError('Failed to list members of "%s" group ' % groupname, e)
 
 
+class cmd_group_nis_add(Command):
+    """Add NIS attributes to a group.
+
+This command adds NIS info to a group account in the Active Directory domain.  
+The groupname specified on the command is the sAMaccountName.
+
+Unix (RFC2307) attributes will be added to the group account. 
+Configure 'idmap_ldb:use rfc2307 = Yes' in smb.conf to use these GID mapping 
+attributes.
+
+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 against a remote 
+server.
+
+Example:
+samba-tool group nisadd Group1 --nis-domain=samdom
+
+The example shows how to add RFC2307/NIS attributes to a domain enabled group 
+account. 
+
+The groups gidNumber will be set automatically from either: 
+The 'msSFU30MaxGidNumber' attribute (if set),
+The last 'gidNumber' attribute found in AD,
+Or '10000' (as ADUC).
+
+When a group is updated to a NIS group with samba-tool, 
+the 'msSFU30MaxGidNumber' attribute will be created/updated with the next 
+'gidNumber'.
+
+"""
+    synopsis = "%prog <groupname> [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server", 
+               type=str, metavar="URL", dest="H"),
+        Option("--nis-domain", help="Group's Unix/RFC2307 NIS domain", 
+               type=str),
+    ]
+
+    takes_args = ["groupname"]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, groupname, credopts=None, sambaopts=None, versionopts=None, 
+            H=None, nis_domain=None, gid_number=None):
+
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp)
+
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+
+        if nis_domain is None:
+            raise CommandError('Missing parameter.'
+                               ' To enable NIS features, the following option '
+                               'has to be given: --nis-domain='
+                               ', Operation cancelled.')
+
+        domain_dn = samdb.domain_dn()
+        search_filter = "(samaccountname=%s)" % groupname
+        group_dn = samdb.get_object_dn(search_filter)
+
+        # what if group already is a NIS group??
+        res = samdb.search(group_dn,
+                           scope=ldb.SCOPE_BASE, 
+                           attrs=["gidNumber"])
+        if "gidNumber" in res[0]:
+            raise CommandError("Group %s already is a NIS group." % groupname) 
+
+        maxattr = "msSFU30MaxGidNumber"
+        idattr = "gidNumber"
+        idexpr = ("(objectClass=group)")
+        gid_number = samdb.get_next_idnumber(nis_domain, maxattr, idattr, 
+                                             idexpr)
+
+        if not lp.get("idmap_ldb:use rfc2307"):
+            self.outf.write("You are setting a Unix/RFC2307 GID. \
+You may want to set 'idmap_ldb:use rfc2307 = Yes' in smb.conf to \
+use this attribute for XID/SID-mapping.\n")
+
+        update_group = """
+dn: %s
+changetype: modify
+add: msSFU30NisDomain
+msSFU30NisDomain: %s
+-
+add: msSFU30Name
+msSFU30Name: %s
+-
+add: gidNumber
+gidNumber: %s
+-
+""" % (group_dn, nis_domain, groupname, gid_number)
+
+        try:
+            samdb.modify_ldif(update_group)
+        except Exception, e:
+            raise CommandError("Failed to update group '%s': " % groupname, e)
+
+        try:
+            nextmax = int(gid_number) + 1
+            maxattr = "msSFU30MaxGidNumber"
+            samdb.update_max_idnumber(nis_domain, nextmax, maxattr)
+        except Exception, e:
+            raise CommandError("msSFU30MaxGidNumber update failed: ", e)
+
+        self.outf.write("Group '%s' updated successfully\n" % groupname)
+
+
 class cmd_group(SuperCommand):
     """Group management."""
 
@@ -418,3 +575,4 @@ class cmd_group(SuperCommand):
     subcommands["removemembers"] = cmd_group_remove_members()
     subcommands["list"] = cmd_group_list()
     subcommands["listmembers"] = cmd_group_list_members()
+    subcommands["nisadd"] = cmd_group_nis_add()
diff --git a/python/samba/netcmd/user.py b/python/samba/netcmd/user.py
index 2bc5522..8a8a6ba 100644
--- a/python/samba/netcmd/user.py
+++ b/python/samba/netcmd/user.py
@@ -41,49 +41,102 @@ from samba.netcmd import (
 class cmd_user_create(Command):
     """Create a new user.
 
-This command creates a new user account in the Active Directory domain.  The username specified on the command is the sAMaccountName.
-
-User accounts may represent physical entities, such as people or may be used as service accounts for applications.  User accounts are also referred to as security principals and are assigned a security identifier (SID).
-
-A user account enables a user to logon to a computer and domain with an identity that can be authenticated.  To maximize security, each user should have their own unique user account and password.  A user's access to domain resources is based on permissions assigned to the user account.
-
-Unix (RFC2307) attributes may be added to the user account. Attributes taken from NSS are obtained on the local machine. Explicitly given values override values obtained from NSS. Configure 'idmap_ldb:use rfc2307 = Yes' to use these attributes for UID/GID mapping.
-
-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 against a remote server.
+This command creates a new user account in the Active Directory 
+domain.
+The username specified on the command is the sAMaccountName.
+
+User accounts may represent physical entities, such as people or 
+may be used as service accounts for applications.  
+User accounts are also referred to as security principals and are 
+assigned a security identifier (SID).
+
+A user account enables a user to logon to a computer and domain 
+with an identity that can be authenticated.  
+To maximize security, each user should have their own unique user 
+account and password.  
+A user's access to domain resources is based on permissions assigned 
+to the user account.
+
+Unix (RFC2307) attributes may be added to the user account. 
+Attributes taken from NSS are obtained on the local machine. 
+Explicitly given values override values obtained from NSS. 
+Configure 'idmap_ldb:use rfc2307 = Yes' in smb.conf to use these 
+attributes for UID/GID mapping.
+
+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 against 
+a remote server.
 
 Example1:
-samba-tool user add User1 passw0rd --given-name=John --surname=Smith --must-change-at-next-login -H ldap://samba.samdom.example.com -Uadministrator%passw1rd
+samba-tool user create User1 passw0rd --given-name=John \
+--surname=Smith --must-change-at-next-login \
+-H ldap://samba.samdom.example.com -Uadministrator%passw1rd
 
-Example1 shows how to create a new user in the domain against a remote LDAP server.  The -H parameter is used to specify the remote target server.  The -U option is used to pass the userid and password authorized to issue the command remotely.
+Example1 shows how to create a new user in the domain against a 
+remote LDAP server.  
+The -H parameter is used to specify the remote target server.  
+The -U option is used to pass the userid and password authorized to 
+issue the command remotely.
 
 Example2:
-sudo samba-tool user add User2 passw2rd --given-name=Jane --surname=Doe --must-change-at-next-login
+sudo samba-tool user create User2 passw2rd --given-name=Jane \
+--surname=Doe --must-change-at-next-login
 
-Example2 shows how to create a new user in the domain against the local server.   sudo is used so a user may run the command as root.  In this example, after User2 is created, he/she will be forced to change their password when they logon.
+Example2 shows how to create a new user in the domain against the \
+local server.   
+sudo is used so a user may run the command as root.  
+In this example, after User2 is created, he/she will be forced to 
+change their password when they logon.
 
 Example3:
-samba-tool user add User3 passw3rd --userou='OU=OrgUnit'
+samba-tool user create User3 passw3rd --userou='OU=OrgUnit'
 
-Example3 shows how to create a new user in the OrgUnit organizational unit.
+Example3 shows how to create a new user in the OrgUnit 
+organizational unit.
+NOTE: the OU must exist first.
 
 Example4:
-samba-tool user create User4 passw4rd --rfc2307-from-nss --gecos 'some text'
+samba-tool user create User4 passw4rd --rfc2307-from-nss \
+--gecos 'some text'
 
-Example4 shows how to create a new user with Unix UID, GID and login-shell set from the local NSS and GECOS set to 'some text'.
+Example4 shows how to create a new user with Unix UID, GID 
+and login-shell set from the local NSS and GECOS set to 
+'some text'.
 
 Example5:
-samba-tool user add User5 passw5rd --nis-domain=samdom --unix-home=/home/User5 \
-           --uid-number=10005 --login-shell=/bin/false --gid-number=10000
+samba-tool user create User5 passw5rd --nis-domain=samdom \
+--unix-home=/home/User5 --login-shell=/bin/false \
+[--group-name=unixgroup]
+
+Example5 shows how to create an RFC2307/NIS domain enabled user 
+account. If --nis-domain is set, then the next two parameters are 
+mandatory. 
+
+The users uidNumber will be set automatically from either: 
+The 'msSFU30MaxUidNumber' attribute (if set)
+The last 'uidNumber' attribute found in AD
+Or '10000' (as ADUC).
+
+if the parameter '--group-name' is given, then the groups 'gidNumber' 
+will be obtained and used for the users 'gidNumber' attribute, this 
+does of course mean that the group MUST have a 'gidNumber.
 
-Example5 shows how to create an RFC2307/NIS domain enabled user account. If
---nis-domain is set, then the other four parameters are mandatory.
+If the last parameter, '--group-name' is omitted, the users 
+gidNumber will be  set to the gidNumber found  in Domain Users. 
+This means that 'Domain Users' MUST  have a gidNumber.
+
+When a NIS user is created with samba-tool, the 
+'msSFU30MaxUidNumber' attribute is created/updated with the next 
+'uidNumber'.
 
 """
     synopsis = "%prog <username> [<password>] [options]"
 
     takes_options = [
-        Option("-H", "--URL", help="LDB URL for database or target server", type=str,
-                metavar="URL", dest="H"),
+        Option("-H", "--URL", 
+               help="LDB URL for database or target server", type=str,
+               metavar="URL", dest="H"),
         Option("--must-change-at-next-login",
                 help="Force password to be changed on next login",
                 action="store_true"),
@@ -94,8 +147,9 @@ Example5 shows how to create an RFC2307/NIS domain enabled user account. If
                 help="Force use of username as user's CN",
                 action="store_true"),
         Option("--userou",
-                help="DN of alternative location (without domainDN counterpart) to default CN=Users in which new user object will be created. E. g. 'OU=<OU name>'",
-                type=str),
+                help="DN of alternative location (without domainDN \
+counterpart) to default CN=Users in which new user object will be \
+created. E. g. 'OU=<OU name>'", type=str),
         Option("--surname", help="User's surname", type=str),
         Option("--given-name", help="User's given name", type=str),
         Option("--initials", help="User's initials", type=str),
@@ -110,18 +164,20 @@ Example5 shows how to create an RFC2307/NIS domain enabled user account. If
         Option("--mail-address", help="User's email address", type=str),
         Option("--internet-address", help="User's home page", type=str),
         Option("--telephone-number", help="User's phone number", type=str),
-        Option("--physical-delivery-office", help="User's office location", type=str),
+        Option("--physical-delivery-office", help="User's office location", 
+               type=str),
         Option("--rfc2307-from-nss",
-                help="Copy Unix user attributes from NSS (will be overridden by explicit UID/GID/GECOS/shell)",
-                action="store_true"),
+                help="Copy Unix user attributes from NSS (will be overridden \
+by explicit UID/GID/GECOS/shell)", action="store_true"),
         Option("--nis-domain", help="User's Unix/RFC2307 NIS domain", type=str),
         Option("--unix-home", help="User's Unix/RFC2307 home directory",
                 type=str),
         Option("--uid", help="User's Unix/RFC2307 username", type=str),
-        Option("--uid-number", help="User's Unix/RFC2307 numeric UID", type=int),
-        Option("--gid-number", help="User's Unix/RFC2307 primary GID number", type=int),
+        Option("--group-name", help="A Unix/RFC2307 enabled AD \group", 
+                type=str), 
         Option("--gecos", help="User's Unix/RFC2307 GECOS field", type=str),
-        Option("--login-shell", help="User's Unix/RFC2307 login shell", type=str),
+        Option("--login-shell", help="User's Unix/RFC2307 login shell", 
+                type=str),
     ]
 
     takes_args = ["username", "password?"]
@@ -141,7 +197,7 @@ Example5 shows how to create an RFC2307/NIS domain enabled user account. If
             mail_address=None, internet_address=None, telephone_number=None,
             physical_delivery_office=None, rfc2307_from_nss=False,
             nis_domain=None, unix_home=None, uid=None, uid_number=None,
-            gid_number=None, gecos=None, login_shell=None):
+            gid_number=None, group_name=None, gecos=None, login_shell=None):
 
         if random_password:
             password = generate_random_password(128, 255)
@@ -171,33 +227,86 @@ Example5 shows how to create an RFC2307/NIS domain enabled user account. If
         lp = sambaopts.get_loadparm()
         creds = credopts.get_credentials(lp)
 
-        if uid_number or gid_number:
-            if not lp.get("idmap_ldb:use rfc2307"):
-                self.outf.write("You are setting a Unix/RFC2307 UID or GID. You may want to set 'idmap_ldb:use rfc2307 = Yes' to use those attributes for XID/SID-mapping.\n")
+        samdb = SamDB(url=H, session_info=system_session(),
+                      credentials=creds, lp=lp)
+        domain_dn = samdb.domain_dn()
 
         if nis_domain is not None:
-            if None in (uid_number, login_shell, unix_home, gid_number):
-                raise CommandError('Missing parameters. To enable NIS features, '
-                                   'the following options have to be given: '
-                                   '--nis-domain=, --uidNumber=, --login-shell='
-                                   ', --unix-home=, --gid-number= Operation '
-                                   'cancelled.')
+            if None in (login_shell, unix_home):
+                raise CommandError('Missing parameters. To enable NIS features'
+                                   ', the following options have to be given: '
+                                   '--nis-domain=, --login-shell=, '
+                                   ' --unix-home=  Operation cancelled.')
+
+            # Get uidNumber for user
+            maxattr = "msSFU30MaxUidNumber"
+            idattr = "uidNumber"
+            idexpr = ("(objectClass=user)")
+            uid_number = samdb.get_next_idnumber(nis_domain, 
+                                                 maxattr, idattr, idexpr)
+
+            if group_name is not None:
+                # get users primary Unix GID from group_name
+                search_filter = "samaccountname=%s" % group_name
+                group_dn = samdb.get_object_dn(search_filter)
+                try:
+                    res = samdb.search(group_dn,
+                                       scope=ldb.SCOPE_SUBTREE, 
+                                       attrs=["gidNumber"])
+                    assert len(res) == 1
+                    gid_number = res[0]["gidNumber"][0]
+                except:
+                    raise CommandError("Group %s does not have a \
+gidNumber attribute" % group_name)
+
+            if group_name is None:
+                # set users primary GID to the one from Domain Users
+                du_dn = "CN=Domain Users,CN=Users," + domain_dn
+                try:
+                    res = samdb.search(du_dn,
+                                       scope=ldb.SCOPE_SUBTREE, 
+                                       attrs=["gidNumber"])
+                    assert len(res) == 1
+                    gid_number = res[0]["gidNumber"][0]
+                except:
+                    raise CommandError("Domain Users Group does \
+not have a gidNumber attribute")
+
+            if not lp.get("idmap_ldb:use rfc2307"):
+                self.outf.write("You are setting a Unix/RFC2307 \
+UID or GID. You may want to set 'idmap_ldb:use rfc2307 = Yes' in\
+ smb.conf to use those attributes for XID/SID-mapping.\n")
 
         try:
             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,
-                          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,
-                          telephonenumber=telephone_number, physicaldeliveryoffice=physical_delivery_office,
+            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, 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,
+                          telephonenumber=telephone_number, 
+                          physicaldeliveryoffice=physical_delivery_office,
                           nisdomain=nis_domain, unixhome=unix_home, uid=uid,
                           uidnumber=uid_number, gidnumber=gid_number,
                           gecos=gecos, loginshell=login_shell)
         except Exception, e:
             raise CommandError("Failed to add user '%s': " % username, e)
 
+        if nis_domain is not None:
+            nextmax = int(uid_number) + 1
+            maxattr = "msSFU30MaxUidNumber"
+            try:
+                samdb.update_max_idnumber(nis_domain, nextmax, maxattr)
+            except Exception, e:
+                raise CommandError("msSFU30MaxUidNumber update failed: " , e)
+
         self.outf.write("User '%s' created successfully\n" % username)
 
 
@@ -611,6 +720,179 @@ Example3 shows how an administrator would reset TestUser3 user's password to pas
         self.outf.write("Changed password OK\n")
 
 
+class cmd_user_nis_add(Command):
+    """Add NIS attributes to a user.
+
+This command adds NIS info to a user account in the Active 
+Directory domain.  
+The username specified on the command is the sAMaccountName.
+
+Unix (RFC2307) attributes will be added to the user account. 
+Add 'idmap_ldb:use rfc2307 = Yes' to smb.conf to use these 
+attributes for UID/GID mapping.
+
+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 
+against a remote server.
+
+Example:
+samba-tool user nisadd User1 --nis-domain=samdom \
+--unix-home=/home/User1 --login-shell=/bin/false \
+[--group-name=unixgroup]
+
+The example shows how to add RFC2307/NIS attributes to a domain 
+enabled user account. 
+If --nis-domain is set, then the next two parameters are mandatory. 
+The users uidNumber will be set  automatically from either: 
+The 'msSFU30MaxUidNumber' attribute (if set)
+The last 'uidNumber' attribute found in AD
+Or '10000' (as ADUC).
+
+if the parameter '--group-name' is given, then the groups 'gidNumber' 
+will be obtained and used for the users 'gidNumber' attribute, this 
+does of course mean that the group MUST have a 'gidNumber.
+
+If the last parameter, '--group-name' & is omitted, the users gidNumber 
+will be  set to the gidNumber found in Domain Users. 
+This means that 'Domain Users' MUST  have a gidNumber.
+
+When a user is updated to a NIS user with samba-tool, the 
+'msSFU30MaxUidNumber' attribute is created/updated with the next 
+'uidNumber'.
+"""
+    synopsis = "%prog <username> [options]"
+
+    takes_options = [
+        Option("-H", "--URL", help="LDB URL for database or target server", 
+                type=str, metavar="URL", dest="H"),
+        Option("--nis-domain", help="User's Unix/RFC2307 NIS domain", type=str),
+        Option("--unix-home", help="User's Unix/RFC2307 home directory", 
+                type=str),
+        Option("--group-name", help="A Unix/RFC2307 enabled AD group", 
+                type=str), 
+        Option("--login-shell", help="User's Unix/RFC2307 login shell", 
+                type=str),
+    ]
+
+    takes_args = ["username"]
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    def run(self, username, credopts=None, sambaopts=None,versionopts=None, 
+            H=None, nis_domain=None,unix_home=None, uid_number=None, 
+            gid_number=None, group_name=None, login_shell=None):
+
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp)
+
+        samdb = SamDB(url=H, session_info=system_session(),credentials=creds, 
+                      lp=lp)
+
+        if None in (nis_domain, login_shell, unix_home):
+            raise CommandError('Missing parameters. To enable NIS features, '
+                               'the following options have to be given: '
+                               '--nis-domain=, --login-shell=, --unix-home='
+                               ' Operation cancelled.')
+
+        domain_dn = samdb.domain_dn()
+        search_filter = "(samaccountname=%s)" % username
+        user_dn = samdb.get_object_dn(search_filter)
+ 
+        # what if user already is a NIS user??
+        res = samdb.search(user_dn,
+                          scope=ldb.SCOPE_BASE, 
+                          attrs=["uidNumber"])
+        if "uidNumber" in res[0]:
+            raise CommandError("User %s already is a NIS user." % username) 
+
+        # Get users uidNumber
+        maxattr = "msSFU30MaxUidNumber"
+        idattr = "uidNumber"
+        idexpr = ("(objectClass=user)")
+        uid_number = samdb.get_next_idnumber(nis_domain, maxattr, idattr, 
+                                             idexpr)
+
+        if group_name is not None:
+            # get users primary GID from group_name
+            search_filter = "samaccountname=%s" % group_name
+            group_dn = samdb.get_object_dn(search_filter)
+            try:
+                res = samdb.search(group_dn,
+                                   scope=ldb.SCOPE_SUBTREE, 
+                                   attrs=["gidNumber"])
+                assert len(res) == 1
+                gid_number = res[0]["gidNumber"][0]
+            except:
+                raise CommandError("Group %s does not have a gidNumber" % 
+                                    group_name)
+
+        if group_name is None:
+            # set users primary GID to the one from Domain Users
+            du_dn = "CN=Domain Users,CN=Users," + domain_dn
+            try:
+                res = samdb.search(du_dn,
+                                   scope=ldb.SCOPE_SUBTREE, 
+                                   attrs=["gidNumber"])
+                assert len(res) == 1
+                gid_number = res[0]["gidNumber"][0]
+            except:
+                raise CommandError("Domain Users Group does not have a \
+gidNumber")
+
+        if not lp.get("idmap_ldb:use rfc2307"):
+            self.outf.write("You are setting a Unix/RFC2307 UID or GID. \
+You may want to set 'idmap_ldb:use rfc2307 = Yes' in smb.conf to use those \
+attributes for XID/SID-mapping.\n")
+
+        update_user = """
+dn: %s
+changetype: modify
+add: uid
+uid: %s
+-
+add: msSFU30Name
+msSFU30Name: %s
+-
+add: msSFU30NisDomain
+msSFU30NisDomain: %s
+-
+add: uidNumber
+uidNumber: %s
+-
+add: gidNumber
+gidNumber: %s
+-
+add: loginShell
+loginShell: %s
+-
+add: unixHomeDirectory
+unixHomeDirectory: %s
+-
+add: unixUserPassword
+unixUserPassword: ABCD!efgh12345$67890
+""" % (user_dn, username, username, nis_domain, uid_number, gid_number, 
+       login_shell, unix_home)
+
+        try:
+            samdb.modify_ldif(update_user)
+        except Exception, e:
+            raise CommandError("Failed to update user '%s': " % username, e)
+
+        nextmax = int(uid_number) + 1
+        maxattr = "msSFU30MaxUidNumber"
+        try:
+            samdb.update_max_idnumber(nis_domain, nextmax, maxattr)
+        except Exception, e:
+            raise CommandError("msSFU30MaxUidNumber update failed: " , e)
+
+        self.outf.write("User '%s' updated successfully\n" % username)
+
+
 class cmd_user(SuperCommand):
     """User management."""
 
@@ -624,3 +906,4 @@ class cmd_user(SuperCommand):
     subcommands["setexpiry"] = cmd_user_setexpiry()
     subcommands["password"] = cmd_user_password()
     subcommands["setpassword"] = cmd_user_setpassword()
+    subcommands["nisadd"] = cmd_user_nis_add()
diff --git a/python/samba/samdb.py b/python/samba/samdb.py
index e3a6292..d5f4f89 100644
--- a/python/samba/samdb.py
+++ b/python/samba/samdb.py
@@ -908,3 +908,89 @@ accountExpires: %u
         '''get the server DN from the rootDSE'''
         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
         return res[0]["serverName"][0]
+
+    def update_max_idnumber(self, nis_domain, nextmax, maxattr):
+        """Updates msSFU30Max*idNumber with next *idNumber
+
+        :param nis_domain: The NIS domain
+        :param nextmax: The next idNumber
+        :param maxattr: the attribute to update 
+         (msSFU30MaxUidNumber or msSFU30MaxGidNumber)
+        """
+        domain_dn = self.domain_dn()
+        nis_dn = "CN=%s,CN=ypservers,CN=ypServ30,\
+CN=RpcServices,CN=System,%s" % (nis_domain, domain_dn)
+
+        mod = """
+dn: %s
+changetype: modify
+replace: %s
+%s: %s
+""" % (nis_dn, maxattr, maxattr, nextmax)
+
+        self.modify_ldif(mod)
+
+    def get_next_idnumber(self, nis_domain, maxattr, idattr, idexpr):
+        """Gets next available msSFU30 ID number
+
+        :param nis_domain: The NIS domain
+        :param maxattr: the attribute to get
+         (msSFU30MaxUidNumber or msSFU30MaxGidNumber)
+        :param idattr: the attribute to search for if
+                      msSFU30Max*idNumber not set
+        :param idexpr: the objectClass to search with
+                       if msSFU30Max*idNumber not set 
+        """
+
+        domain_dn = self.domain_dn()
+        nis_dn = ("CN=%s,CN=ypservers,CN=ypServ30,\
+CN=RpcServices,CN=System,%s" % (nis_domain, domain_dn))
+        res = self.search(nis_dn,
+                          scope=ldb.SCOPE_BASE, 
+                          attrs=[maxattr])
+        if maxattr in res[0]:
+            # msSFU30Max*idNumber is set
+            assert len(res) == 1
+            id_number = res[0][maxattr][0]
+            return id_number
+
+        # msSFU30Max*idNumber is not set
+        # Check for ID Number attributes in AD
+        idmax = 0
+        res = self.search(domain_dn,
+                          scope=ldb.SCOPE_SUBTREE,
+                          expression=idexpr,
+                          attrs=[idattr])
+        for msg in res:
+            if idattr in msg:
+                n = str("%s" % msg[idattr])
+                idn = int(n)
+                if idn > idmax:
+                    idmax = idn
+
+        if idmax != 0:
+           id_number = idmax + 1
+           return id_number
+        else:
+            # msSFU30Max*idNumber is not set
+            # There are no *idNumber attributes in AD.
+            # 10000 is the start number used by ADUC
+            id_number = "10000"
+            return id_number
+
+    def get_object_dn(self, search_filter):
+        """Gets an objects DN
+
+        :param search_filter: LDAP filter to find the object (eg
+            samaccountname=name)
+        """
+        res = self.search(base=self.domain_dn(), 
+                          scope=ldb.SCOPE_SUBTREE,
+                          expression=search_filter,
+                          attrs=["dn", "sAMAccountName"])
+        if len(res) == 0:
+            raise Exception('Unable to find object "%s"' % 
+                            search_filter)
+        assert(len(res) == 1)
+        return res[0].dn
+
-- 
1.9.1


More information about the samba-technical mailing list