#!%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.


# This file is a part of VHFFS4 software, a hosting platform suite
# Please respect the licence of this file and whole program

# Author : soda < dieu at gunnm dot org >

=pod

=head1 SYNOPSYS

Vhffs::Services::Mail - Handle mail domains in VHFFS.

=head2 METHODS

=cut

use strict;
use utf8;

package Vhffs::Services::Mail;

use base qw(Vhffs::Object);
use DBI;
use Vhffs::Services::MailingList;

sub delete
{
    my $self = shift;
	my $query;
	my $request;


    $query = "DELETE FROM vhffs_mxdomain WHERE object_id='".$self->{'object_id'}."'";
    $request = $self->{'db'}->prepare($query);
    $request->execute or return -1;

    $query = "DELETE FROM vhffs_boxes WHERE domain='".$self->{'domain'}."'";
    $request = $self->{'db'}->prepare($query);
    $request->execute or return -1;

    $query = "DELETE FROM vhffs_forward WHERE domain='".$self->{'domain'}."'";
    $request = $self->{'db'}->prepare($query);
    $request->execute or return -1;





    return -2 if( $self->SUPER::delete < 0 );

    return 1;
}

=pod

=head2 create

    my $mail = Vhffs::Services::Mail::create($main, $domain, $description, $user, $group);

Create a new domain mail in database and return corresponding object.

=cut

sub create {
    my ($main, $domain, $description, $user, $group) = @_;
    return undef unless(defined($user) && defined($group));
    return undef unless(Vhffs::Functions::check_domain_name($domain));

    my $mail;
    my $dbh = $main->get_db_object();
    local $dbh->{RaiseError} = 1;
    local $dbh->{PrintError} = 0;
    $dbh->begin_work;

    eval {
        my $parent = Vhffs::Object::create($main, $user->get_uid, $group->get_gid, $description, undef, Vhffs::Constants::TYPE_MAIL);

        die('Unable to create parent object') unless(defined $parent);

        my $sql = 'INSERT INTO vhffs_mxdomain(domain, boxes_path, catchall, object_id) VALUES(?, ?, \'\', ?)';
        my $domain_hash = Vhffs::Functions::hash_mxdomain($domain);
        my $sth = $dbh->prepare($sql);
        $sth->execute($domain, $domain_hash, $parent->get_oid);

        $dbh->commit;
        $mail = get_by_mxdomain($main, $domain);
    };

    if($@) {
        warn "Unable to create mail domain $domain: $@\n";
        $dbh->rollback;
    }

    return $mail;
}

# Commit all changes of the current instance in the database
sub commit
{
	use Vhffs::Functions;

	my $self = shift;	

	my( $request , $result , $rows , $query , $name , $password);

	$self->{'catchall'} = "" if( ! defined $self->{'catchall'} );

	$query = "UPDATE vhffs_mxdomain SET catchall='".$self->{'catchall'}."' WHERE domain='".$self->{'domain'}."'";
	$request = $self->{'db'}->prepare( $query );
	$request->execute;

	return -3 if( $self->SUPER::commit < 0 );

	return 1;
}


sub get_dir
{
	my $self = shift;

	return undef unless defined $self;
	return( $self->{'main'}->get_config->get_datadir . '/mail/boxes/' . $self->{'boxes_path'} );
}


sub change_forward
{
	my $self = shift;
	my $local_part = shift;
	my $destination = shift;

    return -1 unless( defined($self->{forward}{$local_part}) && defined($local_part) && defined($destination) && Vhffs::Functions::valid_mail( $destination ) );

    my $sql = 'UPDATE vhffs_forward SET remote_name = ? WHERE local_part = ? AND domain = ?';
    my $dbh = $self->{db};
    my $sth = $dbh->prepare($sql);
    $sth->execute($destination, $local_part, $self->{domain}) or return -1;

	$self->{'forward'}{$local_part}{'remote_name'} = $destination;

	return 1;
}

