#!/usr/bin/rhswish -f
########################################
# External modules

if {[catch set env(CONTROL_PANEL_LIB_DIR)] != 0} {
    set env(CONTROL_PANEL_LIB_DIR) /usr/lib/rhs/control-panel
}

if {[catch {source $env(CONTROL_PANEL_LIB_DIR)/dialog.tcl}] != 0} {
    puts "Couldn't load dialog.tcl"
    puts "Start from control-panel or set environment variable"
    puts "CONTROL_PANEL_LIB_DIR to the control-panel library dir."
    puts "(normally this is /usr/lib/rhs/control-panel)"
    exit 0
}

########################################
# Global variables (too many of these, if you ask me)

set orphaneduser nobody
set orphanedgroup nobody

# Used to generate unique numbers

set tmpnum 0
set tmpa(0) 0

# Assorted paths

set passwdfilename "/etc/passwd"
set groupfilename "/etc/group"
set shellfilename "/etc/shells"
set maildir "/var/spool/mail"
set defhomepath "/home"
set skeldir "/etc/skel"

# Directories that will NOT be searched when looking for orphaned files

set excludedirs "/mnt;/proc"

# First user UID, user GID, and system GID

set useruidbegin 501
set usergidbegin 501
set sysgidbegin 100

# The disp* variables control which fields of /etc/passwd are shown in the
# user list.

set dispname 1
set disppassword 1
set dispuid 0
set dispgroup 1
set dispgecos 1
set dispshell 0
set disphome 1

# If showupg = 0, user private groups are not displayed in the group frame.

set showupg 0

# passstyle = 0 : Passwords are shown as `exists', `blank', or `locked'
#           = 1 : Passwords are shown as they appear in /etc/passwd

set passstyle 0

# gidstyle = 0 : Groups are shown by name
#          = 1 : Groups are shown by GID

set gidstyle 1

# confirmremove = 1 : Users are prompted for confirmation before users or
#                     groups are removed

set confirmremove 1

# Internal representations of /etc/passwd, /etc/group, and /etc/shells

set passwdlist {}
set grouplist {}
set shellslist {}

# Current number of open add user/group windows

set numadds 0

# Current editing mode -- user or group

set curmode "user"

# Variables used by assorted entry widgets

set name(0) ""
set password(0) ""
set uid(0) ""
set gid(0) ""
set group(0) ""
set gecos(0) ""
set shell(0) ""
set home(0) ""

set visframe "user"

set adduserup 0
set configup 0
set pathsup 0

set addingsgroups(0) 0
set addinggmems(0) 0

########################################
# Utilities

##########
# Get a unique UID, with a minimum value of $m.

proc getuniqueuid {m} {
  global passwdlist
  
  set i $m
  set f 0
  while {$f == 0} {
    set f 1
    foreach x $passwdlist {
      if {$i == [lindex $x 2]} {
	incr i
	set f 0
      }
    }
  }
  return $i
}

##########
# Get a unique GID, with a minimum value of $m.

proc getuniquegid {m} {
  global grouplist
  
  set i $m
  set f 0
  while {$f == 0} {
    set f 1
    foreach x $grouplist {
      if {$i == [lindex $x 2]} {
	incr i
	set f 0
      }
    }
  }
  return $i
}

##########
# Return a list made up from the contents of listbox $box.

proc listfrombox {box} {
  set l {}

  for {set x 0} {$x < [$box size]} {incr x} {
    lappend l [$box get $x]
  }

  return $l
}

##########
# Return a list made up of the selected items in listbox $box.

proc selfrombox {box} {
  set l {}

  set s [$box curselection]
  foreach x $s {
    lappend l [$box get $x]
  }

  return $l
}

##########
# Read the password file into the password list.

proc getpasswd {} {
  global passwdfilename
  
  if {[catch {set f [open $passwdfilename r]}] != 0} {
    rhs_error_dialog "Couldn't read from $passwdfilename."
    return
  }
  
  while {[gets $f str] >= 0} {
    lappend passwdlist [split "${str}:0" :]
  }

  close $f
  
  return $passwdlist
}

##########
# Read the group file into the group list.

proc getgroup {} {
  global groupfilename
  
  if {[catch {set f [open $groupfilename r]}] != 0} {
    rhs_error_dialog "Couldn't read from $groupfilename."
    return
  }
  
  while {[gets $f str] >= 0} {
    set t [split "${str}:0" :]
    lappend grouplist [lreplace $t 3 3 [split [lindex $t 3] ,]]
  }

  close $f
  
  return $grouplist
}

##########
# Read the shells file into the shells list.

proc getshells {} {
  global shellfilename
  
  if {[catch {set f [open $shellfilename r]}] != 0} {
    rhs_error_dialog "Couldn't read $shellfilename."
    return
  }
  
  while {[gets $f str] >= 0} {
    if {[string index $str 0] != "#"} {
      lappend shellslist $str
    }
  }
  
  return $shellslist
}

##########
# Replace an entry in the password list.
#   i    - The value of the lock field (7) in the password list
#   name - The value of the name field (0) in the password list
#   ent  - The entry to substitute into the list

proc passwdlistreplace {i name ent} {
  global passwdlist

  set j 0
  foreach x $passwdlist {
    if {[lindex $x 0] == $name} {
      if {[lindex $x 7] == $i} {
	set passwdlist [lreplace $passwdlist $j $j $ent]
      }
    }
    incr j
  }
}

##########
# Replace an entry in the group list.
#   i    - The value of the lock field (4) in the group list
#   name - The value of the name field (0) in the group list
#   ent  - The entry to substitute into the list

proc grouplistreplace {i name ent} {
  global grouplist

  set j 0
  foreach x $grouplist {
    if {[lindex $x 0] == $name} {
      if {[lindex $x 4] == $i} {
	set grouplist [lreplace $grouplist $j $j $ent]
      }
    }
    incr j
  }
}

##########
# Rebuild the contents of the .user.users listbox from the password list.
# The label .user.lframe.label is also regenerated.

proc regenuserlist {} {
  global passwdlist grouplist
  global dispname disppassword dispuid dispgroup dispgecos disphome
  global dispshell passstyle gidstyle

  set idx [.user.users nearest 0]
  .user.users delete 0 end
  foreach x $passwdlist {
    set ulistwidth 0
    set s ""
    set l ""
    if {$dispname == 1} {
      set s $s[format "%-10s " [lindex $x 0]]
      set ulistwidth [expr $ulistwidth + 11]
      set l "${l}Name       "
    }
    if {$disppassword == 1} {
      if {$passstyle == 0} {
	set t [lindex $x 1]
	if {[string length $t] == 0} {
	  set s "${s}blank  "
	} elseif {[string index $t 0] == "*"} {
	  set s "${s}locked "
	} else {
	  set s "${s}exists "
	}
	set ulistwidth [expr $ulistwidth + 7]
	set l "${l}Passwd "
      } elseif {$passstyle == 1} {
	set s $s[format "%-15s " [lindex $x 1]]
	set ulistwidth [expr $ulistwidth + 16]
	set l "${l}Password        "
      }
    }
    if {$dispuid == 1} {
      set s $s[format "%-5s " [lindex $x 2]]
      set ulistwidth [expr $ulistwidth + 6]
      set l "${l}UID   "
    }
    if {$dispgroup == 1} {
      if {$gidstyle == 0} {
	set s $s[format "%-5s " [lindex $x 3]]
	set ulistwidth [expr $ulistwidth + 6]
	set l "${l}GID   "
      } elseif {$gidstyle == 1} {
	set t "-none-"
	foreach y $grouplist {
	  if {[lindex $x 3] == [lindex $y 2]} {
	    set t [lindex $y 0]
	    break
	  }
	}
	set s $s[format "%-10s " $t]
	set ulistwidth [expr $ulistwidth + 11]
	set l "${l}Group      "
      }
    }
    if {$dispgecos == 1} {
      set s $s[format "%-25s " [lindex $x 4]]
      set ulistwidth [expr $ulistwidth + 26]
      set l "${l}Full name                 "
    }
    if {$disphome == 1} {
      set s $s[format "%-15s " [lindex $x 5]]
      set ulistwidth [expr $ulistwidth + 16]
      set l "${l}Home directory  "
    }
    if {$dispshell == 1} {
      set s $s[format "%-15s " [lindex $x 6]]
      set ulistwidth [expr $ulistwidth + 16]
      set l "${l}Shell           "
    }
    .user.users insert end $s
  }

  .user.lframe.label configure -text $l
  if {$ulistwidth == 0} {
    set ulistwidth 1
  }
  .user.users configure -geometry ${ulistwidth}x10
  .user.users yview $idx
}

##########
# Rebuild the contents of the .group.groups listbox from the group list.
# The label .group.lframe.label is also regenerated.

proc regengrouplist {} {
  global grouplist usergidbegin showupg
  
  set idx [.group.groups nearest 0]
  .group.groups delete 0 end
  foreach x $grouplist {
    if {([lindex $x 2] < $usergidbegin) || ($showupg == 1)} {
      set s [format "%-10s %-5s %-5s" [lindex $x 0] [lindex $x 2] \
	     [llength [lindex $x 3]]]
      .group.groups insert end $s
    }
  }
  .group.groups yview $idx
}

##########
# Write the password and group files back to the disk.

proc syncfiles {} {
  global passwdfilename passwdlist groupfilename grouplist

  if {[catch {set p [open $passwdfilename w]}] != 0} {
    rhs_error_dialog "Couldn't write to $passwdfilename."
    return
  }

  if {[catch {set g [open $groupfilename w]}] != 0} {
    rhs_error_dialog "Couldn't write to $groupfilename."
    return
  }

  foreach x $passwdlist {
    set x [lreplace $x 7 7]
    puts $p [join $x :]
  }

  foreach x $grouplist {
    set x [lreplace $x 4 4]
    set y [lindex $x 3]
    set x [lreplace $x 3 3]
    puts $g "[join $x :]:[join $y ,]"
  }

  close $p
  close $g
}

##########
# Change files owned by specified user and/or group IDs to a new UID/GID.
#   uid      - A UID to change. (Set to -1 to skip searching for UIDs.)
#   gid      - A GID to change. (Set to -1 to skip searching for GIDs.)
#   newuser  - The new user ID or username.
#   newgroup - The new group ID or groupname.
#   basedir  - The directory to search. (All subdirs of this directory will
#              be searched. Directories listed in $excludedirs will NOT be
#              searched.)

