#!/usr/bin/perl

#
# Guillaume Cottenceau (gc@mandrakesoft.com)
#
# Copyright 2000 MandrakeSoft
#
# This software may be freely redistributed under the terms of the GNU
# public license.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

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

use common qw(:common :system :file);
use interactive;
use standalone;
use log;
use c;
use netconnect;
use detect_devices;

$::isInstall and die "Not supported during install.\n";


$::isEmbedded = ($::XID, $::CCPID) = "@ARGV" =~ /--embedded (\w+) (\w+)/;

local $_ = join '', @ARGV;

/-h/ and die "usage: drakgw [--version]\n";
/-version/ and die 'version: $Id$ '."\n";
$::isEmbedded or $::isWizard = 1;
$::direct = /-direct/;


my $sysconf_network = "/etc/sysconfig/network";
my $conf_linuxconf = "/etc/conf.linuxconf";
my $rc_firewall_generic = "/etc/rc.d/rc.firewall";
my $rc_firewall_22 = "/etc/rc.d/rc.firewall.inet_sharing-2.2";
my $rc_firewall_24 = "/etc/rc.d/rc.firewall.inet_sharing-2.4";
my $dhcpd_conf = "/etc/dhcpd.conf";
my $cups_conf = "/etc/cups/cupsd.conf";
my $drakgw_setup = "/etc/sysconfig/inet_sharing";


my ($kernel_version) = c::kernel_version() =~ /(...)/;
log::l("[drakgw] kernel_version $kernel_version");

$kernel_version eq '2.2' || $kernel_version eq '2.4' or die "Only for 2.2 or 2.4 kernels.\n";


my $in = interactive::vnew('su');


sub sys { system(@_) == 0 or die "sys: $_[0]" }


sub start_daemons()
{
    my $cups_used = 0;
    log::l("[drakgw] Starting daemons");
    if (-f "/etc/rc.d/init.d/cups") {
        if (grep(/is running/, `/etc/rc.d/init.d/cups status`)) {
           $cups_used = 1;
           sys("/etc/rc.d/init.d/cups stop");
        }
    }
    grep(/is running/, `/etc/rc.d/init.d/dhcpd status 2> /dev/null`) and sys("/etc/rc.d/init.d/dhcpd stop");
    grep(/connection refused/, `/etc/rc.d/init.d/named status 2> /dev/null`) or sys("/etc/rc.d/init.d/named stop");
    sys("/etc/rc.d/init.d/network restart");
    sys("sh $rc_firewall_generic");

    sys("/etc/rc.d/init.d/$_ start"), sys("/sbin/chkconfig --level 345 $_ on") foreach 'named', 'dhcpd';
    sys("/etc/rc.d/init.d/cups start") if $cups_used;
    output $drakgw_setup, "INET_SHARING=enabled\n";
}

sub stop_daemons()
{
    log::l("[drakgw] Stopping daemons");
    grep /is running/, `/etc/rc.d/init.d/dhcpd status` and sys("/etc/rc.d/init.d/dhcpd stop");
    grep /Connection refused/, `/etc/rc.d/init.d/named status 2>&1` or sys("/etc/rc.d/init.d/named stop");
    if ($kernel_version eq "2.2") {
	sys("/sbin/ipchains -F");
    } else {
	sys("/sbin/iptables -t nat -F");
    }
    sys("/sbin/chkconfig --level 345 $_ off") foreach 'named', 'dhcpd';

    output $drakgw_setup, "INET_SHARING=disabled\n";
}

sub fatal_quit($)
{
    log::l("[drakgw] FATAL: $_[0]");
    undef $wait_configuring;
    $in->ask_warn('', $_[0]);
    quit_global($in, -1);
}


