#!/usr/bin/perl -w

use POSIX;
use strict;
use Getopt::Long;

my $version = "0.11";

#
# Set these environment variables, because we need to
# make sure that system utilities, like 'ifconfig' return
# output in English. This is important, because we are looking
# for certain strings in the output. If the utility doesn't
# spit out english, we'll never get a match.
#
# Thanks to Francis Giraldeau for submitting the patch.
#
$ENV{LANG}        = "en_US";
$ENV{LANGUAGE}    = "en_US";
$ENV{LC_COLLATE}  = "en_US";
$ENV{LC_CTYPE}    = "en_US";
$ENV{LC_MESSAGES} = "en_US";
$ENV{LC_MONETARY} = "en_US";
$ENV{LC_NUMERIC}  = "en_US";
$ENV{LC_TIME}     = "en_US";

#
# Globals
#

# The following arrays are lists of locations of various
# daemons and configuration files.
# If you have a new location for one of them, then please
# add it to the list, and send the change to the
# maintainer of this program.  Thank you.

my @tftpd_locs    = ( "/usr/sbin/in.tftpd",
                      "/usr/bin/in.tftpd",
                      "/sbin/in.tftpd",
                    );

my @dhcpd_locs    = ( "/usr/sbin/dhcpd3",
                      "/usr/sbin/dhcpd",
                    );

my @lease_locs    = ( "/var/lib/dhcp",
                      "/var/lib/dhcp3",
                      "/var/state/dhcp",
                    );

my @portmap_locs  = ( "/sbin/portmap",
                      "/sbin/rpc.portmap",
                    );

my @nfsd_locs     = ( "/usr/sbin/rpc.nfsd",
                    );

my @kdm_locs      = ( "/usr/bin/kdm",
                      "/opt/kde/bin/kdm",
                      "/opt/kde2/bin/kdm",
                      "/opt/kde3/bin/kdm",
                    );

my @kdmrc_locs    = ( "/etc/kde/kdm/kdmrc",
                      "/usr/share/config/kdm/kdmrc",
                      "/etc/opt/kde3/share/config/kdm/kdmrc",
                      "/opt/kde3/share/config/kdm/kdmrc",
                      "/opt/kde3/share/config/SuSE/default/kdmrc",
                    );

my @gdm_locs      = ( "/opt/gnome/bin/gdm",
                      "/usr/bin/gdm",
                    );

my @gdm_conf_locs = ( "/etc/X11/gdm/gdm.conf",
                      "/etc/opt/gnome/gdm/gdm.conf",
                    );

my @xdm_locs      = ( "/usr/X11R6/bin/xdm",
                    );

my $ltsp_dir;                  # Location of the client root fs

################################################################################
package Runlevel;      # An object representing the system runlevel
################################################################################
{
  my $current_level;           # What the current runlevel is, determined
                               # by running the 'runlevel' command.

  my $configured_level;        # Runlevel set in /etc/inittab.  Not necessarily
                               # the current runlevel.

  my $inittab_file = "/etc/inittab";

  sub new {
    my $pkg           = shift;
    main::verbose("Checking Runlevel");
    $current_level    = $pkg->get_current_level();
    $configured_level = $pkg->get_configured_level();
    main::verbose("....: $current_level");
    main::verbose_close();
  }

  sub get_current_level {
    my $self = shift;
    return ( ( split /\s/, `LANG=C runlevel` ) [1] );
  }
  
  sub get_configured_level {
    #
    # Read the 'initdefault' entry in the /etc/inittab file.
    #
    my $self = shift;
    my $initdefault_line = `LANG=C egrep "^id:.:initdefault" $inittab_file`;
    return ( ( split /:/, $initdefault_line ) [1] );
  }
  
  sub set_level {
    #
    # Set the runlevel by changing the 'initdefault' entry in
    # the /etc/initab file.
    #
    my $self      = shift;
    my $new_level = shift;

    print("Setting runlevel to $new_level\n");

    my $filename  = $inittab_file;
    my $old_level = "";

    open(FH, "+<$filename") or die "Unable to open $filename: $!";
    my $curpos = tell FH;               # Hang on to the current record pointer
    while(<FH>){
      if( m/^id:.:initdefault/ ){       # If the line is the 'initdefault' line
        $old_level = substr($_,3,1);
        substr( $_, 3, 1, $new_level ); # Change the 4th byte
        seek( FH, $curpos, 0 );         # Position the record pointer
        print FH;                       # spit out the new value
        last;                           # We're done, exit the loop
      }
      $curpos = tell FH;
    }
    close(FH);

    main::log_it("Changed default runlevel from $old_level to $new_level\n");

    $configured_level = get_configured_level();
  }

  sub print {
    my $self = shift;

    printf("Configured runlevel: %-3d       %s\n",
           get_configured_level(),
           "(value of initdefault in $inittab_file)");
    printf("   Current runlevel: %-3d       %s\n",
           get_current_level(),
           "(output of the 'runlevel' command)");
    printf("\n");
  }

  sub configure {
    my $self = shift;
   
    main::header();

    print <<"EOF";
A runlevel is a software configuration variable that init uses to determine
which programs/services to run.  Various distributions have different ideas
of what each runlevel is for.  Systems derived from Redhat typically use
runlevel 3 for character mode console, and runlevel 5 for X-Windows based
console.  Debian typically uses runlevel 2 all the time.  Slackware uses
runlevel 4 for X-Windows mode.

You need to decide what runlevel you want to run the server in.  This can
cause a bit of a problem if you change it while you are logged in on the
console.  If you change from a non-gui runlevel to a gui runlevel, it may
start X Windows running on your current screen.
EOF

    print("\n");

    my $cfg_level = get_configured_level();
    my $new_level = $cfg_level;
    my $done      = 0;

    while( ! $done ){
      print("Select a runlevel (2,3,4,5) [$cfg_level]: ");
      $new_level = <STDIN>;
      chomp($new_level);          # get rid of that pesky newline
      if( $new_level eq "" ){
        $new_level = $cfg_level;
      }
      if( grep /$new_level/, ( 2,3,4,5 ) ){
        $done = 1;
      }
      else{
        print("\nInvalid runlevel!\n");
      }
    }
    if( $new_level eq $cfg_level ){
      print("\nLeaving runlevel unchanged\n");
      sleep(1);
    }
    else{
      $self->set_level( $new_level );

      main::header();

      print <<"EOF";
The default runlevel has been changed, but the system is still in
runlevel $current_level.  To continue with the LTSP server configuration, you 
really need to get the server into runlevel $new_level.  You can do this
by exiting from this program, and running:

   init $new_level

That will cause the system to figure out which old processes need to be
terminated, and which new processes need to be started, then it will do
the dirty work of making it all happen.  Beware that it may try to launch
X Windows on the console, and if X isn't configured properly, it may fail.
EOF
      print("\n");
      main::cont();
    }
  }  # sub configure
}  # package Runlevel

################################################################################
package Interface;      # A single Ethernet interface object
################################################################################

{
  sub new {
    my $pkg = shift;
    my $attributes = {
      interface        => '',
      ipaddr           => '',
      bcast            => '',
      netmask          => '',
      network          => '',
    };
    return ( bless $attributes, $pkg );
  }

  sub print {
    my $self = shift;
    my $rbracket = "";

    if( $self->{interface} eq Interfaces->get_primary_name() ){
      $rbracket = "<-----";
    }

    printf( "%-9s %-15s %-15s %-15s %-15s %s\n",
            $self->{interface},
            $self->{ipaddr},
            $self->{netmask},
            $self->{network},
            $self->{bcast},
            $rbracket  );

  }  # sub print
}  # package Interface

################################################################################
package Interfaces;    # A collection of Ethernet interfaces
################################################################################
{
  my @interfaces;
  my $primary_interface = "";

  sub new {
    my $pkg = shift;
    main::verbose("Checking Ethernet Interfaces");
    @interfaces = $pkg->get_interfaces();      # Save the array
    if( @interfaces == 1 ){
      $pkg->set_primary($interfaces[0]->{interface});
    }
    $pkg->get_configured_pri();
    main::verbose_close();
  }

  sub print {
    my $self = shift;

    print(   main::highlight( "Interface" )
           . " "
           . main::highlight( "IP Address     " )
           . " "
           . main::highlight( "Netmask        " )
           . " "
           . main::highlight( "Network        " )
           . " "
           . main::highlight( "Broadcast      " )
           . " "
           . main::highlight( " Used " )
           . "\n");

    if( @interfaces > 0 ){
      for( my $i=0; $i < @interfaces; $i++ ){
        $interfaces[$i]->print();
      }
    }
    else{
      printf("\nNo up Ethernet interfaces detected!\n\n");
    }
    printf("\n");
  }

  sub get_names {
    my $self = shift;
    my @names;
    for( my $i=0; $i < @interfaces; $i++ ){
      $names[$i] = $interfaces[$i]->{interface};
    }
    return @names;
  }

  sub count {
    my $self = shift;
    return( scalar(@interfaces) );
  }

  sub need_primary_msg {
    my $service = shift;
    print("\nThe primary network interface must be configured\n" );
    print("before $service can be configured!\n");
    main::cont();
  }

  sub set_primary {
    my $self   = shift;
    my $choice = shift;

    my $i      = 0;
    my $found  = 0;
    while( ! $found && $i < @interfaces ){
      if( lc($choice) eq lc($interfaces[$i]->{interface}) ){
        $primary_interface = $interfaces[$i];
        $found = 1;
      }
      $i++;
    }

    if( $found ){
      main::set_conf_value("LTSP_ETH_INTERFACE",
                           $primary_interface->{interface});
    }
#    else{
#      print("\n\n");
#      print("    The primary interface is configured as $choice, but that\n");
#      print("    interface does NOT currently exist!\n");
#      main::cont();
#    }
  }

  #
  # Returns true, if we have a primary interface
  #
  sub have_primary {
      return( ($primary_interface) ? 1 : 0 );
  }

  sub get_primary_name {
    return( ($primary_interface) ? $primary_interface->{interface} : "" );
  }

  sub get_primary_addr {
    return( ($primary_interface) ? $primary_interface->{ipaddr}    : "" );
  }

  sub get_primary_bcast {
    return( ($primary_interface) ? $primary_interface->{bcast}     : "" );
  }

  sub get_primary_netmask {
    return( ($primary_interface) ? $primary_interface->{netmask}   : "" );
  }

  sub get_primary_network {
    return( ($primary_interface) ? $primary_interface->{network}   : "" );
  }

  sub get_interfaces {

    my $self = shift;

    my $interface;                           # The current interface
    my $ipaddr = "";
    my $bcast  = "";
    my $mask   = "";

    my @interfaces;                          # An array to hold the interfaces
    splice( @interfaces, 0 );                # Make sure it is empty

    open FH, "LANG=C ifconfig -a |" or die "Unable to run ifconfig: $!";

    while(<FH>){
      chomp;                                 # Get rid of the trailing newline
      if( /Link encap/ ){
        $interface = $_;
        $interface =~ s/\s.*$//;
      }
      if( $interface =~ /^(lo|ppp)/ ){             # Skip Loopback and ppp
        next;                                      # interfaces
      }
      if( /inet addr/ ){
        #
        # Get the IP address
        #
        $ipaddr = $_;
        $ipaddr =~ s/^.*inet addr://;
        $ipaddr =~ s/\s.*$//;

        #
        # Get the Broadcast address
        #
        $bcast = $_;
        $bcast =~ s/^.*Bcast://;
        $bcast =~ s/\s.*$//;
      
        #
        # Get the Netmask
        #
        $mask = $_;
        $mask =~ s/^.*Mask://;
        $mask =~ s/\s.*$//;
      }
      if( /UP BROADCAST/ and $ipaddr ){    # Look only at interfaces marked UP

        my $iface = Interface->new();        # Allocate an interface object

        $iface->{interface} = $interface;
        $iface->{ipaddr}    = $ipaddr;
        $iface->{bcast}     = $bcast;
        $iface->{netmask}   = $mask;

        my ( @addr_octets, @mask_octets, @netw_octets, $idx );

        @addr_octets = split /\./, $ipaddr;
        @mask_octets = split /\./, $mask;

        for( $idx = 0; $idx < 4; $idx++ ){ 
          $netw_octets[$idx] = int($addr_octets[$idx])
                             & int($mask_octets[$idx]);
        }

        $iface->{network} = join ".", @netw_octets;    # Build a nice string

        #
        # Finally, if the interface is UP, lets grab it
        #
        push @interfaces, $iface;
      }
    }
    close FH;

    return(@interfaces);       # Return the array of interfaces
  }

  sub get_configured_pri {
    my $self = shift;

    if( my $default = main::get_conf_value("LTSP_ETH_INTERFACE") ){
      $self->set_primary($default);
    }
  }

  sub select_interface {
    my $self = shift;
    main::header();

    $self->print();
    print("\n\n");
 
    my $count   = $self->count();
    my @ints    = Interfaces->get_names();
    my $default = Interfaces->get_primary_name();
    my $list_of_interfaces = "";
    my $choice;

    for( my $i = 0; $i < @ints; $i++ ){
      $list_of_interfaces .= $ints[$i] . ",";
    }
    chop($list_of_interfaces);    # Remove the last comma

    if( $count < 1 ){
      main::cont();
    }
    else{
      if( $count == 1 ){
        print("Only 1 Ethernet interface found, using $ints[0]\n");
        $choice = $ints[0];
        main::cont();
      }
      else{
        print("Found $count Ethernet interfaces.\n");
        print("\nYou need to indicate which interface the workstations are "
              . "connected to.\n\n");
        my $done = 0;
        while( ! $done ){
          print( "Select one of ($list_of_interfaces) or 'Q' "
                . "to quit [$default]: ");
          $choice = <STDIN>;
          chomp($choice);
          $choice = lc( ( $choice eq "" ) ? $default : $choice );

          last if( $choice eq "q" );      # Get out of loop if user entered 'q'

          my $i     = 0;
          my $found = 0;
          while( ! $found && $i < @ints ){
            $found = 1 if( $choice eq lc($ints[$i]) );
            $i++;
          }
          if( $found ){ $done = 1; }
          else        { print("\nInvalid response!\n\n"); }
        }

        if( $choice ne "q" ){
          if( $choice eq $default ){
            print("\nLeaving primary interface unchanged\n");
            sleep(1);
          }
          else{
            print("\nUsing $choice as the primary interface\n");
            Interfaces->set_primary($choice);
            main::cont();
          }
        }  # choice ne q
      }  # count > 1
    }  # count !< 1
  }  # sub select_interface
}  # package Interfaces

