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