#!/usr/bin/perl

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell

eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
    if 0; # not running under some shell

#   $Id: mkdhcpconf 5135 2006-07-12 09:28:37Z efocht $

#   Copyright (c) 2001 International Business Machines
#
#   Copyright (c) 2004-2006 Erich Focht <efocht@hpce.nec.com>
#                           All rights reserved

#   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 of the License, 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 strict;
use vars qw($config);
use lib "/usr/lib/systeminstaller";
use lib "/usr/lib/systemconfig";
use SIS::Client;
use SIS::Adapter;
use SIS::Image;
use SIS::DB;
use SystemInstaller::Env;
use SystemInstaller::Log qw(start_verbose stop_verbose verbose);
use Net::Netmask;
use POSIX;
use Carp;
use AppConfig qw(:argcount);


$config->define(
        Help=>{ ARGCOUNT=> ARGCOUNT_NONE,
                ALIAS => "h"},
        interface=>{ ARGCOUNT=> ARGCOUNT_ONE,
                DEFAULT=> "eth0"},
        bootfile=> {ARGCOUNT=> ARGCOUNT_ONE,
                DEFAULT=> "pxelinux.0:i686,pxelinux.0:i586,pxelinux.0:x86_64,elilo.efi:ia64"},
        gateway=>{ARGCOUNT=> ARGCOUNT_ONE},
        domain=>{ARGCOUNT=> ARGCOUNT_ONE},
        nameservers=>{ARGCOUNT=> ARGCOUNT_ONE},
        outfile=> {ARGCOUNT=> ARGCOUNT_ONE},
        multicast=>{ARGCOUNT=> ARGCOUNT_ONE},
);

unless ($config->getopt()){
	&usage;
	exit 1;
}

unless (&check_args) {
	&usage;
	exit 1;
}

if ($config->Help){
	&usage;
	exit 0;
}

&verbose("Opening output file.");
my $ofn=$config->outfile;
open(OUTFILE,">$ofn") or croak("Could not open output file.");

# Get some info about the local host.
&verbose("Getting local host info.");
my %HINFO;
($HINFO{ip},$HINFO{bcast},$HINFO{mask})=&find_internal_ip($config->interface);
my $block=new Net::Netmask ($HINFO{ip},$HINFO{mask});
$HINFO{net}=$block->base();

$HINFO{host} = (uname)[1];
if ($config->domain) {
	$HINFO{domain}=$config->domain;
}else{
	my $junk;
	($junk,$HINFO{domain})  = split(/\./,$HINFO{host},2);
}

# parse new bootfile options
my %bootfile;
if ($config->bootfile) {
    foreach (split(/,/,$config->bootfile)) {
	my ($bname,$arch) = split(/:/);
	if (!$arch) {
	    $arch = `uname -m`;
            chomp $arch;
	}
	$bootfile{$arch} = $bname;
    }
}

&verbose("Getting dhcpd version");
my $dhcpdver=&dhcpd_version;

&verbose("Resolving machine list.");
my @machinelist = list_client();
my @adaplist = list_adapter();

# collect all domain names
# - first take those passed on the command line
# - add domains of all defined machines
my %all_domains;
for my $dom (split(" ",$HINFO{domain})) {
    $dom =~ s/^\.//;
    next if ($dom eq "");
    $all_domains{$dom} = 1;
}
foreach my $mach (@machinelist) {
    if ($mach->domainname) {
	my $k = $mach->domainname;
	$all_domains{$k} = 1;
    }
}
$HINFO{domain} = join(" ",keys(%all_domains));

&verbose("Printing file preamble");
&preamble(%HINFO);

# write host specific entries
foreach my $mach (@machinelist) {
    foreach my $adapter (@adaplist){
	if (($mach->name eq $adapter->client) && ($adapter->mac) ) {
	    &write_entry($mach, $adapter);
	}
    }
}
# interface definitions for appliance adapters
foreach my $adapter (@adaplist){
    if (($adapter->client =~ /^__.*__$/) && ($adapter->mac) ) {
	&write_appl_entry($adapter);
    }
}

print OUTFILE "\t}\n}\n";
&verbose("Adding other networks");
my @INTS=&find_ints;
foreach my $int (@INTS) {
	unless (($int eq "lo")||($int eq $config->interface)){
		my ($ip,$bcast,$mask)=&find_internal_ip($int);
		$mask ||= "255.255.255.0"; # ensure non-null mask
		my $block=new Net::Netmask ($ip,$mask);
		my $net=$block->base();
		print OUTFILE "\n# This entry ignores requests on $int...\n";
		print OUTFILE "subnet $net netmask $mask {\n\tnot authoritative;\n}\n";
	}
}
close(OUTFILE);

exit 0; 

sub preamble {
	my %HINFO = @_;
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$idst)= localtime(time);
	$year=($year+1900);
	print OUTFILE <<PREAMBLE;
####################################################################
# This dhcpd.conf file was generated by the systeminstaller command
# mkdhcpconf. It reflects the contents of the CLAMDR database.
# File generated at $hour:$min:$sec on $mon/$mday/$year
####################################################################

PREAMBLE
	print OUTFILE "deny unknown-clients;\n";
	print OUTFILE "option subnet-mask $HINFO{mask};\n";
	print OUTFILE "option broadcast-address $HINFO{bcast};\n";
	print OUTFILE "option domain-name \"$HINFO{domain}\";\n";
	if ($config->gateway) {
		print OUTFILE "option routers ".$config->gateway.";\n";
	}
	if ($config->nameservers) {
		print OUTFILE "option domain-name-servers ".$config->nameservers.";\n";
	}
	if ($config->multicast) {
		print OUTFILE "option option-143 code 143 = string; # For flamethrower\n";
		print OUTFILE "option option-143 \"9000\"; # Also for flamethrower\n";
	}
	if ($dhcpdver >= 3) {
		print OUTFILE "ddns-update-style none; # For dhpcd version 3\n";
	}
	print OUTFILE "\n";

	print OUTFILE "# Defined cluster nodes...\n";
	print OUTFILE "subnet $HINFO{net} netmask $HINFO{mask} {\n";
	print OUTFILE "\tgroup {\n";

} #preamble

