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