#!/usr/bin/perl # # Copyright (C) 2002 by MandrakeSoft (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. # # first pass at an interactive tool to help setup/maintain the Mandrake # Terminal Server implementation # # Requires: etherboot (on x86), mkinitrd-net, terminal-server, dhcp-server # clusternfs, tftp-server # # Tasks: # 1) creation/management of boot images (kernel+initrd, etherboot enabled) # mkinitrd-net is the command line interface for this # 2) create/modify /etc/dhcpd.conf for diskless clients # 3) create/modify /etc/exports for clusternfs export of "/" # 4) add/remove entries in /etc/shadow$$CLIENTS$$ to allow user access # 5) per client XF86Config-4, using /etc/XF86Config-4$$IP-ADDRESS$$ # 6) other per client customizations (modules.conf, keyboard, mouse) # 7) enable/modify /etc/xinetd.d/tftp for etherboot # 8) create etherboot floppies for client machines # # Thanks to the fine work of the folks involved in ltsp.org, and # Michael Brown # 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 MDK::Common::File qw(:all); use Config; use POSIX; my $in = 'interactive'->vnew('su'); my @buff; #- used to display status info my $central_widget; my $window1; my $windows; my $status_box; my $main_box; my $nfs_subnet; my $nfs_mask; my $thin_clients = 0; my $cfg_dir = "/etc/drakxtools/draktermserv/"; my $cfg_file = $cfg_dir . "draktermserv.conf"; my $server_ip = get_ip_from_sys(); my $changes_made = 0; #- make sure terminal server and friends are installed my $ts = system("rpm -qa | grep terminal-server > /dev/null"); if ($ts == 256) { if ($ENV{DISPLAY}) { system("urpmi --X terminal-server > /dev/null"); } else { system("urpmi terminal-server > /dev/null"); } $ts = system("rpm -qa | grep terminal-server > /dev/null"); if ($ts == 256) { warn(N("Useless without Terminal Server")); exit(1); } } if ("@ARGV" =~ /--enable/) { my $cmd_line = 1; enable_ts($cmd_line); exit(0); } if ("@ARGV" =~ /--disable/) { my $cmd_line = 1; disable_ts($cmd_line); exit(0); } if ("@ARGV" =~ /--start/) { my $cmd_line = 1; start_ts($cmd_line); exit(0); } if ("@ARGV" =~ /--stop/) { my $cmd_line = 1; stop_ts($cmd_line); exit(0); } if ("@ARGV" =~ /--adduser/) { die N("%s: %s requires a username...\n", $0, $ARGV[0]) if $#ARGV < 1; my $cmd_line = 1; adduser($cmd_line, $ARGV[1]); exit(0); } if ("@ARGV" =~ /--deluser/) { die N("%s: %s requires a username...\n", $0, $ARGV[0]) if $#ARGV < 1; my $cmd_line = 1; deluser($cmd_line, $ARGV[1]); 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; my $cmd_line = 1; addclient($cmd_line, @ARGV[1..6]); exit(0); } if ("@ARGV" =~ /--delclient/) { die N("%s: %s requires hostname...\n", $0, $ARGV[0]) if $#ARGV < 1; my $cmd_line = 1; delclient($cmd_line, $ARGV[1]); exit(0); } read_conf_file(); interactive_mode() if $#ARGV < 1; sub read_conf_file() { if (-e $cfg_file) { local *CONF_FILE; open(CONF_FILE, "<" . $cfg_file) || print N("You must be root to read configuration file. \n"); local $_; while () { if (/^\bALLOW_THIN\b/) { $thin_clients = 1; last; } } } } sub write_conf_file() { my @cfg_list; if ($thin_clients == 1) { @cfg_list = "ALLOW_THIN\n"; } output_p($cfg_file, @cfg_list); chmod(0600, $cfg_file); } sub write_thin_inittab { my ($client_ip) = @_; my $inittab = " # /etc/inittab\$\$IP=$client_ip\$\$ # 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\$\$IP=$client_ip\$\$"; local *INITTAB; open(INITTAB, "> $inittab_file") or warn("Can't open $inittab_file!"); print INITTAB $inittab; close INITTAB } sub cursor_wait() { # turn the cursor to a watch $window1->{rwindow}->window->set_cursor(new Gtk2::Gdk::Cursor("GDK_WATCH")); gtkflush(); } sub cursor_norm() { # restore normal cursor $window1->{rwindow}->window->set_cursor(new Gtk2::Gdk::Cursor("GDK_LEFT_PTR")); gtkflush(); } sub display_error { my ($message) = @_; my $error_box; destroy_widget(); gtkpack($status_box, $error_box = gtkpack_(new Gtk2::VBox(0,0), 1, new Gtk2::Label($message), 0, gtkadd(gtkset_layout(new Gtk2::HButtonBox, 'spread'), gtksignal_connect(new Gtk2::Button(N("OK")), clicked => sub { destroy_widget(); }), ), ) ); $central_widget = \$error_box; } sub interactive_mode() { my $darea; $window1 = ugtk2->new('drakTermServ'); $window1->{rwindow}->signal_connect(delete_event => sub { ugtk2->exit(0) }); unless ($::isEmbedded) { $window1->{rwindow}->set_position('center'); $window1->{rwindow}->set_title(N("Mandrake Terminal Server Configuration")); my $pixbuf_icon = gtkcreate_pixbuf("ic82-network-40"); $darea = Gtk2::DrawingArea->new; $darea->set_size_request(540, 45); $darea->modify_font(Gtk2::Pango::FontDescription->from_string('Sans Italic 24')); my $layout = $darea->create_pango_layout(N("DrakTermServ")); $darea->signal_connect(realize => sub { set_back_pixbuf($darea, gtkcreate_pixbuf('title-tile')) }); $darea->signal_connect(expose_event => sub { my (undef, undef, $dx, $dy) = $darea->allocation->values; $darea->window->draw_pixbuf($darea->style->white_gc, $pixbuf_icon, 0, 0, $dx - $pixbuf_icon->get_width, $dy - $pixbuf_icon->get_height, -1, -1, 'none', 0, 0); my ($lx, $ly) = $layout->get_pixel_size; $darea->window->draw_layout($darea->style->white_gc, ($dx-$lx)/2, ($dy-$ly)/2, $layout); 0; }); } $window1->{rwindow}->set_border_width(5); gtkadd($window1->{window}, gtkpack_(gtkset_size_request(new Gtk2::VBox(0,2), 620, 400), if_(!$::isEmbedded, 0, $darea), 1, gtkpack_(new Gtk2::HBox(0,2), 1, gtkpack_(new Gtk2::VBox(0,2), 1, gtkpack($status_box = new Gtk2::VBox(0,5), $main_box = new Gtk2::VBox(0,10), ), 1, gtkpack_(new Gtk2::HBox(0,2), 0, gtkadd(gtkset_layout(Gtk2::VButtonBox->new, 'end'), gtksignal_connect(new Gtk2::Button(N("Enable Server")), clicked => sub { destroy_widget(); $windows = 1; cursor_wait(); enable_ts(); cursor_norm(); }), gtksignal_connect(new Gtk2::Button(N("Disable Server")), clicked => sub { destroy_widget(); cursor_wait(); disable_ts(); cursor_norm(); }), ), 0, gtkadd(gtkset_layout(Gtk2::VButtonBox->new, 'end'), gtksignal_connect(new Gtk2::Button(N("Start Server")), clicked => sub { destroy_widget(); $windows = 0; cursor_wait(); start_ts(); cursor_norm(); }), gtksignal_connect(new Gtk2::Button(N("Stop Server")), clicked => sub { destroy_widget(); cursor_wait(); stop_ts(); cursor_norm(); }), ), 0, gtkadd(gtkset_layout(Gtk2::VButtonBox->new, 'end'), gtksignal_connect(new Gtk2::Button(N("Etherboot Floppy/ISO")), clicked => sub { destroy_widget(); $windows = 1; make_boot(); }), gtksignal_connect(new Gtk2::Button(N("Net Boot Images")), clicked => sub { destroy_widget(); make_nbi(); }), ), 0, gtkadd(gtkset_layout(Gtk2::VButtonBox->new, 'end'), gtksignal_connect(new Gtk2::Button(N("Add/Del Users")), clicked => sub { destroy_widget(); $windows = 0; maintain_users(); }), gtksignal_connect(new Gtk2::Button(N("Add/Del Clients")), clicked => sub { destroy_widget(); maintain_clients() }), ), 1, new Gtk2::HBox(0,2), 0, gtkadd(gtkset_layout(Gtk2::VButtonBox->new, 'end'), gtksignal_connect(new Gtk2::Button(N("Help")),clicked => sub { destroy_widget(); help(); }), gtksignal_connect(new Gtk2::Button(N("Close")), clicked => sub { write_conf_file(); if ($changes_made == 1) { restart_server(); } Gtk2->main_quit; }), ), ), ), ), ), ); $central_widget = \$main_box; $window1->{rwindow}->show_all; $window1->{rwindow}->realize; $window1->{rwindow}->show_all; $window1->main; ugtk2->exit(0); } sub about() { text_view(N(" Copyright (C) 2002 by MandrakeSoft Stew Benedict sbenedict\@mandrakesoft.com ") . $::license . N(" Thanks: - LTSP Project http://www.ltsp.org - Michael Brown ")); } sub text_view { my ($text) = @_; my $box; gtkpack($status_box, $box = gtkpack_(new Gtk2::VBox(0,10), 1, gtkpack_(new Gtk2::HBox(0,0), 1, create_scrolled_window(gtktext_insert( new Gtk2::TextView, [ [ $text ] ]) ), ), 0, gtkpack(gtkset_layout(new Gtk2::HButtonBox, 'spread'), gtksignal_connect(new Gtk2::Button(N("OK")), clicked => sub { destroy_widget() }), ), ) ); gtkset_size_request($box, 580, 280); $central_widget = \$box; $status_box->show_all; } sub help() { my $inittab_str = '/etc/inittab$$IP=client_ip$$'; my $shadow_str = '/etc/shadow$$CLIENT$$'; my $xfconfig_str = '/etc/X11/XF86Config-4$$IP=client_ip$$'; text_view(N("drakTermServ 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 initate 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/lib/etherboot/boot1a.bin \\ /usr/lib/etherboot/lzrom/3c509.lzrom > /dev/fd0") . "\n\n"); } sub make_boot() { #- make a boot image on floppy or iso from etherboot images my $boot_box; my $rom_path = "/usr/lib/etherboot"; my @nics = all("/usr/lib/etherboot/lzrom"); my $list_nics = new Gtk2::List(); my $nic; foreach (@nics) { my $t = $_; $list_nics->add(gtkshow(gtksignal_connect(new Gtk2::ListItem($t), select => sub { $nic = $t }))); } $list_nics->set_selection_mode('single'); gtkpack($status_box, $boot_box = gtkpack_(new Gtk2::VBox(0,10), 0, gtkadd(new Gtk2::HBox(0,10), new Gtk2::HBox(0,5), create_scrolled_window($list_nics), gtkadd(new Gtk2::VBox(1,10), new Gtk2::HBox(0,20), gtksignal_connect(new Gtk2::Button(N("Boot Floppy")), clicked => sub { write_eb_image($nic, $rom_path, "floppy") }), gtksignal_connect(new Gtk2::Button(N("Boot ISO")), clicked => sub { write_eb_image($nic, $rom_path, "iso") }), new Gtk2::HBox(0,20), ), new Gtk2::HBox(0,5), ), ), ); $central_widget = \$boot_box; $boot_box->show_all; } sub make_nbi() { my $nbi_box; my @kernels = grep { /vmlinuz/ } all("/boot"); my $kernel; my $nic; #- just a static list for the moment #- method in mknbi-net is much better my @nics = ("3c509", "3c59x", "3c90x", "8139cp", "8139too", "acenic", "airo", "aironet4500_card", "bcm5700", "dgrs", "dl2k", "dmfe", "e100", "e1000", "eepro100", "epic100", "fealnx", "hamachi", "hp100", "hysdn", "natsemi", "natsemi_old", "ne", "ne2k-pci", "ns83820", "pcnet32", "pegasus", "prism2_pci", "prism2_plx", "rcpci", "sis900", "starfire", "sundance", "sungem", "sunhme", "tlan", "tulip-old", "via-rhine", "winbond-840", "xircom_cb", "xircom_tulip_cb", "yellowfin"); #- 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 @nbis = grep { /\.nbi/ } all("/var/lib/tftpboot"); my $nbi; my $nbi_iter; my $iter; foreach (@nbis) { $list_model->append_set(undef, $_); } $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; }); gtkpack($status_box, $nbi_box = gtkpack_(new Gtk2::VBox(1,10), 0, gtkadd(new Gtk2::HBox(0,10), create_scrolled_window($tree_kernels), gtkadd(new Gtk2::VBox(1,10), gtksignal_connect(new Gtk2::Button(N("Build Whole Kernel -->")), clicked => sub { if ($kernel) { $in->ask_warn('', N("This will take a few minutes.")); cursor_wait(); system("/usr/bin/mknbi-set -k /boot/$kernel"); $list_model->clear; @nbis = grep { /\.nbi/ } all("/var/lib/tftpboot"); foreach (@nbis) { $list_model->append_set($iter, $_); } cursor_norm(); } else { $in->ask_warn('', N("No kernel selected!")) if !($kernel); } }), gtksignal_connect(new Gtk2::Button(N("Build Single NIC -->")), clicked => sub { if ($nic) { system("/usr/bin/mknbi-set -k /boot/$kernel -r $nic"); $list_model->clear; @nbis = grep { /\.nbi/ } all("/var/lib/tftpboot"); foreach (@nbis) { $list_model->append_set($iter, $_); } } else { $in->ask_warn('', N("No NIC selected!")); } }), gtksignal_connect(new Gtk2::Button(N("Build All Kernels -->")), clicked => sub { $in->ask_warn('', N("This will take a few minutes.")); cursor_wait(); system("/usr/bin/mknbi-set"); $list_model->clear; @nbis = grep { /\.nbi/ } all("/var/lib/tftpboot"); foreach (@nbis) { $list_model->append_set($iter, $_); } cursor_norm(); }), new Gtk2::HBox(1,1), gtksignal_connect(new Gtk2::Button(N("<-- Delete")), clicked => sub { my $nbi = "/var/lib/tftpboot/" . $nbi; my $result = unlink($nbi) || warn("Can't delete $nbi..."); if ($result == 1) { $list_model->remove($nbi_iter); } }), gtksignal_connect(new Gtk2::Button(N("Delete All NBIs")), clicked => sub { cursor_wait(); foreach (grep { /\.nbi/ } all("/var/lib/tftpboot")) { my $nbi = "/var/lib/tftpboot/" . $_; unlink($nbi) || warn("Can't delete $nbi..."); } $list_model->clear; cursor_norm(); }), new Gtk2::HBox(1,1), ), create_scrolled_window($list_nbis), ),), ); $central_widget = \$nbi_box; $nbi_box->show_all; } 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 my @homes = all("/home"); 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(undef, $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(undef, $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_(new Gtk2::VBox(0,10), 0, gtkadd(new Gtk2::Label(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(new Gtk2::HBox(0,20), create_scrolled_window($list_sys_users), gtkadd(new Gtk2::VBox(1,10), new Gtk2::HBox(0,10), gtksignal_connect(new Gtk2::Button(N("Add User -->")), clicked => sub { my $result = adduser(0, $sys_user); if ($result == 0) { $list_model->append_set(undef, $sys_user); } }), gtksignal_connect(new Gtk2::Button(N("<-- Del User")), clicked => sub { deluser(0, $ts_user); $list_model->remove($titer); }), new Gtk2::HBox(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 { $client = ''; } $button_edit->set_sensitive(1); $button_config->set_sensitive(1); $button_delete->set_sensitive(1); }); #- entry boxes for client data entry my $label_host = new Gtk2::Label("Client Name:"); my $entry_host = new Gtk2::Entry(); my $label_mac = new Gtk2::Label("MAC Address:"); my $entry_mac = new Gtk2::Entry(); my $label_ip = new Gtk2::Label("IP Address:"); my $entry_ip = new Gtk2::Entry(); my $label_nbi = new Gtk2::Label("Kernel Netboot Image:"); my $entry_nbi = new Gtk2::Combo(); gtksignal_connect(my $check_hdw_config = new Gtk2::CheckButton(N("Allow local hardware\nconfiguration.")), clicked => sub { invbool \$local_config }); my @images = grep { /\.nbi/ } all("/var/lib/tftpboot/"); my $have_nbis = @images; if ($have_nbis) { unshift(@images, ""); $entry_nbi->set_popdown_strings(@images); } else { $in->ask_warn('', N("No net boot images created!")); make_nbi(); return 1; } my $check_thin; my $check_allow_thin; my $is_thin = 0; gtkpack($status_box, $client_box = gtkpack_(new Gtk2::VBox(0,10), 0, gtkadd(new Gtk2::HBox(1,5), gtkadd(new Gtk2::VBox(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 = new Gtk2::CheckButton(N("Thin Client")), clicked => sub { invbool \$is_thin }), ), gtkadd(new Gtk2::VBox(1,10), $check_allow_thin = new Gtk2::CheckButton(N("Allow Thin Clients")), gtksignal_connect(new Gtk2::Button(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(0, $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 = new Gtk2::Button(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(0, $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 = new Gtk2::Button(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 = new Gtk2::Button(N("Delete Client")), clicked => sub { my $result = delclient(0, $client); if ($result == 0) { $model->remove($citer); $button_edit->set_sensitive(0); $button_config->set_sensitive(0); $button_delete->set_sensitive(0); } }), gtksignal_connect(new Gtk2::Button(N("dhcpd Config...")), clicked => sub { $client_box->destroy; dhcpd_config() }), ), create_scrolled_window($tree_clients), ),), ); $check_allow_thin->set_active($thin_clients); $check_thin->set_sensitive($thin_clients); gtksignal_connect($check_allow_thin, clicked => sub { invbool \$thin_clients; $check_thin->set_sensitive($thin_clients); # 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 ($thin_clients == 1) { 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 doesn't work - x11 doesn't help either update_hosts_allow("enable"); } else { 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"); } $in->ask_warn('', N("Need to restart the Display Manager for full changes to take effect. \n(service dm restart - at the console)")); } ); $button_edit->set_sensitive(0); $button_config->set_sensitive(0); $button_delete->set_sensitive(0); $central_widget = \$client_box; $client_box->show_all; } sub dhcpd_config() { #- do main dhcp server config my $dhcpd_box; my @ifvalues; my @resolve; my %netconfig; my @nservers; #- entry boxes for data entry my $box_subnet = new Gtk2::HBox(0,0); my $label_subnet = new Gtk2::Label(N("Subnet:")); $label_subnet->set_justify('right'); my $entry_subnet = new Gtk2::Entry(); $box_subnet->pack_end($entry_subnet, 0, 0, 10); $box_subnet->pack_end($label_subnet, 0, 0, 10); my $box_netmask = new Gtk2::HBox(0,0); my $label_netmask = new Gtk2::Label(N("Netmask:")); $label_netmask->set_justify('left'); my $entry_netmask = new Gtk2::Entry(); $box_netmask->pack_end($entry_netmask, 0, 0, 10); $box_netmask->pack_end($label_netmask, 0, 0, 10); my $box_routers = new Gtk2::HBox(0,0); my $label_routers = new Gtk2::Label(N("Routers:")); $label_routers->set_justify('left'); my $entry_routers = new Gtk2::Entry(); $box_routers->pack_end($entry_routers, 0, 0, 10); $box_routers->pack_end($label_routers, 0, 0, 10); my $box_subnet_mask = new Gtk2::HBox(0,0); my $label_subnet_mask = new Gtk2::Label(N("Subnet Mask:")); $label_subnet_mask->set_justify('left'); my $entry_subnet_mask = new Gtk2::Entry(); $box_subnet_mask->pack_end($entry_subnet_mask, 0, 0, 10); $box_subnet_mask->pack_end($label_subnet_mask, 0, 0, 10); my $box_broadcast = new Gtk2::HBox(0,0); my $label_broadcast = new Gtk2::Label(N("Broadcast Address:")); $label_broadcast->set_justify('left'); my $entry_broadcast = new Gtk2::Entry(); $box_broadcast->pack_end($entry_broadcast, 0, 0, 10); $box_broadcast->pack_end($label_broadcast, 0, 0, 10); my $box_domain = new Gtk2::HBox(0,0); my $label_domain = new Gtk2::Label(N("Domain Name:")); $label_domain->set_justify('left'); my $entry_domain = new Gtk2::Entry(); $box_domain->pack_end($entry_domain, 0, 0, 10); $box_domain->pack_end($label_domain, 0, 0, 10); my $box_name_servers = new Gtk2::HBox(0,0); my $box_name_servers_entry = new Gtk2::VBox(0,0); my $label_name_servers = new Gtk2::Label(N("Name Servers:")); $label_name_servers->set_justify('left'); my $entry_name_server1 = new Gtk2::Entry(); my $entry_name_server2 = new Gtk2::Entry(); my $entry_name_server3 = new Gtk2::Entry(); $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, 10); $box_name_servers->pack_end($label_name_servers, 0, 0, 10); my $label_ip_range_start = new Gtk2::Label(N("IP Range Start:")); my $label_ip_range_end = new Gtk2::Label(N("IP Range End:")); my $entry_ip_range_start = new Gtk2::Entry(); my $entry_ip_range_end = new Gtk2::Entry(); #- 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]); gtkpack($status_box, $dhcpd_box = gtkpack_(new Gtk2::HBox(1,10), 0, gtkadd((new Gtk2::VBox), 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(new Gtk2::VBox(0,0), new Gtk2::Label(N("dhcpd Server Configuration") . "\n\n" . N("Most of these values were extracted\nfrom your running system.\nYou can modify as needed.")), new Gtk2::HSeparator, gtkadd((new Gtk2::HBox), new Gtk2::Label(N("Dynamic IP Address Pool:")), ), gtkadd((new Gtk2::HBox(0,0)), gtkadd((new Gtk2::VBox), gtkadd($label_ip_range_start), gtkadd($entry_ip_range_start), ), gtkadd((new Gtk2::VBox), gtkadd($label_ip_range_end), gtkadd($entry_ip_range_end), ), ), gtkadd(new Gtk2::HBox), gtksignal_connect(new Gtk2::Button(N("Write Config")), clicked => sub { write_dhcpd_config( $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, ) }), new Gtk2::HBox(0,10), ), ), ); $central_widget = \$dhcpd_box; $dhcpd_box->show_all; } sub get_mask_from_sys() { my %netconfig; if (-e "/etc/sysconfig/network-scripts/ifcfg-eth0") { %netconfig = getVarsFromSh("/etc/sysconfig/network-scripts/ifcfg-eth0"); $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 eth0`; my @ifvalues = split(/[: \t]+/, $ifconfig[0]); $ifvalues[5]; } sub get_ip_from_sys() { my @ifconfig = grep { /inet/ } `/sbin/ifconfig eth0`; my @ifvalues = split(/[: \t]+/, $ifconfig[0]); $ifvalues[3]; } sub write_dhcpd_config { my ($subnet, $netmask, $routers, $subnet_mask, $broadcast, $domain, $ns1, $ns2, $ns3, $pool_start, $pool_end) = @_; $nfs_subnet = $subnet; $nfs_mask = $subnet_mask; local *FHANDLE; open(FHANDLE, "> /etc/dhcpd.conf"); print FHANDLE "#dhcpd.conf - generated by drakTermServ\n\n"; print FHANDLE "ddns-update-style none;\n\n"; print FHANDLE "# Long leases (48 hours)\ndefault-lease-time 172800;\nmax-lease-time 172800;\n\n"; print FHANDLE "# Include Etherboot definitions and defaults\ninclude \"/etc/dhcpd.conf.etherboot.include\";\n\n"; print FHANDLE "# Network-specific section\n\n"; print FHANDLE "subnet $subnet netmask $netmask {\n"; print FHANDLE "\toption routers $routers;\n" if $routers; print FHANDLE "\toption subnet-mask $subnet_mask;\n" if $subnet_mask; print FHANDLE "\toption broadcast-address $broadcast;\n" if $broadcast; print FHANDLE "\toption domain-name \"$domain\";\n" if $domain; my $pool_string = "\trange dynamic-bootp " . $pool_start . " " . $pool_end . ";\n" if $pool_start && $pool_end; print FHANDLE $pool_string if $pool_string; my $ns_string = "\toption domain-name-servers " . $ns1 if $ns1; $ns_string = $ns_string . ", " . $ns2 if $ns2; $ns_string = $ns_string . ", " . $ns3 if $ns3; $ns_string = $ns_string . ";\n" if $ns_string; print FHANDLE $ns_string if $ns_string; print FHANDLE "}\n\n"; print FHANDLE "# Include client machine configurations\ninclude \"/etc/dhcpd.conf.etherboot.clients\";\n"; close FHANDLE } sub write_eb_image { #- write a bootable etherboot CD image or floppy my ($nic, $rom_path, $type) = @_; 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 = system("cat $rom_path/boot1a.bin $rom_path/lzrom/$nic > /dev/fd0") if $result; if ($result) { $in->ask_warn('', N("Couldn't access the floppy!")) } else { $in->ask_warn('', N("Floppy can be removed now")) } } else { $in->ask_warn('', N("No floppy drive available!")); } } else { mkdir_p("/tmp/eb"); system("cat $rom_path/boot1a.bin $rom_path/lzrom/$nic > /tmp/eb/eb.img"); system("dd if=/dev/zero of=/tmp/eb/eb.img bs=512 seek=72 count=2808"); system("mkisofs -b eb.img -o /tmp/$nic.iso /tmp/eb"); rm_rf("/tmp/eb"); if (-e "/tmp/$nic.iso") { $in->ask_warn('', N("Etherboot ISO image is %s", "/tmp/$nic.iso")) } else { $in->ask_warn('', N("Something went wrong! - Is mkisofs installed?")) } } } sub enable_ts { #- setup default config files for terminal server my $cmd_line = @_; @buff = (); $buff[0] = "Enabling Terminal Server...\n\n"; $buff[1] = "\tChecking default /etc/dhcpd.conf...\n"; my @my_conf = cat_("/etc/dhcpd.conf"); if ($my_conf[0] !~ /drakTermServ/) { if ($cmd_line == 1) { print("No /etc/dhcpd.conf built yet - use GUI to create!!\n"); return; } else { $in->ask_warn('', N("Need to create /etc/dhcpd.conf first!")); #$central_widget->destroy; dhcpd_config(); return; } } 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"; local *FHANDLE; open(FHANDLE, "> /etc/exports"); print FHANDLE "#/etc/exports - generated by drakTermServ\n\n"; print FHANDLE "/\t(ro,all_squash)\n"; if ($nfs_subnet eq '') { $nfs_subnet = get_subnet_from_sys(); $nfs_mask = get_mask_from_sys(); my $sys_broadcast = get_broadcast_from_sys(); $nfs_subnet = get_subnet_from_sys($sys_broadcast, $nfs_mask); } print FHANDLE "/home\t$nfs_subnet/$nfs_mask(rw,root_squash)\n"; close FHANDLE; $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 my $cmd_line = @_; @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"; 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", "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) = @_; system("/sbin/chkconfig $service $state"); $buff[$buff_index] = "\tTurning $service $state...\n"; $buff_index++; $buff_index; } sub service_change { my ($service, $command, $buff_index) = @_; system("BOOTUP=serial /sbin/service $service $command > /tmp/drakTSservice.status 2>&1"); local *STATUS; open(STATUS, "/tmp/drakTSservice.status"); local $_; while () { $buff[$buff_index] = "\t$_"; $buff_index++; } close STATUS; unlink "/tmp/drakTSservice.status" or warn("Can't delete /tmp/drakTSservice.status\n"); $buff_index; } sub start_ts { #- start the terminal server my $cmd_line = @_; my $pcimap = "/etc/dhcpd.conf.etherboot-pcimap.include"; @buff = (); if (-f $pcimap) { $buff[0] = "Starting Terminal Server...\n\n"; my $buff_index = service_change("dhcpd", "start", 2); $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 my $cmd_line = @_; @buff = (); $buff[0] = "Stopping Terminal Server...\n\n"; my $buff_index = service_change("dhcpd", "stop", 2); $buff_index = service_change("clusternfs", "stop", $buff_index); $buff[$buff_index] = "\n\tDone!"; if ($cmd_line == 1) { print "@buff\n"; return; } show_status(@buff); } sub show_status() { text_view("@buff"); } sub adduser { my ($cmd_line, $username) = @_; my @active_users = cat_("/etc/shadow"); my @ts_users = cat_('/etc/shadow$$CLIENT$$'); my $is_user = grep { /$username/ } @active_users; my $add_fail = 0; my $in_already; if ($is_user) { my @shadow_entry = grep { /$username/ } @active_users; my $is_ts_user = grep { /$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 "$username passwd bad in Terminal Server - rewriting...\n"; deluser($cmd_line, $username); adduser($cmd_line, $username); } } else { # new ts user local *FHANDLE; open(FHANDLE, '>> /etc/shadow$$CLIENT$$'); print FHANDLE $shadow_entry[0] or $add_fail = 1; close FHANDLE; $in_already = 0; } } if ($cmd_line == 1) { print "$username is not a user..\n" if !($is_user); print "$username is already a Terminal Server user\n" if $in_already; if ($add_fail == 1 || $in_already || !$is_user) { print "Addition of $username to Terminal Server failed!\n"; } else { print "$username added to Terminal Server\n"; } return; } else { $in_already; } } sub deluser { # del a user from the shadow$$CLIENT$$ file my ($cmd_line, $username) = @_; my $i; my $user_deleted; my @ts_users = cat_('/etc/shadow\$\$CLIENT$$'); my $is_ts_user = grep { /$username/ } @ts_users; if ($is_ts_user) { $i = 0; foreach my $user (@ts_users) { if ($user =~ /$username/) { splice(@ts_users, $i, 1); $user_deleted = 1; last; } $i++; } local *FHANDLE; open(FHANDLE, '> /etc/shadow$$CLIENT$$'); print FHANDLE $_ foreach @ts_users; close FHANDLE; } if ($cmd_line == 1) { if ($user_deleted) { print "Deleted $username...\n"; } else { print "$username not found...\n"; } return; } } sub addclient { #- add a new client entry after checking for dups my ($cmd_line, $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 $clients = "/etc/dhcpd.conf.etherboot.clients"; local *CLIENT; open(CLIENT, ">> $clients") or warn(N("Can't open %s!", $clients)); my $client_entry = format_client_entry($hostname, %ts_clients); print CLIENT $client_entry; close CLIENT; $changes_made = 1; 0; } } sub delclient { #- find a client and delete the entry in dhcpd.conf my ($cmd_line, $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 don't 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 $ip = get_ip_from_sys(); my @values = split(/\./, $ip); my $subnet = $values[0] . "." . $values[1] . "." . $values[2] . "."; my $i; if ($mode eq "enable") { my $has_all = `grep ALL /etc/hosts.allow`; if ($has_all) { $in->ask_warn('', 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\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 = "host $client {\n"; $entry .= "\thardware ethernet\t$ts_clients{$client}{hardware};\n"; $entry .= "\tfixed-address\t\t$ts_clients{$client}{address};\n"; $entry .= "\t#type\t\t\t$ts_clients{$client}{type};\n" if $ts_clients{$client}{type}; $entry .= "\tfilename\t\t\"$ts_clients{$client}{filename}\";\n" if $ts_clients{$client}{filename}; $entry .= "\t#hdw_config\t\t$ts_clients{$client}{hdw_config};\n" if $ts_clients{$client}{hdw_config}; $entry .= "}\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 $clients = "/etc/dhcpd.conf.etherboot.clients"; local *CLIENT; open(CLIENT, "> $clients") or warn("Can't open $clients!"); foreach my $key (keys(%ts_clients)) { my $client_entry = format_client_entry($key, %ts_clients); print CLIENT $client_entry; } close CLIENT } sub read_dhcpd_conf() { my $clients = "/etc/dhcpd.conf.etherboot.clients"; my %ts_clients; my $hostname; #- read and parse current client entries local *CLIENTS; open(CLIENTS, $clients) or warn("Can't open $clients\n"); while () { 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; } } } close CLIENTS; %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/XF86Config-4$$CLIENT$$', "/etc/X11/XF86Config-4$suffix") if -f '/etc/X11/XF86Config-4$$CLIENT$$'; cp_af("/dev/null", "/etc/modules.conf$suffix"); cp_af("/dev/null", "/etc/modules$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/X11/XF86Config$suffix\t$mnt_access\n"); append_to_file("/etc/exports", "/etc/X11/XF86Config-4$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 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/X11/XF86Config$suffix") }; eval { rm_rf("/etc/X11/XF86Config-4$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 = ''; } }