#!/usr/bin/perl -w

#
# Script to automatically restrict the printer information broadcasting the
# accepting of external broadcasts, and the access to the printers to local
# (eth?) networks 
#

#
# Till Kamppeter (till@mandrakesoft.com)
#
# Copyright 2001
#
# This software may be freely redistributed under the terms of the GNU
# General 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.
#

# Do not do any changes when the user chose manual configuration in
# printerdrake

my $manual = 0;
my $manualconffile = "/etc/sysconfig/printing";
if (open MANUALCONFFILE, "< $manualconffile") {
    @manualconf_content = <MANUALCONFFILE>;
    close MANUALCONFFILE;
    ($_ =~ /^\s*CUPS_CONFIG\s*=\s*manual\s*$/ and $manual = 1) foreach @manualconf_content;
}
if ($manual) {exit;}

# Read CUPS config file or create an empty one if necessary.

my $cups_conf = "/etc/cups/cupsd.conf";
my @cups_conf_content;

if (!(-f $cups_conf)) {
    warn "No CUPS configuration file $cups_conf, creating one ...\n";
    @cups_conf_content = ();
} else {
    open CONF_CUPS, "$cups_conf" or die "Can't open $cups_conf!";
    @cups_conf_content = <CONF_CUPS>;
    close CONF_CUPS;
}

# If it contains at least one "BrowseAddress" line broadcasting is already
# configured and we are done, stop silently here.

#grep(/^\s*BrowseAddress[^:]/, @cups_conf_content) and exit 0;

# Read the /etc/sysconfig/network-scripts/draknet_conf (if available)

my $internetaccess = "";
my $internetinterface = "";
my $draknet_conf = "/etc/sysconfig/network-scripts/draknet_conf";
if (open CONF_DRAKNET, "$draknet_conf") {
    my @draknet_conf_content = <CONF_DRAKNET>;
    close CONF_DRAKNET;

    # Check the internet connection type

    ($_ =~ /^\s*InternetAccessType=(.*)$/ and $internetaccess = $1) foreach @draknet_conf_content;
    
    # Check the internet access interface

    ($_ =~ /^\s*InternetInterface=(.*)$/ and $internetinterface = $1) foreach @draknet_conf_content;

    if ($internetaccess eq "lan") {$internetinterface = ""};

}

# Read the output of "ifconfig" to look for local networks

my $dev_is_localnet = 0;
my @local_networks = ();
my @local_ips = ();
my @local_bcasts = ();
my @local_ifaces = ();
my $current_ip = "";
my $current_mask = "";
my $current_bcast = "";

