s4 net: Add spn module to list/add/remove spn on objects
authorMatthieu Patou <mat@matws.net>
Fri, 2 Jul 2010 19:52:33 +0000 (23:52 +0400)
committerStefan Metzmacher <metze@samba.org>
Sat, 10 Jul 2010 09:18:17 +0000 (11:18 +0200)
Signed-off-by: Stefan Metzmacher <metze@samba.org>
source4/scripting/python/samba/netcmd/__init__.py
source4/scripting/python/samba/netcmd/spn.py [new file with mode: 0644]
source4/selftest/tests.sh
source4/setup/tests/blackbox_spn.sh [new file with mode: 0755]

index 6fd72944ec8a9f0d76a073b9532cd4a776e409de..08ddcefe910adb2aa63e914880f5bfc4bcb2e9ea 100644 (file)
@@ -160,5 +160,7 @@ from samba.netcmd.vampire import cmd_vampire
 commands["vampire"] = cmd_vampire()
 from samba.netcmd.machinepw import cmd_machinepw
 commands["machinepw"] = cmd_machinepw()
+from samba.netcmd.spn import cmd_spn
+commands["spn"] = cmd_spn()
 from samba.netcmd.group import cmd_group
 commands["group"] = cmd_group()
diff --git a/source4/scripting/python/samba/netcmd/spn.py b/source4/scripting/python/samba/netcmd/spn.py
new file mode 100644 (file)
index 0000000..e2eb05d
--- /dev/null
@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+#
+# spn management
+#
+# Copyright Matthieu Patou mat@samba.org 2010
+#
+# 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 samba.getopt as options
+import ldb
+import re
+from samba import provision
+from samba.samdb import SamDB
+from samba.auth import system_session
+from samba.netcmd import (
+    Command,
+    CommandError,
+    SuperCommand,
+    Option
+    )
+
+def _get_user_realm_domain(user):
+    """ get the realm or the domain and the base user
+        from user like:
+        * username
+        * DOMAIN\username
+        * username@REALM
+    """
+    baseuser = user
+    realm = ""
+    domain = ""
+    m = re.match(r"(\w+)\\(\w+$)", user)
+    if m:
+        domain = m.group(1)
+        baseuser = m.group(2)
+        return (baseuser.lower(), domain.upper(), realm)
+    m = re.match(r"(\w+)@(\w+)", user)
+    if m:
+        baseuser = m.group(1)
+        realm = m.group(2)
+    return (baseuser.lower(), domain, realm.upper())
+
+class cmd_spn_list(Command):
+    """List spns of a given user."""
+    synopsis = "%prog spn list <user>"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    takes_args = ["user"]
+
+    def run(self, user, credopts=None, sambaopts=None, versionopts=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp)
+        paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+        sam = SamDB(paths.samdb, session_info=system_session(),
+                    credentials=creds, lp=lp)
+        # TODO once I understand how, use the domain info to naildown
+        # to the correct domain
+        (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+        print cleaneduser
+        res = sam.search(expression="samaccountname=%s" % cleaneduser,
+                            scope=ldb.SCOPE_SUBTREE,
+                            attrs=["servicePrincipalName"])
+        if len(res) >0:
+            spns = res[0].get("servicePrincipalName")
+            found = False
+            flag = ldb.FLAG_MOD_ADD
+            if spns != None:
+                print "User %s has the following servicePrincipalName: " %  str(res[0].dn)
+                for e in spns:
+                    print "\t %s" % (str(e))
+
+            else:
+                print "User %s has no servicePrincipalName" % str(res[0].dn)
+        else:
+            raise CommandError("User %s not found" % user)
+
+class cmd_spn_add(Command):
+    """Create a new spn."""
+    synopsis = "%prog spn add [--force] <name> <user>"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+    takes_options = [
+        Option("--force", help="Force the addition of the spn"\
+                               " even it exists already", action="store_true"),
+            ]
+    takes_args = ["name", "user"]
+
+    def run(self, name, user,  force=False, credopts=None, sambaopts=None, versionopts=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp)
+        paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+        sam = SamDB(paths.samdb, session_info=system_session(),
+                    credentials=creds, lp=lp)
+        res = sam.search(expression="servicePrincipalName=%s" % name,
+                            scope=ldb.SCOPE_SUBTREE,
+                            )
+        if len(res) != 0  and not force:
+            raise CommandError("Service principal %s already"
+                                   " affected to another user" % name)
+
+        (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+        res = sam.search(expression="samaccountname=%s" % cleaneduser,
+                            scope=ldb.SCOPE_SUBTREE,
+                            attrs=["servicePrincipalName"])
+        if len(res) >0:
+            res[0].dn
+            msg = ldb.Message()
+            spns = res[0].get("servicePrincipalName")
+            tab = []
+            found = False
+            flag = ldb.FLAG_MOD_ADD
+            if spns != None:
+                for e in spns:
+                    if str(e) == name:
+                        found = True
+                    tab.append(str(e))
+                flag = ldb.FLAG_MOD_REPLACE
+            tab.append(name)
+            msg.dn = res[0].dn
+            msg["servicePrincipalName"] = ldb.MessageElement(tab, flag,
+                                                "servicePrincipalName")
+            if not found:
+                sam.modify(msg)
+            else:
+                raise CommandError("Service principal %s already"
+                                       " affected to %s" % (name, user))
+        else:
+            raise CommandError("User %s not found" % user)
+
+
+class cmd_spn_delete(Command):
+    """Delete a spn."""
+    synopsis = "%prog spn delete <name> [user]"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "versionopts": options.VersionOptions,
+        }
+
+    takes_args = ["name", "user?"]
+
+    def run(self, name, user=None, credopts=None, sambaopts=None, versionopts=None):
+        lp = sambaopts.get_loadparm()
+        creds = credopts.get_credentials(lp)
+        paths = provision.provision_paths_from_lp(lp, lp.get("realm"))
+        sam = SamDB(paths.samdb, session_info=system_session(),
+                    credentials=creds, lp=lp)
+        res = sam.search(expression="servicePrincipalName=%s" % name,
+                            scope=ldb.SCOPE_SUBTREE,
+                            attrs=["servicePrincipalName", "samAccountName"])
+        if len(res) >0:
+            result = None
+            if user is not None:
+                (cleaneduser, realm, domain) = _get_user_realm_domain(user)
+                for elem in res:
+                    if str(elem["samAccountName"]).lower() == cleaneduser:
+                        result = elem
+                if result is None:
+                    raise CommandError("Unable to find user %s with"
+                                           " spn %s" % (user, name))
+            else:
+                if len(res) != 1:
+                    listUser = ""
+                    for r in res:
+                        listUser = "%s\n%s" % (listUser, str(r.dn))
+                    raise CommandError("More than one user has the spn %s "\
+                           "and no specific user was specified, list of users"\
+                           " with this spn:%s" % (name, listUser))
+                else:
+                    result=res[0]
+
+
+            msg = ldb.Message()
+            spns = result.get("servicePrincipalName")
+            tab = []
+            if spns != None:
+                for e in spns:
+                    if str(e) != name:
+                        tab.append(str(e))
+                flag = ldb.FLAG_MOD_REPLACE
+            msg.dn = result.dn
+            msg["servicePrincipalName"] = ldb.MessageElement(tab, flag,
+                                            "servicePrincipalName")
+            sam.modify(msg)
+        else:
+            raise CommandError("Service principal %s not affected" % name)
+
+class cmd_spn(SuperCommand):
+    """User management [server connection needed]"""
+
+    subcommands = {}
+    subcommands["add"] = cmd_spn_add()
+    subcommands["list"] = cmd_spn_list()
+    subcommands["delete"] = cmd_spn_delete()
+
index 3d331f34a92b3b15c3d3e47fa377cd8e2e784fcf..c70eb877cfb7686c25ab94bfe481c508121ad01e 100755 (executable)
@@ -520,6 +520,7 @@ plantestsuite "blackbox.upgradeprovision.py" none PYTHON="$PYTHON" $samba4srcdir
 plantestsuite "blackbox.setpassword.py" none PYTHON="$PYTHON" $samba4srcdir/setup/tests/blackbox_setpassword.sh "$PREFIX/provision"
 plantestsuite "blackbox.newuser.py" none PYTHON="$PYTHON" $samba4srcdir/setup/tests/blackbox_newuser.sh "$PREFIX/provision"
 plantestsuite "blackbox.group.py" none PYTHON="$PYTHON" $samba4srcdir/setup/tests/blackbox_group.sh "$PREFIX/provision"
+plantestsuite "blackbox.spn.py" dc:local PYTHON="$PYTHON" $samba4srcdir/setup/tests/blackbox_spn.sh "$PREFIX/dc"
 
 # DRS python tests
 plantestsuite "drs_delete_object.python" vampire_dc PYTHONPATH="$PYTHONPATH:$samba4srcdir/torture/drs/python" DC1=\$DC_SERVER DC2=\$VAMPIRE_DC_SERVER $SUBUNITRUN delete_object -U"\$DOMAIN/\$DC_USERNAME"%"\$DC_PASSWORD"
diff --git a/source4/setup/tests/blackbox_spn.sh b/source4/setup/tests/blackbox_spn.sh
new file mode 100755 (executable)
index 0000000..f2fd8ac
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+if [ $# -lt 1 ]; then
+cat <<EOF
+Usage: blackbox_group.sh PREFIX
+EOF
+exit 1;
+fi
+
+PREFIX="$1"
+shift 1
+
+. `dirname $0`/../../../testprogs/blackbox/subunit.sh
+
+
+net="./bin/net"
+
+CONFIG="--configfile=$PREFIX/etc/smb.conf"
+
+#creation of two test subjects
+testit "addspn" $net spn add FOO/bar Administrator $CONFIG
+testit "delspn" $net spn delete FOO/bar $CONFIG
+testit "readdspn" $net spn add FOO/bar Administrator $CONFIG
+testit_expect_failure "failexistingspn" $net spn add FOO/bar Guest $CONFIG
+testit "existingspnforce" $net spn add --force FOO/bar Guest  $CONFIG
+testit_expect_failure "faildelspnnotgooduser" $net spn delete FOO/bar krbtgt $CONFIG
+testit_expect_failure "faildelspnmoreoneuser" $net spn delete FOO/bar $CONFIG
+testit "deluserspn" $net spn delete FOO/bar Guest $CONFIG
+testit "dellastuserspn" $net spn delete FOO/bar $CONFIG
+testit_expect_failure "faildelspn" $net spn delete FOO/bar $CONFIG
+testit_expect_failure "failaddspn" $net spn add FOO/bar nonexistinguser $CONFIG
+
+exit $failed