#!/usr/bin/perl
#
# Copyright (c) 2018-2019 AT&T Intellectual Property. All rights reserved.
# Copyright (c) 2007-2015, Brocade Communications Systems, Inc.
# All rights reserved.
#
# SPDX-License-Identifier: GPL-2.0-only
#

use strict;
use warnings;

use lib "/opt/vyatta/share/perl5/";
use Vyatta::Config;
use Vyatta::DHCPClient qw(generate_dhclient_intf_files get_dhclient_options is_dhcp_enabled);
use Vyatta::File qw(touch);
use Vyatta::Interface;
use Getopt::Long;
use Config::Tiny;

my $dhcp_daemon = '/sbin/dhclient';
my $dhcp_path   = '/var/lib/dhcp';
my $script_file = '/sbin/dhclient-script';
my $ztconf_file = '/config/zerotouch/dhcp-hook.cfg';

my ( $dev, $dhcp_command );

sub usage {
    print <<EOF;
Usage: $0 --dev=<interface> --action=<command>
EOF
    exit 1;
}

GetOptions(
    "dev=s"                  => \$dev,
    "action=s"               => \$dhcp_command,
) or usage();

dhcp( $dhcp_command, $dev ) if ($dhcp_command);
exit 0;

sub dhcp_write_file {
    my ( $file, $data ) = @_;

    open( my $fh, '>', $file ) || die "Couldn't open $file - $!";
    print $fh $data;
    close $fh;
}

sub dhcp_conf_header {
    my $output;

    my $date = `date`;
    chomp $date;
    $output = "#\n# autogenerated by $0\n# on $date\n#\n";
    return $output;
}

sub get_hostname {
    my $config = new Vyatta::Config("system");

    return $config->returnValue("host-name");
}

sub is_domain_name_set {
    my $config = new Vyatta::Config("system");

    return $config->returnValue("domain-name");
}

sub rfc3442_enabled {
    my $name = shift;
    my $intf = new Vyatta::Interface($name);
    my $config = new Vyatta::Config;
    $config->setLevel($intf->path());

    return !$config->exists("dhcp-options no-rfc3442");
}

sub load_ztopts {
    my ($f, $ifname) = (@_);
    return unless -s $f;
    my $ztcfg = Config::Tiny->read($f);
    my $t     = $ztcfg->{_};
    return
      unless defined( $t->{'ZEROTOUCH_DHCP'} )
          and $t->{'ZEROTOUCH_DHCP'} eq "yes"
          and defined( $t->{'ZT_INTERFACE'} )
          and $t->{'ZT_INTERFACE'} eq $ifname
          and defined( $t->{'ZT_DHCP_OPT'} )
          and $t->{'ZT_DHCP_OPT'} ne ""
          and defined( $t->{'ZT_DHCP_OPT_CODE'} )
          and $t->{'ZT_DHCP_OPT_CODE'} ne "";
    return ( $t->{'ZT_DHCP_OPT'}, $t->{'ZT_DHCP_OPT_CODE'} );
}

sub get_zt_optstr {
    my ( $name, $code ) = (@_);
    return "option $name code $code = array of ip-address;";
}

sub get_mtu {
    my $name = shift;
    my $intf = new Vyatta::Interface($name);
    return $intf->mtu();
}

sub dhcp_update_config {
    my ( $conf_file, $intf ) = @_;

    my $output   = dhcp_conf_header();
    my $hostname = get_hostname();

    my ( $dhclient_globals, $dhclient_req_opt ) = get_dhclient_options($intf, "ipv4");

    $output .= "option dhcp-max-message-size code 57 = unsigned integer 16;\n\n";

    if (rfc3442_enabled($intf)) {
    	$output .= "option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;\n\n";
    }

    my ( $ztopt, $ztcode ) = load_ztopts($ztconf_file, $intf);
    $output .= get_zt_optstr( $ztopt, $ztcode ) . "\n\n" if defined $ztopt;

    # Update the dhclient global parameters which are read form dhcp option file
    $output .= " " . $dhclient_globals if defined $dhclient_globals;

    $output .= "interface \"$intf\" {\n";
    if ( defined($hostname) ) {
        $output .= "\tsend host-name \"$hostname\";\n";
    }

    $output .= "\tsend dhcp-max-message-size 1500;\n";

    $output .=
      "\trequest subnet-mask, broadcast-address, routers, domain-name-servers";
    my $domainname = is_domain_name_set();
    if ( !defined($domainname) ) {
        $output .= ", domain-name";
    }

    my $mtu = get_mtu($intf);
    $output .= ", interface-mtu" unless $mtu;

    if (rfc3442_enabled($intf)) {
    	$output .= ", rfc3442-classless-static-routes";
    }

    $output .= ", " . $ztopt if defined $ztopt;

    # Update the dhclient request parameters which are read form dhcp option file
    $output .= " " . $dhclient_req_opt if defined $dhclient_req_opt;

    $output .= ";\n";
    $output .= "}\n\n";

    dhcp_write_file( $conf_file, $output );
}

