* make QP encoding of senders optional, so we can flag it if we need it.
[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_ONLY),
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                     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         while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
534                 if (!isspace(buf[0])) {
535                         ok = 0;
536                         if (doing_headers == 0) ok = 1;
537                         else {
538                                 if (headers_not) ok = 1;
539                                 else ok = 0;
540                                 for (i=0; i<num_parms; ++i) {
541                                         if ( (!strncasecmp(buf, parms[i],
542                                            strlen(parms[i]))) &&
543                                            (buf[strlen(parms[i])]==':') ) {
544                                                 if (headers_not) ok = 0;
545                                                 else ok = 1;
546                                         }
547                                 }
548                         }
549                 }
550
551                 if (ok) {
552                         strcat(boiled_headers, buf);
553                         strcat(boiled_headers, "\r\n");
554                 }
555
556                 if (IsEmptyStr(buf)) done_headers = 1;
557                 if (buf[0]=='\r') done_headers = 1;
558                 if (buf[0]=='\n') done_headers = 1;
559         }
560
561         strcat(boiled_headers, "\r\n");
562
563         /* Now save it back (it'll always be smaller) */
564         strcpy(CC->redirect_buffer, boiled_headers);
565         CC->redirect_len = strlen(boiled_headers);
566
567         free(which_fields);
568         free(boiled_headers);
569 }
570
571
572 /*
573  * Implements the BODY and BODY.PEEK fetch items
574  */
575 void imap_fetch_body(long msgnum, char *item, int is_peek) {
576         struct CtdlMessage *msg = NULL;
577         char section[SIZ];
578         char partial[SIZ];
579         int is_partial = 0;
580         size_t pstart, pbytes;
581         int loading_body_now = 0;
582         int need_body = 1;
583         int burn_the_cache = 0;
584
585         /* extract section */
586         safestrncpy(section, item, sizeof section);
587         if (strchr(section, '[') != NULL) {
588                 stripallbut(section, '[', ']');
589         }
590         CtdlLogPrintf(CTDL_DEBUG, "Section is: %s%s\n", 
591                 section, 
592                 IsEmptyStr(section) ? "(empty)" : "");
593
594         /*
595          * We used to have this great optimization in place that would avoid
596          * fetching the entire RFC822 message from disk if the client was only
597          * asking for the headers.  Unfortunately, fetching only the Citadel
598          * headers omits "Content-type:" and this behavior breaks the iPhone
599          * email client.  So we have to fetch the whole message from disk.  The
600          *
601          *      if (!strncasecmp(section, "HEADER", 6)) {
602          *              need_body = 0;
603          *      }
604          *
605          */
606
607         /* Burn the cache if we don't have the same section of the 
608          * same message again.
609          */
610         if (IMAP->cached_body != NULL) {
611                 if (IMAP->cached_bodymsgnum != msgnum) {
612                         burn_the_cache = 1;
613                 }
614                 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
615                         burn_the_cache = 1;
616                 }
617                 else if (strcasecmp(IMAP->cached_bodypart, section)) {
618                         burn_the_cache = 1;
619                 }
620                 if (burn_the_cache) {
621                         /* Yup, go ahead and burn the cache. */
622                         free(IMAP->cached_body);
623                         IMAP->cached_body_len = 0;
624                         IMAP->cached_body = NULL;
625                         IMAP->cached_bodymsgnum = (-1);
626                         strcpy(IMAP->cached_bodypart, "");
627                 }
628         }
629
630         /* extract partial */
631         safestrncpy(partial, item, sizeof partial);
632         if (strchr(partial, '<') != NULL) {
633                 stripallbut(partial, '<', '>');
634                 is_partial = 1;
635         }
636         if (is_partial == 0) strcpy(partial, "");
637         /* if (!IsEmptyStr(partial)) CtdlLogPrintf(CTDL_DEBUG, "Partial is %s\n", partial); */
638
639         if (IMAP->cached_body == NULL) {
640                 CC->redirect_buffer = malloc(SIZ);
641                 CC->redirect_len = 0;
642                 CC->redirect_alloc = SIZ;
643                 loading_body_now = 1;
644                 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
645         }
646
647         /* Now figure out what the client wants, and get it */
648
649         if (!loading_body_now) {
650                 /* What we want is already in memory */
651         }
652
653         else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
654                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, 0);
655         }
656
657         else if (!strcmp(section, "")) {
658                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, 0);
659         }
660
661         /*
662          * If the client asked for just headers, or just particular header
663          * fields, strip it down.
664          */
665         else if (!strncasecmp(section, "HEADER", 6)) {
666                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1, 0);
667                 imap_strip_headers(section);
668         }
669
670         /*
671          * Strip it down if the client asked for everything _except_ headers.
672          */
673         else if (!strncasecmp(section, "TEXT", 4)) {
674                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1, 0);
675         }
676
677         /*
678          * Anything else must be a part specifier.
679          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
680          */
681         else {
682                 mime_parser(msg->cm_fields['M'], NULL,
683                                 *imap_load_part, NULL, NULL,
684                                 section,
685                                 1);
686         }
687
688         if (loading_body_now) {
689                 IMAP->cached_body = CC->redirect_buffer;
690                 IMAP->cached_body_len = CC->redirect_len;
691                 IMAP->cached_bodymsgnum = msgnum;
692                 IMAP->cached_body_withbody = need_body;
693                 strcpy(IMAP->cached_bodypart, section);
694                 CC->redirect_buffer = NULL;
695                 CC->redirect_len = 0;
696                 CC->redirect_alloc = 0;
697         }
698
699         if (is_partial == 0) {
700                 cprintf("BODY[%s] {" SIZE_T_FMT "}\r\n", section, IMAP->cached_body_len);
701                 pstart = 0;
702                 pbytes = IMAP->cached_body_len;
703         }
704         else {
705                 sscanf(partial, SIZE_T_FMT "." SIZE_T_FMT, &pstart, &pbytes);
706                 if (pbytes > (IMAP->cached_body_len - pstart)) {
707                         pbytes = IMAP->cached_body_len - pstart;
708                 }
709                 cprintf("BODY[%s]<" SIZE_T_FMT "> {" SIZE_T_FMT "}\r\n", section, pstart, pbytes);
710         }
711
712         /* Here we go -- output it */
713         client_write(&IMAP->cached_body[pstart], pbytes);
714
715         if (msg != NULL) {
716                 CtdlFreeMessage(msg);
717         }
718
719         /* Mark this message as "seen" *unless* this is a "peek" operation */
720         if (is_peek == 0) {
721                 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
722         }
723 }
724
725 /*
726  * Called immediately before outputting a multipart bodystructure
727  */
728 void imap_fetch_bodystructure_pre(
729                 char *name, char *filename, char *partnum, char *disp,
730                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
731                 void *cbuserdata
732                 ) {
733
734         cprintf("(");
735 }
736
737
738
739 /*
740  * Called immediately after outputting a multipart bodystructure
741  */
742 void imap_fetch_bodystructure_post(
743                 char *name, char *filename, char *partnum, char *disp,
744                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
745                 void *cbuserdata
746                 ) {
747
748         char subtype[128];
749
750         cprintf(" ");
751
752         /* disposition */
753         extract_token(subtype, cbtype, 1, '/', sizeof subtype);
754         imap_strout(subtype);
755
756         /* body language */
757         /* cprintf(" NIL"); We thought we needed this at one point, but maybe we don't... */
758
759         cprintf(")");
760 }
761
762
763
764 /*
765  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
766  *
767  */
768 void imap_fetch_bodystructure_part(
769                 char *name, char *filename, char *partnum, char *disp,
770                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
771                 void *cbuserdata
772                 ) {
773
774         int have_cbtype = 0;
775         int have_encoding = 0;
776         int lines = 0;
777         size_t i;
778         char cbmaintype[128];
779         char cbsubtype[128];
780
781         if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
782         if (have_cbtype) {
783                 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
784                 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
785         }
786         else {
787                 strcpy(cbmaintype, "TEXT");
788                 strcpy(cbsubtype, "PLAIN");
789         }
790
791         cprintf("(");
792         imap_strout(cbmaintype);                                        /* body type */
793         cprintf(" ");
794         imap_strout(cbsubtype);                                         /* body subtype */
795         cprintf(" ");
796
797         cprintf("(");                                                   /* begin body parameter list */
798
799         /* "NAME" must appear as the first parameter.  This is not required by IMAP,
800          * but the Asterisk voicemail application blindly assumes that NAME will be in
801          * the first position.  If it isn't, it rejects the message.
802          */
803         if (name != NULL) if (!IsEmptyStr(name)) {
804                 cprintf("\"NAME\" ");
805                 imap_strout(name);
806                 cprintf(" ");
807         }
808
809         cprintf("\"CHARSET\" ");
810         if (cbcharset == NULL) {
811                 imap_strout("US-ASCII");
812         }
813         else if (cbcharset[0] == 0) {
814                 imap_strout("US-ASCII");
815         }
816         else {
817                 imap_strout(cbcharset);
818         }
819         cprintf(") ");                                                  /* end body parameter list */
820
821         cprintf("NIL ");                                                /* Body ID */
822         cprintf("NIL ");                                                /* Body description */
823
824         if (encoding != NULL) if (encoding[0] != 0)  have_encoding = 1;
825         if (have_encoding) {
826                 imap_strout(encoding);
827         }
828         else {
829                 imap_strout("7BIT");
830         }
831         cprintf(" ");
832
833         /* The next field is the size of the part in bytes. */
834         cprintf("%ld ", (long)length);  /* bytes */
835
836         /* The next field is the number of lines in the part, if and only
837          * if the part is TEXT.  More gratuitous complexity.
838          */
839         if (!strcasecmp(cbmaintype, "TEXT")) {
840                 if (length) for (i=0; i<length; ++i) {
841                         if (((char *)content)[i] == '\n') ++lines;
842                 }
843                 cprintf("%d ", lines);
844         }
845
846         /* More gratuitous complexity */
847         if ((!strcasecmp(cbmaintype, "MESSAGE"))
848            && (!strcasecmp(cbsubtype, "RFC822"))) {
849                 /* FIXME
850                      A body type of type MESSAGE and subtype RFC822
851                      contains, immediately after the basic fields, the
852                      envelope structure, body structure, and size in
853                      text lines of the encapsulated message.
854                 */
855         }
856
857         /* MD5 value of body part; we can get away with NIL'ing this */
858         cprintf("NIL ");
859
860         /* Disposition */
861         if (disp == NULL) {
862                 cprintf("NIL");
863         }
864         else if (IsEmptyStr(disp)) {
865                 cprintf("NIL");
866         }
867         else {
868                 cprintf("(");
869                 imap_strout(disp);
870                 if (filename != NULL) if (!IsEmptyStr(filename)) {
871                         cprintf(" (\"FILENAME\" ");
872                         imap_strout(filename);
873                         cprintf(")");
874                 }
875                 cprintf(")");
876         }
877
878         /* Body language (not defined yet) */
879         cprintf(" NIL)");
880 }
881
882
883
884 /*
885  * Spew the BODYSTRUCTURE data for a message.
886  *
887  */
888 void imap_fetch_bodystructure (long msgnum, char *item,
889                 struct CtdlMessage *msg) {
890         char *rfc822 = NULL;
891         char *rfc822_body = NULL;
892         size_t rfc822_len;
893         size_t rfc822_headers_len;
894         size_t rfc822_body_len;
895         char *ptr = NULL;
896         char buf[SIZ];
897         int lines = 0;
898
899         /* Handle NULL message gracefully */
900         if (msg == NULL) {
901                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
902                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
903                         "\"7BIT\" 0 0)");
904                 return;
905         }
906
907         /* For non-RFC822 (ordinary Citadel) messages, this is short and
908          * sweet...
909          */
910         if (msg->cm_format_type != FMT_RFC822) {
911
912                 /* *sigh* We have to RFC822-format the message just to be able
913                  * to measure it.  FIXME use smi cached fields if possible
914                  */
915
916                 CC->redirect_buffer = malloc(SIZ);
917                 CC->redirect_len = 0;
918                 CC->redirect_alloc = SIZ;
919                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1, 0);
920                 rfc822 = CC->redirect_buffer;
921                 rfc822_len = CC->redirect_len;
922                 CC->redirect_buffer = NULL;
923                 CC->redirect_len = 0;
924                 CC->redirect_alloc = 0;
925
926                 ptr = rfc822;
927                 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
928                         ++lines;
929                         if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
930                                 rfc822_body = ptr;
931                         }
932                 }
933
934                 rfc822_headers_len = rfc822_body - rfc822;
935                 rfc822_body_len = rfc822_len - rfc822_headers_len;
936                 free(rfc822);
937
938                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
939                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
940                         "\"7BIT\" " SIZE_T_FMT " %d)", rfc822_body_len, lines);
941
942                 return;
943         }
944
945         /* For messages already stored in RFC822 format, we have to parse. */
946         cprintf("BODYSTRUCTURE ");
947         mime_parser(msg->cm_fields['M'],
948                         NULL,
949                         *imap_fetch_bodystructure_part, /* part */
950                         *imap_fetch_bodystructure_pre,  /* pre-multi */
951                         *imap_fetch_bodystructure_post, /* post-multi */
952                         NULL,
953                         1);     /* don't decode -- we want it as-is */
954 }
955
956
957 /*
958  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
959  * individual message, once it has been selected for output.
960  */
961 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
962         int i;
963         struct CtdlMessage *msg = NULL;
964         int body_loaded = 0;
965
966         /* Don't attempt to fetch bogus messages or UID's */
967         if (seq < 1) return;
968         if (IMAP->msgids[seq-1] < 1L) return;
969
970         buffer_output();
971         cprintf("* %d FETCH (", seq);
972
973         for (i=0; i<num_items; ++i) {
974
975                 /* Fetchable without going to the message store at all */
976                 if (!strcasecmp(itemlist[i], "UID")) {
977                         imap_fetch_uid(seq);
978                 }
979                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
980                         imap_fetch_flags(seq-1);
981                 }
982
983                 /* Potentially fetchable from cache, if the client requests
984                  * stuff from the same message several times in a row.
985                  */
986                 else if (!strcasecmp(itemlist[i], "RFC822")) {
987                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
988                 }
989                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
990                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
991                 }
992                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
993                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
994                 }
995                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
996                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
997                 }
998
999                 /* BODY fetches do their own fetching and caching too. */
1000                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
1001                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
1002                 }
1003                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
1004                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
1005                 }
1006
1007                 /* Otherwise, load the message into memory.
1008                  */
1009                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
1010                         if ((msg != NULL) && (!body_loaded)) {
1011                                 CtdlFreeMessage(msg);   /* need the whole thing */
1012                                 msg = NULL;
1013                         }
1014                         if (msg == NULL) {
1015                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
1016                                 body_loaded = 1;
1017                         }
1018                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
1019                                         itemlist[i], msg);
1020                 }
1021                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1022                         if (msg == NULL) {
1023                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1024                                 body_loaded = 0;
1025                         }
1026                         imap_fetch_envelope(msg);
1027                 }
1028                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1029                         if (msg == NULL) {
1030                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1031                                 body_loaded = 0;
1032                         }
1033                         imap_fetch_internaldate(msg);
1034                 }
1035
1036                 if (i != num_items-1) cprintf(" ");
1037         }
1038
1039         cprintf(")\r\n");
1040         unbuffer_output();
1041         if (msg != NULL) {
1042                 CtdlFreeMessage(msg);
1043         }
1044 }
1045
1046
1047
1048 /*
1049  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1050  * validated and boiled down the request a bit.
1051  */
1052 void imap_do_fetch(int num_items, char **itemlist) {
1053         int i;
1054
1055         if (IMAP->num_msgs > 0) {
1056                 for (i = 0; i < IMAP->num_msgs; ++i) {
1057
1058                         /* Abort the fetch loop if the session breaks.
1059                          * This is important for users who keep mailboxes
1060                          * that are too big *and* are too impatient to
1061                          * let them finish loading.  :)
1062                          */
1063                         if (CC->kill_me) return;
1064
1065                         /* Get any message marked for fetch. */
1066                         if (IMAP->flags[i] & IMAP_SELECTED) {
1067                                 imap_do_fetch_msg(i+1, num_items, itemlist);
1068                         }
1069                 }
1070         }
1071 }
1072
1073
1074
1075 /*
1076  * Back end for imap_handle_macros()
1077  * Note that this function *only* looks at the beginning of the string.  It
1078  * is not a generic search-and-replace function.
1079  */
1080 void imap_macro_replace(char *str, char *find, char *replace) {
1081         char holdbuf[SIZ];
1082         int findlen;
1083
1084         findlen = strlen(find);
1085
1086         if (!strncasecmp(str, find, findlen)) {
1087                 if (str[findlen]==' ') {
1088                         strcpy(holdbuf, &str[findlen+1]);
1089                         strcpy(str, replace);
1090                         strcat(str, " ");
1091                         strcat(str, holdbuf);
1092                 }
1093                 if (str[findlen]==0) {
1094                         strcpy(holdbuf, &str[findlen+1]);
1095                         strcpy(str, replace);
1096                 }
1097         }
1098 }
1099
1100
1101
1102 /*
1103  * Handle macros embedded in FETCH data items.
1104  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1105  * the computer at the other end the trouble of typing a lot of characters?)
1106  */
1107 void imap_handle_macros(char *str) {
1108         int i;
1109         int nest = 0;
1110
1111         for (i=0; str[i]; ++i) {
1112                 if (str[i]=='(') ++nest;
1113                 if (str[i]=='[') ++nest;
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
1121                 if (nest <= 0) {
1122                         imap_macro_replace(&str[i],
1123                                 "ALL",
1124                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1125                         );
1126                         imap_macro_replace(&str[i],
1127                                 "BODY",
1128                                 "BODYSTRUCTURE"
1129                         );
1130                         imap_macro_replace(&str[i],
1131                                 "FAST",
1132                                 "FLAGS INTERNALDATE RFC822.SIZE"
1133                         );
1134                         imap_macro_replace(&str[i],
1135                                 "FULL",
1136                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1137                         );
1138                 }
1139         }
1140 }
1141
1142
1143 /*
1144  * Break out the data items requested, possibly a parenthesized list.
1145  * Returns the number of data items, or -1 if the list is invalid.
1146  * NOTE: this function alters the string it is fed, and uses it as a buffer
1147  * to hold the data for the pointers it returns.
1148  */
1149 int imap_extract_data_items(char **argv, char *items) {
1150         int num_items = 0;
1151         int nest = 0;
1152         int i;
1153         char *start;
1154         long initial_len;
1155
1156         /* Convert all whitespace to ordinary space characters. */
1157         for (i=0; items[i]; ++i) {
1158                 if (isspace(items[i])) items[i]=' ';
1159         }
1160
1161         /* Strip leading and trailing whitespace, then strip leading and
1162          * trailing parentheses if it's a list
1163          */
1164         striplt(items);
1165         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1166                 items[strlen(items)-1] = 0;
1167                 strcpy(items, &items[1]);
1168                 striplt(items);
1169         }
1170
1171         /* Parse any macro data items */
1172         imap_handle_macros(items);
1173
1174         /*
1175          * Now break out the data items.  We throw in one trailing space in
1176          * order to avoid having to break out the last one manually.
1177          */
1178         strcat(items, " ");
1179         start = items;
1180         initial_len = strlen(items);
1181         for (i=0; i<initial_len; ++i) {
1182                 if (items[i]=='(') ++nest;
1183                 if (items[i]=='[') ++nest;
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
1191                 if (nest <= 0) if (items[i]==' ') {
1192                         items[i] = 0;
1193                         argv[num_items++] = start;
1194                         start = &items[i+1];
1195                 }
1196         }
1197
1198         return(num_items);
1199
1200 }
1201
1202
1203 /*
1204  * One particularly hideous aspect of IMAP is that we have to allow the client
1205  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1206  * handles this by setting the IMAP_SELECTED flag for each message specified in
1207  * the ranges/sets, then looping through the message array, outputting messages
1208  * with the flag set.  We don't bother returning an error if an out-of-range
1209  * number is specified (we just return quietly) because any client braindead
1210  * enough to request a bogus message number isn't going to notice the
1211  * difference anyway.
1212  *
1213  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1214  * message included in the specified range.
1215  *
1216  * Set is_uid to 1 to fetch by UID instead of sequence number.
1217  */
1218 void imap_pick_range(char *supplied_range, int is_uid) {
1219         int i;
1220         int num_sets;
1221         int s;
1222         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1223         long lo, hi;
1224         char actual_range[SIZ];
1225         struct citimap *Imap;
1226
1227         /* 
1228          * Handle the "ALL" macro
1229          */
1230         if (!strcasecmp(supplied_range, "ALL")) {
1231                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1232         }
1233         else {
1234                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1235         }
1236
1237         Imap = IMAP;
1238         /*
1239          * Clear out the IMAP_SELECTED flags for all messages.
1240          */
1241         for (i = 0; i < Imap->num_msgs; ++i) {
1242                 Imap->flags[i] = Imap->flags[i] & ~IMAP_SELECTED;
1243         }
1244
1245         /*
1246          * Now set it for all specified messages.
1247          */
1248         num_sets = num_tokens(actual_range, ',');
1249         for (s=0; s<num_sets; ++s) {
1250                 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1251
1252                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1253                 if (num_tokens(setstr, ':') >= 2) {
1254                         extract_token(histr, setstr, 1, ':', sizeof histr);
1255                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1256                 } 
1257                 else {
1258                         safestrncpy(histr, lostr, sizeof histr);
1259                 }
1260                 lo = atol(lostr);
1261                 hi = atol(histr);
1262
1263                 /* Loop through the array, flipping bits where appropriate */
1264                 for (i = 1; i <= Imap->num_msgs; ++i) {
1265                         if (is_uid) {   /* fetch by sequence number */
1266                                 if ( (Imap->msgids[i-1]>=lo)
1267                                    && (Imap->msgids[i-1]<=hi)) {
1268                                         Imap->flags[i-1] |= IMAP_SELECTED;
1269                                 }
1270                         }
1271                         else {          /* fetch by uid */
1272                                 if ( (i>=lo) && (i<=hi)) {
1273                                         Imap->flags[i-1] |= IMAP_SELECTED;
1274                                 }
1275                         }
1276                 }
1277         }
1278
1279 }
1280
1281
1282
1283 /*
1284  * This function is called by the main command loop.
1285  */
1286 void imap_fetch(int num_parms, char *parms[]) {
1287         char items[SIZ];
1288         char *itemlist[512];
1289         int num_items;
1290         int i;
1291
1292         if (num_parms < 4) {
1293                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1294                 return;
1295         }
1296
1297         imap_pick_range(parms[2], 0);
1298
1299         strcpy(items, "");
1300         for (i=3; i<num_parms; ++i) {
1301                 strcat(items, parms[i]);
1302                 if (i < (num_parms-1)) strcat(items, " ");
1303         }
1304
1305         num_items = imap_extract_data_items(itemlist, items);
1306         if (num_items < 1) {
1307                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1308                 return;
1309         }
1310
1311         imap_do_fetch(num_items, itemlist);
1312         cprintf("%s OK FETCH completed\r\n", parms[0]);
1313 }
1314
1315 /*
1316  * This function is called by the main command loop.
1317  */
1318 void imap_uidfetch(int num_parms, char *parms[]) {
1319         char items[SIZ];
1320         char *itemlist[512];
1321         int num_items;
1322         int i;
1323         int have_uid_item = 0;
1324
1325         if (num_parms < 5) {
1326                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1327                 return;
1328         }
1329
1330         imap_pick_range(parms[3], 1);
1331
1332         strcpy(items, "");
1333         for (i=4; i<num_parms; ++i) {
1334                 strcat(items, parms[i]);
1335                 if (i < (num_parms-1)) strcat(items, " ");
1336         }
1337
1338         num_items = imap_extract_data_items(itemlist, items);
1339         if (num_items < 1) {
1340                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1341                 return;
1342         }
1343
1344         /* If the "UID" item was not included, we include it implicitly
1345          * (at the beginning) because this is a UID FETCH command
1346          */
1347         for (i=0; i<num_items; ++i) {
1348                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1349         }
1350         if (have_uid_item == 0) {
1351                 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1352                 ++num_items;
1353                 itemlist[0] = "UID";
1354         }
1355
1356         imap_do_fetch(num_items, itemlist);
1357         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1358 }
1359
1360