proc cleanorphans {uid gid newuser newgroup basedir} {
  global passwdlist grouplist excludedirs

  set newu $newuser
  if {$uid != -1} {
    foreach u $passwdlist {
      if {[lindex $u 0] == $newuser} {
	set newu [lindex $u 2]
	continue
      }
    }
  }

  set newg $newgroup
  if {$gid != -1} {
    foreach g $grouplist {
      if {[lindex $g 0] == $newgroup} {
	set newg [lindex $g 2]
	continue
      }
    }
  }

  if {$newu == "" && $newg == ""} {
    return 
  }

  if {$uid != -1} {
    set com "/usr/bin/find $basedir"
    foreach d [split $excludedirs \;] {
      set com "$com \\\( -path $d -prune \\\) -o"
    }
    set com "$com -uid $uid -exec /bin/chown $newu \\\{\\\} \\\; >&/dev/null &"

    catch {eval exec $com}
  }

  if {$gid != -1} {
    set com "/usr/bin/find $basedir"
    foreach d [split $excludedirs \;] {
      set com "$com \\\( -path $d -prune \\\) -o"
    }
    set com "$com -gid $gid  -exec /bin/chown .$newg \\\{\\\} \\\; >&/dev/null &"

    catch {eval exec $com}
  }
}   

########################################
# Password section

##########
# Variables

# The encrypted password.

set tpass(0) ""

# The password currently being entered. (Unencrypted, of course.)

set epass(0) ""

# A set of hashmarks (#) to display instead of the password being edited.

set ephash(0) ""

# The password being verified. If this is "", then any password entered is
# assumed to be a first attempt.

set epver(0) ""

# A lock variable to prevent simultaneous edits of the same password.

set editingpass(0) 0

##########
# Fill ephash($i) with as many hashmarks as there are characters in epass($i).

proc pwdgenhash {i} {
	global epass ephash

	set ephash($i) ""
	for {set x [string length $epass($i)]} {$x > 0} {incr x -1} {
		set ephash($i) "$ephash($i)#"
	}
}

##########
# Delete the last letter of the password being edited. Invoked when BackSpace,
# Delete, or C-h is pressed.

proc delpwdletter {i} {
	global epass

	set epass($i) [string range $epass($i) 0 \
		[expr "[string length $epass($i)]-2"]]
	pwdgenhash $i
}

##########
# Add $c to the end of the password being edited.

proc addpwdletter {i c} {
	global epass

	set epass($i) $epass($i)$c
	pwdgenhash $i
}

##########
# If this is the first attempt at entering the password, save it and switch
# to verify mode. Otherwise, process the entered password.

proc pwdhitenter {i} {
	global tpass epass ephash epver

	if {$epver($i) == ""} {
		if {$epass($i) != ""} {
			.editpwd$i.pwd.label configure -text "Verify:"
			set epver($i) $epass($i)
			set epass($i) ""
			set ephash($i) ""
		}
	} else {
		if {$epass($i) == $epver($i)} {
			.editpwd$i.pwd.label configure -text "Password:"
			set tpass($i) [crypt $epass($i)]
			set epass($i) ""
			set ephash($i) ""
			set epver($i) ""
		} else {
			.editpwd$i.pwd.label configure -text "Password:"
			set epass($i) ""
			set ephash($i) ""
			set epver($i) ""
		}
	}
}

##########
# Enter the new password into password($i) and exit.

proc pwdeditok {i} {
	global password tpass epass ephash epver editingpass
 
	set password($i) $tpass($i)
	unset tpass($i) epass($i) ephash($i) epver($i)
	set editingpass($i) 0
	destroy .editpwd$i
}

##########
# Exit and discard the new password.

proc pwdeditcancel {i} {
	global tpass epass ephash epver editingpass

	unset tpass($i) epass($i) ephash($i) epver($i)
	set editingpass($i) 0
	destroy .editpwd$i
}

##########
# Open a window to allow editing a password.

proc editpasswd {i} {
	global password tpass epass ephash epver editingpass name

	if {$editingpass($i) == 1} {
		return
	}
	set editingpass($i) 1

	set tpass($i) $password($i)
	set epver($i) ""
	set editingpass($i) 1

	toplevel .editpwd$i

	frame .editpwd$i.ents
	pack .editpwd$i.ents -side top -expand 1 -fill both -padx 4 -pady 4

	frame .editpwd$i.enc
	pack .editpwd$i.enc -side top -expand 1 -fill x -padx 4 \
		-in .editpwd$i.ents
	label .editpwd$i.enc.label -text "Encrypted password:"
	entry .editpwd$i.enc.entry -width 15 -relief sunken -bd 2 \
		-textvariable tpass($i)
	pack .editpwd$i.enc.label -side left -anchor w
	pack .editpwd$i.enc.entry -side right -anchor e

	frame .editpwd$i.pwd
	pack .editpwd$i.pwd -side top -expand 1 -fill x -padx 4 \
		-in .editpwd$i.ents
	label .editpwd$i.pwd.label -text "Password:"
	entry .editpwd$i.pwd.entry -width 15 -relief sunken -bd 2 \
		-textvariable ephash($i)
	pack .editpwd$i.pwd.label -side left -anchor w
	pack .editpwd$i.pwd.entry -side right -anchor e

	frame .editpwd$i.buttons
	pack .editpwd$i.buttons -side top -expand 1 -fill x -padx 4 -pady 4

	button .editpwd$i.ok -width 7 -text "OK" -command "pwdeditok $i"
	button .editpwd$i.cancel -width 7 -text "Cancel" \
	  -command "pwdeditcancel $i"
	pack .editpwd$i.ok .editpwd$i.cancel -side left -expand 1 \
		-in .editpwd$i.buttons

	set epass($i) ""
	set ephash($i) ""

	bind .editpwd$i.enc.entry <Return> "focus .editpwd$i.pwd.entry"

	bind .editpwd$i.pwd.entry <Any-Key> "addpwdletter $i %A"
	bind .editpwd$i.pwd.entry <Delete> "delpwdletter $i"
	bind .editpwd$i.pwd.entry <BackSpace> "delpwdletter $i"
	bind .editpwd$i.pwd.entry <Control-h> "delpwdletter $i"
	bind .editpwd$i.pwd.entry <Return> "pwdhitenter $i"

	focus .editpwd$i.pwd.entry

	if {$name($i) != ""} {
	  wm title .editpwd$i "Edit password for $name($i)"
	} else {
	  wm title .editpwd$i "Edit password for <unnamed>"
	}	  
}

########################################
# User section

####################
# Functions common to adding and editing users.

##########
# Check to see if a home directory exists. If not, offer to create it.
#   i             - The index of the user data in the name, uid, etc. arrays.

proc makehomedir {i} {
  global name password uid group gecos home shell
  global skeldir

  if {[file isdirectory $home($i)] == 0} {
    set x [rhs_dialog .veruser$i "Question" \
	   "The directory $home($i) does not exist.\nDo you want to create it?" \
	     question 0 "Yes" "No"]
    if {$x == 0} {
      set e 0
      if {[catch {exec /bin/cp -apR $skeldir $home($i)}] != 0} {
	set e 1
      } else {
	if {[catch {exec /bin/chown -R $uid($i).$group($i) $home($i)}] != 0} {
	  set e 1
	}
      }
      if {$e == 1} {
	rhs_error_dialog "The home directory was not created properly."
      }
    }
  }
}

##########
# Verify that a given set of user parameters make sense. Returns 1 if
# everything is OK, 0 is something went wrong.
#   i             - The index of the user data in the name, uid, etc. arrays.
#   checkuniqname - 1 if the username unicity should be checked.
#   checkuniquid  - 1 if the UID unicity should be checked.

proc verifyuserent {i checkuniqname checkuniquid} {
  global passwdlist grouplist useruidbegin usergidbegin
  global name password uid group gecos home shell
  
  # Check for a blank username.

  if {[string length $name($i)] == 0} {
    rhs_error_dialog "The user name may not be left blank."
    return 0
  }

  # Check for a blank primary group.

  if {[string length $uid($i)] == 0} {
    rhs_error_dialog "The primary group may not be left blank."
    return 0
  }

  # Verify that the UID is valid.

  set x 0
  for { set j 0 } { $j < [string length $uid($i)] } { incr j } {
    if {0==[string match "\[0123456789\]" [string index $uid($i) $j]]} {
      set x 1
    }
  }
  if {$x == 0} {
    if {[string length $uid($i)] == 0} {
      set x 1
    }
  }
  if {$x == 0} {
    if {$uid($i) > 32767} {
      set x 1
    }
  }
  if {$x == 1} {
    set x [rhs_dialog .veruser$i "Warning" \
	   "\"$uid($i)\" is not a valid user ID." \
	     warning 0 "Generate valid ID" "Abort"]
    if {$x == 1} {
      return 0
    }
    set uid($i) [getuniqueuid $useruidbegin]
    rhs_dialog .veruser$i "Information" \
      "The user ID has been set to $uid($i)." \
      info 0 "OK"
  }

  if {$checkuniqname == 1 || $checkuniquid == 1} {
    foreach u $passwdlist {

      if {$checkuniqname == 1} {

	# Verify that the username is unique.

	if {$name($i) == [lindex $u 0]} {
	  rhs_error_dialog "There is already a user named $name($i)."
	  return 0
	}
      }

      if {$checkuniquid == 1} {

	# Verify that the UID is unique.

	if {$uid($i) == [lindex $u 2]} {
	  set x [rhs_dialog .veruser$i "Warning" \
		   "There is already a user with UID $uid($i)." \
		   warning 1 "Continue" "Generate valid ID" "Abort"]

	  if {$x == 1} {

	    # Generate a unique UID.

	    set uid($i) [getuniqueuid $useruidbegin]
	    rhs_dialog .veruser$i "Information" \
	      "The user ID has been set to $uid($i)." \
	      info 0 "OK"
	  }

	  if {$x == 2} {
	    return 0
	  }
	}
      }

    }
  }

  # Verify that the home directory is an absolute path.

  if {$home($i) != "" && [string first / $home($i)] != 0} {
    set x [rhs_dialog .veruser$i "Warning" \
	   "The home directory is not an absolute path." \
	     warning 1 "Continue" "Abort"]
    if {$x == 1} {
      return 0
    }
  }

  # Verify that the primary group is valid.

  set x 0
  for { set j 0 } { $j < [string length $group($i)] } { incr j } {
    if {0==[string match "\[0123456789\]" \
	    [string index $group($i) $j]]} {
	      set x 1
	    }
  }

  if {$x == 0} {

    # The primary group has been specified as a GID.

    if {$group($i) > 32767} {
      rhs_error_dialog "$group($i) is not a valid group ID."
      return 0
    }
    set j 1
    foreach y $grouplist {
      if {[lindex $y 2] == $group($i)} {
	set j 0
	break
      }
    }
    if {$j == 1} {
      rhs_error_dialog "There is no group with ID $group($i)."
      return 0
    }
  } else {

    # The primary group has been specified as a groupname.

    set j 1
    foreach y $grouplist {
      if {[lindex $y 0] == $group($i)} {
	set j 0
	set group($i) [lindex $y 2]
	break
      }
    }
    if {$j == 1} {
      if {$group($i) == $name($i)} {
	set r 0
      } else {
	set r [rhs_dialog .veruser$i "Warning" \
	       "There is no group named \"$group($i)\"." \
		 warning 0 "Create it" "Abort"]
      }
      if {$r == 1} {
	return 0
      }

      # Generate the group.

      set newg {}
      lappend newg $group($i) "" [getuniquegid $usergidbegin] $name($i) 0
      set group($i) [lindex $newg 2]
      lappend grouplist $newg
      regengrouplist
    }
  }
  
  return 1
}

