#!/usr/bin/perl -I/usr/local/sauron
#
# modhosts - utility to modify (move,delete) host records
#
# Copyright (c) Michal Kostenec <kostenec@civ.zcu.cz> 2013-2014.
# Copyright (c) Timo Kokkonen <tjko@iki.fi>  2001-2003.
# $Id: modhosts,v 1.16 2005/04/26 04:01:52 tjko Exp $
#
require 5;
use Getopt::Long;
use Net::Netmask;
use Sauron::DB;
use Sauron::Util;
use Sauron::BackEnd;
use Sauron::Sauron;
use Net::IP qw(:PROC);


load_config();

##############################################

$user = (getpwuid($<))[0];

GetOptions("help|h","cidr=s","name=s","move=s","delete",
	   "info=s","rename=s","commit","excludeip=s",
	   "excludename=s","type=s","ether=s","etherempty",
	   "setedate=s","verbose","setgroup=s");

if ($opt_help || @ARGV < 2) {
  print "syntax: $0 <server> <zone> [options]\n\n",
        "options:\n",
	"\t--cidr=<cidr>\t\tselect hosts within CIDR block\n",
	"\t--name=<regexp>\t\tselect hostnames matching the regexp\n",
	"\t--info=<regexp>\t\tselect host with matching info\n\n",
	"\t--type=<type>\t\tselect only hosts of this type\n",
	"\t--ether=<regexp>\t\tselect hosts with matching MAC\n",
	"\t--etherempty\t\tselect hosts without a MAC\n",
	"\t--delete\t\tdelete matching hosts\n",
	"\t--move=<CIDR>,<IP>\tmove hosts to given net starting from IP\n",
	"\t--rename=<regexp>\trename hosts with given substitution 'regexp'\n",
	"\t--setedate=<days>\tset host expiration date to today+days\n",
	"\t--setgroup=<name>\tassign host to a group\n",
	"\n",
	"\t--commit\t\tcommit changes (w/o this NO changes are made)\n",
	"\n";
  print "" if ($opt_help);
  exit(0);
}


db_connect();

set_muser($user);

$server=$ARGV[0];
$zone=$ARGV[1];
$opt_verbose=($opt_verbose ? 1 : 0);
$opt_etherempty=($opt_etherempty ? 1 : 0);

$serverid=get_server_id($server);
fatal("cannot find server '$server'") unless ($serverid > 0);

$zoneid=get_zone_id($zone,$serverid);
fatal("cannot find zone '$zone'") unless ($zoneid > 0);

fatal("no host selection criteria(s) specified")
	  unless ($opt_cidr || $opt_name || $opt_info || $opt_ether);


if ($opt_cidr || $opt_move) {
  $cidr_t=", a_entries a ";
  $cidr_f=", a.ip,a.id ";
  $cidr_r=" AND a.host=h.id ";
}

if ($opt_cidr) {
  fatal("invalid CIDR: $cidr") unless (is_cidr($opt_cidr));
  if (new Net::IP($opt_cidr)->size() == 1) {
    $cidr .= " AND a.ip = '$opt_cidr' ";
  } else {
    $cidr.= " AND a.ip << '$opt_cidr' ";
  }
  print "CIDR criteria: $opt_cidr\n";
}

if ($opt_ether) {
    print "ETHER: criteria: $opt_ether\n";
    $ether= " AND h.ether ~ '$opt_ether' ";
}
if ($opt_etherempty) {
    $ether= " AND (h.ether = '' OR h.ether IS NULL) ";
}

if ($opt_setedate) {
    fatal("invalid arguments for setedate option: $opt_setedate")
	unless ($opt_setedate =~ /^\d+$/);
    $new_edate = time() + int($opt_setedate * 86400);
    print "SET expiration date to: ".localtime($new_edate)." ($new_edate)\n";
}

if ($opt_setgroup) {
    if($opt_setgroup eq "NONE") {
        $gid = -1;
    }
    else {
        fatal("cannot find group '$opt_setgroup'") if (($gid=get_group_by_name($serverid,$opt_setgroup)) < 0);
        fatal("cannot get group type for '$opt_setgroup'") if (($gtype = get_group_type_by_name($serverid,$opt_setgroup)) < 0);
        fatal("cannot assign group with type 'DHCP Class' ($opt_setgroup)") if $gtype == 3;
    }
}

if ($opt_type) {
  $opt_type=lc($opt_type);
  if ($opt_type eq 'host') { $type=1; }
  elsif ($opt_type eq 'cname') { $type=4; }
  elsif ($opt_type eq 'arec') { $type=7; }
  elsif ($opt_type eq 'srv') { $type=8; }
  else {
    fatal("unknown type for option --type");
  }
  $type = " AND h.type = $type ";
}

if ($opt_name) {
  $name=" AND h.domain ~* '$opt_name' ";
  print "Hostname regexp: $opt_name\n";
}

if ($opt_info) {
  $info=" AND (h.info ~* " . db_encode_str($opt_info) . " OR h.huser ~* " . db_encode_str($opt_info) .  
        " OR h.dept ~* " . db_encode_str($opt_info) . " OR h.location ~* " . db_encode_str($opt_info) . ") ";
  print "Host info regexp: $opt_info\n";
}

