]> code.citadel.org Git - citadel.git/blob - citadel/imap_fetch.c
* Minor and/or cosmetic changes made during x64 troubleshooting
[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, int num_items, char **itemlist) {
940         int i;
941         struct CtdlMessage *msg = NULL;
942
943         lprintf(CTDL_DEBUG, "imap_do_fetch_msg(%d, %d)\n", seq, num_items);
944         for (i=0; i<num_items; ++i) {
945                 lprintf(CTDL_DEBUG, "                  %s\n", itemlist[i]);
946         }
947
948         cprintf("* %d FETCH (", seq);
949
950         for (i=0; i<num_items; ++i) {
951
952                 /* Fetchable without going to the message store at all */
953                 if (!strcasecmp(itemlist[i], "UID")) {
954                         imap_fetch_uid(seq);
955                 }
956                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
957                         imap_fetch_flags(seq-1);
958                 }
959
960                 /* Potentially fetchable from cache, if the client requests
961                  * stuff from the same message several times in a row.
962                  */
963                 else if (!strcasecmp(itemlist[i], "RFC822")) {
964                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
965                 }
966                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
967                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
968                 }
969                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
970                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
971                 }
972                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
973                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
974                 }
975
976                 /* BODY fetches do their own fetching and caching too. */
977                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
978                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
979                 }
980                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
981                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
982                 }
983
984                 /* Otherwise, load the message into memory.
985                  */
986                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
987                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
988                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
989                                         itemlist[i], msg);
990                 }
991                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
992                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
993                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
994                 }
995                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
996                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
997                         imap_fetch_internaldate(msg);
998                 }
999
1000                 if (i != num_items-1) cprintf(" ");
1001         }
1002
1003         cprintf(")\r\n");
1004         if (msg != NULL) {
1005                 CtdlFreeMessage(msg);
1006         }
1007 }
1008
1009
1010
1011 /*
1012  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1013  * validated and boiled down the request a bit.
1014  */
1015 void imap_do_fetch(int num_items, char **itemlist) {
1016         int i;
1017
1018         if (IMAP->num_msgs > 0) {
1019                 for (i = 0; i < IMAP->num_msgs; ++i) {
1020                         if (IMAP->flags[i] & IMAP_SELECTED) {
1021                                 imap_do_fetch_msg(i+1, num_items, itemlist);
1022                         }
1023                 }
1024         }
1025 }
1026
1027
1028
1029 /*
1030  * Back end for imap_handle_macros()
1031  * Note that this function *only* looks at the beginning of the string.  It
1032  * is not a generic search-and-replace function.
1033  */
1034 void imap_macro_replace(char *str, char *find, char *replace) {
1035         char holdbuf[SIZ];
1036
1037         if (!strncasecmp(str, find, strlen(find))) {
1038                 if (str[strlen(find)]==' ') {
1039                         strcpy(holdbuf, &str[strlen(find)+1]);
1040                         strcpy(str, replace);
1041                         strcat(str, " ");
1042                         strcat(str, holdbuf);
1043                 }
1044                 if (str[strlen(find)]==0) {
1045                         strcpy(holdbuf, &str[strlen(find)+1]);
1046                         strcpy(str, replace);
1047                 }
1048         }
1049 }
1050
1051
1052
1053 /*
1054  * Handle macros embedded in FETCH data items.
1055  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1056  * the computer at the other end the trouble of typing a lot of characters?)
1057  */
1058 void imap_handle_macros(char *str) {
1059         int i;
1060         int nest = 0;
1061
1062         for (i=0; i<strlen(str); ++i) {
1063                 if (str[i]=='(') ++nest;
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
1072                 if (nest <= 0) {
1073                         imap_macro_replace(&str[i],
1074                                 "ALL",
1075                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1076                         );
1077                         imap_macro_replace(&str[i],
1078                                 "BODY",
1079                                 "BODYSTRUCTURE"
1080                         );
1081                         imap_macro_replace(&str[i],
1082                                 "FAST",
1083                                 "FLAGS INTERNALDATE RFC822.SIZE"
1084                         );
1085                         imap_macro_replace(&str[i],
1086                                 "FULL",
1087                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1088                         );
1089                 }
1090         }
1091 }
1092
1093
1094 /*
1095  * Break out the data items requested, possibly a parenthesized list.
1096  * Returns the number of data items, or -1 if the list is invalid.
1097  * NOTE: this function alters the string it is fed, and uses it as a buffer
1098  * to hold the data for the pointers it returns.
1099  */
1100 int imap_extract_data_items(char **argv, char *items) {
1101         int num_items = 0;
1102         int nest = 0;
1103         int i, initial_len;
1104         char *start;
1105
1106         /* Convert all whitespace to ordinary space characters. */
1107         for (i=0; i<strlen(items); ++i) {
1108                 if (isspace(items[i])) items[i]=' ';
1109         }
1110
1111         /* Strip leading and trailing whitespace, then strip leading and
1112          * trailing parentheses if it's a list
1113          */
1114         striplt(items);
1115         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1116                 items[strlen(items)-1] = 0;
1117                 strcpy(items, &items[1]);
1118                 striplt(items);
1119         }
1120
1121         /* Parse any macro data items */
1122         imap_handle_macros(items);
1123
1124         /*
1125          * Now break out the data items.  We throw in one trailing space in
1126          * order to avoid having to break out the last one manually.
1127          */
1128         strcat(items, " ");
1129         start = items;
1130         initial_len = strlen(items);
1131         for (i=0; i<initial_len; ++i) {
1132                 if (items[i]=='(') ++nest;
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
1141                 if (nest <= 0) if (items[i]==' ') {
1142                         items[i] = 0;
1143                         argv[num_items++] = start;
1144                         start = &items[i+1];
1145                 }
1146         }
1147
1148         return(num_items);
1149
1150 }
1151
1152
1153 /*
1154  * One particularly hideous aspect of IMAP is that we have to allow the client
1155  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1156  * handles this by setting the IMAP_SELECTED flag for each message specified in
1157  * the ranges/sets, then looping through the message array, outputting messages
1158  * with the flag set.  We don't bother returning an error if an out-of-range
1159  * number is specified (we just return quietly) because any client braindead
1160  * enough to request a bogus message number isn't going to notice the
1161  * difference anyway.
1162  *
1163  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1164  * message included in the specified range.
1165  *
1166  * Set is_uid to 1 to fetch by UID instead of sequence number.
1167  */
1168 void imap_pick_range(char *supplied_range, int is_uid) {
1169         int i;
1170         int num_sets;
1171         int s;
1172         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1173         long lo, hi;
1174         char actual_range[SIZ];
1175
1176         /* 
1177          * Handle the "ALL" macro
1178          */
1179         if (!strcasecmp(supplied_range, "ALL")) {
1180                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1181         }
1182         else {
1183                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1184         }
1185
1186         /*
1187          * Clear out the IMAP_SELECTED flags for all messages.
1188          */
1189         for (i = 0; i < IMAP->num_msgs; ++i) {
1190                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1191         }
1192
1193         /*
1194          * Now set it for all specified messages.
1195          */
1196         num_sets = num_tokens(actual_range, ',');
1197         for (s=0; s<num_sets; ++s) {
1198                 extract_token(setstr, actual_range, s, ',');
1199
1200                 extract_token(lostr, setstr, 0, ':');
1201                 if (num_tokens(setstr, ':') >= 2) {
1202                         extract_token(histr, setstr, 1, ':');
1203                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1204                 } 
1205                 else {
1206                         strcpy(histr, lostr);
1207                 }
1208                 lo = atol(lostr);
1209                 hi = atol(histr);
1210
1211                 /* Loop through the array, flipping bits where appropriate */
1212                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1213                         if (is_uid) {   /* fetch by sequence number */
1214                                 if ( (IMAP->msgids[i-1]>=lo)
1215                                    && (IMAP->msgids[i-1]<=hi)) {
1216                                         IMAP->flags[i-1] =
1217                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1218                                 }
1219                         }
1220                         else {          /* fetch by uid */
1221                                 if ( (i>=lo) && (i<=hi)) {
1222                                         IMAP->flags[i-1] =
1223                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1224                                 }
1225                         }
1226                 }
1227         }
1228
1229 }
1230
1231
1232
1233 /*
1234  * This function is called by the main command loop.
1235  */
1236 void imap_fetch(int num_parms, char *parms[]) {
1237         char items[SIZ];
1238         char *itemlist[SIZ];
1239         int num_items;
1240         int i;
1241
1242         if (num_parms < 4) {
1243                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1244                 return;
1245         }
1246
1247         imap_pick_range(parms[2], 0);
1248
1249         strcpy(items, "");
1250         for (i=3; i<num_parms; ++i) {
1251                 strcat(items, parms[i]);
1252                 if (i < (num_parms-1)) strcat(items, " ");
1253         }
1254
1255         num_items = imap_extract_data_items(itemlist, items);
1256         if (num_items < 1) {
1257                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1258                 return;
1259         }
1260
1261         imap_do_fetch(num_items, itemlist);
1262         cprintf("%s OK FETCH completed\r\n", parms[0]);
1263 }
1264
1265 /*
1266  * This function is called by the main command loop.
1267  */
1268 void imap_uidfetch(int num_parms, char *parms[]) {
1269         char items[SIZ];
1270         char *itemlist[SIZ];
1271         int num_items;
1272         int i;
1273         int have_uid_item = 0;
1274
1275         if (num_parms < 5) {
1276                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1277                 return;
1278         }
1279
1280         imap_pick_range(parms[3], 1);
1281
1282         strcpy(items, "");
1283         for (i=4; i<num_parms; ++i) {
1284                 strcat(items, parms[i]);
1285                 if (i < (num_parms-1)) strcat(items, " ");
1286         }
1287
1288         num_items = imap_extract_data_items(itemlist, items);
1289         if (num_items < 1) {
1290                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1291                 return;
1292         }
1293
1294         /* If the "UID" item was not included, we include it implicitly
1295          * because this is a UID FETCH command
1296          */
1297         for (i=0; i<num_items; ++i) {
1298                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1299         }
1300         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1301
1302         imap_do_fetch(num_items, itemlist);
1303         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1304 }
1305
1306