Skip to content

Commit 9cfbc7e

Browse files
committed
imap.c: Add support for RFC 5465 NOTIFY.
Add support for NOTIFY, so that we can receive updates about new mail in folders besides the currently selected one. We already handled STATUS in IDLE responses, so we just needed to add the code to enable the NOTIFY extension appropriately. As part of this change, we also treat INBOX folders case-insensitively, as in InterLinked1/wssmail@e454859. This change (and the referenced one) are needed to account for scenarios such as when Other Users.example.Inbox appear in the LIST response and navigation, but STATUS updates use a different case, e.g. Other Users.example.INBOX.
1 parent 079d726 commit 9cfbc7e

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

.github/workflows/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ jobs:
7676
uses: actions/checkout@v4
7777
- name: Start build
7878
run: |
79+
sed -i 's|deb.debian.org/debian buster main|archive.debian.org/debian buster main|g' /etc/apt/sources.list
80+
sed -i 's|deb.debian.org/debian buster-updates main|archive.debian.org/debian buster-updates main|g' /etc/apt/sources.list
81+
sed -i 's|deb.debian.org/debian-security buster/updates main|archive.debian.org/debian-security buster/updates main|g' /etc/apt/sources.list
7982
apt-get update
8083
apt-get install -y git wget automake pkg-config libtool m4 build-essential libncurses-dev
8184
cd ..

imap.c

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ static void list_status_logger(mailstream *imap_stream, int log_type, const char
387387
return;
388388
}
389389

390-
/* Can be broken up across multiple log callback calls, so append to a dynstr */
390+
/* Can be broken up across multiple log callback calls */
391391
client_debug(6, "Log callback of %lu bytes for LIST-STATUS", size);
392392
/* Since this can take quite a bit of time, send an update here */
393393
/* Look for "* STATUS " */
@@ -875,6 +875,7 @@ static int __client_list(struct client *client)
875875
clist *imap_list;
876876
int res, i;
877877
int needunselect = 0;
878+
int numother = 0;
878879
/* This is a single-threaded application, so there is no concurrency risk to making this static/global,
879880
* and it's probably better to put such a large buffer in the global segment rather than on the stack. */
880881
static char list_status_buf[32768]; /* Hopefully big enough to fit the entire LIST-STATUS response */
@@ -956,6 +957,10 @@ static int __client_list(struct client *client)
956957
client->delimiter = mb_list->mb_delimiter;
957958
client->mailboxes[i].name = strdup(name);
958959

960+
if (IMAP_HAS_CAPABILITY(client, IMAP_CAPABILITY_NOTIFY) && !strncmp(name, "Other Users.", STRLEN("Other Users."))) {
961+
numother++;
962+
}
963+
959964
if (client->mailboxes[i].flags & IMAP_MAILBOX_NOSELECT) {
960965
continue;
961966
}
@@ -1019,6 +1024,53 @@ static int __client_list(struct client *client)
10191024
free(mb_names);
10201025
}
10211026

1027+
if (IMAP_HAS_CAPABILITY(client, IMAP_CAPABILITY_NOTIFY)) {
1028+
char cmd[2048];
1029+
char buf[sizeof(cmd) - 147];
1030+
int bytes = 0;
1031+
int otherinboxonly = numother > 10;
1032+
1033+
#define OTHER_USERS_WATCHALL_THRESHOLD 25
1034+
1035+
if (numother > OTHER_USERS_WATCHALL_THRESHOLD) {
1036+
clistiter *cur;
1037+
for (cur = clist_begin(imap_list); cur; cur = clist_next(cur)) {
1038+
struct mailimap_mailbox_list *mb_list = clist_content(cur);
1039+
const char *name = mb_list->mb_name;
1040+
if (!strncmp(name, "Other Users.", STRLEN("Other Users."))) {
1041+
if (otherinboxonly && !strcasestr(name, "INBOX")) {
1042+
continue;
1043+
}
1044+
} else if (!strncmp(name, "Shared Folders.", STRLEN("Shared Folders."))) {
1045+
if (!strcasestr(name, "INBOX")) {
1046+
continue;
1047+
}
1048+
} else {
1049+
continue; /* Personal namespace */
1050+
}
1051+
bytes += snprintf(buf + bytes, sizeof(buf) - bytes, " %c%s%c", '"', name, '"');
1052+
if ((size_t) bytes >= sizeof(buf)) {
1053+
client_warning("Truncation occured when building NOTIFY command");
1054+
break;
1055+
}
1056+
}
1057+
}
1058+
1059+
/* We just want notifications when something happens, having an untagged FETCH sent to us isn't that important.
1060+
* We can wake up and do some work if really needed. */
1061+
if (bytes > 0 && (size_t) bytes <= sizeof(buf)) {
1062+
snprintf(cmd, sizeof(cmd), "NOTIFY SET (SELECTED-DELAYED (MessageNew MessageExpunge FlagChange)) %s(personal (MessageNew MessageExpunge)) (mailboxes%s (MessageNew MessageExpunge))",
1063+
numother <= OTHER_USERS_WATCHALL_THRESHOLD ? "(subtree \"Other Users\" (MessageNew MessageExpunge FlagChange))" : "",
1064+
buf);
1065+
} else {
1066+
snprintf(cmd, sizeof(cmd), "NOTIFY SET (SELECTED-DELAYED (MessageNew MessageExpunge FlagChange)) (personal (MessageNew MessageExpunge))%s", numother ? " (subtree \"Other Users\" (MessageNew MessageExpunge))" : "");
1067+
}
1068+
res = mailimap_custom_command(client->imap, cmd);
1069+
if (MAILIMAP_ERROR(res)) {
1070+
client_warning("NOTIFY SET failed\n");
1071+
}
1072+
}
1073+
10221074
if (needunselect) {
10231075
/* UNSELECT itself is an extension. Only do if supported. */
10241076
if (IMAP_HAS_CAPABILITY(client, IMAP_CAPABILITY_UNSELECT)) {
@@ -1325,13 +1377,31 @@ static inline void masquerade_mailbox(struct mailbox *restrict new_mbox, struct
13251377
#undef COPY_MBOX_FIELD
13261378
}
13271379

1380+
static int str_case_ends_with(const char *str, const char *suffix)
1381+
{
1382+
size_t str_len;
1383+
size_t suffix_len;
1384+
1385+
str_len = strlen(str);
1386+
suffix_len = strlen(suffix);
1387+
1388+
return str_len >= suffix_len && !strcasecmp(str + str_len - suffix_len, suffix);
1389+
}
1390+
13281391
static struct mailbox *find_mailbox_by_name(struct client *client, const char *name)
13291392
{
13301393
int i;
13311394
for (i = 0; i < client->num_mailboxes; i++) {
1395+
/* In IMAP, mailbox names are case-sensitive...
1396+
* (even though most server implementations are case-insensitive, we can't assume that) */
13321397
if (!strcmp(client->mailboxes[i].name, name)) {
13331398
return &client->mailboxes[i];
13341399
}
1400+
/* ... except for INBOX (RFC 3501 5.1)
1401+
* We do case-insensitive matches for INBOX, as well as any subfolder named "INBOX". */
1402+
if ((!strcasecmp(name, "INBOX") || str_case_ends_with(name, ".INBOX")) && !strcasecmp(client->mailboxes[i].name, name)) {
1403+
return &client->mailboxes[i];
1404+
}
13351405
}
13361406
return NULL;
13371407
}

0 commit comments

Comments
 (0)