if ($opt_move) {
  fatal("invalid parameters to option --move")
    unless ($opt_move =~ /^(\S+),(\S+)$/);
  $move_net=$1;
  $move_ip=$2;
    
  $m_net = new Net::IP($move_net) or print "nelze net\n";
  $m_ip = new Net::IP($move_ip) or print "nelze ip\n";

  fatal("invalid CIDR parameter to option move: $move_net")
    unless $m_net;
  fatal("invalid IP parameter to option move: $move_ip")
    unless $m_ip;
  print "Move matching hosts to net $move_net starting from $move_ip\n";
  fatal("invalid parameters to option move") if ($m_net->overlaps($m_ip) == $IP_NO_OVERLAP or $m_net->overlaps($m_ip) == undef);
}

if ($opt_rename) {
  fatal("invalid substituion regexp for parameter rename: $opt_rename")
    unless ($opt_rename =~ /^s\/(.*)\/(.*)\/$/);
  $rename1=$1; $rename2=$2;
  print "Rename rule: s/$rename1/$rename2/\n";
}

if ($opt_excludeip) {
  @exclude_ips = split(/,/,$opt_excludeip);
  # $ecount=@exclude_ips;
  foreach $ip (@exclude_ips) {
    print "Excluded IP: $ip\n";
    fatal("Invalid exclude IP sepcified: $ip") unless (is_cidr($ip));
  }
}


$sql="SELECT h.id,h.domain,h.type,h.ether $cidr_f FROM hosts h $cidr_t " .
     "WHERE h.zone=$zoneid $cidr_r $cidr $name $ether $info $type " .
     "ORDER BY h.domain;";

print "$sql\n" if ($opt_verbose);

db_query($sql,\@q);
print db_errormsg() . "\n" if (db_errormsg());
$count=@q;

print "Found $count hosts(s) matching the criteria.\n";
exit unless ($count > 0);

if ($opt_delete || $opt_rename || $opt_move || $opt_setgroup ||
    $opt_setedate) {
  if ($opt_commit) {
    print "Are you sure you want to apply changes to database? [y/N]? ";
    chomp ($answer = <STDIN>);
    exit unless ($answer eq 'y' || $answer eq 'Y');
  }
}

db_begin();
db_ignore_begin_and_commit(1);

for $i (0..$#q) {
  printf "%-6d %-30s %2d %12s %s\n", $q[$i][0],$q[$i][1],$q[$i][2],
                                $q[$i][3],$q[$i][4];
  if ($opt_excludeip) {
    $ip_skip=0;
    for $tmpip (@exclude_ips) {
      if ($tmpip eq $q[$i][4]) {
	print "Skipping excluded IP: $tmpip\n";
	$ip_skip=1;
      }
    }
    next if ($ip_skip);
  }

  $id=$q[$i][0];
  $domain=$q[$i][1];
  if ($opt_rename) {
    $domain =~ s/$rename1/$rename2/;
    print "\trename: $q[$i][1] --> $domain\n";
  }
  if ($opt_move) {
    $m_net += ($m_ip->intip() - $m_net->intip());
    $new_ip = ip_compress_address($m_net->ip(), $m_net->version());

    while (ip_in_use($serverid,$new_ip)) {
      #print "\tSKIP ip: $new_ip\n";
      $m_net++; 
      $new_ip = ip_compress_address($m_net->ip(), $m_net->version());
    }
    fatal("cannot find new ip!") if (ip_in_use($serverid,$new_ip));
    print "\tnew ip: $new_ip\n";
  }

  undef %host;
  fatal("cannot get host record (id=$id)") if (get_host($id,\%host));

  if ($opt_delete) {
    print "\tDelete: $q[$i][1]\n";
    fatal("cannot delete host: $q[$i][1]") if (delete_host($id));
  }
  elsif ($opt_rename || $opt_move || $opt_setedate || $opt_setgroup) {
    $host{domain}=$domain;
    if ($opt_move) {
      for $j (1..$#{$host{ip}}) {
	#print "IP: $host{ip}[$j][0] $host{ip}[$j][1]\n";
	if ($host{ip}[$j][0] eq $q[$i][5]) {
	  print "\tIP MATCH: $host{ip}[$j][0] $host{ip}[$j][1]\n";
	  $host{ip}[$j][1]=$new_ip;
	  $host{ip}[$j][4]=1;
	}
      }
    }

    $host{expiration}=$new_edate if ($opt_setedate);
    if ($opt_setgroup) {
	print "Changing group $host{grp} --> $gid\n";
	$host{grp}=$gid;
    }

    print "Updating $q[$i][1]...\n";
    fatal("cannot update host: $q[$i][1]\n".db_errormsg()."\n") 
	if (update_host(\%host));
  }

}

db_ignore_begin_and_commit(0);

if ($opt_commit) {
  fatal("cannot commit changes to database") if (db_commit() < 0);
} else {
  print "NO CHANGES MADE!\n";
}

exit;


# eof :-)