sub change_box_password
{
	my $self = shift;
	my $local_part = shift;
	my $password = shift;

	return -1 unless defined $self->{'boxes'}{$local_part};

    $password = crypt_pwd( $password );

	$self->{'boxes'}{$local_part}{'password'} = $password;

    my $sql = 'UPDATE vhffs_boxes SET password = ? WHERE local_part = ? AND domain = ?';
    my $dbh = $self->{db};
    my $sth = $dbh->prepare($sql);
    $sth->execute($password, $local_part, $self->{domain}) or return -1;

	return 1;
}

# The change_spam_status function change the status of a box 
# on the mail domain given in parmeter
# If the spam removal was enable, it will be disable at next commit() on object
sub change_spam_status
{
	my $self = shift;
	my $local_part = shift;

	return -1 if( ! defined $self->{'boxes'}{$local_part} );

    my $nospam = $self->{boxes}{$local_part}{nospam};
    if( defined( $nospam ) ) {
        $nospam = ( $nospam + 1 ) % 2;
    } else {
        $nospam = 1;
    }
    $self->{boxes}{$local_part}{nospam} = $nospam;

    my $sql = 'UPDATE vhffs_boxes SET nospam = ? WHERE domain = ? AND local_part = ?';
    my $dbh = $self->{db};
    my $sth = $dbh->prepare($sql);
    $sth->execute($nospam, $self->{domain}, $local_part) or return -1;

	return 1;
}

=head2 set_spam_status

    $mail->set_spam_status($boxname, $status);

Sets the spam protection status to C<$status> (0 or 1).

=cut

sub set_spam_status {
    my ($self, $boxname, $status) = @_;
    
    return -1 unless(defined $self->{boxes}{$boxname});

    $self->{boxes}{$boxname}{nospam} = $status;
    my $sql = 'UPDATE vhffs_boxes SET nospam = ? WHERE domain = ? AND local_part = ?';
    my $dbh = $self->get_db_object();
    return -2 unless( $dbh->do($sql, undef, $status, $self->{domain}, $boxname) );
    return 1;
}


=head2 set_virus_status

    $mail->set_virus_status($boxname, $status);

Sets the virus protection status to C<$status> (0 or 1).

=cut

sub set_virus_status {
    my ($self, $boxname, $status) = @_;
    
    return -1 unless(defined $self->{boxes}{$boxname});

    $self->{boxes}{$boxname}{novirus} = $status;
    my $sql = 'UPDATE vhffs_boxes SET novirus = ? WHERE domain = ? AND local_part = ?';
    my $dbh = $self->get_db_object();
    return -2 unless( $dbh->do($sql, undef, $status, $self->{domain}, $boxname) );
    return 1;
}


#work as change_spam_status
sub change_virus_status
{
	my $self = shift;
	my $local_part = shift;

	return -1 if( ! defined $self->{'boxes'}{$local_part} );

    my $novirus = $self->{boxes}{$local_part}{novirus};
    if(defined $novirus) {
        $novirus = ($novirus + 1) % 2;
    } else {
        $novirus = 1;
    }

	$self->{'boxes'}{$local_part}{'novirus'} = $novirus;

    my $sql = 'UPDATE vhffs_boxes SET novirus = ? WHERE domain = ? AND local_part = ?';
    my $dbh = $self->{db};
    my $sth = $dbh->prepare($sql);
    $sth->execute($novirus, $self->{domain}, $local_part) or return -1;

	return 1;
}


sub set_box_allowpop
{
	my $self = shift;
	my $local_part = shift;
	my $allow = shift;

	return -1 unless defined $self->{'boxes'}{$local_part};

	$self->{'boxes'}{$local_part}{'allowpop'} = $allow;

	my $sql = 'UPDATE vhffs_boxes SET allowpop=? WHERE domain=? AND local_part=?';
	my $dbh = $self->{db};
	my $sth = $dbh->prepare($sql);
	$sth->execute($allow, $self->{domain}, $local_part) or return -1;

	return 1;
}


