#!/usr/bin/perl 
#
# Copyright (C) 2002-2005 by Mandriva (sbenedict@mandrakesoft.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

use lib qw(/usr/lib/libDrakX);

use standalone;     #- warning, standalone must be loaded very first, for 'explanations'
use strict;

use interactive;
use ugtk2 qw(:helpers :wrappers :create);
use common;
use run_program;
use bootloader;
use network::pxe;

use Config;
use POSIX;

my $in = 'interactive'->vnew('su');

my @buff;	#- used to display status info

my $central_widget;
my $window1;
my $status_box;
my $main_box;
my $wizard_buttons;
my $previous_button;
my $cancel_button;
my $next_button;
my $main_buttons;
my $progress;
my $plabel;
my $in_wizard = 0;
my $config_written = 0;
my $clients_set = 0;
my @nothing = (0..10);
my %conf;
$conf{ALLOW_THIN} = 0;
$conf{CREATE_PXE} = 0;

my $nfs_subnet;
my $nfs_mask;
my $cfg_dir = "/etc/drakxtools/draktermserv/";
-e $cfg_dir or mkdir_p($cfg_dir);
my $cfg_file = $cfg_dir . "draktermserv.conf";
my $interface = get_net_interface();
my $server_ip = get_ip_from_sys();
my $changes_made = 0;
my $client_cfg = "/etc/dhcpd.conf.etherboot.clients";
my $tftpboot = "/var/lib/tftpboot";
my @kernels = bootloader::installed_vmlinuz();
my $kcount = @kernels;
my $cmd_line = 1;
my $mknbi = "/usr/bin/mknbi-set";

#- make sure terminal server and friends are installed
$in->do_pkgs->ensure_is_installed('terminal-server', '/usr/bin/drakTermServ') or $in->exit(-1);

if ("@ARGV" =~ /--enable/) {
	enable_ts();
	exit(0);	
}

if ("@ARGV" =~ /--disable/) {
	disable_ts();
	exit(0);	
}

if ("@ARGV" =~ /--restart/) {
	stop_ts();
	start_ts();
	exit(0);	
}

if ("@ARGV" =~ /--start/) {
	start_ts();
	exit(0);	
}

if ("@ARGV" =~ /--stop/) {
	stop_ts();
	exit(0);	
}

if ("@ARGV" =~ /--adduser/) {
	die N("%s: %s requires a username...\n", $0, $ARGV[0]) if $#ARGV < 1;
	adduser($ARGV[1]);
	exit(0);	
}

if ("@ARGV" =~ /--deluser/) {
	die N("%s: %s requires a username...\n", $0, $ARGV[0]) if $#ARGV < 1;
	deluser($ARGV[1]);
	exit(0);	
}

if ("@ARGV" =~ /--syncusers/) {
	sync_users();
	exit(0);
}

if ("@ARGV" =~ /--addclient/) {
	die N("%s: %s requires hostname, MAC address, IP, nbi-image, 0/1 for THIN_CLIENT, 0/1 for Local Config...\n", $0, $ARGV[0]) if $#ARGV < 6;
	addclient(@ARGV[1..6]);
	exit(0);	
}

if ("@ARGV" =~ /--delclient/) {
	die N("%s: %s requires hostname...\n", $0, $ARGV[0]) if $#ARGV < 1;
	delclient($ARGV[1]);
	exit(0);	
}

read_conf_file();	
interactive_mode() if $#ARGV < 1;

sub read_conf_file() {
	if (-e $cfg_file) {
		substInFile { s/ALLOW_THIN$/ALLOW_THIN=1/ } $cfg_file;
		%conf = getVarsFromSh($cfg_file);
	}
}

sub write_conf_file() {
	setVarsInSh($cfg_file, \%conf);
	chmod(0600, $cfg_file);
}

sub write_thin_inittab {
	my ($client_ip) = @_;
	my $suffix;
	if ($client_ip eq "CLIENT") {
		$suffix = '$$CLIENT$$';
	} else {
		$suffix = "\$\$IP=$client_ip\$\$";
	}
	
	my $inittab = "
# /etc/inittab$suffix
# created by drakTermServ

id:5:initdefault:

# System initialization.
si::sysinit:/etc/rc.d/rc.sysinit

l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
l2:2:wait:/etc/rc.d/rc 2
l3:3:wait:/etc/rc.d/rc 3
l4:4:wait:/etc/rc.d/rc 4
l5:5:wait:/etc/rc.d/rc 5
l6:6:wait:/etc/rc.d/rc 6

# Things to run in every runlevel.
ud::once:/sbin/update

# Trap CTRL-ALT-DELETE
ca::ctrlaltdel:/sbin/reboot -f

# Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1

# Connect to X server
x:5:respawn:/usr/X11R6/bin/X -ac -query $server_ip\n";

	my $inittab_file = "/etc/inittab$suffix";
	output_p($inittab_file, $inittab);	
}

sub display_error {
    my ($message) = @_;
    my $error_box;
    destroy_widget();
    gtkpack($status_box,
	    $error_box = gtkpack_(Gtk2::VBox->new(0,0),
			1, Gtk2::Label->new($message),
			0, gtkadd(gtkset_layout(Gtk2::HButtonBox->new, 'spread'),
				gtksignal_connect(Gtk2::Button->new(N("Ok")), clicked => sub {
					destroy_widget();
				}),
			),
		)
	);
    $central_widget = \$error_box;
}

sub interactive_mode() {
	$cmd_line = 0;
    $in = 'interactive'->vnew;
	$::Wizard_title = N("Terminal Server Configuration");
	$::Wizard_pix_up = "ic82-network-40.png";
	$in->isa('interactive::gtk') and $::isWizard = 0;
    $window1 = ugtk2->new(N("Terminal Server Configuration"));
    $window1->{rwindow}->signal_connect(delete_event => sub { ugtk2->exit(0) });
    $window1->{rwindow}->set_border_width(5);

	my $s_buttons = Gtk2::HBox->new;
	gtkpack_($s_buttons,
		1, gtksignal_connect(Gtk2::Button->new(N("dhcpd Config")), clicked => sub { 
			destroy_widget(); 
			dhcpd_config(); 
		}),
		1, gtksignal_connect(Gtk2::Button->new(N("Enable Server")), clicked => sub {
			destroy_widget();
			gtkset_mousecursor_wait(); 
			enable_ts();
			gtkset_mousecursor_normal();
		}),
		1, gtksignal_connect(Gtk2::Button->new(N("Disable Server")), clicked => sub {
			destroy_widget();
			gtkset_mousecursor_wait();
			disable_ts();
			gtkset_mousecursor_normal(); 
		}),
		1, gtksignal_connect(Gtk2::Button->new(N("Start Server")), clicked => sub { 
			destroy_widget();
			gtkset_mousecursor_wait();	
			start_ts();
			gtkset_mousecursor_normal();
		}),
		1, gtksignal_connect(Gtk2::Button->new(N("Stop Server")), clicked => sub {
			destroy_widget();
			gtkset_mousecursor_wait();
			stop_ts();
			gtkset_mousecursor_normal();
		}),
	);
	my $i_buttons = Gtk2::HBox->new;
	gtkpack_($i_buttons,
		1, gtksignal_connect(Gtk2::Button->new(N("Etherboot Floppy/ISO")), clicked => sub { 
			destroy_widget();
			make_boot();
		}),
		1, gtksignal_connect(Gtk2::Button->new(N("Net Boot Images")), clicked => sub {
			destroy_widget();
			make_nbi();
		}),
	);
	my $c_buttons = Gtk2::HBox->new;
	gtkpack_($c_buttons,
		1, gtksignal_connect(Gtk2::Button->new(N("Add/Del Users")), clicked => sub {
			destroy_widget();
			maintain_users();
		}),
		1, gtksignal_connect(Gtk2::Button->new(N("Add/Del Clients")), clicked => sub { 
			destroy_widget(); 
			maintain_clients(); 
		}),
	);

	$main_buttons = Gtk2::Notebook->new;
	$main_buttons->append_page($s_buttons, gtkshow(Gtk2::Label->new(N("Server"))));
	$main_buttons->append_page($i_buttons, gtkshow(Gtk2::Label->new(N("Images"))));
	$main_buttons->append_page($c_buttons, gtkshow(Gtk2::Label->new(N("Clients/Users"))));
	$main_buttons->set_show_border(0);
	$main_buttons->set_tab_pos('bottom');
	gtkadd($window1->{window}, 
		gtkpack_(gtkset_size_request(Gtk2::VBox->new(0,2), 620, 400),
		    1, gtkpack_(Gtk2::HBox->new(0,2),
				1, gtkpack_(Gtk2::VBox->new(0,2),
					1, gtkpack($status_box = Gtk2::VBox->new(0,5),
						$main_box = Gtk2::VBox->new(0,10),
					),
					0, $wizard_buttons = gtkpack_(Gtk2::HBox->new(1,2)),
					0, $main_buttons,
					0, gtkpack_(Gtk2::HBox->new,
						0, gtksignal_connect(Gtk2::Button->new(N("Help")),clicked => sub {
							destroy_widget();
							help();
						}),
						1, "",
						0, gtksignal_connect(Gtk2::Button->new(N("First Time Wizard")), clicked => sub {
							destroy_widget();
							start_wizard();
						}),
						1, "",
						0, gtksignal_connect(Gtk2::Button->new(N("Close")), clicked => sub {
							write_conf_file();
							restart_server() if $changes_made == 1;
							Gtk2->main_quit;
						}),
					),
				),
			),
		),
	);
    $central_widget = \$main_box;
    $window1->{rwindow}->show_all;
    $window1->{rwindow}->realize;
    $window1->{rwindow}->show_all;
	#- strange behavior with tabs, pressing a button actually ends up
	#- activating a hidden button below it
  	$main_buttons->set_current_page(2);
	$main_buttons->set_current_page(1);
	$main_buttons->set_current_page(0);
	gtksignal_connect($main_buttons, switch_page => sub { destroy_widget() });
	gtkflush(); 
    $window1->main;
    ugtk2->exit(0);
}

sub check_gdm() {
	#- gdm now needs gdm user in /etc/passwd$$CLIENT$$
	my %desktop = getVarsFromSh("/etc/sysconfig/desktop");
	my $dm = $desktop{DISPLAYMANAGER}; 
	$dm =~ tr/a-z/A-Z/;
	my $gdm = `grep gdm '/etc/passwd\$\$CLIENT\$\$'`;
	if ($dm =~ /GNOME|GDM/ && !$gdm) {
		$in->ask_warn(N("Warning"), N("%s defined as dm, adding gdm user to /etc/passwd\$\$CLIENT\$\$", $dm)) if !$cmd_line;
		warn(N("%s defined as dm, adding gdm user to /etc/passwd\$\$CLIENT\$\$", $dm)) if $cmd_line;
		adduser("gdm");
	}
}

