summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2013-03-30 23:10:34 +0100
committerArthur de Jong <arthur@arthurdejong.org>2013-03-30 23:10:34 +0100
commit62a409cb43b441c32692f414a1867176d37034ac (patch)
tree50c70342106c2674d61b5559f7dfa89dc1f506bc /utils
parentaae36cfcfb6ec00776f6da1e0d1fd5f90a72f2dd (diff)
parent012b18554e5e6a408a11a7157a30c5d068f2d3d1 (diff)
Implement used modification functionality
This adds user information modification functionality to nslcd and pynslcd and implements a chsh.ldap utility that can be used to change the login shell of a user (similar to the normal chsh command). The user modification functionality should allow for generic modifications of user information. More utility commands to perform modifications remain to be implemented.
Diffstat (limited to 'utils')
-rw-r--r--utils/Makefile.am4
-rwxr-xr-xutils/chsh.py70
-rw-r--r--utils/cmdline.py18
-rw-r--r--utils/nslcd.py23
-rw-r--r--utils/shells.py64
-rw-r--r--utils/users.py60
6 files changed, 237 insertions, 2 deletions
diff --git a/utils/Makefile.am b/utils/Makefile.am
index e9233d8..a10e61d 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -19,7 +19,7 @@
utilsdir = $(datadir)/nslcdutils
-utils_PYTHON = cmdline.py getent.py nslcd.py
+utils_PYTHON = cmdline.py nslcd.py getent.py chsh.py shells.py users.py
nodist_utils_PYTHON = constants.py
CLEANFILES = $(nodist_utils_PYTHON)
@@ -36,7 +36,7 @@ constants.py: ../pynslcd/constants.py
# create symbolic links to the commands and fix permissions
install-data-hook:
$(MKDIR_P) $(DESTDIR)$(bindir)
- set -ex; for cmd in getent ; do \
+ set -ex; for cmd in getent chsh ; do \
chmod a+rx $(DESTDIR)$(utilsdir)/$$cmd.py ; \
[ -L $(DESTDIR)$(bindir)/$$cmd.ldap ] || $(LN_S) $(utilsdir)/$$cmd.py $(DESTDIR)$(bindir)/$$cmd.ldap ; \
done
diff --git a/utils/chsh.py b/utils/chsh.py
new file mode 100755
index 0000000..30c5c12
--- /dev/null
+++ b/utils/chsh.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# chsh.py - program for changing the login shell using nslcd
+#
+# Copyright (C) 2013 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import argparse
+
+from cmdline import VersionAction, ListShellsAction
+import constants
+import nslcd
+import shells
+import users
+
+
+# set up command line parser
+parser = argparse.ArgumentParser(
+ description='Change the user login shell in LDAP.',
+ epilog='Report bugs to <%s>.' % constants.PACKAGE_BUGREPORT
+ )
+parser.add_argument('-V', '--version', action=VersionAction)
+parser.add_argument('-s', '--shell', help='login shell for the user account')
+parser.add_argument('-l', '--list-shells', action=ListShellsAction)
+parser.add_argument('username', metavar='USER', nargs='?',
+ help="the user who's shell to change")
+
+
+def ask_shell(oldshell):
+ """Ask the user to provide a shell."""
+ shell = raw_input(' Login Shell [%s]: ' % oldshell)
+ return shell or oldshell
+
+
+# parse arguments
+args = parser.parse_args()
+# check username part
+user = users.User(args.username)
+user.check()
+# check the command line shell if one was provided (to fail early)
+shell = args.shell
+if shell is not None:
+ shells.check(shell, user.asroot)
+# prompt for a password if required
+password = user.get_passwd()
+# prompt for a shell if it was not specified on the command line
+if shell is None:
+ print 'Enter the new value, or press ENTER for the default'
+ shell = ask_shell(user.shell)
+ shells.check(shell, user.asroot)
+# perform the modification
+result = nslcd.usermod(user.username, user.asroot, password, {
+ constants.NSLCD_USERMOD_SHELL: shell,
+ })
+# TODO: print proper response
diff --git a/utils/cmdline.py b/utils/cmdline.py
index eb84fe3..3d7d58f 100644
--- a/utils/cmdline.py
+++ b/utils/cmdline.py
@@ -48,3 +48,21 @@ class VersionAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
print version_string
parser.exit()
+
+
+class ListShellsAction(argparse.Action):
+
+ def __init__(self, option_strings, dest,
+ help='list the shells found in /etc/shells'):
+ super(ListShellsAction, self).__init__(
+ option_strings=option_strings,
+ dest=argparse.SUPPRESS,
+ default=argparse.SUPPRESS,
+ nargs=0,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ import shells
+ for shell in shells.list_shells():
+ print shell
+ parser.exit()
diff --git a/utils/nslcd.py b/utils/nslcd.py
index 06165cc..65e5822 100644
--- a/utils/nslcd.py
+++ b/utils/nslcd.py
@@ -111,3 +111,26 @@ class NslcdClient(object):
def __del__(self):
self.close()
+
+
+def usermod(username, asroot=False, password=None, args=None):
+ # open a connection to nslcd
+ con = NslcdClient(constants.NSLCD_ACTION_USERMOD)
+ # write the request information
+ con.write_string(username)
+ con.write_int32(1 if asroot else 0)
+ con.write_string(password)
+ for k, v in args.items():
+ con.write_int32(k)
+ con.write_string(v)
+ con.write_int32(constants.NSLCD_USERMOD_END)
+ # read the response
+ assert con.get_response() == constants.NSLCD_RESULT_BEGIN
+ response = {}
+ while True:
+ key = con.read_int32()
+ if key == constants.NSLCD_USERMOD_END:
+ break
+ response[key] = con.read_string()
+ # return the response
+ return response
diff --git a/utils/shells.py b/utils/shells.py
new file mode 100644
index 0000000..cc3fca1
--- /dev/null
+++ b/utils/shells.py
@@ -0,0 +1,64 @@
+# coding: utf-8
+
+# shells.py - functions for validating user shells
+#
+# Copyright (C) 2013 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import ctypes
+import ctypes.util
+import os
+import sys
+
+
+def list_shells():
+ """List the shells from /etc/shells."""
+ libc = ctypes.CDLL(ctypes.util.find_library("c"))
+ libc.setusershell()
+ while True:
+ shell = ctypes.c_char_p(libc.getusershell()).value
+ if not shell:
+ break
+ yield shell
+ libc.endusershell()
+
+
+def shellexists(shell):
+ """Check if the provided shell exists and is executable."""
+ return os.path.isfile(shell) and os.access(shell, os.X_OK)
+
+
+def check(shell, asroot=False):
+ """Check if the specified shell is valid and exit if it isn't."""
+ # if the shell is listed in /etc/shells, everything should be OK
+ if shell in list_shells():
+ return
+ # if we are not root, bail out
+ if not asroot:
+ if not shell:
+ # FIXME: print to stderr
+ print '%s: empty shell not allowed' % sys.argv[0]
+ else:
+ # FIXME: print to stderr
+ print '%s: %s is an invalid shell' % (sys.argv[0], shell)
+ sys.exit(1)
+ # warn if something seems wrong
+ if not shell:
+ # FIXME: print to stderr
+ print '%s: Warning: setting empty shell' % sys.argv[0]
+ elif not shellexists(shell):
+ print '%s: Warning: %s does not exist' % (sys.argv[0], shell)
diff --git a/utils/users.py b/utils/users.py
new file mode 100644
index 0000000..02216d6
--- /dev/null
+++ b/utils/users.py
@@ -0,0 +1,60 @@
+# coding: utf-8
+
+# users.py - functions for validating the user to change information for
+#
+# Copyright (C) 2013 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+import getpass
+import os
+import pwd
+import sys
+
+
+class User(object):
+
+ def __init__(self, username):
+ self.myuid = os.getuid()
+ if username:
+ userinfo = pwd.getpwnam(username)
+ else:
+ self.asroot = False
+ userinfo = pwd.getpwuid(self.myuid)
+ (self.username, ignore, self.uid, self.gid, self.gecos, self.homedir,
+ self.shell) = userinfo
+ # if we are trying to modify another user we should be root
+ self.asroot = self.myuid != self.uid
+
+ def check(self):
+ """Check if the user we want to modify is an LDAP user and whether
+ we may modify the user information."""
+ if self.asroot and self.myuid != 0:
+ print "%s: you may not modify user '%s'.\n" % \
+ (sys.argv[0], self.username)
+ sys.exit(1)
+ # FIXME: check if the user is an LDAP user
+
+ def get_passwd(self):
+ """Ask and return a password that is required to change the user."""
+ # FIXME: only ask the password if we require it
+ # (e.g. when root and nslcd has userpwmoddn we don't need to)
+ return getpass.getpass(
+ 'LDAP administrator password: '
+ if self.asroot else
+ 'LDAP password for %s: ' % self.username
+ )
+ # FIXME: check if the provided password is valid