#!/usr/bin/python

# usercfg -- User/Group Configuration Tool
# Copyright (C) 1996, 1997 Red Hat Software, Inc
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


import string
import time

from Tkinter import *
from rhtkinter import *
from buttonbar import *
from foldertabs import *
from rhutil import *
from rhdialog import Dialog
from rhentry import *
from listbox import *
from Conf import *

import rhdialog
import os
from sys import exit
import posixpath
import regex
import regsub
import glob

VERSION = "3.5"
COPYRIGHT = "Copyright (C) 1996, 1997 Red Hat Software\nRedistributable under the terms of the GNU General Public License"

print "Red Hat Linux usercfg", VERSION
print COPYRIGHT






def displaypass(password):
    if not len(password):
        return 'empty'
    elif not cmp(password[0], '*'):
        if len(password) > 1:
	    return 'locked'
        else:
	    return 'disabled'
    else:
        return 'exists'



class Global:
    def __init__(self):
	self.group = ConfGroup()
	self.pw = ConfUnix()
	self.shells = []
	shells = Conf('/etc/shells')
	shells.rewind()
	while shells.findnextline():
	    self.shells.append(shells.getline())
	    shells.nextline()
	self.homedirlist = []
    def save(self):
	self.group.write()
	self.pw.write()
	for userlist in self.homedirlist:
	    createhomedir(userlist[0], userlist[1], userlist[2])
	    createmailbox(self.pw[userlist[0]], self)
	self.homedirlist = []







