DovecotXuidBug
Dealing with the Dovecot X-UID Problem
How I got bit by the dovecot X-UID bug and how I solved it.
Versions of the dovecot IMAP server prior to .99.15 contained a bug which would cause the insertion of too-large X-UID headers in mbox-format mail spool files. This causes Thunderbird to choke with a message like "The mail server responded: Invalid messageset: -2147483648". In a normal mail spool file the first delivered message should have an X-UID header value of 1, and for each message the X-UID header is incremented by one. If you examine the spool file manually in this case you will see X-UID values like 4270588972, which would not be possible unless a user had received more than four billion messages!
This bug potentially affects a large number of installed systems because even the most recent CentOS 4 (4.7, as of February 2009) ships with dovecot .99.13. Even worse, this bug can cause mailbox open failures indefinitely because the invalid X-UID headers are left in the spool file even when you upgrade to a newer version of dovecot. To completely fix this problem you have to both upgrade dovecot and fix existing spool files.
If you are running CentOS 4 or any .99.X version of dovecot on any platform, first upgrade to the latest stable dovecot (1.1.11 as of this writing). If you are on CentOS 4 you have to either build dovecot yourself or obtain a prebuilt rpm from a third party site. I used the rpm from ATrpms and it worked fine.
Here's a relevant blog post detailing the problem and a manual fix. Here's a Redhat bugzilla report on the issue.
Finally here is my cleanup script, which does three key things:
- Stops mail delivery via imap and smtp for duration of cleanup
- Removes all X-UID and X-IMAPbase headers
- Removes all user imap index files
Note that if you do not remove both the X-UID and X-IMAPbase headers dovecot will not regenerate the X-UID headers. Also my fix users formail to parse the mailbox, which means that messages embedded in other messages as MIME attachments can still have bogus X-UID headers.
#!/bin/bash
# script to clean bad X-UID and X-IMAPbase headers from
# username spool files, and erase dovecot imap index files.
# all of this stuff gets regenerated the next time dovecot
# accesses the mailbox.
daemons="dovecot sendmail"
# remember to including trailing slash if spooldir is a symlink!
spooldir="/var/mail/"
[ $(id -u) -eq 0 ] || \
{ echo "this script must be run as root, aborting" >&2; exit 1; };
[ -r $spooldir ] || \
{ echo "$spooldir not readable, aborting" >&2; exit 1; };
# we can't be delivering mail while we are cleaning up spool files.
for daemon in $daemons
do
echo "shutting down $daemon"
if ps -C $daemon >/dev/null
then
/etc/init.d/$daemon stop
fi
sleep 5
if ps -C $daemon >/dev/null
then
{ echo "$daemon still running, aborting" >&2 && exit 1; };
fi
done
for file in $(find $spooldir -type f -maxdepth 1)
do
username=$(basename $file)
if id $username >/dev/null
then
# user exists, continue
spooltemp=$(mktemp) || \
{ echo "problem creating tempfile, aborting." >&2 && exit 1; };
echo "removing X-UID and X-IMAPbase headers from $file"
if ! formail -I X-UID: -I X-IMAPbase: -s < $file >$spooltemp
then
echo \
"not replacing $file with $spooltemp because formail reported an error"
else
# cat file to preserve permissions. this is kind of slow.
cat $spooltemp >$file
rm $spooltemp
echo "removing dovecot index directory /home/$username/mail/.imap"
rm -rf /home/$username/mail/.imap
fi
else
echo "skipping $file because user $username doesn't exist."
fi
done
# all done, start daemons back up to resume mail delivery.
for daemon in dovecot sendmail
do
/etc/init.d/$daemon start
done
exit 0

