1 /*
2 * Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * 3. The name "Carnegie Mellon University" must not be used to
17 * endorse or promote products derived from this software without
18 * prior written permission. For permission or any legal
19 * details, please contact
20 * Carnegie Mellon University
21 * Center for Technology Transfer and Enterprise Creation
22 * 4615 Forbes Avenue
23 * Suite 302
24 * Pittsburgh, PA 15213
25 * (412) 268-7393, fax: (412) 268-7395
26 * innovation@andrew.cmu.edu
27 *
28 * 4. Redistributions of any form whatsoever must retain the following
29 * acknowledgment:
30 * "This product includes software developed by Computing Services
31 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
32 *
33 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
34 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
35 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
36 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
37 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
38 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
39 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 *
41 * Author: Sébastien Michel from Atos Worldline
42 */
43 #include <config.h>
44 #include "imap/mboxevent.h"
45 #include <stdlib.h>
46 #include <stdio.h>
47 #include <string.h>
48 #include <sysexits.h>
49 #include <syslog.h>
50 #include <time.h>
51 #include <unistd.h>
52
53 #include <jansson.h>
54
55 #include "annotate.h"
56 #include "assert.h"
57 #ifdef WITH_DAV
58 #include "caldav_db.h"
59 #include "carddav_db.h"
60 #endif /* WITH_DAV */
61 #include "global.h"
62 #include "imapurl.h"
63 #include "libconfig.h"
64 #include "map.h"
65 #include "times.h"
66 #include "xmalloc.h"
67
68 #include "map.h"
69 #include "mboxevent.h"
70 #include "mboxname.h"
71 #include "msgrecord.h"
72 #include "notify.h"
73 #include "global.h"
74
75 #define MESSAGE_EVENTS (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_EXPIRE|\
76 EVENT_MESSAGE_EXPUNGE|EVENT_MESSAGE_NEW|\
77 EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE)
78
79 #define FLAGS_EVENTS (EVENT_FLAGS_SET|EVENT_FLAGS_CLEAR|EVENT_MESSAGE_READ|\
80 EVENT_MESSAGE_TRASH)
81
82 #define MAILBOX_EVENTS (EVENT_MAILBOX_CREATE|EVENT_MAILBOX_DELETE|\
83 EVENT_MAILBOX_RENAME|EVENT_ACL_CHANGE|EVENT_MAILBOX_MODSEQ)
84
85 #define SUBS_EVENTS (EVENT_MAILBOX_SUBSCRIBE|EVENT_MAILBOX_UNSUBSCRIBE)
86
87 #define QUOTA_EVENTS (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN|EVENT_QUOTA_CHANGE)
88
89 #define CALENDAR_EVENTS (EVENT_CALENDAR_ALARM)
90
91 #define APPLEPUSHSERVICE_EVENTS (EVENT_APPLEPUSHSERVICE|EVENT_APPLEPUSHSERVICE_DAV)
92
93
94 static const char *notifier = NULL;
95 static struct namespace namespace;
96
97 static const char *client_id = NULL;
98
99 static strarray_t *excluded_flags;
100 static strarray_t *excluded_specialuse;
101 static int enable_subfolder = 1;
102
103 static int enabled_events = 0;
104 static unsigned long extra_params;
105
106 static struct mboxevent event_template =
107 { 0,
108 /* ordered to optimize the parsing of the notification message */
109 {
110 /* 0 */ { EVENT_TIMESTAMP, "timestamp", EVENT_PARAM_STRING, { 0 }, 0 },
111 /* 1 */ { EVENT_SERVICE, "service", EVENT_PARAM_STRING, { 0 }, 0 },
112 /* 2 */ { EVENT_SERVER_ADDRESS, "serverAddress", EVENT_PARAM_STRING, { 0 }, 0 },
113 /* 3 */ { EVENT_CLIENT_ADDRESS, "clientAddress", EVENT_PARAM_STRING, { 0 }, 0 },
114 /* 4 */ { EVENT_OLD_MAILBOX_ID, "oldMailboxID", EVENT_PARAM_STRING, { 0 }, 0 },
115 /* 5 */ { EVENT_OLD_UIDSET, "vnd.cmu.oldUidset", EVENT_PARAM_STRING, { 0 }, 0 },
116 /* 6 */ { EVENT_MAILBOX_ID, "mailboxID", EVENT_PARAM_STRING, { 0 }, 0 },
117 /* 7 */ { EVENT_URI, "uri", EVENT_PARAM_STRING, { 0 }, 0 },
118 /* 8 */ { EVENT_MODSEQ, "modseq", EVENT_PARAM_INT, { 0 }, 0 },
119 /* 9 */ { EVENT_QUOTA_STORAGE, "diskQuota", EVENT_PARAM_INT, { 0 }, 0 },
120 /* 10 */ { EVENT_DISK_USED, "diskUsed", EVENT_PARAM_INT, { 0 }, 0 },
121 /* 11 */ { EVENT_QUOTA_MESSAGES, "maxMessages", EVENT_PARAM_INT, { 0 }, 0 },
122 /* 12 */ { EVENT_MESSAGES, "messages", EVENT_PARAM_INT, { 0 }, 0 },
123 /* 13 */ { EVENT_UNSEEN_MESSAGES, "vnd.cmu.unseenMessages", EVENT_PARAM_INT, { 0 }, 0 },
124 /* 14 */ { EVENT_UIDNEXT, "uidnext", EVENT_PARAM_INT, { 0 }, 0 },
125 /* 15 */ { EVENT_UIDSET, "uidset", EVENT_PARAM_STRING, { 0 }, 0 },
126 /* 16 */ { EVENT_MIDSET, "vnd.cmu.midset", EVENT_PARAM_STRING, { 0 }, 0 },
127 /* 17 */ { EVENT_FLAG_NAMES, "flagNames", EVENT_PARAM_STRING, { 0 }, 0 },
128 /* 18 */ { EVENT_PID, "pid", EVENT_PARAM_INT, { 0 }, 0 },
129 /* 19 */ { EVENT_ACL_SUBJECT, "aclSubject", EVENT_PARAM_STRING, { 0 }, 0 },
130 /* 20 */ { EVENT_ACL_RIGHTS, "aclRights", EVENT_PARAM_STRING, { 0 }, 0 },
131 /* 21 */ { EVENT_USER, "user", EVENT_PARAM_STRING, { 0 }, 0 },
132 /* 22 */ { EVENT_MESSAGE_SIZE, "messageSize", EVENT_PARAM_INT, { 0 }, 0 },
133 /* 23 */ { EVENT_MBTYPE, "vnd.cmu.mbtype", EVENT_PARAM_STRING, { 0 }, 0 },
134 { EVENT_SERVERFQDN, "serverFQDN", EVENT_PARAM_STRING, { 0 }, 0 },
135 { EVENT_MAILBOX_ACL, "vnd.cmu.mailboxACL", EVENT_PARAM_STRING, { 0 }, 0 },
136 /* 24 */ { EVENT_DAV_FILENAME, "vnd.cmu.davFilename", EVENT_PARAM_STRING, { 0 }, 0 },
137 /* 25 */ { EVENT_DAV_UID, "vnd.cmu.davUid", EVENT_PARAM_STRING, { 0 }, 0 },
138 /* 26 */ { EVENT_ENVELOPE, "vnd.cmu.envelope", EVENT_PARAM_STRING, { 0 }, 0 },
139 /* 27 */ { EVENT_SESSIONID, "vnd.cmu.sessionId", EVENT_PARAM_STRING, { 0 }, 0 },
140 /* 28 */ { EVENT_BODYSTRUCTURE, "bodyStructure", EVENT_PARAM_STRING, { 0 }, 0 },
141 /* 29 */ { EVENT_CLIENT_ID, "vnd.fastmail.clientId", EVENT_PARAM_STRING, { 0 }, 0 },
142 /* 30 */ { EVENT_SESSION_ID, "vnd.fastmail.sessionId", EVENT_PARAM_STRING, { 0 }, 0 },
143 { EVENT_CONVEXISTS, "vnd.fastmail.convExists", EVENT_PARAM_INT, { 0 }, 0 },
144 { EVENT_CONVUNSEEN, "vnd.fastmail.convUnseen", EVENT_PARAM_INT, { 0 }, 0 },
145 { EVENT_MESSAGE_CID, "vnd.fastmail.cid", EVENT_PARAM_STRING, { 0 }, 0 },
146 { EVENT_COUNTERS, "vnd.fastmail.counters", EVENT_PARAM_STRING, { 0 }, 0 },
147 { EVENT_MESSAGE_EMAILID, "vnd.cmu.emailid", EVENT_PARAM_STRING, { 0 }, 0 },
148 { EVENT_MESSAGE_THREADID, "vnd.cmu.threadid", EVENT_PARAM_STRING, { 0 }, 0 },
149
150 /* calendar params for calalarmd/notifyd */
151 { EVENT_CALENDAR_ALARM_TIME, "alarmTime", EVENT_PARAM_STRING, { 0 }, 0 },
152 { EVENT_CALENDAR_ALARM_RECIPIENTS, "alarmRecipients", EVENT_PARAM_ARRAY, { 0 }, 0 },
153 { EVENT_CALENDAR_USER_ID, "userId", EVENT_PARAM_STRING, { 0 }, 0 },
154 { EVENT_CALENDAR_CALENDAR_ID, "calendarId", EVENT_PARAM_STRING, { 0 }, 0 },
155 { EVENT_CALENDAR_CALENDAR_NAME, "calendarName", EVENT_PARAM_STRING, { 0 }, 0 },
156 { EVENT_CALENDAR_CALENDAR_COLOR, "calendarColor", EVENT_PARAM_STRING, { 0 }, 0 },
157 { EVENT_CALENDAR_UID, "uid", EVENT_PARAM_STRING, { 0 }, 0 },
158 { EVENT_CALENDAR_ACTION, "action", EVENT_PARAM_STRING, { 0 }, 0 },
159 { EVENT_CALENDAR_SUMMARY, "summary", EVENT_PARAM_STRING, { 0 }, 0 },
160 { EVENT_CALENDAR_DESCRIPTION, "description", EVENT_PARAM_STRING, { 0 }, 0 },
161 { EVENT_CALENDAR_LOCATION, "location", EVENT_PARAM_STRING, { 0 }, 0 },
162 { EVENT_CALENDAR_TIMEZONE, "timezone", EVENT_PARAM_STRING, { 0 }, 0 },
163 { EVENT_CALENDAR_START, "start", EVENT_PARAM_STRING, { 0 }, 0 },
164 { EVENT_CALENDAR_END, "end", EVENT_PARAM_STRING, { 0 }, 0 },
165 { EVENT_CALENDAR_ALLDAY, "allDay", EVENT_PARAM_INT, { 0 }, 0 },
166 { EVENT_CALENDAR_ATTENDEE_NAMES, "attendeeNames", EVENT_PARAM_ARRAY, { 0 }, 0 },
167 { EVENT_CALENDAR_ATTENDEE_EMAILS, "attendeeEmails", EVENT_PARAM_ARRAY, { 0 }, 0 },
168 { EVENT_CALENDAR_ATTENDEE_STATUS, "attendeeStatus", EVENT_PARAM_ARRAY, { 0 }, 0 },
169 { EVENT_CALENDAR_ORGANIZER, "organizer", EVENT_PARAM_STRING, { 0 }, 0 },
170
171 /* apple push params for notifyd */
172 { EVENT_APPLEPUSHSERVICE_VERSION, "apsVersion", EVENT_PARAM_INT, { 0 }, 0 },
173 { EVENT_APPLEPUSHSERVICE_ACCOUNT_ID, "apsAccountId", EVENT_PARAM_STRING, { 0 }, 0 },
174 { EVENT_APPLEPUSHSERVICE_DEVICE_TOKEN, "apsDeviceToken", EVENT_PARAM_STRING, { 0 }, 0 },
175 { EVENT_APPLEPUSHSERVICE_SUBTOPIC, "apsSubtopic", EVENT_PARAM_STRING, { 0 }, 0 },
176 { EVENT_APPLEPUSHSERVICE_MAILBOXES, "mailboxes", EVENT_PARAM_ARRAY, { 0 }, 0 },
177
178 /* for dav push */
179 { EVENT_APPLEPUSHSERVICE_DAV_TOPIC, "apsTopic", EVENT_PARAM_STRING, { 0 }, 0 },
180 { EVENT_APPLEPUSHSERVICE_DAV_DEVICE_TOKEN, "apsDeviceToken", EVENT_PARAM_STRING, { 0 }, 0 },
181 { EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_USER, "mailboxUser", EVENT_PARAM_STRING, { 0 }, 0 },
182 { EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_UNIQUEID, "mailboxUniqueId", EVENT_PARAM_STRING, { 0 }, 0 },
183 { EVENT_APPLEPUSHSERVICE_DAV_EXPIRY, "expiry", EVENT_PARAM_INT, { 0 }, 0 },
184
185 /* always at end to let the parser to easily truncate this part */
186 /* 31 */ { EVENT_MESSAGE_CONTENT, "messageContent", EVENT_PARAM_STRING, { 0 }, 0 }
187 },
188 STRARRAY_INITIALIZER, { 0, 0 }, NULL, STRARRAY_INITIALIZER, NULL, NULL, NULL
189 };
190
191 static char *json_formatter(enum event_type type, struct event_parameter params[]);
192 static int filled_params(enum event_type type, struct mboxevent *mboxevent);
193 static int mboxevent_expected_param(enum event_type type, enum event_param param);
194
195 static int mboxevent_initialized = 0;
196
done_cb(void * rock)197 static void done_cb(void *rock __attribute__((unused))) {
198 /* do nothing */
199 }
200
init_internal()201 static void init_internal() {
202 if (!mboxevent_initialized) {
203 mboxevent_init();
204 cyrus_modules_add(done_cb, NULL);
205 }
206 }
207
mboxevent_init(void)208 EXPORTED int mboxevent_init(void)
209 {
210 const char *options;
211 int groups;
212
213 if (!(notifier = config_getstring(IMAPOPT_EVENT_NOTIFIER))) return 0;
214
215 /* some don't want to notify events for some IMAP flags */
216 options = config_getstring(IMAPOPT_EVENT_EXCLUDE_FLAGS);
217 excluded_flags = strarray_split(options, NULL, 0);
218
219 /* some don't want to notify events on some folders (ie. Sent, Spam) */
220 /* identify those folders with IMAP SPECIAL-USE */
221 options = config_getstring(IMAPOPT_EVENT_EXCLUDE_SPECIALUSE);
222 excluded_specialuse = strarray_split(options, NULL, 0);
223
224 /* special meaning to disable event notification on all sub folders */
225 if (strarray_find_case(excluded_specialuse, "ALL", 0) >= 0)
226 enable_subfolder = 0;
227
228 /* get event types's extra parameters */
229 extra_params = config_getbitfield(IMAPOPT_EVENT_EXTRA_PARAMS);
230
231 /* groups of related events to turn on notification */
232 groups = config_getbitfield(IMAPOPT_EVENT_GROUPS);
233 if (groups & IMAP_ENUM_EVENT_GROUPS_MESSAGE)
234 enabled_events |= MESSAGE_EVENTS;
235
236 if (groups & IMAP_ENUM_EVENT_GROUPS_QUOTA)
237 enabled_events |= QUOTA_EVENTS;
238
239 if (groups & IMAP_ENUM_EVENT_GROUPS_FLAGS)
240 enabled_events |= FLAGS_EVENTS;
241
242 if (groups & IMAP_ENUM_EVENT_GROUPS_ACCESS)
243 enabled_events |= (EVENT_LOGIN|EVENT_LOGOUT|EVENT_ACL_CHANGE);
244
245 if (groups & IMAP_ENUM_EVENT_GROUPS_SUBSCRIPTION)
246 enabled_events |= SUBS_EVENTS;
247
248 if (groups & IMAP_ENUM_EVENT_GROUPS_MAILBOX)
249 enabled_events |= MAILBOX_EVENTS;
250
251 if (groups & IMAP_ENUM_EVENT_GROUPS_CALENDAR)
252 enabled_events |= CALENDAR_EVENTS;
253
254 if (groups & IMAP_ENUM_EVENT_GROUPS_APPLEPUSHSERVICE)
255 enabled_events |= APPLEPUSHSERVICE_EVENTS;
256
257 mboxevent_initialized = 1;
258
259 return enabled_events;
260 }
261
mboxevent_setnamespace(struct namespace * n)262 EXPORTED void mboxevent_setnamespace(struct namespace *n)
263 {
264 namespace = *n;
265 /* standardize IMAP URL format */
266 namespace.isadmin = 1;
267 namespace.isalt = 0;
268 }
269
mboxevent_enabled_for_mailbox(struct mailbox * mailbox)270 static int mboxevent_enabled_for_mailbox(struct mailbox *mailbox)
271 {
272 struct buf attrib = BUF_INITIALIZER;
273 char *userid = NULL;
274 strarray_t *specialuse = NULL;
275 int enabled = 1;
276 int i = 0;
277 int r = 0;
278
279 init_internal();
280
281 if (!enable_subfolder && !mboxname_isusermailbox(mailbox->name, 1)) {
282 enabled = 0;
283 goto done;
284 }
285
286 /* test if the mailbox has a special-use attribute in the exclude list */
287 if (strarray_size(excluded_specialuse) > 0) {
288 userid = mboxname_to_userid(mailbox->name);
289
290 r = annotatemore_lookup(mailbox->name, "/specialuse", userid, &attrib);
291 if (r) goto done; /* XXX - return -1? Failure? */
292
293 /* get info and set flags */
294 specialuse = strarray_split(buf_cstring(&attrib), NULL, 0);
295
296 for (i = 0; i < strarray_size(specialuse) ; i++) {
297 const char *attribute = strarray_nth(specialuse, i);
298 if (strarray_find(excluded_specialuse, attribute, 0) >= 0) {
299 enabled = 0;
300 goto done;
301 }
302 }
303 }
304
305 done:
306 strarray_free(specialuse);
307 buf_free(&attrib);
308 free(userid);
309 return enabled;
310 }
311
mboxevent_new(enum event_type type)312 EXPORTED struct mboxevent *mboxevent_new(enum event_type type)
313 {
314 struct mboxevent *mboxevent = NULL;
315
316 init_internal();
317
318 /* event notification is completely disabled */
319 if (!notifier)
320 return NULL;
321
322 /* the group to which belong the event is not enabled */
323 if (!(enabled_events & type))
324 return NULL;
325
326 mboxevent = xmalloc(sizeof(struct mboxevent));
327 memcpy(mboxevent, &event_template, sizeof(struct mboxevent));
328
329 unsigned i;
330 for (i = 0; mboxevent->params[i].id; i++) {
331 assert(i == mboxevent->params[i].id);
332 }
333
334 mboxevent->type = type;
335
336 /* From RFC 5423:
337 * the time at which the event occurred that triggered the notification
338 * (...). This MAY be an approximate time.
339 *
340 * so it seems appropriate here */
341 if (mboxevent_expected_param(type, EVENT_TIMESTAMP))
342 gettimeofday(&mboxevent->timestamp, NULL);
343
344 FILL_UNSIGNED_PARAM(mboxevent, EVENT_PID, getpid());
345
346 if (mboxevent_expected_param(type, EVENT_SESSIONID)) {
347 FILL_STRING_PARAM(mboxevent, EVENT_SESSIONID, xstrdup(session_id()));
348 }
349
350 if (mboxevent_expected_param(type, EVENT_CLIENT_ID)) {
351 // OK to be blank
352 FILL_STRING_PARAM(mboxevent, EVENT_CLIENT_ID, xstrdupsafe(client_id));
353 }
354
355 if (mboxevent_expected_param(type, EVENT_SESSION_ID)) {
356 FILL_STRING_PARAM(mboxevent, EVENT_SESSION_ID, xstrdup(session_id()));
357 }
358
359 return mboxevent;
360 }
361
mboxevent_enqueue(enum event_type type,struct mboxevent ** mboxevents)362 struct mboxevent *mboxevent_enqueue(enum event_type type,
363 struct mboxevent **mboxevents)
364 {
365 struct mboxevent *mboxevent = NULL;
366 struct mboxevent *ptr;
367
368 if (!(mboxevent = mboxevent_new(type)))
369 return NULL;
370
371 if (mboxevents) {
372 if (*mboxevents == NULL)
373 *mboxevents = mboxevent;
374 else {
375 /* append the newly created event at end of the chained list */
376 ptr = *mboxevents;
377 while (ptr->next)
378 ptr = ptr->next;
379 ptr->next = mboxevent;
380 mboxevent->prev = ptr;
381 }
382 }
383
384 return mboxevent;
385 }
386
mboxevent_free(struct mboxevent ** mboxevent)387 EXPORTED void mboxevent_free(struct mboxevent **mboxevent)
388 {
389 struct mboxevent *event = *mboxevent;
390 int i;
391
392 if (!event)
393 return;
394
395 seqset_free(event->uidset);
396 seqset_free(event->olduidset);
397 strarray_fini(&event->midset);
398 strarray_fini(&event->flagnames);
399
400 for (i = 0; i <= MAX_PARAM; i++) {
401 if (event->params[i].filled && event->params[i].type == EVENT_PARAM_STRING)
402 free(event->params[i].value.s);
403 }
404
405 if (event->prev)
406 event->prev->next = event->next;
407
408 if (event->next)
409 event->next->prev = event->prev;
410
411 free(event);
412
413 *mboxevent = NULL;
414 }
415
mboxevent_freequeue(struct mboxevent ** mboxevent)416 void mboxevent_freequeue(struct mboxevent **mboxevent)
417 {
418 struct mboxevent *next, *event = *mboxevent;
419
420 if (!event)
421 return;
422
423 do {
424 next = event->next;
425 mboxevent_free(&event);
426 event = next;
427 }
428 while (event);
429
430 *mboxevent = NULL;
431 }
432
mboxevent_expected_calendar_param(enum event_param param)433 static int mboxevent_expected_calendar_param(enum event_param param)
434 {
435 switch (param) {
436 case EVENT_CALENDAR_ALARM_TIME:
437 case EVENT_CALENDAR_ALARM_RECIPIENTS:
438 case EVENT_CALENDAR_USER_ID:
439 case EVENT_CALENDAR_CALENDAR_ID:
440 case EVENT_CALENDAR_CALENDAR_NAME:
441 case EVENT_CALENDAR_CALENDAR_COLOR:
442 case EVENT_CALENDAR_UID:
443 case EVENT_CALENDAR_ACTION:
444 case EVENT_CALENDAR_SUMMARY:
445 case EVENT_CALENDAR_DESCRIPTION:
446 case EVENT_CALENDAR_LOCATION:
447 case EVENT_CALENDAR_TIMEZONE:
448 case EVENT_CALENDAR_START:
449 case EVENT_CALENDAR_END:
450 case EVENT_CALENDAR_ALLDAY:
451 case EVENT_CALENDAR_ATTENDEE_NAMES:
452 case EVENT_CALENDAR_ATTENDEE_EMAILS:
453 case EVENT_CALENDAR_ATTENDEE_STATUS:
454 case EVENT_CALENDAR_ORGANIZER:
455 return 1;
456 case EVENT_SERVERFQDN: /* needed to see who is master */
457 return 1;
458 default:
459 return 0;
460 }
461 }
462
mboxevent_expected_applepushservice_param(enum event_param param)463 static int mboxevent_expected_applepushservice_param(enum event_param param) {
464 switch (param) {
465 case EVENT_APPLEPUSHSERVICE_VERSION:
466 case EVENT_APPLEPUSHSERVICE_ACCOUNT_ID:
467 case EVENT_APPLEPUSHSERVICE_DEVICE_TOKEN:
468 case EVENT_APPLEPUSHSERVICE_SUBTOPIC:
469 case EVENT_APPLEPUSHSERVICE_MAILBOXES:
470 case EVENT_USER:
471 return 1;
472 default:
473 return 0;
474 }
475 }
476
mboxevent_expected_applepushservice_dav_param(enum event_param param)477 static int mboxevent_expected_applepushservice_dav_param(enum event_param param) {
478 switch (param) {
479 case EVENT_APPLEPUSHSERVICE_DAV_TOPIC:
480 case EVENT_APPLEPUSHSERVICE_DAV_DEVICE_TOKEN:
481 case EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_USER:
482 case EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_UNIQUEID:
483 case EVENT_APPLEPUSHSERVICE_DAV_EXPIRY:
484 case EVENT_USER:
485 return 1;
486 default:
487 return 0;
488 }
489 }
490
mboxevent_expected_param(enum event_type type,enum event_param param)491 static int mboxevent_expected_param(enum event_type type, enum event_param param)
492 {
493 if (type == EVENT_CALENDAR_ALARM)
494 return mboxevent_expected_calendar_param(param);
495
496 if (type == EVENT_APPLEPUSHSERVICE)
497 return mboxevent_expected_applepushservice_param(param);
498 if (type == EVENT_APPLEPUSHSERVICE_DAV)
499 return mboxevent_expected_applepushservice_dav_param(param);
500
501 switch (param) {
502 case EVENT_BODYSTRUCTURE:
503 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_BODYSTRUCTURE) &&
504 (type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND));
505 case EVENT_CLIENT_ADDRESS:
506 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_CLIENTADDRESS) &&
507 (type & (EVENT_LOGIN|EVENT_LOGOUT));
508 case EVENT_QUOTA_STORAGE:
509 return type & QUOTA_EVENTS;
510 case EVENT_DISK_USED:
511 return (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN) ||
512 /* quota usage is not known on event MessageNew, MessageAppend,
513 * MessageCopy and MessageExpunge.
514 * Thus, some code refactoring is needed to support diskUsed
515 * extra parameter */
516 ((extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_DISKUSED) &&
517 (type & (EVENT_QUOTA_CHANGE))));
518 case EVENT_ENVELOPE:
519 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_ENVELOPE) &&
520 (type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND));
521 case EVENT_FLAG_NAMES:
522 return (type & (EVENT_FLAGS_SET|EVENT_FLAGS_CLEAR)) ||
523 ((extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_FLAGNAMES) &&
524 (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)));
525 case EVENT_CLIENT_ID:
526 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CLIENTID;
527 case EVENT_SESSION_ID:
528 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_SESSIONID;
529 case EVENT_MAILBOX_ID:
530 return (type & MAILBOX_EVENTS);
531 case EVENT_MBTYPE:
532 return (type & MAILBOX_EVENTS);
533 case EVENT_MAILBOX_ACL:
534 return (type & MAILBOX_EVENTS);
535 case EVENT_QUOTA_MESSAGES:
536 return type & QUOTA_EVENTS;
537 case EVENT_MESSAGE_CONTENT:
538 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGECONTENT) &&
539 (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
540 case EVENT_MESSAGE_SIZE:
541 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGESIZE) &&
542 (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
543 case EVENT_DAV_FILENAME:
544 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVFILENAME) &&
545 (type & EVENT_CALENDAR);
546 case EVENT_DAV_UID:
547 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVUID) &&
548 (type & EVENT_CALENDAR);
549 case EVENT_MESSAGE_CID:
550 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CID) &&
551 (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
552 case EVENT_MESSAGE_EMAILID:
553 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_EMAILID) &&
554 (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
555 case EVENT_MESSAGE_THREADID:
556 return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_THREADID) &&
557 (type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
558 case EVENT_MESSAGES:
559 if (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN))
560 return 1;
561 if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGES))
562 return 0;
563 break;
564 case EVENT_MODSEQ:
565 if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MODSEQ))
566 return 0;
567 break;
568 case EVENT_OLD_MAILBOX_ID:
569 return type & (EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE|EVENT_MAILBOX_RENAME);
570 case EVENT_SERVER_ADDRESS:
571 return type & (EVENT_LOGIN|EVENT_LOGOUT);
572 case EVENT_SERVICE:
573 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_SERVICE;
574 case EVENT_TIMESTAMP:
575 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_TIMESTAMP;
576 case EVENT_ACL_SUBJECT:
577 return type & EVENT_ACL_CHANGE;
578 case EVENT_ACL_RIGHTS:
579 return type & EVENT_ACL_CHANGE;
580 case EVENT_UIDNEXT:
581 if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_UIDNEXT))
582 return 0;
583 break;
584 case EVENT_UIDSET:
585 if (type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND))
586 return 0;
587 break;
588 case EVENT_URI:
589 return 1;
590 case EVENT_PID:
591 return 1;
592 case EVENT_SERVERFQDN:
593 return 1;
594 case EVENT_USER:
595 return (
596 type & MESSAGE_EVENTS ||
597 type & FLAGS_EVENTS ||
598 type & MAILBOX_EVENTS ||
599 type & SUBS_EVENTS ||
600 type & (EVENT_LOGIN|EVENT_LOGOUT|EVENT_QUOTA_CHANGE)
601 );
602 case EVENT_MIDSET:
603 if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_MIDSET))
604 return 0;
605 break;
606 case EVENT_SESSIONID:
607 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_SESSIONID;
608 case EVENT_UNSEEN_MESSAGES:
609 if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_UNSEENMESSAGES))
610 return 0;
611 break;
612 case EVENT_CONVEXISTS:
613 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CONVEXISTS;
614 case EVENT_CONVUNSEEN:
615 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CONVUNSEEN;
616 case EVENT_COUNTERS:
617 return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_COUNTERS;
618 case EVENT_OLD_UIDSET:
619 return type & (EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE);
620 default:
621 return 0;
622 }
623
624 /* test if the parameter is related to a message event */
625 return type & (MESSAGE_EVENTS|FLAGS_EVENTS);
626 }
627
628 #define TIMESTAMP_MAX 32
mboxevent_notify(struct mboxevent ** mboxevents)629 EXPORTED void mboxevent_notify(struct mboxevent **mboxevents)
630 {
631 enum event_type type;
632 struct mboxevent *event;
633 char stimestamp[TIMESTAMP_MAX+1];
634 char *formatted_message;
635 const char *fname = NULL;
636
637 /* nothing to notify */
638 if (!*mboxevents)
639 return;
640
641 init_internal();
642
643 /* loop over the chained list of events */
644 for (event = *mboxevents; event; event = event->next) {
645 if (event->type == EVENT_CANCELLED)
646 continue;
647
648 /* swap FlagsSet and FlagsClear notification order depending the presence of
649 * the \Seen flag because it changes the value of vnd.cmu.unseenMessages.
650 * kinda bogus because it only finds two next to each other, but hey */
651 if (event->type == EVENT_FLAGS_SET &&
652 event->next &&
653 event->next->type == EVENT_FLAGS_CLEAR &&
654 strarray_find_case(&event->next->flagnames, "\\Seen", 0) >= 0) {
655
656 struct mboxevent *other = event->next;
657 // swap the outsides first
658 other->prev = event->prev;
659 event->next = other->next;
660 // swap the insides
661 event->prev = other;
662 other->next = event;
663 // switch the head if needed
664 if (event == *mboxevents) *mboxevents = other;
665 // and jump to this one for further processing
666 event = other;
667 }
668
669 /* verify that at least one message has been added depending the event type */
670 if (event->type & (MESSAGE_EVENTS|FLAGS_EVENTS)) {
671 if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND)) {
672 if (!event->params[EVENT_URI].filled)
673 continue;
674 }
675 else
676 if (event->uidset == NULL)
677 continue;
678 }
679
680 /* others quota are not supported by RFC 5423 */
681 if ((event->type & QUOTA_EVENTS) &&
682 !event->params[EVENT_QUOTA_STORAGE].filled &&
683 !event->params[EVENT_QUOTA_MESSAGES].filled)
684 continue;
685
686 /* finish to fill event parameters structure */
687
688 if (mboxevent_expected_param(event->type, EVENT_SERVICE)) {
689 FILL_STRING_PARAM(event, EVENT_SERVICE, xstrdup(config_ident));
690 }
691
692 if (mboxevent_expected_param(event->type, EVENT_SERVERFQDN)) {
693 FILL_STRING_PARAM(event, EVENT_SERVERFQDN, xstrdup(config_servername));
694 }
695
696 if (mboxevent_expected_param(event->type, EVENT_TIMESTAMP)) {
697 timeval_to_iso8601(&event->timestamp, timeval_ms,
698 stimestamp, sizeof(stimestamp));
699 FILL_STRING_PARAM(event, EVENT_TIMESTAMP, xstrdup(stimestamp));
700 }
701
702 if (event->uidset) {
703 FILL_STRING_PARAM(event, EVENT_UIDSET, seqset_cstring(event->uidset));
704 }
705 if (strarray_size(&event->midset) > 0) {
706 FILL_ARRAY_PARAM(event, EVENT_MIDSET, &event->midset);
707 }
708 if (event->olduidset) {
709 FILL_STRING_PARAM(event, EVENT_OLD_UIDSET, seqset_cstring(event->olduidset));
710 }
711
712 /* may split FlagsSet event in several event notifications */
713 do {
714 type = event->type;
715 /* prefer MessageRead and MessageTrash to FlagsSet as
716 * advised in RFC 5423 section 4.2
717 */
718 if (type == EVENT_FLAGS_SET) {
719 int i;
720
721 if ((i = strarray_find(&event->flagnames, "\\Deleted", 0)) >= 0) {
722 type = EVENT_MESSAGE_TRASH;
723 free(strarray_remove(&event->flagnames, i));
724 }
725 else if ((i = strarray_find(&event->flagnames, "\\Seen", 0)) >= 0) {
726 type = EVENT_MESSAGE_READ;
727 free(strarray_remove(&event->flagnames, i));
728 }
729 }
730
731 if (strarray_size(&event->flagnames) > 0) {
732 /* don't send flagNames parameter for those events */
733 if (type != EVENT_MESSAGE_TRASH && type != EVENT_MESSAGE_READ) {
734 char *flagnames = strarray_join(&event->flagnames, " ");
735 FILL_STRING_PARAM(event, EVENT_FLAG_NAMES, flagnames);
736
737 /* stop to loop for flagsSet event here */
738 strarray_fini(&event->flagnames);
739 }
740 }
741
742 /* check if expected event parameters are filled */
743 assert(filled_params(type, event));
744
745 /* notification is ready to send */
746 formatted_message = json_formatter(type, event->params);
747 notify(notifier, "EVENT", NULL, NULL, NULL, 0, NULL, formatted_message, fname);
748
749 free(formatted_message);
750 }
751 while (strarray_size(&event->flagnames) > 0);
752 }
753
754 return;
755 }
756
mboxevent_add_flags(struct mboxevent * event,char * flagnames[MAX_USER_FLAGS],bit32 system_flags,bit32 user_flags[MAX_USER_FLAGS/32])757 EXPORTED void mboxevent_add_flags(struct mboxevent *event, char *flagnames[MAX_USER_FLAGS],
758 bit32 system_flags, bit32 user_flags[MAX_USER_FLAGS/32])
759 {
760 unsigned flag, flagmask = 0;
761
762 if (!event)
763 return;
764
765 /* add system flags */
766 if (system_flags & FLAG_DELETED) {
767 if (strarray_find_case(excluded_flags, "\\Deleted", 0) < 0)
768 strarray_add_case(&event->flagnames, "\\Deleted");
769 }
770 if (system_flags & FLAG_ANSWERED) {
771 if (strarray_find_case(excluded_flags, "\\Answered", 0) < 0)
772 strarray_add_case(&event->flagnames, "\\Answered");
773 }
774 if (system_flags & FLAG_FLAGGED) {
775 if (strarray_find_case(excluded_flags, "\\Flagged", 0) < 0)
776 strarray_add_case(&event->flagnames, "\\Flagged");
777 }
778 if (system_flags & FLAG_DRAFT) {
779 if (strarray_find_case(excluded_flags, "\\Draft", 0) < 0)
780 strarray_add_case(&event->flagnames, "\\Draft");
781 }
782 if (system_flags & FLAG_SEEN) {
783 if (strarray_find_case(excluded_flags, "\\Seen", 0) < 0)
784 strarray_add_case(&event->flagnames, "\\Seen");
785 }
786
787 /* add user flags */
788 for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
789 if ((flag & 31) == 0) {
790 flagmask = user_flags[flag/32];
791 }
792 if (!(flagnames[flag] && (flagmask & (1<<(flag & 31)))))
793 continue;
794
795 if (strarray_find_case(excluded_flags, flagnames[flag], 0) < 0)
796 strarray_add_case(&event->flagnames, flagnames[flag]);
797 }
798 }
799
mboxevent_add_flag(struct mboxevent * event,const char * flag)800 EXPORTED void mboxevent_add_flag(struct mboxevent *event, const char *flag)
801 {
802 if (!event)
803 return;
804
805 if (mboxevent_expected_param(event->type, EVENT_FLAG_NAMES))
806 strarray_add_case(&event->flagnames, flag);
807 }
808
mboxevent_set_access(struct mboxevent * event,const char * serveraddr,const char * clientaddr,const char * userid,const char * mailboxname,const int ext_name)809 EXPORTED void mboxevent_set_access(struct mboxevent *event,
810 const char *serveraddr, const char *clientaddr,
811 const char *userid, const char *mailboxname,
812 const int ext_name __attribute__((unused)))
813 {
814 char url[MAX_MAILBOX_PATH+1];
815 struct imapurl imapurl;
816 int r;
817
818 if (!event)
819 return;
820
821 init_internal();
822
823 /* only notify Logout after successful Login */
824 if (!userid && event->type & EVENT_LOGOUT) {
825 event->type = EVENT_CANCELLED;
826 return;
827 }
828
829 /* all events needs uri parameter */
830 memset(&imapurl, 0, sizeof(struct imapurl));
831 imapurl.server = config_servername;
832
833 mbname_t *mbname = mbname_from_intname(mailboxname);
834 char *extname = xstrdupnull(mbname_extname(mbname, &namespace, NULL));
835 imapurl.mailbox = extname;
836 mbname_free(&mbname);
837
838 imapurl_toURL(url, &imapurl);
839
840 // All events want a URI parameter, which in the case of Login/Logout
841 // might be useful if it took in to account TLS SNI for example.
842 if (!event->params[EVENT_URI].filled) {
843 FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
844 }
845
846 // Login and Logout events do not have a mailboxname, so avoid looking that up...
847 if (mailboxname) {
848 mbentry_t *mbentry = NULL;
849 r = mboxlist_lookup(mailboxname, &mbentry, NULL);
850 if (!r && mbentry->uniqueid) {
851 /* mboxevent_extract_mailbox may already have set EVENT_MAILBOX_ID,
852 * so make sure to deallocate its previous value */
853 if (event->params[EVENT_MAILBOX_ID].filled) {
854 free(event->params[EVENT_MAILBOX_ID].value.s);
855 }
856 FILL_STRING_PARAM(event, EVENT_MAILBOX_ID, xstrdup(mbentry->uniqueid));
857 }
858 mboxlist_entry_free(&mbentry);
859 }
860
861 if (serveraddr && mboxevent_expected_param(event->type, EVENT_SERVER_ADDRESS)) {
862 FILL_STRING_PARAM(event, EVENT_SERVER_ADDRESS, xstrdup(serveraddr));
863 }
864
865 if (clientaddr && mboxevent_expected_param(event->type, EVENT_CLIENT_ADDRESS)) {
866 FILL_STRING_PARAM(event, EVENT_CLIENT_ADDRESS, xstrdup(clientaddr));
867 }
868
869 if (userid && mboxevent_expected_param(event->type, EVENT_USER)) {
870 FILL_STRING_PARAM(event, EVENT_USER, xstrdupsafe(userid));
871 }
872
873 free(extname);
874 }
875
mboxevent_set_acl(struct mboxevent * event,const char * identifier,const char * rights)876 EXPORTED void mboxevent_set_acl(struct mboxevent *event, const char *identifier,
877 const char *rights)
878 {
879 if (!event)
880 return;
881
882 init_internal();
883
884 FILL_STRING_PARAM(event, EVENT_ACL_SUBJECT, xstrdup(identifier));
885 // If rights == 0x0, perhaps this is a Deleteacl command, that
886 // deletes the rights for a subject, rather than a *setting* the
887 // acl to an empty string like Setacl: Setacl <folder> <subject> ""
888 if (rights == 0x0) {
889 // Pretend it is filled, but do it with null or mboxevent_free
890 // will trip.
891 FILL_STRING_PARAM(event, EVENT_ACL_RIGHTS, NULL);
892 } else {
893 FILL_STRING_PARAM(event, EVENT_ACL_RIGHTS, xstrdup(rights));
894 }
895 }
896
mboxevent_extract_record(struct mboxevent * event,struct mailbox * mailbox,struct index_record * record)897 EXPORTED void mboxevent_extract_record(struct mboxevent *event, struct mailbox *mailbox,
898 struct index_record *record)
899 {
900 char *msgid = NULL;
901
902 if (!event)
903 return;
904
905 init_internal();
906
907 /* add modseq only on first call, cancel otherwise */
908 if (mboxevent_expected_param(event->type, EVENT_MODSEQ)) {
909 if (event->uidset == NULL || (seqset_first(event->uidset) == seqset_last(event->uidset))) {
910 FILL_UNSIGNED_PARAM(event, EVENT_MODSEQ, record->modseq);
911 }
912 else {
913 /* From RFC 5423:
914 * modseq May be included with any notification referring
915 * to one message.
916 *
917 * thus cancel inclusion of modseq parameter
918 */
919 event->params[EVENT_MODSEQ].filled = 0;
920 }
921 }
922
923 /* add UID to uidset */
924 if (event->uidset == NULL)
925 event->uidset = seqset_init(0, SEQ_SPARSE);
926 seqset_add(event->uidset, record->uid, 1);
927
928 if (event->type == EVENT_CANCELLED)
929 return;
930
931 /* add Message-Id to midset or NIL if doesn't exists */
932 if (mboxevent_expected_param(event->type, (EVENT_MIDSET))) {
933 msgid = mailbox_cache_get_env(mailbox, record, ENV_MSGID);
934 strarray_add(&event->midset, msgid ? msgid : "NIL");
935
936 if (msgid)
937 free(msgid);
938 }
939
940 /* add message size */
941 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_SIZE)) {
942 FILL_UNSIGNED_PARAM(event, EVENT_MESSAGE_SIZE, record->size);
943 }
944
945 /* add message CID */
946 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_CID)) {
947 FILL_STRING_PARAM(event, EVENT_MESSAGE_CID,
948 xstrdup(conversation_id_encode(record->cid)));
949 }
950
951 /* add message EMAILID */
952 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_EMAILID)) {
953 char emailid[26];
954 emailid[0] = 'M';
955 memcpy(emailid+1, message_guid_encode(&record->guid), 24);
956 emailid[25] = '\0';
957 FILL_STRING_PARAM(event, EVENT_MESSAGE_EMAILID, xstrdup(emailid));
958 }
959
960 /* add message THREADID */
961 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_THREADID)) {
962 char threadid[18];
963 if (!record->cid) {
964 threadid[0] = 'N';
965 threadid[1] = 'I';
966 threadid[2] = 'L';
967 threadid[3] = '\0';
968 }
969 else {
970 threadid[0] = 'T';
971 memcpy(threadid+1, conversation_id_encode(record->cid), 16);
972 threadid[17] = '\0';
973 }
974 FILL_STRING_PARAM(event, EVENT_MESSAGE_THREADID, xstrdup(threadid));
975 }
976
977 /* add vnd.cmu.envelope */
978 if (mboxevent_expected_param(event->type, EVENT_ENVELOPE)) {
979 FILL_STRING_PARAM(event, EVENT_ENVELOPE,
980 xstrndup(cacheitem_base(record, CACHE_ENVELOPE),
981 cacheitem_size(record, CACHE_ENVELOPE)));
982 }
983
984 /* add bodyStructure */
985 if (mboxevent_expected_param(event->type, EVENT_BODYSTRUCTURE)) {
986 FILL_STRING_PARAM(event, EVENT_BODYSTRUCTURE,
987 xstrndup(cacheitem_base(record, CACHE_BODYSTRUCTURE),
988 cacheitem_size(record, CACHE_BODYSTRUCTURE)));
989 }
990
991 #ifdef WITH_DAV
992 /* add caldav items */
993 if ((mailbox->mbtype & (MBTYPES_DAV)) &&
994 (mboxevent_expected_param(event->type, EVENT_DAV_FILENAME) ||
995 mboxevent_expected_param(event->type, EVENT_DAV_UID))) {
996 struct body *body = NULL;
997 const char *resource = NULL;
998 struct param *param;
999
1000 if (mailbox_cacherecord(mailbox, record))
1001 return;
1002 message_read_bodystructure(record, &body);
1003
1004 for (param = body->disposition_params; param; param = param->next) {
1005 if (!strcmp(param->attribute, "FILENAME")) {
1006 resource = param->value;
1007 }
1008 }
1009
1010 if (resource) {
1011 FILL_STRING_PARAM(event, EVENT_DAV_FILENAME, xstrdup(resource));
1012 }
1013
1014 if (mboxevent_expected_param(event->type, EVENT_DAV_UID)) {
1015 if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) {
1016 struct carddav_db *carddavdb = NULL;
1017 struct carddav_data *cdata = NULL;
1018 carddavdb = mailbox_open_carddav(mailbox);
1019 carddav_lookup_resource(carddavdb, mailbox->name, resource, &cdata, 1);
1020 FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->vcard_uid));
1021 }
1022 else if (mailbox->mbtype & MBTYPE_CALENDAR) {
1023 struct caldav_db *caldavdb = NULL;
1024 struct caldav_data *cdata = NULL;
1025 caldavdb = mailbox_open_caldav(mailbox);
1026 caldav_lookup_resource(caldavdb, mailbox->name, resource, &cdata, 1);
1027 FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->ical_uid));
1028 }
1029 else {
1030 /* don't bail for MBTYPE_COLLECTION or any new things */
1031 FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(""));
1032 }
1033 }
1034 }
1035 #endif // WITH_DAV
1036 }
1037
mboxevent_extract_msgrecord(struct mboxevent * event,msgrecord_t * msgrec)1038 EXPORTED void mboxevent_extract_msgrecord(struct mboxevent *event, msgrecord_t *msgrec)
1039 {
1040 int r;
1041 uint32_t uid;
1042
1043 if (!event)
1044 return;
1045
1046 init_internal();
1047
1048 if ((r = msgrecord_get_uid(msgrec, &uid))) {
1049 syslog(LOG_ERR, "mboxevent: can't extract uid: %s", error_message(r));
1050 return;
1051 }
1052
1053 /* add modseq only on first call, cancel otherwise */
1054 if (mboxevent_expected_param(event->type, EVENT_MODSEQ)) {
1055 modseq_t modseq = 0;
1056 if ((r = msgrecord_get_modseq(msgrec, &modseq))) {
1057 syslog(LOG_ERR, "mboxevent: can't extract modseq: %s", error_message(r));
1058 return;
1059 }
1060 if (event->uidset == NULL || (seqset_first(event->uidset) == seqset_last(event->uidset))) {
1061 FILL_UNSIGNED_PARAM(event, EVENT_MODSEQ, modseq);
1062 }
1063 else {
1064 /* From RFC 5423:
1065 * modseq May be included with any notification referring
1066 * to one message.
1067 *
1068 * thus cancel inclusion of modseq parameter
1069 */
1070 event->params[EVENT_MODSEQ].filled = 0;
1071 }
1072 }
1073
1074 /* add UID to uidset */
1075 if (event->uidset == NULL)
1076 event->uidset = seqset_init(0, SEQ_SPARSE);
1077 seqset_add(event->uidset, uid, 1);
1078
1079 if (event->type == EVENT_CANCELLED)
1080 return;
1081
1082 /* add Message-Id to midset or NIL if doesn't exists */
1083 if (mboxevent_expected_param(event->type, (EVENT_MIDSET))) {
1084 char *msgid = NULL;
1085 if ((r = msgrecord_get_cache_env(msgrec, ENV_MSGID, &msgid))) {
1086 syslog(LOG_ERR, "mboxevent: can't extract msgid: %s", error_message(r));
1087 return;
1088 }
1089 strarray_add(&event->midset, msgid ? msgid : "NIL");
1090 free(msgid);
1091 }
1092
1093 /* add message size */
1094 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_SIZE)) {
1095 uint32_t size;
1096 if ((r = msgrecord_get_size(msgrec, &size))) {
1097 syslog(LOG_ERR, "mboxevent: can't extract size: %s", error_message(r));
1098 return;
1099 }
1100 FILL_UNSIGNED_PARAM(event, EVENT_MESSAGE_SIZE, size);
1101 }
1102
1103 /* add message CID */
1104 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_CID)) {
1105 bit64 cid;
1106 if ((r = msgrecord_get_cid(msgrec, &cid))) {
1107 syslog(LOG_ERR, "mboxevent: can't extract cid: %s", error_message(r));
1108 return;
1109 }
1110 FILL_STRING_PARAM(event, EVENT_MESSAGE_CID,
1111 xstrdup(conversation_id_encode(cid)));
1112 }
1113
1114 /* add message EMAILID */
1115 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_EMAILID)) {
1116 struct message_guid guid;
1117 if ((r = msgrecord_get_guid(msgrec, &guid))) {
1118 syslog(LOG_ERR, "mboxevent: can't extract guid: %s", error_message(r));
1119 return;
1120 }
1121 char emailid[26];
1122 emailid[0] = 'M';
1123 memcpy(emailid+1, message_guid_encode(&guid), 24);
1124 emailid[25] = '\0';
1125 FILL_STRING_PARAM(event, EVENT_MESSAGE_EMAILID, xstrdup(emailid));
1126 }
1127
1128 /* add message THREADID */
1129 if (mboxevent_expected_param(event->type, EVENT_MESSAGE_THREADID)) {
1130 bit64 cid;
1131 if ((r = msgrecord_get_cid(msgrec, &cid))) {
1132 syslog(LOG_ERR, "mboxevent: can't extract cid: %s", error_message(r));
1133 return;
1134 }
1135 char threadid[18];
1136 if (!cid) {
1137 threadid[0] = 'N';
1138 threadid[1] = 'I';
1139 threadid[2] = 'L';
1140 threadid[3] = '\0';
1141 }
1142 else {
1143 threadid[0] = 'T';
1144 memcpy(threadid+1, conversation_id_encode(cid), 16);
1145 threadid[17] = '\0';
1146 }
1147 FILL_STRING_PARAM(event, EVENT_MESSAGE_THREADID, xstrdup(threadid));
1148 }
1149
1150 /* add vnd.cmu.envelope */
1151 if (mboxevent_expected_param(event->type, EVENT_ENVELOPE)) {
1152 char *env;
1153 if ((r = msgrecord_get_cache_item(msgrec, CACHE_ENVELOPE, &env))) {
1154 syslog(LOG_ERR, "mboxevent: can't extract cache envelope: %s", error_message(r));
1155 return;
1156 }
1157 FILL_STRING_PARAM(event, EVENT_ENVELOPE, env);
1158 }
1159
1160 /* add bodyStructure */
1161 if (mboxevent_expected_param(event->type, EVENT_BODYSTRUCTURE)) {
1162 char *bs;
1163 if ((r = msgrecord_get_cache_item(msgrec, CACHE_BODYSTRUCTURE, &bs))) {
1164 syslog(LOG_ERR, "mboxevent: can't extract cached bodystructure: %s", error_message(r));
1165 return;
1166 }
1167 FILL_STRING_PARAM(event, EVENT_BODYSTRUCTURE, bs);
1168 }
1169
1170 #ifdef WITH_DAV
1171 /* add caldav items */
1172 struct mailbox *mailbox;
1173 r = msgrecord_get_mailbox(msgrec, &mailbox);
1174 if (r) return;
1175
1176 if ((mailbox->mbtype & (MBTYPES_DAV)) &&
1177 (mboxevent_expected_param(event->type, EVENT_DAV_FILENAME) ||
1178 mboxevent_expected_param(event->type, EVENT_DAV_UID))) {
1179 struct body *body = NULL;
1180 const char *resource = NULL;
1181 struct param *param;
1182
1183 r = msgrecord_extract_bodystructure(msgrec, &body);
1184 if (r) return;
1185
1186 for (param = body->disposition_params; param; param = param->next) {
1187 if (!strcmp(param->attribute, "FILENAME")) {
1188 resource = param->value;
1189 }
1190 }
1191
1192 if (resource) {
1193 FILL_STRING_PARAM(event, EVENT_DAV_FILENAME, xstrdup(resource));
1194 }
1195
1196 if (mboxevent_expected_param(event->type, EVENT_DAV_UID)) {
1197 if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) {
1198 struct carddav_db *carddavdb = NULL;
1199 struct carddav_data *cdata = NULL;
1200 carddavdb = mailbox_open_carddav(mailbox);
1201 carddav_lookup_resource(carddavdb, mailbox->name, resource, &cdata, 1);
1202 FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->vcard_uid));
1203 }
1204 else if (mailbox->mbtype & MBTYPE_CALENDAR) {
1205 struct caldav_db *caldavdb = NULL;
1206 struct caldav_data *cdata = NULL;
1207 caldavdb = mailbox_open_caldav(mailbox);
1208 caldav_lookup_resource(caldavdb, mailbox->name, resource, &cdata, 1);
1209 FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->ical_uid));
1210 }
1211 else {
1212 /* don't bail for MBTYPE_COLLECTION or any new things */
1213 FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(""));
1214 }
1215 }
1216
1217 if (body) message_free_body(body);
1218 free(body);
1219 }
1220 #endif // WITH_DAV
1221 }
1222
mboxevent_extract_copied_record(struct mboxevent * event,const struct mailbox * mailbox,struct index_record * record)1223 void mboxevent_extract_copied_record(struct mboxevent *event,
1224 const struct mailbox *mailbox,
1225 struct index_record *record)
1226 {
1227 int first = 0;
1228
1229 if (!event)
1230 return;
1231
1232 /* add the source message's UID to oldUidset */
1233 if (event->olduidset == NULL) {
1234 event->olduidset = seqset_init(0, SEQ_SPARSE);
1235 first = 1;
1236 }
1237 seqset_add(event->olduidset, record->uid, 1);
1238
1239 /* generate an IMAP URL to reference the old mailbox */
1240 if (first)
1241 mboxevent_extract_old_mailbox(event, mailbox);
1242 }
1243
mboxevent_extract_copied_msgrecord(struct mboxevent * event,msgrecord_t * msgrec)1244 void mboxevent_extract_copied_msgrecord(struct mboxevent *event,
1245 msgrecord_t *msgrec)
1246 {
1247 int first = 0;
1248 uint32_t uid;
1249
1250 if (!event)
1251 return;
1252
1253 /* add the source message's UID to oldUidset */
1254 if (event->olduidset == NULL) {
1255 event->olduidset = seqset_init(0, SEQ_SPARSE);
1256 first = 1;
1257 }
1258 msgrecord_get_uid(msgrec, &uid);
1259 seqset_add(event->olduidset, uid, 1);
1260
1261 /* generate an IMAP URL to reference the old mailbox */
1262 if (first) {
1263 struct mailbox *mailbox = NULL;
1264 msgrecord_get_mailbox(msgrec, &mailbox);
1265 mboxevent_extract_old_mailbox(event, mailbox);
1266 }
1267 }
1268
mboxevent_extract_content_msgrec(struct mboxevent * event,msgrecord_t * msgrec,FILE * content)1269 void mboxevent_extract_content_msgrec(struct mboxevent *event,
1270 msgrecord_t *msgrec, FILE* content)
1271 {
1272 const char *base = NULL;
1273 size_t offset, size, truncate, len = 0;
1274 uint32_t record_size, header_size;
1275
1276 if (!event)
1277 return;
1278
1279 if (!mboxevent_expected_param(event->type, EVENT_MESSAGE_CONTENT))
1280 return;
1281
1282 if (msgrecord_get_size(msgrec, &record_size) ||
1283 msgrecord_get_header_size(msgrec, &header_size)) {
1284 syslog(LOG_ERR, "mobxevent: can't determine content size");
1285 return;
1286 }
1287
1288 truncate = config_getint(IMAPOPT_EVENT_CONTENT_SIZE);
1289
1290 switch (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE)) {
1291 /* include message up to 'truncate' in size with the notification */
1292 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD:
1293 if (!truncate || record_size <= truncate) {
1294 offset = 0;
1295 size = record_size;
1296 }
1297 else {
1298 /* XXX RFC 5423 suggests to include a URLAUTH [RFC 4467] reference
1299 * for larger messages. IMAP URL of mailboxID seems enough though */
1300 return;
1301 }
1302 break;
1303 /* include message truncated to a size of 'truncate' */
1304 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_MESSAGE:
1305 offset = 0;
1306 size = (truncate && (record_size > truncate)) ?
1307 truncate : record_size;
1308 break;
1309 /* include headers truncated to a size of 'truncate' */
1310 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADER:
1311 offset = 0;
1312 size = (truncate && (header_size > truncate)) ?
1313 truncate : header_size;
1314 break;
1315 /* include body truncated to a size of 'truncate' */
1316 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_BODY:
1317 offset = header_size;
1318 size = (truncate && ((record_size - header_size) > truncate)) ?
1319 truncate : record_size - header_size;
1320 break;
1321 /* include full headers and body truncated to a size of 'truncate' */
1322 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADERBODY:
1323 offset = 0;
1324 size = (truncate && ((record_size - header_size) > truncate)) ?
1325 header_size + truncate : record_size;
1326 break;
1327 /* never happen */
1328 default:
1329 return;
1330 }
1331
1332 map_refresh(fileno(content), 1, &base, &len, record_size, "new message", 0);
1333 FILL_STRING_PARAM(event, EVENT_MESSAGE_CONTENT, xstrndup(base+offset, size));
1334 map_free(&base, &len);
1335 }
1336
1337
mboxevent_extract_content(struct mboxevent * event,const struct index_record * record,FILE * content)1338 void mboxevent_extract_content(struct mboxevent *event,
1339 const struct index_record *record, FILE* content)
1340 {
1341 const char *base = NULL;
1342 size_t offset, size, truncate, len = 0;
1343
1344 if (!event)
1345 return;
1346
1347 if (!mboxevent_expected_param(event->type, EVENT_MESSAGE_CONTENT))
1348 return;
1349
1350 truncate = config_getint(IMAPOPT_EVENT_CONTENT_SIZE);
1351
1352 switch (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE)) {
1353 /* include message up to 'truncate' in size with the notification */
1354 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD:
1355 if (!truncate || record->size <= truncate) {
1356 offset = 0;
1357 size = record->size;
1358 }
1359 else {
1360 /* XXX RFC 5423 suggests to include a URLAUTH [RFC 4467] reference
1361 * for larger messages. IMAP URL of mailboxID seems enough though */
1362 return;
1363 }
1364 break;
1365 /* include message truncated to a size of 'truncate' */
1366 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_MESSAGE:
1367 offset = 0;
1368 size = (truncate && (record->size > truncate)) ?
1369 truncate : record->size;
1370 break;
1371 /* include headers truncated to a size of 'truncate' */
1372 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADER:
1373 offset = 0;
1374 size = (truncate && (record->header_size > truncate)) ?
1375 truncate : record->header_size;
1376 break;
1377 /* include body truncated to a size of 'truncate' */
1378 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_BODY:
1379 offset = record->header_size;
1380 size = (truncate && ((record->size - record->header_size) > truncate)) ?
1381 truncate : record->size - record->header_size;
1382 break;
1383 /* include full headers and body truncated to a size of 'truncate' */
1384 case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADERBODY:
1385 offset = 0;
1386 size = (truncate && ((record->size - record->header_size) > truncate)) ?
1387 record->header_size + truncate : record->size;
1388 break;
1389 /* never happen */
1390 default:
1391 return;
1392 }
1393
1394 map_refresh(fileno(content), 1, &base, &len, record->size, "new message", 0);
1395 FILL_STRING_PARAM(event, EVENT_MESSAGE_CONTENT, xstrndup(base+offset, size));
1396 map_free(&base, &len);
1397 }
1398
mboxevent_extract_quota(struct mboxevent * event,const struct quota * quota,enum quota_resource res)1399 void mboxevent_extract_quota(struct mboxevent *event, const struct quota *quota,
1400 enum quota_resource res)
1401 {
1402 struct imapurl imapurl;
1403 char url[MAX_MAILBOX_PATH+1];
1404
1405 if (!event)
1406 return;
1407
1408 switch(res) {
1409 case QUOTA_STORAGE:
1410 if (mboxevent_expected_param(event->type, EVENT_QUOTA_STORAGE)) {
1411 if (quota->limits[res] >= 0) {
1412 FILL_UNSIGNED_PARAM(event, EVENT_QUOTA_STORAGE, quota->limits[res]);
1413 }
1414 }
1415 if (mboxevent_expected_param(event->type, EVENT_DISK_USED)) {
1416 FILL_UNSIGNED_PARAM(event, EVENT_DISK_USED,
1417 quota->useds[res] / quota_units[res]);
1418 }
1419 break;
1420 case QUOTA_MESSAGE:
1421 FILL_UNSIGNED_PARAM(event, EVENT_QUOTA_MESSAGES, quota->limits[res]);
1422 FILL_UNSIGNED_PARAM(event, EVENT_MESSAGES, quota->useds[res]);
1423 break;
1424 default:
1425 /* others quota are not supported by RFC 5423 */
1426 break;
1427 }
1428
1429 /* From RFC 5423 :
1430 * The parameters SHOULD include at least the relevant user
1431 * and quota and, optionally, the mailbox.
1432 *
1433 * It seems that it does not correspond to the concept of
1434 * quota root specified in RFC 2087. Thus we fill uri with quota root
1435 */
1436 if (!event->params[EVENT_URI].filled && event->type & QUOTA_EVENTS) {
1437 memset(&imapurl, 0, sizeof(struct imapurl));
1438 imapurl.server = config_servername;
1439
1440 /* translate internal mailbox name to external */
1441 char *extname = mboxname_to_external(quota->root, &namespace, NULL);
1442 imapurl.mailbox = extname;
1443
1444 imapurl_toURL(url, &imapurl);
1445
1446 free(extname);
1447
1448 if (!event->params[EVENT_URI].filled) {
1449 FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
1450 }
1451
1452 /* Note that userbuf for shared folders is NULL, and xstrdup
1453 * doesn't like it. However, shared folder hierarchies can have
1454 * quotas applied too, and it really requires the 'user' param
1455 * to be filled.
1456 */
1457
1458 if (!event->params[EVENT_USER].filled) {
1459 char *userid = mboxname_to_userid(quota->root);
1460 FILL_STRING_PARAM(event, EVENT_USER, xstrdupsafe(userid));
1461 free(userid);
1462 }
1463 }
1464 }
1465
mboxevent_set_numunseen(struct mboxevent * event,struct mailbox * mailbox,int numunseen)1466 EXPORTED void mboxevent_set_numunseen(struct mboxevent *event,
1467 struct mailbox *mailbox, int numunseen)
1468 {
1469 if (!event)
1470 return;
1471
1472 init_internal();
1473
1474 if (mboxevent_expected_param(event->type, EVENT_UNSEEN_MESSAGES)) {
1475 unsigned count = (numunseen >= 0) ? (unsigned)numunseen
1476 : mailbox_count_unseen(mailbox);
1477 /* as event notification is focused on mailbox, we don't care about the
1478 * authenticated user but the mailbox's owner.
1479 * it could be a problem only if it is a shared or public folder */
1480 FILL_UNSIGNED_PARAM(event, EVENT_UNSEEN_MESSAGES, count);
1481 }
1482 }
1483
mboxevent_extract_mailbox(struct mboxevent * event,struct mailbox * mailbox)1484 EXPORTED void mboxevent_extract_mailbox(struct mboxevent *event,
1485 struct mailbox *mailbox)
1486 {
1487 struct imapurl imapurl;
1488 char url[MAX_MAILBOX_PATH+1];
1489
1490 if (!event)
1491 return;
1492
1493 init_internal();
1494
1495 /* mboxevent_extract_mailbox should be called only once */
1496 if (event->params[EVENT_URI].filled)
1497 return;
1498
1499 /* verify if event notification should be disabled for this mailbox */
1500 if (!mboxevent_enabled_for_mailbox(mailbox)) {
1501 event->type = EVENT_CANCELLED;
1502 return;
1503 }
1504
1505 /* translate internal mailbox name to external */
1506 memset(&imapurl, 0, sizeof(struct imapurl));
1507 imapurl.server = config_servername;
1508 imapurl.uidvalidity = mailbox->i.uidvalidity;
1509
1510 char *extname = mboxname_to_external(mailbox->name, &namespace, NULL);
1511 imapurl.mailbox = extname;
1512
1513 if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND) && event->uidset) {
1514 imapurl.uid = seqset_first(event->uidset);
1515 /* don't add uidset parameter to MessageNew and MessageAppend events */
1516 seqset_free(event->uidset);
1517 event->uidset = NULL;
1518 }
1519
1520 /* all events needs uri parameter */
1521 imapurl_toURL(url, &imapurl);
1522 FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
1523
1524 free(extname);
1525
1526 FILL_STRING_PARAM(event, EVENT_MBTYPE,
1527 xstrdup(mboxlist_mbtype_to_string(mailbox->mbtype)));
1528
1529 FILL_STRING_PARAM(event, EVENT_MAILBOX_ACL, xstrdup(mailbox->acl));
1530
1531 /* mailbox related events also require mailboxID */
1532 if (event->type & MAILBOX_EVENTS) {
1533 FILL_STRING_PARAM(event, EVENT_MAILBOX_ID, xstrdup(mailbox->uniqueid));
1534 }
1535
1536 if (mboxevent_expected_param(event->type, EVENT_UIDNEXT)) {
1537 FILL_UNSIGNED_PARAM(event, EVENT_UIDNEXT, mailbox->i.last_uid+1);
1538 }
1539
1540 /* From RFC 5423 :
1541 * messages
1542 * Included with QuotaExceed and QuotaWithin notifications relating
1543 * to a user or mailbox message count quota. May be included with
1544 * other notifications.
1545 *
1546 * Number of messages in the mailbox. This is typically included
1547 * with message addition and deletion events.
1548 *
1549 * we are in case messages is relative to the number of messages in the
1550 * mailbox and not the message count quota.
1551 */
1552 if (mboxevent_expected_param(event->type, EVENT_MESSAGES)) {
1553 FILL_UNSIGNED_PARAM(event, EVENT_MESSAGES, mailbox->i.exists);
1554 }
1555
1556 if (mboxevent_expected_param(event->type, EVENT_CONVEXISTS) ||
1557 mboxevent_expected_param(event->type, EVENT_CONVUNSEEN)) {
1558 conv_status_t status = CONV_STATUS_INIT;
1559
1560 struct conversations_state *cstate = mailbox_get_cstate(mailbox);
1561 if (cstate)
1562 conversation_getstatus(cstate, mailbox->name, &status);
1563
1564 if (mboxevent_expected_param(event->type, EVENT_CONVEXISTS)) {
1565 FILL_UNSIGNED_PARAM(event, EVENT_CONVEXISTS, status.threadexists);
1566 }
1567
1568 if (mboxevent_expected_param(event->type, EVENT_CONVUNSEEN)) {
1569 FILL_UNSIGNED_PARAM(event, EVENT_CONVUNSEEN, status.threadunseen);
1570 }
1571 }
1572
1573 if (mboxevent_expected_param(event->type, EVENT_COUNTERS)) {
1574 struct mboxname_counters counters;
1575 struct buf value = BUF_INITIALIZER;
1576
1577 int r = mboxname_read_counters(mailbox->name, &counters);
1578 if (!r) buf_printf(&value, "%u %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %u",
1579 counters.version, counters.highestmodseq,
1580 counters.mailmodseq, counters.caldavmodseq,
1581 counters.carddavmodseq, counters.notesmodseq,
1582 counters.mailfoldersmodseq, counters.caldavfoldersmodseq,
1583 counters.carddavfoldersmodseq, counters.notesfoldersmodseq,
1584 counters.quotamodseq, counters.raclmodseq,
1585 counters.uidvalidity);
1586
1587 FILL_STRING_PARAM(event, EVENT_COUNTERS, buf_release(&value));
1588 }
1589 }
1590
mboxevent_extract_old_mailbox(struct mboxevent * event,const struct mailbox * mailbox)1591 void mboxevent_extract_old_mailbox(struct mboxevent *event,
1592 const struct mailbox *mailbox)
1593 {
1594 struct imapurl imapurl;
1595 char url[MAX_MAILBOX_PATH+1];
1596
1597 if (!event)
1598 return;
1599
1600 memset(&imapurl, 0, sizeof(struct imapurl));
1601 imapurl.server = config_servername;
1602 imapurl.uidvalidity = mailbox->i.uidvalidity;
1603
1604 /* translate internal mailbox name to external */
1605 char *extname = mboxname_to_external(mailbox->name, &namespace, NULL);
1606 imapurl.mailbox = extname;
1607
1608 imapurl_toURL(url, &imapurl);
1609 FILL_STRING_PARAM(event, EVENT_OLD_MAILBOX_ID, xstrdup(url));
1610
1611 free(extname);
1612 }
1613
mboxevent_set_client_id(const char * id)1614 EXPORTED void mboxevent_set_client_id(const char *id)
1615 {
1616 if (client_id)
1617 free((char *)client_id);
1618 client_id = xstrdupnull(id);
1619 }
1620
mboxevent_set_applepushservice(struct mboxevent * event,struct applepushserviceargs * applepushserviceargs,strarray_t * mailboxes,const char * userid)1621 EXPORTED void mboxevent_set_applepushservice(struct mboxevent *event,
1622 struct applepushserviceargs *applepushserviceargs,
1623 strarray_t *mailboxes,
1624 const char *userid)
1625 {
1626 FILL_UNSIGNED_PARAM(event, EVENT_APPLEPUSHSERVICE_VERSION, applepushserviceargs->aps_version);
1627 FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_ACCOUNT_ID, (char *) buf_cstring(&applepushserviceargs->aps_account_id));
1628 FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DEVICE_TOKEN, (char *) buf_cstring(&applepushserviceargs->aps_device_token));
1629 FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_SUBTOPIC, (char *) buf_cstring(&applepushserviceargs->aps_subtopic));
1630 FILL_ARRAY_PARAM(event, EVENT_APPLEPUSHSERVICE_MAILBOXES, mailboxes);
1631
1632 FILL_STRING_PARAM(event, EVENT_USER, xstrdupsafe(userid));
1633 }
1634
mboxevent_set_applepushservice_dav(struct mboxevent * event,const char * aps_topic,const char * device_token,const char * userid,const char * mailbox_userid,const char * mailbox_uniqueid,int mbtype,unsigned int expiry)1635 EXPORTED void mboxevent_set_applepushservice_dav(struct mboxevent *event,
1636 const char *aps_topic,
1637 const char *device_token,
1638 const char *userid,
1639 const char *mailbox_userid,
1640 const char *mailbox_uniqueid,
1641 int mbtype,
1642 unsigned int expiry)
1643 {
1644 FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_TOPIC, xstrdupnull(aps_topic));
1645 FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_DEVICE_TOKEN, xstrdupnull(device_token));
1646 FILL_STRING_PARAM(event, EVENT_USER, xstrdupnull(userid));
1647 FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_USER, xstrdupnull(mailbox_userid));
1648 FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_UNIQUEID, xstrdupnull(mailbox_uniqueid));
1649 FILL_STRING_PARAM(event, EVENT_MBTYPE, xstrdup(mboxlist_mbtype_to_string(mbtype)));
1650 FILL_UNSIGNED_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_EXPIRY, expiry);
1651
1652 }
1653
event_to_name(enum event_type type)1654 static const char *event_to_name(enum event_type type)
1655 {
1656 if (type == (EVENT_MESSAGE_NEW|EVENT_CALENDAR))
1657 return "MessageNew";
1658
1659 switch (type) {
1660 case EVENT_MESSAGE_APPEND:
1661 return "MessageAppend";
1662 case EVENT_MESSAGE_EXPIRE:
1663 return "MessageExpire";
1664 case EVENT_MESSAGE_EXPUNGE:
1665 return "MessageExpunge";
1666 case EVENT_MESSAGE_NEW:
1667 return "MessageNew";
1668 case EVENT_MESSAGE_COPY:
1669 return "vnd.cmu.MessageCopy";
1670 case EVENT_MESSAGE_MOVE:
1671 return "vnd.cmu.MessageMove";
1672 case EVENT_QUOTA_EXCEED:
1673 return "QuotaExceed";
1674 case EVENT_QUOTA_WITHIN:
1675 return "QuotaWithin";
1676 case EVENT_QUOTA_CHANGE:
1677 return "QuotaChange";
1678 case EVENT_MESSAGE_READ:
1679 return "MessageRead";
1680 case EVENT_MESSAGE_TRASH:
1681 return "MessageTrash";
1682 case EVENT_FLAGS_SET:
1683 return "FlagsSet";
1684 case EVENT_FLAGS_CLEAR:
1685 return "FlagsClear";
1686 case EVENT_LOGIN:
1687 return "Login";
1688 case EVENT_LOGOUT:
1689 return "Logout";
1690 case EVENT_MAILBOX_CREATE:
1691 return "MailboxCreate";
1692 case EVENT_MAILBOX_DELETE:
1693 return "MailboxDelete";
1694 case EVENT_MAILBOX_RENAME:
1695 return "MailboxRename";
1696 case EVENT_MAILBOX_SUBSCRIBE:
1697 return "MailboxSubscribe";
1698 case EVENT_MAILBOX_UNSUBSCRIBE:
1699 return "MailboxUnSubscribe";
1700 case EVENT_ACL_CHANGE:
1701 return "AclChange";
1702 case EVENT_CALENDAR_ALARM:
1703 return "CalendarAlarm";
1704 case EVENT_APPLEPUSHSERVICE:
1705 return "ApplePushService";
1706 case EVENT_APPLEPUSHSERVICE_DAV:
1707 return "ApplePushServiceDAV";
1708 case EVENT_MAILBOX_MODSEQ:
1709 return "MailboxModseq";
1710 default:
1711 fatal("Unknown message event", EX_SOFTWARE);
1712 }
1713
1714 /* never happen */
1715 return NULL;
1716 }
1717
json_formatter(enum event_type type,struct event_parameter params[])1718 static char *json_formatter(enum event_type type, struct event_parameter params[])
1719 {
1720 int param, ival;
1721 char *val, *ptr, *result;
1722 json_t *event_json = json_object();
1723 json_t *jarray;
1724
1725 json_object_set_new(event_json, "event", json_string(event_to_name(type)));
1726
1727 for (param = 0; param <= MAX_PARAM; param++) {
1728
1729 if (!params[param].filled)
1730 continue;
1731
1732 switch (params[param].id) {
1733 case EVENT_CLIENT_ADDRESS:
1734 /* come from saslprops structure */
1735 val = strdup(params[param].value.s);
1736 ptr = strchr(val, ';');
1737 *ptr++ = '\0';
1738
1739 json_object_set_new(event_json, "clientIP", json_string(val));
1740
1741 if (parseint32(ptr, (const char **)&ptr, &ival) >= 0)
1742 json_object_set_new(event_json, "clientPort", json_integer(ival));
1743
1744 free(val);
1745 break;
1746 case EVENT_SERVER_ADDRESS:
1747 /* come from saslprops structure */
1748 val = strdup(params[param].value.s);
1749 ptr = strchr(val, ';');
1750 *ptr++ = '\0';
1751
1752 json_object_set_new(event_json, "serverDomain", json_string(val));
1753
1754 if (parseint32(ptr, (const char **)&ptr, &ival) >= 0)
1755 json_object_set_new(event_json, "serverPort", json_integer(ival));
1756
1757 free(val);
1758 break;
1759 default:
1760 switch (params[param].type) {
1761 case EVENT_PARAM_INT:
1762 json_object_set_new(event_json, params[param].name,
1763 json_integer(params[param].value.u));
1764 break;
1765 case EVENT_PARAM_STRING:
1766 json_object_set_new(event_json, params[param].name,
1767 json_string(params[param].value.s));
1768 break;
1769 case EVENT_PARAM_ARRAY:
1770 jarray = json_array();
1771 strarray_t *sarray = params[param].value.a;
1772 int i;
1773
1774 for (i = 0; i < strarray_size(sarray); i++) {
1775 json_array_append_new(jarray, json_string(strarray_nth(sarray, i)));
1776 }
1777
1778 json_object_set_new(event_json, params[param].name, jarray);
1779 break;
1780 }
1781 break;
1782 }
1783 }
1784
1785 result = json_dumps(event_json, JSON_PRESERVE_ORDER|JSON_COMPACT);
1786 json_decref(event_json);
1787
1788 return result;
1789
1790 }
1791
1792 #ifdef NDEBUG
filled_params(enum event_type type,struct mboxevent * event)1793 static int filled_params(
1794 enum event_type type __attribute__((unused)),
1795 struct mboxevent *event __attribute__((unused))
1796 )
1797 {
1798 return 1;
1799 }
1800
1801 #else /* NDEBUG */
1802 /* overrides event->type with event_type because FlagsSet may be derived to
1803 * MessageTrash or MessageRead */
filled_params(enum event_type type,struct mboxevent * event)1804 static int filled_params(enum event_type type, struct mboxevent *event)
1805 {
1806 struct buf missing = BUF_INITIALIZER;
1807 int param, ret = 1;
1808
1809 if (!event)
1810 return 0;
1811
1812 for (param = 0; param <= MAX_PARAM; param++) {
1813
1814 if (mboxevent_expected_param(type, param) &&
1815 !event->params[param].filled) {
1816 switch (event->params[param].id) {
1817 case EVENT_FLAG_NAMES:
1818 /* flagNames may be included with MessageAppend and MessageNew
1819 * also we don't expect it here. */
1820 if (!(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)))
1821 buf_appendcstr(&missing, " flagNames");
1822 break;
1823 case EVENT_MESSAGE_CONTENT:
1824 /* messageContent is not included in standard mode if the size
1825 * of the message exceed the limit */
1826 if (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE) !=
1827 IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD)
1828 buf_appendcstr(&missing, " messageContent");
1829 break;
1830 case EVENT_MODSEQ:
1831 /* modseq is not included if notification refers to several
1832 * messages */
1833 if (!event->uidset || (seqset_first(event->uidset) == seqset_last(event->uidset)))
1834 buf_appendcstr(&missing, " modseq");
1835 break;
1836 default:
1837 buf_appendcstr(&missing, " ");
1838 buf_appendcstr(&missing, event->params[param].name);
1839 break;
1840 }
1841 }
1842 }
1843
1844 if (buf_len(&missing)) {
1845 syslog(LOG_ALERT, "Cannot notify event %s: missing parameters:%s",
1846 event_to_name(type), buf_cstring(&missing));
1847 ret = 0;
1848 }
1849
1850 buf_free(&missing);
1851 return ret;
1852 }
1853 #endif /* NDEBUG */
1854