Mail quota alternative

From VHCP Support

Jump to: navigation, search

Summary

This mail quota solution adds disk space used by e-mail to the disk space total per domain. It is no per mailbox limit as in the "vhcp + postfix-vda" solution.

In some situations it is useful to limit the total disk space a domain user can have counting both webspace and mailspace as one including all mailboxes.

It is based on an idea from a post in the old forum: Counting email quota in disk quota

The author adds disk space used by e-mail to the disk space used by web pages and limits incoming ftp uploads if necessary. But there is still no limit to incoming e-mails. When the disk space limit is reached incoming mails are still delivered to the users mailbox regardless of its size and quota.

The mail quota solution below is based on the "error" transport of postfix which delivers a bounce for every incoming mail when quota limits are reached. This mechanism carries one major drawback since postfix has to reload its config whenever transport maps are changed i.e. whenever a quota situation changes. Everybody feel free to find a better way and give me a hint.


How to patch?

Add these lines to /etc/vhcp/postfix/vhcp/transport:

## VHCP QUOTA BLOCK BEGIN
## VHCP QUOTA BLOCK END

After that edit /var/www/webadmin/engine/quota/vhcp-dsk-quota and replace the function "dsk_quota_engine" by the following code. You can find the function by searching for "sub dsk_quota_engine" in your favourite text editor. The whole method is enclosed by brackets "{}".


sub dsk_quota_engine {

    my ($rs, $ref, $rdata) = (undef, undef, undef);

    push_el(\@main::el, 'dsk_quota_engine()', 'Starting...');

    my $sql = "select domain_id, domain_name, domain_disk_limit from domain where domain_status = 'ok';";

    ($rs, $ref) = doSQL($sql);

    return $rs if ($rs != 0);

    my $cmd_du = $main::cfg{'CMD_DU'};

    my @quota_domains;

    foreach (@$ref) {

        my $disk_limit  = @$_[2];

        my $domain_name = @$_[1];

        my $domain_id   = @$_[0];

        my $size = 0;

        my $s = `$cmd_du -sb $main::cfg{APACHE_WWW_DIR}/$domain_name`;

        $s =~ /^(\d+)/; $size += $1;

        $s = `$cmd_du -sb $main::cfg{MTA_VIRTUAL_MAIL_DIR}/$domain_name`;

        $s =~ /^(\d+)/; $size += $1;

        if ( defined($disk_limit) && $disk_limit > 0 && ( $size / 1048576 > $disk_limit) ) {

            push(@quota_domains, $domain_name);

        }

        if ($size > 0) {

            $sql = "update domain set domain_disk_usage='$size' where domain_id='$domain_id'";

            ($rs, $rdata) = doSQL($sql);

            return $rs if ($rs != 0);

            $sql = "select count(name) as cnt from quotatallies where name = '$domain_name'";

            ($rs, $rdata) = doSQL($sql);

            return $rs if ($rs != 0);

            $rdata = @$rdata[0];

            my $quota_ent = @$rdata[0];

            if ($quota_ent > 0) {

                $sql = "update quotatallies set bytes_in_used = '$size' where name = '$domain_name'";

                ($rs, $rdata) = doSQL($sql);

                return $rs if ($rs != 0);

            }

        }
    }

    my $transport = $main::cfg{MTA_TRANSPORT_HASH};

    my $file_opened = 1;

    if ( ! open(INHASH, $transport) ) {

      push_el(\@main::el, 'dsk_quota_engine()', "Cannot open transport file $transport for reading: $!");

      $file_opened = undef;

    }

    my $backup = '/etc/vhcp/postfix/backup/transport.'.time();

    if ( ! open(OUTHASH, ">$backup") ) {

      push_el(\@main::el, 'dsk_quota_engine()', "Cannot open backup file $backup for writing: $!");

      $file_opened = undef;

    }

    if ( $file_opened ) {

      my $quotablock;

      my $msg = $main::cfg{MTA_MSG_QUOTA_EXCEEDED};

      if ( !defined($msg) ) {

        $msg = 'Quota exceeded.';

      }

      while (<INHASH>) {

        s/^\s+//;

        if ( /^## VHCP QUOTA BLOCK BEGIN/ ) {

          $quotablock = 1;

          print OUTHASH $_;

         next;

        }

        if (  /^## VHCP QUOTA BLOCK END/ ) {

          $quotablock = undef;

          foreach my $domain_name ( @quota_domains ) {

            print OUTHASH "$domain_name\terror:$msg\n";

          }

        }

        if ( ! $quotablock ) {

          print OUTHASH $_;

        }

      }

      close INHASH;

      close OUTHASH;

      my $postfix = $main::cfg{CMD_MTA};

      my $postmap = $main::cfg{CMD_POSTMAP};

      my $cp = $main::cfg{CMD_CP};

      system("$cp -f $backup $transport; $postmap $transport; $postfix reload 2>&1 >/dev/null");

    }

    push_el(\@main::el, 'dsk_quota_engine()', 'Ending...');

    return 0;

}

Optionally you can add a new config parameter to /etc/vhcp/vhcp.conf defining a custom bounce message text. If you don't a standard message is taken ("Quota exceeded.")

MTA_MSG_QUOTA_EXCEEDED = <Your custom quota full message>

Optionally shorten the interval for quota counting. By default quotas are checked once per day at 11:40pm (in my distribution). While this is fairly ok for webspace your mail users cannot afford to be unreachable for one whole day most probably.

As root do:

crontab -e

And change the line


40 23 * * *  /var/www/webadmin/engine/quota/vhcp-dsk-quota &>/var/log/vhcp/vhcp-qsk-quota.log

to (example "every 30 minutes")


*/30 * * * *  /var/www/webadmin/engine/quota/vhcp-dsk-quota &>/var/log/vhcp/vhcp-qsk-quota.log

See "man 5 crontab" for details.

Finally, restart vhcp_daemon:

/etc/init.d/vhcp_daemon restart

Sincerely,

Jan Obladen

Personal tools