#!/usr/bin/perl

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

#   $Header: /cvsroot/systeminstaller/systeminstaller/bin/mkdhcpconf,v 1.21 2003/10/23 22:22:44 pramath Exp $

#   Copyright (c) 2001 International Business Machines

#   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.bin"},
        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);
}

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

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

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

foreach my $mach (@machinelist) {
	foreach my $adapter (@adaplist){
		if (($mach->name eq $adapter->client) && ($adapter->mac) ) {
			&write_entry($mach, $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);
		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_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\toption routers ". $mach->route .";\n" if $mach->route;
        print OUTFILE "\t\t\toption domain-name \"". $mach->domainname . "\";\n" if $mach->domainname;
        print OUTFILE "\t\t}\n";
} #write_entry


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
