]> diplodocus.org Git - nmh/blob - uip/mhfixmsg.c
pending-release-notes: add mhshow's "-prefer", and mh-format's %(kibi/kilo)
[nmh] / uip / mhfixmsg.c
1 /*
2 * mhfixmsg.c -- rewrite a message with various tranformations
3 *
4 * This code is Copyright (c) 2002 and 2013, by the authors of nmh.
5 * See the COPYRIGHT file in the root directory of the nmh
6 * distribution for complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <h/mime.h>
11 #include <h/mhparse.h>
12 #include <h/utils.h>
13 #include <h/signals.h>
14 #include <fcntl.h>
15
16 #define MHFIXMSG_SWITCHES \
17 X("decodetext 8bit|7bit", 0, DECODETEXTSW) \
18 X("nodecodetext", 0, NDECODETEXTSW) \
19 X("textcharset", 0, TEXTCHARSETSW) \
20 X("notextcharset", 0, NTEXTCHARSETSW) \
21 X("reformat", 0, REFORMATSW) \
22 X("noreformat", 0, NREFORMATSW) \
23 X("replacetextplain", 0, REPLACETEXTPLAINSW) \
24 X("noreplacetextplain", 0, NREPLACETEXTPLAINSW) \
25 X("fixboundary", 0, FIXBOUNDARYSW) \
26 X("nofixboundary", 0, NFIXBOUNDARYSW) \
27 X("fixcte", 0, FIXCTESW) \
28 X("nofixcte", 0, NFIXCTESW) \
29 X("fixtype mimetype", 0, FIXTYPESW) \
30 X("file file", 0, FILESW) \
31 X("outfile file", 0, OUTFILESW) \
32 X("rmmproc program", 0, RPROCSW) \
33 X("normmproc", 0, NRPRCSW) \
34 X("changecur", 0, CHGSW) \
35 X("nochangecur", 0, NCHGSW) \
36 X("verbose", 0, VERBSW) \
37 X("noverbose", 0, NVERBSW) \
38 X("version", 0, VERSIONSW) \
39 X("help", 0, HELPSW) \
40
41 #define X(sw, minchars, id) id,
42 DEFINE_SWITCH_ENUM(MHFIXMSG);
43 #undef X
44
45 #define X(sw, minchars, id) { sw, minchars, id },
46 DEFINE_SWITCH_ARRAY(MHFIXMSG, switches);
47 #undef X
48
49
50 int verbosw;
51 int debugsw; /* Needed by mhparse.c. */
52
53 #define quitser pipeser
54
55 /* mhparse.c */
56 extern int skip_mp_cte_check; /* flag to InitMultiPart */
57 extern int suppress_bogus_mp_content_warning; /* flag to InitMultiPart */
58 extern int bogus_mp_content; /* flag from InitMultiPart */
59 /* flags to/from parse_header_attrs */
60 extern int suppress_extraneous_trailing_semicolon_warning;
61 extern int extraneous_trailing_semicolon;
62
63 /* mhoutsbr.c */
64 int output_message (CT, char *);
65
66 /* mhmisc.c */
67 void flush_errors (void);
68
69 /* mhfree.c */
70 extern CT *cts;
71 void freects_done (int) NORETURN;
72
73 /*
74 * static prototypes
75 */
76 typedef struct fix_transformations {
77 int fixboundary;
78 int fixcte;
79 svector_t fixtypes;
80 int reformat;
81 int replacetextplain;
82 int decodetext;
83 char *textcharset;
84 } fix_transformations;
85
86 int mhfixmsgsbr (CT *, const fix_transformations *, char *);
87 static int fix_boundary (CT *, int *);
88 static int get_multipart_boundary (CT, char **);
89 static int replace_boundary (CT, char *, char *);
90 static int fix_types (CT, svector_t, int *);
91 static char *replace_substring (char **, const char *, const char *);
92 static int fix_multipart_cte (CT, int *);
93 static int set_ce (CT, int);
94 static int ensure_text_plain (CT *, CT, int *, int);
95 static int find_textplain_sibling (CT, int, int *);
96 static int insert_new_text_plain_part (CT, int, CT);
97 static CT build_text_plain_part (CT);
98 static CT divide_part (CT);
99 static void copy_ctinfo (CI, CI);
100 static int decode_part (CT);
101 static int reformat_part (CT, char *, char *, char *, int);
102 static int charset_encoding (CT);
103 static CT build_multipart_alt (CT, CT, int, int);
104 static int boundary_in_content (FILE **, char *, const char *);
105 static void transfer_noncontent_headers (CT, CT);
106 static int set_ct_type (CT, int type, int subtype, int encoding);
107 static int decode_text_parts (CT, int, int *);
108 static int content_encoding (CT, const char **);
109 static int strip_crs (CT, int *);
110 static int convert_charsets (CT, char *, int *);
111 static int fix_always (CT, int *);
112 static int write_content (CT, char *, char *, int, int);
113 static int remove_file (char *);
114 static void report (char *, char *, char *, char *, ...);
115 static void pipeser (int);
116
117
118 int
119 main (int argc, char **argv) {
120 int msgnum;
121 char *cp, *file = NULL, *folder = NULL;
122 char *maildir, buf[100], *outfile = NULL;
123 char **argp, **arguments;
124 struct msgs_array msgs = { 0, 0, NULL };
125 struct msgs *mp = NULL;
126 CT *ctp;
127 FILE *fp;
128 int using_stdin = 0;
129 int chgflag = 1;
130 int status = OK;
131 fix_transformations fx;
132 fx.reformat = fx.fixcte = fx.fixboundary = 1;
133 fx.fixtypes = NULL;
134 fx.replacetextplain = 0;
135 fx.decodetext = CE_8BIT;
136 fx.textcharset = NULL;
137
138 if (nmh_init(argv[0], 1)) { return 1; }
139
140 done = freects_done;
141
142 arguments = getarguments (invo_name, argc, argv, 1);
143 argp = arguments;
144
145 /*
146 * Parse arguments
147 */
148 while ((cp = *argp++)) {
149 if (*cp == '-') {
150 switch (smatch (++cp, switches)) {
151 case AMBIGSW:
152 ambigsw (cp, switches);
153 done (1);
154 case UNKWNSW:
155 adios (NULL, "-%s unknown", cp);
156
157 case HELPSW:
158 snprintf (buf, sizeof buf, "%s [+folder] [msgs] [switches]",
159 invo_name);
160 print_help (buf, switches, 1);
161 done (0);
162 case VERSIONSW:
163 print_version(invo_name);
164 done (0);
165
166 case DECODETEXTSW:
167 if (! (cp = *argp++) || *cp == '-')
168 adios (NULL, "missing argument to %s", argp[-2]);
169 if (! strcasecmp (cp, "8bit")) {
170 fx.decodetext = CE_8BIT;
171 } else if (! strcasecmp (cp, "7bit")) {
172 fx.decodetext = CE_7BIT;
173 } else {
174 adios (NULL, "invalid argument to %s", argp[-2]);
175 }
176 continue;
177 case NDECODETEXTSW:
178 fx.decodetext = 0;
179 continue;
180 case TEXTCHARSETSW:
181 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
182 adios (NULL, "missing argument to %s", argp[-2]);
183 fx.textcharset = cp;
184 continue;
185 case NTEXTCHARSETSW:
186 fx.textcharset = 0;
187 continue;
188 case FIXBOUNDARYSW:
189 fx.fixboundary = 1;
190 continue;
191 case NFIXBOUNDARYSW:
192 fx.fixboundary = 0;
193 continue;
194 case FIXCTESW:
195 fx.fixcte = 1;
196 continue;
197 case NFIXCTESW:
198 fx.fixcte = 0;
199 continue;
200 case FIXTYPESW:
201 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
202 adios (NULL, "missing argument to %s", argp[-2]);
203 if (! strncasecmp (cp, "multipart/", 10) ||
204 ! strncasecmp (cp, "message/", 8)) {
205 adios (NULL, "-fixtype %s not allowed", cp);
206 } else if (! strchr (cp, '/')) {
207 adios (NULL, "-fixtype requires type/subtype");
208 }
209 if (fx.fixtypes == NULL) { fx.fixtypes = svector_create (10); }
210 svector_push_back (fx.fixtypes, cp);
211 continue;
212 case REFORMATSW:
213 fx.reformat = 1;
214 continue;
215 case NREFORMATSW:
216 fx.reformat = 0;
217 continue;
218 case REPLACETEXTPLAINSW:
219 fx.replacetextplain = 1;
220 continue;
221 case NREPLACETEXTPLAINSW:
222 fx.replacetextplain = 0;
223 continue;
224 case FILESW:
225 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
226 adios (NULL, "missing argument to %s", argp[-2]);
227 file = *cp == '-' ? add (cp, NULL) : path (cp, TFILE);
228 continue;
229 case OUTFILESW:
230 if (! (cp = *argp++) || (*cp == '-' && cp[1]))
231 adios (NULL, "missing argument to %s", argp[-2]);
232 outfile = *cp == '-' ? add (cp, NULL) : path (cp, TFILE);
233 continue;
234 case RPROCSW:
235 if (!(rmmproc = *argp++) || *rmmproc == '-')
236 adios (NULL, "missing argument to %s", argp[-2]);
237 continue;
238 case NRPRCSW:
239 rmmproc = NULL;
240 continue;
241 case CHGSW:
242 chgflag = 1;
243 continue;
244 case NCHGSW:
245 chgflag = 0;
246 continue;
247 case VERBSW:
248 verbosw = 1;
249 continue;
250 case NVERBSW:
251 verbosw = 0;
252 continue;
253 }
254 }
255 if (*cp == '+' || *cp == '@') {
256 if (folder)
257 adios (NULL, "only one folder at a time!");
258 else
259 folder = pluspath (cp);
260 } else {
261 if (*cp == '/') {
262 /* Interpret a full path as a filename, not a message. */
263 file = add (cp, NULL);
264 } else {
265 app_msgarg (&msgs, cp);
266 }
267 }
268 }
269
270 SIGNAL (SIGQUIT, quitser);
271 SIGNAL (SIGPIPE, pipeser);
272
273 /*
274 * Read the standard profile setup
275 */
276 if ((fp = fopen (cp = etcpath ("mhn.defaults"), "r"))) {
277 readconfig ((struct node **) 0, fp, cp, 0);
278 fclose (fp);
279 }
280
281 suppress_bogus_mp_content_warning = skip_mp_cte_check = 1;
282 suppress_extraneous_trailing_semicolon_warning = 1;
283
284 if (! context_find ("path"))
285 free (path ("./", TFOLDER));
286
287 if (file && msgs.size)
288 adios (NULL, "cannot specify msg and file at same time!");
289
290 /*
291 * check if message is coming from file
292 */
293 if (file) {
294 /* If file is stdin, create a tmp file name before parse_mime()
295 has a chance, because it might put in on a different
296 filesystem than the output file. Instead, put it in the
297 user's preferred tmp directory. */
298 CT ct;
299
300 if (! strcmp ("-", file)) {
301 int fd;
302 char *cp;
303
304 using_stdin = 1;
305
306 if ((cp = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) {
307 adios (NULL, "unable to create temporary file in %s",
308 get_temp_dir());
309 } else {
310 free (file);
311 file = add (cp, NULL);
312 cpydata (STDIN_FILENO, fd, "-", file);
313 }
314
315 if (close (fd)) {
316 (void) m_unlink (file);
317 adios (NULL, "failed to write temporary file");
318 }
319 }
320
321 if (! (cts = (CT *) mh_xcalloc ((size_t) 2, sizeof *cts))) {
322 adios (NULL, "out of memory");
323 }
324 ctp = cts;
325
326 if ((ct = parse_mime (file))) { *ctp++ = ct; }
327 } else {
328 /*
329 * message(s) are coming from a folder
330 */
331 CT ct;
332
333 if (! msgs.size)
334 app_msgarg(&msgs, "cur");
335 if (! folder)
336 folder = getfolder (1);
337 maildir = m_maildir (folder);
338
339 if (chdir (maildir) == NOTOK)
340 adios (maildir, "unable to change directory to");
341
342 /* read folder and create message structure */
343 if (! (mp = folder_read (folder, 1)))
344 adios (NULL, "unable to read folder %s", folder);
345
346 /* check for empty folder */
347 if (mp->nummsg == 0)
348 adios (NULL, "no messages in %s", folder);
349
350 /* parse all the message ranges/sequences and set SELECTED */
351 for (msgnum = 0; msgnum < msgs.size; msgnum++)
352 if (! m_convert (mp, msgs.msgs[msgnum]))
353 done (1);
354 seq_setprev (mp); /* set the previous-sequence */
355
356 if (! (cts =
357 (CT *) mh_xcalloc ((size_t) (mp->numsel + 1), sizeof *cts))) {
358 adios (NULL, "out of memory");
359 }
360 ctp = cts;
361
362 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
363 if (is_selected(mp, msgnum)) {
364 char *msgnam;
365
366 msgnam = m_name (msgnum);
367 if ((ct = parse_mime (msgnam))) { *ctp++ = ct; }
368 }
369 }
370
371 if (chgflag) {
372 seq_setcur (mp, mp->hghsel); /* update current message */
373 }
374 seq_save (mp); /* synchronize sequences */
375 context_replace (pfolder, folder);/* update current folder */
376 context_save (); /* save the context file */
377 }
378
379 if (*cts) {
380 for (ctp = cts; *ctp; ++ctp) {
381 status += mhfixmsgsbr (ctp, &fx, outfile);
382
383 if (using_stdin) {
384 (void) m_unlink (file);
385
386 if (! outfile) {
387 /* Just calling m_backup() unlinks the backup file. */
388 (void) m_backup (file);
389 }
390 }
391 }
392 } else {
393 status = 1;
394 }
395
396 if (fx.fixtypes != NULL) { svector_free (fx.fixtypes); }
397 free (outfile);
398 free (file);
399
400 /* done is freects_done, which will clean up all of cts. */
401 done (status);
402 return NOTOK;
403 }
404
405
406 int
407 mhfixmsgsbr (CT *ctp, const fix_transformations *fx, char *outfile) {
408 /* Store input filename in case one of the transformations, i.e.,
409 fix_boundary(), rewrites to a tmp file. */
410 char *input_filename = add ((*ctp)->c_file, NULL);
411 int modify_inplace = 0;
412 int message_mods = 0;
413 int status = OK;
414
415 if (outfile == NULL) {
416 modify_inplace = 1;
417
418 if ((*ctp)->c_file) {
419 char *tempfile;
420 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, NULL)) == NULL) {
421 adios (NULL, "unable to create temporary file in %s",
422 get_temp_dir());
423 }
424 outfile = add (tempfile, NULL);
425 } else {
426 adios (NULL, "missing both input and output filenames\n");
427 }
428 }
429
430 reverse_alternative_parts (*ctp);
431 status = fix_always (*ctp, &message_mods);
432 if (status == OK && fx->fixboundary) {
433 status = fix_boundary (ctp, &message_mods);
434 }
435 if (status == OK && fx->fixtypes != NULL) {
436 status = fix_types (*ctp, fx->fixtypes, &message_mods);
437 }
438 if (status == OK && fx->fixcte) {
439 status = fix_multipart_cte (*ctp, &message_mods);
440 }
441 if (status == OK && fx->reformat) {
442 status =
443 ensure_text_plain (ctp, NULL, &message_mods, fx->replacetextplain);
444 }
445 if (status == OK && fx->decodetext) {
446 status = decode_text_parts (*ctp, fx->decodetext, &message_mods);
447 }
448 if (status == OK && fx->textcharset != NULL) {
449 status = convert_charsets (*ctp, fx->textcharset, &message_mods);
450 }
451
452 if (status == OK && ! (*ctp)->c_umask) {
453 /* Set the umask for the contents file. This currently
454 isn't used but just in case it is in the future. */
455 struct stat st;
456
457 if (stat ((*ctp)->c_file, &st) != NOTOK) {
458 (*ctp)->c_umask = ~(st.st_mode & 0777);
459 } else {
460 (*ctp)->c_umask = ~m_gmprot();
461 }
462 }
463
464 /*
465 * Write the content to a file
466 */
467 if (status == OK) {
468 status = write_content (*ctp, input_filename, outfile, modify_inplace,
469 message_mods);
470 } else if (! modify_inplace) {
471 /* Something went wrong. Output might be expected, such
472 as if this were run as a filter. Just copy the input
473 to the output. */
474 int in = open (input_filename, O_RDONLY);
475 int out = strcmp (outfile, "-")
476 ? open (outfile, O_WRONLY | O_CREAT, m_gmprot ())
477 : STDOUT_FILENO;
478
479 if (in != -1 && out != -1) {
480 cpydata (in, out, input_filename, outfile);
481 } else {
482 status = NOTOK;
483 }
484
485 close (out);
486 close (in);
487 }
488
489 if (modify_inplace) {
490 if (status != OK) { (void) m_unlink (outfile); }
491 free (outfile);
492 outfile = NULL;
493 }
494
495 free (input_filename);
496
497 return status;
498 }
499
500
501 static int
502 fix_boundary (CT *ct, int *message_mods) {
503 struct multipart *mp;
504 int status = OK;
505
506 if (ct && (*ct)->c_type == CT_MULTIPART && bogus_mp_content) {
507 mp = (struct multipart *) (*ct)->c_ctparams;
508
509 /*
510 * 1) Get boundary at end of part.
511 * 2) Get boundary at beginning of part and compare to the end-of-part
512 * boundary.
513 * 3) Write out contents of ct to tmp file, replacing boundary in
514 * header with boundary from part. Set c_unlink to 1.
515 * 4) Free ct.
516 * 5) Call parse_mime() on the tmp file, replacing ct.
517 */
518
519 if (mp && mp->mp_start) {
520 char *part_boundary;
521
522 if (get_multipart_boundary (*ct, &part_boundary) == OK) {
523 char *fixed;
524
525 if ((fixed = m_mktemp2 (NULL, invo_name, NULL, &(*ct)->c_fp))) {
526 if (replace_boundary (*ct, fixed, part_boundary) == OK) {
527 char *filename = add ((*ct)->c_file, NULL);
528 CT fixed_ct;
529
530 free_content (*ct);
531 if ((fixed_ct = parse_mime (fixed))) {
532 *ct = fixed_ct;
533 (*ct)->c_unlink = 1;
534
535 ++*message_mods;
536 if (verbosw) {
537 report (NULL, NULL, filename,
538 "fix multipart boundary");
539 }
540 } else {
541 *ct = NULL;
542 advise (NULL, "unable to parse fixed part");
543 status = NOTOK;
544 }
545 free (filename);
546 } else {
547 advise (NULL, "unable to replace broken boundary");
548 status = NOTOK;
549 }
550 } else {
551 advise (NULL, "unable to create temporary file in %s",
552 get_temp_dir());
553 status = NOTOK;
554 }
555
556 free (part_boundary);
557 }
558 }
559 }
560
561 return status;
562 }
563
564
565 static int
566 get_multipart_boundary (CT ct, char **part_boundary) {
567 char buffer[BUFSIZ];
568 char *end_boundary = NULL;
569 off_t begin = (off_t) ct->c_end > (off_t) (ct->c_begin + sizeof buffer)
570 ? (off_t) (ct->c_end - sizeof buffer)
571 : (off_t) ct->c_begin;
572 size_t bytes_read;
573 int status = OK;
574
575 /* This will fail if the boundary spans fread() calls. BUFSIZ should
576 be big enough, even if it's just 1024, to make that unlikely. */
577
578 /* free_content() will close ct->c_fp. */
579 if (! ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
580 advise (ct->c_file, "unable to open for reading");
581 return NOTOK;
582 }
583
584 /* Get boundary at end of multipart. */
585 while (begin >= (off_t) ct->c_begin) {
586 fseeko (ct->c_fp, begin, SEEK_SET);
587 while ((bytes_read = fread (buffer, 1, sizeof buffer, ct->c_fp)) > 0) {
588 char *cp = rfind_str (buffer, bytes_read, "--");
589
590 if (cp) {
591 char *end;
592
593 /* Trim off trailing "--" and anything beyond. */
594 *cp-- = '\0';
595 if ((end = rfind_str (buffer, cp - buffer, "\n"))) {
596 if (strlen (end) > 3 && *end++ == '\n' &&
597 *end++ == '-' && *end++ == '-') {
598 end_boundary = add (end, NULL);
599 break;
600 }
601 }
602 }
603 }
604
605 if (! end_boundary && begin > (off_t) (ct->c_begin + sizeof buffer)) {
606 begin -= sizeof buffer;
607 } else {
608 break;
609 }
610 }
611
612 /* Get boundary at beginning of multipart. */
613 if (end_boundary) {
614 fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
615 while ((bytes_read = fread (buffer, 1, sizeof buffer, ct->c_fp)) > 0) {
616 if (bytes_read >= strlen (end_boundary)) {
617 char *cp = find_str (buffer, bytes_read, end_boundary);
618
619 if (cp && cp - buffer >= 2 && *--cp == '-' &&
620 *--cp == '-' && (cp > buffer && *--cp == '\n')) {
621 status = OK;
622 break;
623 }
624 } else {
625 /* The start and end boundaries didn't match, or the
626 start boundary doesn't begin with "\n--" (or "--"
627 if at the beginning of buffer). Keep trying. */
628 status = NOTOK;
629 }
630 }
631 } else {
632 status = NOTOK;
633 }
634
635 if (status == OK) {
636 *part_boundary = end_boundary;
637 } else {
638 *part_boundary = NULL;
639 free (end_boundary);
640 }
641
642 return status;
643 }
644
645
646 /* Open and copy ct->c_file to file, replacing the multipart boundary. */
647 static int
648 replace_boundary (CT ct, char *file, char *boundary) {
649 FILE *fpin, *fpout;
650 int compnum, state;
651 char buf[BUFSIZ], name[NAMESZ];
652 char *np, *vp;
653 m_getfld_state_t gstate = 0;
654 int status = OK;
655
656 if (ct->c_file == NULL) {
657 advise (NULL, "missing input filename");
658 return NOTOK;
659 }
660
661 if ((fpin = fopen (ct->c_file, "r")) == NULL) {
662 advise (ct->c_file, "unable to open for reading");
663 return NOTOK;
664 }
665
666 if ((fpout = fopen (file, "w")) == NULL) {
667 fclose (fpin);
668 advise (file, "unable to open for writing");
669 return NOTOK;
670 }
671
672 for (compnum = 1;;) {
673 int bufsz = (int) sizeof buf;
674
675 switch (state = m_getfld (&gstate, name, buf, &bufsz, fpin)) {
676 case FLD:
677 case FLDPLUS:
678 compnum++;
679
680 /* get copies of the buffers */
681 np = add (name, NULL);
682 vp = add (buf, NULL);
683
684 /* if necessary, get rest of field */
685 while (state == FLDPLUS) {
686 bufsz = sizeof buf;
687 state = m_getfld (&gstate, name, buf, &bufsz, fpin);
688 vp = add (buf, vp); /* add to previous value */
689 }
690
691 if (strcasecmp (TYPE_FIELD, np)) {
692 fprintf (fpout, "%s:%s", np, vp);
693 } else {
694 char *new_ctline, *new_params;
695
696 replace_param(&ct->c_ctinfo.ci_first_pm,
697 &ct->c_ctinfo.ci_last_pm, "boundary",
698 boundary, 0);
699
700 new_ctline = concat(" ", ct->c_ctinfo.ci_type, "/",
701 ct->c_ctinfo.ci_subtype, NULL);
702 new_params = output_params(strlen(TYPE_FIELD) +
703 strlen(new_ctline) + 1,
704 ct->c_ctinfo.ci_first_pm, NULL, 0);
705 fprintf (fpout, "%s:%s%s\n", np, new_ctline,
706 new_params ? new_params : "");
707 free(new_ctline);
708 if (new_params)
709 free(new_params);
710 }
711
712 free (vp);
713 free (np);
714
715 continue;
716
717 case BODY:
718 fputs ("\n", fpout);
719 /* buf will have a terminating NULL, skip it. */
720 if ((int) fwrite (buf, 1, bufsz-1, fpout) < bufsz-1) {
721 advise (file, "fwrite");
722 }
723 continue;
724
725 case FILEEOF:
726 break;
727
728 case LENERR:
729 case FMTERR:
730 advise (NULL, "message format error in component #%d", compnum);
731 status = NOTOK;
732 break;
733
734 default:
735 advise (NULL, "getfld() returned %d", state);
736 status = NOTOK;
737 break;
738 }
739
740 break;
741 }
742
743 m_getfld_state_destroy (&gstate);
744 fclose (fpout);
745 fclose (fpin);
746
747 return status;
748 }
749
750
751 static int
752 fix_types (CT ct, svector_t fixtypes, int *message_mods) {
753 int status = OK;
754
755 switch (ct->c_type) {
756 case CT_MULTIPART: {
757 struct multipart *m = (struct multipart *) ct->c_ctparams;
758 struct part *part;
759
760 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
761 status = fix_types (part->mp_part, fixtypes, message_mods);
762 }
763 break;
764 }
765
766 case CT_MESSAGE:
767 if (ct->c_subtype == MESSAGE_EXTERNAL) {
768 struct exbody *e = (struct exbody *) ct->c_ctparams;
769
770 status = fix_types (e->eb_content, fixtypes, message_mods);
771 }
772 break;
773
774 default: {
775 char **typep, *type;
776
777 if (ct->c_ctinfo.ci_type && ct->c_ctinfo.ci_subtype) {
778 for (typep = svector_strs (fixtypes);
779 typep && (type = *typep);
780 ++typep) {
781 char *type_subtype =
782 concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype,
783 NULL);
784
785 if (! strcasecmp (type, type_subtype) &&
786 decode_part (ct) == OK &&
787 ct->c_cefile.ce_file != NULL) {
788 char *ct_type_subtype = mime_type (ct->c_cefile.ce_file);
789 char *cp;
790
791 if ((cp = strchr (ct_type_subtype, ';'))) {
792 /* Truncate to remove any parameter list from
793 mime_type () result. */
794 *cp = '\0';
795 }
796
797 if (strcasecmp (type, ct_type_subtype)) {
798 char *ct_type, *ct_subtype;
799 HF hf;
800
801 /* The Content-Type header does not match the
802 content, so update these struct Content
803 fields to match:
804 * c_type, c_subtype
805 * c_ctinfo.ci_type, c_ctinfo.ci_subtype
806 * c_ctline
807 */
808 /* Extract type and subtype from type/subtype. */
809 ct_type = getcpy (ct_type_subtype);
810 if ((cp = strchr (ct_type, '/'))) {
811 *cp = '\0';
812 ct_subtype = getcpy (++cp);
813 } else {
814 advise (NULL, "missing / in MIME type of %s %s",
815 ct->c_file, ct->c_partno);
816 free (ct_type);
817 return NOTOK;
818 }
819
820 ct->c_type = ct_str_type (ct_type);
821 ct->c_subtype = ct_str_subtype (ct->c_type, ct_subtype);
822
823 free (ct->c_ctinfo.ci_type);
824 ct->c_ctinfo.ci_type = ct_type;
825 free (ct->c_ctinfo.ci_subtype);
826 ct->c_ctinfo.ci_subtype = ct_subtype;
827 if (! replace_substring (&ct->c_ctline, type,
828 ct_type_subtype)) {
829 advise (NULL, "did not find %s in %s",
830 type, ct->c_ctline);
831 }
832
833 /* Update Content-Type header field. */
834 for (hf = ct->c_first_hf; hf; hf = hf->next) {
835 if (! strcasecmp (TYPE_FIELD, hf->name)) {
836 if (replace_substring (&hf->value, type,
837 ct_type_subtype)) {
838 ++*message_mods;
839 if (verbosw) {
840 report (NULL, ct->c_partno, ct->c_file,
841 "change Content-Type in header "
842 "from %s to %s",
843 type, ct_type_subtype);
844 }
845 break;
846 } else {
847 advise (NULL, "did not find %s in %s",
848 type, hf->value);
849 }
850 }
851 }
852 }
853 free (ct_type_subtype);
854 }
855 free (type_subtype);
856 }
857 }
858 }}
859
860 return status;
861 }
862
863 char *
864 replace_substring (char **str, const char *old, const char *new) {
865 char *cp;
866
867 if ((cp = strstr (*str, old))) {
868 char *remainder = cp + strlen (old);
869 char *prefix, *new_str;
870
871 if (cp - *str) {
872 prefix = getcpy (*str);
873 *(prefix + (cp - *str)) = '\0';
874 new_str = concat (prefix, new, remainder, NULL);
875 free (prefix);
876 } else {
877 new_str = concat (new, remainder, NULL);
878 }
879
880 free (*str);
881
882 return *str = new_str;
883 } else {
884 return NULL;
885 }
886 }
887
888
889 static int
890 fix_multipart_cte (CT ct, int *message_mods) {
891 int status = OK;
892
893 if (ct->c_type == CT_MULTIPART) {
894 struct multipart *m;
895 struct part *part;
896
897 if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT &&
898 ct->c_encoding != CE_BINARY) {
899 HF hf;
900
901 for (hf = ct->c_first_hf; hf; hf = hf->next) {
902 char *name = hf->name;
903 for (; *name && isspace ((unsigned char) *name); ++name) {
904 continue;
905 }
906
907 if (! strncasecmp (name, ENCODING_FIELD,
908 strlen (ENCODING_FIELD))) {
909 char *prefix = "Nmh-REPLACED-INVALID-";
910 HF h = mh_xmalloc (sizeof *h);
911
912 h->name = add (hf->name, NULL);
913 h->hf_encoding = hf->hf_encoding;
914 h->next = hf->next;
915 hf->next = h;
916
917 /* Retain old header but prefix its name. */
918 free (hf->name);
919 hf->name = concat (prefix, h->name, NULL);
920
921 ++*message_mods;
922 if (verbosw) {
923 char *encoding = cpytrim (hf->value);
924 report (NULL, ct->c_partno, ct->c_file,
925 "replace Content-Transfer-Encoding of %s "
926 "with 8 bit", encoding);
927 free (encoding);
928 }
929
930 h->value = add (" 8bit\n", NULL);
931
932 /* Don't need to warn for multiple C-T-E header
933 fields, parse_mime() already does that. But
934 if there are any, fix them all as necessary. */
935 hf = h;
936 }
937 }
938
939 set_ce (ct, CE_8BIT);
940 }
941
942 m = (struct multipart *) ct->c_ctparams;
943 for (part = m->mp_parts; part; part = part->mp_next) {
944 if (fix_multipart_cte (part->mp_part, message_mods) != OK) {
945 status = NOTOK;
946 break;
947 }
948 }
949 }
950
951 return status;
952 }
953
954
955 static int
956 set_ce (CT ct, int encoding) {
957 const char *ce = ce_str (encoding);
958 const struct str2init *ctinit = get_ce_method (ce);
959
960 if (ctinit) {
961 char *cte = concat (" ", ce, "\n", NULL);
962 int found_cte = 0;
963 HF hf;
964 /* Decoded contents might be in ct->c_cefile.ce_file, if the
965 caller is decode_text_parts (). Save because we'll
966 overwrite below. */
967 struct cefile decoded_content_info = ct->c_cefile;
968
969 ct->c_encoding = encoding;
970
971 ct->c_ctinitfnx = ctinit->si_init;
972 /* This will assign ct->c_cefile with an all-0 struct, which
973 is what we want. */
974 (*ctinit->si_init) (ct);
975 /* After returning, the caller should set
976 ct->c_cefile.ce_file to the name of the file containing
977 the contents. */
978
979 /* Restore the cefile. */
980 ct->c_cefile = decoded_content_info;
981
982 /* Update/add Content-Transfer-Encoding header field. */
983 for (hf = ct->c_first_hf; hf; hf = hf->next) {
984 if (! strcasecmp (ENCODING_FIELD, hf->name)) {
985 found_cte = 1;
986 free (hf->value);
987 hf->value = cte;
988 }
989 }
990 if (! found_cte) {
991 add_header (ct, add (ENCODING_FIELD, NULL), cte);
992 }
993
994 /* Update c_celine. It's used only by mhlist -debug. */
995 free (ct->c_celine);
996 ct->c_celine = add (cte, NULL);
997
998 return OK;
999 } else {
1000 return NOTOK;
1001 }
1002 }
1003
1004
1005 /* Make sure each text part has a corresponding text/plain part. */
1006 static int
1007 ensure_text_plain (CT *ct, CT parent, int *message_mods, int replacetextplain) {
1008 int status = OK;
1009
1010 switch ((*ct)->c_type) {
1011 case CT_TEXT: {
1012 /* Nothing to do for text/plain. */
1013 if ((*ct)->c_subtype == TEXT_PLAIN) { return OK; }
1014
1015 if (parent && parent->c_type == CT_MULTIPART &&
1016 parent->c_subtype == MULTI_ALTERNATE) {
1017 int new_subpart_number = 1;
1018 int has_text_plain =
1019 find_textplain_sibling (parent, replacetextplain,
1020 &new_subpart_number);
1021
1022 if (! has_text_plain) {
1023 /* Parent is a multipart/alternative. Insert a new
1024 text/plain subpart. */
1025 const int inserted =
1026 insert_new_text_plain_part (*ct, new_subpart_number,
1027 parent);
1028 if (inserted) {
1029 ++*message_mods;
1030 if (verbosw) {
1031 report (NULL, parent->c_partno, parent->c_file,
1032 "insert text/plain part");
1033 }
1034 } else {
1035 status = NOTOK;
1036 }
1037 }
1038 } else if (parent && parent->c_type == CT_MULTIPART &&
1039 parent->c_subtype == MULTI_RELATED) {
1040 char *type_subtype =
1041 concat ((*ct)->c_ctinfo.ci_type, "/",
1042 (*ct)->c_ctinfo.ci_subtype, NULL);
1043 const char *parent_type =
1044 get_param (parent->c_ctinfo.ci_first_pm, "type", '?', 1);
1045 int new_subpart_number = 1;
1046 int has_text_plain = 0;
1047
1048 /* Have to do string comparison on the subtype because we
1049 don't enumerate all of them in c_subtype values.
1050 parent_type will be NULL if the multipart/related part
1051 doesn't have a type parameter. The type parameter must
1052 be specified according to RFC 2387 Sec. 3.1 but not all
1053 messages comply. */
1054 if (parent_type && strcasecmp (type_subtype, parent_type) == 0) {
1055 /* The type of this part matches the root type of the
1056 parent multipart/related. Look to see if there's
1057 text/plain sibling. */
1058 has_text_plain =
1059 find_textplain_sibling (parent, replacetextplain,
1060 &new_subpart_number);
1061 }
1062
1063 free (type_subtype);
1064
1065 if (! has_text_plain) {
1066 /* Parent is a multipart/alternative. Insert a new
1067 text/plain subpart. */
1068 const int inserted =
1069 insert_new_text_plain_part (*ct, new_subpart_number,
1070 parent);
1071 if (inserted) {
1072 ++*message_mods;
1073 if (verbosw) {
1074 report (NULL, parent->c_partno, parent->c_file,
1075 "insert text/plain part");
1076 }
1077 } else {
1078 status = NOTOK;
1079 }
1080 }
1081 } else {
1082 /* Slip new text/plain part into a new multipart/alternative. */
1083 CT tp_part = build_text_plain_part (*ct);
1084
1085 if (tp_part) {
1086 CT mp_alt = build_multipart_alt (*ct, tp_part, CT_MULTIPART,
1087 MULTI_ALTERNATE);
1088 if (mp_alt) {
1089 struct multipart *mp =
1090 (struct multipart *) mp_alt->c_ctparams;
1091
1092 if (mp && mp->mp_parts) {
1093 mp->mp_parts->mp_part = tp_part;
1094 /* Make the new multipart/alternative the parent. */
1095 *ct = mp_alt;
1096
1097 ++*message_mods;
1098 if (verbosw) {
1099 report (NULL, (*ct)->c_partno, (*ct)->c_file,
1100 "insert text/plain part");
1101 }
1102 } else {
1103 free_content (tp_part);
1104 free_content (mp_alt);
1105 status = NOTOK;
1106 }
1107 } else {
1108 status = NOTOK;
1109 }
1110 } else {
1111 status = NOTOK;
1112 }
1113 }
1114 break;
1115 }
1116
1117 case CT_MULTIPART: {
1118 struct multipart *mp = (struct multipart *) (*ct)->c_ctparams;
1119 struct part *part;
1120
1121 for (part = mp->mp_parts; status == OK && part; part = part->mp_next) {
1122 if ((*ct)->c_type == CT_MULTIPART) {
1123 status = ensure_text_plain (&part->mp_part, *ct, message_mods,
1124 replacetextplain);
1125 }
1126 }
1127 break;
1128 }
1129
1130 case CT_MESSAGE:
1131 if ((*ct)->c_subtype == MESSAGE_EXTERNAL) {
1132 struct exbody *e = (struct exbody *) (*ct)->c_ctparams;
1133
1134 status = ensure_text_plain (&e->eb_content, *ct, message_mods,
1135 replacetextplain);
1136 }
1137 break;
1138 }
1139
1140 return status;
1141 }
1142
1143
1144 /* See if there is a sibling text/plain. */
1145 static int
1146 find_textplain_sibling (CT parent, int replacetextplain,
1147 int *new_subpart_number) {
1148 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1149 struct part *part, *prev;
1150 int has_text_plain = 0;
1151
1152 for (prev = part = mp->mp_parts; part; part = part->mp_next) {
1153 ++*new_subpart_number;
1154 if (part->mp_part->c_type == CT_TEXT &&
1155 part->mp_part->c_subtype == TEXT_PLAIN) {
1156 if (replacetextplain) {
1157 struct part *old_part;
1158 if (part == mp->mp_parts) {
1159 old_part = mp->mp_parts;
1160 mp->mp_parts = part->mp_next;
1161 } else {
1162 old_part = prev->mp_next;
1163 prev->mp_next = part->mp_next;
1164 }
1165 if (verbosw) {
1166 report (NULL, parent->c_partno, parent->c_file,
1167 "remove text/plain part %s",
1168 old_part->mp_part->c_partno);
1169 }
1170 free_content (old_part->mp_part);
1171 free (old_part);
1172 } else {
1173 has_text_plain = 1;
1174 }
1175 break;
1176 }
1177 prev = part;
1178 }
1179
1180 return has_text_plain;
1181 }
1182
1183
1184 static int
1185 insert_new_text_plain_part (CT ct, int new_subpart_number, CT parent) {
1186 struct multipart *mp = (struct multipart *) parent->c_ctparams;
1187 struct part *new_part = mh_xmalloc (sizeof *new_part);
1188
1189 if ((new_part->mp_part = build_text_plain_part (ct))) {
1190 char buffer[16];
1191 snprintf (buffer, sizeof buffer, "%d", new_subpart_number);
1192
1193 new_part->mp_next = mp->mp_parts;
1194 mp->mp_parts = new_part;
1195 new_part->mp_part->c_partno =
1196 concat (parent->c_partno ? parent->c_partno : "1", ".",
1197 buffer, NULL);
1198
1199 return 1;
1200 } else {
1201 free_content (new_part->mp_part);
1202 free (new_part);
1203
1204 return 0;
1205 }
1206 }
1207
1208
1209 static CT
1210 build_text_plain_part (CT encoded_part) {
1211 CT tp_part = divide_part (encoded_part);
1212 char *tmp_plain_file = NULL;
1213
1214 if (decode_part (tp_part) == OK) {
1215 /* Now, tp_part->c_cefile.ce_file is the name of the tmp file that
1216 contains the decoded contents. And the decoding function, such
1217 as openQuoted, will have set ...->ce_unlink to 1 so that it will
1218 be unlinked by free_content (). */
1219 char *tempfile;
1220
1221 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, NULL)) == NULL) {
1222 advise (NULL, "unable to create temporary file in %s",
1223 get_temp_dir());
1224 } else {
1225 tmp_plain_file = add (tempfile, NULL);
1226 if (reformat_part (tp_part, tmp_plain_file,
1227 tp_part->c_ctinfo.ci_type,
1228 tp_part->c_ctinfo.ci_subtype,
1229 tp_part->c_type) == OK) {
1230 return tp_part;
1231 }
1232 }
1233 }
1234
1235 free_content (tp_part);
1236 if (tmp_plain_file) { (void) m_unlink (tmp_plain_file); }
1237 free (tmp_plain_file);
1238
1239 return NULL;
1240 }
1241
1242
1243 static CT
1244 divide_part (CT ct) {
1245 CT new_part;
1246
1247 if ((new_part = (CT) mh_xcalloc (1, sizeof *new_part)) == NULL)
1248 adios (NULL, "out of memory");
1249
1250 /* Just copy over what is needed for decoding. c_vrsn and
1251 c_celine aren't necessary. */
1252 new_part->c_file = add (ct->c_file, NULL);
1253 new_part->c_begin = ct->c_begin;
1254 new_part->c_end = ct->c_end;
1255 copy_ctinfo (&new_part->c_ctinfo, &ct->c_ctinfo);
1256 new_part->c_type = ct->c_type;
1257 new_part->c_cefile = ct->c_cefile;
1258 new_part->c_encoding = ct->c_encoding;
1259 new_part->c_ctinitfnx = ct->c_ctinitfnx;
1260 new_part->c_ceopenfnx = ct->c_ceopenfnx;
1261 new_part->c_ceclosefnx = ct->c_ceclosefnx;
1262 new_part->c_cesizefnx = ct->c_cesizefnx;
1263
1264 /* c_ctline is used by reformat__part(), so it can preserve
1265 anything after the type/subtype. */
1266 new_part->c_ctline = add (ct->c_ctline, NULL);
1267
1268 return new_part;
1269 }
1270
1271
1272 static void
1273 copy_ctinfo (CI dest, CI src) {
1274 PM s_pm, d_pm;
1275
1276 dest->ci_type = src->ci_type ? add (src->ci_type, NULL) : NULL;
1277 dest->ci_subtype = src->ci_subtype ? add (src->ci_subtype, NULL) : NULL;
1278
1279 for (s_pm = src->ci_first_pm; s_pm; s_pm = s_pm->pm_next) {
1280 d_pm = add_param(&dest->ci_first_pm, &dest->ci_last_pm, s_pm->pm_name,
1281 s_pm->pm_value, 0);
1282 if (s_pm->pm_charset)
1283 d_pm->pm_charset = getcpy(s_pm->pm_charset);
1284 if (s_pm->pm_lang)
1285 d_pm->pm_lang = getcpy(s_pm->pm_lang);
1286 }
1287
1288 dest->ci_comment = src->ci_comment ? add (src->ci_comment, NULL) : NULL;
1289 dest->ci_magic = src->ci_magic ? add (src->ci_magic, NULL) : NULL;
1290 }
1291
1292
1293 static int
1294 decode_part (CT ct) {
1295 char *tmp_decoded;
1296 int status;
1297 char *tempfile;
1298
1299 if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, NULL)) == NULL) {
1300 adios (NULL, "unable to create temporary file in %s", get_temp_dir());
1301 }
1302 tmp_decoded = add (tempfile, NULL);
1303 /* The following call will load ct->c_cefile.ce_file with the tmp
1304 filename of the decoded content. tmp_decoded will contain the
1305 encoded output, get rid of that. */
1306 status = output_message (ct, tmp_decoded);
1307 (void) m_unlink (tmp_decoded);
1308 free (tmp_decoded);
1309
1310 return status;
1311 }
1312
1313
1314 /* Some of the arguments aren't really needed now, but maybe will
1315 be in the future for other than text types. */
1316 static int
1317 reformat_part (CT ct, char *file, char *type, char *subtype, int c_type) {
1318 int output_subtype, output_encoding;
1319 char *cp, *cf;
1320 int status;
1321
1322 /* Hacky: this redirects the output from whatever command is used
1323 to show the part to a file. So, the user can't have any output
1324 redirection in that command.
1325 Could show_multi() in mhshowsbr.c avoid this? */
1326
1327 /* Check for invo_name-format-type/subtype. */
1328 if ((cf = context_find_by_type ("format", type, subtype)) == NULL) {
1329 if (verbosw) {
1330 advise (NULL, "Don't know how to convert %s, there is no "
1331 "%s-format-%s/%s profile entry",
1332 ct->c_file, invo_name, type, subtype);
1333 }
1334 return NOTOK;
1335 } else {
1336 if (strchr (cf, '>')) {
1337 advise (NULL, "'>' prohibited in \"%s\",\nplease fix your "
1338 "%s-format-%s/%s profile entry", cf, invo_name, type,
1339 subtype ? subtype : "");
1340
1341 return NOTOK;
1342 }
1343 }
1344
1345 cp = concat (cf, " >", file, NULL);
1346 status = show_content_aux (ct, 0, cp, NULL, NULL);
1347 free (cp);
1348
1349 /* Unlink decoded content tmp file and free its filename to avoid
1350 leaks. The file stream should already have been closed. */
1351 if (ct->c_cefile.ce_unlink) {
1352 (void) m_unlink (ct->c_cefile.ce_file);
1353 free (ct->c_cefile.ce_file);
1354 ct->c_cefile.ce_file = NULL;
1355 ct->c_cefile.ce_unlink = 0;
1356 }
1357
1358 if (c_type == CT_TEXT) {
1359 output_subtype = TEXT_PLAIN;
1360 } else {
1361 /* Set subtype to 0, which is always an UNKNOWN subtype. */
1362 output_subtype = 0;
1363 }
1364 output_encoding = charset_encoding (ct);
1365
1366 if (set_ct_type (ct, c_type, output_subtype, output_encoding) == OK) {
1367 ct->c_cefile.ce_file = file;
1368 ct->c_cefile.ce_unlink = 1;
1369 } else {
1370 ct->c_cefile.ce_unlink = 0;
1371 status = NOTOK;
1372 }
1373
1374 return status;
1375 }
1376
1377
1378 /* Identifies 7bit or 8bit content based on charset. */
1379 static int
1380 charset_encoding (CT ct) {
1381 char *ct_charset = content_charset (ct);
1382 int encoding = strcasecmp (ct_charset, "US-ASCII") ? CE_8BIT : CE_7BIT;
1383
1384 free (ct_charset);
1385
1386 return encoding;
1387 }
1388
1389
1390 static CT
1391 build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) {
1392 char *boundary_prefix = "----=_nmh-multipart";
1393 char *boundary = concat (boundary_prefix, first_alt->c_partno, NULL);
1394 char *boundary_indicator = "; boundary=";
1395 char *typename, *subtypename, *name;
1396 CT ct;
1397 struct part *p;
1398 struct multipart *m;
1399 const struct str2init *ctinit;
1400
1401 if ((ct = (CT) mh_xcalloc (1, sizeof *ct)) == NULL)
1402 adios (NULL, "out of memory");
1403
1404 /* Set up the multipart/alternative part. These fields of *ct were
1405 initialized to 0 by mh_xcalloc():
1406 c_fp, c_unlink, c_begin, c_end,
1407 c_vrsn, c_ctline, c_celine,
1408 c_id, c_descr, c_dispo, c_partno,
1409 c_ctinfo.ci_comment, c_ctinfo.ci_magic,
1410 c_cefile, c_encoding,
1411 c_digested, c_digest[16], c_ctexbody,
1412 c_ctinitfnx, c_ceopenfnx, c_ceclosefnx, c_cesizefnx,
1413 c_umask, c_rfc934,
1414 c_showproc, c_termproc, c_storeproc, c_storage, c_folder
1415 */
1416
1417 ct->c_file = add (first_alt->c_file, NULL);
1418 ct->c_type = type;
1419 ct->c_subtype = subtype;
1420
1421 ctinit = get_ct_init (ct->c_type);
1422
1423 typename = ct_type_str (type);
1424 subtypename = ct_subtype_str (type, subtype);
1425
1426 {
1427 int serial = 0;
1428 int found_boundary = 1;
1429
1430 while (found_boundary && serial < 1000000) {
1431 found_boundary = 0;
1432
1433 /* Ensure that the boundary doesn't appear in the decoded
1434 content. */
1435 if (new_part->c_cefile.ce_file) {
1436 if ((found_boundary =
1437 boundary_in_content (&new_part->c_cefile.ce_fp,
1438 new_part->c_cefile.ce_file,
1439 boundary)) == -1) {
1440 free (ct);
1441 return NULL;
1442 }
1443 }
1444
1445 /* Ensure that the boundary doesn't appear in the encoded
1446 content. */
1447 if (! found_boundary && new_part->c_file) {
1448 if ((found_boundary = boundary_in_content (&new_part->c_fp,
1449 new_part->c_file,
1450 boundary)) == -1) {
1451 free (ct);
1452 return NULL;
1453 }
1454 }
1455
1456 if (found_boundary) {
1457 /* Try a slightly different boundary. */
1458 char buffer2[16];
1459
1460 free (boundary);
1461 ++serial;
1462 snprintf (buffer2, sizeof buffer2, "%d", serial);
1463 boundary =
1464 concat (boundary_prefix,
1465 first_alt->c_partno ? first_alt->c_partno : "",
1466 "-", buffer2, NULL);
1467 }
1468 }
1469
1470 if (found_boundary) {
1471 advise (NULL, "giving up trying to find a unique boundary");
1472 free (ct);
1473 return NULL;
1474 }
1475 }
1476
1477 name = concat (" ", typename, "/", subtypename, boundary_indicator, "\"",
1478 boundary, "\"", NULL);
1479
1480 /* Load c_first_hf and c_last_hf. */
1481 transfer_noncontent_headers (first_alt, ct);
1482 add_header (ct, add (TYPE_FIELD, NULL), concat (name, "\n", NULL));
1483 free (name);
1484
1485 /* Load c_partno. */
1486 if (first_alt->c_partno) {
1487 ct->c_partno = add (first_alt->c_partno, NULL);
1488 free (first_alt->c_partno);
1489 first_alt->c_partno = concat (ct->c_partno, ".1", NULL);
1490 new_part->c_partno = concat (ct->c_partno, ".2", NULL);
1491 } else {
1492 first_alt->c_partno = add ("1", NULL);
1493 new_part->c_partno = add ("2", NULL);
1494 }
1495
1496 if (ctinit) {
1497 ct->c_ctinfo.ci_type = add (typename, NULL);
1498 ct->c_ctinfo.ci_subtype = add (subtypename, NULL);
1499 }
1500
1501 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
1502 "boundary", boundary, 0);
1503
1504 p = (struct part *) mh_xmalloc (sizeof *p);
1505 p->mp_next = (struct part *) mh_xmalloc (sizeof *p->mp_next);
1506 p->mp_next->mp_next = NULL;
1507 p->mp_next->mp_part = first_alt;
1508
1509 if ((m = (struct multipart *) mh_xcalloc (1, sizeof (struct multipart))) ==
1510 NULL)
1511 adios (NULL, "out of memory");
1512 m->mp_start = concat (boundary, "\n", NULL);
1513 m->mp_stop = concat (boundary, "--\n", NULL);
1514 m->mp_parts = p;
1515 ct->c_ctparams = m;
1516
1517 free (boundary);
1518
1519 return ct;
1520 }
1521
1522
1523 /* Check that the boundary does not appear in the content. */
1524 static int
1525 boundary_in_content (FILE **fp, char *file, const char *boundary) {
1526 char buffer[BUFSIZ];
1527 size_t bytes_read;
1528 int found_boundary = 0;
1529
1530 /* free_content() will close *fp if we fopen it here. */
1531 if (! *fp && (*fp = fopen (file, "r")) == NULL) {
1532 advise (file, "unable to open %s for reading", file);
1533 return NOTOK;
1534 }
1535
1536 fseeko (*fp, 0L, SEEK_SET);
1537 while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) > 0) {
1538 if (find_str (buffer, bytes_read, boundary)) {
1539 found_boundary = 1;
1540 break;
1541 }
1542 }
1543
1544 return found_boundary;
1545 }
1546
1547
1548 /* Remove all non-Content headers. */
1549 static void
1550 transfer_noncontent_headers (CT old, CT new) {
1551 HF hp, hp_prev;
1552
1553 hp_prev = hp = old->c_first_hf;
1554 while (hp) {
1555 HF next = hp->next;
1556
1557 if (strncasecmp (XXX_FIELD_PRF, hp->name, strlen (XXX_FIELD_PRF))) {
1558 if (hp == old->c_last_hf) {
1559 if (hp == old->c_first_hf) {
1560 old->c_last_hf = old->c_first_hf = NULL;
1561 } else {
1562 hp_prev->next = NULL;
1563 old->c_last_hf = hp_prev;
1564 }
1565 } else {
1566 if (hp == old->c_first_hf) {
1567 old->c_first_hf = next;
1568 } else {
1569 hp_prev->next = next;
1570 }
1571 }
1572
1573 /* Put node hp in the new CT. */
1574 if (new->c_first_hf == NULL) {
1575 new->c_first_hf = hp;
1576 } else {
1577 new->c_last_hf->next = hp;
1578 }
1579 new->c_last_hf = hp;
1580 } else {
1581 /* A Content- header, leave in old. */
1582 hp_prev = hp;
1583 }
1584
1585 hp = next;
1586 }
1587 }
1588
1589
1590 static int
1591 set_ct_type (CT ct, int type, int subtype, int encoding) {
1592 char *typename = ct_type_str (type);
1593 char *subtypename = ct_subtype_str (type, subtype);
1594 /* E.g, " text/plain" */
1595 char *type_subtypename = concat (" ", typename, "/", subtypename, NULL);
1596 /* E.g, " text/plain\n" */
1597 char *name_plus_nl = concat (type_subtypename, "\n", NULL);
1598 int found_content_type = 0;
1599 HF hf;
1600 const char *cp = NULL;
1601 char *ctline;
1602 int status;
1603
1604 /* Update/add Content-Type header field. */
1605 for (hf = ct->c_first_hf; hf; hf = hf->next) {
1606 if (! strcasecmp (TYPE_FIELD, hf->name)) {
1607 found_content_type = 1;
1608 free (hf->value);
1609 hf->value = (cp = strchr (ct->c_ctline, ';'))
1610 ? concat (type_subtypename, cp, "\n", NULL)
1611 : add (name_plus_nl, NULL);
1612 }
1613 }
1614 if (! found_content_type) {
1615 add_header (ct, add (TYPE_FIELD, NULL),
1616 (cp = strchr (ct->c_ctline, ';'))
1617 ? concat (type_subtypename, cp, "\n", NULL)
1618 : add (name_plus_nl, NULL));
1619 }
1620
1621 /* Some of these might not be used, but set them anyway. */
1622 ctline = cp
1623 ? concat (type_subtypename, cp, NULL)
1624 : concat (type_subtypename, NULL);
1625 free (ct->c_ctline);
1626 ct->c_ctline = ctline;
1627 /* Leave other ctinfo members as they were. */
1628 free (ct->c_ctinfo.ci_type);
1629 ct->c_ctinfo.ci_type = add (typename, NULL);
1630 free (ct->c_ctinfo.ci_subtype);
1631 ct->c_ctinfo.ci_subtype = add (subtypename, NULL);
1632 ct->c_type = type;
1633 ct->c_subtype = subtype;
1634
1635 free (name_plus_nl);
1636 free (type_subtypename);
1637
1638 status = set_ce (ct, encoding);
1639
1640 return status;
1641 }
1642
1643
1644 static int
1645 decode_text_parts (CT ct, int encoding, int *message_mods) {
1646 int status = OK;
1647
1648 switch (ct->c_type) {
1649 case CT_TEXT:
1650 switch (ct->c_encoding) {
1651 case CE_BASE64:
1652 case CE_QUOTED: {
1653 int ct_encoding;
1654
1655 if (decode_part (ct) == OK && ct->c_cefile.ce_file) {
1656 const char *reason = NULL;
1657
1658 if ((ct_encoding = content_encoding (ct, &reason)) == CE_BINARY
1659 && encoding != CE_BINARY) {
1660 /* The decoding isn't acceptable so discard it.
1661 Leave status as OK to allow other transformations. */
1662 if (verbosw) {
1663 report (NULL, ct->c_partno, ct->c_file,
1664 "will not decode%s because it is binary (%s)",
1665 ct->c_partno ? ""
1666 : ct->c_ctline ? ct->c_ctline
1667 : "",
1668 reason);
1669 }
1670 (void) m_unlink (ct->c_cefile.ce_file);
1671 free (ct->c_cefile.ce_file);
1672 ct->c_cefile.ce_file = NULL;
1673 } else if (ct->c_encoding == CE_QUOTED &&
1674 ct_encoding == CE_8BIT && encoding == CE_7BIT) {
1675 /* The decoding isn't acceptable so discard it.
1676 Leave status as OK to allow other transformations. */
1677 if (verbosw) {
1678 report (NULL, ct->c_partno, ct->c_file,
1679 "will not decode%s because it is 8bit",
1680 ct->c_partno ? ""
1681 : ct->c_ctline ? ct->c_ctline
1682 : "");
1683 }
1684 (void) m_unlink (ct->c_cefile.ce_file);
1685 free (ct->c_cefile.ce_file);
1686 ct->c_cefile.ce_file = NULL;
1687 } else {
1688 int enc;
1689 if (ct_encoding == CE_BINARY)
1690 enc = CE_BINARY;
1691 else if (ct_encoding == CE_8BIT && encoding == CE_7BIT)
1692 enc = CE_QUOTED;
1693 else
1694 enc = charset_encoding (ct);
1695 if (set_ce (ct, enc) == OK) {
1696 ++*message_mods;
1697 if (verbosw) {
1698 report (NULL, ct->c_partno, ct->c_file, "decode%s",
1699 ct->c_ctline ? ct->c_ctline : "");
1700 }
1701 strip_crs (ct, message_mods);
1702 } else {
1703 status = NOTOK;
1704 }
1705 }
1706 } else {
1707 status = NOTOK;
1708 }
1709 break;
1710 }
1711 case CE_8BIT:
1712 case CE_7BIT:
1713 strip_crs (ct, message_mods);
1714 break;
1715 default:
1716 break;
1717 }
1718
1719 break;
1720
1721 case CT_MULTIPART: {
1722 struct multipart *m = (struct multipart *) ct->c_ctparams;
1723 struct part *part;
1724
1725 /* Should check to see if the body for this part is encoded?
1726 For now, it gets passed along as-is by InitMultiPart(). */
1727 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
1728 status = decode_text_parts (part->mp_part, encoding, message_mods);
1729 }
1730 break;
1731 }
1732
1733 case CT_MESSAGE:
1734 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1735 struct exbody *e = (struct exbody *) ct->c_ctparams;
1736
1737 status = decode_text_parts (e->eb_content, encoding, message_mods);
1738 }
1739 break;
1740
1741 default:
1742 break;
1743 }
1744
1745 return status;
1746 }
1747
1748
1749 /* See if the decoded content is 7bit, 8bit, or binary. It's binary
1750 if it has any NUL characters, a CR not followed by a LF, or lines
1751 greater than 998 characters in length. If binary, reason is set
1752 to a string explaining why. */
1753 static int
1754 content_encoding (CT ct, const char **reason) {
1755 CE ce = &ct->c_cefile;
1756 int encoding = CE_7BIT;
1757
1758 if (ce->ce_file) {
1759 size_t line_len = 0;
1760 char buffer[BUFSIZ];
1761 size_t inbytes;
1762
1763 if (! ce->ce_fp && (ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1764 advise (ce->ce_file, "unable to open for reading");
1765 return CE_UNKNOWN;
1766 }
1767
1768 fseeko (ce->ce_fp, 0L, SEEK_SET);
1769 while (encoding != CE_BINARY &&
1770 (inbytes = fread (buffer, 1, sizeof buffer, ce->ce_fp)) > 0) {
1771 char *cp;
1772 size_t i;
1773 int last_char_was_cr = 0;
1774
1775 for (i = 0, cp = buffer; i < inbytes; ++i, ++cp) {
1776 if (*cp == '\0' || ++line_len > 998 ||
1777 (*cp != '\n' && last_char_was_cr)) {
1778 encoding = CE_BINARY;
1779 if (*cp == '\0') {
1780 *reason = "null character";
1781 } else if (line_len > 998) {
1782 *reason = "line length > 998";
1783 } else if (*cp != '\n' && last_char_was_cr) {
1784 *reason = "CR not followed by LF";
1785 } else {
1786 /* Should not reach this. */
1787 *reason = "";
1788 }
1789 break;
1790 } else if (*cp == '\n') {
1791 line_len = 0;
1792 } else if (! isascii ((unsigned char) *cp)) {
1793 encoding = CE_8BIT;
1794 }
1795
1796 last_char_was_cr = *cp == '\r' ? 1 : 0;
1797 }
1798 }
1799
1800 fclose (ce->ce_fp);
1801 ce->ce_fp = NULL;
1802 } /* else should never happen */
1803
1804 return encoding;
1805 }
1806
1807
1808 static int
1809 strip_crs (CT ct, int *message_mods) {
1810 char *charset = content_charset (ct);
1811 int status = OK;
1812
1813 /* Only strip carriage returns if content is ASCII or another
1814 charset that has the same readily recognizable CR followed by a
1815 LF. We can include UTF-8 here because if the high-order bit of
1816 a UTF-8 byte is 0, then it must be a single-byte ASCII
1817 character. */
1818 if (! strcasecmp (charset, "US-ASCII") ||
1819 ! strcasecmp (charset, "UTF-8") ||
1820 ! strncasecmp (charset, "ISO-8859-", 9) ||
1821 ! strncasecmp (charset, "WINDOWS-12", 10)) {
1822 char **file = NULL;
1823 FILE **fp = NULL;
1824 size_t begin;
1825 size_t end;
1826 int has_crs = 0;
1827 int opened_input_file = 0;
1828
1829 if (ct->c_cefile.ce_file) {
1830 file = &ct->c_cefile.ce_file;
1831 fp = &ct->c_cefile.ce_fp;
1832 begin = end = 0;
1833 } else if (ct->c_file) {
1834 file = &ct->c_file;
1835 fp = &ct->c_fp;
1836 begin = (size_t) ct->c_begin;
1837 end = (size_t) ct->c_end;
1838 } /* else don't know where the content is */
1839
1840 if (file && *file && fp) {
1841 if (! *fp) {
1842 if ((*fp = fopen (*file, "r")) == NULL) {
1843 advise (*file, "unable to open for reading");
1844 status = NOTOK;
1845 } else {
1846 opened_input_file = 1;
1847 }
1848 }
1849 }
1850
1851 if (fp && *fp) {
1852 char buffer[BUFSIZ];
1853 size_t bytes_read;
1854 size_t bytes_to_read =
1855 end > 0 && end > begin ? end - begin : sizeof buffer;
1856
1857 fseeko (*fp, begin, SEEK_SET);
1858 while ((bytes_read = fread (buffer, 1,
1859 min (bytes_to_read, sizeof buffer),
1860 *fp)) > 0) {
1861 /* Look for CR followed by a LF. This is supposed to
1862 be text so there should be LF's. If not, don't
1863 modify the content. */
1864 char *cp;
1865 size_t i;
1866 int last_char_was_cr = 0;
1867
1868 if (end > 0) { bytes_to_read -= bytes_read; }
1869
1870 for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
1871 if (*cp == '\n' && last_char_was_cr) {
1872 has_crs = 1;
1873 break;
1874 }
1875
1876 last_char_was_cr = *cp == '\r' ? 1 : 0;
1877 }
1878 }
1879
1880 if (has_crs) {
1881 int fd;
1882 char *stripped_content_file;
1883 char *tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL);
1884
1885 if (tempfile == NULL) {
1886 adios (NULL, "unable to create temporary file in %s",
1887 get_temp_dir());
1888 }
1889 stripped_content_file = add (tempfile, NULL);
1890
1891 /* Strip each CR before a LF from the content. */
1892 fseeko (*fp, begin, SEEK_SET);
1893 while ((bytes_read = fread (buffer, 1, sizeof buffer, *fp)) >
1894 0) {
1895 char *cp;
1896 size_t i;
1897 int last_char_was_cr = 0;
1898
1899 for (i = 0, cp = buffer; i < bytes_read; ++i, ++cp) {
1900 if (*cp == '\r') {
1901 last_char_was_cr = 1;
1902 } else if (last_char_was_cr) {
1903 if (*cp != '\n') {
1904 if (write (fd, "\r", 1) < 0) {
1905 advise (tempfile, "CR write");
1906 }
1907 }
1908 if (write (fd, cp, 1) < 0) {
1909 advise (tempfile, "write");
1910 }
1911 last_char_was_cr = 0;
1912 } else {
1913 if (write (fd, cp, 1) < 0) {
1914 advise (tempfile, "write");
1915 }
1916 last_char_was_cr = 0;
1917 }
1918 }
1919 }
1920
1921 if (close (fd)) {
1922 admonish (NULL, "unable to write temporary file %s",
1923 stripped_content_file);
1924 (void) m_unlink (stripped_content_file);
1925 status = NOTOK;
1926 } else {
1927 /* Replace the decoded file with the converted one. */
1928 if (ct->c_cefile.ce_file) {
1929 if (ct->c_cefile.ce_unlink) {
1930 (void) m_unlink (ct->c_cefile.ce_file);
1931 }
1932 free (ct->c_cefile.ce_file);
1933 }
1934 ct->c_cefile.ce_file = stripped_content_file;
1935 ct->c_cefile.ce_unlink = 1;
1936
1937 ++*message_mods;
1938 if (verbosw) {
1939 report (NULL, ct->c_partno,
1940 begin == 0 && end == 0 ? "" : *file,
1941 "stripped CRs");
1942 }
1943 }
1944 }
1945
1946 if (opened_input_file) {
1947 fclose (*fp);
1948 *fp = NULL;
1949 }
1950 }
1951 }
1952
1953 free (charset);
1954
1955 return status;
1956 }
1957
1958
1959 static int
1960 convert_charsets (CT ct, char *dest_charset, int *message_mods) {
1961 int status = OK;
1962
1963 switch (ct->c_type) {
1964 case CT_TEXT:
1965 if (ct->c_subtype == TEXT_PLAIN) {
1966 status = convert_charset (ct, dest_charset, message_mods);
1967 if (status == OK) {
1968 if (verbosw) {
1969 char *ct_charset = content_charset (ct);
1970
1971 report (NULL, ct->c_partno, ct->c_file,
1972 "convert %s to %s", ct_charset, dest_charset);
1973 free (ct_charset);
1974 }
1975 } else {
1976 char *ct_charset = content_charset (ct);
1977
1978 report ("iconv", ct->c_partno, ct->c_file,
1979 "failed to convert %s to %s", ct_charset, dest_charset);
1980 free (ct_charset);
1981 }
1982 }
1983 break;
1984
1985 case CT_MULTIPART: {
1986 struct multipart *m = (struct multipart *) ct->c_ctparams;
1987 struct part *part;
1988
1989 /* Should check to see if the body for this part is encoded?
1990 For now, it gets passed along as-is by InitMultiPart(). */
1991 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
1992 status =
1993 convert_charsets (part->mp_part, dest_charset, message_mods);
1994 }
1995 break;
1996 }
1997
1998 case CT_MESSAGE:
1999 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2000 struct exbody *e = (struct exbody *) ct->c_ctparams;
2001
2002 status =
2003 convert_charsets (e->eb_content, dest_charset, message_mods);
2004 }
2005 break;
2006
2007 default:
2008 break;
2009 }
2010
2011 return status;
2012 }
2013
2014
2015 /*
2016 * Fix various problems that aren't handled elsewhere. These
2017 * are fixed unconditionally: there are no switches to disable
2018 * them. (Currently, "problems" is just one: an extraneous
2019 * semicolon at the end of a header parameter list.)
2020 */
2021 static int
2022 fix_always (CT ct, int *message_mods) {
2023 int status = OK;
2024
2025 switch (ct->c_type) {
2026 case CT_MULTIPART: {
2027 struct multipart *m = (struct multipart *) ct->c_ctparams;
2028 struct part *part;
2029
2030 for (part = m->mp_parts; status == OK && part; part = part->mp_next) {
2031 status = fix_always (part->mp_part, message_mods);
2032 }
2033 break;
2034 }
2035
2036 case CT_MESSAGE:
2037 if (ct->c_subtype == MESSAGE_EXTERNAL) {
2038 struct exbody *e = (struct exbody *) ct->c_ctparams;
2039
2040 status = fix_always (e->eb_content, message_mods);
2041 }
2042 break;
2043
2044 default: {
2045 HF hf;
2046
2047 for (hf = ct->c_first_hf; hf; hf = hf->next) {
2048 size_t len = strlen (hf->value);
2049
2050 if (strcasecmp (hf->name, TYPE_FIELD) != 0 &&
2051 strcasecmp (hf->name, DISPO_FIELD) != 0) {
2052 /* Only do this for Content-Type and
2053 Content-Disposition fields because those are the
2054 only headers that parse_mime() warns about. */
2055 continue;
2056 }
2057
2058 /* whitespace following a trailing ';' will be nuked as well */
2059 if (hf->value[len - 1] == '\n') {
2060 while (isspace((unsigned char)(hf->value[len - 2]))) {
2061 if (len-- == 0) { break; }
2062 }
2063 }
2064
2065 if (hf->value[len - 2] == ';') {
2066 /* Remove trailing ';' from parameter value. */
2067 hf->value[len - 2] = '\n';
2068 hf->value[len - 1] = '\0';
2069
2070 /* Also, if Content-Type parameter, remove trailing ';'
2071 from ct->c_ctline. This probably isn't necessary
2072 but can't hurt. */
2073 if (strcasecmp(hf->name, TYPE_FIELD) == 0 && ct->c_ctline) {
2074 size_t l = strlen(ct->c_ctline) - 1;
2075 while (isspace((unsigned char)(ct->c_ctline[l])) ||
2076 ct->c_ctline[l] == ';') {
2077 ct->c_ctline[l--] = '\0';
2078 if (l == 0) { break; }
2079 }
2080 }
2081
2082 ++*message_mods;
2083 if (verbosw) {
2084 report (NULL, ct->c_partno, ct->c_file,
2085 "remove trailing ; from %s parameter value",
2086 hf->name);
2087 }
2088 }
2089 }
2090 }}
2091
2092 return status;
2093 }
2094
2095
2096 static int
2097 write_content (CT ct, char *input_filename, char *outfile, int modify_inplace,
2098 int message_mods) {
2099 int status = OK;
2100
2101 if (modify_inplace) {
2102 if (message_mods > 0) {
2103 if ((status = output_message (ct, outfile)) == OK) {
2104 char *infile = input_filename
2105 ? add (input_filename, NULL)
2106 : add (ct->c_file ? ct->c_file : "-", NULL);
2107
2108 if (remove_file (infile) == OK) {
2109 if (rename (outfile, infile)) {
2110 /* Rename didn't work, possibly because of an
2111 attempt to rename across filesystems. Try
2112 brute force copy. */
2113 int old = open (outfile, O_RDONLY);
2114 int new =
2115 open (infile, O_WRONLY | O_CREAT, m_gmprot ());
2116 int i = -1;
2117
2118 if (old != -1 && new != -1) {
2119 char buffer[BUFSIZ];
2120
2121 while ((i = read (old, buffer, sizeof buffer)) >
2122 0) {
2123 if (write (new, buffer, i) != i) {
2124 i = -1;
2125 break;
2126 }
2127 }
2128 }
2129 if (new != -1) { close (new); }
2130 if (old != -1) { close (old); }
2131 (void) m_unlink (outfile);
2132
2133 if (i < 0) {
2134 /* The -file argument processing used path() to
2135 expand filename to absolute path. */
2136 int file = ct->c_file && ct->c_file[0] == '/';
2137
2138 admonish (NULL, "unable to rename %s %s to %s",
2139 file ? "file" : "message", outfile,
2140 infile);
2141 status = NOTOK;
2142 }
2143 }
2144 } else {
2145 admonish (NULL, "unable to remove input file %s, "
2146 "not modifying it", infile);
2147 (void) m_unlink (outfile);
2148 status = NOTOK;
2149 }
2150
2151 free (infile);
2152 } else {
2153 status = NOTOK;
2154 }
2155 } else {
2156 /* No modifications and didn't need the tmp outfile. */
2157 (void) m_unlink (outfile);
2158 }
2159 } else {
2160 /* Output is going to some file. Produce it whether or not
2161 there were modifications. */
2162 status = output_message (ct, outfile);
2163 }
2164
2165 flush_errors ();
2166 return status;
2167 }
2168
2169
2170 /*
2171 * If "rmmproc" is defined, call that to remove the file. Otherwise,
2172 * use the standard MH backup file.
2173 */
2174 static int
2175 remove_file (char *file) {
2176 if (rmmproc) {
2177 char *rmm_command = concat (rmmproc, " ", file, NULL);
2178 int status = system (rmm_command);
2179
2180 free (rmm_command);
2181 return WIFEXITED (status) ? WEXITSTATUS (status) : NOTOK;
2182 } else {
2183 /* This is OK for a non-message file, it still uses the
2184 BACKUP_PREFIX form. The backup file will be in the same
2185 directory as file. */
2186 return rename (file, m_backup (file));
2187 }
2188 }
2189
2190
2191 static void
2192 report (char *what, char *partno, char *filename, char *message, ...) {
2193 va_list args;
2194 char *fmt;
2195
2196 if (verbosw) {
2197 va_start (args, message);
2198 fmt = concat (filename, partno ? " part " : ", ",
2199 partno ? partno : "", partno ? ", " : "", message, NULL);
2200
2201 advertise (what, NULL, fmt, args);
2202
2203 free (fmt);
2204 va_end (args);
2205 }
2206 }
2207
2208
2209 static void
2210 pipeser (int i)
2211 {
2212 if (i == SIGQUIT) {
2213 fflush (stdout);
2214 fprintf (stderr, "\n");
2215 fflush (stderr);
2216 }
2217
2218 done (1);
2219 /* NOTREACHED */
2220 }