#!/usr/bin/perl -w

use strict;
use warnings;
use JSON;
use Getopt::Long;
use File::Which;
use Date::Parse;
use File::ReadBackwards;
use Data::Dumper;

my $samba_tool = which('samba-tool');
my $pdbedit = which('pdbedit');
# Number of seconds in the past to count authentications
my $since = 300;
my $pretty = 0;
my $general = 1;
my $ou = undef;

# This log is expected to be in JSON format. For example, in smb.conf :
# log level = 1 auth_audit:3 auth_json_audit:4@/var/log/samba/audit_auth.log
my $audit_auth_log = '/var/log/samba/json/auth.log';

if (not defined $samba_tool or not defined $pdbedit){
  print 'ZBX_NOTSUPPORTED';
  exit 1;
}

GetOptions(
  'pretty'           => \$pretty,
  'since=i'          => \$since,
  'audit-auth-log=s' => \$audit_auth_log,
  'general'          => \$general,
  'ou=s'             => \$ou
);

if ($since !~ m/^\d+$/){
  die "Invalid value for since\n";
}

my $json = {};

if (defined $ou){
  $json = {
    objects => 0
  };
  if ($ou !~ m/^(?<RDN>(?<Key>(?:\\[0-9A-Fa-f]{2}|\\\[^=\,\\]|[^=\,\\]+)+)\=(?<Value>(?:\\[0-9A-Fa-f]{2}|\\\[^=\,\\]|[^=\,\\]+)+))(?:\s*\,\s*(?<RDN>(?<Key>(?:\\[0-9A-Fa-f]{2}|\\\[^=\,\\]|[^=\,\\]+)+)\=(?<Value>(?:\\[0-9A-Fa-f]{2}|\\\[^=\,\\]|[^=\,\\]+)+)))*$/){
    die "Invalid OU\n";
  }
  foreach (qx($samba_tool ou listobjects '$ou' 2>/dev/null)){
    die "Error while counting objects of OU $ou\n" if ($? != 0);
    chomp;
    $json->{objects}++;
  }
} elsif ($general){
  $json = {
    accounts => {
      users          => 0,
      inactive_users => 0,
      active_users   => 0,
      groups         => 0,
      computers      => 0
    },
    replication => 'UNKNWON',
    processes => {
      cldap_server => 0,
      kccsrv          => 0,
      dreplsrv        => 0,
      ldap_server     => 0,
      kdc_server      => 0,
      dnsupdate       => 0,
      'notify-daemon' => 0,
      rpc_server      => 0,
      winbind_server  => 0,
      nbt_server      => 0,
      dnssrv          => 0,
      samba           => 0,
    },
    gpo => 0,
    ou => 0,
    activity => {
      authentications => {
        users => {
          success => 0,
          failure => 0
        },
        computers => {
          success => 0,
          failure => 0
        }
      },
      authorizations => {
        users     => 0,
        computers => 0
      },
      since => $since
    }
  };

  # Get the numbers of users. pdbedit is prefered here because we can
  # differentiate active and inactive users, which samba-tool can't do
  # While at it, also get the computers
  foreach (qx($pdbedit -L -v)){
    next unless (m/^Account Flags:\s+\[(.*)\]/);
    my $flags = $1;
    if ($flags =~ m/U/){
      $json->{accounts}->{users}++;
      if ($flags =~ m/D/){
        $json->{accounts}->{inactive_users}++;
      } else {
        $json->{accounts}->{active_users}++;
      }
    } elsif ($flags =~ m/W/){
      $json->{accounts}->{computers}++;
    }
  }

  # Now count groups
  foreach (qx($samba_tool group list 2>/dev/null)){
    $json->{accounts}->{groups}++;
  }

  # Get replication status
  # We want just a quick summary, so only output the first line
  # manual checks will be needed to get the details, but if this field doesn't contains [ALL GOOD],
  # then something is probably wrong
  $json->{replication} = (split(/\n/, qx($samba_tool drs showrepl --summary 2>/dev/null)))[0];

  # Get the list of workers
  foreach (qx($samba_tool processes 2>/dev/null)){
    if (/^([^\(\s]+).+\d+$/){
      $json->{processes}->{$1}++;
    }
  }

  # Get the number of GPO
  foreach (qx($samba_tool gpo listall 2>/dev/null)){
    next unless (/^GPO/);
    $json->{gpo}++;
  }

  # Get the number of OU
  foreach (qx($samba_tool ou list 2>/dev/null)){
    $json->{ou}++;
  }

  if (-e $audit_auth_log){
    my $backward = File::ReadBackwards->new( $audit_auth_log ) or die "Couldn't open $audit_auth_log : $!\n";
    while (defined (my $line = $backward->readline)){
      my $event;
      eval {
        $event = from_json($line);
      };
      # Skip the log entry if we can't parse JSON
      next if (not defined $event);
      my $type = $event->{type};
      # We're only interested in Authentication and Authorization messages
      next if ($type ne 'Authentication' and $type ne 'Authorization');
      # Parse the date in the timstamp field
      my $timestamp = str2time($event->{timestamp});

      # Skip if date couldn't be parsed
      next if (not defined $timestamp);
      # As we're reading in reverse order, if we reached an events prior to now - since, then we can stop, as all the other will be even earlier
      last if (time() - $timestamp > $since);

      my $subject;
      if ($type eq 'Authentication'){
        # Accounts ending with $ are for computers
        $subject = (($event->{$type}->{mappedAccount} || $event->{$type}->{clientAccount} || '')=~ m/\$$/) ? 'computers' : 'users';
        if ($event->{Authentication}->{status} eq 'NT_STATUS_OK'){
          $json->{activity}->{authentications}->{$subject}->{success}++;
        } else {
          $json->{activity}->{authentications}->{$subject}->{failure}++;
        }
      } else {
        $subject = ($event->{$type}->{account} =~ m/\$$/) ? 'computers' : 'users';
        $json->{activity}->{authorizations}->{$subject}++;
      }
    }
  }
}

print to_json($json, { pretty => $pretty });
