diff --git a/bin/csc-chfn b/bin/csc-chfn new file mode 100755 index 0000000..4b6be80 --- /dev/null +++ b/bin/csc-chfn @@ -0,0 +1,153 @@ +#!/usr/bin/python2.4 -- +""" +chfn - change real user name and information + +This utility imitates chfn(1) from the shadow password suite, but makes its +changes in the LDAP directory rather than in the passwd file. + +When run from an unprivileged account, authentication will be performed +before the account information is changed. +""" +import os, sys, pwd, getopt, PAM + +safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG' + 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY', + 'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK', + 'SSH_CLIENT'] + +for key in os.environ.keys(): + if key not in safe_environment: + del os.environ[key] + +os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin' + +for pathent in sys.path[:]: + if not pathent.find('/usr') == 0: + sys.path.remove(pathent) + +from csc.common.excep import InvalidArgument +from csc.adm import accounts + +progname = os.path.basename(sys.argv[0]) + +OPTION_MAP = { + '-f': 'fullname', + '-r': 'roomnumber', + '-w': 'workphone', + '-h': 'homephone', + '-o': 'other' +} +LONG_NAMES = [ + ('fullname', 'Full Name'), + ('roomnumber', 'Room Number'), + ('workphone', 'Work Phone'), + ('homephone', 'Home Phone'), + ('other', 'Other') +] +READONLY_FIELDS = [ 'fullname', 'other' ] + +def usage(): + umesg = "Usage: %s [-f full name] [-r room no] [-w work ph] " + \ + "[-h home ph] [-o other] [user]" + print umesg % progname + sys.exit(2) + + +def whoami(): + uid = os.getuid() + username = os.getlogin() + if pwd.getpwnam(username).pw_uid != uid: + username = pwd.getpwuid(uid).pw_name + return (uid, username) + +def authenticate(username): + auth = PAM.pam() + auth.start('chsh', username) + try: + auth.authenticate() + auth.acct_mgmt() + except PAM.error, resp: + print "%s: %s" % (progname, resp.args[0]) + sys.exit(1) + +def main(): + + pwuid, pwnam = whoami() + + gecos_params = {} + + try: + options, arguments = getopt.gnu_getopt(sys.argv[1:], 'f:r:w:h:o:') + for opt, val in options: + gecos_params[OPTION_MAP[opt]] = val + if len(arguments) > 1: + usage() + elif len(arguments) == 1: + username = arguments[0] + else: + username = pwnam + except getopt.GetoptError, e: + usage() + + for field in READONLY_FIELDS: + if field in gecos_params and pwuid: + print "%s: Permission denied." % progname + sys.exit(1) + + try: + if pwuid and pwd.getpwnam(username).pw_uid != pwuid: + print "%s: Permission denied." % progname + sys.exit(1) + except KeyError: + print "%s: unknown user %s" % (progname, username) + sys.exit(1) + + try: + accounts.connect() + gecos_raw = accounts.get_gecos(username) + gecos = accounts.parse_gecos(gecos_raw) + + if pwuid: + authenticate(username) + + if not gecos_params: + print "Changing the user information for %s" % username + print "Enter the new value, or press ENTER for the default" + for field, longname in LONG_NAMES: + if pwuid and field == 'other' and 'other' in READONLY_FIELDS: + continue + if gecos[field] is None: + gecos[field] = "" + if field in READONLY_FIELDS and pwuid: + print " %s: %s" % (longname, gecos[field]) + else: + print " %s: [%s]:" % (longname, gecos[field]), + new_value = raw_input() + if new_value: + gecos[field] = new_value.strip() + else: + gecos.update(gecos_params) + + gecos_raw_new = accounts.build_gecos(**gecos) + if gecos_raw != gecos_raw_new: + accounts.update_gecos(username, gecos_raw_new) + + except InvalidArgument, e: + longnames = dict(LONG_NAMES) + longname = longnames.get(e.argname, e.argname).lower() + print "%s: invalid %s: %s" % (progname, longname, e.argval) + sys.exit(1) + +if __name__ == '__main__': + exceps = ( accounts.ConfigurationException, accounts.LDAPException, + accounts.KrbException, accounts.AccountException ) + try: + main() + except KeyboardInterrupt: + sys.exit(130) + except IOError, e: + print "%s: %s: %s" % (progname, e.filename, e.strerror) + sys.exit(1) + except exceps, e: + print "%s: %s" % (progname, e) + sys.exit(1) diff --git a/bin/csc-chsh b/bin/csc-chsh new file mode 100755 index 0000000..ef0db49 --- /dev/null +++ b/bin/csc-chsh @@ -0,0 +1,118 @@ +#!/usr/bin/python2.4 -- +""" +chsh - change login shell + +This utility imitates chsh(1) from the shadow password suite, but makes its +changes in the LDAP directory rather than in the passwd file. + +When run from an unprivileged account, authentication will be performed +before the shell is changed, and the new shell must be listed in /etc/shells. +""" +import os, sys, pwd, getopt, PAM + +safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG' + 'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY', + 'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK', + 'SSH_CLIENT'] + +for key in os.environ.keys(): + if key not in safe_environment: + del os.environ[key] + +os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin' + +for pathent in sys.path[:]: + if not pathent.find('/usr') == 0: + sys.path.remove(pathent) + +from csc.common.excep import InvalidArgument +from csc.adm import accounts + +progname = os.path.basename(sys.argv[0]) + +def usage(): + print "Usage: %s [-s shell] [username]" % progname + sys.exit(2) + +def whoami(): + uid = os.getuid() + username = os.getlogin() + if pwd.getpwnam(username).pw_uid != uid: + username = pwd.getpwuid(uid).pw_name + return (uid, username) + +def authenticate(username): + auth = PAM.pam() + auth.start('chsh', username) + try: + auth.authenticate() + auth.acct_mgmt() + except PAM.error, resp: + print "%s: %s" % (progname, resp.args[0]) + sys.exit(1) + +def main(): + + pwuid, pwnam = whoami() + + try: + options, arguments = getopt.gnu_getopt(sys.argv[1:], 's:') + new_shell = None + for opt, val in options: + if opt == '-s': + new_shell = val + if len(arguments) > 1: + usage() + elif len(arguments) == 1: + username = arguments[0] + else: + username = pwnam + except getopt.GetoptError, e: + usage() + + try: + if pwuid and pwd.getpwnam(username).pw_uid != pwuid: + print "%s: You may not change the shell for %s." % (progname, username) + sys.exit(1) + except KeyError: + print "%s: unknown user %s" % (progname, username) + sys.exit(1) + + try: + accounts.connect() + current_shell = accounts.get_shell(username) + + if pwuid: + authenticate(username) + + if not new_shell: + print "Changing the login shell for %s" % username + print "Enter the new value, or press ENTER for the default" + print " Login Shell [%s]:" % current_shell, + new_shell = raw_input() + if not new_shell: + new_shell = current_shell + + if new_shell != current_shell: + accounts.update_shell(username, new_shell, pwuid != 0) + + except InvalidArgument, e: + if e.argname == 'shell': + print "%s: %s: invalid shell" % (progname, new_shell) + sys.exit(1) + else: + raise + +if __name__ == '__main__': + exceps = ( accounts.ConfigurationException, accounts.LDAPException, + accounts.KrbException, accounts.AccountException ) + try: + main() + except KeyboardInterrupt: + sys.exit(130) + except IOError, e: + print "%s: %s: %s" % (progname, e.filename, e.strerror) + sys.exit(1) + except exceps, e: + print "%s: %s" % (progname, e) + sys.exit(1) diff --git a/pylib/csc/adm/accounts.py b/pylib/csc/adm/accounts.py index caa111e..288fc7c 100644 --- a/pylib/csc/adm/accounts.py +++ b/pylib/csc/adm/accounts.py @@ -412,7 +412,7 @@ def update_shell(username, shell, check=True): # reject nonexistent or nonexecutable shells if not os.access(shell, os.X_OK) or not os.path.isfile(shell): - raise InvalidArgument("shell", shell, "is not a regular executable file") + raise InvalidArgument("shell", shell, "not an executable file") if check: diff --git a/pylib/csc/common/conf.py b/pylib/csc/common/conf.py index e22d03e..f9e6972 100644 --- a/pylib/csc/common/conf.py +++ b/pylib/csc/common/conf.py @@ -66,7 +66,7 @@ def read(filename, included=None): included - files previously read (internal) Exceptions: - ConfigurationException - when the configuration file cannot be read + IOError - when the configuration file cannot be read """ if not included: @@ -75,10 +75,7 @@ def read(filename, included=None): return {} included.append(filename) - try: - conffile = open(filename) - except IOError: - raise ConfigurationException('unable to read configuration file: "%s"' % filename) + conffile = open(filename) options = {}