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