#!/usr/bin/perl
#
# Module: vyatta-dhcpv6-client
#
# **** License ****
# Copyright (c) 2017-2019 AT&T Intellectual Property. All rights reserved.
# Copyright (c) 2014 by Brocade Communications Systems, Inc.
# All rights reserved.
#
# This code was originally developed by Vyatta, Inc.
# Copyright (C) 2010 Vyatta, Inc.
# All Rights Reserved.
#
# SPDX-License-Identifier: GPL-2.0-only
#
# Author: Bob Gilligan <gilligan@vyatta.com>
# Date: April 2010
# Description: Start and stop DHCPv6 client daemon for an interface.
#
# **** End License ****
#
#

use strict;
use warnings;

use lib "/opt/vyatta/share/perl5/";
use File::Slurp qw( write_file );
use Sys::Hostname;
use Vyatta::Config;
use Vyatta::DHCPClient qw(get_dhclient_options);
use Vyatta::Interface;
use Getopt::Long;

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

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

sub gen_conf_file {
    my ($conffile, $ifname) = @_;
    my $FD_WR;

    open($FD_WR, '>', $conffile)
	or die "Can't write config file: $conffile : $!\n";

    my $date = localtime;
    my $user = getpwuid($<);

    my ( $dhclient_globals, $dhclient_req_opt ) = get_dhclient_options($ifname, "ipv6");

    print $FD_WR "# This file was auto-generated by the Vyatta\n";
    print $FD_WR "# configuration sub-system.  Do not edit it.\n";
    print $FD_WR "\n";
    print $FD_WR "#   Generated on $date by $user\n";
    print $FD_WR "#\n";

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

    print $FD_WR "interface \"$ifname\" {\n";
#    my $hostname = hostname;
#    print $FD_WR "        send host-name \"$hostname\";\n";
#    print $FD_WR "        send dhcp6.oro 1, 2, 7, 12, 13, 23, 24, 39;\n";
    if ($dhclient_req_opt) {
      print $FD_WR "        request $dhclient_req_opt;\n";
    }
    print $FD_WR "}\n";
    close $FD_WR;
}

sub usage {
    print "Usage: $0 --ifname=dpN --{start|stop|renew|release}\n";
    exit 1;
}

sub dhcpv6_options {
    my ( $ifname, $cfg_flag ) = @_;
    my $intf = new Vyatta::Interface($ifname);
    
    die "Unknown interface type for $ifname" unless $intf;

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

    my $args = "";
    my $option = $config->returnOrigValue("dhcpv6-options");

    if ($option) {
        if ($option eq "temporary") {
            $args = " -T";
        } elsif ($option eq "parameters-only") {
            $args = " -S";
        } elsif ($option eq "prefix-delegation") {
            $args = " -P";
        }
    }

    if (defined($cfg_flag)) {
        my $cfg_option = $config->returnValue("dhcpv6-options");
        my $cfg_args = "";

	if ($cfg_option) {
            if ($cfg_option eq "temporary") {
                $cfg_args = " -T";
            } elsif ($cfg_option eq "parameters-only") {
                $cfg_args = " -S";
            } elsif ($cfg_option eq "prefix-delegation") {
                $cfg_args = " -P";
            }
        }

        if (($args eq " -T" or $args eq " -P")
                  and $cfg_args eq "") {
            $args = " -N"; 
        } else {
            $args = $cfg_args; 
        }     
    }

    return $args;
}

sub write_accept_ra {
    my ( $name, $value ) = @_;
    write_file( "/proc/sys/net/ipv6/conf/$name/accept_ra",
        { err_mode => 'quiet' }, $value )
      or printf("Warning: unable to modify setting to accept RA on $name...\n");
}

sub enable_accept_ra_on_fwd_port {
    write_accept_ra( shift, 2 );
}

sub disable_accept_ra_on_fwd_port {
    write_accept_ra( shift, 1 );
}

#
# Main Section
#

my $start_flag;		# Start the daemon
my $stop_flag;		# Stop the daemon and delete all config files
my $release_flag;	# Stop the daemon, but leave config file
my $renew_flag;		# Re-start the daemon.  Functionally same as start_flag
my $ifname;
my $cfgmode_flag;

GetOptions("start" => \$start_flag,
	   "stop" => \$stop_flag,
	   "release" => \$release_flag,
	   "renew" => \$renew_flag,
	   "ifname=s" => \$ifname,
	   "cfgmode" => \$cfgmode_flag,
    ) or usage();

die "Error: Interface name must be specified with --ifname parameter.\n"
    unless $ifname;

