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