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