#!/usr/bin/perl
#
# sg-mod - script for modifying (add/del/replace) squidGuard blacklists bases.
# author: Vladimir Lettiev aka crux <crux@altlinux.org> 2004
# license: LGPL
# version: 0.2
#

use strict;
use DB_File;

my (%db, %domains, %urls, @dest, %bases, @bases, $output, $entries, $action, $baseType, $db_link, $string, $string2);

my $config = "/etc/squid/squidGuard.conf";
my $dbdir = "/var/lib/squidGuard";

unless (@ARGV && ($#ARGV == 2 || $#ARGV == 3)) {
    print &usage;
    exit 1;
}

unless ($ARGV[0] eq "add" || $ARGV[0] eq "del" || $ARGV[0] eq "replace"){
    print &usage;
    exit 1;
}
$action = $ARGV[0];

if (($ARGV[1] eq "ALL" && $action ne "del") || ($action eq "replace" && (! defined($ARGV[3]) || $ARGV[3] eq ""))){
    print &usage;
    exit 1;
}

parseConf();

if ($ARGV[1] ne "ALL"){
    foreach my $dest (keys %domains) {
        $baseType = "domain";
        if (ref($domains{$dest})) {
            foreach my $subdest (@{$domains{$dest}}) {
                if ($subdest ne $ARGV[1]) { next; }
                push(@bases,$subdest);
                last;
            }
            if (@bases) { last; }
        } else {
            if ($domains{$dest} ne $ARGV[1]) { next; }
            push(@bases,$domains{$dest});
            last;
        }
    }
    if (! @bases){
        $baseType = "url";
        foreach my $dest (keys %urls) {
            if (ref($urls{$dest})) {
                foreach my $subdest (@{$urls{$dest}}) {
                    if ($subdest ne $ARGV[1]) { next; }
                    push(@bases,$subdest);
                    last;
                }
                if (@bases) { last; }
            } else {
                if ($urls{$dest} ne $ARGV[1]) { next; }
                push(@bases,$urls{$dest});
                last;
            }
        }
    }
    if (! @bases){
        print "list \"$ARGV[1]\" not found in $config\n";
        exit 2;
    }
} else {
    foreach my $dest (keys %domains) {
        if (ref($domains{$dest})) {
            foreach my $subdest (@{$domains{$dest}}) {
                $bases{$subdest} = "domain";
                push(@bases,$subdest);
            }
        } else {
            $bases{$domains{$dest}}="domain";
            push(@bases,$domains{$dest});
        }
    }
    foreach my $dest (keys %urls) {
        if (ref($urls{$dest})) {
            foreach my $subdest (@{$urls{$dest}}) {
                $bases{$subdest} = "url";
                push(@bases,$subdest);
            }
        } else {
            $bases{$urls{$dest}} = "url";
            push(@bases,$urls{$dest});
        }
    }
}

$string = lc($ARGV[2]);
$string =~ s/^[\.]*//;
if ($string =~ /^(http|https|ftp|wais)\:\//) {
    print "url or domain must not include a method, such as http://, ftp:// and so on\n";
    exit 3;
}

foreach my $curbase (@bases){

    if (! $baseType) {
        $baseType = $bases{$curbase};
    }

    if ($baseType eq "domain"){
        $DB_BTREE->{compare} = \&domainmatch;
    } elsif ($baseType eq "url"){
        $DB_BTREE->{compare} = \&urlmatch;    
    } else {
        print "internal error, please report a bug\n";
        exit 4;
    }

    unless ($db_link = tie %db, "DB_File", $dbdir."/".$curbase.".db", O_RDWR|O_CREAT, 0664, $DB_BTREE) {
        print "Skip base $dbdir/$curbase.db: $!\n";
        next;
    }
    if ($action eq "del"){
        if (exists($db{$string})){
            $db_link->del($string);
            $db_link->sync;
            $entries++;
            $output .= "deleted from $curbase\n";
        } elsif (exists($db{".$string"})) {
            $db_link->del(".$string");
            $db_link->sync;
            $output .= "deleted from $curbase\n";
            $entries++;
        }
    } elsif ($action eq "add") {
        if ($baseType eq "domain"){
            if (! exists($db{".$string"})){
                $db_link->put(".$string","");
                $db_link->sync;
                $output = "Domain \"$string\" successfully added into $curbase\n";
            } else {
                print "Domain \"$string\" already exist in $curbase\n";
            }
        } else {
            if (! exists($db{$string})){
                $db_link->put($string,"");
                $db_link->sync;
                $output = "Url \"$string\" successfully added into $curbase\n";
            } else {
                print "Url \"$string\" already exist in $curbase\n";
            }
        }
    } elsif ($action eq "replace"){
        $string2 = lc($ARGV[3]);
        $string2 =~ s/^[\.]*//;
        if ($string =~ /^(http|https|ftp)\:\//) {
            print "url or domain must not include a method, such as http://, ftp:// and so on\n";
            exit 3;
        }
        if ($baseType eq "domain"){
            if (exists($db{".$string"}) && ! exists($db{".$string2"})){
                $db_link->del(".$string");
                $db_link->sync;
                $db_link->put(".$string2","");
                $db_link->sync;
                $output = "Domain \"$string\" successfully replaced by \"$string2\" in $curbase\n";
            } else {
                print "Domain \"$string\" does not exists OR \"$string2\" already exist in $curbase\n";
            }
        } else {
            if (exists($db{$string}) && ! exists($db{$string2}) ){
                $db_link->del($string);
                $db_link->sync;
                $db_link->put($string2,"");
                $db_link->sync;
                $output = "Url \"$string\" successfully  replaced by \"$string2\" in $curbase\n";
            } else {
                print "Url \"$string\" does not exists OR \"$string2\" already exist in $curbase\n";
            }
        }
    } else {
        $output = "internal error, please report a bug";
    }
    undef $db_link;
    untie %db;
}

if ($action eq "del"){
    if ($output) {
        $output .= "\nDeleted $entries entries of domain or url \"$string\".\n";
    } else {
        $output = "Sorry, domain or url $string do not exist\n";
    }
}
print $output;

sub parseConf () {
    my $dest;
    open(CONF,$config) || die "can't open $config: $!\n";
    while (<CONF>) {
        unless (/^\s*dbhome\s+(\S+)/) { next; }
        $dbdir = $1;
        last;
    }
    while (<CONF>) {
        unless (/^\s*dest\s+(\S+)/) { next; }
        $dest = $1;
        push (@dest,$dest);
        while (<CONF>) {
            if (/^\s*domainlist\s+(\S+)/) {
                if (exists ($domains{$dest})){
                    $domains{$dest} = [ $domains{$dest},$1 ];
                } else {
                    $domains{$dest} = $1;
                }
            } elsif (/^\s*urllist\s+(\S+)/) {
                if (exists ($urls{$dest})){
                    $urls{$dest} = [ $urls{$dest},$1 ];
                } else {
                    $urls{$dest} = $1;
                }
            } elsif (/\}/) {
                last;
            }
        }
    }
    close(CONF);
}