################################################################################
package Service;
################################################################################
{
  my @services;

  sub new {
    my $pkg  = shift;
    my $name = shift;
    my $attributes = {
        name       => "",
        installed  => "",
        enabled    => 0,
        running    => 0,
    };
    push @services, $attributes;            # Add the object to the array
    return ( bless $attributes, $pkg );
  }

  sub list_services {
    my $self = shift;
    printf(  main::highlight( "Service   " )
           . " "
           . main::highlight( "Installed  " )
           . " "
           . main::highlight( "Enabled  " )
           . " "
           . main::highlight( "Running  " )
           . " "
           . main::highlight( "Notes" . " " x 32 )
           . "\n" );
    for( my $i = 0; $i < @services; $i++ ){
      $services[$i]->print();
    }
    printf("\n");
  }

  sub is_running {
    my ( $self, $proto, $port ) = @_;
    my $result = 0;

    if( Interfaces->have_primary() ){
      my $cmd = "LANG=C netstat -an | grep \":$port \"";
      open ( FH, "$cmd |" ) or die "Couldn't open pipe from netstat: $!";
      while(<FH>){
        $result = 1 if ( $_ =~ "^$proto" );
      }
      close(FH);
      return( $self->{running} = $result );
    }
  }

  sub suse_style_service {
    my( $self, $rcentry, $svcname ) = @_;
    my $result = 0;;

    if ( -f "/etc/rc.config" ){
      #
      # Probably a SuSE system (before 8.0)
      #
      if( `LANG=C grep -c $rcentry /etc/rc.config` > 0 ){
        open FH, "</etc/rc.config" or die "Unable to read /etc/rc.config: $!";
        while(<FH>){
          if( /^$rcentry=\"yes\"/ ){
            $result = 1;
            last;
          }
        }
        close FH;
      }
      else {
        #
        # If we do have an rc.config file, but it doesn't contain
        # the entry we are looking for, then it is most likely
        # a SuSE 8.0 (or newer) system. In 8.0, they tried to
        # make it more sysv_init, but their chkconfig command isn't
        # quite compatible with that of redhat or mandrake.
        # we have to examine the output of the chkconfig command, rather
        # than the value returned as its exit status.
        #
        my $found = 0;
        for my $dir ( split /:/, $ENV{PATH} ){
          my $path = $dir . "/chkconfig";
          if( -x $path ){
            $found = 1;
            last;
          }
        }
        if( $found ){
          open(FH, "chkconfig $svcname|") or die
               "checking if $svcname is enabled has failed: $!";
          my $rec = <FH>;
          close(FH);
          my @fields = split /\s+/, $rec;
          $result = ( $fields[1] eq "on" ) ? 1 : 0;
        }
      }
    }
    return $result;
  }

  sub has_cmd {
    my $self = shift;
    my $cmd  = shift;

    my $found = 0;
    for my $dir ( split /:/, $ENV{PATH} ){
      my $path = $dir . "/" . $cmd;
      if( -x $path ){
        $found = 1;
        last;
      }
    }
    return($found);
  }
  
  sub has_chkconfig {
    my $self = shift;
    return( $self->has_cmd("chkconfig") );
  }

  sub print {
    my $self = shift;
    
    printf("%-10s %-11s %-9s %-8s  %s\n",
            $self->{name},
            yes_no_na( $self->{installed} ),
            yes_no_na( $self->{enabled}   ),
            yes_no_na( $self->{running}   ),
            $self->show_notes() );
  }

  sub yes_no_na {
    my $flag   = shift;
    my $result = "";

    if( $flag == 0 ){
      $result = "no";
    }
    elsif( $flag == 1 ){
      $result = "Yes";
    }
    elsif( $flag == -1 ){
#      $result = "n/a";
      $result = "-";
    }
    return $result;
  }

  sub show_notes {
    my $self = shift;
    return "";
  }

  sub configure {
    my $self     = shift;
    my $servname = shift;

    my $serv = "";
    for my $i ( @services ){
      if( $i->{name} eq $servname ){
        $serv = $i;
      }
    }

    if( $serv eq "" ){
      die( "Internal error, couldn't locate service: $servname\n");
    }

    $serv->configure_screen();
  }

  sub configure_screen {
    my $self = shift;
    printf( "\nThe configuration screen for "
           . $self->{name} . " has not been written yet\n");
    main::cont();
  }

  sub running {
    my $self     = shift;
    my $servname = shift;

    my $serv = "";
    for my $i ( @services ){
      if( $i->{name} eq $servname ){
        $serv = $i;
      }
    }

    if( $serv eq "" ){
      die( "Internal error, couldn't locate service: $servname\n");
    }

    return( $serv->is_running() );
  }
}

################################################################################
package Rpcservice;
our @ISA = ("Service");                        # Inherits from Service
################################################################################
{
  sub new {
    my $self = shift;
    my $name = shift;
    my $obj  = $self->SUPER::new($name);       # Call base class constructor

    return $obj;
  }

  sub is_running {
    my $self   = shift;
    my $serv   = shift;
    my $result = 0;

    if( Interfaces->have_primary() && Service->running( "portmapper") ){
      my $cmd = "rpcinfo -p | grep $serv";
      open(FH, "$cmd |") or die "Couldn't open pipe from rpcinfo: $!";
      while(<FH>){
        $result = 1;
      }
      close(FH);
    }
    return( $self->{running} = $result );

  }  # sub is_running
}  # package Rpcservice

################################################################################
package Tftpd;
our @ISA = ("Service");                        # Inherits from Service
################################################################################
{

  my $has_s_arg = 0;

  sub new {
    my $self = shift;
    my $name = shift;
    main::verbose("Checking Tftpd");

    my $obj = $self->SUPER::new($name);        # Call base class constructor
    $obj->{name}       = "tftpd";
    $obj->{method}     = "",

    $obj->is_installed();                      # When we instantiate the object
    $obj->is_enabled();                        # we go out and figure out what
    $obj->is_running();                        # we can about it.
    $obj->check_s_arg();

    main::verbose_close();
    return $obj;
  }

  sub is_installed {
    my $self    = shift;
    main::verbose_dot();
    my $result  = 0;
    if( ! $self->{installed} ){
      for my $i ( @tftpd_locs ){
        if( -x $i ){
          $result = 1;
          last;
        }
      }
    }
    return( $self->{installed} = $result );
  }

  sub method {
    my $self  = shift;
    main::verbose_dot();
    my $method = $self->{method};
    if( $self->{installed} ){
      if( $self->{method} eq "" ){
        if( -f "/etc/xinetd.d/tftp" ){
          $method = "xinetd";
        }
        elsif( -f "/etc/inetd.conf" && `LANG=C grep tftp /etc/inetd.conf` =~ "tftp" ){
          $method = "inetd";
        }
      }
    }
    return( $self->{method} = $method );
  }

  sub is_enabled {
    my $self   = shift;
    main::verbose_dot();
    my $result = 0;

    if( $self->{installed} ){
      if( $self->method() eq "inetd" ){
        $result = ( `LANG=C grep -c "^tftp" /etc/inetd.conf` > 0 ) ? 1 : 0;
      }
      elsif( $self->method() eq "xinetd" ){
        my $disable_line = `LANG=C egrep "^[[:space:]]*disable" /etc/xinetd.d/tftp`;
        if( $disable_line ){
          my @fields       = split / *= */, $disable_line;
          chomp($fields[1]);
          $result = ( $fields[1] eq "no" ) ? 1 : 0;
        }
        else{
          $result = 1;
        }
      }
    }
    return( $self->{enabled} = $result );
  }

  sub is_running {
    my $self = shift;
    main::verbose_dot();
    return( ($self->{installed}) ? $self->SUPER::is_running("udp",69) : 0 );
  }

  sub check_s_arg {
    my $self   = shift;
    main::verbose_dot();
    my $found  = 0;
    if( $self->{installed} ){
      if( $self->method() eq "xinetd" ){
        my $infile = "/etc/xinetd.d/tftp";
        open( FH, "< $infile" ) or die "can't open $infile: $!";
        while( <FH> ){
          if( /^\s*server_args\s*=/ ){
            if( /-s\s/ ){
              $found = 1;
              last;
            }
          }
        }
        close( FH );
      }
      elsif( $self->method() eq "inetd" ){
        my $infile = "/etc/inetd.conf";
        open( FH, "< $infile" ) or die "can't open $infile: $!";
        while( <FH> ){
          if( /^tftp\s/ ){
            if( /-s\s/ ){
              $found = 1;
              last;
            }
          }
        }
        close( FH );
      }
    }
    return( $has_s_arg = $found );
  }  # sub check_s_arg

  sub has_s_arg {
    return( $has_s_arg );
  }

  sub show_notes {
    my $self  = shift;

    my $result;

    if( ! $self->{installed} ){
      $result = "Not installed !!!";
    }
    elsif( $has_s_arg ){
      $result = "Has '-s' flag";
    }
    else{
      $result = "No '-s' flag";
    }
    return( $result );
  }

  sub configure_screen {
    my $self = shift;

    main::header();

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg("tftpd");
      return;
    }

    print(  "tftpd is the daemon that implements the "
          . "'Trivial File Transfer Protocol'.\n");
    print(  "This is needed for LTSP workstations to "
          . "download the kernel from\n");
    print("the server.\n");

    if( ! $self->{installed} ){
      print("\n\nThe tftpd package is not installed!\n");
      print("\nPlease install it, and then come back here and enable it.\n");
      main::cont();
    }
    elsif( $self->{running} ){
      print("\n\ntftpd is already enabled and running!\n");
      main::cont();
    }
    else{
      my $answer = main::get_yn("Do you want to enable tftpd");
      if( $answer eq "Y" ){
        my $enabled = 0;
        if( $self->{method} eq "inetd" ){
          my $filename = "/etc/inetd.conf";
          open( FH, "+<$filename") or die "Unable to open $filename: $!";
          my @lines = <FH>;             # Suck the entire file into an array
          truncate( FH, 0 );            # Truncate the file, to empty it
          seek( FH, 0, 0 );             # Position file pointer to the beginning
          for( my $i = 0; $i < @lines; $i++ ){
            $lines[$i] =~ s/^#*\s*(tftp\s.*)/$1/;
            print FH $lines[$i];
          }
          close(FH);
          my @args = ("killall","-HUP","inetd");
          system(@args);                # Tell xinetd to re-read config files
          $enabled = 1;
        }
        elsif( $self->{method} eq "xinetd" ){
          my $filename = "/etc/xinetd.d/tftp";
          open( FH, "+<$filename") or die "Unable to open $filename: $!";
          my @lines = <FH>;             # Suck the entire file into an array
          truncate( FH, 0 );            # Truncate the file, to empty it
          seek( FH, 0, 0 );             # Position file pointer to the beginning
          for( my $i = 0; $i < @lines; $i++ ){
            $lines[$i] =~ s/^(\s*disable\s*=\s)yes/$1no/;
            print FH $lines[$i];
          }
          close(FH);
          my @args = ( "killall", "-USR2", "xinetd" );
          system(@args);                # Tell xinetd to re-read config files
          $enabled = 1;
          sleep(2);
        }
        else{
          print("\n\nI cannot determine how tftpd is invoked.  "
                . "You may need to\n");
          print("manually configure tftpd on your server.\n");
          main::cont();
        }

        if( $enabled ){
          $self->is_enabled();          # update the enabled status
          $self->is_running();          # update the running status
  
          if( $self->{running} ){
            print("\n\ntftpd has been successfully started\n");
          }
          else{
            print("\n\nThe signal has been sent to start tftpd, but it "
                  . "doesn't appear\nto be running.\n");
          }
          main::cont();
        }  # enabled = true
      }  # Enable tftpd = Y
    }  # installed, but not running
  }  # sub configure_screen
}  # package Tftpd