##########
# Adds the selected groups from the .addsg$i listbox into the .euserui$i
# listbox.

proc addsg {i} {
  global grouplist
  
  set l [.addsg$i.lframe.list curselection]
  
  foreach x $l {
    .euserui$i.lframe.list insert end \
      [.addsg$i.lframe.list get $x]
  }
  
  while {[llength $l] > 0} {
    .addsg$i.lframe.list delete [lindex $l 0] [lindex $l 0]
    set l [.addsg$i.lframe.list curselection]
  }  
}

##########
# Opens a window with a listbox to select secondary groups from.

proc addsgroup {i} {
  global grouplist addingsgroups name

  if {$addingsgroups($i) == 1} {
    return
  }
  set addingsgroups($i) 1

  toplevel .addsg$i
  wm withdraw .addsg$i
  
  frame .addsg$i.lframe
  pack .addsg$i.lframe -side top
  
  listbox .addsg$i.lframe.list -relief raised -borderwidth 2 \
    -yscrollcommand ".addsg$i.lframe.scroll set" -font fixed \
    -geometry 10x5
  pack .addsg$i.lframe.list -side left -expand 1 -fill both
  scrollbar .addsg$i.lframe.scroll\
    -command ".addsg$i.lframe.list yview"
  pack .addsg$i.lframe.scroll -side left -fill y
  
  frame .addsg$i.bframe
  pack .addsg$i.bframe -side top -fill x
  
  button .addsg$i.add -text "Add" -command "addsg $i"
  button .addsg$i.cancel -text "Done" \
    -command "set addingsgroups($i) 0 ; destroy .addsg$i"
  pack .addsg$i.add .addsg$i.cancel -side left \
    -expand 1 -in .addsg$i.bframe -padx 4 -pady 4

  set sg [listfrombox .euserui$i.lframe.list]
  foreach x $grouplist {
    if {[lsearch $sg [lindex $x 0]] == -1} {
      .addsg$i.lframe.list insert end [lindex $x 0]
    }
  }

  if {$name($i) != ""} {
    wm title .addsg$i "Add secondary group for $name($i)"
  } else {
    wm title .addsg$i "Add secondary group for <unnamed>"
  }

  update idletasks
  set x [expr - [winfo reqwidth .addsg$i] + [winfo rootx .euserui$i.lframe] \
	- 15]
  set y [expr [winfo height .euserui$i.lframe]/2 - \
	 [winfo reqheight .addsg$i]/2 + [winfo rooty .euserui$i.lframe] \
	  + [winfo vrooty .euserui$i.lframe]]
  wm group .addsg$i .euserui$i.lframe
  wm transient .addsg$i .euserui$i.lframe
  wm geometry .addsg$i +$x+$y
  wm deiconify .addsg$i
}

##########
# Deletes the selected secondary groups from the .euserui$i listbox.

proc rmsgroup {i} {
  global passwdlist
  
  set l [.euserui$i.lframe.list curselection]
  
  foreach x $l {
    lappend d [.euserui$i.lframe.list get $x]
  }

  foreach y $d {
    for {set x 0} {$x < [.euserui$i.lframe.list size]} {incr x} {
      if {$y == [.euserui$i.lframe.list get $x]} {
	.euserui$i.lframe.list delete $x $x
	break
      }
    }
  }
}  

##########
# Generates the primary interface componants for adding/editing groups.

proc edituserui {i} {
  global passwdlist grouplist tmpnum useruidbegin defhomepath shellslist
  global name password uid group gecos home shell
  global editingpass addingsgroups
  
  set editingpass($i) 0
  set addingsgroups($i) 0
  
  toplevel .euserui$i
  
  frame .euserui$i.fa
  pack .euserui$i.fa -side top -expand 1 -fill both -padx 4 -pady 4

  frame .euserui$i.ents
  pack .euserui$i.ents -side left -expand 1 -fill x -in .euserui$i.fa
  
  frame .euserui$i.name
  pack .euserui$i.name -side top -expand 1 -fill x -padx 4 \
    -in .euserui$i.ents
  label .euserui$i.name.label -text "Name:"
  label .euserui$i.name.bit -bitmap cleardd
  entry .euserui$i.name.entry -width 30 -relief sunken -bd 2 \
    -textvariable name($i)
  pack .euserui$i.name.label -side left -anchor w
  pack .euserui$i.name.entry .euserui$i.name.bit -side right -anchor e
  
  frame .euserui$i.password
  pack .euserui$i.password -side top -expand 1 -fill x -padx 4 \
    -in .euserui$i.ents
  label .euserui$i.password.label -text "Password:"
  menubutton .euserui$i.password.bit -bitmap dropdown -relief raised \
    -menu .euserui$i.password.bit.menu
  entry .euserui$i.password.entry -width 30 -relief sunken -bd 2 \
    -textvariable password($i)
  pack .euserui$i.password.label -side left -anchor w
  pack .euserui$i.password.entry .euserui$i.password.bit -side right \
    -anchor e
  menu .euserui$i.password.bit.menu
  .euserui$i.password.bit.menu add command -label "- edit -" \
    -command "editpasswd $i"
  .euserui$i.password.bit.menu add command -label "- none -" \
    -command "set password($i) {}"
  .euserui$i.password.bit.menu add command -label "- lock -" \
    -command "set password($i) *"
  bind .euserui$i.password.entry <Escape> "editpasswd $i"
  
  frame .euserui$i.uid
  pack .euserui$i.uid -side top -expand 1 -fill x -padx 4 \
    -in .euserui$i.ents
  label .euserui$i.uid.label -text "UID:"
  label .euserui$i.uid.bit -bitmap cleardd
  entry .euserui$i.uid.entry -width 30 -relief sunken -bd 2 \
    -textvariable uid($i)
  pack .euserui$i.uid.label -side left -anchor w
  pack .euserui$i.uid.entry .euserui$i.uid.bit -side right -anchor e
  
  frame .euserui$i.group
  pack .euserui$i.group -side top -expand 1 -fill x -padx 4 \
    -in .euserui$i.ents
  label .euserui$i.group.label -text "Primary Group:"
  label .euserui$i.group.bit -bitmap cleardd
  entry .euserui$i.group.entry -width 30 -relief sunken -bd 2 \
    -textvariable group($i)
  pack .euserui$i.group.label -side left -anchor w
  pack .euserui$i.group.entry .euserui$i.group.bit -side right -anchor e
  
  frame .euserui$i.gecos
  pack .euserui$i.gecos -side top -expand 1 -fill x -padx 4 \
    -in .euserui$i.ents
  label .euserui$i.gecos.label -text "Full name:"
  label .euserui$i.gecos.bit -bitmap cleardd
  entry .euserui$i.gecos.entry -width 30 -relief sunken -bd 2 \
    -textvariable gecos($i)
  pack .euserui$i.gecos.label -side left -anchor w
  pack .euserui$i.gecos.entry .euserui$i.gecos.bit -side right -anchor e

  frame .euserui$i.home
  pack .euserui$i.home -side top -expand 1 -fill x -padx 4 \
    -in .euserui$i.ents
  label .euserui$i.home.label -text "Home:"
  label .euserui$i.home.bit -bitmap cleardd
  entry .euserui$i.home.entry -width 30 -relief sunken -bd 2 \
    -textvariable home($i)
  pack .euserui$i.home.label -side left -anchor w
  pack .euserui$i.home.entry .euserui$i.home.bit -side right -anchor e
  
  frame .euserui$i.shell
  pack .euserui$i.shell -side top -expand 1 -fill x -padx 4 \
    -in .euserui$i.ents
  label .euserui$i.shell.label -text "Shell:"
  menubutton .euserui$i.shell.bit -bitmap dropdown -relief raised \
    -menu .euserui$i.shell.bit.menu
  entry .euserui$i.shell.entry -width 30 -relief sunken -bd 2 \
    -textvariable shell($i)
  pack .euserui$i.shell.label -side left -anchor w
  pack .euserui$i.shell.entry .euserui$i.shell.bit -side right -anchor e
  menu .euserui$i.shell.bit.menu
  foreach x $shellslist {
    .euserui$i.shell.bit.menu add command -label $x \
      -command "set shell($i) $x"
  }

  frame .euserui$i.fb
  pack .euserui$i.fb -side left -padx 4 -in .euserui$i.fa

  frame .euserui$i.fc
  pack .euserui$i.fc -side left -in .euserui$i.fa -expand 1 -fill both

  frame .euserui$i.lframe
  pack .euserui$i.lframe -side top -fill both -expand 1 -in .euserui$i.fc
  
  label .euserui$i.lframe.label -text "Secondary groups"
  pack .euserui$i.lframe.label -side top -fill x -expand 0
  
  listbox .euserui$i.lframe.list -relief raised -borderwidth 2 \
    -yscrollcommand ".euserui$i.lframe.scroll set" -font fixed \
    -geometry 10x5
  pack .euserui$i.lframe.list -side left -expand 1 -fill both
  scrollbar .euserui$i.lframe.scroll -command ".euserui$i.lframe.list yview"
  pack .euserui$i.lframe.scroll -side left -fill y

  frame .euserui$i.gbuttons
  pack .euserui$i.gbuttons -side bottom -fill x -expand 0 -in .euserui$i.fc

  button .euserui$i.addg -width 7 -text "Add" -command "addsgroup $i"
  button .euserui$i.delg -width 7 -text "Remove" -command "rmsgroup $i"
  pack .euserui$i.addg .euserui$i.delg -side left -expand 1 \
    -in .euserui$i.gbuttons
  
  bind .euserui$i.name.entry <Return> " focus .euserui$i.password.entry "
  bind .euserui$i.password.entry <Return> " focus .euserui$i.uid.entry "
  bind .euserui$i.uid.entry <Return> " focus .euserui$i.group.entry "
  bind .euserui$i.group.entry <Return> " focus .euserui$i.gecos.entry "
  bind .euserui$i.gecos.entry <Return> " focus .euserui$i.home.entry "
  bind .euserui$i.home.entry <Return> " focus .euserui$i.shell.entry "
  bind .euserui$i.shell.entry <Return> " focus .euserui$i.name.entry "
  focus .euserui$i.name.entry
}

####################
# Add user

##########
# Finishes an add user operation. If $t is 1, the new user is created.

