#!%PERL%
# Copyright (c) vhffs project and its contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in
#   the documentation and/or other materials provided with the
#   distribution.
#3. Neither the name of vhffs nor the names of its contributors
#   may be used to endorse or promote products derived from this
#   software without specific prior written permission.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
#FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
#COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# Mirroring script for exim on mx2 (no listengine).
# Set master & slave DB params and put it in a cron.
# Slave database must have at least vhffs_mx2domain and
# vhffs_mx2addresses tables.

use DBI;
use strict;
use utf8;

# Master DB params
my $MASTER_DB_HOST = 'localhost';
my $MASTER_DB_PORT = 5432;
my $MASTER_DB_NAME = 'vhffs_auth';
my $MASTER_DB_USER = 'vhffs';
my $MASTER_DB_PASS = 'vhffs';

# Slave DB params
my $SLAVE_DB_HOST = 'localhost';
my $SLAVE_DB_PORT = 5432;
my $SLAVE_DB_NAME = 'exim';
my $SLAVE_DB_USER = 'vhffs';
my $SLAVE_DB_PASS = 'vhffs';

# We've to connect to the master DB, fetch
# mxdomain, boxes, forward & ml
# tables and reinject them in slave DB
# We just fetch fields usefull for address verification

my $master_dbh = DBI->connect("DBI:Pg:dbname=$MASTER_DB_NAME;host=$MASTER_DB_HOST;port=$MASTER_DB_PORT", $MASTER_DB_USER, $MASTER_DB_PASS)
    or die("Unable to open master connection\n");

my $slave_dbh = DBI->connect("DBI:Pg:dbname=$SLAVE_DB_NAME;host=$SLAVE_DB_HOST;port=$SLAVE_DB_PORT", $SLAVE_DB_USER, $SLAVE_DB_PASS)
    or die("Unable to open slave connection\n");

# Create temporary tables
$slave_dbh->do('CREATE TEMPORARY TABLE tmp_mxdomain(LIKE vhffs_mx2domain)')
    or die("Unable to create temporary MX domain table\n");
$slave_dbh->do('CREATE TEMPORARY TABLE tmp_addresses(LIKE vhffs_mx2addresses)')
    or die("Unable to create temporary boxes table\n");

$master_dbh->{AutoCommit} = 0;
$slave_dbh->{AutoCommit} = 0;

# We need to set transaction isolation level to serializable to avoid
# foreign key issues
$master_dbh->do('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE')
    or die("Unable to set transaction level on master DB\n");

# Replicate vhffs_mxdomain table
my $msth = $master_dbh->prepare(q{SELECT d.domain, d.catchall
    FROM vhffs_mxdomain d 
    INNER JOIN vhffs_object o ON o.object_id = d.object_id
    WHERE o.state = 6})
    or die("Unable to prepare SELECT query for vhffs_mxdomain\n");
my $ssth = $slave_dbh->prepare(q{INSERT INTO tmp_mxdomain(domain, catchall)
    VALUES(?, ?)}) 
    or die("Unable to prepare INSERT query for tmp_mxdomain\n");

$msth->execute()
    or die("Unable to execute SELECT query for vhffs_mxdomain\n");

while(my $row = $msth->fetchrow_hashref()) {
    $ssth->execute($row->{domain}, $row->{catchall})
    or die('Unable to insert mxdomain #'.$row->{id}."\n");
}

$ssth->finish();
$msth->finish();

# Replicate all addresses (boxes, forward & ml)

# First, we fetch boxes & fowards since it's the
# same scheme (a box/forward depends from a domain which
# is active or not.
$msth = $master_dbh->prepare(q{SELECT bf.domain,  bf.local_part
    FROM (SELECT domain AS domain, local_part AS local_part
        FROM vhffs_boxes b WHERE state = 6
        UNION 
        SELECT domain AS domain, local_part AS local_part
        FROM vhffs_forward f) AS bf
    INNER JOIN vhffs_mxdomain d ON d.domain = bf.domain
    INNER JOIN vhffs_object o ON o.object_id = d.object_id
    WHERE o.state = 6})
    or die("Unable to prepare SELECT query for vhffs_boxes/forward\n");
$ssth = $slave_dbh->prepare(q{INSERT INTO tmp_addresses(domain, local_part)
    VALUES(?, ?)})
    or die("Unable to prepare INSERT query for tmp_addresses\n");

$msth->execute()
    or die("Unable to execute SELECT query for vhffs_boxes/forward\n");

while(my $row = $msth->fetchrow_hashref()) {
    $ssth->execute($row->{domain}, $row->{local_part})
            or die('Unable to insert box/forward '.$row->{local_part}.'@'.
                $row->{domain}."\n");
}
$ssth->finish();
$msth->finish();

# OK, now we've to fetch mailing lists. The scheme isn't the same since a
# mailing list is an object. Its activation doesn't really depends from its
# domain activation. Moreover we've to add -request address.

$msth = $master_dbh->prepare(q{SELECT ml.domain,  ml.local_part
    FROM vhffs_ml ml
    INNER JOIN vhffs_object o ON o.object_id = ml.object_id
    WHERE o.state = 6})
    or die("Unable to prepare SELECT query for vhffs_ml\n");

$msth->execute()
    or die("Unable to execute SELECT query for vhffs_ml\n");

while(my $row = $msth->fetchrow_hashref()) {
    $ssth->execute($row->{domain}, $row->{local_part})
        or die('Unable to insert ml '.$row->{local_part}.'@'.
            $row->{domain}."\n");
    $ssth->execute($row->{domain}, $row->{local_part}.'-request')
        or die('Unable to insert ml request
        address'.$row->{local_part}.'-request@'.$row->{domain}."\n");
}
$ssth->finish();
$msth->finish();

# We're done fetching data
$master_dbh->disconnect();

my $count;

($count = $slave_dbh->do(q{DELETE FROM vhffs_mx2addresses WHERE
    NOT EXISTS(SELECT * FROM tmp_addresses t 
    WHERE t.domain = vhffs_mx2addresses.domain
    AND t.local_part = vhffs_mx2addresses.local_part)}))
    or die("Unable to delete no more existing addresses\n");
print "$count addresses deleted\n";

($count = $slave_dbh->do(q{DELETE from vhffs_mx2domain
    WHERE domain NOT IN(SELECT domain FROM tmp_mxdomain)}))
    or die("Unable to delete o more existing domains\n");
print "$count domains deleted\n";

# The only update case is domain catchall since others data are
# unique

($count = $slave_dbh->do(q{UPDATE vhffs_mx2domain SET catchall = tmp.catchall
    FROM tmp_mxdomain tmp WHERE tmp.domain = vhffs_mx2domain.domain}))
    or die("Unable to update catchall for existing domains\n");
print "$count domains updated\n";

# Insert new addresses/domains

($count = $slave_dbh->do(q{INSERT INTO vhffs_mx2domain(domain, catchall)
    SELECT domain, catchall FROM tmp_mxdomain WHERE domain NOT
    IN(SELECT domain FROM vhffs_mx2domain)}))
    or die("Unable to insert new mail domains\n");
print "$count domains added\n";

($count = $slave_dbh->do(q{INSERT INTO vhffs_mx2addresses(domain, local_part)
    SELECT domain, local_part FROM tmp_addresses tmp WHERE NOT EXISTS(
    SELECT * FROM vhffs_mx2addresses a WHERE a.domain = tmp.domain AND
    a.local_part = tmp.local_part)}))
    or die("Unable to insert new addresses\n");
print "$count addresses added\n";

$slave_dbh->commit();
$slave_dbh->disconnect();
