#!/usr/bin/perl
#
# 2011/06/21 gabriel
#
# apt-get install iputils-ping rsync nmap perl-base libyaml-perl libio-all-perl libfile-finder-perl libnet-ldap-perl

use strict;
use warnings;

use YAML;
use IO::All;
use File::Basename;
use File::Finder;
use Net::LDAP;
use List::Util qw(shuffle);

my %CMD_DB = (
   help           => \&cmd_help,
   version        => \&cmd_version,
   generate       => \&cmd_generate,
   update         => \&cmd_update,
   init           => \&cmd_init_db,
   'exclude-list' => \&cmd_exclude_list,
   );

my ($LDAP_H, $LDAP_BASE);
my $LIMIT_TIMESTAMP = time() - (8 * 24 * 3600); # 8 days

my $cmd = shift @ARGV || 'help';
if (defined $CMD_DB{$cmd}) {
   $CMD_DB{$cmd}->(@ARGV);
   }
else {
   print {*STDERR} "backuppc-silzigan: command $cmd not found\n\n";
   $CMD_DB{help}->();
   exit 1;
   }

exit;

sub open_ldap {
   # AuthLDAPUrl          "ldap://ldapserver.mylab.fr/ou=Users,dc=mylab,dc=fr?uid?sub"
   # AuthLDAPBindDN       "cn=ldapconnect,ou=System,dc=mylab,dc=fr"
   # AuthLDAPBindPassword "Rhalala128"

   my ($masterLDAP, $masterDN, $masterPw);

   for my $config_line (io('/etc/apache2/conf.d/backuppc.conf')->chomp->slurp) {
      ($masterLDAP, $LDAP_BASE) = ($1, $2) if $config_line =~ m{ ^\s* AuthLDAPUrl [^/]+ // ([^/]+) / (ou=[^?]+) }xms;
      $masterDN = $1 if $config_line =~ m{ ^\s* AuthLDAPBindDN \s+ " ([^"]+) " }xms;
      $masterPw = $1 if $config_line =~ m{ ^\s* AuthLDAPBindPassword \s+ " ([^"]+) " }xms;
      }

   $LDAP_H = Net::LDAP->new( "$masterLDAP" ) or die "$@";
   my $mesg = $LDAP_H->bind("$masterDN", password => "$masterPw");

   return;
   }

sub close_ldap {
   $LDAP_H->unbind();
   return;
   }

sub cmd_update {
   my $search_config_file = File::Finder->type('f')->name('*.yaml');
   for my $config_file (File::Finder->eval($search_config_file)->in('/etc/backuppc')) {
      cmd_generate("$config_file");
      }

   update_hosts();
   }

sub cmd_generate {
   my $config_file = shift;
   
   my $CONFIG = YAML::LoadFile($config_file);

   $CONFIG->{default}{namespace} ||= basename($config_file, '.yaml');
   $CONFIG->{default}{path}      ||= "/etc/backuppc/auto/$CONFIG->{default}{namespace}";
   $CONFIG->{default}{hosts}     ||= "$CONFIG->{default}{path}/hosts";
   $CONFIG->{default}{exclude}   ||= "/usr/lib/kont/etc/backuppc/exclude.txt";

   if (not -d "/etc/backuppc/auto/$CONFIG->{default}{namespace}") {
      io("/etc/backuppc/auto/$CONFIG->{default}{namespace}")->mkpath({mode => 0755});
      }

   print '' > io($CONFIG->{default}{hosts});

   open_ldap();
   LOOP_ON_COMPUTER:
   for my $computer ( keys %{$CONFIG->{computers}}) {
      my $login = $CONFIG->{computers}{$computer}{login} || 'root';

      LOOP_ON_USER:
      for my $user (  keys %{$CONFIG->{computers}{$computer}{users}}) {
         my $pathshare = $CONFIG->{computers}{$computer}{share}
                   || $CONFIG->{default}{share}
                   || "/home/users";
         my $share  = $CONFIG->{computers}{$computer}{users}{$user}{share}
                   || "$pathshare/$user";
         my $status = $CONFIG->{computers}{$computer}{users}{$user}{status}
                   || $CONFIG->{computers}{$computer}{status}
                   || $CONFIG->{default}{status}
                   || 'auto';
         my $admin  = $CONFIG->{computers}{$computer}{users}{$user}{admin}
                   || $CONFIG->{computers}{$computer}{admin}
                   || $CONFIG->{default}{admin}
                   || 'root';

         my $exclude = $CONFIG->{computers}{$computer}{users}{$user}{exclude}
                   || $CONFIG->{computers}{$computer}{exclude}
                   || '';

         my @exclude_list = ();
         if (ref($exclude) eq "ARRAY") {
            push @exclude_list, @{$exclude};
            }
         else {
            push @exclude_list, $exclude if not $exclude =~ m/^$/;
            }
         push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp;
         my $exclude_string = join ",\n", map { "    '$_'" } @exclude_list;

         if ($status eq "auto") {
            $status = "disable";
            my $ldb = $LDAP_H->search(
               base     => "$LDAP_BASE",
               filter   => "(uid=$user)",
               attrs    => ['shadowExpire', 'sambaKickoffTime'],
               );
            if (not $ldb->code ) {

               LDAP_RESULT:
               foreach my $entry ($ldb->entries) {

                  my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0;
                  my $user_shadow_expire = $entry->get_value('shadowExpire') || 0;

                  if ($user_shadow_expire == 0) {
                     $status = "enable";
                     last LDAP_RESULT;
                     }
                  elsif (
                     ( $user_expire_timestamp ne "" )
                        and ( $user_expire_timestamp >  $LIMIT_TIMESTAMP )
                     ) {
                     $status = "enable";
                     last LDAP_RESULT;
                     }

                  }
               }
            }

         write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string);

         }

      if (exists $CONFIG->{computers}{$computer}{subfolder}) {
         my $home_path = $CONFIG->{computers}{$computer}{subfolder};
         my @ls = `/bin/ping -W 2 -c 1 $computer > /dev/null 2>&1 && {
            /usr/bin/nmap  -p 22 -PN $computer | grep -q '^22/tcp[[:space:]]*open\b' && {
               /usr/bin/rsync --dry-run $login\@$computer:$home_path /tmp/backuppc-test/ || echo Error for $login\@$computer | logger -t backuppc-silzigan;
               };
            };`;

         $home_path =~ s{/[^/]*$}{};
         LINE:
         for my $line (@ls) {
            chomp $line;
            next LINE if not $line =~ m/skipping\sdirectory/;
            next LINE if $line =~ m/lost+found/;
            next LINE if $line =~ m/administrator/;
            my ($user) = reverse split /\s+/, $line;
            $user =~ s{/$}{};
            $user =~ s{.*/}{};
            next LINE if not $user =~ m/^\w/;

            my $share = "$home_path/$user";

            my @exclude_list = ();
            push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp;
            my $exclude_string = join ",\n", map { "    '$_'" } @exclude_list;

            my $admin = $CONFIG->{computers}{$computer}{admin}
                     || $CONFIG->{default}{admin}
                     || 'root';

            my $status = "disable";
            my $ldb = $LDAP_H->search(
               base     => "$LDAP_BASE",
               filter   => "(uid=$user)",
               attrs    => ['shadowExpire', 'sambaKickoffTime'],
               );
            if (not $ldb->code ) {

               LDAP_RESULT:
               foreach my $entry ($ldb->entries) {

                  my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0;
                  my $user_shadow_expire = $entry->get_value('shadowExpire') || 0;

                  if ($user_shadow_expire == 0) {
                     $status = "enable";
                     last LDAP_RESULT;
                     }
                  elsif (
                     ( $user_expire_timestamp ne "" )
                        and ( $user_expire_timestamp >  $LIMIT_TIMESTAMP )
                     ) {
                     $status = "enable";
                     last LDAP_RESULT;
                     }

                  }
               }

            write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string);
            }
         }
      }
   close_ldap();
   }

