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