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