diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2013-03-30 22:59:57 +0100 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2013-03-30 23:09:30 +0100 |
commit | 012b18554e5e6a408a11a7157a30c5d068f2d3d1 (patch) | |
tree | 50c70342106c2674d61b5559f7dfa89dc1f506bc /utils | |
parent | d0482fb56654037d01feb0f7a27206aefacf7112 (diff) |
Initial version of a chsh.ldap utility
Diffstat (limited to 'utils')
-rw-r--r-- | utils/Makefile.am | 4 | ||||
-rwxr-xr-x | utils/chsh.py | 70 | ||||
-rw-r--r-- | utils/cmdline.py | 18 | ||||
-rw-r--r-- | utils/nslcd.py | 23 | ||||
-rw-r--r-- | utils/shells.py | 64 | ||||
-rw-r--r-- | utils/users.py | 60 |
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 |