#!/usr/bin/env perl
#
# **** License ****
#
# Copyright (C) 2020 by Helm Rock Consulting
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# **** End License ****
#
# Author: Neil Beadle
# Description: Script installs dnsmasq blacklisting EdgeOS template files to redirect dns
# look ups to alternative IPs (blackholes, pixel servers etc.)
# USE AT YOUR OWN RISK!
#

use Cwd qw(getcwd);
use feature qw(switch);
use File::Basename;
use Getopt::Long;
use IO::Select;
use IPC::Open3;
use lib q{/opt/vyatta/share/perl5};
use lib q{./lib};
use Term::ReadKey qw(GetTerminalSize);
use Sys::Syslog qw(:standard :macros);
use threads;
use v5.14;
use strict;
use warnings;
use EdgeOS::DNS::Blacklist (
  qw{
    $c
    $FALSE
    $NAME
    $TRUE
    $VERSION
    get_cfg_actv
    get_cols
    get_file
    get_url
    get_user
    is_admin
    is_build
    is_configure
    is_version
    log_msg
    process_data
    set_dev_grp
    }
);

my $SHOW    = $TRUE;
my $cols    = get_cols();
my $prg     = { errors => 0, calls => 0, };
my $program = basename($0);
my $edgeOS  = is_version();
my $name    = q{dnsmasq blacklist};
my $begin   = q{/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper begin};
my $commit  = q{/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper commit};
my $delete  = q{/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper delete};
my $end     = q{/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper end};
my $save    = q{sudo /opt/vyatta/sbin/vyatta-cfg-cmd-wrapper save};
my $set     = q{/opt/vyatta/sbin/vyatta-cfg-cmd-wrapper set};
my $cfg     = {
  actv     => q{/opt/vyatta/config/active/service/dns/forwarding/},
  cfg_dir  => q{/opt/vyatta/config},
  cmdablk  => q{./blacklist.cmds},
  cnf_ext  => q{.conf},
  disabled => 0,
  dmasq_d  => q{/etc/dnsmasq.d},
  EdgeOS   => q{EdgeOS},
  flg_file => q{/var/log/update-dnsmasq-flagged.cmds},
  flg_lvl  => 5,
  group    => q{users},
  lib      => q{/config/lib/perl},
  no_op    => q{/tmp/.update-dnsmasq.no-op},
  old_lib  => q{/opt/vyatta/share/perl5},
  oldconf  => qq{{dnsmasq,domai*,hos*,zon*}.blacklist.conf},
  oldscrpt => q{update-blacklists-dnsmasq.pl},
  postcfg  => qq{Install_${NAME}},
  postcfgd => q{/config/scripts/post-config.d},
  script   => q{update-dnsmasq.pl},
  scrpt_d  => q{/config/scripts},
  tmplts   => q{/opt/vyatta/share/vyatta-cfg/templates/service/dns/forwarding},
  tstscrpt => q{blacklist.t},
  utility  => q{ubnt-cln-cfg-orphans.sh},
  domains  => {
    duplicates => 0,
    icount     => 0,
    records    => 0,
    target     => q{address},
    type       => q{domains},
    unique     => 0,
  },
  hosts => {
    duplicates => 0,
    icount     => 0,
    records    => 0,
    target     => q{address},
    type       => q{hosts},
    unique     => 0,
  },
  zones => {
    duplicates => 0,
    icount     => 0,
    records    => 0,
    target     => q{server},
    type       => q{zones},
    unique     => 0,
  },
};

# Don't send output to screen if running from post-boot
if ( getcwd eq $cfg->{postcfgd} ) { $SHOW = $FALSE }

############################### script runs here ###############################
exit if &main();
exit 1;
################################################################################

# Remove previous configuration files
sub delete_file {
  my $input = shift;
  my $cmd   = qq{sudo rm $input->{file} 2>&1};

  if ( -e $input->{file} ) {
    log_msg(
      {
        show    => $SHOW,
        cols    => $cols,
        eof     => qq{\n},
        logsys  => q{},
        msg_typ => q{INFO},
        msg_str => sprintf( q{Deleting file %s} =>
            $input->{file} ),
      }
    );
    qx{$cmd};
    $prg->{calls}++;
  }

  if ( -e $input->{file} ) {
    $prg->{fail}->{ $prg->{errors}++ } = $cmd;
    log_msg(
      {
        cols    => $cols,
        eof     => qq{\n},
        msg_str => sprintf( q{Unable to delete %s} => $input->{file} ),
        msg_typ => q{WARNING},
        logsys  => q{},
        show    => $SHOW,
      }
    );
    return;
  }
  return $TRUE;
}

