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