sub get_box_allowpop
{
	my $self = shift;
	my $local_part = shift;

	return undef unless defined $self->{'boxes'}{$local_part};
	return $self->{'boxes'}{$local_part}{'allowpop'};
}


sub set_box_allowimap
{
	my $self = shift;
	my $local_part = shift;
	my $allow = shift;

	return -1 unless defined $self->{'boxes'}{$local_part};

	$self->{'boxes'}{$local_part}{'allowimap'} = $allow;

	my $sql = 'UPDATE vhffs_boxes SET allowimap=? WHERE domain=? AND local_part=?';
	my $dbh = $self->{db};
	my $sth = $dbh->prepare($sql);
	$sth->execute($allow, $self->{domain}, $local_part) or return -1;

	return 1;
}


sub get_box_allowimap
{
	my $self = shift;
	my $local_part = shift;

	return undef unless defined $self->{'boxes'}{$local_part};
	return $self->{'boxes'}{$local_part}{'allowimap'};
}


sub set_box_status
{
	my $self = shift;
	my $local_part = shift;
	my $state = shift;

	return -1 unless defined $self->{'boxes'}{$local_part};

	$self->{'boxes'}{$local_part}{'state'} = $state;

	my $sql = 'UPDATE vhffs_boxes SET state=? WHERE domain=? AND local_part=?';
	my $dbh = $self->{db};
	my $sth = $dbh->prepare($sql);
	$sth->execute($state, $self->{domain}, $local_part) or return -1;

	return 1;
}


sub get_box_status
{
	my $self = shift;
	my $local_part = shift;

	return undef unless defined $self->{'boxes'}{$local_part};
	return $self->{'boxes'}{$local_part}{'state'};
}


sub get_box_dir
{
	my $self = shift;
	my $local_part = shift;

	return undef unless( defined $self  &&  defined $local_part  &&  defined $self->{'boxes'}{$local_part} );
 
	return( $self->{'main'}->get_config->get_datadir . '/mail/boxes/' . $self->{'boxes_path'} . '/' . $self->{'boxes'}{$local_part}{'mbox_name'} );
}


=pod

=head2 addforward

    die("Unable to add forward $local_part\n") unless($mail->addforward($local_part, $remote_address) > 0);

Add a forward from $local_part@$mail->get_domain to $remote_address.

=cut

sub addforward
{
	my $self = shift;
	my $name = shift;
	my $remote = shift;

	return -1 unless(defined($name) && defined($remote) && $name =~ /^[a-z0-9\_\-\.]+$/ && Vhffs::Functions::valid_mail( $remote ) );
	return -2 if( defined $self->{'forward'}{$name}
		||  ( defined $self->{'boxes'}{$name}  &&  $self->{'boxes'}{$name}{'state'} != Vhffs::Constants::TO_DELETE )
		||  Vhffs::Services::MailingList::address_exists($self->get_main, $name, $self->{domain}) );

    my $sql = 'INSERT INTO vhffs_forward(domain, local_part, remote_name, password) VALUES(?, ?, ?, \'x\')';
    my $dbh = $self->{db};
    my $sth = $dbh->prepare($sql);
    $sth->execute($self->{domain}, $name, $remote) or return -3;

	$self->{'forward'}{$name}{'local_part'} = $name;
	$self->{'forward'}{$name}{'domain'} = $self->{'domain'};
	$self->{'forward'}{$name}{'remote_name'} = $remote;
    $self->{forward}{$name}{password} = 'x';
	
	return 1;
}

=pod

=head2 addbox

    die("Unable to create box\n") unless($mail->addbox($local_part, $password));

Add a new mailbox to the mail domain.

=cut

