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