From 2129f2b3b70c3f07609112ecf27cec36a2c9f718 Mon Sep 17 00:00:00 2001 From: InterLinked1 <24227567+InterLinked1@users.noreply.github.com> Date: Tue, 22 Oct 2024 20:08:26 -0400 Subject: [PATCH] mod_mail_trash: Pass full directory path to maildir_indicate_expunged. Previously, we were only passing the base directory name to a function that expects the full path to the maildir's curdir. Construct and pass the correct path, and consolidate and add checks to make sure clearly invalid maildir paths are not being used. LBBS-25 #close --- bbs/backtrace.c | 5 +++-- include/mod_mail.h | 9 ++++++++- modules/mod_mail.c | 42 ++++++++++++++++++++++++++++++++++++--- modules/mod_mail_events.c | 4 +++- modules/mod_mail_trash.c | 14 ++++++++++++- nets/net_imap.c | 13 +++++------- 6 files changed, 71 insertions(+), 16 deletions(-) diff --git a/bbs/backtrace.c b/bbs/backtrace.c index 86b0e90..9c0295e 100644 --- a/bbs/backtrace.c +++ b/bbs/backtrace.c @@ -132,8 +132,9 @@ static void process_section(bfd *bfdobj, asection *section, void *obj) /* file can possibly be null even with a success result from bfd_find_nearest_line */ file = file ? file : ""; fn = strrchr(file, '/'); -#define FMT_INLINED "[%14s] %-17s %24s:%-5u %s()" -#define FMT_NOT_INLINED "[%14p] %-17s %24s:%-5u %s()" + /* The second argument is the .so and the third is the .c, so fmt length of arg 2 should be one more than of arg 3 */ +#define FMT_INLINED "[%14s] %-25s %24s:%-5u %s()" +#define FMT_NOT_INLINED "[%14p] %-25s %24s:%-5u %s()" snprintf(data->msg, BT_MSG_BUFF_LEN, inlined ? FMT_INLINED : FMT_NOT_INLINED, inlined ? "inlined" : (char *)(uintptr_t) data->pc, diff --git a/include/mod_mail.h b/include/mod_mail.h index a5942c8..3510152 100644 --- a/include/mod_mail.h +++ b/include/mod_mail.h @@ -269,7 +269,7 @@ unsigned long mailbox_quota_remaining(struct mailbox *mbox); /*! * \brief Get the maildir of a mailbox - * \param mbox Mailbox. If NULL, the top-level maildir path will be returned. + * \param mbox Mailbox. If NULL, the top-level (root) maildir path will be returned. * \return Full path to home maildir of mailbox. */ const char *mailbox_maildir(struct mailbox *mbox) __attribute__((returns_nonnull)); @@ -277,6 +277,13 @@ const char *mailbox_maildir(struct mailbox *mbox) __attribute__((returns_nonnull /*! \brief Get the string length of a mailbox's maildir */ size_t mailbox_maildir_len(struct mailbox *mbox); +/*! + * \brief Validate that a maildir path is valid + * \param maildir Full directory path, which will be validated to ensure it is strictly a subdirectory of mailbox_maildir(NULL) + * \retval -1 if invalid, 0 if valid + */ +int mailbox_maildir_validate(const char *maildir); + /*! * \brief Get the mailbox ID of a mailbox for personal mailboxes (same as the user ID of the user that owns this mailbox) * \param mbox Mailbox. Must not be NULL. diff --git a/modules/mod_mail.c b/modules/mod_mail.c index 5af3ce3..5371855 100644 --- a/modules/mod_mail.c +++ b/modules/mod_mail.c @@ -225,12 +225,15 @@ void mailbox_dispatch_event(struct mailbox_event *event) event->id = ++next_eventid; bbs_mutex_unlock(&eventidlock); - bbs_debug(6, "Dispatching mailbox event '%s'\n", mailbox_event_type_name(event->type)); - /* Sanity checks */ bbs_assert(!event->uids || event->numuids > 0); /* If we provided UIDs, then we must specify how many */ bbs_assert(!event->maildir || event->maildir[0] == '/'); /* This is supposed to be a full path, not relative */ + bbs_debug(6, "Dispatching mailbox event '%s' (maildir: %s)\n", mailbox_event_type_name(event->type), S_IF(event->maildir)); + if (!strlen_zero(event->maildir)) { + mailbox_maildir_validate(event->maildir); + } + /* At this point in time, we have an event, * with all the information that was "free" for the caller to provide. * e.g. If the caller already knew the MODSEQ, it should be filled in, @@ -890,11 +893,37 @@ const char *mailbox_maildir(struct mailbox *mbox) return mbox->maildir; } +#define CHECK_MAILDIR_LEN_INTEGRITY + size_t mailbox_maildir_len(struct mailbox *mbox) { +#ifdef CHECK_MAILDIR_LEN_INTEGRITY + if (strlen(mbox->maildir) != mbox->maildirlen) { + bbs_warning("maildir length mismatch: %lu != %lu\n", strlen(mbox->maildir), mbox->maildirlen); + bbs_soft_assert(0); + } +#endif return mbox->maildirlen; } +int mailbox_maildir_validate(const char *maildir) +{ + size_t dirlen = maildir ? strlen(maildir) : 0; + size_t rootmaildirlen = strlen(mailbox_maildir(NULL)); + + if (!dirlen) { + bbs_warning("Empty maildir provided\n"); + bbs_soft_assert(0); + return -1; + } + if (dirlen <= rootmaildirlen) { + bbs_warning("maildir (%s) len (%lu) <= maildir root (%s) len (%lu)?\n", maildir, dirlen, mailbox_maildir(NULL), rootmaildirlen); + bbs_soft_assert(0); + return -1; + } + return 0; +} + int mailbox_id(struct mailbox *mbox) { return (int) mbox->id; @@ -1015,6 +1044,10 @@ unsigned int mailbox_get_next_uid(struct mailbox *mbox, struct bbs_node *node, c } } + /* This should be a subdirectory of the root maildir, + * as there shouldn't be a UID file in that directory. */ + mailbox_maildir_validate(directory); + /* A single file that stores the UIDVALIDITY and UIDNEXT for this folder. * We can't use a single file since the UIDNEXT for each directory has to be unique. * We probably wouldn't want to use the same UIDVALIDITY globally either for the entire mailbox, @@ -1164,6 +1197,8 @@ static unsigned long __maildir_modseq(struct mailbox *mbox, const char *director UNUSED(mbox); /* Not currently used, but could be useful for future caching strategies? */ + bbs_soft_assert(strchr(directory, '/') != NULL); + /* Use a separate file from .uidvalidity for simplicity and ease of parsing, since this file is going to get used a lot more than the uidvalidity file * Also, since this file may be very large, since it needs to permanently store the MODSEQ of every single expunged message, forever. * For this reason, and for ease and speed of modifying the file in place, this is also a binary file, NOT a text file. */ @@ -1176,7 +1211,7 @@ static unsigned long __maildir_modseq(struct mailbox *mbox, const char *director const char *modseq; /* Order of traversal does not matter, so use opendir instead of scandir for efficiency. */ if (!(dir = opendir(directory))) { - bbs_error("Error opening directory - %s: %s\n", directory, strerror(errno)); + bbs_error("Error opening directory '%s': %s\n", directory, strerror(errno)); return 0; } @@ -1346,6 +1381,7 @@ unsigned long maildir_indicate_expunged(enum mailbox_event_type type, struct bbs mailbox_uid_lock(mbox); maxmodseq = __maildir_modseq(mbox, directory, 1); /* Must be atomic */ + bbs_soft_assert(strstr(directory, "/cur") != NULL); /* directory should be the curdir for this maildir, not the maildir itself */ snprintf(modseqfile, sizeof(modseqfile), "%s/../.modseqs", directory); fp = fopen(modseqfile, "ab"); if (!fp) { diff --git a/modules/mod_mail_events.c b/modules/mod_mail_events.c index 5767554..115ecd6 100644 --- a/modules/mod_mail_events.c +++ b/modules/mod_mail_events.c @@ -55,8 +55,10 @@ static int generate_imap_uri(struct mailbox *mbox, const char *maildir, char *re const char *mboxname = ""; size_t dirlen = maildir ? strlen(maildir) : 0; + mailbox_maildir_validate(maildir); + /* This check is on mbox (mailbox_maildir_validate just takes a path): */ if (dirlen && dirlen < mailbox_maildir_len(mbox)) { - bbs_warning("maildir len (%lu) is less than maildir root len (%lu)?\n", dirlen, mailbox_maildir_len(mbox)); + bbs_warning("maildir (%s) len (%lu) is less than maildir root (%s) len (%lu)?\n", maildir, dirlen, mailbox_maildir(NULL), mailbox_maildir_len(mbox)); return -1; } diff --git a/modules/mod_mail_trash.c b/modules/mod_mail_trash.c index bfadc83..7cec179 100644 --- a/modules/mod_mail_trash.c +++ b/modules/mod_mail_trash.c @@ -28,6 +28,9 @@ #include "include/mod_mail.h" +/* Purely for testing/debugging. Always delete one message in the Trash when the trash reaper runs. */ +/* #define FORCE_DELETE_ONE_IMMEDIATELY */ + static unsigned int trashdays = 7; static pthread_t trash_thread = 0; @@ -63,7 +66,11 @@ static int on_mailbox_trash(const char *dir_name, const char *filename, int seqn tstamp = st.st_ctime; elapsed = now - tstamp; bbs_debug(7, "Encountered in trash: %s (%" TIME_T_FMT " s ago)\n", fullname, elapsed); +#ifdef FORCE_DELETE_ONE_IMMEDIATELY + if (1 || elapsed > trashsec) { +#else if (elapsed > trashsec) { +#endif if (unlink(fullname)) { bbs_error("unlink(%s) failed: %s\n", fullname, strerror(errno)); } else { @@ -72,6 +79,9 @@ static int on_mailbox_trash(const char *dir_name, const char *filename, int seqn } maildir_parse_uid_from_filename(filename, &msguid); uintlist_append2(&traversal->a, &traversal->sa, &traversal->lengths, &traversal->allocsizes, msguid, (unsigned int) seqno); +#ifdef FORCE_DELETE_ONE_IMMEDIATELY + return 1; +#endif } return 0; } @@ -80,6 +90,7 @@ static void scan_mailboxes(void) { DIR *dir; struct dirent *entry; + char fulldir[512]; char trashdir[515]; /* Traverse each mailbox top-level maildir. The order of maildir traversal does not matter. */ @@ -109,7 +120,8 @@ static void scan_mailboxes(void) traversal.mbox = mbox; maildir_ordered_traverse(trashdir, on_mailbox_trash, &traversal); /* Traverse files in the Trash folder */ if (traversal.lengths) { - maildir_indicate_expunged(EVENT_MESSAGE_EXPIRE, NULL, mbox, entry->d_name, traversal.a, traversal.sa, traversal.lengths, 0); + snprintf(fulldir, sizeof(fulldir), "%s/%s/cur", mailbox_maildir(NULL), entry->d_name); /* Construct the full curdir path for this maildir */ + maildir_indicate_expunged(EVENT_MESSAGE_EXPIRE, NULL, mbox, fulldir, traversal.a, traversal.sa, traversal.lengths, 0); free_if(traversal.a); free_if(traversal.sa); } diff --git a/nets/net_imap.c b/nets/net_imap.c index 4d17ad6..1c43433 100644 --- a/nets/net_imap.c +++ b/nets/net_imap.c @@ -376,11 +376,10 @@ static int generate_status(struct imap_session *imap, const char *folder, char * static int generate_mailbox_name(struct imap_session *imap, const char *restrict maildir, char *restrict buf, size_t len) { - size_t rootlen, fulllen; + size_t rootlen; unsigned int userid; unsigned int muserid; const char *fulldir = maildir; - const char *rootdir = mailbox_maildir(NULL); int acl = 0; if (!bbs_user_is_registered(imap->node->user)) { @@ -388,12 +387,9 @@ static int generate_mailbox_name(struct imap_session *imap, const char *restrict } userid = imap->node->user->id; - rootlen = strlen(rootdir); - fulllen = strlen(fulldir); + rootlen = strlen(mailbox_maildir(NULL)); - if (fulllen <= rootlen) { - bbs_error("Maildir length %lu <= root maildir length %lu?\n", fulllen, rootlen); - bbs_soft_assert(0); + if (mailbox_maildir_validate(fulldir)) { return -1; } @@ -403,7 +399,8 @@ static int generate_mailbox_name(struct imap_session *imap, const char *restrict } if (strlen_zero(fulldir)) { - bbs_error("Invalid maildir\n"); + bbs_error("Empty maildir?\n"); + bbs_soft_assert(0); return -1; }