#!/usr/bin/perl -w
#----------------------------------------------------------------------
# copyright (C) 1999-2008 Mitel Networks Corporation
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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
# 
#----------------------------------------------------------------------
package esmith;

use strict;
use Errno;
use esmith::ConfigDB;
use esmith::DomainsDB;
use esmith::NetworksDB;
use esmith::util;

sub allow_networks_2access_cache;
sub delegate_domains_2DNS;

unless (-f "/var/service/dnscache/seed")
{
    system(qw(/bin/dd
              if=/dev/urandom
              of=/var/service/dnscache/seed
              bs=128 count=1)) == 0
                  or warn("Could not create seed file");
}

open(STDIN, "<./seed") or warn "Failed to redirect input from seed file: $!";
open(STDERR, ">&STDOUT") or warn "Failed to redirect stderr to stdout: $!";;

my $config = esmith::ConfigDB->open or die "Could not open config db.";
my $dnscache = $config->get('dnscache');
my $forwarders = $dnscache->prop("Forwarder") || "";
if ($dnscache->prop("Forwarder2"))
{
    $forwarders .= "," . $dnscache->prop("Forwarder2");
}

unless ($dnscache)
{
    die "dnscache not configured in configuration db\n";
}

my $localip = $config->get('LocalIP');
unless ($localip)
{
    die "localip not configured in configuration db\n";
}

my $tinydns = $config->get('tinydns');
unless ($tinydns)
{
    die "tinydns not configured in configuration db\n";
}
my $tinydns_ip = $tinydns->prop('ListenIP') || "127.0.0.1";

$ENV{FORWARDONLY} = '1';
$ENV{IP} = $dnscache->prop('ListenIP') || $localip->value;
$ENV{IPSEND} = $dnscache->prop('SendIP') || '0.0.0.0';
$ENV{CACHESIZE} = 200000;
$ENV{ROOT} = '/service/dnscache/root';

my $domains = esmith::DomainsDB->open or die "Could not open Domains db.";
my $nets = esmith::NetworksDB->open or die "Could not open Networks db.";

#------------------------------------------------------------
# Configure DNS cache access.
#------------------------------------------------------------

# allow my networks to access the nameserver cache
my @localnetworks = ();
my %reversenets = ();

foreach my $net ($nets->get_all_by_prop('type', 'network'))
{
    my $mask = $net->prop('Mask');
    my $key = $net->key;
    my $systemlocalnetwork = $net->prop('SystemLocalNetwork') || 'no';
    my $nameserver = $net->prop('NameServer');
    if (defined $nameserver and $nameserver eq $ENV{IP})
    {
	warn("NameServer property for net $key ($nameserver) would create loop - ignoring\n");
	$nameserver = undef;
    }
    $nameserver ||= ($systemlocalnetwork eq 'yes') ? $tinydns_ip : 'none';
    my @all_network_prefixes = esmith::util::computeAllLocalNetworkPrefixes ($key, $mask);
    push @localnetworks, @all_network_prefixes;

    unless ($nameserver eq 'none')
    {
	foreach (@all_network_prefixes)
	{
	    my @quads = split(/\./, $_);
	    my $reverse_zone = join('.', reverse @quads) . ".in-addr.arpa";
	    $reversenets{$reverse_zone} = $nameserver;
	}
    }
}

allow_networks_2access_cache(@localnetworks);

delegate_domains_2DNS(
        %reversenets,
            map { $_->key => ($_->prop('Nameservers') || $tinydns_ip) }
		($domains->get_all_by_prop('type', 'domain'), 
		$domains->get_all_by_prop('type', 'domain-remote')
));

my $datalimit = $dnscache->prop('DataLimit') || 3000000;

# Ensure that forwarder instance is started
system("sv", "u", "/service/dnscache.forwarder");

# http://marc.theaimsgroup.com/?l=djbdns&m=104812086607532&w=2
$SIG{'PIPE'} = 'IGNORE';
exec("envuidgid", "dnscache", "softlimit", "-o250", "-d", "$datalimit",
    "/usr/local/bin/dnscache")
    or die "failed to execute envuidgid/softlimit/dnscache: $!";
# NOTREACHED
exit (1);

sub allow_networks_2access_cache
{
    my %access = map { $_ => 1 } @_;

    my $dir = '/var/service/dnscache/root/ip';
    unless (-f "$dir/127.0.0.1")
    {
        open F,">$dir/127.0.0.1"
            || die "Cannot add access file for loopback network: $!\n";
        close F;
    }

    opendir(ACCESS, $dir) or
    die "Cannot read dnscache access directory: $!";

    foreach my $aclfile (readdir (ACCESS))
    {
        next if "$aclfile" eq "127.0.0.1";
        next if -d "$aclfile";
        if (exists $access{$aclfile})
        {
            # Cross this one off the list so that we don't bother creating it
            delete $access{$aclfile};
        }
        else
        {
            # We no longer need this entry
            unlink "$dir/$aclfile" or
            warn "Could not delete dnscache access file $dir/$aclfile: $!\n";
        }
    }
    closedir(ACCESS);

    foreach my $aclfile (keys %access)
    {
        link "$dir/127.0.0.1", "$dir/$aclfile" or 
        die "Cannot add network access for $dir/$aclfile: $!\n";
    }
}

sub delegate_domains_2DNS
{
    my %delegations = @_;
    my $serversdir = '/var/service/dnscache/root/servers';

    opendir(SERVERS, $serversdir) or
    die "Cannot read dnscache servers directory: $!";

    foreach my $delegatefile (readdir (SERVERS))
    {
        next if "$delegatefile" eq '@';

        next if -d "$serversdir/$delegatefile";

        unless (exists $delegations{$delegatefile})
        {
            # We no longer need this entry
            unlink "$serversdir/$delegatefile" or
            warn "Could not delete dnscache domain file $serversdir/$delegatefile: $!\n";
        }
    }
    closedir(SERVERS);

    foreach my $delegatefile (keys %delegations)
    {
        if (-l "$serversdir/$delegatefile")
        {
            # Legacy symlink - we use files now
            unlink "$serversdir/$delegatefile" or
            warn "Could not delete dnscache domain link $serversdir/$delegatefile: $!\n";
        }

        open DELEGATE, ">$serversdir/$delegatefile" or
            die "Couldn't create $serversdir/$delegatefile\n";

        for my $ns ( split /,/, $delegations{$delegatefile} )
        {
            if ($ns =~ /^localhost$/)
            {
                print DELEGATE $tinydns_ip . "\n";
            }
            elsif ($ns =~ /^corporate$/)
            {
		print DELEGATE join("\n", split /,/, $forwarders) . "\n";
            }
            elsif ($ns =~ /^internet$/)
            {
                unlink "$serversdir/$delegatefile" or
                    warn "Couldn't unlink $serversdir/$delegatefile\n"
            }
            else
            {
                print DELEGATE $ns . "\n";
            }
        }

        close DELEGATE;
    }
}