proc endadduser {i t} {
  global passwdlist grouplist
  global name password uid group gecos home shell
  global editingpass addingsgroups numadds
  
  if {$editingpass($i) == 1} {
    destroy .editpwd$i
  }
  if {$addingsgroups($i) == 1} {
    destroy .addsg$i
  }

  set name($i) [string trim $name($i)]
  set password($i) [string trim $password($i)]
  set uid($i) [string trim $uid($i)]
  set group($i) [string trim $group($i)]
  set gecos($i) [string trim $gecos($i)]
  set home($i) [string trim $home($i)]
  set shell($i) [string trim $shell($i)]
  
  if {$t == 1} {
    set x [verifyuserent $i 1 1]
    if {$x == 0} {
      return
    }
    
    lappend passwdlist [split "$name($i):$password($i):$uid($i):$group($i):$gecos($i):$home($i):$shell($i):0" :]

    set sg [listfrombox .euserui$i.lframe.list]
    foreach x $sg {
      foreach y $grouplist {
	if {[lindex $y 0] == $x} {
	  if {[lsearch [lindex $y 3] $name($i)] == -1} {
	    set y [lreplace $y 3 3 \
		    [lsort [linsert [lindex $y 3] 0 $name($i)]]]
	    grouplistreplace [lindex $y 4] [lindex $y 0] $y
	  }
	}
      }
    }

    regenuserlist
    regengrouplist
    syncfiles

    makehomedir $i
  }
  
  unset name($i) password($i) uid($i) group($i) gecos($i)
  unset home($i) shell($i)
  unset editingpass($i)
  
  destroy .euserui$i
  incr numadds -1
}

##########
# When a new username is entered, sets group($i) and home($i) to reasonable
# defaults.

proc addnameproc {i} {
  global defhomepath
  global name password uid group gecos home shell
  focus .euserui$i.password.entry
  
  if {$name($i) != ""} {
    set group($i) $name($i)
    set home($i) $defhomepath/$name($i)
  }
}

##########
# Begin adding a user.

proc adduser {} {
  global passwdlist grouplist tmpnum useruidbegin defhomepath shellslist
  global name password uid group gecos home shell
  global editingpass numadds
  
  set i [incr tmpnum]
  incr numadds

  edituserui $i
  bind .euserui$i.name.entry <Return> "addnameproc $i"

  foreach x $grouplist {
    if {[lindex $x 0] == "users"} {
      .euserui$i.lframe.list insert end "users"
    }
  }
  set name($i) ""
  set password($i) ""
  set uid($i) [getuniqueuid $useruidbegin]
  set group($i) ""
  set gecos($i) ""
  set home($i) ""
  set shell($i) [lindex $shellslist 0]
  
  frame .euserui$i.buttons
  pack .euserui$i.buttons -side top -expand 0 -fill x -padx 4 -pady 4
  button .euserui$i.add -width 7 -text "Add" -command "endadduser $i 1"
  
  button .euserui$i.cancel -width 7 -text "Cancel" -command "endadduser $i 0"
  pack .euserui$i.add .euserui$i.cancel -side left -expand 1 \
    -in .euserui$i.buttons

  wm title .euserui$i "Add user"
}

####################
# Reactivate user

##########
# Deactivate the users selected in the primary user list.