################################################################################
package Dhcpd;
our @ISA = ("Service");                        # Inherits from Service
################################################################################
{

  sub new {
    my $self         = shift;
    my $name         = shift;
    main::verbose("Checking Dhcpd");
    my $obj          = $self->SUPER::new($name); # Call base class constructor
    $obj->{name}     = "dhcpd";
    $obj->{version}  = 0;                      # Add a new member variable
    $obj->{bin_name} = "";                     # Add a new member variable

    $obj->is_installed();                      # When we instantiate the object
    $obj->is_enabled();                        # we go out and figure out what
    $obj->is_running();                        # we can about it.
    $obj->version();

    main::verbose_close();
    return $obj;
  }

  sub is_installed {
    my $self   = shift;
    my $result = 0;
    main::verbose_dot();
    if( $self->{installed} eq "" ){
      for my $i ( @dhcpd_locs ){
        if( -x $i ){
          $result = 1;
          $self->{bin_name} = $i;
          last;
        }
      }
    }
    $self->{installed} = $result;
    return $self->{installed};
  }

  sub method {
    return "";
  }

  sub is_enabled {
    my $self   = shift;
    my $res = 0;
	
    main::verbose_dot();
    if( $self->{installed} ){
	  my $level = Runlevel::get_configured_level();
      if( -x "/usr/sbin/update-rc.d" ){                   # Debian system
	    $res = system("(ls /etc/rc$level.d/S??dhcp* 2>&1) >/dev/null") ? 0 : 1;
	  }
      elsif ( -f "/etc/rc.config" ){
        $res = $self->suse_style_service( "START_DHCPD", "dhcpd" );
      }
      elsif( $self->has_chkconfig() ){
        $res = system( "chkconfig dhcpd" ) ? 0 : 1;
      }
    }
    return( $self->{enabled} = $res );
  }

  sub is_running {
    my $self   = shift;
    main::verbose_dot();
    return( ( $self->{installed} ) ? $self->SUPER::is_running("udp",67) : 0 );
  }

  sub version {
    my $self    = shift;
    main::verbose_dot();
    my $version = "";
    if( $self->{installed} ){
      $version = $self->check_version_dash_ver() or
      $version = $self->check_version_dpkg()     or
      $version = $self->check_version_runit()    or
      $version = $self->check_version_rpm()      or
      $version = "Unknown";
    }
    $self->{version} = $version;
    return $version;
  }

  sub check_version_dash_ver {
    my $self   = shift;
    my $result = "";
    main::verbose_dot();
    my $cmd = $self->{bin_name} . " --version 2>&1";

    open(FH, "$cmd |")
      or die "checking version of dhcpd failed: $!";

    my @output = <FH>;
    close(FH);

    if( @output == 1 ){
      chomp($output[0]);
      $output[0] =~ s/isc-dhcpd-V//;
      $output[0] =~ s/\..*$//;
      $result = $output[0];
    }
    return $result;
  }

  sub check_version_dpkg {
    my $self   = shift;
    my $result = "";
    main::verbose_dot();
    my $found  = 0;
    for my $i ( "/usr/bin/dpkg", ){
      if( -x $i ){
        $found = 1;
        last;
      }
    }

    if( $found ){
      open(FH, "dpkg -l | grep \"ii *dhcp3\" | sed '/client/d;/common/d' |")
        or die "using dpkg failed! - $!";
      my @output = <FH>;
      if( @output == 1 ){
        my @fields = split /\s+/, $output[0];
        $fields[2] =~ s/\..*$//;
        $result = $fields[2];
      }
      close(FH);
    }
    return $result;
  }

  sub check_version_runit {
    #
    # To check the version number by running the dhcpd program,
    # we need to create a bogus configuration file, and then
    # run dhcpd using that bogus configuration file.  This will
    # cause it to fail, but in the error message that spits out
    # will be the version number.  Kind of lame, but it works.
    #
    my $self   = shift;
    my $result = "";
    main::verbose_dot();

    my $tmp_dhcpd_conf;

    do {
      $tmp_dhcpd_conf = main::tmpnam();
    } until sysopen( TMP_DHCPD_CONF,
                     $tmp_dhcpd_conf,
                     ( main::O_RDWR | main::O_CREAT | main::O_EXCL ),
                     0600 );

    printf( TMP_DHCPD_CONF "junk to make dhcpd fail\n" );

    my $cmd = $self->{bin_name} . " -cf $tmp_dhcpd_conf 2>&1";

    open(FH, "$cmd |")
      or die "checking version of dhcpd by running it failed: $!";

    my @output = <FH>;
    close(FH);

    if( @output > 0 ){
      chomp($output[0]);
      $output[0] =~ s/Internet Software Consortium DHCP Server //;
      $output[0] =~ s/\..*$//;
      $result = $output[0];
    }
    close( TMP_DHCPD_CONF );
    unlink( $tmp_dhcpd_conf );
    return $result;
  }

  sub check_version_rpm {
    my $self   = shift;
    my $result = "";
    main::verbose_dot();
    my $found  = 0;
    for my $i ( "/bin/rpm", ) {
      if( -x $i ){
        $found = 1;
        last;
      }
    }

    if( $found ){
      open(FH, "rpm -qa | grep dhcp | sed '/dhcpcd/d;/common/d' |")
        or die "using rpm failed! - $!";
      my @output = <FH>;
      if( @output == 1 ){
        chomp($output[0]);              # Remove trailing newline
        $output[0] =~ s/dhcp-server-//; # Remove some leading crap
        $output[0] =~ s/dhcp-//;
        $output[0] =~ s/\..*$//;        # Get rid of everything after the
                                        # first dot.
        $result = $output[0];
      }
      close(FH);
    }
    return $result;
  }

  sub show_notes {
    my $self = shift;
	
    return( ($self->{version}) ? "Version ".$self->{version} : "" );
  }

  sub configure_screen {
    my $self = shift;

    main::header();

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg("dhcpd");
      return;
    }

    print <<"EOF";
dhcpd is the daemon that implements the Dynamic Host Configuration Protocol.
This is needed for LTSP workstations to obtain an IP address and other vital
information from the server.

There are 2 main steps to configuring dhcpd:

  1)  Build the configuration file

  2)  Enable the daemon to run when the system is booted

Currently, this utility is not a full dhcp configuration tool.  It will
only create a sample dhcpd.conf file that can be hand tuned for your
specific needs.
EOF

    if( ! $self->{installed} ){
      print("\n\nThe dhcpd package is not installed!\n");
      print("\nPlease install it, and then come back here and enable it.\n");
      main::cont();
    }
    elsif( $self->{enabled} ){
      my $run = ($self->{running}) ? " and running" : "";
      print("\n\ndhcpd is already enabled$run!\n");
      if( main::get_yn("Do you want to build a dhcpd.conf file") eq "Y" ){
        $self->build_conf();
      }
    }
    else{
      if( main::get_yn("Do you want to enable the dhcpd daemon") eq "Y" ){
        $self->enable(); 
      }
      if( main::get_yn("Do you want to build a dhcpd.conf file") eq "Y" ){
        $self->build_conf(); 
      }
    }
    main::cont();
  }  # sub configure_screen

  sub build_conf {
    my $self = shift;
    main::header();

    my $address   = Interfaces->get_primary_addr();
    my $broadcast = Interfaces->get_primary_bcast();
    my $netmask   = Interfaces->get_primary_netmask();
    my $network   = Interfaces->get_primary_network();

    print ("Step 1: Building a dhcpd.conf file:\n\n");
    
	my $filepath;
	if ( $self->{version}=="3" && -d "/etc/dhcp3" ) {
	  $filepath = "/etc/dhcp3";
	}
	else {
      $filepath = "/etc";
	}
    my $filename = "$filepath/dhcpd.conf";
    if( -f $filename ){
      print("  $filename already exists, creating new file as $filename.sample!\n\n");
      $filename .= ".sample";
    }

    open( FH, ">$filename" ) or die "Couldn't create $filename: $!";

    print FH <<"EOF";
#
# Sample configuration file for ISC dhcpd
#
# Make changes to this file and copy it to $filename
#

EOF
    if( $self->{version} ge "3" ){
      print("Adding ddns info...\n");
      print FH "ddns-update-style            none;\n";
      print FH "\n";
    }

    print("Adding global info...\n");

    print FH <<"EOF";
default-lease-time           21600;
max-lease-time               21600;

option subnet-mask           $netmask;
option broadcast-address     $broadcast;
option routers               $address;
option domain-name-servers   $address;
option domain-name           "ltsp";          # <--Fix this domain name

option root-path             "$address:$ltsp_dir/i386";

EOF

    if( $self->{version} ge "3" ){
      print("Adding custom option codes...\n");
      print FH "option option-128 code 128 = string;\n";
      print FH "option option-129 code 129 = text;\n";
      print FH "\n";
    }

    main::verbose_line("Adding network segment info...");

    print FH <<"EOF";
subnet $network netmask $netmask {
    use-host-decl-names      on;
    option log-servers       $address;

EOF

    main::verbose_line("Adding individual host info...");

    my $prefix = ( Tftpd->has_s_arg() ) ? "" : "/tftpboot";

    print FH <<"EOF";

##
## If you want to use static IP address for your workstations, then un-comment
## the following section and modify to suit your network.
## Then, duplicate this section for each workstation that needs a static
## IP address.
##
##    host ws001 {                                       <----- Fix this hostname
##        hardware ethernet    00:11:22:33:44:55;           <-- Fix this MAC addr
##        fixed-address        192.168.0.1;                 <-- Fix this IP addr
##        filename             "${prefix}/lts/vmlinuz-2.4.26-ltsp-1";
##    }

##
## If you want to use a dynamic pool of addresses, then un-comment the following
## lines and modify to match your network.
##
##    subnet  192.168.0.0 netmask 255.255.255.0 {
##        range dynamic-bootp 192.168.0.1 192.168.0.253;
##    }
##

}

EOF

    main::verbose_line("Adding comments for kernel parameters...");
    print FH <<"EOF";
#
# If you need to pass parameters on the kernel command line, you can
# do it with option-129.  In order for Etherboot to look at option-129,
# you MUST have option-128 set to a specific value.  The value is a
# special Etherboot signature of 'e4:45:74:68:00:00'.
#
# Add these two lines to the host entry that needs kernel parameters
#
#        option option-128     e4:45:74:68:00:00;       # NOT a mac address
#        option option-129     "NIC=ne IO=0x300";
#
EOF

    close(FH);

    print("Done\n");

    main::verbose_line("\nThe dhcpd config file has been created as: $filename");
  }  # sub build_conf

  sub enable {
    my $self = shift;

    main::header();

    main::verbose_line("Step 2: Enabling the dhcpd daemon\n");

#
# This routine needs to do the following:
#
#   1) Make sure leases file exists
#
#   2) Make sure that dhcpd is configured to listen on 
#      the correct interface
#
#   3) Make sure that dhcpd is configured to start
#      at boot time
#


print <<"EOF";
Enabling dhcpd to run can be a really tricky thing. 
The dhcpd.conf file and the leases file MUST exist.  Figuring out where
the leases file belongs is a challenge, as it seems to be different on
each distro.  Also, telling dhcpd which interface to listen on is done
differently for the various distros.

So, we'll do our best to figure it all out.

EOF

    main::verbose_line("Checking leases file");

    #
    # Check for the leases file.  If it doesn't exist, then create it.
    #
    #   Redhat 7.0      /var/lib/dhcp/dhcpd.leases
    #   Redhat 7.2      /var/lib/dhcp/dhcpd.leases
    #   Redhat 7.3      /var/lib/dhcp/dhcpd.leases
    #   Debian Woody    /var/lib/dhcp3/dhcpd.leases
    #   Mandrake 8.2    /var/lib/dhcp/dhcpd.leases
    #   SuSE 8.0        /var/lib/dhcp/dhcpd.leases
    #   SuSE 7.3        /var/lib/dhcp/dhcpd.leases
    #

    my ( $dir, $file );

    for $dir ( @lease_locs ){
      if( -d $dir ){
        $file = $dir . "/dhcpd.leases";
        if( ! -f $file ){
          open( FH, ">$file" ) or die "Couldn't create the $file file: $!\n";
          close( FH );
          chmod( 0644, $file ) or die "Couldn't set the perms on $file: $!\n";
          chown( 0, 0, $file ) or die "Couldn't set ownership on $file: $!\n";
        }
      }
    }

    main::verbose_line("Setting dhcpd to listen on primary interface");

    #
    #   Figure out how to specify a certain Ethernet interface to listen on
    #
    #      Redhat 7.2      /etc/sysconfig/dhcpd, 'DHCPDARGS="eth0"'
    #      Redhat 7.3      /etc/sysconfig/dhcpd, 'DHCPDARGS="eth0"'
    #      Redhat 7.0 didn't have an option file, the /etc/init.d/dhcpd script
    #        would have to be modified, to include 'eth0' after the
    #        'daemon /usr/sbin/dhcpd' line.
    #      Mandrake 8.2    /etc/sysconfig/dhcpd,  'INTERFACES="eth0"'
    #      SuSE 8.0        /etc/sysconfig/dhcpd,  'DHCPD_INTERFACE="eth0"'
    #      SuSE 7.3        /etc/rc.config.d/dhcpd.rc.config
    #                                             'DHCPD_INTERFACE="eth0"'
    #

    if( -f "/etc/sysconfig/dhcpd" ){
      my $filename = "/etc/sysconfig/dhcpd";
      if( `LANG=C grep -c DHCPDARGS $filename` > 0 ){     # RH7.2, 7.3
        open(FH,"+<$filename") or die "Unable to open $filename: $!";
        my @lines = <FH>;             # Suck the entire file into an array
        truncate( FH, 0 );            # Truncate the file to empty it
        seek( FH, 0, 0 );             # Position file pointer to the beginning
        for( my $i = 0; $i < @lines; $i++ ){
          if( $lines[$i] =~ /^\s*DHCPDARGS/ ){
            print(FH "DHCPDARGS=\""
                   . Interfaces->get_primary_name()
                   . "\"\n");
          }
          else{
            print(FH $lines[$i]);
          }
        }  # for loop
        close(FH);
      }  # grep -c DHCPDARGS > 0

      elsif( -f "/etc/rc.d/init.d/dhcpd"
             && `LANG=C grep -c INTERFACES /etc/rc.d/init.d/dhcpd` > 0 ){  # Mdk 8.2
        open(FH,"+<$filename") or die "Unable to open $filename: $!";
        my @lines = <FH>;             # Suck the entire file into an array
        truncate( FH, 0 );            # Trunacate the file to empty it
        seek( FH, 0, 0 );             # Position file pointer to the beginning
        for( my $i = 0; $i < @lines; $i++ ){
          if( ! $lines[$i] =~ /^\s*INTERFACES/ ){
            print(FH $lines[$i]);
          }
        }
        print(FH "INTERFACES=\""
               . Interfaces->get_primary_name()
               . "\"\n");
        close(FH);
      }
      elsif( `LANG=C grep -c DHCPD_INTERFACE $filename` > 0 ){             # SuSE 8.0
        open(FH,"+<$filename") or die "Unable to open $filename: $!";
        my @lines = <FH>;             # Suck the entire file into an array
        truncate( FH, 0 );            # Trunacate the file to empty it
        seek( FH, 0, 0 );             # Position file pointer to the beginning
        for( my $i = 0; $i < @lines; $i++ ){
          if( $lines[$i] =~ /^\s*DHCPD_INTERFACE/ ){
            print(FH "DHCPD_INTERFACE=\""
                   . Interfaces->get_primary_name()
                   . "\"\n");
          }
          else{
            print(FH $lines[$i]);
          }
        }
        close(FH);
      }
    }  # if -f "/etc/sysconfig/dhcpd"

    elsif( -f "/etc/rc.config.d/dhcpd.rc.config" ){                 # SuSE 7.3
      my $filename = "/etc/rc.config.d/dhcpd.rc.config";
      if( `LANG=C grep -c DHCPD_INTERFACE $filename` > 0 ){
        open(FH,"+<$filename") or die "Unable to open $filename: $!";
        my @lines = <FH>;             # Suck the entire file into an array
        truncate( FH, 0 );            # Trunacate the file to empty it
        seek( FH, 0, 0 );             # Position file pointer to the beginning
        for( my $i = 0; $i < @lines; $i++ ){
          if( $lines[$i] =~ /^\s*DHCPD_INTERFACE/ ){
            print(FH "DHCPD_INTERFACE=\""
                   . Interfaces->get_primary_name()
                   . "\"\n");
          }
          else{
            print(FH $lines[$i]);
          }
        }
        close(FH);
      }
    }  # if -f /etc/rc.config.d/dhcpd.rc.config

    elsif( -f "/etc/rc.d/init.d/dhcpd"                                  # RH 7.0
        && `LANG=C egrep -c "daemon \/usr\/sbin\/dhcpd\$" /etc/rc.d/init.d/dhcpd` > 0){
      my $filename = "/etc/rc.d/init.d/dhcpd";
      open(FH,"+<$filename") or die "Unable to open $filename: $!";
      my @lines = <FH>;             # Suck the entire file into an array
      truncate( FH, 0 );            # Trunacate the file to empty it
      seek( FH, 0, 0 );             # Position file pointer to the beginning
      for( my $i = 0; $i < @lines; $i++ ){
        if( $lines[$i] =~ /^\s*daemon \/usr\/sbin\/dhcpd/ ){
          print(FH "\tdaemon /usr/sbin/dhcpd "
                 . Interfaces->get_primary_name()
                 . "\n");
        }
        else{
          print(FH $lines[$i]);
        }
      }
      close(FH);
    }  # if -f /etc/rc.d/init.d/dhcpd    (RH 7.0)

    #   figure out how to enable the daemon.
    #      Redhat           'chkconfig --level X dhcpd on'
    #      Mandrake 8.2     'chkconfig --level X dhcpd on'
    #      Debian           'update-rc.d dhcp3-server start 20 X .'
    #      SuSE 8.0         'insserv /etc/init.d/dhcpd,start=X'
    #      SuSE 7.3         'insserv /etc/init.d/dhcpd,start=X'
    #

    main::verbose_line("Configuring dhcpd to start at boot time");

    my $level = Runlevel::get_configured_level();

    if( -x "/sbin/insserv" ){                              # SuSE system
      my $cmd = "insserv /etc/init.d/dhcpd,start=$level";
      system( $cmd );
    }
    elsif( -x "/usr/sbin/update-rc.d" ){                   # Debian system
	  my $serv;
	  if ( -f "/etc/init.d/dhcp" ){
	    $serv = "dhcp";
	  }
	  elsif ( -f "/etc/init.d/dhcp3-server" ){
	     $serv = "dhcp3-server";
	  }
	  
	  if ( $serv ) {			
        my $cmd = "update-rc.d $serv start 20 $level .";
	    system( $cmd );
	  }
    }
    elsif( -x "/sbin/chkconfig" ){                         # Redhat or Mandrake
      my $cmd = "chkconfig --level $level dhcpd on";
      system( $cmd );
    }
    else{
      print("\nSorry, I don't know how to enable dhcpd on this system\n");
      print("You will have to enable it manually\n");
    }

    $self->is_enabled();       # After enabling, lets check to see if it worked

  }  # sub enable
}  # package Dhcpd