if (-x "/sbin/ifconfig") {
  open IFCONFIG_OUT, "export LC_MESSAGES=C; /sbin/ifconfig|" or die "Couldn't run \"ifconfig\"!";
  while (defined($readline = <IFCONFIG_OUT>)) {
    # New entry ...
    if ($readline =~ /^(\S+)\s/) {
      $dev = $1;
      # ... for a local network (eth = ethernet, vmnet = VMWare,
      #                          ethernet card connected to ISP excluded)?
      if ((($dev =~ /^eth/) || ($dev =~ /^vmnet/)) &&
	  (!($dev eq $internetinterface))) {$dev_is_localnet = 1} 
      else {$dev_is_localnet = 0};
      # delete previous network data
      $current_ip = "";
      $current_mask = "";
      $current_bcast = "";
    }
    # Are we in an entry for a local network?
    if ($dev_is_localnet == 1) {
      # Are we in the important line now?
      if ($readline =~ /\sinet addr:[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\s/) {
        # Rip out the network addresses
        if ($readline =~ /\sinet addr:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s/) {
          $current_ip = $1;
        }
        if ($readline =~ /\sBcast:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s/) {
          $current_bcast = $1;
        }
        if ($readline =~ /\sMask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s/) {
          $current_mask = $1;
        }
        # Is the entry valid?
        if (($current_ip ne "") and ($current_mask ne "")) {
	  # Store interface name
	  push @local_ifaces, $dev;
          # Store current IP address
	  push @local_ips, $current_ip;
          # Is there a broadcast address?
          if ($current_bcast eq "") {$current_bcast = $current_ip};
          # Store broadcast address (or current IP if there is none)
	  push @local_bcasts, $current_bcast;
          # Calculate mask for access restriction
          $current_ip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/;
	  ($i1, $i2, $i3, $i4) = ($1, $2, $3, $4);
          $current_mask =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/;
	  ($m1, $m2, $m3, $m4) = ($1, $2, $3, $4);
          $current_net = "";
          if ($m1 eq "255") {$current_net = "${current_net}${i1}\."};
          if ($m2 eq "255") {$current_net = "$current_net$i2."};
          if ($m3 eq "255") {$current_net = "$current_net$i3."};
          if ($m4 eq "255") {$current_net = "$current_net$i4"}
          else {$current_net = "$current_net*"};
          # Store mask
	  push @local_networks, $current_net;
        }
      }
    }
  }
  close(IFCONFIG_OUT);
}

# Find principal IP (the one to set as "ServerName" and to use for
# broadcasting.

my $servername = "";
my $browserelayfrom = "";
if ($#local_networks == -1) {
    # No local network at all => No broadcasting, ServerName = "localhost"
    $servername = "127.0.0.1";
    @local_bcasts = ("127.0.0.1");
} elsif ($#local_networks == 0) {
    # One local network => Broadcast to this network, ServerName = IP address
    $servername = $local_ips[0];
    @local_bcasts = ($local_bcasts[0]);
} else {
    # Two or more local networks
    # try to find out which local network leads to outside, do only
    # broadcast to the others. BrowseRelay the printers from outside to
    # inside.
    my $togateway = "";
    if (-x "/sbin/route") {
	open ROUTE_OUT, "export LC_MESSAGES=C; /sbin/route|" or die "Couldn't run \"route\"!";
	while ($readline = <ROUTE_OUT>) {
	    if ($readline =~ m!^default\s+.*\s+(\S+)$!) {
		$togateway = $1;
	    }
	}
	close ROUTE_OUT;
    }
    # Mark network with the gateway for being removed from the broadcast list,
    # set network with gateway as the network from where to browserelay.
    my $gatewaynet;
    my $i;
    for ($i = 0; $i <= $#local_networks; $i++) {
	if ($local_ifaces[$i] eq $togateway) {
	    $browserelayfrom = $local_networks[$i];
	    $gatewaynet = $i;
	    last;
	}
    }
    # Check if we have a real hostname (not "localhost"). If so, we can use it
    # as the ServerName and then broadcast to all internal subnets. If not, we
    # must set the IP of the first internal network as the ServerName and only
    # broadcast to that subnet. If we have only one network to broadcast to, we
    # set the IP of this network as the ServerName.
    my $hostname = `hostname`;
    if (($hostname eq "localhost") || ($hostname =~ m!^localhost\.!) ||
	((defined($gatewaynet)) && ($#local_networks == 1))) {
	if ((!defined($gatewaynet)) || ($gatewaynet != 0)) {
 	    $servername = $local_ips[0];
	    @local_bcasts = ($local_bcasts[0]);
	} else {
 	    $servername = $local_ips[1];
	    @local_bcasts = ($local_bcasts[1]);
	}
    } else {
	$servername = "";
	# Remove the network with the gateway from the broadcast list
	if (defined($gatewaynet)) {
	    splice(@local_bcasts,$gatewaynet,1);
	}
    }
}

# Check whether LPD/LPRng is installed and turn off creation of an
# /etc/printcap file by CUPS.

my $printcap = "/etc/printcap";
if (-x "/usr/sbin/lpd") {
    $printcap = "";
}

# Remove all valid "Printcap" lines
($_ =~ /^\s*Printcap[^:]/ and $_="") foreach @cups_conf_content;

# Insert the new "Printcap" line
if ($printcap ne "/etc/printcap") {
    push @cups_conf_content, "Printcap $printcap\n";
}

# Remove all valid "TempDir" lines
($_ =~ /^\s*TempDir[^:]/ and $_="") foreach @cups_conf_content;

# Insert the new "Printcap" line
push @cups_conf_content, "TempDir /var/spool/cups/tmp\n";

# Delete the former "Port" and "Listen" lines and collect the port
# numbers of them
my $ports = {};
($_ =~ /^\s*Port\s*(\d+)\s*$/ and do {$ports->{$1}=1;$_=""}) foreach @cups_conf_content;
($_ =~ /^\s*Listen\s*[^:\s]+:(\d+)\s*$/ and do {$ports->{$1}=1;$_=""}) foreach @cups_conf_content;
if (keys %{$ports} < 0) {
    $ports->{'631'}=1;
}
# Insert a "Listen" line for each local interface and each port
for my $port (keys %{$ports}) {
    push @cups_conf_content, "Listen 127.0.0.1:$port\n";
    (push(@cups_conf_content,"Listen $_:$port\n")) foreach @local_ips;
}
   
# Remove all valid "ServerName" lines
($_ =~ /^\s*ServerName[^:]/ and $_="") foreach @cups_conf_content;

# Insert the new "ServerName" line
if ($servername ne "") {
    push @cups_conf_content, "ServerName $servername\n";
}
   
# Remove all valid "BrowseAddress" lines
($_ =~ /^\s*BrowseAddress[^:]/ and $_="") foreach @cups_conf_content;

# Insert the new "BrowseAddress" lines
(push @cups_conf_content, "BrowseAddress $_\n") foreach @local_bcasts;

# Remove all valid "BrowseRelay" lines
($_ =~ /^\s*BrowseRelay[^:]/ and $_="") foreach @cups_conf_content;

# Insert the new "BrowseAddress" lines
if ($browserelayfrom ne "") {
    (push @cups_conf_content, "BrowseRelay $browserelayfrom $_\n") foreach @local_bcasts;
}

# Delete all "BrowseOrder", "BrowseAllow" and "BrowseDeny" lines from the file

($_ =~ /^\s*BrowseOrder/ and $_="") foreach @cups_conf_content;
($_ =~ /^\s*BrowseAllow/ and $_="") foreach @cups_conf_content;
($_ =~ /^\s*BrowseDeny/ and $_="") foreach @cups_conf_content;

# Add the new "BrowseOrder" and "BrowseDeny" lines

push(@cups_conf_content,"BrowseOrder Deny,Allow\n");
push(@cups_conf_content,"BrowseDeny All\n");
push(@cups_conf_content,"BrowseAllow 127.0.0.1\n");

# Add a "BrowseAllow" line for every local network

(push(@cups_conf_content,"BrowseAllow $_\n")) foreach @local_networks;

# Cut out the root location block
#
#   <Location />
#   ...
#   </Location>
#
# 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 ($i = 0; 
       ($i <= $#cups_conf_content) and ($root_location_end == -1);
       $i++) {
    if ($cups_conf_content[$i] =~ m!^\s*<\s*Location\s+/\s*>!) {
      # Start line of block
      $root_location_start = $i;
    } elsif (($cups_conf_content[$i] =~ m!^\s*<\s*/Location\s*>!) and
               ($root_location_start != -1)) {
      # End line of block
      $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 + 1;
  @root_location = ();
  push @root_location, "<Location />\n";
  push @root_location, "</Location>\n";
}

# Delete all former "Order", "Allow", and "Deny" lines from the root location
# block

($_ =~ /^\s*Order/ and $_="") foreach @root_location;
($_ =~ /^\s*Allow/ and $_="") foreach @root_location;
($_ =~ /^\s*Deny/ and $_="") foreach @root_location;

# Add the new "Order" and "Deny" lines

splice(@root_location,-1,0,"Order Deny,Allow\n");
splice(@root_location,-1,0,"Deny From All\n");
splice(@root_location,-1,0,"Allow From 127.0.0.1\n");

# Add an "Allow" line for every local network

(splice(@root_location,-1,0,"Allow From $_\n")) foreach @local_networks;

# Put the changed root location block back into the file

splice(@cups_conf_content,$root_location_start,0,@root_location);

# Is there an "admin" location block? If not, add a default one to give
# security
if (!(grep(m!^\s*<Location\s+/admin\s*>!, @cups_conf_content))) {
    push @cups_conf_content, "<Location /admin>\n";
    push @cups_conf_content, "AuthType Basic\n";
    push @cups_conf_content, "AuthClass System\n";
    push @cups_conf_content, "Order Deny,Allow\n";
    push @cups_conf_content, "Deny From All\n";
    push @cups_conf_content, "Allow From 127.0.0.1\n";
    push @cups_conf_content, "</Location>\n";
}

# Write back the modified CUPS config file

open CONF_CUPS, ">$cups_conf" or die "Can't open $cups_conf";
print CONF_CUPS @cups_conf_content;
close CONF_CUPS;