proc deactivateuser {} {
  global passwdlist tmpnum tmpa defhomepath

  set i [incr tmpnum]

  # Lock the required user entries.

  set l {}
  set el {}
  set s [.user.users curselection]
  foreach x $s {
    set u [lindex $passwdlist $x]
    if {[lindex $u 7] != 0} {
      lappend el $u
    } else {
      set u [lreplace $u 7 7 $i]
      set passwdlist [lreplace $passwdlist $x $x $u]
      lappend l $u
    }
  }
  if {[llength $el] > 0} {
    set str "I cannot deactivate the following user(s)\nuntil you finish editing them:\n"
    foreach x $el {
      set str "$str     [lindex $x 0]\n"
    }
    rhs_error_dialog $str
  }

  foreach x $l {
    set y 0

    # Check that the user is not already deactivated.

    if {[string index [lindex $x 1] 0] == "*"} {
      rhs_error_dialog "[lindex $x 0] is already deactivated."
      continue
    }

    # Lock the password.

    set x [lreplace $x 1 1 "*[lindex $x 1]"]

    # Update the password list.

    set x [lreplace $x 7 7 0]
    passwdlistreplace $i [lindex $x 0] $x

    # Check to see if anything needs to be done with the user's home directory.

    set r 2
    if {[lindex $x 5] != "/"} {
      if {[file isdirectory [lindex $x 5]] == 1} {
	set r [rhs_dialog .deactuser$i "Question" \
	       "Should I compress [lindex $x 5],
delete it, or ignore it?" \
		 question 0 "Compress" "Delete" "Ignore"]
      }
    }

    if {$r == 0} {

      # Compress the home directory.

      toplevel .deactuser$i
      wm withdraw .deactuser$i
      grab .deactuser$i
      listbox .deactuser$i.list -geometry 60x5
      pack .deactuser$i.list -expand 1 -fill both -side top
      set tmpa($i) 0
      button .deactuser$i.button -text "OK" -command "set tmpa($i) 1" \
	-state disabled
      pack .deactuser$i.button -side top -expand 1 -padx 4 -pady 4
      wm title .deactuser$i "Progress report"
      center_dialog .deactuser$i
      tkwait visibility .deactuser$i
      .deactuser$i.list insert end \
 	"Packing [lindex $x 5] into $defhomepath/[lindex $x 0].tar..."
      update
      if {[catch {exec /bin/tar -cPf $defhomepath/[lindex $x 0].tar [lindex $x 5]}] \
	!= 0} {
	  .deactuser$i.list insert end \
	    "An error occurred while packing."
      } else {
	.deactuser$i.list insert end \
	  "Compressing $defhomepath/[lindex $x 0].tar..."
	update
	if {[catch {exec /bin/gzip -9 $defhomepath/[lindex $x 0].tar}] != 0} {
	  .deactuser$i.list insert end \
	    "An error occurred while compressing."
	} else {
	  .deactuser$i.list insert end \
	    "Setting permissions on $defhomepath/[lindex $x 0].tar.gz..."
	  update
	  if {[catch {exec chmod 600 $defhomepath/[lindex $x 0].tar.gz}]
	    != 0} {
	      .deactuser$i.list insert end \
		"An error occurred while setting permissions."
	    } else {
	      .deactuser$i.list insert end \
		"Deleting [lindex $x 5]..."
	      update
	      if {[catch {exec /bin/rm -rf [lindex $x 5]}] != 0} {
		.deactuser$i.list insert end \
		  "An error occurred while deleting."
	      } else {
		.deactuser$i.list insert end "Done."
	      }
	    }
	}
      }
      .deactuser$i.button configure -state normal
      set tmpa($i) 0
      tkwait variable tmpa($i)
      unset tmpa($i)
      destroy .deactuser$i
    } elseif {$r == 1} {

      # Delete the home directory.

      toplevel .deactuser$i
      wm withdraw .deactuser$i
      grab .deactuser$i
      listbox .deactuser$i.list -geometry 60x2
      pack .deactuser$i.list -expand 1 -fill both
      set tmpa($i) 0
      button .deactuser$i.button -text "OK" -command "set tmpa($i) 1" \
	-state disabled
      pack .deactuser$i.button -side top -expand 1 -padx 4 -pady 4
      wm title .deactuser$i "Progress report"
      center_dialog .deactuser$i
      tkwait visibility .deactuser$i.list
      .deactuser$i.list insert end \
	"Deleting [lindex $x 5]..."
      update
      if {[catch {exec /bin/rm -rf [lindex $x 5]}] != 0} {
	.deactuser$i.list insert end \
	  "An error occurred while deleting."
      } else {
	.deactuser$i.list insert end "Done."
      }
      .deactuser$i.button configure -state normal
      set tmpa($i) 0
      tkwait variable tmpa($i)
      unset tmpa($i)
      destroy .deactuser$i
    }
  }

  regenuserlist
  syncfiles
}

####################
# Reactivate user

##########
# Reactivate the selected users.

proc reactivateuser {} {
  global passwdlist defhomepath tmpnum tmpa editingpass password
  global uid home group

  set i [incr tmpnum]

  # Lock the selected users

  set l {}
  set el {}
  set s [.user.users curselection]
  foreach x $s {
    set u [lindex $passwdlist $x]
    if {[lindex $u 7] != 0} {
      lappend el $u
    } else {
      set u [lreplace $u 7 7 $i]
      set passwdlist [lreplace $passwdlist $x $x $u]
      lappend l $u
    }
  }
  if {[llength $el] > 0} {
    set str "I cannot reactivate the following user(s)\nuntil you finish editing them:\n"
    foreach x $el {
      set str "$str     [lindex $x 0]\n"
    }
    rhs_error_dialog $str
  }

  foreach x $l {
    set y 0

    # Check that the user is deactivated.

    if {[string index [lindex $x 1] 0] != "*"} {
      rhs_error_dialog "[lindex $x 0] is not deactivated."
      continue
    }

    # Generate and verify the new password.

    set newp [string range [lindex $x 1] 1 end]
    for {set j 0} {$j < [string length $newp]} {incr j} {
      if {[string match "\[./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\]" [string index $newp $j]] == 0} {
	set y 1
      }
    }

    if {$y == 1} {
      set r [rhs_dialog .reactuser$i "Error" \
	"The password for \"[lindex $x 0]\"
could not be restored." \
	warning 0 "Enter a new password" "Leave it blank" "Abort"]
      if {$r == 0} {

	# Get a new password from the user.

	set password($i) ""
	set editingpass($i) 0
	editpasswd $i
	tkwait window .editpwd$i
	set newp $password($i)
	unset password($i)
	unset editingpass($i)
	set y 0
      } elseif {$r == 1} {
	set newp ""
	set y 0
      }
    }

    if {$y == 0} {

      # Update the password file

      set x [lreplace $x 1 1 $newp]
      set x [lreplace $x 7 7 0]
      passwdlistreplace $i [lindex $x 0] $x
    }

    # Check for any compressed home dirs lying around.

    if {[file exists [lindex $x 5]] == 0} {
      if {[file exists $defhomepath/[lindex $x 0].tar.gz] == 1} {

	# Uncompress the home directory.

	toplevel .reactuser$i
	wm withdraw .reactuser$i
	grab .reactuser$i
	listbox .reactuser$i.list -geometry 60x3
	pack .reactuser$i.list -expand 1 -fill both
	set tmpa($i) 0
	button .reactuser$i.button -text "OK" -command "set tmpa($i) 1" \
	  -state disabled
	pack .reactuser$i.button -side top -expand 1 -padx 4 -pady 4
	wm title .reactuser$i "Progress report"
	center_dialog .reactuser$i
	tkwait visibility .reactuser$i.list
	set errstat 0
	.reactuser$i.list insert end \
	  "Unpacking $defhomepath/[lindex $x 0].tar.gz..."
	update
	if {[catch {exec /bin/tar -xzpPf $defhomepath/[lindex $x 0].tar.gz}] \
	  != 0} {
	    .reactuser$i.list insert end \
	      "An error occurred while unpacking."
	    set errstat 1
	  } else {
	    .reactuser$i.list insert end \
	      "Deleting $defhomepath/[lindex $x 0].tar.gz..."
	    update
	    if {[catch {exec /bin/rm -f $defhomepath/[lindex $x 0].tar.gz}] \
	      != 0} {
		.reactuser$i.list insert end \
		  "An error occurred while deleting."
		set errstat 1
	      }
	  }
	if {$errstat == 0} {
	  .reactuser$i.list insert end "Done."
	}
	.reactuser$i.button configure -state normal
	set tmpa($i) 0
	tkwait variable tmpa($i)
	unset tmpa($i)
	destroy .reactuser$i
      } else {

	# No home directory exists, so ask if one should be made.

	set uid($i) [lindex $x 2]
	set group($i) [lindex $x 3]
	set home($i) [lindex $x 5]
	makehomedir $i
	unset uid($i)
	unset group($i)
	unset home($i)
      }
    }
  }

  regenuserlist
  syncfiles
}

####################
# Delete a user

##########
# Delete the selected users

proc removeuser {} {
  global passwdlist defhomepath grouplist tmpnum tmpa confirmremove maildir
  global orphaneduser orphanedgroup
  
  set i [incr tmpnum]

  set delusers [.user.users curselection]
  if {[llength $delusers] == 0} {
    return
  }
  
  foreach x $delusers {
    lappend t [lindex $passwdlist $x]
  }
  
  # Lock the selected users.

  set el {}
  set l {}
  foreach x $t {
    if {[lindex $x 7] != 0} {
      lappend el $x
    } else {
      lappend l $x
      set x [lreplace $x 7 7 $i]
      passwdlistreplace 0 [lindex $x 0] $x
    }
  }

  if {[llength $el] > 0} {
    set str "I cannot remove the following user(s)\nuntil you finish editing them:\n"
    foreach x $el {
      set str "$str     [lindex $x 0]\n"
    }
    rhs_error_dialog $str
  }

  foreach x $l {

    # Ask for confirmation.

    if {$confirmremove == 1} {
      set r [rhs_dialog .deluser$i "Question" \
	     "Are you certain you want to\
remove [lindex $x 0]?" \
	       question 0 "Yes" "No"]
      if {$r == 1} {
	passwdlistreplace $i [lindex $x 0] $x
	continue
      }
    }

    # Check if the home directory should be deleted.

    if {[file exists [lindex $x 5]] == 1} {
      if {[lindex $x 5] != "/"} {
	set r [rhs_dialog .deluser$i "Question" \
	       "Do you want to delete [lindex $x 5]\nand $maildir/[lindex $x 0]?" \
		 question 0 "Yes" "No" "Cancel"]
	if {$r == 0} {

	  # Delete the home directory.

	  toplevel .deluser$i
	  wm withdraw .deluser$i
	  listbox .deluser$i.list -geometry 60x3
	  pack .deluser$i.list -expand 1 -fill both
	  set tmpa($i) 0
	  button .deluser$i.button -text "OK" -command "set tmpa($i) 1" \
	    -state disabled
	  pack .deluser$i.button -side top -expand 1 -padx 4 -pady 4
	  wm title .deluser$i "Progress report"
	  center_dialog .deluser$i
	  tkwait visibility .deluser$i.list
	  set errstat 0
	  .deluser$i.list insert end \
	    "Deleting [lindex $x 5]..."
	  update
	  if {[catch {exec /bin/rm -rf [lindex $x 5]}] != 0} {
	    .deluser$i.list insert end \
	      "An error occurred while deleting [lindex $x 5]."
	    set errstat 1
	  } elseif {[file exists $maildir/[lindex $x 0]] == 1} {
	    .deluser$i.list insert end \
	      "Deleting $maildir/[lindex $x 0]..."
	    if {[catch {exec /bin/rm -f $maildir/[lindex $x 0]}] != 0} {
	      .deluser$i.list insert end \
		"An error occurred while deleting $maildir/[lindex $x 0]."
	      set errstat 1
	    }
	  }
	  if {$errstat == 0} {
	    .deluser$i.list insert end "Done."
	  }
	  .deluser$i.button configure -state normal
	  set tmpa($i) 0
	  tkwait variable tmpa($i)
	  unset tmpa($i)
	  destroy .deluser$i
	} elseif {$r == 2} {
	  passwdlistreplace $i [lindex $x 0] $x
	  continue
	}
      }
    }

    # Check if an old, compressed home dir exists and should be deleted.

    if {[file exists $defhomepath/[lindex $x 0].tar.gz] == 1} {
      set r [rhs_dialog .deluser$i "Question" \
	     "Do you want to delete\n$defhomepath/[lindex $x 0].tar.gz?" \
	       question 0 "Yes" "No" "Cancel"]
      if {$r == 0} {

	# Delete the old home dir.

	if {[catch {exec /bin/rm -f $defhomepath/[lindex $x 0].tar.gz}] != 0} {
	  rhs_error_dialog "An error occured while deleting $defhomepath/[lindex $x 0].tar.gz."
	}
      } elseif {$r == 2} {
	passwdlistreplace $i [lindex $x 0] $x
	continue
      }
    }

    # Find the index of the user in the user list.

    set j 0
    set k -1
    foreach y $passwdlist {
      if {[lindex $y 0] == [lindex $x 0]} {
	if {[lindex $y 7] == $i} {
	  set k $j
	}
      }
      incr j
    }

    # Delete the user.

    if {$k != -1} {
      set passwdlist [lreplace $passwdlist $k $k]
    }

    # Check to see if there is a user private group that should be deleted.

    set remgrp -1
    set ng {}
    foreach g $grouplist {
      set su [lindex $g 3]
      set j [lsearch -exact $su [lindex $x 0]]
      if {$j != -1} {
	set su [lreplace $su $j $j]
      }
      if {$su == ""} {
	set su {}
      }
      set g [lreplace $g 3 3 $su]
      set t 1
      if {[lindex $g 0] == [lindex $x 0]} {
	if {[llength [lindex $g 3]] == 0} {
	  set t 0
	  foreach u $passwdlist {
	    if {[lindex $g 2] == [lindex $u 3]} {
	      set t 1
	    }
	  }
	}
      }
      if {$t == 1} {
	lappend ng $g
      } else {
	set remgrp [lindex $g 2]
      }
    }
    set grouplist $ng
    regenuserlist
    regengrouplist
    syncfiles

    # Check to see if files orphaned by the deletion should be cleaned up.

    set r [rhs_dialog .deluser$i "Question" \
	   "Do you want to search the filesystem for\nfiles that have been orphaned by removing\n[lindex $x 0]? (This will be done in the background.)\nThe files will be owner/group $orphaneduser/$orphanedgroup." \
	     question 1 "Yes" "No"]
    if {$r == 0} {
      cleanorphans [lindex $x 2] $remgrp $orphaneduser $orphanedgroup /
    }
  }
}

####################
# Edit user

##########
# Finishes an end user operation. If $t = 1, the user is updated. $u contains
# the old password list entry of the user.

proc endedituser {i t u} {
  global passwdlist grouplist
  global name password uid group gecos home shell
  global editingpass addingsgroups
  global orphaneduser orphanedgroup
  
  if {$editingpass($i) == 1} {
    destroy .editpwd$i
  }
  if {$addingsgroups($i) == 1} {
    destroy .addsg$i
  }

  set name($i) [string trim $name($i)]
  set password($i) [string trim $password($i)]
  set uid($i) [string trim $uid($i)]
  set group($i) [string trim $group($i)]
  set gecos($i) [string trim $gecos($i)]
  set home($i) [string trim $home($i)]
  set shell($i) [string trim $shell($i)]
  
  if {$t == 1} {

    # Perform user verification.

    if {$name($i) == [lindex $u 0]} {
      set uniqname 0
    } else {
      set uniqname 1
    }
    if {$uid($i) == [lindex $u 2]} {
      set uniquid 0
    } else {
      set uniquid 1
    }
    set x [verifyuserent $i $uniqname $uniquid]
    if {$x == 0} {
      return
    }

    # Handle orphaned files.

    if {$uid($i) != [lindex $u 2] && $group($i) != [lindex $u 3]} {
      set r [rhs_dialog .edituserend$i "Question" \
	     "You have changed [lindex $u 0]'s UID and GID.\nThis means that all files that were owned by\n[lindex $u 0] must be updated to reflect the new\nUID and GID if [lindex $u 0] is to use them. You\ncan process only the files in [lindex $u 0]'s home\ndirectory, or you can process all the files on the\nsystem. Which would you like to do?" \
	       question 0 "Home directory" "Entire system" "None" "Abort"]
      if {$r == 0} {
	cleanorphans [lindex $u 2] [lindex $u 3] $uid($i) $group($i) \
	  [lindex $u 5]
      } elseif {$r == 1} {
	cleanorphans [lindex $u 2] [lindex $u 3] $uid($i) $group($i) /
      } elseif {$r == 3} {
	return
      }
    } elseif {$uid($i) != [lindex $u 2]} {
      set r [rhs_dialog .edituserend$i "Question" \
	     "You have changed [lindex $u 0]'s UID. This means\nthat all files that were owned by [lindex $u 0] must\nbe updated to reflect the new UID if [lindex $u 0]\nis to use them. You can process only the files in\n[lindex $u 0]'s home directory, or you can process\nall the files on the system.\nWhich would you like to do?" \
	       question 0 "Home directory" "Entire system" "None" "Abort"]
      if {$r == 0} {
	cleanorphans [lindex $u 2] -1 $uid($i) $orphanedgroup [lindex $u 5]
      } elseif {$r == 1} {
	cleanorphans [lindex $u 2] -1 $uid($i) $orphanedgroup /
      } elseif {$r == 3} {
	return
      }
    } elseif {$group($i) != [lindex $u 3]} {
      set r [rhs_dialog .edituserend$i "Question" \
	     "You have changed [lindex $u 0]'s GID. This means\nthat all files that were owned by [lindex $u 0] must\nbe updated to reflect the new GID if [lindex $u 0]\nis to use them. You can process only the files in\n[lindex $u 0]'s home directory, or you can process\nall the files on the system.\nWhich would you like to do?" \
        question 0 "Home directory" "Entire system" "None" "Abort"]
      if {$r == 0} {
	cleanorphans -1 [lindex $u 3] $orphaneduser $group($i) [lindex $u 5]
      } elseif {$r == 1} {
	cleanorphans -1 [lindex $u 3] $orphaneduser $group($i) /
      } elseif {$r == 3} {
	return
      }
    }

    # Handle changed home directory locations.

    if {$home($i) != [lindex $u 5]} {
      if {[file exists $home($i)] == 0 && [file exists [lindex $u 5]] == 1} {
	if {[lindex $u 5] != "/"} {
	  set r [rhs_dialog .edituserend$i "Question" \
		 "You have changed [lindex $u 0]'s home\ndirectory. Would you like to move\nthe old directory to the new location?" \
		   question 0 "Yes" "No"]
	  if {$r == 0} {
	    if {[catch {exec mv -f [lindex $u 5] $home($i) >&/dev/null}]
	      != 0} {
		if {[catch {exec cp -af [lindex $u 5] $home($i) >&/dev/null}]
		  != 0} {
		    rhs_error_dialog "An error occurred while moving the directory."
		  } else {
		    catch {exec rm -rf [lindex $u 5] >&/dev/null}
		  }
	      }
	  }
	}
      }
    }

    # Update the user list.

    passwdlistreplace $i [lindex $u 0] [split "$name($i):$password($i):$uid($i):$group($i):$gecos($i):$home($i):$shell($i):0" :]

    # If the username has changed, check for secondary group listings that
    # must be moved.

    if {$name($i) != [lindex $u 0]} {
      foreach x $grouplist {
	set sg [lindex $x 3]
	set y [lsearch -exact $sg [lindex $u 0]]
	if {$y != -1} {
	  set sg [lreplace $sg $y $y $name($i)]
	  set x [lreplace $x 3 3 $sg]
	  grouplistreplace [lindex $x 4] [lindex $x 0] $x
	}
      }
    }

    # Update secondary groups.

    set sg [listfrombox .euserui$i.lframe.list]
    set ng {}
    foreach x $sg {
      foreach y $grouplist {
	if {[lindex $y 0] == $x} {
	  if {[lsearch -exact [lindex $y 3] $name($i)] == -1} {
	    set sgt [lindex $y 3]
	    lappend sgt $name($i)
	    set sgt [lsort $sgt]
	    set y [lreplace $y 3 3 $sgt]
	    grouplistreplace [lindex $y 4] [lindex $y 0] $y
	  }
	}
      }
    }

    regenuserlist
    regengrouplist
    syncfiles
    makehomedir $i
  } else {
    set u [lreplace $u 7 7 0]
    passwdlistreplace $i [lindex $u 0] $u
  }
  
  unset name($i) password($i) uid($i) group($i) gecos($i)
  unset home($i) shell($i)
  unset editingpass($i)
  
  destroy .euserui$i
}

##########
# Begin an edit user operation on a specific user. $u contains the original
# password list entry of the user.

proc edituser {i u} {
  global passwdlist grouplist tmpnum shellslist
  global name password uid group gecos home shell
  
  edituserui $i
  
  set name($i) [lindex $u 0]
  set password($i) [lindex $u 1]
  set uid($i) [lindex $u 2]
  set gecos($i) [lindex $u 4]
  set home($i) [lindex $u 5]
  set shell($i) [lindex $u 6]
  foreach x $grouplist {
    if {[lindex $x 2] == [lindex $u 3]} {
      set group($i) [lindex $x 0]
    }
    if {[lsearch [lindex $x 3] [lindex $u 0]] != -1} {
      .euserui$i.lframe.list insert end [lindex $x 0]
    }
  }
  
  frame .euserui$i.buttons
  pack .euserui$i.buttons -side top -expand 1 -fill x -padx 4 -pady 4
  button .euserui$i.ok -width 7 -text OK -command "endedituser $i 1 {$u}"
  
  button .euserui$i.cancel -width 7 -text Cancel \
    -command "endedituser $i 0 {$u}"
  pack .euserui$i.ok .euserui$i.cancel -side left -expand 1 \
    -in .euserui$i.buttons

  wm title .euserui$i "Edit user [lindex $u 0]"
}

##########
# Edit the selected users.

proc beginedituser {l} {
  global passwdlist tmpnum

  set sl {}
  set fl {}
  set el {}
  foreach x $l {
    set u [lindex $passwdlist $x]
    if {[lindex $u 7] == 0} {
      set i [incr tmpnum]
      set u [lreplace $u 7 7 $i]
      passwdlistreplace 0 [lindex $u 0] $u
      lappend sl $u
    } elseif {[winfo exists .euserui[lindex $u 7]] == 1} {
      lappend fl $u
    } else {
      lappend el $u
    }
  }

  foreach x $fl {
    catch {wm deiconify .euserui[lindex $x 7]}
    catch {raise .euserui[lindex $x 7]}
  }

  foreach x $sl {
    edituser [lindex $x 7] $x
  }
}

####################
# The primary user frame.

##########
# Set up the user frame.

proc setupuserframe {} {
  frame .user
  
  frame .user.lframe
  pack .user.lframe -side top -fill both -expand 1 -padx 4 -pady 4
  
  label .user.lframe.label -font fixed -anchor w
  pack .user.lframe.label -side top -fill x -expand 0 -padx 2
  
  scrollbar .user.userscroll -command ".user.users yview"
  pack .user.userscroll -side right -fill y -in .user.lframe
  listbox .user.users -relief raised -borderwidth 2 \
    -yscrollcommand ".user.userscroll set" -font fixed \
    -geometry 80x10 -setgrid 1
  pack .user.users -side left -expand 1 -fill both -in .user.lframe
  
  frame .user.bframe
  pack .user.bframe -side top -fill x
  
  button .user.add -width 10 -text "Add" -command { adduser }
  button .user.deactivate -width 10 -text "Deactivate" \
    -command { deactivateuser }
  button .user.reactivate -width 10 -text "Reactivate" \
    -command { reactivateuser }
  button .user.remove -width 10 -text "Remove" -command { removeuser }
  button .user.edit -text "Edit" -width 10 \
    -command { beginedituser [.user.users curselection] }
  pack .user.add .user.deactivate .user.reactivate .user.remove .user.edit \
    -side left -expand 1 -in .user.bframe -padx 4 -pady 4
}

########################################
# Group section

####################
# Functions common to adding and editing groups.

##########
# Verify that a given set of group parameters make sense. Returns 1 if
# everything is OK, 0 is something went wrong.
#   i             - The index of the group data in the name, gid, etc. arrays.
#   checkuniqname - 1 if the groupname unicity should be checked.
#   checkuniqgid  - 1 if the GID unicity should be checked.

proc verifygroupent {i checkuniqname checkuniqgid} {
  global sysgidbegin
  global name gid
  global grouplist

  # If gen is set to 1, a new GID must be generated.
  
  set gen 0

  # Check for a blank groupname.

  if {[string length $name($i)] == 0} {
    rhs_error_dialog "You have not specified a group name." \
    return 0
  }

  # Verify that the GID is valid.

  set x 0
  for { set j 0 } { $j < [string length $gid($i)] } { incr j } {
    if {0==[string match "\[0123456789\]" \
	    [string index $gid($i) $j]]} {
	      set x 1
	    }
  }
  if {$x == 0} {
    if {[string length $gid($i)] == 0} {
      set x 1
    }
  }
  if {$x == 0} {
    if {$gid($i) > 32767} {
      set x 1
    }
  }
  if {$x == 1} {
    set x [rhs_dialog .veruser$i "Warning" \
	     "\"$gid($i)\" is not a valid group ID." \
	     warning 0 "Generate valid ID" "Abort"]
    if {$x == 1} {
      return 0
    }
    set gen 1
  }

  if {$checkuniqname == 1 || $checkuniqgid == 1} {
    foreach x $grouplist {

      # Check groupname unicity.

      if {[lindex $x 0] == $name($i)} {
	if {$checkuniqname == 1} {
	  rhs_error_dialog "There is already a group named \"$name($i)\"."
	  return 0
	}
      }

      # Check GID unicity.

      if {[lindex $x 2] == $gid($i)} {
	if {$checkuniqgid == 1 && $gen != 1} {
	  set r [rhs_dialog .veruser$i "Warning" \
		 "There is already a group with ID $gid($i)." \
		   warning 1 "Continue" "Generate valid ID" "Abort"]
	  if {$r == 1} {
	    set gen 1
	  } elseif {$r == 2} {
	    return 0
	  }
	}
      }
    }
  }

  if {$gen == 1} {

    # Generate a new GID.

    set gid($i) [getuniquegid $sysgidbegin]
    rhs_dialog .veruser$i "Information" \
      "The group ID has been set to $gid($i)." \
      info 0 "OK"
  }
  return 1
}

##########
# Add the selected users from .addgmem$i's listbox to .egroupui$i's list
# of group members.

proc addgmem {i} {
  global passwdlist
  
  set l [.addgmem$i.lframe.list curselection]
  
  foreach x $l {
    .egroupui$i.lframe.list insert end \
      [.addgmem$i.lframe.list get $x]
  }
  
  while {[llength $l] > 0} {
    .addgmem$i.lframe.list delete [lindex $l 0] [lindex $l 0]
    set l [.addgmem$i.lframe.list curselection]
  }
}

##########
# Open a window to allow selection of group members to be added to the
# listbox in .egroupui$i.

proc addgroupmember {i} {
  global passwdlist addinggmems name
  
  if {$addinggmems($i) == 1} {
    return
  }
  set addinggmems($i) 1

  toplevel .addgmem$i
  wm withdraw .addgmem$i
  
  frame .addgmem$i.lframe
  pack .addgmem$i.lframe -side top
  
  listbox .addgmem$i.lframe.list -relief raised -borderwidth 2 \
    -yscrollcommand ".addgmem$i.lframe.scroll set" -font fixed \
    -geometry 10x5
  pack .addgmem$i.lframe.list -side left -expand 1 -fill both
  scrollbar .addgmem$i.lframe.scroll\
    -command ".addgmem$i.lframe.list yview"
  pack .addgmem$i.lframe.scroll -side left -fill y
  
  frame .addgmem$i.bframe
  pack .addgmem$i.bframe -side top -fill x
  
  button .addgmem$i.add -text "Add" -command "addgmem $i"
  button .addgmem$i.cancel -text "Done" \
    -command "set addinggmems($i) 0 ; destroy .addgmem$i"
  pack .addgmem$i.add .addgmem$i.cancel -side left \
    -expand 1 -in .addgmem$i.bframe -padx 4 -pady 4
  
  foreach x $passwdlist {
    set y 0
    for {set j 0} {$j < [.egroupui$i.lframe.list size]} {incr j} {
      set t [.egroupui$i.lframe.list get $j]
      set s [string trim [string range $t 0 9]]
      if {$s == [lindex $x 0]} {
	set y 1
      }
    }
    if {$y == 0} {
      .addgmem$i.lframe.list insert end [lindex $x 0]
    }
  }

  if {$name($i) != ""} {
    wm title .addgmem$i "Add member for $name($i)"
  } else {
    wm title .addgmem$i "Add member for <unnamed>"
  }

  update idletasks
  set x [expr - [winfo reqwidth .addgmem$i] + \
	 [winfo rootx .egroupui$i.lframe] + [winfo width .egroupui$i.lframe]]
  set y [expr [winfo height .egroupui$i.lframe]/2 - \
	 [winfo reqheight .addgmem$i]/2 + [winfo rooty .egroupui$i.lframe] \
	  + [winfo vrooty .egroupui$i.lframe]]
  wm group .addgmem$i 
  wm transient .addgmem$i .egroupui$i.lframe
  wm geometry .addgmem$i +$x+$y
  wm deiconify .addgmem$i
}

##########
# Remove a group member from .egroupui$i's list.

proc rmgroupmember {i} {
  global passwdlist
  
  set l [.egroupui$i.lframe.list curselection]
  
  foreach x $l {
    lappend d [.egroupui$i.lframe.list get $x]
  }

  foreach y $d {
    for {set x 0} {$x < [.egroupui$i.lframe.list size]} {incr x} {
      if {$y == [.egroupui$i.lframe.list get $x]} {
	.egroupui$i.lframe.list delete $x $x
	break
      }
    }
  }
}  

##########
# Generate the basic user interface for adding or editing users.

proc editgroupui {i} {
  global grouplist tmpnum sysgidbegin
  global name gid
  global addinggmems

  set addinggmems($i) 0
  
  toplevel .egroupui$i
  
  frame .egroupui$i.ents
  pack .egroupui$i.ents -side top -expand 1 -fill both -padx 4 -pady 4
  
  frame .egroupui$i.name
  pack .egroupui$i.name -side top -expand 1 -fill x -padx 4 \
    -in .egroupui$i.ents
  label .egroupui$i.name.label -text "Name:"
  label .egroupui$i.name.bit -bitmap cleardd
  entry .egroupui$i.name.entry -width 30 -relief sunken -bd 2 \
    -textvariable name($i)
  pack .egroupui$i.name.label -side left -anchor w
  pack .egroupui$i.name.entry .egroupui$i.name.bit -side right -anchor e
  
  frame .egroupui$i.gid
  pack .egroupui$i.gid -side top -expand 1 -fill x -padx 4 \
    -in .egroupui$i.ents
  label .egroupui$i.gid.label -text "GID:"
  label .egroupui$i.gid.bit -bitmap cleardd
  entry .egroupui$i.gid.entry -width 30 -relief sunken -bd 2 \
    -textvariable gid($i)
  pack .egroupui$i.gid.label -side left -anchor w
  pack .egroupui$i.gid.entry .egroupui$i.gid.bit -side right -anchor e
  
  frame .egroupui$i.lframe
  pack .egroupui$i.lframe -side top -fill both -expand 1
  
  frame .egroupui$i.lframe.leftpad
  pack .egroupui$i.lframe.leftpad -side left -fill y -padx 2
  frame .egroupui$i.lframe.rightpad
  pack .egroupui$i.lframe.rightpad -side right -fill y -padx 2
  
  label .egroupui$i.lframe.label -text "Members"
  pack .egroupui$i.lframe.label -side top -fill x -expand 1
  
  frame .egroupui$i.lframe.bframe
  pack .egroupui$i.lframe.bframe -side left -fill y -padx 4
  
  button .egroupui$i.lframe.bframe.add -text "Add" \
    -command "addgroupmember $i"
  button .egroupui$i.lframe.bframe.remove -text "Remove" \
    -command "rmgroupmember $i"
  pack .egroupui$i.lframe.bframe.add .egroupui$i.lframe.bframe.remove \
    -side top -expand 1 -fill x
  
  listbox .egroupui$i.lframe.list -relief raised -borderwidth 2 \
    -yscrollcommand ".egroupui$i.lframe.scroll set" -font fixed \
    -geometry 10x5
  pack .egroupui$i.lframe.list -side left -expand 1 -fill both
  scrollbar .egroupui$i.lframe.scroll\
    -command ".egroupui$i.lframe.list yview"
  pack .egroupui$i.lframe.scroll -side left -fill y

  bind .egroupui$i.name.entry <Return> "focus .egroupui$i.gid.entry"
  bind .egroupui$i.gid.entry <Return> "focus .egroupui$i.name.entry"
  
  focus .egroupui$i.name.entry
}

####################
# Add group

##########
# Finish an add group operation. If $t = 1, the new group is created.

proc endaddgroup {i t} {
  global grouplist
  global name gid
  global addinggmems numadds
  
  if {$addinggmems($i) == 1} {
    destroy .addgmem$i
  }

  set name($i) [string trim $name($i)]
  set gid($i) [string trim $gid($i)]
  
  if {$t == 1} {
    set x [verifygroupent $i 1 1]
    if {$x == 0} {
      return
    }
    
    set l {}
    for {set j 0} {$j < [.egroupui$i.lframe.list size]} {incr j} {
      lappend l [.egroupui$i.lframe.list get $j]
    }
    set l [lsort $l]

    set g [split "$name($i)::$gid($i)" :]
    lappend g $l
    lappend g 0
    lappend grouplist $g
    regengrouplist
    syncfiles
  }
  
  unset name($i)
  unset gid($i)
  
  destroy .egroupui$i
  incr numadds -1
}

##########
# Begin an add group operation.

proc addgroup {} {
  global grouplist tmpnum sysgidbegin numadds
  global name gid
  
  set i [incr tmpnum]
  incr numadds

  editgroupui $i
  
  frame .egroupui$i.buttons
  pack .egroupui$i.buttons -side top -expand 1 -fill x -padx 4 -pady 4
  button .egroupui$i.ok -text Add -command "endaddgroup $i 1"
  
  button .egroupui$i.cancel -text Cancel -command "endaddgroup $i 0"
  pack .egroupui$i.ok .egroupui$i.cancel -side left -expand 1 \
    -in .egroupui$i.buttons
  
  set gid($i) [getuniquegid $sysgidbegin]

  wm title .egroupui$i "Add group"
}

####################
# Remove group

##########
# Remove the selected groups from the group list.

proc removegroup {} {
  global grouplist
  global orphaneduser orphanedgroup
  
  set delgroups [.group.groups curselection]
  if {[llength $delgroups] == 0} {
    return
  }

  set t {}
  set el {}
  foreach x $delgroups {
    set g [lindex $grouplist $x]
    if {[lindex $g 4] != 0} {
      lappend el $g
    } else {
      lappend t $g
    }
  }

  if {[llength $el] > 0} {
    set str "I cannot remove the following group(s)\nuntil you finish editing them:\n"
    foreach x $el {
      set str "$str     [lindex $x 0]\n"
    }
    rhs_error_dialog $str
  }
  
  foreach x $t {
    set y [lsearch $grouplist $x]
    set grouplist [lreplace $grouplist $y $y]

    regengrouplist
    syncfiles

    set r [rhs_dialog .remgrp "Question" \
	   "Do you want to search the filesystem\nfor files that have been orphaned by\nremoving [lindex $x 0]? (This will be done\nin the background.)" \
	     question 1 "Yes" "No"]
    if {$r == 0} {
      cleanorphans -1 [lindex $x 2] $orphaneduser $orphanedgroup /
    }
  }
}

####################
# Edit group

##########
# Finish an edit group operation. If $t = 1, the modifed group is saved.
# $g contains the original group list entry of the group.

proc endeditgroup {i t g} {
  global grouplist passwdlist
  global name gid
  global addinggmems
  global orphaneduser orphanedgroup

  if {$addinggmems($i) == 1} {
    destroy .addgmem$i
  }
  
  set name($i) [string trim $name($i)]
  set gid($i) [string trim $gid($i)]
  
  if {$t == 1} {

    # Verify the new group parameters.

    if {$name($i) != [lindex $g 0]} {
      set vern 1
    } else {
      set vern 0
    }
    if {$gid($i) != [lindex $g 2]} {
      set veri 1
    } else {
      set veri 0
    }
    set x [verifygroupent $i $vern $veri]
    if {$x == 0} {
      return
    }

    # Check for orphaned files, if necessary.
    
    if {$gid($i) != [lindex $g 2]} {
      set r [rhs_dialog .edituserend$i "Question" \
"You have changed [lindex $g 0]'s GID. This\nmeans that all files that were owned by\n[lindex $g 0] should be updated to reflect the\nnew GID. Do you want to update these files?\n(This will be done in the background.)" \
        question 0 "Yes" "No" "Abort"]
      if {$r == 0} {
	cleanorphans -1 [lindex $g 2] $orphaneduser $gid($i) /
      } elseif {$r == 2} {
	return
      }
    }

    # Save the edited group.

    set l {}
    for {set j 0} {$j < [.egroupui$i.lframe.list size]} {incr j} {
      lappend l [.egroupui$i.lframe.list get $j]
    }
    set ng [split "$name($i)::$gid($i)" :]
    lappend ng $l
    lappend ng 0
    grouplistreplace $i [lindex $g 0] $ng

    # Update GID references in the password list, if necessary.

    if {$gid($i) != [lindex $g 2]} {
      foreach u $passwdlist {
	if {[lindex $u 3] == [lindex $g 2]} {
	  set u [lreplace $u 3 3 $gid($i)]
	  passwdlistreplace [lindex $u 7] [lindex $u 0] $u
	}
      }
    }

    regenuserlist
    regengrouplist
    syncfiles
  } else {
    set g [lreplace $g 4 4 0]
    grouplistreplace $i [lindex $g 0] $g
  }
  
  unset name($i)
  unset gid($i)
  unset addinggmems($i)
  
  destroy .egroupui$i
}

##########
# Edit a specific group. $g contains the group's group list entry.

proc editgroup {i g} {
  global grouplist tmpnum sysgidbegin
  global name gid
  
  editgroupui $i

  set name($i) [lindex $g 0]
  set gid($i) [lindex $g 2]
  foreach x [lindex $g 3] {
    .egroupui$i.lframe.list insert end $x
  }
  
  frame .egroupui$i.buttons
  pack .egroupui$i.buttons -side top -expand 1 -fill x -padx 4 -pady 4
  button .egroupui$i.ok -text OK -command "endeditgroup $i 1 {$g}"
  
  button .egroupui$i.cancel -text Cancel -command "endeditgroup $i 0 {$g}"
  pack .egroupui$i.ok .egroupui$i.cancel -side left -expand 1 \
    -in .egroupui$i.buttons

  wm title .egroupui$i "Edit group [lindex $g 0]"
}

##########
# Edit the selected groups.

proc begineditgroup {} {
  global grouplist tmpnum
  
  set l [.group.groups curselection]
  set sl {}
  set fl {}
  set el {}
  foreach x $l {
    set g [lindex $grouplist $x]
    if {[lindex $g 4] == 0} {
      set i [incr tmpnum]
      set g [lreplace $g 4 4 $i]
      grouplistreplace 0 [lindex $g 0] $g
      lappend sl $g
    } elseif {[winfo exists .egroupui[lindex $g 4]] == 1} {
      lappend fl $g
    } else {
      lappend el $g
    }
  }

  foreach x $fl {
    catch {wm deiconify .egroupui[lindex $x 4]}
    catch {raise .egroupui[lindex $x 4]}
  }

  foreach x $sl {
    editgroup [lindex $x 4] $x
  }
}

####################
# The primary group frame.

##########
# Set up the group frame.

proc setupgroupframe {} {
  frame .group
  
  frame .group.lframe
  pack .group.lframe -side top -fill both -expand 1 -padx 4 -pady 4
  
  label .group.lframe.label -font fixed -anchor w \
    -text "Group      GID   Members"
  pack .group.lframe.label -side top -fill x -padx 2
  
  scrollbar .group.groupscroll -command ".group.groups yview"
  pack .group.groupscroll -side right -fill y -in .group.lframe
  listbox .group.groups -relief raised -borderwidth 2 \
    -yscrollcommand ".group.groupscroll set" -font fixed \
    -geometry 23x10 -setgrid 1
  pack .group.groups -side left -expand 1 -fill both -in .group.lframe
  
  frame .group.bframe
  pack .group.bframe -side top -fill x
  
  button .group.add -text "Add" -command addgroup -width 6
  button .group.remove -text "Remove" -command removegroup -width 7
  button .group.edit -text "Edit" -command begineditgroup -width 6
  pack .group.add .group.remove .group.edit -side left \
    -expand 1 -in .group.bframe -padx 4 -pady 4
}

########################################
# Edit paths

####################
# Finish editing the paths, and save the modified values.

proc endeditpaths {} { 
  global pathsup shellslist
  global shellfilename defhomepath skeldir maildir excludedirs

  set pathsup 0
  destroy .paths

  set shellfilename [string trim $shellfilename]
  set defhomepath [string trim $defhomepath]
  set skeldir [string trim $skeldir]
  set maildir [string trim $maildir]
  set excludedirs [string trim $excludedirs]

  set shellslist [getshells]
}

####################
# Edit the paths.

proc editpaths {} {
  global pathsup
  global shellfilename defhomepath skeldir maildir excludedirs

  if {$pathsup == 1} {
    return
  }
  set pathsup 1
  toplevel .paths
  wm withdraw .paths

  frame .paths.ents
  pack .paths.ents -side top -expand 1 -fill both -padx 4 -pady 4

  frame .paths.shell
  pack .paths.shell -side top -expand 1 -fill x -padx 4 \
    -in .paths.ents
  label .paths.shell.label -text "Shells file:"
  entry .paths.shell.entry -width 30 -relief sunken -bd 2 \
    -textvariable shellfilename
  pack .paths.shell.label -side left -anchor w
  pack .paths.shell.entry -side right -anchor e

  frame .paths.home
  pack .paths.home -side top -expand 1 -fill x -padx 4 \
    -in .paths.ents
  label .paths.home.label -text "Home directory:"
  entry .paths.home.entry -width 30 -relief sunken -bd 2 \
    -textvariable defhomepath
  pack .paths.home.label -side left -anchor w
  pack .paths.home.entry -side right -anchor e

  frame .paths.skel
  pack .paths.skel -side top -expand 1 -fill x -padx 4 \
    -in .paths.ents
  label .paths.skel.label -text "Skeleton directory:"
  entry .paths.skel.entry -width 30 -relief sunken -bd 2 \
    -textvariable skeldir
  pack .paths.skel.label -side left -anchor w
  pack .paths.skel.entry -side right -anchor e

  frame .paths.mail
  pack .paths.mail -side top -expand 1 -fill x -padx 4 \
    -in .paths.ents
  label .paths.mail.label -text "Mail directory:"
  entry .paths.mail.entry -width 30 -relief sunken -bd 2 \
    -textvariable maildir
  pack .paths.mail.label -side left -anchor w
  pack .paths.mail.entry -side right -anchor e

  frame .paths.exclude
  pack .paths.exclude -side top -expand 1 -fill x -padx 4 \
    -in .paths.ents
  label .paths.exclude.label -text "Search excludes:"
  entry .paths.exclude.entry -width 30 -relief sunken -bd 2 \
    -textvariable excludedirs
  pack .paths.exclude.label -side left -anchor w
  pack .paths.exclude.entry -side right -anchor e

  bind .paths.shell.entry <Return> "focus .paths.home.entry"
  bind .paths.home.entry <Return> "focus .paths.skel.entry"
  bind .paths.skel.entry <Return> "focus .paths.mail.entry"
  bind .paths.mail.entry <Return> "focus .paths.mail.entry"
  bind .paths.exclude.entry <Return> "focus .paths.shell.entry"
  focus .paths.shell.entry

  frame .paths.buttons
  pack .paths.buttons -side top -fill x -expand 1 -padx 4 -pady 4
  
  button .paths.buttons.ok -text "OK" -command endeditpaths
  pack .paths.buttons.ok -side top -expand 1

  wm title .paths "Set paths"
  center_dialog .paths
  grab .paths
}

########################################
# Edit configuration

##########
# End editing the config, and save the new values.

proc endeditconfig {} { 
  global configup
  set configup 0
  destroy .config
  regenuserlist
  regengrouplist
}

##########
# Edit the program configuration.

proc editconfig {} {
  global configup
  global dispname disppassword dispuid dispgroup dispgecos disphome
  global dispshell passstyle gidstyle confirmremove showupg

  if {$configup == 1} {
    return
  }
  set configup 1
  toplevel .config
  wm withdraw .config
  
  frame .config.top
  pack .config.top -side top -expand 1 -fill both

  frame .config.fa
  pack .config.fa -side left -fill both -expand 1 -in .config.top
  
  frame .config.fields -relief groove -borderwidth 2
  pack .config.fields -side top -fill x -expand 1 -padx 4 -pady 4 \
    -in .config.fa
  frame .config.fields.in
  pack .config.fields.in -expand 1 -fill both -side top -padx 4 -pady 4
  label .config.fields.label -text "Displayed fields"
  checkbutton .config.fields.name -text "Name" -variable dispname \
    -anchor w
  checkbutton .config.fields.password -text "Password" \
    -variable disppassword -anchor w
  checkbutton .config.fields.uid -text "UID" -variable dispuid \
    -anchor w
  checkbutton .config.fields.group -text "Group" -variable dispgroup \
    -anchor w
  checkbutton .config.fields.gecos -text "Full name" \
    -variable dispgecos -anchor w
  checkbutton .config.fields.home -text "Home" -variable disphome \
    -anchor w
  checkbutton .config.fields.shell -text "Shell" -variable dispshell \
    -anchor w
  pack .config.fields.label .config.fields.name .config.fields.password \
    .config.fields.uid .config.fields.group .config.fields.gecos \
    .config.fields.home .config.fields.shell -side top -fill x \
    -in .config.fields.in

  frame .config.fb
  pack .config.fb -side left -fill both -expand 1 -in .config.top
  
  frame .config.pwd -relief groove -borderwidth 2
  pack .config.pwd -side top -fill x -expand 1 -padx 4 -pady 4 \
    -in .config.fb
  frame .config.pwd.in
  pack .config.pwd.in -expand 1 -fill both -side top -padx 4 -pady 4
  label .config.pwd.label -text "Password display"
  radiobutton .config.pwd.short -text "Short" -variable passstyle \
    -anchor w -value 0
  radiobutton .config.pwd.long -text "Long" -variable passstyle \
    -anchor w -value 1
  pack .config.pwd.label .config.pwd.short .config.pwd.long -side top \
    -fill x -in .config.pwd.in
  
  frame .config.gid -relief groove -borderwidth 2
  pack .config.gid -side top -fill x -expand 1 -padx 4 -pady 4 -in .config.fb
  frame .config.gid.in
  pack .config.gid.in -expand 1 -fill both -side top -padx 4 -pady 4
  label .config.gid.label -text "GID display"
  radiobutton .config.gid.number -text "Number" -variable gidstyle \
    -anchor w -value 0
  radiobutton .config.gid.name -text "Name" -variable gidstyle \
    -anchor w -value 1
  pack .config.gid.label .config.gid.number .config.gid.name -side top \
    -fill x -in .config.gid.in

  frame .config.confrem -relief groove -borderwidth 2
  pack .config.confrem -side top -fill x -expand 1 -padx 4 -pady 4 \
    -in .config.fb
  frame .config.confrem.in
  pack .config.confrem.in -expand 1 -fill both -side top -padx 4 -pady 4
  checkbutton .config.confrem.button -text "Confirm removals" \
    -variable confirmremove -anchor w
  pack .config.confrem.button -side top -fill x -in .config.confrem.in

  frame .config.bottom
  pack .config.bottom -side top -expand 1 -fill both

  frame .config.upg -relief groove -borderwidth 2
  pack .config.upg -side top -fill x -expand 1 -padx 4 -pady 4 \
    -in .config.bottom
  frame .config.upg.in
  pack .config.upg.in -expand 1 -fill both -side top -padx 4 -pady 4
  checkbutton .config.upg.button -text "Show user private groups" \
    -variable showupg -anchor w
  pack .config.upg.button -side top -fill x -in .config.upg.in

  frame .config.buttons
  pack .config.buttons -side top -fill x -expand 1 -padx 4 -pady 4
  
  button .config.buttons.ok -text "OK" -command endeditconfig
  pack .config.buttons.ok -side top -expand 1

  wm title .config "Configuration"
  center_dialog .config
  grab .config
}

########################################
# Basic setup and maintenance

##########
# Set up the root menu.

proc setupmenu {} {
  global visframe
  
  frame .mbar -relief raised -bd 2
  pack .mbar -side top -fill x
  
  menubutton .mbar.usercfg -text "UserCfg" -menu .mbar.usercfg.menu
  menu .mbar.usercfg.menu
  .mbar.usercfg.menu add command -label "Set Paths" -command editpaths
  .mbar.usercfg.menu add command -label "Options" -command editconfig
  .mbar.usercfg.menu add separator
  .mbar.usercfg.menu add radiobutton -label "Edit users" \
    -variable visframe -value "user" -command { toggleframes }
  .mbar.usercfg.menu add radiobutton -label "Edit groups" \
    -variable visframe -value "group" -command { toggleframes }
  .mbar.usercfg.menu add separator
#  .mbar.usercfg.menu add command -label "About"
#  .mbar.usercfg.menu add separator
  .mbar.usercfg.menu add command -label "Quit" -command exit
  
  pack .mbar.usercfg -side left
  
#  menubutton .mbar.help -text "Help" -menu .mbar.help.menu
#  pack .mbar.help -side right
#  menu .mbar.help.menu
#  .mbar.help.menu add command -label "Help"

#  tk_menuBar .mbar .mbar.usercfg .mbar.help
  tk_menuBar .mbar .mbar.usercfg
}

##########
# Switch between user and group mode.

proc toggleframes {} {
  global visframe curmode numadds passwdlist grouplist
  
  set ok 1
  if {$numadds > 0} {
    set ok 0
  }
  if {$curmode == "user"} {
    foreach u $passwdlist {
      if {[lindex $u 7] != 0} {
	set ok 0
	break
      }
    }
  } else {
    foreach g $grouplist {
      if {[lindex $g 4] != 0} {
	set ok 0
	break
      }
    }
  }
  
  if  {$ok == 0} {
    rhs_error_dialog "Please finish all edit and add operations before changing between user mode and group mode."
    set visframe $curmode
    return
  }

  set curmode $visframe
  
  if {$visframe == "user"} {
    catch {[pack forget .group]}
    catch {[pack .user -side top -expand 1 -fill both]}
    wm minsize . 67 5
  } else {
    catch {[pack forget .user]}
    catch {[pack .group -side top -expand 1 -fill both]}
    wm minsize . 30 10
  }
}

set passwdlist [getpasswd]
set grouplist [getgroup]
set shellslist [getshells]

setupmenu
setupuserframe
setupgroupframe
regenuserlist
regengrouplist
toggleframes
wm title . "RHS Linux User/Group Manager"
wm maxsize . {} {}