sub usage() {
return <<EOF;
sg-mod - script for modifying squidGuard blacklists bases.
        Note! you must have write access to .db files and
        read access to squidGuard.conf.
        In most cases you dont need root privileges, use
        sudo -u squid sg-mod <params> -- this will be enough.

usage:
    sg-mod <add|del|replace> <list|ALL> string [string2]

    ACTIONS:
    --------
    add     - add a new entry to base
    del     - delete entry from base
    replace - replace "string" by "string2" in base

    LISTS:
    ------
    list    - domain or url list, such as "db/porn/domains", "db/porn/urls"...
              check your $config for correct paths...
    ALL     - all lists (used only for 'delete' action!)
    
    STRINGS:
    --------
    string  - domain or url
    string2 - domain or url to replace (used only for 'replace' action!)
    
EOF
}

sub mirror($) {
    scalar(reverse(shift));
}

sub domainmatch($$) {
    my $search = mirror(lc(shift));
    my $found = mirror(lc(shift));
    if ("$search." eq $found) {
        return(0);
    } else {
        return(substr($search,0,length($found)) cmp $found);
    }
}

sub urlmatch($$) {
    my $search = lc(shift) . "/";
    my $found = lc(shift) . "/";
    if ($search eq $found) {
        return(0);
    } else {
        return(substr($search,0,length($found)) cmp $found);
    }
}
