]> code.citadel.org Git - citadel.git/blob - citadel/imap_fetch.c
* Realized that memreadline() does NOT return a NULL pointer when it hits
[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 != 0) {
174                         striplt(buf);
175                         if (strlen(buf) == 0) {
176                                 headers_size = ptr - IMAP->cached_rfc822_data;
177                         }
178                 }
179         } while ( (headers_size == 0) && (*ptr != 0) );
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                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
589                 CtdlRedirectOutput(NULL);
590         }
591
592         /*
593          * If the client asked for just headers, or just particular header
594          * fields, strip it down.
595          */
596         else if (!strncasecmp(section, "HEADER", 6)) {
597                 CtdlRedirectOutput(tmp);
598                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_ONLY, 0, 1);
599                 CtdlRedirectOutput(NULL);
600                 imap_strip_headers(tmp, section);
601         }
602
603         /*
604          * Strip it down if the client asked for everything _except_ headers.
605          */
606         else if (!strncasecmp(section, "TEXT", 4)) {
607                 CtdlRedirectOutput(tmp);
608                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, HEADERS_NONE, 0, 1);
609                 CtdlRedirectOutput(NULL);
610         }
611
612         /*
613          * Anything else must be a part specifier.
614          * (Note value of 1 passed as 'dont_decode' so client gets it encoded)
615          */
616         else {
617                 safestrncpy(imfp.desired_section, section, sizeof(imfp.desired_section));
618                 imfp.output_fp = tmp;
619                 mime_parser(msg->cm_fields['M'], NULL,
620                                 *imap_load_part, NULL, NULL,
621                                 (void *)&imfp,
622                                 1);
623         }
624
625
626         fseek(tmp, 0L, SEEK_END);
627         bytes_remaining = ftell(tmp);
628
629         if (is_partial == 0) {
630                 rewind(tmp);
631                 cprintf("BODY[%s] {%ld}\r\n", section, bytes_remaining);
632         }
633         else {
634                 sscanf(partial, "%ld.%ld", &pstart, &pbytes);
635                 if ((bytes_remaining - pstart) < pbytes) {
636                         pbytes = bytes_remaining - pstart;
637                 }
638                 fseek(tmp, pstart, SEEK_SET);
639                 bytes_remaining = pbytes;
640                 cprintf("BODY[%s]<%ld> {%ld}\r\n",
641                         section, pstart, bytes_remaining);
642         }
643
644         blocksize = (long)sizeof(buf);
645         while (bytes_remaining > 0L) {
646                 if (blocksize > bytes_remaining) blocksize = bytes_remaining;
647                 fread(buf, blocksize, 1, tmp);
648                 client_write(buf, (int)blocksize);
649                 bytes_remaining = bytes_remaining - blocksize;
650         }
651
652         /* Don't close it ... cache it! */
653         /* fclose(tmp); */
654         IMAP->cached_body = tmp;
655         IMAP->cached_bodymsgnum = msgnum;
656         strcpy(IMAP->cached_bodypart, section);
657
658         if (msg != NULL) {
659                 CtdlFreeMessage(msg);
660         }
661
662         /* Mark this message as "seen" *unless* this is a "peek" operation */
663         if (is_peek == 0) {
664                 CtdlSetSeen(msgnum, 1, ctdlsetseen_seen);
665         }
666 }
667
668 /*
669  * Called immediately before outputting a multipart bodystructure
670  */
671 void imap_fetch_bodystructure_pre(
672                 char *name, char *filename, char *partnum, char *disp,
673                 void *content, char *cbtype, size_t length, char *encoding,
674                 void *cbuserdata
675                 ) {
676
677         cprintf("(");
678 }
679
680
681
682 /*
683  * Called immediately after outputting a multipart bodystructure
684  */
685 void imap_fetch_bodystructure_post(
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         char subtype[SIZ];
692
693         cprintf(" ");
694
695         /* disposition */
696         extract_token(subtype, cbtype, 1, '/');
697         imap_strout(subtype);
698
699         /* body language */
700         cprintf(" NIL");
701
702         cprintf(")");
703 }
704
705
706
707 /*
708  * Output the info for a MIME part in the format required by BODYSTRUCTURE.
709  *
710  */
711 void imap_fetch_bodystructure_part(
712                 char *name, char *filename, char *partnum, char *disp,
713                 void *content, char *cbtype, size_t length, char *encoding,
714                 void *cbuserdata
715                 ) {
716
717         int have_cbtype = 0;
718         int have_encoding = 0;
719         int lines = 0;
720         size_t i;
721         char cbmaintype[SIZ];
722         char cbsubtype[SIZ];
723
724         if (cbtype != NULL) if (strlen(cbtype)>0) have_cbtype = 1;
725         if (have_cbtype) {
726                 extract_token(cbmaintype, cbtype, 0, '/');
727                 extract_token(cbsubtype, cbtype, 1, '/');
728         }
729         else {
730                 strcpy(cbmaintype, "TEXT");
731                 strcpy(cbsubtype, "PLAIN");
732         }
733
734         cprintf("(");
735         imap_strout(cbmaintype);
736         cprintf(" ");
737         imap_strout(cbsubtype);
738         cprintf(" ");
739
740         cprintf("(\"CHARSET\" \"US-ASCII\"");
741
742         if (name != NULL) if (strlen(name)>0) {
743                 cprintf(" \"NAME\" ");
744                 imap_strout(name);
745         }
746
747         cprintf(") ");
748
749         cprintf("NIL ");        /* Body ID */
750         cprintf("NIL ");        /* Body description */
751
752         if (encoding != NULL) if (strlen(encoding) > 0)  have_encoding = 1;
753         if (have_encoding) {
754                 imap_strout(encoding);
755         }
756         else {
757                 imap_strout("7BIT");
758         }
759         cprintf(" ");
760
761         /* The next field is the size of the part in bytes. */
762         cprintf("%ld ", (long)length);  /* bytes */
763
764         /* The next field is the number of lines in the part, if and only
765          * if the part is TEXT.  More gratuitous complexity.
766          */
767         if (!strcasecmp(cbmaintype, "TEXT")) {
768                 if (length) for (i=0; i<length; ++i) {
769                         if (((char *)content)[i] == '\n') ++lines;
770                 }
771                 cprintf("%d ", lines);
772         }
773
774         /* More gratuitous complexity */
775         if ((!strcasecmp(cbmaintype, "MESSAGE"))
776            && (!strcasecmp(cbsubtype, "RFC822"))) {
777                 /* FIXME
778                      A body type of type MESSAGE and subtype RFC822
779                      contains, immediately after the basic fields, the
780                      envelope structure, body structure, and size in
781                      text lines of the encapsulated message.
782                 */
783         }
784
785         /* MD5 value of body part; we can get away with NIL'ing this */
786         cprintf("NIL ");
787
788         /* Disposition */
789         if (disp == NULL) {
790                 cprintf("NIL");
791         }
792         else if (strlen(disp) == 0) {
793                 cprintf("NIL");
794         }
795         else {
796                 cprintf("(");
797                 imap_strout(disp);
798                 if (filename != NULL) if (strlen(filename)>0) {
799                         cprintf(" (\"FILENAME\" ");
800                         imap_strout(filename);
801                         cprintf(")");
802                 }
803                 cprintf(")");
804         }
805
806         /* Body language (not defined yet) */
807         cprintf(" NIL)");
808 }
809
810
811
812 /*
813  * Spew the BODYSTRUCTURE data for a message.
814  *
815  */
816 void imap_fetch_bodystructure (long msgnum, char *item,
817                 struct CtdlMessage *msg) {
818         char *rfc822 = NULL;
819         char *rfc822_body = NULL;
820         size_t rfc822_len;
821         size_t rfc822_headers_len;
822         size_t rfc822_body_len;
823         char *ptr = NULL;
824         char buf[SIZ];
825         int lines = 0;
826
827         /* For non-RFC822 (ordinary Citadel) messages, this is short and
828          * sweet...
829          */
830         if (msg->cm_format_type != FMT_RFC822) {
831
832                 /* *sigh* We have to RFC822-format the message just to be able
833                  * to measure it.  FIXME use smi cached fields if possible
834                  */
835
836                 CC->redirect_buffer = malloc(SIZ);
837                 CC->redirect_len = 0;
838                 CC->redirect_alloc = SIZ;
839                 CtdlOutputPreLoadedMsg(msg, msgnum, MT_RFC822, 0, 0, 1);
840                 rfc822 = CC->redirect_buffer;
841                 rfc822_len = CC->redirect_len;
842                 CC->redirect_buffer = NULL;
843                 CC->redirect_len = 0;
844                 CC->redirect_alloc = 0;
845
846                 ptr = rfc822;
847                 while (ptr = memreadline(ptr, buf, sizeof buf), *ptr != 0) {
848                         ++lines;
849                         if ((strlen(buf) == 0) && (rfc822_body == NULL)) {
850                                 rfc822_body = ptr;
851                         }
852                 }
853
854                 rfc822_headers_len = rfc822_body - rfc822;
855                 rfc822_body_len = rfc822_len - rfc822_headers_len;
856                 free(rfc822);
857
858                 cprintf("BODYSTRUCTURE (\"TEXT\" \"PLAIN\" "
859                         "(\"CHARSET\" \"US-ASCII\") NIL NIL "
860                         "\"7BIT\" %d %d)", rfc822_body_len, lines);
861
862                 return;
863         }
864
865         /* For messages already stored in RFC822 format, we have to parse. */
866         cprintf("BODYSTRUCTURE ");
867         mime_parser(msg->cm_fields['M'],
868                         NULL,
869                         *imap_fetch_bodystructure_part, /* part */
870                         *imap_fetch_bodystructure_pre,  /* pre-multi */
871                         *imap_fetch_bodystructure_post, /* post-multi */
872                         NULL,
873                         1);     /* don't decode -- we want it as-is */
874 }
875
876
877 /*
878  * imap_do_fetch() calls imap_do_fetch_msg() to output the data of an
879  * individual message, once it has been selected for output.
880  */
881 void imap_do_fetch_msg(int seq, int num_items, char **itemlist) {
882         int i;
883         struct CtdlMessage *msg = NULL;
884
885         cprintf("* %d FETCH (", seq);
886
887         for (i=0; i<num_items; ++i) {
888
889                 /* Fetchable without going to the message store at all */
890                 if (!strcasecmp(itemlist[i], "UID")) {
891                         imap_fetch_uid(seq);
892                 }
893                 else if (!strcasecmp(itemlist[i], "FLAGS")) {
894                         imap_fetch_flags(seq-1);
895                 }
896
897                 /* Potentially fetchable from cache, if the client requests
898                  * stuff from the same message several times in a row.
899                  */
900                 else if (!strcasecmp(itemlist[i], "RFC822")) {
901                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
902                 }
903                 else if (!strcasecmp(itemlist[i], "RFC822.HEADER")) {
904                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
905                 }
906                 else if (!strcasecmp(itemlist[i], "RFC822.SIZE")) {
907                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
908                 }
909                 else if (!strcasecmp(itemlist[i], "RFC822.TEXT")) {
910                         imap_fetch_rfc822(IMAP->msgids[seq-1], itemlist[i]);
911                 }
912
913                 /* BODY fetches do their own fetching and caching too. */
914                 else if (!strncasecmp(itemlist[i], "BODY[", 5)) {
915                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 0);
916                 }
917                 else if (!strncasecmp(itemlist[i], "BODY.PEEK[", 10)) {
918                         imap_fetch_body(IMAP->msgids[seq-1], itemlist[i], 1);
919                 }
920
921                 /* Otherwise, load the message into memory.
922                  */
923                 else if (!strcasecmp(itemlist[i], "BODYSTRUCTURE")) {
924                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
925                         imap_fetch_bodystructure(IMAP->msgids[seq-1],
926                                         itemlist[i], msg);
927                 }
928                 else if (!strcasecmp(itemlist[i], "ENVELOPE")) {
929                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
930                         imap_fetch_envelope(IMAP->msgids[seq-1], msg);
931                 }
932                 else if (!strcasecmp(itemlist[i], "INTERNALDATE")) {
933                         if (msg == NULL) msg = CtdlFetchMessage(IMAP->msgids[seq-1], 1);
934                         imap_fetch_internaldate(msg);
935                 }
936
937                 if (i != num_items-1) cprintf(" ");
938         }
939
940         cprintf(")\r\n");
941         if (msg != NULL) {
942                 CtdlFreeMessage(msg);
943         }
944 }
945
946
947
948 /*
949  * imap_fetch() calls imap_do_fetch() to do its actual work, once it's
950  * validated and boiled down the request a bit.
951  */
952 void imap_do_fetch(int num_items, char **itemlist) {
953         int i;
954
955         if (IMAP->num_msgs > 0) {
956                 for (i = 0; i < IMAP->num_msgs; ++i) {
957                         if (IMAP->flags[i] & IMAP_SELECTED) {
958                                 imap_do_fetch_msg(i+1, num_items, itemlist);
959                         }
960                 }
961         }
962 }
963
964
965
966 /*
967  * Back end for imap_handle_macros()
968  * Note that this function *only* looks at the beginning of the string.  It
969  * is not a generic search-and-replace function.
970  */
971 void imap_macro_replace(char *str, char *find, char *replace) {
972         char holdbuf[SIZ];
973
974         if (!strncasecmp(str, find, strlen(find))) {
975                 if (str[strlen(find)]==' ') {
976                         strcpy(holdbuf, &str[strlen(find)+1]);
977                         strcpy(str, replace);
978                         strcat(str, " ");
979                         strcat(str, holdbuf);
980                 }
981                 if (str[strlen(find)]==0) {
982                         strcpy(holdbuf, &str[strlen(find)+1]);
983                         strcpy(str, replace);
984                 }
985         }
986 }
987
988
989
990 /*
991  * Handle macros embedded in FETCH data items.
992  * (What the heck are macros doing in a wire protocol?  Are we trying to save
993  * the computer at the other end the trouble of typing a lot of characters?)
994  */
995 void imap_handle_macros(char *str) {
996         int i;
997         int nest = 0;
998
999         for (i=0; i<strlen(str); ++i) {
1000                 if (str[i]=='(') ++nest;
1001                 if (str[i]=='[') ++nest;
1002                 if (str[i]=='<') ++nest;
1003                 if (str[i]=='{') ++nest;
1004                 if (str[i]==')') --nest;
1005                 if (str[i]==']') --nest;
1006                 if (str[i]=='>') --nest;
1007                 if (str[i]=='}') --nest;
1008
1009                 if (nest <= 0) {
1010                         imap_macro_replace(&str[i],
1011                                 "ALL",
1012                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
1013                         );
1014                         imap_macro_replace(&str[i],
1015                                 "BODY",
1016                                 "BODYSTRUCTURE"
1017                         );
1018                         imap_macro_replace(&str[i],
1019                                 "FAST",
1020                                 "FLAGS INTERNALDATE RFC822.SIZE"
1021                         );
1022                         imap_macro_replace(&str[i],
1023                                 "FULL",
1024                                 "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY"
1025                         );
1026                 }
1027         }
1028 }
1029
1030
1031 /*
1032  * Break out the data items requested, possibly a parenthesized list.
1033  * Returns the number of data items, or -1 if the list is invalid.
1034  * NOTE: this function alters the string it is fed, and uses it as a buffer
1035  * to hold the data for the pointers it returns.
1036  */
1037 int imap_extract_data_items(char **argv, char *items) {
1038         int num_items = 0;
1039         int nest = 0;
1040         int i, initial_len;
1041         char *start;
1042
1043         /* Convert all whitespace to ordinary space characters. */
1044         for (i=0; i<strlen(items); ++i) {
1045                 if (isspace(items[i])) items[i]=' ';
1046         }
1047
1048         /* Strip leading and trailing whitespace, then strip leading and
1049          * trailing parentheses if it's a list
1050          */
1051         striplt(items);
1052         if ( (items[0]=='(') && (items[strlen(items)-1]==')') ) {
1053                 items[strlen(items)-1] = 0;
1054                 strcpy(items, &items[1]);
1055                 striplt(items);
1056         }
1057
1058         /* Parse any macro data items */
1059         imap_handle_macros(items);
1060
1061         /*
1062          * Now break out the data items.  We throw in one trailing space in
1063          * order to avoid having to break out the last one manually.
1064          */
1065         strcat(items, " ");
1066         start = items;
1067         initial_len = strlen(items);
1068         for (i=0; i<initial_len; ++i) {
1069                 if (items[i]=='(') ++nest;
1070                 if (items[i]=='[') ++nest;
1071                 if (items[i]=='<') ++nest;
1072                 if (items[i]=='{') ++nest;
1073                 if (items[i]==')') --nest;
1074                 if (items[i]==']') --nest;
1075                 if (items[i]=='>') --nest;
1076                 if (items[i]=='}') --nest;
1077
1078                 if (nest <= 0) if (items[i]==' ') {
1079                         items[i] = 0;
1080                         argv[num_items++] = start;
1081                         start = &items[i+1];
1082                 }
1083         }
1084
1085         return(num_items);
1086
1087 }
1088
1089
1090 /*
1091  * One particularly hideous aspect of IMAP is that we have to allow the client
1092  * to specify arbitrary ranges and/or sets of messages to fetch.  Citadel IMAP
1093  * handles this by setting the IMAP_SELECTED flag for each message specified in
1094  * the ranges/sets, then looping through the message array, outputting messages
1095  * with the flag set.  We don't bother returning an error if an out-of-range
1096  * number is specified (we just return quietly) because any client braindead
1097  * enough to request a bogus message number isn't going to notice the
1098  * difference anyway.
1099  *
1100  * This function clears out the IMAP_SELECTED bits, then sets that bit for each
1101  * message included in the specified range.
1102  *
1103  * Set is_uid to 1 to fetch by UID instead of sequence number.
1104  */
1105 void imap_pick_range(char *supplied_range, int is_uid) {
1106         int i;
1107         int num_sets;
1108         int s;
1109         char setstr[SIZ], lostr[SIZ], histr[SIZ];
1110         long lo, hi;
1111         char actual_range[SIZ];
1112
1113         /* 
1114          * Handle the "ALL" macro
1115          */
1116         if (!strcasecmp(supplied_range, "ALL")) {
1117                 safestrncpy(actual_range, "1:*", sizeof actual_range);
1118         }
1119         else {
1120                 safestrncpy(actual_range, supplied_range, sizeof actual_range);
1121         }
1122
1123         /*
1124          * Clear out the IMAP_SELECTED flags for all messages.
1125          */
1126         for (i = 0; i < IMAP->num_msgs; ++i) {
1127                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SELECTED;
1128         }
1129
1130         /*
1131          * Now set it for all specified messages.
1132          */
1133         num_sets = num_tokens(actual_range, ',');
1134         for (s=0; s<num_sets; ++s) {
1135                 extract_token(setstr, actual_range, s, ',');
1136
1137                 extract_token(lostr, setstr, 0, ':');
1138                 if (num_tokens(setstr, ':') >= 2) {
1139                         extract_token(histr, setstr, 1, ':');
1140                         if (!strcmp(histr, "*")) snprintf(histr, sizeof histr, "%ld", LONG_MAX);
1141                 } 
1142                 else {
1143                         strcpy(histr, lostr);
1144                 }
1145                 lo = atol(lostr);
1146                 hi = atol(histr);
1147
1148                 /* Loop through the array, flipping bits where appropriate */
1149                 for (i = 1; i <= IMAP->num_msgs; ++i) {
1150                         if (is_uid) {   /* fetch by sequence number */
1151                                 if ( (IMAP->msgids[i-1]>=lo)
1152                                    && (IMAP->msgids[i-1]<=hi)) {
1153                                         IMAP->flags[i-1] =
1154                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1155                                 }
1156                         }
1157                         else {          /* fetch by uid */
1158                                 if ( (i>=lo) && (i<=hi)) {
1159                                         IMAP->flags[i-1] =
1160                                                 IMAP->flags[i-1] | IMAP_SELECTED;
1161                                 }
1162                         }
1163                 }
1164         }
1165
1166 }
1167
1168
1169
1170 /*
1171  * This function is called by the main command loop.
1172  */
1173 void imap_fetch(int num_parms, char *parms[]) {
1174         char items[SIZ];
1175         char *itemlist[SIZ];
1176         int num_items;
1177         int i;
1178
1179         if (num_parms < 4) {
1180                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1181                 return;
1182         }
1183
1184         imap_pick_range(parms[2], 0);
1185
1186         strcpy(items, "");
1187         for (i=3; i<num_parms; ++i) {
1188                 strcat(items, parms[i]);
1189                 if (i < (num_parms-1)) strcat(items, " ");
1190         }
1191
1192         num_items = imap_extract_data_items(itemlist, items);
1193         if (num_items < 1) {
1194                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1195                 return;
1196         }
1197
1198         imap_do_fetch(num_items, itemlist);
1199         cprintf("%s OK FETCH completed\r\n", parms[0]);
1200 }
1201
1202 /*
1203  * This function is called by the main command loop.
1204  */
1205 void imap_uidfetch(int num_parms, char *parms[]) {
1206         char items[SIZ];
1207         char *itemlist[SIZ];
1208         int num_items;
1209         int i;
1210         int have_uid_item = 0;
1211
1212         if (num_parms < 5) {
1213                 cprintf("%s BAD invalid parameters\r\n", parms[0]);
1214                 return;
1215         }
1216
1217         imap_pick_range(parms[3], 1);
1218
1219         strcpy(items, "");
1220         for (i=4; i<num_parms; ++i) {
1221                 strcat(items, parms[i]);
1222                 if (i < (num_parms-1)) strcat(items, " ");
1223         }
1224
1225         num_items = imap_extract_data_items(itemlist, items);
1226         if (num_items < 1) {
1227                 cprintf("%s BAD invalid data item list\r\n", parms[0]);
1228                 return;
1229         }
1230
1231         /* If the "UID" item was not included, we include it implicitly
1232          * because this is a UID FETCH command
1233          */
1234         for (i=0; i<num_items; ++i) {
1235                 if (!strcasecmp(itemlist[i], "UID")) ++have_uid_item;
1236         }
1237         if (have_uid_item == 0) itemlist[num_items++] = "UID";
1238
1239         imap_do_fetch(num_items, itemlist);
1240         cprintf("%s OK UID FETCH completed\r\n", parms[0]);
1241 }
1242
1243