1 /* burst.c -- explode digests into individual messages
2  *
3  * This code is Copyright (c) 2002, by the authors of nmh.  See the
4  * COPYRIGHT file in the root directory of the nmh distribution for
5  * complete copyright information.
6  */
7 
8 #include <h/mh.h>
9 #include <h/utils.h>
10 #include <h/mhparse.h>
11 #include "../sbr/m_maildir.h"
12 #include "../sbr/m_mktemp.h"
13 #include "mhfree.h"
14 
15 #define BURST_SWITCHES \
16     X("inplace", 0, INPLSW) \
17     X("noinplace", 0, NINPLSW) \
18     X("mime", 0, MIMESW) \
19     X("nomime", 0, NMIMESW) \
20     X("automime", 0, AUTOMIMESW) \
21     X("quiet", 0, QIETSW) \
22     X("noquiet", 0, NQIETSW) \
23     X("verbose", 0, VERBSW) \
24     X("noverbose", 0, NVERBSW) \
25     X("version", 0, VERSIONSW) \
26     X("help", 0, HELPSW) \
27 
28 #define X(sw, minchars, id) id,
29 DEFINE_SWITCH_ENUM(BURST);
30 #undef X
31 
32 #define X(sw, minchars, id) { sw, minchars, id },
33 DEFINE_SWITCH_ARRAY(BURST, switches);
34 #undef X
35 
36 struct smsg {
37     off_t s_start;
38     off_t s_stop;
39 };
40 
41 /*
42  * For the MIME parsing routines
43  */
44 
45 int debugsw = 0;
46 
47 /*
48  * static prototypes
49  */
50 static int find_delim (int, struct smsg *, int *);
51 static void find_mime_parts (CT, struct smsg *, int *);
52 static void burst (struct msgs **, int, struct smsg *, int, int, int,
53 		   char *, int);
54 static void cpybrst (FILE *, FILE *, char *, char *, int, int);
55 
56 /*
57  * A macro to check to see if we have reached a message delimiter
58  * (an encapsulation boundary, EB, in RFC 934 parlance).
59  *
60  * According to RFC 934, an EB is simply a line which starts with
61  * a "-" and is NOT followed by a space.  So even a single "-" on a line
62  * by itself would be an EB.
63  */
64 
65 #define CHECKDELIM(buffer) (buffer[0] == '-' && buffer[1] != ' ')
66 
67 int
main(int argc,char ** argv)68 main (int argc, char **argv)
69 {
70     int inplace = 0, quietsw = 0, verbosw = 0, mimesw = 1;
71     int hi, msgnum, numburst;
72     char *cp, *maildir, *folder = NULL, buf[BUFSIZ];
73     char **argp, **arguments;
74     struct msgs_array msgs = { 0, 0, NULL };
75     struct smsg *smsgs;
76     struct msgs *mp;
77 
78     if (nmh_init(argv[0], 1)) { return 1; }
79 
80     arguments = getarguments (invo_name, argc, argv, 1);
81     argp = arguments;
82 
83     while ((cp = *argp++)) {
84 	if (*cp == '-') {
85 	    switch (smatch (++cp, switches)) {
86 	    case AMBIGSW:
87 		ambigsw (cp, switches);
88 		done (1);
89 	    case UNKWNSW:
90 		adios (NULL, "-%s unknown\n", cp);
91 
92 	    case HELPSW:
93 		snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
94 			invo_name);
95 		print_help (buf, switches, 1);
96 		done (0);
97 	    case VERSIONSW:
98 		print_version(invo_name);
99 		done (0);
100 
101 	    case INPLSW:
102 		inplace++;
103 		continue;
104 	    case NINPLSW:
105 		inplace = 0;
106 		continue;
107 
108 	    case MIMESW:
109 	    	mimesw = 2;
110 		continue;
111 	    case NMIMESW:
112 	    	mimesw = 0;
113 		continue;
114 	    case AUTOMIMESW:
115 	    	mimesw = 1;
116 		continue;
117 
118 	    case QIETSW:
119 		quietsw++;
120 		continue;
121 	    case NQIETSW:
122 		quietsw = 0;
123 		continue;
124 
125 	    case VERBSW:
126 		verbosw++;
127 		continue;
128 	    case NVERBSW:
129 		verbosw = 0;
130 		continue;
131 	    }
132 	}
133 	if (*cp == '+' || *cp == '@') {
134 	    if (folder)
135 		adios (NULL, "only one folder at a time!");
136 	    else
137 		folder = pluspath (cp);
138 	} else {
139 	    app_msgarg(&msgs, cp);
140 	}
141     }
142 
143     if (!context_find ("path"))
144 	free (path ("./", TFOLDER));
145     if (!msgs.size)
146 	app_msgarg(&msgs, "cur");
147     if (!folder)
148 	folder = getfolder (1);
149     maildir = m_maildir (folder);
150 
151     if (chdir (maildir) == NOTOK)
152 	adios (maildir, "unable to change directory to");
153 
154     /* read folder and create message structure */
155     if (!(mp = folder_read (folder, 1)))
156 	adios (NULL, "unable to read folder %s", folder);
157 
158     /* check for empty folder */
159     if (mp->nummsg == 0)
160 	adios (NULL, "no messages in %s", folder);
161 
162     /* parse all the message ranges/sequences and set SELECTED */
163     for (msgnum = 0; msgnum < msgs.size; msgnum++)
164 	if (!m_convert (mp, msgs.msgs[msgnum]))
165 	    done (1);
166     seq_setprev (mp);	/* set the previous-sequence */
167 
168     smsgs = mh_xcalloc(MAXFOLDER + 2, sizeof *smsgs);
169 
170     hi = mp->hghmsg + 1;
171 
172     /* burst all the SELECTED messages */
173     for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
174 	if (is_selected (mp, msgnum)) {
175 	    if ((numburst = find_delim (msgnum, smsgs, &mimesw)) >= 1) {
176 		if (verbosw)
177 		    printf ("%d message%s exploded from digest %d\n",
178 			    numburst, PLURALS(numburst), msgnum);
179 		burst (&mp, msgnum, smsgs, numburst, inplace, verbosw,
180 		       maildir, mimesw);
181 	    } else {
182 		if (numburst == 0) {
183 		    if (!quietsw)
184 			inform("message %d not in digest format, continuing...",
185 				  msgnum);
186 		}  /* this pair of braces was missing before 1999-07-15 */
187 		else
188 		    adios (NULL, "burst() botch -- you lose big");
189 	    }
190 	}
191     }
192 
193     free(smsgs);
194     context_replace (pfolder, folder);	/* update current folder */
195 
196     /*
197      * If -inplace is given, then the first message burst becomes
198      * the current message (which will now show a table of contents).
199      * Otherwise, the first message extracted from the first digest
200      * becomes the current message.
201      */
202     if (inplace) {
203 	if (mp->lowsel != mp->curmsg)
204 	    seq_setcur (mp, mp->lowsel);
205     } else {
206 	if (hi <= mp->hghmsg)
207 	    seq_setcur (mp, hi);
208     }
209 
210     seq_save (mp);	/* synchronize message sequences */
211     context_save ();	/* save the context file         */
212     folder_free (mp);	/* free folder/message structure */
213     done (0);
214     return 1;
215 }
216 
217 
218 /*
219  * Scan the message and find the beginning and
220  * end of all the messages in the digest.
221  *
222  * If requested, see if the message is MIME-formatted and contains any
223  * message/rfc822 parts; if so, burst those parts.
224  */
225 
226 static int
find_delim(int msgnum,struct smsg * smsgs,int * mimesw)227 find_delim (int msgnum, struct smsg *smsgs, int *mimesw)
228 {
229     int wasdlm = 0, msgp;
230     off_t pos;
231     char c, *msgnam;
232     char buffer[BUFSIZ];
233     FILE *in;
234     CT content;
235 
236     msgnam = m_name (msgnum);
237 
238     /*
239      * If mimesw is 1 or 2, try to see if it's got proper MIME formatting.
240      */
241 
242     if (*mimesw > 0) {
243     	content = parse_mime(msgnam);
244 	if (! content && *mimesw == 2)
245 	    return 0;
246 	if (content) {
247 	    smsgs[0].s_start = 0;
248 	    smsgs[0].s_stop = content->c_begin - 1;
249 	    msgp = 1;
250 	    find_mime_parts(content, smsgs, &msgp);
251 	    free_content(content);
252 	    if (msgp == 1 && *mimesw == 2) {
253 	    	adios (msgnam, "does not have any message/rfc822 parts");
254 	    } else if (msgp > 1) {
255 	    	*mimesw = 1;
256 		return (msgp - 1);
257 	    }
258 	}
259     }
260 
261     *mimesw = 0;
262 
263     if ((in = fopen (msgnam, "r")) == NULL)
264 	adios (msgnam, "unable to read message");
265 
266     for (msgp = 0, pos = 0L; msgp <= MAXFOLDER;) {
267     	/*
268 	 * We're either at the beginning of the whole message, or
269 	 * we're just past the delimiter of the last message.
270 	 * Swallow lines until we get to something that's not a newline
271 	 */
272 	while (fgets (buffer, sizeof(buffer), in) && buffer[0] == '\n')
273 	    pos += (long) strlen (buffer);
274 	if (feof (in))
275 	    break;
276 
277 	/*
278 	 * Reset to the beginning of the last non-blank line, and save our
279 	 * starting position.  This is where the encapsulated message
280 	 * starts.
281 	 */
282 	fseeko (in, pos, SEEK_SET);
283 	smsgs[msgp].s_start = pos;
284 
285 	/*
286 	 * Read in lines until we get to a message delimiter.
287 	 *
288 	 * Previously we checked to make sure the preceding line and
289 	 * next line was a newline.  That actually does not comply with
290 	 * RFC 934, so make sure we break on a message delimiter even
291 	 * if the previous character was NOT a newline.
292 	 */
293 	for (c = 0; fgets (buffer, sizeof(buffer), in); c = buffer[0]) {
294 	    if ((wasdlm = CHECKDELIM(buffer)))
295 		break;
296 	    else
297 		pos += (long) strlen (buffer);
298 	}
299 
300 	/*
301 	 * Only count as a new message if we got the message delimiter.
302 	 * Swallow a blank line if it was right before the message delimiter.
303 	 */
304 	if (smsgs[msgp].s_start != pos && wasdlm)
305 	    smsgs[msgp++].s_stop = (c == '\n' && wasdlm) ? pos - 1 : pos;
306 
307 	if (feof (in)) {
308 #if 0
309 	    if (wasdlm) {
310 		smsgs[msgp - 1].s_stop -= ((long) strlen (buffer) + 1);
311 		msgp++;		/* fake "End of XXX Digest" */
312 	    }
313 #endif
314 	    break;
315 	}
316 	pos += (long) strlen (buffer);
317     }
318 
319     fclose (in);
320     return (msgp - 1);		/* return the number of messages burst */
321 }
322 
323 
324 /*
325  * Find any MIME content in the message that is a message/rfc822 and add
326  * it to the list of messages to burst.
327  */
328 
329 static void
find_mime_parts(CT content,struct smsg * smsgs,int * msgp)330 find_mime_parts (CT content, struct smsg *smsgs, int *msgp)
331 {
332     struct multipart *m;
333     struct part *part;
334 
335     /*
336      * If we have a message/rfc822, then it's easy.
337      */
338 
339     if (content->c_type == CT_MESSAGE &&
340     			content->c_subtype == MESSAGE_RFC822) {
341 	smsgs[*msgp].s_start = content->c_begin;
342 	smsgs[*msgp].s_stop = content->c_end;
343 	(*msgp)++;
344 	return;
345     }
346 
347     /*
348      * Otherwise, if we do have multiparts, try all of the sub-parts.
349      */
350 
351     if (content->c_type == CT_MULTIPART) {
352     	m = (struct multipart *) content->c_ctparams;
353 
354 	for (part = m->mp_parts; part; part = part->mp_next)
355 	    find_mime_parts(part->mp_part, smsgs, msgp);
356     }
357 }
358 
359 
360 /*
361  * Burst out the messages in the digest into the folder
362  */
363 
364 static void
burst(struct msgs ** mpp,int msgnum,struct smsg * smsgs,int numburst,int inplace,int verbosw,char * maildir,int mimesw)365 burst (struct msgs **mpp, int msgnum, struct smsg *smsgs, int numburst,
366 	int inplace, int verbosw, char *maildir, int mimesw)
367 {
368     int i, j, mode;
369     char *msgnam;
370     char f1[BUFSIZ], f2[BUFSIZ], f3[BUFSIZ];
371     FILE *in, *out;
372     struct stat st;
373     struct msgs *mp;
374 
375     if ((in = fopen (msgnam = m_name (msgnum), "r")) == NULL)
376 	adios (msgnam, "unable to read message");
377 
378     mode =
379       fstat (fileno(in), &st) != NOTOK ? (int) (st.st_mode & 0777) : m_gmprot();
380     mp = *mpp;
381 
382     /*
383      * See if we have enough space in the folder
384      * structure for all the new messages.
385      */
386     if ((mp->hghmsg + numburst > mp->hghoff) &&
387 	!(mp = folder_realloc (mp, mp->lowoff, mp->hghmsg + numburst)))
388 	adios (NULL, "unable to allocate folder storage");
389     *mpp = mp;
390 
391     j = mp->hghmsg;		/* old value */
392     mp->hghmsg += numburst;
393     mp->nummsg += numburst;
394 
395     /*
396      * If this is not the highest SELECTED message, then
397      * increment mp->hghsel by numburst, since the highest
398      * SELECTED is about to be slid down by that amount.
399      */
400     if (msgnum < mp->hghsel)
401 	mp->hghsel += numburst;
402 
403     /*
404      * If -inplace is given, renumber the messages after the
405      * source message, to make room for each of the messages
406      * contained within the digest.
407      *
408      * This is equivalent to refiling a message from the point
409      * of view of the external hooks.
410      */
411     if (inplace) {
412 	for (i = mp->hghmsg; j > msgnum; i--, j--) {
413 	    strncpy (f1, m_name (i), sizeof(f1));
414 	    strncpy (f2, m_name (j), sizeof(f2));
415 	    if (does_exist (mp, j)) {
416 		if (verbosw)
417 		    printf ("message %d becomes message %d\n", j, i);
418 
419 		if (rename (f2, f1) == NOTOK)
420 		    admonish (f1, "unable to rename %s to", f2);
421 
422 		(void)snprintf(f1, sizeof (f1), "%s/%d", maildir, i);
423 		(void)snprintf(f2, sizeof (f2), "%s/%d", maildir, j);
424 		ext_hook("ref-hook", f1, f2);
425 
426 		copy_msg_flags (mp, i, j);
427 		clear_msg_flags (mp, j);
428 		mp->msgflags |= SEQMOD;
429 	    }
430 	}
431     }
432 
433     unset_selected (mp, msgnum);
434 
435     /* new hghmsg is hghmsg + numburst
436      *
437      * At this point, there is an array of numburst smsgs, each element of
438      * which contains the starting and stopping offsets (seeks) of the message
439      * in the digest.  The inplace flag is set if the original digest is replaced
440      * by a message containing the table of contents.  smsgs[0] is that table of
441      * contents.  Go through the message numbers in reverse order (high to low).
442      *
443      * Set f1 to the name of the destination message, f2 to the name of a scratch
444      * file.  Extract a message from the digest to the scratch file.  Move the
445      * original message to a backup file if the destination message number is the
446      * same as the number of the original message, which only happens if the
447      * inplace flag is set.  Then move the scratch file to the destination message.
448      *
449      * Moving the original message to the backup file is equivalent to deleting the
450      * message from the point of view of the external hooks.  And bursting each
451      * message is equivalent to adding a new message.
452      */
453 
454     i = inplace ? msgnum + numburst : mp->hghmsg;
455     for (j = numburst; j >= (inplace ? 0 : 1); i--, j--) {
456         char *tempfile;
457 
458 	if ((tempfile = m_mktemp2(NULL, invo_name, NULL, &out)) == NULL) {
459 	    adios(NULL, "unable to create temporary file in %s",
460 		  get_temp_dir());
461 	}
462 	strncpy (f2, tempfile, sizeof(f2));
463 	strncpy (f1, m_name (i), sizeof(f1));
464 
465 	if (verbosw && i != msgnum)
466 	    printf ("message %d of digest %d becomes message %d\n", j, msgnum, i);
467 
468 	chmod (f2, mode);
469 	fseeko (in, smsgs[j].s_start, SEEK_SET);
470 	cpybrst (in, out, msgnam, f2,
471 		(int) (smsgs[j].s_stop - smsgs[j].s_start), mimesw);
472 	fclose (out);
473 
474 	if (i == msgnum) {
475 	    strncpy (f3, m_backup (f1), sizeof(f3));
476 	    if (rename (f1, f3) == NOTOK)
477 		admonish (f3, "unable to rename %s to", f1);
478 
479 	    (void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
480 	    ext_hook("del-hook", f3, NULL);
481 	}
482 	if (rename (f2, f1) == NOTOK)
483 	    admonish (f1, "unable to rename %s to", f2);
484 
485 	(void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
486 	ext_hook("add-hook", f3, NULL);
487 
488 	copy_msg_flags (mp, i, msgnum);
489 	mp->msgflags |= SEQMOD;
490     }
491 
492     fclose (in);
493 }
494 
495 
496 #define	S1  0
497 #define	S2  1
498 #define	S3  2
499 #define S4  3
500 
501 /*
502  * Copy a message which is being burst out of a digest.
503  * It will remove any "dashstuffing" in the message.
504  */
505 
506 static void
cpybrst(FILE * in,FILE * out,char * ifile,char * ofile,int len,int mime)507 cpybrst (FILE *in, FILE *out, char *ifile, char *ofile, int len, int mime)
508 {
509     int c, state;
510 
511     for (state = mime ? S4 : S1; (c = fgetc (in)) != EOF && len > 0; len--) {
512 	if (c == 0)
513 	    continue;
514 	switch (state) {
515 	    case S1:
516 		switch (c) {
517 		    case '-':
518 			state = S3;
519 			break;
520 
521 		    default:
522 			state = S2;
523 			/* FALLTHRU */
524 		    case '\n':
525 			fputc (c, out);
526 			break;
527 		}
528 		break;
529 
530 	    case S2:
531 		switch (c) {
532 		    case '\n':
533 			state = S1;
534 			/* FALLTHRU */
535 		    default:
536 			fputc (c, out);
537 			break;
538 		}
539 		break;
540 
541 	    case S3:
542 		switch (c) {
543 		    case ' ':
544 			state = S2;
545 			break;
546 
547 		    default:
548 			state = (c == '\n') ? S1 : S2;
549 			fputc ('-', out);
550 			fputc (c, out);
551 			break;
552 		}
553 		break;
554 
555 	    case S4:
556 	   	fputc (c, out);
557 		break;
558 	}
559     }
560 
561     if (ferror (in) && !feof (in))
562 	adios (ifile, "error reading");
563     if (ferror (out))
564 	adios (ofile, "error writing");
565 }
566