Merge branch 'master' of /users/git/public/pyceo
authorDavid Bartley <dtbartle@csclub.uwaterloo.ca>
Wed, 5 Dec 2007 03:19:03 +0000 (22:19 -0500)
committerDavid Bartley <dtbartle@csclub.uwaterloo.ca>
Wed, 5 Dec 2007 03:19:03 +0000 (22:19 -0500)
bin/ceo-old [deleted file]
bin/ceoquery
debian/postinst
debian/rules
docs/BUGS [deleted file]
docs/GIT-HOWTO
docs/INSTALLING
etc/ldap.cf
pylib/csc/apps/legacy/__init__.py [deleted file]
pylib/csc/apps/legacy/helpers.py [deleted file]
pylib/csc/apps/legacy/main.py [deleted file]

diff --git a/bin/ceo-old b/bin/ceo-old
deleted file mode 100755 (executable)
index 8550f79..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/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()
index a9ceeed..e16c0e4 100755 (executable)
@@ -3,31 +3,6 @@
 ceoquery - a script to lookup member and account information
 """
 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['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)
-
 from csc.adm import members, terms
 
 try:
index 59af2f0..ff51da0 100644 (file)
@@ -28,7 +28,7 @@ case "$1" in
         fi
 
         if ! dpkg-statoverride --list /usr/bin/ceoquery > /dev/null; then
-            dpkg-statoverride --add --update $CEO root $SUIDALL /usr/bin/ceoquery
+            dpkg-statoverride --add --update root root 755 /usr/bin/ceoquery
         fi
 
         if ! dpkg-statoverride --list /usr/bin/csc-chsh > /dev/null; then
@@ -42,24 +42,6 @@ case "$1" in
         if [ -f /etc/csc/ldap.cf ] && ! dpkg-statoverride --list /etc/csc/ldap.cf > /dev/null; then
             dpkg-statoverride --add --update $CEO staff 640 /etc/csc/ldap.cf
         fi
-
-        if [ ! -e /etc/csc/ceo.keytab ] && [ -x /usr/sbin/kadmin.local ]; then
-            if dpkg-statoverride --list /etc/csc/ceo.keytab > /dev/null; then
-                dpkg-statoverride --remove /etc/csc/ceo.keytab || true
-            fi    
-            echo 'warning: re-creating ceo.keytab'
-            echo 'ktadd -k /etc/csc/ceo.keytab ceo/admin' | /usr/sbin/kadmin.local || true
-            if [ -e /etc/csc/ceo.keytab ]; then
-                echo -e "\nSuccess!"
-            else
-                echo -e "\nFailed!"
-            fi
-        fi
-
-        if [ -f /etc/csc/ceo.keytab ] && ! dpkg-statoverride --list /etc/csc/ceo.keytab > /dev/null; then
-            dpkg-statoverride --add --update $CEO staff 640 /etc/csc/ceo.keytab
-        fi
-        
     ;;
     
     abort-upgrade|abort-remove|abort-deconfigure)
index 4344045..a8e3fe1 100755 (executable)
@@ -8,7 +8,6 @@ build-stamp:
        mkdir build
        $(CC) -DFULL_PATH='"/usr/lib/csc/ceo"' -o build/ceo 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
        $(CC) -DFULL_PATH='"/usr/lib/csc/csc-chsh"' -o build/csc-chsh misc/setuid-prog.c
        touch build-stamp
@@ -30,8 +29,8 @@ install: build
        dh_install pylib/* usr/lib/$(PYTHON)/site-packages/
        dh_install etc/* etc/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/addhomedir bin/csc-chsh bin/csc-chfn usr/lib/csc/
+       dh_install build/ceo build/addhomedir bin/ceoquery build/csc-chsh build/csc-chfn usr/bin/
        dh_install misc/csc.schema etc/ldap/schema/
        
 binary-arch: build install
diff --git a/docs/BUGS b/docs/BUGS
deleted file mode 100644 (file)
index edd2867..0000000
--- a/docs/BUGS
+++ /dev/null
@@ -1,8 +0,0 @@
-
-Bugs and Caveats
-================
-
-CEO:
-    - curses does not draw borders/lines correctly in a screen session. screen apparently ignores
-      some font-changing characters. workaround should be possible (other progs work).
-    - the menu is not redrawn between windows and therefore a gap tends to grow there
index ceb739f..44cd629 100644 (file)
@@ -11,15 +11,9 @@ When you check out the sources, you will get the entire history along with
 the latest version. You do not need any special permissions to clone a
 repository and start making changes.
 
-To retrieve the ceo sources, type:
-
-    git clone /users/git/mspang/csc.git
-
-This will give you the latest copy of mspang's source tree. Once cloned,
-the repository is independent from the original and collaboration is done
-by "pulling" changes. There may be other repositories to clone from in the
-future.
+To retrieve the ceo sources, clone the public repository:
 
+    git clone /users/git/public/pyceo.git
 
 Making Changes
 --------------
index 53068b2..049f7a2 100644 (file)
@@ -9,15 +9,9 @@ can safely skip it.
 Building the Package
 --------------------
 
-To build a Debian package out of the sources, run one of the following
-commands at the top of the source tree:
-
-    A. debuild
-    B. dpkg-buildpackage -rfakeroot
-    C. git-buildpackage
-
-It doesn't matter which, so 'debuild' is probably easiest. If all goes well,
-a Debian package and source tarball will appear in the parent directory.
+To build a Debian package out of the sources, run `debuild` at the top
+of the source tree. If all goes well, a Debian package and source tarball
+will appear in the parent directory.
 
 Do NOT build the package as root (rather, don't build anything as root in
 general). Use 'fakeroot' so that the permissions in the .deb can be set
@@ -61,8 +55,7 @@ To install the package:
 
     5. Build the package
 
-        Use 'debuild' or 'fakeroot dpkg-buildpackage -us -uc' to build the
-        package.
+        Use 'debuild' to build the package.
 
     5. Install the package
 
@@ -74,6 +67,12 @@ To install the package:
         and a .dsc. Save these to a safe place (preferably in /users/git
         so other can find them easily).
 
+    7. Push to /users/git/public/pyceo.git
+
+        This is a convenient hub for pushing/pulling between contributors.
+        You must be in the 'git' group to do this - if you're able to install
+        the package you will certainly be able to add yourself to this group.
+
 If everyone follows these steps, every installed version will be a
 descendant of the previous. Further, since old versions are archived it
 will be easy to quickly get ceo working again after a bad update.
index c32724c..bc5a73e 100644 (file)
@@ -5,5 +5,9 @@ server_url = "ldaps:///"
 users_base  = "ou=People,dc=csclub,dc=uwaterloo,dc=ca"
 groups_base = "ou=Group,dc=csclub,dc=uwaterloo,dc=ca"
 
-admin_bind_dn = "cn=ceo,dc=csclub,dc=uwaterloo,dc=ca"
-admin_bind_pw = "secret"
+admin_bind_dn =
+admin_bind_userid = "ceo"
+admin_bind_keytab = "/etc/csc/ceo.keytab"
+
+sasl_mech = "GSSAPI"
+sasl_realm = "CSCLUB.UWATERLOO.CA"
diff --git a/pylib/csc/apps/legacy/__init__.py b/pylib/csc/apps/legacy/__init__.py
deleted file mode 100644 (file)
index 0bc0b65..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-"""
-Legacy User Interface
-
-This module contains the legacy CEO user interface and related modules.
-
-    main    - all of the main UI logic
-    helpers - user interface library routines
-"""
diff --git a/pylib/csc/apps/legacy/helpers.py b/pylib/csc/apps/legacy/helpers.py
deleted file mode 100644 (file)
index a18a193..0000000
+++ /dev/null
@@ -1,411 +0,0 @@
-"""
-Helpers for legacy User Interface
-
-This module contains numerous functions that are designed to immitate
-the look and behavior of the previous CEO. Included is code for various
-curses-based UI widgets that were provided by Perl 5's Curses and
-Curses::Widgets libraries.
-
-Though attempts have been made to keep the UI [bug-]compatible with
-the previous system, some compromises have been made. For example,
-the input and textboxes draw 'OK' and 'Cancel' buttons where the old
-CEO had them, but they are fake. That is, the buttons in the old
-CEO were selectable but non-operational, but in the new CEO they are
-not even selectable.
-"""
-import curses.ascii
-
-# key constants not defined in CURSES
-KEY_RETURN = ord('\n')
-KEY_ESCAPE = 27
-KEY_EOT = 4
-
-
-def center(parent_dim, child_dim):
-    """Helper for centering a length in a larget length."""
-    return (parent_dim-child_dim)/2
-
-
-def read_input(wnd, offy, offx, width, maxlen, echo=True):
-    """
-    Read user input within a confined region of a window.
-
-    Basic line-editing is supported:
-        LEFT, RIGHT, HOME, and END move around.
-        BACKSPACE and DEL remove characters.
-        INSERT switches between insert and overwrite mode.
-        ESC and C-d abort input.
-        RETURN completes input.
-    
-    Parameters:
-        wnd    - parent window for region
-        offy   - the vertical offset for the beginning of the input region
-        offx   - the horizontal offset for the beginning of the input region
-        width  - the width of the region
-        maxlen - greatest number of characters to read (0 for no limit)
-        echo   - boolean: whether to display typed characters
-    
-    Returns: the string, or None when the user aborts.
-    """
-
-    # turn on cursor
-    try:
-        curses.curs_set(1)
-    except curses.error:
-        pass
-
-    # set keypad mode to allow UP, DOWN, etc
-    wnd.keypad(1)
-
-    # the input string
-    inputbuf = ""
-
-    # offset of cursor in input
-    # i.e. the next operation is applied at input[inputoff]
-    inputoff = 0
-
-    # display offset (for scrolling)
-    # i.e. the first character in the region is input[displayoff]
-    displayoff = 0
-
-    # insert mode (True) or overwrite mode (False)
-    insert = True
-
-    while True:
-
-        # echo mode, display the string
-        if echo:
-            # discard characters before displayoff, 
-            # as the window may be scrolled to the right
-            substring = inputbuf[displayoff:]
-    
-            # pad the string with zeroes to overwrite stale characters
-            substring = substring + " " * (width - len(substring))
-    
-            # display the substring
-            wnd.addnstr(offy, offx, substring, width)
-    
-            # await input
-            key = wnd.getch(offy, offx + inputoff - displayoff)
-
-        # not echo mode, don't display the string
-        else:
-            # await input at arbitrary location
-            key = wnd.getch(offy, offx)
-
-        # enter returns input
-        if key == KEY_RETURN:
-            return inputbuf
-
-        # escape aborts input
-        elif key == KEY_ESCAPE:
-            return None
-
-        # EOT (C-d) aborts if there is no input
-        elif key == KEY_EOT:
-            if len(inputbuf) == 0:
-                return None
-
-        # backspace removes the previous character
-        elif key == curses.KEY_BACKSPACE:
-            if inputoff > 0:
-
-                # remove the character immediately before the input offset
-                inputbuf = inputbuf[0:inputoff-1] + inputbuf[inputoff:]
-                inputoff -= 1
-
-                # move either the cursor or entire line of text left
-                if displayoff > 0:
-                    displayoff -= 1
-
-        # delete removes the current character
-        elif key == curses.KEY_DC:
-            if inputoff < len(input):
-                
-                # remove the character at the input offset
-                inputbuf = inputbuf[0:inputoff] + inputbuf[inputoff+1:]
-
-        # left moves the cursor one character left
-        elif key == curses.KEY_LEFT:
-            if inputoff > 0:
-
-                # move the cursor to the left
-                inputoff -= 1
-
-                # scroll left if necessary
-                if inputoff < displayoff:
-                    displayoff -= 1
-
-        # right moves the cursor one character right
-        elif key == curses.KEY_RIGHT:
-            if inputoff < len(inputbuf):
-                
-                # move the cursor to the right
-                inputoff += 1
-
-                # scroll right if necessary
-                if displayoff - inputoff == width:
-                    displayoff += 1
-
-        # home moves the cursor to the first character
-        elif key == curses.KEY_HOME:
-            inputoff = 0
-            displayoff = 0
-
-        # end moves the cursor past the last character
-        elif key == curses.KEY_END:
-            inputoff = len(inputbuf)
-            displayoff = len(inputbuf) - width + 1
-
-        # insert toggles insert/overwrite mode
-        elif key == curses.KEY_IC:
-            insert = not insert
-
-        # other (printable) characters are added to the input string
-        elif curses.ascii.isprint(key):
-            if len(inputbuf) < maxlen or maxlen == 0:
-
-                # insert mode: insert before current offset
-                if insert:
-                    inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff:]
-    
-                # overwrite mode: replace current offset
-                else:
-                    inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff+1:]
-    
-                # increment the input offset
-                inputoff += 1
-    
-                # scroll right if necessary
-                if inputoff - displayoff == width:
-                    displayoff += 1
-
-
-def inputbox(wnd, prompt, field_width, echo=True):
-    """Display a window for user input."""
-
-    wnd_height, wnd_width = wnd.getmaxyx()
-    height, width = 12, field_width + 7
-
-    # draw a window for the dialog
-    childy, childx = center(wnd_height-1, height)+1, center(wnd_width, width)
-    child_wnd = wnd.subwin(height, width, childy, childx)
-    child_wnd.clear()
-    child_wnd.border()
-
-    # draw another window for the text box
-    texty, textx = center(height-1, 3)+1, center(width-1, width-5)+1
-    textheight, textwidth = 3, width-5
-    text_wnd = child_wnd.derwin(textheight, textwidth, texty, textx)
-    text_wnd.clear()
-    text_wnd.border()
-    
-    # draw the prompt
-    prompty, promptx = 2, 3
-    child_wnd.addnstr(prompty, promptx, prompt, width-2)
-
-    # draw the fake buttons
-    fakey, fakex = 9, width - 19
-    child_wnd.addstr(fakey, fakex, "< OK > < Cancel >")
-    child_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE)
-    child_wnd.addch(fakey, fakex+9, "C", curses.A_UNDERLINE)
-
-    # update the screen
-    child_wnd.noutrefresh()
-    text_wnd.noutrefresh()
-    curses.doupdate()
-
-    # read an input string within the field region of text_wnd
-    inputy, inputx, inputwidth = 1, 1, textwidth - 2
-    inputbuf = read_input(text_wnd, inputy, inputx, inputwidth, 0, echo)
-    
-    # erase the window
-    child_wnd.erase()
-    child_wnd.refresh()
-
-    return inputbuf
-
-
-def line_wrap(line, width):
-    """Wrap a string to a certain width (returns a list of strings)."""
-
-    wrapped_lines = []
-    tokens = line.split(" ")
-    tokens.reverse()
-    tmp = tokens.pop()
-    if len(tmp) > width:
-        wrapped_lines.append(tmp[0:width])
-        tmp = tmp[width:]
-    while len(tokens) > 0:
-        token = tokens.pop()
-        if len(tmp) + len(token) + 1 <= width:
-            tmp += " " + token
-        elif len(token) > width:
-            tmp += " " + token[0:width-len(tmp)-1]
-            tokens.push(token[width-len(tmp)-1:])
-        else:
-            wrapped_lines.append(tmp)
-            tmp = token
-    wrapped_lines.append(tmp)
-    return wrapped_lines
-
-
-def msgbox(wnd, msg, title="Message"):
-    """Display a message in a window."""
-
-    # split message into a list of lines
-    lines = msg.split("\n")
-    
-    # determine the dimensions of the method
-    message_height = len(lines)
-    message_width = 0
-    for line in lines:
-        if len(line) > message_width:
-            message_width = len(line)
-
-    # ensure the window fits the title
-    if len(title) > message_width:
-        message_width = len(title)
-
-    # maximum message width
-    parent_height, parent_width = wnd.getmaxyx()
-    max_message_width = parent_width - 8
-
-    # line-wrap if necessary
-    if message_width > max_message_width:
-        newlines = []
-        for line in lines:
-            for newline in line_wrap(line, max_message_width):
-                newlines.append(newline)
-        lines = newlines
-        message_width = max_message_width
-        message_height = len(lines)
-
-    # random padding that perl's curses adds
-    pad_width = 2
-
-    # create the outer window
-    outer_height, outer_width = message_height + 8, message_width + pad_width + 6
-    outer_y, outer_x = center(parent_height+1, outer_height)-1, center(parent_width, outer_width)
-    outer_wnd = wnd.derwin(outer_height, outer_width, outer_y, outer_x)
-    outer_wnd.erase()
-    outer_wnd.border()
-
-    # create the inner window
-    inner_height, inner_width = message_height + 2, message_width + pad_width + 2
-    inner_y, inner_x = 2, center(outer_width, inner_width)
-    inner_wnd = outer_wnd.derwin(inner_height, inner_width, inner_y, inner_x)
-    inner_wnd.border()
-
-    # display the title
-    outer_wnd.addstr(0, 1, " " + title + " ", curses.A_REVERSE | curses.A_BOLD)
-    
-    # display the message
-    for i in xrange(len(lines)):
-        inner_wnd.addnstr(i+1, 1, lines[i], message_width)
-
-        # draw a solid block at the end of the first few lines
-        if i < len(lines) - 1:
-            inner_wnd.addch(i+1, inner_width-1, ' ', curses.A_REVERSE)
-
-    # display the fake OK button
-    fakey, fakex = outer_height - 3, outer_width - 8
-    outer_wnd.addstr(fakey, fakex, "< OK >", curses.A_REVERSE)
-    outer_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE | curses.A_REVERSE)
-
-    # update display
-    outer_wnd.noutrefresh()
-    inner_wnd.noutrefresh()
-    curses.doupdate()
-
-    # read a RETURN or ESC before returning
-    curses.curs_set(0)
-    outer_wnd.keypad(1)
-    while True:
-        key = outer_wnd.getch(0, 0)
-        if key == KEY_RETURN or key == KEY_ESCAPE:
-            break
-
-    # clear the window
-    outer_wnd.erase()
-    outer_wnd.refresh()
-    
-
-def menu(wnd, offy, offx, width, options, _acquire_wnd=None):
-    """
-    Draw a menu and wait for a selection.
-
-    Parameters:
-        wnd          - parent window
-        offy         - vertical offset for menu region
-        offx         - horizontal offset for menu region
-        width        - width of menu region
-        options      - a list of selections
-        _acquire_wnd - hack to support resize: must be a function callback
-                       that returns new values for wnd, offy, offx, height,
-                       width. Unused if None.
-
-    Returns: index into options that was selected
-    """
-
-    # the currently selected option
-    selected = 0
-
-    while True:
-        # disable cursor
-        curses.curs_set(0)
-
-        # hack to support resize: recreate the
-        # parent window every iteration
-        if _acquire_wnd:
-            wnd, offy, offx, height, width = _acquire_wnd()
-
-        # keypad mode so getch() works with up, down
-        wnd.keypad(1)
-
-        # display the menu
-        for i in xrange(len(options)):
-            text, callback = options[i]
-            text = text + " " * (width - len(text))
-
-            # the selected option is displayed in reverse video
-            if i == selected:
-                wnd.addstr(i+offy, offx, text, curses.A_REVERSE)
-            else:
-                wnd.addstr(i+offy, offx, text)
-                    # display the member
-
-        wnd.refresh()
-        
-        # read one keypress
-        keypress = wnd.getch()
-
-        # UP moves to the previous option
-        if (keypress == curses.KEY_UP or keypress == ord('k')) and selected > 0:
-            selected = (selected - 1)
-
-        # DOWN moves to the next option
-        elif (keypress == curses.KEY_DOWN or keypress == ord('j')) and selected < len(options) - 1:
-            selected = (selected + 1)
-
-        # RETURN runs the callback for the selected option
-        elif keypress == KEY_RETURN:
-            text, callback = options[selected]
-
-            # highlight the selected option
-            text = text + " " * (width - len(text))
-            wnd.addstr(selected+offy, offx, text, curses.A_REVERSE | curses.A_BOLD)
-            wnd.refresh()
-
-            # execute the selected option
-            if callback(wnd): # success
-                break
-
-
-def reset():
-    """Reset the terminal and clear the screen."""
-
-    reset = curses.tigetstr('rs1')
-    if not reset: reset = '\x1bc'
-    curses.putp(reset)
-
diff --git a/pylib/csc/apps/legacy/main.py b/pylib/csc/apps/legacy/main.py
deleted file mode 100644 (file)
index 98df1f7..0000000
+++ /dev/null
@@ -1,531 +0,0 @@
-"""
-CEO-like Frontend
-
-This frontend aims to be compatible in both look and function with the
-curses UI of CEO.
-
-Some small improvements have been made, such as not echoing passwords and
-aborting when nothing is typed into the first input box during an operation.
-
-This frontend is poorly documented, deprecated, and undoubtedly full of bugs.
-"""
-import curses.ascii, re, os
-from helpers import menu, inputbox, msgbox, reset
-from csc.adm import accounts, members, terms
-from csc.common.excep import InvalidArgument
-
-# color of the ceo border
-BORDER_COLOR = curses.COLOR_RED
-
-
-def read_uid(wnd):
-    """Read a username."""
-
-    prompt = 'Username:'
-    return inputbox(wnd, prompt, 36)
-
-def read_member(wnd):
-    """Looks up a member."""
-
-    # connect the members module to its backend if necessary
-    if not members.connected(): members.connect()
-
-    uid = read_uid(wnd)
-    if not uid or uid.lower() == 'exit':
-        return
-
-    member = members.get(uid)
-    if not member:
-        msgbox(wnd, "Invalid username: %s" % uid)
-        return
-
-    # display user
-    display_member_details(wnd, member)
-
-    return member
-
-
-def action_library(wnd):
-    """Display a link to the library."""
-    msgbox(wnd, "Please visit library.csclub.uwaterloo.ca")
-
-def action_new_member(wnd):
-    """Interactively add a new member."""
-
-    userid, program = '', ''
-
-    msgbox(wnd, "Membership is $2.00 CDN. Please ensure\n"
-                "the money is desposited in the safe\n"
-                "before continuing.")
-
-    # read the name
-    prompt = "New member's full name: "
-    realname = inputbox(wnd, prompt, 30)
-
-    # abort if no name is entered
-    if not realname or realname.lower() == 'exit':
-        return False
-
-    # read the program of study
-    prompt = "New member's program of study:"
-    program = inputbox(wnd, prompt, 30)
-
-    # abort if exit is entered
-    if program is None or program.lower() == 'exit':
-        return False
-
-    # read user id
-    prompt = "New member's UWdir username:"
-    while userid == '':
-        userid = inputbox(wnd, prompt, 30)
-
-    # user abort
-    if userid is None or userid.lower() == 'exit':
-        return False
-
-    # connect the members module to its backend if necessary
-    if not members.connected(): members.connect()
-
-    # attempt to create the member
-    try:
-        members.new(userid, realname, program)
-
-        msgbox(wnd, "Success! Your username is %s.  You are now registered\n"
-                    % userid + "for the " + terms.current() + " term.")
-
-    except members.InvalidRealName:
-        msgbox(wnd, 'Invalid real name: "%s"' % realname)
-        return False
-    except InvalidArgument, e:
-        if e.argname == 'uid' and e.explanation == 'duplicate uid':
-            msgbox(wnd, 'A member with this user ID exists.')
-            return False
-        else:
-            raise
-
-
-def action_term_register(wnd):
-    """Interactively register a member for a term."""
-
-    term = ''
-
-    member = read_member(wnd)
-    if not member:
-        return False
-    uid = member['uid'][0]
-
-    # verify member
-    prompt = "Is this the correct member?"
-    answer = None
-    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
-        answer = inputbox(wnd, prompt, 28) 
-
-    # user abort
-    if answer == "exit":
-        return False
-
-    # read the term
-    prompt = "Which term to register for ([wsf]20nn):"
-    while not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit':
-        term = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if term.lower() == 'exit':
-        return False
-
-    # already registered?
-    if members.registered(uid, term):
-        msgbox(wnd, "You are already registered for term " + term)
-        return False
-
-    try:
-
-        # attempt to register
-        members.register(uid, term)
-        
-        # display success message
-        msgbox(wnd, "You are now registered for term " + term)
-
-    except members.InvalidTerm:
-        msgbox(wnd, "Term is not valid: %s" % term)
-
-    return False
-
-
-def action_term_register_multiple(wnd):
-    """Interactively register a member for multiple terms."""
-
-    base, num = '', None
-
-    member = read_member(wnd)
-    if not member:
-        return False
-    uid = member['uid'][0]
-
-    # verify member
-    prompt = "Is this the correct member?"
-    answer = None
-    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
-        answer = inputbox(wnd, prompt, 28) 
-
-    # user abort
-    if answer == "exit":
-        return False
-
-    # read the base
-    prompt = "Which term to start registering ([fws]20nn):"
-    while not re.match('^[wsf][0-9]{4}$', base) and not base == 'exit':
-        base = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if base.lower() == 'exit':
-        return False
-
-    # read number of terms
-    prompt = 'How many terms?'
-    while not num or not re.match('^[0-9]*$', num):
-        num = inputbox(wnd, prompt, 36)
-    num = int(num)
-
-    # any terms in the range?
-    if num < 1:
-        msgbox(wnd, "No terms to register.")
-        return False
-
-    # compile a list to register
-    term_list = terms.interval(base, num)
-
-    # already registered?
-    for term in term_list:
-        if members.registered(uid, term):
-            msgbox(wnd, "You are already registered for term " + term)
-            return False
-
-    try:
-
-        # attempt to register all terms
-        members.register(uid, term_list)
-        
-        # display success message [sic]
-        msgbox(wnd, "Your are now registered for terms: " + ", ".join(term_list))
-
-    except members.InvalidTerm:
-        msgbox(wnd, "Invalid term entered.")
-
-    return False
-
-
-def input_password(wnd):
-
-    # password input loop
-    password = "password"
-    check = "check"
-    while password != check:
-    
-        # read password
-        prompt = "User password:"
-        password = None
-        while not password:
-            password = inputbox(wnd, prompt, 18, False) 
-
-        # read another password
-        prompt = "Enter the password again:"
-        check = None
-        while not check:
-            check = inputbox(wnd, prompt, 27, False) 
-
-    return password
-
-
-def action_create_account(wnd):
-    """Interactively create an account for a member."""
-    
-    member = read_member(wnd)
-    if not member:
-        return False
-
-    # member already has an account?
-    if not accounts.connected(): accounts.connect()
-    if 'posixAccount' in member['objectClass']:
-        msgbox(wnd, "Account already exists.")
-        return False
-
-    # verify member
-    prompt = "Is this the correct member?"
-    answer = None
-    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
-        answer = inputbox(wnd, prompt, 28) 
-
-    # user abort
-    if answer == "exit":
-        return False
-
-    # incorrect member; abort
-    if answer == "no" or answer == "n":
-        msgbox(wnd, "I suggest searching for the member by userid or name from the main menu.")
-        return False
-
-    msgbox(wnd, "Ensure the member has signed the machine\n"
-                "usage policy. Accounts of users who have\n"
-                "not signed will be suspended if discovered.")
-
-    # read password
-    password = input_password(wnd)
-
-    # create the UNIX account
-    try:
-        if not accounts.connected(): accounts.connect()
-        accounts.create_member(member['uid'][0], password, member['cn'][0])
-    except accounts.NameConflict, e:
-        msgbox(wnd, str(e))
-        return False
-    except accounts.NoAvailableIDs, e:
-        msgbox(wnd, str(e))
-        return False
-    except accounts.InvalidArgument, e:
-        msgbox(wnd, str(e))
-        return False
-    except accounts.LDAPException, e:
-        msgbox(wnd, "Error creating LDAP entry - Contact the Systems Administrator: %s" % e)
-        return False
-    except accounts.KrbException, e:
-        msgbox(wnd, "Error creating Kerberos principal - Contact the Systems Administrator: %s" % e)
-        return False
-
-    # success
-    msgbox(wnd, "Please run 'addhomedir " + member['uid'][0] + "'.")
-    msgbox(wnd, "Success! Your account has been added")
-
-    return False
-
-
-def display_member_details(wnd, member):
-    """Display member attributes in a message box."""
-
-    # clone and sort term_list
-    if 'term' in member:
-        term_list = list(member['term'])
-    else:
-        term_list = []
-    term_list.sort( terms.compare )
-
-    # labels for data
-    id_label, name_label = "ID:", "Name:"
-    program_label, terms_label = "Program:", "Terms:"
-
-    if 'program' in member:
-        program = member['program'][0]
-    else:
-        program = None
-
-    # format it all into a massive string
-    message =  "%8s %-20s %10s %-10s\n" % (name_label, member['cn'][0], id_label, member['uid'][0]) + \
-               "%8s %-20s\n" % (program_label, program )
-
-    message += "%s %s" % (terms_label, " ".join(term_list))
-
-    # display the string in a message box
-    msgbox(wnd, message)
-    
-
-def action_display_member(wnd):
-    """Interactively display a member."""
-    
-    if not members.connected(): members.connect()
-    member = read_member(wnd)
-    return False
-
-
-def page(text):
-    """Send a text buffer to an external pager for display."""
-    
-    try:
-        pager = '/usr/bin/less'
-        pipe = os.popen(pager, 'w')
-        pipe.write(text)
-        pipe.close() 
-    except IOError:
-        # broken pipe (user didn't read the whole list)
-        pass
-    
-
-def format_members(member_list):
-    """Format a member list into a string."""
-
-    # clone and sort member_list
-    member_list = list(member_list)
-    member_list.sort( lambda x, y: cmp(x['uid'], y['uid']) )
-
-    buf = ''
-    
-    for member in member_list:
-        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
-
-    return buf
-
-
-def action_list_term(wnd):
-    """Interactively list members registered in a term."""
-
-    term = None
-
-    # read the term
-    prompt = "Which term to list members for ([fws]20nn): "
-    while term is None or (not term == '' and not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit'):
-        term = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if not term or term.lower() == 'exit':
-        return False
-
-    # connect the members module to its backends if necessary
-    if not members.connected(): members.connect()
-    
-    # retrieve a list of members for term
-    member_list = members.list_term(term)
-
-    # format the data into a mess of text
-    buf = format_members(member_list.values())
-
-    # display the mass of text with a pager
-    page( buf )
-
-    return False
-
-
-def action_list_name(wnd):
-    """Interactively search for members by name."""
-    
-    name = None
-
-    # read the name
-    prompt = "Enter the member's name: "
-    name = inputbox(wnd, prompt, 41) 
-
-    # abort when exit is entered
-    if not name or name.lower() == 'exit':
-        return False
-
-    # connect the members module to its backends if necessary
-    if not members.connected(): members.connect()
-    
-    # retrieve a list of members with similar names
-    member_list = members.list_name(name)
-
-    # format the data into a mess of text
-    buf = format_members(member_list.values())
-
-    # display the mass of text with a pager
-    page( buf )
-
-    return False
-
-
-def null_callback(wnd):
-    """Callback for unimplemented menu options."""
-    return False
-
-
-def exit_callback(wnd):
-    """Callback for the exit option."""
-    return True
-
-
-# the top level ceo menu
-top_menu = [
-    ( "New member", action_new_member ),
-    ( "Register for a term", action_term_register ),
-    ( "Register for multiple terms", action_term_register_multiple ),
-    ( "Display a member", action_display_member ),
-    ( "List members registered in a term", action_list_term ),
-    ( "Search for a member by name", action_list_name ),
-    ( "Create an account", action_create_account ),
-    ( "Library functions", action_library ),
-    ( "Exit", exit_callback ),
-]
-
-
-def acquire_ceo_wnd(screen=None):
-    """Create the top level ceo window."""
-    
-    # hack to get a reference to the entire screen
-    # even when the caller doesn't (shouldn't) have one
-    if screen is None:
-        screen = globals()['screen']
-    else:
-        globals()['screen'] = screen
-
-    # if the screen changes size, a mess may be left
-    screen.erase()
-
-    # for some reason, the legacy ceo system
-    # excluded the top line from its window
-    height, width = screen.getmaxyx()
-    ceo_wnd = screen.subwin(height-1, width, 1, 0)
-
-    # draw the border around the ceo window
-    curses.init_pair(1, BORDER_COLOR, -1)
-    color_attr = curses.color_pair(1) | curses.A_BOLD
-    ceo_wnd.attron(color_attr)
-    ceo_wnd.border()
-    ceo_wnd.attroff(color_attr)
-
-    # return window and dimensions of inner area
-    return ceo_wnd, 1, 1, height-2, width-2
-
-
-def ceo_main_curses(screen):
-    """Wrapped main for curses."""
-    
-    curses.use_default_colors()
-
-    # workaround for SSH sessions on virtual consoles (reset terminal)
-    reset()
-
-    # create ceo window
-    ceo_wnd, menu_y, menu_x, menu_height, menu_width = acquire_ceo_wnd(screen)
-
-    try:
-        # display the top level menu
-        menu(ceo_wnd, menu_y, menu_x, menu_width, top_menu, acquire_ceo_wnd)
-    finally:
-        members.disconnect()
-        accounts.disconnect()
-
-
-def run():
-    """Main function for legacy UI."""
-
-    # workaround for xterm-color (bad terminfo? - curs_set(0) fails)
-    if "TERM" in os.environ and os.environ['TERM'] == "xterm-color":
-        os.environ['TERM'] = "xterm"
-
-    # wrap the entire program using curses.wrapper
-    # so that the terminal is restored to a sane state
-    # when the program exits
-    try:
-        curses.wrapper(ceo_main_curses)
-    except KeyboardInterrupt:
-        pass
-    except curses.error:
-        print "Is your screen too small?"
-        raise
-    except:
-        reset()
-        raise
-
-    # clean up screen before exit
-    reset()
-
-if __name__ == '__main__':
-    run()
-