* strlen holy war: loops. in loops it's very evil. the easy ones go away now.
[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; !IsEmptyStr(&which_fields[i]); ++i) {
514                 if (which_fields[i]=='(')
515                         strcpy(which_fields, &which_fields[i+1]);
516         }
517         for (i=0; !IsEmptyStr(&which_fields[i]); ++i) {
518                 if (which_fields[i]==')') {
519                         which_fields[i] = 0;
520                         break;
521                 }
522         }
523         num_parms = imap_parameterize(parms, which_fields);
524
525         boiled_headers = malloc(CC->redirect_alloc);
526         strcpy(boiled_headers, "");
527
528         ptr = CC->redirect_buffer;
529         ok = 0;
530         while ( (done_headers == 0) && (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) ) {
531                 if (!isspace(buf[0])) {
532                         ok = 0;
533                         if (doing_headers == 0) ok = 1;
534                         else {
535                                 if (headers_not) ok = 1;
536                                 else ok = 0;
537                                 for (i=0; i<num_parms; ++i) {
538                                         if ( (!strncasecmp(buf, parms[i],
539                                            strlen(parms[i]))) &&
540                                            (buf[strlen(parms[i])]==':') ) {
541                                                 if (headers_not) ok = 0;
542                                                 else ok = 1;
543                                         }
544                                 }
545                         }
546                 }
547
548                 if (ok) {
549                         strcat(boiled_headers, buf);
550                         strcat(boiled_headers, "\r\n");
551                 }
552
553                 if (IsEmptyStr(buf)) done_headers = 1;
554                 if (buf[0]=='\r') done_headers = 1;
555                 if (buf[0]=='\n') done_headers = 1;
556         }
557
558         strcat(boiled_headers, "\r\n");
559
560         /* Now save it back (it'll always be smaller) */
561         strcpy(CC->redirect_buffer, boiled_headers);
562         CC->redirect_len = strlen(boiled_headers);
563
564         free(which_fields);
565         free(boiled_headers);
566 }
567
568
569 /*
570  * Implements the BODY and BODY.PEEK fetch items
571  */
572 void imap_fetch_body(long msgnum, char *item, int is_peek) {
573         struct CtdlMessage *msg = NULL;
574         char section[SIZ];
575         char partial[SIZ];
576         int is_partial = 0;
577         size_t pstart, pbytes;
578         int loading_body_now = 0;
579         int need_body = 1;
580         int burn_the_cache = 0;
581
582         /* extract section */
583         safestrncpy(section, item, sizeof section);
584         if (strchr(section, '[') != NULL) {
585                 stripallbut(section, '[', ']');
586         }
587         lprintf(CTDL_DEBUG, "Section is: %s%s\n", 
588                 section, 
589                 IsEmptyStr(section) ? "(empty)" : "");
590         if (!strncasecmp(section, "HEADER", 6)) {
591                 need_body = 0;
592         }
593
594         /* Burn the cache if we don't have the same section of the 
595          * same message again.
596          */
597         if (IMAP->cached_body != NULL) {
598                 if (IMAP->cached_bodymsgnum != msgnum) {
599                         burn_the_cache = 1;
600                 }
601                 else if ( (!IMAP->cached_body_withbody) && (need_body) ) {
602                         burn_the_cache = 1;
603                 }
604                 else if (strcasecmp(IMAP->cached_bodypart, section)) {
605                         burn_the_cache = 1;
606                 }
607                 if (burn_the_cache) {
608                         /* Yup, go ahead and burn the cache. */
609                         free(IMAP->cached_body);
610                         IMAP->cached_body_len = 0;
611                         IMAP->cached_body = NULL;
612                         IMAP->cached_bodymsgnum = (-1);
613                         strcpy(IMAP->cached_bodypart, "");
614                 }
615         }
616
617         /* extract partial */
618         safestrncpy(partial, item, sizeof partial);
619         if (strchr(partial, '<') != NULL) {
620                 stripallbut(partial, '<', '>');
621                 is_partial = 1;
622         }
623         if (is_partial == 0) strcpy(partial, "");
624         /* if (!IsEmptyStr(partial)) lprintf(CTDL_DEBUG, "Partial is %s\n", partial); */
625
626         if (IMAP->cached_body == NULL) {
627                 CC->redirect_buffer = malloc(SIZ);
628                 CC->redirect_len = 0;
629                 CC->redirect_alloc = SIZ;
630                 loading_body_now = 1;
631                 msg = CtdlFetchMessage(msgnum, (need_body ? 1 : 0));
632         }
633
634         /* Now figure out what the client wants, and get it */
635
636         if (!loading_body_now) {
637                 /* What we want is already in memory */
638         }
639
640         else if ( (!strcmp(section, "1")) && (msg->cm_format_type != 4) ) {
641                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
642         }
643
644         else if (!strcmp(section, "")) {
645                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1);
646         }
647
648         /*
649          * If the client asked for just headers, or just particular header
650          * fields, strip it down.
651          */
652         else if (!strncasecmp(section, "HEADER", 6)) {
653                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ONLY, 0, 1);
654                 imap_strip_headers(section);
655         }
656
657         /*
658          * Strip it down if the client asked for everything _except_ headers.
659          */
660         else if (!strncasecmp(section, "TEXT", 4)) {
661                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_NONE, 0, 1);
662         }
663
664         /*
665          * Anything else must be a part specifier.
666          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
667          */
668         else {
669                 mime_parser(msg->cm_fields['M'], NULL,
670                                 *imap_load_part, NULL, NULL,
671                                 section,
672                                 1);
673         }
674
675         if (loading_body_now) {
676                 IMAP->cached_body = CC->redirect_buffer;
677                 IMAP->cached_body_len = CC->redirect_len;
678                 IMAP->cached_bodymsgnum = msgnum;
679                 IMAP->cached_body_withbody = need_body;
680                 strcpy(IMAP->cached_bodypart, section);
681                 CC->redirect_buffer = NULL;
682                 CC->redirect_len = 0;
683                 CC->redirect_alloc = 0;
684         }
685
686         if (is_partial == 0) {
687                 cprintf("BODY[%s] {%d}\r\n", section, IMAP->cached_body_len);
688                 pstart = 0;
689                 pbytes = IMAP->cached_body_len;
690         }
691         else {
692                 sscanf(partial, "%d.%d", &pstart, &pbytes);
693                 if (pbytes > (IMAP->cached_body_len - pstart)) {
694                         pbytes = IMAP->cached_body_len - pstart;
695                 }
696                 cprintf("BODY[%s]<%d> {%d}\r\n", section, pstart, pbytes);
697         }
698
699         /* Here we go -- output it */
700         client_write(&IMAP->cached_body[pstart], pbytes);
701
702         if (msg != NULL) {
703                 CtdlFreeMessage(msg);
704         }
705
706         /* Mark this message as "seen" *unless* this is a "peek" operation */
707         if (is_peek == 0) {
708                 CtdlSetSeen(&msgnum, 1, 1, ctdlsetseen_seen, NULL, NULL);
709         }
710 }
711
712 /*
713  * Called immediately before outputting a multipart bodystructure
714  */
715 void imap_fetch_bodystructure_pre(
716                 char *name, char *filename, char *partnum, char *disp,
717                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
718                 void *cbuserdata
719                 ) {
720
721         cprintf("(");
722 }
723
724
725
726 /*
727  * Called immediately after outputting a multipart bodystructure
728  */
729 void imap_fetch_bodystructure_post(
730                 char *name, char *filename, char *partnum, char *disp,
731                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
732                 void *cbuserdata
733                 ) {
734
735         char subtype[128];
736
737         cprintf(" ");
738
739         /* disposition */
740         extract_token(subtype, cbtype, 1, '/', sizeof subtype);
741         imap_strout(subtype);
742
743         /* body language */
744         cprintf(" NIL");
745
746         cprintf(")");
747 }
748
749
750
751 /*
752  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
753  *
754  */
755 void imap_fetch_bodystructure_part(
756                 char *name, char *filename, char *partnum, char *disp,
757                 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
758                 void *cbuserdata
759                 ) {
760
761         int have_cbtype = 0;
762         int have_encoding = 0;
763         int lines = 0;
764         size_t i;
765         char cbmaintype[128];
766         char cbsubtype[128];
767
768         if (cbtype != NULL) if (!IsEmptyStr(cbtype)) have_cbtype = 1;
769         if (have_cbtype) {
770                 extract_token(cbmaintype, cbtype, 0, '/', sizeof cbmaintype);
771                 extract_token(cbsubtype, cbtype, 1, '/', sizeof cbsubtype);
772         }
773         else {
774                 strcpy(cbmaintype, "TEXT");
775                 strcpy(cbsubtype, "PLAIN");
776         }
777
778         cprintf("(");
779         imap_strout(cbmaintype);
780         cprintf(" ");
781         imap_strout(cbsubtype);
782         cprintf(" ");
783
784         if (cbcharset == NULL) {
785                 cprintf("(\"CHARSET\" \"US-ASCII\"");
786         }
787         else if (IsEmptyStr(cbcharset)) {
788                 cprintf("(\"CHARSET\" \"US-ASCII\"");
789         }
790         else {
791                 cprintf("(\"CHARSET\" ");
792                 imap_strout(cbcharset);
793         }
794
795         if (name != NULL) if (!IsEmptyStr(name)) {
796                 cprintf(" \"NAME\" ");
797                 imap_strout(name);
798         }
799
800         cprintf(") ");
801
802         cprintf("NIL ");        /* Body ID */
803         cprintf("NIL ");        /* Body description */
804
805         if (encoding != NULL) if (!IsEmptyStr(encoding))  have_encoding = 1;
806         if (have_encoding) {
807                 imap_strout(encoding);
808         }
809         else {
810                 imap_strout("7BIT");
811         }
812         cprintf(" ");
813
814         /* The next field is the size of the part in bytes. */
815         cprintf("%ld ", (long)length);  /* bytes */
816
817         /* The next field is the number of lines in the part, if and only
818          * if the part is TEXT.  More gratuitous complexity.
819          */
820         if (!strcasecmp(cbmaintype, "TEXT")) {
821                 if (length) for (i=0; i<length; ++i) {
822                         if (((char *)content)[i] == '\n') ++lines;
823                 }
824                 cprintf("%d ", lines);
825         }
826
827         /* More gratuitous complexity */
828         if ((!strcasecmp(cbmaintype, "MESSAGE"))
829            && (!strcasecmp(cbsubtype, "RFC822"))) {
830                 /* FIXME
831                      A body type of type MESSAGE and subtype RFC822
832                      contains, immediately after the basic fields, the
833                      envelope structure, body structure, and size in
834                      text lines of the encapsulated message.
835                 */
836         }
837
838         /* MD5 value of body part; we can get away with NIL'ing this */
839         cprintf("NIL ");
840
841         /* Disposition */
842         if (disp == NULL) {
843                 cprintf("NIL");
844         }
845         else if (IsEmptyStr(disp)) {
846                 cprintf("NIL");
847         }
848         else {
849                 cprintf("(");
850                 imap_strout(disp);
851                 if (filename != NULL) if (!IsEmptyStr(filename)) {
852                         cprintf(" (\"FILENAME\" ");
853                         imap_strout(filename);
854                         cprintf(")");
855                 }
856                 cprintf(")");
857         }
858
859         /* Body language (not defined yet) */
860         cprintf(" NIL)");
861 }
862
863
864
865 /*
866  * Spew the BODYSTRUCTURE data for a message.
867  *
868  */
869 void imap_fetch_bodystructure (long msgnum, char *item,
870                 struct CtdlMessage *msg) {
871         char *rfc822 = NULL;
872         char *rfc822_body = NULL;
873         size_t rfc822_len;
874         size_t rfc822_headers_len;
875         size_t rfc822_body_len;
876         char *ptr = NULL;
877         char buf[SIZ];
878         int lines = 0;
879
880         /* Handle NULL message gracefully */
881         if (msg == NULL) {
882                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
883                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
884                         "\"7BIT\" 0 0)");
885                 return;
886         }
887
888         /* For non-RFC822 (ordinary Citadel) messages, this is short and
889          * sweet...
890          */
891         if (msg->cm_format_type != FMT_RFC822) {
892
893                 /* *sigh* We have to RFC822-format the message just to be able
894                  * to measure it.  FIXME use smi cached fields if possible
895                  */
896
897                 CC->redirect_buffer = malloc(SIZ);
898                 CC->redirect_len = 0;
899                 CC->redirect_alloc = SIZ;
900                 CtdlOutputPreLoadedMsg(msg, MT_RFC822, 0, 0, 1);
901                 rfc822 = CC->redirect_buffer;
902                 rfc822_len = CC->redirect_len;
903                 CC->redirect_buffer = NULL;
904                 CC->redirect_len = 0;
905                 CC->redirect_alloc = 0;
906
907                 ptr = rfc822;
908                 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
909                         ++lines;
910                         if ((IsEmptyStr(buf)) && (rfc822_body == NULL)) {
911                                 rfc822_body = ptr;
912                         }
913                 }
914
915                 rfc822_headers_len = rfc822_body - rfc822;
916                 rfc822_body_len = rfc822_len - rfc822_headers_len;
917                 free(rfc822);
918
919                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
920                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
921                         "\"7BIT\" %d %d)", rfc822_body_len, lines);
922
923                 return;
924         }
925
926         /* For messages already stored in RFC822 format, we have to parse. */
927         cprintf("BODYSTRUCTURE ");
928         mime_parser(msg->cm_fields['M'],
929                         NULL,
930                         *imap_fetch_bodystructure_part, /* part */
931                         *imap_fetch_bodystructure_pre,  /* pre-multi */
932                         *imap_fetch_bodystructure_post, /* post-multi */
933                         NULL,
934                         1);     /* don't decode -- we want it as-is */
935 }
936
937
938 /*
939  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
940  * individual message, once it has been selected for output.
941  */
942 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
943         int i;
944         struct CtdlMessage *msg = NULL;
945         int body_loaded = 0;
946
947         /* Don't attempt to fetch bogus messages or UID's */
948         if (seq < 1) return;
949         if (IMAP->msgids[seq-1] < 1L) return;
950
951         buffer_output();
952         cprintf("* %d FETCH (", seq);
953
954         for (i=0; i<num_items; ++i) {
955
956                 /* Fetchable without going to the message store at all */
957                 if (!strcasecmp(itemlist[i], "UID")) {
958                         imap_fetch_uid(seq);
959                 }
960                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
961                         imap_fetch_flags(seq-1);
962                 }
963
964                 /* Potentially fetchable from cache, if the client requests
965                  * stuff from the same message several times in a row.
966                  */
967                 else if (!strcasecmp(itemlist[i], "RFC822")) {
968                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
969                 }
970                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
971                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
972                 }
973                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
974                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
975                 }
976                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
977                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
978                 }
979
980                 /* BODY fetches do their own fetching and caching too. */
981                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
982                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
983                 }
984                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
985                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
986                 }
987
988                 /* Otherwise, load the message into memory.
989                  */
990                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
991                         if ((msg != NULL) && (!body_loaded)) {
992                                 CtdlFreeMessage(msg);   /* need the whole thing */
993                                 msg = NULL;
994                         }
995                         if (msg == NULL) {
996                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
997                                 body_loaded = 1;
998                         }
999                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
1000                                         itemlist[i], msg);
1001                 }
1002                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
1003                         if (msg == NULL) {
1004                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1005                                 body_loaded = 0;
1006                         }
1007                         imap_fetch_envelope(msg);
1008                 }
1009                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
1010                         if (msg == NULL) {
1011                                 msg = CtdlFetchMessage(IMAP->msgids[seq-1], 0);
1012                                 body_loaded = 0;
1013                         }
1014                         imap_fetch_internaldate(msg);
1015                 }
1016
1017                 if (i != num_items-1) cprintf(" ");
1018         }
1019
1020         cprintf(")\r\n");
1021         unbuffer_output();
1022         if (msg != NULL) {
1023                 CtdlFreeMessage(msg);
1024         }
1025 }
1026
1027
1028
1029 /*
1030  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
1031  * validated and boiled down the request a bit.
1032  */
1033 void imap_do_fetch(int num_items, char **itemlist) {
1034         int i;
1035
1036         if (IMAP->num_msgs > 0) {
1037                 for (i = 0; i < IMAP->num_msgs; ++i) {
1038
1039                         /* Abort the fetch loop if the session breaks.
1040                          * This is important for users who keep mailboxes
1041                          * that are too big *and* are too impatient to
1042                          * let them finish loading.  :)
1043                          */
1044                         if (CC->kill_me) return;
1045
1046                         /* Get any message marked for fetch. */
1047                         if (IMAP->flags[i] & IMAP_SELECTED) {
1048                                 imap_do_fetch_msg(i+1, num_items, itemlist);
1049                         }
1050                 }
1051         }
1052 }
1053
1054
1055
1056 /*
1057  * Back end for imap_handle_macros()
1058  * Note that this function *only* looks at the beginning of the string.  It
1059  * is not a generic search-and-replace function.
1060  */
1061 void imap_macro_replace(char *str, char *find, char *replace) {
1062         char holdbuf[SIZ];
1063
1064         if (!strncasecmp(str, find, strlen(find))) {
1065                 if (str[strlen(find)]==' ') {
1066                         strcpy(holdbuf, &str[strlen(find)+1]);
1067                         strcpy(str, replace);
1068                         strcat(str, " ");
1069                         strcat(str, holdbuf);
1070                 }
1071                 if (str[strlen(find)]==0) {
1072                         strcpy(holdbuf, &str[strlen(find)+1]);
1073                         strcpy(str, replace);
1074                 }
1075         }
1076 }
1077
1078
1079
1080 /*
1081  * Handle macros embedded in FETCH data items.
1082  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1083  * the computer at the other end the trouble of typing a lot of characters?)
1084  */
1085 void imap_handle_macros(char *str) {
1086         int i;
1087         int nest = 0;
1088
1089         for (i=0; !IsEmptyStr(&str[i]); ++i) {
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                 if (str[i]=='>') --nest;
1097                 if (str[i]=='}') --nest;
1098
1099                 if (nest <= 0) {
1100                         imap_macro_replace(&str[i],
1101                                 "ALL",
1102                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1103                         );
1104                         imap_macro_replace(&str[i],
1105                                 "BODY",
1106                                 "BODYSTRUCTURE"
1107                         );
1108                         imap_macro_replace(&str[i],
1109                                 "FAST",
1110                                 "FLAGS INTERNALDATE RFC822.SIZE"
1111                         );
1112                         imap_macro_replace(&str[i],
1113                                 "FULL",
1114                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1115                         );
1116                 }
1117         }
1118 }
1119
1120
1121 /*
1122  * Break out the data items requested, possibly a parenthesized list.
1123  * Returns the number of data items, or -1 if the list is invalid.
1124  * NOTE: this function alters the string it is fed, and uses it as a buffer
1125  * to hold the data for the pointers it returns.
1126  */
1127 int imap_extract_data_items(char **argv, char *items) {
1128         int num_items = 0;
1129         int nest = 0;
1130         int i;
1131         char *start;
1132
1133         /* Convert all whitespace to ordinary space characters. */
1134         for (i=0; !IsEmptyStr(&items[i]); ++i) {
1135                 if (isspace(items[i])) items[i]=' ';
1136         }
1137
1138         /* Strip leading and trailing whitespace, then strip leading and
1139          * trailing parentheses if it's a list
1140          */
1141         striplt(items);
1142         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1143                 items[strlen(items)-1] = 0;
1144                 strcpy(items, &items[1]);
1145                 striplt(items);
1146         }
1147
1148         /* Parse any macro data items */
1149         imap_handle_macros(items);
1150
1151         /*
1152          * Now break out the data items.  We throw in one trailing space in
1153          * order to avoid having to break out the last one manually.
1154          */
1155         strcat(items, " ");
1156         start = items;
1157         initial_len = strlen(items);
1158         for (i=0; i<initial_len; ++i) {
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                 if (items[i]=='>') --nest;
1166                 if (items[i]=='}') --nest;
1167
1168                 if (nest <= 0) if (items[i]==' ') {
1169                         items[i] = 0;
1170                         argv[num_items++] = start;
1171                         start = &items[i+1];
1172                 }
1173         }
1174
1175         return(num_items);
1176
1177 }
1178
1179
1180 /*
1181  * One particularly hideous aspect of IMAP is that we have to allow the client
1182  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1183  * handles this by setting the IMAP_SELECTED flag for each message specified in
1184  * the ranges/sets, then looping through the message array, outputting messages
1185  * with the flag set.  We don't bother returning an error if an out-of-range
1186  * number is specified (we just return quietly) because any client braindead
1187  * enough to request a bogus message number isn't going to notice the
1188  * difference anyway.
1189  *
1190  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1191  * message included in the specified range.
1192  *
1193  * Set is_uid to 1 to fetch by UID instead of sequence number.
1194  */
1195 void imap_pick_range(char *supplied_range, int is_uid) {
1196         int i;
1197         int num_sets;
1198         int s;
1199         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1200         long lo, hi;
1201         char actual_range[SIZ];
1202
1203         /* 
1204          * Handle the "ALL" macro
1205          */
1206         if (!strcasecmp(supplied_range, "ALL")) {
1207                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1208         }
1209         else {
1210                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1211         }
1212
1213         /*
1214          * Clear out the IMAP_SELECTED flags for all messages.
1215          */
1216         for (i = 0; i < IMAP->num_msgs; ++i) {
1217                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1218         }
1219
1220         /*
1221          * Now set it for all specified messages.
1222          */
1223         num_sets = num_tokens(actual_range, ',');
1224         for (s=0; s<num_sets; ++s) {
1225                 extract_token(setstr, actual_range, s, ',', sizeof setstr);
1226
1227                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
1228                 if (num_tokens(setstr, ':') >= 2) {
1229                         extract_token(histr, setstr, 1, ':', sizeof histr);
1230                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1231                 } 
1232                 else {
1233                         safestrncpy(histr, lostr, sizeof histr);
1234                 }
1235                 lo = atol(lostr);
1236                 hi = atol(histr);
1237
1238                 /* Loop through the array, flipping bits where appropriate */
1239                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1240                         if (is_uid) {   /* fetch by sequence number */
1241                                 if ( (IMAP->msgids[i-1]>=lo)
1242                                    && (IMAP->msgids[i-1]<=hi)) {
1243                                         IMAP->flags[i-1] |= IMAP_SELECTED;
1244                                 }
1245                         }
1246                         else {          /* fetch by uid */
1247                                 if ( (i>=lo) && (i<=hi)) {
1248                                         IMAP->flags[i-1] |= IMAP_SELECTED;
1249                                 }
1250                         }
1251                 }
1252         }
1253
1254 }
1255
1256
1257
1258 /*
1259  * This function is called by the main command loop.
1260  */
1261 void imap_fetch(int num_parms, char *parms[]) {
1262         char items[SIZ];
1263         char *itemlist[512];
1264         int num_items;
1265         int i;
1266
1267         if (num_parms < 4) {
1268                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1269                 return;
1270         }
1271
1272         imap_pick_range(parms[2], 0);
1273
1274         strcpy(items, "");
1275         for (i=3; i<num_parms; ++i) {
1276                 strcat(items, parms[i]);
1277                 if (i < (num_parms-1)) strcat(items, " ");
1278         }
1279
1280         num_items = imap_extract_data_items(itemlist, items);
1281         if (num_items < 1) {
1282                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1283                 return;
1284         }
1285
1286         imap_do_fetch(num_items, itemlist);
1287         cprintf("%s OK FETCH completed\r\n", parms[0]);
1288 }
1289
1290 /*
1291  * This function is called by the main command loop.
1292  */
1293 void imap_uidfetch(int num_parms, char *parms[]) {
1294         char items[SIZ];
1295         char *itemlist[512];
1296         int num_items;
1297         int i;
1298         int have_uid_item = 0;
1299
1300         if (num_parms < 5) {
1301                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1302                 return;
1303         }
1304
1305         imap_pick_range(parms[3], 1);
1306
1307         strcpy(items, "");
1308         for (i=4; i<num_parms; ++i) {
1309                 strcat(items, parms[i]);
1310                 if (i < (num_parms-1)) strcat(items, " ");
1311         }
1312
1313         num_items = imap_extract_data_items(itemlist, items);
1314         if (num_items < 1) {
1315                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1316                 return;
1317         }
1318
1319         /* If the "UID" item was not included, we include it implicitly
1320          * (at the beginning) because this is a UID FETCH command
1321          */
1322         for (i=0; i<num_items; ++i) {
1323                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1324         }
1325         if (have_uid_item == 0) {
1326                 memmove(&itemlist[1], &itemlist[0], (sizeof(itemlist[0]) * num_items));
1327                 ++num_items;
1328                 itemlist[0] = "UID";
1329         }
1330
1331         imap_do_fetch(num_items, itemlist);
1332         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1333 }
1334
1335