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