sub start_wizard() {
    text_view(N("
 This wizard routine will:
 	1) Ask you to select either 'thin' or 'fat' clients.
	2) Setup DHCP.
	
After doing these steps, the wizard will:
	
    a) Make all nbis.                                                                
    b) Activate the server.                                                          
    c) Start the server.                                                     
    d) Synchronize the shadow files so that all users, including root, 
       are added to the shadow\$\$CLIENT\$\$ file.                                              
    e) Ask you to make a boot floppy.
    f) If it's thin clients, ask if you want to restart KDM.
"), "wizard");
}

sub do_wizard() {
	destroy_widget();
	$main_buttons->hide;
	$in_wizard = 1;
	$config_written = 0;
	%conf = '';
	wizard_step(\&client_type, 1);
}

sub wizard_step {
	my ($do_step, $step) = @_;
	&$do_step();
	gtkadd($wizard_buttons, 
		gtksignal_connect($previous_button = Gtk2::Button->new(N("Previous")), clicked => sub { 
			clear_buttons();
			if ($step == 1) {
				exit_wizard();
				start_wizard();
			}
			wizard_step(\&client_type, 1) if $step == 2;
			wizard_step(\&dhcpd_config, 2) if $step == 3;
			wizard_step(\&make_nbis, 3) if $step == 4;
			wizard_step(\&enable_ts, 4) if $step == 5;
			wizard_step(\&restart_ts, 5) if $step == 6;
			wizard_step(\&sync_users, 6) if $step == 7;
			wizard_step(\&make_boot, 7) if $step == 8;
		})
	); 	
	gtkadd($wizard_buttons, 
		gtksignal_connect($cancel_button = Gtk2::Button->new(N("Cancel Wizard")), clicked => sub { 
			exit_wizard();
		})
	); 	
	gtkadd($wizard_buttons, 
		gtksignal_connect($next_button = Gtk2::Button->new(N("Next")), clicked => sub { 
			clear_buttons();
			if ($step == 1) {
				client_X_keyboard() if $conf{SYNC_KBD};
				wizard_step(\&dhcpd_config, 2);
			}
			if ($step == 2) {
				if ($config_written == 1) {
					wizard_step(\&make_nbis, 3);
				} else {
					$in->ask_warn(N("Error"), N("Please save dhcpd config!"));
					wizard_step(\&dhcpd_config, 2);
				} 
			}
			wizard_step(\&enable_ts, 4) if $step == 3;				 
			wizard_step(\&restart_ts, 5) if $step == 4;				 
			wizard_step(\&sync_users, 6) if $step == 5;
			wizard_step(\&make_boot, 7) if $step == 6;
			wizard_step(\&restart_dm, 8) if $step == 7;
		})
	);
	exit_wizard() if $step == 8; 	
}

sub exit_wizard() {
	clear_buttons();
	$in_wizard = 0;
	$main_buttons->show;
}

sub clear_buttons() {
	destroy_widget();
	$previous_button->destroy;
	$cancel_button->destroy;
	$next_button->destroy;
}

