Restoring yanked messages to the Qmail queue

Submitted by davidc on Mon, 12/08/2014 - 20:22

qmail-remove provides a means to easily remove messages from the Qmail queue, but restoring messages is rather more complicated. Here is a quick shell script that will allow messages to be correctly restored, undoing the yank.

I've been privileged to not have to deal with Qmail much in recent years, so when I had occasion to mop-up a client's mail queue from a deluge of spam using qmail-remove, I missed the all-important "-p" flag before the search string - resulting in the entire queue being yanked.

After cleaning up the actual spam, I was left with a yanked directory that contained messages I needed to restore to the queue.

There's no good way to do this using the provided tools - the closest would be to parse the control files (info, local and remote) and re-inject them using qmail-queue. This would have had the side-effect of modifying the message (adding a new Received header), as well as it being complicated to parse correctly to avoid redelivery for any recipients that had already received their copy.

The best solution would be to move the files back exactly where they came from, however qmail-remove doesn't provide a tool to do this. So, here is a short bash script that does exactly that. It does a bit of consistency checking, for example checking that the .mess file's inode still matches its filename, but generally expects everything to be where it should be.

Simply type unyank <msgid>, where msgid is the message number in the filename (you can also specify a full filename for ease of use - it will be stripped down to just them msgid).

You can also restore the entire yanked directory using something like for i in /var/qmail/queue/yanked/*mess; do ./unyank $i; done

Important: You must shut down qmail-send before running this tool as it directly modifies the queue directory!

Adjust the directories at the top of the script if you are non-standard locations. You will also need to adjust the confsplit variable if you have changed from the default of 23.

# Author: David Croft
# To the extent possible under law, the person who associated CC0 with this work has
# waived all copyright and related or neighboring rights to this work.
if [ -z "$1" ]; then
    echo "syntax: $0 <msgid>"
    echo "Restores the message to the queue. Make sure qmail-send is not running!"
    exit 1
# allow the msgid to be passed as a (possibly absolute) filename - strip it down to just the msgid
msgid=`basename $1 | sed 's/\.\(info\|mess\|remote\|bounce\|intd\|todo\)//'`
# check that the mess file exists
if [ ! -f $mess ]; then
    echo "Message file does not exist ($mess)"
    exit 1
# validate inode number still matches filename
inode=`ls -di $mess | cut -d ' ' -f 1`
if [ "$msgid" -ne "$inode" ]; then
    echo "Cannot unyank message $msgid - inode is different ($inode)"
    exit 1
# Calculate the hashed dir
let dirnum="$msgid % $confsplit"
echo "restoring message $msgid to split dir $dirnum"
# Restore the message
mv $yankeddir/$msgid.mess $queuedir/mess/$dirnum/$msgid
mv $yankeddir/$ $queuedir/info/$dirnum/$msgid
if [ -f $yankeddir/$msgid.remote ]; then
    mv $yankeddir/$msgid.remote $queuedir/remote/$dirnum/$msgid
if [ -f $yankeddir/$msgid.local ]; then
    mv $yankeddir/$msgid.local $queuedir/local/$dirnum/$msgid
if [ -f $yankeddir/$msgid.intd ]; then
    mv $yankeddir/$msgid.intd $queuedir/intd/$dirnum/$msgid
if [ -f $yankeddir/$msgid.bounce ]; then
    mv $yankeddir/$msgid.bounce $queuedir/bounce/$msgid
if [ -f $yankeddir/$msgid.todo ]; then
    mv $yankeddir/$msgid.todo $queuedir/todo/$msgid