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