sub find_ints {
	my @INTS;
	open(CMD,"netstat -i |");
	while (<CMD>){
		unless ((/^Iface/) || (/^Kernel/)) {
			my ($int,$junk)=split;
			push @INTS,$int;
		}
	}
	close(CMD) or (carp("Unable to get interface list on server"),return 1);
	return @INTS;
} #find_ints

sub dhcpd_version {
        # Gets the version number of dhcpd.
        my $vstring=`/usr/sbin/dhcpd --version 2>&1`;
        my ($stuff,$version)=split(/-V/,$vstring);
        my ($major,$minor)=split(/\./,$version);
        return $major;
} # dhcpd_version        

sub find_internal_ip {
    my $interface = shift;
    # normally I hate sub processes, but for this I make an exception
    my $string = qx/ifconfig $interface | grep inet/;

    if($string =~ /addr:([\d\.]+).*cast:([\d\.]+).*ask:([\d\.]+)/) {
        return $1,$2,$3;
    }
    return undef;
} #find_internal_ip

sub write_appl_entry {
	my ($adapter) = @_;
	my $name = $adapter->client;
	$name =~ /^__(.*)__$/;
	$name = $1."_appliance";
	print OUTFILE "\t\thost ".$name." {\n";
	print OUTFILE "\t\t\thardware ethernet ".$adapter->mac.";\n";
	print OUTFILE "\t\t\tfixed-address ".$adapter->ip.";\n";
        print OUTFILE "\t\t\tfilename \"\";\n";    
        print OUTFILE "\t\t}\n";
} #write_appl_entry

sub write_entry {
	my ($mach, $adapter) = @_;
	print OUTFILE "\t\thost ".$adapter->client."{\n";
	print OUTFILE "\t\t\thardware ethernet ".$adapter->mac.";\n";
	print OUTFILE "\t\t\tfixed-address ".$adapter->ip.";\n";
        # print OUTFILE "\t\t\tfilename \"".$config->bootfile."\";\n";    
        print OUTFILE "\t\t\tfilename \"".
	    $bootfile{arch_of_machine($mach->name)}."\";\n";    
        print OUTFILE "\t\t\toption routers ". $mach->route .";\n" if $mach->route;
	# add default search domains to that of the client node
	# (doesn't matter if they are the same)
	if ($mach->domainname) {
	    print OUTFILE "\t\t\toption domain-name \"".$mach->domainname."\";\n";
	}
	print OUTFILE "\t\t\tnext-server oscar_server;\n";
        print OUTFILE "\t\t}\n";
} #write_entry

sub arch_of_machine {
    my $mname = shift;
    my @machine = list_client(name => $mname);
    if (scalar @machine == 0) {
	return `uname -m`;
    }
    my @image = list_image(name => $machine[0]->imagename);
    my $arch = $image[0]->arch;
    return $arch;
}
    
sub check_args {

	# Get verbose option
	if ($config->verbose){
		start_verbose;
	}
	# Default to list
	&verbose("Checking arguments.");
	unless ($config->outfile){
		carp("--outfile is required.");
		return 0;
	}
	return 1;

}# check_args

sub usage {
    my $progname = $0;
    if ($progname =~ m/(.+\/)(\w+)/) {
	$progname = $2;
    }
    print <<USAGE;
usage: $progname <options>

  options
    --outfile <name>        the filename to write the output to
    --interface <name>      the server's network interface to listen on. (default, eth0)
    --bootfile <file>       the bootfile to serve to clients. (default, /tftpboot/pxelinux.bin)
    --gateway <host>        the default route for the machines
    --domain <domain>       the domain of the machines (default, server domain)
    --multicast <yes>       to enable multicastng
    --nameservers <servers> a comma delimited list of nameservers.
    -v, --verbose           massive verbose output


USAGE
} #usage

__END__

=head1 NAME

mkdhcpconf - command shell to create a dhcpd.conf file.

=head1 SYNOPSIS

  mkdhcpconf -o /etc/dhcpd.conf

=head1 DESCRIPTION

The mkdhcpconf command is used to create a dhcpd.conf file
based on the contents of the SIS database

=head2 Syntax

mkdhcpconf [options]

=head2 Options

Recognized options include:

=over 4

=item --outfile <filename>

The filename to write to. This is a required option.

=item --interface <interface>

The server interface that is connected to the cluster nodes. 
The default is eth0.

=item --bootfile <filename>

The file to serve to the nodes when a boot request is received.
The default is /tftpboot/pxelinux.bin

=item --gateway <IP address>

The default gateway to assign to the remote nodes.

=item  --domain <domain name>

The domain name to assign to the remote nodes.

=item  --multicast <yes>

To enable multicastng invoke with --mutlticast=yes. Defaults to no multicasting.

=item  --nameservers <IP address list>

A comma delimited list of nameserver addresses to 
assign to the remote nodes.

=item -v, --verbose

Lots of trace and debug output.

=back

=head1 NOTES

=head1 AUTHOR

Michael Chase-Salerno, mchasal@users.sf.net

=head1 SEE ALSO

perl(1),  mksimachine(1).

=cut
