Fixed a serious problem with IMAP header fetch. memreadline() returns a pointer...
[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  * convuluted, 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.  The
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
852                      A body type of type MESSAGE and subtype RFC822
853                      contains, immediately after the basic fields, the
854                      envelope structure, body structure, and size in
855                      text lines of the encapsulated message.
856                 */
857         }
858
859         /* MD5 value of body part; we can get away with NIL'ing this */
860         cprintf("NIL ");
861
862         /* Disposition */
863         if (disp == NULL) {
864                 cprintf("NIL");
865         }
866         else if (IsEmptyStr(disp)) {
867                 cprintf("NIL");
868         }
869         else {
870                 cprintf("(");
871                 imap_strout(disp);
872                 if (filename != NULL) if (!IsEmptyStr(filename)) {
873                         cprintf(" (\"FILENAME\" ");
874                         imap_strout(filename);
875                         cprintf(")");
876                 }
877                 cprintf(")");
878         }
879
880         /* Body language (not defined yet) */
881         cprintf(" NIL)");
882 }
883
884
885
886 /*
887  * Spew the BODYSTRUCTURE data for a message.
888  *
889  */
890 void imap_fetch_bodystructure (long msgnum, char *item,
891                 struct CtdlMessage *msg) {
892         char *rfc822 = NULL;
893         char *rfc822_body = NULL;
894         size_t rfc822_len;
895         size_t rfc822_headers_len;
896         size_t rfc822_body_len;
897         char *ptr = NULL;
898         char buf[SIZ];
899         int lines = 0;
900
901         /* Handle NULL message gracefully */
902         if (msg == NULL) {
903                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
904                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
905                         "\"7BIT\" 0 0)");
906                 return;
907         }
908
909         /* For non-RFC822 (ordinary Citadel) messages, this is short and
910          * sweet...
911          */
912         if (msg->cm_format_type != FMT_RFC822) {
913
914                 /* *sigh* We have to RFC822-format the message just to be able
915                  * to measure it.  FIXME use smi cached fields if possible
916                  */
917
918                 CC->redirect_buffer = malloc(SIZ);
919                 CC->redirect_len = 0;
920                 CC->redirect_alloc = SIZ;
921                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, 0);
922                 rfc822 = CC->redirect_buffer;
923                 rfc822_len = CC->redirect_len;
924                 CC->redirect_buffer = NULL;
925                 CC->redirect_len = 0;
926                 CC->redirect_alloc = 0;
927
928                 ptr = rfc822;
929                 do {
930                         ptr = memreadline(ptr, buf, sizeof buf);
931                         ++lines;
932                         if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
933                                 rfc822_body = ptr;
934                         }
935                 } while (*ptr != 0);
936
937                 rfc822_headers_len = rfc822_body - rfc822;
938                 rfc822_body_len = rfc822_len - rfc822_headers_len;
939                 free(rfc822);
940
941                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
942                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
943                         "\"7BIT\" " SIZE_T_FMT " %d)", rfc822_body_len, lines);
944
945                 return;
946         }
947
948         /* For messages already stored in RFC822 format, we have to parse. */
949         cprintf("BODYSTRUCTURE ");
950         mime_parser(msg->cm_fields['M'],
951                         NULL,
952                         *imap_fetch_bodystructure_part, /* part */
953                         *imap_fetch_bodystructure_pre,  /* pre-multi */
954                         *imap_fetch_bodystructure_post, /* post-multi */
955                         NULL,
956                         1);     /* don't decode -- we want it as-is */
957 }
958
959
960 /*
961  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
962  * individual message, once it has been selected for output.
963  */
964 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
965         int i;
966         struct CtdlMessage *msg = NULL;
967         int body_loaded = 0;
968
969         /* Don't attempt to fetch bogus messages or UID's */
970         if (seq < 1) return;
971         if (IMAP->msgids[seq-1] < 1L) return;
972
973         buffer_output();
974         cprintf("* %d FETCH (", seq);
975
976         for (i=0; i<num_items; ++i) {
977
978                 /* Fetchable without going to the message store at all */
979                 if (!strcasecmp(itemlist[i], "UID")) {
980                         imap_fetch_uid(seq);
981                 }
982                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
983                         imap_fetch_flags(seq-1);
984                 }
985
986                 /* Potentially fetchable from cache, if the client requests
987                  * stuff from the same message several times in a row.
988                  */
989                 else if (!strcasecmp(itemlist[i], "RFC822")) {
990                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
991                 }
992                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
993                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
994                 }
995                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
996                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
997                 }
998                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
999                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
1000                 }
1001
1002                 /* BODY fetches do their own fetching and caching too. */
1003                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
1004                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
1005                 }
1006                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
1007                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
1008                 }
1009
1010                 /* Otherwise, load the message into memory.
1011                  */
1012                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
1013                         if ((msg != NULL) && (!body_loaded)) {
1014                                 CtdlFreeMessage(msg);   /* need the whole thing */
1015                                 msg = NULL;
1016                         }
1017                         if (msg == NULL) {
1018                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
1019                                 body_loaded = 1;
1020                         }
1021                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
1022                                         itemlist[i], msg);
1023                 }
1024                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1025                         if (msg == NULL) {
1026                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1027                                 body_loaded = 0;
1028                         }
1029                         imap_fetch_envelope(msg);
1030                 }
1031                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1032                         if (msg == NULL) {
1033                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1034                                 body_loaded = 0;
1035                         }
1036                         imap_fetch_internaldate(msg);
1037                 }
1038
1039                 if (i != num_items-1) cprintf(" ");
1040         }
1041
1042         cprintf(")\r\n");
1043         unbuffer_output();
1044         if (msg != NULL) {
1045                 CtdlFreeMessage(msg);
1046         }
1047 }
1048
1049
1050
1051 /*
1052  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1053  * validated and boiled down the request a bit.
1054  */
1055 void imap_do_fetch(int num_items, char **itemlist) {
1056         int i;
1057
1058         if (IMAP->num_msgs > 0) {
1059                 for (i = 0; i < IMAP->num_msgs; ++i) {
1060
1061                         /* Abort the fetch loop if the session breaks.
1062                          * This is important for users who keep mailboxes
1063                          * that are too big *and* are too impatient to
1064                          * let them finish loading.  :)
1065                          */
1066                         if (CC->kill_me) return;
1067
1068                         /* Get any message marked for fetch. */
1069                         if (IMAP->flags[i] & IMAP_SELECTED) {
1070                                 imap_do_fetch_msg(i+1, num_items, itemlist);
1071                         }
1072                 }
1073         }
1074 }
1075
1076
1077
1078 /*
1079  * Back end for imap_handle_macros()
1080  * Note that this function *only* looks at the beginning of the string.  It
1081  * is not a generic search-and-replace function.
1082  */
1083 void imap_macro_replace(char *str, char *find, char *replace) {
1084         char holdbuf[SIZ];
1085         int findlen;
1086
1087         findlen = strlen(find);
1088
1089         if (!strncasecmp(str, find, findlen)) {
1090                 if (str[findlen]==' ') {
1091                         strcpy(holdbuf, &str[findlen+1]);
1092                         strcpy(str, replace);
1093                         strcat(str, " ");
1094                         strcat(str, holdbuf);
1095                 }
1096                 if (str[findlen]==0) {
1097                         strcpy(holdbuf, &str[findlen+1]);
1098                         strcpy(str, replace);
1099                 }
1100         }
1101 }
1102
1103
1104
1105 /*
1106  * Handle macros embedded in FETCH data items.
1107  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1108  * the computer at the other end the trouble of typing a lot of characters?)
1109  */
1110 void imap_handle_macros(char *str) {
1111         int i;
1112         int nest = 0;
1113
1114         for (i=0; str[i]; ++i) {
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                 if (str[i]=='}') --nest;
1123
1124                 if (nest <= 0) {
1125                         imap_macro_replace(&str[i],
1126                                 "ALL",
1127                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1128                         );
1129                         imap_macro_replace(&str[i],
1130                                 "BODY",
1131                                 "BODYSTRUCTURE"
1132                         );
1133                         imap_macro_replace(&str[i],
1134                                 "FAST",
1135                                 "FLAGS INTERNALDATE RFC822.SIZE"
1136                         );
1137                         imap_macro_replace(&str[i],
1138                                 "FULL",
1139                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1140                         );
1141                 }
1142         }
1143 }
1144
1145
1146 /*
1147  * Break out the data items requested, possibly a parenthesized list.
1148  * Returns the number of data items, or -1 if the list is invalid.
1149  * NOTE: this function alters the string it is fed, and uses it as a buffer
1150  * to hold the data for the pointers it returns.
1151  */
1152 int imap_extract_data_items(char **argv, char *items) {
1153         int num_items = 0;
1154         int nest = 0;
1155         int i;
1156         char *start;
1157         long initial_len;
1158
1159         /* Convert all whitespace to ordinary space characters. */
1160         for (i=0; items[i]; ++i) {
1161                 if (isspace(items[i])) items[i]=' ';
1162         }
1163
1164         /* Strip leading and trailing whitespace, then strip leading and
1165          * trailing parentheses if it's a list
1166          */
1167         striplt(items);
1168         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1169                 items[strlen(items)-1] = 0;
1170                 strcpy(items, &items[1]);
1171                 striplt(items);
1172         }
1173
1174         /* Parse any macro data items */
1175         imap_handle_macros(items);
1176
1177         /*
1178          * Now break out the data items.  We throw in one trailing space in
1179          * order to avoid having to break out the last one manually.
1180          */
1181         strcat(items, " ");
1182         start = items;
1183         initial_len = strlen(items);
1184         for (i=0; i<initial_len; ++i) {
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                 if (items[i]=='}') --nest;
1193
1194                 if (nest <= 0) if (items[i]==' ') {
1195                         items[i] = 0;
1196                         argv[num_items++] = start;
1197                         start = &items[i+1];
1198                 }
1199         }
1200
1201         return(num_items);
1202
1203 }
1204
1205
1206 /*
1207  * One particularly hideous aspect of IMAP is that we have to allow the client
1208  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1209  * handles this by setting the IMAP_SELECTED flag for each message specified in
1210  * the ranges/sets, then looping through the message array, outputting messages
1211  * with the flag set.  We don't bother returning an error if an out-of-range
1212  * number is specified (we just return quietly) because any client braindead
1213  * enough to request a bogus message number isn't going to notice the
1214  * difference anyway.
1215  *
1216  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1217  * message included in the specified range.
1218  *
1219  * Set is_uid to 1 to fetch by UID instead of sequence number.
1220  */
1221 void imap_pick_range(char *supplied_range, int is_uid) {
1222         int i;
1223         int num_sets;
1224         int s;
1225         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1226         long lo, hi;
1227         char actual_range[SIZ];
1228         struct citimap *Imap;
1229
1230         /* 
1231          * Handle the "ALL" macro
1232          */
1233         if (!strcasecmp(supplied_range, "ALL")) {
1234                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1235         }
1236         else {
1237                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1238         }
1239
1240         Imap = IMAP;
1241         /*
1242          * Clear out the IMAP_SELECTED flags for all messages.
1243          */
1244         for (i = 0; i < Imap->num_msgs; ++i) {
1245                 Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
1246         }
1247
1248         /*
1249          * Now set it for all specified messages.
1250          */
1251         num_sets = num_tokens(actual_range, ',');
1252         for (s=0; s<num_sets; ++s) {
1253                 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1254
1255                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1256                 if (num_tokens(setstr, ':') >= 2) {
1257                         extract_token(histr, setstr, 1, ':', sizeof histr);
1258                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1259                 } 
1260                 else {
1261                         safestrncpy(histr, lostr, sizeof histr);
1262                 }
1263                 lo = atol(lostr);
1264                 hi = atol(histr);
1265
1266                 /* Loop through the array, flipping bits where appropriate */
1267                 for (i = 1; i <= Imap->num_msgs; ++i) {
1268                         if (is_uid) {   /* fetch by sequence number */
1269                                 if ( (Imap->msgids[i-1]>=lo)
1270                                    && (Imap->msgids[i-1]<=hi)) {
1271                                         Imap->flags[i-1] |= IMAP_SELECTED;
1272                                 }
1273                         }
1274                         else {          /* fetch by uid */
1275                                 if ( (i>=lo) && (i<=hi)) {
1276                                         Imap->flags[i-1] |= IMAP_SELECTED;
1277                                 }
1278                         }
1279                 }
1280         }
1281
1282 }
1283
1284
1285
1286 /*
1287  * This function is called by the main command loop.
1288  */
1289 void imap_fetch(int num_parms, char *parms[]) {
1290         char items[SIZ];
1291         char *itemlist[512];
1292         int num_items;
1293         int i;
1294
1295         if (num_parms < 4) {
1296                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1297                 return;
1298         }
1299
1300         imap_pick_range(parms[2], 0);
1301
1302         strcpy(items, "");
1303         for (i=3; i<num_parms; ++i) {
1304                 strcat(items, parms[i]);
1305                 if (i < (num_parms-1)) strcat(items, " ");
1306         }
1307
1308         num_items = imap_extract_data_items(itemlist, items);
1309         if (num_items < 1) {
1310                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1311                 return;
1312         }
1313
1314         imap_do_fetch(num_items, itemlist);
1315         cprintf("%s OK FETCH completed\r\n", parms[0]);
1316 }
1317
1318 /*
1319  * This function is called by the main command loop.
1320  */
1321 void imap_uidfetch(int num_parms, char *parms[]) {
1322         char items[SIZ];
1323         char *itemlist[512];
1324         int num_items;
1325         int i;
1326         int have_uid_item = 0;
1327
1328         if (num_parms < 5) {
1329                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1330                 return;
1331         }
1332
1333         imap_pick_range(parms[3], 1);
1334
1335         strcpy(items, "");
1336         for (i=4; i<num_parms; ++i) {
1337                 strcat(items, parms[i]);
1338                 if (i < (num_parms-1)) strcat(items, " ");
1339         }
1340
1341         num_items = imap_extract_data_items(itemlist, items);
1342         if (num_items < 1) {
1343                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1344                 return;
1345         }
1346
1347         /* If the "UID" item was not included, we include it implicitly
1348          * (at the beginning) because this is a UID FETCH command
1349          */
1350         for (i=0; i<num_items; ++i) {
1351                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1352         }
1353         if (have_uid_item == 0) {
1354                 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1355                 ++num_items;
1356                 itemlist[0] = "UID";
1357         }
1358
1359         imap_do_fetch(num_items, itemlist);
1360         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1361 }
1362
1363