sub addbox
{
	my $self = shift;
	my $name = shift;
	my $password = shift;

    return -1 unless( defined($password)  &&  defined($name)  &&  $name =~ /^[a-z0-9\_\-\.]+$/ );
    return -2 if( ( defined ( $self->{'boxes'}{$name} ) ) || ( defined( $self->{'forward'}{$name} ) ) 
        || Vhffs::Services::MailingList::address_exists($self->get_main, $name, $self->{domain}) );

    $password = crypt_pwd( $password );
    my $domainhash = Vhffs::Functions::hash_mxdomain($self->{domain});
    my $userhash = Vhffs::Functions::hash_popuser( $name );
	$self->{'boxes'}{$name}{'local_part'} = $name;
	$self->{'boxes'}{$name}{'password'} = $password;
	$self->{'boxes'}{$name}{'domain'} = $self->{'domain'};
    $self->{boxes}{$name}{domain_hash} = $domainhash;
    $self->{boxes}{$name}{password} = $password;
    $self->{boxes}{$name}{novirus} = 0;
    $self->{boxes}{$name}{nospam} = 0;
    $self->{boxes}{$name}{mbox_name} = $userhash;
    $self->{boxes}{$name}{state} = Vhffs::Constants::WAITING_FOR_CREATION;
	
    my $sql = 'INSERT INTO vhffs_boxes(domain, local_part, domain_hash, password, mbox_name, nospam, novirus, allowpop, allowimap, state) VALUES(?, ?, ?, ?, ?, FALSE, FALSE, TRUE, TRUE,  ?)';
    my $dbh = $self->{db};
    my $sth = $dbh->prepare($sql);
    $sth->execute($self->{domain}, $name, $domainhash, $password, $userhash, Vhffs::Constants::WAITING_FOR_CREATION) or return -3;

	return 1;
}

sub delbox
{
	my $self = shift;
	my $name = shift;

	return -1 unless( defined $name && ( $name =~ /^[a-z0-9\_\-\.]+$/ ) );

	delete $self->{boxes}{$name};

    my $dbh = $self->get_db_object;
    my $sql = 'DELETE FROM vhffs_boxes WHERE local_part = ? AND domain = ?';
    $dbh->do($sql, undef, $name, $self->{domain});
    $sql = 'UPDATE vhffs_mxdomain SET catchall = \'\' WHERE catchall = ?';
    $dbh->do($sql, undef, $name.'@'.$self->{domain});
}


sub delforward
{
	my $self = shift;
	my $name = shift;

	return -1 unless( defined $name  && ( $name =~ /^[a-z0-9\_\-\.]+$/ ) );
	delete $self->{forward}{$name};

    my $dbh = $self->get_db_object;
    my $sql = 'DELETE FROM vhffs_forward WHERE local_part = ? AND domain = ?';
    $dbh->do($sql, undef, $name, $self->{domain});
    $sql = 'UPDATE vhffs_mxdomain SET catchall = \'\' WHERE catchall = ?';
    $dbh->do($sql, undef, $name.'@'.$self->{domain});
}

# Returns a hashref with all forwards
# Ths key of this hash is the local part for the forward
sub get_forwards
{
	my $self = shift;
	return $self->{'forward'};
}

sub exists
{
	my $self = shift;
	my $name = shift;

	return 1 if( ( $self->exists_forward( $name ) ==1 ) || ( $self->exists_box( $name ) ==1 ) );
	return 0;
}

sub exists_forward
{
	my $self = shift;
	my $name = shift;
	return -1 if( ! ( $name =~ /^[a-z0-9\_\-\.]+$/ ) );
    my $request = $self->{'db'}->prepare("SELECT * FROM vhffs_forward where domain='".$self->{'domain'}."' AND local_part='".$name."'") or return -1;
    return 1 if( $request->execute() > 0 );
	return 0;
}

sub exists_box
{
	my $self = shift;
	my $name = shift;
	return -1 if( ! ( $name =~ /^[a-z0-9\_\-\.]+$/ ) );
    my $request = $self->{'db'}->prepare("SELECT * FROM vhffs_boxes where domain='".$self->{'domain'}."' AND local_part='".$name."'") or return -1;
    return 1 if( $request->execute() > 0 );
	return 0;
}