while ($::isEmbedded) {
    kill USR2, $::CCPID;

    my $setup_state = grep(/disabled/, cat_($drakgw_setup)) ? _("The setup has already been done, but it's currently disabled.") :
                      grep(/enabled/, cat_($drakgw_setup))  ? _("The setup has already been done, and it's currently enabled.") :
                      _("No Internet Connection Sharing has ever been configured.");
    
    $in->ask_okcancel('', 
_("Welcome to the Internet Connection Sharing utility!

%s

Click on ``Ok'' if you want to launch the setup wizard.", $setup_state)) and system("/usr/sbin/drakgw");

    kill USR1, $::CCPID;
}


begin:

#- **********************************
#- * 0th step: verify if we are already set up

if (-f $drakgw_setup) {
    $::Wizard_no_previous = 1;

    if (grep(/enabled/, cat_($drakgw_setup))) {
	my $r = $in->ask_from_list_(_("Internet Connection Sharing currently enabled"),
_("The setup of Internet connection sharing has already been done.
It's currently enabled. 

What would you like to do?"),
				   [ __("disable"), __("reconfigure"), __("dismiss") ]) or quit_global($in, 0);
	if ($r eq "disable") {
	    {
		my $wait_disabl = $in->wait_message('', _("Disabling servers..."));
		stop_daemons();
	    }
	    foreach ($dhcpd_conf, $rc_firewall_22, $rc_firewall_24) {
		renamef($_, "$_.drakgwdisable") or die "Could not rename $_ to $_.drakgwdisable" 
	    }
	    log::l("[drakgw] Disabled");
	    $::Wizard_finished = 1;
	    $in->ask_okcancel('', _("Internet connection sharing is now disabled."));
	    quit_global($in, 0);
	}
	if ($r eq "dismiss") {
	    quit_global($in, 0);
	}
    }
    elsif (grep(/disabled/, cat_($drakgw_setup)))
    {
	my $r = $in->ask_from_list_(_("Internet Connection Sharing currently disabled"),
_("The setup of Internet connection sharing has already been done.
It's currently disabled.

What would you like to do?"),
				   [ __("enable"), __("reconfigure"), __("dismiss") ]);
	if ($r eq "enable") {
	    foreach ($dhcpd_conf, $rc_firewall_22, $rc_firewall_24) {
		rename($_, "$_.old") if -f $_;
		rename("$_.drakgwdisable", $_) or die "Could not find configuration. Please reconfigure.";
	    }
	    {
		my $wait_enabl = $in->wait_message('', _("Enabling servers..."));
		start_daemons();
	    }
            log::l("[drakgw] Enabled");
	    $::Wizard_finished = 1;
	    $in->ask_okcancel('', _("Internet connection sharing is now enabled."));
	    quit_global($in, 0);
	}
	if ($r eq "dismiss") {
	    quit_global($in, 0);
	}
    }
    else {
	$in->ask_warn(_("Unrecognized config file"), _("Config file content could not be interpreted."));
	quit_global($in, -1);
    }
}


#- **********************************
#- * 1st step: detect/setup
step_ask_confirm:

$::Wizard_no_previous = 1;
    
$::direct or $in->ask_okcancel(_("Internet Connection Sharing"),
_("You are about to configure your computer to share its Internet connection.

Note: you need a dedicated Network Adapter to set up a Local Area Network (LAN)."), 1) or quit_global($in, 0);

undef $::Wizard_no_previous;


step_detectsetup:

my @configured_devices = map { /ifcfg-(\S+)/ } `ls /etc/sysconfig/network-scripts/ifcfg*`;

my %aliased_devices; 
/^\s*alias\s+(eth[0-9])\s+(\S+)/ and $aliased_devices{$1} = $2 foreach cat_("/etc/modules.conf");

my $card_netconnect = netconnect::get_net_device();
defined $card_netconnect and log::l("[drakgw] Information from netconnect: ignore card $card_netconnect");

my @cards = grep {
    log::l("[drakgw] Have network card: $_");
    $_ ne $card_netconnect
} detect_devices::getNet();
log::l("[drakgw] Available network cards: ", join(", ", @cards));

my $format = sub {
    $aliased_devices{$_[0]} ?
      _("Interface %s (using module %s)", $_[0], $aliased_devices{$_[0]}) :
      _("Interface %s", $_[0]);
};

#- setup the network interface we shall use

my $device;
if (!@cards)
{
    $in->ask_warn(_("No network adapter on your system!"), 
		  _("No ethernet network adapter has been detected on your system. Please run the hardware configuration tool."));
    quit_global($in);
}
elsif (@cards == 1)
{
    $device = $cards[0];
    print "device $device\n";
    my $t = $format->($device);
    print "format $t\n";

    $in->ask_okcancel(_("Network interface"),
_("There is only one configured network adapter on your system:

%s

I am about to setup your Local Area Network with that adapter.", $format->($device)), 1) or goto step_ask_confirm;
}
else
{
    $device = $in->ask_from_listf(_("Choose the network interface"),
				    _("Please choose what network adapter will be connected to your Local Area Network."),
				     $format,
				    \@cards,
				    ) or goto step_ask_confirm;
    defined $device or quit_global($in);
}
log::l("[drakgw] Choosing network card: $device\n");

grep(/$device/, @configured_devices) and
    ($in->ask_okcancel('', _("Warning, the network adapter is already configured. I will reconfigure it.")) or goto step_detectsetup);


my $lan_address = "192.168.0";


#- test for potential conflict with other networks

foreach (grep { $_ ne $device } @configured_devices)
{
    grep(/$lan_address/, cat_("/etc/sysconfig/network-scripts/ifcfg-$_")) and
      ($in->ask_warn('', _("Potential LAN address conflict found in current config of $_!\n")) or goto step_detectsetup);
}


#- test for potential conflict with previous firewall config

if ($kernel_version eq '2.2') {
    if (-f '/etc/sysconfig/ipchains' || -x '/sbin/ipchains' && listlength(`/sbin/ipchains -nL`) > 3) {
	$in->ask_okcancel(_("Firewalling configuration detected!"),
			  _("Warning! An existing firewalling configuration has been detected. You may need some manual fix after installation.")) or goto step_detectsetup;
    }
} else {
    system('modprobe iptable_nat');
    if (-f '/etc/sysconfig/iptables' || -x '/sbin/iptables' && listlength(`/sbin/iptables -t nat -nL`) > 8) {
	$in->ask_okcancel(_("Firewalling configuration detected!"),
			  _("Warning! An existing firewalling configuration has been detected. You may need some manual fix after installation.")) or goto step_detectsetup;
    }
}


#- **********************************
#- * 2nd step: configure

$wait_configuring = $in->wait_message(_("Configuring..."), 
				      _("Configuring scripts, installing software, starting servers..."));

#- setup the /etc/sysconfig/network-script/ script

my $network_scripts = "/etc/sysconfig/network-scripts";
my $ifcfg = "$network_scripts/ifcfg-$device";
renamef($ifcfg, "$network_scripts/old.ifcfg-$device");
output($ifcfg, qq(DEVICE=$device
BOOTPROTO=static
IPADDR=$lan_address.1
NETMASK=255.255.255.0
NETWORK=$lan_address.0
BROADCAST=$lan_address.255
ONBOOT=yes
));


#- install and setup the RPM packages

my $rpms_to_install;
my %rpm2file = ( ipchains => "/sbin/ipchains",
		 iptables => "/sbin/iptables",
		 dhcp => "/usr/sbin/dhcpd",
		 linuxconf => $conf_linuxconf,
		 bind => "/usr/sbin/named",
		 "caching-nameserver" => "/var/named/named.local");

$in->standalone::pkgs_install(grep { !-e $rpm2file{$_} } keys %rpm2file);
-e $rpm2file{$_} or fatal_quit(_("Problems installing package $_")) foreach keys %rpm2file;


#- setup the masquerading configuration

if (!-f $rc_firewall_generic || !grep(/drakgw/, cat_($rc_firewall_generic))) {
    output($rc_firewall_generic, q(#!/bin/sh
#
# Automatically generated by drakgw

KERNELMAJ=`uname -r | sed                   -e 's,\..*,,'`
KERNELMIN=`uname -r | sed -e 's,[^\.]*\.,,' -e 's,\..*,,'`

if [ "$KERNELMAJ" -eq 2 -a "$KERNELMIN" -eq 2 ]; then
    [ -x ) . $rc_firewall_22 . ' ] && ' . $rc_firewall_22 . q(
fi
if [ "$KERNELMAJ" -eq 2 -a "$KERNELMIN" -eq 4 ]; then
    [ -x ) . $rc_firewall_24 . ' ] && ' . $rc_firewall_24 . q(
fi
    ));
}

chmod 0700, $rc_firewall_generic;


output($rc_firewall_22, qq(#!/bin/sh
#
# rc.firewall - Initial SIMPLE IP Masquerade test for 2.1.x and 2.2.x kernels using IPCHAINS
#
# Load all required IP MASQ modules
#
#   NOTE:  Only load the IP MASQ modules you need.  All current IP MASQ modules
#          are shown below but are commented out from loading.

# Needed to initially load modules
#
/sbin/depmod -a

# Supports the proper masquerading of FTP file transfers using the PORT method
#
/sbin/modprobe ip_masq_ftp

# Supports the masquerading of RealAudio over UDP.  Without this module,
#       RealAudio WILL function but in TCP mode.  This can cause a reduction
#       in sound quality
#
/sbin/modprobe ip_masq_raudio

# Supports the masquerading of IRC DCC file transfers
#
/sbin/modprobe ip_masq_irc


# Supports the masquerading of Quake and QuakeWorld by default.  This modules is
#   for for multiple users behind the Linux MASQ server.  If you are going to play
#   Quake I, II, and III, use the second example.
#
#   NOTE:  If you get ERRORs loading the QUAKE module, you are running an old
#   -----  kernel that has bugs in it.  Please upgrade to the newest kernel.
#
#Quake I / QuakeWorld (ports 26000 and 27000)
#/sbin/modprobe ip_masq_quake
#
#Quake I/II/III / QuakeWorld (ports 26000, 27000, 27910, 27960)
/sbin/modprobe ip_masq_quake 26000,27000,27910,27960


# Supports the masquerading of the CuSeeme video conferencing software
#
/sbin/modprobe ip_masq_cuseeme

#Supports the masquerading of the VDO-live video conferencing software
#
/sbin/modprobe ip_masq_vdolive


#CRITICAL:  Enable IP forwarding since it is disabled by default since
#
#           Redhat Users:  you may try changing the options in /etc/sysconfig/network from:
#
#                       FORWARD_IPV4=false
#                             to
#                       FORWARD_IPV4=true
#
echo 1 > /proc/sys/net/ipv4/ip_forward


# Dynamic IP users:
#
#   If you get your IP address dynamically from SLIP, PPP, or DHCP, enable this following
#       option.  This enables dynamic-ip address hacking in IP MASQ, making the life 
#       with Diald and similar programs much easier.
#
#echo 1 > /proc/sys/net/ipv4/ip_dynaddr


# MASQ timeouts
#
#   2 hrs timeout for TCP session timeouts
#  10 sec timeout for traffic after the TCP/IP "FIN" packet is received
#  160 sec timeout for UDP traffic (Important for MASQ'ed ICQ users) 
#
/sbin/ipchains -M -S 7200 10 160


# DHCP:  For people who receive their external IP address from either DHCP or BOOTP
#        such as ADSL or Cablemodem users, it is necessary to use the following
#        before the deny command.  The "bootp_client_net_if_name" should be replaced
#        the name of the link that the DHCP/BOOTP server will put an address on to?
#        This will be something like "eth0", "eth1", etc.
#
#        This example is currently commented out.
#
#
#/sbin/ipchains -A input -j ACCEPT -i bootp_clients_net_if_name -s 0/0 67 -d 0/0 68 -p udp

# Enable simple IP forwarding and Masquerading
#
#  NOTE:  The following is an example for an internal LAN address in the 192.168.0.x
#         network with a 255.255.255.0 or a "24" bit subnet mask.
#
#         Please change this network number and subnet mask to match your internal LAN setup
#
/sbin/ipchains -P forward DENY
/sbin/ipchains -A forward -s $lan_address.0/24 -j MASQ
));
chmod 0700, $rc_firewall_22;


output($rc_firewall_24, qq(#!/bin/sh
# Load the NAT module (this pulls in all the others).
modprobe iptable_nat

# Turn on IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

# In the NAT table (-t nat), Append a rule (-A) after routing (POSTROUTING)
# which says to MASQUERADE the connection (-j MASQUERADE).
iptables -t nat -A POSTROUTING -s $lan_address.0/24 -j MASQUERADE
));
chmod 0700, $rc_firewall_24;


#- be sure that FORWARD_IPV4 is enabled in /etc/sysconfig/network

substInFile { s/^FORWARD_IPV4.*\n//; $_ .= "FORWARD_IPV4=true\n" if eof } $sysconf_network;


#- setup the DHCP server

renamef($dhcpd_conf, "$dhcpd_conf.old");
output($dhcpd_conf, qq(subnet $lan_address.0 netmask 255.255.255.0 {
	# default gateway
	option routers $lan_address.1;
	option subnet-mask 255.255.255.0;

	option domain-name "homelan.org";
	option domain-name-servers $lan_address.1;

	range dynamic-bootp $lan_address.16 $lan_address.253;
	default-lease-time 21600;
	max-lease-time 43200;
}
));


#- put the interface for the dhcp server in linuxconf config, for the /etc script of dhcpd

substInFile { s/^DHCP.interface.*\n//; $_ .= "DHCP.interface $device\n" if eof } $conf_linuxconf;


#- Set up /etc/cups/cupsd.conf to make the broadcasting of the printer info
#- working correctly: 
#- 
#-  1. ServerName <server's IP address>  # because clients do necessarily 
#-                                       # know the server's name
#-
#-  2. BrowseAddress <server's Broadcast IP> # broadcast printer info into
#-                                           # the local network.
#-
#-  3. BrowseOrder Deny,Allow
#-     BrowseDeny All
#-     BrowseAllow <IP mask for local net> # Only accept broadcast signals 
#-                                         # coming from local network
#-
#-  4. <Location />
#-     Order Deny,Allow
#-     Deny From All
#-     Allow From <IP mask for local net> # Allow only machines of local 
#-     </Location>                        # network to access the server
#-     
#- These steps are only done when the CUPS package is installed.

substInFile {
    s/^ServerName[^:].*\n//; $_ .= "ServerName $lan_address.1\n" if eof;
    s/^BrowseAddress.*\n//; $_ .= "BrowseAddress $lan_address.255\n" if eof;
    s/^BrowseOrder.*\n//; $_ .= "BrowseOrder Deny,Allow\n" if eof;
    s/^BrowseDeny.*\n//; $_ .= "BrowseDeny All\n" if eof;
    s/^BrowseAllow.*\n//; $_ .= "BrowseAllow $lan_address.*\n" if eof;
} $cups_conf;


#- Modify the root location block in /etc/cups/cupsd.conf

if (-f $cups_conf) {
    my @cups_conf_content = cat_($cups_conf);
    my @root_location; my $root_location_start; my $root_location_end;

    # Cut out the root location block so that it can be treated seperately
    # without affecting the rest of the file
    if (grep(m|^\s*<Location\s+/\s*>|, @cups_conf_content)) {
	$root_location_start = -1;
	$root_location_end = -1;
	# Go through all the lines, bail out when start and end line found
	for (my $i = 0; $i < @cups_conf_content && $root_location_end == -1; $i++) {
	    if ($cups_conf_content[$i] =~ m|^\s*<\s*Location\s+/\s*>|) {
		$root_location_start = $i;
	    } elsif (($cups_conf_content[$i] =~ m|^\s*<\s*/Location\s*>|) && ($root_location_start != -1)) {
		$root_location_end = $i;
	    }
	}
	# Rip out the block and store it seperately
	@root_location = splice(@cups_conf_content, $root_location_start, $root_location_end - $root_location_start + 1);
    } else {
	# If there is no root location block, create one
	$root_location_start = @cups_conf_content;
	@root_location = ("<Location />\n", "</Location>\n");
    }
 
    # Delete all former "Order", "Allow", and "Deny" lines from the root location block
    s/^\s*Order.*//, s/^\s*Allow.*//, s/^\s*Deny.*// foreach @root_location;
 
    # Add the new "Order" and "Deny" lines, add an "Allow" line for the local network
    splice(@root_location, -1, 0, $_) foreach ("Order Deny,Allow\n", "Deny From All\n", "Allow From 127.0.0.1\n",
					       "Allow From $lan_address.*\n");
 
    # Put the changed root location block back into the file
    splice(@cups_conf_content, $root_location_start, 0, @root_location);
 
    output $cups_conf, @cups_conf_content;
}


#- start the daemons

start_daemons();


#- bye-bye message

undef $wait_configuring;

$::Wizard_no_previous = 1;
$::Wizard_finished = 1;

$in->ask_okcancel(_("Congratulations!"), 
_("Everything has been configured.
You may now share Internet connection with other computers on your Local Area Network, using automatic network configuration (DHCP)."));


log::l("[drakgw] Installation complete, exiting\n");
quit_global($in, 0);

sub quit_global {
    my ($in, $exitcode) = @_;
    $in->exit($exitcode);
    goto begin
}



#-------------------------------------------------
#- $Log$
#- Revision 1.29  2001/03/26 15:29:01  gc
#- first attempt at pixelization of code (till's cups patches)
#-
#- Revision 1.28  2001/03/21 18:07:36  gc
#- honour embedded mode
#-
#- Revision 1.27  2001/03/13 16:23:29  gc
#- fix for bind
#-
#- Revision 1.26  2001/03/13 15:31:05  gc
#- - fix destructive parts of pixelization
#- - fix some own bugs
#-
#- Revision 1.25  2001/03/13 00:00:11  prigaux
#- pixelization
#-
#- Revision 1.24  2001/03/12 18:26:16  gc
#- - make it work as a wizard
#- - make it work with iptables (kernel-2.4)
#-
#- Revision 1.23  2001/03/01 00:18:17  damien
#- updated embedded mode
#-
#- Revision 1.22  2001/02/26 18:39:12  prigaux
#- pixelization
#-
#- Revision 1.21  2001/02/08 10:11:37  damien
#- implemented or updated embedded mode
#-
#- Revision 1.20  2001/02/08 07:00:41  damien
#- added embedded and (ugly) wizard mode.
#-
#- Revision 1.19  2001/01/10 00:32:42  prigaux
#- use standalone and standalone::pkgs_install
#-
#- Revision 1.18  2000/12/16 16:13:34  prigaux
#- use ldetect-lst
#-
#- Revision 1.17  2000/11/13 15:48:33  gc
#- Integrate Till's patches for better work with Cups.
#-
#- Revision 1.16  2000/10/10 15:31:50  gc
#- make only one call to urpmi in order to install all the needed rpm's
#-