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