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