# Error handler
sub error_handler {
  my $input = shift;
  $prg->{calls}++;
  if ( $input->{exit_code} >> 8 != 0 ) {
    $prg->{fail}->{ $prg->{errors}++ } = $input->{cmd};
    return;
  }
  else {
    return $TRUE;
  }
}

# Exec a command using qx
sub exec_command {
  my $input = shift;
  $prg->{calls}++;

  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => $input->{exec_msg},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  my $output = qx{sudo $input->{cmd} 2>&1};

  if ( !error_handler( { exit_code => $?, cmd => $input->{cmd} } ) ) {
    log_msg(
      {
        cols    => $cols,
        eof     => qq{\n},
        logsys  => qq{$output},
        msg_str => $input->{fail_msg} . qq{\n$output\n},
        msg_typ => q{ERROR},
        show    => $SHOW,
      }
    );
    $prg->{fail}->{ $prg->{errors}++ } = $input->{cmd};
    return;
  }
  return $TRUE;
}

# Run configure and execute the commands
sub exec_shell {
  my $input = shift;
  my ( %cmd_errs, %cmds_out, $cmd_err, $cmd_out, $in, $pid )
    = ( (), (), q{}, q{}, q{}, undef );

  eval { $pid = open3 $in, $cmd_out, $cmd_err, qq{@{$input->{commands}}}; };
  if ($@) {
    if ( $@ =~ m/^ open3/ ) {
      warn qq{open3 failed: $!\ n $@\ n};
      return;
    }
    say qq{FATAL: Unable to open a shell!} && return;
  }

  waitpid( $pid, 0 );
  close $in;

  my $selector = IO::Select->new();
  $selector->add( $cmd_err, $cmd_out );
  while ( my @ready = $selector->can_read ) {
    for my $fh (@ready) {
      if ( defined $cmd_err ) {
        if ( fileno $fh == fileno $cmd_err ) {
          %cmd_errs = map { my $key = $_; $key => 1; } <$cmd_err>;
        }
      }
      else {
        %cmds_out = map { my $key = $_; $key => 1; } <$cmd_out>;
      }
      $selector->remove($fh) if eof $fh;
    }
  }
  close $cmd_out if $cmd_out;
  close $cmd_err if $cmd_err;

  my $re = {
    errors => qr{(?:aborted|error|failed|failure)}i,
    info => qr{(?:Installing|Restarting|\[\s|Starting|Stopping)},
    saving => qr{(?:^Saving configuration)},
    warnings => qr{(?:^Delete failed\n|sh: line|Nothing to delete|The specified configuration node already exists|The specified configuration node is not valid\n|Updating|Upgrading)},
  };

  for my $feed ( \%cmds_out, \%cmd_errs ) {
    while ( my ( $cli_out, $cli_err ) = each %{$feed} ) {
      $prg->{calls}++;
      if ($cli_out) {
        for ($cli_out) {
          $cli_out =~ s/\R//g;
          when (/$re->{warnings}/) { }
          when (/$re->{info}/) {
            log_msg(
              {
                cols    => $cols,
                eof     => qq{\n},
                logsys  => q{},
                msg_str => $cli_out,
                msg_typ => q{INFO},
                show    => $SHOW,
              }
            );
          }
          when (/$re->{saving}/) {
            log_msg(
              {
                cols    => $cols,
                eof     => qq{\n},
                logsys  => q{},
                msg_str => $cli_out,
                msg_typ => q{INFO},
                show    => $SHOW,
              }
            );
          }
          when (/$re->{errors}/) {
            $prg->{fail}->{ $prg->{errors}++ } = $cli_out;
            log_msg(
              {
                cols    => $cols,
                eof     => qq{\n},
                logsys  => q{},
                msg_str => $cli_out,
                msg_typ => q{ERROR},
                show    => $SHOW,
              }
            );
          }
          default                  { print $cli_out; }
        }
      }
      elsif ($cli_err) {
        for ($cli_err) {
          when (/$re->{warnings}/) { }
          when (/$re->{info}/)     { }
          when (/$re->{errors}/) {
            $prg->{fail}->{ $prg->{errors}++ } = $cli_err;
            log_msg(
              {
                cols    => $cols,
                eof     => qq{\n},
                logsys  => q{},
                msg_str => $cli_err,
                msg_typ => q{ERROR},
                show    => $SHOW,
              }
            );
          }
          default { print $cli_err; }
        }
      }
    }
  }
  return $TRUE;
}

# Main function
sub main {

  # Start logging
  my $log_name = qq{$program};
  openlog( $program, q{}, LOG_DAEMON );

  for ($program) {
    when (m/setup/) {
      if ( is_build() ) {
        log_msg(
          {
            cols   => $cols,
            eof    => qq{\n},
            logsys => q{},
            msg_str =>
              qq{Installing $name $program v$VERSION support},
            msg_typ => q{INFO},
            show    => $SHOW,
          }
        );
        if ( setup() ) {
          log_msg(
            {
              cols    => $cols,
              eof     => qq{\n},
              logsys  => q{},
              msg_str => qq{$name v$VERSION installation successful},
              msg_typ => q{INFO},
              show    => $SHOW,
            }
          );
        }
        else {
          for my $key ( sort keys %{ $prg->{fail} } ) {
            log_msg(
              {
                cols    => $cols,
                eof     => qq{\n},
                logsys  => q{},
                msg_str => qq{$prg->{fail}->{$key} failed!},
                msg_typ => q{ERROR},
                show    => $SHOW,
              }
            );
          }
          log_msg(
            {
              cols   => $cols,
              eof    => qq{\n},
              logsys => qq{$name v$VERSION installation failed!}
                . qq{$prg->{errors}/$prg->{errors} calls failed},
              msg_str => qq{$name v$VERSION installation failed!}
                . qq{\nReview /var/log/messages\n}
                . qq{$prg->{errors}/$prg->{errors} calls failed},
              msg_typ => q{ERROR},
              show    => $SHOW,
            }
          );
          say q{};
          exit 1;
        }
      }
      else {
        log_msg(
          {
            cols   => $cols,
            eof    => qq{\n},
            logsys => q{},
            msg_str =>
              qq{Edgemax $edgeOS->{version} is not supported, upgrade!},
            msg_typ => q{ERROR},
            show    => $SHOW,
          }
        );
        say q{};
        exit 1;
      }
    }
    when (m/remove/) {
      if ( remove() ) {
        log_msg(
          {
            cols    => $cols,
            eof     => qq{\n},
            logsys  => q{},
            msg_str => qq{$name v$VERSION removal successful},
            msg_typ => q{INFO},
            show    => $SHOW,
          }
        );
      }
      else {
        for my $key ( sort keys %{ $prg->{fail} } ) {
          log_msg(
            {
              cols    => $cols,
              eof     => qq{\n},
              logsys  => q{},
              msg_str => qq{$prg->{fail}->{$key} failed!},
              msg_typ => q{ERROR},
              show    => $SHOW,
            }
          );
        }
        log_msg(
          {
            cols   => $cols,
            eof    => qq{\n},
            logsys => qq{$name v$VERSION removal failed! }
              . qq{$prg->{errors}/$prg->{errors} calls failed},
            msg_str => qq{$name v$VERSION removal failed!}
              . qq{\nReview /var/log/messages\n}
              . qq{$prg->{errors}/$prg->{errors} calls failed},
            msg_typ => q{ERROR},
            show    => $SHOW,
          }
        );
        say q{};
        exit 1;
      }
    }
  }

  say q{};
  print $c->{on};

  closelog();
  if ( $prg->{errors} ) {
    return;
  }
  else {
    return $TRUE;
  }
}

# Remove blacklist support
sub remove {
  my @files = (
    $cfg->{flg_file},
    $cfg->{no_op},
    qq{$cfg->{postcfgd}/$cfg->{postcfg}},
    qq{$cfg->{scrpt_d}/$cfg->{oldscrpt}},
    qq{$cfg->{scrpt_d}/$cfg->{script}},
    qq{$cfg->{scrpt_d}/$cfg->{tstscrpt}},
    glob(qq{/var/log/update*.cmds}),
    glob(qq{$cfg->{dmasq_d}/$cfg->{oldconf}})
  );

  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => q{Removing blacklist support files and scripts...},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  for my $file (@files) {
    delete_file( { file => $file } ) if -e $file;
  }

  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => q{Checking active configuration directory permissions},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  set_dev_grp(
    {
      dir_handle => $cfg->{actv}, dir_grp     => $cfg->{group},
      grps       => get_user(     { attribute => q{grps} } )
    }
    )
    or die q{Unable to repair active configuration directory permissions!};

  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => qq{Removing $name configuration},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  my @commands = (
    qq{$begin\n},
    qq{$delete service dns forwarding blacklist\n},
    qq{$delete system task-scheduler task update_blacklists\n},
    qq{$commit\n},
    qq{$save\n},
    qq{$end\n},
  );

  # Run configure and execute the commands
  exec_shell( { commands => \@commands } )
    or die qq{Unable to execute removal commands!};

  exec_command(
    {
      cmd =>
        qq{sudo apt-get -qq -y remove libnet-nslookup-perl libnet-dns-perl > /dev/null 2>&1&},
      exec_msg => qq{Removing Perl Net::Nslookup package},
      fail_msg => qq{Unable to remove packages},
    }
  );

  # Remove configuration templates
  if ( -d qq{$cfg->{tmplts}/blacklist/} ) {
    exec_command(
      {
        cmd      => qq{rm -rf "$cfg->{tmplts}/blacklist/"},
        exec_msg => qq{Removing $name configuration templates},
        fail_msg => qq{Unable to remove $name configuration templates},
      }
    );
    if ( -d qq{$cfg->{tmplts}/blacklist/} ) {
      $prg->{fail}->{ $prg->{errors}++ }
        = qq{rm -rf "$cfg->{tmplts}/blacklist/"};
      log_msg(
        {
          cols    => $cols,
          eof     => qq{\n},
          logsys  => q{},
          msg_str => qq{Unable to remove $name configuration templates},
          msg_typ => q{ERROR},
          show    => $SHOW,
        }
      );
    }
  }

  # Remove Blacklist.pm module and lib directories
  if ( -d qq{$cfg->{lib}/$cfg->{EdgeOS}/} ) {
    exec_command(
      {
        cmd      => qq{rm -rf "$cfg->{lib}/$cfg->{EdgeOS}/"},
        exec_msg => qq{Removing $name perl library},
        fail_msg => qq{Unable to remove $name perl library},
      }
    );
    if ( -d qq{$cfg->{lib}/$cfg->{EdgeOS}/} ) {
      $prg->{fail}->{ $prg->{errors}++ }
        = qq{rm -rf "$cfg->{lib}/$cfg->{EdgeOS}/"};
      log_msg(
        {
          cols    => $cols,
          eof     => qq{\n},
          logsys  => q{},
          msg_str => qq{Unable to remove $name perl library},
          msg_typ => q{ERROR},
          show    => $SHOW,
        }
      );
    }
  }

  # Remove old version Blacklist.pm module and lib directories
  if ( -d qq{$cfg->{old_lib}/$cfg->{EdgeOS}/} ) {
    exec_command(
      {
        cmd      => qq{rm -rf "$cfg->{old_lib}/$cfg->{EdgeOS}/"},
        exec_msg => qq{Removing $name perl library},
        fail_msg => qq{Unable to remove $name perl library},
      }
    );
    if ( -d qq{$cfg->{old_lib}/$cfg->{EdgeOS}/} ) {
      $prg->{fail}->{ $prg->{errors}++ }
        = qq{rm -rf "$cfg->{old_lib}/$cfg->{EdgeOS}/"};
      log_msg(
        {
          cols    => $cols,
          eof     => qq{\n},
          logsys  => q{},
          msg_str => qq{Unable to remove $name perl library},
          msg_typ => q{ERROR},
          show    => $SHOW,
        }
      );
    }
  }

  # Restart dnsmasq
  exec_command(
    {
      cmd      => qq{service dnsmasq restart},
      exec_msg => qq{Reloading dnsmasq configuration},
      fail_msg => qq{Unable reload dnsmasq configuration!},
    }
  );

  if ( !$prg->{errors} ) {
    return $TRUE;
  }
  else {
    return;
  }
}

# Install blacklist support
sub setup {

  # Create a no op file to stop the CLI automatically running update-dnsmasq.pl
  open my $NO_OP => q{>}, qq{$cfg->{no_op}};
  close $NO_OP;

  my @directory = glob qq{$cfg->{dmasq_d}/$cfg->{oldconf}};

  # Remove old version files
  if (@directory) {
    log_msg(
      {
        cols   => $cols,
        eof    => qq{\n},
        logsys => q{},
        msg_str =>
          qq{Removing stale blacklist files from $cfg->{dmasq_d}/},
        msg_typ => q{INFO},
        show    => $SHOW,
      }
    );

    for my $file (@directory) {
      if ( -e $file ) {
        delete_file( { file => $file } ) if $file;
      }
    }
  }

  if ( -e qq{$cfg->{scrpt_d}/$cfg->{oldscrpt}} ) {
    delete_file( { file => qq{$cfg->{scrpt_d}/$cfg->{oldscrpt}} } );
  }

  # Create script directory in upgrade persistent /config/scripts
  if ( !-e qq{$cfg->{scrpt_d}/$cfg->{scrpt_d}/$cfg->{script}} ) {

    # Install update-dnsmasq.pl script
    exec_command(
      {
        cmd => qq{install -D -o root -g vyattacfg -m 0755 $cfg->{script} }
          . qq{"$cfg->{scrpt_d}/$cfg->{script}"},
        exec_msg =>
          qq{Installing $name $cfg->{script} to $cfg->{scrpt_d}/},
        fail_msg =>
          qq{Unable to install $name $cfg->{script} to $cfg->{scrpt_d}/},
      }
    );
  }

  # Install blacklist.t test script
  exec_command(
    {
      cmd =>
        qq{install -o root -g vyattacfg -m 0755 $cfg->{tstscrpt} "$cfg->{scrpt_d}/$cfg->{tstscrpt}"},
      exec_msg =>
        qq{Installing $name $cfg->{tstscrpt} to $cfg->{scrpt_d}/},
      fail_msg =>
        qq{Unable to install $name $cfg->{tstscrpt} to $cfg->{scrpt_d}/},
    }
  );

#   if ( !-e qq{$cfg->{postcfgd}/$cfg->{postcfg}} ) {
#
#     # Install post-configuration install script
#     exec_command(
#       {
#         cmd =>
#           qq{install -o root -g vyattacfg -m 0555 $cfg->{postcfg} "$cfg->{postcfgd}/$cfg->{postcfg}"},
#         exec_msg =>
#           qq{Installing $name $cfg->{postcfg} to $cfg->{postcfgd}/},
#         fail_msg =>
#           qq{Unable to install $name $cfg->{postcfg} to $cfg->{postcfgd}/},
#       }
#     );
#   }

  # Copy the template directories and files to /opt/vyatta/share/vyatta-cfg/
  exec_command(
    {
      cmd      => qq{cp -rf blacklist/ "$cfg->{tmplts}/"},
      exec_msg => qq{Installing $name configuration templates},
      fail_msg =>
        qq{Unable to install $name configuration templates to $cfg->{tmplts}/blacklist/},
    }
  );

  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => qq{Checking active configuration directory permissions},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  # Fix permissions on the active configuration directory
  set_dev_grp(
    {
      dir_handle => $cfg->{actv}, dir_grp     => $cfg->{group},
      grps       => get_user(     { attribute => q{grps} } )
    }
    )
    or die qq{Unable to repair active configuration directory permissions!};

  exec_command(
    {
      cmd =>
        qq{install -Dv -o root -g vyattacfg -m 0755 "./lib/$cfg->{EdgeOS}/DNS/Blacklist.pm" "$cfg->{lib}/$cfg->{EdgeOS}/DNS/Blacklist.pm"},
      exec_msg => qq{Installing Blacklist.pm module},
      fail_msg =>
        qq{Unable to install Blacklist.pm module to $cfg->{lib}/$cfg->{EdgeOS}/DNS/},
    }
  );

  log_msg(
    {
      cols   => $cols,
      eof    => qq{\n},
      logsys => q{},
      msg_str =>
        qq{Creating default $name sources and task scheduler entries},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => qq{Checking active configuration directory permissions},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  # Make sure the configuration directories have vyattacfg group set
  set_dev_grp(
    {
      dir_handle => qq{$cfg->{cfg_dir}/}, dir_grp     => $cfg->{group},
      grps       => get_user(             { attribute => q{grps} } )
    }
    )
    or die qq{Unable to repair active configuration directory permissions!};

  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => qq{Installing dnsmasq integration templates},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  log_msg(
    {
      cols   => $cols,
      eof    => qq{\n},
      logsys => q{},
      msg_str =>
        q{Updating EdgeOS configuration, this will take a while...},
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  open my $CF => q{<}, $cfg->{cmdablk}
    or die qq{error: Unable to open $cfg->{cmdablk}: $!};

  my @commands;

LINE: while ( my $line = <$CF> ) {
    chomp $line;
    for ($line) {
      when (/^#/) {
        next LINE;
      }
      when (/^begin/) {
        $line =~ s/\Abegin(?<BEGIN>.*)\z/$begin$+{BEGIN}/ms;
      }
      when (/^commit/) {
        $line =~ s/\Acommit(?<COMMIT>.*)\z/$commit$+{COMMIT}/ms;
      }
      when (/^delete/) {
        $line =~ s/\Adelete(?<DELETE>.*)\z/$delete$+{DELETE}/ms;
      }
      when (/^end/) {
        $line =~ s/\Aend(?<END>.*)\z/$end$+{END}/ms;
      }
      when (/^loggit/) {
        $line =~ s/^\S+\s*//;
        log_msg(
          {
            cols    => $cols,
            eof     => qq{\n},
            logsys  => q{},
            msg_str => $line,
            msg_typ => q{INFO},
            show    => $SHOW,
          }
        );
      }
      when (/^save/) {
        $line =~ s/\Asave(?<SAVE>.*)\z/$save$+{SAVE}/ms;
      }
      when (/^set/) {
        $line =~ s/\Aset(?<SET>.*)\z/$set$+{SET}/ms;
      }
      when (/^sudo/) { }
      default        { next LINE }
    }
    push @commands => qq{$line\n};
  }
  close $CF;

  # Run configure and execute the commands
  exec_shell( { commands => \@commands } )
    or die log_msg(
    {
      cols   => $cols,
      eof    => qq{\n},
      logsys => q{},
      msg_str =>
        q{Unable to execute blacklist configuration commands!},
      msg_typ => q{FATAL},
      show    => $SHOW,
    }
    );

  delete_file( { file => $cfg->{no_op} } );

#   $prg->{errors}++ if not qx{sudo $cfg->{scrpt_d}/$cfg->{script} -v\n};
  $prg->{errors}++
    if system( "/usr/bin/sudo", "$cfg->{scrpt_d}/$cfg->{script}", "-v" );
  if ( !$prg->{errors} ) {
    return $TRUE;
  }
  else {
    return;
  }
}

# Write the data to file
sub write_file {
  my $input = shift;

  return if !@{ $input->{data} };

  exec_command(
    {
      cmd      => qq{touch $input->{file}},
      exec_msg => qq{Creating $input->{file}},
      fail_msg => qq{Unable to create $input->{file} permissions},
    }
  );

  exec_command(
    {
      cmd      => qq{chmod 0666 $input->{file}},
      exec_msg => qq{Setting $input->{file} permissions},
      fail_msg => qq{Unable to set $input->{file} permissions},
    }
  );

  open my $FH => q{>}, $input->{file} or return;
  log_msg(
    {
      cols    => $cols,
      eof     => qq{\n},
      logsys  => q{},
      msg_str => sprintf( q{Saving %s} => basename( $input->{file} ) ),
      msg_typ => q{INFO},
      show    => $SHOW,
    }
  );

  print {$FH} @{ $input->{data} };

  close $FH;

  exec_command(
    {
      cmd      => qq{chmod 0755 $input->{file}},
      exec_msg => qq{Resetting $input->{file} perms},
      fail_msg => qq{Unable to reset $input->{file} perms},
    }
  );

  return $TRUE;
}
