cbc3298cf09d0440677e47931d778133676396d0
[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 = NULL;
528         char section[1024];
529         char partial[1024];
530         int is_partial = 0;
531         char buf[1024];
532         int i;
533         FILE *tmp = NULL;
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         /* Burn the cache if we don't have the same section of the 
550          * same message again.
551          */
552         if (IMAP->cached_body != NULL) {
553                 if ((IMAP->cached_bodymsgnum != msgnum)
554                    || (strcasecmp(IMAP->cached_bodypart, section)) ) {
555                         fclose(IMAP->cached_body);
556                         IMAP->cached_body = NULL;
557                         IMAP->cached_bodymsgnum = (-1);
558                         strcpy(IMAP->cached_bodypart, "");
559                 }
560         }
561
562         /* extract partial */
563         strcpy(partial, item);
564         for (i=0; i<strlen(partial); ++i) {
565                 if (partial[i]=='<') {
566                         strcpy(partial, &partial[i+1]);
567                         is_partial = 1;
568                 }
569         }
570         for (i=0; i<strlen(partial); ++i) {
571                 if (partial[i]=='>') partial[i] = 0;
572         }
573         if (is_partial == 0) strcpy(partial, "");
574         if (strlen(partial) > 0) lprintf(CTDL_DEBUG, "Partial is %s\n", partial);
575
576         if (IMAP->cached_body == NULL) {
577                 tmp = tmpfile();
578                 if (tmp == NULL) {
579                         lprintf(CTDL_CRIT, "Cannot open temp file: %s\n", strerror(errno));
580                         return;
581                 }
582                 msg = CtdlFetchMessage(msgnum);
583         }
584
585         /* Now figure out what the client wants, and get it */
586
587         if (IMAP->cached_body != NULL) {
588                 tmp = IMAP->cached_body;
589         }
590         else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
591                 CtdlRedirectOutput(tmp, -1);
592                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
593                                                 HEADERS_NONE, 0, 1);
594                 CtdlRedirectOutput(NULL, -1);
595         }
596
597         else if (!strcmp(section, "")) {
598                 CtdlRedirectOutput(tmp, -1);
599                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
600                                                 HEADERS_ALL, 0, 1);
601                 CtdlRedirectOutput(NULL, -1);
602         }
603
604         /*
605          * If the client asked for just headers, or just particular header
606          * fields, strip it down.
607          */
608         else if (!strncasecmp(section, "HEADER", 6)) {
609                 CtdlRedirectOutput(tmp, -1);
610                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
611                                                 HEADERS_ONLY, 0, 1);
612                 CtdlRedirectOutput(NULL, -1);
613                 imap_strip_headers(tmp, section);
614         }
615
616         /*
617          * Strip it down if the client asked for everything _except_ headers.
618          */
619         else if (!strncasecmp(section, "TEXT", 4)) {
620                 CtdlRedirectOutput(tmp, -1);
621                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822,
622                                                 HEADERS_NONE, 0, 1);
623                 CtdlRedirectOutput(NULL, -1);
624         }
625
626         /*
627          * Anything else must be a part specifier.
628          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
629          */
630         else {
631                 safestrncpy(imfp.desired_section, section,
632                                 sizeof(imfp.desired_section));
633                 imfp.output_fp = tmp;
634
635                 mime_parser(msg->cm_fields['M'], NULL,
636                                 *imap_load_part, NULL, NULL,
637                                 (void *)&imfp,
638                                 1);
639         }
640
641
642         fseek(tmp, 0L, SEEK_END);
643         bytes_remaining = ftell(tmp);
644
645         if (is_partial == 0) {
646                 rewind(tmp);
647                 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
648         }
649         else {
650                 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
651                 if ((bytes_remaining - pstart) < pbytes) {
652                         pbytes = bytes_remaining - pstart;
653                 }
654                 fseek(tmp, pstart, SEEK_SET);
655                 bytes_remaining = pbytes;
656                 cprintf("BODY[%s]<%ld> {%ld}\r\n",
657                         section, pstart, bytes_remaining);
658         }
659
660         blocksize = sizeof(buf);
661         while (bytes_remaining > 0L) {
662                 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
663                 fread(buf, blocksize, 1, tmp);
664                 client_write(buf, blocksize);
665                 bytes_remaining = bytes_remaining - blocksize;
666         }
667
668         /* Don't close it ... cache it! */
669         /* fclose(tmp); */
670         IMAP->cached_body = tmp;
671         IMAP->cached_bodymsgnum = msgnum;
672         strcpy(IMAP->cached_bodypart, section);
673
674         if (msg != NULL) {
675                 CtdlFreeMessage(msg);
676         }
677
678         /* Mark this message as "seen" *unless* this is a "peek" operation */
679         if (is_peek == 0) {
680                 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
681         }
682 }
683
684 /*
685  * Called immediately before outputting a multipart bodystructure
686  */
687 void imap_fetch_bodystructure_pre(
688                 char *name, char *filename, char *partnum, char *disp,
689                 void *content, char *cbtype, size_t length, char *encoding,
690                 void *cbuserdata
691                 ) {
692
693         cprintf("(");
694 }
695
696
697
698 /*
699  * Called immediately after outputting a multipart bodystructure
700  */
701 void imap_fetch_bodystructure_post(
702                 char *name, char *filename, char *partnum, char *disp,
703                 void *content, char *cbtype, size_t length, char *encoding,
704                 void *cbuserdata
705                 ) {
706
707         char subtype[SIZ];
708
709         cprintf(" ");
710
711         /* disposition */
712         extract_token(subtype, cbtype, 1, '/');
713         imap_strout(subtype);
714
715         /* body language */
716         cprintf(" NIL");
717
718         cprintf(")");
719 }
720
721
722
723 /*
724  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
725  *
726  */
727 void imap_fetch_bodystructure_part(
728                 char *name, char *filename, char *partnum, char *disp,
729                 void *content, char *cbtype, size_t length, char *encoding,
730                 void *cbuserdata
731                 ) {
732
733         int have_cbtype = 0;
734         int have_encoding = 0;
735         int lines = 0;
736         size_t i;
737         char cbmaintype[SIZ];
738         char cbsubtype[SIZ];
739
740         if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
741         if (have_cbtype) {
742                 extract_token(cbmaintype, cbtype, 0, '/');
743                 extract_token(cbsubtype, cbtype, 1, '/');
744         }
745         else {
746                 strcpy(cbmaintype, "TEXT");
747                 strcpy(cbsubtype, "PLAIN");
748         }
749
750         cprintf("(");
751         imap_strout(cbmaintype);
752         cprintf(" ");
753         imap_strout(cbsubtype);
754         cprintf(" ");
755
756         cprintf("(\"CHARSET\" \"US-ASCII\"");
757
758         if (name != NULL) if (strlen(name)>0) {
759                 cprintf(" \"NAME\" ");
760                 imap_strout(name);
761         }
762
763         cprintf(") ");
764
765         cprintf("NIL ");        /* Body ID */
766         cprintf("NIL ");        /* Body description */
767
768         if (encoding != NULL) if (strlen(encoding) > 0)  have_encoding = 1;
769         if (have_encoding) {
770                 imap_strout(encoding);
771         }
772         else {
773                 imap_strout("7BIT");
774         }
775         cprintf(" ");
776
777         /* The next field is the size of the part in bytes. */
778         cprintf("%ld ", (long)length);  /* bytes */
779
780         /* The next field is the number of lines in the part, if and only
781          * if the part is TEXT.  Crispin is a fscking idiot.
782          */
783         if (!strcasecmp(cbmaintype, "TEXT")) {
784                 if (length) for (i=0; i<length; ++i) {
785                         if (((char *)content)[i] == '\n') ++lines;
786                 }
787                 cprintf("%d ", lines);
788         }
789
790         /* More of Crispin being a fscking idiot */
791         if ((!strcasecmp(cbmaintype, "MESSAGE"))
792            && (!strcasecmp(cbsubtype, "RFC822"))) {
793                 /* FIXME
794                      A body type of type MESSAGE and subtype RFC822
795                      contains, immediately after the basic fields, the
796                      envelope structure, body structure, and size in
797                      text lines of the encapsulated message.
798                 */
799         }
800
801         /* MD5 value of body part; we can get away with NIL'ing this */
802         cprintf("NIL ");
803
804         /* Disposition */
805         if (disp == NULL) {
806                 cprintf("NIL");
807         }
808         else if (strlen(disp) == 0) {
809                 cprintf("NIL");
810         }
811         else {
812                 cprintf("(");
813                 imap_strout(disp);
814                 if (filename != NULL) if (strlen(filename)>0) {
815                         cprintf(" (\"FILENAME\" ");
816                         imap_strout(filename);
817                         cprintf(")");
818                 }
819                 cprintf(")");
820         }
821
822         /* Body language (not defined yet) */
823         cprintf(" NIL)");
824 }
825
826
827
828 /*
829  * Spew the BODYSTRUCTURE data for a message.  (Do you need a silencer if
830  * you're going to shoot a MIME?  Do you need a reason to shoot Mark Crispin?
831  * No, and no.)
832  *
833  */
834 void imap_fetch_bodystructure (long msgnum, char *item,
835                 struct CtdlMessage *msg) {
836         FILE *tmp;
837         char buf[1024];
838         long lines = 0L;
839         long start_of_body = 0L;
840         long body_bytes = 0L;
841
842         /* For non-RFC822 (ordinary Citadel) messages, this is short and
843          * sweet...
844          */
845         if (msg->cm_format_type != FMT_RFC822) {
846
847                 /* *sigh* We have to RFC822-format the message just to be able
848                  * to measure it.
849                  */
850                 tmp = tmpfile();
851                 if (tmp == NULL) return;
852                 CtdlRedirectOutput(tmp, -1);
853                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
854                 CtdlRedirectOutput(NULL, -1);
855
856                 rewind(tmp);
857                 while (fgets(buf, sizeof buf, tmp) != NULL) {
858                         ++lines;
859                         if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
860                                 start_of_body = ftell(tmp);
861                         }
862                 }
863                 body_bytes = ftell(tmp) - start_of_body;
864                 fclose(tmp);
865
866                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
867                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
868                         "\"7BIT\" %ld %ld)", body_bytes, lines);
869
870                 return;
871         }
872
873         /* For messages already stored in RFC822 format, we have to parse. */
874         cprintf("BODYSTRUCTURE ");
875         mime_parser(msg->cm_fields['M'],
876                         NULL,
877                         *imap_fetch_bodystructure_part, /* part */
878                         *imap_fetch_bodystructure_pre,  /* pre-multi */
879                         *imap_fetch_bodystructure_post, /* post-multi */
880                         NULL,
881                         1);     /* don't decode -- we want it as-is */
882 }
883
884
885 /*
886  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
887  * individual message, once it has been selected for output.
888  */
889 void imap_do_fetch_msg(int seq,
890                         int num_items, char **itemlist) {
891         int i;
892         struct CtdlMessage *msg = NULL;
893
894         cprintf("* %d FETCH (", seq);
895
896         for (i=0; i<num_items; ++i) {
897
898                 /* Fetchable without going to the message store at all */
899                 if (!strcasecmp(itemlist[i], "UID")) {
900                         imap_fetch_uid(seq);
901                 }
902                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
903                         imap_fetch_flags(seq-1);
904                 }
905
906                 /* Potentially fetchable from cache, if the client requests
907                  * stuff from the same message several times in a row.
908                  */
909                 else if (!strcasecmp(itemlist[i], "RFC822")) {
910                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
911                 }
912                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
913                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
914                 }
915                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
916                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
917                 }
918                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
919                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
920                 }
921
922                 /* BODY fetches do their own fetching and caching too. */
923                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
924                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
925                 }
926                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
927                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
928                 }
929
930                 /* Otherwise, load the message into memory.
931                  */
932                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
933                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
934                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
935                                         itemlist[i], msg);
936                 }
937                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
938                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
939                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
940                 }
941                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
942                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1]);
943                         imap_fetch_internaldate(msg);
944                 }
945
946                 if (i != num_items-1) cprintf(" ");
947         }
948
949         cprintf(")\r\n");
950         if (msg != NULL) {
951                 CtdlFreeMessage(msg);
952         }
953 }
954
955
956
957 /*
958  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
959  * validated and boiled down the request a bit.
960  */
961 void imap_do_fetch(int num_items, char **itemlist) {
962         int i;
963
964         if (IMAP->num_msgs > 0) {
965                 for (i = 0; i < IMAP->num_msgs; ++i) {
966                         if (IMAP->flags[i] & IMAP_SELECTED) {
967                                 imap_do_fetch_msg(i+1, num_items, itemlist);
968                         }
969                 }
970         }
971 }
972
973
974
975 /*
976  * Back end for imap_handle_macros()
977  * Note that this function *only* looks at the beginning of the string.  It
978  * is not a generic search-and-replace function.
979  */
980 void imap_macro_replace(char *str, char *find, char *replace) {
981         char holdbuf[1024];
982
983         if (!strncasecmp(str, find, strlen(find))) {
984                 if (str[strlen(find)]==' ') {
985                         strcpy(holdbuf, &str[strlen(find)+1]);
986                         strcpy(str, replace);
987                         strcat(str, " ");
988                         strcat(str, holdbuf);
989                 }
990                 if (str[strlen(find)]==0) {
991                         strcpy(holdbuf, &str[strlen(find)+1]);
992                         strcpy(str, replace);
993                 }
994         }
995 }
996
997
998
999 /*
1000  * Handle macros embedded in FETCH data items.
1001  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1002  * the computer at the other end the trouble of typing a lot of characters?)
1003  */
1004 void imap_handle_macros(char *str) {
1005         int i;
1006         int nest = 0;
1007
1008         for (i=0; i<strlen(str); ++i) {
1009                 if (str[i]=='(') ++nest;
1010                 if (str[i]=='[') ++nest;
1011                 if (str[i]=='<') ++nest;
1012                 if (str[i]=='{') ++nest;
1013                 if (str[i]==')') --nest;
1014                 if (str[i]==']') --nest;
1015                 if (str[i]=='>') --nest;
1016                 if (str[i]=='}') --nest;
1017
1018                 if (nest <= 0) {
1019                         imap_macro_replace(&str[i],
1020                                 "ALL",
1021                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1022                         );
1023                         imap_macro_replace(&str[i],
1024                                 "BODY",
1025                                 "BODYSTRUCTURE"
1026                         );
1027                         imap_macro_replace(&str[i],
1028                                 "FAST",
1029                                 "FLAGS INTERNALDATE RFC822.SIZE"
1030                         );
1031                         imap_macro_replace(&str[i],
1032                                 "FULL",
1033                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1034                         );
1035                 }
1036         }
1037 }
1038
1039
1040 /*
1041  * Break out the data items requested, possibly a parenthesized list.
1042  * Returns the number of data items, or -1 if the list is invalid.
1043  * NOTE: this function alters the string it is fed, and uses it as a buffer
1044  * to hold the data for the pointers it returns.
1045  */
1046 int imap_extract_data_items(char **argv, char *items) {
1047         int num_items = 0;
1048         int nest = 0;
1049         int i, initial_len;
1050         char *start;
1051
1052         /* Convert all whitespace to ordinary space characters. */
1053         for (i=0; i<strlen(items); ++i) {
1054                 if (isspace(items[i])) items[i]=' ';
1055         }
1056
1057         /* Strip leading and trailing whitespace, then strip leading and
1058          * trailing parentheses if it's a list
1059          */
1060         striplt(items);
1061         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1062                 items[strlen(items)-1] = 0;
1063                 strcpy(items, &items[1]);
1064                 striplt(items);
1065         }
1066
1067         /* Parse any macro data items */
1068         imap_handle_macros(items);
1069
1070         /*
1071          * Now break out the data items.  We throw in one trailing space in
1072          * order to avoid having to break out the last one manually.
1073          */
1074         strcat(items, " ");
1075         start = items;
1076         initial_len = strlen(items);
1077         for (i=0; i<initial_len; ++i) {
1078                 if (items[i]=='(') ++nest;
1079                 if (items[i]=='[') ++nest;
1080                 if (items[i]=='<') ++nest;
1081                 if (items[i]=='{') ++nest;
1082                 if (items[i]==')') --nest;
1083                 if (items[i]==']') --nest;
1084                 if (items[i]=='>') --nest;
1085                 if (items[i]=='}') --nest;
1086
1087                 if (nest <= 0) if (items[i]==' ') {
1088                         items[i] = 0;
1089                         argv[num_items++] = start;
1090                         start = &items[i+1];
1091                 }
1092         }
1093
1094         return(num_items);
1095
1096 }
1097
1098
1099 /*
1100  * One particularly hideous aspect of IMAP is that we have to allow the client
1101  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1102  * handles this by setting the IMAP_SELECTED flag for each message specified in
1103  * the ranges/sets, then looping through the message array, outputting messages
1104  * with the flag set.  We don't bother returning an error if an out-of-range
1105  * number is specified (we just return quietly) because any client braindead
1106  * enough to request a bogus message number isn't going to notice the
1107  * difference anyway.
1108  *
1109  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1110  * message included in the specified range.
1111  *
1112  * Set is_uid to 1 to fetch by UID instead of sequence number.
1113  */
1114 void imap_pick_range(char *supplied_range, int is_uid) {
1115         int i;
1116         int num_sets;
1117         int s;
1118         char setstr[SIZ], lostr[SIZ], histr[SIZ];       /* was 1024 */
1119         int lo, hi;
1120         char actual_range[SIZ];
1121
1122         /* 
1123          * Handle the "ALL" macro
1124          */
1125         if (!strcasecmp(supplied_range, "ALL")) {
1126                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1127         }
1128         else {
1129                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1130         }
1131
1132         /*
1133          * Clear out the IMAP_SELECTED flags for all messages.
1134          */
1135         for (i = 0; i < IMAP->num_msgs; ++i) {
1136                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1137         }
1138
1139         /*
1140          * Now set it for all specified messages.
1141          */
1142         num_sets = num_tokens(actual_range, ',');
1143         for (s=0; s<num_sets; ++s) {
1144                 extract_token(setstr, actual_range, s, ',');
1145
1146                 extract_token(lostr, setstr, 0, ':');
1147                 if (num_tokens(setstr, ':') >= 2) {
1148                         extract_token(histr, setstr, 1, ':');
1149                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
1150                 } 
1151                 else {
1152                         strcpy(histr, lostr);
1153                 }
1154                 lo = atoi(lostr);
1155                 hi = atoi(histr);
1156
1157                 /* Loop through the array, flipping bits where appropriate */
1158                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1159                         if (is_uid) {   /* fetch by sequence number */
1160                                 if ( (IMAP->msgids[i-1]>=lo)
1161                                    && (IMAP->msgids[i-1]<=hi)) {
1162                                         IMAP->flags[i-1] =
1163                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1164                                 }
1165                         }
1166                         else {          /* fetch by uid */
1167                                 if ( (i>=lo) && (i<=hi)) {
1168                                         IMAP->flags[i-1] =
1169                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1170                                 }
1171                         }
1172                 }
1173         }
1174
1175 }
1176
1177
1178
1179 /*
1180  * This function is called by the main command loop.
1181  */
1182 void imap_fetch(int num_parms, char *parms[]) {
1183         char items[SIZ];        /* was 1024 */
1184         char *itemlist[SIZ];
1185         int num_items;
1186         int i;
1187
1188         if (num_parms < 4) {
1189                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1190                 return;
1191         }
1192
1193         imap_pick_range(parms[2], 0);
1194
1195         strcpy(items, "");
1196         for (i=3; i<num_parms; ++i) {
1197                 strcat(items, parms[i]);
1198                 if (i < (num_parms-1)) strcat(items, " ");
1199         }
1200
1201         num_items = imap_extract_data_items(itemlist, items);
1202         if (num_items < 1) {
1203                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1204                 return;
1205         }
1206
1207         imap_do_fetch(num_items, itemlist);
1208         cprintf("%s OK FETCH completed\r\n", parms[0]);
1209 }
1210
1211 /*
1212  * This function is called by the main command loop.
1213  */
1214 void imap_uidfetch(int num_parms, char *parms[]) {
1215         char items[SIZ];        /* was 1024 */
1216         char *itemlist[SIZ];
1217         int num_items;
1218         int i;
1219         int have_uid_item = 0;
1220
1221         if (num_parms < 5) {
1222                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1223                 return;
1224         }
1225
1226         imap_pick_range(parms[3], 1);
1227
1228         strcpy(items, "");
1229         for (i=4; i<num_parms; ++i) {
1230                 strcat(items, parms[i]);
1231                 if (i < (num_parms-1)) strcat(items, " ");
1232         }
1233
1234         num_items = imap_extract_data_items(itemlist, items);
1235         if (num_items < 1) {
1236                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1237                 return;
1238         }
1239
1240         /* If the "UID" item was not included, we include it implicitly
1241          * because this is a UID FETCH command
1242          */
1243         for (i=0; i<num_items; ++i) {
1244                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1245         }
1246         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1247
1248         imap_do_fetch(num_items, itemlist);
1249         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1250 }
1251
1252