1 /* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
2
3 #include "imap-common.h"
4 #include "str.h"
5 #include "mailbox-list-iter.h"
6 #include "imap-quote.h"
7 #include "imap-commands.h"
8 #include "imap-fetch.h"
9 #include "imap-list.h"
10 #include "imap-status.h"
11 #include "imap-notify.h"
12
13 #define IMAP_NOTIFY_MAX_NAMES_PER_NS 100
14
15 static const char *imap_notify_event_names[] = {
16 "MessageNew", "MessageExpunge", "FlagChange", "AnnotationChange",
17 "MailboxName", "SubscriptionChange", "MailboxMetadataChange",
18 "ServerMetadataChange"
19 };
20
21 static int
cmd_notify_parse_event(const struct imap_arg * arg,enum imap_notify_event * event_r)22 cmd_notify_parse_event(const struct imap_arg *arg,
23 enum imap_notify_event *event_r)
24 {
25 const char *str;
26 unsigned int i;
27
28 if (!imap_arg_get_atom(arg, &str))
29 return -1;
30
31 for (i = 0; i < N_ELEMENTS(imap_notify_event_names); i++) {
32 if (strcasecmp(str, imap_notify_event_names[i]) == 0) {
33 *event_r = (enum imap_notify_event)(1 << i);
34 return 0;
35 }
36 }
37 return -1;
38 }
39
40 static int
cmd_notify_parse_fetch(struct imap_notify_context * ctx,const struct imap_arg * list)41 cmd_notify_parse_fetch(struct imap_notify_context *ctx,
42 const struct imap_arg *list)
43 {
44 if (list->type == IMAP_ARG_EOL)
45 return -1; /* at least one attribute must be set */
46 return imap_fetch_att_list_parse(ctx->client, ctx->pool, list,
47 &ctx->fetch_ctx, &ctx->error);
48 }
49
50 static int
cmd_notify_set_selected(struct imap_notify_context * ctx,const struct imap_arg * events)51 cmd_notify_set_selected(struct imap_notify_context *ctx,
52 const struct imap_arg *events)
53 {
54 #define EV_NEW_OR_EXPUNGE \
55 (IMAP_NOTIFY_EVENT_MESSAGE_NEW | IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE)
56 const struct imap_arg *list, *fetch_att_list;
57 const char *str;
58 enum imap_notify_event event;
59
60 if (imap_arg_get_atom(events, &str) &&
61 strcasecmp(str, "NONE") == 0) {
62 /* no events for selected mailbox. this is also the default
63 when NOTIFY command doesn't specify it explicitly */
64 if (events[1].type != IMAP_ARG_EOL)
65 return -1; /* no extra parameters */
66 return 0;
67 }
68
69 if (!imap_arg_get_list(events, &list))
70 return -1;
71 if (events[1].type != IMAP_ARG_EOL)
72 return -1; /* no extra parameters */
73 if (list->type == IMAP_ARG_EOL)
74 return -1; /* at least one event */
75
76 for (; list->type != IMAP_ARG_EOL; list++) {
77 if (cmd_notify_parse_event(list, &event) < 0)
78 return -1;
79 ctx->selected_events |= event;
80 ctx->global_used_events |= event;
81
82 if (event == IMAP_NOTIFY_EVENT_MESSAGE_NEW &&
83 imap_arg_get_list(&list[1], &fetch_att_list)) {
84 /* MessageNew: list of fetch-att */
85 if (cmd_notify_parse_fetch(ctx, fetch_att_list) < 0)
86 return -1;
87 list++;
88 }
89 }
90
91 /* if MessageNew or MessageExpunge is specified, both of them must */
92 if ((ctx->selected_events & EV_NEW_OR_EXPUNGE) != 0 &&
93 (ctx->selected_events & EV_NEW_OR_EXPUNGE) != EV_NEW_OR_EXPUNGE) {
94 ctx->error = "MessageNew and MessageExpunge must be together";
95 return -1;
96 }
97
98 /* if FlagChange or AnnotationChange is specified,
99 MessageNew and MessageExpunge must also be specified */
100 if ((ctx->selected_events &
101 (IMAP_NOTIFY_EVENT_FLAG_CHANGE |
102 IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE)) != 0 &&
103 (ctx->selected_events & IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE) == 0) {
104 ctx->error = "FlagChange requires MessageNew and MessageExpunge";
105 return -1;
106 }
107 return 0;
108 }
109
110 static struct imap_notify_namespace *
imap_notify_namespace_get(struct imap_notify_context * ctx,struct mail_namespace * ns)111 imap_notify_namespace_get(struct imap_notify_context *ctx,
112 struct mail_namespace *ns)
113 {
114 struct imap_notify_namespace *notify_ns;
115
116 array_foreach_modifiable(&ctx->namespaces, notify_ns) {
117 if (notify_ns->ns == ns)
118 return notify_ns;
119 }
120 notify_ns = array_append_space(&ctx->namespaces);
121 notify_ns->ctx = ctx;
122 notify_ns->ns = ns;
123 p_array_init(¬ify_ns->mailboxes, ctx->pool, 4);
124 return notify_ns;
125 }
126
127 static struct imap_notify_mailboxes *
imap_notify_mailboxes_get(struct imap_notify_namespace * notify_ns,enum imap_notify_type type,enum imap_notify_event events)128 imap_notify_mailboxes_get(struct imap_notify_namespace *notify_ns,
129 enum imap_notify_type type,
130 enum imap_notify_event events)
131 {
132 struct imap_notify_mailboxes *notify_boxes;
133
134 array_foreach_modifiable(¬ify_ns->mailboxes, notify_boxes) {
135 if (notify_boxes->type == type &&
136 notify_boxes->events == events)
137 return notify_boxes;
138 }
139 notify_boxes = array_append_space(¬ify_ns->mailboxes);
140 notify_boxes->type = type;
141 notify_boxes->events = events;
142 p_array_init(¬ify_boxes->names, notify_ns->ctx->pool, 4);
143 return notify_boxes;
144 }
145
146 static void
cmd_notify_add_mailbox(struct imap_notify_context * ctx,struct mail_namespace * ns,const char * name,enum imap_notify_type type,enum imap_notify_event events)147 cmd_notify_add_mailbox(struct imap_notify_context *ctx,
148 struct mail_namespace *ns, const char *name,
149 enum imap_notify_type type,
150 enum imap_notify_event events)
151 {
152 struct imap_notify_namespace *notify_ns;
153 struct imap_notify_mailboxes *notify_boxes;
154 const char *const *names;
155 unsigned int i, count;
156 size_t cur_len, name_len = strlen(name);
157 char ns_sep = mail_namespace_get_sep(ns);
158
159 if (mail_namespace_is_removable(ns)) {
160 /* exclude removable namespaces */
161 return;
162 }
163
164 if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
165 !str_begins(name, "INBOX") &&
166 strncasecmp(name, "INBOX", 5) == 0 &&
167 (name[5] == '\0' || name[5] == ns_sep)) {
168 /* we'll do only case-sensitive comparisons later,
169 so sanitize INBOX to be uppercase */
170 name = t_strconcat("INBOX", name + 5, NULL);
171 }
172
173 notify_ns = imap_notify_namespace_get(ctx, ns);
174 notify_boxes = imap_notify_mailboxes_get(notify_ns, type, events);
175
176 names = array_get(¬ify_boxes->names, &count);
177 for (i = 0; i < count; ) {
178 if (strcmp(names[i], name) == 0) {
179 /* exact duplicate, already added */
180 return;
181 }
182 if (type != IMAP_NOTIFY_TYPE_SUBTREE)
183 i++;
184 else {
185 /* see if one is a subtree of the other */
186 cur_len = strlen(names[i]);
187 if (str_begins(name, names[i]) &&
188 names[i][cur_len] == ns_sep) {
189 /* already matched in this subtree */
190 return;
191 }
192 if (strncmp(names[i], name, name_len) == 0 &&
193 names[i][name_len] == ns_sep) {
194 /* we're adding a parent, remove the child */
195 array_delete(¬ify_boxes->names, i, 1);
196 names = array_get(¬ify_boxes->names, &count);
197 } else {
198 i++;
199 }
200 }
201 }
202 name = p_strdup(ctx->pool, name);
203 array_push_back(¬ify_boxes->names, &name);
204
205 ctx->global_max_mailbox_names =
206 I_MAX(ctx->global_max_mailbox_names,
207 array_count(¬ify_boxes->names));
208 }
209
cmd_notify_add_personal(struct imap_notify_context * ctx,enum imap_notify_event events)210 static void cmd_notify_add_personal(struct imap_notify_context *ctx,
211 enum imap_notify_event events)
212 {
213 struct mail_namespace *ns;
214
215 for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) {
216 if (ns->type == MAIL_NAMESPACE_TYPE_PRIVATE) {
217 cmd_notify_add_mailbox(ctx, ns, "",
218 IMAP_NOTIFY_TYPE_SUBTREE, events);
219 }
220 }
221 }
222
223 static int
imap_notify_refresh_subscriptions(struct client_command_context * cmd,struct imap_notify_context * ctx)224 imap_notify_refresh_subscriptions(struct client_command_context *cmd,
225 struct imap_notify_context *ctx)
226 {
227 struct mailbox_list_iterate_context *iter;
228 struct mail_namespace *ns;
229
230 if (!ctx->have_subscriptions)
231 return 0;
232
233 /* make sure subscriptions are refreshed at least once */
234 for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) {
235 iter = mailbox_list_iter_init(ns->list, "*", MAILBOX_LIST_ITER_SELECT_SUBSCRIBED);
236 (void)mailbox_list_iter_next(iter);
237 if (mailbox_list_iter_deinit(&iter) < 0) {
238 client_send_list_error(cmd, ns->list);
239 return -1;
240 }
241 }
242 return 0;
243 }
244
cmd_notify_add_subscribed(struct imap_notify_context * ctx,enum imap_notify_event events)245 static void cmd_notify_add_subscribed(struct imap_notify_context *ctx,
246 enum imap_notify_event events)
247 {
248 struct mail_namespace *ns;
249
250 ctx->have_subscriptions = TRUE;
251 for (ns = ctx->client->user->namespaces; ns != NULL; ns = ns->next) {
252 cmd_notify_add_mailbox(ctx, ns, "",
253 IMAP_NOTIFY_TYPE_SUBSCRIBED, events);
254 }
255 }
256
257 static void
cmd_notify_add_mailbox_namespaces(struct imap_notify_context * ctx,const char * name,enum imap_notify_type type,enum imap_notify_event events)258 cmd_notify_add_mailbox_namespaces(struct imap_notify_context *ctx,
259 const char *name,
260 enum imap_notify_type type,
261 enum imap_notify_event events)
262 {
263 struct mail_namespace *ns;
264
265 ns = mail_namespace_find(ctx->client->user->namespaces, name);
266 cmd_notify_add_mailbox(ctx, ns, name, type, events);
267 }
268
269 static int
cmd_notify_add_mailboxes(struct imap_notify_context * ctx,const struct imap_arg * arg,enum imap_notify_type type,enum imap_notify_event events)270 cmd_notify_add_mailboxes(struct imap_notify_context *ctx,
271 const struct imap_arg *arg,
272 enum imap_notify_type type,
273 enum imap_notify_event events)
274 {
275 const struct imap_arg *list;
276 const char *name;
277
278 if (imap_arg_get_astring(arg, &name)) {
279 cmd_notify_add_mailbox_namespaces(ctx, name, type, events);
280 return 0;
281 }
282 if (!imap_arg_get_list(arg, &list))
283 return -1;
284
285 for (; list->type != IMAP_ARG_EOL; list++) {
286 if (!imap_arg_get_astring(list, &name))
287 return -1;
288
289 cmd_notify_add_mailbox_namespaces(ctx, name, type, events);
290 }
291 return 0;
292 }
293
294 static int
cmd_notify_set(struct imap_notify_context * ctx,const struct imap_arg * args)295 cmd_notify_set(struct imap_notify_context *ctx, const struct imap_arg *args)
296 {
297 const struct imap_arg *event_group, *mailboxes, *list;
298 const char *str, *filter_mailboxes;
299 enum imap_notify_event event, event_mask;
300
301 if (imap_arg_get_atom(args, &str) &&
302 strcasecmp(str, "STATUS") == 0) {
303 /* send STATUS replies for all matched mailboxes before
304 NOTIFY's OK reply */
305 ctx->send_immediate_status = TRUE;
306 args++;
307 }
308 for (; args->type != IMAP_ARG_EOL; args++) {
309 if (!imap_arg_get_list(args, &event_group))
310 return -1;
311
312 /* filter-mailboxes */
313 if (!imap_arg_get_atom(event_group, &filter_mailboxes))
314 return -1;
315 event_group++;
316
317 if (strcasecmp(filter_mailboxes, "selected") == 0 ||
318 strcasecmp(filter_mailboxes, "selected-delayed") == 0) {
319 /* setting events for selected mailbox.
320 handle specially. */
321 if (ctx->selected_set) {
322 ctx->error = "Duplicate selected filter";
323 return -1;
324 }
325 ctx->selected_set = TRUE;
326 if (strcasecmp(filter_mailboxes, "selected") == 0)
327 ctx->selected_immediate_expunges = TRUE;
328 if (cmd_notify_set_selected(ctx, event_group) < 0)
329 return -1;
330 continue;
331 }
332
333 if (strcasecmp(filter_mailboxes, "subtree") == 0 ||
334 strcasecmp(filter_mailboxes, "mailboxes") == 0) {
335 if (event_group->type == IMAP_ARG_EOL)
336 return -1;
337 mailboxes = event_group++;
338 /* check that the mailboxes parameter is valid */
339 if (IMAP_ARG_IS_ASTRING(mailboxes))
340 ;
341 else if (!imap_arg_get_list(mailboxes, &list))
342 return -1;
343 else if (list->type == IMAP_ARG_EOL) {
344 /* should have at least one mailbox */
345 return -1;
346 }
347 } else {
348 mailboxes = NULL;
349 }
350
351 /* parse events */
352 if (imap_arg_get_atom(event_group, &str) &&
353 strcasecmp(str, "NONE") == 0) {
354 /* NONE is the default, ignore this */
355 continue;
356 }
357 if (!imap_arg_get_list(event_group, &list) ||
358 list[0].type == IMAP_ARG_EOL)
359 return -1;
360
361 event_mask = 0;
362 for (; list->type != IMAP_ARG_EOL; list++) {
363 if (cmd_notify_parse_event(list, &event) < 0)
364 return -1;
365 event_mask |= event;
366 ctx->global_used_events |= event;
367 }
368
369 /* we can't currently know inboxes, so treat it the
370 same as personal */
371 if (strcasecmp(filter_mailboxes, "inboxes") == 0 ||
372 strcasecmp(filter_mailboxes, "personal") == 0)
373 cmd_notify_add_personal(ctx, event_mask);
374 else if (strcasecmp(filter_mailboxes, "subscribed") == 0)
375 cmd_notify_add_subscribed(ctx, event_mask);
376 else if (strcasecmp(filter_mailboxes, "subtree") == 0) {
377 if (cmd_notify_add_mailboxes(ctx, mailboxes,
378 IMAP_NOTIFY_TYPE_SUBTREE,
379 event_mask) < 0)
380 return -1;
381 } else if (strcasecmp(filter_mailboxes, "mailboxes") == 0) {
382 if (cmd_notify_add_mailboxes(ctx, mailboxes,
383 IMAP_NOTIFY_TYPE_MAILBOX,
384 event_mask) < 0)
385 return -1;
386 } else {
387 return -1;
388 }
389 }
390 return 0;
391 }
392
393 static void
imap_notify_box_list_noperm(struct client * client,struct mailbox * box)394 imap_notify_box_list_noperm(struct client *client, struct mailbox *box)
395 {
396 string_t *str = t_str_new(128);
397 char ns_sep = mail_namespace_get_sep(mailbox_get_namespace(box));
398 enum mailbox_info_flags mailbox_flags;
399
400 if (mailbox_list_mailbox(mailbox_get_namespace(box)->list,
401 mailbox_get_name(box), &mailbox_flags) < 0)
402 mailbox_flags = 0;
403
404 str_append(str, "* LIST (");
405 if (imap_mailbox_flags2str(str, mailbox_flags))
406 str_append_c(str, ' ');
407 str_append(str, "\\NoAccess) \"");
408 if (ns_sep == '\\')
409 str_append_c(str, '\\');
410 str_append_c(str, ns_sep);
411 str_append(str, "\" ");
412
413 imap_append_astring(str, mailbox_get_vname(box));
414 client_send_line(client, str_c(str));
415 }
416
417 static void
imap_notify_box_send_status(struct client_command_context * cmd,struct imap_notify_context * ctx,const struct mailbox_info * info)418 imap_notify_box_send_status(struct client_command_context *cmd,
419 struct imap_notify_context *ctx,
420 const struct mailbox_info *info)
421 {
422 struct mailbox *box;
423 struct imap_status_items items;
424 struct imap_status_result result;
425
426 if ((info->flags & (MAILBOX_NONEXISTENT | MAILBOX_NOSELECT)) != 0)
427 return;
428
429 /* don't send STATUS to selected mailbox */
430 if (cmd->client->mailbox != NULL &&
431 mailbox_equals(cmd->client->mailbox, info->ns, info->vname))
432 return;
433
434 i_zero(&items);
435 i_zero(&result);
436
437 items.flags = IMAP_STATUS_ITEM_UIDVALIDITY | IMAP_STATUS_ITEM_UIDNEXT |
438 IMAP_STATUS_ITEM_MESSAGES | IMAP_STATUS_ITEM_UNSEEN;
439 if ((ctx->global_used_events & (IMAP_NOTIFY_EVENT_FLAG_CHANGE |
440 IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE)) != 0)
441 items.flags |= IMAP_STATUS_ITEM_HIGHESTMODSEQ;
442
443 box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY);
444 mailbox_set_reason(box, "NOTIFY send STATUS");
445 (void)mailbox_enable(box, client_enabled_mailbox_features(ctx->client));
446
447 if (imap_status_get(cmd, info->ns, info->vname, &items, &result) < 0) {
448 if (result.error == MAIL_ERROR_PERM)
449 imap_notify_box_list_noperm(ctx->client, box);
450 else if (result.error != MAIL_ERROR_NOTFOUND) {
451 client_send_line(ctx->client,
452 t_strconcat("* ", result.errstr, NULL));
453 }
454 } else {
455 imap_status_send(ctx->client, info->vname, &items, &result);
456 }
457 mailbox_free(&box);
458 }
459
imap_notify_ns_want_status(struct imap_notify_namespace * notify_ns)460 static bool imap_notify_ns_want_status(struct imap_notify_namespace *notify_ns)
461 {
462 const struct imap_notify_mailboxes *notify_boxes;
463
464 array_foreach(¬ify_ns->mailboxes, notify_boxes) {
465 if ((notify_boxes->events &
466 (IMAP_NOTIFY_EVENT_MESSAGE_NEW |
467 IMAP_NOTIFY_EVENT_MESSAGE_EXPUNGE |
468 IMAP_NOTIFY_EVENT_ANNOTATION_CHANGE |
469 IMAP_NOTIFY_EVENT_FLAG_CHANGE)) != 0)
470 return TRUE;
471 }
472 return FALSE;
473 }
474
475 static void
imap_notify_ns_send_status(struct client_command_context * cmd,struct imap_notify_context * ctx,struct imap_notify_namespace * notify_ns)476 imap_notify_ns_send_status(struct client_command_context *cmd,
477 struct imap_notify_context *ctx,
478 struct imap_notify_namespace *notify_ns)
479 {
480 struct mailbox_list_iterate_context *iter;
481 const struct imap_notify_mailboxes *notify_boxes;
482 const struct mailbox_info *info;
483
484 if (!imap_notify_ns_want_status(notify_ns))
485 return;
486
487 /* set _RETURN_SUBSCRIBED flag just in case IMAP_NOTIFY_TYPE_SUBSCRIBED
488 is used, which requires refreshing subscriptions */
489 iter = mailbox_list_iter_init(notify_ns->ns->list, "*",
490 MAILBOX_LIST_ITER_RETURN_SUBSCRIBED |
491 MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
492 while ((info = mailbox_list_iter_next(iter)) != NULL) {
493 array_foreach(¬ify_ns->mailboxes, notify_boxes) {
494 if (imap_notify_match_mailbox(notify_ns, notify_boxes,
495 info->vname)) {
496 imap_notify_box_send_status(cmd, ctx, info);
497 break;
498 }
499 }
500 }
501 if (mailbox_list_iter_deinit(&iter) < 0) {
502 client_send_line(notify_ns->ctx->client,
503 "* NO Mailbox listing failed");
504 }
505 }
506
cmd_notify_send_status(struct client_command_context * cmd,struct imap_notify_context * ctx)507 static void cmd_notify_send_status(struct client_command_context *cmd,
508 struct imap_notify_context *ctx)
509 {
510 struct imap_notify_namespace *notify_ns;
511
512 array_foreach_modifiable(&ctx->namespaces, notify_ns)
513 imap_notify_ns_send_status(cmd, ctx, notify_ns);
514 }
515
cmd_notify(struct client_command_context * cmd)516 bool cmd_notify(struct client_command_context *cmd)
517 {
518 struct imap_notify_context *ctx;
519 const struct imap_arg *args;
520 const char *str;
521 int ret = 0;
522 pool_t pool;
523
524 if (!client_read_args(cmd, 0, 0, &args))
525 return FALSE;
526
527 pool = pool_alloconly_create("imap notify context", 1024);
528 ctx = p_new(pool, struct imap_notify_context, 1);
529 ctx->pool = pool;
530 ctx->client = cmd->client;
531 p_array_init(&ctx->namespaces, pool, 4);
532
533 if (!imap_arg_get_atom(&args[0], &str))
534 ret = -1;
535 else if (strcasecmp(str, "NONE") == 0)
536 ;
537 else if (strcasecmp(str, "SET") == 0)
538 ret = cmd_notify_set(ctx, args+1);
539 else
540 ret = -1;
541
542 if (ret < 0) {
543 client_send_command_error(cmd, ctx->error != NULL ? ctx->error :
544 "Invalid arguments.");
545 pool_unref(&pool);
546 return TRUE;
547 }
548
549 if ((ctx->global_used_events & UNSUPPORTED_EVENTS) != 0) {
550 string_t *client_error = t_str_new(128);
551 unsigned int i;
552
553 str_append(client_error, "NO [BADEVENT");
554 for (i = 0; i < N_ELEMENTS(imap_notify_event_names); i++) {
555 if ((ctx->global_used_events & (1 << i)) != 0 &&
556 ((1 << i) & UNSUPPORTED_EVENTS) != 0) {
557 str_append_c(client_error, ' ');
558 str_append(client_error, imap_notify_event_names[i]);
559 }
560 }
561 str_append(client_error, "] Unsupported NOTIFY events.");
562 client_send_tagline(cmd, str_c(client_error));
563 pool_unref(&pool);
564 return TRUE;
565 }
566
567 if (array_count(&ctx->namespaces) == 0) {
568 /* selected mailbox only */
569 } else if (ctx->global_max_mailbox_names > IMAP_NOTIFY_MAX_NAMES_PER_NS) {
570 client_send_tagline(cmd,
571 "NO [NOTIFICATIONOVERFLOW] Too many mailbox names");
572 pool_unref(&pool);
573 return TRUE;
574 } else if (imap_notify_refresh_subscriptions(cmd, ctx) < 0) {
575 /* tagline already sent */
576 pool_unref(&pool);
577 return TRUE;
578 } else if (imap_notify_begin(ctx) < 0) {
579 client_send_tagline(cmd,
580 "NO [NOTIFICATIONOVERFLOW] NOTIFY not supported for these mailboxes.");
581 pool_unref(&pool);
582 return TRUE;
583 }
584 if (cmd->client->notify_ctx != NULL)
585 imap_notify_deinit(&cmd->client->notify_ctx);
586
587 if (ctx->send_immediate_status)
588 cmd_notify_send_status(cmd, ctx);
589 cmd->client->notify_immediate_expunges =
590 ctx->selected_immediate_expunges;
591 cmd->client->notify_count_changes =
592 (ctx->selected_events & IMAP_NOTIFY_EVENT_MESSAGE_NEW) != 0;
593 cmd->client->notify_flag_changes =
594 (ctx->selected_events & IMAP_NOTIFY_EVENT_FLAG_CHANGE) != 0;
595
596 cmd->client->notify_ctx = ctx;
597 return cmd_sync(cmd, 0, IMAP_SYNC_FLAG_SAFE, "OK NOTIFY completed.");
598 }
599