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