#!/usr/bin/perl -w

use File::Tail;
use DBI;
use URI;
use Getopt::Long;
use strict;

our %opts = ();

# Set default options
$opts{log} = '/var/log/messages';
$opts{debug} = 0;
$opts{dbhost} = 'localhost';
$opts{dbname} = 'samba_log';
$opts{dbuser} = 'samba';
$opts{dbpass} = 'samba';
our @exclude = ();

# get command line arguments
GetOptions(
    "debug=i"            => \$opts{debug},
    "log=s"              => \$opts{log},
    "dbhost=s"           => \$opts{dbhost},
    "dbname=s"           => \$opts{dbname},
    "dbuser=s"           => \$opts{dbuser},
    "dbpass=s"           => \$opts{dbpass},
    "exclude=s"          => \@exclude
);

@exclude = split(/,/,join(',',@exclude));

# Disable output buffering
select(STDOUT);
$| = 1;
select(STDERR);
$| = 1;

open STDERR, '>&STDOUT';

# Set process name
$0 = 'samba-db-logd';

# Get hostname
our $host = `hostname`;
chomp($host);

### Subroutines

# Print messages on stderr
# for debuging purpose
sub printlog {
    my $msg = shift;
    print "$msg\n";
    return;
}

# Connect to the database
sub db_connect {
    my $dbh = DBI->connect("DBI:mysql:database=$opts{dbname};host=$opts{dbhost}",
                  $opts{dbuser}, $opts{dbpass}, {RaiseError => 1});
    die "Couldn't connect to database\n" unless ($dbh);
    $dbh->do("SET NAMES 'UTF8';") or die "Couldn't set names to UTF-8: ".DBI::errstr;
    return $dbh;
}

# escape chars for MySQL queries
sub mysql_escape {
    my $string = shift;
    $string =~ s|'|\\'|g;
    return $string;
}

# Prepare query once
sub prepare_query {
    my $dbh = shift;
    my $q = "INSERT INTO audit ".
            "(samba_host,date_day,date_time,username,client_ip,client_name,".
            "action,access_mode,status,share,file_src,file_dst)".
            "VALUES(?,?,?,?,?,?,?,?,?,?,?,?);";
    my $qh = $dbh->prepare($q);
    return $qh;
}

my $dbh = db_connect;
my $qh = prepare_query($dbh);

# Open log file

printlog("opening log file") if ($opts{debug} ge 1);

my $tail = File::Tail->new(name=>$opts{log}, maxinterval=>15);

while (defined(my $line=$tail->read)){
    chomp($line);
    my ($username, $client_ip, $client_name, $share,
        $action, $status, $access_mode, $file_src, $file_dst) = undef;

    # Oct 12 17:20:24 sme8 smbd[11176]: admin|192.168.7.50|pc10-45|intranet|mkdir|Nouveau dossier
    if ($line =~ m/^\w+\s\d+\s\d+:\d+:\d+\s\w+\ssmbd\[\d+\]:\s+(\w+)\|(\d+\.\d+\.\d+\.\d+)\|([\w\.\-]+)\|(\w+)\|(\w+)/){
        $username = $1;
        $client_ip = $2;
        $client_name = $3;
        $share = $4;
        $action = $5;
        # Skip logging if listed in --exclude
        next if (grep { $action eq $_ } @exclude);
    }
    else{
        printlog("Couldn't parse this line: $line");
        next;
    }
    my @other = split /\|/, $line;

    if (($action eq 'opendir') ||
        ($action eq 'connect') ||
        ($action eq 'disconnect') ||
        ($action eq 'close')){
        # Oct 12 17:20:24 sme8 smbd[11176]: admin|192.168.7.50|pc11-45|intranet|opendir|ok|./
        $status = $other[5];
        $file_src = $other[6];
        $access_mode = 'r';
    }
    elsif ($action eq 'chdir'){
        #Oct 19 19:14:52 sme8 smbd[2241]: admin|192.168.7.50|pc11-45|intranet|chdir|ok|chdir|/
        $status = $other[5];
        $file_src = $other[7];
        $access_mode = 'r';
    }
    elsif (($action eq 'rmdir') || ($action eq 'mkdir') || ($action eq 'unlink')){
        $status = $other[5];
        $file_src = $other[6];
        $access_mode = 'w';
    }
    elsif ($action eq 'open'){
        # Oct 12 17:20:28 sme8 smbd[11176]: admin|192.168.7.50|pc10-45|intranet|open|ok|r|Nouveau document
        $status = $other[5];
        $access_mode = $other[6];
        $file_src = $other[7];
    }
    elsif ($action eq 'rename'){
        # Oct 12 17:20:28 sme8 smbd[11176]: admin|192.168.7.50|pc10-45|intranet|rename|ok|./Nouveau document|Nouveau document 2
        $status = $other[5];
        $file_src = $other[6];
        $file_dst = $other[7];
        $access_mode = 'w';
    }

    my ($sec,$min,$hour,$day,$mon,$year) = localtime;
    $year += 1900;
    $mon += 1;
    my $date = $year.'-'.$mon.'-'.$day;
    my $time = $hour.':'.$min.':'.$sec;

    # MySQL escape
    # Shouldn't be needed, but just in case logs contains junk
    $username    = mysql_escape($username);
    $client_ip   = mysql_escape($client_ip);
    $client_name = mysql_escape($client_name);
    $share       = mysql_escape($share);
    $action      = mysql_escape($action);
    $access_mode = mysql_escape($access_mode);
    $status      = mysql_escape($status);
    $file_src    = mysql_escape($file_src);
    $file_dst    = mysql_escape($file_dst) if (defined $file_dst);

    # File names may appear with a space at the end in the logs
    $file_src =~ s/\s+$//;
    $file_dst =~ s/\s+$// if (defined $file_dst);

    if ($opts{debug} ge 2){
        my $msg = "New audit entry:\ndate: $date\nhour: $time\nusername: $username\n".
                  "client_ip: $client_ip\nclient_name: $client_name\nshare: $share\n".
                  "action: $action\nstatus: $status\nfile_src: $file_src\naccess_mode: $access_mode";
        $msg .= "\nfile_dst: $file_dst" if (defined $file_dst);
        $msg .= "\n";
        printlog($msg);
    }

    $qh->execute($host,$date,$time,$username,$client_ip,$client_name,$action,
                  $access_mode,$status,$share,$file_src,$file_dst) ||
         die "Database error: ".$qh->errstr;
}

exit(0);
