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