]> diplodocus.org Git - nmh/blob - uip/forw.c
864622163138ff6750f287ad535b0d243b623d85
[nmh] / uip / forw.c
1 /* forw.c -- forward a message, or group of 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 "sbr/m_gmprot.h"
10 #include "sbr/getarguments.h"
11 #include "sbr/read_switch_multiword.h"
12 #include "sbr/seq_setprev.h"
13 #include "sbr/seq_setcur.h"
14 #include "sbr/seq_save.h"
15 #include "sbr/showfile.h"
16 #include "sbr/smatch.h"
17 #include "sbr/refile.h"
18 #include "sbr/cpydata.h"
19 #include "sbr/cpydgst.h"
20 #include "sbr/m_draft.h"
21 #include "sbr/m_convert.h"
22 #include "sbr/getfolder.h"
23 #include "sbr/folder_read.h"
24 #include "sbr/context_save.h"
25 #include "sbr/context_replace.h"
26 #include "sbr/context_find.h"
27 #include "sbr/ambigsw.h"
28 #include "sbr/pidstatus.h"
29 #include "sbr/path.h"
30 #include "sbr/print_version.h"
31 #include "sbr/print_help.h"
32 #include "sbr/arglist.h"
33 #include "sbr/error.h"
34 #include <fcntl.h>
35 #include "h/tws.h"
36 #include "h/done.h"
37 #include "h/utils.h"
38 #include "sbr/m_maildir.h"
39 #include "forwsbr.h"
40
41
42 #define IFORMAT "digest-issue-%s"
43 #define VFORMAT "digest-volume-%s"
44
45 #define FORW_SWITCHES \
46 X("annotate", 0, ANNOSW) \
47 X("noannotate", 0, NANNOSW) \
48 X("draftfolder +folder", 0, DFOLDSW) \
49 X("draftmessage msg", 0, DMSGSW) \
50 X("nodraftfolder", 0, NDFLDSW) \
51 X("editor editor", 0, EDITRSW) \
52 X("noedit", 0, NEDITSW) \
53 X("filter filterfile", 0, FILTSW) \
54 X("form formfile", 0, FORMSW) \
55 X("format", 5, FRMTSW) \
56 X("noformat", 7, NFRMTSW) \
57 X("inplace", 0, INPLSW) \
58 X("noinplace", 0, NINPLSW) \
59 X("mime", 0, MIMESW) \
60 X("nomime", 0, NMIMESW) \
61 X("digest list", 0, DGSTSW) \
62 X("issue number", 0, ISSUESW) \
63 X("volume number", 0, VOLUMSW) \
64 X("whatnowproc program", 0, WHATSW) \
65 X("nowhatnowproc", 0, NWHATSW) \
66 X("dashstuffing", 0, BITSTUFFSW) /* interface to mhl */ \
67 X("nodashstuffing", 0, NBITSTUFFSW) \
68 X("version", 0, VERSIONSW) \
69 X("help", 0, HELPSW) \
70 X("file file", 4, FILESW) \
71 X("build", 5, BILDSW) /* interface from mhe */ \
72 X("from address", 0, FROMSW) \
73 X("to address", 0, TOSW) \
74 X("cc address", 0, CCSW) \
75 X("subject text", 0, SUBJECTSW) \
76 X("fcc mailbox", 0, FCCSW) \
77 X("width columns", 0, WIDTHSW) \
78
79 #define X(sw, minchars, id) id,
80 DEFINE_SWITCH_ENUM(FORW);
81 #undef X
82
83 #define X(sw, minchars, id) { sw, minchars, id },
84 DEFINE_SWITCH_ARRAY(FORW, switches);
85 #undef X
86
87 #define DISPO_SWITCHES \
88 X("quit", 0, NOSW) \
89 X("replace", 0, YESW) \
90 X("list", 0, LISTDSW) \
91 X("refile +folder", 0, REFILSW) \
92 X("new", 0, NEWSW) \
93
94 #define X(sw, minchars, id) id,
95 DEFINE_SWITCH_ENUM(DISPO);
96 #undef X
97
98 #define X(sw, minchars, id) { sw, minchars, id },
99 DEFINE_SWITCH_ARRAY(DISPO, aqrnl);
100 #undef X
101
102 static struct swit aqrl[] = {
103 { "quit", 0, NOSW },
104 { "replace", 0, YESW },
105 { "list", 0, LISTDSW },
106 { "refile +folder", 0, REFILSW },
107 { NULL, 0, 0 }
108 };
109
110 static char drft[BUFSIZ];
111
112 static char delim3[] =
113 "\n------------------------------------------------------------\n\n";
114 static char delim4[] = "\n------------------------------\n\n";
115
116
117 static struct msgs *mp = NULL; /* used a lot */
118
119
120 /*
121 * static prototypes
122 */
123 static void mhl_draft (int, char *, int, int, char *, char *, int);
124 static void copy_draft (int, char *, char *, int, int, int);
125 static void copy_mime_draft (int);
126
127
128 int
129 main (int argc, char **argv)
130 {
131 bool anot = false;
132 bool inplace = true;
133 bool mime = false;
134 int issue = 0, volume = 0, dashstuff = 0;
135 bool nedit = false;
136 bool nwhat = false;
137 int i, in;
138 int out, isdf = 0, msgnum = 0;
139 int outputlinelen = OUTPUTLINELEN;
140 int dat[5];
141 char *cp, *cwd, *maildir, *dfolder = NULL;
142 char *dmsg = NULL, *digest = NULL, *ed = NULL;
143 char *file = NULL, *filter = NULL, *folder = NULL, *fwdmsg = NULL;
144 char *from = NULL, *to = NULL, *cc = NULL, *subject = NULL, *fcc = NULL;
145 char *form = NULL, buf[BUFSIZ];
146 char **argp, **arguments;
147 struct stat st;
148 struct msgs_array msgs = { 0, 0, NULL };
149 bool buildsw = false;
150
151 if (nmh_init(argv[0], true, true)) { return 1; }
152
153 arguments = getarguments (invo_name, argc, argv, 1);
154 argp = arguments;
155
156 while ((cp = *argp++)) {
157 if (*cp == '-') {
158 switch (smatch (++cp, switches)) {
159 case AMBIGSW:
160 ambigsw (cp, switches);
161 done (1);
162 case UNKWNSW:
163 die("-%s unknown", cp);
164
165 case HELPSW:
166 snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
167 invo_name);
168 print_help (buf, switches, 1);
169 done (0);
170 case VERSIONSW:
171 print_version(invo_name);
172 done (0);
173
174 case ANNOSW:
175 anot = true;
176 continue;
177 case NANNOSW:
178 anot = false;
179 continue;
180
181 case EDITRSW:
182 if (!(ed = *argp++) || *ed == '-')
183 die("missing argument to %s", argp[-2]);
184 nedit = false;
185 continue;
186 case NEDITSW:
187 nedit = true;
188 continue;
189
190 case WHATSW:
191 if (!(whatnowproc = *argp++) || *whatnowproc == '-')
192 die("missing argument to %s", argp[-2]);
193 nwhat = false;
194 continue;
195 case BILDSW:
196 buildsw = true;
197 /* FALLTHRU */
198 case NWHATSW:
199 nwhat = true;
200 continue;
201
202 case FILESW:
203 if (file)
204 die("only one file at a time!");
205 if (!(cp = *argp++) || *cp == '-')
206 die("missing argument to %s", argp[-2]);
207 file = path (cp, TFILE);
208 continue;
209 case FILTSW:
210 if (!(cp = *argp++) || *cp == '-')
211 die("missing argument to %s", argp[-2]);
212 filter = mh_xstrdup(etcpath(cp));
213 mime = false;
214 continue;
215 case FORMSW:
216 if (!(form = *argp++) || *form == '-')
217 die("missing argument to %s", argp[-2]);
218 continue;
219
220 case FRMTSW:
221 filter = mh_xstrdup(etcpath(mhlforward));
222 continue;
223 case NFRMTSW:
224 filter = NULL;
225 continue;
226
227 case INPLSW:
228 inplace = true;
229 continue;
230 case NINPLSW:
231 inplace = false;
232 continue;
233
234 case MIMESW:
235 mime = true;
236 filter = NULL;
237 continue;
238 case NMIMESW:
239 mime = false;
240 continue;
241
242 case DGSTSW:
243 if (!(cp = *argp++) || *cp == '-')
244 die("missing argument to %s", argp[-2]);
245 digest = mh_xstrdup(cp);
246 mime = false;
247 continue;
248 case ISSUESW:
249 if (!(cp = *argp++) || *cp == '-')
250 die("missing argument to %s", argp[-2]);
251 if ((issue = atoi (cp)) < 1)
252 die("bad argument %s %s", argp[-2], cp);
253 continue;
254 case VOLUMSW:
255 if (!(cp = *argp++) || *cp == '-')
256 die("missing argument to %s", argp[-2]);
257 if ((volume = atoi (cp)) < 1)
258 die("bad argument %s %s", argp[-2], cp);
259 continue;
260
261 case DFOLDSW:
262 if (dfolder)
263 die("only one draft folder at a time!");
264 if (!(cp = *argp++) || *cp == '-')
265 die("missing argument to %s", argp[-2]);
266 dfolder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp,
267 *cp != '@' ? TFOLDER : TSUBCWF);
268 continue;
269 case DMSGSW:
270 if (dmsg)
271 die("only one draft message at a time!");
272 if (!(dmsg = *argp++) || *dmsg == '-')
273 die("missing argument to %s", argp[-2]);
274 continue;
275 case NDFLDSW:
276 dfolder = NULL;
277 isdf = NOTOK;
278 continue;
279
280 case BITSTUFFSW:
281 dashstuff = 1; /* ternary logic */
282 continue;
283 case NBITSTUFFSW:
284 dashstuff = -1; /* ternary logic */
285 continue;
286
287 case FROMSW:
288 if (!(cp = *argp++) || *cp == '-')
289 die("missing argument to %s", argp[-2]);
290 from = addlist(from, cp);
291 continue;
292 case TOSW:
293 if (!(cp = *argp++) || *cp == '-')
294 die("missing argument to %s", argp[-2]);
295 to = addlist(to, cp);
296 continue;
297 case CCSW:
298 if (!(cp = *argp++) || *cp == '-')
299 die("missing argument to %s", argp[-2]);
300 cc = addlist(cc, cp);
301 continue;
302 case FCCSW:
303 if (!(cp = *argp++) || *cp == '-')
304 die("missing argument to %s", argp[-2]);
305 fcc = addlist(fcc, cp);
306 continue;
307 case SUBJECTSW:
308 if (!(cp = *argp++) || *cp == '-')
309 die("missing argument to %s", argp[-2]);
310 subject = mh_xstrdup(cp);
311 continue;
312
313 case WIDTHSW:
314 if (!(cp = *argp++) || *cp == '-')
315 die("missing argument to %s", argp[-2]);
316 if ((outputlinelen = atoi(cp)) < 10)
317 die("impossible width %d", outputlinelen);
318 continue;
319 }
320 }
321 if (*cp == '+' || *cp == '@') {
322 if (folder)
323 die("only one folder at a time!");
324 folder = pluspath (cp);
325 } else {
326 app_msgarg(&msgs, cp);
327 }
328 }
329
330 cwd = mh_xstrdup(pwd ());
331
332 if (!context_find ("path"))
333 free (path ("./", TFOLDER));
334 if (file && (msgs.size || folder))
335 die("can't mix files and folders/msgs");
336
337 try_it_again:
338
339 strncpy (drft, buildsw ? m_maildir ("draft")
340 : m_draft (dfolder, NULL, NOUSE, &isdf), sizeof(drft));
341
342 /* Check if a draft already exists */
343 if (!buildsw && stat (drft, &st) != NOTOK) {
344 printf ("Draft \"%s\" exists (%ld bytes).", drft, (long) st.st_size);
345 for (i = LISTDSW; i != YESW;) {
346 if (!(argp = read_switch_multiword ("\nDisposition? ",
347 isdf ? aqrnl : aqrl)))
348 done (1);
349 switch (i = smatch (*argp, isdf ? aqrnl : aqrl)) {
350 case NOSW:
351 done (0);
352 case NEWSW:
353 dmsg = NULL;
354 goto try_it_again;
355 case YESW:
356 break;
357 case LISTDSW:
358 showfile (++argp, drft);
359 break;
360 case REFILSW:
361 if (refile (++argp, drft) == 0)
362 i = YESW;
363 break;
364 default:
365 inform("say what?");
366 break;
367 }
368 }
369 }
370
371 if (file) {
372 /*
373 * Forwarding a file.
374 */
375 anot = false; /* don't want to annotate a file */
376 } else {
377 /*
378 * Forwarding a message.
379 */
380 if (!msgs.size)
381 app_msgarg(&msgs, "cur");
382 if (!folder)
383 folder = getfolder (1);
384 maildir = m_maildir (folder);
385
386 if (chdir (maildir) == NOTOK)
387 adios (maildir, "unable to change directory to");
388
389 /* read folder and create message structure */
390 if (!(mp = folder_read (folder, 1)))
391 die("unable to read folder %s", folder);
392
393 /* check for empty folder */
394 if (mp->nummsg == 0)
395 die("no messages in %s", folder);
396
397 /* parse all the message ranges/sequences and set SELECTED */
398 for (msgnum = 0; msgnum < msgs.size; msgnum++)
399 if (!m_convert (mp, msgs.msgs[msgnum]))
400 done (1);
401
402 seq_setprev (mp); /* set the previous sequence */
403
404 /*
405 * Find the first message in our set and use it as the input
406 * for the component scanner
407 */
408
409 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
410 if (is_selected (mp, msgnum)) {
411 fwdmsg = mh_xstrdup(m_name(msgnum));
412 break;
413 }
414
415 if (! fwdmsg)
416 die("Unable to find input message");
417 }
418
419 if (filter && access (filter, R_OK) == NOTOK)
420 adios (filter, "unable to read");
421
422 /*
423 * Open form (component) file.
424 */
425 if (digest) {
426 if (issue == 0) {
427 snprintf (buf, sizeof(buf), IFORMAT, digest);
428 if (volume == 0
429 && (cp = context_find (buf))
430 && ((issue = atoi (cp)) < 0))
431 issue = 0;
432 issue++;
433 }
434 if (volume == 0) {
435 snprintf (buf, sizeof(buf), VFORMAT, digest);
436 if ((cp = context_find (buf)) == NULL || (volume = atoi (cp)) <= 0)
437 volume = 1;
438 }
439 if (!form)
440 form = digestcomps;
441 } else {
442 if (!form)
443 form = forwcomps;
444 }
445
446 dat[0] = digest ? issue : msgnum;
447 dat[1] = volume;
448 dat[2] = 0;
449 dat[3] = outputlinelen;
450 dat[4] = 0;
451
452
453 in = build_form (form, digest, dat, from, to, cc, fcc, subject,
454 file ? file : fwdmsg);
455
456 if ((out = creat (drft, m_gmprot ())) == NOTOK)
457 adios (drft, "unable to create");
458
459 /*
460 * copy the components into the draft
461 */
462 cpydata (in, out, form, drft);
463 close (in);
464
465 if (file) {
466 /* just copy the file into the draft */
467 if ((in = open (file, O_RDONLY)) == NOTOK)
468 adios (file, "unable to open");
469 cpydata (in, out, file, drft);
470 close (in);
471 close (out);
472 } else {
473 /*
474 * If filter file is defined, then format the
475 * messages into the draft using mhlproc.
476 */
477 if (filter)
478 mhl_draft (out, digest, volume, issue, drft, filter, dashstuff);
479 else if (mime)
480 copy_mime_draft (out);
481 else
482 copy_draft (out, digest, drft, volume, issue, dashstuff);
483 close (out);
484
485 if (digest) {
486 snprintf (buf, sizeof(buf), IFORMAT, digest);
487 context_replace (buf, mh_xstrdup(m_str(issue)));
488 snprintf (buf, sizeof(buf), VFORMAT, digest);
489 context_replace (buf, mh_xstrdup(m_str(volume)));
490 }
491
492 context_replace (pfolder, folder); /* update current folder */
493 seq_setcur (mp, mp->lowsel); /* update current message */
494 seq_save (mp); /* synchronize sequences */
495 context_save (); /* save the context file */
496 }
497
498 if (nwhat)
499 done (0);
500 what_now (ed, nedit, NOUSE, drft, NULL, 0, mp,
501 anot ? "Forwarded" : NULL, inplace, cwd, 0);
502 done (1);
503 return 1;
504 }
505
506
507 /*
508 * Filter the messages you are forwarding, into the
509 * draft calling the mhlproc, and reading its output
510 * from a pipe.
511 */
512
513 static void
514 mhl_draft (int out, char *digest, int volume, int issue,
515 char *file, char *filter, int dashstuff)
516 {
517 pid_t child_id;
518 int msgnum, pd[2];
519 char buf1[BUFSIZ];
520 char buf2[BUFSIZ];
521 char *program;
522 struct msgs_array vec = { 0, 0, NULL };
523
524 if (pipe (pd) == NOTOK)
525 adios ("pipe", "unable to create");
526
527 argsplit_msgarg(&vec, mhlproc, &program);
528
529 child_id = fork();
530 switch (child_id) {
531 case NOTOK:
532 adios ("fork", "unable to");
533
534 case OK:
535 close (pd[0]);
536 dup2 (pd[1], 1);
537 close (pd[1]);
538
539 app_msgarg(&vec, "-forwall");
540 app_msgarg(&vec, "-form");
541 app_msgarg(&vec, filter);
542
543 if (digest) {
544 app_msgarg(&vec, "-digest");
545 app_msgarg(&vec, digest);
546 app_msgarg(&vec, "-issue");
547 snprintf (buf1, sizeof(buf1), "%d", issue);
548 app_msgarg(&vec, buf1);
549 app_msgarg(&vec, "-volume");
550 snprintf (buf2, sizeof(buf2), "%d", volume);
551 app_msgarg(&vec, buf2);
552 }
553
554 /*
555 * Are we dashstuffing (quoting) the lines that begin
556 * with `-'. We use the mhl default (don't add any flag)
557 * unless the user has specified a specific flag.
558 */
559 if (dashstuff > 0)
560 app_msgarg(&vec, "-dashstuffing");
561 else if (dashstuff < 0)
562 app_msgarg(&vec, "-nodashstuffing");
563
564 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
565 if (is_selected (mp, msgnum))
566 app_msgarg(&vec, mh_xstrdup(m_name (msgnum)));
567 }
568
569 app_msgarg(&vec, NULL);
570
571 execvp (program, vec.msgs);
572 fprintf (stderr, "unable to exec ");
573 perror (mhlproc);
574 _exit(1);
575
576 default:
577 close (pd[1]);
578 cpydata (pd[0], out, vec.msgs[0], file);
579 close (pd[0]);
580 pidXwait(child_id, mhlproc);
581 break;
582 }
583 }
584
585
586 /*
587 * Copy the messages into the draft. The messages are
588 * not filtered through the mhlproc. Do dashstuffing if
589 * necessary.
590 */
591
592 static void
593 copy_draft (int out, char *digest, char *file, int volume, int issue, int dashstuff)
594 {
595 int fd,i, msgcnt, msgnum;
596 int len, buflen;
597 char *bp, *msgnam;
598 char buffer[BUFSIZ];
599
600 msgcnt = 1;
601 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
602 if (is_selected (mp, msgnum)) {
603 if (digest) {
604 strncpy (buffer, msgnum == mp->lowsel ? delim3 : delim4,
605 sizeof(buffer));
606 } else {
607 /* Get buffer ready to go */
608 bp = buffer;
609 buflen = sizeof(buffer);
610
611 strncpy (bp, "\n-------", buflen);
612 len = strlen (bp);
613 bp += len;
614 buflen -= len;
615
616 if (msgnum == mp->lowsel) {
617 snprintf (bp, buflen, " Forwarded Message%s",
618 PLURALS(mp->numsel));
619 } else {
620 snprintf (bp, buflen, " Message %d", msgcnt);
621 }
622 len = strlen (bp);
623 bp += len;
624 buflen -= len;
625
626 strncpy (bp, "\n\n", buflen);
627 }
628 if (write (out, buffer, strlen (buffer)) < 0) {
629 advise (drft, "write");
630 }
631
632 if ((fd = open (msgnam = m_name (msgnum), O_RDONLY)) == NOTOK) {
633 admonish (msgnam, "unable to read message");
634 continue;
635 }
636
637 /*
638 * Copy the message. Add RFC934 quoting (dashstuffing)
639 * unless given the -nodashstuffing flag.
640 */
641 if (dashstuff >= 0)
642 cpydgst (fd, out, msgnam, file);
643 else
644 cpydata (fd, out, msgnam, file);
645
646 close (fd);
647 msgcnt++;
648 }
649 }
650
651 if (digest) {
652 strncpy (buffer, delim4, sizeof(buffer));
653 } else {
654 snprintf (buffer, sizeof(buffer), "\n------- End of Forwarded Message%s\n",
655 PLURALS(mp->numsel));
656 }
657 if (write (out, buffer, strlen (buffer)) < 0) {
658 advise (drft, "write");
659 }
660
661 if (digest) {
662 snprintf (buffer, sizeof(buffer), "End of %s Digest [Volume %d Issue %d]\n",
663 digest, volume, issue);
664 i = strlen (buffer);
665 for (bp = buffer + i; i > 1; i--)
666 *bp++ = '*';
667 *bp++ = '\n';
668 *bp = 0;
669 if (write (out, buffer, strlen (buffer)) < 0) {
670 advise (drft, "write");
671 }
672 }
673 }
674
675
676 /*
677 * Create a mhn composition file for forwarding message.
678 */
679
680 static void
681 copy_mime_draft (int out)
682 {
683 int msgnum;
684 char buffer[BUFSIZ];
685
686 snprintf (buffer, sizeof(buffer), "#forw [forwarded message%s] +%s",
687 PLURALS(mp->numsel), mp->foldpath);
688 if (write (out, buffer, strlen (buffer)) < 0) {
689 advise (drft, "write");
690 }
691 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
692 if (is_selected (mp, msgnum)) {
693 snprintf (buffer, sizeof(buffer), " %s", m_name (msgnum));
694 if (write (out, buffer, strlen (buffer)) < 0) {
695 advise (drft, "write");
696 }
697 }
698 if (write (out, "\n", 1) < 0) {
699 advise (drft, "write newline");
700 }
701 }