* MSG4 (and CtdlOutputMsg() as well) now accepts an optional MIME part
[citadel.git] / citadel / imap_fetch.c
1 /*
2  * $Id$
3  *
4  * Implements the FETCH command in IMAP.
5  * This is a good example of the protocol's gratuitous complexity.
6  *
7  */
8
9
10 #include "sysdep.h"
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <stdio.h>
14 #include <fcntl.h>
15 #include <signal.h>
16 #include <pwd.h>
17 #include <errno.h>
18 #include <sys/types.h>
19
20 #if TIME_WITH_SYS_TIME
21 # include <sys/time.h>
22 # include <time.h>
23 #else
24 # if HAVE_SYS_TIME_H
25 #  include <sys/time.h>
26 # else
27 #  include <time.h>
28 # endif
29 #endif
30
31 #include <sys/wait.h>
32 #include <ctype.h>
33 #include <string.h>
34 #include <limits.h>
35 #include "citadel.h"
36 #include "server.h"
37 #include "sysdep_decls.h"
38 #include "citserver.h"
39 #include "support.h"
40 #include "config.h"
41 #include "serv_extensions.h"
42 #include "room_ops.h"
43 #include "user_ops.h"
44 #include "policy.h"
45 #include "database.h"
46 #include "msgbase.h"
47 #include "tools.h"
48 #include "internet_addressing.h"
49 #include "mime_parser.h"
50 #include "serv_imap.h"
51 #include "imap_tools.h"
52 #include "imap_fetch.h"
53 #include "genstamp.h"
54
55
56
57 /*
58  * Individual field functions for imap_do_fetch_msg() ...
59  */
60
61 void imap_fetch_uid(int seq) {
62         cprintf("UID %ld", IMAP->msgids[seq-1]);
63 }
64
65 void imap_fetch_flags(int seq) {
66         int num_flags_printed = 0;
67         cprintf("FLAGS (");
68         if (IMAP->flags[seq] & IMAP_DELETED) {
69                 if (num_flags_printed > 0) cprintf(" ");
70                 cprintf("\\Deleted");
71                 ++num_flags_printed;
72         }
73         if (IMAP->flags[seq] & IMAP_SEEN) {
74                 if (num_flags_printed > 0) cprintf(" ");
75                 cprintf("\\Seen");
76                 ++num_flags_printed;
77         }
78         if (IMAP->flags[seq] & IMAP_ANSWERED) {
79                 if (num_flags_printed > 0) cprintf(" ");
80                 cprintf("\\Answered");
81                 ++num_flags_printed;
82         }
83         if (IMAP->flags[seq] & IMAP_RECENT) {
84                 if (num_flags_printed > 0) cprintf(" ");
85                 cprintf("\\Recent");
86                 ++num_flags_printed;
87         }
88         cprintf(")");
89 }
90
91 void imap_fetch_internaldate(struct CtdlMessage *msg) {
92         char buf[SIZ];
93         time_t msgdate;
94
95         if (!msg) return;
96         if (msg->cm_fields['T'] != NULL) {
97                 msgdate = atol(msg->cm_fields['T']);
98         }
99         else {
100                 msgdate = time(NULL);
101         }
102
103         datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
104         cprintf("INTERNALDATE \"%s\"", buf);
105 }
106
107
108 /*
109  * Fetch RFC822-formatted messages.
110  *
111  * 'whichfmt' should be set to one of:
112  *      "RFC822"        entire message
113  *      "RFC822.HEADER" headers only (with trailing blank line)
114  *      "RFC822.SIZE"   size of translated message
115  *      "RFC822.TEXT"   body only (without leading blank line)
116  */
117 void imap_fetch_rfc822(long msgnum, char *whichfmt) {
118         char buf[SIZ];
119         char *ptr = NULL;
120         size_t headers_size, text_size, total_size;
121         size_t bytes_to_send = 0;
122         struct MetaData smi;
123         int need_to_rewrite_metadata = 0;
124         int need_body = 0;
125
126         /* Determine whether this particular fetch operation requires
127          * us to fetch the message body from disk.  If not, we can save
128          * on some disk operations...
129          */
130         if ( (!strcasecmp(whichfmt, "RFC822"))
131            || (!strcasecmp(whichfmt, "RFC822.TEXT")) ) {
132                 need_body = 1;
133         }
134
135         /* If this is an RFC822.SIZE fetch, first look in the message's
136          * metadata record to see if we've saved that information.
137          */
138         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
139                 GetMetaData(&smi, msgnum);
140                 if (smi.meta_rfc822_length > 0L) {
141                         cprintf("RFC822.SIZE %ld", smi.meta_rfc822_length);
142                         return;
143                 }
144                 need_to_rewrite_metadata = 1;
145                 need_body = 1;
146         }
147         
148         /* Cache the most recent RFC822 FETCH because some clients like to
149          * fetch in pieces, and we don't want to have to go back to the
150          * message store for each piece.  We also burn the cache if the
151          * client requests something that involves reading the message
152          * body, but we haven't fetched the body yet.
153          */
154         if ((IMAP->cached_rfc822_data != NULL)
155            && (IMAP->cached_rfc822_msgnum == msgnum)
156            && (IMAP->cached_rfc822_withbody || (!need_body)) ) {
157                 /* Good to go! */
158         }
159         else if (IMAP->cached_rfc822_data != NULL) {
160                 /* Some other message is cached -- free it */
161                 free(IMAP->cached_rfc822_data);
162                 IMAP->cached_rfc822_data = NULL;
163                 IMAP->cached_rfc822_msgnum = (-1);
164                 IMAP->cached_rfc822_len = 0;
165         }
166
167         /* At this point, we now can fetch and convert the message iff it's not
168          * the one we had cached.
169          */
170         if (IMAP->cached_rfc822_data == NULL) {
171                 /*
172                  * Load the message into memory for translation & measurement
173                  */
174                 CC->redirect_buffer = malloc(SIZ);
175                 CC->redirect_len = 0;
176                 CC->redirect_alloc = SIZ;
177                 CtdlOutputMsg(msgnum, MT_RFC822,
178                         (need_body ? HEADERS_ALL : HEADERS_ONLY),
179                         0, 1, NULL);
180                 if (!need_body) cprintf("\r\n");        /* extra trailing newline */
181                 IMAP->cached_rfc822_data = CC->redirect_buffer;
182                 IMAP->cached_rfc822_len = CC->redirect_len;
183                 IMAP->cached_rfc822_msgnum = msgnum;
184                 IMAP->cached_rfc822_withbody = need_body;
185                 CC->redirect_buffer = NULL;
186                 CC->redirect_len = 0;
187                 CC->redirect_alloc = 0;
188                 if ( (need_to_rewrite_metadata) && (IMAP->cached_rfc822_len > 0) ) {
189                         smi.meta_rfc822_length = (long)IMAP->cached_rfc822_len;
190                         PutMetaData(&smi);
191                 }
192         }
193
194         /*
195          * Now figure out where the headers/text break is.  IMAP considers the
196          * intervening blank line to be part of the headers, not the text.
197          */
198         headers_size = 0;
199         text_size = 0;
200         total_size = 0;
201
202         if (need_body) {
203                 ptr = IMAP->cached_rfc822_data;
204                 do {
205                         ptr = memreadline(ptr, buf, sizeof buf);
206                         if (*ptr != 0) {
207                                 striplt(buf);
208                                 if (strlen(buf) == 0) {
209                                         headers_size = ptr - IMAP->cached_rfc822_data;
210                                 }
211                         }
212                 } while ( (headers_size == 0) && (*ptr != 0) );
213
214                 total_size = IMAP->cached_rfc822_len;
215                 text_size = total_size - headers_size;
216         }
217         else {
218                 headers_size = IMAP->cached_rfc822_len;
219                 total_size = IMAP->cached_rfc822_len;
220                 text_size = 0;
221         }
222
223         lprintf(CTDL_DEBUG, "RFC822: headers=%d, text=%d, total=%d\n",
224                 headers_size, text_size, total_size);
225
226         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
227                 cprintf("RFC822.SIZE %d", total_size);
228                 return;
229         }
230
231         else if (!strcasecmp(whichfmt, "RFC822")) {
232                 ptr = IMAP->cached_rfc822_data;
233                 bytes_to_send = total_size;
234         }
235
236         else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
237                 ptr = IMAP->cached_rfc822_data;
238                 bytes_to_send = headers_size;
239         }
240
241         else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
242                 ptr = &IMAP->cached_rfc822_data[headers_size];
243                 bytes_to_send = text_size;
244         }
245
246         cprintf("%s {%d}\r\n", whichfmt, bytes_to_send);
247         client_write(ptr, bytes_to_send);
248 }
249
250
251
252 /*
253  * Load a specific part of a message into the temp file to be output to a
254  * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
255  * but we still can't handle "2.HEADER" (which might not be a problem, because
256  * we currently don't have the ability to break out nested RFC822's anyway).
257  *
258  * Note: mime_parser() was called with dont_decode set to 1, so we have the
259  * luxury of simply spewing without having to re-encode.
260  */
261 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
262                     void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
263                     void *cbuserdata)
264 {
265         char mbuf2[SIZ];
266         char *desired_section;
267
268         desired_section = (char *)cbuserdata;
269
270         if (!strcasecmp(partnum, desired_section)) {
271                 client_write(content, length);
272         }
273
274         snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
275
276         if (!strcasecmp(desired_section, mbuf2)) {
277                 cprintf("Content-type: %s", cbtype);
278                 if (strlen(cbcharset) > 0)
279                         cprintf("; charset=\"%s\"", cbcharset);
280                 if (strlen(name) > 0)
281                         cprintf("; name=\"%s\"", name);
282                 cprintf("\r\n");
283                 if (strlen(encoding) > 0)
284                         cprintf("Content-Transfer-Encoding: %s\r\n", encoding);
285                 if (strlen(encoding) > 0) {
286                         cprintf("Content-Disposition: %s", disp);
287                         if (strlen(filename) > 0) {
288                                 cprintf("; filename=\"%s\"", filename);
289                         }
290                         cprintf("\r\n");
291                 }
292                 cprintf("Content-Length: %ld\r\n", (long)length);
293                 cprintf("\r\n");
294         }
295                         
296
297 }
298
299
300 /* 
301  * Called by imap_fetch_envelope() to output the "From" field.
302  * This is in its own function because its logic is kind of complex.  We
303  * really need to make this suck less.
304  */
305 void imap_output_envelope_from(struct CtdlMessage *msg) {
306         char user[SIZ], node[SIZ], name[SIZ];
307
308         if (!msg) return;
309
310         /* For anonymous messages, it's so easy! */
311         if (!is_room_aide() && (msg->cm_anon_type == MES_ANONONLY)) {
312                 cprintf("((\"----\" NIL \"x\" \"x.org\")) ");
313                 return;
314         }
315         if (!is_room_aide() && (msg->cm_anon_type == MES_ANONOPT)) {
316                 cprintf("((\"anonymous\" NIL \"x\" \"x.org\")) ");
317                 return;
318         }
319
320         /* For everything else, we do stuff. */
321         cprintf("((");                          /* open double-parens */
322         imap_strout(msg->cm_fields['A']);       /* personal name */
323         cprintf(" NIL ");                       /* source route (not used) */
324
325
326         if (msg->cm_fields['F'] != NULL) {
327                 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
328                 imap_strout(user);              /* mailbox name (user id) */
329                 cprintf(" ");
330                 if (!strcasecmp(node, config.c_nodename)) {
331                         imap_strout(config.c_fqdn);
332                 }
333                 else {
334                         imap_strout(node);              /* host name */
335                 }
336         }
337         else {
338                 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
339                 cprintf(" ");
340                 imap_strout(msg->cm_fields['N']);       /* host name */
341         }
342         
343         cprintf(")) ");                         /* close double-parens */
344 }
345
346
347
348 /*
349  * Output an envelope address (or set of addresses) in the official,
350  * convuluted, braindead format.  (Note that we can't use this for
351  * the "From" address because its data may come from a number of different
352  * fields.  But we can use it for "To" and possibly others.
353  */
354 void imap_output_envelope_addr(char *addr) {
355         char individual_addr[256];
356         int num_addrs;
357         int i;
358         char user[256];
359         char node[256];
360         char name[256];
361
362         if (addr == NULL) {
363                 cprintf("NIL ");
364                 return;
365         }
366
367         if (strlen(addr) == 0) {
368                 cprintf("NIL ");
369                 return;
370         }
371
372         cprintf("(");
373
374         /* How many addresses are listed here? */
375         num_addrs = num_tokens(addr, ',');
376
377         /* Output them one by one. */
378         for (i=0; i<num_addrs; ++i) {
379                 extract_token(individual_addr, addr, i, ',', sizeof individual_addr);
380                 striplt(individual_addr);
381                 process_rfc822_addr(individual_addr, user, node, name);
382                 cprintf("(");
383                 imap_strout(name);
384                 cprintf(" NIL ");
385                 imap_strout(user);
386                 cprintf(" ");
387                 imap_strout(node);
388                 cprintf(")");
389                 if (i < (num_addrs-1)) cprintf(" ");
390         }
391
392         cprintf(") ");
393 }
394
395
396 /*
397  * Implements the ENVELOPE fetch item
398  * 
399  * Note that the imap_strout() function can cleverly output NULL fields as NIL,
400  * so we don't have to check for that condition like we do elsewhere.
401  */
402 void imap_fetch_envelope(struct CtdlMessage *msg) {
403         char datestringbuf[SIZ];
404         time_t msgdate;
405         char *fieldptr = NULL;
406
407         if (!msg) return;
408
409         /* Parse the message date into an IMAP-format date string */
410         if (msg->cm_fields['T'] != NULL) {
411                 msgdate = atol(msg->cm_fields['T']);
412         }
413         else {
414                 msgdate = time(NULL);
415         }
416         datestring(datestringbuf, sizeof datestringbuf,
417                 msgdate, DATESTRING_IMAP);
418
419         /* Now start spewing data fields.  The order is important, as it is
420          * defined by the protocol specification.  Nonexistent fields must
421          * be output as NIL, existent fields must be quoted or literalled.
422          * The imap_strout() function conveniently does all this for us.
423          */
424         cprintf("ENVELOPE (");
425
426         /* Date */
427         imap_strout(datestringbuf);
428         cprintf(" ");
429
430         /* Subject */
431         imap_strout(msg->cm_fields['U']);
432         cprintf(" ");
433
434         /* From */
435         imap_output_envelope_from(msg);
436
437         /* Sender (default to same as 'From' if not present) */
438         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Sender");
439         if (fieldptr != NULL) {
440                 imap_output_envelope_addr(fieldptr);
441                 free(fieldptr);
442         }
443         else {
444                 imap_output_envelope_from(msg);
445         }
446
447         /* Reply-to */
448         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Reply-to");
449         if (fieldptr != NULL) {
450                 imap_output_envelope_addr(fieldptr);
451                 free(fieldptr);
452         }
453         else {
454                 imap_output_envelope_from(msg);
455         }
456
457         /* To */
458         imap_output_envelope_addr(msg->cm_fields['R']);
459
460         /* Cc (we do it this way because there might be a legacy non-Citadel Cc: field present) */
461         fieldptr = msg->cm_fields['Y'];
462         if (fieldptr != NULL) {
463                 imap_output_envelope_addr(fieldptr);
464         }
465         else {
466                 fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Cc");
467                 imap_output_envelope_addr(fieldptr);
468                 if (fieldptr != NULL) free(fieldptr);
469         }
470
471         /* Bcc */
472         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "Bcc");
473         imap_output_envelope_addr(fieldptr);
474         if (fieldptr != NULL) free(fieldptr);
475
476         /* In-reply-to */
477         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
478         imap_strout(fieldptr);
479         cprintf(" ");
480         if (fieldptr != NULL) free(fieldptr);
481
482         /* message ID */
483         imap_strout(msg->cm_fields['I']);
484
485         cprintf(")");
486 }
487
488 /*
489  * This function is called only when CC->redirect_buffer contains a set of
490  * RFC822 headers with no body attached.  Its job is to strip that set of
491  * headers down to *only* the ones we're interested in.
492  */
493 void imap_strip_headers(char *section) {
494         char buf[SIZ];
495         char *which_fields = NULL;
496         int doing_headers = 0;
497         int headers_not = 0;
498         char *parms[SIZ];
499         int num_parms = 0;
500         int i;
501         char *boiled_headers = NULL;
502         int ok = 0;
503         int done_headers = 0;
504         char *ptr = NULL;
505
506         if (CC->redirect_buffer == NULL) return;
507
508         which_fields = strdup(section);
509
510         if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
511                 doing_headers = 1;
512         if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
513                 headers_not = 1;
514
515         for (i=0; i<strlen(which_fields); ++i) {
516                 if (which_fields[i]=='(')
517                         strcpy(which_fields, &which_fields[i+1]);
518         }
519         for (i=0; i<strlen(which_fields); ++i) {
520                 if (which_fields[i]==')')
521                         which_fields[i] = 0;
522         }
523         num_parms = imap_parameterize(parms, which_fields);
524
525         boiled_headers = malloc(CC->redirect_alloc);
526         strcpy(boiled_headers, "");
527
528         ptr = CC->redirect_buffer;
529         ok = 0;
530         while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
531                 if (!isspace(buf[0])) {
532                         ok = 0;
533                         if (doing_headers == 0) ok = 1;
534                         else {
535                                 if (headers_not) ok = 1;
536                                 else ok = 0;
537                                 for (i=0; i<num_parms; ++i) {
538                                         if ( (!strncasecmp(buf, parms[i],
539                                            strlen(parms[i]))) &&
540                                            (buf[strlen(parms[i])]==':') ) {
541                                                 if (headers_not) ok = 0;
542                                                 else ok = 1;
543                                         }
544                                 }
545                         }
546                 }
547
548                 if (ok) {
549                         strcat(boiled_headers, buf);
550                         strcat(boiled_headers, "\r\n");
551                 }
552
553                 if (strlen(buf) == 0) done_headers = 1;
554                 if (buf[0]=='\r') done_headers = 1;
555                 if (buf[0]=='\n') done_headers = 1;
556         }
557
558         strcat(boiled_headers, "\r\n");
559
560         /* Now save it back (it'll always be smaller) */
561         strcpy(CC->redirect_buffer, boiled_headers);
562         CC->redirect_len = strlen(boiled_headers);
563
564         free(which_fields);
565         free(boiled_headers);
566 }
567
568
569 /*
570  * Implements the BODY and BODY.PEEK fetch items
571  */
572 void imap_fetch_body(long msgnum, char *item, int is_peek) {
573         struct CtdlMessage *msg = NULL;
574         char section[SIZ];
575         char partial[SIZ];
576         int is_partial = 0;
577         size_t pstart, pbytes;
578         int loading_body_now = 0;
579         int need_body = 1;
580         int burn_the_cache = 0;
581
582         /* extract section */
583         safestrncpy(section, item, sizeof section);
584         if (strchr(section, '[') != NULL) {
585                 stripallbut(section, '[', ']');
586         }
587         lprintf(CTDL_DEBUG, "Section is: %s%s\n", section, ((strlen(section)==0) ? "(empty)" : "") );
588         if (!strncasecmp(section, "HEADER", 6)) {
589                 need_body = 0;
590         }
591
592         /* Burn the cache if we don't have the same section of the 
593          * same message again.
594          */
595         if (IMAP->cached_body != NULL) {
596                 if (IMAP->cached_bodymsgnum != msgnum) {
597                         burn_the_cache = 1;
598                 }
599                 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
600                         burn_the_cache = 1;
601                 }
602                 else if (strcasecmp(IMAP->cached_bodypart, section)) {
603                         burn_the_cache = 1;
604                 }
605                 if (burn_the_cache) {
606                         /* Yup, go ahead and burn the cache. */
607                         free(IMAP->cached_body);
608                         IMAP->cached_body_len = 0;
609                         IMAP->cached_body = NULL;
610                         IMAP->cached_bodymsgnum = (-1);
611                         strcpy(IMAP->cached_bodypart, "");
612                 }
613         }
614
615         /* extract partial */
616         safestrncpy(partial, item, sizeof partial);
617         if (strchr(partial, '<') != NULL) {
618                 stripallbut(partial, '<', '>');
619                 is_partial = 1;
620         }
621         if (is_partial == 0) strcpy(partial, "");
622         /* if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
623
624         if (IMAP->cached_body == NULL) {
625                 CC->redirect_buffer = malloc(SIZ);
626                 CC->redirect_len = 0;
627                 CC->redirect_alloc = SIZ;
628                 loading_body_now = 1;
629                 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
630         }
631
632         /* Now figure out what the client wants, and get it */
633
634         if (!loading_body_now) {
635                 /* What we want is already in memory */
636         }
637
638         else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
639                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
640         }
641
642         else if (!strcmp(section, "")) {
643                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
644         }
645
646         /*
647          * If the client asked for just headers, or just particular header
648          * fields, strip it down.
649          */
650         else if (!strncasecmp(section, "HEADER", 6)) {
651                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
652                 imap_strip_headers(section);
653         }
654
655         /*
656          * Strip it down if the client asked for everything _except_ headers.
657          */
658         else if (!strncasecmp(section, "TEXT", 4)) {
659                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
660         }
661
662         /*
663          * Anything else must be a part specifier.
664          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
665          */
666         else {
667                 mime_parser(msg->cm_fields['M'], NULL,
668                                 *imap_load_part, NULL, NULL,
669                                 section,
670                                 1);
671         }
672
673         if (loading_body_now) {
674                 IMAP->cached_body = CC->redirect_buffer;
675                 IMAP->cached_body_len = CC->redirect_len;
676                 IMAP->cached_bodymsgnum = msgnum;
677                 IMAP->cached_body_withbody = need_body;
678                 strcpy(IMAP->cached_bodypart, section);
679                 CC->redirect_buffer = NULL;
680                 CC->redirect_len = 0;
681                 CC->redirect_alloc = 0;
682         }
683
684         if (is_partial == 0) {
685                 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
686                 pstart = 0;
687                 pbytes = IMAP->cached_body_len;
688         }
689         else {
690                 sscanf(partial, "%d.%d", &pstart, &pbytes);
691                 if (pbytes > (IMAP->cached_body_len - pstart)) {
692                         pbytes = IMAP->cached_body_len - pstart;
693                 }
694                 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
695         }
696
697         /* Here we go -- output it */
698         client_write(&IMAP->cached_body[pstart], pbytes);
699
700         if (msg != NULL) {
701                 CtdlFreeMessage(msg);
702         }
703
704         /* Mark this message as "seen" *unless* this is a "peek" operation */
705         if (is_peek == 0) {
706                 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
707         }
708 }
709
710 /*
711  * Called immediately before outputting a multipart bodystructure
712  */
713 void imap_fetch_bodystructure_pre(
714                 char *name, char *filename, char *partnum, char *disp,
715                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
716                 void *cbuserdata
717                 ) {
718
719         cprintf("(");
720 }
721
722
723
724 /*
725  * Called immediately after outputting a multipart bodystructure
726  */
727 void imap_fetch_bodystructure_post(
728                 char *name, char *filename, char *partnum, char *disp,
729                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
730                 void *cbuserdata
731                 ) {
732
733         char subtype[128];
734
735         cprintf(" ");
736
737         /* disposition */
738         extract_token(subtype, cbtype, 1, '/', sizeof subtype);
739         imap_strout(subtype);
740
741         /* body language */
742         cprintf(" NIL");
743
744         cprintf(")");
745 }
746
747
748
749 /*
750  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
751  *
752  */
753 void imap_fetch_bodystructure_part(
754                 char *name, char *filename, char *partnum, char *disp,
755                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
756                 void *cbuserdata
757                 ) {
758
759         int have_cbtype = 0;
760         int have_encoding = 0;
761         int lines = 0;
762         size_t i;
763         char cbmaintype[128];
764         char cbsubtype[128];
765
766         if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
767         if (have_cbtype) {
768                 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
769                 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
770         }
771         else {
772                 strcpy(cbmaintype, "TEXT");
773                 strcpy(cbsubtype, "PLAIN");
774         }
775
776         cprintf("(");
777         imap_strout(cbmaintype);
778         cprintf(" ");
779         imap_strout(cbsubtype);
780         cprintf(" ");
781
782         if (cbcharset == NULL) {
783                 cprintf("(\"CHARSET\" \"US-ASCII\"");
784         }
785         else if (strlen(cbcharset) == 0) {
786                 cprintf("(\"CHARSET\" \"US-ASCII\"");
787         }
788         else {
789                 cprintf("(\"CHARSET\" ");
790                 imap_strout(cbcharset);
791         }
792
793         if (name != NULL) if (strlen(name)>0) {
794                 cprintf(" \"NAME\" ");
795                 imap_strout(name);
796         }
797
798         cprintf(") ");
799
800         cprintf("NIL ");        /* Body ID */
801         cprintf("NIL ");        /* Body description */
802
803         if (encoding != NULL) if (strlen(encoding) > 0)  have_encoding = 1;
804         if (have_encoding) {
805                 imap_strout(encoding);
806         }
807         else {
808                 imap_strout("7BIT");
809         }
810         cprintf(" ");
811
812         /* The next field is the size of the part in bytes. */
813         cprintf("%ld ", (long)length);  /* bytes */
814
815         /* The next field is the number of lines in the part, if and only
816          * if the part is TEXT.  More gratuitous complexity.
817          */
818         if (!strcasecmp(cbmaintype, "TEXT")) {
819                 if (length) for (i=0; i<length; ++i) {
820                         if (((char *)content)[i] == '\n') ++lines;
821                 }
822                 cprintf("%d ", lines);
823         }
824
825         /* More gratuitous complexity */
826         if ((!strcasecmp(cbmaintype, "MESSAGE"))
827            && (!strcasecmp(cbsubtype, "RFC822"))) {
828                 /* FIXME
829                      A body type of type MESSAGE and subtype RFC822
830                      contains, immediately after the basic fields, the
831                      envelope structure, body structure, and size in
832                      text lines of the encapsulated message.
833                 */
834         }
835
836         /* MD5 value of body part; we can get away with NIL'ing this */
837         cprintf("NIL ");
838
839         /* Disposition */
840         if (disp == NULL) {
841                 cprintf("NIL");
842         }
843         else if (strlen(disp) == 0) {
844                 cprintf("NIL");
845         }
846         else {
847                 cprintf("(");
848                 imap_strout(disp);
849                 if (filename != NULL) if (strlen(filename)>0) {
850                         cprintf(" (\"FILENAME\" ");
851                         imap_strout(filename);
852                         cprintf(")");
853                 }
854                 cprintf(")");
855         }
856
857         /* Body language (not defined yet) */
858         cprintf(" NIL)");
859 }
860
861
862
863 /*
864  * Spew the BODYSTRUCTURE data for a message.
865  *
866  */
867 void imap_fetch_bodystructure (long msgnum, char *item,
868                 struct CtdlMessage *msg) {
869         char *rfc822 = NULL;
870         char *rfc822_body = NULL;
871         size_t rfc822_len;
872         size_t rfc822_headers_len;
873         size_t rfc822_body_len;
874         char *ptr = NULL;
875         char buf[SIZ];
876         int lines = 0;
877
878         /* For non-RFC822 (ordinary Citadel) messages, this is short and
879          * sweet...
880          */
881         if (msg->cm_format_type != FMT_RFC822) {
882
883                 /* *sigh* We have to RFC822-format the message just to be able
884                  * to measure it.  FIXME use smi cached fields if possible
885                  */
886
887                 CC->redirect_buffer = malloc(SIZ);
888                 CC->redirect_len = 0;
889                 CC->redirect_alloc = SIZ;
890                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1);
891                 rfc822 = CC->redirect_buffer;
892                 rfc822_len = CC->redirect_len;
893                 CC->redirect_buffer = NULL;
894                 CC->redirect_len = 0;
895                 CC->redirect_alloc = 0;
896
897                 ptr = rfc822;
898                 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
899                         ++lines;
900                         if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
901                                 rfc822_body = ptr;
902                         }
903                 }
904
905                 rfc822_headers_len = rfc822_body - rfc822;
906                 rfc822_body_len = rfc822_len - rfc822_headers_len;
907                 free(rfc822);
908
909                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
910                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
911                         "\"7BIT\" %d %d)", rfc822_body_len, lines);
912
913                 return;
914         }
915
916         /* For messages already stored in RFC822 format, we have to parse. */
917         cprintf("BODYSTRUCTURE ");
918         mime_parser(msg->cm_fields['M'],
919                         NULL,
920                         *imap_fetch_bodystructure_part, /* part */
921                         *imap_fetch_bodystructure_pre,  /* pre-multi */
922                         *imap_fetch_bodystructure_post, /* post-multi */
923                         NULL,
924                         1);     /* don't decode -- we want it as-is */
925 }
926
927
928 /*
929  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
930  * individual message, once it has been selected for output.
931  */
932 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
933         int i;
934         struct CtdlMessage *msg = NULL;
935         int body_loaded = 0;
936
937         /* Don't attempt to fetch bogus messages or UID's */
938         if (seq < 1) return;
939         if (IMAP->msgids[seq-1] < 1L) return;
940
941         buffer_output();
942         cprintf("* %d FETCH (", seq);
943
944         for (i=0; i<num_items; ++i) {
945
946                 /* Fetchable without going to the message store at all */
947                 if (!strcasecmp(itemlist[i], "UID")) {
948                         imap_fetch_uid(seq);
949                 }
950                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
951                         imap_fetch_flags(seq-1);
952                 }
953
954                 /* Potentially fetchable from cache, if the client requests
955                  * stuff from the same message several times in a row.
956                  */
957                 else if (!strcasecmp(itemlist[i], "RFC822")) {
958                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
959                 }
960                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
961                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
962                 }
963                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
964                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
965                 }
966                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
967                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
968                 }
969
970                 /* BODY fetches do their own fetching and caching too. */
971                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
972                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
973                 }
974                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
975                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
976                 }
977
978                 /* Otherwise, load the message into memory.
979                  */
980                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
981                         if ((msg != NULL) && (!body_loaded)) {
982                                 CtdlFreeMessage(msg);   /* need the whole thing */
983                                 msg = NULL;
984                         }
985                         if (msg == NULL) {
986                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
987                                 body_loaded = 1;
988                         }
989                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
990                                         itemlist[i], msg);
991                 }
992                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
993                         if (msg == NULL) {
994                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
995                                 body_loaded = 0;
996                         }
997                         imap_fetch_envelope(msg);
998                 }
999                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1000                         if (msg == NULL) {
1001                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1002                                 body_loaded = 0;
1003                         }
1004                         imap_fetch_internaldate(msg);
1005                 }
1006
1007                 if (i != num_items-1) cprintf(" ");
1008         }
1009
1010         cprintf(")\r\n");
1011         unbuffer_output();
1012         if (msg != NULL) {
1013                 CtdlFreeMessage(msg);
1014         }
1015 }
1016
1017
1018
1019 /*
1020  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1021  * validated and boiled down the request a bit.
1022  */
1023 void imap_do_fetch(int num_items, char **itemlist) {
1024         int i;
1025
1026         if (IMAP->num_msgs > 0) {
1027                 for (i = 0; i < IMAP->num_msgs; ++i) {
1028                         if (IMAP->flags[i] & IMAP_SELECTED) {
1029                                 imap_do_fetch_msg(i+1, num_items, itemlist);
1030                         }
1031                 }
1032         }
1033 }
1034
1035
1036
1037 /*
1038  * Back end for imap_handle_macros()
1039  * Note that this function *only* looks at the beginning of the string.  It
1040  * is not a generic search-and-replace function.
1041  */
1042 void imap_macro_replace(char *str, char *find, char *replace) {
1043         char holdbuf[SIZ];
1044
1045         if (!strncasecmp(str, find, strlen(find))) {
1046                 if (str[strlen(find)]==' ') {
1047                         strcpy(holdbuf, &str[strlen(find)+1]);
1048                         strcpy(str, replace);
1049                         strcat(str, " ");
1050                         strcat(str, holdbuf);
1051                 }
1052                 if (str[strlen(find)]==0) {
1053                         strcpy(holdbuf, &str[strlen(find)+1]);
1054                         strcpy(str, replace);
1055                 }
1056         }
1057 }
1058
1059
1060
1061 /*
1062  * Handle macros embedded in FETCH data items.
1063  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1064  * the computer at the other end the trouble of typing a lot of characters?)
1065  */
1066 void imap_handle_macros(char *str) {
1067         int i;
1068         int nest = 0;
1069
1070         for (i=0; i<strlen(str); ++i) {
1071                 if (str[i]=='(') ++nest;
1072                 if (str[i]=='[') ++nest;
1073                 if (str[i]=='<') ++nest;
1074                 if (str[i]=='{') ++nest;
1075                 if (str[i]==')') --nest;
1076                 if (str[i]==']') --nest;
1077                 if (str[i]=='>') --nest;
1078                 if (str[i]=='}') --nest;
1079
1080                 if (nest <= 0) {
1081                         imap_macro_replace(&str[i],
1082                                 "ALL",
1083                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1084                         );
1085                         imap_macro_replace(&str[i],
1086                                 "BODY",
1087                                 "BODYSTRUCTURE"
1088                         );
1089                         imap_macro_replace(&str[i],
1090                                 "FAST",
1091                                 "FLAGS INTERNALDATE RFC822.SIZE"
1092                         );
1093                         imap_macro_replace(&str[i],
1094                                 "FULL",
1095                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1096                         );
1097                 }
1098         }
1099 }
1100
1101
1102 /*
1103  * Break out the data items requested, possibly a parenthesized list.
1104  * Returns the number of data items, or -1 if the list is invalid.
1105  * NOTE: this function alters the string it is fed, and uses it as a buffer
1106  * to hold the data for the pointers it returns.
1107  */
1108 int imap_extract_data_items(char **argv, char *items) {
1109         int num_items = 0;
1110         int nest = 0;
1111         int i, initial_len;
1112         char *start;
1113
1114         /* Convert all whitespace to ordinary space characters. */
1115         for (i=0; i<strlen(items); ++i) {
1116                 if (isspace(items[i])) items[i]=' ';
1117         }
1118
1119         /* Strip leading and trailing whitespace, then strip leading and
1120          * trailing parentheses if it's a list
1121          */
1122         striplt(items);
1123         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1124                 items[strlen(items)-1] = 0;
1125                 strcpy(items, &items[1]);
1126                 striplt(items);
1127         }
1128
1129         /* Parse any macro data items */
1130         imap_handle_macros(items);
1131
1132         /*
1133          * Now break out the data items.  We throw in one trailing space in
1134          * order to avoid having to break out the last one manually.
1135          */
1136         strcat(items, " ");
1137         start = items;
1138         initial_len = strlen(items);
1139         for (i=0; i<initial_len; ++i) {
1140                 if (items[i]=='(') ++nest;
1141                 if (items[i]=='[') ++nest;
1142                 if (items[i]=='<') ++nest;
1143                 if (items[i]=='{') ++nest;
1144                 if (items[i]==')') --nest;
1145                 if (items[i]==']') --nest;
1146                 if (items[i]=='>') --nest;
1147                 if (items[i]=='}') --nest;
1148
1149                 if (nest <= 0) if (items[i]==' ') {
1150                         items[i] = 0;
1151                         argv[num_items++] = start;
1152                         start = &items[i+1];
1153                 }
1154         }
1155
1156         return(num_items);
1157
1158 }
1159
1160
1161 /*
1162  * One particularly hideous aspect of IMAP is that we have to allow the client
1163  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1164  * handles this by setting the IMAP_SELECTED flag for each message specified in
1165  * the ranges/sets, then looping through the message array, outputting messages
1166  * with the flag set.  We don't bother returning an error if an out-of-range
1167  * number is specified (we just return quietly) because any client braindead
1168  * enough to request a bogus message number isn't going to notice the
1169  * difference anyway.
1170  *
1171  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1172  * message included in the specified range.
1173  *
1174  * Set is_uid to 1 to fetch by UID instead of sequence number.
1175  */
1176 void imap_pick_range(char *supplied_range, int is_uid) {
1177         int i;
1178         int num_sets;
1179         int s;
1180         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1181         long lo, hi;
1182         char actual_range[SIZ];
1183
1184         /* 
1185          * Handle the "ALL" macro
1186          */
1187         if (!strcasecmp(supplied_range, "ALL")) {
1188                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1189         }
1190         else {
1191                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1192         }
1193
1194         /*
1195          * Clear out the IMAP_SELECTED flags for all messages.
1196          */
1197         for (i = 0; i < IMAP->num_msgs; ++i) {
1198                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1199         }
1200
1201         /*
1202          * Now set it for all specified messages.
1203          */
1204         num_sets = num_tokens(actual_range, ',');
1205         for (s=0; s<num_sets; ++s) {
1206                 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1207
1208                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1209                 if (num_tokens(setstr, ':') >= 2) {
1210                         extract_token(histr, setstr, 1, ':', sizeof histr);
1211                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1212                 } 
1213                 else {
1214                         safestrncpy(histr, lostr, sizeof histr);
1215                 }
1216                 lo = atol(lostr);
1217                 hi = atol(histr);
1218
1219                 /* Loop through the array, flipping bits where appropriate */
1220                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1221                         if (is_uid) {   /* fetch by sequence number */
1222                                 if ( (IMAP->msgids[i-1]>=lo)
1223                                    && (IMAP->msgids[i-1]<=hi)) {
1224                                         IMAP->flags[i-1] |= IMAP_SELECTED;
1225                                 }
1226                         }
1227                         else {          /* fetch by uid */
1228                                 if ( (i>=lo) && (i<=hi)) {
1229                                         IMAP->flags[i-1] |= IMAP_SELECTED;
1230                                 }
1231                         }
1232                 }
1233         }
1234
1235 }
1236
1237
1238
1239 /*
1240  * This function is called by the main command loop.
1241  */
1242 void imap_fetch(int num_parms, char *parms[]) {
1243         char items[SIZ];
1244         char *itemlist[512];
1245         int num_items;
1246         int i;
1247
1248         if (num_parms < 4) {
1249                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1250                 return;
1251         }
1252
1253         imap_pick_range(parms[2], 0);
1254
1255         strcpy(items, "");
1256         for (i=3; i<num_parms; ++i) {
1257                 strcat(items, parms[i]);
1258                 if (i < (num_parms-1)) strcat(items, " ");
1259         }
1260
1261         num_items = imap_extract_data_items(itemlist, items);
1262         if (num_items < 1) {
1263                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1264                 return;
1265         }
1266
1267         imap_do_fetch(num_items, itemlist);
1268         cprintf("%s OK FETCH completed\r\n", parms[0]);
1269 }
1270
1271 /*
1272  * This function is called by the main command loop.
1273  */
1274 void imap_uidfetch(int num_parms, char *parms[]) {
1275         char items[SIZ];
1276         char *itemlist[512];
1277         int num_items;
1278         int i;
1279         int have_uid_item = 0;
1280
1281         if (num_parms < 5) {
1282                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1283                 return;
1284         }
1285
1286         imap_pick_range(parms[3], 1);
1287
1288         strcpy(items, "");
1289         for (i=4; i<num_parms; ++i) {
1290                 strcat(items, parms[i]);
1291                 if (i < (num_parms-1)) strcat(items, " ");
1292         }
1293
1294         num_items = imap_extract_data_items(itemlist, items);
1295         if (num_items < 1) {
1296                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1297                 return;
1298         }
1299
1300         /* If the "UID" item was not included, we include it implicitly
1301          * (at the beginning) because this is a UID FETCH command
1302          */
1303         for (i=0; i<num_items; ++i) {
1304                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1305         }
1306         if (have_uid_item == 0) {
1307                 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1308                 ++num_items;
1309                 itemlist[0] = "UID";
1310         }
1311
1312         imap_do_fetch(num_items, itemlist);
1313         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1314 }
1315
1316