################################################################################
package Portmapper;
our @ISA = ("Service");                        # Inherits from Service
################################################################################
{

  sub new {
    my $self = shift;
    my $name = shift;
    main::verbose("Checking Portmapper");
    my $obj  = $self->SUPER::new($name);       # Call base class constructor

    $obj->{name}       = "portmapper";

    $obj->check_installed();                   # When we instantiate the object
    $obj->is_enabled();                        # we go out and figure out what
    $obj->is_running();                        # we can about it.

    main::verbose_close();
    return $obj;
  }

  sub check_installed {
    my $self    = shift;
    main::verbose_dot();
    my $result  = 0;
    for my $i ( @portmap_locs ){
      if( -x $i ){
        $result = 1;
        last;
      }
    }
    return( $self->{installed} = $result );
  }

  sub is_installed {
    my $self = shift;
    return( ( $self->{installed} eq "" )
                ? $self->check_installed()
                : $self->{installed} );
  }

  sub is_enabled {
    my $self   = shift;
    main::verbose_dot();
    my $result = 0;
    if( $self->{installed} ){
	  if ( !system("(ls /etc/rcS.d/S??portmap 2>&1 ) > /dev/null") ){
		 $result = 1;
	  }
      elsif ( -f "/etc/rc.config" ){
        $result = $self->suse_style_service("START_PORTMAP", "portmap");
      }
      elsif( $self->has_chkconfig() ){
        #
        # chkconfig will return a '0' if the service is configured, and
        # it will return '1' if NOT configured.
        #
        $result = system( "chkconfig portmap" ) ? 0 : 1;
      }
      else{
        my $script = "/etc/rc.d/rc.inet2";
        if( -f $script ){                     # Probably Slackware
          if( `LANG=C egrep -c "^[[:space:]]*\/sbin\/rpc\.portmap" $script` > 0 ){
            $result = 1;
          }
        }
      }
    }
    return( $self->{enabled} = $result );
  }

  sub is_running {
    my $self   = shift;
    main::verbose_dot();
    return( ($self->{installed}) ? $self->SUPER::is_running( "udp", 111 ) : 0 );
  }

  sub configure_screen {
    my $self = shift;

    main::header();

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg("portmap");
      return;
    }

    print <<"EOF";
portmap is the daemon that converts RPC Program numbers to DARPA protocol
port numbers.  This is needed for NFS and NIS to function properly.  The
clients will query the portmapper daemon to figure out which ports to connect
to for the various services.

EOF

    if( ! $self->{installed} ){
      print("\n\nThe portmap package is not installed!\n");
      print("\nPlease install it, and then come back here and enable it.\n");
      main::cont();
    }
    elsif( $self->{enabled} ){
	  my $run = ($self->{running}) ? " and running!" : "!";
      print("\n\nportmap is already enabled$run\n");
      main::cont();
    }
    else{
      my $answer = main::get_yn("Do you want to enable the portmap daemon");
      if( $answer eq "Y" ){
        $self->enable(); 
      }
    }
  }

  sub enable {
    my $self = shift;

    main::header();

    main::verbose_line("Step 1: Enabling the portmap daemon\n");

    #   figure out how to enable the daemon.
    #      Redhat           'chkconfig --level X portmap on'
    #      Mandrake 8.2     'chkconfig --level X portmap on'
    #      Debian           'update-rc.d portmap start 20 X .'
    #      SuSE 8.0         'insserv /etc/init.d/portmap,start=X'
    #      SuSE 7.3         'insserv /etc/init.d/portmap,start=X'
    #   
    main::verbose("Configuring portmap to start at boot time");
    main::verbose_close();

    my $level = Runlevel::get_configured_level();

    if( -x "/sbin/insserv" ){                              # SuSE system
      my $cmd = "insserv /etc/init.d/portmap,start=$level";
	  if ( ! system( $cmd ) ) {
	    $self->{enabled} = 1;
	  }
    }
    elsif( -x "/usr/sbin/update-rc.d" ){                   # Debian system
      # Debian starts portmap in runlevel S ... try to rebuild link if it's missing
	  my $cmd ="update-rc.d -f portmap start 41 S . stop 10 0 6 .";
	  if ( ! system( $cmd ) ) {
	    $self->{enabled} = 1;
	  }
    }
    elsif( -x "/sbin/chkconfig" ){                         # Redhat or Mandrake
      my $cmd = "chkconfig --level $level portmap on";
	  if ( ! system( $cmd ) ) {
	    $self->{enabled} = 1;
	  }
    }
    else{
      print("\nSorry, I don't know how to enable the portmap daemon ");
      print("on this system.\nYou will have to enable it manually.\n");
    }
    print "\n";
    main::cont();

  }  # sub enable
}  # package Portmapper

################################################################################
package Nfs;
our @ISA = ("Rpcservice");                     # Inherits from Rpcservice
################################################################################
{

  sub new {
    my $self = shift;
    my $name = shift;
    main::verbose("Checking nfs");
    my $obj = $self->SUPER::new($name);        # Call base class constructor

    $obj->{name} = "nfs";
    $obj->is_installed();                      # When we instantiate the object
    $obj->is_enabled();                        # we go out and figure out what
    $obj->is_running();                        # we can about it.
    main::verbose_close();
    return $obj;
  }

  sub is_installed {
    my $self    = shift;
    main::verbose_dot();
    my $result  = 0;
    #
    # NFS is actually multiple services (nfsd,mountd), so to determine
    # if it is installed and running, we should be checking all of the
    # services
    #
    if( $self->{installed} eq "" ){
      for my $i ( @nfsd_locs ){
        if( -x $i ){
          $result = 1;
          last;
        }
      }
    }
    return( $self->{installed} = $result );
  }

  sub is_enabled {
    my $self   = shift;
    main::verbose_dot();
    my $result = 0;
    if( $self->{installed} ){
      if ( -f "/etc/rc.config" ){
        $result = $self->suse_style_service( "NFS_SERVER", "nfs" );
      }
      elsif( $self->has_chkconfig() ){
        $result = system( "chkconfig nfs" ) ? 0 : 1;
      }
      else{
        #
        # See if we have a /etc/rc.d/rc.inet2 script.  If we do,
        # then most likely, it's a Slackware system
        #
        my $script = "/etc/rc.d/rc.inet2";
        if( -f $script ){
          my $c = `LANG=C egrep -c "^[[:space:]]*\/etc\/rc\.d\/rc\.nfsd" $script`;
          if( $c > 0 ){
            $result = 1;
          }
        }
        else{
          #
          # See if we have a /etc/init.d/rc script.  If we do,
          # then it's probably a Debian system.
          #
          if( -f "/etc/init.d/rc" ){
            my ( $junk, $rl ) = split /\s/, `LANG=C runlevel`;
            if( -f '/etc/rc' . $rl . '.d/S20nfs-kernel-server' ){
              $result = 1;
            }
          }
        }
      }
    }  # if installed

    return( $self->{enabled} = $result );

  }  # sub is_enabled

  sub is_running {
    my $self   = shift;
    main::verbose_dot();
    return( ( $self->{installed} ) ? $self->SUPER::is_running( "nfs" ) : 0 );
  }

  sub configure_screen {
    my $self = shift;

    main::header();

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg("nfsd");
      return;
    }

    print <<"EOF";
nfsd is the daemon that implements the user level part of the NFS service.

The main functionality of NFS is typically handled by a kernel module
called nfsd.o.

EOF

    if( ! $self->{installed} ){
      print("\n\nThe nfs package is not installed!\n");
      print("\nPlease install it, and then come back here and enable it.\n");
      main::cont();
    }
    elsif( $self->{running} ){
      print("\n\nnfsd is already enabled and running!\n");
      main::cont();
    }
    else{
      if( main::get_yn("Do you want to enable the nfs daemon") eq "Y" ){
        $self->enable(); 
        $self->is_enabled();
      }
    }
  }  # sub configure_screen

  sub enable {
    my $self = shift;

    main::header();

    print("Step 1: Enabling the nfs daemon\n\n");

    #   figure out how to enable the daemon.
    #      Redhat           'chkconfig --level X portmap on'
    #      Mandrake 8.2     'chkconfig --level X portmap on'
    #      Debian           'update-rc.d portmap start 20 X .'
    #      SuSE 8.0         'insserv /etc/init.d/portmap,start=X'
    #      SuSE 7.3         'insserv /etc/init.d/portmap,start=X'
    #   
    main::verbose("Configuring nfsd to start at boot time");
    main::verbose_close();

    my $level = Runlevel::get_configured_level();

    if( -x "/sbin/insserv" ){                              # SuSE system
      my $cmd = "insserv /etc/init.d/nfsserver,start=$level";
      system( $cmd );
    }
    elsif( -x "/usr/sbin/update-rc.d" ){                   # Debian system
      # Don't need to do anything, cuz Debian starts portmap in runlevel S
      print("\nIt seems this is a Debian system;\n"
           . "portmap is started in runlevel S, nothing to do.\n");
    }
    elsif( -x "/sbin/chkconfig" ){                         # Redhat or Mandrake
      my $cmd = "chkconfig --level $level nfs on";
      system( $cmd );
    }
    else{
      print("\nSorry, I don't know how to enable nfsd on this system\n");
      print("You will have to enable it manually\n");
    }

    main::cont();

  }  # sub enable
}  # package Nfs

