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