Remove chfn and chsh and allow shell changes in the gui

The chsh and chfn programs were broken anyway.
This commit is contained in:
Michael Spang 2007-12-16 01:16:21 -05:00
parent 1231cddf56
commit 9470a42998
7 changed files with 136 additions and 250 deletions

View File

@ -1,140 +0,0 @@
#!/usr/bin/python
"""
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
from ceo import accounts
from ceo.excep import InvalidArgument
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()
euid = os.geteuid()
os.setreuid(euid, euid)
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)

View File

@ -1,105 +0,0 @@
#!/usr/bin/python
"""
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
from ceo import accounts
from ceo.excep import InvalidArgument
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()
euid = os.geteuid()
os.setreuid(euid, euid)
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)

View File

@ -9,7 +9,7 @@ Transactions are used in each method that modifies the database.
Future changes to the members database that need to be atomic
must also be moved into this module.
"""
import re, subprocess, ldap
import os, re, subprocess, ldap
from ceo import conf, ldapi
from ceo.excep import InvalidArgument
@ -307,6 +307,32 @@ def change_group_member(action, group, userid):
### Shells ###
def get_shell(userid):
member = ldapi.lookup(ld, 'uid', userid, cfg['users_base'])
if not member:
raise NoSuchMember(userid)
if 'loginShell' not in member:
return
return member['loginShell'][0]
def get_shells():
return [ sh for sh in open(cfg['shells_file']).read().split("\n")
if sh
and sh[0] == '/'
and not '#' in sh
and os.access(sh, os.X_OK) ]
def set_shell(userid, shell):
if not shell in get_shells():
raise InvalidArgument("shell", shell, "is not in %s" % cfg['shells_file'])
ldapi.modify(ld, 'uid', userid, cfg['users_base'], [ (ldap.MOD_REPLACE, 'loginShell', [ shell ]) ])
### Clubs ###
def create_club(username, name):

View File

@ -25,11 +25,13 @@ class InfoPage(WizardPanel):
name = member.get('cn', [''])[0]
userid = self.state['userid']
program = member.get('program', [''])[0]
shell = member.get('loginShell', [''])[0]
terms = member.get('term', [])
self.name.set_text("Name: %s" % name)
self.userid.set_text("User: %s" % userid)
self.program.set_text("Program: %s" % program)
self.program.set_text("Shell: %s" % shell)
self.terms.set_text("Terms: %s" % ", ".join(terms))
def check(self):
pop_window()

View File

@ -2,7 +2,7 @@ import sys, random, ldap, urwid.curses_display
from ceo import members, ldapi
from ceo.urwid.widgets import *
from ceo.urwid.window import *
from ceo.urwid import newmember, renew, info, search, positions, groups
from ceo.urwid import newmember, renew, info, search, positions, groups, shell
ui = urwid.curses_display.Screen()
@ -52,15 +52,13 @@ syscom_data = {
"groups" : [ "office", "staff", "adm", "src" ],
}
def menu_items(items):
return [ urwid.AttrWrap( ButtonText( cb, data, txt ), 'menu', 'selected') for (txt, cb, data) in items ]
def main_menu():
menu = [
("New Member", new_member, None),
("Renew Membership", renew_member, None),
("Create Club Account", new_club, None),
("Display Member", display_member, None),
("Change Shell", change_shell, None),
("Search", search_members, None),
("Manage Club or Group Members", manage_group, None),
("Manage Positions", manage_positions, None),
@ -136,6 +134,14 @@ def manage_positions(data):
positions.EndPage,
], (50, 15))
def change_shell(data):
push_wizard("Change Shell", [
shell.IntroPage,
shell.YouPage,
shell.ShellPage,
shell.EndPage
], (50, 15))
def run():
push_window( main_menu(), program_name() )
event_loop( ui )

94
ceo/urwid/shell.py Normal file
View File

@ -0,0 +1,94 @@
import urwid, ldap, pwd, os
from ceo import members, terms, ldapi
from ceo.urwid.widgets import *
from ceo.urwid.window import *
class IntroPage(WizardPanel):
def init_widgets(self):
self.widgets = [
urwid.Text( "Changing Login Shell" ),
urwid.Divider(),
urwid.Text( "You can change your shell here. Request more shells "
"by emailing systems-committee." )
]
def focusable(self):
return False
class YouPage(WizardPanel):
def init_widgets(self):
you = pwd.getpwuid(os.getuid()).pw_name
self.userid = WordEdit("Username: ", you)
self.widgets = [
urwid.Text( "Member Information" ),
urwid.Divider(),
self.userid,
]
def check(self):
self.state['userid'] = self.userid.get_edit_text()
self.state['member'] = None
if self.state['userid']:
self.state['member'] = members.get(self.userid.get_edit_text())
if not self.state['member']:
set_status("Member not found")
self.focus_widget(self.userid)
return True
class ShellPage(WizardPanel):
def init_widgets(self):
self.midtext = urwid.Text("")
self.widgets = [
urwid.Text("Choose a Shell"),
urwid.Divider(),
]
def set_shell(radio_button, new_state, shell):
if new_state:
self.state['shell'] = shell
radio_group = []
self.shells = members.get_shells()
self.shellw = [ urwid.RadioButton(radio_group, shell,
on_state_change=set_shell, user_data=shell)
for shell in self.shells ]
self.widgets.extend(self.shellw)
def set_shell(self, shell):
i = self.shells.index(shell)
self.shellw[i].set_state(True)
def focusable(self):
return True
def activate(self):
self.set_shell(self.state['member']['loginShell'][0])
class EndPage(WizardPanel):
def init_widgets(self):
self.headtext = urwid.Text("")
self.midtext = urwid.Text("")
self.widgets = [
self.headtext,
urwid.Divider(),
self.midtext,
]
def focusable(self):
return False
def activate(self):
problem = None
try:
user, shell = self.state['userid'], self.state['shell']
members.set_shell(user, shell)
self.headtext.set_text("Login Shell Changed")
self.midtext.set_text("The shell for %s has been changed to %s."
% (user, shell))
except ldap.LDAPError, e:
problem = ldapi.format_ldaperror(e)
except members.MemberException, e:
problem = str(e)
if problem:
self.headtext.set_text("Failed to Change Shell")
self.midtext.set_text("Perhaps you don't have permission to change %s's shell? "
"The error was:\n\n%s" % (user, problem))
def check(self):
pop_window()

View File

@ -2,6 +2,9 @@ import urwid
from ceo.urwid.ldapfilter import *
from ceo.urwid.window import raise_back, push_window
def menu_items(items):
return [ urwid.AttrWrap( ButtonText( cb, data, txt ), 'menu', 'selected') for (txt, cb, data) in items ]
def push_wizard(name, pages, dimensions=(50, 10)):
state = {}
wiz = Wizard()