/*
* Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Sébastien Michel from Atos Worldline
*/
#include <config.h>
#include "imap/mboxevent.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <jansson.h>
#include "annotate.h"
#include "assert.h"
#ifdef WITH_DAV
#include "caldav_db.h"
#include "carddav_db.h"
#endif /* WITH_DAV */
#include "global.h"
#include "imapurl.h"
#include "libconfig.h"
#include "map.h"
#include "times.h"
#include "xmalloc.h"
#include "map.h"
#include "mboxevent.h"
#include "mboxname.h"
#include "msgrecord.h"
#include "notify.h"
#include "global.h"
#define MESSAGE_EVENTS (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_EXPIRE|\
EVENT_MESSAGE_EXPUNGE|EVENT_MESSAGE_NEW|\
EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE)
#define FLAGS_EVENTS (EVENT_FLAGS_SET|EVENT_FLAGS_CLEAR|EVENT_MESSAGE_READ|\
EVENT_MESSAGE_TRASH)
#define MAILBOX_EVENTS (EVENT_MAILBOX_CREATE|EVENT_MAILBOX_DELETE|\
EVENT_MAILBOX_RENAME|EVENT_ACL_CHANGE|EVENT_MAILBOX_MODSEQ)
#define SUBS_EVENTS (EVENT_MAILBOX_SUBSCRIBE|EVENT_MAILBOX_UNSUBSCRIBE)
#define QUOTA_EVENTS (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN|EVENT_QUOTA_CHANGE)
#define CALENDAR_EVENTS (EVENT_CALENDAR_ALARM)
#define APPLEPUSHSERVICE_EVENTS (EVENT_APPLEPUSHSERVICE|EVENT_APPLEPUSHSERVICE_DAV)
static const char *notifier = NULL;
static struct namespace namespace;
static const char *client_id = NULL;
static strarray_t *excluded_flags;
static strarray_t *excluded_specialuse;
static int enable_subfolder = 1;
static int enabled_events = 0;
static unsigned long extra_params;
static struct mboxevent event_template =
{ 0,
/* ordered to optimize the parsing of the notification message */
{
/* 0 */ { EVENT_TIMESTAMP, "timestamp", EVENT_PARAM_STRING, { 0 }, 0 },
/* 1 */ { EVENT_SERVICE, "service", EVENT_PARAM_STRING, { 0 }, 0 },
/* 2 */ { EVENT_SERVER_ADDRESS, "serverAddress", EVENT_PARAM_STRING, { 0 }, 0 },
/* 3 */ { EVENT_CLIENT_ADDRESS, "clientAddress", EVENT_PARAM_STRING, { 0 }, 0 },
/* 4 */ { EVENT_OLD_MAILBOX_ID, "oldMailboxID", EVENT_PARAM_STRING, { 0 }, 0 },
/* 5 */ { EVENT_OLD_UIDSET, "vnd.cmu.oldUidset", EVENT_PARAM_STRING, { 0 }, 0 },
/* 6 */ { EVENT_MAILBOX_ID, "mailboxID", EVENT_PARAM_STRING, { 0 }, 0 },
/* 7 */ { EVENT_URI, "uri", EVENT_PARAM_STRING, { 0 }, 0 },
/* 8 */ { EVENT_MODSEQ, "modseq", EVENT_PARAM_INT, { 0 }, 0 },
/* 9 */ { EVENT_QUOTA_STORAGE, "diskQuota", EVENT_PARAM_INT, { 0 }, 0 },
/* 10 */ { EVENT_DISK_USED, "diskUsed", EVENT_PARAM_INT, { 0 }, 0 },
/* 11 */ { EVENT_QUOTA_MESSAGES, "maxMessages", EVENT_PARAM_INT, { 0 }, 0 },
/* 12 */ { EVENT_MESSAGES, "messages", EVENT_PARAM_INT, { 0 }, 0 },
/* 13 */ { EVENT_UNSEEN_MESSAGES, "vnd.cmu.unseenMessages", EVENT_PARAM_INT, { 0 }, 0 },
/* 14 */ { EVENT_UIDNEXT, "uidnext", EVENT_PARAM_INT, { 0 }, 0 },
/* 15 */ { EVENT_UIDSET, "uidset", EVENT_PARAM_STRING, { 0 }, 0 },
/* 16 */ { EVENT_MIDSET, "vnd.cmu.midset", EVENT_PARAM_STRING, { 0 }, 0 },
/* 17 */ { EVENT_FLAG_NAMES, "flagNames", EVENT_PARAM_STRING, { 0 }, 0 },
/* 18 */ { EVENT_PID, "pid", EVENT_PARAM_INT, { 0 }, 0 },
/* 19 */ { EVENT_ACL_SUBJECT, "aclSubject", EVENT_PARAM_STRING, { 0 }, 0 },
/* 20 */ { EVENT_ACL_RIGHTS, "aclRights", EVENT_PARAM_STRING, { 0 }, 0 },
/* 21 */ { EVENT_USER, "user", EVENT_PARAM_STRING, { 0 }, 0 },
/* 22 */ { EVENT_MESSAGE_SIZE, "messageSize", EVENT_PARAM_INT, { 0 }, 0 },
/* 23 */ { EVENT_MBTYPE, "vnd.cmu.mbtype", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_SERVERFQDN, "serverFQDN", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_MAILBOX_ACL, "vnd.cmu.mailboxACL", EVENT_PARAM_STRING, { 0 }, 0 },
/* 24 */ { EVENT_DAV_FILENAME, "vnd.cmu.davFilename", EVENT_PARAM_STRING, { 0 }, 0 },
/* 25 */ { EVENT_DAV_UID, "vnd.cmu.davUid", EVENT_PARAM_STRING, { 0 }, 0 },
/* 26 */ { EVENT_ENVELOPE, "vnd.cmu.envelope", EVENT_PARAM_STRING, { 0 }, 0 },
/* 27 */ { EVENT_SESSIONID, "vnd.cmu.sessionId", EVENT_PARAM_STRING, { 0 }, 0 },
/* 28 */ { EVENT_BODYSTRUCTURE, "bodyStructure", EVENT_PARAM_STRING, { 0 }, 0 },
/* 29 */ { EVENT_CLIENT_ID, "vnd.fastmail.clientId", EVENT_PARAM_STRING, { 0 }, 0 },
/* 30 */ { EVENT_SESSION_ID, "vnd.fastmail.sessionId", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CONVEXISTS, "vnd.fastmail.convExists", EVENT_PARAM_INT, { 0 }, 0 },
{ EVENT_CONVUNSEEN, "vnd.fastmail.convUnseen", EVENT_PARAM_INT, { 0 }, 0 },
{ EVENT_MESSAGE_CID, "vnd.fastmail.cid", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_COUNTERS, "vnd.fastmail.counters", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_MESSAGE_EMAILID, "vnd.cmu.emailid", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_MESSAGE_THREADID, "vnd.cmu.threadid", EVENT_PARAM_STRING, { 0 }, 0 },
/* calendar params for calalarmd/notifyd */
{ EVENT_CALENDAR_ALARM_TIME, "alarmTime", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_ALARM_RECIPIENTS, "alarmRecipients", EVENT_PARAM_ARRAY, { 0 }, 0 },
{ EVENT_CALENDAR_USER_ID, "userId", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_CALENDAR_ID, "calendarId", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_CALENDAR_NAME, "calendarName", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_CALENDAR_COLOR, "calendarColor", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_UID, "uid", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_ACTION, "action", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_SUMMARY, "summary", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_DESCRIPTION, "description", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_LOCATION, "location", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_TIMEZONE, "timezone", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_START, "start", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_END, "end", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_CALENDAR_ALLDAY, "allDay", EVENT_PARAM_INT, { 0 }, 0 },
{ EVENT_CALENDAR_ATTENDEE_NAMES, "attendeeNames", EVENT_PARAM_ARRAY, { 0 }, 0 },
{ EVENT_CALENDAR_ATTENDEE_EMAILS, "attendeeEmails", EVENT_PARAM_ARRAY, { 0 }, 0 },
{ EVENT_CALENDAR_ATTENDEE_STATUS, "attendeeStatus", EVENT_PARAM_ARRAY, { 0 }, 0 },
{ EVENT_CALENDAR_ORGANIZER, "organizer", EVENT_PARAM_STRING, { 0 }, 0 },
/* apple push params for notifyd */
{ EVENT_APPLEPUSHSERVICE_VERSION, "apsVersion", EVENT_PARAM_INT, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_ACCOUNT_ID, "apsAccountId", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_DEVICE_TOKEN, "apsDeviceToken", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_SUBTOPIC, "apsSubtopic", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_MAILBOXES, "mailboxes", EVENT_PARAM_ARRAY, { 0 }, 0 },
/* for dav push */
{ EVENT_APPLEPUSHSERVICE_DAV_TOPIC, "apsTopic", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_DAV_DEVICE_TOKEN, "apsDeviceToken", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_USER, "mailboxUser", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_UNIQUEID, "mailboxUniqueId", EVENT_PARAM_STRING, { 0 }, 0 },
{ EVENT_APPLEPUSHSERVICE_DAV_EXPIRY, "expiry", EVENT_PARAM_INT, { 0 }, 0 },
/* always at end to let the parser to easily truncate this part */
/* 31 */ { EVENT_MESSAGE_CONTENT, "messageContent", EVENT_PARAM_STRING, { 0 }, 0 }
},
STRARRAY_INITIALIZER, { 0, 0 }, NULL, STRARRAY_INITIALIZER, NULL, NULL, NULL
};
static char *json_formatter(enum event_type type, struct event_parameter params[]);
static int filled_params(enum event_type type, struct mboxevent *mboxevent);
static int mboxevent_expected_param(enum event_type type, enum event_param param);
static int mboxevent_initialized = 0;
static void done_cb(void *rock __attribute__((unused))) {
/* do nothing */
}
static void init_internal() {
if (!mboxevent_initialized) {
mboxevent_init();
cyrus_modules_add(done_cb, NULL);
}
}
EXPORTED int mboxevent_init(void)
{
const char *options;
int groups;
if (!(notifier = config_getstring(IMAPOPT_EVENT_NOTIFIER))) return 0;
/* some don't want to notify events for some IMAP flags */
options = config_getstring(IMAPOPT_EVENT_EXCLUDE_FLAGS);
excluded_flags = strarray_split(options, NULL, 0);
/* some don't want to notify events on some folders (ie. Sent, Spam) */
/* identify those folders with IMAP SPECIAL-USE */
options = config_getstring(IMAPOPT_EVENT_EXCLUDE_SPECIALUSE);
excluded_specialuse = strarray_split(options, NULL, 0);
/* special meaning to disable event notification on all sub folders */
if (strarray_find_case(excluded_specialuse, "ALL", 0) >= 0)
enable_subfolder = 0;
/* get event types's extra parameters */
extra_params = config_getbitfield(IMAPOPT_EVENT_EXTRA_PARAMS);
/* groups of related events to turn on notification */
groups = config_getbitfield(IMAPOPT_EVENT_GROUPS);
if (groups & IMAP_ENUM_EVENT_GROUPS_MESSAGE)
enabled_events |= MESSAGE_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_QUOTA)
enabled_events |= QUOTA_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_FLAGS)
enabled_events |= FLAGS_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_ACCESS)
enabled_events |= (EVENT_LOGIN|EVENT_LOGOUT|EVENT_ACL_CHANGE);
if (groups & IMAP_ENUM_EVENT_GROUPS_SUBSCRIPTION)
enabled_events |= SUBS_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_MAILBOX)
enabled_events |= MAILBOX_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_CALENDAR)
enabled_events |= CALENDAR_EVENTS;
if (groups & IMAP_ENUM_EVENT_GROUPS_APPLEPUSHSERVICE)
enabled_events |= APPLEPUSHSERVICE_EVENTS;
mboxevent_initialized = 1;
return enabled_events;
}
EXPORTED void mboxevent_setnamespace(struct namespace *n)
{
namespace = *n;
/* standardize IMAP URL format */
namespace.isadmin = 1;
namespace.isalt = 0;
}
static int mboxevent_enabled_for_mailbox(struct mailbox *mailbox)
{
struct buf attrib = BUF_INITIALIZER;
char *userid = NULL;
strarray_t *specialuse = NULL;
int enabled = 1;
int i = 0;
int r = 0;
init_internal();
if (!enable_subfolder && !mboxname_isusermailbox(mailbox->name, 1)) {
enabled = 0;
goto done;
}
/* test if the mailbox has a special-use attribute in the exclude list */
if (strarray_size(excluded_specialuse) > 0) {
userid = mboxname_to_userid(mailbox->name);
r = annotatemore_lookup(mailbox->name, "/specialuse", userid, &attrib);
if (r) goto done; /* XXX - return -1? Failure? */
/* get info and set flags */
specialuse = strarray_split(buf_cstring(&attrib), NULL, 0);
for (i = 0; i < strarray_size(specialuse) ; i++) {
const char *attribute = strarray_nth(specialuse, i);
if (strarray_find(excluded_specialuse, attribute, 0) >= 0) {
enabled = 0;
goto done;
}
}
}
done:
strarray_free(specialuse);
buf_free(&attrib);
free(userid);
return enabled;
}
EXPORTED struct mboxevent *mboxevent_new(enum event_type type)
{
struct mboxevent *mboxevent = NULL;
init_internal();
/* event notification is completely disabled */
if (!notifier)
return NULL;
/* the group to which belong the event is not enabled */
if (!(enabled_events & type))
return NULL;
mboxevent = xmalloc(sizeof(struct mboxevent));
memcpy(mboxevent, &event_template, sizeof(struct mboxevent));
unsigned i;
for (i = 0; mboxevent->params[i].id; i++) {
assert(i == mboxevent->params[i].id);
}
mboxevent->type = type;
/* From RFC 5423:
* the time at which the event occurred that triggered the notification
* (...). This MAY be an approximate time.
*
* so it seems appropriate here */
if (mboxevent_expected_param(type, EVENT_TIMESTAMP))
gettimeofday(&mboxevent->timestamp, NULL);
FILL_UNSIGNED_PARAM(mboxevent, EVENT_PID, getpid());
if (mboxevent_expected_param(type, EVENT_SESSIONID)) {
FILL_STRING_PARAM(mboxevent, EVENT_SESSIONID, xstrdup(session_id()));
}
if (mboxevent_expected_param(type, EVENT_CLIENT_ID)) {
// OK to be blank
FILL_STRING_PARAM(mboxevent, EVENT_CLIENT_ID, xstrdupsafe(client_id));
}
if (mboxevent_expected_param(type, EVENT_SESSION_ID)) {
FILL_STRING_PARAM(mboxevent, EVENT_SESSION_ID, xstrdup(session_id()));
}
return mboxevent;
}
struct mboxevent *mboxevent_enqueue(enum event_type type,
struct mboxevent **mboxevents)
{
struct mboxevent *mboxevent = NULL;
struct mboxevent *ptr;
if (!(mboxevent = mboxevent_new(type)))
return NULL;
if (mboxevents) {
if (*mboxevents == NULL)
*mboxevents = mboxevent;
else {
/* append the newly created event at end of the chained list */
ptr = *mboxevents;
while (ptr->next)
ptr = ptr->next;
ptr->next = mboxevent;
mboxevent->prev = ptr;
}
}
return mboxevent;
}
EXPORTED void mboxevent_free(struct mboxevent **mboxevent)
{
struct mboxevent *event = *mboxevent;
int i;
if (!event)
return;
seqset_free(event->uidset);
seqset_free(event->olduidset);
strarray_fini(&event->midset);
strarray_fini(&event->flagnames);
for (i = 0; i <= MAX_PARAM; i++) {
if (event->params[i].filled && event->params[i].type == EVENT_PARAM_STRING)
free(event->params[i].value.s);
}
if (event->prev)
event->prev->next = event->next;
if (event->next)
event->next->prev = event->prev;
free(event);
*mboxevent = NULL;
}
void mboxevent_freequeue(struct mboxevent **mboxevent)
{
struct mboxevent *next, *event = *mboxevent;
if (!event)
return;
do {
next = event->next;
mboxevent_free(&event);
event = next;
}
while (event);
*mboxevent = NULL;
}
static int mboxevent_expected_calendar_param(enum event_param param)
{
switch (param) {
case EVENT_CALENDAR_ALARM_TIME:
case EVENT_CALENDAR_ALARM_RECIPIENTS:
case EVENT_CALENDAR_USER_ID:
case EVENT_CALENDAR_CALENDAR_ID:
case EVENT_CALENDAR_CALENDAR_NAME:
case EVENT_CALENDAR_CALENDAR_COLOR:
case EVENT_CALENDAR_UID:
case EVENT_CALENDAR_ACTION:
case EVENT_CALENDAR_SUMMARY:
case EVENT_CALENDAR_DESCRIPTION:
case EVENT_CALENDAR_LOCATION:
case EVENT_CALENDAR_TIMEZONE:
case EVENT_CALENDAR_START:
case EVENT_CALENDAR_END:
case EVENT_CALENDAR_ALLDAY:
case EVENT_CALENDAR_ATTENDEE_NAMES:
case EVENT_CALENDAR_ATTENDEE_EMAILS:
case EVENT_CALENDAR_ATTENDEE_STATUS:
case EVENT_CALENDAR_ORGANIZER:
return 1;
case EVENT_SERVERFQDN: /* needed to see who is master */
return 1;
default:
return 0;
}
}
static int mboxevent_expected_applepushservice_param(enum event_param param) {
switch (param) {
case EVENT_APPLEPUSHSERVICE_VERSION:
case EVENT_APPLEPUSHSERVICE_ACCOUNT_ID:
case EVENT_APPLEPUSHSERVICE_DEVICE_TOKEN:
case EVENT_APPLEPUSHSERVICE_SUBTOPIC:
case EVENT_APPLEPUSHSERVICE_MAILBOXES:
case EVENT_USER:
return 1;
default:
return 0;
}
}
static int mboxevent_expected_applepushservice_dav_param(enum event_param param) {
switch (param) {
case EVENT_APPLEPUSHSERVICE_DAV_TOPIC:
case EVENT_APPLEPUSHSERVICE_DAV_DEVICE_TOKEN:
case EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_USER:
case EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_UNIQUEID:
case EVENT_APPLEPUSHSERVICE_DAV_EXPIRY:
case EVENT_USER:
return 1;
default:
return 0;
}
}
static int mboxevent_expected_param(enum event_type type, enum event_param param)
{
if (type == EVENT_CALENDAR_ALARM)
return mboxevent_expected_calendar_param(param);
if (type == EVENT_APPLEPUSHSERVICE)
return mboxevent_expected_applepushservice_param(param);
if (type == EVENT_APPLEPUSHSERVICE_DAV)
return mboxevent_expected_applepushservice_dav_param(param);
switch (param) {
case EVENT_BODYSTRUCTURE:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_BODYSTRUCTURE) &&
(type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND));
case EVENT_CLIENT_ADDRESS:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_CLIENTADDRESS) &&
(type & (EVENT_LOGIN|EVENT_LOGOUT));
case EVENT_QUOTA_STORAGE:
return type & QUOTA_EVENTS;
case EVENT_DISK_USED:
return (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN) ||
/* quota usage is not known on event MessageNew, MessageAppend,
* MessageCopy and MessageExpunge.
* Thus, some code refactoring is needed to support diskUsed
* extra parameter */
((extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_DISKUSED) &&
(type & (EVENT_QUOTA_CHANGE))));
case EVENT_ENVELOPE:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_ENVELOPE) &&
(type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND));
case EVENT_FLAG_NAMES:
return (type & (EVENT_FLAGS_SET|EVENT_FLAGS_CLEAR)) ||
((extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_FLAGNAMES) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)));
case EVENT_CLIENT_ID:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CLIENTID;
case EVENT_SESSION_ID:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_SESSIONID;
case EVENT_MAILBOX_ID:
return (type & MAILBOX_EVENTS);
case EVENT_MBTYPE:
return (type & MAILBOX_EVENTS);
case EVENT_MAILBOX_ACL:
return (type & MAILBOX_EVENTS);
case EVENT_QUOTA_MESSAGES:
return type & QUOTA_EVENTS;
case EVENT_MESSAGE_CONTENT:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGECONTENT) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
case EVENT_MESSAGE_SIZE:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGESIZE) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
case EVENT_DAV_FILENAME:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVFILENAME) &&
(type & EVENT_CALENDAR);
case EVENT_DAV_UID:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_DAVUID) &&
(type & EVENT_CALENDAR);
case EVENT_MESSAGE_CID:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CID) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
case EVENT_MESSAGE_EMAILID:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_EMAILID) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
case EVENT_MESSAGE_THREADID:
return (extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_THREADID) &&
(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW));
case EVENT_MESSAGES:
if (type & (EVENT_QUOTA_EXCEED|EVENT_QUOTA_WITHIN))
return 1;
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MESSAGES))
return 0;
break;
case EVENT_MODSEQ:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_MODSEQ))
return 0;
break;
case EVENT_OLD_MAILBOX_ID:
return type & (EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE|EVENT_MAILBOX_RENAME);
case EVENT_SERVER_ADDRESS:
return type & (EVENT_LOGIN|EVENT_LOGOUT);
case EVENT_SERVICE:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_SERVICE;
case EVENT_TIMESTAMP:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_TIMESTAMP;
case EVENT_ACL_SUBJECT:
return type & EVENT_ACL_CHANGE;
case EVENT_ACL_RIGHTS:
return type & EVENT_ACL_CHANGE;
case EVENT_UIDNEXT:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_UIDNEXT))
return 0;
break;
case EVENT_UIDSET:
if (type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND))
return 0;
break;
case EVENT_URI:
return 1;
case EVENT_PID:
return 1;
case EVENT_SERVERFQDN:
return 1;
case EVENT_USER:
return (
type & MESSAGE_EVENTS ||
type & FLAGS_EVENTS ||
type & MAILBOX_EVENTS ||
type & SUBS_EVENTS ||
type & (EVENT_LOGIN|EVENT_LOGOUT|EVENT_QUOTA_CHANGE)
);
case EVENT_MIDSET:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_MIDSET))
return 0;
break;
case EVENT_SESSIONID:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_SESSIONID;
case EVENT_UNSEEN_MESSAGES:
if (!(extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_CMU_UNSEENMESSAGES))
return 0;
break;
case EVENT_CONVEXISTS:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CONVEXISTS;
case EVENT_CONVUNSEEN:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_CONVUNSEEN;
case EVENT_COUNTERS:
return extra_params & IMAP_ENUM_EVENT_EXTRA_PARAMS_VND_FASTMAIL_COUNTERS;
case EVENT_OLD_UIDSET:
return type & (EVENT_MESSAGE_COPY|EVENT_MESSAGE_MOVE);
default:
return 0;
}
/* test if the parameter is related to a message event */
return type & (MESSAGE_EVENTS|FLAGS_EVENTS);
}
#define TIMESTAMP_MAX 32
EXPORTED void mboxevent_notify(struct mboxevent **mboxevents)
{
enum event_type type;
struct mboxevent *event;
char stimestamp[TIMESTAMP_MAX+1];
char *formatted_message;
const char *fname = NULL;
/* nothing to notify */
if (!*mboxevents)
return;
init_internal();
/* loop over the chained list of events */
for (event = *mboxevents; event; event = event->next) {
if (event->type == EVENT_CANCELLED)
continue;
/* swap FlagsSet and FlagsClear notification order depending the presence of
* the \Seen flag because it changes the value of vnd.cmu.unseenMessages.
* kinda bogus because it only finds two next to each other, but hey */
if (event->type == EVENT_FLAGS_SET &&
event->next &&
event->next->type == EVENT_FLAGS_CLEAR &&
strarray_find_case(&event->next->flagnames, "\\Seen", 0) >= 0) {
struct mboxevent *other = event->next;
// swap the outsides first
other->prev = event->prev;
event->next = other->next;
// swap the insides
event->prev = other;
other->next = event;
// switch the head if needed
if (event == *mboxevents) *mboxevents = other;
// and jump to this one for further processing
event = other;
}
/* verify that at least one message has been added depending the event type */
if (event->type & (MESSAGE_EVENTS|FLAGS_EVENTS)) {
if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND)) {
if (!event->params[EVENT_URI].filled)
continue;
}
else
if (event->uidset == NULL)
continue;
}
/* others quota are not supported by RFC 5423 */
if ((event->type & QUOTA_EVENTS) &&
!event->params[EVENT_QUOTA_STORAGE].filled &&
!event->params[EVENT_QUOTA_MESSAGES].filled)
continue;
/* finish to fill event parameters structure */
if (mboxevent_expected_param(event->type, EVENT_SERVICE)) {
FILL_STRING_PARAM(event, EVENT_SERVICE, xstrdup(config_ident));
}
if (mboxevent_expected_param(event->type, EVENT_SERVERFQDN)) {
FILL_STRING_PARAM(event, EVENT_SERVERFQDN, xstrdup(config_servername));
}
if (mboxevent_expected_param(event->type, EVENT_TIMESTAMP)) {
timeval_to_iso8601(&event->timestamp, timeval_ms,
stimestamp, sizeof(stimestamp));
FILL_STRING_PARAM(event, EVENT_TIMESTAMP, xstrdup(stimestamp));
}
if (event->uidset) {
FILL_STRING_PARAM(event, EVENT_UIDSET, seqset_cstring(event->uidset));
}
if (strarray_size(&event->midset) > 0) {
FILL_ARRAY_PARAM(event, EVENT_MIDSET, &event->midset);
}
if (event->olduidset) {
FILL_STRING_PARAM(event, EVENT_OLD_UIDSET, seqset_cstring(event->olduidset));
}
/* may split FlagsSet event in several event notifications */
do {
type = event->type;
/* prefer MessageRead and MessageTrash to FlagsSet as
* advised in RFC 5423 section 4.2
*/
if (type == EVENT_FLAGS_SET) {
int i;
if ((i = strarray_find(&event->flagnames, "\\Deleted", 0)) >= 0) {
type = EVENT_MESSAGE_TRASH;
free(strarray_remove(&event->flagnames, i));
}
else if ((i = strarray_find(&event->flagnames, "\\Seen", 0)) >= 0) {
type = EVENT_MESSAGE_READ;
free(strarray_remove(&event->flagnames, i));
}
}
if (strarray_size(&event->flagnames) > 0) {
/* don't send flagNames parameter for those events */
if (type != EVENT_MESSAGE_TRASH && type != EVENT_MESSAGE_READ) {
char *flagnames = strarray_join(&event->flagnames, " ");
FILL_STRING_PARAM(event, EVENT_FLAG_NAMES, flagnames);
/* stop to loop for flagsSet event here */
strarray_fini(&event->flagnames);
}
}
/* check if expected event parameters are filled */
assert(filled_params(type, event));
/* notification is ready to send */
formatted_message = json_formatter(type, event->params);
notify(notifier, "EVENT", NULL, NULL, NULL, 0, NULL, formatted_message, fname);
free(formatted_message);
}
while (strarray_size(&event->flagnames) > 0);
}
return;
}
EXPORTED void mboxevent_add_flags(struct mboxevent *event, char *flagnames[MAX_USER_FLAGS],
bit32 system_flags, bit32 user_flags[MAX_USER_FLAGS/32])
{
unsigned flag, flagmask = 0;
if (!event)
return;
/* add system flags */
if (system_flags & FLAG_DELETED) {
if (strarray_find_case(excluded_flags, "\\Deleted", 0) < 0)
strarray_add_case(&event->flagnames, "\\Deleted");
}
if (system_flags & FLAG_ANSWERED) {
if (strarray_find_case(excluded_flags, "\\Answered", 0) < 0)
strarray_add_case(&event->flagnames, "\\Answered");
}
if (system_flags & FLAG_FLAGGED) {
if (strarray_find_case(excluded_flags, "\\Flagged", 0) < 0)
strarray_add_case(&event->flagnames, "\\Flagged");
}
if (system_flags & FLAG_DRAFT) {
if (strarray_find_case(excluded_flags, "\\Draft", 0) < 0)
strarray_add_case(&event->flagnames, "\\Draft");
}
if (system_flags & FLAG_SEEN) {
if (strarray_find_case(excluded_flags, "\\Seen", 0) < 0)
strarray_add_case(&event->flagnames, "\\Seen");
}
/* add user flags */
for (flag = 0; flag < MAX_USER_FLAGS; flag++) {
if ((flag & 31) == 0) {
flagmask = user_flags[flag/32];
}
if (!(flagnames[flag] && (flagmask & (1<<(flag & 31)))))
continue;
if (strarray_find_case(excluded_flags, flagnames[flag], 0) < 0)
strarray_add_case(&event->flagnames, flagnames[flag]);
}
}
EXPORTED void mboxevent_add_flag(struct mboxevent *event, const char *flag)
{
if (!event)
return;
if (mboxevent_expected_param(event->type, EVENT_FLAG_NAMES))
strarray_add_case(&event->flagnames, flag);
}
EXPORTED void mboxevent_set_access(struct mboxevent *event,
const char *serveraddr, const char *clientaddr,
const char *userid, const char *mailboxname,
const int ext_name __attribute__((unused)))
{
char url[MAX_MAILBOX_PATH+1];
struct imapurl imapurl;
int r;
if (!event)
return;
init_internal();
/* only notify Logout after successful Login */
if (!userid && event->type & EVENT_LOGOUT) {
event->type = EVENT_CANCELLED;
return;
}
/* all events needs uri parameter */
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
mbname_t *mbname = mbname_from_intname(mailboxname);
char *extname = xstrdupnull(mbname_extname(mbname, &namespace, NULL));
imapurl.mailbox = extname;
mbname_free(&mbname);
imapurl_toURL(url, &imapurl);
// All events want a URI parameter, which in the case of Login/Logout
// might be useful if it took in to account TLS SNI for example.
if (!event->params[EVENT_URI].filled) {
FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
}
// Login and Logout events do not have a mailboxname, so avoid looking that up...
if (mailboxname) {
mbentry_t *mbentry = NULL;
r = mboxlist_lookup(mailboxname, &mbentry, NULL);
if (!r && mbentry->uniqueid) {
/* mboxevent_extract_mailbox may already have set EVENT_MAILBOX_ID,
* so make sure to deallocate its previous value */
if (event->params[EVENT_MAILBOX_ID].filled) {
free(event->params[EVENT_MAILBOX_ID].value.s);
}
FILL_STRING_PARAM(event, EVENT_MAILBOX_ID, xstrdup(mbentry->uniqueid));
}
mboxlist_entry_free(&mbentry);
}
if (serveraddr && mboxevent_expected_param(event->type, EVENT_SERVER_ADDRESS)) {
FILL_STRING_PARAM(event, EVENT_SERVER_ADDRESS, xstrdup(serveraddr));
}
if (clientaddr && mboxevent_expected_param(event->type, EVENT_CLIENT_ADDRESS)) {
FILL_STRING_PARAM(event, EVENT_CLIENT_ADDRESS, xstrdup(clientaddr));
}
if (userid && mboxevent_expected_param(event->type, EVENT_USER)) {
FILL_STRING_PARAM(event, EVENT_USER, xstrdupsafe(userid));
}
free(extname);
}
EXPORTED void mboxevent_set_acl(struct mboxevent *event, const char *identifier,
const char *rights)
{
if (!event)
return;
init_internal();
FILL_STRING_PARAM(event, EVENT_ACL_SUBJECT, xstrdup(identifier));
// If rights == 0x0, perhaps this is a Deleteacl command, that
// deletes the rights for a subject, rather than a *setting* the
// acl to an empty string like Setacl: Setacl <folder> <subject> ""
if (rights == 0x0) {
// Pretend it is filled, but do it with null or mboxevent_free
// will trip.
FILL_STRING_PARAM(event, EVENT_ACL_RIGHTS, NULL);
} else {
FILL_STRING_PARAM(event, EVENT_ACL_RIGHTS, xstrdup(rights));
}
}
EXPORTED void mboxevent_extract_record(struct mboxevent *event, struct mailbox *mailbox,
struct index_record *record)
{
char *msgid = NULL;
if (!event)
return;
init_internal();
/* add modseq only on first call, cancel otherwise */
if (mboxevent_expected_param(event->type, EVENT_MODSEQ)) {
if (event->uidset == NULL || (seqset_first(event->uidset) == seqset_last(event->uidset))) {
FILL_UNSIGNED_PARAM(event, EVENT_MODSEQ, record->modseq);
}
else {
/* From RFC 5423:
* modseq May be included with any notification referring
* to one message.
*
* thus cancel inclusion of modseq parameter
*/
event->params[EVENT_MODSEQ].filled = 0;
}
}
/* add UID to uidset */
if (event->uidset == NULL)
event->uidset = seqset_init(0, SEQ_SPARSE);
seqset_add(event->uidset, record->uid, 1);
if (event->type == EVENT_CANCELLED)
return;
/* add Message-Id to midset or NIL if doesn't exists */
if (mboxevent_expected_param(event->type, (EVENT_MIDSET))) {
msgid = mailbox_cache_get_env(mailbox, record, ENV_MSGID);
strarray_add(&event->midset, msgid ? msgid : "NIL");
if (msgid)
free(msgid);
}
/* add message size */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_SIZE)) {
FILL_UNSIGNED_PARAM(event, EVENT_MESSAGE_SIZE, record->size);
}
/* add message CID */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_CID)) {
FILL_STRING_PARAM(event, EVENT_MESSAGE_CID,
xstrdup(conversation_id_encode(record->cid)));
}
/* add message EMAILID */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_EMAILID)) {
char emailid[26];
emailid[0] = 'M';
memcpy(emailid+1, message_guid_encode(&record->guid), 24);
emailid[25] = '\0';
FILL_STRING_PARAM(event, EVENT_MESSAGE_EMAILID, xstrdup(emailid));
}
/* add message THREADID */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_THREADID)) {
char threadid[18];
if (!record->cid) {
threadid[0] = 'N';
threadid[1] = 'I';
threadid[2] = 'L';
threadid[3] = '\0';
}
else {
threadid[0] = 'T';
memcpy(threadid+1, conversation_id_encode(record->cid), 16);
threadid[17] = '\0';
}
FILL_STRING_PARAM(event, EVENT_MESSAGE_THREADID, xstrdup(threadid));
}
/* add vnd.cmu.envelope */
if (mboxevent_expected_param(event->type, EVENT_ENVELOPE)) {
FILL_STRING_PARAM(event, EVENT_ENVELOPE,
xstrndup(cacheitem_base(record, CACHE_ENVELOPE),
cacheitem_size(record, CACHE_ENVELOPE)));
}
/* add bodyStructure */
if (mboxevent_expected_param(event->type, EVENT_BODYSTRUCTURE)) {
FILL_STRING_PARAM(event, EVENT_BODYSTRUCTURE,
xstrndup(cacheitem_base(record, CACHE_BODYSTRUCTURE),
cacheitem_size(record, CACHE_BODYSTRUCTURE)));
}
#ifdef WITH_DAV
/* add caldav items */
if ((mailbox->mbtype & (MBTYPES_DAV)) &&
(mboxevent_expected_param(event->type, EVENT_DAV_FILENAME) ||
mboxevent_expected_param(event->type, EVENT_DAV_UID))) {
struct body *body = NULL;
const char *resource = NULL;
struct param *param;
if (mailbox_cacherecord(mailbox, record))
return;
message_read_bodystructure(record, &body);
for (param = body->disposition_params; param; param = param->next) {
if (!strcmp(param->attribute, "FILENAME")) {
resource = param->value;
}
}
if (resource) {
FILL_STRING_PARAM(event, EVENT_DAV_FILENAME, xstrdup(resource));
}
if (mboxevent_expected_param(event->type, EVENT_DAV_UID)) {
if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) {
struct carddav_db *carddavdb = NULL;
struct carddav_data *cdata = NULL;
carddavdb = mailbox_open_carddav(mailbox);
carddav_lookup_resource(carddavdb, mailbox->name, resource, &cdata, 1);
FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->vcard_uid));
}
else if (mailbox->mbtype & MBTYPE_CALENDAR) {
struct caldav_db *caldavdb = NULL;
struct caldav_data *cdata = NULL;
caldavdb = mailbox_open_caldav(mailbox);
caldav_lookup_resource(caldavdb, mailbox->name, resource, &cdata, 1);
FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->ical_uid));
}
else {
/* don't bail for MBTYPE_COLLECTION or any new things */
FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(""));
}
}
}
#endif // WITH_DAV
}
EXPORTED void mboxevent_extract_msgrecord(struct mboxevent *event, msgrecord_t *msgrec)
{
int r;
uint32_t uid;
if (!event)
return;
init_internal();
if ((r = msgrecord_get_uid(msgrec, &uid))) {
syslog(LOG_ERR, "mboxevent: can't extract uid: %s", error_message(r));
return;
}
/* add modseq only on first call, cancel otherwise */
if (mboxevent_expected_param(event->type, EVENT_MODSEQ)) {
modseq_t modseq = 0;
if ((r = msgrecord_get_modseq(msgrec, &modseq))) {
syslog(LOG_ERR, "mboxevent: can't extract modseq: %s", error_message(r));
return;
}
if (event->uidset == NULL || (seqset_first(event->uidset) == seqset_last(event->uidset))) {
FILL_UNSIGNED_PARAM(event, EVENT_MODSEQ, modseq);
}
else {
/* From RFC 5423:
* modseq May be included with any notification referring
* to one message.
*
* thus cancel inclusion of modseq parameter
*/
event->params[EVENT_MODSEQ].filled = 0;
}
}
/* add UID to uidset */
if (event->uidset == NULL)
event->uidset = seqset_init(0, SEQ_SPARSE);
seqset_add(event->uidset, uid, 1);
if (event->type == EVENT_CANCELLED)
return;
/* add Message-Id to midset or NIL if doesn't exists */
if (mboxevent_expected_param(event->type, (EVENT_MIDSET))) {
char *msgid = NULL;
if ((r = msgrecord_get_cache_env(msgrec, ENV_MSGID, &msgid))) {
syslog(LOG_ERR, "mboxevent: can't extract msgid: %s", error_message(r));
return;
}
strarray_add(&event->midset, msgid ? msgid : "NIL");
free(msgid);
}
/* add message size */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_SIZE)) {
uint32_t size;
if ((r = msgrecord_get_size(msgrec, &size))) {
syslog(LOG_ERR, "mboxevent: can't extract size: %s", error_message(r));
return;
}
FILL_UNSIGNED_PARAM(event, EVENT_MESSAGE_SIZE, size);
}
/* add message CID */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_CID)) {
bit64 cid;
if ((r = msgrecord_get_cid(msgrec, &cid))) {
syslog(LOG_ERR, "mboxevent: can't extract cid: %s", error_message(r));
return;
}
FILL_STRING_PARAM(event, EVENT_MESSAGE_CID,
xstrdup(conversation_id_encode(cid)));
}
/* add message EMAILID */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_EMAILID)) {
struct message_guid guid;
if ((r = msgrecord_get_guid(msgrec, &guid))) {
syslog(LOG_ERR, "mboxevent: can't extract guid: %s", error_message(r));
return;
}
char emailid[26];
emailid[0] = 'M';
memcpy(emailid+1, message_guid_encode(&guid), 24);
emailid[25] = '\0';
FILL_STRING_PARAM(event, EVENT_MESSAGE_EMAILID, xstrdup(emailid));
}
/* add message THREADID */
if (mboxevent_expected_param(event->type, EVENT_MESSAGE_THREADID)) {
bit64 cid;
if ((r = msgrecord_get_cid(msgrec, &cid))) {
syslog(LOG_ERR, "mboxevent: can't extract cid: %s", error_message(r));
return;
}
char threadid[18];
if (!cid) {
threadid[0] = 'N';
threadid[1] = 'I';
threadid[2] = 'L';
threadid[3] = '\0';
}
else {
threadid[0] = 'T';
memcpy(threadid+1, conversation_id_encode(cid), 16);
threadid[17] = '\0';
}
FILL_STRING_PARAM(event, EVENT_MESSAGE_THREADID, xstrdup(threadid));
}
/* add vnd.cmu.envelope */
if (mboxevent_expected_param(event->type, EVENT_ENVELOPE)) {
char *env;
if ((r = msgrecord_get_cache_item(msgrec, CACHE_ENVELOPE, &env))) {
syslog(LOG_ERR, "mboxevent: can't extract cache envelope: %s", error_message(r));
return;
}
FILL_STRING_PARAM(event, EVENT_ENVELOPE, env);
}
/* add bodyStructure */
if (mboxevent_expected_param(event->type, EVENT_BODYSTRUCTURE)) {
char *bs;
if ((r = msgrecord_get_cache_item(msgrec, CACHE_BODYSTRUCTURE, &bs))) {
syslog(LOG_ERR, "mboxevent: can't extract cached bodystructure: %s", error_message(r));
return;
}
FILL_STRING_PARAM(event, EVENT_BODYSTRUCTURE, bs);
}
#ifdef WITH_DAV
/* add caldav items */
struct mailbox *mailbox;
r = msgrecord_get_mailbox(msgrec, &mailbox);
if (r) return;
if ((mailbox->mbtype & (MBTYPES_DAV)) &&
(mboxevent_expected_param(event->type, EVENT_DAV_FILENAME) ||
mboxevent_expected_param(event->type, EVENT_DAV_UID))) {
struct body *body = NULL;
const char *resource = NULL;
struct param *param;
r = msgrecord_extract_bodystructure(msgrec, &body);
if (r) return;
for (param = body->disposition_params; param; param = param->next) {
if (!strcmp(param->attribute, "FILENAME")) {
resource = param->value;
}
}
if (resource) {
FILL_STRING_PARAM(event, EVENT_DAV_FILENAME, xstrdup(resource));
}
if (mboxevent_expected_param(event->type, EVENT_DAV_UID)) {
if (mailbox->mbtype & MBTYPE_ADDRESSBOOK) {
struct carddav_db *carddavdb = NULL;
struct carddav_data *cdata = NULL;
carddavdb = mailbox_open_carddav(mailbox);
carddav_lookup_resource(carddavdb, mailbox->name, resource, &cdata, 1);
FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->vcard_uid));
}
else if (mailbox->mbtype & MBTYPE_CALENDAR) {
struct caldav_db *caldavdb = NULL;
struct caldav_data *cdata = NULL;
caldavdb = mailbox_open_caldav(mailbox);
caldav_lookup_resource(caldavdb, mailbox->name, resource, &cdata, 1);
FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(cdata->ical_uid));
}
else {
/* don't bail for MBTYPE_COLLECTION or any new things */
FILL_STRING_PARAM(event, EVENT_DAV_UID, xstrdup(""));
}
}
if (body) message_free_body(body);
free(body);
}
#endif // WITH_DAV
}
void mboxevent_extract_copied_record(struct mboxevent *event,
const struct mailbox *mailbox,
struct index_record *record)
{
int first = 0;
if (!event)
return;
/* add the source message's UID to oldUidset */
if (event->olduidset == NULL) {
event->olduidset = seqset_init(0, SEQ_SPARSE);
first = 1;
}
seqset_add(event->olduidset, record->uid, 1);
/* generate an IMAP URL to reference the old mailbox */
if (first)
mboxevent_extract_old_mailbox(event, mailbox);
}
void mboxevent_extract_copied_msgrecord(struct mboxevent *event,
msgrecord_t *msgrec)
{
int first = 0;
uint32_t uid;
if (!event)
return;
/* add the source message's UID to oldUidset */
if (event->olduidset == NULL) {
event->olduidset = seqset_init(0, SEQ_SPARSE);
first = 1;
}
msgrecord_get_uid(msgrec, &uid);
seqset_add(event->olduidset, uid, 1);
/* generate an IMAP URL to reference the old mailbox */
if (first) {
struct mailbox *mailbox = NULL;
msgrecord_get_mailbox(msgrec, &mailbox);
mboxevent_extract_old_mailbox(event, mailbox);
}
}
void mboxevent_extract_content_msgrec(struct mboxevent *event,
msgrecord_t *msgrec, FILE* content)
{
const char *base = NULL;
size_t offset, size, truncate, len = 0;
uint32_t record_size, header_size;
if (!event)
return;
if (!mboxevent_expected_param(event->type, EVENT_MESSAGE_CONTENT))
return;
if (msgrecord_get_size(msgrec, &record_size) ||
msgrecord_get_header_size(msgrec, &header_size)) {
syslog(LOG_ERR, "mobxevent: can't determine content size");
return;
}
truncate = config_getint(IMAPOPT_EVENT_CONTENT_SIZE);
switch (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE)) {
/* include message up to 'truncate' in size with the notification */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD:
if (!truncate || record_size <= truncate) {
offset = 0;
size = record_size;
}
else {
/* XXX RFC 5423 suggests to include a URLAUTH [RFC 4467] reference
* for larger messages. IMAP URL of mailboxID seems enough though */
return;
}
break;
/* include message truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_MESSAGE:
offset = 0;
size = (truncate && (record_size > truncate)) ?
truncate : record_size;
break;
/* include headers truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADER:
offset = 0;
size = (truncate && (header_size > truncate)) ?
truncate : header_size;
break;
/* include body truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_BODY:
offset = header_size;
size = (truncate && ((record_size - header_size) > truncate)) ?
truncate : record_size - header_size;
break;
/* include full headers and body truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADERBODY:
offset = 0;
size = (truncate && ((record_size - header_size) > truncate)) ?
header_size + truncate : record_size;
break;
/* never happen */
default:
return;
}
map_refresh(fileno(content), 1, &base, &len, record_size, "new message", 0);
FILL_STRING_PARAM(event, EVENT_MESSAGE_CONTENT, xstrndup(base+offset, size));
map_free(&base, &len);
}
void mboxevent_extract_content(struct mboxevent *event,
const struct index_record *record, FILE* content)
{
const char *base = NULL;
size_t offset, size, truncate, len = 0;
if (!event)
return;
if (!mboxevent_expected_param(event->type, EVENT_MESSAGE_CONTENT))
return;
truncate = config_getint(IMAPOPT_EVENT_CONTENT_SIZE);
switch (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE)) {
/* include message up to 'truncate' in size with the notification */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD:
if (!truncate || record->size <= truncate) {
offset = 0;
size = record->size;
}
else {
/* XXX RFC 5423 suggests to include a URLAUTH [RFC 4467] reference
* for larger messages. IMAP URL of mailboxID seems enough though */
return;
}
break;
/* include message truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_MESSAGE:
offset = 0;
size = (truncate && (record->size > truncate)) ?
truncate : record->size;
break;
/* include headers truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADER:
offset = 0;
size = (truncate && (record->header_size > truncate)) ?
truncate : record->header_size;
break;
/* include body truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_BODY:
offset = record->header_size;
size = (truncate && ((record->size - record->header_size) > truncate)) ?
truncate : record->size - record->header_size;
break;
/* include full headers and body truncated to a size of 'truncate' */
case IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_HEADERBODY:
offset = 0;
size = (truncate && ((record->size - record->header_size) > truncate)) ?
record->header_size + truncate : record->size;
break;
/* never happen */
default:
return;
}
map_refresh(fileno(content), 1, &base, &len, record->size, "new message", 0);
FILL_STRING_PARAM(event, EVENT_MESSAGE_CONTENT, xstrndup(base+offset, size));
map_free(&base, &len);
}
void mboxevent_extract_quota(struct mboxevent *event, const struct quota *quota,
enum quota_resource res)
{
struct imapurl imapurl;
char url[MAX_MAILBOX_PATH+1];
if (!event)
return;
switch(res) {
case QUOTA_STORAGE:
if (mboxevent_expected_param(event->type, EVENT_QUOTA_STORAGE)) {
if (quota->limits[res] >= 0) {
FILL_UNSIGNED_PARAM(event, EVENT_QUOTA_STORAGE, quota->limits[res]);
}
}
if (mboxevent_expected_param(event->type, EVENT_DISK_USED)) {
FILL_UNSIGNED_PARAM(event, EVENT_DISK_USED,
quota->useds[res] / quota_units[res]);
}
break;
case QUOTA_MESSAGE:
FILL_UNSIGNED_PARAM(event, EVENT_QUOTA_MESSAGES, quota->limits[res]);
FILL_UNSIGNED_PARAM(event, EVENT_MESSAGES, quota->useds[res]);
break;
default:
/* others quota are not supported by RFC 5423 */
break;
}
/* From RFC 5423 :
* The parameters SHOULD include at least the relevant user
* and quota and, optionally, the mailbox.
*
* It seems that it does not correspond to the concept of
* quota root specified in RFC 2087. Thus we fill uri with quota root
*/
if (!event->params[EVENT_URI].filled && event->type & QUOTA_EVENTS) {
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
/* translate internal mailbox name to external */
char *extname = mboxname_to_external(quota->root, &namespace, NULL);
imapurl.mailbox = extname;
imapurl_toURL(url, &imapurl);
free(extname);
if (!event->params[EVENT_URI].filled) {
FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
}
/* Note that userbuf for shared folders is NULL, and xstrdup
* doesn't like it. However, shared folder hierarchies can have
* quotas applied too, and it really requires the 'user' param
* to be filled.
*/
if (!event->params[EVENT_USER].filled) {
char *userid = mboxname_to_userid(quota->root);
FILL_STRING_PARAM(event, EVENT_USER, xstrdupsafe(userid));
free(userid);
}
}
}
EXPORTED void mboxevent_set_numunseen(struct mboxevent *event,
struct mailbox *mailbox, int numunseen)
{
if (!event)
return;
init_internal();
if (mboxevent_expected_param(event->type, EVENT_UNSEEN_MESSAGES)) {
unsigned count = (numunseen >= 0) ? (unsigned)numunseen
: mailbox_count_unseen(mailbox);
/* as event notification is focused on mailbox, we don't care about the
* authenticated user but the mailbox's owner.
* it could be a problem only if it is a shared or public folder */
FILL_UNSIGNED_PARAM(event, EVENT_UNSEEN_MESSAGES, count);
}
}
EXPORTED void mboxevent_extract_mailbox(struct mboxevent *event,
struct mailbox *mailbox)
{
struct imapurl imapurl;
char url[MAX_MAILBOX_PATH+1];
if (!event)
return;
init_internal();
/* mboxevent_extract_mailbox should be called only once */
if (event->params[EVENT_URI].filled)
return;
/* verify if event notification should be disabled for this mailbox */
if (!mboxevent_enabled_for_mailbox(mailbox)) {
event->type = EVENT_CANCELLED;
return;
}
/* translate internal mailbox name to external */
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
imapurl.uidvalidity = mailbox->i.uidvalidity;
char *extname = mboxname_to_external(mailbox->name, &namespace, NULL);
imapurl.mailbox = extname;
if (event->type & (EVENT_MESSAGE_NEW|EVENT_MESSAGE_APPEND) && event->uidset) {
imapurl.uid = seqset_first(event->uidset);
/* don't add uidset parameter to MessageNew and MessageAppend events */
seqset_free(event->uidset);
event->uidset = NULL;
}
/* all events needs uri parameter */
imapurl_toURL(url, &imapurl);
FILL_STRING_PARAM(event, EVENT_URI, xstrdup(url));
free(extname);
FILL_STRING_PARAM(event, EVENT_MBTYPE,
xstrdup(mboxlist_mbtype_to_string(mailbox->mbtype)));
FILL_STRING_PARAM(event, EVENT_MAILBOX_ACL, xstrdup(mailbox->acl));
/* mailbox related events also require mailboxID */
if (event->type & MAILBOX_EVENTS) {
FILL_STRING_PARAM(event, EVENT_MAILBOX_ID, xstrdup(mailbox->uniqueid));
}
if (mboxevent_expected_param(event->type, EVENT_UIDNEXT)) {
FILL_UNSIGNED_PARAM(event, EVENT_UIDNEXT, mailbox->i.last_uid+1);
}
/* From RFC 5423 :
* messages
* Included with QuotaExceed and QuotaWithin notifications relating
* to a user or mailbox message count quota. May be included with
* other notifications.
*
* Number of messages in the mailbox. This is typically included
* with message addition and deletion events.
*
* we are in case messages is relative to the number of messages in the
* mailbox and not the message count quota.
*/
if (mboxevent_expected_param(event->type, EVENT_MESSAGES)) {
FILL_UNSIGNED_PARAM(event, EVENT_MESSAGES, mailbox->i.exists);
}
if (mboxevent_expected_param(event->type, EVENT_CONVEXISTS) ||
mboxevent_expected_param(event->type, EVENT_CONVUNSEEN)) {
conv_status_t status = CONV_STATUS_INIT;
struct conversations_state *cstate = mailbox_get_cstate(mailbox);
if (cstate)
conversation_getstatus(cstate, mailbox->name, &status);
if (mboxevent_expected_param(event->type, EVENT_CONVEXISTS)) {
FILL_UNSIGNED_PARAM(event, EVENT_CONVEXISTS, status.threadexists);
}
if (mboxevent_expected_param(event->type, EVENT_CONVUNSEEN)) {
FILL_UNSIGNED_PARAM(event, EVENT_CONVUNSEEN, status.threadunseen);
}
}
if (mboxevent_expected_param(event->type, EVENT_COUNTERS)) {
struct mboxname_counters counters;
struct buf value = BUF_INITIALIZER;
int r = mboxname_read_counters(mailbox->name, &counters);
if (!r) buf_printf(&value, "%u %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu %u",
counters.version, counters.highestmodseq,
counters.mailmodseq, counters.caldavmodseq,
counters.carddavmodseq, counters.notesmodseq,
counters.mailfoldersmodseq, counters.caldavfoldersmodseq,
counters.carddavfoldersmodseq, counters.notesfoldersmodseq,
counters.quotamodseq, counters.raclmodseq,
counters.uidvalidity);
FILL_STRING_PARAM(event, EVENT_COUNTERS, buf_release(&value));
}
}
void mboxevent_extract_old_mailbox(struct mboxevent *event,
const struct mailbox *mailbox)
{
struct imapurl imapurl;
char url[MAX_MAILBOX_PATH+1];
if (!event)
return;
memset(&imapurl, 0, sizeof(struct imapurl));
imapurl.server = config_servername;
imapurl.uidvalidity = mailbox->i.uidvalidity;
/* translate internal mailbox name to external */
char *extname = mboxname_to_external(mailbox->name, &namespace, NULL);
imapurl.mailbox = extname;
imapurl_toURL(url, &imapurl);
FILL_STRING_PARAM(event, EVENT_OLD_MAILBOX_ID, xstrdup(url));
free(extname);
}
EXPORTED void mboxevent_set_client_id(const char *id)
{
if (client_id)
free((char *)client_id);
client_id = xstrdupnull(id);
}
EXPORTED void mboxevent_set_applepushservice(struct mboxevent *event,
struct applepushserviceargs *applepushserviceargs,
strarray_t *mailboxes,
const char *userid)
{
FILL_UNSIGNED_PARAM(event, EVENT_APPLEPUSHSERVICE_VERSION, applepushserviceargs->aps_version);
FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_ACCOUNT_ID, (char *) buf_cstring(&applepushserviceargs->aps_account_id));
FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DEVICE_TOKEN, (char *) buf_cstring(&applepushserviceargs->aps_device_token));
FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_SUBTOPIC, (char *) buf_cstring(&applepushserviceargs->aps_subtopic));
FILL_ARRAY_PARAM(event, EVENT_APPLEPUSHSERVICE_MAILBOXES, mailboxes);
FILL_STRING_PARAM(event, EVENT_USER, xstrdupsafe(userid));
}
EXPORTED void 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)
{
FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_TOPIC, xstrdupnull(aps_topic));
FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_DEVICE_TOKEN, xstrdupnull(device_token));
FILL_STRING_PARAM(event, EVENT_USER, xstrdupnull(userid));
FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_USER, xstrdupnull(mailbox_userid));
FILL_STRING_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_MAILBOX_UNIQUEID, xstrdupnull(mailbox_uniqueid));
FILL_STRING_PARAM(event, EVENT_MBTYPE, xstrdup(mboxlist_mbtype_to_string(mbtype)));
FILL_UNSIGNED_PARAM(event, EVENT_APPLEPUSHSERVICE_DAV_EXPIRY, expiry);
}
static const char *event_to_name(enum event_type type)
{
if (type == (EVENT_MESSAGE_NEW|EVENT_CALENDAR))
return "MessageNew";
switch (type) {
case EVENT_MESSAGE_APPEND:
return "MessageAppend";
case EVENT_MESSAGE_EXPIRE:
return "MessageExpire";
case EVENT_MESSAGE_EXPUNGE:
return "MessageExpunge";
case EVENT_MESSAGE_NEW:
return "MessageNew";
case EVENT_MESSAGE_COPY:
return "vnd.cmu.MessageCopy";
case EVENT_MESSAGE_MOVE:
return "vnd.cmu.MessageMove";
case EVENT_QUOTA_EXCEED:
return "QuotaExceed";
case EVENT_QUOTA_WITHIN:
return "QuotaWithin";
case EVENT_QUOTA_CHANGE:
return "QuotaChange";
case EVENT_MESSAGE_READ:
return "MessageRead";
case EVENT_MESSAGE_TRASH:
return "MessageTrash";
case EVENT_FLAGS_SET:
return "FlagsSet";
case EVENT_FLAGS_CLEAR:
return "FlagsClear";
case EVENT_LOGIN:
return "Login";
case EVENT_LOGOUT:
return "Logout";
case EVENT_MAILBOX_CREATE:
return "MailboxCreate";
case EVENT_MAILBOX_DELETE:
return "MailboxDelete";
case EVENT_MAILBOX_RENAME:
return "MailboxRename";
case EVENT_MAILBOX_SUBSCRIBE:
return "MailboxSubscribe";
case EVENT_MAILBOX_UNSUBSCRIBE:
return "MailboxUnSubscribe";
case EVENT_ACL_CHANGE:
return "AclChange";
case EVENT_CALENDAR_ALARM:
return "CalendarAlarm";
case EVENT_APPLEPUSHSERVICE:
return "ApplePushService";
case EVENT_APPLEPUSHSERVICE_DAV:
return "ApplePushServiceDAV";
case EVENT_MAILBOX_MODSEQ:
return "MailboxModseq";
default:
fatal("Unknown message event", EX_SOFTWARE);
}
/* never happen */
return NULL;
}
static char *json_formatter(enum event_type type, struct event_parameter params[])
{
int param, ival;
char *val, *ptr, *result;
json_t *event_json = json_object();
json_t *jarray;
json_object_set_new(event_json, "event", json_string(event_to_name(type)));
for (param = 0; param <= MAX_PARAM; param++) {
if (!params[param].filled)
continue;
switch (params[param].id) {
case EVENT_CLIENT_ADDRESS:
/* come from saslprops structure */
val = strdup(params[param].value.s);
ptr = strchr(val, ';');
*ptr++ = '\0';
json_object_set_new(event_json, "clientIP", json_string(val));
if (parseint32(ptr, (const char **)&ptr, &ival) >= 0)
json_object_set_new(event_json, "clientPort", json_integer(ival));
free(val);
break;
case EVENT_SERVER_ADDRESS:
/* come from saslprops structure */
val = strdup(params[param].value.s);
ptr = strchr(val, ';');
*ptr++ = '\0';
json_object_set_new(event_json, "serverDomain", json_string(val));
if (parseint32(ptr, (const char **)&ptr, &ival) >= 0)
json_object_set_new(event_json, "serverPort", json_integer(ival));
free(val);
break;
default:
switch (params[param].type) {
case EVENT_PARAM_INT:
json_object_set_new(event_json, params[param].name,
json_integer(params[param].value.u));
break;
case EVENT_PARAM_STRING:
json_object_set_new(event_json, params[param].name,
json_string(params[param].value.s));
break;
case EVENT_PARAM_ARRAY:
jarray = json_array();
strarray_t *sarray = params[param].value.a;
int i;
for (i = 0; i < strarray_size(sarray); i++) {
json_array_append_new(jarray, json_string(strarray_nth(sarray, i)));
}
json_object_set_new(event_json, params[param].name, jarray);
break;
}
break;
}
}
result = json_dumps(event_json, JSON_PRESERVE_ORDER|JSON_COMPACT);
json_decref(event_json);
return result;
}
#ifdef NDEBUG
static int filled_params(
enum event_type type __attribute__((unused)),
struct mboxevent *event __attribute__((unused))
)
{
return 1;
}
#else /* NDEBUG */
/* overrides event->type with event_type because FlagsSet may be derived to
* MessageTrash or MessageRead */
static int filled_params(enum event_type type, struct mboxevent *event)
{
struct buf missing = BUF_INITIALIZER;
int param, ret = 1;
if (!event)
return 0;
for (param = 0; param <= MAX_PARAM; param++) {
if (mboxevent_expected_param(type, param) &&
!event->params[param].filled) {
switch (event->params[param].id) {
case EVENT_FLAG_NAMES:
/* flagNames may be included with MessageAppend and MessageNew
* also we don't expect it here. */
if (!(type & (EVENT_MESSAGE_APPEND|EVENT_MESSAGE_NEW)))
buf_appendcstr(&missing, " flagNames");
break;
case EVENT_MESSAGE_CONTENT:
/* messageContent is not included in standard mode if the size
* of the message exceed the limit */
if (config_getenum(IMAPOPT_EVENT_CONTENT_INCLUSION_MODE) !=
IMAP_ENUM_EVENT_CONTENT_INCLUSION_MODE_STANDARD)
buf_appendcstr(&missing, " messageContent");
break;
case EVENT_MODSEQ:
/* modseq is not included if notification refers to several
* messages */
if (!event->uidset || (seqset_first(event->uidset) == seqset_last(event->uidset)))
buf_appendcstr(&missing, " modseq");
break;
default:
buf_appendcstr(&missing, " ");
buf_appendcstr(&missing, event->params[param].name);
break;
}
}
}
if (buf_len(&missing)) {
syslog(LOG_ALERT, "Cannot notify event %s: missing parameters:%s",
event_to_name(type), buf_cstring(&missing));
ret = 0;
}
buf_free(&missing);
return ret;
}
#endif /* NDEBUG */