class lockuser:
    def __init__(self, username, delete, G):
	self.next = 'cancel'
	self.L = Toplevel()
	if delete:
	    self.delete = 1
	    self.action = 'delete'
	    self.L.title('Delete User')
	else:
	    self.delete = 0
	    self.action = 'archive'
	    self.L.title('Archive User')
	# create a frame to hold the home directory processing radiobox
	HF= Frame(self.L, {'relief':'groove'})
	Label(HF, {'text':'Process '+username+'\'s home directory?'}).pack(
	      {'side':'top', 'pady':'6', 'ipady':'2', 'ipadx':'8'})
	Frame(HF).pack({'side':'left', 'padx':'15'})
	ProcessHome = IntVar(self.L)
	Radiobutton(HF, {'text':'Ignore', 'variable':ProcessHome, 'value':0}).pack(
	  {'side':'top', 'anchor':'w'})
	Radiobutton(HF, {'text':'Archive and compress', 'variable':ProcessHome, 'value':1}).pack(
	  {'side':'top', 'anchor':'w'})
	Radiobutton(HF, {'text':'Delete', 'variable':ProcessHome, 'value':2}).pack(
	  {'side':'top', 'anchor':'w'})
	HF.pack({'side':'top', 'anchor':'w'})
	DeleteMailSpool = IntVar(self.L)
	Checkbutton(self.L, {'text':'Delete '+username+'\'s mail spool?', 'variable':DeleteMailSpool}).pack(
	  {'side':'top', 'anchor':'w'})
	SearchFileSystem = IntVar(self.L)
	FindFiles = IntVar(self.L)
	Checkbutton(self.L, {'text':'Search for '+username+'\'s files?', 'variable':FindFiles}).pack(
	  {'side':'top', 'anchor':'w'})
	# Frame to hold search  stuff...
	FF = Frame(self.L)
	Frame(FF).pack({'side':'left', 'padx':'15'})
	Label(FF, {'text':'...and do what with them?'})
	ProcessFiles = IntVar(self.L)
	Radiobutton(FF, {'text':'Make `nobody\' own them', 'variable':ProcessFiles, 'value':0}).pack(
	  {'side':'top', 'anchor':'w'})
	Radiobutton(FF, {'text':'Delete them', 'variable':ProcessFiles, 'value':1}).pack(
	  {'side':'top', 'anchor':'w'})
	MailRoot = IntVar(self.L)
	Checkbutton(FF, {'text':'Mail a report of errors to root?', 'variable':MailRoot}).pack(
	  {'side':'top', 'anchor':'w'})
	FF.pack({'side':'top', 'anchor':'w'})
	BB = ButtonBar(self.L)
	BB.setOrientation('horizontal')
	BB.addButton('Done', self.done)
	BB.addButton('Cancel', self.cancel)
	BB.pack({'side':'bottom'})
	self.L.update()
	self.L.grab_set()
	self.L.wait_window(self.L)
	self.L.grab_release()
	# compare self.next and decide whether to make change...
	if not cmp(self.next, 'cancel'):
	    return
	# now make sure user really wants to do this...
	if not Dialog('Warning',
		'Do you really want to '+self.action+' user '+username+'?\n' +
		'This will cause all changes made so far to be saved!',
		'warning', 0, ['Cancel', 'Really '+self.action]).num:
	    return
	# First, get information we need on user in case user was deleted
	uid = G.pw[username].uid
	gid = G.pw[username].gid
	homedir = G.pw[username].homedir
	# might as well get 'nobody' uid and gid while we are at it
	nuid = G.pw['nobody'].uid
	ngid = G.pw['nobody'].gid
	# lock password field
	if not G.pw[username].password or \
	   cmp(G.pw[username].password[0], '*'):
	    G.pw[username].password = '*'+G.pw[username].password
	# Next, save all changes so far
	G.save()
	# Handle home directory
	if ProcessHome.get():
	    # need directory home directory is in (this is "dirname")
	    homedirdir = string.join(string.split(homedir, '/')[:-1], '/')
	    if ProcessHome.get() == 1:
		# tar and gzip directory
		os.system('(cd '+homedirdir+
			'; /bin/tar cf - '+username+' ) | gzip -9 > '+
			homedirdir+'/'+username+'.tar.gz')
	    # delete directory
	    os.system('/bin/rm -rf '+homedir)
	# Handle mail spool
	if DeleteMailSpool.get() and \
	    os.path.isfile('/var/spool/mail/'+username):
	    os.unlink('/var/spool/mail/'+username)
	# Start async search of filesystem
	# *** Make sure to use *numeric* uid and gid as the textual ones
	# *** may be deleted before this process is finished.
	if FindFiles.get():
	    # do a find job, and if MailRoot is set, send a report of
	    # any errors to root.
	    # This will be done by writing a shell script in /tmp that
	    # deletes itself when it is done
	    deleteFilename = '/tmp/ucf_delete_'+username
	    deleteScript = open(deleteFilename, 'w')
	    deleteScript.write('#!/bin/sh\n# script to delete '+username+'\n')
	    if MailRoot.get():
		deleteScript.write('(')
	    deleteScript.write('find / -type f -uid '+uid+' -exec ')
	    if ProcessFiles.get():
		# delete all files owned by username's uid
		deleteScript.write('rm -f {} \; ')
	    else:
		# change all files owned by username's uid to nobody:nobody
		deleteScript.write('chown {} '+nuid+':'+ngid+' \; ')
	    deleteScript.write('-or -type f -gid '+gid+' -exec chgrp {} '+ngid+' \; ')
	    if MailRoot.get():
		deleteScript.write(' ; echo \'To: root\nFrom: root\n' +
			'Subject: User Deletion Report for '+username+'\n\n\''+
			') | /usr/sbin/sendmail -oi -t\n')
	    else:
		deleteScript.write('> /dev/null 2>&1\n')
	    deleteScript.write('rm '+deleteFilename+'\n')
	    deleteScript.close()
	    os.chmod(deleteFilename, 0700)
	    os.system(deleteFilename+'&')
    def done(self, event=None):
	self.next = 'done'
	self.L.destroy()
    def cancel(self, event=None):
	self.next = 'cancel'
	self.L.destroy()

class unlockuser:
    def __init__(self, username, G):
	# make sure user really wants to do this...
	if not Dialog('Warning',
		'Do you really want to unlock user '+username+'?\n' +
		'This will cause all changes made so far to be saved!',
		'warning', 0, ['Cancel', 'Really unlock']).num:
	    return
	# First, remove * from front of password if it exists
	if G.pw[username].password and \
	   not cmp(G.pw[username].password[0], '*'):
	    G.pw[username].password = G.pw[username].password[1:]
	# Next, save all changes so far
	G.save()
	# Handle home directory
	# need directory home directory is in (this is "dirname")
	homedirdir = string.join(string.split(G.pw[username].homedir,
                                              '/')[:-1], '/')
	if not os.path.exists(G.pw[username].homedir):
	    if os.path.exists(G.pw[username].homedir+'.tar.gz'):
		os.system('cd '+homedirdir+' ; ' +
                          '(/bin/gzip -dc '+username+'.tar.gz | ' +
		          '/bin/tar xf -)')
	# Handle mail spool
	createmailbox(G.pw[username], G)

