[vhffs-dev] [2145] new VHFFS mail management, with a new, clear, almost perfect database schema

[ Thread Index | Date Index | More vhffs.org/vhffs-dev Archives ]


Revision: 2145
Author:   gradator
Date:     2012-04-12 00:01:48 +0200 (Thu, 12 Apr 2012)
Log Message:
-----------
new VHFFS mail management, with a new, clear, almost perfect database schema

Modified Paths:
--------------
    trunk/vhffs-api/examples/create_box.pl
    trunk/vhffs-api/examples/create_forward.pl
    trunk/vhffs-api/examples/create_list.pl
    trunk/vhffs-api/src/Vhffs/Group.pm
    trunk/vhffs-api/src/Vhffs/Panel/Auth.pm
    trunk/vhffs-api/src/Vhffs/Panel/Group.pm
    trunk/vhffs-api/src/Vhffs/Panel/Mail.pm
    trunk/vhffs-api/src/Vhffs/Panel/MailingList.pm
    trunk/vhffs-api/src/Vhffs/Panel/Public.pm
    trunk/vhffs-api/src/Vhffs/Panel/Stats.pm
    trunk/vhffs-api/src/Vhffs/Panel/User.pm
    trunk/vhffs-api/src/Vhffs/Robots/Mail.pm
    trunk/vhffs-api/src/Vhffs/Robots/Mercurial.pm
    trunk/vhffs-api/src/Vhffs/Robots.pm
    trunk/vhffs-api/src/Vhffs/Services/Mail.pm
    trunk/vhffs-api/src/Vhffs/Services/MailGroup.pm
    trunk/vhffs-api/src/Vhffs/Services/MailUser.pm
    trunk/vhffs-api/src/Vhffs/Services/MailingList.pm
    trunk/vhffs-api/src/Vhffs/Services/Newsletter.pm
    trunk/vhffs-api/src/Vhffs/Stats.pm
    trunk/vhffs-api/src/Vhffs/User.pm
    trunk/vhffs-backend/src/mirror/mx1-mirror.pl
    trunk/vhffs-backend/src/mirror/mx1-mirror.sql
    trunk/vhffs-backend/src/pgsql/initdb.sql.in
    trunk/vhffs-compat/from-4.4-to-4.5.sql
    trunk/vhffs-listengine/src/listengine.pl
    trunk/vhffs-panel/templates/group/prefs.tt
    trunk/vhffs-panel/templates/mail/prefs.tt
    trunk/vhffs-panel/templates/mailinglist/create.tt
    trunk/vhffs-panel/templates/user/prefs.tt
    trunk/vhffs-public/templates/content/group-details.tt
    trunk/vhffs-robots/src/mail.pl
    trunk/vhffs-tools/src/vhffs-box-add
    trunk/vhffs-tools/src/vhffs-managemail

Modified: trunk/vhffs-api/examples/create_box.pl
===================================================================
--- trunk/vhffs-api/examples/create_box.pl	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/examples/create_box.pl	2012-04-11 22:01:48 UTC (rev 2145)
@@ -10,12 +10,12 @@
 
 my $vhffs = new Vhffs;
 
-die("Usage: $0 mail_domain local_part password\n") unless(@ARGV == 3);
+die("Usage: $0 mail_domain localpart password\n") unless(@ARGV == 3);
 
 my ($domain, $local, $pass) = @ARGV;
 
 my $mail = Vhffs::Services::Mail::get_by_mxdomain($vhffs, $domain);
 die("Mail domain $domain not found\n") unless(defined $mail);
 
-die("Unable to create box $local\@$domain\n") if( $mail->addbox($local, $pass) < 0 );
+die("Unable to create box $local\@$domain\n") if( $mail->add_box($local, $pass) < 0 );
 print "Box $local\@$domain successfully created\n";

Modified: trunk/vhffs-api/examples/create_forward.pl
===================================================================
--- trunk/vhffs-api/examples/create_forward.pl	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/examples/create_forward.pl	2012-04-11 22:01:48 UTC (rev 2145)
@@ -19,8 +19,7 @@
 die("Mail domain $domain not found\n") unless(defined $mail);
 
 
