]> code.citadel.org Git - citadel.git/blob - citadel/imap_fetch.c
* imap_fetch.c: don't fetch the message from disk at all for UID and FLAGS
[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                 /* Fetchable without going to the message store at all */
873                 if (!strcasecmp(itemlist[i], "UID")) {
874                         imap_fetch_uid(seq);
875                 }
876                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
877                         imap_fetch_flags(seq-1);
878                 }
879
880                 /* Potentially fetchable from cache, if the client requests
881                  * stuff from the same message several times in a row.
882                  */
883                 else if (!strcasecmp(itemlist[i], "RFC822")) {
884                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
885                 }
886                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
887                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
888                 }
889                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
890                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
891                 }
892                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
893                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
894                 }
895
896                 /* Otherwise, load the message into memory.
897                  */
898                 msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
899                 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
900                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
901                                         0, msg);
902                 }
903                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
904                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
905                                         1, msg);
906                 }
907                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
908                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
909                                         itemlist[i], msg);
910                 }
911                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
912                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
913                 }
914                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
915                         imap_fetch_internaldate(msg);
916                 }
917
918                 if (i != num_items-1) cprintf(" ");
919         }
920
921         cprintf(")\r\n");
922         if (msg != NULL) {
923                 CtdlFreeMessage(msg);
924         }
925 }
926
927
928
929 /*
930  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
931  * validated and boiled down the request a bit.
932  */
933 void imap_do_fetch(int num_items, char **itemlist) {
934         int i;
935
936         if (IMAP->num_msgs > 0) {
937                 for (i = 0; i < IMAP->num_msgs; ++i) {
938                         if (IMAP->flags[i] & IMAP_SELECTED) {
939                                 imap_do_fetch_msg(i+1, num_items, itemlist);
940                         }
941                 }
942         }
943 }
944
945
946
947 /*
948  * Back end for imap_handle_macros()
949  * Note that this function *only* looks at the beginning of the string.  It
950  * is not a generic search-and-replace function.
951  */
952 void imap_macro_replace(char *str, char *find, char *replace) {
953         char holdbuf[1024];
954
955         if (!strncasecmp(str, find, strlen(find))) {
956                 if (str[strlen(find)]==' ') {
957                         strcpy(holdbuf, &str[strlen(find)+1]);
958                         strcpy(str, replace);
959                         strcat(str, " ");
960                         strcat(str, holdbuf);
961                 }
962                 if (str[strlen(find)]==0) {
963                         strcpy(holdbuf, &str[strlen(find)+1]);
964                         strcpy(str, replace);
965                 }
966         }
967 }
968
969
970
971 /*
972  * Handle macros embedded in FETCH data items.
973  * (What the heck are macros doing in a wire protocol?  Are we trying to save
974  * the computer at the other end the trouble of typing a lot of characters?)
975  */
976 void imap_handle_macros(char *str) {
977         int i;
978         int nest = 0;
979
980         for (i=0; i<strlen(str); ++i) {
981                 if (str[i]=='(') ++nest;
982                 if (str[i]=='[') ++nest;
983                 if (str[i]=='<') ++nest;
984                 if (str[i]=='{') ++nest;
985                 if (str[i]==')') --nest;
986                 if (str[i]==']') --nest;
987                 if (str[i]=='>') --nest;
988                 if (str[i]=='}') --nest;
989
990                 if (nest <= 0) {
991                         imap_macro_replace(&str[i],
992                                 "ALL",
993                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
994                         );
995                         imap_macro_replace(&str[i],
996                                 "BODY",
997                                 "BODYSTRUCTURE"
998                         );
999                         imap_macro_replace(&str[i],
1000                                 "FAST",
1001                                 "FLAGS INTERNALDATE RFC822.SIZE"
1002                         );
1003                         imap_macro_replace(&str[i],
1004                                 "FULL",
1005                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1006                         );
1007                 }
1008         }
1009 }
1010
1011
1012 /*
1013  * Break out the data items requested, possibly a parenthesized list.
1014  * Returns the number of data items, or -1 if the list is invalid.
1015  * NOTE: this function alters the string it is fed, and uses it as a buffer
1016  * to hold the data for the pointers it returns.
1017  */
1018 int imap_extract_data_items(char **argv, char *items) {
1019         int num_items = 0;
1020         int nest = 0;
1021         int i, initial_len;
1022         char *start;
1023
1024         /* Convert all whitespace to ordinary space characters. */
1025         for (i=0; i<strlen(items); ++i) {
1026                 if (isspace(items[i])) items[i]=' ';
1027         }
1028
1029         /* Strip leading and trailing whitespace, then strip leading and
1030          * trailing parentheses if it's a list
1031          */
1032         striplt(items);
1033         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1034                 items[strlen(items)-1] = 0;
1035                 strcpy(items, &items[1]);
1036                 striplt(items);
1037         }
1038
1039         /* Parse any macro data items */
1040         imap_handle_macros(items);
1041
1042         /*
1043          * Now break out the data items.  We throw in one trailing space in
1044          * order to avoid having to break out the last one manually.
1045          */
1046         strcat(items, " ");
1047         start = items;
1048         initial_len = strlen(items);
1049         for (i=0; i<initial_len; ++i) {
1050                 if (items[i]=='(') ++nest;
1051                 if (items[i]=='[') ++nest;
1052                 if (items[i]=='<') ++nest;
1053                 if (items[i]=='{') ++nest;
1054                 if (items[i]==')') --nest;
1055                 if (items[i]==']') --nest;
1056                 if (items[i]=='>') --nest;
1057                 if (items[i]=='}') --nest;
1058
1059                 if (nest <= 0) if (items[i]==' ') {
1060                         items[i] = 0;
1061                         argv[num_items++] = start;
1062                         start = &items[i+1];
1063                 }
1064         }
1065
1066         return(num_items);
1067
1068 }
1069
1070
1071 /*
1072  * One particularly hideous aspect of IMAP is that we have to allow the client
1073  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1074  * handles this by setting the IMAP_SELECTED flag for each message specified in
1075  * the ranges/sets, then looping through the message array, outputting messages
1076  * with the flag set.  We don't bother returning an error if an out-of-range
1077  * number is specified (we just return quietly) because any client braindead
1078  * enough to request a bogus message number isn't going to notice the
1079  * difference anyway.
1080  *
1081  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1082  * message included in the specified range.
1083  *
1084  * Set is_uid to 1 to fetch by UID instead of sequence number.
1085  */
1086 void imap_pick_range(char *supplied_range, int is_uid) {
1087         int i;
1088         int num_sets;
1089         int s;
1090         char setstr[SIZ], lostr[SIZ], histr[SIZ];       /* was 1024 */
1091         int lo, hi;
1092         char actual_range[SIZ];
1093
1094         /* 
1095          * Handle the "ALL" macro
1096          */
1097         if (!strcasecmp(supplied_range, "ALL")) {
1098                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1099         }
1100         else {
1101                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1102         }
1103
1104         /*
1105          * Clear out the IMAP_SELECTED flags for all messages.
1106          */
1107         for (i = 0; i < IMAP->num_msgs; ++i) {
1108                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1109         }
1110
1111         /*
1112          * Now set it for all specified messages.
1113          */
1114         num_sets = num_tokens(actual_range, ',');
1115         for (s=0; s<num_sets; ++s) {
1116                 extract_token(setstr, actual_range, s, ',');
1117
1118                 extract_token(lostr, setstr, 0, ':');
1119                 if (num_tokens(setstr, ':') >= 2) {
1120                         extract_token(histr, setstr, 1, ':');
1121                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1122                 } 
1123                 else {
1124                         strcpy(histr, lostr);
1125                 }
1126                 lo = atoi(lostr);
1127                 hi = atoi(histr);
1128
1129                 /* Loop through the array, flipping bits where appropriate */
1130                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1131                         if (is_uid) {   /* fetch by sequence number */
1132                                 if ( (IMAP->msgids[i-1]>=lo)
1133                                    && (IMAP->msgids[i-1]<=hi)) {
1134                                         IMAP->flags[i-1] =
1135                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1136                                 }
1137                         }
1138                         else {          /* fetch by uid */
1139                                 if ( (i>=lo) && (i<=hi)) {
1140                                         IMAP->flags[i-1] =
1141                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1142                                 }
1143                         }
1144                 }
1145         }
1146
1147 }
1148
1149
1150
1151 /*
1152  * This function is called by the main command loop.
1153  */
1154 void imap_fetch(int num_parms, char *parms[]) {
1155         char items[SIZ];        /* was 1024 */
1156         char *itemlist[SIZ];
1157         int num_items;
1158         int i;
1159
1160         if (num_parms < 4) {
1161                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1162                 return;
1163         }
1164
1165         imap_pick_range(parms[2], 0);
1166
1167         strcpy(items, "");
1168         for (i=3; i<num_parms; ++i) {
1169                 strcat(items, parms[i]);
1170                 if (i < (num_parms-1)) strcat(items, " ");
1171         }
1172
1173         num_items = imap_extract_data_items(itemlist, items);
1174         if (num_items < 1) {
1175                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1176                 return;
1177         }
1178
1179         imap_do_fetch(num_items, itemlist);
1180         cprintf("%s OK FETCH completed\r\n", parms[0]);
1181 }
1182
1183 /*
1184  * This function is called by the main command loop.
1185  */
1186 void imap_uidfetch(int num_parms, char *parms[]) {
1187         char items[SIZ];        /* was 1024 */
1188         char *itemlist[SIZ];
1189         int num_items;
1190         int i;
1191         int have_uid_item = 0;
1192
1193         if (num_parms < 5) {
1194                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1195                 return;
1196         }
1197
1198         imap_pick_range(parms[3], 1);
1199
1200         strcpy(items, "");
1201         for (i=4; i<num_parms; ++i) {
1202                 strcat(items, parms[i]);
1203                 if (i < (num_parms-1)) strcat(items, " ");
1204         }
1205
1206         num_items = imap_extract_data_items(itemlist, items);
1207         if (num_items < 1) {
1208                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1209                 return;
1210         }
1211
1212         /* If the "UID" item was not included, we include it implicitly
1213          * because this is a UID FETCH command
1214          */
1215         for (i=0; i<num_items; ++i) {
1216                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1217         }
1218         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1219
1220         imap_do_fetch(num_items, itemlist);
1221         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1222 }
1223
1224