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