-if( $mail->addforward($local, $remote) < 0 )
-{
+unless( $mail->add_redirect($local, $remote) ) {
     die "Unable to add forward $local\@". $mail->get_domain ." -> $remote\n";
 }
 

Modified: trunk/vhffs-api/examples/create_list.pl
===================================================================
--- trunk/vhffs-api/examples/create_list.pl	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/examples/create_list.pl	2012-04-11 22:01:48 UTC (rev 2145)
@@ -14,7 +14,7 @@
 
 my $vhffs = new Vhffs;
 
-die("Usage: $0 local_part domain admin_email description owner_username owner_groupname\n") unless(@ARGV == 6);
+die("Usage: $0 localpart domain admin_email description owner_username owner_groupname\n") unless(@ARGV == 6);
 
 my ($local, $domain, $admin, $description, $username, $groupname) = @ARGV;
 

Modified: trunk/vhffs-api/src/Vhffs/Group.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Group.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Group.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -427,10 +427,7 @@
 
 	require Vhffs::Services::MailGroup;
 	my $mg = new Vhffs::Services::MailGroup( $self->get_vhffs, $self );
-	if( defined $mg ) {
-		$mg->delbox;
-		$mg->delforward;
-	}
+	$mg->delete if defined $mg;
 
 	# User references corresponding object with an ON DELETE cascade foreign key
 	# so we don't even need to delete group

Modified: trunk/vhffs-api/src/Vhffs/Panel/Auth.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Panel/Auth.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Panel/Auth.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -162,7 +162,10 @@
 		$user->commit;
 
 		my $mu = new Vhffs::Services::MailUser( $vhffs, $user );
-		$mu->changepassword( $password ) if defined $mu and $mu->exists_box;
+		if( defined $mu and defined $mu->get_localpart ) {
+			$mu->get_localpart->set_password( $password );
+			$mu->get_localpart->commit;
+		}
 
 		# Send a mail with plain text password inside
 		my $subject = sprintf('Password changed on %s', $vhffs->get_config->get_host_name );

Modified: trunk/vhffs-api/src/Vhffs/Panel/Group.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Panel/Group.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Panel/Group.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -500,21 +500,20 @@
 		}
 
 	} elsif( defined( $cgi->param('contact_email_submit') ) ) {
-		unless( $vhffs->get_config->get_service_availability('mailgroup')  &&  $user->can_modify( $group ) ) {
+		unless( $user->can_modify( $group ) ) {
 			$panel->add_error( gettext( 'You\'re not allowed to do this (ACL rights)' ) );
 		} else {
-			my $forward = $cgi->param( 'contact_email' );
-			unless( defined $forward  &&  ( $forward eq ''  ||  Vhffs::Functions::valid_mail( $forward ) )  ) {
-				$panel->add_error( gettext('The email you entered fails syntax check') );
-			} else {
-				my $mg = new Vhffs::Services::MailGroup( $vhffs , $group );
-				if( defined $mg ) {
-					if( $forward eq '' )  {
-						$mg->delforward( $forward );
-						$panel->add_info( gettext('Forward deleted') );
-					} else {
-						$mg->addforward( $forward );
+			my $forward = $cgi->param('contact_email');
+			my $mg = new Vhffs::Services::MailGroup( $vhffs, $group );
+			if( defined $mg ) {
+				unless( defined $forward and $forward !~ /^\s*$/ ) {
+					$mg->delete_redirect;
+					$panel->add_info( gettext('Redirect deleted') );
+				} else {
+					if( $mg->add_redirect( $forward ) ) {
 						$panel->add_info( gettext('Forward added') );
+					} else {
+						$panel->add_error( gettext('The email you entered fails syntax check') );
 					}
 				}
 			}

Modified: trunk/vhffs-api/src/Vhffs/Panel/Mail.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Panel/Mail.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Panel/Mail.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -44,7 +44,7 @@
 
 	my @params;
 	my $sql = 'SELECT m.domain as label, g.groupname as owner_group, o.state, u.username as owner_user '.
-	  'FROM vhffs_mxdomain m '.
+	  'FROM vhffs_mx m '.
 	  'INNER JOIN vhffs_object o ON (o.object_id = m.object_id) '.
 	  'INNER JOIN vhffs_groups g ON (g.gid = o.owner_gid) '.
 	  'INNER JOIN vhffs_users u ON (u.uid = o.owner_uid) ';
@@ -74,7 +74,7 @@
 	my ( $vhffs, $gid ) = @_;
 
 	my $dbh = $vhffs->get_db;
-	my $sql = 'SELECT m.object_id AS oid, m.domain AS displayname, o.state FROM vhffs_mxdomain m INNER JOIN vhffs_object o ON m.object_id = o.object_id WHERE o.owner_gid = ? ORDER BY m.domain';
+	my $sql = 'SELECT m.object_id AS oid, m.domain AS displayname, o.state FROM vhffs_mx m INNER JOIN vhffs_object o ON m.object_id = o.object_id WHERE o.owner_gid = ? ORDER BY m.domain';
 	my $sth = $dbh->prepare($sql) or return -1;
 	$sth->execute($gid) or return -2;
 	my $mails = [];
@@ -87,40 +87,6 @@
 	return $mails;
 }
 
-=pod
-
-=head2 is_owned_by_group
-
-	print "Group #$gid owns mail domain $domain\n" if(Vhffs::Panel::Mail::is_owned_by_group( $vhffs, $domain, $gid );
-
-Returns true if a given mail domain is owned by a given group (GID), false otherwise.
-C<$domain> is a string containing the mail domain name.
-
-=cut
-
-sub is_owned_by_group($$$) {
-	my ($vhffs, $domain, $gid) = @_;
-
-	my $dbh = $vhffs->get_db;
-	my $sql = 'SELECT COUNT(*) FROM vhffs_mxdomain m INNER JOIN vhffs_object o ON o.object_id = m.object_id WHERE m.domain = ? AND o.owner_gid = ?';
-	my $sth = $dbh->prepare( $sql );
-	$sth->execute( $domain, $gid );
-	my ($count) = $sth->fetchrow_array();
-	return $count > 0;
-}
-
-sub create_mail {
-	my( $vhffs , $domain , $description, $user , $group ) = @_;
-	return undef unless defined $user;
-	return undef unless defined $group;
-
-	my $mail = Vhffs::Services::Mail::create($vhffs, $domain, $description, $user, $group);
-	return undef unless defined $mail;
-
-
-	return $mail;
-}
-
 sub create {
 	my $panel = shift;
 
@@ -152,7 +118,7 @@
 		}
 
 		unless( $panel->has_errors() ) {
-			my $mail = Vhffs::Panel::Mail::create_mail( $vhffs , $domain , $description, $user , $group );
+			my $mail = Vhffs::Services::Mail::create( $vhffs, $domain, $description, $user, $group );
 			if( defined $mail ) {
 				my $url = '?do=groupview;group='.$group->get_groupname.';msg='.gettext('Mail domain successfully created !');
 				$panel->redirect($url);
@@ -179,19 +145,18 @@
 	my $session = $panel->{'session'};
 	my $user = $panel->{'user'};
 
-	my $mail_config = $vhffs->get_config->get_service('mail');
-
 	my $domain = $cgi->param('name');
 	unless( defined $domain ) {
 		$panel->render('misc/message.tt', { message => gettext( 'CGI Error !' ) } );
 		return;
 	}
 
-	my $mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs , $domain );
+	my $mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $domain );
 	unless( defined $mail ) {
 		$panel->render('misc/message.tt', { message => sprintf( gettext('Unable to get information on mail domain %s'), $domain ) } );
 		return;
 	}
+	$mail->fetch_localparts;
 	$panel->set_group( $mail->get_group );
 
 	unless( $user->can_view($mail) ) {
@@ -199,13 +164,14 @@
 		return;
 	}
 
-	my $catchall_type = lc($mail_config->{allowed_catchall});
-	$catchall_type = 'domain' unless(defined $catchall_type and $catchall_type ne '');
+	my $mail_config = $mail->get_config;
 
-	if( defined $cgi->param('modify_catchall_submit') ) {
-		update_catchall($panel, $mail, $catchall_type);
-	} elsif( defined $cgi->param('update_box_submit') ) {
-		update_box($panel, $mail);
+	if( defined $cgi->param('add_catchall_submit') ) {
+		add_catchall($panel, $mail);
+	} elsif( defined $cgi->param('delete_catchall_submit') ) {
+		delete_catchall($panel, $mail);
+	} elsif( defined $cgi->param('update_localpart_submit') ) {
+		update_localpart($panel, $mail);
 	} elsif(defined $cgi->param('delete_box_submit')) {
 		delete_box($panel, $mail);
 	} elsif(defined $cgi->param('add_box_submit')) {
@@ -219,25 +185,26 @@
 	}
 
 	my $vars = {
-	  mail => $mail,
-	  catchall_type => $catchall_type,
-	  novirus => $mail_config->{'use_novirus'},
-	  nospam => $mail_config->{'use_novirus'}
-	  };
+		mail => $mail,
+		catchall_state => {
+			none => Vhffs::Services::Mail::CATCHALL_ALLOW_NONE,
+			domain => Vhffs::Services::Mail::CATCHALL_ALLOW_DOMAIN,
+			open => Vhffs::Services::Mail::CATCHALL_ALLOW_OPEN,
+		},
+		novirus => $mail_config->{use_novirus},
+		nospam => $mail_config->{use_nospam}
+	};
 
-	my @sorted_boxes = sort { $a->{local_part} cmp $b->{local_part} } (values %{$mail->get_boxes});
-	$vars->{sorted_boxes} = \@sorted_boxes;
-	my @sorted_forwards = sort { $a->{local_part} cmp $b->{local_part} } (values %{$mail->get_forwards});
-	$vars->{sorted_forwards} = \@sorted_forwards;
+	my @sorted_localparts = sort { $a->{localpart} cmp $b->{localpart} } (values %{$mail->get_localparts});
+	$vars->{sorted_localparts} = \@sorted_localparts;
 
 	$panel->set_title( gettext("Mail Administration for domain ") );
 	$panel->render('mail/prefs.tt', $vars);
 }
 
-sub update_catchall {
+sub add_catchall {
 	my $panel = shift;
 	my $mail = shift;
-	my $type = shift;
 	my $vhffs = $panel->{'vhffs'};
 	my $cgi = $panel->{'cgi'};
 	my $user = $panel->{'user'};
@@ -248,37 +215,57 @@
 		return;
 	}
 
-	if($type eq 'none') {
-		$panel->add_error( gettext('Catchall isn\'t enabled on this platform') );
-		return;
-	}
-
 	my $catchall = $cgi->param('catchall');
 	unless( defined $catchall ) {
 		$panel->add_error( gettext('CGI Error !') );
 		return;
 	}
 
-	if($type eq 'domain' and $catchall ne '') {
-		unless( $mail->exists_box($catchall) and $mail->get_box_status($catchall) == Vhffs::Constants::ACTIVATED) {
-			$panel->add_error( gettext('Selected mailbox doesn\'t exist for this mail domain or is not yet active.') );
-			return;
-		}
-		my $domain = $mail->get_domain;
-		$catchall .= "\@$domain";
+	my ( $localpart, $domain ) = split /\@/, $catchall;
+
+	# Try to fetch the catchall using light mx objects
+	my $othermail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $domain );
+	my $lp = $othermail->fetch_localpart( $localpart ) if defined $othermail;
+	unless( defined $othermail and defined $lp and defined $lp->get_box ) {
+		$panel->add_error(gettext( 'An error occured while fetching the catchall box' ) );
+		return;
 	}
 
-	if($mail->set_catchall($catchall) < 0) {
-		$panel->add_error( sprintf( gettext('%s is not a correct mail address'), $catchall) );
+	my $box = $lp->get_box;
+	unless( $mail->add_catchall( $box ) ) {
+		$panel->add_error(gettext( 'An error occured while adding the catchall box' ) );
 		return;
 	}
 
-	if($mail->commit() < 0) {
-		$panel->add_error( gettext('An error occured while updating the mail domain') );
+	$panel->add_info( gettext('Catchall box successfully added') );
+}
+
+sub delete_catchall {
+	my $panel = shift;
+	my $mail = shift;
+	my $vhffs = $panel->{'vhffs'};
+	my $cgi = $panel->{'cgi'};
+	my $user = $panel->{'user'};
+
+	# User wants to delete catchall address.
+	unless( $user->can_modify($mail) ) {
+		$panel->add_error( gettext('You are not allowed to modify this object') );
 		return;
 	}
 
-	$panel->add_info(gettext('Catchall address successfully changed'));
+	my $boxname = $cgi->param('boxname');
+	unless( defined $boxname ) {
+		$panel->add_error( gettext('CGI Error !') );
+		return;
+	}
+
+	my $catchall = $mail->get_catchall( $boxname );
+	unless( defined $catchall and $catchall->delete ) {
+		$panel->add_error( sprintf(gettext('Unable to delete catchall %s'), $boxname) );
+		return;
+	}
+
+	$panel->add_info( sprintf(gettext('Catchall %s deleted'), $boxname) );
 }
 
 sub add_box {
@@ -294,13 +281,13 @@
 	}
 
 	my $box = $cgi->param('localpart');
-	my $passwd = $cgi->param('box_password');
+	my $passwd = $cgi->param('localpart_password');
 	unless( defined $box and defined $passwd) {
 		$panel->add_error( gettext('CGI Error !') );
 		return;
 	}
 
-	if($mail->addbox( $box, $passwd ) < 0 ) {
+	unless( $mail->add_box( $box, $passwd ) ) {
 		$panel->add_error(gettext( "This box already exists for this domain or parameters are not valid. Check your domain." ) );
 		return;
 	}
@@ -308,56 +295,54 @@
 	$panel->add_info( gettext('Box successfully added') );
 }
 
-sub update_box {
+sub update_localpart {
 	my $panel = shift;
 	my $mail = shift;
 	my $vhffs = $panel->{'vhffs'};
 	my $cgi = $panel->{'cgi'};
 	my $user = $panel->{'user'};
 
-	# User wants to update information about a box
-	my $box = $cgi->param('localpart');
-
-	unless( $user->can_modify($mail) and $mail->get_box_status( $box ) == Vhffs::Constants::ACTIVATED ) {
+	unless( $user->can_modify($mail) ) {
 		$panel->add_error( gettext('You are not allowed to modify this object') );
 		return;
 	}
 
-	my $passwd = $cgi->param('box_password');
+	# User wants to update information about a localpart
+	my $lp = $mail->get_localpart( $cgi->param('localpart') );
+	my $passwd = $cgi->param('localpart_password');
 	my $use_antispam = $cgi->param('use_antispam');
 	my $use_antivirus = $cgi->param('use_antivirus');
-	my $mail_config = $vhffs->get_config->get_service('mail');
-	unless( defined $box and defined $passwd
+	my $mail_config = $mail->get_config;
+	unless( defined $lp and defined $passwd
 	  and (not $mail_config->{'use_novirus'} or defined $use_antivirus)
 	  and (not $mail_config->{'use_nospam'} or defined $use_antispam) ) {
 		$panel->add_error( gettext('CGI Error !') );
 		return;
 	}
 
+	my @infos;
+
 	if($passwd !~ /^\s*$/) {
-		if($mail->change_box_password( $box, $passwd ) < 0) {
-			$panel->add_error( gettext('Could not change box password') );
-		} else {
-			$panel->add_info( gettext('Box password updated') );
-		}
+		$lp->set_password( $passwd );
+		push @infos, gettext('Box password updated');
 	}
 
 	if(defined $use_antispam) {
-		# We've to explicitely pass 1 or 0 to avoid passing '' to underlying SQL query
-		if( $mail->set_spam_status($box, ($use_antispam eq 'yes' ? 1 : 0 ) ) < 0) {
-			$panel->add_error( sprintf( gettext('Could not update spam status for box %s'), $box ) );
-		} else {
-			$panel->add_info( gettext('Spam status updated') );
-		}
+		$lp->set_nospam( ($use_antispam eq 'yes') );
+		push @infos, gettext('Spam status updated');
 	}
 
 	if(defined $use_antivirus) {
-		if( $mail->set_virus_status($box, ($use_antivirus eq 'yes' ? 1 : 0) ) < 0) {
-			$panel->add_error( sprintf( gettext('Could not update antivirus status for box %s'), $box ) );
-		} else {
-			$panel->add_info( gettext('Virus status updated') );
-		}
+		$lp->set_novirus( ($use_antivirus eq 'yes') );
+		push @infos, gettext('Virus status updated');
 	}
+
+	unless( $lp->commit ) {
+		$panel->add_error( sprintf( gettext('An error occured while updating localpart %s'), $lp->get_localpart ) );
+		return;
+	}
+
+	$panel->add_info( $_ ) foreach( @infos );
 }
 
 sub delete_box {
@@ -368,23 +353,25 @@
 	my $user = $panel->{'user'};
 
 	# User wants to delete a box
-	my $box = $cgi->param('localpart');
-	unless( $user->can_modify($mail) and $mail->get_box_status( $box ) == Vhffs::Constants::ACTIVATED ) {
-		$panel->add_error( gettext('You are not allowed to modify this object') );
+	my $local = $cgi->param('localpart');
+	unless( defined $local ) {
+		$panel->add_error( gettext('CGI Error !') );
 		return;
 	}
 
-	unless( defined $box ) {
-		$panel->add_error( gettext('CGI Error !') );
+	my $box = $mail->get_box( $local );
+	unless( $user->can_modify($mail) and defined $box and $box->get_status == Vhffs::Constants::ACTIVATED ) {
+		$panel->add_error( gettext('You are not allowed to modify this object') );
 		return;
 	}
 
-	if($mail->set_box_status($box, Vhffs::Constants::WAITING_FOR_DELETION) < 0) {
-		$panel->add_error( sprintf(gettext('Unable to delete box %s'), $box) );
+	$box->set_status( Vhffs::Constants::WAITING_FOR_DELETION );
+	unless( $box->commit ) {
+		$panel->add_error( sprintf(gettext('Unable to delete box %s'), $local) );
 		return;
 	}
 
-	$panel->add_info( sprintf(gettext('Box %s deleted'), $box) );
+	$panel->add_info( sprintf(gettext('Box %s deleted'), $local) );
 }
 
 sub add_forward {
@@ -406,7 +393,7 @@
 		return;
 	}
 
-	if($mail->addforward( $local, $remote ) < 0) {
+	unless( $mail->add_redirect( $local, $remote ) ) {
 		$panel->add_error( sprintf(gettext('Unable to add forward %s'), $local) );
 		return;
 	}
@@ -427,13 +414,15 @@
 	}
 
 	my $local = $cgi->param('localpart');
-	my $remote = $cgi->param('forward');
-	unless( defined $local and defined $remote ) {
+	my $remote = $cgi->param('remote');
+	my $newremote = $cgi->param('newremote');
+	my $redirect = $mail->get_redirect( $local, $remote );
+	unless( defined $redirect and defined $newremote ) {
 		$panel->add_error( gettext('CGI Error !') );
 		return;
 	}
 
-	if($mail->change_forward( $local, $remote ) < 0) {
+	unless( $redirect->set_redirect( $newremote ) and $redirect->commit ) {
 		$panel->add_error( sprintf(gettext('Unable to modify forward %s' ), $local ) );
 		return;
 	}
@@ -454,17 +443,19 @@
 	}
 
 	my $local = $cgi->param('localpart');
-	unless( defined $local ) {
+	my $remote = $cgi->param('remote');
+	unless( defined $local and defined $remote ) {
 		$panel->add_error( gettext('CGI Error !') );
 		return;
 	}
 
-	if($mail->delforward($local) < 0) {
-		$panel->add_error( sprintf(gettext('Unable to delete forward %s'), $local) );
+	my $redirect = $mail->get_redirect( $local, $remote );
+	unless( defined $redirect and $redirect->delete ) {
+		$panel->add_error( sprintf(gettext('Unable to delete forward %s to %s'), $local, $remote) );
 		return;
 	}
 
-	$panel->add_info( sprintf(gettext('Forward %s deleted'), $local) );
+	$panel->add_info( sprintf(gettext('Forward %s to %s deleted'), $local, $remote) );
 }
 
 sub index {

Modified: trunk/vhffs-api/src/Vhffs/Panel/MailingList.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Panel/MailingList.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Panel/MailingList.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -45,18 +45,20 @@
 	my ($vhffs, $name) = @_;
 
 	my @params;
-	my $sql = 'SELECT m.local_part || \'@\' || m.domain as label, g.groupname as owner_group, o.state, u.username as owner_user '.
-	  'FROM vhffs_ml m '.
-	  'INNER JOIN vhffs_object o ON (o.object_id = m.object_id) '.
-	  'INNER JOIN vhffs_groups g ON (g.gid = o.owner_gid) '.
-	  'INNER JOIN vhffs_users u ON (u.uid = o.owner_uid) ';
+	my $sql = 'SELECT mlp.localpart || \'@\' || mx.domain as label, g.groupname as owner_group, o.state, u.username as owner_user '.
+	  'FROM vhffs_mx_ml ml '.
+	  'INNER JOIN vhffs_object o ON o.object_id = ml.object_id '.
+	  'INNER JOIN vhffs_groups g ON g.gid = o.owner_gid '.
+	  'INNER JOIN vhffs_users u ON u.uid = o.owner_uid '.
+	  'INNER JOIN vhffs_mx_localpart mlp ON mlp.localpart_id=ml.localpart_id '.
+	  'INNER JOIN vhffs_mx mx ON mx.mx_id=mlp.mx_id ';
 
 	if( defined $name ) {
-		$sql .= 'WHERE m.local_part LIKE ? OR m.domain LIKE ? ';
+		$sql .= 'WHERE mlp.localpart LIKE ? OR mx.domain LIKE ? ';
 		push(@params, '%'.lc($name).'%' , '%'.lc($name).'%' );
 	}
 
-	$sql .= 'ORDER BY m.domain, m.local_part';
+	$sql .= 'ORDER BY mx.domain, mlp.localpart';
 
 	my $dbh = $vhffs->get_db();
 	return $dbh->selectall_arrayref($sql, { Slice => {} }, @params);
@@ -76,7 +78,8 @@
 	my ( $vhffs, $gid ) = @_;
 
 	my $dbh = $vhffs->get_db;
-	my $sql = 'SELECT l.object_id AS oid, l.local_part || \'@\' || l.domain AS displayname, o.state FROM vhffs_ml l INNER JOIN vhffs_object o ON l.object_id = o.object_id WHERE o.owner_gid = ? ORDER BY l.local_part, l.domain';
+	my $sql = 'SELECT ml.object_id AS oid, mlp.localpart || \'@\' || mx.domain AS displayname, o.state FROM vhffs_mx_ml ml INNER JOIN vhffs_object o ON ml.object_id = o.object_id '.
+		'INNER JOIN vhffs_mx_localpart mlp ON mlp.localpart_id=ml.localpart_id INNER JOIN vhffs_mx mx ON mx.mx_id=mlp.mx_id WHERE o.owner_gid = ? ORDER BY mlp.localpart, mx.domain';
 	my $sth = $dbh->prepare($sql) or return -1;
 	$sth->execute($gid) or return -2;
 	my $mls = [];
@@ -93,7 +96,8 @@
 	my ($vhffs, $gid) = @_;
 
 	my $dbh = $vhffs->get_db;
-	my $sql = 'SELECT l.local_part || \'@\' || l.domain AS listname, l.local_part, l.domain, l.open_archive, o.description FROM vhffs_ml l INNER JOIN vhffs_object o ON l.object_id = o.object_id WHERE o.owner_gid = ? AND o.state = ?';
+	my $sql = 'SELECT mlp.localpart || \'@\' || mx.domain AS listname, mlp.localpart, mx.domain, ml.open_archive, o.description FROM vhffs_mx_ml ml INNER JOIN vhffs_object o ON ml.object_id = o.object_id '.
+		'INNER JOIN vhffs_mx_localpart mlp ON mlp.localpart_id=ml.localpart_id INNER JOIN vhffs_mx mx ON mx.mx_id=mlp.mx_id WHERE o.owner_gid = ? AND o.state = ?';
 	return $dbh->selectall_arrayref($sql, { Slice => {} }, $gid, Vhffs::Constants::ACTIVATED);
 }
 
@@ -112,22 +116,10 @@
 	my ($vhffs, $gid) = @_;
 
 	my $dbh = $vhffs->get_db;
-	my $sql = q{ SELECT m.domain FROM vhffs_mxdomain m INNER JOIN vhffs_object o ON o.object_id = m.object_id WHERE o.owner_gid = ? AND o.state = ? ORDER BY m.domain };
+	my $sql = q{ SELECT m.domain FROM vhffs_mx m INNER JOIN vhffs_object o ON o.object_id = m.object_id WHERE o.owner_gid = ? AND o.state = ? ORDER BY m.domain };
 	return ($dbh->selectall_arrayref($sql, { Slice => {} }, $gid, Vhffs::Constants::ACTIVATED) );
 }
 
-sub create_list {
-	my ($vhffs, $lpart, $domain, $description, $user, $group) = @_;
-	return undef unless defined $user;
-	return undef unless defined $group;
-
-	my $list = Vhffs::Services::MailingList::create( $vhffs , $lpart , $domain, $description, $user, $group );
-	return undef unless defined $list;
-
-
-	return $list;
-}
-
 sub create {
 	my $panel = shift;
 
@@ -151,26 +143,25 @@
 	}
 
 	my $submitted = $cgi->param('mailing_submit');
-	my $localpart = '';
-	my $domain = '';
-	my $description = '';
 	my $vars = {};
 
 	if( $submitted ) {
-		$localpart = $cgi->param( 'localpart' );
-		$domain = $cgi->param( 'domain' );
-		$description = Encode::decode_utf8( $cgi->param( 'description' ) );
+		my $localpart = $cgi->param( 'localpart' );
+		my $domain = $cgi->param( 'domain' );
+		my $description = Encode::decode_utf8( $cgi->param( 'description' ) );
+		my $mail;
 
 		unless( defined $localpart and defined $domain and defined $description ) {
 			$panel->add_error( gettext('CGI Error !') );
 		} else {
 			$panel->add_error( gettext('You must enter a description') ) unless $description !~ /^\s*$/;
 			$panel->add_error( gettext('Invalid local part') ) unless $localpart =~ /^[a-z0-9\_\-]+$/;
-			$panel->add_error( gettext('You do not own this domain !') ) unless( ($domain eq $default_domain) or Vhffs::Panel::Mail::is_owned_by_group( $vhffs, $domain, $group->get_gid ) );
+			$mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $domain );
+			$panel->add_error( gettext('You do not own this domain !') ) unless( ($domain eq $default_domain) or $mail->get_group->get_gid == $group->get_gid );
 		}
 
 		unless( $panel->has_errors() ) {
-			my $mailinglist =  Vhffs::Panel::MailingList::create_list( $vhffs, $localpart, $domain, $description, $user, $group );
+			my $mailinglist =  Vhffs::Services::MailingList::create( $vhffs, $mail, $localpart, $description, $user, $group ); 
 			if( defined $mailinglist ) {
 				my $url = '?do=groupview;group='.$group->get_groupname.';msg='.gettext('The mailing list object was successfully created !');
 				$panel->redirect( $url );
@@ -180,7 +171,7 @@
 			$panel->add_error( gettext('An error occured while creating the object.It probably already exists') );
 		}
 
-		$vars->{local_part} = $localpart;
+		$vars->{localpart} = $localpart;
 		$vars->{domain} = $domain;
 		$vars->{description} = $description;
 	}
@@ -228,6 +219,8 @@
 		return;
 	}
 
+	$list->fetch_subs;
+
 	if(defined $cgi->param('options_submit')) {
 		update_ml_options($panel, $list);
 	} elsif(defined $cgi->param('delete_submit')) {

Modified: trunk/vhffs-api/src/Vhffs/Panel/Public.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Panel/Public.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Panel/Public.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -465,7 +465,7 @@
 	$output .= '  <awaitingmoderation>'.$stats->get_mail_in_moderation.'</awaitingmoderation>'."\n";
 	$output .= '  <activated>'.$stats->get_mail_activated.'</activated>'."\n";
 	$output .= '  <boxes>'.$stats->get_mail_total_boxes.'</boxes>'."\n";
-	$output .= '  <forwards>'.$stats->get_mail_total_forwards.'</forwards>'."\n";
+	$output .= '  <forwards>'.$stats->get_mail_total_redirects.'</forwards>'."\n";
 	$output .= '</service>'."\n";
 
 	$output .= '<service name="mailinglist">'."\n";

Modified: trunk/vhffs-api/src/Vhffs/Panel/Stats.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Panel/Stats.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Panel/Stats.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -90,7 +90,7 @@
 		waiting_mail_domains_count => $stats->get_mail_in_moderation,
 		activated_mail_domains_count => $stats->get_mail_activated,
 		mail_boxes_count => $stats->get_mail_total_boxes,
-		mail_forwards_count => $stats->get_mail_total_forwards,
+		mail_forwards_count => $stats->get_mail_total_redirects,
 
 		waiting_mysql_count => $stats->get_mysql_in_moderation,
 		activated_mysql_count => $stats->get_mysql_activated,

Modified: trunk/vhffs-api/src/Vhffs/Panel/User.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Panel/User.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Panel/User.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -393,7 +393,7 @@
 			}
 
 			# Commit all the changes for the current user
-			unless( defined $firstname && defined $lastname && defined $city && defined $mail && defined $zipcode && defined $country && defined $address && defined $shell )  {
+			unless( defined $firstname and defined $lastname and defined $city and defined $mail and defined $zipcode and defined $country and defined $address and defined $shell )  {
 				$panel->add_error( gettext( 'CGI Error !' ) );
 			}
 			else {
@@ -433,9 +433,6 @@
 						$pwd_change = 1;
 						$userp->set_password( $pass1 );
 						$panel->add_info( gettext('Password changed') );
-
-						my $mu = new Vhffs::Services::MailUser( $vhffs , $userp );
-						$mu->changepassword( $pass1 ) if defined $mu and $mu->exists_box;
 					}
 
 					my $prevmail = $userp->get_mail();
@@ -462,47 +459,55 @@
 					if( defined $mu )  {
 
 						my $mail_activate = $cgi->param( 'mail_activate' );
+						$mail_activate = ( defined $mail_activate and $mail_activate eq 'on' );
 						my $nospam = $cgi->param( 'mail_nospam' );
-						my $novirus = $cgi->param( 'mail_novirus' );
 						$nospam = ( defined $nospam and $nospam eq 'on' );
+						my $novirus = $cgi->param( 'mail_novirus' );
 						$novirus = ( defined $novirus and $novirus eq 'on' );
 
-						if( defined $mail_activate and $mail_activate eq 'on' ) {
+						if( $mail_activate ) {
 							my $usage = $cgi->param( 'mail_usage' );
 							unless( defined $usage ) {
 								$panel->add_error( gettext('You must choose a method for your mail') );
 							}
 							elsif( $usage == 1 ) {
-								#Delete forward if necessary
-								#In this case, we treat for popable accounts
-								if( $mu->exists_box == 0 ) {
+								#here, we create the box
+								my $box = $mu->get_box;
+								unless( $box ) {
 									# Box doesn't exists, need a password
-									if( $pwd_change == 0 ) {
+									unless( $pwd_change ) {
 										$panel->add_error( gettext('Error ! You MUST provide a password in your account when you create your popable account') );
 									} else  {
-										# Del forward if needed
-										$mu->delforward;
-										if( $mu->addbox($pass1) < 0 ) {
+										unless( $box = $mu->add_box($pass1) ) {
 											$panel->add_error( gettext('An error occured while adding the box') );
-										} elsif( $nospam and $mu->change_spam_status < 0 ) {
-											$panel->add_error( gettext('An error occured while adding the box (anti-spam adding)') );
-										} elsif( $novirus and $mu->change_virus_status < 0 ) {
-											$panel->add_error( gettext('An error occured while adding the box (anti-virus adding)') );
 										} else {
-											$panel->add_info( gettext('Mailbox successfully added') );
+											$box->get_localpart->set_nospam( $nospam );
+											$box->get_localpart->set_novirus( $novirus );
+											unless( $box->get_localpart->commit ) {
+												$panel->add_error( gettext('An error occured while adding the box (anti-spam or anti-virus adding)') );
+											} else {
+												$panel->add_info( gettext('Mailbox successfully added') );
+											}
 										}
 									}
 								} else {
 									#Box already exists
 									# The user changed his password, we must update password for mail
 									if( $pwd_change ) {
-										$mu->changepassword( $pass1);
+										my $lp = $mu->get_box->get_localpart;
+										$lp->set_password( $pass1 );
+										unless( $lp->commit ) {
+											$panel->add_error( gettext('An error occured while changing the box password') );
+										} else {
+											$panel->add_info( gettext('Mailbox password changed') );
+										}
 									}
 
 									# We change the spam status. if the spam status changed
-									if( $vhffs->get_config->get_service('mail')->{'use_nospam'} ) {
-										if( $nospam != $mu->use_nospam ) {
-											if( $mu->change_spam_status == 1 ) {
+									if( $mu->use_nospam ) {
+										if( $nospam != $box->get_localpart->get_nospam ) {
+											$box->get_localpart->toggle_nospam;
+											if( $box->get_localpart->commit ) {
 												$panel->add_info( gettext( 'Changed spam protection status for your account' ) );
 											} else {
 												$panel->add_error( gettext( 'Error for spam protection' ) );
@@ -511,9 +516,10 @@
 									}
 
 									# As spam, the virus status changes only if the user changed values
-									if( $vhffs->get_config->get_service('mail')->{'use_novirus'} ) {
-										if( $novirus != $mu->use_novirus ) {
-											if( $mu->change_virus_status == 1 ) {
+									if( $mu->use_novirus ) {
+										if( $novirus != $box->get_localpart->get_novirus ) {
+											$box->get_localpart->toggle_novirus;
+											if( $box->get_localpart->commit ) {
 												$panel->add_info( gettext( 'Changed anti-virus status for your account' ) );
 											} else {
 												$panel->add_error( gettext( 'Error for virus protection' ) );
@@ -523,35 +529,28 @@
 								}
 							}
 							elsif( $usage == 2 ) {
+								my $redirect = $mu->get_redirect;
 								#Here, we create the forward
-								my $ad = $userp->get_mail;
-								unless( $mu->exists_forward ) {
-									unless( defined $ad ) {
-										$panel->add_error( gettext('There is a problem with the address you filled in your profile, unable to add forwarding') );
+								unless( $redirect ) {
+									unless( $redirect = $mu->add_redirect( $userp->get_mail ) ) {
+										$panel->add_error(  gettext('There is a problem with the address you filled in your profile, unable to add forwarding') );
 									} else {
-										# Delete the box if necessary
-										$mu->delbox;
-										if( $mu->addforward( $userp->get_mail ) < 0) {
-											$panel->add_error(  gettext('An error occured while adding the forwarding') );
-										} else {
-											$panel->add_info( gettext('Forward added') );
-										}
+										$panel->add_info( gettext('Forward added') );
 									}
 								}
+								#here, we update the forward
 								elsif( $mail_change ) {
-									$mu->delforward;
-									if( $mu->addforward( $mail ) < 0 ) {
-										$panel->add_error( gettext('An error occured while the forwarding') );
+									if( $redirect->set_redirect( $mail ) and $redirect->commit ) {
+										$panel->add_info( gettext('Redirect updated') );
 									} else {
-										$panel->add_info( gettext('Forward updated') );
+										$panel->add_error( gettext('An error occured while updating the redirect') );
 									}
 								}
 							}
-						} elsif($mu->exists_box || $mu->exists_forward) {
+						} elsif( $mu->get_localpart ) {
 							$panel->add_info( gettext('Mail deleted') );
 							# User doesn't want mail anymore
-							$mu->delbox;
-							$mu->delforward;
+							$mu->delete;
 						}
 					}
 
@@ -627,19 +626,7 @@
 	my $newsletter = new Vhffs::Services::Newsletter( $vhffs , $userp );
 	$vars->{newsletter} = { active => 1, subscribed => $newsletter->exists } if defined $newsletter and $newsletter->get_collectmode != Vhffs::Services::Newsletter::PERMANENT;
 
-	my $mu = new Vhffs::Services::MailUser( $vhffs , $userp );
-	if(defined $mu) {
-		my $mu_config = $vhffs->get_config->get_service( 'mailuser' );
-		my $mail_config = $vhffs->get_config->get_service('mail');
-		$vars->{mail_user} = {
-		  service => $mu,
-		  domain => $mu_config->{domain},
-		  help_url => $mu_config->{url_doc},
-		  nospam => $mail_config->{use_nospam},
-		  novirus => $mail_config->{use_novirus}
-		};
-	}
-
+	$vars->{mail_user} = new Vhffs::Services::MailUser( $vhffs, $userp );
 	$vars->{use_avatars} = $panel->use_users_avatars;
 
 	$panel->render('user/prefs.tt', $vars);

Modified: trunk/vhffs-api/src/Vhffs/Robots/Mail.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Robots/Mail.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Robots/Mail.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -43,18 +43,26 @@
 
 sub getall_boxes {
 	my ( $vhffs, $state ) = @_;
-
 	my @params;
 
-	my $sql = 'SELECT m.domain, m.boxes_path, b.local_part, b.mbox_name, b.state FROM vhffs_boxes b INNER JOIN vhffs_mxdomain m ON m.domain=b.domain';
+	my $sql = 'SELECT mx.domain, lp.localpart FROM vhffs_mx_box mb INNER JOIN vhffs_mx_localpart lp ON lp.localpart_id=mb.localpart_id INNER JOIN vhffs_mx mx ON mx.mx_id=lp.mx_id';
 	if( defined $state)   {
-		$sql .= ' WHERE b.state=?';
+		$sql .= ' WHERE mb.state=?';
 		push(@params, $state);
 	}
 
+	my $boxes = [];
 	my $request = $vhffs->get_db->prepare( $sql );
-	return undef unless $request->execute( @params );
-	return $request->fetchall_arrayref({});
+	return unless $request->execute( @params );
+	while( my $s = $request->fetchrow_hashref() ) {
+		my $mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $s->{domain} );
+		next unless defined $mail;
+		my $lp = $mail->fetch_localpart( $s->{localpart} );
+		next unless defined $lp and defined $lp->get_box;
+		push( @$boxes, $lp->get_box );
+	}
+
+	return $boxes;
 }
 
 sub create {
@@ -75,30 +83,24 @@
 	return undef unless defined $mail and $mail->get_status == Vhffs::Constants::WAITING_FOR_DELETION;
 
 	my $vhffs = $mail->get_vhffs;
-	my $dir = $mail->get_dir();
+	my $dir = $mail->get_dir;
 
-	require Vhffs::Services::MailingList;
-	my $lists = Vhffs::Services::MailingList::getall( $vhffs, undef, undef, undef, $mail->get_domain );
-	if( @$lists ) {
-		foreach( @{$lists} ) {
-			next if $_->get_status == Vhffs::Constants::WAITING_FOR_DELETION;
-			$_->set_status( Vhffs::Constants::WAITING_FOR_DELETION );
-			$_->commit;
+	my $localparts = $mail->fetch_localparts;
+	foreach my $localpart ( values %{$localparts} ) {
+		my $box = $localpart->get_box;
+		if( defined $box ) {
+			$box->set_status( Vhffs::Constants::WAITING_FOR_DELETION );  # We don't have to commit
+			return 1 unless delete_mailbox( $box );
 		}
-		Vhffs::Robots::vhffs_log( $vhffs, 'Cannot delete mail domain '.$mail->get_domain.', remaining mailing lists, requests have been sent to delete them' );
-		# Do not call next right now, check for boxes to avoir further failure
-	}
-
-	if( $mail->nb_boxes ) {
-		foreach ( values( %{$mail->get_boxes} ) ) {
-			$mail->set_box_status( $_->{'local_part'}, Vhffs::Constants::WAITING_FOR_DELETION );
+		my $ml = $localpart->get_ml;
+		if( defined $ml ) {
+			require Vhffs::Robots::MailingList;
+			my $fml = $ml->get_ml_object;
+			$fml->set_status( Vhffs::Constants::WAITING_FOR_DELETION );  # We don't have to commit
+			return 1 unless Vhffs::Robots::MailingList::delete( $fml );
 		}
-		Vhffs::Robots::vhffs_log( $vhffs, 'Cannot delete mail domain '.$mail->get_domain.', '.$mail->nb_boxes.' boxes remaining, requests have been sent to delete them' );
-		return 1;
 	}
 
-	return 1 if @$lists;
-
 	File::Path::remove_tree( $dir, { error => \my $errors });
 	# Mail domain directories are hashed on two levels, so we've potentially two empty directories to delete
 	my $parent = File::Basename::dirname($dir);
@@ -134,23 +136,23 @@
 }
 
 sub create_mailbox {
-	my $mail = shift;
-	my $localpart = shift;
-	return undef unless defined $mail and defined $localpart and $mail->get_box_status($localpart) == Vhffs::Constants::WAITING_FOR_CREATION;
+	my $box = shift;
+	return undef unless defined $box and $box->get_status == Vhffs::Constants::WAITING_FOR_CREATION;
 
+	my $mail = $box->get_mail;
 	my $vhffs = $mail->get_vhffs;
-	my $mailconf = $vhffs->get_config->get_service('mail');
+	my $mailconf = $mail->get_config;
+	my $dir = $box->get_dir;
 
-	my $dir = $mail->get_box_dir($localpart);
-
 	my $prevumask = umask 0;
 	File::Path::make_path( $dir, { mode => 0755, error => \my $errors } );
 	File::Path::make_path( $dir.'/Maildir/cur', $dir.'/Maildir/new', $dir.'/Maildir/tmp', { mode => 0700, error => \$errors }) unless @$errors;
 	umask $prevumask;
 
 	if( @$errors ) {
-		$mail->set_box_status( $localpart, Vhffs::Constants::CREATION_ERROR );
-		Vhffs::Robots::vhffs_log( $vhffs, 'An error occured while creating mail box '.$localpart.'@'.$mail->get_domain.' to the filesystem: '.join(', ', @$errors) );
+		$box->set_status( Vhffs::Constants::CREATION_ERROR );
+		$box->commit;
+		Vhffs::Robots::vhffs_log( $vhffs, 'An error occured while creating mail box '.$box->get_localpart->get_localpart.'@'.$mail->get_domain.' to the filesystem: '.join(', ', @$errors) );
 		return undef;
 	}
 
@@ -158,33 +160,36 @@
 	Vhffs::Robots::chown_recur( $dir, $mailconf->{'boxes_uid'}, $mailconf->{'boxes_gid'} );
 	chmod 0700, $dir;
 
-	Vhffs::Robots::vhffs_log( $vhffs, 'Created mail box '.$localpart.'@'.$mail->get_domain );
-	$mail->set_box_status( $localpart, Vhffs::Constants::ACTIVATED );
+	Vhffs::Robots::vhffs_log( $vhffs, 'Created mail box '.$box->get_localpart->get_localpart.'@'.$mail->get_domain );
+	$box->set_status( Vhffs::Constants::ACTIVATED );
+	$box->commit;
 	return 1;
 }
 
 sub delete_mailbox {
-	my $mail = shift;
-	my $localpart = shift;
-	return undef unless defined $mail and defined $localpart and $mail->get_box_status($localpart) == Vhffs::Constants::WAITING_FOR_DELETION;
+	my $box = shift;
+	return undef unless defined $box and $box->get_status == Vhffs::Constants::WAITING_FOR_DELETION;
 
+	my $mail = $box->get_mail;
 	my $vhffs = $mail->get_vhffs;
-	my $dir = $mail->get_box_dir($localpart);
+	my $mailconf = $mail->get_config;
+	my $dir = $box->get_dir;
 
-	Vhffs::Robots::archive_targz( $mail, $dir, [ $localpart ] );
+	Vhffs::Robots::archive_targz( $mail, $dir, [ $box->get_localpart->get_localpart ] );
 
 	File::Path::remove_tree( $dir, { error => \my $errors } );
 	# Remove the letter/ directory if empty
 	rmdir File::Basename::dirname($dir);
 
 	if( @$errors ) {
-		$mail->set_box_status( $localpart, Vhffs::Constants::DELETION_ERROR );
-		Vhffs::Robots::vhffs_log( $vhffs, 'An error occured while deleting mail box '.$localpart.'@'.$mail->get_domain.' from the filesystem: '.join(', ', @$errors) );
+		$box->set_status( Vhffs::Constants::DELETION_ERROR );
+		$box->commit;
+		Vhffs::Robots::vhffs_log( $vhffs, 'An error occured while deleting mail box '.$box->get_localpart->get_localpart.'@'.$mail->get_domain.' from the filesystem: '.join(', ', @$errors) );
 		return undef;
 	}
 
-	Vhffs::Robots::vhffs_log( $vhffs, 'Deleted mail box '.$localpart.'@'.$mail->get_domain );
-	return $mail->delbox($localpart);
+	Vhffs::Robots::vhffs_log( $vhffs, 'Deleted mail box '.$box->get_localpart->get_localpart.'@'.$mail->get_domain );
+	return $box->delete;
 }
 
 1;

Modified: trunk/vhffs-api/src/Vhffs/Robots/Mercurial.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Robots/Mercurial.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Robots/Mercurial.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -164,7 +164,7 @@
 	print $rcfileout 'description ='."\n".'  '.$description."\n";
 	require Vhffs::Services::MailGroup;
 	my $mg = new Vhffs::Services::MailGroup( $vhffs, $mercurial->get_group );
-	print $rcfileout 'contact = '.$mercurial->get_group->get_groupname.'@'.$mg->{config}->{domain}."\n" if defined $mg;
+	print $rcfileout 'contact = '.$mg->get_localpart->get_localpart.'@'.$mg->get_domain."\n" if defined $mg and defined $mg->get_localpart;
 	print $rcfileout "\n";
 
 	if( $mladdress !~ /^\s*$/ ) {

Modified: trunk/vhffs-api/src/Vhffs/Robots.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Robots.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Robots.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -217,7 +217,7 @@
 
 	return undef unless defined $object and defined $dir and -d $dir;
 
-	my $robotconf = $object->get_config->get_robots;
+	my $robotconf = $object->get_vhffs->get_config->get_robots;
 	return undef unless defined $robotconf and $robotconf->{'archive_deleted'} and defined $robotconf->{'archive_deleted_path'} and -d $robotconf->{'archive_deleted_path'};
 
 	my $oldcwd = getcwd();

Modified: trunk/vhffs-api/src/Vhffs/Services/Mail.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Services/Mail.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Services/Mail.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -29,12 +29,6 @@
 # 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
@@ -48,13 +42,773 @@
 use strict;
 use utf8;
 
-package Vhffs::Services::Mail;
-
-use base qw(Vhffs::Object);
 use DBI;
 use Vhffs::Functions;
 use Vhffs::Services::MailingList;
 
+package Vhffs::Services::Mail;
+
+use constant {
+	CATCHALL_ALLOW_NONE => 1,
+	CATCHALL_ALLOW_DOMAIN => 2,
+	CATCHALL_ALLOW_OPEN => 3,
+};
+
+
+package Vhffs::Services::Mail::Localpart;
+
+=head2 _new
+
+	Self constructor, almost private.
+
+=cut
+sub _new {
+	my ($class, $mail, $localpart_id, $localpart, $password, $nospam, $novirus ) = @_;
+	my $self = {};
+	bless( $self, $class );
+	$self->{mail} = $mail; # a C<Vhffs::Services::Mail>
+	$self->{localpart_id} = $localpart_id;
+	$self->{localpart} = $localpart;
+	$self->{password} = $password;
+	$self->{nospam} = $nospam;
+	$self->{novirus} = $novirus;
+	return $self;
+}
+
+=head2 create
+
+	Create a C<Vhffs::Services::Mail::Localpart> in database.
+
+	Returns a C<Vhffs::Services::Mail::Localpart> which is not yet attached to C<Vhffs::Services::Mail> to allow using SQL transactions.
+
+=cut
+sub create {
+	my $mail = shift; # a C<Vhffs::Services::Mail>
+	my $localpart = shift;
+	my $password = shift;
+
+	return undef unless defined $localpart and $localpart =~ /^[a-z0-9\_\-\.]+$/;
+
+	my $query = 'INSERT INTO vhffs_mx_localpart (localpart_id, mx_id, localpart, password, nospam, novirus) VALUES(DEFAULT, ?, ?, ?, DEFAULT, DEFAULT) RETURNING localpart_id,localpart,password,nospam,novirus';
+	my $request = $mail->get_db->prepare( $query );
+	$request->execute( $mail->{mx_id}, $localpart, $password ) or return;
+
+	my @returning = $request->fetchrow_array;
+	return _new Vhffs::Services::Mail::Localpart( $mail, @returning );
+}
+
+=head2 commit
+
+	$box->commit or die();
+
+Commit all changes of the current localpart in the database
+
+=cut
+sub commit {
+	my $self = shift;
+
+	my $query = 'UPDATE vhffs_mx_localpart SET password=?,nospam=?,novirus=? WHERE localpart_id=?';
+	return $self->{mail}->get_db->do($query, undef, $self->{password}, $self->{nospam}, $self->{novirus}, $self->{localpart_id});
+}
+
+=head2 get_mail
+
+	my $mail = $box->get_mail
+
+Returns the C<Vhffs::Services::Mail>.
+
+=cut
+sub get_mail {
+	my $self = shift;
+	return $self->{mail};
+}
+
+=head2 get_localpart
+
+	my $localpart = $lp->get_localpart;
+
+Returns the localpart name.
+
+=cut
+sub get_localpart {
+	my $self = shift;
+	return $self->{localpart};
+}
+
+=head2 get_redirects
+
+	my $redirects = $lp->get_redirects;
+
+Returns a hashref of all redirects of a localpart.
+
+=cut
+sub get_redirects {
+	my $self = shift;
+	return $self->{redirects};
+}
+
+=head2 get_redirect
+
+	my $redirect = $lp->get_redirect( $remote );
+
+Returns a C<Vhffs::Services::Mail::Redirect> from $remote name.
+
+=cut
+sub get_redirect {
+	my $self = shift;
+	my $remote = shift;
+	return undef unless defined $remote and defined $self->get_redirects;
+	return $self->get_redirects->{$remote};
+}
+
+=head2 get_box
+
+	my $box = $lp->get_box;
+
+Returns the localpart's box.
+
+=cut
+sub get_box {
+	my $self = shift;
+	return $self->{box};
+}
+
+=head2 get_ml
+
+	my $ml = $lp->get_ml;
+
+Returns the mailing list embryo.
+
+=cut
+sub get_ml {
+	my $self = shift;
+	return $self->{ml};
+}
+
+=head2 get_nospam
+
+	my $nospam = $lp->get_nospam;
+
+Returns whether spam filtering is enabled or not.
+
+=cut
+sub get_nospam {
+	my $self = shift;
+	return $self->{nospam};
+}
+
+=head2 set_nospam
+
+	$lp->set_nospam( $enable );
+
+Unset or set the spam filtering.
+
+=cut
+sub set_nospam {
+	my $self = shift;
+	my $on = shift;
+	$self->{nospam} = $on ? 1 : 0;
+}
+
+=head2 toggle_nospam
+
+	$lp->toggle_nospam;
+
+Toggle nospam flag.
+
+=cut
+sub toggle_nospam {
+	my $self = shift;
+	$self->{nospam} = ($self->{nospam}+1)%2;
+}
+
+=head2 get_novirus
+
+	my $novirus = $lp->get_novirus;
+
+Returns whether virus filtering is enabled or not.
+
+=cut
+sub get_novirus {
+	my $self = shift;
+	return $self->{novirus};
+}
+
+=head2 set_novirus
+
+	$lp->set_novirus( $enable );
+
+Unset or set the virus filtering.
+
+=cut
+sub set_novirus {
+	my $self = shift;
+	my $on = shift;
+	$self->{novirus} = $on ? 1 : 0;
+}
+
+=head2 toggle_novirus
+
+	$lp->toggle_novirus;
+
+Toggle novirus flag.
+
+=cut
+sub toggle_novirus {
+	my $self = shift;
+	$self->{novirus} = ($self->{novirus}+1)%2;
+}
+
+=head2 set_password
+
+	$lp->set_password( $password );
+
+Set the localpart password. Undef or a likely-null value clear the password.
+
+=cut
+sub set_password {
+	my $self = shift;
+	my $password = shift;
+	unless( defined $password and $password !~ /^\s*$/ ) {
+		$self->{password} = undef;
+		return;
+	}
+	$self->{password} = Vhffs::Services::Mail::password_encrypt( $password );
+	return 1;
+}
+
+=head2 check_password
+
+	$lp->check_password( $clearpassword );
+
+Check the localpart password, return true or false.
+
+=cut
+sub check_password {
+	my $self = shift;
+	my $clearpw = shift;
+	return ( $self->{password} eq crypt( $clearpw, $self->{password} ) );
+}
+
+=head2 nb_ref
+
+	my $ref = $lp->nb_ref;
+
+Returns the number of referenced stuff on this localpart. (box + redirects + ml)
+
+=cut
+sub nb_ref {
+	my $self = shift;
+	my $ref = 0;
+	$ref++ if $self->get_box;
+	$ref++ if $self->get_box and $self->get_box->get_status != Vhffs::Constants::WAITING_FOR_DELETION; # a created box count twice!
+	$ref++ if $self->get_ml;
+	$ref+= scalar( keys %{$self->get_redirects} ) if $self->get_redirects;
+	return $ref;
+}
+
+=head2 delete
+
+	Remove a C<Vhffs::Services::Mail::Localpart> from database, and much more.
+
+	Actually calling the C<Vhffs::Services::Mail> delete_localpart method.
+
+=cut
+sub delete {
+	my $self = shift;
+	my $force = shift;
+	return $self->{mail}->delete_localpart( $self, $force );
+}
+
+=head2 destroy
+
+	Remove a C<Vhffs::Services::Mail::Localpart> from database.
+
+	You should call delete instead of destroy, destroy remove the localpart without checking anything.
+
+=cut
+sub destroy {
+	my $self = shift;
+
+	my $sql = 'DELETE FROM vhffs_mx_localpart WHERE localpart_id=?';
+	return $self->{mail}->get_db->do($sql, undef, $self->{localpart_id});
+}
+
+
+package Vhffs::Services::Mail::Box;
+
+=head2 _new
+
+	Self constructor, almost private.
+
+=cut
+sub _new {
+	my ($class, $localpart, $box_id, $allowpop, $allowimap, $state ) = @_;
+	my $self = {};
+	bless( $self, $class );
+	$self->{localpart} = $localpart; # a C<Vhffs::Services::Mail::Localpart>
+	$self->{box_id} = $box_id;
+	$self->{allowpop} = $allowpop;
+	$self->{allowimap} = $allowimap;
+	$self->{state} = $state;
+	return $self;
+}
+
+=head2 create
+
+	Create a C<Vhffs::Services::Mail::Box> in database.
+
+	Returns a C<Vhffs::Services::Mail::Box> which is not yet attached to C<Vhffs::Services::Mail::Localpart> to allow using SQL transactions.
+
+=cut
+sub create {
+	my $lp = shift; # a C<Vhffs::Services::Mail::Localpart>
+
+	my $query = 'INSERT INTO vhffs_mx_box (box_id, localpart_id, allowpop, allowimap, state) VALUES(DEFAULT, ?, DEFAULT, DEFAULT, ?) RETURNING box_id,allowpop,allowimap,state';
+	my $request = $lp->{mail}->get_db->prepare( $query );
+	$request->execute( $lp->{localpart_id}, Vhffs::Constants::WAITING_FOR_CREATION ) or return;
+
+	my @returning = $request->fetchrow_array;
+	return _new Vhffs::Services::Mail::Box( $lp, @returning );
+}
+
+=head2 commit
+
+	$box->commit or die();
+
+Commit all changes of the current box in the database
+
+=cut
+sub commit {
+	my $self = shift;
+
+	my $query = 'UPDATE vhffs_mx_box SET allowpop=?,allowimap=?,state=? WHERE box_id=?';
+	return $self->{localpart}->{mail}->get_db->do($query, undef, $self->{allowpop}, $self->{allowimap}, $self->{state}, $self->{box_id});
+}
+
+=head2 get_mail
+
+	my $mail = $box->get_mail
+
+Returns the C<Vhffs::Services::Mail>.
+
+=cut
+sub get_mail {
+	my $self = shift;
+	return $self->{localpart}->{mail};
+}
+
+=head2 get_localpart
+
+	my $localpart = $box->get_localpart;
+
+Returns the C<Vhffs::Services::Mail::Localpart>.
+
+=cut
+sub get_localpart {
+	my $self = shift;
+	return $self->{localpart};
+}
+
+=head2 get_boxname
+
+	my $boxname = $box->get_boxname;
+
+Returns the box' full email.
+
+=cut
+sub get_boxname {
+	my $self = shift;
+	return $self->{localpart}->get_localpart.'@'.$self->{localpart}->{mail}->get_domain;
+}
+
+=head2 get_status
+
+	my $state = $box->get_status;
+
+Returns the box' status.
+
+=cut
+sub get_status {
+	my $self = shift;
+	return $self->{state};
+}
+
+=head2 set_status
+
+	$box->set_status( $status );
+
+Set the box' status.
+
+=cut
+sub set_status {
+	my $self = shift;
+	my $state = shift;
+	$self->{state} = $state;
+}
+
+=head2 get_allowpop
+
+	my $allowpop = $box->get_allowpop;
+
+Returns whether POP is allowed.
+
+=cut
+sub get_allowpop {
+	my $self = shift;
+	return $self->{allowpop};
+}
+
+=head2 set_allowpop
+
+	$box->set_allowpop( $allow );
+
+Set whether POP is allowed.
+
+=cut
+sub set_allowpop {
+	my $self = shift;
+	my $allow = shift;
+	$self->{allowpop} = $allow ? 1 : 0;
+}
+
+=head2 get_allowimap
+
+	my $allowimap = $box->get_allowimap;
+
+Returns whether IMAP is allowed.
+
+=cut
+sub get_allowimap {
+	my $self = shift;
+	return $self->{allowimap};
+}
+
+=head2 set_allowimap
+
+	$box->set_allowimap( $allow );
+
+Set whether IMAP is allowed.
+
+=cut
+sub set_allowimap {
+	my $self = shift;
+	my $allow = shift;
+	$self->{allowimap} = $allow ? 1 : 0;
+}
+
+=head2 get_dir
+
+Returns mail box dir.
+
+=cut
+sub get_dir {
+	my $self = shift;
+	my $lp = $self->{localpart};
+	return( $lp->{mail}->get_dir().'/'.substr( $lp->get_localpart, 0, 1 ).'/'.$lp->get_localpart );
+}
+
+=head2 delete
+
+	Remove a C<Vhffs::Services::Mail::Box> from database, and much more.
+
+	Actually calling the C<Vhffs::Services::Mail> delete_box method.
+
+=cut
+sub delete {
+	my $self = shift;
+	return $self->{localpart}->{mail}->delete_box( $self );
+}
+
+=head2 destroy
+
+	Remove a C<Vhffs::Services::Mail::Box> from database.
+
+	You should call delete instead of destroy, destroy only remove the box without deleting the localpart if necessary.
+
+=cut
+sub destroy {
+	my $self = shift;
+
+	my $sql = 'DELETE FROM vhffs_mx_box WHERE box_id=?';
+	return $self->{localpart}->{mail}->get_db->do($sql, undef, $self->{box_id});
+}
+
+
+package Vhffs::Services::Mail::Redirect;
+
+=head2 _new
+
+	Self constructor, almost private.
+
+=cut
+sub _new {
+	my ($class, $localpart, $redirect_id, $redirect ) = @_;
+	my $self = {};
+	bless( $self, $class );
+	$self->{localpart} = $localpart; # a C<Vhffs::Services::Mail::Localpart>
+	$self->{redirect_id} = $redirect_id;
+	$self->{redirect} = $redirect;
+	return $self;
+}
+
+=head2 create
+
+	Create a C<Vhffs::Services::Mail::Redirect> in database.
+
+	Returns a C<Vhffs::Services::Mail::Redirect> which is not yet attached to C<Vhffs::Services::Mail::Localpart> to allow using SQL transactions.
+
+=cut
+sub create {
+	my $lp = shift; # a C<Vhffs::Services::Mail::Localpart>
+	my $remote = shift;
+
+	return undef unless defined $remote and Vhffs::Functions::valid_mail( $remote );
+
+	my $query = 'INSERT INTO vhffs_mx_redirect (redirect_id, localpart_id, redirect) VALUES(DEFAULT, ?, ?) RETURNING redirect_id,redirect';
+	my $request = $lp->{mail}->get_db->prepare( $query );
+	$request->execute( $lp->{localpart_id}, $remote ) or return;
+
+	my @returning = $request->fetchrow_array;
+	return _new Vhffs::Services::Mail::Redirect( $lp, @returning );
+}
+
+=head2 commit
+
+	$redirect->commit or die();
+
+Commit all changes of the current redirect in the database
+
+=cut
+sub commit {
+	my $self = shift;
+
+	my $query = 'UPDATE vhffs_mx_redirect SET redirect=? WHERE redirect_id=?';
+	return $self->{localpart}->{mail}->get_db->do($query, undef, $self->{redirect}, $self->{redirect_id});
+}
+
+=head2 get_mail
+
+	my $mail = $box->get_mail
+
+Returns the C<Vhffs::Services::Mail>.
+
+=cut
+sub get_mail {
+	my $self = shift;
+	return $self->{localpart}->{mail};
+}
+
+=head2 get_localpart
+
+	my $localpart = $box->get_localpart;
+
+Returns the C<Vhffs::Services::Mail::Localpart>.
+
+=cut
+sub get_localpart {
+	my $self = shift;
+	return $self->{localpart};
+}
+
+=head2 delete
+
+	Remove a C<Vhffs::Services::Mail::Redirect> from database, and much more.
+
+	Actually calling the C<Vhffs::Services::Mail> delete_redirect method.
+
+=cut
+sub delete {
+	my $self = shift;
+	return $self->{localpart}->{mail}->delete_redirect( $self );
+}
+
+=head2 destroy
+
+	Remove a C<Vhffs::Services::Mail::Redirect> from database.
+
+	You should call delete instead of destroy, destroy only remove the redirect without deleting the localpart if necessary.
+
+=cut
+sub destroy {
+	my $self = shift;
+
+	my $sql = 'DELETE FROM vhffs_mx_redirect WHERE redirect_id=?';
+	return $self->{localpart}->{mail}->get_db->do($sql, undef, $self->{redirect_id});
+}
+
+=head2 get_redirect
+
+	my $remote = $redirect->get_redirect;
+
+Returns the redirect destination (remote).
+
+=cut
+sub get_redirect {
+	my $self = shift;
+	return $self->{redirect};
+}
+
+=head2 set_redirect
+
+	$redirect->set_redirect( $remote );
+
+Set the redirect remote address.
+
+=cut
+sub set_redirect {
+	my $self = shift;
+	my $remote = shift;
+	return undef unless defined $remote and Vhffs::Functions::valid_mail( $remote );
+	$self->{redirect} = $remote;
+	return 1;
+}
+
+
+package Vhffs::Services::Mail::Ml;
+
+=head2 _new
+
+	Self constructor, almost private.
+
+=cut
+sub _new {
+	my ( $class, $localpart, $ml_id ) = @_;
+	my $self = {};
+	bless( $self, $class );
+	$self->{localpart} = $localpart; # a C<Vhffs::Services::Mail::Localpart>
+	$self->{ml_id} = $ml_id;
+	return $self;
+}
+
+=head2 get_mail
+
+	my $mail = $ml->get_mail
+
+Returns the C<Vhffs::Services::Mail>.
+
+=cut
+sub get_mail {
+	my $self = shift;
+	return $self->{localpart}->{mail};
+}
+
+=head2 get_localpart
+
+	my $localpart = $ml->get_localpart;
+
+Returns the C<Vhffs::Services::Mail::Localpart>.
+
+=cut
+sub get_localpart {
+	my $self = shift;
+	return $self->{localpart};
+}
+
+=head2 get_ml_object
+
+	my $ml = $ml->get_ml_object;
+
+Returns the full C<Vhffs::Services::MailingList> object.
+
+=cut
+sub get_ml_object {
+	my $self = shift;
+	require Vhffs::Services::MailingList;
+	return Vhffs::Services::MailingList::get_by_id( $self->get_mail->get_vhffs, $self->{ml_id} );
+}
+
+package Vhffs::Services::Mail::Catchall;
+
+=head2 _new
+
+	Self constructor, almost private.
+
+=cut
+sub _new {
+	my ($class, $mail, $catchall_id, $box_id, $boxname ) = @_;
+	my $self = {};
+	bless( $self, $class );
+	$self->{mail} = $mail; # a C<Vhffs::Services::Mail>
+	$self->{catchall_id} = $catchall_id;
+	$self->{box_id} = $box_id;
+	$self->{boxname} = $boxname;
+	return $self;
+}
+
+=head2 create
+
+	Create a C<Vhffs::Services::Mail::Catchall> in database.
+
+	Returns a C<Vhffs::Services::Mail::Catchall> which is not yet attached to C<Vhffs::Services::Mail> to allow using SQL transactions.
+
+=cut
+sub create {
+	my $mail = shift; # a C<Vhffs::Services::Mail>
+	my $box = shift; # a C<Vhffs::Services::Mail::Box>
+
+	my $query = 'INSERT INTO vhffs_mx_catchall (catchall_id, mx_id, box_id) VALUES(DEFAULT, ?, ?) RETURNING catchall_id';
+	my $request = $mail->get_db->prepare( $query );
+	$request->execute( $mail->{mx_id}, $box->{box_id} ) or return;
+
+	my @returning = $request->fetchrow_array;
+	return _new Vhffs::Services::Mail::Catchall( $mail, @returning, $box->{box_id}, $box->get_boxname );
+}
+
+=head2 get_catchall
+
+	my $boxname = $catchall->get_catchall;
+
+Returns the catchcall boxname
+
+=cut
+sub get_catchall {
+	my $self = shift;
+	return $self->{boxname};
+}
+
+=head2 get_mail
+
+	my $mail = $box->get_mail
+
+Returns the C<Vhffs::Services::Mail>.
+
+=cut
+sub get_mail {
+	my $self = shift;
+	return $self->{mail};
+}
+
+=head2 delete
+
+	Remove a C<Vhffs::Services::Mail::Catchall> from database, and much more.
+
+	Actually calling the C<Vhffs::Services::Mail> delete_catchall method.
+
+=cut
+sub delete {
+	my $self = shift;
+	return $self->{mail}->delete_catchall( $self );
+}
+
+=head2 destroy
+
+	Remove a C<Vhffs::Services::Mail::Catchall> from database.
+
+	You should call delete instead of destroy, destroy remove the catchall without checking anything.
+
+=cut
+sub destroy {
+	my $self = shift;
+
+	my $sql = 'DELETE FROM vhffs_mx_catchall WHERE catchall_id=?';
+	return $self->{mail}->get_db->do($sql, undef, $self->{catchall_id});
+}
+
+
+package Vhffs::Services::Mail;
+use base qw(Vhffs::Object);
+
 =pod
 
 =head2 password_encrypt
@@ -69,42 +823,32 @@
 	return crypt($password, '$6$'.join( '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[map {rand 64} (1..16)]) );
 }
 
-=pod address_exists
+=head2 _new
 
-	print ("Adress $local_part\@$domain already exists as a box or forward\n")
-		if(Vhffs::Services::Mail::address_exists($vhffs, $local_part, $domain));
+	Self constructor, almost private.
 
-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 = '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';
-
-	my $dbh = $vhffs->get_db();
-	my $res = $dbh->selectrow_array($sql, {}, $domain, $local_part, $domain, $local_part);
-	return (defined $res and $res > 0);
-}
-
 sub _new {
-	my ($class, $vhffs, $mxdomain_id, $owner_gid, $domain, $boxes_path, $catchall, $oid, $owner_uid, $date_creation, $state, $description, $boxes, $forwards) = @_;
+	my ($class, $vhffs, $oid, $owner_uid, $owner_gid, $date_creation, $description, $state, $mx_id, $domain, $localparts) = @_;
 	my $self = $class->SUPER::_new($vhffs, $oid, $owner_uid, $owner_gid, $date_creation, $description, '', $state, Vhffs::Constants::TYPE_MAIL);
-	return undef unless defined($self);
+	return undef unless defined $self;
 
-	$self->{mxdomain_id} = $mxdomain_id;
+	$self->{mx_id} = $mx_id;
 	$self->{domain} = $domain;
-	$self->{boxes_path} = $boxes_path;
-	$self->{catchall} = $catchall;
-	$self->{boxes} = $boxes;
-	$self->{forward} = $forwards;
+	$self->{nb_localparts} = 0;
+	$self->{nb_boxes} = 0;
+	$self->{nb_redirects} = 0;
+	$self->{nb_mls} = 0;
+	$self->{nb_catchalls} = 0;
+	$self->{localpart} = {};
+	$self->{catchall} = {};
 
+	my $mail_config = $vhffs->get_config->get_service('mail');
+	my $allowed_catchall = CATCHALL_ALLOW_DOMAIN;
+	$allowed_catchall = CATCHALL_ALLOW_NONE if $mail_config->{allowed_catchall} =~ /^none$/i;
+	$allowed_catchall = CATCHALL_ALLOW_OPEN if $mail_config->{allowed_catchall} =~ /^open$/i;
+	$self->{conf_allowed_catchall} = $allowed_catchall;
+
 	return $self;
 }
 
@@ -123,7 +867,7 @@
 	return undef unless(Vhffs::Functions::check_domain_name($domain));
 
 	my $mail;
-	my $dbh = $vhffs->get_db();
+	my $dbh = $vhffs->get_db;
 	local $dbh->{RaiseError} = 1;
 	local $dbh->{PrintError} = 0;
 	$dbh->begin_work;
@@ -133,17 +877,16 @@
 
 		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 = substr( $domain, 0, 1 ).'/'.substr( $domain, 1, 1 ).'/'.$domain ;
+		my $sql = 'INSERT INTO vhffs_mx(domain, object_id) VALUES(?, ?)';
 		my $sth = $dbh->prepare($sql);
-		$sth->execute($domain, $domain_hash, $parent->get_oid);
+		$sth->execute($domain, $parent->get_oid);
 
 		$dbh->commit;
 		$mail = get_by_mxdomain($vhffs, $domain);
 	};
 
 	if($@) {
-		warn "Unable to create mail domain $domain: $@\n";
+		warn 'Unable to create mail domain '.$domain.': '.$@."\n";
 		$dbh->rollback;
 	}
 
@@ -157,16 +900,18 @@
 =cut
 sub fill_object {
 	my ($class, $obj) = @_;
-	my $sql = q{SELECT mxdomain_id, domain, boxes_path,
-	catchall FROM vhffs_mxdomain WHERE object_id = ?};
+	my $sql = q{SELECT mx_id, domain FROM vhffs_mx WHERE object_id = ?};
 	$obj = $class->SUPER::_fill_object($obj, $sql);
-	if($obj->isa('Vhffs::Services::Mail')) {
-		$obj->{boxes} = fetch_boxes($obj->get_db, $obj->{domain});
-		$obj->{forward} = fetch_forwards($obj->get_db, $obj->{domain});
-	}
 	return $obj;
 }
 
+=head2 getall
+
+	my @mails = Vhffs::Services::Mail::getall( $vhffs, $state, $name, $group );
+
+Returns C<Vhffs::Services::Mail> objects based on $state and/or part of $name and/or owned by a C<Vhffs::Group>.
+
+=cut
 sub getall {
 	my ($vhffs, $state, $name, $group) = @_;
 
@@ -174,7 +919,7 @@
 	my @params;
 
 	my $sql = 'SELECT m.domain
-		FROM vhffs_mxdomain m INNER JOIN vhffs_object o ON m.object_id = o.object_id';
+		FROM vhffs_mx m INNER JOIN vhffs_object o ON m.object_id=o.object_id';
 	if(defined $state) {
 		$sql .= ' AND o.state=?';
 		push(@params, $state);
@@ -213,490 +958,648 @@
 sub get_by_mxdomain {
 	my ($vhffs, $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 = $vhffs->get_db();
+	my $sql = 'SELECT o.object_id, o.owner_uid, o.owner_gid, o.date_creation, o.description, o.state, m.mx_id, m.domain FROM vhffs_mx m INNER JOIN vhffs_object o ON o.object_id=m.object_id WHERE m.domain=?';
+	my $dbh = $vhffs->get_db;
 	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($vhffs, @params, $boxes, $forwards);
+	return _new Vhffs::Services::Mail( $vhffs, @params );
 }
 
-=head2 fetch_boxes
+=head2 fetch_localparts
 
-	my $boxes = fetch_boxes($dbh, $domain);
+	my $lps = $mail->fetch_localparts;
 
-Returns an hashref of hashrefs containing all this domain's boxes, indexed on
-local_part.
+Fill $mail->{localpart} with a hashref of hashrefs containing all this domain's boxes,
+redirects, mailing lists embryo, indexed on localpart.
 
-Internal module use.
+Also fetch catchall entries.
 
+Returns the $mail->{localpart}, but should only be called once.
+
 =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);
+sub fetch_localparts {
+	my $mail = shift;
+	my $localparts = {};
+	my $catchalls = {};
+	$mail->{nb_localparts} = 0;
+	$mail->{nb_catchalls} = 0;
+	$mail->{nb_boxes} = 0;
+	$mail->{nb_redirects} = 0;
+	$mail->{nb_mls} = 0;
+
+	# Localparts
+	my $sql = 'SELECT mxl.localpart_id,mxl.localpart,mxl.password,mxl.nospam,mxl.novirus FROM vhffs_mx_localpart mxl WHERE mxl.mx_id=?';
+	my $sth = $mail->get_db->prepare($sql);
+	$sth->execute($mail->{mx_id});
+	while ( my @lp = $sth->fetchrow_array ) {
+		my $o = _new Vhffs::Services::Mail::Localpart( $mail, @lp );
+		$localparts->{$o->{localpart}} = $o;
+		$mail->{nb_localparts}++;
+	}
+
+	# Catchall
+	$sql = 'SELECT mxc.catchall_id,mb.box_id,mxl.localpart||\'@\'||mx.domain AS boxname FROM vhffs_mx_catchall mxc INNER JOIN vhffs_mx_box mb ON mb.box_id=mxc.box_id INNER JOIN vhffs_mx_localpart mxl ON mxl.localpart_id=mb.localpart_id INNER JOIN vhffs_mx mx ON mx.mx_id=mxl.mx_id WHERE mxc.mx_id=?';
+	$sth = $mail->get_db->prepare($sql);
+	$sth->execute($mail->{mx_id});
+	while ( my @catchall = $sth->fetchrow_array ) {
+		my $o = _new Vhffs::Services::Mail::Catchall( $mail, @catchall );
+		$catchalls->{$o->{boxname}} = $o;
+		$mail->{nb_catchalls}++;
+	}
+
+	# Boxes
+	$sql = 'SELECT mxl.localpart,mb.box_id,mb.allowpop,mb.allowimap,mb.state FROM vhffs_mx_box mb INNER JOIN vhffs_mx_localpart mxl ON mb.localpart_id=mxl.localpart_id WHERE mxl.mx_id=?';
+	$sth = $mail->get_db->prepare($sql);
+	$sth->execute($mail->{mx_id});
+	while ( my @box = $sth->fetchrow_array ) {
+		my $lp = $localparts->{ shift @box };
+		$lp->{box} = _new Vhffs::Services::Mail::Box( $lp, @box );
+		$mail->{nb_boxes}++;
+	}
+
+	# Redirects
+	$sql = 'SELECT mxl.localpart,mr.redirect_id,mr.redirect FROM vhffs_mx_redirect mr INNER JOIN vhffs_mx_localpart mxl ON mr.localpart_id=mxl.localpart_id WHERE mxl.mx_id=?';
+	$sth = $mail->get_db->prepare($sql);
+	$sth->execute($mail->{mx_id});
+	while ( my @redirect = $sth->fetchrow_array ) {
+		my $lp = $localparts->{ shift @redirect };
+		my $o = _new Vhffs::Services::Mail::Redirect( $lp, @redirect );
+		$lp->{redirects}->{$o->{redirect}} = $o;
+		$mail->{nb_redirects}++;
+	}
+
+	# Embryos of ML (embryos because we are Vhffs::Services::Mail, not Vhffs::Services::MailingList, but we need to have a way to know which localparts are actually a mailing list)
+	$sql = 'SELECT mxl.localpart,ml.ml_id FROM vhffs_mx_ml ml INNER JOIN vhffs_mx_localpart mxl ON ml.localpart_id=mxl.localpart_id WHERE mxl.mx_id=?';
+	$sth = $mail->get_db->prepare($sql);
+	$sth->execute($mail->{mx_id});
+	while ( my @ml = $sth->fetchrow_array ) {
+		my $lp = $localparts->{ shift @ml };
+		$lp->{ml} = _new Vhffs::Services::Mail::Ml( $lp, @ml );
+		$mail->{nb_mls}++;
+	}
+
+	$mail->{catchall} = $catchalls;
+	$mail->{localpart} = $localparts;
+	return $localparts;
 }
 
-=head2 fetch_forwards
+=head2 fetch_localpart
 
-	my $forwards = fetch_forwards($dbh, $domain);
+	my $lp = $mail->fetch_localpart( $localpart );
 
-Returns an hashref of hashrefs containing all this domain's forwards, indexed
-on local_part.
+Fill $mail->{localpart} with a new hashref entry containing one localpart.
 
-Internal module use.
+Useful for mailing lists, MailGroup and MailUser modules, so that we don't fetch all localparts.
 
 =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 fetch_localpart {
+	my $mail = shift;
+	my $localpart_name = shift;
+
+	# Localpart
+	my $sql = 'SELECT mxl.localpart_id,mxl.localpart,mxl.password,mxl.nospam,mxl.novirus FROM vhffs_mx_localpart mxl WHERE mxl.mx_id=? AND mxl.localpart=?';
+	my $sth = $mail->get_db->prepare($sql);
+	$sth->execute($mail->{mx_id}, $localpart_name);
+	my @lp = $sth->fetchrow_array;
+	return unless @lp;
+	my $localpart = _new Vhffs::Services::Mail::Localpart( $mail, @lp );
+
+	# Box
+	$sql = 'SELECT mb.box_id,mb.allowpop,mb.allowimap,mb.state FROM vhffs_mx_box mb INNER JOIN vhffs_mx_localpart mxl ON mb.localpart_id=mxl.localpart_id WHERE mxl.localpart_id=?';
+	$sth = $mail->get_db->prepare($sql);
+	$sth->execute($localpart->{localpart_id});
+	my @box = $sth->fetchrow_array;
+	$localpart->{box} = _new Vhffs::Services::Mail::Box( $localpart, @box ) if @box;
+
+	# Redirects
+	$sql = 'SELECT mr.redirect_id,mr.redirect FROM vhffs_mx_redirect mr INNER JOIN vhffs_mx_localpart mxl ON mr.localpart_id=mxl.localpart_id WHERE mxl.localpart_id=?';
+	$sth = $mail->get_db->prepare($sql);
+	$sth->execute($localpart->{localpart_id});
+	while ( my @redirect = $sth->fetchrow_array ) {
+		my $o = _new Vhffs::Services::Mail::Redirect( $localpart, @redirect );
+		$localpart->{redirects}->{$o->{redirect}} = $o;
+	}
+
+	# Embryos of ML (embryos because we are Vhffs::Services::Mail, not Vhffs::Services::MailingList, but we need to have a way to know which localparts are actually a mailing list)
+	$sql = 'SELECT ml.ml_id FROM vhffs_mx_ml ml INNER JOIN vhffs_mx_localpart mxl ON ml.localpart_id=mxl.localpart_id WHERE mxl.localpart_id=?';
+	$sth = $mail->get_db->prepare($sql);
+	$sth->execute($localpart->{localpart_id});
+	my @ml = $sth->fetchrow_array;
+	$localpart->{ml} = _new Vhffs::Services::Mail::Ml( $localpart, @ml ) if @ml;
+
+	$mail->{localpart}->{$localpart->{localpart}} = $localpart;
+	return $localpart;
 }
 
-# Commit all changes of the current instance in the database
+=head2 commit
+
+	$mail->commit or die();
+
+Commit all changes of the current instance in the database
+
+=cut
 sub commit {
 	my $self = shift;
+	return $self->SUPER::commit;
+}
 
-	my( $request , $result , $rows , $query , $name , $password);
+=head2 get_label
 
-	$self->{'catchall'} = "" if( ! defined $self->{'catchall'} );
+See C<Vhffs::Object::get_label>.
 
-	$query = "UPDATE vhffs_mxdomain SET catchall='".$self->{'catchall'}."' WHERE domain='".$self->{'domain'}."'";
-	$request = $self->get_db->prepare( $query );
-	$request->execute;
+=cut
+sub get_label {
+	my $self = shift;
+	return $self->{domain};
+}
 
-	return -3 if( $self->SUPER::commit < 0 );
+=head2 get_config
 
-	return 1;
+See C<Vhffs::Object::get_config>.
+
+=cut
+sub get_config {
+	my $self = shift;
+	return $self->{vhffs}->get_config->get_service('mail');
 }
 
+=head2 get_domain
+
+	my $domain = $mail->get_domain;
+
+Returns domain name.
+
+=cut
+sub get_domain {
+	my $self = shift;
+	return $self->{domain};
+}
+
+=head2 get_dir
+
+Returns mail domain root dir.
+
+=cut
 sub get_dir {
 	my $self = shift;
-
-	return undef unless defined $self;
-	return( $self->get_config->get_datadir . '/mail/boxes/' . $self->{'boxes_path'} );
+	return( $self->get_vhffs->get_config->get_datadir.'/mail/boxes/'.substr( $self->{domain}, 0, 1 ).'/'.substr( $self->{domain}, 1, 1 ).'/'.$self->{domain} );
 }
 
-sub change_forward {
+=head2 nb_localparts
+
+Returns the number of localparts on this domain.
+
+=cut
+sub nb_localparts {
 	my $self = shift;
-	my $local_part = shift;
-	my $destination = shift;
+	return $self->{nb_localparts};
+}
 
-	return -1 unless( defined($self->{forward}{$local_part}) && defined($local_part) && defined($destination) && Vhffs::Functions::valid_mail( $destination ) );
+=head2 nb_boxes
 
-	my $sql = 'UPDATE vhffs_forward SET remote_name = ? WHERE local_part = ? AND domain = ?';
-	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($destination, $local_part, $self->{domain}) or return -1;
+Returns the number of boxes on this domain.
 
-	$self->{'forward'}{$local_part}{'remote_name'} = $destination;
-
-	return 1;
+=cut
+sub nb_boxes {
+	my $self = shift;
+	return $self->{nb_boxes};
 }
 
-sub change_box_password {
+=head2 nb_redirects
+
+Returns the number of redirects on this domain.
+
+=cut
+sub nb_redirects {
 	my $self = shift;
-	my $local_part = shift;
-	my $password = shift;
+	return $self->{nb_redirects};
+}
 
-	return -1 unless defined $self->{'boxes'}{$local_part};
+=head2 nb_mls
 
-	$password = password_encrypt( $password );
+Returns the number of mailing lists on this domain.
 
-	$self->{'boxes'}{$local_part}{'password'} = $password;
+=cut
+sub nb_mls {
+	my $self = shift;
+	return $self->{nb_mls};
+}
 
-	my $sql = 'UPDATE vhffs_boxes SET password = ? WHERE local_part = ? AND domain = ?';
-	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($password, $local_part, $self->{domain}) or return -1;
+=head2 nb_catchalls
 
-	return 1;
+Returns the number of catchalls on this domain.
+
+=cut
+sub nb_catchalls {
+	my $self = shift;
+	return $self->{nb_catchalls};
 }
 
-# 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 {
+=head2 get_localparts
+
+	my $localparts = $mail->get_localparts;
+
+Returns a hashref with all localparts
+The key of this hash is the local part for the forward
+
+=cut
+sub get_localparts {
 	my $self = shift;
-	my $local_part = shift;
+	return $self->{localpart};
+}
 
-	return -1 if( ! defined $self->{'boxes'}{$local_part} );
+=head2 get_localpart
 
-	my $nospam = $self->{boxes}{$local_part}{nospam};
-	if( defined( $nospam ) ) {
-		$nospam = ( $nospam + 1 ) % 2;
-	} else {
-		$nospam = 1;
-	}
-	$self->{boxes}{$local_part}{nospam} = $nospam;
+	my $localpart = $mail->get_localpart( $localname );
 
-	my $sql = 'UPDATE vhffs_boxes SET nospam = ? WHERE domain = ? AND local_part = ?';
-	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($nospam, $self->{domain}, $local_part) or return -1;
+Returns a C<Vhffs::Services::Mail::Localpart> from $localname.
 
-	return 1;
+=cut
+sub get_localpart {
+	my $self = shift;
+	my $localpart = shift;
+	return undef unless defined $localpart and defined $self->get_localparts;
+	return $self->get_localparts->{$localpart};
 }
 
-=head2 set_spam_status
+=pod
 
-	$mail->set_spam_status($boxname, $status);
+=head2 delete_localpart
 
-Sets the spam protection status to C<$status> (0 or 1).
+	die("Unable to delete localpart $localpart\n") unless $mail->delete_localpart($localpart);
 
+Delete a localpart from $localpart@$mail->get_domain.
+
 =cut
-sub set_spam_status {
-	my ($self, $boxname, $status) = @_;
+sub delete_localpart {
+	my $self = shift;
+	my $localpart = shift; # a C<Vhffs::Services::Mail::Localpart> object
+	my $force = shift;
+	return undef unless defined $localpart and $localpart->{mail} == $self; # Do not be cheated
 
-	return -1 unless(defined $self->{boxes}{$boxname});
+	# FIXME: Should we recursively delete all objects referencing this localpart ?
+	# That would be a lovely feature but quite hard to implement
+	# at least it requires to redesign fetch_localparts sub
 
-	$self->{boxes}{$boxname}{nospam} = $status;
-	my $sql = 'UPDATE vhffs_boxes SET nospam = ? WHERE domain = ? AND local_part = ?';
-	my $dbh = $self->get_db();
-	return -2 unless( $dbh->do($sql, undef, $status, $self->{domain}, $boxname) );
-	return 1;
+	# Count references, only delete localpart if references are below 1
+	my $ref = $force ? 0 : $localpart->nb_ref;
+
+	return undef if $ref > 1;
+	return $localpart->destroy;
 }
 
-=head2 set_virus_status
+=pod
 
-	$mail->set_virus_status($boxname, $status);
+=head2 add_redirect
 
-Sets the virus protection status to C<$status> (0 or 1).
+	die("Unable to add redirect $localpart to $remote\n") unless $mail->add_redirect($localpart, $remote);
 
+Add a redirect from $localpart@$mail->get_domain to $remote.
+
 =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();
-	return -2 unless( $dbh->do($sql, undef, $status, $self->{domain}, $boxname) );
-	return 1;
-}
-
-#work as change_spam_status
-sub change_virus_status {
+sub add_redirect {
 	my $self = shift;
-	my $local_part = shift;
-	return -1 unless defined $self->{'boxes'}{$local_part};
+	my $localpart = shift;
+	my $remote = shift;
 
-	my $novirus = $self->{boxes}{$local_part}{novirus};
-	if(defined $novirus) {
-		$novirus = ($novirus + 1) % 2;
-	} else {
-		$novirus = 1;
-	}
+	# $localpart is checked by Localpart class
+	# $remote is checked by Redirect class
 
-	$self->{'boxes'}{$local_part}{'novirus'} = $novirus;
+	my $lp = $self->get_localpart($localpart);
+	return undef if defined $lp and defined $lp->{redirects}->{$remote};
+	my $redirect;
 
-	my $sql = 'UPDATE vhffs_boxes SET novirus = ? WHERE domain = ? AND local_part = ?';
 	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($novirus, $self->{domain}, $local_part) or return -1;
+	local $dbh->{RaiseError} = 1;
+	local $dbh->{PrintError} = 0;
+	$dbh->begin_work;
 
-	return 1;
-}
+	eval {
+		# create localpart if necessary
+		unless( defined $lp ) {
+			$lp = Vhffs::Services::Mail::Localpart::create( $self, $localpart );
+			die unless defined $lp;
+		}
 
-sub set_box_allowpop {
-	my $self = shift;
-	my $local_part = shift;
-	my $allow = shift;
-	return -1 unless defined $self->{'boxes'}{$local_part};
+		# create redirect
+		$redirect = Vhffs::Services::Mail::Redirect::create( $lp, $remote );
+		die unless defined $redirect;
 
-	$self->{'boxes'}{$local_part}{'allowpop'} = $allow;
+		$dbh->commit;
+	};
 
-	my $sql = 'UPDATE vhffs_boxes SET allowpop=? WHERE domain=? AND local_part=?';
-	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($allow, $self->{domain}, $local_part) or return -1;
+	if($@) {
+		$dbh->rollback;
+		return undef;
+	}
 
-	return 1;
-}
+	# Attach localpart and box
+	unless( defined $self->get_localparts->{$localpart} ) {
+		$self->get_localparts->{$localpart} = $lp;
+		$self->{nb_localparts}++;
+	}
+	$lp->{redirects}->{$remote} = $redirect;
+	$self->{nb_redirects}++;
 
-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'};
+	$self->add_history( $localpart.'@'.$self->get_domain.' forward added to '.$remote );
+	return $redirect;
 }
 
-sub set_box_allowimap {
-	my $self = shift;
-	my $local_part = shift;
-	my $allow = shift;
-	return -1 unless defined $self->{'boxes'}{$local_part};
+=pod
 
-	$self->{'boxes'}{$local_part}{'allowimap'} = $allow;
+=head2 get_redirects
 
-	my $sql = 'UPDATE vhffs_boxes SET allowimap=? WHERE domain=? AND local_part=?';
-	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($allow, $self->{domain}, $local_part) or return -1;
+	$redirects = $mail->get_redirects( $localpart );
 
-	return 1;
-}
+Returns a hashref of C<Vhffs::Services::Mail::Redirect> from $localpart.
 
-sub get_box_allowimap {
+=cut
+sub get_redirects {
 	my $self = shift;
-	my $local_part = shift;
-	return undef unless defined $self->{'boxes'}{$local_part};
-	return $self->{'boxes'}{$local_part}{'allowimap'};
+	my $localpart = shift;
+	return undef unless defined $localpart;
+	my $lp = $self->get_localpart($localpart);
+	return undef unless defined $lp;
+	return $lp->get_redirects;
 }
 
-sub set_box_status {
-	my $self = shift;
-	my $local_part = shift;
-	my $state = shift;
-	return -1 unless defined $self->{'boxes'}{$local_part};
+=pod
 
-	$self->{'boxes'}{$local_part}{'state'} = $state;
+=head2 get_redirect
 
-	my $sql = 'UPDATE vhffs_boxes SET state=? WHERE domain=? AND local_part=?';
-	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($state, $self->{domain}, $local_part) or return -1;
+	$redirect = $mail->get_redirect( $localpart, $remote );
 
-	return 1;
-}
+Returns a C<Vhffs::Services::Mail::Redirect> from $localpart and $remote.
 
-sub get_box_status {
+=cut
+sub get_redirect {
 	my $self = shift;
-	my $local_part = shift;
-	return undef unless defined $self->{'boxes'}{$local_part};
-	return $self->{'boxes'}{$local_part}{'state'};
+	my $localpart = shift;
+	my $remote = shift;
+	return undef unless defined $localpart and defined $remote;
+	my $lp = $self->get_localpart($localpart);
+	return undef unless defined $lp;
+	return $lp->get_redirect($remote);
 }
 
-sub get_box_dir {
-	my $self = shift;
-	my $local_part = shift;
-	return undef unless defined $self and defined $local_part and defined $self->{'boxes'}{$local_part};
-
-	return( $self->get_config->get_datadir . '/mail/boxes/' . $self->{'boxes_path'} . '/' . $self->{'boxes'}{$local_part}{'mbox_name'} );
-}
-
 =pod
 
-=head2 addforward
+=head2 delete_redirect
 
-	die("Unable to add forward $local_part\n") unless($mail->addforward($local_part, $remote_address) > 0);
+	die("Unable to delete redirect $localpart to $redirect->get_redirect\n") unless $mail->delete_redirect($localpart, $redirect);
 
-Add a forward from $local_part@$mail->get_domain to $remote_address.
+Delete a redirect from $localpart@$mail->get_domain to $redirect->get_redirect.
 
 =cut
-sub addforward {
+sub delete_redirect {
 	my $self = shift;
-	my $name = shift;
-	my $remote = shift;
+	my $redirect = shift; # a C<Vhffs::Services::Mail::Redirect> object
+	return undef unless defined $redirect;
 
-	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::WAITING_FOR_DELETION )
-		||  Vhffs::Services::MailingList::address_exists($self->get_vhffs, $name, $self->{domain}) );
+	my $lp = $redirect->{localpart};
+	return undef unless defined $lp and $lp->{mail} == $self; # Do not be cheated
 
-	my $sql = 'INSERT INTO vhffs_forward(domain, local_part, remote_name, password) VALUES(?, ?, ?, \'x\')';
 	my $dbh = $self->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($self->{domain}, $name, $remote) or return -3;
+	local $dbh->{RaiseError} = 1;
+	local $dbh->{PrintError} = 0;
+	$dbh->begin_work;
 
-	$self->{'forward'}{$name}{'local_part'} = $name;
-	$self->{'forward'}{$name}{'domain'} = $self->{'domain'};
-	$self->{'forward'}{$name}{'remote_name'} = $remote;
-	$self->{forward}{$name}{password} = 'x';
+	my $lpdestroyed;
+	eval {
+		# delete redirect
+		die unless $redirect->destroy;
+		$lpdestroyed = $lp->delete;
 
-	$self->add_history( $name.'@'.$self->get_domain.' forward added to '.$remote );
+		$dbh->commit;
+	};
+
+	if($@) {
+		$dbh->rollback;
+		return undef;
+	}
+
+	# Detach localpart and redirect
+	delete $lp->{redirects}->{$redirect->get_redirect};
+	$self->{nb_redirects}--;
+	if( $lpdestroyed ) {
+		delete $self->get_localparts->{$lp->get_localpart};
+		$self->{nb_localparts}--;
+	}
+
+	$self->add_history( $lp->get_localpart.'@'.$self->get_domain.' to '.$redirect->get_redirect.' forward deleted' );
 	return 1;
 }
 
 =pod
 
-=head2 addbox
+=head2 add_box
 
-	die("Unable to create box\n") unless($mail->addbox($local_part, $password));
+	die("Unable to create box\n") unless($mail->add_box($localpart, $password);
 
 Add a new mailbox to the mail domain.
 
 =cut
-sub addbox {
+sub add_box {
 	my $self = shift;
-	my $name = shift;
+	my $localpart = 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_vhffs, $name, $self->{domain}) );
+	# $localpart is checked by Localpart class
 
+	return undef unless defined $password;
+
+	my $lp = $self->get_localpart($localpart);
+	return undef if defined $lp and defined $lp->{box};
+	my $box;
+
 	$password = password_encrypt( $password );
-	my $domainhash = $self->{boxes_path};
-	my $userhash = substr( $name, 0, 1 ).'/'.$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->get_db;
-	my $sth = $dbh->prepare($sql);
-	$sth->execute($self->{domain}, $name, $domainhash, $password, $userhash, Vhffs::Constants::WAITING_FOR_CREATION) or return -3;
+	local $dbh->{RaiseError} = 1;
+	local $dbh->{PrintError} = 0;
+	$dbh->begin_work;
 
-	$self->add_history( $name.'@'.$self->get_domain.' mail box added' );
-	return 1;
-}
+	eval {
+		# create localpart if necessary
+		unless( defined $lp ) {
+			$lp = Vhffs::Services::Mail::Localpart::create( $self, $localpart, $password );
+			die unless defined $lp;
+		}
+		# if localpart exists, update the password
+		else {
+			$lp->set_password( $password );
+			$lp->commit
+		}
 
-sub delbox {
-	my $self = shift;
-	my $name = shift;
+		# create box
+		$box = Vhffs::Services::Mail::Box::create( $lp );
+		die unless defined $box;
 
-	return -1 unless( defined $name && ( $name =~ /^[a-z0-9\_\-\.]+$/ ) );
+		$dbh->commit;
+	};
 
-	delete $self->{boxes}{$name};
+	if($@) {
+		$dbh->rollback;
+		return undef;
+	}
 
-	my $dbh = $self->get_db;
-	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});
+	# Attach localpart and box
+	unless( defined $self->get_localparts->{$localpart} ) {
+		$self->get_localparts->{$localpart} = $lp;
+		$self->{nb_localparts}++;
+	}
+	$lp->{box} = $box;
+	$self->{nb_boxes}++;
 
-	$self->add_history( $name.'@'.$self->get_domain.' mail box deleted' );
+	$self->add_history( $localpart.'@'.$self->get_domain.' mail box added' );
+	return $box;
 }
 
-sub delforward {
-	my $self = shift;
-	my $name = shift;
+=pod
 
-	return -1 unless( defined $name  && ( $name =~ /^[a-z0-9\_\-\.]+$/ ) );
-	delete $self->{forward}{$name};
+=head2 get_box
 
-	my $dbh = $self->get_db;
-	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});
+	my $box = $mail->get_box( $localpart );
 
-	$self->add_history( $name.'@'.$self->get_domain.' forward deleted' );
-}
+Returns the C<Vhffs::Services::Mail::Box> from $localpart.
 
-# Returns a hashref with all forwards
-# Ths key of this hash is the local part for the forward
-sub get_forwards {
+=cut
+sub get_box {
 	my $self = shift;
-	return $self->{'forward'};
+	my $localpart = shift;
+	return undef unless defined $localpart;
+	my $lp = $self->get_localpart($localpart);
+	return undef unless defined $lp;
+	return $lp->get_box;
 }
 
-sub exists {
-	my $self = shift;
-	my $name = shift;
-	return( $self->exists_forward($name) or $self->exists_box($name) );
-}
+=pod
 
-sub exists_forward {
-	my $self = shift;
-	my $name = shift;
-	return undef unless $name =~ /^[a-z0-9\_\-\.]+$/;
-	my $request = $self->get_db->prepare('SELECT COUNT(*) FROM vhffs_forward where domain=? AND local_part=?') or return undef;
-	$request->execute( $self->{'domain'}, $name ) or return undef;
-	my ( $rows ) = $request->fetchrow();
-	return $rows;
-}
+=head2 delete_box
 
-sub exists_box {
-	my $self = shift;
-	my $name = shift;
-	return undef unless $name =~ /^[a-z0-9\_\-\.]+$/;
-	my $request = $self->get_db->prepare('SELECT COUNT(*) FROM vhffs_boxes where domain=? AND local_part=?') or return undef;
-	$request->execute( $self->{'domain'}, $name ) or return undef;
-	my ( $rows ) = $request->fetchrow();
-	return $rows;
-}
+	die("Unable to delete box $localpart\n") unless $mail->delete_box($localpart);
 
-sub nb_boxes {
-	my $self = shift;
-	return scalar( keys( %{$self->{'boxes'}} ) );
-}
+Delete a box from $localpart@$mail->get_domain.
 
-sub nb_forwards {
+=cut
+sub delete_box {
 	my $self = shift;
-	return scalar( keys( %{$self->{'forward'}} ) );
-}
+	my $box = shift; # a C<Vhffs::Services::Mail::Box> object
+	return undef unless defined $box and $box->get_status == Vhffs::Constants::WAITING_FOR_DELETION; 
 
-# 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;
+	my $lp = $box->{localpart};
+	return undef unless defined $lp and $lp->{mail} == $self; # Do not be cheated
 
-	return 0 unless defined $self->{'boxes'}{$local_part};
-	my $dbpass = $self->{'boxes'}{$local_part}{'password'};
+	my $dbh = $self->get_db;
+	local $dbh->{RaiseError} = 1;
+	local $dbh->{PrintError} = 0;
+	$dbh->begin_work;
 
-	return ( $dbpass eq crypt( $clearpw, $dbpass ) );
-}
+	my $lpdestroyed;
+	eval {
+		# delete box
+		die unless $box->destroy;
+		$lpdestroyed = $lp->delete;
 
-# 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'};
-}
+		$dbh->commit;
+	};
 
-sub get_label {
-	my $self = shift;
-	return $self->{domain};
-}
+	if($@) {
+		$dbh->rollback;
+		return undef;
+	}
 
-sub get_domain {
-	my $self = shift;
-	return $self->{'domain'};
+	# Detach localpart and box
+	delete $lp->{box};
+	$self->{nb_boxes}--;
+	if( $lpdestroyed ) {
+		delete $self->get_localparts->{$lp->get_localpart};
+		$self->{nb_localparts}--;
+	}
+
+	$self->add_history( $lp->get_localpart.'@'.$self->get_domain.' mail box deleted' );
+	return 1;
 }
 
-###########################
-# 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 	undef if error
-# 			0 if nospam is used
-#			1 if spam is used
-sub use_nospam {
+=pod
+
+=head2 add_catchall
+
+	die("Unable to create catchall\n") unless($mail->add_catchall($boxname);
+
+Add a new catchall box to the mail domain.
+
+=cut
+sub add_catchall {
 	my $self = shift;
-	my $box = shift;
-	return undef unless defined $self->{'boxes'}->{$box};
-	return $self->{'boxes'}->{$box}{'nospam'};
-}
+	my $box = shift; # a C<Vhffs::Services::Mail::Box> object, belonging to this domain or not
 
-sub use_novirus {
-	my $self = shift;
-	my $box = shift;
-	return undef unless defined $self->{'boxes'}->{$box};
-	return $self->{'boxes'}->{$box}{'novirus'};
+	return unless defined $box;
+	return if $self->get_catchall($box);
+
+	# Is catchall allowed ?
+	return if $self->{conf_allowed_catchall} == CATCHALL_ALLOW_NONE;
+	return if $self->{conf_allowed_catchall} == CATCHALL_ALLOW_DOMAIN and $box->{localpart}->{mail}->get_domain ne $self->get_domain;
+
+	# create catchcall
+	my $catchall = Vhffs::Services::Mail::Catchall::create( $self, $box );
+	return unless defined $catchall;
+
+	# Attach catchall
+	$self->get_catchalls->{$catchall->get_catchall} = $catchall;
+	$self->{nb_catchalls}++;
+
+	$self->add_history( $catchall->get_catchall.' catchall box added' );
+	return $catchall;
 }
 
-sub get_boxespath {
+=head2 get_catchalls
+
+	my $catchalls = $mail->get_catchalls;
+
+Returns a hashref with all catchalls
+The key of this hash is the full catchall boxname local@domaine.
+
+=cut
+sub get_catchalls {
 	my $self = shift;
-	return $self->{'boxes_path'};
+	return $self->{catchall};
 }
 
+=head2 get_catchall
+
+	my $catchall = $mail->get_catchall( $boxname );
+
+Returns a C<Vhffs::Services::Mail::Localpart> from $localname.
+
+=cut
 sub get_catchall {
 	my $self = shift;
-	return $self->{'catchall'};
+	my $catchall = shift;
+	return undef unless defined $catchall and defined $self->get_catchalls;
+	return $self->get_catchalls->{$catchall};
 }
 
-sub set_catchall {
-	my( $self , $value ) = @_;
-	return -1 unless ( $value eq '' or Vhffs::Functions::valid_mail( $value ) );
-	$self->{'catchall'} = $value;
+=pod
+
+=head2 delete_catchall
+
+	die("Unable to delete catchall $catchall\n") unless $mail->delete_catchall($catchall);
+
+Delete a catchall from $mail->get_domain.
+
+=cut
+sub delete_catchall {
+	my $self = shift;
+	my $catchall = shift; # a C<Vhffs::Services::Mail::Catchall> object
+	return undef unless defined $catchall and $catchall->{mail} == $self; # Do not be cheated
+
+	return unless $catchall->destroy;
+	delete $self->get_catchalls->{$catchall->get_catchall};
+	$self->{nb_catchalls}--;
+
+	$self->add_history( $catchall->get_catchall.' catchall box deleted' );
 	return 1;
 }
 

Modified: trunk/vhffs-api/src/Vhffs/Services/MailGroup.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Services/MailGroup.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Services/MailGroup.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -54,97 +54,107 @@
 	my $vhffs = shift;
 	my $group = shift;
 
-	return undef unless defined $vhffs and defined $group and $vhffs->get_config->get_service_availability('mailgroup');
+	return unless defined $vhffs and defined $group and $vhffs->get_config->get_service_availability('mailgroup');
 	my $config = $vhffs->get_config->get_service('mailgroup');
 
 	# Fetches the mail domain defined in config
-	my $mail_service = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $config->{domain} );
-	return undef unless defined $mail_service;
+	my $mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $config->{domain} );
+	return unless defined $mail;
 
+	# Fetch only the localpart we need
+	my $lp = $mail->fetch_localpart( $group->get_groupname );	
+
 	my $this = {};
 	$this->{vhffs} = $vhffs;
-	$this->{config} = $config;
 	$this->{group} = $group;
-	$this->{localpart} = $group->get_groupname;
-	$this->{domain} = $config->{domain};
-	$this->{mail_service} = $mail_service;
+	$this->{mail} = $mail;
 	bless( $this, $class );
 	return $this;
 }
 
-sub exists {
+=head2 get_config
+
+See C<Vhffs::Object::get_config>.
+
+=cut
+sub get_config {
 	my $self = shift;
-	return $self->{mail_service}->exists( $self->{localpart} );
+	return $self->{vhffs}->get_config->get_service('mailgroup');
 }
 
-sub exists_forward {
+sub use_nospam {
 	my $self = shift;
-	return $self->{mail_service}->exists_forward( $self->{localpart} );
+	return $self->{mail}->get_config->{use_nospam};
 }
 
-sub exists_box {
+sub use_novirus {
 	my $self = shift;
-	return $self->{mail_service}->exists_box( $self->{localpart} );
+	return $self->{mail}->get_config->{use_novirus};
 }
 
-sub use_nospam {
+sub get_domain {
 	my $self = shift;
-	return $self->{mail_service}->use_nospam( $self->{localpart} );
+	return $self->{mail}->get_domain;
 }
 
-sub use_novirus {
+sub get_localpart {
 	my $self = shift;
-	return $self->{mail_service}->use_novirus( $self->{localpart} );
+	return $self->{mail}->get_localpart( $self->{group}->get_groupname );
 }
 
-sub change_spam_status {
+sub get_redirect {
 	my $self = shift;
-	$self->{mail_service}->change_spam_status( $self->{localpart} );
-	return 1;
+	my $redirects = $self->{mail}->get_redirects( $self->{group}->get_groupname );
+	return unless defined $redirects;
+	return ((values %{$redirects})[0]);
 }
 
-sub change_virus_status {
+sub get_box {
 	my $self = shift;
-	$self->{mail_service}->change_virus_status( $self->{localpart} );
-	return 1;
+	return $self->{mail}->get_box( $self->{group}->get_groupname );
 }
 
-sub addforward {
+sub add_redirect {
 	my $self = shift;
 	my $remote = shift;
-
-	$self->{mail_service}->delforward( $self->{localpart} ) if $self->exists_forward;
-	$self->{mail_service}->set_box_status( $self->{localpart}, Vhffs::Constants::WAITING_FOR_DELETION ) if $self->exists_box;
-	return $self->{mail_service}->addforward( $self->{localpart}, $remote );
+	my $redirect = $self->get_redirect;
+	if( defined $redirect ) {
+		return unless $redirect->set_redirect( $remote );
+		return unless $redirect->commit;
+		return $redirect;
+	}
+	$redirect = $self->{mail}->add_redirect( $self->{group}->get_groupname, $remote );
+	$self->delete_box if defined $redirect;
+	return $redirect;
 }
 
-sub getforward {
+sub add_box {
 	my $self = shift;
-	return undef unless $self->{mail_service}->{'forward'}->{$self->{localpart}};
-	return $self->{mail_service}->{'forward'}->{$self->{localpart}}->{'remote_name'};
+	my $password = shift;
+	my $box = $self->{mail}->add_box( $self->{group}->get_groupname, $password );
+	$self->delete_redirect if defined $box;
+	return $box;
 }
 
-sub delforward {
+sub delete_redirect {
 	my $self = shift;
-	$self->{mail_service}->delforward( $self->{localpart} );
+	my $redirect = $self->get_redirect;
+	return unless defined $redirect;
+	return $redirect->delete;
 }
 
-sub delbox {
+sub delete_box {
 	my $self = shift;
-	$self->{mail_service}->set_box_status( $self->{localpart}, Vhffs::Constants::WAITING_FOR_DELETION );
+	my $box = $self->get_box;
+	return unless defined $box;
+	$box->set_status( Vhffs::Constants::WAITING_FOR_DELETION );
+	return $box->commit;
 }
 
-sub addbox {
+sub delete {
 	my $self = shift;
-	my $password = shift;
-
-	$self->{mail_service}->delforward( $self->{localpart} ) if $self->exists_forward;
-	return $self->{mail_service}->addbox( $self->{localpart}, $password );
+	$self->delete_redirect;
+	$self->delete_box;
 }
 
-sub changepassword {
-	my ($self, $newpass) = @_;
-	return $self->{mail_service}->change_box_password( $self->{localpart}, $newpass );
-}
-
 1;

Modified: trunk/vhffs-api/src/Vhffs/Services/MailUser.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Services/MailUser.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Services/MailUser.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -54,105 +54,109 @@
 	my $vhffs = shift;
 	my $user = shift;
 
-	return undef unless defined $vhffs and defined $user and $vhffs->get_config->get_service_availability('mailuser');
+	return unless defined $vhffs and defined $user and $vhffs->get_config->get_service_availability('mailuser');
 	my $config = $vhffs->get_config->get_service('mailuser');
 
 	# Fetches the mail domain defined in config
-	my $mail_service = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $config->{domain} );
-	return undef unless defined $mail_service;
+	my $mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $config->{domain} );
+	return unless defined $mail;
 
-	return undef unless( not $config->{groupneeded} or $user->have_activegroups > 0 or $mail_service->exists($user->get_username) );
+	# Fetch only the localpart we need
+	my $lp = $mail->fetch_localpart( $user->get_username );	
 
+	return unless( not $config->{groupneeded} or $user->have_activegroups > 0 or defined $lp );
+
 	my $this = {};
 	$this->{vhffs} = $vhffs;
-	$this->{config} = $config;
 	$this->{user} = $user;
-	$this->{localpart} = $user->get_username;
-	$this->{domain} = $config->{domain};
-	$this->{mail_service} = $mail_service;
+	$this->{mail} = $mail;
 	bless( $this, $class );
 	return $this;
 }
 
-sub exists {
-	my $self = shift;
-	return $self->{mail_service}->exists( $self->{localpart} );
-}
+=head2 get_config
 
-sub exists_forward {
-	my $self = shift;
-	return $self->{mail_service}->exists_forward( $self->{localpart} );
-}
+See C<Vhffs::Object::get_config>.
 
-sub exists_box {
+=cut
+sub get_config {
 	my $self = shift;
-	return $self->{mail_service}->exists_box( $self->{localpart} );
+	return $self->{vhffs}->get_config->get_service('mailuser');
 }
 
 sub use_nospam {
 	my $self = shift;
-	return $self->{mail_service}->use_nospam( $self->{localpart} );
+	return $self->{mail}->get_config->{use_nospam};
 }
 
 sub use_novirus {
 	my $self = shift;
-	return $self->{mail_service}->use_novirus( $self->{localpart} );
+	return $self->{mail}->get_config->{use_novirus};
 }
 
-sub change_spam_status {
+sub get_domain {
 	my $self = shift;
-	$self->{mail_service}->change_spam_status( $self->{localpart} );
-	return 1;
+	return $self->{mail}->get_domain;
 }
 
-sub change_virus_status {
+sub get_localpart {
 	my $self = shift;
-	$self->{mail_service}->change_virus_status( $self->{localpart} );
-	return 1;
+	return $self->{mail}->get_localpart( $self->{user}->get_username );
 }
 
-sub get_box_status {
+sub get_redirect {
 	my $self = shift;
-	return undef unless defined $self->{mail_service}->{'boxes'}->{$self->{localpart}};
-	return $self->{mail_service}->{'boxes'}->{$self->{localpart}}{'state'};
+	my $redirects = $self->{mail}->get_redirects( $self->{user}->get_username );
+	return unless defined $redirects;
+	return ((values %{$redirects})[0]);
 }
 
-sub addforward {
+sub get_box {
 	my $self = shift;
-	my $remote = shift;
-
-	$self->{mail_service}->delforward( $self->{localpart} ) if $self->exists_forward;
-	$self->{mail_service}->set_box_status( $self->{localpart}, Vhffs::Constants::WAITING_FOR_DELETION ) if $self->exists_box;
-	return $self->{mail_service}->addforward( $self->{localpart}, $remote );
+	return $self->{mail}->get_box( $self->{user}->get_username );
 }
 
-sub getforward {
+sub add_redirect {
 	my $self = shift;
-	return undef unless $self->{mail_service}->{'forward'}->{$self->{localpart}};
-	return $self->{mail_service}->{'forward'}->{$self->{localpart}}->{'remote_name'};
+	my $remote = shift;
+	my $redirect = $self->get_redirect;
+	if( defined $redirect ) {
+		return unless $redirect->set_redirect( $remote );
+		return unless $redirect->commit;
+		return $redirect;
+	}
+	$redirect = $self->{mail}->add_redirect( $self->{user}->get_username, $remote );
+	$self->delete_box if defined $redirect;
+	return $redirect;
 }
 
-sub delforward {
+sub add_box {
 	my $self = shift;
-	$self->{mail_service}->delforward( $self->{localpart} );
+	my $password = shift;
+	my $box = $self->{mail}->add_box( $self->{user}->get_username, $password );
+	$self->delete_redirect if defined $box;
+	return $box;
 }
 
-sub delbox {
+sub delete_redirect {
 	my $self = shift;
-	$self->{mail_service}->set_box_status( $self->{localpart}, Vhffs::Constants::WAITING_FOR_DELETION );
+	my $redirect = $self->get_redirect;
+	return unless defined $redirect;
+	return $redirect->delete;
 }
 
-sub addbox {
+sub delete_box {
 	my $self = shift;
-	my $password = shift;
-
-	$self->{mail_service}->delforward( $self->{localpart} ) if $self->exists_forward;
-	return $self->{mail_service}->addbox( $self->{localpart}, $password );
+	my $box = $self->get_box;
+	return unless defined $box;
+	$box->set_status( Vhffs::Constants::WAITING_FOR_DELETION );
+	return $box->commit;
 }
 
-sub changepassword {
-	my ($self, $newpass) = @_;
-	return $self->{mail_service}->change_box_password( $self->{localpart}, $newpass );
+sub delete {
+	my $self = shift;
+	$self->delete_redirect;
+	$self->delete_box;
 }
 
 1;

Modified: trunk/vhffs-api/src/Vhffs/Services/MailingList.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Services/MailingList.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Services/MailingList.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -49,6 +49,7 @@
 
 use strict;
 use utf8;
+use Vhffs::Services::Mail;
 
 package Vhffs::Services::MailingList;
 
@@ -56,13 +57,13 @@
 use DBI;
 
 sub _new {
-	my ($class, $vhffs, $ml_id, $local_part, $domain, $prefix, $owner_gid, $open_archive, $reply_to, $sub_ctrl, $post_ctrl, $signature, $oid, $owner_uid, $date_creation, $state, $description, $subs) = @_;
+	my ($class, $vhffs, $ml_id, $domain, $localpart, $prefix, $owner_gid, $open_archive, $reply_to, $sub_ctrl, $post_ctrl, $signature, $oid, $owner_uid, $date_creation, $state, $description) = @_;
 
 	my $self = $class->SUPER::_new($vhffs, $oid, $owner_uid, $owner_gid, $date_creation, $description, '', $state, Vhffs::Constants::TYPE_ML);
 	return undef unless defined($self);
 
 	$self->{ml_id} = $ml_id;
-	$self->{local_part} = $local_part;
+	$self->{localpart} = $localpart;
 	$self->{domain} = $domain,
 	$self->{prefix} = $prefix;
 	$self->{open_archive} = $open_archive;
@@ -70,7 +71,7 @@
 	$self->{sub_ctrl} = $sub_ctrl;
 	$self->{post_ctrl} = $post_ctrl;
 	$self->{signature} = $signature;
-	$self->{subs} = $subs;
+	$self->{subs} = {};
 
 	return $self;
 }
@@ -79,48 +80,46 @@
 
 =head2 create
 
-	my $ml = Vhffs::Services::MailingList::create($local, $domain, $description, $user, $group);
+	my $ml = Vhffs::Services::MailingList::create($vhffs, $localpart, $description, $user, $group);
 	die('Unable to create list') unless defined $ml;
 
 Creates a new mailing list in database and returns the corresponding fully functional object.
 Returns undef if an error occurs (box, forward or mailing list with the same address already
 exists, domain not found, ...).
 
+$localpart must be a previously created C<Vhffs::Services::Mail::Localpart>
+
 =cut
 sub create {
-	my ($vhffs, $local, $domain, $description, $user, $group) = @_;
+	my ($vhffs, $mail, $localpart, $description, $user, $group) = @_;
+	return unless defined $mail and defined $localpart and defined $user and defined $group;
 
-	return undef unless(defined $user && defined $group);
-	return undef unless($local =~ /^[a-z0-9\_\-]+$/);
-	return undef unless(Vhffs::Functions::check_domain_name($domain));
-	return undef if(Vhffs::Services::Mail::address_exists($vhffs, $local, $domain));
-
 	my $ml;
 
-	my $dbh = $vhffs->get_db();
+	my $dbh = $vhffs->get_db;
 	local $dbh->{RaiseError} = 1;
 	local $dbh->{PrintError} = 0;
 	$dbh->begin_work;
 
 	eval {
-		# Group must be the mail domain owner or use default mail domain.
-		my $sql = 'SELECT mxdomain_id FROM vhffs_mxdomain m INNER JOIN vhffs_object o ON o.object_id = m.object_id WHERE m.domain = ? AND o.owner_gid = ?';
-		die('Mail domain not found') unless($domain eq $vhffs->get_config()->get_service('mailinglist')->{'default_domain'} ||
-			$dbh->do($sql, undef, $domain, $group->get_gid) > 0);
-
 		my $parent = Vhffs::Object::create($vhffs, $user->get_uid, $group->get_gid, $description, undef, Vhffs::Constants::TYPE_ML);
-		die('Unable to create parent object') unless(defined $parent);
+		die('Unable to create parent object') unless defined $parent;
 
-	# open sub, post members only
-		$sql = 'INSERT INTO vhffs_ml(local_part, domain, prefix, object_id, open_archive, reply_to, sub_ctrl, post_ctrl) VALUES(?, ?, ?, ?, FALSE, TRUE, ?, ? )';
+		# Create a new mail localpart if required
+		my $lp = $mail->fetch_localpart( $localpart );
+		$lp = Vhffs::Services::Mail::Localpart::create( $mail, $localpart ) unless defined $lp;
+		die('Unable to create mail localpart object') unless defined $lp;
+
+		# open sub, post members only
+		my $sql = 'INSERT INTO vhffs_mx_ml(localpart_id, prefix, object_id, open_archive, reply_to, sub_ctrl, post_ctrl) VALUES (?, ?, ?, FALSE, TRUE, ?, ? )';
 		my $sth = $dbh->prepare($sql);
-		$sth->execute($local, $domain, $local, $parent->get_oid, Vhffs::Constants::ML_SUBSCRIBE_NO_APPROVAL_REQUIRED, Vhffs::Constants::ML_POSTING_MEMBERS_ONLY);
+		$sth->execute($lp->{localpart_id}, $lp->get_localpart, $parent->get_oid, Vhffs::Constants::ML_SUBSCRIBE_NO_APPROVAL_REQUIRED, Vhffs::Constants::ML_POSTING_MEMBERS_ONLY);
 		$dbh->commit;
-		$ml = get_by_mladdress($vhffs, $local, $domain);
+		$ml = get_by_mladdress($vhffs, $localpart, $mail->get_domain);
 	};
 
 	if($@) {
-		warn 'Unable to create mailing list '.$local.'@'.$domain.': '.$@."\n";
+		warn 'Unable to create mailing list '.$localpart.'@'.$mail->get_domain.': '.$@."\n";
 		$dbh->rollback;
 	}
 
@@ -134,13 +133,12 @@
 =cut
 sub fill_object {
 	my ($class, $obj) = @_;
-	my $sql = q{SELECT ml_id, local_part, domain, prefix, open_archive,
-		reply_to, sub_ctrl, post_ctrl, signature FROM vhffs_ml
-		WHERE object_id = ?};
+	my $sql = q{SELECT ml.ml_id, mx.domain, mlp.localpart, ml.prefix, o.owner_gid, ml.open_archive, ml.reply_to, ml.sub_ctrl, ml.post_ctrl, ml.signature, o.object_id, o.owner_uid, o.date_creation, o.state, o.description FROM vhffs_mx_ml ml
+		INNER JOIN vhffs_object o ON o.object_id = ml.object_id
+		INNER JOIN vhffs_mx_localpart mlp ON mlp.localpart_id=ml.localpart_id
+		INNER JOIN vhffs_mx mx ON mx.mx_id=mlp.mx_id
+		WHERE ml.object_id = ?};
 	$obj = $class->SUPER::_fill_object($obj, $sql);
-	if($obj->isa('Vhffs::Services::MailingList')) {
-		$obj->{subs} = fetch_subs($obj->get_db, $obj->{ml_id});
-	}
 	return $obj;
 }
 
@@ -150,8 +148,8 @@
 	my $mls = [];
 	my @params;
 
-	my $sql = 'SELECT ml.local_part, ml.domain
-		FROM vhffs_ml ml INNER JOIN vhffs_object o ON ml.object_id = o.object_id';
+	my $sql = 'SELECT mlp.localpart, mx.domain
+		FROM vhffs_mx_ml ml INNER JOIN vhffs_object o ON ml.object_id = o.object_id INNER JOIN vhffs_mx_localpart mlp ON mlp.localpart_id=ml.localpart_id INNER JOIN vhffs_mx mx ON mx.mx_id=mlp.mx_id';
 	if(defined $state) {
 		$sql .= ' AND o.state = ?';
 		push @params, $state;
@@ -161,14 +159,14 @@
 		push @params, $group->get_gid;
 	}
 	if(defined $name) {
-		$sql .= ' AND ( local_part LIKE ? OR domain LIKE ?)';
+		$sql .= ' AND ( mlp.localpart LIKE ? OR mx.domain LIKE ?)';
 		push @params, '%'.$name.'%', '%'.$name.'%';
 	}
 	if(defined $domain) {
-		$sql .= ' AND ml.domain = ?';
+		$sql .= ' AND mx.domain = ?';
 		push @params, $domain;
 	}
-	$sql .= ' ORDER BY ml.local_part, ml.domain';
+	$sql .= ' ORDER BY mlp.localpart, mx.domain';
 
 	my $dbh = $vhffs->get_db;
 	my $sth = $dbh->prepare($sql);
@@ -184,49 +182,85 @@
 
 =head2 get_by_mladdress
 
-	my $ml = Vhffs::Services::MailingList::get_by_mladdress($vhffs, $local_part, $domain);
+	my $ml = Vhffs::Services::MailingList::get_by_mladdress($vhffs, $localpart, $domain);
 	die("Mailing list $localpart\@$domain not found\n") unless(defined $ml);
 
-Fetches the mailing list $local_part@$domain.
+Fetches the mailing list $localpart@$domain.
 
 =cut
 sub get_by_mladdress {
 	my ($vhffs, $local, $domain) = @_;
 
 	my $dbh = $vhffs->get_db();
-	my $sql = 'SELECT ml.ml_id, ml.local_part, ml.domain, ml.prefix, o.owner_gid, ml.open_archive, ml.reply_to, ml.sub_ctrl, ml.post_ctrl, ml.signature, o.object_id, o.owner_uid, o.date_creation, o.state, o.description FROM vhffs_ml ml INNER JOIN vhffs_object o ON o.object_id = ml.object_id WHERE domain = ? and local_part = ?';
+	my $sql = 'SELECT ml.ml_id, mx.domain, mlp.localpart, ml.prefix, o.owner_gid, ml.open_archive, ml.reply_to, ml.sub_ctrl, ml.post_ctrl, ml.signature, o.object_id, o.owner_uid, o.date_creation, o.state, o.description FROM vhffs_mx_ml ml INNER JOIN vhffs_object o ON o.object_id = ml.object_id INNER JOIN vhffs_mx_localpart mlp ON mlp.localpart_id=ml.localpart_id INNER JOIN vhffs_mx mx ON mx.mx_id=mlp.mx_id WHERE mx.domain = ? and mlp.localpart = ?';
 	my $sth = $dbh->prepare($sql);
 	return undef unless ($sth->execute($domain, $local) > 0);
 	my @params = $sth->fetchrow_array;
+	return _new Vhffs::Services::MailingList($vhffs, @params);
+}
 
-	push @params, fetch_subs($dbh, $params[0]);
+=pod
+
+=head2 get_by_id
+
+	my $ml = Vhffs::Services::MailingList::get_by_id($vhffs, $mlid);
+	die("Mailing list not found\n") unless(defined $ml);
+
+Fetches the mailing list $mlid.
+
+=cut
+sub get_by_id {
+	my ($vhffs, $id) = @_;
+
+	my $dbh = $vhffs->get_db();
+	my $sql = 'SELECT ml.ml_id, mx.domain, mlp.localpart, ml.prefix, o.owner_gid, ml.open_archive, ml.reply_to, ml.sub_ctrl, ml.post_ctrl, ml.signature, o.object_id, o.owner_uid, o.date_creation, o.state, o.description FROM vhffs_mx_ml ml INNER JOIN vhffs_object o ON o.object_id = ml.object_id INNER JOIN vhffs_mx_localpart mlp ON mlp.localpart_id=ml.localpart_id INNER JOIN vhffs_mx mx ON mx.mx_id=mlp.mx_id WHERE ml.ml_id = ?';
+	my $sth = $dbh->prepare($sql);
+	return undef unless ($sth->execute($id) > 0);
+	my @params = $sth->fetchrow_array;
 	return _new Vhffs::Services::MailingList($vhffs, @params);
 }
 
 =head2 fetch_subs
 
-	my $subs = fetch_subs($dbh, $ml_id);
+	my $subs = $ml->fetch_subs;
 
 Returns an hashref of hashrefs containing all subscribers indexed on their
 mail addresses.
 
-Internal module use only.
-
 =cut
 sub fetch_subs {
+	my $self = shift;
 
-	my ($dbh, $ml_id) = @_;
+	my $sql = q{SELECT sub_id, member, perm, hash, ml_id, language FROM vhffs_mx_ml_subscribers WHERE ml_id = ?};
+	$self->{subs} = $self->get_db->selectall_hashref($sql, 'member', undef, $self->{ml_id});
+	return $self->{subs};
+}
 
-	my $sql = q{SELECT sub_id, member, perm, hash, ml_id, language
-		FROM vhffs_ml_subscribers WHERE ml_id = ?};
-	return $dbh->selectall_hashref($sql, 'member', undef, $ml_id);
+=head2 fetch_sub
+
+	my $sub = $ml->fetch_sub($mail);
+
+Returns a hashref containing a subscribers.
+
+=cut
+sub fetch_sub {
+	my $self = shift;
+	my $mail = shift;
+
+	my $sql = q{SELECT sub_id, member, perm, hash, ml_id, language FROM vhffs_mx_ml_subscribers WHERE ml_id = ? AND member = ?};
+	my $sth = $self->get_db->prepare($sql);
+	$sth->execute( $self->{ml_id}, $mail );
+	my $sub = $sth->fetchrow_hashref;
+	return unless $sub;
+	$self->{subs}->{$mail} = $sub;
+	return $sub;
 }
 
 # Commit all changes of the current instance in the database
 sub commit {
 	my $self = shift;
 
-	my $sql = 'UPDATE vhffs_ml SET prefix = ?, open_archive = ?, reply_to = ?, sub_ctrl = ?, post_ctrl = ?, signature = ? WHERE ml_id = ?';
+	my $sql = 'UPDATE vhffs_mx_ml SET prefix = ?, open_archive = ?, reply_to = ?, sub_ctrl = ?, post_ctrl = ?, signature = ? WHERE ml_id = ?';
 	my $dbh = $self->get_db;
 	$dbh->do($sql, undef, $self->{prefix}, $self->{open_archive}, $self->{reply_to}, $self->{sub_ctrl}, $self->{post_ctrl}, $self->{signature}, $self->{ml_id});
 
@@ -236,7 +270,7 @@
 sub change_right_for_sub {
 	my ($self, $subscriber, $right) = @_;
 
-	my $sql = 'UPDATE vhffs_ml_subscribers SET perm = ? WHERE ml_id = ? AND member = ?';
+	my $sql = 'UPDATE vhffs_mx_ml_subscribers SET perm = ? WHERE ml_id = ? AND member = ?';
 	my $dbh = $self->get_db;
 	# FIXME compatibility hack, we should return a boolean
 	return -1 unless($dbh->do($sql, undef, $right, $self->{ml_id}, $subscriber) > 0);
@@ -253,11 +287,11 @@
 	return -2 if $subscriber =~ /[<>\s]/;
 	return -3 unless $right =~ /^[\d]+$/;
 
-	my $sql = 'INSERT INTO vhffs_ml_subscribers (member, perm, hash, ml_id, language) VALUES (?, ?, NULL, ?, NULL)';
+	my $sql = 'INSERT INTO vhffs_mx_ml_subscribers (member, perm, hash, ml_id, language) VALUES (?, ?, NULL, ?, NULL)';
 	my $dbh = $self->get_db;
 	$dbh->do($sql, undef, $subscriber, $right, $self->{ml_id}) or return -4;
 
-	my $id = $dbh->last_insert_id(undef, undef, 'vhffs_ml_subscribers', undef);
+	my $id = $dbh->last_insert_id(undef, undef, 'vhffs_mx_ml_subscribers', undef);
 
 	$self->{subs}->{$subscriber} = {
 		sub_id => $id,
@@ -283,11 +317,11 @@
 
 	my $pass = Vhffs::Functions::generate_random_password();
 
-	my $sql = 'INSERT INTO vhffs_ml_subscribers(member, perm, hash, ml_id, language) VALUES(?, ?, ?, ?, NULL)';
+	my $sql = 'INSERT INTO vhffs_mx_ml_subscribers(member, perm, hash, ml_id, language) VALUES(?, ?, ?, ?, NULL)';
 	my $dbh = $self->get_db;
 	$dbh->do($sql, undef, $subscriber, Vhffs::Constants::ML_RIGHT_SUB_WAITING_FOR_REPLY, $pass, $self->{ml_id}) or return undef;
 
-	my $id = $dbh->last_insert_id(undef, undef, 'vhffs_ml_subscribers', undef);
+	my $id = $dbh->last_insert_id(undef, undef, 'vhffs_mx_ml_subscribers', undef);
 
 	$self->{subs}->{$subscriber} = {
 		sub_id => $id,
@@ -305,7 +339,7 @@
 	my $self = shift;
 	my $subscriber = shift;
 
-	my $sql = 'DELETE FROM vhffs_ml_subscribers WHERE ml_id = ? AND member = ?';
+	my $sql = 'DELETE FROM vhffs_mx_ml_subscribers WHERE ml_id = ? AND member = ?';
 	# FIXME we should return a boolean
 	return -1 unless($self->get_db->do($sql, undef, $self->{ml_id}, $subscriber) > 0);
 
@@ -318,7 +352,7 @@
 	my $subscriber = shift;
 	my $pass = Vhffs::Functions::generate_random_password();
 
-	my $sql = 'UPDATE vhffs_ml_subscribers SET hash = ? WHERE ml_id = ? AND member = ?';
+	my $sql = 'UPDATE vhffs_mx_ml_subscribers SET hash = ? WHERE ml_id = ? AND member = ?';
 	return undef unless($self->get_db->do($sql, undef, $pass, $self->{ml_id}, $subscriber) > 0);
 
 	$self->{subs}->{$subscriber}->{hash} = $pass;
@@ -329,7 +363,7 @@
 	my $self = shift;
 	my $subscriber = shift;
 
-	my $sql = 'UPDATE vhffs_ml_subscribers SET hash = NULL WHERE ml_id = ? AND member = ?';
+	my $sql = 'UPDATE vhffs_mx_ml_subscribers SET hash = NULL WHERE ml_id = ? AND member = ?';
 	# FIXME we should return a boolean
 	return -1 unless($self->get_db->do($sql, undef, $self->{ml_id}, $subscriber) > 0);
 
@@ -337,23 +371,6 @@
 	return 1;
 }
 
-=head2 address_exists
-
-	print("A mailing list with the same address already exists\n")
-		if Vhffs::Mailing::address_exists($vhffs, $local_part, $domain;
-
-Return true if a mailing list C<$local_part>@C<$domain> already exists.
-
-=cut
-sub address_exists($$$) {
-	my ($vhffs, $local_part, $domain) = @_;
-
-	my $sql = 'SELECT COUNT(*) FROM vhffs_ml WHERE local_part = ? AND domain = ?';
-	my $dbh = $vhffs->get_db();
-	my $res = $dbh->selectrow_array($sql, {}, $local_part, $domain);
-	return (defined($res) && $res > 0);
-}
-
 sub del_sub_with_reply {
 	use Digest::MD5;
 
@@ -362,7 +379,7 @@
 
 	my $hash = Digest::MD5::md5_hex( Vhffs::Functions::generate_random_password() );
 
-	my $sql = 'UPDATE vhffs_ml_subscribers SET perm = ?, hash = ? WHERE ml_id = ? AND member = ? AND perm IN (?, ?)';
+	my $sql = 'UPDATE vhffs_mx_ml_subscribers SET perm = ?, hash = ? WHERE ml_id = ? AND member = ? AND perm IN (?, ?)';
 	# FIXME we should return a boolean
 	return undef unless($self->get_db->do($sql, undef, Vhffs::Constants::ML_RIGHT_SUB_WAITING_FOR_DEL, $hash, $self->{ml_id}, $subscriber, Vhffs::Constants::ML_RIGHT_SUB, Vhffs::Constants::ML_RIGHT_ADMIN) > 0);
 
@@ -374,7 +391,7 @@
 sub get_language_for_sub {
 	my ($vhffs, $sub) = @_;
 
-	my $sql = 'SELECT language FROM vhffs_ml_subscribers WHERE member = ?';
+	my $sql = 'SELECT language FROM vhffs_mx_ml_subscribers WHERE member = ?';
 	my $lang = $vhffs->get_db->selectrow_array($sql, undef, $sub);
 	return $lang;
 }
@@ -383,13 +400,13 @@
 	my ($vhffs, $sub, $language) = @_;
 
 	$language = 'en_US' unless( $language =~ /^\w+$/ );
-	my $sql = 'UPDATE vhffs_ml_subscribers SET language = ? WHERE member = ?';
+	my $sql = 'UPDATE vhffs_mx_ml_subscribers SET language = ? WHERE member = ?';
 	$vhffs->get_db->do($sql, undef, $language, $sub) or return -1;
 }
 
 sub get_localpart {
 	my $self = shift;
-	return $self->{'local_part'};
+	return $self->{'localpart'};
 }
 
 sub get_signature {
@@ -484,7 +501,7 @@
 =cut
 sub get_label {
 	my $self = shift;
-	return $self->{local_part}.'@'.$self->{domain};
+	return $self->{localpart}.'@'.$self->{domain};
 }
 
 sub get_listname {

Modified: trunk/vhffs-api/src/Vhffs/Services/Newsletter.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Services/Newsletter.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Services/Newsletter.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -64,6 +64,7 @@
 	if( defined $config->{'mailinglist'} )  {
 		my ( $localpart, $domain ) = ( $config->{'mailinglist'} =~ /(.+)\@(.+)/ );
 		$mailinglist_service = Vhffs::Services::MailingList::get_by_mladdress( $vhffs, $localpart, $domain );
+		$mailinglist_service->fetch_sub( $user->get_mail );
 	}
 	return undef unless defined $mailinglist_service;
 

Modified: trunk/vhffs-api/src/Vhffs/Stats.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/Stats.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/Stats.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -111,7 +111,7 @@
 sub get_lists_totalsubs {
 	my $self = shift;
 	unless(defined $self->{stats}->{lists}->{totalsubs}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_ml_subscribers';
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx_ml_subscribers';
 		($self->{stats}->{lists}->{totalsubs}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql )};
 	}
 	return $self->{stats}->{lists}->{totalsubs};
@@ -129,7 +129,7 @@
 sub get_lists_in_moderation {
 	my $self = shift;
 	unless(defined $self->{stats}->{lists}->{awaiting_validation}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_ml ml, vhffs_object o WHERE o.object_id=ml.object_id AND o.state='.Vhffs::Constants::WAITING_FOR_VALIDATION;
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx_ml ml, vhffs_object o WHERE o.object_id=ml.object_id AND o.state='.Vhffs::Constants::WAITING_FOR_VALIDATION;
 		($self->{stats}->{lists}->{awaiting_validation}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql )};
 	}
 	return $self->{stats}->{lists}->{awaiting_validation};
@@ -147,7 +147,7 @@
 sub get_lists_activated {
 	my $self = shift;
 	unless(defined $self->{stats}->{lists}->{activated}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_ml ml, vhffs_object o WHERE o.object_id=ml.object_id AND o.state = ?';
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx_ml ml, vhffs_object o WHERE o.object_id=ml.object_id AND o.state = ?';
 		($self->{stats}->{lists}->{activated}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql, undef, Vhffs::Constants::ACTIVATED )};
 	}
 	return $self->{stats}->{lists}->{activated};
@@ -165,7 +165,7 @@
 sub get_lists_total {
 	my $self = shift;
 	unless(defined $self->{stats}->{lists}->{total}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_ml';
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx_ml';
 		($self->{stats}->{lists}->{total}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql )};
 	}
 	return $self->{stats}->{lists}->{total};
@@ -310,7 +310,7 @@
 sub get_mail_in_moderation {
 	my $self = shift;
 	unless(defined $self->{stats}->{mail}->{awaiting_validation}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_mxdomain w INNER JOIN vhffs_object o ON o.object_id=w.object_id WHERE o.state = ?';
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx w INNER JOIN vhffs_object o ON o.object_id=w.object_id WHERE o.state = ?';
 		($self->{stats}->{mail}->{awaiting_validation}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql, undef, Vhffs::Constants::WAITING_FOR_VALIDATION )};
 	}
 	return $self->{stats}->{mail}->{awaiting_validation};
@@ -328,7 +328,7 @@
 sub get_mail_activated {
 	my $self = shift;
 	unless(defined $self->{stats}->{mail}->{activated}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_mxdomain w INNER JOIN vhffs_object o ON o.object_id=w.object_id WHERE o.state = ?';
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx w INNER JOIN vhffs_object o ON o.object_id=w.object_id WHERE o.state = ?';
 		($self->{stats}->{mail}->{activated}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql, undef, Vhffs::Constants::ACTIVATED )};
 	}
 	return $self->{stats}->{mail}->{activated};
@@ -346,7 +346,7 @@
 sub get_mail_total_boxes {
 	my $self = shift;
 	unless(defined $self->{stats}->{mail}->{total_boxes}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_boxes';
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx_box';
 		($self->{stats}->{mail}->{total_boxes}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql )};
 	}
 	return $self->{stats}->{mail}->{total_boxes};
@@ -354,20 +354,20 @@
 
 =pod
 
-=head2 get_mail_total_forwards
+=head2 get_mail_total_redirects
 
-	my $count = $stats->get_mail_total_forwards();
+	my $count = $stats->get_mail_total_redirects();
 
 Returns the total count of mail forwards.
 
 =cut
-sub get_mail_total_forwards {
+sub get_mail_total_redirects {
 	my $self = shift;
-	unless(defined $self->{stats}->{mail}->{total_forwards}) {
-		my $sql = 'SELECT COUNT(*) FROM vhffs_forward';
-		($self->{stats}->{mail}->{total_forwards}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql )};
+	unless(defined $self->{stats}->{mail}->{total_redirects}) {
+		my $sql = 'SELECT COUNT(*) FROM vhffs_mx_redirect';
+		($self->{stats}->{mail}->{total_redirects}) = @{$self->{vhffs}->get_db->selectrow_arrayref( $sql )};
 	}
-	return $self->{stats}->{mail}->{total_forwards};
+	return $self->{stats}->{mail}->{total_redirects};
 }
 
 =pod

Modified: trunk/vhffs-api/src/Vhffs/User.pm
===================================================================
--- trunk/vhffs-api/src/Vhffs/User.pm	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-api/src/Vhffs/User.pm	2012-04-11 22:01:48 UTC (rev 2145)
@@ -405,10 +405,7 @@
 	# delete mail user if mail_user is enabled
 	use Vhffs::Services::MailUser;
 	my $mu = new Vhffs::Services::MailUser( $self->get_vhffs, $self );
-	if( defined $mu ) {
-		$mu->delbox;
-		$mu->delforward;
-	}
+	$mu->delete if defined $mu;
 
 	# remove subscription from newsletter
 	use Vhffs::Services::Newsletter;

Modified: trunk/vhffs-backend/src/mirror/mx1-mirror.pl
===================================================================
--- trunk/vhffs-backend/src/mirror/mx1-mirror.pl	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-backend/src/mirror/mx1-mirror.pl	2012-04-11 22:01:48 UTC (rev 2145)
@@ -32,7 +32,7 @@
 # Mirroring script for exim on mx1.
 # Set master & slave DB params and put it in a cron.
 # Slave database must have at least vhffs_mxdomain,
-# vhffs_boxes, vhffs_forward, vhffs_ml, vhffs_ml_subscribers
+# vhffs_boxes, vhffs_forward, vhffs_ml, vhffs_mx_ml_subscribers
 # and vhffs_object tables
 
 use DBI;
@@ -73,7 +73,7 @@
     or die("Unable to create temporary forward table\n");
 $slave_dbh->do('CREATE TEMPORARY TABLE tmp_ml(LIKE vhffs_ml)')
     or die("Unable to create temporary ml table\n");
-$slave_dbh->do('CREATE TEMPORARY TABLE tmp_ml_subscribers(LIKE vhffs_ml_subscribers)')
+$slave_dbh->do('CREATE TEMPORARY TABLE tmp_ml_subscribers(LIKE vhffs_mx_ml_subscribers)')
     or die("Unable to create temporary ml_subscribers table\n");
 $slave_dbh->do('CREATE TEMPORARY TABLE tmp_object(LIKE vhffs_object)')
     or die("Unable to create temporary object table\n");
@@ -221,17 +221,17 @@
 
 $msth = $master_dbh->prepare(q{SELECT ms.sub_id, ms.member, ms.perm, ms.hash,
     ms.ml_id, ms.language
-    FROM vhffs_ml_subscribers ms
+    FROM vhffs_mx_ml_subscribers ms
     INNER JOIN vhffs_ml ml ON ms.ml_id = ml.ml_id
     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_subscribers\n");
+    or die("Unable to prepare SELECT query for vhffs_mx_ml_subscribers\n");
 $ssth = $slave_dbh->prepare(q{INSERT INTO tmp_ml_subscribers(sub_id, member,
     perm, hash, ml_id, language) VALUES(?, ?, ?, ?, ?, ?)})
     or die("Unable to prepare INSERT query for tmp_ml_subscribers\n");
 
 $msth->execute()
-    or die("Unable to execute SELECT query for vhffs_ml_subscribers\n");
+    or die("Unable to execute SELECT query for vhffs_mx_ml_subscribers\n");
 
 while(my $row = $msth->fetchrow_hashref()) {
     $ssth->execute($row->{sub_id}, $row->{member}, $row->{perm}, $row->{hash},
@@ -247,7 +247,7 @@
 
 my $count;
 
-($count = $slave_dbh->do(q{DELETE FROM vhffs_ml_subscribers WHERE
+($count = $slave_dbh->do(q{DELETE FROM vhffs_mx_ml_subscribers WHERE
     sub_id NOT IN (SELECT sub_id FROM tmp_ml_subscribers)}))
     or die("Unable to delete no more existing ml users\n");
 print "$count subscribers deleted\n";
@@ -309,9 +309,9 @@
     tmp.domain = vhffs_forward.domain})
     or die("Unable to update forwards data\n");
 
-$slave_dbh->do(q{UPDATE vhffs_ml_subscribers SET perm = tmp.perm,
+$slave_dbh->do(q{UPDATE vhffs_mx_ml_subscribers SET perm = tmp.perm,
     hash = tmp.hash, language = tmp.language FROM tmp_ml_subscribers tmp
-    WHERE tmp.sub_id = vhffs_ml_subscribers.sub_id})
+    WHERE tmp.sub_id = vhffs_mx_ml_subscribers.sub_id})
     or die("Unable to update subscribers data\n");
 
 $slave_dbh->do(q{UPDATE vhffs_ml SET prefix = tmp.prefix,
@@ -364,10 +364,10 @@
     or die("Unable to insert new ml\n");
 print "$count mailing lists inserted\n";
 
-($count = $slave_dbh->do(q{INSERT INTO vhffs_ml_subscribers(sub_id, member, perm, hash,
+($count = $slave_dbh->do(q{INSERT INTO vhffs_mx_ml_subscribers(sub_id, member, perm, hash,
     ml_id, language) SELECT sub_id, member, perm, hash, ml_id, language FROM
     tmp_ml_subscribers ms WHERE ms.sub_id NOT IN(SELECT sub_id FROM
-    vhffs_ml_subscribers)}))
+    vhffs_mx_ml_subscribers)}))
     or die("Unable to insert new subscribers\n");
 print "$count subscribers inserted\n";
 $slave_dbh->commit();

Modified: trunk/vhffs-backend/src/mirror/mx1-mirror.sql
===================================================================
--- trunk/vhffs-backend/src/mirror/mx1-mirror.sql	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-backend/src/mirror/mx1-mirror.sql	2012-04-11 22:01:48 UTC (rev 2145)
@@ -48,7 +48,7 @@
 ) WITH OIDS;
 
 
-CREATE TABLE vhffs_ml_subscribers
+CREATE TABLE vhffs_mx_ml_subscribers
 (
     sub_id integer,
     member varchar(256) NOT NULL,
@@ -56,7 +56,7 @@
     hash varchar,
     ml_id int4 NOT NULL,
     language varchar(16),
-    CONSTRAINT vhffs_ml_subscribers_pkey PRIMARY KEY (sub_id)
+    CONSTRAINT vhffs_mx_ml_subscribers_pkey PRIMARY KEY (sub_id)
 ) WITH OIDS;
 
 CREATE TABLE vhffs_object
@@ -89,11 +89,11 @@
 ALTER TABLE vhffs_forward ADD CONSTRAINT fk_vhffs_forward_vhffs_mxdomain FOREIGN KEY (domain) REFERENCES vhffs_mxdomain(domain) ON DELETE CASCADE;
 ALTER TABLE vhffs_ml ADD CONSTRAINT fk_vhffs_ml_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 ALTER TABLE vhffs_ml ADD CONSTRAINT fk_vhffs_ml_vhffs_mxdomain FOREIGN KEY (domain) REFERENCES vhffs_mxdomain(domain) ON DELETE CASCADE;
-ALTER TABLE vhffs_ml_subscribers ADD CONSTRAINT fk_vhffs_ml_subscribers_vhffs_ml FOREIGN KEY (ml_id) REFERENCES vhffs_ml(ml_id) ON DELETE CASCADE;
+ALTER TABLE vhffs_mx_ml_subscribers ADD CONSTRAINT fk_vhffs_mx_ml_subscribers_vhffs_ml FOREIGN KEY (ml_id) REFERENCES vhffs_ml(ml_id) ON DELETE CASCADE;
 ALTER TABLE vhffs_mxdomain ADD CONSTRAINT fk_vhffs_mxdomain_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 
 -- unique constraints (implicitely create indices)
 ALTER TABLE vhffs_mxdomain ADD CONSTRAINT vhffs_mxdomain_unique_domainname UNIQUE (domain);
 ALTER TABLE vhffs_ml ADD CONSTRAINT vhffs_ml_unique_address UNIQUE (local_part, domain);
-ALTER TABLE vhffs_ml_subscribers ADD CONSTRAINT vhffs_ml_subscribers_member_list UNIQUE (ml_id, member);
+ALTER TABLE vhffs_mx_ml_subscribers ADD CONSTRAINT vhffs_mx_ml_subscribers_member_list UNIQUE (ml_id, member);
 

Modified: trunk/vhffs-backend/src/pgsql/initdb.sql.in
===================================================================
--- trunk/vhffs-backend/src/pgsql/initdb.sql.in	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-backend/src/pgsql/initdb.sql.in	2012-04-11 22:01:48 UTC (rev 2145)
@@ -127,31 +127,6 @@
 
 SELECT setval('vhffs_users_uid_seq', @MINUID@) ;
 
--- Mailboxes hosted on this platform
-CREATE TABLE vhffs_boxes (
--- Maildomain to which the box is attached
-	domain varchar,
--- Left part of the address
-	local_part varchar,
--- Domain hash to compute box path (should'n we store it in mxdomain?)
-	domain_hash varchar NOT NULL,
--- Box name appended to domain_hash
-	mbox_name varchar NOT NULL,
--- Box password
-	password varchar NOT NULL,
--- Is antispam activated
-	nospam boolean,
--- Is antivirus activated?
-	novirus boolean,
--- Allow pop login ?
-	allowpop boolean NOT NULL,
--- Allow imap login ?
-	allowimap boolean NOT NULL,
--- State of the box (we don't have object for this entity...)
-	state int4 NOT NULL,
-	CONSTRAINT vhffs_boxes_pkey PRIMARY KEY (domain,local_part)
-) WITH (OIDS);
-
 -- CVS repos on this platform
 CREATE TABLE vhffs_cvs (
 	cvs_id serial,
@@ -196,19 +171,6 @@
 	CONSTRAINT vhffs_dns_rr_pkey PRIMARY KEY (id)
 ) WITH (OIDS);	
 
--- Mail forwards on this platform
-CREATE TABLE vhffs_forward (
--- Mail domain to which this forward is linked
-	domain varchar NOT NULL,
--- Left part of the mail address
-	local_part varchar NOT NULL,
--- Mail address to which mails are forwarded
-	remote_name varchar NOT NULL,
--- Is this used?!
-	password varchar NOT NULL,
-	CONSTRAINT vhffs_forward_pkey PRIMARY KEY (domain , local_part)
-) WITH (OIDS);
-
 -- Webareas for this platform
 CREATE TABLE vhffs_httpd (
 	httpd_id serial,
@@ -245,58 +207,6 @@
 	CONSTRAINT vhffs_mailings_pkey PRIMARY KEY (mailing_id)
 ) WITH (OIDS);
 
--- Mail domains hosted on this platform
-CREATE TABLE vhffs_mxdomain (
-	mxdomain_id serial,
--- Domain name
-	domain varchar,
--- Where are the box stored? (Can't we compute it rather than store it?)
-	boxes_path varchar NOT NULL,
--- Catchall address for the mail domain
-	catchall varchar NOT NULL,
-	object_id int4 NOT NULL,
-	CONSTRAINT vhffs_mxdomain_pkey PRIMARY KEY (mxdomain_id)
-) WITH (OIDS);
-
--- Mailing lists of this platform
-CREATE TABLE vhffs_ml (
-	ml_id serial,
--- Left part of the mailing list address
-	local_part varchar(256) NOT NULL,
--- Right part of the ml address, refers to an existing mail domain
-	domain varchar(256) NOT NULL,
--- Prefix prepended to all subjects
-	prefix varchar(32),
-	object_id int4 NOT NULL,
--- How are subscriptions managed
-	sub_ctrl int4 NOT NULL,
--- Posting policy
-	post_ctrl int4 NOT NULL,
--- Add Reply-To header?
-	reply_to boolean,
--- Do we keep open archives for this list?
-	open_archive boolean,
--- Signature appended to all messages
-	signature varchar(250),
-	CONSTRAINT vhffs_ml_pkey PRIMARY KEY (ml_id)
-) WITH (OIDS);
-
--- Subscribers of a mailing list
-CREATE TABLE vhffs_ml_subscribers (
-	sub_id serial,
--- Email address of the subscriber
-	member varchar(256) NOT NULL,
--- Access level of this member
-	perm int4 NOT NULL,
--- Hash for activation
-	hash varchar,
--- Mailing list to which this address has subscribed
-	ml_id int4 NOT NULL,
--- Language of the subscriber
-	language varchar(16),
-	CONSTRAINT vhffs_ml_subscribers_pkey PRIMARY KEY (sub_id)
-) WITH (OIDS);
-
 -- MySQL databases
 CREATE TABLE vhffs_mysql (
 	mysql_id serial,
@@ -324,11 +234,11 @@
 
 -- Link objects -> tags
 CREATE TABLE vhffs_object_tag (
-    object_id int4 NOT NULL,
-    tag_id int4 NOT NULL,
-    updated int8 NOT NULL,
-    updater_id int4,
-    CONSTRAINT vhffs_object_tag_pkey PRIMARY KEY ( object_id, tag_id )
+	object_id int4 NOT NULL,
+	tag_id int4 NOT NULL,
+	updated int8 NOT NULL,
+	updater_id int4,
+	CONSTRAINT vhffs_object_tag_pkey PRIMARY KEY ( object_id, tag_id )
 ) WITH (OIDS);
 
 -- PostgreSQL databases on the platform
@@ -424,66 +334,66 @@
 
 -- Table containing all registered tags for this platform
 CREATE TABLE vhffs_tag (
-    tag_id SERIAL,
+	tag_id SERIAL,
 -- Label for the tag in platform's default language
-    label VARCHAR(30) NOT NULL,
-    description TEXT NOT NULL,
-    updated int8 NOT NULL,
+	label VARCHAR(30) NOT NULL,
+	description TEXT NOT NULL,
+	updated int8 NOT NULL,
 -- This tag's creator id, null if user has been deleted
-    updater_id int4,
-    category_id int4 NOT NULL,
-    CONSTRAINT vhffs_tag_pkey PRIMARY KEY( tag_id )
+	updater_id int4,
+	category_id int4 NOT NULL,
+	CONSTRAINT vhffs_tag_pkey PRIMARY KEY( tag_id )
 ) WITH (OIDS);
 
 -- Table containing all tag categories for this platform
 -- See vhffs_tag for description...
 CREATE TABLE vhffs_tag_category (
-    tag_category_id SERIAL,
-    label VARCHAR(30) NOT NULL,
-    description TEXT NOT NULL,
+	tag_category_id SERIAL,
+	label VARCHAR(30) NOT NULL,
+	description TEXT NOT NULL,
 -- Access level of the category
-    visibility int4 NOT NULL DEFAULT 0,
-    updated int8 NOT NULL,
-    updater_id int4,
-    CONSTRAINT vhffs_tag_category_pkey PRIMARY KEY( tag_category_id )
+	visibility int4 NOT NULL DEFAULT 0,
+	updated int8 NOT NULL,
+	updater_id int4,
+	CONSTRAINT vhffs_tag_category_pkey PRIMARY KEY( tag_category_id )
 ) WITH (OIDS);
 
 -- Tag categories' translations
 CREATE TABLE vhffs_tag_category_translation (
-    tag_category_id int4 NOT NULL,
-    lang VARCHAR(16) NOT NULL,
-    label VARCHAR(30) NOT NULL,
-    description TEXT NOT NULL,
-    updated int8 NOT NULL,
-    updater_id int4,
-    CONSTRAINT vhffs_tag_category_translation_pkey PRIMARY KEY( tag_category_id, lang )
+	tag_category_id int4 NOT NULL,
+	lang VARCHAR(16) NOT NULL,
+	label VARCHAR(30) NOT NULL,
+	description TEXT NOT NULL,
+	updated int8 NOT NULL,
+	updater_id int4,
+	CONSTRAINT vhffs_tag_category_translation_pkey PRIMARY KEY( tag_category_id, lang )
 ) WITH (OIDS);
 
 -- Tags requested by users
 CREATE TABLE vhffs_tag_request (
-    tag_request_id SERIAL,
+	tag_request_id SERIAL,
 -- Label of the category. We could have a label
 -- and an id and fill in the correct field depending
 -- of the existence of the category
-    category_label VARCHAR(30) NOT NULL,
-    tag_label VARCHAR(30) NOT NULL,
-    created int8 NOT NULL,
+	category_label VARCHAR(30) NOT NULL,
+	tag_label VARCHAR(30) NOT NULL,
+	created int8 NOT NULL,
 -- User who requested the tag
-    requester_id int4,
+	requester_id int4,
 -- For which object
-    tagged_id int4,
-    CONSTRAINT vhffs_tag_request_pkey PRIMARY KEY( tag_request_id )
+	tagged_id int4,
+	CONSTRAINT vhffs_tag_request_pkey PRIMARY KEY( tag_request_id )
 );
 
 -- Description & label translation for a tag
 CREATE TABLE vhffs_tag_translation (
-    tag_id int4 NOT NULL,
-    lang VARCHAR(16) NOT NULL,
-    label VARCHAR(30) NOT NULL,
-    description TEXT NOT NULL,
-    updated int8 NOT NULL,
-    updater_id int4,
-    CONSTRAINT vhffs_tag_translation_pkey PRIMARY KEY( tag_id, lang )
+	tag_id int4 NOT NULL,
+	lang VARCHAR(16) NOT NULL,
+	label VARCHAR(30) NOT NULL,
+	description TEXT NOT NULL,
+	updated int8 NOT NULL,
+	updater_id int4,
+	CONSTRAINT vhffs_tag_translation_pkey PRIMARY KEY( tag_id, lang )
 ) WITH (OIDS);
 
 -- Users of a group, groups of an user
@@ -495,14 +405,123 @@
 	CONSTRAINT vhffs_user_group_pkey PRIMARY KEY (uid,gid)
 ) WITH (OIDS);
 
+-- Mail domains
+CREATE TABLE vhffs_mx (
+	mx_id serial,
+-- Domain name
+	domain varchar NOT NULL,
+	object_id int4 NOT NULL,
+	CONSTRAINT vhffs_mx_pkey PRIMARY KEY (mx_id),
+	CONSTRAINT vhffs_mx_unique_domain UNIQUE (domain),
+	CONSTRAINT vhffs_mx_unique_object_id UNIQUE (object_id)
+) WITH (OIDS);
 
+-- Catchall boxes
+CREATE TABLE vhffs_mx_catchall (
+	catchall_id serial,
+-- Mail domain
+	mx_id int4 NOT NULL,
+-- Box
+	box_id int4 NOT NULL,
+	CONSTRAINT vhffs_mx_catchall_pkey PRIMARY KEY (catchall_id),
+	CONSTRAINT vhffs_mx_catchall_unique_domain_box UNIQUE (mx_id, box_id)
+) WITH (OIDS);
+
+-- Mail localparts
+CREATE TABLE vhffs_mx_localpart (
+	localpart_id serial,
+-- Mail domain
+	mx_id int4 NOT NULL,
+-- Local part of the address (part before @)
+	localpart varchar NOT NULL,
+-- Password (of the box and/or a future redirect administration)
+	password varchar,
+-- Is antispam activated ?
+	nospam boolean NOT NULL DEFAULT FALSE,
+-- Is antivirus activated ?
+	novirus boolean NOT NULL DEFAULT FALSE,
+	CONSTRAINT vhffs_mx_localpart_pkey PRIMARY KEY (localpart_id),
+	CONSTRAINT vhffs_mx_localpart_unique_domain_localpart UNIQUE (mx_id, localpart)
+) WITH (OIDS);
+
+-- Mail redirects
+CREATE TABLE vhffs_mx_redirect (
+	redirect_id serial,
+-- Local part
+	localpart_id int4 NOT NULL,
+-- Mail address to which mails are forwarded
+	redirect varchar NOT NULL,
+	CONSTRAINT vhffs_mx_redirect_pkey PRIMARY KEY (redirect_id),
+	CONSTRAINT vhffs_mx_redirect_unique_localpart_redirect UNIQUE (localpart_id, redirect)
+) WITH (OIDS);
+
+-- Mail boxes
+CREATE TABLE vhffs_mx_box (
+	box_id serial,
+-- Local part
+	localpart_id int4 NOT NULL,
+-- Allow pop login ?
+	allowpop boolean NOT NULL DEFAULT TRUE,
+-- Allow imap login ?
+	allowimap boolean NOT NULL DEFAULT TRUE,
+-- State of the box (we don't have object for this entity...)
+	state int4 NOT NULL,
+	CONSTRAINT vhffs_mx_box_pkey PRIMARY KEY (box_id),
+	CONSTRAINT vhffs_mx_box_unique_domain_localpart UNIQUE (localpart_id)
+) WITH (OIDS);
+-- state is used in vhffs_mx_box in where clauses
+CREATE INDEX idx_vhffs_mx_box_state ON vhffs_mx_box(state);
+
+-- Mailing lists
+CREATE TABLE vhffs_mx_ml (
+	ml_id serial,
+-- Local part
+	localpart_id int4 NOT NULL,
+-- Object
+	object_id int4 NOT NULL,
+-- Prefix prepended to all subjects
+	prefix varchar,
+-- How are subscriptions managed
+	sub_ctrl int4 NOT NULL,
+-- Posting policy
+	post_ctrl int4 NOT NULL,
+-- Add Reply-To header?
+	reply_to boolean NOT NULL DEFAULT FALSE,
+-- Do we keep open archives for this list?
+	open_archive boolean NOT NULL DEFAULT FALSE,
+-- Signature appended to all messages
+	signature text,
+	CONSTRAINT vhffs_mx_ml_pkey PRIMARY KEY (ml_id),
+	CONSTRAINT vhffs_mx_ml_unique_domain_localpart UNIQUE (localpart_id),
+	CONSTRAINT vhffs_mx_ml_unique_object_id UNIQUE (object_id)
+) WITH (OIDS);
+-- vhffs_mx_ml.open_archive may be used in where clause to select on public ml
+CREATE INDEX idx_vhffs_mx_ml_open_archive ON vhffs_mx_ml(open_archive);
+
+-- Subscribers of a mailing list
+CREATE TABLE vhffs_mx_ml_subscribers (
+	sub_id serial,
+-- Mailing list to which this address has subscribed
+	ml_id int4 NOT NULL,
+-- Email address of the subscriber
+	member varchar NOT NULL,
+-- Access level of this member
+	perm int4 NOT NULL,
+-- Hash for activation
+	hash varchar,
+-- Language of the subscriber
+	language varchar(16),
+	CONSTRAINT vhffs_mx_ml_subscribers_pkey PRIMARY KEY (sub_id),
+	CONSTRAINT vhffs_mx_ml_subscribers_member_list UNIQUE (ml_id, member)
+) WITH (OIDS);
+
+
 /****** Indexes and unique constraints *******/
 ALTER TABLE vhffs_users ADD CONSTRAINT vhffs_users_unique_username UNIQUE (username);
 ALTER TABLE vhffs_users ADD CONSTRAINT vhffs_users_unique_ircnick UNIQUE (ircnick);
 ALTER TABLE vhffs_groups ADD CONSTRAINT vhffs_groups_unique_groupname UNIQUE (groupname);
 ALTER TABLE vhffs_cvs ADD CONSTRAINT vhffs_cvs_unique_cvsroot UNIQUE (cvsroot);
 ALTER TABLE vhffs_httpd ADD CONSTRAINT vhffs_httpd_unique_servername UNIQUE (servername);
-ALTER TABLE vhffs_mxdomain ADD CONSTRAINT vhffs_mxdomain_unique_domainname UNIQUE (domain);
 ALTER TABLE vhffs_mysql ADD CONSTRAINT vhffs_mysql_unique_dbname UNIQUE (dbname);
 ALTER TABLE vhffs_mysql ADD CONSTRAINT vhffs_mysql_unique_dbuser UNIQUE (dbuser);
 ALTER TABLE vhffs_pgsql ADD CONSTRAINT vhffs_pgsql_unique_dbname UNIQUE (dbname);
@@ -513,8 +532,6 @@
 ALTER TABLE vhffs_mercurial ADD CONSTRAINT vhffs_mercurial_unique_reponame UNIQUE (reponame);
 ALTER TABLE vhffs_bazaar ADD CONSTRAINT vhffs_bazaar_unique_reponame UNIQUE (reponame);
 ALTER TABLE vhffs_dns ADD CONSTRAINT vhffs_dns_unique_domain UNIQUE (domain);
-ALTER TABLE vhffs_ml ADD CONSTRAINT vhffs_ml_unique_address UNIQUE (local_part, domain);
-ALTER TABLE vhffs_ml_subscribers ADD CONSTRAINT vhffs_ml_subscribers_member_list UNIQUE (ml_id, member);
 ALTER TABLE vhffs_cron ADD CONSTRAINT vhffs_cron_unique_cronpath UNIQUE (cronpath);
 ALTER TABLE vhffs_tag_category ADD CONSTRAINT vhffs_tag_category_unique_label UNIQUE(label);
 ALTER TABLE vhffs_tag ADD CONSTRAINT vhffs_tag_unique_label_category UNIQUE(label , category_id);
@@ -524,8 +541,6 @@
 ALTER TABLE vhffs_groups ADD CONSTRAINT vhffs_groups_unique_object_id UNIQUE(object_id);
 ALTER TABLE vhffs_users ADD CONSTRAINT vhffs_users_unique_object_id UNIQUE(object_id);
 ALTER TABLE vhffs_httpd ADD CONSTRAINT vhffs_httpd_unique_object_id UNIQUE(object_id);
-ALTER TABLE vhffs_ml ADD CONSTRAINT vhffs_ml_unique_object_id UNIQUE(object_id);
-ALTER TABLE vhffs_mxdomain ADD CONSTRAINT vhffs_mxdomain_unique_object_id UNIQUE(object_id);
 ALTER TABLE vhffs_mysql ADD CONSTRAINT vhffs_mysql_unique_object_id UNIQUE(object_id);
 ALTER TABLE vhffs_pgsql ADD CONSTRAINT vhffs_pgsql_unique_object_id UNIQUE(object_id);
 ALTER TABLE vhffs_repository ADD CONSTRAINT vhffs_repository_unique_object_id UNIQUE(object_id);
@@ -542,8 +557,6 @@
 CREATE INDEX idx_vhffs_mailings_state ON vhffs_mailings(state);
 -- state is used in vhffs_user_group in where clauses
 CREATE INDEX idx_vhffs_user_group_state ON vhffs_user_group(state);
--- state is used in vhffs_boxes in where clauses
-CREATE INDEX idx_vhffs_boxes_state ON vhffs_boxes(state);
 -- vhffs_cvs.public may be used in where clause to display public cvs
 CREATE INDEX idx_vhffs_cvs_public ON vhffs_cvs(public);
 -- vhffs_svn.public may be used in where clause to display public svn
@@ -554,8 +567,6 @@
 CREATE INDEX idx_vhffs_mercurial_public ON vhffs_mercurial(public);
 -- vhffs_bazaar.public may be used in where clause to display public bazaar
 CREATE INDEX idx_vhffs_bazaar_public ON vhffs_bazaar(public);
--- vhffs_ml.open_archive may be used in where clause to select on public ml
-CREATE INDEX idx_vhffs_ml_open_archive ON vhffs_ml(open_archive);
 -- vhffs_object.owner_uid and owner_gid is used a lot in where clauses
 CREATE INDEX idx_vhffs_object_owner_uid ON vhffs_object(owner_uid);
 CREATE INDEX idx_vhffs_object_owner_gid ON vhffs_object(owner_gid);
@@ -582,14 +593,12 @@
 CREATE INDEX idx_vhffs_tag_category_visibility ON vhffs_tag_category(visibility);
 
 /****** Non primary key constraints.
-      Defining foreign keys here allow to create tables in any order.
+	Defining foreign keys here allow to create tables in any order.
 *******/
 
 ALTER TABLE vhffs_acl ADD CONSTRAINT fk_vhffs_acl_vhffs_object_dst FOREIGN KEY (target_oid) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 ALTER TABLE vhffs_acl ADD CONSTRAINT fk_vhffs_acl_vhffs_object_src FOREIGN KEY (granted_oid) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 
-ALTER TABLE vhffs_boxes ADD CONSTRAINT fk_vhffs_boxes_vhffs_mxdomain FOREIGN KEY (domain) REFERENCES vhffs_mxdomain(domain) ON DELETE CASCADE;
-
 ALTER TABLE vhffs_cvs ADD CONSTRAINT fk_vhffs_cvs_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 
 ALTER TABLE vhffs_dns ADD CONSTRAINT fk_vhffs_dns_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
@@ -597,8 +606,6 @@
 ALTER TABLE vhffs_dns_rr ADD CONSTRAINT fk_vhffs_dns_rr_vhffs_dns FOREIGN KEY (zone) REFERENCES vhffs_dns(dns_id) ON DELETE CASCADE;
 ALTER TABLE vhffs_dns_rr ADD CONSTRAINT fk_vhffs_dns_rr_chk_type CHECK (type='A' OR type='AAAA' OR type='CNAME' OR type='HINFO' OR type='MX' OR type='NS' OR type='PTR' OR type='RP' OR type='SRV' OR type='TXT');
 
-ALTER TABLE vhffs_forward ADD CONSTRAINT fk_vhffs_forward_vhffs_mxdomain FOREIGN KEY (domain) REFERENCES vhffs_mxdomain(domain) ON DELETE CASCADE;
-
 ALTER TABLE vhffs_groups ADD CONSTRAINT fk_vhffs_group_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 
 ALTER TABLE vhffs_history ADD CONSTRAINT fk_vhffs_history_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
@@ -607,13 +614,6 @@
 
 ALTER TABLE vhffs_httpd ADD CONSTRAINT fk_vhffs_httpd_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 
-ALTER TABLE vhffs_ml ADD CONSTRAINT fk_vhffs_ml_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
-ALTER TABLE vhffs_ml ADD CONSTRAINT fk_vhffs_ml_vhffs_mxdomain FOREIGN KEY (domain) REFERENCES vhffs_mxdomain(domain) ON DELETE CASCADE;
-
-ALTER TABLE vhffs_ml_subscribers ADD CONSTRAINT fk_vhffs_ml_subscribers_vhffs_ml FOREIGN KEY (ml_id) REFERENCES vhffs_ml(ml_id) ON DELETE CASCADE;
-
-ALTER TABLE vhffs_mxdomain ADD CONSTRAINT fk_vhffs_mxdomain_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
-
 ALTER TABLE vhffs_mysql ADD CONSTRAINT fk_vhffs_mysql_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 
 ALTER TABLE vhffs_object ADD CONSTRAINT fk_vhffs_object_vhffs_user FOREIGN KEY (owner_uid) REFERENCES vhffs_users(uid) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
@@ -651,7 +651,23 @@
 ALTER TABLE vhffs_tag_request ADD CONSTRAINT fk_vhffs_tag_request_vhffs_user FOREIGN KEY ( requester_id ) REFERENCES vhffs_users( uid ) ON DELETE SET NULL;
 ALTER TABLE vhffs_tag_request ADD CONSTRAINT fk_vhffs_tag_request_vhffs_object FOREIGN KEY ( tagged_id ) REFERENCES vhffs_object( object_id ) ON DELETE SET NULL;
 
+ALTER TABLE vhffs_mx ADD CONSTRAINT fk_vhffs_mx_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
 
+ALTER TABLE vhffs_mx_localpart ADD CONSTRAINT fk_vhffs_mx_localpart_vhffs_mx FOREIGN KEY (mx_id) REFERENCES vhffs_mx(mx_id) ON DELETE CASCADE;
+
+ALTER TABLE vhffs_mx_catchall ADD CONSTRAINT fk_vhffs_mx_catchall_vhffs_mx FOREIGN KEY (mx_id) REFERENCES vhffs_mx(mx_id) ON DELETE CASCADE;
+ALTER TABLE vhffs_mx_catchall ADD CONSTRAINT fk_vhffs_mx_catchall_vhffs_mx_box FOREIGN KEY (box_id) REFERENCES vhffs_mx_box(box_id) ON DELETE CASCADE;
+
+ALTER TABLE vhffs_mx_box ADD CONSTRAINT fk_vhffs_mx_box_vhffs_localpart FOREIGN KEY (localpart_id) REFERENCES vhffs_mx_localpart(localpart_id) ON DELETE CASCADE;
+
+ALTER TABLE vhffs_mx_redirect ADD CONSTRAINT fk_vhffs_mx_redirect_vhffs_localpart FOREIGN KEY (localpart_id) REFERENCES vhffs_mx_localpart(localpart_id) ON DELETE CASCADE;
+
+ALTER TABLE vhffs_mx_ml ADD CONSTRAINT fk_vhffs_mx_ml_vhffs_object FOREIGN KEY (object_id) REFERENCES vhffs_object(object_id) ON DELETE CASCADE;
+ALTER TABLE vhffs_mx_ml ADD CONSTRAINT fk_vhffs_mx_ml_vhffs_localpart FOREIGN KEY (localpart_id) REFERENCES vhffs_mx_localpart(localpart_id) ON DELETE CASCADE;
+
+ALTER TABLE vhffs_mx_ml_subscribers ADD CONSTRAINT fk_vhffs_mx_ml_subscribers_vhffs_mx_ml FOREIGN KEY (ml_id) REFERENCES vhffs_mx_ml(ml_id) ON DELETE CASCADE;
+
+
 -- VIEWS
 
 CREATE VIEW vhffs_passwd AS

Modified: trunk/vhffs-compat/from-4.4-to-4.5.sql
===================================================================
--- trunk/vhffs-compat/from-4.4-to-4.5.sql	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-compat/from-4.4-to-4.5.sql	2012-04-11 22:01:48 UTC (rev 2145)
@@ -41,3 +41,37 @@
 ALTER TABLE vhffs_git ADD CONSTRAINT vhffs_git_unique_object_id UNIQUE(object_id);
 ALTER TABLE vhffs_mercurial ADD CONSTRAINT vhffs_mercurial_unique_object_id UNIQUE(object_id);
 ALTER TABLE vhffs_bazaar ADD CONSTRAINT vhffs_bazaar_unique_object_id UNIQUE(object_id);
+
+-- Migration to the new VHFFS mail database
+
+-- fill vhffs_mx from former vhffs_mxdomain
+INSERT INTO vhffs_mx (domain, object_id) SELECT domain, object_id FROM vhffs_mxdomain;
+
+-- migrate boxes from vhffs_boxes to vhffs_mx_localpart,vhffs_mx_box
+INSERT INTO vhffs_mx_localpart (mx_id,localpart,password,nospam,novirus) SELECT mx.mx_id,mb.local_part,mb.password,mb.nospam,mb.novirus FROM vhffs_mx mx INNER JOIN vhffs_boxes mb ON mb.domain=mx.domain;
+INSERT INTO vhffs_mx_box (localpart_id,allowpop,allowimap,state) SELECT mxl.localpart_id,mb.allowpop,mb.allowimap,mb.state FROM vhffs_mx mx INNER JOIN vhffs_boxes mb ON mb.domain=mx.domain INNER JOIN vhffs_mx_localpart mxl ON mxl.localpart=mb.local_part AND mxl.mx_id=mx.mx_id;
+
+-- migrate catchall from vhffs_mxdomain to vhffs_mx_localpart,vhffs_redirect
+-- We only migrate catchall on box, catchall on any address is no more supported
+INSERT INTO vhffs_mx_catchall (mx_id,box_id) SELECT mx.mx_id,mb.box_id FROM vhffs_mxdomain mxd INNER JOIN vhffs_mx mx ON mx.domain=mxd.domain INNER JOIN vhffs_mx_localpart mxl ON mxl.mx_id=mx.mx_id INNER JOIN vhffs_mx_box mb ON mb.localpart_id=mxl.localpart_id WHERE mxd.catchall IS NOT NULL AND mxd.catchall != '' AND mxl.localpart=substring(mxd.catchall from '(.*)@') AND mx.domain=substring(mxd.catchall from '@(.*)');
+
+-- migrate forwards from vhffs_forwards to vhffs_mx_localpart,vhffs_mx_redirect
+INSERT INTO vhffs_mx_localpart (mx_id,localpart) SELECT mx.mx_id,mf.local_part FROM vhffs_mx mx INNER JOIN vhffs_forward mf ON mf.domain=mx.domain;
+INSERT INTO vhffs_mx_redirect (localpart_id,redirect) SELECT mxl.localpart_id,mf.remote_name FROM vhffs_mx mx INNER JOIN vhffs_forward mf ON mf.domain=mx.domain INNER JOIN vhffs_mx_localpart mxl ON mxl.localpart=mf.local_part AND mxl.mx_id=mx.mx_id;
+
+-- migrate mailing list from vhffs_ml to vhffs_mx_ml
+INSERT INTO vhffs_mx_localpart (mx_id,localpart) SELECT mx.mx_id,ml.local_part FROM vhffs_ml ml INNER JOIN vhffs_mx mx ON mx.domain=ml.domain;
+INSERT INTO vhffs_mx_ml (ml_id,localpart_id,object_id,prefix,sub_ctrl,post_ctrl,reply_to,open_archive,signature) SELECT ml.ml_id,mxl.localpart_id,ml.object_id,ml.prefix,ml.sub_ctrl,ml.post_ctrl,ml.reply_to,ml.open_archive,ml.signature FROM vhffs_ml ml INNER JOIN vhffs_mx mx ON mx.domain=ml.domain INNER JOIN vhffs_mx_localpart mxl ON mxl.localpart=ml.local_part AND mxl.mx_id=mx.mx_id;
+SELECT setval('vhffs_mx_ml_ml_id_seq', (SELECT COALESCE(MAX(ml_id), 1) FROM vhffs_mx_ml));
+
+-- migrate mailing list subscribers
+INSERT INTO vhffs_mx_ml_subscribers (sub_id,member,perm,hash,ml_id,language) SELECT sub_id,member,perm,hash,ml_id,language FROM vhffs_ml_subscribers;
+SELECT setval('vhffs_mx_ml_subscribers_sub_id_seq', (SELECT COALESCE(MAX(sub_id), 1) FROM vhffs_mx_ml_subscribers));
+
+-- DROP old table, commented out by default
+
+-- DROP TABLE vhffs_ml_subscribers;
+-- DROP TABLE vhffs_ml;
+-- DROP TABLE vhffs_boxes;
+-- DROP TABLE vhffs_forward;
+-- DROP TABLE vhffs_mxdomain;

Modified: trunk/vhffs-listengine/src/listengine.pl
===================================================================
--- trunk/vhffs-listengine/src/listengine.pl	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-listengine/src/listengine.pl	2012-04-11 22:01:48 UTC (rev 2145)
@@ -50,7 +50,7 @@
 #Huho, if the program stop heres, you have some problems with your MTA configuration
 if( $#ARGV != 2 )
 {
-	print 'involve as: listengine action local_part domain'."\n";
+	print 'involve as: listengine action localpart domain'."\n";
 	exit 1;
 }
 
@@ -1157,6 +1157,7 @@
 #Build the list object from VHFFS
 my $list = Vhffs::Services::MailingList::get_by_mladdress( $vhffs , $lpart , $domain );
 exit( 0 ) unless defined $list;
+$list->fetch_subs;
 
 
 verify_mail_with_list( $list , $mail );

Modified: trunk/vhffs-panel/templates/group/prefs.tt
===================================================================
--- trunk/vhffs-panel/templates/group/prefs.tt	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-panel/templates/group/prefs.tt	2012-04-11 22:01:48 UTC (rev 2145)
@@ -19,15 +19,15 @@
 [% IF mailgroup.defined() %]
 <h2>[% 'Project contact' | i18n | html %]</h2>
 <p>
-[% 'We offer you the possibility to forward emails from %s@%s.' | i18n | pretty_print(group.get_groupname, mailgroup.config.domain) %]
+[% 'We offer you the possibility to forward emails from %s@%s.' | i18n | pretty_print(group.get_groupname, mailgroup.get_config.domain) %]
 <form class="table-like" action="#" method="post" accept-charset="utf-8">
-[% 'Forward emails from %s@%s to ' | i18n | pretty_print(group.get_groupname, mailgroup.config.domain) %]
-<input type="text" name="contact_email" id="contact_email" value="[% mailgroup.getforward %]"/> 
+[% 'Forward emails from %s@%s to ' | i18n | pretty_print(group.get_groupname, mailgroup.get_config.domain) %]
+<input type="text" name="contact_email" id="contact_email" value="[% mailgroup.get_redirect.get_redirect %]"/> 
 <input type="hidden" name="group" value="[% group.get_groupname %]"/>
 <input type="submit" value="[% 'Modify' | i18n %]" name="contact_email_submit"/>
 </form>
-[% IF mailgroup.config.url_doc.defined() %]
-<a href="[% mailgroup.config.url_doc | html %]">[% 'Help' | i18n | html %]</a>
+[% IF mailgroup.get_config.url_doc %]
+<a href="[% mailgroup.get_config.url_doc | html %]">[% 'Help' | i18n | html %]</a>
 [% END %]
 </p>
 [% END %]

Modified: trunk/vhffs-panel/templates/mail/prefs.tt
===================================================================
--- trunk/vhffs-panel/templates/mail/prefs.tt	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-panel/templates/mail/prefs.tt	2012-04-11 22:01:48 UTC (rev 2145)
@@ -1,63 +1,94 @@
-[% UNLESS catchall_type == 'none' %]
+[% UNLESS mail.conf_allowed_catchall == catchall_state.none %]
 <h2>[% 'Catchall address' | i18n | html %]</h2>
-
+  <p>[% 'Catchall address receive all emails for which no box nor forward exists on this domain.' | i18n | html %]</p>
+  <p>[% 'To avoid blacklisting issues due to spam forwarding, catchall address are restricted to boxes belongin to this mail domain.' | i18n | html %]</p>
+  [% FOREACH catchall IN mail.catchall.values %]
+    <form class="table-like" method="post" action="#" accept-charset="utf-8">
+	<p>[% catchall.boxname | html %]&#160;<input
+	    type="submit" value="[% 'Delete this catchall box' | i18n | html %]" name="delete_catchall_submit"/></p>
+		<input type="hidden" name="boxname" value="[% catchall.boxname | html %]" />
+		<input type="hidden" name="name" value="[% mail.domain | html %]" />
+    </form>
+  [%END # FOREACH mail.catchall %]
 <form class="table-like" method="post" action="#" accept-charset="utf-8">
-	<p>[% 'Catchall address receive all emails for which no box nor forward exists on this domain.' | i18n | html %]</p>
-[% IF catchall_type == 'open' %]
-	<p>
-		<label for="catchall">[% 'Catchall destination address:' | i18n | html %]</label>
-		<input type="text" name="catchall" id="catchall" value="[% mail.get_catchall | html %]" />
-    </p>
-[% ELSE %]
-    <p>[% 'To avoid blacklisting issues due to spam forwarding, catchall address are restricted to boxes belongin to this mail domain.' | i18n | html %]</p>
-	<p>
-		<label for="catchall">[% 'Catchall destination address:' | i18n | html %]</label>
+  <p>
+	<label for="catchall">[% 'Catchall destination address:' | i18n | html %]</label>
+[% IF mail.conf_allowed_catchall == catchall_state.domain %]
         <select name="catchall" id="catchall">
-            <option value="">---</option>
-[% FOREACH b IN sorted_boxes %]
-[% IF b.state == constants.object_statuses.ACTIVATED %]
-[% SET address = b.local_part _ '@' _ b.domain %]
-	            <option[% ' selected="selected"' IF mail.get_catchall == address %] value="[% b.local_part | html %]">[% address | html %]</option>
+          <option value="">---</option>
+[% FOREACH lp IN sorted_localparts %]
+[% IF lp.box AND lp.box.state == constants.object_statuses.ACTIVATED %]
+[% SET address = lp.localpart _ '@' _ mail.domain %]
+          <option value="[% lp.localpart _ '@' _ mail.domain | html %]">[% address | html %]</option>
 [% END %]
-[% END # b IN sorted_boxes %]
+[% END # lp IN sorted_localparts %]
         </select>
-	</p>
-[% END # catchall_type == 'open' %]
-	<p class="button" id="buttonModify">
-		<input type="hidden" name="name" value="[% mail.get_domain | html %]" />
-		<input type="submit" name="modify_catchall_submit" value="[% 'Change catchall forward' | i18n | html %]"/>
-	</p>
+[% ELSE # mail.conf_allowed_catchall == catchall_state.open %]
+	<input type="text" name="catchall" id="catchall" />
+[% END %]
+	<input type="hidden" name="name" value="[% mail.get_domain | html %]" />
+	<input type="submit" name="add_catchall_submit" value="[% 'Add a catchall box' | i18n | html %]"/>
+  </p>
 </form>
-[% END # catchall_type == 'none' %]
-
+[% END # mail.conf_allowed_catchall == catchall_state.none %]
 <h2>[% 'Accounts' | i18n | html %]</h2>
-
-<h3>[% 'Existing accounts' | i18n | html %]</h3>
-[% FOREACH b IN sorted_boxes %]
+[% FOREACH lp IN sorted_localparts %]
 <fieldset>
-<legend>[% b.local_part _ '@' _ b.domain | html %]</legend>
-[% IF b.state == constants.object_statuses.ACTIVATED %]
+<legend>[% lp.localpart _ '@' _ mail.domain | html %]</legend>
+[% IF lp.box %]
+<fieldset>
+<legend>[% 'Box' | i18n | html %]</legend>
+[% IF lp.box.state == constants.object_statuses.ACTIVATED %]
 		<form class="table-like" method="post" action="#" accept-charset="utf-8">
-                <p>[% 'New password:' | i18n | html %]<input type="password" name="box_password" value="" autocomplete="off"/></p>
+                <p>[% 'New password:' | i18n | html %]<input type="password" name="localpart_password" value="" autocomplete="off"/></p>
 [% IF nospam %]
                 <p>[% 'Enable antispam?' | i18n | html %]
-                  <input type="radio" name="use_antispam"[% ' checked="checked"' IF b.nospam %] value="yes"/>&#160;[% 'Yes' | i18n | html %]
-                  <input type="radio" name="use_antispam"[% ' checked="checked"' UNLESS b.nospam %] value="no"/>&#160;[% 'No' | i18n | html %]</p>
+                  <input type="radio" name="use_antispam"[% ' checked="checked"' IF lp.nospam %] value="yes"/>&#160;[% 'Yes' | i18n | html %]
+                  <input type="radio" name="use_antispam"[% ' checked="checked"' UNLESS lp.nospam %] value="no"/>&#160;[% 'No' | i18n | html %]</p>
 [% END %]
 [% IF novirus %]
                 <p>[% 'Enable antivirus?' | i18n | html %]
-                  <input type="radio" name="use_antivirus"[% ' checked="checked"' IF b.novirus %] value="yes"/>&#160;[% 'Yes' | i18n | html %]
-                  <input type="radio" name="use_antivirus"[% ' checked="checked"' UNLESS b.novirus %] value="no"/>&#160;[% 'No' | i18n | html %]</p>
+                  <input type="radio" name="use_antivirus"[% ' checked="checked"' IF lp.novirus %] value="yes"/>&#160;[% 'Yes' | i18n | html %]
+                  <input type="radio" name="use_antivirus"[% ' checked="checked"' UNLESS lp.novirus %] value="no"/>&#160;[% 'No' | i18n | html %]</p>
 [% END %]
-                <p><input type="submit" name="update_box_submit" value="[% 'Update' | i18n | html %]"/>&#160;<input type="submit" name="delete_box_submit" value="[% 'Delete this mail account' | i18n | html %]"/></p>
-			<input type="hidden" name="localpart" value="[% b.local_part | html %]" />
-			<input type="hidden" name="name" value="[% b.domain | html %]" />
+                <p><input type="submit" name="update_localpart_submit" value="[% 'Update' | i18n | html %]"/>&#160;<input type="submit" name="delete_box_submit" value="[% 'Delete this mail account' | i18n | html %]"/></p>
+			<input type="hidden" name="localpart" value="[% lp.localpart | html %]" />
+			<input type="hidden" name="name" value="[% mail.domain | html %]" />
+
 		</form>
 [% ELSE %]
-    <p>[% b.state | stringify_status | html %]</p>
-[% END # b.state == constants.object_statuses.ACTIVATED %]
+   <p>[% lp.box.state | stringify_status | html %]</p>
+[% END # lp.box.state == constants.object_statuses.ACTIVATED %]
 </fieldset>
-[% END # b IN sorted_boxes %]
+[% END # lp.box %]
+[% IF lp.redirects %]
+<fieldset>
+<legend>[% 'Redirects' | i18n | html %]</legend>
+  [% FOREACH lpr IN lp.redirects.values %]
+    <form class="table-like" method="post" action="#" accept-charset="utf-8">
+	<p><input type="text" name="newremote" value="[% lpr.redirect | html %]" />&#160;<input
+	    type="submit" value="[% 'Update forward' | i18n | html %]" name="update_forward_submit"/>&#160;<input
+	    type="submit" value="[% 'Delete forward' | i18n | html %]" name="delete_forward_submit"/></p>
+		<input type="hidden" name="localpart" value="[% lp.localpart | html %]" />
+		<input type="hidden" name="name" value="[% mail.domain | html %]" />
+		<input type="hidden" name="remote" value="[% lpr.redirect | html %]" />
+    </form>
+  [%END # FOREACH lp.redirects %]
+  <form class="table-like" method="post" action="#" accept-charset="utf-8">
+	<p class="button">
+		<input type="text" name="forward" id="new_forward_forward" />
+		<input type="hidden" name="localpart" id="new_forward_localpart" value="[% lp.localpart | html %]"/>
+		<input type="hidden" name="name" value="[% mail.domain | html %]" />
+		<input type="submit" value="[% 'Add forward' | i18n | html %]" name="add_forward_submit"/>
+	</p>
+  </form>
+</fieldset>
+[% END # IF lp.redirects %]
+[% IF lp.ml %]
+	<p>[% 'Is a mailing list' | i18n | html %]</p>
+[% END # IF lp.ml %]
+</fieldset>
+[% END # lp IN sorted_localparts %]
 
 <h3>[% 'Add an account' | i18n | html %]</h3>
 
@@ -67,8 +98,8 @@
 		<input type="text" name="localpart" id="new_box_localpart" />@[% mail.get_domain | html %]
 	</p>
 	<p>
-		<label for="new_box_password">[% 'Password:' | i18n | html %]</label>
-		<input type="password" name="box_password" id="new_box_password" autocomplete="off"/>
+		<label for="new_localpart_password">[% 'Password:' | i18n | html %]</label>
+		<input type="password" name="localpart_password" id="new_localpart_password" autocomplete="off"/>
 	</p>
 	<p class="button">
 		<input type="hidden" name="name" value="[% mail.get_domain | html %]" />
@@ -76,26 +107,6 @@
 	</p>
 </form>
 
-<h2>[% 'Forwards' | i18n | html %]</h2>
-
-<h3>[% 'Existing forwards' | i18n | html %]</h3>
-
-[% FOREACH f IN sorted_forwards %]
-<fieldset>
-<legend>[% f.local_part _ '@' _ f.domain | html %]</legend>
-    <form class="table-like" method="post" action="#" accept-charset="utf-8">
-		<p>
-            <input type="text" name="forward" value="[% f.remote_name | html %]" /></p>
-        <p><input type="submit" value="[% 'Update forward' | i18n | html %]"
-        name="update_forward_submit"/>&#160;<input type="submit"
-            value="[% 'Delete forward' | i18n | html %]"
-            name="delete_forward_submit"/></p>
-		<input type="hidden" name="localpart" value="[% f.local_part | html %]" />
-		<input type="hidden" name="name" value="[% f.domain | html %]" />
-    </form>
-</fieldset>
-[% END %]
-
 <h3>[% 'Add forward' | i18n | html %]</h3>
 <form class="table-like" method="post" action="#" accept-charset="utf-8">
 	<p>

Modified: trunk/vhffs-panel/templates/mailinglist/create.tt
===================================================================
--- trunk/vhffs-panel/templates/mailinglist/create.tt	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-panel/templates/mailinglist/create.tt	2012-04-11 22:01:48 UTC (rev 2145)
@@ -7,7 +7,7 @@
     </p>
     <div class="clear"></div>
     <p>
-		<input type="text" name="localpart" id="localpart" value="[% local_part | html %]"/>@<select name="domain">
+		<input type="text" name="localpart" id="localpart" value="[% localpart | html %]"/>@<select name="domain">
 [% FOREACH d IN domains %]
             <option value="[% d.domain | html %]"[% ' selected="selected"' IF d.domain == domain %]>[% d.domain | html %]</option>
 [% END %]

Modified: trunk/vhffs-panel/templates/user/prefs.tt
===================================================================
--- trunk/vhffs-panel/templates/user/prefs.tt	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-panel/templates/user/prefs.tt	2012-04-11 22:01:48 UTC (rev 2145)
@@ -84,48 +84,48 @@
 
 [% IF mail_user.defined() %]
 				<fieldset id="chooseMailMode">
-					<legend>[% 'We offer you the possibility to have one email box on the domain %s' | i18n | pretty_print(mail_user.domain) | html %]</legend>
-[% IF (!mail_user.service.exists) || mail_user.service.exists_forward || 
-    (mail_user.service.exists_box && mail_user.service.get_box_status == constants.object_statuses.ACTIVATED ) %]
-[% IF mail_user.help_url %]
-                    <p><a href="[% mail_user.help_url | html %]">[% 'Read more about this service.' | i18n | html %]</a></p>
+					<legend>[% 'We offer you the possibility to have one email box on the domain %s' | i18n | pretty_print(mail_user.get_domain) | html %]</legend>
+[% IF (!mail_user.get_localpart) || mail_user.get_redirect || 
+    (mail_user.get_box && mail_user.get_box.get_status == constants.object_statuses.ACTIVATED ) %]
+[% IF mail_user.get_config.url_doc %]
+                    <p><a href="[% mail_user.get_config.url_doc | html %]">[% 'Read more about this service.' | i18n | html %]</a></p>
 [% END %]
 					<p>
 						<input name="mail_activate" id="activate-platform-email" class="labeled"
-                            type="checkbox"[% ' checked="chedcked"' IF mail_user.service.exists() %]/>
-                            <label class="inline" for="activate-platform-email">[% 'Activate %s@%s email' | i18n | pretty_print(user.get_username, mail_user.domain) | html %]</label>
+                            type="checkbox"[% ' checked="checked"' IF mail_user.get_localpart %]/>
+                            <label class="inline" for="activate-platform-email">[% 'Activate %s@%s email' | i18n | pretty_print(user.get_username, mail_user.get_domain) | html %]</label>
 					</p>
-                    <div id="platform-email-options"[% ' style="display:none"' UNLESS  mail_user.service.exists() %]>
+                    <div id="platform-email-options"[% ' style="display:none"' UNLESS  mail_user.get_localpart %]>
 					<p>[% 'There are two possible usages:' | i18n | html %]</p>
 					
 					<p>
 						<input name="mail_usage" id="plaftorm-email-option-box" value="1" type="radio"
-                            class="labeled"[% ' checked="checked"' IF mail_user.service.exists_box && !mail_user.service.exists_forward %]/>
+                            class="labeled"[% ' checked="checked"' IF mail_user.get_box && !mail_user.get_redirect %]/>
 						<label class="inline" for="plaftorm-email-option-box" >[% 'Use VHFFS servers to manage this mail.' | i18n | html  %]
-                            <br/>[% 'You should use use the host pop.%s or imap.%s to fetch your mails.' | i18n | pretty_print(mail_user.domain, mail_user.domain) | html %]</label>
+                            <br/>[% 'You should use use the host pop.%s or imap.%s to fetch your mails.' | i18n | pretty_print(mail_user.get_domain, mail_user.get_domain) | html %]</label>
 					</p>
-					<div id="platform-email-box-options"[% ' style="display:none"' UNLESS mail_user.service.exists_box && !mail_user.service.exists_forward %]>
-[% IF mail_user.nospam %]
+					<div id="platform-email-box-options"[% ' style="display:none"' UNLESS mail_user.get_box && !mail_user.get_redirect %]>
+[% IF mail_user.use_nospam %]
                         <p><input name="mail_nospam" id="mail_nospam" type="checkbox"
-                            class="labeled"[% ' checked="checked"' IF mail_user.service.use_nospam %]/>
+                            class="labeled"[% ' checked="checked"' IF mail_user.get_localpart.get_nospam %]/>
                             <label class="inline" for="mail_nospam">[% 'Activate anti-spam protection' | i18n | html %]</label></p>
 [% END %]
-[% IF mail_user.novirus %]
+[% IF mail_user.use_novirus %]
                         <p><input name="mail_novirus" id="mail_novirus" type="checkbox"
-                            class="labeled"[% ' checked="checked"' IF mail_user.service.use_novirus %]/>
+                            class="labeled"[% ' checked="checked"' IF mail_user.get_localpart.get_novirus %]/>
                             <label class="inline" for="mail_novirus">[% 'Activate anti-virus protection' | i18n | html %]</label></p>
 [% END %]
 					</div>
 					<p>
 						<input name="mail_usage" 
                             id="plaftorm-email-option-forward" value="2" class="labeled"
-                            type="radio"[% ' checked="checked"' IF mail_user.service.exists_forward %]/>
-						<label class="inline" for="plaftorm-email-option-forward">[% 'Forward emails from %s@%s to %s' | i18n | pretty_print(user.get_username, mail_user.domain, user.get_mail) | html %]</label>
+                            type="radio"[% ' checked="checked"' IF mail_user.get_redirect %]/>
+						<label class="inline" for="plaftorm-email-option-forward">[% 'Forward emails from %s@%s to %s' | i18n | pretty_print(user.get_username, mail_user.get_domain, user.get_mail) | html %]</label>
 					</p>
 					</div>
 
 [% ELSE # mailbox does not exists or is activated %]
-                    <p>[% mail_user.service.get_box_status | stringify_status | html %]</p>
+                    <p>[% mail_user.get_box.get_status | stringify_status | html %]</p>
 [% END %]
 				</fieldset>
 [% END # mail_user.defined %]

Modified: trunk/vhffs-public/templates/content/group-details.tt
===================================================================
--- trunk/vhffs-public/templates/content/group-details.tt	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-public/templates/content/group-details.tt	2012-04-11 22:01:48 UTC (rev 2145)
@@ -100,7 +100,7 @@
 [% FOREACH list = ml.lists %]
 <li>
 [% IF list.open_archive %]
-<p><a class="list-archives-link" href="[% ml.archives_url %]/[% list.domain %]/[% list.local_part %]">
+<p><a class="list-archives-link" href="[% ml.archives_url %]/[% list.domain %]/[% list.localpart %]">
 	[% list.listname | mail %]</a></p>
 [% ELSE %]
 <p>[% list.listname | mail %]</p>

Modified: trunk/vhffs-robots/src/mail.pl
===================================================================
--- trunk/vhffs-robots/src/mail.pl	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-robots/src/mail.pl	2012-04-11 22:01:48 UTC (rev 2145)
@@ -60,14 +60,12 @@
 
 my $boxes = Vhffs::Robots::Mail::getall_boxes( $vhffs, Vhffs::Constants::WAITING_FOR_CREATION );
 foreach ( @{$boxes} ) {
-	my $mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $_->{domain} );
-	Vhffs::Robots::Mail::create_mailbox( $mail, $_->{local_part} );
+	Vhffs::Robots::Mail::create_mailbox( $_ );
 }
 
 $boxes = Vhffs::Robots::Mail::getall_boxes( $vhffs, Vhffs::Constants::WAITING_FOR_DELETION );
 foreach ( @{$boxes} ) {
-	my $mail = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $_->{domain} );
-	Vhffs::Robots::Mail::delete_mailbox( $mail, $_->{local_part} );
+	Vhffs::Robots::Mail::delete_mailbox( $_ );
 }
 
 Vhffs::Robots::unlock( $vhffs, 'mail' );

Modified: trunk/vhffs-tools/src/vhffs-box-add
===================================================================
--- trunk/vhffs-tools/src/vhffs-box-add	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-tools/src/vhffs-box-add	2012-04-11 22:01:48 UTC (rev 2145)
@@ -17,12 +17,12 @@
 
 my $vhffs = new Vhffs;
 
-die("Usage $0 maildomain local_part password\n") unless(@ARGV == 3);
+die("Usage $0 maildomain localpart password\n") unless(@ARGV == 3);
 
 my $domain = Vhffs::Services::Mail::get_by_mxdomain( $vhffs, $ARGV[0] );
 die 'Invalid domain specified ('.$ARGV[0].')' unless(defined $domain);
 
-my $rval = $domain->addbox($ARGV[1], $ARGV[2]);
+my $rval = $domain->add_box($ARGV[1], $ARGV[2]);
 die( 'Invalid address' )	 if($rval == -1);
 die( 'Address already exists' )  if( $rval == -2 );
 die( 'Error while adding box' )  if( $rval == -3 );

Modified: trunk/vhffs-tools/src/vhffs-managemail
===================================================================
--- trunk/vhffs-tools/src/vhffs-managemail	2012-04-04 20:14:52 UTC (rev 2144)
+++ trunk/vhffs-tools/src/vhffs-managemail	2012-04-11 22:01:48 UTC (rev 2145)
@@ -583,7 +583,7 @@
 		return;
 	}
 
-	my $rval = $domain->addbox($local_part, $password);
+	my $rval = $domain->add_box($local_part, $password);
 	$ui->error( 'Invalid address' ) 	if( $rval == -1 );
 	$ui->error( 'Address already exists' ) 	if( $rval == -2 );
 	$ui->error( 'Error while adding box' )	if( $rval == -3 );
@@ -610,7 +610,7 @@
 		return;
 	}
 
-	my $rval = $domain->addforward( $local_part, $remote_address );
+	my $rval = $domain->add_redirect( $local_part, $remote_address );
 	$ui->error( 'Invalid local part or remote address' ) if($rval == -1);
 	$ui->error( 'Address already exists' ) if($rval == -2);
 	$ui->error( 'Error while adding box') if($rval == -3);


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/