sub client_type() {
	my $check_allow_thin = Gtk2::CheckButton->new(N("Use thin clients."));
	$check_allow_thin->set_active($conf{ALLOW_THIN});
	my $check_sync_kbd = Gtk2::CheckButton->new(N("Sync client X keyboard settings with server."));
	$check_sync_kbd->set_active($conf{SYNC_KBD});
    text_view(N("Please select default client type (Fat is the default type if 'Use thin' is unchecked).
    'Thin' clients run everything off the server's CPU/RAM, using the client display.
    'Fat' clients use their own CPU/RAM but the server's filesystem."), "wizard");
    gtkpack_($$central_widget,
		0, gtkpack_(Gtk2::HBox->new(0,0), 
			1, Gtk2::VBox->new,
			0, gtksignal_connect($check_allow_thin, clicked => sub { 
					invbool \$conf{ALLOW_THIN};
					client_set("all"); 
				}),
			0, gtksignal_connect($check_sync_kbd, clicked => sub { 
					invbool \$conf{SYNC_KBD}; 
				}),
			1, Gtk2::VBox->new,
		),
		0, Gtk2::VBox->new,
	);
}

sub make_nbis() {
	my $buff = N("Creating net boot images for all kernels");
	$in->ask_warn(N("Information"), N("This will take a few minutes."));
	if (check_nbi_space($kernels[0], $kcount)) { 
		$wizard_buttons->hide;
		exit_wizard();
	} else {
		build_w_progress(undef, undef);
		$buff .= "\n\n\t" . N("Done!");
		text_view($buff, "wizard");
	}
}

sub check_nbi_space {
	my ($kernel, $kcount) = @_;
	log::explanations("Checking for adequate free space to create NBIs for $kcount kernel(s)");
	my $nbi_count = `$mknbi -c -k /boot/$kernel`;
	chomp $nbi_count;
	if ($nbi_count eq '') {
		$in->ask_warn(N("Error"), N("%s failed", $mknbi));
		return 1;
	}
	my $needed_space = $nbi_count * $kcount * 2;
    my $free = `df -P $tftpboot | tail -1`;
    my @line_data = split(/[ \t,]+/, $free);
	#- don't use more than 80% 
    my $free_space = int($line_data[3] / 1024);
	if ($needed_space > $free_space * 0.8) {
		$in->ask_warn(N("Error"), N("Not enough space to create\nNBIs in %s.\nNeeded: %d MB, Free: %d MB", $tftpboot, $needed_space, $free_space));
		return 1;
	}
}

sub sync_users() {
	my $buff = N("Syncing server user list with client list, including root.");
	my @active_users = cat_("/etc/shadow");

	my $shadow = '/etc/shadow$$CLIENT$$';
    my @userlist;
	
	#- only users with home dirs, and root
	foreach my $user (@active_users) {
		my @fields = split(/:/, $user);
		if (-d "/home/" . $fields[0] || $fields[0] eq "root") {
			push @userlist, $user;
		}
	}
	output_p($shadow, @userlist);
	$buff .= "\n\n\t" . N("Done!");
	text_view($buff, "wizard") if !$cmd_line;
}

sub restart_dm() {
	if ($clients_set) {
    	my $result = $in->ask_okcancel('', N("In order to enable changes made for thin clients, the display manager must be restarted. Restart now?"));
		run_program::run('nohup /sbin/service dm restart') if $result;
	}
}

sub text_view {
    my ($text, $option) = @_;
    my $box;
    gtkpack($status_box,
	    $box = gtkpack_(Gtk2::VBox->new(0,10),
			1, gtkpack_(Gtk2::HBox->new(0,0),
				1, create_scrolled_window(gtktext_insert(
					Gtk2::TextView->new, [ [ $text ] ])
				),
			),
			0, gtkpack(gtkset_layout(Gtk2::HButtonBox->new, 'spread'),
				gtksignal_connect(my $ok_button = Gtk2::Button->new(N("Ok")), clicked => sub { 
					destroy_widget() if $option eq "close";
					do_wizard() if $option eq "wizard"; 
				}),
			),
		)
	);
	gtkset_size_request($box, 580, 280);
    $central_widget = \$box;
    $status_box->show_all;
	$ok_button->hide if $in_wizard;
	$main_buttons->hide if $in_wizard;
}

sub help() {
	my $inittab_str = '/etc/inittab$$IP=client_ip$$';
	my $shadow_str = '/etc/shadow$$CLIENT$$';
	my $xfconfig_str = '/etc/X11/xorg.conf$$IP=client_ip$$';
	
    text_view(N("Terminal Server Overview") . "\n\n" .
N("        - Create Etherboot Enabled Boot Images:
        	To boot a kernel via etherboot, a special kernel/initrd image must be created.
        	mkinitrd-net does much of this work and drakTermServ is just a graphical 
        	interface to help manage/customize these images. To create the file 
        	/etc/dhcpd.conf.etherboot-pcimap.include that is pulled in as an include in 
        	dhcpd.conf, you should create the etherboot images for at least one full kernel.") . "\n\n" .
N("        - Maintain /etc/dhcpd.conf:
        	To net boot clients, each client needs a dhcpd.conf entry, assigning an IP 
        	address and net boot images to the machine. drakTermServ helps create/remove 
        	these entries.
			
        	(PCI cards may omit the image - etherboot will request the correct image. 
			You should also consider that when etherboot looks for the images, it expects 
			names like boot-3c59x.nbi, rather than boot-3c59x.2.4.19-16mdk.nbi).
			 
        	A typical dhcpd.conf stanza to support a diskless client looks like:") . "\n\n" .
'			host curly {
				hardware ethernet        00:20:af:2f:f7:9d;
				fixed-address              192.168.192.3;
				#type                          fat;
				filename                       "i386/boot/boot-3c509.2.4.18-6mdk.nbi";
				#hdw_config                true;
			}
			' . "\n" .
N("        While you can use a pool of IP addresses, rather than setup a specific entry for
        a client machine, using a fixed address scheme facilitates using the functionality
        of client-specific configuration files that ClusterNFS provides.
			
        Note: The '#type' entry is only used by drakTermServ.  Clients can either be 'thin'
        or 'fat'.  Thin clients run most software on the server via XDMCP, while fat clients run 
        most software on the client machine. A special inittab, %s is
        written for thin clients. System config files xdm-config, kdmrc, and gdm.conf are 
        modified if thin clients are used, to enable XDMCP. Since there are security issues in 
        using XDMCP, hosts.deny and hosts.allow are modified to limit access to the local
        subnet.
			
        Note: The '#hdw_config' entry is also only used by drakTermServ.  Clients can either 
        be 'true' or 'false'.  'true' enables root login at the client machine and allows local 
        hardware configuration of sound, mouse, and X, using the 'drak' tools. This is enabled 
        by creating separate config files associated with the client's IP address and creating 
        read/write mount points to allow the client to alter the file. Once you are satisfied 
        with the configuration, you can remove root login privileges from the client.
			
        Note: You must stop/start the server after adding or changing clients.", $inittab_str) . "\n\n" .
N("        - Maintain /etc/exports:
        	Clusternfs allows export of the root filesystem to diskless clients. drakTermServ
        	sets up the correct entry to allow anonymous access to the root filesystem from
        	diskless clients.

        	A typical exports entry for clusternfs is:
        		
        	/					(ro,all_squash)
        	/home				SUBNET/MASK(rw,root_squash)
			
        	With SUBNET/MASK being defined for your network.") .
        		"\n\n" .
N("        - Maintain %s:
        	For users to be able to log into the system from a diskless client, their entry in
        	/etc/shadow needs to be duplicated in %s. drakTermServ
        	helps in this respect by adding or removing system users from this file.", $shadow_str, $shadow_str) . "\n\n" .
N("        - Per client %s:
        	Through clusternfs, each diskless client can have its own unique configuration files
        	on the root filesystem of the server. By allowing local client hardware configuration, 
        	drakTermServ will help create these files.", $xfconfig_str) .
"\n\n" .
N("        - Per client system configuration files:
        	Through clusternfs, each diskless client can have its own unique configuration files
        	on the root filesystem of the server. By allowing local client hardware configuration, 
        	clients can customize files such as /etc/modules.conf, /etc/sysconfig/mouse, 
        	/etc/sysconfig/keyboard on a per-client basis.

        Note: Enabling local client hardware configuration does enable root login to the terminal 
        server on each client machine that has this feature enabled.  Local configuration can be
        turned back off, retaining the configuration files, once the client machine is configured.") . "\n\n" .
N("        - /etc/xinetd.d/tftp:
        	drakTermServ will configure this file to work in conjunction with the images created
        	by mkinitrd-net, and the entries in /etc/dhcpd.conf, to serve up the boot image to 
        	each diskless client.

        	A typical TFTP configuration file looks like:
        		
        	service tftp
			{
                        disable         = no
                        socket_type  = dgram
                        protocol        = udp
                        wait             = yes
                        user             = root
                        server          = /usr/sbin/in.tftpd
                        server_args  = -s /var/lib/tftpboot
        	}
        		
        	The changes here from the default installation are changing the disable flag to
        	'no' and changing the directory path to /var/lib/tftpboot, where mkinitrd-net
        	puts its images.") . "\n\n" .
N("        - Create etherboot floppies/CDs:
        	The diskless client machines need either ROM images on the NIC, or a boot floppy
        	or CD to initiate the boot sequence.  drakTermServ will help generate these
        	images, based on the NIC in the client machine.
        		
        	A basic example of creating a boot floppy for a 3Com 3c509 manually:
        		
        	cat /usr/share/etherboot/zdsk/3c509.zdsk > /dev/fd0") . "\n\n", "close");
}

sub make_boot() {
	#- make a boot image on floppy or iso from etherboot images
	my $boot_box;
	my $rom_path = "/usr/share/etherboot";
	#- does not return list sorted
	my @nics = sort(all("/usr/share/etherboot/zdsk"));
	my $list_nics = Gtk2::List->new;
	my $nic;
	
	foreach (@nics) {
		my $t = $_;
		$list_nics->add(gtkshow(gtksignal_connect(Gtk2::ListItem->new($t), 
		select => sub { $nic = $t })));
	}
	$list_nics->set_selection_mode('single');
    
	gtkpack($status_box,
		$boot_box = gtkpack_(Gtk2::VBox->new(0,10),
			0, gtkadd(Gtk2::HBox->new(0,10),
				Gtk2::HBox->new(0,5),
				create_scrolled_window($list_nics),
				gtkadd(Gtk2::VBox->new(1,10),
					Gtk2::HBox->new(0,20),
					gtksignal_connect(Gtk2::Button->new(N("Boot Floppy")), clicked => 
						sub { write_eb_image($nic, $rom_path, "floppy") }),
					gtksignal_connect(Gtk2::Button->new(N("Boot ISO")), clicked => 
						sub { write_eb_image($nic, $rom_path, "iso") }),
					gtksignal_connect(Gtk2::Button->new(N("PXE Image")), clicked => 
						sub { write_eb_image($nic, $rom_path, "pxe") }),
					Gtk2::HBox->new(0,20),
				),
				Gtk2::HBox->new(0,5),
			),
		),
	);
	
	$central_widget = \$boot_box;
	$boot_box->show_all;
}

sub make_nbi() {
	my $nbi_box;
	my $kernel;
	my $nic;
	
	require list_modules;
	my @nics = sort(list_modules::category2modules(list_modules::ethernet_categories()));

	#- kernel/module info in tree view
	my $model = Gtk2::TreeStore->new("Glib::String");
    my $tree_kernels = Gtk2::TreeView->new_with_model($model);
    $tree_kernels->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0));
    $tree_kernels->set_headers_visible(0);
    $tree_kernels->get_selection->set_mode('single');

	foreach (@kernels) {
    	my $t_kernel = $model->append_set(undef, [ 0 => $_ ]);
		foreach (@nics) {
    		$model->append_set($t_kernel, [ 0 => $_ ]);
		}
	}	

	$tree_kernels->get_selection->signal_connect(changed => sub {
		$kernel = '';
		$nic = '';
   		my ($model, $iter) = $_[0]->get_selected;
   		$model && $iter or return;
    	my $value = $model->get($iter, 0);
		my $path = $model->get_path_str($iter);
		if ($path !~ /:/) {
			$kernel = $value;
		} else {
			my @elements = split(/:/, $path);
			$nic = $value;
			$kernel = $kernels[$elements[0]];			
		}
	});
	
	# existing nbi images in list
	my $list_model = Gtk2::ListStore->new("Glib::String");
	my $list_nbis = Gtk2::TreeView->new_with_model($list_model);
	$list_nbis->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0));
	$list_nbis->set_headers_visible(0);	
	my $nbi;
	my $nbi_iter;
		
 	update_list($list_model);

	my $combo_default_kernel = Gtk2::ComboBox->new_with_strings([ N("Default kernel version"), 
                                                                   map { bootloader::vmlinuz2version($_) } @kernels ]);
	
	my $check_pxe = Gtk2::CheckButton->new(N("Create PXE images."));
	$check_pxe->set_active($conf{CREATE_PXE});
	$check_pxe->signal_connect('clicked' => sub { invbool \$conf{CREATE_PXE} });

    $combo_default_kernel->set_active(0);
	$combo_default_kernel->entry->signal_connect('changed', sub {
		my $default_kernel = $combo_default_kernel->entry->get_text;
		my $config;
		if ($default_kernel eq translate("Default kernel version")) {
			$config = "";
		} else {
			$config = 'option bootfile-name = pick-first-value ( concat ( "boot-",' . "\n";
			$config .= '    config-option etherboot.kmod, ".' . $default_kernel . '", ".nbi" ), concat' . "\n"; 
            $config .= '    ( "boot-", config-option etherboot.kmod, ".nbi") ,"boot.nbi" );' . "\n";
		}
		output_p("/etc/dhcpd.conf.etherboot.kernel", $config);
	});

	$list_nbis->get_selection->signal_connect(changed => sub {
    	my ($model, $iter) = $_[0]->get_selected;
    	$model && $iter or return;
    	$nbi = $model->get($iter, 0);
		$nbi_iter = $iter;
	});
	my $button_i586_kernel;
	if (arch() eq "i686" && !(any { /i586/ } @kernels)) {
		$button_i586_kernel = Gtk2::Button->new(N("Install i586 kernel for older clients"));
		gtksignal_connect($button_i586_kernel, clicked => sub {
			$in->do_pkgs->install('kernel-i586-up-1GB-2.6');
		});		
	}					
	gtkpack($status_box,
		$nbi_box = gtkpack_(Gtk2::VBox->new(1,10),
			0, gtkadd(Gtk2::HBox->new(0,10),
				create_scrolled_window($tree_kernels),
				gtkadd(Gtk2::VBox->new(1,10),
					gtksignal_connect(Gtk2::Button->new(N("Build Whole Kernel -->")), clicked => sub { 
						if ($kernel) {
							$in->ask_warn(N("Information"), N("This will take a few minutes."));
							gtkset_mousecursor_wait();
							build_n_update($kernel, $list_model, undef) unless check_nbi_space($kernel, 1);
							gtkset_mousecursor_normal(); 
						} else {
							$in->ask_warn(N("Error"), N("No kernel selected!")) if !($kernel); 
						}
					}),
					gtksignal_connect(Gtk2::Button->new(N("Build Single NIC -->")), clicked => sub { 
						if ($nic) {
							build_n_update($kernel, $list_model, $nic);
						} else {
							$in->ask_warn(N("Error"), N("No NIC selected!"));
						}
					}),
					gtksignal_connect(Gtk2::Button->new(N("Build All Kernels -->")), clicked => sub {
						$in->ask_warn(N("Information"), N("This will take a few minutes."));
						my $kcount = @kernels;
						if (check_nbi_space($kernels[0], $kcount)) {
							return;
						} else {
							build_w_progress($list_model, undef);
							make_nbi(); 
						}
					}),
					$button_i586_kernel,
					$combo_default_kernel,
					$check_pxe,
					gtksignal_connect(Gtk2::Button->new(N("<-- Delete")), clicked => sub { 
						if ($nbi) {
							my $result = clear_nbi($nbi);
							$list_model->remove($nbi_iter) if $result == 1;
						} else {
							$in->ask_warn(N("Error"), N("No image selected!"));
						}
					}),
					gtksignal_connect(Gtk2::Button->new(N("Delete All NBIs")), clicked => sub { 
						gtkset_mousecursor_wait();
						foreach (grep { /\.nbi/ } all($tftpboot)) {
							clear_nbi($_);
						}
						$list_model->clear;
						gtkset_mousecursor_normal();
					}),
					Gtk2::HBox->new(1,1),
				),
				create_scrolled_window($list_nbis),
			),),
	);
	
	$central_widget = \$nbi_box;
	$nbi_box->show_all;
}

sub clear_nbi {
	my ($nbi) = @_;
	$nbi = $tftpboot . "/" . $nbi;
	my $result = unlink($nbi) or warn("Can not delete $nbi...");
	$nbi =~ s|boot-|initrd-|;
	$nbi =~ s|nbi|img|;
	unlink($nbi);
	if ($conf{CREATE_PXE}) {
		my $pxe = get_platform_pxe();
		$nbi =~ s|$tftpboot/|$pxe|;
		unlink($nbi);
		del_pxe_entry($nbi) if -d $pxe;	
	}
	return $result;
}

sub update_list {
    my ($list_model) = @_;
    $list_model->clear;
    $list_model->append_set(0, $_) foreach grep { /\.nbi/ } sort(all($tftpboot));
}

sub build_n_update {
    my ($kernel, $list_model, $nic) = @_;
	my $command = "-k /boot/$kernel";
	$command .= " -r $nic" if $nic;
    run_program::run("$mknbi -v $command") or $in->ask_warn(N("Error"), N("%s failed", $mknbi));
	if ($conf{CREATE_PXE}) {
		my $pxedir = get_platform_pxe();
		if (-d $pxedir) {
			cp_af("/boot/$kernel", $pxedir) if !-f "$pxedir/$kernel";
			link_pxe($kernel, $nic);
			add_pxe_entry($kernel, $nic);
		}
	}
    update_list($list_model) if $list_model;
}

sub build_w_progress {
	my ($widget, $nic) = @_;
	gtkset_mousecursor_wait();
	show_progress();
	my $k = 1;
	foreach (@kernels) {
		build_n_update($_, $widget, $nic);
		update_progress($k, $kcount, $_);
		$k++;
	}
	destroy_widget();
	gtkset_mousecursor_normal();
}

sub show_progress() {
	my $progress_box;
	destroy_widget();
	gtkpack($status_box,
		$progress_box = gtkpack_(Gtk2::VBox->new(0,10),
			0, Gtk2::Label->new(N("Building images for kernel:")),
			0, $plabel = Gtk2::Label->new(''),
			0, $progress = Gtk2::ProgressBar->new,
			1, Gtk2::HBox->new(0,0)
		)
	);
	$central_widget = \$progress_box;
	$progress_box->show_all;
	mygtk2::flush();
}

sub update_progress {
	my ($fraction, $total, $kernel) = @_;
	$plabel->set_text($kernel);
	$progress->set_fraction($fraction / $total);
	mygtk2::flush();
}

sub add_pxe_entry {
	my ($kernel, $nic) = @_;
	my $pxeconf = get_platform_pxe() . "pxelinux.cfg/default";
	$kernel =~ s|vmlinuz-||;
	my $label = uc($nic) . "." . uc($kernel);	
	my $conf = network::pxe::read_pxelinux_conf($pxeconf, undef);
	push @{$conf->{entries}}, {label => $label , kernel => "vmlinuz-$kernel", initrd => "initrd-$nic.$kernel.img" };
	network::pxe::write_pxelinux_conf($conf, $pxeconf);
}

sub del_pxe_entry {
	my ($nbi) = @_;
	my $pxeconf = get_platform_pxe() . "pxelinux.cfg/default";
	$nbi = basename($nbi);
	$nbi =~ s|initrd-||;
	$nbi =~ s|.img||;
	my $conf = network::pxe::read_pxelinux_conf($pxeconf, undef);

	my $index = 0;
	foreach (@{$conf->{entries}}) {
		if ($_->{label} eq uc($nbi)) {
			splice @{$conf->{entries}}, $index, 1;
			last;	
		}
		$index++;
	}
	network::pxe::write_pxelinux_conf($conf, $pxeconf);
}

sub link_pxe {
	my ($kernel, $nic) = @_;
	my $pxedir = get_platform_pxe();
	$kernel =~ s|vmlinuz-||;
	if ($nic) {
		#- symlinkf does not work?
		run_program::run("ln -sf ../../initrd-$nic.$kernel.img $pxedir");
	} else {
		foreach (glob_("$tftpboot/initrd*.$kernel.img")) {
			my $img = basename($_); 
			run_program::run("ln -sf ../../$img $pxedir");
		}
	}
}

sub get_platform_pxe() {
	my $adir = "X86PC";
	$adir = "IA64PC" if arch() =~ /x86_64|ia64/;
	$adir = "$tftpboot/$adir/linux/";
	return $adir;
}

sub maintain_users() {
	#- copy users from /etc/shadow to /etc/shadow$$CLIENT$$ to allow ts login
	my $user_box;
	my @sys_users = cat_("/etc/shadow");
	my @ts_users = cat_('/etc/shadow$$CLIENT$$');
	my $titer;
	
	#- use /homes to filter system daemons
	#- seems suppressing root is less than useful, let it be added
	my @homes = (all("/home"), "root");
	
	my $list_model = Gtk2::ListStore->new("Glib::String");
	my $list_sys_users = Gtk2::TreeView->new_with_model($list_model);
	$list_sys_users->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0));
	$list_sys_users->set_headers_visible(0);
	
	my $sys_user;
	
	foreach (@sys_users) {
		my ($s_label) = split(/:/, $_, 2);
		if (any { /$s_label/ } @homes) {
    		$list_model->append_set(0, $s_label);
		}
	}

	$list_sys_users->get_selection->signal_connect(changed => sub {
    	my ($model, $iter) = $_[0]->get_selected;
    	$model && $iter or return;
    	$sys_user = $model->get($iter, 0);
	});

	$list_model = Gtk2::ListStore->new("Glib::String");
	my $list_ts_users = Gtk2::TreeView->new_with_model($list_model);
	$list_ts_users->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0));
	$list_ts_users->set_headers_visible(0);
	
	my $ts_user;
	
	foreach (@ts_users) {
		my ($t_label) = split(/:/, $_, 2);
		my @system_entry = grep { /$t_label/ } @sys_users; 
		$t_label = $t_label . " !!!" if $_ ne $system_entry[0];
		$list_model->append_set(0, $t_label);
	}
	
	$list_ts_users->get_selection->signal_connect(changed => sub {
		my ($model, $iter) = $_[0]->get_selected;
		$model && $iter or return;
		$ts_user = $model->get($iter, 0);
		$ts_user =~ s| !!!||;
		$titer = $iter;
	});
		
	gtkpack($status_box,
		$user_box = gtkpack_(Gtk2::VBox->new(0,10),
			0, gtkadd(Gtk2::Label->new(N("!!! Indicates the password in the system database is different than\n the one in the Terminal Server database.\nDelete/re-add the user to the Terminal Server to enable login."))),
			0, gtkadd(Gtk2::HBox->new(0,20),
				create_scrolled_window($list_sys_users),
				gtkadd(Gtk2::VBox->new(1,10),
					Gtk2::HBox->new(0,10),
					gtksignal_connect(Gtk2::Button->new(N("Add User -->")), clicked => 
						sub { my $result = adduser($sys_user);
							if ($result == 0) {
    							$list_model->append_set(0, $sys_user);
							}
						}),
					gtksignal_connect(Gtk2::Button->new(N("<-- Del User")), clicked => 
						sub { deluser($ts_user);
							$list_model->remove($titer);
						}),
					Gtk2::HBox->new(0,10),
				),
				create_scrolled_window($list_ts_users),
			),),
	);
	
	$central_widget = \$user_box;
	$user_box->show_all;
}

sub maintain_clients() {
	#- add client machines to Terminal Server config
	my $client_box;
	my %clients = read_dhcpd_conf();
	my $client;
	my $citer;
	my $local_config = 0;
	my $button_edit;
	my $button_config;
	my $button_delete;
	
	#- client info in tree view
	my $model = Gtk2::TreeStore->new("Glib::String");
    my $tree_clients = Gtk2::TreeView->new_with_model($model);
    $tree_clients->append_column(Gtk2::TreeViewColumn->new_with_attributes(undef, Gtk2::CellRendererText->new, 'text' => 0));
    $tree_clients->set_headers_visible(0);
    $tree_clients->get_selection->set_mode('browse');
	
	foreach my $key (keys(%clients)) {		
    	my $t_client = $model->append_set(undef, [ 0 => $key ]);
    	$model->append_set($t_client, [ 0 => $clients{$key}{hardware} ]);
    	$model->append_set($t_client, [ 0 => $clients{$key}{address} ]);
    	$model->append_set($t_client, [ 0 => N("type: %s", $clients{$key}{type}) ]);
		if ($clients{$key}{filename}) {
    		$model->append_set($t_client, [ 0 => $clients{$key}{filename} ]);
		}
    	$model->append_set($t_client, [ 0 => N("local config: %s", $clients{$key}{hdw_config}) ]);
				
	}
	
	$tree_clients->get_selection->signal_connect(changed => sub {
    	my ($model, $iter) = $_[0]->get_selected;
    	$model && $iter or return;
    	my $value = $model->get($iter, 0);
		my $path = $model->get_path_str($iter);
		if ($path !~ /:/) {
			$client = $value;
			$citer = $iter;
		} else {
			$citer = $model->iter_parent($iter);
			$client = $model->get($citer, 0);
		}
		$button_edit->set_sensitive(1);
		$button_config->set_sensitive(1);
		$button_delete->set_sensitive(1);		
	});

	#- entry boxes for client data entry
	my $label_host = Gtk2::Label->new("Client Name:");
	my $entry_host = Gtk2::Entry->new;
	my $label_mac = Gtk2::Label->new("MAC Address:");
	my $entry_mac = Gtk2::Entry->new;
	my $label_ip = Gtk2::Label->new("IP Address:");
	my $entry_ip = Gtk2::Entry->new;
	my $label_nbi = Gtk2::Label->new("Kernel Netboot Image:");
	my $entry_nbi = Gtk2::Combo->new;

	gtksignal_connect(my $check_hdw_config = Gtk2::CheckButton->new(N("Allow local hardware\nconfiguration.")),
		clicked => sub { invbool \$local_config });
					
	my @images = grep { /\.nbi/ } all($tftpboot);
	my $have_nbis = @images;
	if ($have_nbis) {
		unshift(@images, "");
		$entry_nbi->set_popdown_strings(@images);
	} else {
		$in->ask_warn(N("Error"), N("No net boot images created!"));		
		make_nbi();
		return 1;
	}
	
	my $check_thin;
	my $check_allow_thin;
	my $is_thin = 0;
	my $check_sync_kbd;
		 
	gtkpack($status_box,
		$client_box = gtkpack_(Gtk2::VBox->new(0,10),
			0, gtkadd(Gtk2::HBox->new(1,5),
				gtkadd(Gtk2::VBox->new(0,5),
					gtkadd($label_host), gtkadd($entry_host),
					gtkadd($label_mac), gtkadd($entry_mac),
					gtkadd($label_ip), gtkadd($entry_ip),
					gtkadd($label_nbi), gtkadd($entry_nbi),
					gtkadd($check_hdw_config),
					gtksignal_connect($check_thin = Gtk2::CheckButton->new(N("Thin Client")), 
						clicked => sub { invbool \$is_thin }),
				),
				gtkadd(Gtk2::VBox->new(1,10),
					$check_allow_thin = Gtk2::CheckButton->new(N("Allow Thin Clients")),
					$check_sync_kbd = Gtk2::CheckButton->new(N("Sync client X keyboard\n settings with server.")),
					gtksignal_connect(Gtk2::Button->new(N("Add Client -->")), clicked => 
						sub { my $hostname = $entry_host->get_text;
							my $mac = $entry_mac->get_text;
							my $ip = $entry_ip->get_text;
							my $nbi = $entry_nbi->entry->get_text;
							if ($hostname && $mac && $ip) {
	
								my $result = addclient($hostname, $mac, $ip, $nbi, $is_thin, $local_config);
								
								if ($result == 0) {
    								my $t_client = $model->append_set(undef, [ 0 => $hostname ]);
    								$model->append_set($t_client, [ 0 => $mac ]);
    								$model->append_set($t_client, [ 0 => $ip ]);
																		
									my $client_type = N("type: fat");
									$client_type = N("type: thin") if $is_thin == 1;
    								$model->append_set($t_client, [ 0 => $client_type ]);
					
    								$model->append_set($t_client, [ 0 => $nbi ]) if $nbi;
									$check_thin->set_active(0);
									$is_thin = 0;
									
									my $hdw_config = N("local config: false");
									$hdw_config = N("local config: true") if $local_config == 1;
									$model->append_set($t_client, [ 0 => $hdw_config ]);
									$check_hdw_config->set_active(0);
									$local_config = 0;
									%clients = read_dhcpd_conf();
								}		
							}
						}),
					gtksignal_connect($button_edit = Gtk2::Button->new(N("<-- Edit Client")), clicked => 
						sub { $entry_host->set_text($client);
							$entry_mac->set_text($clients{$client}{hardware});
							$entry_ip->set_text($clients{$client}{address});
							my $type = $clients{$client}{type};
							if ($type eq "thin") {
								$check_thin->set_active(1);
							} else {
								$check_thin->set_active(0);
							}	
							$entry_nbi->entry->set_text($clients{$client}{filename});
							my $hdw_config = $clients{$client}{hdw_config};
							if ($hdw_config eq "true") {
								$check_hdw_config->set_active(1);
							} else {
								$check_hdw_config->set_active(0);
							}	
							my $result = delclient($client);
							if ($result == 0) {
								$model->remove($citer);
								$button_edit->set_sensitive(0);
								$button_config->set_sensitive(0);
								$button_delete->set_sensitive(0);
							}
						}),
					gtksignal_connect($button_config = Gtk2::Button->new(N("Disable Local Config")), clicked => 
						sub { 
							my $hdw_config = $clients{$client}{hdw_config};
							if ($hdw_config eq "true") {
								client_hdw_config($clients{$client}{address}, 0);
							}	
						}),	
					gtksignal_connect($button_delete = Gtk2::Button->new(N("Delete Client")), clicked => 
						sub { my $result = delclient($client);
							if ($result == 0) {
								$model->remove($citer);
								$button_edit->set_sensitive(0);
								$button_config->set_sensitive(0);
								$button_delete->set_sensitive(0);
							}
						}),
				),
				create_scrolled_window($tree_clients),
			),
		),
	);

	$check_allow_thin->set_active($conf{ALLOW_THIN});
	$check_sync_kbd->set_active($conf{SYNC_KBD});
	$check_thin->set_sensitive($conf{ALLOW_THIN});
	gtksignal_connect($check_allow_thin, clicked => 
		sub { invbool \$conf{ALLOW_THIN};
			$check_thin->set_sensitive($conf{ALLOW_THIN});
			client_set("single");
			$in->ask_warn(N("Warning"), N("Need to restart the Display Manager for full changes to take effect. \n(service dm restart - at the console)"));
		}
	);
	gtksignal_connect($check_sync_kbd, clicked => 
		sub { invbool \$conf{SYNC_KBD};
			client_X_keyboard() if $conf{SYNC_KBD};
		}
	);
	$button_edit->set_sensitive(0);
	$button_config->set_sensitive(0);
	$button_delete->set_sensitive(0);
	$central_widget = \$client_box;
	$client_box->show_all;
}

sub client_X_keyboard() {
	my $server_conf = "/etc/X11/xorg.conf";
	my $client_conf = '/etc/X11/xorg.conf$$CLIENT$$';
	my @server_X_config = cat_($server_conf);
	foreach (@server_X_config) {
		chomp;
		if (/XkbModel/) {
			my $oldmodel = `grep XkbModel '/etc/X11/xorg.conf\$\$CLIENT\$\$'`;
			chomp $oldmodel;
			my $newmodel = $_;
			substInFile { s/$oldmodel/$newmodel/ } $client_conf;
			log::explanations("Sync XkbModel in $client_conf from $server_conf"); 
		}
		if (/XkbLayout/) {
			my $oldlayout = `grep XkbLayout '/etc/X11/xorg.conf\$\$CLIENT\$\$'`;
			chomp $oldlayout;
			my $newlayout = $_;
			substInFile { s/$oldlayout/$newlayout/ } $client_conf;
			log::explanations("Sync XkbLayout in $client_conf from $server_conf"); 
		}
	}
}

sub client_set {
	my ($default) = @_;
	# we need to change some system files to allow the thin clients
	# to access the server - enabling XDMCP and modify hosts.deny/hosts.allow for some security
	# we also need to set runlevel to 5 and restart the display manager
	if ($conf{ALLOW_THIN} == 1) {
		if (-f "/etc/sysconfig/autologin") {
			my $answer = $in->ask_yesorno('', N("Thin clients will not work with autologin. Disable autologin?"));
			if ($answer == 1) {
				log::explanations("Renaming /etc/sysconfig/autologin to /etc/sysconfig/autologin.bak");
				`mv /etc/sysconfig/autologin /etc/sysconfig/autologin.bak`;
			}
		}
		substInFile { s/id:3:initdefault:/id:5:initdefault:/ } "/etc/inittab";
		substInFile { s/DisplayManager.requestPort:/! DisplayManager.requestPort:/ } "/etc/X11/xdm/xdm-config";
		substInFile { s/Enable=false/Enable=true/ } "/usr/share/config/kdm/kdmrc";
		# This file had 2 "Enable=" entries, one for XDMCP and one for debug
		change_gdm_xdmcp("true");
		log::explanations("Modified files /etc/inittab, /etc/X11/xdm/xdm-config, /usr/share/config/kdm/kdmrc, /etc/X11/gdm/gdm.conf");
		# just XDMCP in hosts.allow is enough for xdm & kdm, but gdm does not work - x11 does not help either
		update_hosts_allow("enable");
		if ($default eq "all") {
			my $inittab = '/etc/initab$$CLIENT$$';
			$in->ask_warn(N("Warning"), N("All clients will use %s", $inittab));
			`mv '/etc/inittab\$\$CLIENT\$\$' '/etc/inittab\$\$CLIENT\$\$.fat'` if -f '/etc/inittab$$CLIENT$$';
			write_thin_inittab("CLIENT");
		}
	} else {
		if (-f "/etc/sysconfig/autologin.bak") {
			log::explanations("Renaming /etc/sysconfig/autologin.bak to /etc/sysconfig/autologin");
			`mv /etc/sysconfig/autologin.bak /etc/sysconfig/autologin`;
		}
		substInFile { s/id:5:initdefault:/id:3:initdefault:/ } '/etc/inittab';
		substInFile { s/! DisplayManager.requestPort:/DisplayManager.requestPort:/ } "/etc/X11/xdm/xdm-config";
		substInFile { s/Enable=true/Enable=false/ } "/usr/share/config/kdm/kdmrc";
		change_gdm_xdmcp("false");
		log::explanations("Modified files /etc/inittab, /etc/X11/xdm/xdm-config, /usr/share/config/kdm/kdmrc, /etc/X11/gdm/gdm.conf");
		update_hosts_allow("disable");
		`mv '/etc/inittab\$\$CLIENT\$\$.fat' '/etc/inittab\$\$CLIENT\$\$'` if $default eq "all" && -f '/etc/inittab$$CLIENT$$.fat';
	}
	$clients_set = 1;
}

sub dhcpd_config() {
	#- do main dhcp server config
	my $dhcpd_box;
	my @ifvalues;
	my @resolve;
	my %netconfig;
	my @nservers;
	my $button_msg;
	my $new_config = 0;
	my $dh_button;
	my $wrote = 0;
				
	#- entry boxes for data entry
	my $box_subnet = Gtk2::HBox->new(0,0);
	my $label_subnet = Gtk2::Label->new(N("Subnet:"));
	$label_subnet->set_justify('right');
	my $entry_subnet = Gtk2::Entry->new;	
	$box_subnet->pack_end($entry_subnet, 0, 0, 2);
	$box_subnet->pack_end($label_subnet, 0, 0, 2);
	
	my $box_netmask = Gtk2::HBox->new(0,0);
	my $label_netmask = Gtk2::Label->new(N("Netmask:"));
	$label_netmask->set_justify('left');
	my $entry_netmask = Gtk2::Entry->new;
	$box_netmask->pack_end($entry_netmask, 0, 0, 2);
	$box_netmask->pack_end($label_netmask, 0, 0, 2);
	
	my $box_routers = Gtk2::HBox->new(0,0);
	my $label_routers = Gtk2::Label->new(N("Routers:"));
	$label_routers->set_justify('left');
	my $entry_routers = Gtk2::Entry->new;
	$box_routers->pack_end($entry_routers, 0, 0, 2);
	$box_routers->pack_end($label_routers, 0, 0, 2);
	
	my $box_subnet_mask = Gtk2::HBox->new(0,0);
	my $label_subnet_mask = Gtk2::Label->new(N("Subnet Mask:"));
	$label_subnet_mask->set_justify('left');
	my $entry_subnet_mask = Gtk2::Entry->new;
	$box_subnet_mask->pack_end($entry_subnet_mask, 0, 0, 2);
	$box_subnet_mask->pack_end($label_subnet_mask, 0, 0, 2);
	
	my $box_broadcast = Gtk2::HBox->new(0,0);
	my $label_broadcast = Gtk2::Label->new(N("Broadcast Address:"));
	$label_broadcast->set_justify('left');
	my $entry_broadcast = Gtk2::Entry->new;
	$box_broadcast->pack_end($entry_broadcast, 0, 0, 2);
	$box_broadcast->pack_end($label_broadcast, 0, 0, 2);
	
	my $box_domain = Gtk2::HBox->new(0,0);
	my $label_domain = Gtk2::Label->new(N("Domain Name:"));
	$label_domain->set_justify('left');
	my $entry_domain = Gtk2::Entry->new;
	$box_domain->pack_end($entry_domain, 0, 0, 2);
	$box_domain->pack_end($label_domain, 0, 0, 2);
	
	my $box_name_servers = Gtk2::HBox->new(0,0);
	my $box_name_servers_entry = Gtk2::VBox->new(0,0);
	my $label_name_servers = Gtk2::Label->new(N("Name Servers:"));
	$label_name_servers->set_justify('left');
	my $entry_name_server1 = Gtk2::Entry->new;
	my $entry_name_server2 = Gtk2::Entry->new;
	my $entry_name_server3 = Gtk2::Entry->new;
	$box_name_servers_entry->pack_start($entry_name_server1, 0, 0, 0);
	$box_name_servers_entry->pack_start($entry_name_server2, 0, 0, 0);
	$box_name_servers_entry->pack_start($entry_name_server3, 0, 0, 0);
	$box_name_servers->pack_end($box_name_servers_entry, 0, 0, 2);
	$box_name_servers->pack_end($label_name_servers, 0, 0, 2);

	my $label_ip_range_start = Gtk2::Label->new(N("IP Range Start:"));
	my $label_ip_range_end = Gtk2::Label->new(N("IP Range End:"));
	my $entry_ip_range_start = Gtk2::Entry->new;
	my $entry_ip_range_end = Gtk2::Entry->new;
	
	#- grab some default entries from the running system
	
	if (-e "/etc/sysconfig/network") {
		%netconfig = getVarsFromSh("/etc/sysconfig/network");
		$entry_domain->set_text($netconfig{DOMAINNAME});
	}
		
	my $sys_netmask = get_mask_from_sys();
	$entry_netmask->set_text($sys_netmask);
	$entry_subnet_mask->set_text($sys_netmask);
	
	my $sys_broadcast = get_broadcast_from_sys();
	$entry_broadcast->set_text($sys_broadcast);
	my $sys_subnet = get_subnet_from_sys($sys_broadcast, $sys_netmask);
		
	$entry_subnet->set_text($sys_subnet);

	my @route = grep { /^0.0.0.0/ } `/sbin/route -n`;
	@ifvalues = split(/[ \t]+/, $route[0]);
	$entry_routers->set_text($ifvalues[1]);
	
	@resolve = cat_("/etc/resolv.conf");
	my $i = 1;
	chop(@resolve);
	
	foreach (@resolve) {
		@ifvalues = split / /;
		if ($ifvalues[0] =~ /nameserver/ && $i < 4) {
			$nservers[$i++] = $ifvalues[1];
		}
	}
	
	$entry_name_server1->set_text($nservers[1]);
	$entry_name_server2->set_text($nservers[2]);
	$entry_name_server3->set_text($nservers[3]);

	my $dhcpd_conf = cat_("/etc/dhcpd.conf");
	if (-e "/etc/dhcpd.conf" && $dhcpd_conf !~ /drakTermServ/) {
		$button_msg = N("Append TS Includes To Existing Config");
	} else {
		$button_msg = N("Write Config");
		$new_config = 1;
	}

	gtkpack($status_box, 
		$dhcpd_box = gtkpack_(Gtk2::HBox->new(1,10),
			0, gtkadd(Gtk2::VBox->new,
				gtkadd($box_subnet),
				gtkadd($box_netmask),
				gtkadd($box_routers),
				gtkadd($box_subnet_mask),
				gtkadd($box_broadcast),
				gtkadd($box_domain),
				gtkadd($box_name_servers),
			),
			0, gtkadd(Gtk2::VBox->new(0,0),
				Gtk2::Label->new(N("dhcpd Server Configuration") . "\n\n" .
					N("Most of these values were extracted\nfrom your running system.\nYou can modify as needed.")),
				Gtk2::HSeparator->new,
				gtkadd(Gtk2::HBox->new,
					Gtk2::Label->new(N("Dynamic IP Address Pool\n(needed for PXE clients):")),
				),
				gtkadd(Gtk2::HBox->new(0,0),
					gtkadd(Gtk2::VBox->new,
						gtkadd($label_ip_range_start),
						gtkadd($entry_ip_range_start),
					),
					gtkadd(Gtk2::VBox->new,
						gtkadd($label_ip_range_end),
						gtkadd($entry_ip_range_end),
					),
				),
				gtkadd(Gtk2::HBox->new),				
				gtksignal_connect($dh_button = Gtk2::Button->new($button_msg), clicked => 
					sub { 
						if ($new_config == 1) {
							$wrote = write_dhcpd_config("full", 
								$entry_subnet->get_text,
								$entry_netmask->get_text,
								$entry_routers->get_text,
								$entry_subnet_mask->get_text,
								$entry_broadcast->get_text,
								$entry_domain->get_text,
								$entry_name_server1->get_text,
								$entry_name_server2->get_text,
								$entry_name_server3->get_text,
								$entry_ip_range_start->get_text,
								$entry_ip_range_end->get_text); 
						} else {
							if ($dhcpd_conf =~ /dhcpd.conf.terminal-server/) {
								$wrote = 1;
								$config_written = 1;
							} else {
								$wrote = write_dhcpd_config("append", @nothing);
							} 
						}
						$dh_button->set_sensitive(0) if $wrote;
					}
				),
				Gtk2::HBox->new(0,10),
			),
		),
	);	

	$central_widget = \$dhcpd_box;
	$dhcpd_box->show_all;	
}

sub get_net_interface() {
	my @interfaces = `/sbin/route | grep -v lo | grep -v vmnet | tail +3 | awk '{print \$8}' | uniq`;
	chop @interfaces;
	my $count = @interfaces;
	if ($count == 1) {
        return @interfaces[0];
	} else {
		foreach (@interfaces) {
			my $is_default = `/sbin/route | grep $_ | grep default`;
			return $_ if !$is_default;
		}
	}
}

sub get_mask_from_sys() {
	my %netconfig;
	if (-e "/etc/sysconfig/network-scripts/ifcfg-$interface") {
		%netconfig = getVarsFromSh("/etc/sysconfig/network-scripts/ifcfg-$interface");
		$netconfig{NETMASK};
	}
}

sub get_subnet_from_sys {
	my ($sys_broadcast, $sys_netmask) = @_;
	my @subnet;
	
	my @netmask = split(/\./, $sys_netmask);	
	my @broadcast = split(/\./, $sys_broadcast);
	
	foreach (0..3) {
		#- wasn't evaluating the & as expected
		my $val1 = $broadcast[$_] + 0;
		my $val2 = $netmask[$_] + 0;
		$subnet[$_] = $val1 & $val2;
	}
	
	join(".", @subnet);
}

sub get_broadcast_from_sys() {
	my @ifconfig = grep { /inet/ } `/sbin/ifconfig $interface`;
	my @ifvalues = split(/[: \t]+/, $ifconfig[0]);
		
	$ifvalues[5];
}

sub get_ip_from_sys() {
	my @ifconfig = grep { /inet/ } `/sbin/ifconfig $interface`;
	my @ifvalues = split(/[: \t]+/, $ifconfig[0]);
		
	$ifvalues[3];
}

sub write_dhcpd_config {
	my ($mode, $subnet, $netmask, $routers, $subnet_mask, $broadcast, $domain, $ns1, $ns2, $ns3, $pool_start, $pool_end) = @_;
	my @dhcpd_config;
    my @includes;
	push @includes, qq(# Include PXE definitions and defaults\ninclude "/etc/dhcpd.conf.pxe.include";\n) if $conf{CREATE_PXE};
	push @includes, qq(# Include Etherboot definitions and defaults\ninclude "/etc/dhcpd.conf.etherboot.include";\n);
	push @includes, qq(# Include Etherboot default kernel version\ninclude "/etc/dhcpd.conf.etherboot.kernel";\n);
	if ($mode eq "append") {
		append_to_file("/etc/dhcpd.conf", qq(include "/etc/dhcpd.conf.terminal-server";\n));
		push @dhcpd_config, @includes;
		push @dhcpd_config, qq(# Include client machine configurations\ninclude "$client_cfg";\n);
		$config_written = write_and_check("/etc/dhcpd.conf.terminal-server", @dhcpd_config);
		return $config_written;
	}

	$nfs_subnet = $subnet;
	$nfs_mask = $subnet_mask;

	push @dhcpd_config, "#dhcpd.conf - generated by drakTermServ\n\n";
	push @dhcpd_config, "ddns-update-style none;\n\n";
	push @dhcpd_config, "# Long leases (48 hours)\ndefault-lease-time 172800;\nmax-lease-time 172800;\n\n";
	push @dhcpd_config, @includes;
	push @dhcpd_config, "# Network-specific section\n\n";
	
	push @dhcpd_config, "subnet $subnet netmask $netmask {\n";
	push @dhcpd_config, "\toption routers $routers;\n" if $routers;
	push @dhcpd_config, "\toption subnet-mask $subnet_mask;\n" if $subnet_mask;
	push @dhcpd_config, "\toption broadcast-address $broadcast;\n" if $broadcast;
	push @dhcpd_config, qq(\toption domain-name "$domain";\n) if $domain;
	
	my $pool_string = $pool_start && $pool_end && "\trange dynamic-bootp " . $pool_start . " " . $pool_end . ";\n";
	push @dhcpd_config, $pool_string if $pool_string;
	
	my $ns_string = $ns1 && "\toption domain-name-servers " . $ns1;
	$ns_string = $ns2 && $ns_string . ", " . $ns2;
	$ns_string = $ns3 && $ns_string . ", " . $ns3;
	$ns_string = $ns_string . ";\n" if $ns_string;
	push @dhcpd_config, $ns_string if $ns_string;

	push @dhcpd_config, "}\n\n";
	
	push @dhcpd_config, qq(# Include client machine configurations\ninclude "$client_cfg";\n);
	$config_written = write_and_check("/etc/dhcpd.conf", @dhcpd_config);
	return $config_written;
}

sub write_and_check {
	my ($ofile, @values) = @_;
	output_p($ofile, @values);
	my $written = cat_($ofile);
	my $source = join("", @values);
	if ($written ne $source) {
		$in->ask_warn(N("Error"), N("Write to %s failed!", $ofile)); 
		return 0;
	}
	return 1;
}

sub write_eb_image {
	#- write a bootable etherboot CD image or floppy - pxe images too
	my ($nic, $rom_path, $type) = @_;
	$in->ask_warn(N("Error"), N("No NIC selected!")) and return if $nic eq '';	 	
	if ($type eq 'floppy') {
		my $in = interactive->vnew;
		if (-e "/dev/fd0") {
			my $result = $in->ask_okcancel('', N("Please insert floppy disk:"));
			return if !($result);
			$result = run_program::run("cat $rom_path/zdsk/$nic > /dev/fd0");
			if ($result == 0) {
				$in->ask_warn(N("Error"), N("Could not access the floppy!"));
			} else {
				$in->ask_warn(N("Information"), N("Floppy can be removed now"));
			}
		} else {
			$in->ask_warn(N("Error"), N("No floppy drive available!"));
		}
	} elsif ($type eq 'pxe') {
		$nic =~ s/.zdsk/.zpxe/;
		run_program::run("cp $rom_path/zpxe/$nic $tftpboot");
		if (-e "$tftpboot/$nic") {
			$in->ask_warn(N("Information"), N("PXE image is %s/%s", $tftpboot, $nic));
		} else { 
			$in->ask_warn(N("Error"), N("Error writing %s/%s", $tftpboot, $nic));
		}			
	} else {
		my $installed = $in->do_pkgs->install('mkisofs') || -f "/usr/bin/mksofs";
		if ($installed) {
			my $tmp = "/root/tmp";
			mkdir_p("$tmp/eb");
			run_program::run("cat $rom_path/zdsk/$nic > $tmp/eb/eb.img");
			run_program::run("dd if=/dev/zero of=$tmp/eb/eb.img bs=512 seek=72 count=2808");
			run_program::run("mkisofs -b eb.img -o $tmp/$nic.iso $tmp/eb");
			rm_rf("$tmp/eb");
			if (-e "$tmp/$nic.iso") {
				$in->ask_warn(N("Information"), N("Etherboot ISO image is %s", "$tmp/$nic.iso"));
				return;
			}
		}
		$in->ask_warn(N("Error"), N("Something went wrong! - Is mkisofs installed?"));		
	}
}

sub enable_ts() {
	#- setup default config files for terminal server

	check_gdm();

	@buff = ();
	$buff[0] = "Enabling Terminal Server...\n\n";
	$buff[1] = "\tChecking default /etc/dhcpd.conf...\n";	
	my $dhcpd_conf = cat_("/etc/dhcpd.conf");
	if ($dhcpd_conf !~ /drakTermServ/) {
		if (-f "/etc/dhcpd.conf") {
			write_dhcpd_config("append", @nothing) if $dhcpd_conf !~ /dhcpd.conf.terminal-server/;
		} else {
			if ($cmd_line == 1) {
				print("No /etc/dhcpd.conf built yet - use GUI to create!!\n");
			} else {
				$in->ask_warn(N("Error"), N("Need to create /etc/dhcpd.conf first!"));
				dhcpd_config();
			}
			return;
		}
	}
	#- suggestion from jmdault - not always needed
	if (! -f $client_cfg) {
		touch($client_cfg);
	}
	my $buff_index = toggle_chkconfig("on", "dhcpd", 2);
	$buff[$buff_index] = "\tSetting up default /etc/exports...\n";
	cp_af("/etc/exports", "/etc/exports.mdkTS") if -e "/etc/exports";
	my $squash = "root_squash";
	my %msec = getVarsFromSh("/etc/sysconfig/msec");
	$squash = "no_root_squash" if $msec{SECURE_LEVEL} > 2;
	my $exports = "#/etc/exports - generated by drakTermServ\n\n";
	if ($nfs_subnet eq '') {
		$nfs_mask = get_mask_from_sys();
		my $sys_broadcast = get_broadcast_from_sys();
		$nfs_subnet = get_subnet_from_sys($sys_broadcast, $nfs_mask);
	}
	$exports .= "/\t$nfs_subnet/$nfs_mask(ro,$squash)\n";
	$exports .= "/home\t$nfs_subnet/$nfs_mask(rw,root_squash)\n";
	output_p("/etc/exports", $exports);
	$buff_index = toggle_chkconfig("on", "portmap", $buff_index+1);
	$buff_index = toggle_chkconfig("on", "clusternfs", $buff_index+1);
	$buff_index = toggle_chkconfig("on", "tftp", $buff_index);	
	$buff_index = service_change("xinetd", "restart", $buff_index);
	$buff[$buff_index] = "\n\tDone!";	

	if ($cmd_line == 1) {
		print "@buff\n";
		return;
	}

    show_status(@buff);
}

sub disable_ts() {
	#- restore pre-terminal server configs 

	@buff = ();
	$buff[0] = "Disabling Terminal Server...\n\n";
	$buff[1] = "\tRestoring original /etc/dhcpd.conf...\n";
	cp_af("/etc/dhcpd.conf.mdkTS", "/etc/dhcpd.conf") if -e "/etc/dhcpd.conf.mdkTS";
	substInFile { s|include "/etc/dhcpd.conf.terminal-server";|| } "/etc/dhcpd.conf";
	my $buff_index = toggle_chkconfig("off", "dhcpd", 2);
	$buff[$buff_index] = "\tRestoring default /etc/exports...\n";
	cp_af("/etc/exports.mdkTS", "/etc/exports") if -e "/etc/exports.mdkTS";
	$buff_index = toggle_chkconfig("off", "portmap", $buff_index+1);
	$buff_index = toggle_chkconfig("off", "clusternfs", $buff_index+1);
	$buff_index = toggle_chkconfig("off", "tftp", $buff_index);
	$buff_index = service_change("xinetd", "restart", $buff_index);
	$buff[$buff_index] = "\n\tDone!";	
	
	if ($cmd_line == 1) {
		print "@buff\n";
		return;
	}
	
	show_status(@buff);	
}

sub toggle_chkconfig {
	#- change service config
	my ($state, $service, $buff_index) = @_;
	run_program::run("/sbin/chkconfig $service $state");
	$buff[$buff_index] = "\tTurning $service $state...\n";
	$buff_index++;
	$buff_index;
}

sub service_change {
	my ($service, $command, $buff_index) = @_;
	run_program::run("BOOTUP=serial /sbin/service $service $command > /tmp/drakTSservice.status 2>&1");
	my @result = cat_("/tmp/drakTSservice.status");
	foreach (@result) {
		$buff[$buff_index] = "\t$_";
		$buff_index++;	
	}
	unlink "/tmp/drakTSservice.status"; 
	$buff_index;		
}
	
sub start_ts() {
	#- start the terminal server
	my $pcimap = "/etc/dhcpd.conf.etherboot-pcimap.include";
	
	@buff = ();
	if (-f $pcimap) {
		$buff[0] = "Starting Terminal Server...\n\n";
		touch("/etc/dhcpd.conf.etherboot.kernel") if ! -f "/etc/dhcpd.conf.etherboot.kernel";
		my $buff_index = service_change("dhcpd", "start", 2);
		$buff_index = service_change("portmap", "start", $buff_index);
		$buff_index = service_change("clusternfs", "start", $buff_index);
		$buff[$buff_index] = "\n\tDone!";	
	} else {
		$buff[0] = "Missing $pcimap - please create net boot images for at least one kernel.";
	}
	
	if ($cmd_line == 1) {
		print "@buff\n";
		return;
	}
	
	show_status(@buff);
}

sub stop_ts() {
	#- stop the terminal server

	@buff = ();
	$buff[0] = "Stopping Terminal Server...\n\n";
	my $buff_index = service_change("dhcpd", "stop", 2);
	$buff_index = service_change("portmap", "stop", $buff_index);
	$buff_index = service_change("clusternfs", "stop", $buff_index);
	$buff[$buff_index] = "\n\tDone!";	

	return if $in_wizard;
		
	if ($cmd_line == 1) {
		print "@buff\n";
		return;
	}

	show_status(@buff);

}

#- for the wizard, stop the server first
sub restart_ts() {
	stop_ts();
	start_ts();
}

sub show_status() {
    text_view("@buff", "close");
}

sub adduser {
	my ($username) = @_;
	my @active_users = cat_("/etc/shadow");
	my @passwd_users = cat_("/etc/passwd");
	my @ts_users = cat_('/etc/shadow$$CLIENT$$');
	my $is_user = any { /$username/ } @active_users;
	my $add_fail = 0;
	my $in_already;
	
	if ($is_user) {
		my @shadow_entry = grep { /$username/ } @active_users;
		my @passwd_entry = grep { /$username/ } @passwd_users;
		my $is_ts_user = any { /$username/ } @ts_users;
		if ($is_ts_user) {
			my @ts_shadow = grep { /$username/ } @ts_users;
			if ($shadow_entry[0] eq $ts_shadow[0]) {
				$in_already = 1;
			} else {
				#in but password changed
				print N("%s passwd bad in Terminal Server - rewriting...\n", $username);
				deluser($username);				
				adduser($username);				
			}
		} else {
			# new ts user
			append_to_file('/etc/shadow$$CLIENT$$', $shadow_entry[0]) or $add_fail = 1;
			append_to_file('/etc/passwd$$CLIENT$$', $passwd_entry[0]) or $add_fail = 1;
			$in_already = 0;
		}
	}	
		
	if ($cmd_line == 1) {
		print N("%s is not a user..\n", $username) if !($is_user);
		print N("%s is already a Terminal Server user\n", $username) if $in_already;
		if ($add_fail == 1 || $in_already || !$is_user) {
			print N("Addition of %s to Terminal Server failed!\n", $username);
		} else {		
			print N("%s added to Terminal Server\n", $username);	
		}
		return;
	} else {
		$in_already;
	}	
}

sub deluser {
	# del a user from the shadow$$CLIENT$$ file
	my ($username) = @_;
	my $user_deleted;
	substInFile { $_ = '', $user_deleted = 1 if begins_with($_, "$username:") } '/etc/shadow$$CLIENT$$';                                        
	substInFile { $_ = '', $user_deleted = 1 if begins_with($_, "$username:") } '/etc/passwd$$CLIENT$$';    

	if ($cmd_line == 1) {
		if ($user_deleted) {
			print N("Deleted %s...\n", $username);
		} else {
			print N("%s not found...\n", $username);
		}
		return;
	}	
}

sub addclient {
	#- add a new client entry after checking for dups
	my ($hostname, $mac, $ip, $nbi, $is_thin, $local_config) = @_;

	my $host_in_use = 0;
	my $mac_in_use = 0;
	my $ip_in_use = 0;
	my %ts_clients = read_dhcpd_conf();

	foreach my $client (keys(%ts_clients)) {
		$host_in_use = 1 if $hostname eq $client;
		$mac_in_use = 1 if $mac eq $ts_clients{$client}{hardware};
		$ip_in_use = 1 if $ip eq $ts_clients{$client}{address};
	}
	
	if ($cmd_line == 1) {
		print N("%s already in use\n", $hostname) if $host_in_use;
		print N("%s already in use\n", $mac) if $mac_in_use;
		print N("%s already in use\n", $ip) if $ip_in_use;
		if ($host_in_use || $mac_in_use || $ip_in_use) {
			return;
		}
	}

	if (!$host_in_use && !$mac_in_use && !$ip_in_use) {
		$ts_clients{$hostname}{hardware} = $mac;
		$ts_clients{$hostname}{address} = $ip;
		if ($is_thin == 1) {
			$ts_clients{$hostname}{type} = "thin";
		} else {
			$ts_clients{$hostname}{type} = "fat";
		}
		$ts_clients{$hostname}{filename} = $nbi;
		if ($local_config == 1) {
			$ts_clients{$hostname}{hdw_config} = "true";
			client_hdw_config($ip, 1);
		} else {
			$ts_clients{$hostname}{hdw_config} = "false";
			client_hdw_config($ip, 0);
		}	
		my $client_entry = format_client_entry($hostname, %ts_clients);
		append_to_file($client_cfg, $client_entry);
		$changes_made = 1;
		create_client_sysnetwork($hostname, $ip);
		0;
	} 
}
	
sub delclient {
	#- find a client and delete the entry in dhcpd.conf
	my ($hostname) = @_;
	my $host_found;
	
	my %ts_clients = read_dhcpd_conf();
	
	foreach my $client (keys(%ts_clients)) {
		if ($hostname eq $client) {
			$host_found = 1;
			clean_client_config($ts_clients{$client}{address});
			delete $ts_clients{$client};
			write_dhcpd_conf(%ts_clients);
			$changes_made = 1;
			return 0;			
		}
	}
		
	if ($cmd_line == 1) {
		print N("%s not found...\n", $hostname) unless $host_found;
		return;
	}	
}

sub change_gdm_xdmcp {
	my ($enable) = @_;
	my @conf_data = cat_("/etc/X11/gdm/gdm.conf");
	for (my $i = 0; $i < @conf_data; $i++) {
		$conf_data[$i] =~ s/^Enable=false/Enable=true/ if $enable eq "true";
		$conf_data[$i] =~ s/^Enable=true/Enable=false/ if $enable eq "false";
		# bail here so we do not alter the debug setting
		if ($conf_data[$i] eq "[debug]\n") {
			output("/etc/X11/gdm/gdm.conf", @conf_data);
			last;
		}
	}		
}

sub update_hosts_allow {
	my ($mode) = @_;
	my $mask = get_mask_from_sys();
	my $subnet = `/sbin/ip route list dev $interface scope link | cut -f1 -d"/"`;
	chop $subnet;
	my $i;
	if ($mode eq "enable") {
		my $has_all = `grep ALL /etc/hosts.allow`;
		if ($has_all) {
			$in->ask_warn(N("Warning"), N("/etc/hosts.allow and /etc/hosts.deny already configured - not changed"));
			return;
		}
		if (!$has_all) {
			log::explanations("Modified file /etc/hosts.allow");
			append_to_file("/etc/hosts.allow", "ALL:\t$subnet/$mask 127.0.0.1\n");
		}
		$has_all = `grep ALL /etc/hosts.deny`;
		if (!$has_all) {
			log::explanations("Modified file /etc/hosts.deny");
			append_to_file("/etc/hosts.deny", "ALL:\tALL\n");
		}
	}
	if ($mode eq "disable") {
		my @allow = cat_("/etc/hosts.allow");
		for ($i = 0; $i < @allow; $i++) {
			if ($allow[$i] =~ /^ALL:\t$subnet/) {
				splice(@allow, $i, 1);
				log::explanations("Modified file /etc/hosts.allow");
				output("/etc/hosts.allow", @allow);
				last;
			}
		}
		my @deny = cat_("/etc/hosts.deny");
		for ($i = 0; $i < @deny; $i++) {
			if ($deny[$i] =~ /^ALL:\tALL/) {
				splice(@deny, $i, 1);
				log::explanations("Modified file /etc/hosts.deny");
				output("/etc/hosts.deny", @deny);
				last;
			}
		}
	}	
}

sub format_client_entry {
	#- create a client entry, in proper format
	my ($client, %ts_clients) = @_;
	my $entry = qq(host $client {\n);
	$entry .= qq(\thardware ethernet\t$ts_clients{$client}{hardware};\n);
	$entry .= qq(\tfixed-address\t\t$ts_clients{$client}{address};\n);
	$entry .= qq(\t#type\t\t\t$ts_clients{$client}{type};\n) if $ts_clients{$client}{type};
	$entry .= qq(\tfilename\t\t"$ts_clients{$client}{filename}";\n) if $ts_clients{$client}{filename}; 
	$entry .= qq(\t#hdw_config\t\t$ts_clients{$client}{hdw_config};\n) if $ts_clients{$client}{hdw_config};
	$entry .= qq(}\n);
	if ($ts_clients{$client}{type} eq "thin") {
		write_thin_inittab($ts_clients{$client}{address}); 
	} else {
		eval { rm_rf("/etc/inittab\$\$IP=$ts_clients{$client}{address}\$\$") };
	}
	$entry;
}

sub write_dhcpd_conf {
	my %ts_clients = @_;
	my @client_data;		
	foreach my $key (keys(%ts_clients)) {
		my $client_entry = format_client_entry($key, %ts_clients);
		push @client_data, $client_entry;
	}
	output_p($client_cfg, @client_data);
}

sub read_dhcpd_conf() {
	my $clients = $client_cfg;
	my %ts_clients;
	my $hostname;
	
	#- read and parse current client entries
	my @client_data = cat_($clients);
	foreach (@client_data) {
		my ($name, $val, $val2) = split ' ';
		$val = $val2 if $name =~ /hardware/;
		$val =~ s/[;"]//g;
		if ($name !~ /}/) {
			if ($name =~ /host/) {
				$hostname = $val;
			} else {
				$name = "address" if $name =~ /fixed-address/;
				$name = "type" if $name =~ /#type/;
				$name = "hdw_config" if $name =~ /#hdw_config/;
				$ts_clients{$hostname}{$name} = $val;
			}
		}	
	}
	%ts_clients;	
}

sub client_hdw_config {
	my ($client_ip, $mode) = @_;
	# configure the files for a client to be able to
	# run drak tools locally and modify configs
	# mode 0 disables root logins but retains configs
	# mode 1 creates the new template files
	if ($mode == 1) {
		log::explanations("Allowing root access for $client_ip");
		my $suffix = "\$\$IP=$client_ip\$\$";
		cp_af('/etc/shadow$$CLIENT$$', "/etc/shadow$suffix");
		my @sys_users = cat_("/etc/shadow");
		foreach (@sys_users) {
			if (/^root:/) {
				# need root access to do the hardware config
				append_to_file("/etc/shadow$suffix", $_);
				last;
			}
		}
		# make all the local config files
		cp_af("/etc/sysconfig/mouse", "/etc/sysconfig/mouse$suffix") if -f "/etc/sysconfig/mouse";
		cp_af("/etc/X11/XF86Config", "/etc/X11/XF86Config$suffix") if -f "/etc/X11/XF86Config";
		cp_af('/etc/X11/xorg.conf$$CLIENT$$', "/etc/X11/xorg.conf$suffix") if -f '/etc/X11/xorg.conf$$CLIENT$$';
		output("/etc/modules.conf$suffix", '');
		output("/etc/modules$suffix", '');
		output("/etc/modprobe.conf$suffix", '');
		output("/etc/modprobe.preload$suffix", '');
		# create mount points so they can be edited by the client
		my $mnt_access = "$client_ip(rw,no_root_squash)";
		append_to_file("/etc/exports", "/etc/sysconfig/mouse$suffix\t$mnt_access\n");
		append_to_file("/etc/exports", "/etc/modules.conf$suffix\t$mnt_access\n");
		append_to_file("/etc/exports", "/etc/modules$suffix\t$mnt_access\n");
		append_to_file("/etc/exports", "/etc/modprobe.conf$suffix\t$mnt_access\n");
		append_to_file("/etc/exports", "/etc/modprobe.preload$suffix\t$mnt_access\n");
		append_to_file("/etc/exports", "/etc/X11/XF86Config$suffix\t$mnt_access\n");
		append_to_file("/etc/exports", "/etc/X11/xorg.conf$suffix\t$mnt_access\n");
	} else {
		log::explanations("Removing root access for $client_ip");
		eval { rm_rf("/etc/shadow\$\$IP=$client_ip\$\$") };
		remove_client_mounts($client_ip);
	}	
}

sub create_client_sysnetwork {
	#- this lets gnome operate properly since udhcpc does not get the hostname from the dhcpd server
	my ($hostname, $ip) = @_;
	log::explanations("Adding /etc/sysconfig/network for $ip");
	my $network_file = "/etc/sysconfig/network\$\$IP=$ip\$\$";
	my @net_data = ("HOSTNAME=$hostname\n", "NETWORKING=yes\n", "FORWARD_IPV4=false\n");
	output_p($network_file, @net_data);
}

sub restart_server() {
	my $answer = $in->ask_yesorno('', N("Configuration changed - restart clusternfs/dhcpd?"));
	if ($answer == 1) {
		stop_ts();
		start_ts();
		$changes_made = 0;
	}
}

sub clean_client_config {
	my ($client_ip) = @_;
	# this routine entirely removes local hardware config settings
	log::explanations("Removing all local hardware config for $client_ip");
	my $suffix = "\$\$IP=$client_ip\$\$";
	eval { rm_rf("/etc/shadow$suffix") };
	eval { rm_rf("/etc/sysconfig/mouse$suffix") };
	eval { rm_rf("/etc/modules.conf$suffix") };
	eval { rm_rf("/etc/modules$suffix") };
	eval { rm_rf("/etc/modprobe.conf$suffix") };
	eval { rm_rf("/etc/modprobe.preload$suffix") };
	eval { rm_rf("/etc/X11/XF86Config$suffix") };
	eval { rm_rf("/etc/X11/xorg.conf$suffix") };
	eval { rm_rf("/etc/sysconfig/network$suffix") };
	remove_client_mounts($client_ip);
}

sub remove_client_mounts {
	my ($client_ip) = @_;
	#remove the mount points also
	log::explanations("Removing read/write mount points for $client_ip");
	substInFile { 
		$_ = '' if /$client_ip/;
	} "/etc/exports";	
}

sub destroy_widget() {
	if ($central_widget ne '') {
		$$central_widget->destroy;
		$central_widget = '';
	}
}