# Is interface disabled in configuration (only valid in config mode)
sub is_intf_disabled {
    my $name = shift;
    my $intf = new Vyatta::Interface($name);
    $intf or die "Unknown interface name/type: $name\n";

    my $config = new Vyatta::Config( $intf->path() );

    return $config->exists("disable");
}

sub run_dhclient {
    my ( $intf, $op_mode ) = @_;
    my $cmd = "";

    my ( $intf_config_file, $intf_process_id_file, $intf_leases_file, $intf_env_file ) =
      generate_dhclient_intf_files($intf);

    # perform config mode actions if not called from op-mode
    if ( !defined $op_mode ) {
        dhcp_update_config( $intf_config_file, $intf );
        return if is_intf_disabled($intf);
    }

    my $interface = new Vyatta::Interface($intf);
    $interface or die "Unknown interface name or type: $intf\n";
    dhcp_write_file( $intf_env_file, "RDCMD=" . $interface->vrf_cmd_prefix() );

    if ( -d "/run/systemd/system" ) { # run as a systemd service
      $cmd =
"systemctl restart vyatta-service-dhcp-client\@$intf.service";
    } else { # run as a daemon
      $cmd =
"$dhcp_daemon -pf $intf_process_id_file -x $intf 2> /dev/null; rm -f $intf_process_id_file 2> /dev/null;";
      $cmd .=
"$dhcp_daemon -q -nw -cf $intf_config_file -pf $intf_process_id_file  -lf $intf_leases_file $intf -sf $script_file 2> /dev/null ";
    }

    # adding & at the end to make the process into a daemon immediately
    system($cmd) == 0
      or warn "start $dhcp_daemon failed: $?\n";
}

sub stop_dhclient {
    my $intf = shift;
    my $release_cmd = "";

    my ( $intf_config_file, $intf_process_id_file, $intf_leases_file, $intf_env_file ) =
      generate_dhclient_intf_files($intf);

    if ( -d "/run/systemd/system" ) { # stop dhcp client systemd service
      my $status = `systemctl is-active vyatta-service-dhcp-client\@$intf.service`;
      if ( ( defined $status ) && ( $status =~ /^active/ ) ) {
        $release_cmd =
"systemctl reload vyatta-service-dhcp-client\@$intf.service;";
      }
    } else { # stop dhcp client daemon
      $release_cmd =
"$dhcp_daemon -q -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file -sf $script_file -r $intf 2> /dev/null;";
    }

    if ( $release_cmd ) {
      system($release_cmd) == 0
        or warn "stop $dhcp_daemon failed: $?\n";
    }
}

sub dhcp {
    my ( $request, $intf ) = @_;

    my $mode = 'cfg_mode';
    if (   ( $request eq 'release' )
        || ( $request eq 'renew' )
        || ( $request eq 'op-restart' )
        || ( $request eq 'op-start' ) )
    {
        $mode = 'op_mode';
    }

    die "$intf is not using DHCP to get an IP address\n"
      unless ( $request eq 'start' || $request eq 'op-restart' ||
	       is_dhcp_enabled( $intf, $mode ) );

    die "$intf is disabled.\n"
      if ( $request ne 'stop'
        && $mode ne 'op_mode'
        && is_intf_disabled($intf) );


    my $tmp_dhclient_dir = '/var/run/vyatta/dhclient/';
    my $release_file     = $tmp_dhclient_dir . 'dhclient_release_' . $intf;
    if ( $request eq "release" ) {
        die "IP address for $intf has already been released.\n"
          if ( -e $release_file );

        print "Releasing DHCP lease on $intf ...\n";
        stop_dhclient($intf);
        mkdir($tmp_dhclient_dir) if ( !-d $tmp_dhclient_dir );
        touch($release_file);
    }
    elsif ( $request eq "renew" ) {
        print "Renewing DHCP lease on $intf ...\n";
        run_dhclient( $intf, 'op_mode' );
        unlink($release_file);
    }
    elsif ( $request eq "start" ) {
        print "Starting DHCP client on $intf ...\n";
        touch("$dhcp_path/$intf");
        run_dhclient($intf);
        unlink($release_file);
    }
    elsif ( $request eq "op-start" ) {
        print "Starting DHCP client on $intf from op mode ...\n";
        touch("$dhcp_path/$intf");
        run_dhclient( $intf, 'op_mode' );
        unlink($release_file);
    }
    elsif ( $request eq "stop" ) {
        print "Stopping DHCP client on $intf ...\n";
        stop_dhclient($intf);
        unlink("$dhcp_path/dhclient_$intf\_lease");
        unlink("$dhcp_path/$intf");
        unlink("/var/run/vyatta/dhclient/dhclient_release_$intf");
        $intf =~ s/\./_/g;
        unlink("$dhcp_path/dhclient_$intf\.leases");
        unlink("$dhcp_path/dhclient_$intf\.conf");
    }
    elsif ( $request eq "op-restart" ) {
        print "Restarting DHCP client on $intf from op mode...\n";
        touch("$dhcp_path/$intf");
        run_dhclient( $intf, 'op_mode' );
        unlink($release_file);
    }
    else {
        die "Unknown DHCP request: $request\n";
    }

    exit 0;
}
