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