my $tmp_dhclient_dir = "/var/run/dhclient/";
my $pidfile = $tmp_dhclient_dir . "dhclient_v6_$ifname.pid";
my $intf_env_file = $tmp_dhclient_dir . "dhclient_v6_$ifname.env";
my $leasefile = "/var/lib/dhcp/dhclient_v6_$ifname.leases";
my $conffile = "/var/lib/dhcp/dhclient_v6_$ifname.conf";
my $script_file = '/sbin/dhclient-script';
my $env_output;

mkdir ( $tmp_dhclient_dir ) if ( !-d $tmp_dhclient_dir );

my $intf = new Vyatta::Interface($ifname);
$intf or die "Unknown interface name or type: $ifname\n";
my $rdcmd   = $intf->vrf_cmd_prefix();
my $cmdname = $rdcmd . ' /sbin/dhclient';

if ($release_flag) {
    die "DHCPv6 client is not configured on interface $ifname.\n"
	unless (-e $conffile);

    die "DHCPv6 client is already released on interface $ifname.\n"
	unless (-e $pidfile);
}

if ($renew_flag) {
    die "DHCPv6 client is not configured on interface $ifname.\n"
	unless (-e $conffile);
}

if ( defined($stop_flag) || defined($release_flag) ) {

    # Stop dhclient -6 on $ifname

    if ( -d "/run/systemd/system" ) { # stop dhcpv6 client systemd service
        my $status = `systemctl is-active vyatta-service-dhcpv6-client\@$ifname.service`;
        if ( ( defined $status ) && ( $status =~ /^active/ ) ) {
            printf("Stopping daemon...\n");
            system(
"systemctl stop vyatta-service-dhcpv6-client\@$ifname.service"
            );
        }
    } else { # non-systemd
        if ( -e $pidfile ) {

            # Avoid stopping/releasing again if already stopped and removed pidfile
            printf("Stopping daemon...\n");
            system(
"$cmdname -6 -1 -cf $conffile -pf $pidfile -lf $leasefile -sf $script_file -r $ifname"
            );
        }
    }

    # Delete files it leaves behind...
    printf("Deleting related files...\n");
    unlink($pidfile);
    if ( defined $stop_flag ) {

        # If just releasing, leave the config file around as a flag that
        # DHCPv6 remains configured on this interface.
        unlink($conffile);
        unlink($leasefile);
    }

    # Disable accepting router advertisements for forwarding ports, as without
    # address allocation there is no need to get a corresponding route via RA
    disable_accept_ra_on_fwd_port($ifname);
}

if (defined($start_flag) || defined ($renew_flag)) {

    # Generate the DHCP client config file...
    gen_conf_file($conffile, $ifname);

    # First, kill any previous instance of dhclient running on this interface
    #
    printf("Stopping old daemon...\n");
    if ( !(-d "/run/systemd/system") ) { # run as none-systemd
        system("$cmdname -6 -pf $pidfile -sf $script_file -x $ifname");
    }

    system("$cmdname -6 -pf $pidfile -sf $script_file -x $ifname 2> /dev/null");

    # Check IPv6 link local address, otherwise, issue a warning that the
    # dhcpv6 address will be applied when the link-local address becomes
    # available (notified via netplug newaddr / linkup dhcpv6 hook).
    my $out = `$rdcmd ip addr list dev $ifname | grep "inet6 fe80"`;
    if (!$out) {
        if ($ifname =~ /bond/) {
            printf("DHCPv6 client will be started once bond interface is up.\n");
        } else {
            printf("DHCPv6 client will be started once a link-local address is available.\n");
        }
        exit 0;
    }

    # Accept router advertisements also for forwarding ports in case this is
    # such a port, so that the route for the allocated address can be added
    enable_accept_ra_on_fwd_port($ifname);

    # start "dhclient -6" on $ifname
    my $args = dhcpv6_options($ifname, $cfgmode_flag);
    my $start_cmd;
    printf("Starting new daemon...\n");
    print( "Prefix matching address must be provided by router advertisement ",
           "from server...\n");

    if ( -d "/run/systemd/system" ) { # run as a systemd service
        $env_output = "RDCMD=$rdcmd\nARGS=$args";
        dhcp_write_file ( $intf_env_file, $env_output );
        $start_cmd = "systemctl start vyatta-service-dhcpv6-client\@$ifname.service 2> /dev/null ";
    } else {
        $start_cmd = "$cmdname -6 -nw -cf $conffile -pf $pidfile -lf $leasefile -sf $script_file $args $ifname";
    }
    exec "$start_cmd"
	or die "Can't exec $cmdname";
}
