1 /* mhstoresbr.c -- routines to save/store the contents of MIME 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 <fcntl.h>
10 #include <h/md5.h>
11 #include <h/mts.h>
12 #include <h/tws.h>
13 #include <h/fmt_scan.h>
14 #include <h/mime.h>
15 #include <h/mhparse.h>
16 #include <h/utils.h>
17 #include "mhshowsbr.h"
18 #include "../sbr/m_maildir.h"
19 #include "../sbr/m_mktemp.h"
20 
21 enum clobber_policy_t {
22   NMH_CLOBBER_ALWAYS = 0,
23   NMH_CLOBBER_AUTO,
24   NMH_CLOBBER_SUFFIX,
25   NMH_CLOBBER_ASK,
26   NMH_CLOBBER_NEVER
27 };
28 
29 static enum clobber_policy_t clobber_policy (const char *);
30 
31 struct mhstoreinfo {
32     CT *cts;                 /* Top-level list of contents to store. */
33     char *cwd;               /* cached current directory */
34     int autosw;              /* -auto enabled */
35     int verbosw;             /* -verbose enabled */
36     int files_not_clobbered; /* output flag indicating that store failed
37                                 in order to not clobber an existing file */
38 
39     /* The following must never be touched by a caller:  they are for
40        internal use by the mhstoresbr functions. */
41     char *dir;               /* directory in which to store contents */
42     enum clobber_policy_t clobber_policy;  /* -clobber selection */
43 };
44 
45 static bool use_param_as_filename(const char *p);
46 
47 mhstoreinfo_t
mhstoreinfo_create(CT * ct,char * pwd,const char * csw,int asw,int vsw)48 mhstoreinfo_create (CT *ct, char *pwd, const char *csw, int asw, int vsw) {
49     mhstoreinfo_t info;
50 
51     NEW(info);
52     info->cts = ct;
53     info->cwd = pwd;
54     info->autosw = asw;
55     info->verbosw = vsw;
56     info->files_not_clobbered = 0;
57     info->dir = NULL;
58     info->clobber_policy = clobber_policy (csw);
59 
60     return info;
61 }
62 
63 void
mhstoreinfo_free(mhstoreinfo_t info)64 mhstoreinfo_free (mhstoreinfo_t info) {
65     free (info->cwd);
66     free (info->dir);
67     free (info);
68 }
69 
70 int
mhstoreinfo_files_not_clobbered(const mhstoreinfo_t info)71 mhstoreinfo_files_not_clobbered (const mhstoreinfo_t info) {
72     return info->files_not_clobbered;
73 }
74 
75 
76 /*
77  * Type for a compare function for qsort.  This keeps
78  * the compiler happy.
79  */
80 typedef int (*qsort_comp) (const void *, const void *);
81 
82 
83 /* mhmisc.c */
84 int part_ok (CT);
85 int type_ok (CT, int);
86 void flush_errors (void);
87 
88 /*
89  * static prototypes
90  */
91 static void store_single_message (CT, mhstoreinfo_t);
92 static int store_switch (CT, mhstoreinfo_t);
93 static int store_generic (CT, mhstoreinfo_t);
94 static int store_application (CT, mhstoreinfo_t);
95 static int store_multi (CT, mhstoreinfo_t);
96 static int store_partial (CT, mhstoreinfo_t);
97 static int store_external (CT, mhstoreinfo_t);
98 static int ct_compar (CT *, CT *);
99 static int store_content (CT, CT, mhstoreinfo_t);
100 static int output_content_file (CT, int);
101 static int output_content_folder (char *, char *);
102 static int parse_format_string (CT, char *, char *, int, char *);
103 static void get_storeproc (CT);
104 static int copy_some_headers (FILE *, CT);
105 static char *clobber_check (char *, mhstoreinfo_t);
106 
107 /*
108  * Main entry point to store content
109  * from a collection of messages.
110  */
111 
112 void
store_all_messages(mhstoreinfo_t info)113 store_all_messages (mhstoreinfo_t info)
114 {
115     CT ct, *ctp;
116     char *cp;
117 
118     /*
119      * Check for the directory in which to
120      * store any contents.
121      */
122     if ((cp = context_find (nmhstorage)) && *cp)
123 	info->dir = mh_xstrdup(cp);
124     else
125 	info->dir = getcpy (info->cwd);
126 
127     for (ctp = info->cts; *ctp; ctp++) {
128 	ct = *ctp;
129 	store_single_message (ct, info);
130     }
131 
132     flush_errors ();
133 }
134 
135 
136 /*
137  * Entry point to store the content
138  * in a (single) message
139  */
140 
141 static void
store_single_message(CT ct,mhstoreinfo_t info)142 store_single_message (CT ct, mhstoreinfo_t info)
143 {
144     if (type_ok (ct, 1)) {
145 	umask (ct->c_umask);
146 	store_switch (ct, info);
147 	if (ct->c_fp) {
148 	    fclose (ct->c_fp);
149 	    ct->c_fp = NULL;
150 	}
151 	if (ct->c_ceclosefnx)
152 	    (*ct->c_ceclosefnx) (ct);
153     }
154 }
155 
156 
157 /*
158  * Switching routine to store different content types
159  */
160 
161 static int
store_switch(CT ct,mhstoreinfo_t info)162 store_switch (CT ct, mhstoreinfo_t info)
163 {
164     switch (ct->c_type) {
165 	case CT_MULTIPART:
166 	    return store_multi (ct, info);
167 
168 	case CT_MESSAGE:
169 	    switch (ct->c_subtype) {
170 		case MESSAGE_PARTIAL:
171 		    return store_partial (ct, info);
172 
173 		case MESSAGE_EXTERNAL:
174 		    return store_external (ct, info);
175 
176 		case MESSAGE_RFC822:
177 		default:
178 		    return store_generic (ct, info);
179 	    }
180 
181 	case CT_APPLICATION:
182 	default:
183 	    return store_application (ct, info);
184 
185 	case CT_TEXT:
186 	case CT_AUDIO:
187 	case CT_IMAGE:
188 	case CT_VIDEO:
189 	    return store_generic (ct, info);
190     }
191 
192     return OK;	/* NOT REACHED */
193 }
194 
195 
196 /*
197  * Generic routine to store a MIME content.
198  * (audio, video, image, text, message/rfc822)
199  */
200 
201 static int
store_generic(CT ct,mhstoreinfo_t info)202 store_generic (CT ct, mhstoreinfo_t info)
203 {
204     /*
205      * Check if the content specifies a filename.
206      * Don't bother with this for type "message"
207      * (only "message/rfc822" will use store_generic).
208      */
209     if (info->autosw && ct->c_type != CT_MESSAGE)
210 	get_storeproc (ct);
211 
212     return store_content (ct, NULL, info);
213 }
214 
215 
216 /*
217  * Store content of type "application"
218  */
219 
220 static int
store_application(CT ct,mhstoreinfo_t info)221 store_application (CT ct, mhstoreinfo_t info)
222 {
223     CI ci = &ct->c_ctinfo;
224 
225     /* Check if the content specifies a filename */
226     if (info->autosw)
227 	get_storeproc (ct);
228 
229     /*
230      * If storeproc is not defined, and the content is type
231      * "application/octet-stream", we also check for various
232      * attribute/value pairs which specify if this a tar file.
233      */
234     if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) {
235 	int tarP = 0, zP = 0, gzP = 0;
236 	char *cp;
237 
238 	if ((cp = get_param(ci->ci_first_pm, "type", ' ', 1))) {
239 	    if (strcasecmp (cp, "tar") == 0)
240 		tarP = 1;
241 	}
242 
243 	/* check for "conversions=compress" attribute */
244 	if ((cp = get_param(ci->ci_first_pm, "conversions", ' ', 1)) ||
245 	    (cp = get_param(ci->ci_first_pm, "x-conversions", ' ', 1))) {
246 	    if (strcasecmp (cp, "compress") == 0 ||
247 		    strcasecmp (cp, "x-compress") == 0) {
248 		zP = 1;
249 	    }
250 	    if (strcasecmp (cp, "gzip") == 0 ||
251 		    strcasecmp (cp, "x-gzip") == 0) {
252 		gzP = 1;
253 	    }
254 	}
255 
256 	if (tarP) {
257 	    ct->c_showproc = add (zP ? "%euncompress | tar tvf -"
258 				  : (gzP ? "%egzip -dc | tar tvf -"
259 				     : "%etar tvf -"), NULL);
260 	    if (!ct->c_storeproc) {
261 		if (info->autosw) {
262 		    ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -"
263 					   : (gzP ? "| gzip -dc | tar xvpf -"
264 					      : "| tar xvpf -"), NULL);
265 		    ct->c_umask = 0022;
266 		} else {
267 		    ct->c_storeproc= add (zP ? "%m%P.tar.Z"
268 				          : (gzP ? "%m%P.tar.gz"
269 					     : "%m%P.tar"), NULL);
270 		}
271 	    }
272 	}
273     }
274 
275     return store_content (ct, NULL, info);
276 }
277 
278 
279 /*
280  * Store the content of a multipart message
281  */
282 
283 static int
store_multi(CT ct,mhstoreinfo_t info)284 store_multi (CT ct, mhstoreinfo_t info)
285 {
286     int	result;
287     struct multipart *m = (struct multipart *) ct->c_ctparams;
288     struct part *part;
289 
290     result = NOTOK;
291     for (part = m->mp_parts; part; part = part->mp_next) {
292 	CT  p = part->mp_part;
293 
294 	if (part_ok (p) && type_ok (p, 1)) {
295 	    if (ct->c_storage) {
296 		/* Support mhstore -outfile.  The MIME parser doesn't
297 		   load c_storage, so we know that p->c_storage is
298 		   NULL here. */
299 		p->c_storage = mh_xstrdup(ct->c_storage);
300 	    }
301 	    result = store_switch (p, info);
302 
303 	    if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
304 		break;
305 	}
306     }
307 
308     return result;
309 }
310 
311 
312 /*
313  * Reassemble and store the contents of a collection
314  * of messages of type "message/partial".
315  */
316 
317 static int
store_partial(CT ct,mhstoreinfo_t info)318 store_partial (CT ct, mhstoreinfo_t info)
319 {
320     int	cur, hi, i;
321     CT p, *ctp, *ctq;
322     CT *base;
323     struct partial *pm, *qm;
324 
325     qm = (struct partial *) ct->c_ctparams;
326     if (qm->pm_stored)
327 	return OK;
328 
329     hi = i = 0;
330     for (ctp = info->cts; *ctp; ctp++) {
331 	p = *ctp;
332 	if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
333 	    pm = (struct partial *) p->c_ctparams;
334 	    if (!pm->pm_stored
335 	            && strcmp (qm->pm_partid, pm->pm_partid) == 0) {
336 		pm->pm_marked = pm->pm_partno;
337 		if (pm->pm_maxno)
338 		    hi = pm->pm_maxno;
339 		pm->pm_stored = 1;
340 		i++;
341 	    }
342 	    else
343 		pm->pm_marked = 0;
344 	}
345     }
346 
347     if (hi == 0) {
348 	inform("missing (at least) last part of multipart message");
349 	return NOTOK;
350     }
351 
352     base = mh_xcalloc(i + 1, sizeof *base);
353     ctq = base;
354     for (ctp = info->cts; *ctp; ctp++) {
355 	p = *ctp;
356 	if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
357 	    pm = (struct partial *) p->c_ctparams;
358 	    if (pm->pm_marked)
359 		*ctq++ = p;
360 	}
361     }
362     *ctq = NULL;
363 
364     if (i > 1)
365 	qsort ((char *) base, i, sizeof(*base), (qsort_comp) ct_compar);
366 
367     cur = 1;
368     for (ctq = base; *ctq; ctq++) {
369 	p = *ctq;
370 	pm = (struct partial *) p->c_ctparams;
371 	if (pm->pm_marked == cur) {
372 	    cur++;
373             continue;
374         }
375 
376         if (pm->pm_marked == cur - 1) {
377             inform("duplicate part %d of %d part multipart message, continuing...",
378                       pm->pm_marked, hi);
379             continue;
380         }
381 
382 missing_part:
383         inform("missing %spart %d of %d part multipart message",
384             cur != hi ? "(at least) " : "", cur, hi);
385         goto losing;
386     }
387     if (hi != --cur) {
388 	cur = hi;
389 	goto missing_part;
390     }
391 
392     /*
393      * Now cycle through the sorted list of messages of type
394      * "message/partial" and save/append them to a file.
395      */
396 
397     ctq = base;
398     ct = *ctq++;
399     if (store_content (ct, NULL, info) == NOTOK) {
400 losing:
401 	free(base);
402 	return NOTOK;
403     }
404 
405     for (; *ctq; ctq++) {
406 	p = *ctq;
407 	if (store_content (p, ct, info) == NOTOK)
408 	    goto losing;
409     }
410 
411     free(base);
412     return OK;
413 }
414 
415 
416 /*
417  * Store content from a message of type "message/external".
418  */
419 
420 static int
store_external(CT ct,mhstoreinfo_t info)421 store_external (CT ct, mhstoreinfo_t info)
422 {
423     int	result = NOTOK;
424     struct exbody *e = (struct exbody *) ct->c_ctparams;
425     CT p = e->eb_content;
426 
427     if (!type_ok (p, 1))
428 	return OK;
429 
430     /*
431      * Check if the parameters for the external body
432      * specified a filename.
433      */
434     if (info->autosw) {
435 	char *cp;
436 
437 	if ((cp = e->eb_name) && use_param_as_filename(cp)) {
438 	    if (!ct->c_storeproc)
439 		ct->c_storeproc = mh_xstrdup(cp);
440 	    if (!p->c_storeproc)
441 		p->c_storeproc = mh_xstrdup(cp);
442 	}
443     }
444 
445     /*
446      * Since we will let the Content structure for the
447      * external body substitute for the current content,
448      * we temporarily change its partno (number inside
449      * multipart), so everything looks right.
450      */
451     p->c_partno = ct->c_partno;
452 
453     /* we probably need to check if content is really there */
454     if (ct->c_storage) {
455 	/* Support mhstore -outfile.  The MIME parser doesn't load
456 	   c_storage, so we know that p->c_storage is NULL here. */
457 	p->c_storage = mh_xstrdup(ct->c_storage);
458     }
459     result = store_switch (p, info);
460 
461     p->c_partno = NULL;
462     return result;
463 }
464 
465 
466 /*
467  * Compare the numbering from two different
468  * message/partials (needed for sorting).
469  */
470 
471 static int
ct_compar(CT * a,CT * b)472 ct_compar (CT *a, CT *b)
473 {
474     struct partial *am = (struct partial *) ((*a)->c_ctparams);
475     struct partial *bm = (struct partial *) ((*b)->c_ctparams);
476 
477     return (am->pm_marked - bm->pm_marked);
478 }
479 
480 
481 /*
482  * Store contents of a message or message part to
483  * a folder, a file, the standard output, or pass
484  * the contents to a command.
485  *
486  * If the current content to be saved is a followup part
487  * to a collection of messages of type "message/partial",
488  * then field "p" is a pointer to the Content structure
489  * to the first message/partial in the group.
490  */
491 
492 static int
store_content(CT ct,CT p,mhstoreinfo_t info)493 store_content (CT ct, CT p, mhstoreinfo_t info)
494 {
495     int appending = 0, msgnum = 0;
496     int is_partial = 0, first_partial = 0;
497     int last_partial = 0;
498     char *cp, buffer[BUFSIZ];
499 
500     /*
501      * Do special processing for messages of
502      * type "message/partial".
503      *
504      * We first check if this content is of type
505      * "message/partial".  If it is, then we need to check
506      * whether it is the first and/or last in the group.
507      *
508      * Then if "p" is a valid pointer, it points to the Content
509      * structure of the first partial in the group.  So we copy
510      * the file name and/or folder name from that message.  In
511      * this case, we also note that we will be appending.
512      */
513     if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
514 	struct partial *pm = (struct partial *) ct->c_ctparams;
515 
516 	/* Yep, it's a message/partial */
517 	is_partial = 1;
518 
519 	/* But is it the first and/or last in the collection? */
520 	if (pm->pm_partno == 1)
521 	    first_partial = 1;
522 	if (pm->pm_maxno && pm->pm_partno == pm->pm_maxno)
523 	    last_partial = 1;
524 
525 	/*
526 	 * If "p" is a valid pointer, then it points to the
527 	 * Content structure for the first message in the group.
528 	 * So we just copy the filename or foldername information
529 	 * from the previous iteration of this function.
530 	 */
531 	if (p) {
532 	    appending = 1;
533             if (! ct->c_storage) {
534 		ct->c_storage = add (p->c_storage, NULL);
535 
536 		/* record the folder name */
537 		if (p->c_folder) {
538 		    ct->c_folder = mh_xstrdup(p->c_folder);
539 		}
540 	    }
541 	    goto got_filename;
542 	}
543     }
544 
545     /*
546      * Get storage formatting string.
547      *
548      * 1) If we have storeproc defined, then use that
549      * 2) Else check for a mhn-store-<type>/<subtype> entry
550      * 3) Else check for a mhn-store-<type> entry
551      * 4) Else if content is "message", use "+" (current folder)
552      * 5) Else use string "%m%P.%s".
553      */
554     if ((cp = ct->c_storeproc) == NULL || *cp == '\0') {
555 	CI ci = &ct->c_ctinfo;
556 
557 	cp = context_find_by_type ("store", ci->ci_type, ci->ci_subtype);
558 	if (cp == NULL) {
559 	    cp = ct->c_type == CT_MESSAGE ? "+" : "%m%P.%s";
560 	}
561     }
562 
563     if (! ct->c_storage) {
564 	/*
565 	 * Check the beginning of storage formatting string
566 	 * to see if we are saving content to a folder.
567 	 */
568 	if (*cp == '+' || *cp == '@') {
569 	    char *tmpfilenam, *folder;
570 
571 	    /* Store content in temporary file for now */
572 	    if ((tmpfilenam = m_mktemp(invo_name, NULL, NULL)) == NULL) {
573 		adios(NULL, "unable to create temporary file in %s",
574 		      get_temp_dir());
575 	    }
576 	    ct->c_storage = mh_xstrdup(tmpfilenam);
577 
578 	    /* Get the folder name */
579 	    if (cp[1])
580 		folder = pluspath (cp);
581 	    else
582 		folder = getfolder (1);
583 
584 	    /* Check if folder exists */
585 	    create_folder(m_mailpath(folder), 0, exit);
586 
587 	    /* Record the folder name */
588 	    ct->c_folder = mh_xstrdup(folder);
589 
590 	    if (cp[1])
591 		free (folder);
592 
593 	    goto got_filename;
594 	}
595 
596 	/*
597 	 * Parse and expand the storage formatting string
598 	 * in `cp' into `buffer'.
599 	 */
600 	parse_format_string (ct, cp, buffer, sizeof(buffer), info->dir);
601 
602 	/*
603 	 * If formatting begins with '|' or '!', then pass
604 	 * content to standard input of a command and return.
605 	 */
606 	if (buffer[0] == '|' || buffer[0] == '!')
607 	    return show_content_aux (ct, 0, buffer + 1, info->dir, NULL);
608 
609         /* record the filename */
610 	if ((ct->c_storage = clobber_check (mh_xstrdup(buffer), info)) ==
611 	    NULL) {
612 	    return NOTOK;
613 	}
614     } else {
615 	/* The output filename was explicitly specified, so use it. */
616 	if ((ct->c_storage = clobber_check (ct->c_storage, info)) ==
617 	    NULL) {
618 	    return NOTOK;
619 	}
620     }
621 
622 got_filename:
623     /* flush the output stream */
624     fflush (stdout);
625 
626     /* Now save or append the content to a file */
627     if (output_content_file (ct, appending) == NOTOK)
628 	return NOTOK;
629 
630     /*
631      * If necessary, link the file into a folder and remove
632      * the temporary file.  If this message is a partial,
633      * then only do this if it is the last one in the group.
634      */
635     if (ct->c_folder && (!is_partial || last_partial)) {
636 	msgnum = output_content_folder (ct->c_folder, ct->c_storage);
637 	(void) m_unlink (ct->c_storage);
638 	if (msgnum == NOTOK)
639 	    return NOTOK;
640     }
641 
642     if (info->verbosw) {
643         /*
644          * Now print out the name/number of the message
645          * that we are storing.
646          */
647         if (is_partial) {
648             if (first_partial)
649                 fprintf (stderr, "reassembling partials ");
650             if (last_partial)
651                 fputs(ct->c_file, stderr);
652             else
653                 fprintf (stderr, "%s,", ct->c_file);
654         } else {
655             fprintf (stderr, "storing message %s", ct->c_file);
656             if (ct->c_partno)
657                 fprintf (stderr, " part %s", ct->c_partno);
658         }
659 
660         /*
661          * Unless we are in the "middle" of group of message/partials,
662          * we now print the name of the file, folder, and/or message
663          * to which we are storing the content.
664          */
665         if (!is_partial || last_partial) {
666             if (ct->c_folder) {
667                 fprintf (stderr, " to folder %s as message %d\n", ct->c_folder,
668                          msgnum);
669             } else if (!strcmp(ct->c_storage, "-")) {
670                 fprintf (stderr, " to stdout\n");
671             } else {
672                 int cwdlen = strlen (info->cwd);
673 
674                 fprintf (stderr, " as file %s\n",
675                          !has_prefix(ct->c_storage, info->cwd)
676                          || ct->c_storage[cwdlen] != '/'
677                          ? ct->c_storage : ct->c_storage + cwdlen + 1);
678             }
679         }
680     }
681 
682     return OK;
683 }
684 
685 
686 /*
687  * Output content to a file
688  */
689 
690 static int
output_content_file(CT ct,int appending)691 output_content_file (CT ct, int appending)
692 {
693     int filterstate;
694     char *file, buffer[BUFSIZ];
695     long pos, last;
696     FILE *fp;
697 
698     /*
699      * If the pathname is absolute, make sure
700      * all the relevant directories exist.
701      */
702     if (strchr(ct->c_storage, '/')
703 	    && make_intermediates (ct->c_storage) == NOTOK)
704 	return NOTOK;
705 
706     if (ct->c_encoding != CE_7BIT) {
707 	int cc, fd;
708 
709 	if (!ct->c_ceopenfnx) {
710 	    inform("don't know how to decode part %s of message %s",
711 		    ct->c_partno, ct->c_file);
712 	    return NOTOK;
713 	}
714 
715 	file = appending || !strcmp (ct->c_storage, "-") ? NULL
716 							   : ct->c_storage;
717 	if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
718 	    return NOTOK;
719 	if (!strcmp (file, ct->c_storage)) {
720 	    (*ct->c_ceclosefnx) (ct);
721 	    return OK;
722 	}
723 
724 	/*
725 	 * Send to standard output
726 	 */
727 	if (!strcmp (ct->c_storage, "-")) {
728 	    int	gd;
729 
730 	    if ((gd = dup (fileno (stdout))) == NOTOK) {
731 		advise ("stdout", "unable to dup");
732 losing:
733 		(*ct->c_ceclosefnx) (ct);
734 		return NOTOK;
735 	    }
736 	    if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) {
737 		advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd,
738 			appending ? "a" : "w");
739 		close (gd);
740 		goto losing;
741 	    }
742 	} else {
743 	    /*
744 	     * Open output file
745 	     */
746 	    if ((fp = fopen (ct->c_storage, appending ? "a" : "w")) == NULL) {
747 		advise (ct->c_storage, "unable to fopen for %s",
748 			appending ? "appending" : "writing");
749 		goto losing;
750 	    }
751 	}
752 
753 	/*
754 	 * Filter the header fields of the initial enclosing
755 	 * message/partial into the file.
756 	 */
757 	if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
758 	    struct partial *pm = (struct partial *) ct->c_ctparams;
759 
760 	    if (pm->pm_partno == 1)
761 		copy_some_headers (fp, ct);
762 	}
763 
764 	for (;;) {
765 	    switch (cc = read (fd, buffer, sizeof(buffer))) {
766 		case NOTOK:
767 		    advise (file, "error reading content from");
768 		    break;
769 
770 		case OK:
771 		    break;
772 
773 		default:
774 		    if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) {
775 			advise ("output_content_file", "fwrite");
776 		    }
777 		    continue;
778 	    }
779 	    break;
780 	}
781 
782 	(*ct->c_ceclosefnx) (ct);
783 
784 	if (cc != NOTOK && fflush (fp))
785 	    advise (ct->c_storage, "error writing to");
786 
787 	fclose (fp);
788 
789 	return (cc != NOTOK ? OK : NOTOK);
790     }
791 
792     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
793 	advise (ct->c_file, "unable to open for reading");
794 	return NOTOK;
795     }
796 
797     pos = ct->c_begin;
798     last = ct->c_end;
799     fseek (ct->c_fp, pos, SEEK_SET);
800 
801     if (!strcmp (ct->c_storage, "-")) {
802 	int gd;
803 
804 	if ((gd = dup (fileno (stdout))) == NOTOK) {
805 	    advise ("stdout", "unable to dup");
806 	    return NOTOK;
807 	}
808 	if ((fp = fdopen (gd, appending ? "a" : "w")) == NULL) {
809 	    advise ("stdout", "unable to fdopen (%d, \"%s\") from", gd,
810 		    appending ? "a" : "w");
811 	    close (gd);
812 	    return NOTOK;
813 	}
814     } else {
815 	if ((fp = fopen (ct->c_storage, appending ? "a" : "w")) == NULL) {
816 	    advise (ct->c_storage, "unable to fopen for %s",
817 		    appending ? "appending" : "writing");
818 	    return NOTOK;
819 	}
820     }
821 
822     /*
823      * Copy a few of the header fields of the initial
824      * enclosing message/partial into the file.
825      */
826     filterstate = 0;
827     if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
828 	struct partial *pm = (struct partial *) ct->c_ctparams;
829 
830 	if (pm->pm_partno == 1) {
831 	    copy_some_headers (fp, ct);
832 	    filterstate = 1;
833 	}
834     }
835 
836     while (fgets (buffer, sizeof buffer, ct->c_fp)) {
837 	if ((pos += strlen (buffer)) > last) {
838 	    int diff;
839 
840 	    diff = strlen (buffer) - (pos - last);
841 	    if (diff >= 0)
842 		buffer[diff] = '\0';
843 	}
844 	/*
845 	 * If this is the first content of a group of
846 	 * message/partial contents, then we only copy a few
847 	 * of the header fields of the enclosed message.
848 	 */
849 	if (filterstate) {
850 	    switch (buffer[0]) {
851 		case ' ':
852 		case '\t':
853 		    if (filterstate < 0)
854 			buffer[0] = 0;
855 		    break;
856 
857 		case '\n':
858 		    filterstate = 0;
859 		    break;
860 
861 		default:
862 		    if (!uprf (buffer, XXX_FIELD_PRF)
863 			    && !uprf (buffer, VRSN_FIELD)
864 			    && !uprf (buffer, "Subject:")
865 			    && !uprf (buffer, "Encrypted:")
866 			    && !uprf (buffer, "Message-ID:")) {
867 			filterstate = -1;
868 			buffer[0] = 0;
869 			break;
870 		    }
871 		    filterstate = 1;
872 		    break;
873 	    }
874 	}
875 	fputs (buffer, fp);
876 	if (pos >= last)
877 	    break;
878     }
879 
880     if (fflush (fp))
881 	advise (ct->c_storage, "error writing to");
882 
883     fclose (fp);
884     fclose (ct->c_fp);
885     ct->c_fp = NULL;
886     return OK;
887 }
888 
889 
890 /*
891  * Add a file to a folder.
892  *
893  * Return the new message number of the file
894  * when added to the folder.  Return -1, if
895  * there is an error.
896  */
897 
898 static int
output_content_folder(char * folder,char * filename)899 output_content_folder (char *folder, char *filename)
900 {
901     int msgnum;
902     struct msgs *mp;
903 
904     /* Read the folder. */
905     if ((mp = folder_read (folder, 0))) {
906 	/* Link file into folder */
907 	msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0, NULL);
908     } else {
909 	inform("unable to read folder %s", folder);
910 	return NOTOK;
911     }
912 
913     /* free folder structure */
914     folder_free (mp);
915 
916     /*
917      * Return msgnum.  We are relying on the fact that
918      * msgnum will be -1, if folder_addmsg() had an error.
919      */
920     return msgnum;
921 }
922 
923 
924 /*
925  * Parse and expand the storage formatting string
926  * pointed to by "cp" into "buffer".
927  */
928 
929 static int
parse_format_string(CT ct,char * cp,char * buffer,int buflen,char * dir)930 parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir)
931 {
932     int len;
933     char *bp;
934     CI ci = &ct->c_ctinfo;
935 
936     /*
937      * If storage string is "-", just copy it, and
938      * return (send content to standard output).
939      */
940     if (cp[0] == '-' && cp[1] == '\0') {
941 	strncpy (buffer, cp, buflen);
942 	return 0;
943     }
944 
945     bp = buffer;
946     bp[0] = '\0';
947 
948     /*
949      * If formatting string is a pathname that doesn't
950      * begin with '/', then preface the path with the
951      * appropriate directory.
952      */
953     if (*cp != '/' && *cp != '|' && *cp != '!') {
954         if (!strcmp(dir, "/"))
955             dir = ""; /* Don't start with "//". */
956 	snprintf (bp, buflen, "%s/", dir);
957 	len = strlen (bp);
958 	bp += len;
959 	buflen -= len;
960     }
961 
962     for (; *cp; cp++) {
963 
964 	/* We are processing a storage escape */
965 	if (*cp == '%') {
966 	    switch (*++cp) {
967 		case 'a':
968 		    /*
969 		     * Insert parameters from Content-Type.
970 		     * This is only valid for '|' commands.
971 		     */
972 		    if (buffer[0] != '|' && buffer[0] != '!') {
973 			*bp++ = *--cp;
974 			*bp = '\0';
975 			buflen--;
976 			continue;
977 		    }
978                     {
979 			PM pm;
980 			char *s = "";
981 
982 			for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
983 			    snprintf (bp, buflen, "%s%s=\"%s\"", s,
984 				      pm->pm_name, get_param_value(pm, '?'));
985 			    len = strlen (bp);
986 			    bp += len;
987 			    buflen -= len;
988 			    s = " ";
989 			}
990 		    }
991 		    break;
992 
993 		case 'm':
994 		    /* insert message number */
995 		    snprintf (bp, buflen, "%s", r1bindex (ct->c_file, '/'));
996 		    break;
997 
998 		case 'P':
999 		    /* insert part number with leading dot */
1000 		    if (ct->c_partno)
1001 			snprintf (bp, buflen, ".%s", ct->c_partno);
1002 		    break;
1003 
1004 		case 'p':
1005 		    /* insert part number without leading dot */
1006 		    if (ct->c_partno)
1007 			strncpy (bp, ct->c_partno, buflen);
1008 		    break;
1009 
1010 		case 't':
1011 		    /* insert content type */
1012 		    strncpy (bp, ci->ci_type, buflen);
1013 		    break;
1014 
1015 		case 's':
1016 		    /* insert content subtype */
1017 		    strncpy (bp, ci->ci_subtype, buflen);
1018 		    break;
1019 
1020 		case '%':
1021 		    /* insert the character % */
1022 		    goto raw;
1023 
1024 		default:
1025 		    *bp++ = *--cp;
1026 		    *bp = '\0';
1027 		    buflen--;
1028 		    continue;
1029 	    }
1030 
1031 	    /* Advance bp and decrement buflen */
1032 	    len = strlen (bp);
1033 	    bp += len;
1034 	    buflen -= len;
1035 
1036 	} else {
1037 raw:
1038 	    *bp++ = *cp;
1039 	    *bp = '\0';
1040 	    buflen--;
1041 	}
1042     }
1043 
1044     return 0;
1045 }
1046 
1047 
1048 /*
1049  * Check if the content specifies a filename
1050  * in its MIME parameters.
1051  */
1052 
1053 static void
get_storeproc(CT ct)1054 get_storeproc (CT ct)
1055 {
1056     char *cp;
1057     CI ci;
1058 
1059     /*
1060      * If the storeproc has already been defined,
1061      * we just return (for instance, if this content
1062      * is part of a "message/external".
1063      */
1064     if (ct->c_storeproc)
1065 	return;
1066 
1067     /*
1068      * If there's a Content-Disposition header and it has a filename,
1069      * use that (RFC-2183).
1070      */
1071     if (ct->c_dispo) {
1072 	if ((cp = get_param(ct->c_dispo_first, "filename", '_', 0)) &&
1073             use_param_as_filename(cp)) {
1074 		ct->c_storeproc = mh_xstrdup(cp);
1075 		free(cp);
1076 		return;
1077 	}
1078         mh_xfree(cp);
1079     }
1080 
1081     /*
1082      * Check the attribute/value pairs, for the attribute "name".
1083      * If found, do a few sanity checks and copy the value into
1084      * the storeproc.
1085      */
1086     ci = &ct->c_ctinfo;
1087     if ((cp = get_param(ci->ci_first_pm, "name", '_', 0)) &&
1088         use_param_as_filename(cp)) {
1089 	    ct->c_storeproc = mh_xstrdup(cp);
1090 
1091     }
1092     mh_xfree(cp);
1093 }
1094 
1095 
1096 /*
1097  * Copy some of the header fields of the initial message/partial
1098  * message into the header of the reassembled message.
1099  */
1100 
1101 static int
copy_some_headers(FILE * out,CT ct)1102 copy_some_headers (FILE *out, CT ct)
1103 {
1104     HF hp;
1105 
1106     hp = ct->c_first_hf;	/* start at first header field */
1107 
1108     while (hp) {
1109 	/*
1110 	 * A few of the header fields of the enclosing
1111 	 * messages are not copied.
1112 	 */
1113 	if (!uprf (hp->name, XXX_FIELD_PRF)
1114 		&& strcasecmp (hp->name, VRSN_FIELD)
1115 		&& strcasecmp (hp->name, "Subject")
1116 		&& strcasecmp (hp->name, "Encrypted")
1117 		&& strcasecmp (hp->name, "Message-ID"))
1118 	    fprintf (out, "%s:%s", hp->name, hp->value);
1119 	hp = hp->next;	/* next header field */
1120     }
1121 
1122     return OK;
1123 }
1124 
1125 /******************************************************************************/
1126 /* -clobber support */
1127 
1128 static
1129 enum clobber_policy_t
clobber_policy(const char * value)1130 clobber_policy (const char *value) {
1131   if (value == NULL  ||  ! strcasecmp (value, "always")) {
1132     return NMH_CLOBBER_ALWAYS;
1133   }
1134   if (! strcasecmp (value, "auto")) {
1135     return NMH_CLOBBER_AUTO;
1136   }
1137   if (! strcasecmp (value, "suffix")) {
1138     return NMH_CLOBBER_SUFFIX;
1139   }
1140   if (! strcasecmp (value, "ask")) {
1141     return NMH_CLOBBER_ASK;
1142   }
1143   if (! strcasecmp (value, "never")) {
1144     return NMH_CLOBBER_NEVER;
1145   }
1146 
1147   adios (NULL, "invalid argument, %s, to clobber", value);
1148 }
1149 
1150 
1151 static char *
next_version(char * file,enum clobber_policy_t clobber_policy)1152 next_version (char *file, enum clobber_policy_t clobber_policy) {
1153   const size_t max_versions = 1000000;
1154   /* 8 = log max_versions  +  one for - or .  +  one for null terminator */
1155   const size_t buflen = strlen (file) + 8;
1156   char *buffer = mh_xmalloc (buflen);
1157   size_t version;
1158 
1159   char *extension = NULL;
1160   if (clobber_policy == NMH_CLOBBER_AUTO  &&
1161       ((extension = strrchr (file, '.')) != NULL)) {
1162     *extension++ = '\0';
1163   }
1164 
1165   for (version = 1; version < max_versions; ++version) {
1166     int fd;
1167 
1168     switch (clobber_policy) {
1169       case NMH_CLOBBER_AUTO: {
1170         snprintf (buffer, buflen, "%s-%ld%s%s", file, (long) version,
1171                   extension == NULL  ?  ""  :  ".",
1172                   extension == NULL  ?  ""  :  extension);
1173         break;
1174       }
1175 
1176       case NMH_CLOBBER_SUFFIX:
1177         snprintf (buffer, buflen, "%s.%ld", file, (long) version);
1178         break;
1179 
1180       default:
1181         /* Should never get here. */
1182         inform("will not overwrite %s, invalid clobber policy", buffer);
1183         free (buffer);
1184         return NULL;
1185     }
1186 
1187     /* Actually (try to) create the file here to avoid a race
1188        condition on file naming + creation.  This won't solve the
1189        problem with old NFS that doesn't support O_EXCL, though.
1190        Let the umask strip off permissions from 0666 as desired.
1191        That's what fopen () would do if it was creating the file. */
1192     if ((fd = open (buffer, O_CREAT | O_EXCL,
1193                     S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
1194                     S_IROTH | S_IWOTH)) >= 0) {
1195       close (fd);
1196       break;
1197     }
1198   }
1199 
1200   free (file);
1201 
1202   if (version >= max_versions) {
1203     inform("will not overwrite %s, too many versions", buffer);
1204     free (buffer);
1205     buffer = NULL;
1206   }
1207 
1208   return buffer;
1209 }
1210 
1211 
1212 static char *
clobber_check(char * original_file,mhstoreinfo_t info)1213 clobber_check (char *original_file, mhstoreinfo_t info) {
1214   /* clobber policy        return value
1215    * --------------        ------------
1216    *   -always             original_file
1217    *   -auto               original_file-<digits>.extension
1218    *   -suffix             original_file.<digits>
1219    *   -ask                original_file, 0, or another filename/path
1220    *   -never              0
1221    */
1222 
1223   char *file;
1224   char *cwd = NULL;
1225   int check_again;
1226 
1227   if (! strcmp (original_file, "-")) {
1228       return original_file;
1229   }
1230 
1231   if (info->clobber_policy == NMH_CLOBBER_ASK) {
1232     /* Save cwd for possible use in loop below. */
1233     char *slash;
1234 
1235     cwd = mh_xstrdup(original_file);
1236     slash = strrchr (cwd, '/');
1237 
1238     if (slash) {
1239       *slash = '\0';
1240     } else {
1241       /* original_file isn't a full path, which should only happen if
1242          it is -. */
1243       free (cwd);
1244       cwd = NULL;
1245     }
1246   }
1247 
1248   do {
1249     struct stat st;
1250 
1251     file = original_file;
1252     check_again = 0;
1253 
1254     switch (info->clobber_policy) {
1255       case NMH_CLOBBER_ALWAYS:
1256         break;
1257 
1258       case NMH_CLOBBER_SUFFIX:
1259       case NMH_CLOBBER_AUTO:
1260         if (stat (file, &st) == OK) {
1261           if ((file = next_version (original_file, info->clobber_policy)) ==
1262               NULL) {
1263               ++info->files_not_clobbered;
1264           }
1265         }
1266         break;
1267 
1268       case NMH_CLOBBER_ASK:
1269         if (stat (file, &st) == OK) {
1270           enum answers { NMH_YES, NMH_NO, NMH_RENAME };
1271           static struct swit answer[4] = {
1272             { "yes", 0, NMH_YES },
1273             { "no", 0, NMH_NO },
1274             { "rename", 0, NMH_RENAME },
1275             { NULL, 0, 0 } };
1276           char **ans;
1277 
1278           if (isatty (fileno (stdin))) {
1279             char *prompt =
1280               concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL);
1281             ans = read_switch_multiword (prompt, answer);
1282             free (prompt);
1283           } else {
1284             /* Overwrite, that's what nmh used to do.  And warn. */
1285             inform("-clobber ask but no tty, so overwrite %s", file);
1286             break;
1287           }
1288 
1289           switch ((enum answers) smatch (*ans, answer)) {
1290             case NMH_YES:
1291               break;
1292             case NMH_NO:
1293               free (file);
1294               file = NULL;
1295               ++info->files_not_clobbered;
1296               break;
1297             case NMH_RENAME: {
1298               char buf[PATH_MAX];
1299               printf ("Enter filename or full path of the new file: ");
1300               if (fgets (buf, sizeof buf, stdin) == NULL  ||
1301                   buf[0] == '\0') {
1302                 file = NULL;
1303                 ++info->files_not_clobbered;
1304               } else {
1305                 trim_suffix_c(buf, '\n');
1306               }
1307 
1308               free (file);
1309 
1310               if (buf[0] == '/') {
1311                 /* Full path, use it. */
1312                 file = mh_xstrdup(buf);
1313               } else {
1314                 /* Relative path. */
1315                 file = cwd  ?  concat (cwd, "/", buf, NULL)  :  mh_xstrdup(buf);
1316               }
1317 
1318               check_again = 1;
1319               break;
1320             }
1321           }
1322         }
1323         break;
1324 
1325       case NMH_CLOBBER_NEVER:
1326         if (stat (file, &st) == OK) {
1327           /* Keep count of files that would have been clobbered,
1328              and return that as process exit status. */
1329           inform("will not overwrite %s with -clobber never", file);
1330           free (file);
1331           file = NULL;
1332           ++info->files_not_clobbered;
1333         }
1334         break;
1335     }
1336 
1337     original_file = file;
1338   } while (check_again);
1339 
1340   free (cwd);
1341 
1342   return file;
1343 }
1344 
use_param_as_filename(const char * p)1345 static bool use_param_as_filename(const char *p)
1346 {
1347     /* Preserve result of original test that considered an empty string
1348      * OK. */
1349     return !*p || (!strchr("/.|!", *p) && !strchr(p, '%'));
1350 }
1351 
1352 /* -clobber support */
1353 /******************************************************************************/
1354