################################################################################
package DisplayManager;
################################################################################
{
  my %files_edited;    # This little hash is used to keep track of the
                       # Xaccess and Xservers files that we have updated, so
                       # that we don't update the same file more than once
                       # when there are multiple kdmrc or xdm-config files found
                       # on the system.
  sub new {
    my $pkg  = shift;
    my $name = shift;
    my $attributes = {
        name       => $name,
        prog       => $name,
        installed  => "",
        enabled    => 0,
        running    => 0,
        notes      => "",
        current    => 0,
    };

    main::verbose_dot();

    return bless $attributes, $pkg ;
  }

  sub is_running {
    my $self   = shift;
    my $result = 0;
#    if( $self->{installed} ){
#      $result = ( ( substr `LANG=C pidof $self->{prog}`, 0, -1 ) eq "" ) ? 0 : 1;
#    }
    $self->{running} = $result;
    return $result;
  }

  sub clear_files_edited {
    my $self = shift;
    delete @files_edited{ keys %files_edited };    # Empty the hash
  }

  sub update_xaccess {
    my $self             = shift;
    my $xaccess_filename = shift;
    #
    # Update the Xaccess file
    #
    if( -f $xaccess_filename ){
      if( ! exists $files_edited{$xaccess_filename} ){
        main::verbose_line("Updating: $xaccess_filename");
        $files_edited{$xaccess_filename} = 1;
        open( FH, "<$xaccess_filename" )
          or die "Unable to open $xaccess_filename: $!";
        my @lines = <FH>;        # Suck the entire file into an array
        close( FH );
        my $found_asterisk = 0;

        for( my $i = 0; $i < @lines; $i++ ){
          my $line = $lines[$i];
          chomp($line);
          $line =~ s/#.*$//;       # Remove comments
          if( $line =~ /^\*\s*$/ ){
            $found_asterisk = 1;
          }
        }

        if( ! $found_asterisk ){
          open( FH, ">>$xaccess_filename" )
            or die "Unable to append to $xaccess_filename: $!";
          print FH "#\n# The following line was added by ltspcfg\n#\n";
          print FH "*    # Allow remote connects\n";
          close( FH );
        }
      }  # if we haven't already edited this Xaccess file
    }  # if Xaccess file exists
  }  # sub update_xaccess

  sub update_xservers {
    my $self              = shift;
    my $xservers_filename = shift;
    #
    # Optionally, update the Xservers file
    #
    if( Xdmcp::get_disable_console_x() eq "Y" ){
      if( -f $xservers_filename ){
        if( ! exists $files_edited{$xservers_filename} ){
          main::verbose_line("Updating: $xservers_filename");
          $files_edited{$xservers_filename} = 1;
          open( FH, "<$xservers_filename" )
            or die "Unable to open $xservers_filename; $!";
          my @lines = <FH>;
          close( FH );
          my $found_enabled_xserver = 0;
          for( my $i = 0; $i < @lines; $i++ ){
            if( $lines[$i] =~ /^:0/ ){
              $found_enabled_xserver = 1;
            }
          }
          if( $found_enabled_xserver ){
            open( FH, ">$xservers_filename" )
              or die "Unable to open $xservers_filename; $!";
            for( my $i = 0; $i < @lines; $i++ ){
              if( $lines[$i] =~ /^:0/ ){
                $lines[$i] =~ s/^:0/#:0/;             # Comment out the entry
              }
              print FH $lines[$i];
            }
            close( FH );
          }
        }  # if we haven't already edited this Xservers file
      }  # if ( -f xservers )
    }  # if get_disable_console_x()
  }  # sub update_xservers

}  # package DisplayManager