sub nb_boxes
{
	my $self = shift;
	return scalar( keys( %{$self->{'boxes'}} ) );
}


sub nb_forwards
{
	my $self = shift;
	return scalar( keys( %{$self->{'forward'}} ) );
}


#Following function is needed by courier to fetch accounts

sub crypt_pwd
{
	my $clear = shift;
	my $salt  = join '', ('.', '/', 0..9,'A'..'Z', 'a'..'z')[rand 64, rand 64];
	return crypt( $clear , $salt );
}


# return 1 if the password is good, else return something 0
sub check_box_password
{
	my $self = shift;
	my $local_part = shift;
	my $clearpw = shift;

	return 0 unless defined $self->{'boxes'}{$local_part};
	my $dbpass = $self->{'boxes'}{$local_part}{'password'};

	return 1 if( $dbpass eq crypt( $clearpw, $dbpass ) );
	return 0;
}

# Returns an hashref with all boxes
# The key of this hash is the local_part of each mailbox
sub get_boxes
{
	my $self = shift;
	return $self->{'boxes'};
}

sub get_label {
    my $self = shift;
    return $self->{domain};
}

sub get_domain
{
	my $self = shift;
	return $self->{'domain'};
}



###########################
# ospam function only use when the mail domain
# if fetched
# It can explain if a box use antispam or not
# for example $mail->use_nospam( 'myaccount' );
# returns 	-1 if error
# 			0 if nospam is used
#			1 if spam is used
sub use_nospam
{
	my $self = shift;
	my $box = shift;

	return -1 if(  ! defined( $self->{'boxes'}->{$box} ) );
	return 1 if( $self->{'boxes'}->{$box}{'nospam'} == 1 );
	return 0;
}

sub use_novirus
{
	my $self = shift;
	my $box = shift;

	return -1 if(  ! defined( $self->{'boxes'}->{$box} ) );
	return 1 if( $self->{'boxes'}->{$box}{'novirus'} == 1 );
	return 0;
}



sub get_boxespath
{
	my $self = shift;
	return $self->{'boxes_path'};
}

sub get_catchall
{
	my $self = shift;
	
	if( defined $self->{'catchall'} )
	{
		return $self->{'catchall'};
	}
	else
	{
		return "";
	}
}

sub set_catchall
{
	my( $self , $value ) = @_;
	use Vhffs::Functions;
	if( $value eq '' || Vhffs::Functions::valid_mail( $value ) )
	{
		$self->{'catchall'} = $value;
		return 1;
	}
	return -1;
}

=pod

=head2 get_by_mxdomain

    my $mail = Vhffs::Services::get_by_mxdomain($main, $domain);
    die("Mail domain $domain not found\n") unless(defined $mail);

Fetches the mail services whose domainname is $domain.

=cut

sub get_by_mxdomain {
    my ($main, $domain) = @_;

    my $sql = 'SELECT m.mxdomain_id, o.owner_gid, m.domain, m.boxes_path, m.catchall, o.object_id, o.owner_uid, o.date_creation, o.state, o.description FROM vhffs_mxdomain m INNER JOIN vhffs_object o ON o.object_id = m.object_id WHERE domain = ?';
    my $dbh = $main->get_db_object();
    my @params;
    return undef unless(@params = $dbh->selectrow_array($sql, undef, $domain));

    # We now need the boxes and forwards
    my $boxes = fetch_boxes($dbh, $domain);
    my $forwards = fetch_forwards($dbh, $domain);


    return _new Vhffs::Services::Mail($main, @params, $boxes, $forwards);

}

=head2 fill_object

See C<Vhffs::Object::fill_object>.

=cut

sub fill_object {
    my ($class, $obj) = @_;
    my $sql = q{SELECT mxdomain_id, domain, boxes_path,
    catchall FROM vhffs_mxdomain WHERE object_id = ?};
    $obj = $class->SUPER::_fill_object($obj, $sql);
    if($obj->isa('Vhffs::Services::Mail')) {
        $obj->{boxes} = fetch_boxes($obj->get_db_object, $obj->{domain});
        $obj->{forward} = fetch_forwards($obj->get_db_object, $obj->{domain});
    }
    return $obj;
}

