#!/usr/bin/perl
#
# Author: Blake, Kuo-Lien Huang
# License: GPL
# Description:
#   2003/04/11 the first version, only work for PXE
#
# Reference: 
#  perldoc IO::Select
#  perldoc IO::Socket:INET
#

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

$|++;

our $start_time = time;
our %config = (
  port => 6461,
  log => 1,
  logfile => '/var/log/ocsmgrd.log',
  lockfile => '/var/log/ocsmgrd.lock',
  tftpboot => '/tftpboot',
  dhcpd_conf => '/etc/dhcp3-server/dhcpd.conf',
  script_to_exec => '',
  rtsfile => '/var/log/ocsmgrd.rts',
  time_to_wait => 0,
  clients_to_wait => 0,
  connected => 0,
  nocheck => 0,
);

while ( $_ = shift ) {
  if (/-p|--port/) { 
    $config{port} = shift;
  } elsif (/-l|--log/) {
    $config{log} = 1;
    $config{logfile} = shift;
  } elsif (/-t|--time_to_wait/) {
    $config{time_to_wait} = shift;
  } elsif (/-c|--clients_to_wait/) {
    $config{clients_to_wait} = shift;
  } elsif(/-s|--script_to_exec/) {
    $config{script_to_exec} = shift;
  } elsif(/--nocheck/) {
    $config{nocheck} = 1;
  }
}

## write rtsfile
open(RTS,">$config{rtsfile}");
printf RTS "time_to_wait=%d\n",$config{time_to_wait};
printf RTS "clients_to_wait=%d\n",$config{clients_to_wait};
printf RTS "finished=0\n";
printf RTS "connected=0\n";
close(RTS);
##

## time_to_wait && script_to_exec
if($config{time_to_wait}!=0 && $config{script_to_exec}) {
  if(fork()) { 
    ## parent process
  } else {
    ## child process
    sleep($config{time_to_wait});
    open(RTS, "$config{rtsfile}");
    while(<RTS>) {
      chop;
      if(/^$/) { next; }
      my $key; my $value;
      ($key,$value) = split(/=/);
      $config{$key} = $value;
    }
    close(RTS);
    print "Timeout! $config{connected} clients are waiting.. Go!!\n";
    system("$config{script_to_exec} $config{connected}");
    exit(0);
  }
} elsif($config{script_to_exec}) {
  system("$config{script_to_exec} 0"); 
}

&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 wait_procedure($) {
  my $finished = shift;

  while($finished) { 
    sleep(30); 

    ## open lock
    my $lockfile = new IO::LockedFile(">$config{lockfile}");

    ## read rts
    open(RTS, "$config{rtsfile}");
    while(<RTS>) {
      chop;
      if(/^$/) { next; }
      my $key; my $value;
      ($key,$value) = split(/=/);
      $config{$key} = $value;
    }
    close(RTS);
    $finished = $config{finished};

    ## release lock
    $lockfile->close();
    #unlink($config{lockfile});
  }
}

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");
  $SIG{CHLD}='IGNORE';

  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());

          ## open lock
          my $lockfile = new IO::LockedFile(">$config{lockfile}");

          ## read rts
          open(RTS, "$config{rtsfile}");
          while(<RTS>) {
            chop;
            if(/^$/) { next; }
            my $key; my $value;
            ($key,$value) = split(/=/);
            $config{$key} = $value;
          }
          close(RTS);
          ##

          my $finished = $config{finished};
          my $connected = $config{connected};

          if( $peeraddr =~ /^connect$/ ) {
            ## ask for connect
            if ( $config{time_to_wait} != 0 && $config{clients_to_wait} == 0 ) {
              ## case 1: only time to wait
              my $rightnow = time;
              if( $start_time + $config{time_to_wait} < $rightnow ) {

                ## release lock
                $lockfile->close();         
                #unlink($config{lockfile});

                &log($fh->peerhost()." wait!!");
                wait_procedure($finished);
                exit(0);

              } else {
                $connected++;
                $finished++;
              }
            } elsif ( $config{time_to_wait} == 0 && $config{clients_to_wait} != 0 ) {
              ## case 2: only number of connect to wait
              if($connected == $config{clients_to_wait}) {

                ## release lock
                $lockfile->close();
                #unlink($config{lockfile});
          
                &log($fh->peerhost()." wait!!");
                wait_procedure($finished);
                exit(0);

              } else {
                $connected++;
                $finished++;
              }
            } elsif ( $config{time_to_wait} != 0 && $config{clients_to_wait} != 0 ) {
              ## case 3: both case
            } else {
              ## case 4: everyone is welcome
              &log($fh->peerhost()." passed!");
              $connected++;
              $finished++;
            }
          }
          else {
            # create PXE config file in $config{tftpboot}/pxelinux.cfg/ 
            if($finished or $config{nocheck}) {

              my $PXECFGFN=`/usr/bin/gethostip $peeraddr | cut -d" " -f3`;
              chomp($PXECFGFN);
              open(PXECFG,">".$config{tftpboot}."/pxelinux.cfg/".$PXECFGFN);
              print PXECFG q{
default local

label local
  localboot 0
};
              close(PXECFG);

              $finished--;
              ## everyone is welcome next time ..
              if($finished==0) {
                $config{time_to_wait} = 0;
                $config{clients_to_wait} = 0;
              }
            }
          }

          ## write rts
          open(RTS,">$config{rtsfile}");
          printf RTS "time_to_wait=%d\n",$config{time_to_wait};
          printf RTS "clients_to_wait=%d\n",$config{clients_to_wait};
          printf RTS "finished=%d\n",$finished;
          printf RTS "connected=%d\n",$connected;
          close(RTS);
          ##

          ## release lock
          $lockfile->close();
          #unlink($config{lockfile});
          
          &log("connection closed: ".$fh->peerhost());
          $fh->close();
        }
      }
    }
  }
}

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 = 'routers';
	  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;
	  }
	  $HoH{ $KoH }{ $key } = $value;
	}
  }
  close(CONFIG);
  return( \%HoH );
}
