defaultspam task breaks Dovecot 2.4 subscriptions files (v2 format): header sorted out of line 1, IMAP subfolders disappear

castris

Verified User
Joined
Apr 16, 2021
Messages
172
Location
Arcenillas
Versions: DirectAdmin 1.713, Dovecot 2.4.5, Debian/Ubuntu (reproduced on two independent servers).

Summary

Saving the SpamAssassin/Rspamd "deliver to spam folder" setting (CMD_API_SPAMASSASSIN action=save where=userspamfolder) queues action=defaultspam. That task appends INBOX.spam to every mailbox's Maildir/subscriptions and then sorts the whole file alphabetically, dropping blank lines.

Dovecot 2.4 writes subscriptions in its v2 format: the first line must be the header V<TAB>2, followed by an empty line, with the folder hierarchy separated by TAB (Archive<TAB>2023). After defaultspam sorts the file, the header is no longer on line 1, so Dovecot parses the file as v1 and treats the TAB as part of the folder name. LSUB then returns non-existent folders such as Archive<TAB>2023 (and even V<TAB>2), while the real subfolders are no longer subscribed. IMAP clients that show only subscribed folders (Thunderbird by default) hide every subfolder of the account. No messages are lost; only the subscription list is corrupted.

Steps to reproduce

  1. Create a mailbox, e.g. [email protected]. DirectAdmin creates subscriptions in v1 format (single line INBOX.spam).
  2. Let Dovecot 2.4 write the file in v2 format. Simplest way: delete the file and subscribe some folders, which is what happens on any mailbox whose file was created by Dovecot 2.4 (e.g. after a migration):
    Bash:
    rm /home/USER/imap/example.com/test/Maildir/subscriptions
    for f in Archive Archive.2023 Archive.2024 Varios; do
      doveadm mailbox create -u [email protected] "$f" 2>/dev/null
      doveadm mailbox subscribe -u [email protected] "$f"
    done
    cat -A /home/USER/imap/example.com/test/Maildir/subscriptions
    # V^I2$
    # $
    # Archive$
    # Archive^I2023$
    # Archive^I2024$
    # Varios$
  3. Save the spam-folder setting for the domain (panel or API):
    Bash:
    curl -s "$DA_URL/CMD_API_SPAMASSASSIN" -d action=save -d domain=example.com -d where=userspamfolder ...
  4. Wait for dataskq to run action=defaultspam&domain=example.com&username=USER.

Actual result

Code:
Archive$
Archive^I2023$
Archive^I2024$
INBOX.spam$
V^I2$
Varios$

  • The new entry is written in v1 form (INBOX.spam, dot separator) inside a v2 file.
  • The whole file is sorted in C order, header included, so V<TAB>2 ends up in the middle.
  • The empty line after the header is removed.

Bash:
doveadm mailbox list -s -u [email protected] | cat -A
# Archive$
# Archive^I2023$
# Archive^I2024$
# INBOX.spam$
# V^I2$
# Varios$

Expected result

The file keeps its format: for a v2 file, V<TAB>2 stays on line 1 followed by the empty line, existing entries keep their order and separator, and the spam folder is added as INBOX<TAB>spam. For a v1 file (no header), the current behaviour is harmless.

Suggested fix

Either of these would avoid the problem:

  • Let Dovecot manage the file: doveadm mailbox subscribe -u <user> INBOX.spam instead of editing subscriptions directly. Dovecot keeps whatever format the file already has.
  • If the file is edited directly: detect a first line equal to V<TAB>2, keep the header and the following empty line untouched, append the entry with TAB as separator, and do not sort the file.

Impact

Every account with Dovecot 2.4 v2 subscriptions files is affected each time the spam-folder setting is saved for one of its domains. On the server where we found it, 52 of 68 mailboxes of one account were broken by a single save; the users saw all their subfolders "disappear" in Thunderbird.

Workaround

Rewrite each affected file with the header on line 1:

Bash:
f=/home/USER/imap/DOMAIN/BOX/Maildir/subscriptions
tmp=$(mktemp)
{ printf 'V\t2\n\n'
  grep -vP '^V\t2$' "$f" | grep -v '^$' | sed 's/^INBOX\.spam$/INBOX\tspam/' | awk '!seen[$0]++'
} > "$tmp" && cat "$tmp" > "$f" && rm -f "$tmp"

Detection: grep -nP '^V\t2$' subscriptions returns a line number other than 1, or doveadm mailbox list -s -u <user> | cat -A | grep '\^I' returns any line.
 
Back
Top