# Copyright (C) 2005-2007 Warp Networks S.L.
# Copyright (C) 2008-2014 Zentyal S.L.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

use strict;
use warnings;

package EBox::Squid::LogHelper;
use base 'EBox::LogHelper';

no warnings 'experimental::smartmatch';
use feature qw(switch);

use EBox;
use EBox::Config;
use EBox::Gettext;
use EBox::Global;
use EBox::Validate;
use POSIX qw(strftime);

use constant SQUIDLOGFILE => '/var/log/squid/access.log';
use constant E2GUARDIANLOGFILE => '/var/log/e2guardian/access.log';

# TODO: Clear this periodically
my %temp;

sub new
{
    my $class = shift;
    my $self = {};

    my $global = EBox::Global->instance();
    my $squid = $global->modInstance('squid');
    $self->{filterNeeded} = $squid->filterNeeded();

    bless($self, $class);
    return $self;
}

# Method: logFiles
#
#   This function must return the file or files to be read from.
#
# Returns:
#
#   array ref - containing the whole paths
#
sub logFiles
{
    return [SQUIDLOGFILE, E2GUARDIANLOGFILE];
}

# Method: processLine
#
#   This method will be run every time a new line is received in
#   the associated file. You must parse the line, and generate
#   the messages which will be logged to ebox through an object
#   implementing EBox::AbstractLogger interface.
# Parameters:
#
#   file - file name
#   line - string containing the log line
#   dbengine- An instance of class implemeting AbstractDBEngineinterface
#
sub processLine # (file, line, logger)
{
    my ($self, $file, $line, $dbengine) = @_;
    chomp $line;

    my @fields = split (/\s+/, $line);

    # FIXME: regex match instead of eq ??
    if ($fields[2] eq '127.0.0.1') {
        return;
    }

    my $event;
    given($fields[3]) {
        when (m{TCP_DENIED(_ABORTED)?/403}) {
            if ($file eq  E2GUARDIANLOGFILE) {
                $event = 'filtered';
            } else {
                $event = 'denied';
            }
        }
        when ('TCP_DENIED/407') {
            # This entry requires authentication, so ignore it
            return;
        }
        default {
            $event = 'accepted';
        }
    }

    # Trim URL string as DB stores it as a varchar(1024)
    my $url = substr($fields[6], 0, 1023);
    if ($file eq  E2GUARDIANLOGFILE) {
        my $time = strftime ('%Y-%m-%d %H:%M:%S', localtime $fields[0]);
        my $domain = $self->_domain($fields[6]);

        if ($url =~ m/$domain$/) {
            # Squid logs adds a final slash as e2guardian does not
            # So we must add final slash
            $url .= '/';
        }

        $temp{$url}->{timestamp} = $time;
        $temp{$url}->{elapsed} = $fields[1];
        $temp{$url}->{remotehost} = $fields[2];
        $temp{$url}->{code} = $fields[3];
        $temp{$url}->{method} = $fields[5];
        $temp{$url}->{url} = $url;
        $temp{$url}->{domain} = substr($domain, 0, 254);
        $temp{$url}->{peer} = $fields[8];
        $temp{$url}->{mimetype} = $fields[9];
        $temp{$url}->{event} = $event;
    } else {
        if ($self->{filterNeeded}) {
            $temp{$url}->{bytes} = $fields[4];
        } else {
            $self->_fillExternalData($url, $event, @fields);
        }
        $temp{$url}->{rfc931} = $fields[7];

        if ($event eq 'denied') {
            $self->_fillExternalData($url, $event, @fields);
        }
    }
    $self->_insertEvent($url, $dbengine);
}

# Group: Private methods

sub _fillExternalData
{
    my ($self, $url, $event, @fields) = @_;

    unless (defined($temp{$url}{timestamp})) {
        my $time = strftime ('%Y-%m-%d %H:%M:%S', localtime $fields[0]);
        my $domain = $self->_domain($fields[6]);
        $temp{$url}->{timestamp} = $time;
        $temp{$url}->{elapsed} = $fields[1];
        $temp{$url}->{remotehost} = $fields[2];
        $temp{$url}->{bytes} = $fields[4];
        $temp{$url}->{method} = $fields[5];
        $temp{$url}->{url} = $url;
        $temp{$url}->{domain} = substr($domain, 0, 254);
        $temp{$url}->{peer} = $fields[8];
        $temp{$url}->{mimetype} = $fields[9];
        $temp{$url}->{event} = $event;
        $temp{$url}->{code} = $fields[3];
    }
}

sub _insertEvent
{
    my ($self, $id, $dbengine) = @_;

    # Check we got all the data
    if (defined($temp{$id}{rfc931}) and
        defined($temp{$id}{timestamp}) and
        defined($temp{$id}{bytes})) {
        $dbengine->insert('squid_access', $temp{$id});
        delete $temp{$id};
    }
}

# Perform the required modifications from a URL to obtain the domain
sub _domain
{
    my ($self, $url) = @_;

    my $domain = $url;
    $domain =~ s{^http(s?)://}{}g;
    $domain =~ s{/.*}{};

    # IPv6 [ip_v6]
    if (substr($domain, 0, 1) eq '[') {
        $domain =~ s{\[|\]}{}g;
        return $domain;
    }

    $domain =~ s{:.*}{}; # Remove port section
    if (EBox::Validate::checkIP($domain)) {
        return $domain;
    }

    my $shortDomain = "";
    my @components = split(/\./, $domain);
    foreach my $element (reverse @components) {
        if ( $shortDomain eq "" ) {
            $shortDomain = $element;
        } elsif ( length($element) < 3 and length($shortDomain) > 5 ) {
            # Then, we should stop here ( a subdomain)
            last;
        } elsif ((length($shortDomain) < 8) or ($components[0] ne $element)) {
            $shortDomain = $element . '.' . $shortDomain;
        } else {
            last;
        }
    }
    return $shortDomain;
}

1;