#!/usr/bin/perl -I/usr/local/sauron
#
# update-hosts -- updates hosts from data in CSV format file
#
# Copyright (c) Michal Kostenec <kostenec@civ.zcu.cz> 2013-2014.
# Copyright (c) Timo Kokkonen <tjko@iki.fi>  2003.
# $Id: update-hosts,v 1.6 2003/12/28 19:29:12 tjko Exp $
#
use Getopt::Long;
use Sauron::DB;
use Sauron::BackEnd;
use Sauron::Util;
use Sauron::Sauron;

$|=1;
load_config();


sub fix_ether($) {
  my($mac) = @_;
  my(@m);

  ($mac = uc($mac)) =~ s/\s+//g;

  if (@m = ($mac =~ /^([0-9A-F]{1,2})[:\-\.]([0-9A-F]{1,2})[:\-\.]([0-9A-F]{1,2})[:\-\.]([0-9A-F]{1,2})[:\-\.]([0-9A-F]{1,2})[:\-\.]([0-9A-F]{1,2})$/)) {
    for $i (0..$#m) { $m[$i] = "0".$m[$i] if (length($m[$i]) < 2); }
    $mac=join('',@m);
  }

  return $mac;
}

sub fix_duid($) {
    my($duid) = @_;

    $duid =~ s/\s+//g;
    $duid =~ s/\-//g;
    $duid =~ s/://g;

    return uc($duid);
}

sub get_host_id_by_ip($$$) {
  my($serverid,$zoneid,$ip) = @_;
  my(@q,$rule,$sql);

  return -1 unless ($serverid > 0);
  if ($zoneid) { return -2 unless ($zoneid > 0); }
  return -3 unless (is_cidr($ip));

  $ip =~ s/\/32$//;
  $ip =~ s/\/128$//;
  if ($zoneid > 0) {
    $rule = " h.zone=$zoneid ";
  } else {
    $rule = " z.server=$serverid AND h.zone=z.id ";
  }

  $sql = "SELECT h.id FROM hosts h, a_entries a, zones z " .
         "WHERE $rule AND a.host=h.id AND a.ip='$ip'";
  db_query($sql,\@q);

  return ($q[0][0] > 0 ? $q[0][0] : 0);
}


GetOptions("help|h","ether=s","ip=s","duid=s","iaid=s","domain=s","hinfo=s","info=s",
	   "dept=s","room=s","user=s","commit","origin=s","verbose",
	   "commit","filter=s","setdept=s","setgrp=s","setmx=s");

if ($opt_help || @ARGV < 2) {
  print "syntax: $0 [OPTIONS] <servername> <zonename> <inputfile>\n\n",
        "\toptions:\n",
	"\t--domain=<n>\t\tcolumn # for domain names\n",
	"\t--ip=<n>\t\tcolumn # for IP addresses\n",
	"\t--ether=<n>\t\tcolumn # for Ethernet addresses\n",
	"\t--duid=<n>\t\tcolumn # for DUID\n",
	"\t--iaid=<n>\t\tcolumn # for IAID\n",
	"\t--hinfo=<n>\t\tcolumn # for HINFO\n",
	"\t--dept=<n>\t\tcolumn # for Department info\n",
	"\t--room=<n>\t\tcolumn # for Location info\n",
	"\t--user=<n>\t\tcolumn # for User info\n",
	"\t--info=<n>\t\tcolumn # for Extra info\n",
	"\n",
	"\t--origin=<origin>\torigin for domain names in input\n",
        "\t--filter=<n>,<regexp>\tselect only lines where column n matches\n",
	"\t--setdept=<dept>\tdefault for department\n",
	"\t--setgrp=<group>\tdefault group for hosts\n",
	"\t--setmx=<mxtemplate>\tdefault MX-template for hosts\n",

        "\t\t\t\tto the regular expression\n",
    "\t--commit\t\tcommit changes (w/o this no changes are made)\n",
	"\n";
  exit(($opt_help ? 0 : 1));
}

$server=shift;
$zone=shift;
$origin=$zone;
$origin.='.' unless ($origin =~ /\.$/);
$filename=shift;
$opt_commit=($opt_commit ? 1 : 0);
$opt_origin.='.' if ($opt_origin && $opt_origin !~ /^\.$/);

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

fatal("cannot read input file: $filename")
  if ($filename ne '-' && ! -r $filename);

db_connect();

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

if ($opt_filter) {
  unless (($filter_col,$filter) = ($opt_filter =~ /^(\d+),(.*)$/)) {
    fatal("invalid arguments to option --filter");
  }
}

if ($opt_setdept) {
  $opt_setdept =~ s/(^\s*|\s*$)//g;
  print "Default department: '$opt_setdept'\n";
}

if ($opt_setgrp) {
  $gid=get_group_by_name($serverid,$opt_setgrp);
  fatal("cannot find group: $opt_setgrp") unless ($gid > 0);
}

if ($opt_setmx) {
  undef @q;
  db_query("SELECT id FROM mx_templates " .
	   "WHERE zone=$zoneid AND name=".db_encode_str($opt_setmx),\@q);
  fatal("cannot find mx template: $opt_setmx") unless ($q[0][0] > 0);
  $mxid=$q[0][0];
}

if ($opt_ip) {
  fatal("invalid parameter for --ip") unless ($opt_ip =~ /^\d+$/);
  $fields{$opt_ip}="ip";
}

if ($opt_ether) {
  fatal("invalid parameter for --ether") unless ($opt_ether =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_ether}")
    if ($fields{$opt_ether});
  $fields{$opt_ether}="ether";
}

if ($opt_duid) {
  fatal("invalid parameter for --duid") unless ($opt_duid =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_duid}")
    if ($fields{$opt_duid});
  $fields{$opt_duid}="duid";
}
if ($opt_iaid) {
  fatal("invalid parameter for --iaid") unless ($opt_iaid =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_iaid}")
    if ($fields{$opt_iaid});
  $fields{$opt_iaid}="iaid";
}

if ($opt_domain) {
  fatal("invalid parameter for --domain") unless ($opt_domain =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_domain}")
    if ($fields{$opt_domain});
  $fields{$opt_domain}="domain";
}

if ($opt_hinfo) {
  fatal("invalid parameter for --domain") unless ($opt_hinfo =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_hinfo}")
    if ($fields{$opt_hinfo});
  $fields{$opt_hinfo}="hinfo";
}

if ($opt_dept) {
  fatal("invalid parameter for --domain") unless ($opt_dept =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_dept}")
    if ($fields{$opt_dept});
  $fields{$opt_dept}="dept";
}

if ($opt_room) {
  fatal("invalid parameter for --domain") unless ($opt_room =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_room}")
    if ($fields{$opt_room});
  $fields{$opt_room}="room";
}

if ($opt_user) {
  fatal("invalid parameter for --domain") unless ($opt_user =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_user}")
    if ($fields{$opt_user});
  $fields{$opt_user}="user";
}

if ($opt_info) {
  fatal("invalid parameter for --info") unless ($opt_info =~ /^\d+$/);
  fatal("cannot use same column twice: $fields{$opt_info}")
    if ($fields{$opt_info});
  $fields{$opt_info}="info";
}

fatal("either --ip or --domain option has to be specified")
  unless ($opt_ip || $opt_domain);


open(FILE,$filename) || fatal("cannot open file: $filename");

while(<FILE>) {
  chomp;
  next if /^\s*$/;
  @l = parse_csv($_);

  if ($opt_filter) {
    next unless ($l[$filter_col-1] =~ /$filter/);
  }

  $domain='';
  if ($opt_domain) {
    $domain=$l[$opt_domain-1];
    $domain =~ s/\s+//g;
    unless (valid_domainname($domain)) {
      error("$filename($.): invalid domain: '$domain'");
      next;
    }
  }

  if ($domain =~ /$zone$/) { $domain = remove_origin($domain,$zone); }
  else { $domain = remove_origin($domain,$origin); }

  $ip='';
  $ip6 = undef;
  if ($opt_ip) {
    $ip=trim($l[$opt_ip-1]);
    if (!is_cidr($ip)) {
      error("$filename($.): invalid ip: $ip");
      next;
    }
    $ip6 = is_ip6($ip);
  }

  $ether='';
  if ($opt_ether) {
    $ether=fix_ether($l[$opt_ether-1]);
    unless ($ether =~ /^[0-9A-F]{12}$/) {
      error("$filename($.): invalid ether: $ether")
	if ($opt_verbose && $ether);
      $ether='';
    } else {
      if ($etherhash{$ether}) {
	error("$filename($.): duplicate MAC address: $ether " .
	      "(see line $etherhash{$ether})");
	$ether='';
      } else {
	$etherhash{$ether}=$.;
      }
    }
  }

  $iaid = '';
  if ($opt_iaid) {
    $iaid0=$l[$opt_iaid-1];
    if($ip6) {
        unless ($iaid = is_iaid($iaid0)) {
          error("$filename($.): invalid iaid: $iaid0");
          $iaid = '';
        }
    }
    else {
        error("$filename($.): ignoring iaid (IPv4 procesed): $iaid0");  
    }
    
  } 

  $duid = '';
  if ($opt_duid) {
    $duid = fix_duid($l[$opt_duid-1]);
    if($ip6) { 
        unless ($duid =~ /^[0-9A-F]{24,40}$/) {
          error("$filename($.): invalid duid: $duid");
          $duid = '';
          error("$filename($.): ignoring iaid: $iaid") if $iaid ne '';
          $iaid = '';
        } 
        else {
            if ($duidhash{$duid} && ($duidiaid{$duid} eq $iaid)) {
                error("$filename($.): duplicate DUID + IAID: $duid + " . sprintf("%x",$iaid) . " (see line $duidhash{$duid})") if $iaid ne '';
                error("$filename($.): duplicate DUID + IAID: $duid + empty IAID " . "(see line $duidhash{$duid})") if $iaid eq '';
                $duid='';
                $iaid='';
            } 
            else {
                $duidhash{$duid} = $.;
                $duidiaid{$duid} = $iaid;
            }
        }
    }
    else {
        error("$filename($.): ignoring DUID (IPv4 procesed): $duid");
        $duid = '';
    }
  }

  undef @hinfo;
  if ($opt_hinfo) {
    $tmp=$l[$opt_hinfo-1];
    $tmp =~ s/\ \+\ /\//;
    $tmp =~ s/\"//g;
    (@hinfo = split('\s+',$tmp));
    $hinfo[1].="-".$hinfo[2] if ($hinfo[2]);
    if (@hinfo > 0 && @hinfo > 3) {
      error("$filename($.): invalid hinfo field: $tmp") if ($opt_verbose);
      #undef @hinfo;
    }
    $hinfo[0] =~ s/[^A-Z0-9\/\-]//g;
    $hinfo[1] =~ s/[^A-Z0-9\/\-]//g;
  }

  $dept='';
  if ($opt_dept) { $dept=$l[$opt_dept-1]; }

  $room='';
  if ($opt_room) { $room=$l[$opt_room-1]; }

  $user='';
  if ($opt_user) { $user=$l[$opt_user-1]; }

  $info='';
  if ($opt_info) { $info=$l[$opt_info-1]; }

  print "$filename($.): domain=$domain,ip=$ip $ether $duid $iaid,$hinfo[0],$hinfo[1],$user,$dept,$room,$info\n"; #    if ($opt_verbose);

  push @data,[$domain,$ip,$ether,$hinfo[0],$hinfo[1],$user,$dept,$room,$info,$duid,$iaid];
}

close(FILE);


print @data . " records found in CSV file.\n";

db_begin();
db_ignore_begin_and_commit(1);

for $i (0..$#data) {
  $id=-1; $id1=-1; $id2=-1;
  $id1=get_host_id($zoneid,$data[$i][0]) if ($opt_domain);
  $id2=get_host_id_by_ip($serverid,$zoneid,$data[$i][1]) if ($opt_ip);

  if ($id1 > 0 && $id2 > 0 && $id1 != $id2) {
    error("skipping $data[$i][0] ($data[$i][1]) ip and domain point to " .
	  "different entries in database ($id1,$id2)");
    next;
  }

  $id=($id1 > 0 ? $id1 : $id2);

  unless ($id > 0) {
    error("cannot find host by domain or ip: " .
	  "$data[$i][0] ($id1) / $data[$i][1] ($id2)");
    next;
  }

  undef %h;
  $h{id}=$id;

  $h{grp}=$gid if ($gid > 0);
  $h{mx}=$mxid if ($mxid > 0);
  $h{dept}=$opt_setdept if ($opt_setdept);

  $h{domain}=$data[$i][0] if ($data[$i][0] && $id1 < 1);
  $h{ether}=$data[$i][2] if ($data[$i][2]);
  $h{hinfo_hw}=$data[$i][3] if ($data[$i][3]);
  $h{hinfo_sw}=$data[$i][4] if ($data[$i][4]);
  $h{huser}=$data[$i][5] if ($data[$i][5]);
  $h{dept}=$data[$i][6] if ($data[$i][6]);
  $h{location}=$data[$i][7] if ($data[$i][7]);
  $h{info}=$data[$i][8] if ($data[$i][8]);
  $h{duid}=$data[$i][9] if ($data[$i][9]);
  $h{iaid}=$data[$i][10] if ($data[$i][10]);

  if($h{duid}) {
    print "Updating record " .($h{domain}?'UPDATENAME':' '). " id=$id ($data[$i][0]/$data[$i][1],$data[$i][2],$data[$i][9],($data[$i][10]))...\n";
  } else {
    print "Updating record " .($h{domain}?'UPDATENAME':' '). " id=$id ($data[$i][0]/$data[$i][1],$data[$i][2])...\n";
  } 
  $r = update_host(\%h);
  fatal("failed to update host: id=$id ($r): ".db_lasterrormsg()) if ($r < 0);
}


unless ($opt_commit) {
  print "No changes made.\n";
  exit;
}

db_ignore_begin_and_commit(0);
fatal("failed to commit changes to database") if (db_commit() < 0);


# eof