sub cmd_exclude_list {
   print io('/usr/lib/kont/etc/backuppc/exclude.txt')->all;
   }

sub write_config {
   my ($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude) = @_;
   my ($c) = split /\./, $computer;
   my $backup_name = $c . '_'. $user;

   return if $status eq 'disable' and not -e "/var/lib/backuppc/pc/$backup_name/backups";

   print "$backup_name 0 $login $admin,$user\n" >> io($CONFIG->{default}{hosts});

   my $share_string = "'$share'";
   if (ref($share) eq "ARRAY") {
      $share_string = join ', ', map("'$_'", @{$share});
      }

#   my $exclude = join ",\n", map { "    '$_'" } io('/usr/lib/kont/etc/backuppc/exclude.txt')->chomp->slurp;

   print <<END > io("$CONFIG->{default}{path}/$backup_name.pl");
\$Conf{XferMethod} = 'rsync';
\$Conf{ClientNameAlias} = '$computer';
\$Conf{RsyncShareName} = [ $share_string ];
\$Conf{RsyncClientCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+';
\$Conf{RsyncClientRestoreCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+';
\$Conf{BackupFilesExclude} = {
  '$share' => [
$exclude
  ]
};
END

   print '$Conf{FullPeriod} = "-2";' >> io("$CONFIG->{default}{path}/$backup_name.pl") if $status eq 'disable';
   symlink "$CONFIG->{default}{path}/$backup_name.pl", "/etc/backuppc/pc/$backup_name.pl";
   }

sub update_hosts {
   io->catfile('/etc/backuppc/hosts.main') > io('/etc/backuppc/hosts.order');
   my @hosts = io('/etc/backuppc/hosts.main')->chomp->slurp;

   my $search_host_file = File::Finder->type('f')->name('hosts');
   for my $host_file (File::Finder->eval($search_host_file)->in('/etc/backuppc/auto')) {
      print "#\n# $host_file\n#\n" >> io('/etc/backuppc/hosts.order');
      io->catfile("$host_file") >> io('/etc/backuppc/hosts.order');
      push @hosts, io("$host_file")->chomp->slurp;
      }
   my ($first, @host) = grep(/./, grep(!/^#/, @hosts));
   print "$first\n" > io('/etc/backuppc/hosts');
   print "$_\n" >> io('/etc/backuppc/hosts') for shuffle(@host);
   }

sub cmd_init_db {
   my $cfg = {
      computers => {
         'machine36.hmg.priv' => {
            login => 'root',
            share => '/home/users/%u',
            status => 'auto',
            users => {
               'dupond' => {
                  share => [ '/home/users/dupond', '/var/www/dupond' ],
                  status => 'enable',
                  },
               'durand' => {
                  share => '/home/users/durand',
                  },
               },
            },
         },
      };

   YAML::DumpFile("/tmp/template-backuppc.yaml", $cfg);
   }

sub cmd_help {
   print <<'END';
backuppc-silzigan - cut into small pieces a computer configuration for backuppc

 backuppc-silzigan init
 backuppc-silzigan generate config_file.yaml
 backuppc-silzigan update
 backuppc-silzigan help
 backuppc-silzigan version
 backuppc-silzigan exclude-list
END
   return;
   }

sub cmd_version {
   print <<'END';
backuppc-silzigan - cut into small pieces a computer configuration for backuppc
Copyright (C) 2011-2019 Gabriel Moreau

$Id: backuppc-silzigan..host.legilnx32 3821 2013-12-20 08:03:14Z g7moreau $
END
   return;
   }
