#!/usr/bin/perl
#
# Author: Blake, Kuo-Lien Huang
# License: GPL
# Description:
#   create the DRBL host directories on demand
#
# Reference: 
#  perldoc IO::Select
#  perldoc IO::Socket:INET
#

use strict;
use IO::Select;
use IO::Socket;

$|++;

our %config = (
  port => 6460,
  log => 1,
  logfile => '/var/log/drblmgrd.log',
  dhcpd_conf => '/etc/dhcp3/dhcpd.conf',
  tftpboot => '/tftpboot',
  drblroot => '/var/lib/diskless/default'
);

if(@ARGV==1 && $ARGV[0] =~ /^init_d$/) {
  open(INIT_D,"> /etc/init.d/drblmgrd");
  print INIT_D "#!/bin/sh";
  print INIT_D q{
case $1 in
  'start')
     echo -n "Starting DRBL server: "
     /opt/drbl/sbin/drblmgrd &
     pid=`/bin/ps -ef 2> /dev/null | grep "drblmgrd" | grep -v "grep" | grep -v "$0" | awk '{ print $2; }'`
     if [ "$pid" != "" ]; then echo "drblmgrd."; fi
     ;;
  'stop')
     echo "Stopping DRBL server: drblmgrd "
     pid=`/bin/ps -ef 2> /dev/null | grep "drblmgrd" | grep -v "grep" | grep -v "$0" | awk '{ print $2; }'`
     if [ "$pid" != "" ]; then kill -9 $pid; fi
     ;;
  'restart')
     $0 stop
     $0 start
     ;;
esac
  }; 
  close(INIT_D);
  system("chmod 755 /etc/init.d/drblmgrd");
} else {
  &start_server(%config);
}

sub log($) {
  my $line = shift;
  return unless $config{log} and $config{logfile};
  open(LOG, ">> $config{logfile}") || die $!;
  my $now_string = localtime;
  print LOG $now_string, ":", $line, "\n";
  close(LOG);
}

sub start_server(%) {
  my %config = @_;
  die unless $config{port} > 0;
  my $lsn = new IO::Socket::INET(Listen => 1, LocalPort => $config{port}, Reuse => 1);
  my $sel = new IO::Select($lsn);

  &log("server started");
  # not createing zombie processes when the partent process fails to "wait()"
  # on its child process
  $SIG{CHLD}='IGNORE';

  my $rHoH=&read_dhcpd($config{dhcpd_conf});
  while(my @ready = $sel->can_read) {
    foreach my $fh (@ready) {
      my $new;
      if($fh == $lsn) {
        # create a new socket
        $new = $lsn->accept;
        $sel->add($new);
      }
      else {
        # process socket
        if(my $pid = fork()) {
          # parent: close the connection so we can keep listening
          $sel->remove($fh);
          $fh->close();
        }
        else {
          # child: deal with the connection
          my $peeraddr = $fh->getline;
          chomp($peeraddr);
          &log("connection opened: ".$fh->peerhost());

          if( $fh->peerhost() =~ /^$peeraddr$/ || 
              $fh->peerhost() =~ $fh->sockhost()) {
            # find tmphost 
            my $tmphost="";
            #my @ip=split(/\./,$peeraddr);
            #my $subnet=$ip[0].".".$ip[1].".".$ip[2].".0";
            #my $rHoH=&read_dhcpd($config{dhcpd_conf});
            #my @acls=split(' ',$rHoH->{$subnet}{'acl'});
            #foreach my $acl (@acls) {
            #  #print "acl=$acl\n";
            #  if( $peeraddr =~ /^$acl$/ ) {
            #    $tmphost=$rHoH->{$subnet}{'next-server'};
            #    #print "tmphost=$tmphost\n";
            #    last; 
            #  }
            #}
            foreach my $subnet (keys %$rHoH) {
               my @acls=split(' ',$rHoH->{$subnet}{'acl'});
               foreach my $acl (@acls) {
                 if( $peeraddr =~ /^$acl$/ ) {
                   $tmphost=$rHoH->{$subnet}{'next-server'};
                   #print "tmphost=$tmphost\n";
                   last; 
                 }
               }
               if($tmphost =~ /^$/) {}
               else { last; }
            }           
 
            # gen drblroot
            if($tmphost =~ /^$/) {}
            elsif(-e $config{tftpboot}."/".$peeraddr) {
              &sync_drblhostname($peeraddr, $config{drblroot});
            }
            else {
              &gen_drblhost($peeraddr,
                            $tmphost,
                            $config{tftpboot},
                            $config{drblroot}); 
            }
            # done. or failed.
            if(-e $config{tftpboot}."/".$peeraddr) { $fh->print("done.\n"); }
            else { $fh->print("failed.\n"); }
          }
          else {
            $fh->print("failed.\n");
          }
          &log("connection closed: ".$fh->peerhost());
          $fh->close();
          exit(0);
        }
      }
    }
  }
}

