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