Corrupt maildirsize

qmail-pop3d with qmail-ldap and Maildir++ installed has a bug that can cause negative mail quota sizes to be reported on servers. At first, I couldn't tell if the problem was with with courier-imap or qmail, but it turned out to be the latter.

Basically, what happens is that a DELE of a missing file will report the deletion to the maildirsize file even though the file had already been deleted (ie. by imap). This case is more prevalent in systems that accept imap connections (outlook or web based email) than pop-only servers.

Here is an example:

telnet pop.mydomain.com 110
Trying 64.202.166.12...
Connected to mail.mydomain.com.
Escape character is '^]'.
+OK <12726.1076018210@pop-1-1b.domain.com>
user username@domain.com
+OK
pass x0x0x0x0
+OK
list
1 5629
2 6499
.
RETR 1
+OK 5629 octets
[[message]]
.
RETR 2
+OK 5629 octets
[[message]]
.
[[[ IMAP CONNECTS AND DELETES EMAIL 2 ]]]
DELE 1
+OK
DELE 2
+OK
QUIT
-Err Unable to unlink all deleted messages
$

Notice the error message. This means that the message cannot be deleted. With the ldap/maildir++ patch, though, this doesn't stop the quota from being updated. Since the file was already deleted by the imap session, and the deletion was already logged to maildirsize, the maildirquota reflects the delete two times. The fix is rather simple. Basically, I just moved the qmail update (qmail_rm) to a point in the code where I was sure the actual delete had occured. The patch follows:

--- qmail/qmail-pop3d.c 2003-10-29 05:09:32.000000000 -0700
+++ qmail.mailquota/qmail-pop3d.c       2004-02-27 13:31:54.000000000 -0700
@@ -199,6 +199,7 @@
 void pop3_quit()
 {
   int i;
+  unsigned long tempSize;
   quota_t q;

 /* qmail-ldap stuff */
@@ -207,9 +208,14 @@
   quota_calc(".",&qfd, &q);
   for (i = 0;i < numm;++i)
     if (m[i].flagdeleted) {
-      if ( qfd != -1 ) quota_rm(qfd, m[i].size, 1);
 /* end qmail-ldap stuff */
-      if (unlink(m[i].fn) == -1) err_nounlink();
+      tempSize = m[i].size;
+      if (unlink(m[i].fn) == -1) {
+         err_nounlink();
+      } else {
+         /* Delete quota only after the file has been deleted*/
+         if ( qfd != -1 ) quota_rm(qfd, tempSize, 1);
+      }
     }
     else
       if (str_start(m[i].fn,"new/")) {