sub read_dhcpd($) {
  my $dhcpd_conf = shift;
  my %HoH = ();
  my $KoH = "";
  my $key = "";
  my $value = "";

  open(CONFIG,$dhcpd_conf);
  while(<CONFIG>) {
    my $tmp;
    my @line;
    if( $_ =~ /subnet/ && $_ =~ /netmask/ ) {
      @line=split(' ');
      $KoH=$line[1];
    } 
    elsif( $_ =~ /next-server/ ) {
      $key = 'next-server';
      s/;//g;
      @line = split(' ');
      $value = $line[1];
      $HoH{ $KoH }{ $key } = $value;
    }
    elsif( $_ =~ /fixed-address/ ) {
      $key = 'acl';
      s/;//g;
      @line = split(' ');
      $value = $line[1];
      if( $HoH{ $KoH }{ $key } =~ /^$/ ) {
        $HoH{ $KoH }{ $key } = $value;
      }
      else {
        $HoH{ $KoH }{ $key } .= " $value";
      }
    }
    elsif( $_ =~ /range/ ) {
      $key = 'acl';
      $value = '';
      s/;//g;
      my @subnet = split(/\./,$KoH);
      my $range_start;
      my @line = split(' ');
      my $range_start = $line[1];
      my $range_end = $line[2];
      @line = split(/\./,$range_start);
      my $start=$line[3];
      @line = split(/\./,$range_end);
      my $end=$line[3];
      $value = $range_start;
      for(my $i=$start+1;$i<=$end;$i++) {
        $value = $value." ".$subnet[0].".".$subnet[1].".".$subnet[2].".".$i;
      }
      if( $HoH{ $KoH }{ $key } =~ /^$/ ) {
        $HoH{ $KoH }{ $key } = $value;
      }
      else {
        $HoH{ $KoH }{ $key } .= " $value";
      }
    }
  }
  close(CONFIG);
  return( \%HoH );
}

sub sync_drblhostname($$) {
  my $ip = shift;
  my $drblroot = shift;

  # hostname
  my $hostname="";
  open(HOSTS,"/etc/hosts");
  while(<HOSTS>) {
    my @line = split(' ',$_,3);
    #print "hosts: ", $line[0], ",", $ip, "\n";
    if($line[0]=~/^$ip$/) { $hostname=$line[2]; chomp($hostname); last; }
  }
  close(HOSTS);
  if($hostname=~/^$/) { return; }

  chomp(my $script = `mktemp /tmp/drbl.XXXXXX`);
  open(SCRIPT,">$script");  
  print SCRIPT <<EOF;
  cp /etc/hosts $drblroot/$ip/etc/
  cat <<-HOSTNAME > $drblroot/$ip/etc/hostname
$hostname
HOSTNAME
EOF
  close(SCRIPT);
  system("chmod 700 $script; $script");
  unlink($script);
}

sub gen_drblhost($$) {
  my $ip = shift;
  my $tmphost = shift;
  my $bootserver = $tmphost;
  my $tftpboot = shift;
  my $drblroot = shift;

  #print "gen_drblhost: $ip $tmphost\n";
 
  # hostname
  my $hostname="";
  open(HOSTS,"/etc/hosts");
  while(<HOSTS>) {
    my @line = split(' ',$_,3);
    #print "hosts: ", $line[0], ",", $ip, "\n";
    if($line[0]=~/^$ip$/) { $hostname=$line[2]; chomp($hostname); last; }
  }
  close(HOSTS);
  if($hostname=~/^$/) { return; }

  &log("create diskless host: $hostname $ip");
  chomp(my $script = `mktemp /tmp/drbl.XXXXXX`);
  open(SCRIPT,">$script");  
  print SCRIPT <<EOF;
cp -a $drblroot/$tmphost $drblroot/$ip
ln -f -s $drblroot/root $tftpboot/$ip

cat <<-FSTAB > $drblroot/$ip/etc/fstab
none    /proc   proc	defaults    1   0
$bootserver:$drblroot/root       /       nfs ro,defaults,nolock 0 1
$bootserver:$drblroot/$ip/var    /var    nfs defaults,nolock 0 1
$bootserver:$drblroot/$ip/root   /root   nfs defaults,nolock 0 2
$bootserver:$drblroot/root/var/lib/dpkg  /var/lib/dpkg   nfs
ro,defaults,nolock  0 1
$bootserver:/opt                                 /opt    nfs defaults,nolock 0 1
tmpfs   /tmp    tmpfs   defaults    0   0
#/dev/nbd0	/tmp	ext2	defaults	0	0
FSTAB

while read spec file vfstype others; do
  if [ "\$file" = "/home" -o "\$file" = "/var/mail" ]; then
    echo "\$spec	\$file	\$vfstype	\$others" >> $drblroot/$ip/etc/fstab
  fi
done < $drblroot/$bootserver/etc/fstab

cat <<-HOSTNAME > $drblroot/$ip/etc/hostname
$hostname
HOSTNAME

cat <<-NTPDATE > $drblroot/$ip/etc/default/ntp-servers
NTPSERVERS="$bootserver"
NTPDATE
EOF
  close(SCRIPT);
  system("chmod 700 $script; $script");
  unlink($script);
}
