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