=head2 fetch_boxes

    my $boxes = fetch_boxes($dbh, $domain);

Returns an hashref of hashrefs containing all this domain's boxes, indexed on
local_part.

Internal module use.

=cut

sub fetch_boxes {
    my ($dbh, $domain) = @_;
    my $sql = q{SELECT domain, local_part, domain_hash, password,
    mbox_name, nospam, novirus, allowpop, allowimap, state FROM vhffs_boxes
    WHERE domain = ? ORDER BY local_part};
    return $dbh->selectall_hashref($sql, 'local_part', undef, $domain);
}

=head2 fetch_forwards

    my $forwards = fetch_forwards($dbh, $domain);

Returns an hashref of hashrefs containing all this domain's forwards, indexed
on local_part.

Internal module use.

=cut

sub fetch_forwards {
    my ($dbh, $domain) = @_;
    my $sql = q{SELECT domain, local_part, remote_name, password
        FROM vhffs_forward WHERE domain = ? ORDER BY local_part};
    return $dbh->selectall_hashref($sql, 'local_part', undef, $domain);
}

sub _new {
    my ($class, $main, $mxdomain_id, $owner_gid, $domain, $boxes_path, $catchall, $oid, $owner_uid, $date_creation, $state, $description, $boxes, $forwards) = @_;
    my $self = $class->SUPER::_new($main, $oid, $owner_uid, $owner_gid, $date_creation, $description, '', $state, Vhffs::Constants::TYPE_MAIL);
    return undef unless defined($self);
    
    $self->{mxdomain_id} = $mxdomain_id;
    $self->{domain} = $domain;
    $self->{boxes_path} = $boxes_path;
    $self->{catchall} = $catchall;
    $self->{boxes} = $boxes;
    $self->{forward} = $forwards;
    
    return $self;
}

sub getall
{
    my ($vhffs,
    $state, $name, $group) = @_;
	
    my $sql;
    my $request;
    my @params;

    my $objs = [];
    my $result;
    my $tmp;

    $sql = 'SELECT m.domain FROM  vhffs_mxdomain m, vhffs_object o WHERE m.object_id = o.object_id';
    if(defined $state) {
        $sql .= ' AND o.state=?';
        push(@params, $state);
    }
    if(defined $name) {
        $sql .= ' AND m.domain LIKE ?';
        push(@params, '%'.$name.'%');
    }
    if(defined $group) {
        $sql .= ' AND o.owner_gid= ?';
        push(@params, $group->get_gid);
    }
    $request = $vhffs->{'db'}->prepare( $sql );
    my $rows = $request->execute(@params);

    return undef if( !$rows );

    while( $result = $request->fetchrow_hashref() )
    {
        push( @$objs, Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $result->{domain} ) );
    }
    return $objs;
}


=pod address_exists

    print ("Adress $local_part\@$domain already exists as a box or forward\n")
        if(Vhffs::Services::Mail::address_exists($vhffs, $local_part, $domain));

Return true if a box or a forward C<$local_part>@C<$domain> already exists.
It does B<not> check for mailing lists (use Vhffs::Services::MailingList::address_exists).

=cut

sub address_exists($$$) {
    my ($vhffs, $local_part, $domain) = @_;

    my $sql = <<EOQ;
SELECT SUM(c) FROM ( 
    (SELECT COUNT(*) AS c FROM vhffs_boxes b WHERE b.domain = ? AND b.local_part = ?)
    UNION 
    (SELECT COUNT(*) AS c FROM vhffs_forward f WHERE f.domain = ? AND f.local_part = ?))
    AS counts
EOQ
    my $dbh = $vhffs->get_db_object();
    my $res = $dbh->selectrow_array($sql, {}, $domain, $local_part, $domain, $local_part);
    return (defined($res) && $res > 0);
}

1;