class createhomedir:
    def __init__(self, username, group, homedir):
	if os.path.isdir(homedir):
	    # the path already exists.  Don't muck with it
	    return
	try:
	    os.system('/bin/cp -apR /etc/skel ' + homedir)
	    os.system('/bin/chown -R ' + username+'.'+group + ' ' + homedir)
	except:
	    Dialog('Error',
		   'Could not create home directory for '+username+'\n' +
		   'Are you running as root?', 'warning', 0, ['Ok'])

class createmailbox:
    def __init__(self, unixentry, G):
	mailbox = '/var/spool/mail/'+unixentry.username
	try:
	    # even if mailbox has already been created, make sure that
	    # the mode is right.  opening in append mode won't hurt
	    # anything if the file already exists.
	    open(mailbox, 'a', 0).close()
	    os.chmod(mailbox, 0660)
	    os.chown(mailbox, string.atoi(unixentry.uid), string.atoi(G.group['mail'].gid))
	except:
	    Dialog('Error',
		   'Could not create mailbox for '+unixentry.username+'\n' +
		   'Are you running as root?', 'warning', 0, ['Ok'])

class shadowedit:
    def __init__(self, G, name, new=0):
	self.L = Toplevel()
	self.L.title('Edit Account Management')
	self.next = 'cancel'
	F = RHFrame(self.L)
	# lastchanged should not be modified explicitly
	self.mindays = IntVar(self.L)
	self.mindays.set(-1)
	m = LabelledStackEntry(F, 'Minimum days between password changes:',
			   self.mindays, '20')
	m.pack({'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	self.maxdays = IntVar(self.L)
	self.maxdays.set(-1)
	LabelledStackEntry(F, 'Maximum days between password changes:',
			   self.maxdays, '20').pack(
	      {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	self.warndays = IntVar(self.L)
	self.warndays.set(-1)
	LabelledStackEntry(F, 'Days to warn before password expires:',
			   self.warndays, '20').pack(
	      {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	self.gracedays = IntVar(self.L)
	self.gracedays.set(-1)
	LabelledStackEntry(F, 'Days after password expiry user may change password:',
			   self.gracedays, '20').pack(
	      {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	self.expires = IntVar(self.L)
	self.expires.set(-1)
	LabelledStackEntry(F, 'Day account (not password) expires:',
			   self.expires, '20').pack(
	      {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	BB = ButtonBar(F)
	BB.setOrientation('horizontal')
	# if quota, add Quota button
	BB.addButton('Done', self.done)
	BB.addButton('Cancel', self.cancel)
	BB.pack({'side':'bottom'})
	F.pack({'side':'top', 'expand':'1', 'fill':'both'})
	m.focus_set()
	if G.pw[name] and G.pw[name].mindays != -1:
	    self.mindays.set(G.pw[name].mindays)
	if G.pw[name] and G.pw[name].maxdays != -1:
	    self.maxdays.set(G.pw[name].maxdays)
	if G.pw[name] and G.pw[name].warndays != -1:
	    self.warndays.set(G.pw[name].warndays)
	if G.pw[name] and G.pw[name].gracedays != -1:
	    self.gracedays.set(G.pw[name].gracedays)
	if G.pw[name] and G.pw[name].expires != -1:
	    self.expires.set(G.pw[name].expires)
	self.L.update()
	self.L.grab_set()
	self.L.wait_window(self.L)
	self.L.grab_release()
    def done(self, event=None):
	self.next = 'done'
	self.L.destroy()
    def cancel(self, event=None):
	self.next = 'cancel'
	self.L.destroy()
    def values(self):
	# compare self.next and decide whether to make change...
	if not cmp(self.next, 'done'):
	    return (1, self.mindays.get(), self.maxdays.get(),
		self.warndays.get(), self.gracedays.get(),
		self.expires.get())
	else:
	    # cancelled
	    return (0, -1, -1, -1, -1,-1)


class useredit:
    def __init__(self, G, userBox, index, name):
	self.G = G
	self.name = name
	self.new = 0
	if not self.name:
	    self.new = 1
	    self.lastchanged = str(int(time.time())/86400)
	    self.mindays = -1
	    self.maxdays = -1
	    self.warndays = -1
	    self.gracedays = -1
	    self.expires = -1
	else:
	    self.lastchanged = G.pw[name].lastchanged
	    self.mindays = G.pw[name].mindays
	    self.maxdays = G.pw[name].maxdays
	    self.warndays = G.pw[name].warndays
	    self.gracedays = G.pw[name].gracedays
	    self.expires = G.pw[name].expires
	self.L = Toplevel()
	self.L.title('Edit User Definition')
	self.next = 'cancel'
	F = RHFrame(self.L)

	# create GUI elements
	if self.new:
	    self.username = StringVar(self.L)
	    ul = LabelledEntry(F, 'Username:', self.username, '22')
	    ul.pack({'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	else:
	    ul = Label(F, {'text':'Username: '+name, 'relief':'groove'})
	    ul.pack({'side':'top', 'pady':'6', 'ipady':'2', 'ipadx':'8'})
	self.password = StringVar(self.L)
	if not self.new:
	    self.origpassword = G.pw[name].password
	else:
	    self.origpassword = '*'
	self.passwordMenu = LabelledMenu(F, 'Encrypted password:', self.password, '22',
		(('command', {'label':'Original', 'command':self.origPasswd}),
		 ('command', {'label':'Change', 'command':self.changePasswd}),
		 ('command', {'label':'No Password', 'command':self.noPasswd}),
		 ('command', {'label':'Lock', 'command':self.lockPasswd}),
		 ('command', {'label':'Unlock', 'command':self.unlockPasswd}))
		)
	self.passwordMenu.pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	uid = StringVar(self.L)
	LabelledEntry(F, 'UID:', uid, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	gid = StringVar(self.L)
	LabelledEntry(F, 'GID:', gid, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	fullname = StringVar(self.L)
	LabelledEntry(F, 'Full name:', fullname, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	office = StringVar(self.L)
	LabelledEntry(F, 'Office:', office, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	officephone = StringVar(self.L)
	LabelledEntry(F, 'Office phone:', officephone, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	homephone = StringVar(self.L)
	LabelledEntry(F, 'Home phone:', homephone, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	self.homedir = StringVar(self.L)
	LabelledEntry(F, 'Home directory:', self.homedir, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	self.shell = StringVar(self.L)
	shellmenulist = []
	if not self.new:
	    shellmenulist.append(['command', {'label':G.pw[name].shell, 'command':lambda x=self:x.shell.set(x.G.pw[x.name].shell)}])
	shellmenulist.append(['command', {'label':'None', 'command':lambda x=self:x.shell.set('')}])
	shellmenulist.append(['command', {'label':'/bin/false', 'command':lambda x=self:x.shell.set('/bin/false')}])
	for shell in G.shells:
	    shellmenulist.append(['command', {'label':shell,
		'command':lambda x=self,y=shell: x.shell.set(y)}])
	self.shellMenu = LabelledMenu(F, 'Shell:', self.shell, '22',
		shellmenulist)
	self.shellMenu.pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})

	# Set data elements (besides name)
	if self.new:
	    uid.set(G.pw.getfreeuid())
	    gid.set(G.group.getfreegid())
	    if '/bin/bash' in G.shells:
		self.shell.set('/bin/bash')
	    else:
		self.shell.set(G.shells[0])
	    fullname.set('Red Hat Linux User')
	else:
	    self.password.set(G.pw[name].password)
	    uid.set(G.pw[name].uid)
	    gid.set(G.pw[name].gid)
	    # GECOS should be split up and entered in separate windows
	    gecosfields = string.split(G.pw[name].gecos, ',')
	    # make sure that gecosfields has enough fields...
	    for i in range(4-len(gecosfields)):
		gecosfields.append('')
	    fullname.set(gecosfields[0])
	    office.set(gecosfields[1])
	    officephone.set(gecosfields[2])
	    homephone.set(gecosfields[3])
	    self.homedir.set(G.pw[name].homedir)
	    self.shell.set(G.pw[name].shell)

	BB = ButtonBar(F)
	BB.setOrientation('horizontal')
	if G.pw.shadowexists():
	    BB.addButton('Shadow Management', self.shadow)
	# if quota, add Quota button
	BB.addButton('Done', self.done)
	BB.addButton('Cancel', self.cancel)
	BB.pack({'side':'bottom'})
	F.pack({'side':'top', 'expand':'1', 'fill':'both'})
	if self.new:
	    ul.focus_set()
	    ul.bind('<FocusOut>', self.dohomemouse)
	    ul.bind('<Return>', self.dohome)
	    ul.bind('<Tab>', self.dohome)
	else:
	    self.passwordMenu.focus_set()
	self.L.update()
	self.L.grab_set()
	self.L.wait_window(self.L)
	self.L.grab_release()

	# compare self.next and decide whether to make change...
	if not cmp(self.next, 'done'):
	    try: string.atoi(gid.get())
	    except: gid.set(G.group[gid.get()].gid)
	    # make sure that there are no , characters in the
	    # gecos entries.  Not sure what the best way to do
	    # this is, though...
	    if self.new:
		name = self.username.get()
		if G.pw.has_key(self.username.get()):
		    Dialog('Error', 'User '+self.username.get()+' already exists',
			   'warning', 0, ['Ok'])
		    return
		G.pw.addentry(self.username.get(), self.password.get(),
		    uid.get(), gid.get(),
		    string.join([fullname.get(), office.get(),
		    officephone.get(), homephone.get(), ''], ','),
		    self.homedir.get(), self.shell.get(),
		    self.lastchanged, self.mindays, self.maxdays,
		    self.warndays, self.gracedays, self.expires)
		nameofgid = G.group.nameofgid(gid.get())
		if nameofgid:
		    G.Groups.addUserToGroup(self.username.get(), nameofgid)
		else:
		    G.group.addentry(self.username.get(), '', gid.get(),
				     self.username.get())
		    G.Groups.addGroup(self.username.get())
		G.Groups.addUserToGroup(self.username.get(), 'users')
		G.homedirlist.append([self.username.get(), gid.get(), self.homedir.get()])
	    else:
		G.pw[name].password = self.password.get()
		G.pw[name].uid = uid.get()
		G.pw[name].gid = gid.get()
		G.pw[name].gecos = string.join([fullname.get(), office.get(),
                    officephone.get(), homephone.get(), ''], ',')
		G.pw[name].homedir = self.homedir.get()
		G.pw[name].shell = self.shell.get()
		if cmp(self.origpassword, self.password.get()):
		    self.lastchanged = str(int(time.time())/86400)
		G.pw[name].lastchanged = self.lastchanged
		G.pw[name].mindays = self.mindays
		G.pw[name].maxdays = self.maxdays
		G.pw[name].warndays = self.warndays
		G.pw[name].gracedays = self.gracedays
		G.pw[name].expires = self.expires
	    if cmp(index, 'end'):
		userBox.delete(index)
	    userBox.insert((G.pw[name].username, G.pw[name].uid, G.pw[name].gid,
			    displaypass(G.pw[name].password),
			    G.pw[name].homedir), index)
	# fall-through on cancel; so nothing is done.
    def dohomemouse(self, event=None):
	if int(event.num) == 8:
	    # 8 is magic number that shows up when Tk generates the
	    # focus change.  We don't know what it means, though...
	    self.dohome()
    def dohome(self, event=None):
	if not self.homedir.get() and self.username.get():
	    self.homedir.set('/home/'+self.username.get())
    def shadow(self, event=None):
	(success, mindays, maxdays, warndays, gracedays, expires) = \
	  shadowedit(self.G, self.name, self.new).values()
	if success:
	    (self.mindays, self.maxdays, self.warndays, self.gracedays,
		self.expires) = (mindays, maxdays, warndays, gracedays, expires)
    def done(self, event=None):
	if self.new and not self.username.get():
	    return
	# make sure we don't leave without the home directory filled in...
	self.dohome()
	self.next = 'done'
	self.L.destroy()
    def cancel(self, event=None):
	self.next = 'cancel'
	self.L.destroy()
    def origPasswd(self):
	self.password.set(self.origpassword)
    def changePasswd(self):
	change = SetPassword(self.L).get()
	if change:
	    self.password.set(change)
    def noPasswd(self):
	self.password.set('')
    def lockPasswd(self):
	if not self.password.get() or \
	   cmp(self.password.get()[0], '*'):
	    self.password.set('*'+self.password.get())
    def unlockPasswd(self):
	if self.password.get() and \
	   not cmp(self.password.get()[0], '*'):
	    self.password.set(self.password.get()[1:])



class Users(SubFrame):
    def __init__(self, Master, G):
	self.G = G
	G.Users = self
        SubFrame.__init__(self, Master)
	self.Box = MultifieldButtonbox(self,
		[('Name', 10, 1), ('UID', 6, 1), ('GID', 6, 1),
		 ('Password', 10, 1), ('Home Directory', 25, 1)],
		[
		#('Search', self.search), ('Sort', self.sort),
		 ('Add', self.add), ('View/Edit', self.editEntry),
		 ('Lock', self.lockEntry), ('Unlock', self.unlockEntry),
		 ('Remove', self.removeEntry)])
	for user in self.G.pw.keys():
	    u = self.G.pw[user]
	    self.Box.insert((u.username, u.uid, u.gid,
			     displaypass(u.password), u.homedir))
	self.Box.bind('<Double-Button-1>', self.editEntry)
	self.Box.pack({'side':'top', 'expand':'yes', 'fill':'both'})

    def search(self):
	pass

    def sort(self):
	pass

    def add(self):
	self.edit('end')

    def editEntry(self, event=None):
	self.edit(self.Box.currentEntry())
    def edit(self, index):
	if index == None:
	    return
	if not cmp(index, 'end'):
	    # add a new user
	    useredit(self.G, self.Box, index, None)
	else:
	    useredit(self.G, self.Box, index,
			self.Box.getSelectedItems()[0][0])

    def lockEntry(self, event=None):
	self.lock(self.Box.currentEntry())
    def lock(self, index):
	if not index:
	    return
	username = self.Box.getSelectedItems()[0][0]
	lockuser(username, 0, self.G)

    def unlockEntry(self, event=None):
	self.unlock(self.Box.currentEntry())
    def unlock(self, index):
	if not index:
	    return
	username = self.Box.getSelectedItems()[0][0]
	unlockuser(username, self.G)

    def removeEntry(self, event=None):
	self.remove(self.Box.currentEntry())
    def remove(self, index):
	if not index:
	    return
	username = self.Box.getSelectedItems()[0][0]
	lockuser(username, 1, self.G)
	del self.G.pw[username]
	self.Box.delete(index)
	# delete user-group if it exists and only has user in it
	if self.G.group.has_key(username) and \
	   not cmp(self.G.group[username].userlist, username):
	    self.G.Groups.removeGroup(username)
	# remove user from the "users" group if it exists
	if self.G.group['users']:
	    self.G.Groups.removeUserFromGroup(username, 'users')
	    










class groupedit:
    def __init__(self, G, groupBox, index, name):
	self.G = G
	self.name = name
	self.new = 0
	if not self.name:
	    self.new = 1
	self.L = Toplevel()
	self.L.title('Edit Group Definition')
	self.next = 'cancel'
	F = RHFrame(self.L)

	# create GUI elements
	if self.new:
	    groupname = StringVar(self.L)
	    ul = LabelledEntry(F, 'Group:', groupname, '22')
	    ul.pack({'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	else:
	    ul = Label(F, {'text':'Group: '+name, 'relief':'groove'})
	    ul.pack({'side':'top', 'pady':'6', 'ipady':'2', 'ipadx':'8'})
	self.password = StringVar(self.L)
	if not self.new:
	    self.origpassword = G.group[name].password
	else:
	    self.origpassword = ''
	self.passwordMenu = LabelledMenu(F, 'Encrypted password:', self.password, '22',
		(('command', {'label':'Original', 'command':self.origPasswd}),
		 ('command', {'label':'Change', 'command':self.changePasswd}),
		 ('command', {'label':'No Password', 'command':self.noPasswd}),
		 ('command', {'label':'Lock', 'command':self.lockPasswd}),
		 ('command', {'label':'Unlock', 'command':self.unlockPasswd}))
		)
	self.passwordMenu.pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	gid = StringVar(self.L)
	LabelledEntry(F, 'GID:', gid, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})
	userlist = StringVar(self.L)
	LabelledEntry(F, 'User list:', userlist, '22').pack(
	  {'side':'top', 'anchor':'w', 'expand':'1', 'fill':'x'})

	# Set data elements (besides name)
	if self.new:
	    gid.set(G.group.getfreegid())
	else:
	    self.password.set(G.group[name].password)
	    gid.set(G.group[name].gid)
	    userlist.set(G.group[name].userlist)

	BB = ButtonBar(F)
	BB.setOrientation('horizontal')
	BB.addButton('Done', self.done)
	BB.addButton('Cancel', self.cancel)
	BB.pack({'side':'bottom'})
	F.pack({'side':'top', 'expand':'1', 'fill':'both'})
	if self.new:
	    ul.focus_set()
	else:
	    self.passwordMenu.focus_set()
	self.L.update()
	self.L.grab_set()
	self.L.wait_window(self.L)
	self.L.grab_release()

	# compare self.next and decide whether to make change...
	if not cmp(self.next, 'done'):
	    # make sure that there are no , characters in the
	    # gecos entries.  Not sure what the best way to do
	    # this is, though...
	    if self.new:
		name = groupname.get()
		# FIXME: test to see that an existing group id hasn't
		# been selected (or should I?).
		if G.group.has_key(name):
		    Dialog('Error', 'Group '+name+' already exists',
			   'warning', 0, ['Ok'])
		    return
		G.group.addentry(name, self.password.get(),
				 gid.get(), userlist.get())
	    else:
		G.group[name].password = self.password.get()
		G.group[name].gid = gid.get()
		G.group[name].userlist = userlist.get()
	    if cmp(index, 'end'):
		groupBox.delete(index)
	    groupBox.insert((name, gid.get(), userlist.get()), index)
	# fall-through on cancel; so nothing is done.
    def done(self, event=None):
	self.next = 'done'
	self.L.destroy()
    def cancel(self, event=None):
	self.next = 'cancel'
	self.L.destroy()
    def origPasswd(self):
	self.password.set(self.origpassword)
    def changePasswd(self):
	change = SetPassword(self.L).get()
	if change:
	    self.password.set(change)
    def noPasswd(self):
	self.password.set('')
    def lockPasswd(self):
	if not self.password.get() or \
	   cmp(self.password.get()[0], '*'):
	    self.password.set('*'+self.password.get())
    def unlockPasswd(self):
	if self.password.get() and \
	   not cmp(self.password.get()[0], '*'):
	    self.password.set(self.password.get()[1:])


class Groups(SubFrame):
    def __init__(self, Master, G):
	self.G = G
	G.Groups = self
        SubFrame.__init__(self, Master)
	self.Box = MultifieldButtonbox(self,
		[('Group', 10, 1), ('GID', 6, 1),
		 ('Members', 44, 1)],
		[
		#('Search', self.search), ('Sort', self.sort),
		 ('Add', self.add), ('View/Edit', self.editEntry),
		 ('Remove', self.removeEntry)])
	for group in self.G.group.keys():
	    g = self.G.group[group]
	    self.Box.insert((g.name, g.gid, g.userlist))
	self.Box.bind('<Double-Button-1>', self.editEntry)
	self.Box.pack({'side':'top', 'expand':'yes', 'fill':'both'})

    def search(self):
	pass

    def sort(self):
	pass

    def add(self):
	self.edit('end')

    def editEntry(self, event=None):
	self.edit(self.Box.currentEntry())
    def edit(self, index):
	if index == None:
	    return
	if not cmp(index, 'end'):
	    # add a new user
	    groupedit(self.G, self.Box, index, None)
	else:
	    groupedit(self.G, self.Box, index,
			self.Box.getSelectedItems()[0][0])

    def insertGroup(self, groupname):
	g = self.G.group[groupname]
	self.Box.insert((g.name, g.gid, g.userlist))

    def addGroup(self, groupname):
	if not self.G.group[groupname]:
	    raise 'Group '+groupname+' does not exist'
	self.insertGroup(groupname)
    def addUserToGroup(self, username, groupname):
	if not self.G.group[groupname]:
	    raise 'Group '+groupname+' does not exist'
	if self.G.group[groupname].userlist:
	    self.G.group[groupname].userlist = \
		self.G.group[groupname].userlist + ',' + username
	else:
	    self.G.group[groupname].userlist = username
	# add user to group box
	i = 0
	for listitem in self.G.Groups.Box.getAllItems():
	    if not cmp(listitem[0], groupname):
		break
	    i = i + 1
	self.G.Groups.Box.delete(i)
	self.insertGroup(groupname)

    def removeEntry(self, event=None):
	self.remove(self.Box.currentEntry())
    def remove(self, index):
	if not index:
	    return
	del self.G.group[self.Box.getSelectedItems()[0][0]]
	self.Box.delete(index)

    def removeGroup(self, groupname):
	if self.G.group[groupname]:
	    del self.G.group[groupname]
	# delete group from group box
	i = 0
	for listitem in self.G.Groups.Box.getAllItems():
	    if not cmp(listitem[0], groupname):
		break
	    i = i + 1
	self.G.Groups.Box.delete(i)
    def removeUserFromGroup(self, username, groupname):
	if not self.G.group[groupname]:
	    raise 'Group '+groupname+' does not exist'
	userlist = regsub.split(self.G.group[groupname].userlist, ',')
	for i in range(len(userlist)):
	    if not cmp(userlist[i], username):
		userlist = userlist[:i] + userlist[i+1:]
		break
	self.G.group[groupname].userlist = joinfields(userlist, ',')
	# change group box
	i = 0
	for listitem in self.G.Groups.Box.getAllItems():
	    if not cmp(listitem[0], groupname):
		break
	    i = i + 1
	self.G.Groups.Box.delete(i)
	self.insertGroup(groupname)










class WindowFrame(RHFrame):
    def save(self):
	self.G.save()

    def showUsers(self):
	self.currentframe.hide()
	self.currentframe = self.Users
	self.Users.show()

    def showGroups(self):
	self.currentframe.hide()
	self.currentframe = self.Groups
	self.Groups.show()

    def __init__(self, Master = None):
	Master.minsize(484, 311)
	Master.title('User Configurator')
	# initialize "global" variables
	self.G = Global()
	# create lock file (FIXME: the name may change)
	open('/etc/.pwd.lock', 'a', 0).close()
	RHFrame.__init__(self, Master)
	FR = Frame(self, {'relief':'groove', 'bd':'4'})
	self.Users = Users(FR, self.G)
	self.currentframe = self.Users
	self.Groups = Groups(FR, self.G)
	TFR = Frame(self)
	FT = FolderTabs(TFR)
	FT.addTab('Users', self.showUsers, 1)
	FT.addTab('Groups', self.showGroups)
	#Help = Button(TFR, {'text':'Help', 'command':self.helpDisabled})
	SM = ButtonBar(self)
	SM.setOrientation('horizontal')
	SM.addButton('Save', self.save)
	SM.addButton('Quit', self.quit)
	FT.pack({'side':'left', 'anchor':'nw'})
	#Help.pack({'side':'right', 'anchor':'ne'})
	TFR.pack({'side':'top', 'anchor':'nw', 'fill':'x'})
	self.Users.show()
	FR.pack({'side':'top', 'expand':'1', 'fill':'both'})
	SM.pack({'side':'top', 'fill':'x'})
	self.pack({'expand':'1', 'fill':'both'})

    def quit(self):
	save = Dialog('Warning',
		'Do you really want to quit without saving any changes?',
		'warning', 0, ['Cancel', 'Save and Quit', 'Abandon']).num
	if not save:
	    return
	if save == 1:
	    self.save()
	# remove lock file (FIXME: the name may change later)
	try:
	    os.unlink('/etc/.pwd.lock')
	except:
	    pass
	RHFrame.quit(self)







# magic to keep a root window from appearing
L = Label()
L.tk.call('wm', 'withdraw', '.')
del L

win = WindowFrame(Toplevel())

win.wait_window(win)

