Skip to content

Commit 7ff8e40

Browse files
committed
net_imap: Fix false warning in __gen_keyword_names.
When moving messages from a mailbox (and possibly other operations) with certain flags to a mailbox with different keyword mappings e.g., Mailbox A (test): a -> encrypted Mailbox B (Trash): a -> NonJunk, b -> encrypted there would be a warning like this: File has 1 custom flags (b), but we only have mappings for 0 of them ()? For one, this was misleading, since actually there was a mapping, just not for the specific keyword, so this is a more appropriate warning: 'Sb' has 1 custom flag (b), but /home/bbs/maildir/1/.test/.keywords only has mappings for 0 of them (a)? Moreover, this warning is still not appropriate, because the keyword mapping in question does exist as 'b' in the destination mailbox, but we were using the source mailbox (since imap->dir is passed in to this function by default). Add a few variants of calling APIs that allow us to pass in the the destination maildir, which avoids this error.
1 parent 5e5970b commit 7ff8e40

File tree

4 files changed

+65
-37
lines changed

4 files changed

+65
-37
lines changed

nets/net_imap.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,7 +1250,7 @@ static void do_qresync(struct imap_session *imap, unsigned long lastmodseq, cons
12501250
bbs_error("Message file %s contains no flags?\n", entry->d_name);
12511251
goto next;
12521252
}
1253-
generate_flag_names_full(imap, flags, flagsbuf, sizeof(flagsbuf), &buf, &len);
1253+
generate_flag_names_full(imap, entry->d_name, flags, flagsbuf, sizeof(flagsbuf), &buf, &len);
12541254
imap_send(imap, "%u FETCH (UID %u %s) MODSEQ (%lu)", seqno, uid, flagsbuf, modseq);
12551255
}
12561256
next:
@@ -1307,7 +1307,7 @@ static int select_examine_response(struct imap_session *imap, enum select_type r
13071307
int condstore_just_enabled = 0;
13081308
unsigned int lastmodseq = 0;
13091309
char *uidrange = NULL, *seqrange = NULL;
1310-
int numkeywords = gen_keyword_names(imap, NULL, keywords, sizeof(keywords)); /* prepends a space before all of them, so this works out great */
1310+
int numkeywords = gen_keyword_names(imap, NULL, NULL, keywords, sizeof(keywords)); /* prepends a space before all of them, so this works out great */
13111311

13121312
if (!strlen_zero(s)) {
13131313
if (STARTS_WITH(s, "(QRESYNC")) {
@@ -2777,7 +2777,7 @@ static int copy_append_cb(const char *dir_name, const char *filename, int seqno,
27772777
bbs_error("Message file %s contains no flags?\n", filename);
27782778
return -1;
27792779
}
2780-
generate_flag_names_full(ca->imap, flags, flagnames, sizeof(flagnames), &pos, &len);
2780+
generate_flag_names_full(ca->imap, fullname, flags, flagnames, sizeof(flagnames), &pos, &len);
27812781
/* Here's something awkward: if you were to just send flagnames as is in the APPEND command,
27822782
* then something like FLAGS (\Seen) might be sent as the flag contents,
27832783
* and libetpan has issues with mailbox flags that contain parentheses,
@@ -3477,13 +3477,13 @@ static int process_flags(struct imap_session *imap, char *s, int usinguid, const
34773477
if (keywords[0]) { /* Current keywords */
34783478
size_t slen = strlen(flagstr);
34793479
/*! \todo We should not append a space before if we're at the beginning of the buffer */
3480-
gen_keyword_names(imap, keywords, flagstr + slen, sizeof(flagstr) - slen); /* Append keywords (include space before) */
3480+
gen_keyword_names(imap, entry->d_name, keywords, flagstr + slen, sizeof(flagstr) - slen); /* Append keywords (include space before) */
34813481
}
34823482
if (!silent) { /* Send the response if not silent */
34833483
if (imap->createdkeyword) {
34843484
/* Server SHOULD send untagged response when a new keyword is created */
34853485
char allkeywords[256] = "";
3486-
gen_keyword_names(imap, NULL, allkeywords, sizeof(allkeywords)); /* prepends a space before all of them, so this works out great */
3486+
gen_keyword_names(imap, NULL, NULL, allkeywords, sizeof(allkeywords)); /* prepends a space before all of them, so this works out great */
34873487
imap_send(imap, "FLAGS (%s%s)", IMAP_FLAGS, allkeywords);
34883488
}
34893489
/*! \todo This is repetitive, clean this up so we're not duplicating this log for UID/no UID, MODSEQ/no MODSEQ, silent/not silent */
@@ -3573,7 +3573,7 @@ static int process_flags(struct imap_session *imap, char *s, int usinguid, const
35733573
if (imap->numappendkeywords) {
35743574
size_t slen = strlen(changedflags);
35753575
/*! \todo We should not append a space before if we're at the beginning of the buffer */
3576-
gen_keyword_names(imap, imap->appendkeywords, changedflags + slen, sizeof(changedflags) - slen);
3576+
gen_keyword_names(imap, "", imap->appendkeywords, changedflags + slen, sizeof(changedflags) - slen);
35773577
}
35783578
mailbox_initialize_event(&e, flagop < 1 ? EVENT_FLAGS_CLEAR : EVENT_FLAGS_SET, imap->node, imap->mbox, imap->dir);
35793579
e.uids = aflagsset;

nets/net_imap/imap_server_fetch.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ static int process_fetch_flags(struct imap_session *imap, const char *filename,
6060
safe_strncpy(inflags + c, flags, sizeof(inflags) - (size_t) c);
6161
flags = inflags;
6262
}
63-
generate_flag_names_full(imap, flags, response, responselen, buf, len);
63+
generate_flag_names_full(imap, filename, flags, response, responselen, buf, len);
6464
return 0;
6565
}
6666

nets/net_imap/imap_server_flags.c

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include <string.h>
2323
#include <ctype.h>
24+
#include <libgen.h> /* use dirname */
2425

2526
#if defined(linux) && defined(__GLIBC__)
2627
#include <bsd/string.h>
@@ -102,18 +103,27 @@ void parse_keyword(struct imap_session *imap, const char *s, const char *directo
102103
fclose(fp);
103104
}
104105

105-
int __gen_keyword_names(const char *s, char *inbuf, size_t inlen, const char *directory)
106+
int __gen_keyword_names(const char *msgfilename, const char *flagstr, char *inbuf, size_t inlen, const char *directory)
106107
{
107108
FILE *fp;
108109
char fbuf[32];
109110
char filename[266];
110111
char *buf = inbuf;
111112
int matches = 0;
112113
int left = (int) inlen;
113-
const char *custom_start = s;
114+
const char *custom_start = flagstr;
115+
const char *tmp;
114116

115117
snprintf(filename, sizeof(filename), "%s/.keywords", directory);
116118

119+
/* If the string contains more than just keywords, skip to the start of the keywords.
120+
* Do this first to make future strchr calls and iterations more efficient. */
121+
if (custom_start) {
122+
while (*custom_start && !islower(*custom_start)) {
123+
custom_start++;
124+
}
125+
}
126+
117127
*buf = '\0';
118128
fp = fopen(filename, "r");
119129
if (!fp) {
@@ -124,20 +134,22 @@ int __gen_keyword_names(const char *s, char *inbuf, size_t inlen, const char *di
124134
}
125135

126136
while ((fgets(fbuf, sizeof(fbuf), fp))) {
127-
if (!s || strchr(s, fbuf[0])) {
137+
if (!custom_start || strchr(custom_start, fbuf[0])) { /* Either it's a keyword letter of interest or we have no filter, match */
128138
matches++;
129139
bbs_strterm(fbuf, '\n');
130140
SAFE_FAST_COND_APPEND_NOSPACE(inbuf, inlen, buf, left, 1, " %s", fbuf + 2);
131141
}
132142
}
133143

134-
if (s) {
144+
if (custom_start) {
135145
int keywordslen = 0;
136-
while (!strlen_zero(s)) {
137-
if (islower(*s)) {
146+
/* Count how many keyword letters there are (as opposed to normal flags) */
147+
tmp = custom_start;
148+
while (*tmp) {
149+
if (islower(*tmp)) {
138150
keywordslen++;
139151
}
140-
s++;
152+
tmp++;
141153
}
142154

143155
if (keywordslen > matches) {
@@ -146,19 +158,14 @@ int __gen_keyword_names(const char *s, char *inbuf, size_t inlen, const char *di
146158
int mpos = 0;
147159
rewind(fp);
148160
while ((fgets(fbuf, sizeof(fbuf), fp))) {
149-
if (!s || strchr(s, fbuf[0])) {
150-
mappings[mpos++] = fbuf[0];
151-
if (mpos >= (int) sizeof(mappings) - 1) {
152-
break;
153-
}
161+
mappings[mpos++] = fbuf[0];
162+
if (mpos >= (int) sizeof(mappings) - 1) {
163+
break;
154164
}
155165
}
156166
mappings[mpos] = '\0';
157-
while (*custom_start && !islower(*custom_start)) {
158-
custom_start++;
159-
}
160167

161-
bbs_warning("File has %d custom flags (%s), but we only have mappings for %d of them (%s)?\n", keywordslen, custom_start, matches, mappings);
168+
bbs_warning("'%s' has %d custom flag%s (%s), but %s only has mappings for %d of them (%s)?\n", msgfilename, keywordslen, ESS(keywordslen), custom_start, filename, matches, mappings);
162169
}
163170
}
164171
fclose(fp);
@@ -340,7 +347,7 @@ int translate_maildir_flags(struct imap_session *imap, const char *oldmaildir, c
340347
* and abstracted away from the IMAP module (i.e. done automatically on any move or copy). */
341348

342349
/* Get the old keyword names themselves */
343-
numkeywords = __gen_keyword_names(oldfilename, keywords, sizeof(keywords), oldmaildir); /* prepends a space before all of them, so this (usually) works out great */
350+
numkeywords = __gen_keyword_names(oldfilenamefull, oldfilename, keywords, sizeof(keywords), oldmaildir); /* prepends a space before all of them, so this (usually) works out great */
344351

345352
if (numkeywords <= 0) {
346353
#ifdef EXTRA_DEBUG
@@ -376,27 +383,29 @@ int translate_maildir_flags(struct imap_session *imap, const char *oldmaildir, c
376383
return maildir_msg_setflags(imap, 0, oldfilenamefull, newflagletters);
377384
}
378385

379-
void generate_flag_names_full(struct imap_session *imap, const char *filename, char *bufstart, size_t bufsize, char **bufptr, int *lenptr)
386+
static void generate_flag_names_full_dir(struct imap_session *imap, const char *filename, const char *flagstr, char *bufstart, size_t bufsize, char **bufptr, int *lenptr, const char *keyworddir)
380387
{
381388
char flagsbuf[256] = "";
382389
int has_flags;
383390
int custom_keywords;
391+
const char *flagstart = flagstr;
384392

385393
char *buf = *bufptr;
386394
size_t len = (size_t) *lenptr;
387395

388-
if (isdigit(*filename)) { /* We have an entire filename */
389-
filename = strchr(filename, ':'); /* Skip everything before the flags, so we don't e.g. interpret ,S= as the Seen flag. */
390-
if (!filename) {
391-
filename = ""; /* There ain't no flags here */
396+
if (isdigit(*flagstart)) { /* We have an entire filename */
397+
flagstart = strchr(flagstart, ':'); /* Skip everything before the flags, so we don't e.g. interpret ,S= as the Seen flag. */
398+
if (!flagstart) {
399+
flagstart = ""; /* There ain't no flags here */
392400
}
393401
} /* else, must just have the "flags" portion of the filename to begin with */
394402

395-
gen_flag_names(filename, flagsbuf, sizeof(flagsbuf));
403+
gen_flag_names(flagstart, flagsbuf, sizeof(flagsbuf));
396404
has_flags = flagsbuf[0] ? 1 : 0;
397405
SAFE_FAST_COND_APPEND(bufstart, bufsize, buf, len, 1, "FLAGS (%s", flagsbuf);
398406
/* If there are any keywords (custom flags), include those as well */
399-
custom_keywords = gen_keyword_names(imap, filename, flagsbuf, sizeof(flagsbuf));
407+
UNUSED(imap);
408+
custom_keywords = gen_keyword_names_dir(filename, flagstr, flagsbuf, sizeof(flagsbuf), keyworddir);
400409
if (has_flags) {
401410
SAFE_FAST_COND_APPEND_NOSPACE(bufstart, bufsize, buf, len, custom_keywords > 0, "%s", flagsbuf);
402411
} else {
@@ -409,13 +418,20 @@ void generate_flag_names_full(struct imap_session *imap, const char *filename, c
409418
*lenptr = (int) len;
410419
}
411420

421+
void generate_flag_names_full(struct imap_session *imap, const char *filename, const char *flagstr, char *bufstart, size_t bufsize, char **bufptr, int *lenptr)
422+
{
423+
/* By default, gen_keyword_names uses imap->dir, so pass that to gen_keyword_names_dir */
424+
return generate_flag_names_full_dir(imap, filename, flagstr, bufstart, bufsize, bufptr, lenptr, imap->dir);
425+
}
426+
412427
int maildir_msg_setflags_modseq(struct imap_session *imap, int seqno, const char *origname, const char *newflagletters, unsigned long *newmodseq)
413428
{
414429
char fullfilename[524];
415430
char newflags[512] = "";
416431
char *newbuf = newflags;
417432
int newlen = sizeof(newflags);
418433
char dirpath[256];
434+
char newmaildir[256];
419435
char *tmp, *filename;
420436
unsigned long modseq;
421437

@@ -495,7 +511,10 @@ int maildir_msg_setflags_modseq(struct imap_session *imap, int seqno, const char
495511
/* If newmodseq is not NULL, then we need to send responses as needed. XXX What if it's not? */
496512

497513
/* Send unilateral untagged FETCH responses to everyone except this session, to notify of the new flags */
498-
generate_flag_names_full(imap, newflagletters, newflags, sizeof(newflags), &newbuf, &newlen);
514+
safe_strncpy(newmaildir, dirpath, sizeof(newmaildir));
515+
/* We pass the new maildir at this point, as the default would be to use imap->dir, i.e. the old maildir,
516+
* but the new maildir is what will have the correct keyword mapping. */
517+
generate_flag_names_full_dir(imap, fullfilename, newflagletters, newflags, sizeof(newflags), &newbuf, &newlen, dirname(newmaildir));
499518
if (seqno) { /* Skip for merely translating flag mappings between maildirs */
500519
char *end;
501520
unsigned int uid;

nets/net_imap/imap_server_flags.h

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
/* The implementation of how keywords are stored is based on how Dovecot stores keywords:
5151
* https://doc.dovecot.org/admin_manual/mailbox_formats/maildir/
5252
* We use 26 lowercase letters, to differentiate from IMAP flags (uppercase letters).
53-
* However, we don't a uidlist file, and we store the keywords in a separate file.
53+
* However, we don't use a uidlist file, and we store the keywords in a separate file.
5454
* The implementation is handled fully in net_imap, since other modules don't care about keywords.
5555
*/
5656

@@ -59,9 +59,18 @@
5959
/*! \brief Check filename for the mapping for keyword. If one does not exist and there is room ( < 26), it will be created. */
6060
void parse_keyword(struct imap_session *imap, const char *s, const char *directory, int create);
6161

62-
#define gen_keyword_names(imap, s, inbuf, inlen) __gen_keyword_names(s, inbuf, inlen, imap->dir)
62+
#define gen_keyword_names(imap, filename, flagstr, inbuf, inlen) __gen_keyword_names(filename, flagstr, inbuf, inlen, imap->dir)
63+
#define gen_keyword_names_dir(filename, flagstr, inbuf, inlen, dir) __gen_keyword_names(filename, flagstr, inbuf, inlen, dir)
6364

64-
int __gen_keyword_names(const char *s, char *inbuf, size_t inlen, const char *directory);
65+
/*!
66+
* \brief Generate keyword names from the letters in a maildir file
67+
* \param msgfilename Filename of message file, used only for logging. NULL to simply fetch all keywords.
68+
* \param flagstr String containing keyword letters (e.g. filename). NULL to simply fetch all keywords.
69+
* \param[out] inbuf Keyword names
70+
* \param inlen Size of inbuf
71+
* \param directory The maildir containing the keywords
72+
*/
73+
int __gen_keyword_names(const char *msgfilename, const char *flagstr, char *inbuf, size_t inlen, const char *directory);
6574

6675
#define parse_flags_string(imap, s) __parse_flags_string(imap, s, imap->dir)
6776

@@ -102,8 +111,8 @@ int restrict_flags(int acl, int *flags);
102111
*/
103112
int translate_maildir_flags(struct imap_session *imap, const char *oldmaildir, const char *oldfilenamefull, const char *oldfilename, const char *newmaildir, int destacl);
104113

105-
/*! \brief base filename The file name of the message file. Please do not provide the full filepath. */
106-
void generate_flag_names_full(struct imap_session *imap, const char *filename, char *bufstart, size_t bufsize, char **bufptr, int *lenptr);
114+
/*! \brief base flagstr The file name of the message file (importantly, we want the part containing the flags). Please do not provide the full filepath. */
115+
void generate_flag_names_full(struct imap_session *imap, const char *filename, const char *flagstr, char *bufstart, size_t bufsize, char **bufptr, int *lenptr);
107116

108117
int maildir_msg_setflags_modseq(struct imap_session *imap, int seqno, const char *origname, const char *newflagletters, unsigned long *newmodseq);
109118

0 commit comments

Comments
 (0)