summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorArthur de Jong <arthur@arthurdejong.org>2013-03-30 22:59:57 +0100
committerArthur de Jong <arthur@arthurdejong.org>2013-03-30 23:09:30 +0100
commit012b18554e5e6a408a11a7157a30c5d068f2d3d1 (patch)
tree50c70342106c2674d61b5559f7dfa89dc1f506bc /utils
parentd0482fb56654037d01feb0f7a27206aefacf7112 (diff)
Initial version of a chsh.ldap utility
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