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