removed all references to sprintf from several files (not all files yet)
[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 "dynloader.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
67
68 void imap_fetch_uid(int seq) {
69         cprintf("UID %ld", IMAP->msgids[seq-1]);
70 }
71
72 void imap_fetch_flags(int seq) {
73         cprintf("FLAGS (");
74         if (IMAP->flags[seq] & IMAP_DELETED) cprintf("\\Deleted ");
75         if (IMAP->flags[seq] & IMAP_SEEN) cprintf("\\Seen ");
76         cprintf(")");
77 }
78
79 void imap_fetch_internaldate(struct CtdlMessage *msg) {
80         char buf[SIZ];
81         time_t msgdate;
82
83         if (msg->cm_fields['T'] != NULL) {
84                 msgdate = atol(msg->cm_fields['T']);
85         }
86         else {
87                 msgdate = time(NULL);
88         }
89
90         datestring(buf, sizeof buf, msgdate, DATESTRING_IMAP);
91         cprintf("INTERNALDATE \"%s\"", buf);
92 }
93
94
95 /*
96  * Fetch RFC822-formatted messages.
97  *
98  * 'whichfmt' should be set to one of:
99  *      "RFC822"        entire message
100  *      "RFC822.HEADER" headers only (with trailing blank line)
101  *      "RFC822.SIZE"   size of translated message
102  *      "RFC822.TEXT"   body only (without leading blank line)
103  */
104 void imap_fetch_rfc822(int msgnum, char *whichfmt, struct CtdlMessage *msg) {
105         FILE *tmp;
106         char buf[1024];
107         char *ptr;
108         long headers_size, text_size, total_size;
109         long bytes_remaining = 0;
110         long blocksize;
111
112         tmp = tmpfile();
113         if (tmp == NULL) {
114                 lprintf(1, "Cannot open temp file: %s\n", strerror(errno));
115                 return;
116         }
117
118         /*
119          * Load the message into a temp file for translation and measurement
120          */ 
121         CtdlRedirectOutput(tmp, -1);
122         CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
123         CtdlRedirectOutput(NULL, -1);
124         if (!is_valid_message(msg)) {
125                 lprintf(1, "WARNING: output clobbered the message!\n");
126         }
127
128         /*
129          * Now figure out where the headers/text break is.  IMAP considers the
130          * intervening blank line to be part of the headers, not the text.
131          */
132         rewind(tmp);
133         headers_size = 0L;
134         do {
135                 ptr = fgets(buf, sizeof buf, tmp);
136                 if (ptr != NULL) {
137                         striplt(buf);
138                         if (strlen(buf) == 0) headers_size = ftell(tmp);
139                 }
140         } while ( (headers_size == 0L) && (ptr != NULL) );
141         fseek(tmp, 0L, SEEK_END);
142         total_size = ftell(tmp);
143         text_size = total_size - headers_size;
144
145         if (!strcasecmp(whichfmt, "RFC822.SIZE")) {
146                 cprintf("RFC822.SIZE %ld", total_size);
147                 fclose(tmp);
148                 return;
149         }
150
151         else if (!strcasecmp(whichfmt, "RFC822")) {
152                 bytes_remaining = total_size;
153                 rewind(tmp);
154         }
155
156         else if (!strcasecmp(whichfmt, "RFC822.HEADER")) {
157                 bytes_remaining = headers_size;
158                 rewind(tmp);
159         }
160
161         else if (!strcasecmp(whichfmt, "RFC822.TEXT")) {
162                 bytes_remaining = text_size;
163                 fseek(tmp, headers_size, SEEK_SET);
164         }
165
166         cprintf("%s {%ld}\r\n", whichfmt, bytes_remaining);
167         blocksize = sizeof(buf);
168         while (bytes_remaining > 0L) {
169                 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
170                 fread(buf, blocksize, 1, tmp);
171                 client_write(buf, blocksize);
172                 bytes_remaining = bytes_remaining - blocksize;
173         }
174
175         fclose(tmp);
176 }
177
178
179
180 /*
181  * Load a specific part of a message into the temp file to be output to a
182  * client.  FIXME we can handle parts like "2" and "2.1" and even "2.MIME"
183  * but we still can't handle "2.HEADER" (which might not be a problem, because
184  * we currently don't have the ability to break out nested RFC822's anyway).
185  *
186  * Note: mime_parser() was called with dont_decode set to 1, so we have the
187  * luxury of simply spewing without having to re-encode.
188  */
189 void imap_load_part(char *name, char *filename, char *partnum, char *disp,
190                     void *content, char *cbtype, size_t length, char *encoding,
191                     void *cbuserdata)
192 {
193         struct imap_fetch_part *imfp;
194         char mbuf2[1024];
195
196         imfp = (struct imap_fetch_part *)cbuserdata;
197
198         if (!strcasecmp(partnum, imfp->desired_section)) {
199                 fwrite(content, length, 1, imfp->output_fp);
200         }
201
202         snprintf(mbuf2, sizeof mbuf2, "%s.MIME", partnum);
203
204         if (!strcasecmp(imfp->desired_section, mbuf2)) {
205                 fprintf(imfp->output_fp, "Content-type: %s", cbtype);
206                 if (strlen(name) > 0)
207                         fprintf(imfp->output_fp, "; name=\"%s\"", name);
208                 fprintf(imfp->output_fp, "\r\n");
209                 if (strlen(encoding) > 0)
210                         fprintf(imfp->output_fp,
211                                 "Content-Transfer-Encoding: %s\r\n", encoding);
212                 if (strlen(encoding) > 0) {
213                         fprintf(imfp->output_fp, "Content-Disposition: %s",
214                                         disp);
215                         if (strlen(filename) > 0) {
216                                 fprintf(imfp->output_fp, "; filename=\"%s\"",
217                                         filename);
218                         }
219                         fprintf(imfp->output_fp, "\r\n");
220                 }
221                 fprintf(imfp->output_fp, "Content-Length: %ld\r\n", (long)length);
222                 fprintf(imfp->output_fp, "\r\n");
223         }
224                         
225
226 }
227
228
229 /* 
230  * Called by imap_fetch_envelope() to output the "From" field.
231  * This is in its own function because its logic is kind of complex.  We
232  * really need to make this suck less.
233  */
234 void imap_output_envelope_from(struct CtdlMessage *msg) {
235         char user[1024], node[1024], name[1024];
236
237         cprintf("((");                          /* open double-parens */
238         imap_strout(msg->cm_fields['A']);       /* personal name */
239         cprintf(" NIL ");                       /* source route (not used) */
240
241         if (msg->cm_fields['F'] != NULL) {
242                 process_rfc822_addr(msg->cm_fields['F'], user, node, name);
243                 imap_strout(user);              /* mailbox name (user id) */
244                 cprintf(" ");
245                 if (!strcasecmp(node, config.c_nodename)) {
246                         imap_strout(config.c_fqdn);
247                 }
248                 else {
249                         imap_strout(node);              /* host name */
250                 }
251         }
252         else {
253                 imap_strout(msg->cm_fields['A']); /* mailbox name (user id) */
254                 cprintf(" ");
255                 imap_strout(msg->cm_fields['N']);       /* host name */
256         }
257         
258         cprintf(")) ");                         /* close double-parens */
259 }
260
261
262 /*
263  * Implements the ENVELOPE fetch item
264  * 
265  * FIXME ... we only output some of the fields right now.  Definitely need
266  *           to do all of them.  Accurately, too.
267  *
268  * Note that the imap_strout() function can cleverly output NULL fields as NIL,
269  * so we don't have to check for that condition like we do elsewhere.
270  */
271 void imap_fetch_envelope(long msgnum, struct CtdlMessage *msg) {
272         char datestringbuf[SIZ];
273         time_t msgdate;
274         char *fieldptr = NULL;
275
276         /* Parse the message date into an IMAP-format date string */
277         if (msg->cm_fields['T'] != NULL) {
278                 msgdate = atol(msg->cm_fields['T']);
279         }
280         else {
281                 msgdate = time(NULL);
282         }
283         datestring(datestringbuf, sizeof datestringbuf, msgdate, DATESTRING_IMAP);
284
285         /* Now start spewing data fields.  The order is important, as it is
286          * defined by the protocol specification.  Nonexistent fields must
287          * be output as NIL, existent fields must be quoted or literalled.
288          * The imap_strout() function conveniently does all this for us.
289          */
290         cprintf("ENVELOPE (");
291
292         /* Date */
293         imap_strout(datestringbuf);
294         cprintf(" ");
295
296         /* Subject */
297         imap_strout(msg->cm_fields['U']);
298         cprintf(" ");
299
300         /* From */
301         imap_output_envelope_from(msg);
302
303         /* Sender */
304         if (0) {
305                 /* FIXME ... check for a *real* Sender: field */
306         }
307         else {
308                 imap_output_envelope_from(msg);
309         }
310
311         /* Reply-to */
312         if (0) {
313                 /* FIXME ... check for a *real* Reply-to: field */
314         }
315         else {
316                 imap_output_envelope_from(msg);
317         }
318
319         cprintf("NIL ");        /* to */
320
321         cprintf("NIL ");        /* cc */
322
323         cprintf("NIL ");        /* bcc */
324
325         /* In-reply-to */
326         fieldptr = rfc822_fetch_field(msg->cm_fields['M'], "In-reply-to");
327         imap_strout(fieldptr);
328         cprintf(" ");
329         if (fieldptr != NULL) phree(fieldptr);
330
331         /* message ID */
332         imap_strout(msg->cm_fields['I']);
333
334         cprintf(") ");
335 }
336
337
338 /*
339  * Strip any non header information out of a chunk of RFC822 data on disk,
340  * then boil it down to just the fields we want.
341  */
342 void imap_strip_headers(FILE *fp, char *section) {
343         char buf[1024];
344         char *which_fields = NULL;
345         int doing_headers = 0;
346         int headers_not = 0;
347         char *parms[SIZ];
348         int num_parms = 0;
349         int i;
350         char *boiled_headers = NULL;
351         int ok = 0;
352         int done_headers = 0;
353
354         which_fields = strdoop(section);
355
356         if (!strncasecmp(which_fields, "HEADER.FIELDS", 13))
357                 doing_headers = 1;
358         if (!strncasecmp(which_fields, "HEADER.FIELDS.NOT", 17))
359                 headers_not = 1;
360
361         for (i=0; i<strlen(which_fields); ++i) {
362                 if (which_fields[i]=='(')
363                         strcpy(which_fields, &which_fields[i+1]);
364         }
365         for (i=0; i<strlen(which_fields); ++i) {
366                 if (which_fields[i]==')')
367                         which_fields[i] = 0;
368         }
369         num_parms = imap_parameterize(parms, which_fields);
370
371         fseek(fp, 0L, SEEK_END);
372         boiled_headers = mallok((size_t)(ftell(fp) + 256L));
373         strcpy(boiled_headers, "");
374
375         rewind(fp);
376         ok = 0;
377         while ( (done_headers == 0) && (fgets(buf, sizeof buf, fp) != NULL) ) {
378                 if (!isspace(buf[0])) {
379                         ok = 0;
380                         if (doing_headers == 0) ok = 1;
381                         else {
382                                 if (headers_not) ok = 1;
383                                 else ok = 0;
384                                 for (i=0; i<num_parms; ++i) {
385                                         if ( (!strncasecmp(buf, parms[i],
386                                            strlen(parms[i]))) &&
387                                            (buf[strlen(parms[i])]==':') ) {
388                                                 if (headers_not) ok = 0;
389                                                 else ok = 1;
390                                         }
391                                 }
392                         }
393                 }
394
395                 if (ok) {
396                         strcat(boiled_headers, buf);
397                 }
398
399                 if (strlen(buf) == 0) done_headers = 1;
400                 if (buf[0]=='\r') done_headers = 1;
401                 if (buf[0]=='\n') done_headers = 1;
402         }
403
404         /* Now write it back */
405         rewind(fp);
406         fwrite(boiled_headers, strlen(boiled_headers), 1, fp);
407         fflush(fp);
408         ftruncate(fileno(fp), ftell(fp));
409         fflush(fp);
410         fprintf(fp, "\r\n");    /* add the trailing newline */
411         rewind(fp);
412         phree(which_fields);
413         phree(boiled_headers);
414 }
415
416
417 /*
418  * Implements the BODY and BODY.PEEK fetch items
419  */
420 void imap_fetch_body(long msgnum, char *item, int is_peek,
421                 struct CtdlMessage *msg) {
422         char section[1024];
423         char partial[1024];
424         int is_partial = 0;
425         char buf[1024];
426         int i;
427         FILE *tmp;
428         long bytes_remaining = 0;
429         long blocksize;
430         long pstart, pbytes;
431         struct imap_fetch_part imfp;
432
433         /* extract section */
434         strcpy(section, item);
435         for (i=0; i<strlen(section); ++i) {
436                 if (section[i]=='[') strcpy(section, &section[i+1]);
437         }
438         for (i=0; i<strlen(section); ++i) {
439                 if (section[i]==']') section[i] = 0;
440         }
441         lprintf(9, "Section is %s\n", section);
442
443         /* extract partial */
444         strcpy(partial, item);
445         for (i=0; i<strlen(partial); ++i) {
446                 if (partial[i]=='<') {
447                         strcpy(partial, &partial[i+1]);
448                         is_partial = 1;
449                 }
450         }
451         for (i=0; i<strlen(partial); ++i) {
452                 if (partial[i]=='>') partial[i] = 0;
453         }
454         if (is_partial == 0) strcpy(partial, "");
455         if (strlen(partial) > 0) lprintf(9, "Partial is %s\n", partial);
456
457         tmp = tmpfile();
458         if (tmp == NULL) {
459                 lprintf(1, "Cannot open temp file: %s\n", strerror(errno));
460                 return;
461         }
462
463         /* Now figure out what the client wants, and get it */
464
465         if (!strcmp(section, "")) {             /* the whole thing */
466                 CtdlRedirectOutput(tmp, -1);
467                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
468                 CtdlRedirectOutput(NULL, -1);
469         }
470
471         /*
472          * If the client asked for just headers, or just particular header
473          * fields, strip it down.
474          */
475         else if (!strncasecmp(section, "HEADER", 6)) {
476                 CtdlRedirectOutput(tmp, -1);
477                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 1, 0, 1);
478                 CtdlRedirectOutput(NULL, -1);
479                 imap_strip_headers(tmp, section);
480         }
481
482         /*
483          * Anything else must be a part specifier.
484          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
485          */
486         else {
487                 safestrncpy(imfp.desired_section, section,
488                                 sizeof(imfp.desired_section));
489                 imfp.output_fp = tmp;
490
491                 mime_parser(msg->cm_fields['M'], NULL,
492                                 *imap_load_part, NULL, NULL,
493                                 (void *)&imfp,
494                                 1);
495         }
496
497
498         fseek(tmp, 0L, SEEK_END);
499         bytes_remaining = ftell(tmp);
500
501         if (is_partial == 0) {
502                 rewind(tmp);
503                 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
504         }
505         else {
506                 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
507                 if ((bytes_remaining - pstart) < pbytes) {
508                         pbytes = bytes_remaining - pstart;
509                 }
510                 fseek(tmp, pstart, SEEK_SET);
511                 bytes_remaining = pbytes;
512                 cprintf("BODY[%s] {%ld}<%ld>\r\n",
513                         section, bytes_remaining, pstart);
514         }
515
516         blocksize = sizeof(buf);
517         while (bytes_remaining > 0L) {
518                 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
519                 fread(buf, blocksize, 1, tmp);
520                 client_write(buf, blocksize);
521                 bytes_remaining = bytes_remaining - blocksize;
522         }
523
524         fclose(tmp);
525
526         /* Mark this message as "seen" *unless* this is a "peek" operation */
527         if (is_peek == 0) {
528                 CtdlSetSeen(msgnum, 1);
529         }
530 }
531
532 /*
533  * Called immediately before outputting a multipart bodystructure
534  */
535 void imap_fetch_bodystructure_pre(
536                 char *name, char *filename, char *partnum, char *disp,
537                 void *content, char *cbtype, size_t length, char *encoding,
538                 void *cbuserdata
539                 ) {
540
541         cprintf("(");
542 }
543
544
545
546 /*
547  * Called immediately after outputting a multipart bodystructure
548  */
549 void imap_fetch_bodystructure_post(
550                 char *name, char *filename, char *partnum, char *disp,
551                 void *content, char *cbtype, size_t length, char *encoding,
552                 void *cbuserdata
553                 ) {
554
555         char subtype[SIZ];
556
557         extract_token(subtype, cbtype, 1, '/');
558         imap_strout(subtype);
559         cprintf(")");
560 }
561
562
563
564 /*
565  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
566  *
567  */
568 void imap_fetch_bodystructure_part(
569                 char *name, char *filename, char *partnum, char *disp,
570                 void *content, char *cbtype, size_t length, char *encoding,
571                 void *cbuserdata
572                 ) {
573
574         char buf[SIZ];
575         int have_cbtype = 0;
576         int have_encoding = 0;
577
578         cprintf("(");
579
580         if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
581
582         if (have_cbtype) {
583                 extract_token(buf, cbtype, 0, '/');
584                 imap_strout(buf);
585                 cprintf(" ");
586                 extract_token(buf, cbtype, 1, '/');
587                 imap_strout(buf);
588                 cprintf(" ");
589         }
590         else {
591                 cprintf("\"TEXT\" \"PLAIN\" ");
592         }
593
594         cprintf("(\"CHARSET\" \"US-ASCII\"");
595
596         if (name != NULL) if (strlen(name)>0) {
597                 cprintf(" \"NAME\" ");
598                 imap_strout(name);
599         }
600
601         if (filename != NULL) if (strlen(filename)>0) {
602                 cprintf(" \"FILENAME\" ");
603                 imap_strout(name);
604         }
605
606         cprintf(") ");
607
608         cprintf("NIL NIL ");
609
610         if (encoding != NULL) if (strlen(encoding) > 0)  have_encoding = 1;
611
612         if (have_encoding) {
613                 imap_strout(encoding);
614         }
615         else {
616                 imap_strout("7BIT");
617         }
618         cprintf(" ");
619
620         cprintf("%ld ", (long)length);  /* bytes */
621         cprintf("NIL) ");               /* lines */
622 }
623
624
625
626 /*
627  * Spew the BODYSTRUCTURE data for a message.  (Do you need a silencer if
628  * you're going to shoot a MIME?  Do you need a reason to shoot Mark Crispin?
629  * No, and no.)
630  *
631  */
632 void imap_fetch_bodystructure (long msgnum, char *item,
633                 struct CtdlMessage *msg) {
634         FILE *tmp;
635         char buf[1024];
636         long lines = 0L;
637         long bytes = 0L;
638
639         /* For non-RFC822 (ordinary Citadel) messages, this is short and
640          * sweet...
641          */
642         if (msg->cm_format_type != FMT_RFC822) {
643
644                 /* *sigh* We have to RFC822-format the message just to be able
645                  * to measure it.
646                  */
647                 tmp = tmpfile();
648                 if (tmp == NULL) return;
649                 CtdlRedirectOutput(tmp, -1);
650                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
651                 CtdlRedirectOutput(NULL, -1);
652
653                 rewind(tmp);
654                 while (fgets(buf, sizeof buf, tmp) != NULL) ++lines;
655                 bytes = ftell(tmp);
656                 fclose(tmp);
657
658                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
659                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
660                         "\"7BIT\" %ld %ld)", bytes, lines);
661
662                 return;
663         }
664
665         /* For messages already stored in RFC822 format, we have to parse. */
666         cprintf("BODYSTRUCTURE ");
667         mime_parser(msg->cm_fields['M'],
668                         NULL,
669                         *imap_fetch_bodystructure_part, /* part */
670                         *imap_fetch_bodystructure_pre,  /* pre-multi */
671                         *imap_fetch_bodystructure_post, /* post-multi */
672                         NULL,
673                         1);     /* don't decode -- we want it as-is */
674 }
675
676
677
678
679
680
681 /*
682  * imap_do_fetch() calls imap_do_fetch_msg() to output the deta of an
683  * individual message, once it has been successfully loaded from disk.
684  */
685 void imap_do_fetch_msg(int seq, struct CtdlMessage *msg,
686                         int num_items, char **itemlist) {
687         int i;
688
689         cprintf("* %d FETCH (", seq);
690
691         for (i=0; i<num_items; ++i) {
692
693                 if (!strncasecmp(itemlist[i], "BODY[", 5)) {
694                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
695                                         0, msg);
696                 }
697                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
698                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i],
699                                         1, msg);
700                 }
701                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
702                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
703                                         itemlist[i], msg);
704                 }
705                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
706                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
707                 }
708                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
709                         imap_fetch_flags(seq-1);
710                 }
711                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
712                         imap_fetch_internaldate(msg);
713                 }
714                 else if (!strcasecmp(itemlist[i], "RFC822")) {
715                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
716                 }
717                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
718                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
719                 }
720                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
721                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
722                 }
723                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
724                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i], msg);
725                 }
726                 else if (!strcasecmp(itemlist[i], "UID")) {
727                         imap_fetch_uid(seq);
728                 }
729
730                 if (i != num_items-1) cprintf(" ");
731         }
732
733         cprintf(")\r\n");
734 }
735
736
737
738 /*
739  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
740  * validated and boiled down the request a bit.
741  */
742 void imap_do_fetch(int num_items, char **itemlist) {
743         int i;
744         struct CtdlMessage *msg;
745
746         if (IMAP->num_msgs > 0)
747          for (i = 0; i < IMAP->num_msgs; ++i)
748           if (IMAP->flags[i] & IMAP_SELECTED) {
749                 msg = CtdlFetchMessage(IMAP->msgids[i]);
750                 if (msg != NULL) {
751                         imap_do_fetch_msg(i+1, msg, num_items, itemlist);
752                         CtdlFreeMessage(msg);
753                 }
754                 else {
755                         cprintf("* %d FETCH <internal error>\r\n", i+1);
756                 }
757         }
758 }
759
760
761
762 /*
763  * Back end for imap_handle_macros()
764  * Note that this function *only* looks at the beginning of the string.  It
765  * is not a generic search-and-replace function.
766  */
767 void imap_macro_replace(char *str, char *find, char *replace) {
768         char holdbuf[1024];
769
770         if (!strncasecmp(str, find, strlen(find))) {
771                 if (str[strlen(find)]==' ') {
772                         strcpy(holdbuf, &str[strlen(find)+1]);
773                         strcpy(str, replace);
774                         strcat(str, " ");
775                         strcat(str, holdbuf);
776                 }
777                 if (str[strlen(find)]==0) {
778                         strcpy(holdbuf, &str[strlen(find)+1]);
779                         strcpy(str, replace);
780                 }
781         }
782 }
783
784
785
786 /*
787  * Handle macros embedded in FETCH data items.
788  * (What the heck are macros doing in a wire protocol?  Are we trying to save
789  * the computer at the other end the trouble of typing a lot of characters?)
790  */
791 void imap_handle_macros(char *str) {
792         int i;
793         int nest = 0;
794
795         for (i=0; i<strlen(str); ++i) {
796                 if (str[i]=='(') ++nest;
797                 if (str[i]=='[') ++nest;
798                 if (str[i]=='<') ++nest;
799                 if (str[i]=='{') ++nest;
800                 if (str[i]==')') --nest;
801                 if (str[i]==']') --nest;
802                 if (str[i]=='>') --nest;
803                 if (str[i]=='}') --nest;
804
805                 if (nest <= 0) {
806                         imap_macro_replace(&str[i],
807                                 "ALL",
808                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
809                         );
810                         imap_macro_replace(&str[i],
811                                 "BODY",
812                                 "BODYSTRUCTURE"
813                         );
814                         imap_macro_replace(&str[i],
815                                 "FAST",
816                                 "FLAGS INTERNALDATE RFC822.SIZE"
817                         );
818                         imap_macro_replace(&str[i],
819                                 "FULL",
820                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
821                         );
822                 }
823         }
824 }
825
826
827 /*
828  * Break out the data items requested, possibly a parenthesized list.
829  * Returns the number of data items, or -1 if the list is invalid.
830  * NOTE: this function alters the string it is fed, and uses it as a buffer
831  * to hold the data for the pointers it returns.
832  */
833 int imap_extract_data_items(char **argv, char *items) {
834         int num_items = 0;
835         int nest = 0;
836         int i, initial_len;
837         char *start;
838
839         /* Convert all whitespace to ordinary space characters. */
840         for (i=0; i<strlen(items); ++i) {
841                 if (isspace(items[i])) items[i]=' ';
842         }
843
844         /* Strip leading and trailing whitespace, then strip leading and
845          * trailing parentheses if it's a list
846          */
847         striplt(items);
848         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
849                 items[strlen(items)-1] = 0;
850                 strcpy(items, &items[1]);
851                 striplt(items);
852         }
853
854         /* Parse any macro data items */
855         imap_handle_macros(items);
856
857         /*
858          * Now break out the data items.  We throw in one trailing space in
859          * order to avoid having to break out the last one manually.
860          */
861         strcat(items, " ");
862         start = items;
863         initial_len = strlen(items);
864         for (i=0; i<initial_len; ++i) {
865                 if (items[i]=='(') ++nest;
866                 if (items[i]=='[') ++nest;
867                 if (items[i]=='<') ++nest;
868                 if (items[i]=='{') ++nest;
869                 if (items[i]==')') --nest;
870                 if (items[i]==']') --nest;
871                 if (items[i]=='>') --nest;
872                 if (items[i]=='}') --nest;
873
874                 if (nest <= 0) if (items[i]==' ') {
875                         items[i] = 0;
876                         argv[num_items++] = start;
877                         start = &items[i+1];
878                 }
879         }
880
881         return(num_items);
882
883 }
884
885
886 /*
887  * One particularly hideous aspect of IMAP is that we have to allow the client
888  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
889  * handles this by setting the IMAP_SELECTED flag for each message specified in
890  * the ranges/sets, then looping through the message array, outputting messages
891  * with the flag set.  We don't bother returning an error if an out-of-range
892  * number is specified (we just return quietly) because any client braindead
893  * enough to request a bogus message number isn't going to notice the
894  * difference anyway.
895  *
896  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
897  * message included in the specified range.
898  *
899  * Set is_uid to 1 to fetch by UID instead of sequence number.
900  */
901 void imap_pick_range(char *supplied_range, int is_uid) {
902         int i;
903         int num_sets;
904         int s;
905         char setstr[SIZ], lostr[SIZ], histr[SIZ];       /* was 1024 */
906         int lo, hi;
907         char actual_range[SIZ];
908
909         /* 
910          * Handle the "ALL" macro
911          */
912         if (!strcasecmp(supplied_range, "ALL")) {
913                 safestrncpy(actual_range, "1:*", sizeof actual_range);
914         }
915         else {
916                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
917         }
918
919         /*
920          * Clear out the IMAP_SELECTED flags for all messages.
921          */
922         for (i = 0; i < IMAP->num_msgs; ++i) {
923                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
924         }
925
926         /*
927          * Now set it for all specified messages.
928          */
929         num_sets = num_tokens(actual_range, ',');
930         for (s=0; s<num_sets; ++s) {
931                 extract_token(setstr, actual_range, s, ',');
932
933                 extract_token(lostr, setstr, 0, ':');
934                 if (num_tokens(setstr, ':') >= 2) {
935                         extract_token(histr, setstr, 1, ':');
936                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%d", INT_MAX);
937                 } 
938                 else {
939                         strcpy(histr, lostr);
940                 }
941                 lo = atoi(lostr);
942                 hi = atoi(histr);
943
944                 /* Loop through the array, flipping bits where appropriate */
945                 for (i = 1; i <= IMAP->num_msgs; ++i) {
946                         if (is_uid) {   /* fetch by sequence number */
947                                 if ( (IMAP->msgids[i-1]>=lo)
948                                    && (IMAP->msgids[i-1]<=hi)) {
949                                         IMAP->flags[i-1] =
950                                                 IMAP->flags[i-1] | IMAP_SELECTED;
951                                 }
952                         }
953                         else {          /* fetch by uid */
954                                 if ( (i>=lo) && (i<=hi)) {
955                                         IMAP->flags[i-1] =
956                                                 IMAP->flags[i-1] | IMAP_SELECTED;
957                                 }
958                         }
959                 }
960         }
961
962 }
963
964
965
966 /*
967  * This function is called by the main command loop.
968  */
969 void imap_fetch(int num_parms, char *parms[]) {
970         char items[SIZ];        /* was 1024 */
971         char *itemlist[SIZ];
972         int num_items;
973         int i;
974
975         if (num_parms < 4) {
976                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
977                 return;
978         }
979
980         imap_pick_range(parms[2], 0);
981
982         strcpy(items, "");
983         for (i=3; i<num_parms; ++i) {
984                 strcat(items, parms[i]);
985                 if (i < (num_parms-1)) strcat(items, " ");
986         }
987
988         num_items = imap_extract_data_items(itemlist, items);
989         if (num_items < 1) {
990                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
991                 return;
992         }
993
994         imap_do_fetch(num_items, itemlist);
995         cprintf("%s OK FETCH completed\r\n", parms[0]);
996 }
997
998 /*
999  * This function is called by the main command loop.
1000  */
1001 void imap_uidfetch(int num_parms, char *parms[]) {
1002         char items[SIZ];        /* was 1024 */
1003         char *itemlist[SIZ];
1004         int num_items;
1005         int i;
1006         int have_uid_item = 0;
1007
1008         if (num_parms < 5) {
1009                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1010                 return;
1011         }
1012
1013         imap_pick_range(parms[3], 1);
1014
1015         strcpy(items, "");
1016         for (i=4; i<num_parms; ++i) {
1017                 strcat(items, parms[i]);
1018                 if (i < (num_parms-1)) strcat(items, " ");
1019         }
1020
1021         num_items = imap_extract_data_items(itemlist, items);
1022         if (num_items < 1) {
1023                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1024                 return;
1025         }
1026
1027         /* If the "UID" item was not included, we include it implicitly
1028          * because this is a UID FETCH command
1029          */
1030         for (i=0; i<num_items; ++i) {
1031                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1032         }
1033         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1034
1035         imap_do_fetch(num_items, itemlist);
1036         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1037 }
1038
1039