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