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