]> code.citadel.org Git - citadel.git/blob - citadel/imap_fetch.c
1672e6e5d0dff2b6b1f87dcbec78f4c2d6efc704
[citadel.git] / citadel / imap_fetch.c
1 /*
2  * $Id$
3  *
4  * Implements the FETCH command in IMAP.
5  * This command is way too convoluted.  Marc Crispin is a fscking idiot.
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  * Crispin-approved 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.  Crispin is a fscking idiot.
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 of Crispin being a fscking idiot */
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.  (Do you need a silencer if
836  * you're going to shoot a MIME?  Do you need a reason to shoot Mark Crispin?
837  * No, and no.)
838  *
839  */
840 void imap_fetch_bodystructure (long msgnum, char *item,
841                 struct CtdlMessage *msg) {
842         FILE *tmp;
843         char buf[SIZ];
844         long lines = 0L;
845         long start_of_body = 0L;
846         long body_bytes = 0L;
847
848         /* For non-RFC822 (ordinary Citadel) messages, this is short and
849          * sweet...
850          */
851         if (msg->cm_format_type != FMT_RFC822) {
852
853                 /* *sigh* We have to RFC822-format the message just to be able
854                  * to measure it.
855                  */
856                 tmp = tmpfile();
857                 if (tmp == NULL) return;
858                 CtdlRedirectOutput(tmp, -1);
859                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
860                 CtdlRedirectOutput(NULL, -1);
861
862                 rewind(tmp);
863                 while (fgets(buf, sizeof buf, tmp) != NULL) {
864                         ++lines;
865                         if ((!strcmp(buf, "\r\n")) && (start_of_body == 0L)) {
866                                 start_of_body = ftell(tmp);
867                         }
868                 }
869                 body_bytes = ftell(tmp) - start_of_body;
870                 fclose(tmp);
871
872                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
873                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
874                         "\"7BIT\" %ld %ld)", body_bytes, lines);
875
876                 return;
877         }
878
879         /* For messages already stored in RFC822 format, we have to parse. */
880         cprintf("BODYSTRUCTURE ");
881         mime_parser(msg->cm_fields['M'],
882                         NULL,
883                         *imap_fetch_bodystructure_part, /* part */
884                         *imap_fetch_bodystructure_pre,  /* pre-multi */
885                         *imap_fetch_bodystructure_post, /* post-multi */
886                         NULL,
887                         1);     /* don't decode -- we want it as-is */
888 }
889
890
891 /*
892  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
893  * individual message, once it has been selected for output.
894  */
895 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
896         int i;
897         struct CtdlMessage *msg = NULL;
898
899         lprintf(CTDL_DEBUG, "imap_do_fetch_msg(%d, %d)\n", seq, num_items);
900         for (i=0; i<num_items; ++i) {
901                 lprintf(CTDL_DEBUG, "                  %s\n", itemlist[i]);
902         }
903
904         cprintf("* %d FETCH (", seq);
905
906         for (i=0; i<num_items; ++i) {
907
908                 /* Fetchable without going to the message store at all */
909                 if (!strcasecmp(itemlist[i], "UID")) {
910                         imap_fetch_uid(seq);
911                 }
912                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
913                         imap_fetch_flags(seq-1);
914                 }
915
916                 /* Potentially fetchable from cache, if the client requests
917                  * stuff from the same message several times in a row.
918                  */
919                 else if (!strcasecmp(itemlist[i], "RFC822")) {
920                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
921                 }
922                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
923                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
924                 }
925                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
926                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
927                 }
928                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
929                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
930                 }
931
932                 /* BODY fetches do their own fetching and caching too. */
933                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
934                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
935                 }
936                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
937                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
938                 }
939
940                 /* Otherwise, load the message into memory.
941                  */
942                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
943                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
944                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
945                                         itemlist[i], msg);
946                 }
947                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
948                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
949                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
950                 }
951                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
952                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
953                         imap_fetch_internaldate(msg);
954                 }
955
956                 if (i != num_items-1) cprintf(" ");
957         }
958
959         cprintf(")\r\n");
960         if (msg != NULL) {
961                 CtdlFreeMessage(msg);
962         }
963 }
964
965
966
967 /*
968  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
969  * validated and boiled down the request a bit.
970  */
971 void imap_do_fetch(int num_items, char **itemlist) {
972         int i;
973
974         if (IMAP->num_msgs > 0) {
975                 for (i = 0; i < IMAP->num_msgs; ++i) {
976                         if (IMAP->flags[i] & IMAP_SELECTED) {
977                                 imap_do_fetch_msg(i+1, num_items, itemlist);
978                         }
979                 }
980         }
981 }
982
983
984
985 /*
986  * Back end for imap_handle_macros()
987  * Note that this function *only* looks at the beginning of the string.  It
988  * is not a generic search-and-replace function.
989  */
990 void imap_macro_replace(char *str, char *find, char *replace) {
991         char holdbuf[SIZ];
992
993         if (!strncasecmp(str, find, strlen(find))) {
994                 if (str[strlen(find)]==' ') {
995                         strcpy(holdbuf, &str[strlen(find)+1]);
996                         strcpy(str, replace);
997                         strcat(str, " ");
998                         strcat(str, holdbuf);
999                 }
1000                 if (str[strlen(find)]==0) {
1001                         strcpy(holdbuf, &str[strlen(find)+1]);
1002                         strcpy(str, replace);
1003                 }
1004         }
1005 }
1006
1007
1008
1009 /*
1010  * Handle macros embedded in FETCH data items.
1011  * (What the heck are macros doing in a wire protocol?  Are we trying to save
1012  * the computer at the other end the trouble of typing a lot of characters?)
1013  */
1014 void imap_handle_macros(char *str) {
1015         int i;
1016         int nest = 0;
1017
1018         for (i=0; i<strlen(str); ++i) {
1019                 if (str[i]=='(') ++nest;
1020                 if (str[i]=='[') ++nest;
1021                 if (str[i]=='<') ++nest;
1022                 if (str[i]=='{') ++nest;
1023                 if (str[i]==')') --nest;
1024                 if (str[i]==']') --nest;
1025                 if (str[i]=='>') --nest;
1026                 if (str[i]=='}') --nest;
1027
1028                 if (nest <= 0) {
1029                         imap_macro_replace(&str[i],
1030                                 "ALL",
1031                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1032                         );
1033                         imap_macro_replace(&str[i],
1034                                 "BODY",
1035                                 "BODYSTRUCTURE"
1036                         );
1037                         imap_macro_replace(&str[i],
1038                                 "FAST",
1039                                 "FLAGS INTERNALDATE RFC822.SIZE"
1040                         );
1041                         imap_macro_replace(&str[i],
1042                                 "FULL",
1043                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1044                         );
1045                 }
1046         }
1047 }
1048
1049
1050 /*
1051  * Break out the data items requested, possibly a parenthesized list.
1052  * Returns the number of data items, or -1 if the list is invalid.
1053  * NOTE: this function alters the string it is fed, and uses it as a buffer
1054  * to hold the data for the pointers it returns.
1055  */
1056 int imap_extract_data_items(char **argv, char *items) {
1057         int num_items = 0;
1058         int nest = 0;
1059         int i, initial_len;
1060         char *start;
1061
1062         /* Convert all whitespace to ordinary space characters. */
1063         for (i=0; i<strlen(items); ++i) {
1064                 if (isspace(items[i])) items[i]=' ';
1065         }
1066
1067         /* Strip leading and trailing whitespace, then strip leading and
1068          * trailing parentheses if it's a list
1069          */
1070         striplt(items);
1071         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1072                 items[strlen(items)-1] = 0;
1073                 strcpy(items, &items[1]);
1074                 striplt(items);
1075         }
1076
1077         /* Parse any macro data items */
1078         imap_handle_macros(items);
1079
1080         /*
1081          * Now break out the data items.  We throw in one trailing space in
1082          * order to avoid having to break out the last one manually.
1083          */
1084         strcat(items, " ");
1085         start = items;
1086         initial_len = strlen(items);
1087         for (i=0; i<initial_len; ++i) {
1088                 if (items[i]=='(') ++nest;
1089                 if (items[i]=='[') ++nest;
1090                 if (items[i]=='<') ++nest;
1091                 if (items[i]=='{') ++nest;
1092                 if (items[i]==')') --nest;
1093                 if (items[i]==']') --nest;
1094                 if (items[i]=='>') --nest;
1095                 if (items[i]=='}') --nest;
1096
1097                 if (nest <= 0) if (items[i]==' ') {
1098                         items[i] = 0;
1099                         argv[num_items++] = start;
1100                         start = &items[i+1];
1101                 }
1102         }
1103
1104         return(num_items);
1105
1106 }
1107
1108
1109 /*
1110  * One particularly hideous aspect of IMAP is that we have to allow the client
1111  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1112  * handles this by setting the IMAP_SELECTED flag for each message specified in
1113  * the ranges/sets, then looping through the message array, outputting messages
1114  * with the flag set.  We don't bother returning an error if an out-of-range
1115  * number is specified (we just return quietly) because any client braindead
1116  * enough to request a bogus message number isn't going to notice the
1117  * difference anyway.
1118  *
1119  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1120  * message included in the specified range.
1121  *
1122  * Set is_uid to 1 to fetch by UID instead of sequence number.
1123  */
1124 void imap_pick_range(char *supplied_range, int is_uid) {
1125         int i;
1126         int num_sets;
1127         int s;
1128         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1129         long lo, hi;
1130         char actual_range[SIZ];
1131
1132         /* 
1133          * Handle the "ALL" macro
1134          */
1135         if (!strcasecmp(supplied_range, "ALL")) {
1136                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1137         }
1138         else {
1139                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1140         }
1141
1142         /*
1143          * Clear out the IMAP_SELECTED flags for all messages.
1144          */
1145         for (i = 0; i < IMAP->num_msgs; ++i) {
1146                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1147         }
1148
1149         /*
1150          * Now set it for all specified messages.
1151          */
1152         num_sets = num_tokens(actual_range, ',');
1153         for (s=0; s<num_sets; ++s) {
1154                 extract_token(setstr, actual_range, s, ',');
1155
1156                 extract_token(lostr, setstr, 0, ':');
1157                 if (num_tokens(setstr, ':') >= 2) {
1158                         extract_token(histr, setstr, 1, ':');
1159                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1160                 } 
1161                 else {
1162                         strcpy(histr, lostr);
1163                 }
1164                 lo = atol(lostr);
1165                 hi = atol(histr);
1166
1167                 /* Loop through the array, flipping bits where appropriate */
1168                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1169                         if (is_uid) {   /* fetch by sequence number */
1170                                 if ( (IMAP->msgids[i-1]>=lo)
1171                                    && (IMAP->msgids[i-1]<=hi)) {
1172                                         IMAP->flags[i-1] =
1173                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1174                                 }
1175                         }
1176                         else {          /* fetch by uid */
1177                                 if ( (i>=lo) && (i<=hi)) {
1178                                         IMAP->flags[i-1] =
1179                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1180                                 }
1181                         }
1182                 }
1183         }
1184
1185 }
1186
1187
1188
1189 /*
1190  * This function is called by the main command loop.
1191  */
1192 void imap_fetch(int num_parms, char *parms[]) {
1193         char items[SIZ];
1194         char *itemlist[SIZ];
1195         int num_items;
1196         int i;
1197
1198         if (num_parms < 4) {
1199                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1200                 return;
1201         }
1202
1203         imap_pick_range(parms[2], 0);
1204
1205         strcpy(items, "");
1206         for (i=3; i<num_parms; ++i) {
1207                 strcat(items, parms[i]);
1208                 if (i < (num_parms-1)) strcat(items, " ");
1209         }
1210
1211         num_items = imap_extract_data_items(itemlist, items);
1212         if (num_items < 1) {
1213                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1214                 return;
1215         }
1216
1217         imap_do_fetch(num_items, itemlist);
1218         cprintf("%s OK FETCH completed\r\n", parms[0]);
1219 }
1220
1221 /*
1222  * This function is called by the main command loop.
1223  */
1224 void imap_uidfetch(int num_parms, char *parms[]) {
1225         char items[SIZ];
1226         char *itemlist[SIZ];
1227         int num_items;
1228         int i;
1229         int have_uid_item = 0;
1230
1231         if (num_parms < 5) {
1232                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1233                 return;
1234         }
1235
1236         imap_pick_range(parms[3], 1);
1237
1238         strcpy(items, "");
1239         for (i=4; i<num_parms; ++i) {
1240                 strcat(items, parms[i]);
1241                 if (i < (num_parms-1)) strcat(items, " ");
1242         }
1243
1244         num_items = imap_extract_data_items(itemlist, items);
1245         if (num_items < 1) {
1246                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1247                 return;
1248         }
1249
1250         /* If the "UID" item was not included, we include it implicitly
1251          * because this is a UID FETCH command
1252          */
1253         for (i=0; i<num_items; ++i) {
1254                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1255         }
1256         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1257
1258         imap_do_fetch(num_items, itemlist);
1259         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1260 }
1261
1262