################################################################################
package Xdmcp;
our @ISA = ("Service");                        # Inherits from Service
################################################################################
#
# xdmcp is the X Display Manager Control Protocol.  In Linux, there are 3
# popular display managers, XDM, GDM and KDM.  Typically one or more of these
# will be installed on a system, and often, all three will exist.
#
{
  my @disp_mgrs;                        # Array of display managers
  my $disable_console_x = "";
  my @chk_active_fncs;                  # Array of funcion names for display manager checking
  
  sub new {
    my $self = shift;
    my $name = shift;
    main::verbose("Checking xdmcp");
    my $obj  = $self->SUPER::new($name);       # Call base class constructor

    $obj->{name}        = "xdmcp";
    $obj->{current_dm}  = "";

    #
    # We need to build a list of display managers
    # Currently, we only know about XDM, GDM and KDM
    #
    push @disp_mgrs, Xdm->new();
    push @disp_mgrs, Gdm->new();
    push @disp_mgrs, Kdm->new();

    $obj->get_active_dm();          # Of the possible display managers, figure
                                    # out which display manager is configured
                                    # for this server.

    my $dm_count = 0;
    my $dm_list  = "";

    my @tmp_dmgrs;
    my $using_msg = "none!";
    for( my $i = 0; $i < @disp_mgrs; $i++ ){
      if( $disp_mgrs[$i]->{installed} ){
        if($disp_mgrs[$i]->{current}){
          $using_msg = $disp_mgrs[$i]->{name};
	}
        push @tmp_dmgrs, $disp_mgrs[$i]->{name};
        $dm_count++;
      }
    }
    $dm_list = ( join ", ", @tmp_dmgrs ) . "    Using: " . $using_msg;

    $obj->{notes}     = $dm_list;

    $obj->{installed} = ( $dm_count ) ? 1 : 0;

    #
    # Now, figure out if the display manager is listening on port 177
    #
    $obj->is_enabled();
    $obj->is_running();

    #
    # Ok, display what we found
    #

    main::verbose( "Found: " . ( $dm_count == 0 ? "none!" : $dm_list ) );

    main::verbose_close();
    return $obj;

  }  # sub new

  sub is_enabled {
    my $self   = shift;
    main::verbose_dot();
    my $result = 0;           ## FIXME

    return( $self->{enabled} = $result );
  }

  sub is_running {
    my $self   = shift;
    my $result = 0;

    main::verbose_dot();

    if( $self->{installed} ){
      $result = $self->SUPER::is_running( "udp", 177 );
    }

    return( $self->{running} = $result );
  }

  sub show_notes {
    my $self  = shift;
    return $self->{notes};
  }

  sub get_active_dm {
    my $self   = shift;
    my $result = "";

    if( exists( $self->{active} ) && $self->{active} ){
      $result = $self->{active};
    }
    else{
      $result = $self->check_active_running() or
      $result = $self->check_active_prefdm()  or
      $result = $self->check_active_suse_dm() or
      $result = $self->check_active_debian_dm();
      
      $self->{active} = $result;

      for( my $i = 0; $i < @disp_mgrs; $i++ ){
        if( $disp_mgrs[$i]->{installed} ){
          if( $disp_mgrs[$i]->{name} eq $result ){
            $disp_mgrs[$i]->{current} = 1;
            $self->{current_dm} = $disp_mgrs[$i];
          }
        }
      }
    }

    return( $result );

  }  # get_active_dm

  sub check_active_running {
    #
    # This routine will attempt to figure out the current display manager
    # by looking for a currently running process.
    #
    my $self   = shift;
    my $result = "";
    my $pid    = 0;

    for( my $i = 0; $i < @disp_mgrs; $i++ ){
      if( $disp_mgrs[$i]->{installed} ){
        $pid = main::get_pid( $disp_mgrs[$i]->{name} );
        if( $pid > 0 ){
          $result = $disp_mgrs[$i]->{name};
          last;
        }
      }
    }
    return $result;
  }

  sub check_active_prefdm {
    #
    # This routine will attempt to figure out the current display manager
    # by looking for the prefdm script.  if it finds it, it will make a
    # modified copy of the script that will echo the name of the display 
    # manager, rather than execute it.
    #
    # Then, once the modified script has been created, we execute the
    # script, and it will (should) spit out the name of the display
    # manager.
    #
    my $self   = shift;
    my $result = "";

    if ( ! -x "/etc/X11/prefdm" ){
      return "";
    }

    my $tmp_prefdm;

    #
    # Generate a unique temporary filename
    #
    do {
      $tmp_prefdm = main::tmpnam();
    } until sysopen( TMP_PREFDM,
                     $tmp_prefdm,
                     ( main::O_RDWR | main::O_CREAT | main::O_EXCL ),
                     0600 );
    my $script = "/etc/X11/prefdm";

    open( PREFDM, "< $script") or die "Unable to open $script, error = $!\n";
    while( <PREFDM> ){
      my @fields = split;
      if( @fields > 0 ){
        if( $fields[0] eq "exec" ){
          my $target = $fields[2];
          $target =~ s/\`//;
          print( TMP_PREFDM "echo " . $target . "\nexit 1\n" );
        }
        else{
          print( TMP_PREFDM $_ );
        }
      }
    }

    close( PREFDM );
    close( TMP_PREFDM );

    $result = `LANG=C sh $tmp_prefdm`;
    unlink( $tmp_prefdm );
    chomp( $result );

    $result =~ s/^.*\///g;

    return( $result );

  }  # sub check_active_prefdm

  sub check_active_suse_dm {
    #
    # This routine will attempt to figure out the current display manager
    # by trying to see if this is a SuSE system, and if it is, it will
    # look at /etc/sysconfig/displaymanager and /etc/rc.d/xdm
    #
    my $self    = shift;

    #
    # If it doesn't look like a SuSE system, then exit this routine
    #
    if ( ! -f "/etc/sysconfig/displaymanager" and
         ! -f "/etc/rc.d/xdm" ){
      return "";
    }

    my $displaymanager = "";
    my $dmconfig = "/etc/sysconfig/displaymanager";
    open( FH, "< $dmconfig" ) or die "Unable to open $dmconfig: $!\n";
    while( <FH> ){
      if( /^DISPLAYMANAGER/ ){
        my ( $kwd, $dm ) = split /\s*=\s*/;
        chomp($dm);
        $dm              =~ s/\"//g;
        $displaymanager  = $dm;
        last;
      }
    }
    close( FH );
    if( grep /^$displaymanager$/, qw( kdm kde KDM KDE ) ){
      #
      # Look for kdm
      #
      for my $i ( @kdm_locs ){
        if( -x $i ){
          $displaymanager = $i;
        }
      }
    }
    elsif( grep /^$displaymanager$/, qw( gdm GDM Gnome GNOME ) ){
      #
      # If no kdm, then look for kdm
      #
      for my $i ( @gdm_locs ){
        if( -x $i ){
          $displaymanager = $i;
        }
      }
    }
    else{
      #
      # Finally, fall back to xdm
      #
      for my $i ( @xdm_locs ){
        if( -x $i ){
          $displaymanager = $i;
        }
      }
    }

    #
    # Now, whatever was detected above as the current display manager
    # needs to be tested, to make sure it exists, and is executable.
    # if not, then we fallback to trusty ole xdm.
    #

    if( ! -x $displaymanager ){
      $displaymanager = "";
    }

    $displaymanager =~ s/^.*\///g;
    return( $displaymanager );
  }  # sub check_active_suse_dm

  sub check_active_debian_dm {
    #
    # This routine will attempt to figure out the current display manager
    # by trying to see if this is a Debian system, and if it is, it will
    # look at /etc/X11/default-display-manager and /etc/rc.d/xdm
    #
    my $self    = shift;

    #
    # If it doesn't look like a Debian system, then exit this routine
    #
    if ( ! -f "/etc/X11/default-display-manager" and
         ! -f "/etc/init.d/xdm" ){
      return "";
    }

    my $displaymanager = "";
    my $dmconfig = "/etc/X11/default-display-manager";
    open( FH, "< $dmconfig" ) or die "Unable to open $dmconfig: $!\n";
    while( <FH> ){
      if( /^\// ){
        chomp;
        $displaymanager  = $_;
        last;
      }
    }
    close( FH );
    
    if( grep /^$displaymanager$/, qw( kdm kde KDM KDE ) ){
      #
      # Look for kdm
      #
      for my $i ( @kdm_locs ){
        if( -x $i ){
          $displaymanager = $i;
        }
      }
    }
    elsif( grep /^$displaymanager$/, qw( gdm GDM Gnome GNOME ) ){
      #
      # If no kdm, then look for kdm
      #
      for my $i ( @gdm_locs ){
        if( -x $i ){
          $displaymanager = $i;
        }
      }
    }
    else{
      #
      # Finally, fall back to xdm
      #
      for my $i ( @xdm_locs ){
        if( -x $i ){
          $displaymanager = $i;
        }
      }
    }
    
    #
    # Now, whatever was detected above as the current display manager
    # needs to be tested, to make sure it exists, and is executable.
    # if not, then we fallback to trusty ole xdm.
    #
    if( ! -x $displaymanager ){
      $displaymanager = "";
    }
    $displaymanager =~ s/^.*\///g ;
    return( $displaymanager );
  }  # sub check_active_debian_dm
  
  sub configure_screen {
    my $self = shift;

    main::header();

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg("Xdmcp");
      return;
    }

    print(  "Xdmcp is the protocol used by a display manager to present\n"
          . "a login dialog box on the workstation.\n");

    if( ! $self->{installed} ){
      print("\n\nThe Xdmcp service is not installed!\n");
      print("\nPlease install it, and then come back here and enable it.\n");
    }
    elsif( $self->{running} ){
      print("\n\nThe Xdmcp service is already enabled and running!\n");
    }
    else{
      if(main::get_yn( "Do you want to enable "
                     . $self->get_active_dm()) eq "Y") {
        print <<"EOF";

Normally, when a display manager is running, it will launch an X server on
the console screen, giving you a graphical login on the console as well as
on the workstations.  Some people prefer to keep their server in character
mode.
EOF
        $disable_console_x = main::get_yn(
                  "Do you want to disable the graphical login on the server",
                  main::get_conf_value( "DISABLE_CONSOLE_X" ) );

        main::set_conf_value( "DISABLE_CONSOLE_X", $disable_console_x );

        $self->{current_dm}->configure();
      }
    }
    main::cont();
  }  # sub configure_screen

  sub get_disable_console_x {
    return( $disable_console_x );
  }

}  # package Xdmcp

################################################################################
package Xdm;
our @ISA = ("DisplayManager");                 # Inherits from DisplayManager
################################################################################
{
  sub new {
    my $self = shift;
    my $obj  = $self->SUPER::new("xdm");       # Call base class constructor

    $obj->is_installed();                      # When we instantiate the object
    $obj->is_enabled();                        # we go out and figure out what
    $obj->is_running();                        # we can about it.

    return $obj;
  }

  sub is_installed {
    my $self    = shift;
    main::verbose_dot();
    my $result  = 0;
    if( $self->{installed} eq "" ){
      for my $i ( @xdm_locs ){
        if( -x $i ){
          $result = 1;
          last;
        }
      }
    }
    return( $self->{installed} = $result );
  }

  sub is_enabled {
    my $self   = shift;
    main::verbose_dot();
    my $result = 0;            ## FIXME

    return( $self->{enabled} = $result );
  }

  sub configure {
    #
    # Configuring XDM to allow remote connections involves the
    # following settings:
    #
    #   1) The display manager needs to listen on port 177 for
    #      remote connections.  This is usually done by finding
    #      the 'DisplayManager.requestPort' entry in the
    #      /etc/X11/xdm/xdm-config file, and commenting out that
    #      line.
    #
    #   2) The connections have to actually be allowed.
    #      This is usually controlled by the 'Xaccess' file.
    #      Within the xdm-config file, the 'DisplayManager.accessFile'
    #      entry points to the full pathname of the Xaccess file.
    #      If not found, we should probably default to the
    #      '/etc/X11/xdm/Xaccess' file.
    #
    #   3) We may need to disable the local X server from
    #      launching.  This is controlled by the 'Xservers'
    #      file.  Within the xdm-config file, the
    #      'DisplayManager.servers' entry points to the full
    #      pathname to the Xservers file.  If not found, we
    #      should probably default to the '/etc/X11/xdm/Xservers'
    #      file.
    #

    my $self = shift;

    print("\nConfiguring Xdm\n");

    $self->clear_files_edited();

    for my $xdm_config ( "/etc/X11/xdm/xdm-config",) {
      if( -f $xdm_config && ! -l $xdm_config ){    # We are only interested in
                                                   # real files, not symlinks
        main::verbose_line("Updating: $xdm_config");
        open( FH, "+<$xdm_config") or die "Unable to open $xdm_config: $!";

        my @lines = <FH>;           # Suck the entire file into an array
        truncate( FH, 0 );          # Truncate the file to empty it
        seek( FH, 0, 0 );           # position file pointer to the beginning
        my $xdmcp_enabled      = 0;
        my $console_x_disabled = 0;
        my $section            = "";
        my $xaccess_filename   = "/etc/X11/xdm/Xaccess";    # Set a reasonable
                                                            # default
        my $xservers_filename  = "/etc/X11/xdm/Xservers";

        for( my $i = 0; $i < @lines; $i++ ){
          if( $lines[$i] =~ /^DisplayManager\.requestPort/ ){
            $lines[$i] =~ s/^Display/# Display/;     # Comment out the entry
          }
          elsif( $lines[$i] =~ /^DisplayManager\.accessFile/ ){
            my $line = $lines[$i];
            chomp($line);
            $line =~ s/DisplayManager\.accessFile:\s*//;
            $line =~ s/\s*$//;                       # trim trailing whitespace
            if( $line ne "" ){
              $xaccess_filename = $line;
            }
          }
          elsif( $lines[$i] =~ /^DisplayManager\.servers/ ){
            my $line = $lines[$i];
            chomp($line);
            $line =~ s/DisplayManager\.servers:\s*//;
            $line =~ s/\s*$//;                       # trim trailing whitespace
            if( $line ne "" ){
              $xservers_filename = $line;
            }
          }
          print( FH $lines[$i] );
        }
        close( FH );

        $self->update_xaccess(  $xaccess_filename  );
        $self->update_xservers( $xservers_filename );

      }  # if( -f .... -l ... )
    }  # 'for loop'
  }  # sub configure
}  # package Xdm

################################################################################
package Gdm;
our @ISA = ("DisplayManager");                 # Inherits from DisplayManager
################################################################################
{
  sub new {
    my $self = shift;
    my $obj  = $self->SUPER::new("gdm");       # Call base class constructor

    $obj->is_installed();                      # When we instantiate the object
    $obj->is_enabled();                        # we go out and figure out what
    $obj->is_running();                        # we can about it.

    return $obj;
  }

  sub is_installed {
    my $self    = shift;
    main::verbose_dot();
    my $result  = 0;
    if( $self->{installed} eq "" ){
      for my $i ( @gdm_locs ){
        if( -x $i ){
          $result = 1;
          last;
        }
      }
    }
    return( $self->{installed} = $result );
  }

  sub is_enabled {
    my $self   = shift;
    main::verbose_dot();

    my $result = 0;            ## FIXME

    return( $self->{enabled} = $result );
  }

  sub configure {
    #
    # Configuring GDM involves modifying the gdm.conf file
    # to allow remote workstations to connect on port 177.
    # It is usually disabled by default.
    #
    # Older versions of GDM use Enable=[0|1]
    # Newer versions of GDM use Enable=[false|true]
    #

    my $self = shift;

    print("\nConfiguring Gdm\n");

    for my $gdmdotconf ( @gdm_conf_locs ){
      if( -f $gdmdotconf ){
        main::verbose_line("Updating: $gdmdotconf");
        open( FH, "+<$gdmdotconf") or die "Unable to open $gdmdotconf: $!";
        my @lines = <FH>;           # Suck the entire file into an array
        truncate( FH, 0 );          # Truncate the file to empty it
        seek( FH, 0, 0 );           # position file pointer to the beginning
        my $xdmcp_enabled      = 0;
        my $console_x_disabled = 0;
        my $section            = "";
        for( my $i = 0; $i < @lines; $i++ ){
          if( $lines[$i] =~ /^\[/ ){
            if( $lines[$i] =~ /^\[xdmcp\]/ ){
              $section = "xdmcp";
            }
            elsif( $lines[$i] =~ /^\[servers\]/ ){
              $section = "servers";
            }
            else{
              $section = "";
            }
          }

          if( $section eq "xdmcp" ){
            if( ! $xdmcp_enabled ){
              if( $lines[$i] =~ /^Enable=/ ){
                my $line = $lines[$i];
                chomp($line);              # get rid of the trailing newline
                $line          =~ s/^Enable=//;
                $lines[$i]     = "Enable=true\n" if $line eq "false";
                $lines[$i]     = "Enable=1\n"    if $line eq "0";
                $xdmcp_enabled = 1;
              }
            }
          }
          elsif( $section eq "servers"
              && Xdmcp::get_disable_console_x() eq "Y" ){
            if( ! $console_x_disabled ){
              if( $lines[$i] =~ /^0/ ){
                $lines[$i] = "#\n"
                           . "# Next line disabled by ltspcfg\n"
                           . "# If you want to re-enable X on the console, "
                           . "then un-comment that line\n"
                           . "#\n"
                           . "# " . $lines[$i]
                           . "#\n";
                $console_x_disabled = 1;
              }
            }
          }
          print( FH $lines[$i] );
        }
        close( FH );

      }  # if -f gdm.conf
    }  # for loop
  }  # sub configure
}  # package Gdm

################################################################################
package Kdm;
our @ISA = ("DisplayManager");                 # Inherits from DisplayManager
################################################################################
{
  sub new {
    my $self  = shift;
    my $obj   = $self->SUPER::new("kdm");      # Call base class constructor

    $obj->is_installed();                      # When we instantiate the object
    $obj->is_enabled();                        # we go out and figure out what
    $obj->is_running();                        # we can about it.

    return $obj;
  }

  sub is_installed {
    my $self    = shift;
    main::verbose_dot();
    my $result  = 0;
    if( $self->{installed} eq "" ){
      for my $i ( @kdm_locs ){
        if( -x $i ){
          $result = 1;
          last;
        }
      }
    }
    return( $self->{installed} = $result );
  }

  sub is_enabled {
    my $self   = shift;
    main::verbose_dot();

    my $result = 0;            ## FIXME

    return( $self->{enabled} = $result );
  }

  sub configure {
    #
    # The kdmrc file can be in one of several places, depending
    # on the distro and the version.  Plus, there may be several
    # symbolic links to the kdmrc file, which we have to be aware
    # of, so that we don't try editing the same file more than once.
    #
    # There are 3 things we need to deal with:
    #
    #   1) The display manager needs to listen on port 177 for
    #      remote connections.  This is usually done by setting
    #      the 'Enable' parameter to 'true'.
    #
    #   2) The connections have to actually be allowed.
    #      This is usually controlled by the 'Xaccess' file.
    #      Within the '[Xdmcp]' section of the kdmrc file
    #      there should be an 'Xaccess' entry that contains
    #      the complete pathname to the Xaccess file.  If
    #      not found, we should probably default to the
    #      '/etc/X11/xdm/Xaccess' file.
    #
    #   3) We may need to disable the local X server from
    #      launching.  This is controlled by the 'Xservers'
    #      file.  Within the '[General]' section, there
    #      should be 'Xservers' entry that contains the
    #      complete pathname to the Xservers file.  If
    #      not found, we should probably default to the
    #      '/etc/X11/xdm/Xservers' file.
    #

    my $self = shift;
    my %files_edited;

    print("\nConfiguring Kdm\n");

    $self->clear_files_edited();

    for my $kdmrc ( @kdmrc_locs ){
      if( -f $kdmrc && ! -l $kdmrc ){    # We are only interested in real
                                         # files, not symlinks
        main::verbose_line("Updating: $kdmrc");
        open( FH, "+<$kdmrc") or die "Unable to open $kdmrc: $!";

        my @lines = <FH>;           # Suck the entire file into an array
        truncate( FH, 0 );          # Truncate the file to empty it
        seek( FH, 0, 0 );           # position file pointer to the beginning
        my $xdmcp_enabled      = 0;
        my $console_x_disabled = 0;
        my $section            = "";
        my $xaccess_filename   = "/etc/X11/xdm/Xaccess";    # Set a reasonable
                                                            # default
        my $xservers_filename  = "/etc/X11/xdm/Xservers";

        for( my $i = 0; $i < @lines; $i++ ){

          if( $lines[$i] =~ /^\[/ ){
            $section = "";  # Start by assuming we don't know the section
            $section = "xdmcp"   if $lines[$i] =~ /^\[Xdmcp\]/;
            $section = "general" if $lines[$i] =~ /^\[General\]/;
          }

          if( $section eq "xdmcp" ){
            if( ! $xdmcp_enabled ){
              if( $lines[$i] =~ /^Enable=/ ){
                my $line = $lines[$i];
                chomp($line);              # get rid of the trailing newline
                $line          =~ s/^Enable=//;
                $lines[$i]     = "Enable=true\n" if $line eq "false";
                $xdmcp_enabled = 1;
              }
            }
            if( $lines[$i] =~ /^\s*Xaccess\s*=/ ){
              my $line = $lines[$i];
              chomp($line);
              $line =~ s/^\s*Xaccess\s*=\s*//;
              $xaccess_filename = $line;
            }
          }
          elsif( $section eq "general" ){
            if( $lines[$i] =~ /^\s*Xservers\s*=/ ){
              my $line = $lines[$i];
              chomp($line);
              $line =~ s/^\s*Xservers\s*=\s*//;
              $xservers_filename = $line;
            }
          }
          print( FH $lines[$i] );
        }
        close( FH );

        $self->update_xaccess ( $xaccess_filename  );
        $self->update_xservers( $xservers_filename );

      }  # if( -f .... -l ... )
    }  # 'for loop'
  }  # sub configure
}  # package Kdm


################################################################################
package File;
################################################################################
{
  my @files;

  sub new {
    my $pkg  = shift;
    my $name = shift;
    my $attributes = {
        name        => "",
        filename    => "",
        configured  => 0,
    };
    push @files, $attributes;            # Add the object to the array
    return ( bless $attributes, $pkg );
  }

  sub list_files {
    my $self = shift;

    print(   main::highlight("File                               ")
           . " "
           . main::highlight("Configured ")
           . " "
           . main::highlight("Notes" . " " x 27 )
           . "\n");

    for( my $i = 0; $i < @files; $i++ ){
      $files[$i]->print();
    }
    printf("\n");
  }

  sub print {
    my $self = shift;
    
    printf("%-35s %-11s %s\n",
            $self->{filename},
            yes_no_na( $self->{configured} ),
            $self->show_notes() );
  }

  sub yes_no_na {
    my $flag   = shift;
    my $result = "";

    if( $flag == 0 ){
      $result = "no";
    }
    elsif( $flag == 1 ){
      $result = "Yes";
    }
    elsif( $flag == -1 ){
      $result = "-";
    }
    return $result;
  }

  sub show_notes {
    my $self = shift;
    return "";
  }

  sub configure {
    my $self     = shift;
    my $filename = shift;
    
    my $file = "";
    for my $i ( @files ){
      if( $i->{name} eq $filename ){
        $file = $i;
      }
    }

    if( $file eq "" ){
      die( "Internal error, couldn't locate file: $filename\n");
    }

    $file->configure_screen();
  }

  sub configure_screen {
    my $self = shift;
    printf( "\nThe configuration screen for "
           . $self->{name} . " has not been written yet\n");
    main::cont();
  }
        
  sub cleanup {
    my $self = shift;
    local (*FH) = shift;  
    main::verbose_dot();
    if( ! $self->{configured} ){
    	return;
    }
    #
    # Remove everything between '## LTSP-begin ##' and '## LTSP-end ##'
    #
    if( -f $self->{filename} ){
      my $filename = $self->{filename};
      
	  main::backup($filename);
      my @lines = <FH>;                  # Suck the entire file into an array
      truncate( FH, 0 );                 # Truncate it, to empty it
      seek( FH, 0, 0 );                  # Position file pointer to beginning
      my $in_lts_sect = 0;
      
      for( my $i=0; $i<@lines; ++$i ){
        if( $lines[$i] =~ /## LTSP-begin ##/ ){
          $in_lts_sect = 1;
          next;
        }
        if( $lines[$i] =~ /## LTSP-end ##/ ){
          $in_lts_sect = 0;
          next;
        }
        if( ! $in_lts_sect ){
          print FH $lines[$i];
        }
      }

      $self->{configured} = 0;
    } # if( -f $self->{filename} )
    
	return($self->{configured});
  } # sub cleanup

  sub is_configured {
    my $self    = shift;
    main::verbose_dot();
    if( ! $self->{configured} ){
      #
      # Look at the file to see if it contains
      # entries with  '## LTSP-begin ##' and '## LTSP-end ##'
      #
      if( -f $self->{filename} ){
        if( `LANG=C grep "## LTSP-begin ##" $self->{filename} ` ){
          $self->{configured} = 1;
        }
      }
    }
    return($self->{configured});
  }

  sub ltsp_conf_header {
    my $today = POSIX::strftime('%a %b %e %H:%M:%S %Y', localtime());
    my $str = "## LTSP-begin ##\n"
            . "#\n"
            . "# The lines between 'LTSP-begin' and 'LTSP-end' were added\n"
            . "# on: $today, by the ltspcfg configuration tool.\n"
            . "# For more information, visit the LTSP homepage\n"
            . "# at http://www.LTSP.org\n"
            . "#\n"
            . "\n";

    return($str);
  }

  sub ltsp_conf_footer {
    my $str = "\n## LTSP-end ##\n";
    return($str);
  }

}

################################################################################
package HostsFile;
our @ISA = ("File");				# Inherits from File
################################################################################
{
  sub new {
    my $self = shift;
    main::verbose("Checking /etc/hosts");
    my $obj = $self->SUPER::new("hosts");
    $obj->{name}     = "hosts";
    $obj->{filename} = "/etc/hosts";

    $obj->is_configured();

    main::verbose_close();
    return($obj);
  }

  sub configure_screen {
    my $self = shift;

    main::header();

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg("/etc/hosts");
      return;
    }

    my $filename = $self->{filename};
    my $again = "";
	
    open( FH, "+<$filename") or die "Unable to open $filename: $!";
    
    if ( $self->{configured} ) {
      print("$filename has been already configured by LTSP.\n");

      my $answer = main::get_yn("Do you want to remove every LTSP entry from $filename");

      if( $answer eq "Y" ){
        $self->cleanup( *FH );
        printf("\nFinished removing $filename entries.\n\n");
        main::cont();
        close( FH );
        return;
      }
      else{
        printf("\n$filename entries NOT removed.\n\n");
      }
      $again = " again";
    }
	
    print("It is important that several services running on the server\n");
    print("are able to map an IP address back to a hostname.  This is\n");
    print("typically referred to as \"reverse mapping\".\n");
    print("\n");
    print("There are 2 common ways to achieve this:\n");
    print("\n");
    print("  1) Entries in /etc/hosts for each workstation.\n");
    print("\n");
    print("  2) Reverse mapping entries in DNS.\n");
    print("\n");
    print("If you have (or will) setup your DNS server to do the proper\n");
    print("reverse mapping for each workstation, you can skip this\n");
    print("configuration step.  Otherwise, it is recommended that you add\n");
    print("entries to the /etc/hosts file for each workstation.\n");

    my $answer = main::get_yn("Do you want to add entries to $filename$again");

    if( $answer eq "Y" ){
	  
      main::backup($filename);
      if ( $self->{configured} ) {
        $self->cleanup( *FH );
      }
      my $server_addr = Interfaces->get_primary_addr();
      my $net_addr    = Interfaces->get_primary_network(); 
      
      my @octets = split /\./, $net_addr;
      
      seek( FH, 0, 2);         # append to file
      print( FH $self->ltsp_conf_header() );

      for( my $host = 1; $host < 255; $host++ ){
        my $addr = join ( ".", ( @octets[0 .. 2], $host ) );
        my $name;
        if( $addr ne $server_addr ){
          $name = sprintf("ws%03d",$host);
          printf( FH "%-15s\t\t%s\t%s\n", $addr, $name . ".ltsp", $name );
        }
      }
      print( FH $self->ltsp_conf_footer() );

      printf("\nFinished adding $filename entries.\n\n");
	  $self->{configured} = 1;
    }
    else{
      printf("\n$filename entries NOT added.\n\n");
    }
    close( FH );
    main::cont();
  }  # sub configure_screen
}

################################################################################
package HostsAllowFile;
our @ISA = ("File");				# Inherits from File
################################################################################
{
  sub new {
    my $self = shift;
    main::verbose("Checking /etc/hosts.allow");
    my $obj = $self->SUPER::new("HostsAllow");
    $obj->{name}     = "HostsAllow";
    $obj->{filename} = "/etc/hosts.allow";

    $obj->is_configured();

    main::verbose_close();
    return($obj);
  }

  sub configure_screen {
    my $self = shift;

    main::header();

    my $filename = $self->{filename};
    my $again = "";
	
    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg($filename);
      return;
    }

    open( FH, "+<$filename") or die "Unable to open $filename: $!";
    
    if ( $self->{configured} ) {
      print("$filename has been already configured by LTSP.\n");
	  
      my $answer = main::get_yn("Do you want to remove every LTSP entry from $filename");

      if( $answer eq "Y" ){
	  	$self->cleanup( *FH );
        printf("\nFinished removing $filename entries.\n\n");
        close( FH );
        main::cont();
		return;
      }
      else{
        printf("\n$filename entries NOT removed.\n\n");
      }
      $again = " again";
	}

    print("Some services, such as dhcpd, tftpd and portmap use a security feature\n");
    print("called 'tcpwrappers'.  This feature restricts connections from any\n");
    print("host addresses specified in /etc/hosts.deny, and allows connections\n");
    print("from host addresses specified in /etc/hosts.allow.\n");
    print("\n");
    print("ltspcfg can add the necessary entries to $filename for you.\n");

    my $answer = main::get_yn("Do you want to add entries to $filename$again");
    if( $answer eq "Y" ){
      main::backup($filename);
      if ( $self->{configured} ) {
	    $self->cleanup( *FH );
      }
      my $server_addr = Interfaces->get_primary_addr();
      my $net_addr    = Interfaces->get_primary_network(); 
      
      my @octets = split /\./, $net_addr;

	  seek( FH, 0, 2);         # append to file
      print( FH $self->ltsp_conf_header() );

      printf( FH "bootpd:     0.0.0.0\n");
      printf( FH "in.tftpd:   %s.\n", join( ".", @octets[0 .. 2] ));
      printf( FH "portmap:    %s.\n", join( ".", @octets[0 .. 2] ));

      print( FH $self->ltsp_conf_footer() );

      printf("\nFinished adding $filename entries.\n\n");
	  $self->{configured} = 1;
    }
    else{
      printf("\n$filename entries NOT added.\n\n");
    }
    close( FH );
    main::cont();
  }  # sub configure_screen
}

################################################################################
package ExportsFile;
our @ISA = ("File");				# Inherits from File
################################################################################
{
  sub new {
    my $self = shift;
    main::verbose("Checking /etc/exports");
    my $obj = $self->SUPER::new("Exports");
    $obj->{name}     = "Exports";
    $obj->{filename} = "/etc/exports";

    $obj->is_configured();

    main::verbose_close();
    return($obj);
  }

  sub configure_screen {
    my $self = shift;
    my $filename = $self->{filename};
    my $again = "";
    
	main::header();

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg($filename);
      return;
    }

    open( FH, "+<$filename") or die "Unable to open $filename: $!";
    
	if ( $self->{configured} ) {
      print("$filename has been already configured by LTSP.\n");
	  
	  my $answer = main::get_yn("Do you want to remove every LTSP entry from $filename?");

      if( $answer eq "Y" ){
	  	$self->cleanup( *FH );
        printf("\nFinished removing $filename entries.\n\n");
        close( FH );
        main::cont();
		return;
      }
      else{
        printf("\n$filename entries NOT removed.\n\n");
      }
      $again = " again";
	}

    print("To tell the NFS system which directories to make available, entries\n");
    print("must exist in /etc/exports, for each directory.  With each entry, is\n");
    print("information about which machines are allowed to access the directory,\n");
    print("and what permissions they will have.\n");
    print("\n");
    print("ltspcfg can add the necessary entries to $filename for you.\n");

    my $answer = main::get_yn("Do you want to add entries to $filename$again?");
    if( $answer eq "Y" ){
      main::backup($filename);
      if ( $self->{configured} ) {
	    $self->cleanup( *FH );
      }

      my $server_addr = Interfaces->get_primary_addr();
      my $net_addr    = Interfaces->get_primary_network(); 
      my $netmask     = Interfaces->get_primary_netmask();
      
      my @octets = split /\./, $net_addr;

	  seek( FH, 0, 2);         # append to file
      print( FH $self->ltsp_conf_header() );

      printf( FH "%-25s %s\n", $ltsp_dir,
                               "$net_addr/$netmask(ro,no_root_squash,sync)" );

      printf( FH "%-25s %s\n", "/var/opt/ltsp/swapfiles",
                               "$net_addr/$netmask(rw,no_root_squash,async)" );

      print( FH $self->ltsp_conf_footer() );

      printf("\nFinished adding $filename entries.\n\n");
	  $self->{configured} = 1;
    }
    else{
      printf("\n$filename entries NOT added.\n\n");
    }
    close( FH );
    main::cont();
  }  # sub configure_screen
}

################################################################################
package LtsConfFile;
our @ISA = ("File");				# Inherits from File
################################################################################
{
  sub new {
    my $self = shift;
    main::verbose("Checking lts.conf");
    my $obj = $self->SUPER::new("LtsConf");
    $obj->{name}     = "LtsConf";
    $obj->{filename} = "$ltsp_dir/i386/etc/lts.conf";

    $obj->is_configured();

    main::verbose_close();
    return($obj);
  }

  sub configure_screen {
    my $self = shift;

    main::header();

    my $filename = $self->{filename};

    if( Interfaces::get_primary_name() eq "" ){
      Interfaces::need_primary_msg("$filename");
      return;
    }

    my $answer;

    if( -f $filename ){
      printf("\n");
      printf("The $filename file already exists!\n");
      $answer = main::get_yn("Are you sure you want to overwrite it");
    }
    else{
      $answer = main::get_yn("Do you want to create a default lts.conf file");
    }

    if( $answer eq "Y" ){
      main::backup($filename);
      my $server_addr = Interfaces->get_primary_addr();
      open( FH, ">$filename") or die "Unable to open $filename: $!";

      print( FH "#\n");
      print( FH "# Copyright (c) 2003 by James A. McQuillan (McQuillan Systems, LLC)\n");
      print( FH "#\n");
      print( FH "# This software is licensed under the Gnu General Public License.\n");
      print( FH "# The full text of which can be found at http://www.LTSP.org/license.txt\n");
      print( FH "#\n");
      print( FH "#\n");
      print( FH "# Config file for the Linux Terminal Server Project (www.ltsp.org)\n");
      print( FH "#\n");
      print( FH "\n");
      print( FH "[Default]\n");
      print( FH "        SERVER             = $server_addr\n");
      print( FH "        XSERVER            = auto\n");
      print( FH "        X_MOUSE_PROTOCOL   = \"PS/2\"\n");
      print( FH "        X_MOUSE_DEVICE     = \"/dev/psaux\"\n");
      print( FH "        X_MOUSE_RESOLUTION = 400\n");
      print( FH "        X_MOUSE_BUTTONS    = 3\n");
      print( FH "        USE_XFS            = N\n");
      print( FH "        SCREEN_01          = startx\n");

      printf("\nFinished adding $filename entries.\n\n");
	  $self->{configured} = 1;
    }
    else{
      printf("\nNothing done on $filename.\n\n");
    }
    close( FH );
    main::cont();
  }  # sub configure_screen

  sub is_configured {
    my $self    = shift;
    main::verbose_dot();
    if( ! $self->{configured} ){
      if( $self->{filename} ){
        $self->{configured} = 1;
      }
    }
    return($self->{configured});
  }
}

################################################################################
package main;
################################################################################

my $answer;
my $nLine;

#
# Some screen attibutes, so we can make the screen look a bit better
#
my $B        = `LANG=C echo -e "smul\nbold" | tput -S`; # Bold
my $N        = `LANG=C tput sgr0`;                      # Normal
my $R        = `LANG=C tput rev`;                       # Reverse

my $clear    = "${N}" . `LANG=C tput clear`;            # Clear Screen
my $cols     = `LANG=C tput cols`;

my $progname = "ltspcfg";
my $ltitle   = $progname . " v" . $version;
my $rtitle   = "The Linux Terminal Server Project (http://www.LTSP.org)";
my $hdr      = sprintf("%s%*s%s",
                       $ltitle,
                       $cols - ( length($ltitle) + length($rtitle) ),
                       "",
                       $rtitle);

sub bold    { return $B; }
sub normal  { return $N; }
sub reverse { return $R; }

sub highlight { my $str = shift;
                return sprintf( $R . $str . $N );
}

sub clear {
  main::print_info() || print "$clear";
  $nLine = 1;
}

sub newline {
  my $cnt = 1;
  if( ($#_ + 1 ) > 0 ){
    $cnt = int($_[0]);
  }
  while( $cnt > 0 ){
    print "\n";
    $nLine++;
    $cnt--;
  }
}

sub header {
  clear();
  main::print_info() ? print "$ltitle" : print $hdr;
  newline(2);
}

sub cont {
  print("Press <enter> to continue.. ");
  <STDIN>;
}

sub return_to_main_menu {
  print("Press <enter> to return to the main menu... ");
  <STDIN>;
}

sub get_yn {
  my $prompt         = shift;
  my $default        = "";
  my $default_prompt = "";
  if( @_ > 0 ){
    $default = shift;
    $default_prompt  = "[$default]";
  }
  my $reply  = "";
  while( $reply ne "Y" && $reply ne "N" ){
    print "\n$prompt (y/n) ${default_prompt}? ";
    $reply = <STDIN>;
    chomp($reply);                      # Remove the trailing newline
    $reply =~ tr/[a-z]/[A-Z]/;          # Convert the reply to upper case
    if( $reply eq "" ){
      $reply = $default;
    }
  }
  return( $reply );
}

sub verbose {
  printf("%s", shift ) unless main::quiet();
}

sub verbose_close {
  print("\n") unless main::quiet();
}

sub verbose_dot {
  print(".") unless main::quiet();
}

sub verbose_line {
  #
  # Print the verbose message followed by a newline
  #
  main::verbose( shift );
  main::verbose_close();
}

sub log_it {
  my $log_rec = shift;

  my $cur_time = strftime "%b %e %H:%M:%S", gmtime;

  my $logfile = "/var/log/ltspcfg";
  open( FH, ">>$logfile" ) or die "Couldn't create the $logfile: $!";
  print FH $cur_time . " " . $log_rec;
  close( FH );
}

sub get_conf_value {
  my $var = shift;
  my $val = "";
  if( @_ > 0 ){
    $val  = shift;
  }
  my $config_file = "/etc/ltsp.conf";

  if ( -f "$config_file" ){
    open FH, "<$config_file" or die "Couldn't open $config_file: $!";
    while(<FH>){
      if( /^\s*${var}\s*=/ ){
        s/^\s*${var}\s*=\s*//;
        my @fields = split /\s+/;
        $val = $fields[0];
      }
    }
    close(FH);
  }
  return($val);
}

sub set_conf_value {

  my $var = shift;
  my $val = shift;

  my $filename = "/etc/ltsp.conf";
  my $match = 0;

  if( -f "$filename" ){
    #
    # If the file already exists, we open it for updating
    #
    open( FH, "+<$filename") or die "Unable to open $filename: $!";
    my @lines = <FH>;              # Suck the entire file into an array
    truncate( FH, 0 );             # Truncate the file, to empty it
    seek( FH, 0, 0 );              # Position file pointer to the beginning

    for( my $i = 0; $i < @lines; $i++ ){
      if( $lines[$i] =~ /^\s*${var}/ ){
        printf FH "%s=%s\n", ${var}, ${val};
        $match = 1;
      }
      else{
        print FH $lines[$i];
      }
    }
  }
  else{
    #
    # If the file didn't already exist, we open it to create it
    #
    open( FH, ">$filename" ) or die "Unable to create $filename: $!";
    print FH  ":\n#\n# Configuration variables for LTSP\n#\n\n";
  }
  if( ! $match ){
    printf FH "%s=%s\n", ${var}, ${val};
  }
  close(FH);
}

sub get_pid {
  my $process = shift;
  my ( $pid );
  $pid = `LANG=C pidof $process`;
  $pid =~ s/[^\d]//g;
  return( ( $pid eq "" ) ? 0 : int( $pid ) );
}

sub backup {
  my $file = shift;
  if( -f $file ){
    my $num  = 1;
    while( 1 ){
      my $backup_file = sprintf( "%s.sv%d", $file, $num );
      if( ! -f $backup_file ){
        system("cp $file $backup_file");
        return;
      }
      $num++;
    }
  }
}

sub main_screen {
  show_menu ( [ "S", "Show the status of all services", \&status_screen  ],
              [ "C", "Configure the services manually", \&configure_menu ],
##              [ "W", "Run the setup wizard",            \&run_wizard     ],
              [ "",  "",                                ""               ],
              [ "Q", "Quit",                            "quit"           ] );
}

sub show_menu {

  my $done = 0;

  while ( ! $done ){
    my($answer,$arr);
    header();

    for $arr ( @_ ){
      my( $choice, $text, $func ) = @$arr;
      if( $choice eq "" ){
        print("\n");
      }
      else{
        print( "  "
               . "${B}"
               . $choice
               . "${N}"
               . " - "
               . $text
               . "\n" );
      }
    }

    print("\nMake a selection: ");
        
    $answer = <STDIN>;

    #
    # clean up the answer by removing the trailing newline,
    # remove spaces, convert to lower case, and check for
    # empty answer.
    #
    chomp $answer;
    $answer =~ s/ //g;

    next if $answer eq "";

    $answer = lc $answer;

    my $match  = 0;

    for $arr ( @_ ){
      my( $choice,$text, $func ) = @$arr;
      if( $answer eq lc($choice) ){
        if( $func eq "quit" ){
          exit;
        }
	elsif( $func eq "back" ){
	  $done = 1;
        }	  
        else{
          $func->();
        }
        $match = 1;
        last;
      }
    }

    if( ! $match ){
      print("\aInvalid Answer!\n");
      sleep(1);
    }
  }
  return;
}

sub status_screen {
  header();
  #
  # Print the stuff we know
  #
  Interfaces->print();
  Service->list_services();
  File->list_files();
  Runlevel->print();
  printf("Installation dir...: %s\n\n", $ltsp_dir);

  main::return_to_main_menu() unless main::print_info();
}

sub run_wizard {
  print("\n\nSorry, the Setup Wizard is not implemented yet!\n\n");
  main::return_to_main_menu();
}

sub configure_menu {

  show_menu ( [ "1",  "Runlevel",                       \&set_runlevel      ],
              [ "2",  "Interface selection",            \&select_interface  ],
              [ "3",  "DHCP configuration",             \&cfg_dhcpd         ],
              [ "4",  "TFTP configuration",             \&cfg_tftpd         ],
              [ "5",  "Portmapper configuration",       \&cfg_portmapper    ],
              [ "6",  "NFS configuration",              \&cfg_nfs           ],
              [ "7",  "XDMCP configuration",            \&cfg_xdmcp         ],
              [ "8",  "Create /etc/hosts entries",      \&hosts_entries     ],
              [ "9",  "Create /etc/hosts.allow entries",\&hostsAllow_entries],
              [ "10", "Create /etc/exports entries",    \&exports_entries   ],
              [ "11", "Create lts.conf file",           \&ltsconf_entries   ],
              [ "",  "",                                ""                  ],
              [ "R", "Return to previous menu",         "back"              ],
              [ "Q", "Quit",                            "quit"              ] );
}

sub set_runlevel          { Runlevel->configure()               };
sub select_interface      { Interfaces->select_interface ()     };
sub cfg_dhcpd             { Service->configure( "dhcpd"       ) };
sub cfg_tftpd             { Service->configure( "tftpd"       ) };
sub cfg_portmapper        { Service->configure( "portmapper"  ) };
sub cfg_nfs               { Service->configure( "nfs"         ) };
sub cfg_xdmcp             { Service->configure( "xdmcp"       ) };
sub hosts_entries         { File->configure   ( "hosts"       ) };
sub hostsAllow_entries    { File->configure   ( "HostsAllow"  ) };
sub exports_entries       { File->configure   ( "Exports"     ) };
sub ltsconf_entries       { File->configure   ( "LtsConf"     ) };

my $quiet      = 0;
my $print_info = 0;
my $print_help = 0;

sub quiet {
  return( $quiet );
}

sub print_info {
  return( $print_info );
}

{

  #
  # Make sure this utility is being run by root.  Also, make sure the
  # root environment is setup properly.
  #
  if( $ENV{LOGNAME} ne "root" ){
    if( $> == 0 ){                        # $EUID
      die( "\n$0: Must be run as root\n\n"
         . "   If you used 'su' to become the SuperUser, make sure\n"
         . "   you include the hyphen '-' as an argument to su.\n"
         . "   that is:\n\n"
         . "       su -\n\n"
         . "   That will ensure that the proper environment is setup.\n\n" );
    }
    else{
      die( "\n$0: Must be run as root\n\n" );
    }
  }

  my $getopt_result = GetOptions( 'q|quiet' => \$quiet,
                                  'p|print' => \$print_info,
                                  'h|help'  => \$print_help );

  if( ! $getopt_result || $print_help ){
    print_help();
    exit;
  }

#  if( $print_info ){
#    $quiet = 1;
#  }

  sub print_help {

    print <<"EOF"

Usage:   $0 [options]

Options:

  -h,  --help           This help screen.

  -q,  --quiet          Disable most of the messages that are displayed while
                        the program gathers data.

  -p,  --print          Gather the information about the server and quit.
                        This is useful when you need someone to help you.
                        You can dump the info, and email to a support person.


EOF
#  -w,  --wizard         Run in 'wizard' mode.  That is, guide the user
#                        through the items that need to be configured.
  }

  main::verbose_line("\nltspcfg - Version $version\n");

  #
  # Gather information
  #
  $ltsp_dir = main::get_conf_value("LTSP_DIR", "/opt/ltsp" );

  Runlevel->new();
  Interfaces->new();
  Dhcpd->new("dhcpd");
  Tftpd->new("tftpd");
  Portmapper->new("portmapper");
  Nfs->new("nfs");
  Xdmcp->new("xdmcp");
  HostsFile->new("hosts");
  HostsAllowFile->new("HostsAllow");
  ExportsFile->new("Exports");
  LtsConfFile->new("LtsConf");

  main::verbose_line("");
  main::cont() unless ( main::quiet() || main::print_info() );

  if( $print_info ){
   $B     = "";
   $N     = "";
   $R     = "";
   $clear = "";
   status_screen();
  }
  else{
    main_screen();
  }

  main::verbose_line("");
}
