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