os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
for pathent in sys.path[:]:
- if not pathent.find('/usr') == 0:
+ if not pathent.find('/usr') == 0 and not pathent.find('/var') == 0:
sys.path.remove(pathent)
euid = os.geteuid()
print str(e)
sys.exit(1)
-import csc.apps.legacy.main
-csc.apps.legacy.main.run()
+import csc.apps.urwid.main
+csc.apps.urwid.main.start()
--- /dev/null
+#!/usr/bin/python2.4 --
+"""CEO SUID Python Wrapper Script"""
+import os, sys
+
+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['LESSSECURE'] = '1'
+os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
+
+for pathent in sys.path[:]:
+ if not pathent.find('/usr') == 0:
+ sys.path.remove(pathent)
+
+euid = os.geteuid()
+egid = os.getegid()
+try:
+ os.setreuid(euid, euid)
+ os.setregid(egid, egid)
+except OSError, e:
+ print str(e)
+ sys.exit(1)
+
+import csc.apps.legacy.main
+csc.apps.legacy.main.run()
build-stamp:
mkdir build
$(CC) -DFULL_PATH='"/usr/lib/csc/ceo"' -o build/ceo misc/setuid-prog.c
+ $(CC) -DFULL_PATH='"/usr/lib/csc/ceo-old"' -o build/ceo-old misc/setuid-prog.c
$(CC) -DFULL_PATH='"/usr/lib/csc/addhomedir"' -o build/addhomedir misc/setuid-prog.c
$(CC) -DFULL_PATH='"/usr/lib/csc/ceoquery"' -o build/ceoquery misc/setuid-prog.c
$(CC) -DFULL_PATH='"/usr/lib/csc/csc-chfn"' -o build/csc-chfn misc/setuid-prog.c
dh_install etc/* etc/csc/
dh_install sql/* usr/share/csc/
- dh_install bin/ceo bin/addhomedir bin/ceoquery bin/csc-chsh bin/csc-chfn usr/lib/csc/
- dh_install build/ceo build/addhomedir build/ceoquery build/csc-chsh build/csc-chfn usr/bin/
+ dh_install bin/ceo bin/ceo-old bin/addhomedir bin/ceoquery bin/csc-chsh bin/csc-chfn usr/lib/csc/
+ dh_install build/ceo build/ceo-old build/addhomedir build/ceoquery build/csc-chsh build/csc-chfn usr/bin/
dh_install misc/csc.schema etc/ldap/schema/
binary-arch: build install
* Python bindings for libkadm5
* Python bindings for quota?
-* New UI: urwid-based?
* Logging via syslog
* Try to recover and roll-back on error during account creation
* Write manpages
--- /dev/null
+"""
+Urwid User Interface
+"""
--- /dev/null
+import urwid
+
+from csc.apps.urwid.widgets import *
+from csc.apps.urwid.window import *
+
+from csc.adm import accounts, members
+from csc.common.excep import InvalidArgument
+
+class InfoPage(WizardPanel):
+ def init_widgets(self):
+ self.userid = urwid.Text("")
+ self.name = urwid.Text("")
+ self.terms = urwid.Text("")
+ self.program = urwid.Text("")
+
+ self.widgets = [
+ urwid.Text( "Member Details" ),
+ urwid.Divider(),
+ self.name,
+ self.userid,
+ self.program,
+ urwid.Divider(),
+ self.terms,
+ ]
+ def focusable(self):
+ return False
+ def activate(self):
+ member = self.state.get('member', {})
+ name = member.get('cn', [''])[0]
+ userid = self.state['userid']
+ program = member.get('program', [''])[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.terms.set_text("Terms: %s" % ", ".join(terms))
+ def check(self):
+ pop_window()
--- /dev/null
+import random, time
+import urwid, urwid.curses_display
+
+from csc.apps.urwid.widgets import *
+from csc.apps.urwid.window import *
+import csc.apps.urwid.newmember as newmember
+import csc.apps.urwid.renew as renew
+import csc.apps.urwid.info as info
+import csc.apps.urwid.search as search
+
+from csc.adm import accounts, members, terms
+from csc.common.excep import InvalidArgument
+
+ui = urwid.curses_display.Screen()
+
+ui.register_palette([
+ # name, foreground, background, mono
+ ('banner', 'light gray', 'default', None),
+ ('menu', 'light gray', 'default', 'bold'),
+ ('selected', 'black', 'light gray', 'bold'),
+])
+
+
+def program_name():
+ cwords = [ "CSC" ] * 20 + [ "Club" ] * 10 + [ "Campus" ] * 5 + \
+ [ "Communist", "Canadian", "Celestial", "Cryptographic", "Calum's",
+ "Canonical", "Capitalist", "Catastrophic", "Ceremonial", "Chaotic", "Civic",
+ "City", "County", "Caffeinated" ]
+ ewords = [ "Embellished", "Ergonomic", "Electric", "Eccentric", "European", "Economic",
+ "Evil", "Egotistical", "Elliptic", "Emasculating", "Embalming",
+ "Embryonic", "Emigrant", "Emissary's", "Emoting", "Employment", "Emulated",
+ "Enabling", "Enamoring", "Encapsulated", "Enchanted", "Encoded", "Encrypted",
+ "Encumbered", "Endemic", "Enhanced", "Enigmatic", "Enlightened", "Enormous",
+ "Enrollment", "Enshrouded", "Ephermal", "Epidemic", "Episodic", "Epsilon",
+ "Equitable", "Equestrian", "Equilateral", "Erroneous", "Erratic",
+ "Espresso", "Essential", "Estate", "Esteemed", "Eternal", "Ethical", "Eucalyptus",
+ "Euphemistic", "Envangelist", "Evasive", "Everyday", "Evidence", "Eviction", "Evildoer's",
+ "Evolution", "Exacerbation", "Exalted", "Examiner's", "Excise", "Exciting", "Exclusion",
+ "Exec", "Executioner's", "Exile", "Existential", "Expedient", "Expert", "Expletive",
+ "Exploiter's", "Explosive", "Exponential", "Exposing", "Extortion", "Extraction",
+ "Extraneous", "Extravaganza", "Extreme", "Extraterrestrial", "Extremist", "Eerie" ]
+ owords = [ "Office" ] * 50 + [ "Outhouse", "Outpost" ]
+
+ cword = random.choice(cwords)
+ eword = random.choice(ewords)
+ oword = random.choice(owords)
+
+ return "%s %s %s" % (cword, eword, oword)
+
+def menu_items(items):
+ return [ urwid.AttrWrap( ButtonText( cb, txt ), 'menu', 'selected') for (txt, cb) in items ]
+
+def main_menu():
+ menu = [
+ ("New Member", new_member),
+ ("Renew Membership", renew_member),
+ ("Display Member", display_member),
+ ("Search", search_members),
+ ("Exit", raise_abort),
+ ]
+
+ listbox = urwid.ListBox( menu_items( menu ) )
+ return listbox
+
+def push_wizard(name, pages, dimensions=(50, 10)):
+ state = {}
+ wiz = Wizard()
+ for page in pages:
+ wiz.add_panel( page(state) )
+ push_window( urwid.Filler( urwid.Padding(
+ urwid.LineBox(wiz), 'center', dimensions[0]),
+ 'middle', dimensions[1] ), name )
+
+def new_member(*args, **kwargs):
+ push_wizard("New Member", [
+ newmember.IntroPage,
+ newmember.InfoPage,
+ newmember.SignPage,
+ newmember.PassPage,
+ newmember.EndPage,
+ ])
+
+def renew_member(*args, **kwargs):
+ push_wizard("Renew Membership", [
+ renew.IntroPage,
+ renew.UserPage,
+ renew.TermPage,
+ renew.PayPage,
+ renew.EndPage,
+ ])
+
+def display_member(a):
+ push_wizard("Display Member", [
+ renew.UserPage,
+ info.InfoPage,
+ ], (60, 15))
+
+def search_members(a):
+ menu = [
+ ("Members by term", search_term),
+ ("Members by name", search_name),
+ ("Back", raise_back),
+ ]
+
+ listbox = urwid.ListBox( menu_items( menu ) )
+ push_window(listbox, "Search")
+
+def search_name(a):
+ push_wizard("By Name", [ search.NamePage ])
+
+def search_term(a):
+ push_wizard("By Term", [ search.TermPage ])
+
+def run():
+ push_window( main_menu(), program_name() )
+ event_loop( ui )
+
+def start():
+ ui.run_wrapper( run )
+
+if __name__ == '__main__':
+ start()
--- /dev/null
+import urwid
+from csc.apps.urwid.widgets import *
+from csc.apps.urwid.window import *
+
+from csc.adm import accounts, members
+from csc.common.excep import InvalidArgument
+
+class IntroPage(WizardPanel):
+ def init_widgets(self):
+ self.widgets = [
+ urwid.Text( "Joining the Computer Science Club" ),
+ urwid.Divider(),
+ urwid.Text( "CSC membership is $2.00 for one term. Please ensure "
+ "the fee is deposited into the safe before continuing." ),
+ ]
+ def focusable(self):
+ return False
+
+class InfoPage(WizardPanel):
+ def init_widgets(self):
+ self.userid = WordEdit("UWdir ID: ")
+ self.name = SingleEdit("Full name: ")
+ self.program = SingleEdit("Program of Study: ")
+ self.widgets = [
+ urwid.Text( "Member Information - Please Check ID" ),
+ urwid.Divider(),
+ self.userid,
+ self.name,
+ self.program,
+ ]
+ def check(self):
+ self.state['userid'] = self.userid.get_edit_text()
+ self.state['name'] = self.name.get_edit_text()
+ self.state['program'] = self.program.get_edit_text()
+
+ if len( self.state['userid'] ) < 4:
+ self.focus_widget( self.userid )
+ set_status("Username is too short")
+ return True
+ elif len( self.state['name'] ) < 4:
+ self.focus_widget( self.name )
+ set_status("Name is too short")
+ return True
+ clear_status()
+
+class SignPage(WizardPanel):
+ def init_widgets(self):
+ self.widgets = [
+ urwid.Text( "Machine Usage Policy" ),
+ urwid.Divider(),
+ urwid.Text( "Ensure the new member has signed the "
+ "Machine Usage Policy. Accounts of users who have not "
+ "signed will be suspended if discovered." ),
+ ]
+ def focusable(self):
+ return False
+
+class PassPage(WizardPanel):
+ def init_widgets(self):
+ self.password = PassEdit("Password: ")
+ self.pwcheck = PassEdit("Re-enter: ")
+ self.widgets = [
+ urwid.Text( "Member Password" ),
+ urwid.Divider(),
+ self.password,
+ self.pwcheck,
+ ]
+ def focus_widget(self, widget):
+ self.box.set_focus( self.widgets.index( widget ) )
+ def clear_password(self):
+ self.focus_widget( self.password )
+ self.password.set_edit_text("")
+ self.pwcheck.set_edit_text("")
+ def check(self):
+ self.state['password'] = self.password.get_edit_text()
+ pwcheck = self.pwcheck.get_edit_text()
+
+ if self.state['password'] != pwcheck:
+ self.clear_password()
+ set_status("Passwords do not match")
+ return True
+ elif len(self.state['password']) < 5:
+ self.clear_password()
+ set_status("Password is too short")
+ return True
+ clear_status()
+
+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 check(self):
+ pop_window()
+ def activate(self):
+ try:
+ if not members.connected(): members.connect()
+ members.new( self.state['userid'], self.state['name'], self.state['program'] )
+ problem = None
+ except members.InvalidRealName:
+ problem = "Invalid real name"
+ except InvalidArgument, e:
+ if e.argname == 'uid' and e.explanation == 'duplicate uid':
+ problem = 'Duplicate userid'
+ else:
+ raise
+ if not problem:
+ try:
+ if not accounts.connected(): accounts.connect()
+ accounts.create_member( self.state['userid'], self.state['password'], self.state['name'] )
+ except accounts.NameConflict, e:
+ problem = str(e)
+ except accounts.NoAvailableIDs, e:
+ problem = str(e)
+ except accounts.InvalidArgument, e:
+ problem = str(e)
+ except accounts.LDAPException, e:
+ problem = str(e)
+ except accounts.KrbException, e:
+ problem = str(e)
+ if problem:
+ self.headtext.set_text("Failed to add member")
+ self.midtext.set_text("The error was: '%s'" % problem)
+ else:
+ self.headtext.set_text("Member Added")
+ self.midtext.set_text("Congratulations, %s has been added "
+ "successfully. Please run 'addhomedir %s'."
+ % (self.state['userid'], self.state['userid']))
--- /dev/null
+import urwid
+
+from csc.apps.urwid.widgets import *
+from csc.apps.urwid.window import *
+
+from csc.adm import members, terms
+
+class IntroPage(WizardPanel):
+ def init_widgets(self):
+ self.widgets = [
+ urwid.Text( "Renewing Membership" ),
+ urwid.Divider(),
+ urwid.Text( "CSC membership is $2.00 per term. You may pre-register "
+ "for future terms if desired." )
+ ]
+ def focusable(self):
+ return False
+
+class UserPage(WizardPanel):
+ def init_widgets(self):
+ self.userid = WordEdit("Username: ")
+
+ 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']:
+ if not members.connected(): members.connect()
+ 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 TermPage(WizardPanel):
+ def init_widgets(self):
+ self.start = SingleEdit("Start: ")
+ self.count = SingleIntEdit("Count: ")
+
+ self.widgets = [
+ urwid.Text( "Terms to Register" ),
+ urwid.Divider(),
+ self.start,
+ self.count,
+ ]
+ def activate(self):
+ if not self.start.get_edit_text():
+ old_terms = []
+ if 'term' in self.state['member']:
+ old_terms = self.state['member']['term']
+ self.start.set_edit_text( terms.next_unregistered( old_terms ) )
+ self.count.set_edit_text( "1" )
+ def check(self):
+ try:
+ self.state['terms'] = terms.interval( self.start.get_edit_text(), self.count.value() )
+ except e:
+ self.focus_widget( self.start )
+ set_status( "Invalid start term" )
+ return True
+ for term in self.state['terms']:
+ if members.registered( self.state['userid'], term):
+ self.focus_widget( self.start )
+ set_status( "Already registered for " + term )
+ return True
+ if len(self.state['terms']) == 0:
+ self.focus_widget(self.count)
+ set_status( "Registering for zero terms?" )
+ return True
+
+class PayPage(WizardPanel):
+ def init_widgets(self):
+ self.midtext = urwid.Text("")
+
+ self.widgets = [
+ urwid.Text("Membership Fee"),
+ urwid.Divider(),
+ self.midtext,
+ ]
+ def focusable(self):
+ return False
+ def activate(self):
+ regterms = self.state['terms']
+ plural = "term"
+ if len(self.state['terms']) > 1:
+ plural = "terms"
+ self.midtext.set_text("You are registering for %d %s, and owe the "
+ "Computer Science Club $%d.00 in membership fees. "
+ "Please deposit the money in the safe before "
+ "continuing. " % ( len(regterms), plural, len(regterms * 2)))
+
+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):
+ try:
+ members.register( self.state['userid'], self.state['terms'] )
+ self.headtext.set_text("Registration Succeeded")
+ self.midtext.set_text("The member has been registered for the following "
+ "terms: " + ", ".join(self.state['terms']) + ".")
+ except Exception, e:
+ self.headtext.set_text("Failed to Register")
+ self.midtext.set_text("You may refund any fees paid or retry."
+ "The error was: '%s'" % e)
+ def check(self):
+ pop_window()
--- /dev/null
+import urwid
+
+from csc.apps.urwid.widgets import *
+from csc.apps.urwid.window import *
+
+from csc.adm import accounts, members, terms
+from csc.common.excep import InvalidArgument
+
+class TermPage(WizardPanel):
+ def init_widgets(self):
+ self.term = SingleEdit("Term: ")
+
+ self.widgets = [
+ urwid.Text( "Terms Members" ),
+ urwid.Divider(),
+ self.term,
+ ]
+ def check(self):
+ if not members.connected(): members.connect()
+ try:
+ self.state['term'] = self.term.get_edit_text()
+ terms.parse( self.state['term'] )
+ except:
+ self.focus_widget( self.term )
+ set_status( "Invalid term" )
+ return True
+ mlist = members.list_term( self.state['term'] ).values()
+ pop_window()
+ member_list( mlist )
+
+class NamePage(WizardPanel):
+ def init_widgets(self):
+ self.name = SingleEdit("Name: ")
+
+ self.widgets = [
+ urwid.Text( "Members by Name" ),
+ urwid.Divider(),
+ self.name,
+ ]
+ def check(self):
+ if not members.connected(): members.connect()
+ self.state['name'] = self.name.get_edit_text()
+ if not self.state['name']:
+ self.focus_widget( self.term )
+ set_status( "Invalid name" )
+ return True
+ mlist = members.list_name( self.state['name'] ).values()
+ pop_window()
+ member_list( mlist )
+
+def member_list(mlist):
+ mlist = list(mlist)
+ mlist.sort( lambda x, y: cmp(x['uid'], y['uid']) )
+ buf = ''
+ for member in mlist:
+ if 'uid' in member:
+ uid = member['uid'][0]
+ else:
+ uid = None
+ if 'program' in member:
+ program = member['program'][0]
+ else:
+ program = None
+ attrs = ( uid, member['cn'][0], program )
+ buf += "%10s %30s\n%41s\n\n" % attrs
+ set_status("Press escape to return to the menu")
+ push_window(urwid.ListBox([urwid.Text(buf)]))
+
+
--- /dev/null
+import urwid
+
+class ButtonText(urwid.Text):
+ def __init__(self, callback, *args, **kwargs):
+ self.callback = callback
+ urwid.Text.__init__(self, *args, **kwargs)
+ def selectable(self):
+ return True
+ def keypress(self, size, key):
+ if key == 'enter':
+ self.callback(self.get_text())
+ else:
+ return key
+
+class SingleEdit(urwid.Edit):
+ def keypress(self, size, key):
+ if key == 'enter':
+ return urwid.Edit.keypress(self, size, 'down')
+ else:
+ return urwid.Edit.keypress(self, size, key)
+
+class SingleIntEdit(urwid.IntEdit):
+ def keypress(self, size, key):
+ if key == 'enter':
+ return urwid.Edit.keypress(self, size, 'down')
+ else:
+ return urwid.Edit.keypress(self, size, key)
+
+class WordEdit(SingleEdit):
+ def valid_char(self, ch):
+ return urwid.Edit.valid_char(self, ch) and ch != ' '
+
+class PassEdit(SingleEdit):
+ def get_text(self):
+ text = urwid.Edit.get_text(self)
+ return (self.caption + " " * len(self.get_edit_text()), text[1])
+
+class Wizard(urwid.WidgetWrap):
+ def __init__(self):
+ self.selected = None
+ self.panels = []
+
+ self.panelwrap = urwid.WidgetWrap( urwid.SolidFill() )
+ self.back = urwid.Button("Back", self.back)
+ self.next = urwid.Button("Next", self.next)
+ self.buttons = urwid.Columns( [ self.back, self.next ], dividechars=3, focus_column=1 )
+ pad = urwid.Padding( self.buttons, ('fixed right', 2), 19 )
+ self.pile = urwid.Pile( [self.panelwrap, ('flow', pad)], 0 )
+ urwid.WidgetWrap.__init__(self, self.pile)
+
+ def add_panel(self, panel):
+ self.panels.append( panel )
+ if len(self.panels) == 1:
+ self.select(0)
+
+ def select(self, panelno, set_focus=True):
+ if 0 <= panelno < len(self.panels):
+ self.selected = panelno
+ self.panelwrap.set_w( self.panels[panelno] )
+ self.panels[panelno].activate()
+
+ if set_focus:
+ if self.panels[panelno].focusable():
+ self.pile.set_focus( 0 )
+ else:
+ self.pile.set_focus( 1 )
+
+ def next(self, *args, **kwargs):
+ if self.panels[self.selected].check():
+ self.select( self.selected )
+ return
+ self.select(self.selected + 1)
+
+ def back(self, *args, **kwargs):
+ self.select(self.selected - 1, False)
+
+class WizardPanel(urwid.WidgetWrap):
+ def __init__(self, state):
+ self.state = state
+ self.init_widgets()
+ self.box = urwid.ListBox( urwid.SimpleListWalker( self.widgets ) )
+ urwid.WidgetWrap.__init__( self, self.box )
+ def init_widgets(self):
+ self.widgets = []
+ def focus_widget(self, widget):
+ self.box.set_focus( self.widgets.index( widget ) )
+ def focusable(self):
+ return True
+ def check(self):
+ return
+ def activate(self):
+ return
--- /dev/null
+import urwid
+
+window_stack = []
+window_names = []
+
+header = urwid.Text( "" )
+footer = urwid.Text( "" )
+top = urwid.Frame( urwid.SolidFill(), header, footer )
+
+def push_window( frame, name=None ):
+ window_stack.append( frame )
+ window_names.append( name )
+ update_top()
+
+def pop_window():
+ if len(window_stack) == 1:
+ return False
+ window_stack.pop()
+ window_names.pop()
+ update_top()
+ return True
+
+def update_top():
+ names = [ n for n in window_names if n ]
+ header.set_text(" - ".join( names ) + "\n")
+ top.set_body( window_stack[-1] )
+
+def set_status(message):
+ footer.set_text(message)
+
+def clear_status():
+ footer.set_text("")
+
+class Abort(Exception):
+ pass
+
+class Back(Exception):
+ pass
+
+def raise_abort(*args, **kwargs):
+ raise Abort()
+
+def raise_back(*args, **kwarg):
+ raise Back()
+
+def event_loop(ui):
+ while True:
+ try:
+ cols, rows = ui.get_cols_rows()
+ canvas = top.render( (cols, rows), focus=True )
+ ui.draw_screen( (cols, rows), canvas )
+
+ keys = ui.get_input()
+ for k in keys:
+ if k == "esc":
+ if not pop_window():
+ break
+ elif k == "window resize":
+ (cols, rows) = ui.get_cols_rows()
+ else:
+ top.keypress( (cols, rows), k )
+ except Back:
+ pop_window()
+ except (Abort, KeyboardInterrupt):
+ return