courier-imap unable to open INBOX

From: Benjamin Lutz <mail_at_maxlor.com>
Date: Sun, 16 Feb 2025 13:52:41 UTC
Hello,

Recently I encountered a problem with courier-imap an two different 
machines. When trying to open the INBOX, courier-imap's imapd would 
answer with:

     NO Unable to open this mailbox.

The existance or absence of the problem can be quickly tested with:

     $ echo 'a SELECT INBOX' | imapd ~/Maildir

I'm running FreeBSD-13.4 and 14.2 on the affected machines, and 
courier-imap is installed via ports, the only enabled CONFIG options are 
INOTIFY and IPV6.

The first thing I tried is to recompile the port and all dependencies, 
but that didn't solve the problem.

So I dug into courier-imap's source code, finally coming to this line in 
maildircreate.c:149:

     if (stat( info->tmpname, &stat_buf) == 0)

What the code is trying to do here is to create a temporary file; it 
runs stat(2) on info->tmpname, expecting a -1 return value and errno set 
to ENOENT, because the file shouldn't currently exist. However, errno is 
not set to ENOENT, which finally results in the "NO Unable to open this 
mailbox." error.

In order to better understand it, I built courier-imap with debugging 
flags and added some debugging code around the line above:

     errno=-999;
     int stat_retval=stat( info->tmpname, &stat_buf);
     fprintf(stderr, "stat=%p, stat(%p, %p), stat_retval=%d, errno=%d,
         strerror(errno)=%s\n", stat, info->tmpname, &stat_buf,
         stat_retval, errno, strerror(errno));
     if (stat_retval == 0)

When SELECTing the INBOX now, I get:

     stat=0x823bc4f70, stat(0x2f67a4269000, 0x820949b40), stat_retval=-1,
         errno=-999, strerror(errno)=Unknown error: -999
     a NO Unable to open this mailbox.

So errno isn't changed by stat(2) (confirmed with other values than 
-999). This has me rather perplexed. How can a stat(2) call not set 
errno? As far as I can tell using GDB, that stat() call really is a call 
into libc, not some other function that might shadow stat(2).

I've extracted maildircreate.c into a separate project to build a 
minimal test, but when I just call the function in question 
(maildir_tmpcreate_fd, which the line above is from), then stat(2) works 
as expected, i.e. errno is set to 2.

Can anyone give me some explanations for the observed behavior? Or even 
just some ideas for analyzing it further?

The current stop-gap measure I've implemented to make courier-imap work 
again is the following patch:

     --- libs/maildir/maildircreate.c.orig   2022-05-23
         11:00:05.000000000 +0200
     +++ libs/maildir/maildircreate.c        2025-02-14
         22:19:39.434539000 +0100
     @@ -146,6 +146,7 @@
             strcat(info->tmpname, hostname);
             strcat(info->tmpname, len_buf);

     +       errno=ENOENT;
             if (stat( info->tmpname, &stat_buf) == 0)
             {
                     maildir_tmpcreate_free(info);

But obviously, that's not exactly a reliable solution.

Thanks,
Benjamin