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