1 /* jmap_mail_submission.c -- Routines for handling JMAP mail submission
2 *
3 * Copyright (c) 1994-2018 Carnegie Mellon University. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The name "Carnegie Mellon University" must not be used to
18 * endorse or promote products derived from this software without
19 * prior written permission. For permission or any legal
20 * details, please contact
21 * Carnegie Mellon University
22 * Center for Technology Transfer and Enterprise Creation
23 * 4615 Forbes Avenue
24 * Suite 302
25 * Pittsburgh, PA 15213
26 * (412) 268-7393, fax: (412) 268-7395
27 * innovation@andrew.cmu.edu
28 *
29 * 4. Redistributions of any form whatsoever must retain the following
30 * acknowledgment:
31 * "This product includes software developed by Computing Services
32 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33 *
34 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41 *
42 */
43
44 #include <config.h>
45
46 #ifdef HAVE_UNISTD_H
47 #include <unistd.h>
48 #endif
49 #include <ctype.h>
50 #include <string.h>
51 #include <syslog.h>
52 #include <assert.h>
53 #include <limits.h>
54 #include <errno.h>
55
56 #include "acl.h"
57 #include "append.h"
58 #include "http_jmap.h"
59 #include "http_proxy.h"
60 #include "jmap_mail.h"
61 #include "jmap_util.h"
62 #include "json_support.h"
63 #include "parseaddr.h"
64 #include "proxy.h"
65 #include "smtpclient.h"
66 #include "sync_support.h"
67 #include "times.h"
68 #include "user.h"
69 #include "util.h"
70
71 /* generated headers are not necessarily in current directory */
72 #include "imap/http_err.h"
73 #include "imap/imap_err.h"
74
75 #define JMAP_SUBID_SIZE 12
76
77 static int jmap_emailsubmission_get(jmap_req_t *req);
78 static int jmap_emailsubmission_set(jmap_req_t *req);
79 static int jmap_emailsubmission_changes(jmap_req_t *req);
80 static int jmap_emailsubmission_query(jmap_req_t *req);
81 static int jmap_emailsubmission_querychanges(jmap_req_t *req);
82 static int jmap_identity_get(jmap_req_t *req);
83
84 static jmap_method_t jmap_emailsubmission_methods_standard[] = {
85 {
86 "EmailSubmission/get",
87 JMAP_URN_SUBMISSION,
88 &jmap_emailsubmission_get,
89 JMAP_NEED_CSTATE
90 },
91 {
92 "EmailSubmission/set",
93 JMAP_URN_SUBMISSION,
94 &jmap_emailsubmission_set,
95 JMAP_NEED_CSTATE | JMAP_READ_WRITE
96 },
97 {
98 "EmailSubmission/changes",
99 JMAP_URN_SUBMISSION,
100 &jmap_emailsubmission_changes,
101 JMAP_NEED_CSTATE
102 },
103 {
104 "EmailSubmission/query",
105 JMAP_URN_SUBMISSION,
106 &jmap_emailsubmission_query,
107 JMAP_NEED_CSTATE
108 },
109 {
110 "EmailSubmission/queryChanges",
111 JMAP_URN_SUBMISSION,
112 &jmap_emailsubmission_querychanges,
113 JMAP_NEED_CSTATE
114 },
115 {
116 "Identity/get",
117 JMAP_URN_SUBMISSION,
118 &jmap_identity_get,
119 /*flags*/0
120 },
121 { NULL, NULL, NULL, 0}
122 };
123
124 static jmap_method_t jmap_emailsubmission_methods_nonstandard[] = {
125 { NULL, NULL, NULL, 0}
126 };
127
jmap_emailsubmission_init(jmap_settings_t * settings)128 HIDDEN void jmap_emailsubmission_init(jmap_settings_t *settings)
129 {
130 jmap_method_t *mp;
131 for (mp = jmap_emailsubmission_methods_standard; mp->name; mp++) {
132 hash_insert(mp->name, mp, &settings->methods);
133 }
134
135 json_object_set_new(settings->server_capabilities,
136 JMAP_URN_SUBMISSION, json_object());
137
138 if (config_getswitch(IMAPOPT_JMAP_NONSTANDARD_EXTENSIONS)) {
139 for (mp = jmap_emailsubmission_methods_nonstandard; mp->name; mp++) {
140 hash_insert(mp->name, mp, &settings->methods);
141 }
142 }
143 }
144
jmap_emailsubmission_capabilities(json_t * account_capabilities)145 HIDDEN void jmap_emailsubmission_capabilities(json_t *account_capabilities)
146 {
147 static json_t *submit_capabilities = NULL;
148 smtpclient_t *smp = NULL;
149
150 if (!submit_capabilities && !smtpclient_open(&smp)) {
151 /* determine extensions from submission server */
152 json_t *submit_ext = json_object();
153 const char *smtp_capa[] = { "FUTURERELEASE", "SIZE", "DSN",
154 "DELIVERBY", "MT-PRIORITY", NULL };
155 const char **capa;
156 struct buf buf = BUF_INITIALIZER;
157 int delay_time = config_getduration(IMAPOPT_JMAP_MAX_DELAYED_SEND, 's');
158 if (delay_time < 0) delay_time = 0;
159
160 for (capa = smtp_capa; *capa; capa++) {
161 const char *args = smtpclient_has_ext(smp, *capa);
162
163 if (args) {
164 strarray_t *sa = strarray_split(args, NULL, STRARRAY_TRIM);
165 json_t *jargs = json_array();
166 int i;
167
168 for (i = 0; i < strarray_size(sa); i++) {
169 buf_setcstr(&buf, strarray_nth(sa, i));
170 json_array_append_new(jargs, json_string(buf_lcase(&buf)));
171 }
172 strarray_free(sa);
173
174 buf_setcstr(&buf, *capa);
175 json_object_set_new(submit_ext, buf_lcase(&buf), jargs);
176 }
177 }
178 smtpclient_close(&smp);
179 buf_free(&buf);
180 submit_capabilities = json_pack("{s:i s:o}",
181 "maxDelayedSend", delay_time,
182 "submissionExtensions", submit_ext);
183 }
184
185 json_object_set(account_capabilities, JMAP_URN_SUBMISSION, submit_capabilities);
186 }
187
_emailsubmission_address_parse(json_t * addr,struct jmap_parser * parser,time_t * holduntil)188 static int _emailsubmission_address_parse(json_t *addr,
189 struct jmap_parser *parser,
190 time_t *holduntil)
191 {
192 int is_valid = 0;
193
194 if (holduntil) *holduntil = 0;
195
196 json_t *email = json_object_get(addr, "email");
197 if (email && json_string_value(email)) {
198 struct address *a = NULL;
199 parseaddr_list(json_string_value(email), &a);
200 if (a && !a->invalid && a->mailbox && a->domain && !a->next) {
201 is_valid = 1;
202 }
203 parseaddr_free(a);
204 }
205 else {
206 jmap_parser_invalid(parser, "email");
207 }
208
209 const char *key;
210 json_t *jval;
211 json_t *parameters = json_object_get(addr, "parameters");
212 jmap_parser_push(parser, "parameters");
213 json_object_foreach(parameters, key, jval) {
214 if (!smtp_is_valid_esmtp_keyword(key)) {
215 jmap_parser_invalid(parser, key);
216 }
217 else if (JNOTNULL(jval) && !json_is_string(jval)) {
218 /* We'll xtext-encode any non-esmtp values later */
219 jmap_parser_invalid(parser, key);
220 }
221 else if (holduntil) {
222 const char *val = json_string_value(jval);
223
224 if (!strcasecmp(key, "HOLDFOR")) {
225 char *endptr = (char *) val;
226 ulong interval = val ? strtoul(val, &endptr, 10) : ULONG_MAX;
227 time_t now = time(0);
228
229 if (endptr == val || *endptr != '\0' ||
230 interval > 99999999 /* per RFC 4865 */) {
231 jmap_parser_invalid(parser, key);
232 }
233 else *holduntil = now + interval;
234 }
235 else if (!strcasecmp(key, "HOLDUNTIL")) {
236 if (!val || time_from_iso8601(val, holduntil) < 0) {
237 jmap_parser_invalid(parser, key);
238 }
239 }
240 }
241 }
242 jmap_parser_pop(parser);
243
244 return is_valid;
245 }
246
lookup_submission_collection(const char * accountid,mbentry_t ** mbentry)247 static int lookup_submission_collection(const char *accountid,
248 mbentry_t **mbentry)
249 {
250 mbname_t *mbname;
251 const char *submissionname;
252 int r;
253
254 /* Create submission mailbox name from the parsed path */
255 mbname = mbname_from_userid(accountid);
256 mbname_push_boxes(mbname, config_getstring(IMAPOPT_JMAPSUBMISSIONFOLDER));
257
258 /* XXX - hack to allow @domain parts for non-domain-split users */
259 if (httpd_extradomain) {
260 /* not allowed to be cross domain */
261 if (mbname_localpart(mbname) &&
262 strcmpsafe(mbname_domain(mbname), httpd_extradomain)) {
263 r = HTTP_NOT_FOUND;
264 goto done;
265 }
266 mbname_set_domain(mbname, NULL);
267 }
268
269 /* Locate the mailbox */
270 submissionname = mbname_intname(mbname);
271 r = proxy_mlookup(submissionname, mbentry, NULL, NULL);
272 if (r == IMAP_MAILBOX_NONEXISTENT) {
273 /* Find location of INBOX */
274 char *inboxname = mboxname_user_mbox(accountid, NULL);
275
276 int r1 = proxy_mlookup(inboxname, mbentry, NULL, NULL);
277 free(inboxname);
278 if (r1 == IMAP_MAILBOX_NONEXISTENT) {
279 r = IMAP_INVALID_USER;
280 goto done;
281 }
282
283 int rights = httpd_myrights(httpd_authstate, *mbentry);
284 if ((rights & JACL_CREATECHILD) != JACL_CREATECHILD) {
285 r = IMAP_PERMISSION_DENIED;
286 goto done;
287 }
288
289 if (*mbentry) free((*mbentry)->name);
290 else *mbentry = mboxlist_entry_create();
291 (*mbentry)->name = xstrdup(submissionname);
292 }
293 else if (!r) {
294 int rights = httpd_myrights(httpd_authstate, *mbentry);
295 if ((rights & JACL_ADDITEMS) != JACL_ADDITEMS) {
296 r = IMAP_PERMISSION_DENIED;
297 goto done;
298 }
299 }
300
301 done:
302 mbname_free(&mbname);
303 return r;
304 }
305
306
ensure_submission_collection(const char * accountid,mbentry_t ** mbentryp,int * created)307 static int ensure_submission_collection(const char *accountid,
308 mbentry_t **mbentryp,
309 int *created)
310 {
311 mbentry_t *mbentry = NULL;
312 if (created) *created = 0;
313
314 /* submission collection */
315 int r = lookup_submission_collection(accountid, &mbentry);
316 if (!r) { // happy path
317 if (mbentryp) *mbentryp = mbentry;
318 else mboxlist_entry_free(&mbentry);
319 return 0;
320 }
321
322 // otherwise, clean up ready for next attempt
323 mboxlist_entry_free(&mbentry);
324
325 struct mboxlock *namespacelock = user_namespacelock(accountid);
326
327 // did we lose the race?
328 r = lookup_submission_collection(accountid, &mbentry);
329
330 if (r == IMAP_MAILBOX_NONEXISTENT) {
331 if (created) *created = 1;
332
333 if (!mbentry) goto done;
334 else if (mbentry->server) {
335 proxy_findserver(mbentry->server, &http_protocol, httpd_userid,
336 &backend_cached, NULL, NULL, httpd_in);
337 goto done;
338 }
339
340 int options = config_getint(IMAPOPT_MAILBOX_DEFAULT_OPTIONS)
341 | OPT_POP3_NEW_UIDL | OPT_IMAP_HAS_ALARMS;
342 r = mboxlist_createmailbox_opts(mbentry->name, MBTYPE_SUBMISSION,
343 NULL, 1 /* admin */, accountid,
344 httpd_authstate,
345 options, 0, 0, 0, 0, NULL, NULL);
346 if (r) {
347 syslog(LOG_ERR, "IOERROR: failed to create %s (%s)",
348 mbentry->name, error_message(r));
349 }
350 }
351
352 done:
353 mboxname_release(&namespacelock);
354 if (mbentryp && !r) *mbentryp = mbentry;
355 else mboxlist_entry_free(&mbentry);
356 return r;
357 }
358
store_submission(struct mailbox * mailbox,struct buf * msg,time_t holduntil,json_t * emailsubmission,json_t ** new_submission)359 static int store_submission(struct mailbox *mailbox,
360 struct buf *msg, time_t holduntil,
361 json_t *emailsubmission,
362 json_t **new_submission)
363 {
364 struct stagemsg *stage = NULL;
365 struct appendstate as;
366 strarray_t flags = STRARRAY_INITIALIZER;
367 struct buf buf = BUF_INITIALIZER;
368 struct body *body = NULL;
369 char datestr[80], *from;
370 size_t msglen = buf_len(msg);
371 FILE *f = NULL;
372 int r;
373 time_t now = time(0);
374 time_t internaldate = holduntil;
375
376 if (!holduntil) {
377 /* Already sent */
378 msglen = 0;
379 internaldate = now;
380 strarray_append(&flags, "\\Answered");
381 if (config_getswitch(IMAPOPT_JMAPSUBMISSION_DELETEONSEND)) {
382 /* delete the EmailSubmission object immediately */
383 strarray_append(&flags, "\\Deleted");
384 // this non-standard flag is magic and works on the append layer
385 strarray_append(&flags, "\\Expunged");
386 }
387 }
388
389 /* Prepare to stage the message */
390 if (!(f = append_newstage(mailbox->name, internaldate, 0, &stage))) {
391 syslog(LOG_ERR, "append_newstage(%s) failed", mailbox->name);
392 r = IMAP_IOERROR;
393 goto done;
394 }
395
396 /* Stage the message to send as message/rfc822 */
397 time_to_rfc5322(now, datestr, sizeof(datestr));
398
399 if (strchr(httpd_userid, '@')) {
400 /* XXX This needs to be done via an LDAP/DB lookup */
401 buf_printf(&buf, "<%s>", httpd_userid);
402 }
403 else {
404 buf_printf(&buf, "<%s@%s>", httpd_userid, config_servername);
405 }
406
407 from = charset_encode_mimeheader(buf_cstring(&buf), buf_len(&buf), 0);
408
409 fprintf(f, "MIME-Version: 1.0\r\n"
410 "Date: %s\r\n"
411 "From: %s\r\n"
412 "Subject: JMAP EmailSubmission for %s\r\n"
413 "Content-Type: message/rfc822\r\n"
414 "Content-Length: %ld\r\n"
415 "%s: ", datestr, from,
416 json_string_value(json_object_get(emailsubmission, "emailId")),
417 msglen, JMAP_SUBMISSION_HDR);
418 free(from);
419
420 /* Add JMAP submission object as content of header field */
421 size_t size = json_dumpb(emailsubmission, NULL, 0, 0);
422 buf_truncate(&buf, size);
423 size = json_dumpb(emailsubmission,
424 (char *) buf_base(&buf), size, JSON_COMPACT);
425 r = fwrite(buf_base(&buf), size, 1, f);
426 buf_free(&buf);
427 if (!r) {
428 r = IMAP_IOERROR;
429 goto done;
430 }
431 fputs("\r\n\r\n", f);
432
433 /* Add submitted message */
434 if ((msglen && !fwrite(buf_base(msg), msglen, 1, f)) || fflush(f)) {
435 r = IMAP_IOERROR;
436 goto done;
437 }
438 fclose(f);
439
440 /* Prepare to append the message to the mailbox */
441 r = append_setup_mbox(&as, mailbox, httpd_userid, httpd_authstate,
442 0, /*quota*/NULL, 0, 0, /*event*/0);
443 if (r) {
444 syslog(LOG_ERR, "append_setup(%s) failed: %s",
445 mailbox->name, error_message(r));
446 goto done;
447 }
448
449 /* Append the message to the mailbox */
450 r = append_fromstage(&as, &body, stage, internaldate, 0, &flags, 0, /*annots*/NULL);
451
452 if (r) {
453 append_abort(&as);
454 syslog(LOG_ERR, "append_fromstage(%s) failed: %s",
455 mailbox->name, error_message(r));
456 goto done;
457 }
458
459 r = append_commit(&as);
460 if (r) {
461 syslog(LOG_ERR, "append_commit(%s) failed: %s",
462 mailbox->name, error_message(r));
463 goto done;
464 }
465
466 /* Create id from message UID, using 'S' prefix */
467 char sub_id[JMAP_SUBID_SIZE];
468 sprintf(sub_id, "S%u", mailbox->i.last_uid);
469
470 char sendat[RFC3339_DATETIME_MAX];
471 time_to_rfc3339(internaldate, sendat, RFC3339_DATETIME_MAX);
472
473 // XXX: we should include all the other fields from the spec
474 *new_submission = json_pack("{s:s, s:s, s:s}",
475 "id", sub_id,
476 "undoStatus", (holduntil ? "pending" : "final"),
477 "sendAt", sendat
478 );
479
480 done:
481 if (body) {
482 message_free_body(body);
483 free(body);
484 }
485 strarray_fini(&flags);
486 append_removestage(stage);
487 if (mailbox) {
488 if (r) mailbox_abort(mailbox);
489 else r = mailbox_commit(mailbox);
490 }
491
492 return r;
493 }
494
_emailsubmission_create(jmap_req_t * req,struct mailbox * submbox,json_t * emailsubmission,json_t ** new_submission,json_t ** set_err,smtpclient_t ** sm,char ** emailid)495 static void _emailsubmission_create(jmap_req_t *req,
496 struct mailbox *submbox,
497 json_t *emailsubmission,
498 json_t **new_submission,
499 json_t **set_err,
500 smtpclient_t **sm, char **emailid)
501 {
502 struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
503 struct buf buf = BUF_INITIALIZER;
504
505 /* messageId */
506 json_t *jemailId = json_object_get(emailsubmission, "emailId");
507 const char *msgid = jmap_id_string_value(req, jemailId);
508 if (!msgid) {
509 jmap_parser_invalid(&parser, "emailId");
510 }
511 *emailid = xstrdupnull(msgid);
512
513 /* identityId */
514 const char *identityid = NULL;
515 json_t *jidentityId = json_object_get(emailsubmission, "identityId");
516 if (json_is_string(jidentityId)) {
517 identityid = json_string_value(jidentityId);
518 if (strcmp(identityid, req->userid)) {
519 jmap_parser_invalid(&parser, "identityId");
520 }
521 }
522 else {
523 jmap_parser_invalid(&parser, "identityId");
524 }
525
526 /* envelope */
527 time_t holduntil = 0;
528 json_t *envelope = json_object_get(emailsubmission, "envelope");
529 if (JNOTNULL(envelope)) {
530 jmap_parser_push(&parser, "envelope");
531 json_t *from = json_object_get(envelope, "mailFrom");
532 if (json_object_size(from)) {
533 jmap_parser_push(&parser, "mailFrom");
534 _emailsubmission_address_parse(from, &parser, &holduntil);
535 jmap_parser_pop(&parser);
536 }
537 else {
538 jmap_parser_invalid(&parser, "mailFrom");
539 }
540 json_t *rcpt = json_object_get(envelope, "rcptTo");
541 if (json_array_size(rcpt)) {
542 size_t i;
543 json_t *addr;
544 json_array_foreach(rcpt, i, addr) {
545 jmap_parser_push_index(&parser, "rcptTo", i, NULL);
546 _emailsubmission_address_parse(addr, &parser, NULL);
547 jmap_parser_pop(&parser);
548 }
549 }
550 else {
551 jmap_parser_invalid(&parser, "rcptTo");
552 }
553 jmap_parser_pop(&parser);
554 } else {
555 envelope = NULL;
556 }
557
558 /* Reject read-only properties */
559 if (json_object_get(emailsubmission, "id")) {
560 jmap_parser_invalid(&parser, "id");
561 }
562 if (json_object_get(emailsubmission, "threadId")) {
563 jmap_parser_invalid(&parser, "threadId");
564 }
565 if (json_object_get(emailsubmission, "sendAt")) {
566 jmap_parser_invalid(&parser, "sendAt");
567 }
568 if (json_object_get(emailsubmission, "undoStatus")) {
569 jmap_parser_invalid(&parser, "undoStatus");
570 }
571 if (json_object_get(emailsubmission, "deliveryStatus")) {
572 jmap_parser_invalid(&parser, "deliveryStatus");
573 }
574 if (json_object_get(emailsubmission, "dsnBlobIds")) {
575 jmap_parser_invalid(&parser, "dsnBlobIds");
576 }
577 if (json_object_get(emailsubmission, "mdnBlobIds")) {
578 jmap_parser_invalid(&parser, "mdnBlobIds");
579 }
580
581 if (json_array_size(parser.invalid)) {
582 *set_err = json_pack("{s:s}", "type", "invalidProperties");
583 json_object_set(*set_err, "properties", parser.invalid);
584 jmap_parser_fini(&parser);
585 return;
586 }
587 jmap_parser_fini(&parser);
588
589 /* No more returns from here on */
590 char *mboxname = NULL;
591 uint32_t uid = 0;
592 struct mailbox *mbox = NULL;
593 json_t *myenvelope = NULL;
594 msgrecord_t *mr = NULL;
595 json_t *msg = NULL;
596 int r = 0;
597 int fd_msg = -1;
598
599 /* Lookup the message */
600 r = jmap_email_find(req, msgid, &mboxname, &uid);
601 if (r) {
602 if (r == IMAP_NOTFOUND) {
603 *set_err = json_pack("{s:s}", "type", "emailNotFound");
604 }
605 goto done;
606 }
607
608 /* Check ACL */
609 if (!jmap_hasrights(req, mboxname, JACL_READITEMS)) {
610 *set_err = json_pack("{s:s}", "type", "emailNotFound");
611 goto done;
612 }
613
614 /* Open the mailboxes */
615 r = jmap_openmbox(req, mboxname, &mbox, 1);
616 if (r) goto done;
617
618 /* Load the message */
619 mr = msgrecord_from_uid(mbox, uid);
620 if (!mr) {
621 /* That's a never-should-happen error */
622 syslog(LOG_ERR, "Unexpected null msgrecord at %s:%d",
623 __FILE__, __LINE__);
624 r = IMAP_INTERNAL;
625 goto done;
626 }
627
628 /* Extract envelope from message */
629 if (!envelope) {
630 hash_table props = HASH_TABLE_INITIALIZER;
631 construct_hash_table(&props, 8, 0);
632 hash_insert("sender", (void*)1, &props);
633 hash_insert("from", (void*)1, &props);
634 hash_insert("to", (void*)1, &props);
635 hash_insert("cc", (void*)1, &props);
636 hash_insert("bcc", (void*)1, &props);
637 hash_insert("replyTo", (void*)1, &props);
638 r = jmap_email_get_with_props(req, &props, mr, &msg);
639 free_hash_table(&props, NULL);
640 if (r) goto done;
641
642 myenvelope = json_object();
643 envelope = myenvelope;
644
645 /* Determine MAIL FROM */
646 json_t *jfrom = json_object_get(json_object_get(msg, "sender"), "email");
647 if (!jfrom) {
648 jfrom = json_object_get(msg, "from");
649 jfrom = json_object_get(json_array_get(jfrom, 0), "email");
650 }
651 if (!jfrom) {
652 *set_err = json_pack("{s:s}", "type", "notPermittedFrom");
653 goto done;
654 }
655 const char *from = json_string_value(jfrom);
656 /* TODO If the address found from this is not allowed by the identity
657 * associated with this submission, the email property from the identity
658 * MUST be used instead. */
659 json_object_set_new(myenvelope, "mailFrom",
660 json_pack("{s:s}", "email", from));
661
662 /* Determine RCPT TO */
663 json_t *rcpts = json_pack("{}"); /* deduplicated set of recipients */
664 json_t *rcptTo = json_array(); /* envelope rcptTo value */
665 size_t i;
666 const char *s;
667 json_t *jval;
668 json_array_foreach(json_object_get(msg, "to"), i, jval) {
669 s = json_string_value(json_object_get(jval, "email"));
670 if (s) json_object_set(rcpts, s, json_true());
671 }
672 json_array_foreach(json_object_get(msg, "cc"), i, jval) {
673 s = json_string_value(json_object_get(jval, "email"));
674 if (s) json_object_set(rcpts, s, json_true());
675 }
676 json_array_foreach(json_object_get(msg, "bcc"), i, jval) {
677 s = json_string_value(json_object_get(jval, "email"));
678 if (s) json_object_set(rcpts, s, json_true());
679 }
680 json_object_foreach(rcpts, s, jval) {
681 json_array_append_new(rcptTo, json_pack("{s:s}", "email", s));
682 }
683 json_decref(rcpts);
684 json_object_set_new(myenvelope, "rcptTo", rcptTo);
685 }
686
687 /* Validate envelope */
688 if (!json_array_size(json_object_get(envelope, "rcptTo"))) {
689 *set_err = json_pack("{s:s}", "type", "noRecipients");
690 goto done;
691 }
692
693 /* Open the message file */
694 const char *fname;
695 r = msgrecord_get_fname(mr, &fname);
696 if (r) goto done;
697
698 fd_msg = open(fname, 0);
699 if (fd_msg == -1) {
700 syslog(LOG_ERR, "_email_submissioncreate: can't open %s: %m", fname);
701 r = IMAP_IOERROR;
702 goto done;
703 }
704
705 struct stat sbuf;
706 if (fstat(fd_msg, &sbuf) == -1) {
707 syslog(LOG_ERR, "_email_submissioncreate: can't fstat %s: %m", fname);
708 goto done;
709 }
710
711 buf_refresh_mmap(&buf, 1, fd_msg, fname, sbuf.st_size, mbox->name);
712 if (!buf_len(&buf)) {
713 syslog(LOG_ERR, "_email_submissioncreate: can't mmap %s: %m", fname);
714 r = IMAP_IOERROR;
715 goto done;
716 }
717
718 /* Fetch and set threadId */
719 char thread_id[JMAP_THREADID_SIZE];
720 bit64 cid;
721
722 r = msgrecord_get_cid(mr, &cid);
723 if (r) goto done;
724
725 jmap_set_threadid(cid, thread_id);
726 json_object_set_new(emailsubmission, "threadId", json_string(thread_id));
727
728 /* Close the message record and mailbox. There's a race
729 * with us still keeping the file descriptor to the
730 * message open. But we don't want to long-lock the
731 * mailbox while sending the mail over to a SMTP host */
732 msgrecord_unref(&mr);
733 jmap_closembox(req, &mbox);
734
735 if (!*sm) {
736 /* Open the SMTP connection */
737 r = smtpclient_open(sm);
738 if (r) goto done;
739 }
740 smtpclient_set_auth(*sm, req->userid);
741
742 /* Prepare envelope */
743 smtp_envelope_t smtpenv = SMTP_ENVELOPE_INITIALIZER;
744 jmap_emailsubmission_envelope_to_smtp(&smtpenv, envelope);
745
746 if (holduntil) {
747 /* Pre-flight the message */
748 smtpclient_set_size(*sm, buf_len(&buf));
749 r = smtpclient_sendprot(*sm, &smtpenv, NULL);
750 }
751 else {
752 /* Send message */
753 r = smtpclient_send(*sm, &smtpenv, &buf);
754 }
755 if (r) {
756 int i, max = 0;
757 json_t *invalid = NULL;
758 const char *desc = smtpclient_get_resp_text(*sm);
759
760 syslog(LOG_ERR, "jmap: can't create message submission: %s",
761 desc ? desc : error_message(r));
762
763 switch (r) {
764 case IMAP_MESSAGE_TOO_LARGE:
765 *set_err = json_pack("{s:s s:i}", "type", "tooLarge",
766 "maxSize", smtpclient_get_maxsize(*sm));
767 break;
768
769 case IMAP_MAILBOX_DISABLED:
770 for (i = 0; i < smtpenv.rcpts.count; i++) {
771 smtp_addr_t *addr = ptrarray_nth(&smtpenv.rcpts, i);
772 max += addr->completed;
773 }
774 *set_err = json_pack("{s:s s:i}", "type", "tooManyRecipients",
775 "maxRecipients", max);
776 break;
777
778 case IMAP_MAILBOX_NONEXISTENT:
779 invalid = json_array();
780 for (i = 0; i < smtpenv.rcpts.count; i++) {
781 smtp_addr_t *addr = ptrarray_nth(&smtpenv.rcpts, i);
782 if (!addr->completed) {
783 json_array_append_new(invalid, json_string(addr->addr));
784 }
785 }
786 *set_err = json_pack("{s:s s:o}", "type", "invalidRecipients",
787 "invalidRecipients", invalid);
788 break;
789
790 case IMAP_REMOTE_DENIED: {
791 char *err = NULL;
792 const char *p;
793
794 if (smtpclient_has_ext(*sm, "ENHANCEDSTATUSCODES")) {
795 p = strchr(desc, ' ');
796 if (p) {
797 desc = p+1;
798 while (*desc == ' ') desc++; /* trim leading whitespace */
799 }
800 }
801 if ((p = strstr(desc, "[jmapError:"))) {
802 p += 11;
803 const char *q = strchr(p, ']');
804 if (q) {
805 err = xstrndup(p, q - p);
806 desc = q+1;
807 while (*desc == ' ') desc++; /* trim leading whitespace */
808 }
809 }
810 if (!err) err = xstrdup("forbiddenToSend");
811 *set_err = json_pack("{s:s s:s}",
812 "type", err, "description", desc);
813 free(err);
814 break;
815 }
816
817 default:
818 *set_err = json_pack("{s:s s:s}", "type", "smtpProtocolError",
819 "description", desc);
820 break;
821 }
822 }
823 smtp_envelope_fini(&smtpenv);
824
825 if (r) goto done;
826
827 /* Replace any creation id with actual emailId */
828 json_object_set_new(emailsubmission, "emailId", json_string(msgid));
829
830 r = store_submission(submbox, &buf, holduntil,
831 emailsubmission, new_submission);
832
833 done:
834 if (r && *set_err == NULL) {
835 *set_err = jmap_server_error(r);
836 }
837 if (fd_msg != -1) close(fd_msg);
838 if (msg) json_decref(msg);
839 if (mr) msgrecord_unref(&mr);
840 if (mbox) jmap_closembox(req, &mbox);
841 if (myenvelope) json_decref(myenvelope);
842 free(mboxname);
843 buf_free(&buf);
844 }
845
msg_from_subid(struct mailbox * submbox,const char * id)846 static message_t *msg_from_subid(struct mailbox *submbox, const char *id)
847 {
848 message_t *msg = NULL;
849 uint32_t uid = 0;
850
851 if (id[0] == 'S' && id[1] != '-' && strlen(id) < JMAP_SUBID_SIZE) {
852 char *endptr = NULL;
853
854 uid = strtoul(id+1, &endptr, 10);
855
856 if (*endptr || errno == ERANGE || uid > UINT_MAX) uid = 0;
857 }
858
859 if (uid) {
860 struct index_record record;
861 int r = mailbox_find_index_record(submbox, uid, &record);
862
863 if (!r && record.uid && !(record.internal_flags & FLAG_INTERNAL_EXPUNGED)) {
864 msg = message_new_from_record(submbox, &record);
865 }
866 }
867
868 return msg;
869 }
870
fetch_submission(message_t * msg)871 static json_t *fetch_submission(message_t *msg)
872 {
873 struct buf buf = BUF_INITIALIZER;
874 json_t *sub = NULL;
875
876 int r = message_get_field(msg, JMAP_SUBMISSION_HDR,
877 MESSAGE_DECODED|MESSAGE_TRIM, &buf);
878
879 if (!r && buf_len(&buf)) {
880 json_error_t jerr;
881 sub = json_loadb(buf_base(&buf), buf_len(&buf),
882 JSON_DISABLE_EOF_CHECK, &jerr);
883 }
884 buf_free(&buf);
885
886 return sub;
887 }
888
_emailsubmission_update(struct mailbox * submbox,const char * id,json_t * emailsubmission,json_t ** set_err,char ** emailid)889 static void _emailsubmission_update(struct mailbox *submbox,
890 const char *id,
891 json_t *emailsubmission,
892 json_t **set_err,
893 char **emailid)
894 {
895 message_t *msg = msg_from_subid(submbox, id);
896 const struct index_record *record;
897 json_t *sub = NULL;
898 int r = 0;
899
900 if (!msg) {
901 /* Not a valid id */
902 *set_err = json_pack("{s:s}", "type", "notFound");
903 return;
904 }
905 record = msg_record(msg);
906
907 sub = fetch_submission(msg);
908 if (!sub) {
909 if (!r) r = IMAP_IOERROR;
910
911 *set_err = json_pack("{s:s, s:s}", "type", "serverFail", "description", error_message(r));
912 goto done;
913 }
914
915 *emailid = xstrdupnull(json_string_value(json_object_get(sub, "emailId")));
916
917 const char *arg;
918 json_t *val;
919 int do_cancel = 0;
920 json_object_foreach(emailsubmission, arg, val) {
921 /* Make sure values in update match */
922 if (!json_equal(val, json_object_get(sub, arg))) {
923 /* Check the values that /get adds to the object */
924 switch (json_typeof(val)) {
925 case JSON_STRING:
926 {
927 const char *strval = json_string_value(val);
928
929 if (!strcmp(arg, "id") && !strcmp(strval, id)) {
930 continue;
931 }
932 else if (!strcmp(arg, "sendAt")) {
933 time_t t = 0;
934 if (time_from_iso8601(strval, &t) == (int) strlen(strval) &&
935 t == record->internaldate) {
936 continue;
937 }
938 }
939 else if (!strcmp(arg, "undoStatus")) {
940 if (record->system_flags & FLAG_ANSWERED) {
941 if (!strcmp(strval, "final")) continue;
942
943 /* Already sent */
944 *set_err = json_pack("{s:s}", "type", "cannotUnsend");
945 }
946 else if (record->system_flags & FLAG_FLAGGED) {
947 if (!strcmp(strval, "canceled")) continue;
948 }
949 else if (!strcmp(strval, "pending")) {
950 continue;
951 }
952 else if (!strcmp(strval, "canceled")) {
953 do_cancel = 1;
954 continue;
955 }
956 }
957 break;
958 }
959
960 case JSON_NULL:
961 if (!strcmp(arg, "deliveryStatus")) continue;
962 break;
963
964 case JSON_ARRAY:
965 if (json_array_size(val) == 0 &&
966 (!strcmp(arg, "dsnBlobIds") ||
967 !strcmp(arg, "mdnBlobIds"))) {
968 continue;
969 }
970 break;
971
972 default:
973 break;
974 }
975
976 if (!*set_err)
977 *set_err = json_pack("{s:s}", "type", "invalidProperties");
978 break;
979 }
980 }
981 json_decref(sub);
982
983 if (*set_err) goto done;
984
985 if (do_cancel) {
986 struct index_record newrecord;
987
988 memcpy(&newrecord, record, sizeof(struct index_record));
989 newrecord.system_flags |= FLAG_FLAGGED;
990 if (config_getswitch(IMAPOPT_JMAPSUBMISSION_DELETEONSEND)) {
991 newrecord.system_flags |= FLAG_DELETED;
992 newrecord.internal_flags |= FLAG_INTERNAL_EXPUNGED;
993 }
994
995 r = mailbox_rewrite_index_record(submbox, &newrecord);
996 if (r) *set_err = json_pack("{s:s, s:s}", "type", "serverFail", "description", error_message(r));
997 }
998
999 done:
1000 message_unref(&msg);
1001 }
1002
_emailsubmission_destroy(struct mailbox * submbox,const char * id,json_t ** set_err,char ** emailid)1003 static void _emailsubmission_destroy(struct mailbox *submbox,
1004 const char *id,
1005 json_t **set_err,
1006 char **emailid)
1007 {
1008 message_t *msg = msg_from_subid(submbox, id);
1009 struct index_record newrecord;
1010 json_t *sub = NULL;
1011 int r = 0;
1012
1013 if (!msg) {
1014 /* Not a valid id */
1015 *set_err = json_pack("{s:s}", "type", "notFound");
1016 return;
1017 }
1018 const struct index_record *record = msg_record(msg);
1019
1020 sub = fetch_submission(msg);
1021 if (!sub) {
1022 if (!r) r = IMAP_IOERROR;
1023
1024 *set_err = json_pack("{s:s, s:s}", "type", "serverFail", "description", error_message(r));
1025 goto done;
1026 }
1027
1028 *emailid = xstrdupnull(json_string_value(json_object_get(sub, "emailId")));
1029
1030 memcpy(&newrecord, record, sizeof(struct index_record));
1031 newrecord.internal_flags |= FLAG_INTERNAL_EXPUNGED;
1032
1033 r = mailbox_rewrite_index_record(submbox, &newrecord);
1034 if (r) *set_err = json_pack("{s:s, s:s}", "type", "serverFail", "description", error_message(r));
1035
1036 done:
1037 json_decref(sub);
1038 message_unref(&msg);
1039 }
1040
getsubmission(struct jmap_get * get,const char * id,message_t * msg)1041 static int getsubmission(struct jmap_get *get,
1042 const char *id, message_t *msg)
1043 {
1044 json_t *sub = NULL;
1045 int r = 0;
1046
1047 sub = fetch_submission(msg);
1048 if (sub) {
1049 /* id */
1050 json_object_set_new(sub, "id", json_string(id));
1051
1052 /* identityId */
1053 if (!jmap_wantprop(get->props, "identityId")) {
1054 json_object_del(sub, "identityId");
1055 }
1056
1057 /* emailId */
1058 if (!jmap_wantprop(get->props, "emailId")) {
1059 json_object_del(sub, "emailId");
1060 }
1061
1062 /* threadId */
1063 if (!jmap_wantprop(get->props, "threadId")) {
1064 json_object_del(sub, "threadId");
1065 }
1066
1067 /* envelope */
1068 if (!jmap_wantprop(get->props, "envelope")) {
1069 json_object_del(sub, "envelope");
1070 }
1071
1072 /* senddAt */
1073 if (jmap_wantprop(get->props, "sendAt")) {
1074 char datestr[RFC3339_DATETIME_MAX];
1075 time_t t;
1076
1077 r = message_get_internaldate(msg, &t);
1078 if (r) goto done;
1079
1080 time_to_rfc3339(t, datestr, RFC3339_DATETIME_MAX);
1081 json_object_set_new(sub, "sendAt", json_string(datestr));
1082 }
1083
1084 /* undoStatus */
1085 if (jmap_wantprop(get->props, "undoStatus")) {
1086 uint32_t system_flags;
1087 const char *status = "pending";
1088
1089 r = message_get_systemflags(msg, &system_flags);
1090 if (r) goto done;
1091
1092 if (system_flags & FLAG_ANSWERED) {
1093 status = "final";
1094 }
1095 else if (system_flags & FLAG_FLAGGED) {
1096 status = "canceled";
1097 }
1098
1099 json_object_set_new(sub, "undoStatus", json_string(status));
1100 }
1101
1102 /* deliveryStatus */
1103 if (jmap_wantprop(get->props, "deliveryStatus")) {
1104 json_object_set_new(sub, "deliveryStatus", json_null());
1105 }
1106
1107 /* dsnBlobIds */
1108 if (jmap_wantprop(get->props, "dsnBlobIds")) {
1109 json_object_set_new(sub, "dsnBlobIds", json_array());
1110 }
1111
1112 /* mdnBlobIds */
1113 if (jmap_wantprop(get->props, "mdnBlobIds")) {
1114 json_object_set_new(sub, "mdnBlobIds", json_array());
1115 }
1116 }
1117
1118 done:
1119 if (!r && sub) {
1120 json_array_append_new(get->list, sub);
1121 }
1122 else {
1123 json_array_append_new(get->not_found, json_string(id));
1124
1125 if (sub) json_decref(sub);
1126
1127 if (r) {
1128 syslog(LOG_ERR,
1129 "jmap: EmailSubmission/get(%s): %s", id, error_message(r));
1130 }
1131 }
1132
1133 return r;
1134 }
1135
1136 static const jmap_property_t submission_props[] = {
1137 {
1138 "id",
1139 NULL,
1140 JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE | JMAP_PROP_ALWAYS_GET
1141 },
1142 {
1143 "identityId",
1144 NULL,
1145 JMAP_PROP_IMMUTABLE
1146 },
1147 {
1148 "emailId",
1149 NULL,
1150 JMAP_PROP_IMMUTABLE
1151 },
1152 {
1153 "threadId",
1154 NULL,
1155 JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
1156 },
1157 {
1158 "envelope",
1159 NULL,
1160 JMAP_PROP_IMMUTABLE
1161 },
1162 {
1163 "sendAt",
1164 NULL,
1165 JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE
1166 },
1167 {
1168 "undoStatus",
1169 NULL,
1170 JMAP_PROP_SERVER_SET
1171 },
1172 {
1173 "deliveryStatus",
1174 NULL,
1175 JMAP_PROP_SERVER_SET
1176 },
1177 {
1178 "dsnBlobIds",
1179 NULL,
1180 JMAP_PROP_SERVER_SET
1181 },
1182 {
1183 "mdnBlobIds",
1184 NULL,
1185 JMAP_PROP_SERVER_SET
1186 },
1187 { NULL, NULL, 0 }
1188 };
1189
jmap_emailsubmission_get(jmap_req_t * req)1190 static int jmap_emailsubmission_get(jmap_req_t *req)
1191 {
1192 struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
1193 struct jmap_get get;
1194 json_t *err = NULL;
1195 mbentry_t *mbentry = NULL;
1196 int created = 0;
1197 struct mailbox *mbox = NULL;
1198
1199 jmap_get_parse(req, &parser, submission_props, /*allow_null_ids*/1,
1200 NULL, NULL, &get, &err);
1201 if (err) {
1202 jmap_error(req, err);
1203 goto done;
1204 }
1205
1206 /* submission collection */
1207 int r = lookup_submission_collection(req->accountid, &mbentry);
1208 if (r == IMAP_MAILBOX_NONEXISTENT) {
1209 r = 0; // that's OK, we'll skip trying to open the mailbox
1210 }
1211 else if (r) {
1212 syslog(LOG_ERR,
1213 "jmap_emailsubmission_get: lookup_submission_collection(%s): %s",
1214 req->accountid, error_message(r));
1215 goto done;
1216 }
1217 else {
1218 r = jmap_openmbox(req, mbentry->name, &mbox, 0);
1219 }
1220 mboxlist_entry_free(&mbentry);
1221 if (r) goto done;
1222
1223 /* Does the client request specific events? */
1224 if (JNOTNULL(get.ids)) {
1225 size_t i;
1226 json_t *val;
1227
1228 json_array_foreach(get.ids, i, val) {
1229 const char *id = json_string_value(val);
1230 message_t *msg = mbox ? msg_from_subid(mbox, id) : NULL;
1231
1232 if (!msg) {
1233 /* Not a valid id */
1234 json_array_append_new(get.not_found, json_string(id));
1235 continue;
1236 }
1237
1238 r = getsubmission(&get, id, msg);
1239 message_unref(&msg);
1240 }
1241 }
1242 else if (mbox) {
1243 struct mailbox_iter *iter = mailbox_iter_init(mbox, 0, ITER_SKIP_EXPUNGED);
1244 const message_t *msg;
1245 while ((msg = mailbox_iter_step(iter))) {
1246 char id[JMAP_SUBID_SIZE];
1247 uint32_t uid;
1248
1249 r = message_get_uid((message_t *) msg, &uid);
1250 if (r) continue;
1251
1252 /* Create id from message UID, using 'S' prefix */
1253 sprintf(id, "S%u", uid);
1254 r = getsubmission(&get, id, (message_t *) msg);
1255 }
1256 mailbox_iter_done(&iter);
1257 }
1258
1259 if (mbox) jmap_closembox(req, &mbox);
1260
1261 /* Build response */
1262 json_t *jstate = jmap_getstate(req, MBTYPE_SUBMISSION, /*refresh*/ created);
1263 get.state = xstrdup(json_string_value(jstate));
1264 json_decref(jstate);
1265 jmap_ok(req, jmap_get_reply(&get));
1266
1267 done:
1268 jmap_parser_fini(&parser);
1269 jmap_get_fini(&get);
1270 return 0;
1271 }
1272
1273 struct submission_set_args {
1274 json_t *onSuccessUpdate;
1275 json_t *onSuccessDestroy;
1276 };
1277
_submission_setargs_parse(jmap_req_t * req,struct jmap_parser * parser,const char * key,json_t * arg,void * rock)1278 static int _submission_setargs_parse(jmap_req_t *req,
1279 struct jmap_parser *parser,
1280 const char *key,
1281 json_t *arg,
1282 void *rock)
1283 {
1284 struct submission_set_args *set = (struct submission_set_args *) rock;
1285 int r = 1;
1286
1287 if (!strcmp(key, "onSuccessUpdateEmail")) {
1288 // need urn:ietf:params:jmap:mail to update emails
1289 if (!jmap_is_using(req, JMAP_URN_MAIL)) return 0;
1290 if (json_is_object(arg)) {
1291 json_t *jval;
1292 const char *emailsubmission_id;
1293 json_object_foreach(arg, emailsubmission_id, jval) {
1294 if (!json_is_object(jval)) {
1295 jmap_parser_push(parser, "onSuccessUpdateEmail");
1296 jmap_parser_invalid(parser, emailsubmission_id);
1297 jmap_parser_pop(parser);
1298 }
1299 }
1300 set->onSuccessUpdate = arg;
1301 }
1302 else if (JNOTNULL(arg)) r = 0;
1303 }
1304
1305 else if (!strcmp(key, "onSuccessDestroyEmail") && JNOTNULL(arg)) {
1306 // need urn:ietf:params:jmap:mail to destroy emails
1307 if (!jmap_is_using(req, JMAP_URN_MAIL)) return 0;
1308 jmap_parse_strings(arg, parser, "onSuccessDestroyEmail");
1309 set->onSuccessDestroy = arg;
1310 }
1311
1312 else r = 0;
1313
1314 return r;
1315 }
1316
jmap_emailsubmission_set(jmap_req_t * req)1317 static int jmap_emailsubmission_set(jmap_req_t *req)
1318 {
1319 struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
1320 struct jmap_set set;
1321 struct submission_set_args sub_args = { NULL, NULL };
1322 json_t *err = NULL;
1323 struct mailbox *submbox = NULL;
1324 mbentry_t *mbentry = NULL;
1325 json_t *success_emailids = json_object();
1326
1327 /* Parse request */
1328 jmap_set_parse(req, &parser, submission_props,
1329 &_submission_setargs_parse, &sub_args,
1330 &set, &err);
1331 if (err) {
1332 jmap_error(req, err);
1333 goto done;
1334 }
1335
1336 /* Validate submissionIds in onSuccessXxxEmail */
1337 if (JNOTNULL(sub_args.onSuccessUpdate)) {
1338 const char *id;
1339 json_t *jemail;
1340
1341 jmap_parser_push(&parser, "onSuccessUpdateEmail");
1342 json_object_foreach(sub_args.onSuccessUpdate, id, jemail) {
1343 int found;
1344
1345 if (*id == '#') {
1346 found = json_object_get(set.create, id+1) != NULL;
1347 }
1348 else {
1349 found = json_object_get(set.update, id) != NULL;
1350 if (!found) found = json_array_find(set.destroy, id) >= 0;
1351 }
1352
1353 if (!found) jmap_parser_invalid(&parser, id);
1354 }
1355 jmap_parser_pop(&parser);
1356 }
1357
1358 if (JNOTNULL(sub_args.onSuccessDestroy)) {
1359 size_t i;
1360 json_t *jid;
1361
1362 jmap_parser_push(&parser, "onSuccessDestroyEmail");
1363 json_array_foreach(sub_args.onSuccessDestroy, i, jid) {
1364 const char *id = json_string_value(jid);
1365 int found;
1366
1367 if (*id == '#') {
1368 found = json_object_get(set.create, id+1) != NULL;
1369 }
1370 else {
1371 found = json_object_get(set.update, id) != NULL;
1372 if (!found) found = json_array_find(set.destroy, id) >= 0;
1373 }
1374
1375 if (!found) jmap_parser_invalid(&parser, id);
1376 }
1377 jmap_parser_pop(&parser);
1378 }
1379
1380 if (json_array_size(parser.invalid)) {
1381 err = json_pack("{s:s}", "type", "invalidProperties");
1382 json_object_set(err, "properties", parser.invalid);
1383 jmap_error(req, err);
1384 goto done;
1385 }
1386
1387 /* Process request */
1388
1389 int r = ensure_submission_collection(req->accountid, &mbentry, NULL);
1390 if (r) {
1391 syslog(LOG_ERR,
1392 "jmap_emailsubmission_set: ensure_submission_collection(%s): %s",
1393 req->accountid, error_message(r));
1394 goto done;
1395 }
1396
1397 r = jmap_openmbox(req, mbentry->name, &submbox, 1);
1398 assert(submbox);
1399 mboxlist_entry_free(&mbentry);
1400 if (r) goto done;
1401
1402 if (set.if_in_state) {
1403 /* TODO rewrite state function to use char* not json_t* */
1404 json_t *jstate = json_string(set.if_in_state);
1405 if (jmap_cmpstate(req, jstate, MBTYPE_SUBMISSION)) {
1406 jmap_error(req, json_pack("{s:s}", "type", "stateMismatch"));
1407 json_decref(jstate);
1408 goto done;
1409 }
1410 json_decref(jstate);
1411 set.old_state = xstrdup(set.if_in_state);
1412 }
1413 else {
1414 json_t *jstate = jmap_getstate(req, MBTYPE_SUBMISSION, /*refresh*/0);
1415 set.old_state = xstrdup(json_string_value(jstate));
1416 json_decref(jstate);
1417 }
1418
1419 /* create */
1420 json_t *jsubmission;
1421 const char *creation_id;
1422 smtpclient_t *sm = NULL;
1423 json_object_foreach(set.create, creation_id, jsubmission) {
1424 json_t *set_err = NULL;
1425 json_t *new_submission = NULL;
1426 char *emailid = NULL;
1427 _emailsubmission_create(req, submbox, jsubmission,
1428 &new_submission, &set_err, &sm, &emailid);
1429 if (set_err) {
1430 json_object_set_new(set.not_created, creation_id, set_err);
1431 free(emailid);
1432 continue;
1433 }
1434 const char *id = json_string_value(json_object_get(new_submission, "id"));
1435 json_object_set_new(set.created, creation_id, new_submission);
1436 json_object_set_new(success_emailids, id, json_string(emailid));
1437 free(emailid);
1438 }
1439 if (sm) smtpclient_close(&sm);
1440
1441 /* update */
1442 const char *id;
1443 json_object_foreach(set.update, id, jsubmission) {
1444 json_t *set_err = NULL;
1445 char *emailid = NULL;
1446 _emailsubmission_update(submbox, id, jsubmission, &set_err, &emailid);
1447 if (set_err) {
1448 json_object_set_new(set.not_updated, id, set_err);
1449 free(emailid);
1450 continue;
1451 }
1452 json_object_set_new(set.updated, id, json_pack("{s:s}", "id", id));
1453 json_object_set_new(success_emailids, id, json_string(emailid));
1454 free(emailid);
1455 }
1456
1457 /* destroy */
1458 size_t i;
1459 json_t *jsubmissionId;
1460 json_array_foreach(set.destroy, i, jsubmissionId) {
1461 const char *id = json_string_value(jsubmissionId);
1462 json_t *set_err = NULL;
1463 char *emailid = NULL;
1464 _emailsubmission_destroy(submbox, id, &set_err, &emailid);
1465 if (set_err) {
1466 json_object_set_new(set.not_destroyed, id, set_err);
1467 free(emailid);
1468 continue;
1469 }
1470 json_array_append_new(set.destroyed, json_string(id));
1471 json_object_set_new(success_emailids, id, json_string(emailid));
1472 free(emailid);
1473 }
1474
1475 /* force modseq to stable */
1476 if (submbox) mailbox_unlock_index(submbox, NULL);
1477
1478 // TODO refactor jmap_getstate to return a string, once
1479 // all code has been migrated to the new JMAP parser.
1480 json_t *jstate = jmap_getstate(req, MBTYPE_SUBMISSION, /*refresh*/1);
1481 set.new_state = xstrdup(json_string_value(jstate));
1482 json_decref(jstate);
1483
1484 jmap_ok(req, jmap_set_reply(&set));
1485
1486 /* Process onSuccessXxxEmail */
1487 if (JNOTNULL(sub_args.onSuccessUpdate) ||
1488 JNOTNULL(sub_args.onSuccessDestroy)) {
1489 json_t *subargs = json_object();
1490
1491 json_object_set_new(subargs, "accountId", json_string(req->accountid));
1492
1493 if (JNOTNULL(sub_args.onSuccessUpdate)) {
1494 json_t *updateEmails = json_object();
1495 const char *jid;
1496 json_t *jemail;
1497
1498 json_object_foreach(sub_args.onSuccessUpdate, jid, jemail) {
1499 const char *id = jid;
1500 if (*id == '#') {
1501 json_t *jsuccess = json_object_get(set.created, id+1);
1502 if (jsuccess)
1503 id = json_string_value(json_object_get(jsuccess, "id"));
1504 }
1505 const char *emailid = json_string_value(json_object_get(success_emailids, id));
1506 if (emailid) json_object_set(updateEmails, emailid, jemail);
1507 }
1508
1509 json_object_set_new(subargs, "update", updateEmails);
1510 }
1511
1512 if (JNOTNULL(sub_args.onSuccessDestroy)) {
1513 json_t *destroyEmails = json_array();
1514 size_t i;
1515 json_t *jid;
1516 json_array_foreach(sub_args.onSuccessDestroy, i, jid) {
1517 const char *id = json_string_value(jid);
1518 if (*id == '#') {
1519 json_t *jsuccess = json_object_get(set.created, id+1);
1520 if (jsuccess)
1521 id = json_string_value(json_object_get(jsuccess, "id"));
1522 }
1523 const char *emailid = json_string_value(json_object_get(success_emailids, id));
1524 if (emailid) json_array_append_new(destroyEmails, json_string(emailid));
1525 }
1526
1527 json_object_set_new(subargs, "destroy", destroyEmails);
1528 }
1529
1530 jmap_add_subreq(req, "Email/set", subargs, NULL);
1531 }
1532
1533 done:
1534 jmap_closembox(req, &submbox);
1535 jmap_parser_fini(&parser);
1536 jmap_set_fini(&set);
1537 json_decref(success_emailids);
1538 return 0;
1539 }
1540
jmap_emailsubmission_changes(jmap_req_t * req)1541 static int jmap_emailsubmission_changes(jmap_req_t *req)
1542 {
1543 struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
1544 struct jmap_changes changes;
1545 struct mailbox *mbox = NULL;
1546 mbentry_t *mbentry = NULL;
1547
1548 json_t *err = NULL;
1549 jmap_changes_parse(req, &parser, req->counters.submissiondeletedmodseq,
1550 NULL, NULL, &changes, &err);
1551 if (err) {
1552 jmap_error(req, err);
1553 return 0;
1554 }
1555
1556 int r = lookup_submission_collection(req->accountid, &mbentry);
1557 if (r == IMAP_MAILBOX_NONEXISTENT) {
1558 mboxlist_entry_free(&mbentry);
1559 r = 0;
1560 changes.new_modseq = jmap_highestmodseq(req, MBTYPE_SUBMISSION);
1561 jmap_ok(req, jmap_changes_reply(&changes));
1562 goto done;
1563 }
1564 if (r) {
1565 syslog(LOG_ERR,
1566 "jmap_emailsubmission_changes: lookup_submission_collection(%s): %s",
1567 req->accountid, error_message(r));
1568 goto done;
1569 }
1570
1571 r = jmap_openmbox(req, mbentry->name, &mbox, 0);
1572 mboxlist_entry_free(&mbentry);
1573 if (r) goto done;
1574
1575 struct mailbox_iter *iter = mailbox_iter_init(mbox, changes.since_modseq, 0);
1576 const message_t *msg;
1577 size_t changes_count = 0;
1578 modseq_t highest_modseq = 0;
1579 while ((msg = mailbox_iter_step(iter))) {
1580 char id[JMAP_SUBID_SIZE];
1581 const struct index_record *record = msg_record(msg);
1582
1583 /* Create id from message UID, using 'S' prefix */
1584 sprintf(id, "S%u", record->uid);
1585
1586 /* Skip any submissions created AND deleted since modseq */
1587 if ((record->internal_flags & FLAG_INTERNAL_EXPUNGED) &&
1588 record->createdmodseq > changes.since_modseq) continue;
1589
1590 /* Apply limit, if any */
1591 if (changes.max_changes && ++changes_count > changes.max_changes) {
1592 changes.has_more_changes = 1;
1593 break;
1594 }
1595
1596 /* Keep track of the highest modseq */
1597 if (highest_modseq < record->modseq) highest_modseq = record->modseq;
1598
1599 /* Add change to the proper array */
1600 if (record->internal_flags & FLAG_INTERNAL_EXPUNGED) {
1601 json_array_append_new(changes.destroyed, json_string(id));
1602 }
1603 else if (record->createdmodseq > changes.since_modseq) {
1604 json_array_append_new(changes.created, json_string(id));
1605 }
1606 else {
1607 json_array_append_new(changes.updated, json_string(id));
1608 }
1609 }
1610 mailbox_iter_done(&iter);
1611
1612 jmap_closembox(req, &mbox);
1613
1614 /* Set new state */
1615 // XXX - this is wrong! If we want to do this, we need to sort all the changes by
1616 // their modseq and then only send some of them. Otherwise consider the following:
1617 // UID=1 HMS=5
1618 // UID=3 HMS=15
1619 // UID=4 HMS=10
1620 // if we issued a query for changes since 6, max_changes 1 - we'd get back
1621 // has_more_changes: true, new_modseq 15, and we'd never see UID=4 as having changed.
1622 changes.new_modseq = changes.has_more_changes ?
1623 highest_modseq : jmap_highestmodseq(req, MBTYPE_SUBMISSION);
1624
1625 jmap_ok(req, jmap_changes_reply(&changes));
1626
1627 done:
1628 jmap_changes_fini(&changes);
1629 jmap_parser_fini(&parser);
1630 return 0;
1631 }
1632
_emailsubmission_filter_parse(jmap_req_t * req,struct jmap_parser * parser,json_t * filter,json_t * unsupported,void * rock,json_t ** err)1633 static void _emailsubmission_filter_parse(jmap_req_t *req __attribute__((unused)),
1634 struct jmap_parser *parser,
1635 json_t *filter,
1636 json_t *unsupported __attribute__((unused)),
1637 void *rock __attribute__((unused)),
1638 json_t **err __attribute__((unused)))
1639 {
1640 const char *field;
1641 json_t *arg;
1642
1643 json_object_foreach(filter, field, arg) {
1644 if (!strcmp(field, "emailIds") ||
1645 !strcmp(field, "identityIds") ||
1646 !strcmp(field, "threadIds")) {
1647 if (!json_is_array(arg)) {
1648 jmap_parser_invalid(parser, field);
1649 }
1650 else {
1651 jmap_parse_strings(arg, parser, field);
1652 }
1653 }
1654 else if (!strcmp(field, "undoStatus")) {
1655 if (!json_is_string(arg)) {
1656 jmap_parser_invalid(parser, field);
1657 }
1658 }
1659 else if (!strcmp(field, "before") ||
1660 !strcmp(field, "after")) {
1661 if (!json_is_utcdate(arg)) {
1662 jmap_parser_invalid(parser, field);
1663 }
1664 }
1665 else {
1666 jmap_parser_invalid(parser, field);
1667 }
1668 }
1669 }
1670
1671
_emailsubmission_comparator_parse(jmap_req_t * req,struct jmap_comparator * comp,void * rock,json_t ** err)1672 static int _emailsubmission_comparator_parse(jmap_req_t *req __attribute__((unused)),
1673 struct jmap_comparator *comp,
1674 void *rock __attribute__((unused)),
1675 json_t **err __attribute__((unused)))
1676 {
1677 if (comp->collation) {
1678 return 0;
1679 }
1680 if (!strcmp(comp->property, "emailId") ||
1681 !strcmp(comp->property, "threadId") ||
1682 !strcmp(comp->property, "sentAt")) {
1683 return 1;
1684 }
1685 return 0;
1686 }
1687
1688 #if (SIZEOF_TIME_T > 4)
1689 static time_t epoch = (time_t) LONG_MIN;
1690 static time_t eternity = (time_t) LONG_MAX;
1691 #else
1692 static time_t epoch = (time_t) INT_MIN;
1693 static time_t eternity = (time_t) INT_MAX;
1694 #endif
1695
1696 typedef struct submission_filter {
1697 strarray_t *identityIds;
1698 strarray_t *emailIds;
1699 strarray_t *threadIds;
1700 const char *undoStatus;
1701 time_t before;
1702 time_t after;
1703 } submission_filter;
1704
1705 /* Parse the JMAP EmailSubmission FilterCondition in arg.
1706 * Report any invalid properties in invalid, prefixed by prefix.
1707 * Return NULL on error. */
submission_filter_build(json_t * arg)1708 static void *submission_filter_build(json_t *arg)
1709 {
1710 submission_filter *f =
1711 (submission_filter *) xzmalloc(sizeof(struct submission_filter));
1712
1713 f->before = eternity;
1714 f->after = epoch;
1715
1716 /* identityIds */
1717 json_t *identityIds = json_object_get(arg, "identityIds");
1718 if (identityIds) {
1719 f->identityIds = strarray_new();
1720 size_t i;
1721 json_t *val;
1722 json_array_foreach(identityIds, i, val) {
1723 const char *id;
1724 if (json_unpack(val, "s", &id) != -1) {
1725 strarray_append(f->identityIds, id);
1726 }
1727 }
1728 }
1729
1730 /* emailIds */
1731 json_t *emailIds = json_object_get(arg, "emailIds");
1732 if (emailIds) {
1733 f->emailIds = strarray_new();
1734 size_t i;
1735 json_t *val;
1736 json_array_foreach(emailIds, i, val) {
1737 const char *id;
1738 if (json_unpack(val, "s", &id) != -1) {
1739 strarray_append(f->emailIds, id);
1740 }
1741 }
1742 }
1743
1744 /* threadIds */
1745 json_t *threadIds = json_object_get(arg, "threadIds");
1746 if (threadIds) {
1747 f->threadIds = strarray_new();
1748 size_t i;
1749 json_t *val;
1750 json_array_foreach(threadIds, i, val) {
1751 const char *id;
1752 if (json_unpack(val, "s", &id) != -1) {
1753 strarray_append(f->threadIds, id);
1754 }
1755 }
1756 }
1757
1758 /* undoStatus */
1759 if (JNOTNULL(json_object_get(arg, "undoStatus"))) {
1760 jmap_readprop(arg, "undoStatus", 0, NULL, "s", &f->undoStatus);
1761 }
1762
1763 /* before */
1764 if (JNOTNULL(json_object_get(arg, "before"))) {
1765 const char *utcDate;
1766 jmap_readprop(arg, "before", 0, NULL, "s", &utcDate);
1767 time_from_iso8601(utcDate, &f->before);
1768 }
1769
1770 /* after */
1771 if (JNOTNULL(json_object_get(arg, "after"))) {
1772 const char *utcDate;
1773 jmap_readprop(arg, "after", 0, NULL, "s", &utcDate);
1774 time_from_iso8601(utcDate, &f->after);
1775 }
1776
1777 return f;
1778 }
1779
1780 typedef struct submission_filter_rock {
1781 const message_t *msg;
1782 const char *emailId;
1783 const char *threadId;
1784 json_t *submission;
1785 } submission_filter_rock;
1786
1787 /* Match the submission in rock against filter. */
submission_filter_match(void * vf,void * rock)1788 static int submission_filter_match(void *vf, void *rock)
1789 {
1790 submission_filter *f = (submission_filter *) vf;
1791 submission_filter_rock *sfrock = (submission_filter_rock*) rock;
1792 const struct index_record *record = msg_record(sfrock->msg);
1793
1794 /* before */
1795 if (record->internaldate >= f->before) return 0;
1796
1797 /* after */
1798 if (record->internaldate < f->after) return 0;
1799
1800 /* undoStatus */
1801 if (f->undoStatus) {
1802 if (record->system_flags & FLAG_ANSWERED) {
1803 if (strcmp(f->undoStatus, "final")) return 0;
1804 }
1805 else if (record->system_flags & FLAG_FLAGGED) {
1806 if (strcmp(f->undoStatus, "canceled")) return 0;
1807 }
1808 else {
1809 if (strcmp(f->undoStatus, "pending")) return 0;
1810 }
1811 }
1812
1813 /* identityIds / emailIds / ThreadIds */
1814 if (f->identityIds || f->emailIds || f->threadIds) {
1815 sfrock->submission = fetch_submission((message_t *) sfrock->msg);
1816
1817 if (!sfrock->submission) return 0;
1818
1819 if (f->identityIds) {
1820 const char *identityId =
1821 json_string_value(json_object_get(sfrock->submission,
1822 "identityId"));
1823
1824 if (strarray_find(f->identityIds, identityId, 0) == -1) return 0;
1825 }
1826 if (f->emailIds) {
1827 sfrock->emailId =
1828 json_string_value(json_object_get(sfrock->submission,
1829 "emailId"));
1830
1831 if (strarray_find(f->emailIds, sfrock->emailId, 0) == -1) return 0;
1832 }
1833 if (f->threadIds) {
1834 sfrock->threadId =
1835 json_string_value(json_object_get(sfrock->submission,
1836 "threadId"));
1837
1838 if (strarray_find(f->threadIds, sfrock->threadId, 0) == -1) return 0;
1839 }
1840 }
1841
1842 /* All matched. */
1843 return 1;
1844 }
1845
1846 /* Free the memory allocated by this submission filter. */
submission_filter_free(void * vf)1847 static void submission_filter_free(void *vf)
1848 {
1849 submission_filter *f = (submission_filter*) vf;
1850 if (f->identityIds) strarray_free(f->identityIds);
1851 if (f->emailIds) strarray_free(f->emailIds);
1852 if (f->threadIds) strarray_free(f->threadIds);
1853 free(f);
1854 }
1855
sub_buildsort(json_t * sort,int * need_submission)1856 static struct sortcrit *sub_buildsort(json_t *sort, int *need_submission)
1857 {
1858 json_t *jcomp;
1859 size_t i;
1860 struct sortcrit *sortcrit;
1861
1862 *need_submission = 0;
1863
1864 sortcrit = xzmalloc((json_array_size(sort) + 1) * sizeof(struct sortcrit));
1865
1866 json_array_foreach(sort, i, jcomp) {
1867 const char *prop = json_string_value(json_object_get(jcomp, "property"));
1868
1869 if (json_object_get(jcomp, "isAscending") == json_false()) {
1870 sortcrit[i].flags |= SORT_REVERSE;
1871 }
1872
1873 /* Note: add any new sort criteria also to is_supported_msglist_sort */
1874
1875 if (!strcmp(prop, "emailId")) {
1876 sortcrit[i].key = SORT_EMAILID;
1877 *need_submission = 1;
1878 }
1879 else if (!strcmp(prop, "threadId")) {
1880 sortcrit[i].key = SORT_THREADID;
1881 *need_submission = 1;
1882 }
1883 else if (!strcmp(prop, "sentAt")) {
1884 sortcrit[i].key = SORT_ARRIVAL;
1885 }
1886 }
1887
1888 i = json_array_size(sort);
1889 sortcrit[i].key = SORT_UID;
1890
1891 return sortcrit;
1892 }
1893
1894 struct sub_match {
1895 char id[JMAP_SUBID_SIZE];
1896 uint32_t uid;
1897 time_t sentAt;
1898 const char *emailId;
1899 const char *threadId;
1900 json_t *submission;
1901 struct sortcrit *sortcrit;
1902 };
1903
1904 /*
1905 * Comparison function for sorting EmailSubmissions.
1906 */
sub_sort_compare(const void ** vp1,const void ** vp2)1907 static int sub_sort_compare(const void **vp1, const void **vp2)
1908 {
1909 struct sub_match *m1 = (struct sub_match *) *vp1;
1910 struct sub_match *m2 = (struct sub_match *) *vp2;
1911 const struct sortcrit *sortcrit = m1->sortcrit;
1912 int reverse, ret = 0, i = 0;
1913
1914 for (i = 0; !ret && sortcrit[i].key != SORT_UID; i++) {
1915 /* determine sort order from reverse flag bit */
1916 reverse = sortcrit[i].flags & SORT_REVERSE;
1917
1918 switch (sortcrit[i].key) {
1919 case SORT_ARRIVAL:
1920 ret = m1->sentAt - m2->sentAt;
1921 break;
1922 case SORT_EMAILID:
1923 if (!m1->emailId) {
1924 m1->emailId =
1925 json_string_value(json_object_get(m1->submission,
1926 "emailId"));
1927 }
1928 if (!m2->emailId) {
1929 m2->emailId =
1930 json_string_value(json_object_get(m2->submission,
1931 "emailId"));
1932 }
1933 ret = strcmpsafe(m1->emailId, m2->emailId);
1934 break;
1935 case SORT_THREADID:
1936 if (!m1->threadId) {
1937 m1->threadId =
1938 json_string_value(json_object_get(m1->submission,
1939 "threadId"));
1940 }
1941 if (!m2->threadId) {
1942 m2->threadId =
1943 json_string_value(json_object_get(m2->submission,
1944 "threadId"));
1945 }
1946 ret = strcmpsafe(m1->threadId, m2->threadId);
1947 break;
1948 }
1949 }
1950
1951 // tiebreaker is UID
1952 if (!ret) return (m1->uid - m2->uid);
1953
1954 return (reverse ? -ret : ret);
1955 }
1956
jmap_emailsubmission_query(jmap_req_t * req)1957 static int jmap_emailsubmission_query(jmap_req_t *req)
1958 {
1959 struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
1960 struct jmap_query query;
1961 struct mailbox *mbox = NULL;
1962 mbentry_t *mbentry = NULL;
1963 int created = 0;
1964 jmap_filter *parsed_filter = NULL;
1965 struct sortcrit *sortcrit = NULL;
1966
1967 /* Parse request */
1968 json_t *err = NULL;
1969 jmap_query_parse(req, &parser, NULL, NULL,
1970 _emailsubmission_filter_parse, NULL,
1971 _emailsubmission_comparator_parse, NULL,
1972 &query, &err);
1973 if (err) {
1974 jmap_error(req, err);
1975 goto done;
1976 }
1977
1978 int r = lookup_submission_collection(req->accountid, &mbentry);
1979 if (r == IMAP_MAILBOX_NONEXISTENT) {
1980 mboxlist_entry_free(&mbentry);
1981 r = 0;
1982 /* Build response */
1983 json_t *jstate = jmap_getstate(req, MBTYPE_SUBMISSION, /*refresh*/ created);
1984 query.query_state = xstrdup(json_string_value(jstate));
1985 json_decref(jstate);
1986 query.result_position = 0;
1987 query.can_calculate_changes = 0;
1988 jmap_ok(req, jmap_query_reply(&query));
1989 goto done;
1990 }
1991 if (r) {
1992 syslog(LOG_ERR,
1993 "jmap_emailsubmission_changes: lookup_submission_collection(%s): %s",
1994 req->accountid, error_message(r));
1995 goto done;
1996 }
1997
1998 r = jmap_openmbox(req, mbentry->name, &mbox, 0);
1999 mboxlist_entry_free(&mbentry);
2000 if (r) goto done;
2001
2002 /* Build filter */
2003 json_t *filter = json_object_get(req->args, "filter");
2004 if (JNOTNULL(filter)) {
2005 parsed_filter = jmap_buildfilter(filter, submission_filter_build);
2006 }
2007
2008 /* Build sortcrit */
2009 int need_submission = 0;
2010 json_t *sort = json_object_get(req->args, "sort");
2011 if (JNOTNULL(sort)) {
2012 sortcrit = sub_buildsort(sort, &need_submission);
2013 }
2014
2015 ptrarray_t matches = PTRARRAY_INITIALIZER;
2016 struct sub_match *anchor = NULL;
2017 struct mailbox_iter *iter = mailbox_iter_init(mbox, 0, ITER_SKIP_EXPUNGED);
2018 const message_t *msg;
2019 while ((msg = mailbox_iter_step(iter))) {
2020 const struct index_record *record = msg_record(msg);
2021 submission_filter_rock sfrock = { msg, NULL, NULL, NULL };
2022
2023 if (query.filter) {
2024 int match = jmap_filter_match(parsed_filter,
2025 &submission_filter_match, &sfrock);
2026 if (!match) {
2027 if (sfrock.submission) json_decref(sfrock.submission);
2028 continue;
2029 }
2030 }
2031
2032 /* Add record of the match to our array */
2033 struct sub_match *match = xmalloc(sizeof(struct sub_match));
2034
2035 /* Create id from message UID, using 'S' prefix */
2036 sprintf(match->id, "S%u", record->uid);
2037 match->uid = record->uid;
2038 match->sentAt = record->internaldate;
2039 match->emailId = sfrock.emailId;
2040 match->threadId = sfrock.threadId;
2041 match->submission = sfrock.submission;
2042 if (!match->submission && need_submission)
2043 match->submission = fetch_submission((message_t *) msg);
2044 match->sortcrit = sortcrit;
2045 ptrarray_append(&matches, match);
2046
2047 if (query.anchor && !strcmp(query.anchor, match->id)) {
2048 /* Mark record corresponding to anchor */
2049 anchor = match;
2050 }
2051
2052 query.total++;
2053 }
2054 mailbox_iter_done(&iter);
2055
2056 jmap_closembox(req, &mbox);
2057
2058 /* Sort results */
2059 if (sortcrit) {
2060 ptrarray_sort(&matches, &sub_sort_compare);
2061 }
2062
2063 /* Process results */
2064 if (query.anchor) {
2065 query.position = ptrarray_find(&matches, anchor, 0);
2066 if (query.position < 0) {
2067 query.position = query.total;
2068 }
2069 else {
2070 query.position += query.anchor_offset;
2071 }
2072 }
2073 else if (query.position < 0) {
2074 query.position += query.total;
2075 }
2076 if (query.position < 0) query.position = 0;
2077
2078 size_t i;
2079 for (i = 0; i < query.total; i++) {
2080 struct sub_match *match = ptrarray_nth(&matches, i);
2081
2082 /* Apply position and limit */
2083 if (i >= (size_t) query.position &&
2084 (!query.limit || query.limit > json_array_size(query.ids))) {
2085 /* Add the submission identifier */
2086 json_array_append_new(query.ids, json_string(match->id));
2087 }
2088
2089 json_decref(match->submission);
2090 free(match);
2091 }
2092 ptrarray_fini(&matches);
2093 free(sortcrit);
2094
2095 /* Build response */
2096 json_t *jstate = jmap_getstate(req, MBTYPE_SUBMISSION, /*refresh*/ created);
2097 query.query_state = xstrdup(json_string_value(jstate));
2098 json_decref(jstate);
2099 query.result_position = query.position;
2100 query.can_calculate_changes = 0;
2101 jmap_ok(req, jmap_query_reply(&query));
2102
2103 done:
2104 jmap_parser_fini(&parser);
2105 jmap_query_fini(&query);
2106 if (parsed_filter) jmap_filter_free(parsed_filter, submission_filter_free);
2107 return 0;
2108 }
2109
jmap_emailsubmission_querychanges(jmap_req_t * req)2110 static int jmap_emailsubmission_querychanges(jmap_req_t *req)
2111 {
2112 struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
2113 struct jmap_querychanges query;
2114
2115 /* Parse arguments */
2116 json_t *err = NULL;
2117 jmap_querychanges_parse(req, &parser, NULL, NULL,
2118 _emailsubmission_filter_parse, NULL,
2119 _emailsubmission_comparator_parse, NULL,
2120 &query, &err);
2121 if (err) {
2122 jmap_error(req, err);
2123 goto done;
2124 }
2125
2126 /* Refuse all attempts to calculcate list updates */
2127 jmap_error(req, json_pack("{s:s}", "type", "cannotCalculateChanges"));
2128
2129 done:
2130 jmap_querychanges_fini(&query);
2131 jmap_parser_fini(&parser);
2132 return 0;
2133
2134 }
2135
2136 /* Identity/get method */
2137 static const jmap_property_t identity_props[] = {
2138 {
2139 "id",
2140 NULL,
2141 JMAP_PROP_SERVER_SET | JMAP_PROP_IMMUTABLE | JMAP_PROP_ALWAYS_GET
2142 },
2143 {
2144 "name",
2145 NULL,
2146 0
2147 },
2148 {
2149 "email",
2150 NULL,
2151 JMAP_PROP_IMMUTABLE
2152 },
2153 {
2154 "replyTo",
2155 NULL,
2156 0
2157 },
2158 {
2159 "bcc",
2160 NULL,
2161 0
2162 },
2163 {
2164 "textSignature",
2165 NULL,
2166 0
2167 },
2168 {
2169 "htmlSignature",
2170 NULL,
2171 0
2172 },
2173 {
2174 "mayDelete",
2175 NULL,
2176 JMAP_PROP_SERVER_SET
2177 },
2178
2179 /* FM extensions (do ALL of these get through to Cyrus?) */
2180 {
2181 "displayName",
2182 JMAP_MAIL_EXTENSION,
2183 0
2184 },
2185 {
2186 "addBccOnSMTP",
2187 JMAP_MAIL_EXTENSION,
2188 0
2189 },
2190 {
2191 "saveSentToMailboxId",
2192 JMAP_MAIL_EXTENSION,
2193 0
2194 },
2195 {
2196 "saveOnSMTP",
2197 JMAP_MAIL_EXTENSION,
2198 0
2199 },
2200 {
2201 "useForAutoReply",
2202 JMAP_MAIL_EXTENSION,
2203 0
2204 },
2205 {
2206 "isAutoConfigured",
2207 JMAP_MAIL_EXTENSION,
2208 0
2209 },
2210 {
2211 "enableExternalSMTP",
2212 JMAP_MAIL_EXTENSION,
2213 0
2214 },
2215 {
2216 "smtpServer",
2217 JMAP_MAIL_EXTENSION,
2218 0
2219 },
2220 {
2221 "smtpPort",
2222 JMAP_MAIL_EXTENSION,
2223 0
2224 },
2225 {
2226 "smtpSSL",
2227 JMAP_MAIL_EXTENSION,
2228 0
2229 },
2230 {
2231 "smtpUser",
2232 JMAP_MAIL_EXTENSION,
2233 0
2234 },
2235 {
2236 "smtpPassword",
2237 JMAP_MAIL_EXTENSION,
2238 0
2239 },
2240 {
2241 "smtpRemoteService",
2242 JMAP_MAIL_EXTENSION,
2243 0
2244 },
2245 {
2246 "popLinkId",
2247 JMAP_MAIL_EXTENSION,
2248 0
2249 },
2250
2251 { NULL, NULL, 0 }
2252 };
2253
jmap_identity_get(jmap_req_t * req)2254 static int jmap_identity_get(jmap_req_t *req)
2255 {
2256 struct jmap_parser parser = JMAP_PARSER_INITIALIZER;
2257 struct jmap_get get;
2258 json_t *err = NULL;
2259
2260 /* Parse request */
2261 jmap_get_parse(req, &parser, identity_props, /*allow_null_ids*/1,
2262 NULL, NULL, &get, &err);
2263 if (err) {
2264 jmap_error(req, err);
2265 goto done;
2266 }
2267
2268 /* Build response */
2269 json_t *me = json_pack("{s:s}", "id", req->userid);
2270 if (jmap_wantprop(get.props, "name")) {
2271 json_object_set_new(me, "name", json_string(""));
2272 }
2273 if (jmap_wantprop(get.props, "email")) {
2274 json_object_set_new(me, "email",
2275 json_string(strchr(req->userid, '@') ? req->userid : ""));
2276 }
2277
2278 if (jmap_wantprop(get.props, "mayDelete")) {
2279 json_object_set_new(me, "mayDelete", json_false());
2280 }
2281 if (json_array_size(get.ids)) {
2282 size_t i;
2283 json_t *val;
2284 json_array_foreach(get.ids, i, val) {
2285 if (strcmp(json_string_value(val), req->userid)) {
2286 json_array_append(get.not_found, val);
2287 }
2288 else {
2289 json_array_append(get.list, me);
2290 }
2291 }
2292 } else if (!JNOTNULL(get.ids)) {
2293 json_array_append(get.list, me);
2294 }
2295 json_decref(me);
2296
2297 /* Reply */
2298 get.state = xstrdup("0");
2299 jmap_ok(req, jmap_get_reply(&get));
2300
2301 done:
2302 jmap_parser_fini(&parser);
2303 jmap_get_fini(&get);
2304 return 0;
2305 }
2306