Removed the draft message in favor for a consistent draft folder facility
[mmh] / uip / forw.c
1 /*
2  * forw.c -- forward a message, or group of messages.
3  *
4  * This code is Copyright (c) 2002, by the authors of nmh.  See the
5  * COPYRIGHT file in the root directory of the nmh distribution for
6  * complete copyright information.
7  */
8
9 #include <h/mh.h>
10 #include <fcntl.h>
11 #include <h/fmt_scan.h>
12 #include <h/tws.h>
13 #include <h/utils.h>
14
15
16 #define IFORMAT  "digest-issue-%s"
17 #define VFORMAT  "digest-volume-%s"
18
19 static struct swit switches[] = {
20 #define ANNOSW  0
21         { "annotate", 0 },
22 #define NANNOSW  1
23         { "noannotate", 0 },
24 #define EDITRSW  2
25         { "editor editor", 0 },
26 #define NEDITSW  3
27         { "noedit", 0 },
28 #define FILTSW  4
29         { "filter filterfile", 0 },
30 #define FORMSW  5
31         { "form formfile", 0 },
32 #define FRMTSW  6
33         { "format", 5 },
34 #define NFRMTSW  7
35         { "noformat", 7 },
36 #define INPLSW  8
37         { "inplace", 0 },
38 #define NINPLSW  9
39         { "noinplace", 0 },
40 #define MIMESW  10
41         { "mime", 0 },
42 #define NMIMESW  11
43         { "nomime", 0 },
44 #define DGSTSW  12
45         { "digest list", 0 },
46 #define ISSUESW  13
47         { "issue number", 0 },
48 #define VOLUMSW  14
49         { "volume number", 0 },
50 #define WHATSW  15
51         { "whatnowproc program", 0 },
52 #define NWHATSW  16
53         { "nowhatnowproc", 0 },
54 #define BITSTUFFSW  17
55         { "dashstuffing", 0 },  /* interface to mhl */
56 #define NBITSTUFFSW  18
57         { "nodashstuffing", 0 },
58 #define VERSIONSW  19
59         { "version", 0 },
60 #define HELPSW  20
61         { "help", 0 },
62 #define FILESW  21
63         { "file file", 4 },  /* interface from msh */
64
65 #ifdef MHE
66 #define BILDSW  22
67         { "build", 5 },  /* interface from mhe */
68 #endif /* MHE */
69
70         { NULL, 0 }
71 };
72
73 static char drft[BUFSIZ];
74
75 static char delim3[] =
76         "\n------------------------------------------------------------\n\n";
77 static char delim4[] = "\n------------------------------\n\n";
78
79
80 static struct msgs *mp = NULL;  /* used a lot */
81
82
83 /*
84  * static prototypes
85  */
86 static void mhl_draft (int, char *, int, int, char *, char *, int);
87 static void copy_draft (int, char *, char *, int, int, int);
88 static void copy_mime_draft (int);
89 static int build_form (char *, char *, int, int);
90
91
92 int
93 main (int argc, char **argv)
94 {
95         int msgp = 0, anot = 0, inplace = 1, mime = 0;
96         int issue = 0, volume = 0, dashstuff = 0;
97         int nedit = 0, nwhat = 0, in;
98         int out, msgnum;
99         char *cp, *cwd, *maildir;
100         char *digest = NULL, *ed = NULL;
101         char *file = NULL, *filter = NULL, *folder = NULL;
102         char *form = NULL, buf[BUFSIZ], value[10];
103         char **argp, **arguments, *msgs[MAXARGS];
104
105 #ifdef MHE
106         int buildsw = 0;
107 #endif /* MHE */
108
109 #ifdef LOCALE
110         setlocale(LC_ALL, "");
111 #endif
112         invo_name = r1bindex (argv[0], '/');
113
114         /* read user profile/context */
115         context_read();
116
117         arguments = getarguments (invo_name, argc, argv, 1);
118         argp = arguments;
119
120         while ((cp = *argp++)) {
121                 if (*cp == '-') {
122                         switch (smatch (++cp, switches)) {
123                                 case AMBIGSW:
124                                         ambigsw (cp, switches);
125                                         done (1);
126                                 case UNKWNSW:
127                                         adios (NULL, "-%s unknown", cp);
128
129                                 case HELPSW:
130                                         snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
131                                                 invo_name);
132                                         print_help (buf, switches, 1);
133                                         done (1);
134                                 case VERSIONSW:
135                                         print_version(invo_name);
136                                         done (1);
137
138                                 case ANNOSW:
139                                         anot++;
140                                         continue;
141                                 case NANNOSW:
142                                         anot = 0;
143                                         continue;
144
145                                 case EDITRSW:
146                                         if (!(ed = *argp++) || *ed == '-')
147                                                 adios (NULL, "missing argument to %s", argp[-2]);
148                                         nedit = 0;
149                                         continue;
150                                 case NEDITSW:
151                                         nedit++;
152                                         continue;
153
154                                 case WHATSW:
155                                         if (!(whatnowproc = *argp++) || *whatnowproc == '-')
156                                                 adios (NULL, "missing argument to %s", argp[-2]);
157                                         nwhat = 0;
158                                         continue;
159 #ifdef MHE
160                                 case BILDSW:
161                                         buildsw++;  /* fall... */
162 #endif /* MHE */
163                                 case NWHATSW:
164                                         nwhat++;
165                                         continue;
166
167                                 case FILESW:
168                                         if (file)
169                                                 adios (NULL, "only one file at a time!");
170                                         if (!(cp = *argp++) || *cp == '-')
171                                                 adios (NULL, "missing argument to %s", argp[-2]);
172                                         file = path (cp, TFILE);
173                                         continue;
174                                 case FILTSW:
175                                         if (!(cp = *argp++) || *cp == '-')
176                                                 adios (NULL, "missing argument to %s", argp[-2]);
177                                         filter = getcpy (etcpath (cp));
178                                         mime = 0;
179                                         continue;
180                                 case FORMSW:
181                                         if (!(form = *argp++) || *form == '-')
182                                                 adios (NULL, "missing argument to %s", argp[-2]);
183                                         continue;
184
185                                 case FRMTSW:
186                                         filter = getcpy (etcpath (mhlforward));
187                                         continue;
188                                 case NFRMTSW:
189                                         filter = NULL;
190                                         continue;
191
192                                 case INPLSW:
193                                         inplace++;
194                                         continue;
195                                 case NINPLSW:
196                                         inplace = 0;
197                                         continue;
198
199                                 case MIMESW:
200                                         mime++;
201                                         filter = NULL;
202                                         continue;
203                                 case NMIMESW:
204                                         mime = 0;
205                                         continue;
206
207                                 case DGSTSW:
208                                         if (!(digest = *argp++) || *digest == '-')
209                                                 adios (NULL, "missing argument to %s", argp[-2]);
210                                         mime = 0;
211                                         continue;
212                                 case ISSUESW:
213                                         if (!(cp = *argp++) || *cp == '-')
214                                                 adios (NULL, "missing argument to %s", argp[-2]);
215                                         if ((issue = atoi (cp)) < 1)
216                                                 adios (NULL, "bad argument %s %s", argp[-2], cp);
217                                         continue;
218                                 case VOLUMSW:
219                                         if (!(cp = *argp++) || *cp == '-')
220                                                 adios (NULL, "missing argument to %s", argp[-2]);
221                                         if ((volume = atoi (cp)) < 1)
222                                                 adios (NULL, "bad argument %s %s", argp[-2], cp);
223                                         continue;
224
225                                 case BITSTUFFSW:
226                                         dashstuff = 1;  /* trinary logic */
227                                         continue;
228                                 case NBITSTUFFSW:
229                                         dashstuff = -1;  /* trinary logic */
230                                         continue;
231                         }
232                 }
233                 if (*cp == '+' || *cp == '@') {
234                         if (folder)
235                                 adios (NULL, "only one folder at a time!");
236                         else
237                                 folder = pluspath (cp);
238                 } else {
239                         msgs[msgp++] = cp;
240                 }
241         }
242
243         cwd = getcpy (pwd ());
244
245         if (!context_find ("path"))
246                 free (path ("./", TFOLDER));
247         if (file && (msgp || folder))
248                 adios (NULL, "can't mix files and folders/msgs");
249
250 #ifdef MHE
251         strncpy (drft, buildsw ? m_maildir ("draft")
252                 : m_draft("new"), sizeof(drft));
253 #else
254         strncpy (drft, m_draft("new"), sizeof(drft));
255 #endif /* MHE */
256
257         if (file) {
258                 /*
259                  * Forwarding a file.
260                  */
261                 anot = 0;  /* don't want to annotate a file */
262         } else {
263                 /*
264                  * Forwarding a message.
265                  */
266                 if (!msgp)
267                         msgs[msgp++] = "cur";
268                 if (!folder)
269                         folder = getfolder (1);
270                 maildir = m_maildir (folder);
271
272                 if (chdir (maildir) == NOTOK)
273                         adios (maildir, "unable to change directory to");
274
275                 /* read folder and create message structure */
276                 if (!(mp = folder_read (folder)))
277                         adios (NULL, "unable to read folder %s", folder);
278
279                 /* check for empty folder */
280                 if (mp->nummsg == 0)
281                         adios (NULL, "no messages in %s", folder);
282
283                 /* parse all the message ranges/sequences and set SELECTED */
284                 for (msgnum = 0; msgnum < msgp; msgnum++)
285                         if (!m_convert (mp, msgs[msgnum]))
286                                 done (1);
287                 seq_setprev (mp);  /* set the previous sequence */
288         }
289
290         if (filter && access (filter, R_OK) == NOTOK)
291                 adios (filter, "unable to read");
292
293         /*
294          * Open form (component) file.
295          */
296         if (digest) {
297                 if (issue == 0) {
298                         snprintf (buf, sizeof(buf), IFORMAT, digest);
299                         if (volume == 0
300                                         && (cp = context_find (buf))
301                                         && ((issue = atoi (cp)) < 0))
302                                 issue = 0;
303                         issue++;
304                 }
305                 if (volume == 0)
306                         snprintf (buf, sizeof(buf), VFORMAT, digest);
307                 if ((cp = context_find (buf)) == NULL || (volume = atoi (cp)) <= 0)
308                         volume = 1;
309                 if (!form)
310                         form = digestcomps;
311                 in = build_form (form, digest, volume, issue);
312         } else
313                 in = open_form(&form, forwcomps);
314
315         if ((out = creat (drft, m_gmprot ())) == NOTOK)
316                 adios (drft, "unable to create");
317
318         /*
319          * copy the components into the draft
320          */
321         cpydata (in, out, form, drft);
322         close (in);
323
324         if (file) {
325                 /* just copy the file into the draft */
326                 if ((in = open (file, O_RDONLY)) == NOTOK)
327                         adios (file, "unable to open");
328                 cpydata (in, out, file, drft);
329                 close (in);
330                 close (out);
331         } else {
332                 /*
333                  * If filter file is defined, then format the
334                  * messages into the draft using mhlproc.
335                  */
336                 if (filter)
337                         mhl_draft (out, digest, volume, issue, drft, filter, dashstuff);
338                 else if (mime)
339                         copy_mime_draft (out);
340                 else
341                         copy_draft (out, digest, drft, volume, issue, dashstuff);
342                 close (out);
343
344                 if (digest) {
345                         snprintf (buf, sizeof(buf), IFORMAT, digest);
346                         snprintf (value, sizeof(value), "%d", issue);
347                         context_replace (buf, getcpy (value));
348                         snprintf (buf, sizeof(buf), VFORMAT, digest);
349                         snprintf (value, sizeof(value), "%d", volume);
350                         context_replace (buf, getcpy (value));
351                 }
352
353                 context_replace (pfolder, folder);  /* update current folder */
354                 seq_setcur (mp, mp->lowsel);  /* update current message */
355                 seq_save (mp);  /* synchronize sequences */
356                 context_save ();  /* save the context file */
357         }
358
359         if (nwhat)
360                 done (0);
361         what_now (ed, nedit, NOUSE, drft, NULL, 0, mp,
362                 anot ? "Forwarded" : NULL, inplace, cwd);
363         done (1);
364         return 1;
365 }
366
367
368 /*
369  * Filter the messages you are forwarding, into the
370  * draft calling the mhlproc, and reading its output
371  * from a pipe.
372  */
373
374 static void
375 mhl_draft (int out, char *digest, int volume, int issue,
376         char *file, char *filter, int dashstuff)
377 {
378         pid_t child_id;
379         int i, msgnum, pd[2];
380         char *vec[MAXARGS];
381         char buf1[BUFSIZ];
382         char buf2[BUFSIZ];
383
384         if (pipe (pd) == NOTOK)
385                 adios ("pipe", "unable to create");
386
387         vec[0] = r1bindex (mhlproc, '/');
388
389         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
390                 sleep (5);
391         switch (child_id) {
392                 case NOTOK:
393                         adios ("fork", "unable to");
394
395                 case OK:
396                         close (pd[0]);
397                         dup2 (pd[1], 1);
398                         close (pd[1]);
399
400                         i = 1;
401                         vec[i++] = "-forwall";
402                         vec[i++] = "-form";
403                         vec[i++] = filter;
404
405                         if (digest) {
406                                 vec[i++] = "-digest";
407                                 vec[i++] = digest;
408                                 vec[i++] = "-issue";
409                                 snprintf (buf1, sizeof(buf1), "%d", issue);
410                                 vec[i++] = buf1;
411                                 vec[i++] = "-volume";
412                                 snprintf (buf2, sizeof(buf2), "%d", volume);
413                                 vec[i++] = buf2;
414                         }
415
416                         /*
417                          * Are we dashstuffing (quoting) the lines that begin
418                          * with `-'.  We use the mhl default (don't add any flag)
419                          * unless the user has specified a specific flag.
420                          */
421                         if (dashstuff > 0)
422                                 vec[i++] = "-dashstuffing";
423                         else if (dashstuff < 0)
424                                 vec[i++] = "-nodashstuffing";
425
426                         if (mp->numsel >= MAXARGS - i)
427                                 adios (NULL, "more than %d messages for %s exec",
428                                                 MAXARGS - i, vec[0]);
429
430                         /*
431                          * Now add the message names to filter.  We can only
432                          * handle about 995 messages (because vec is fixed size),
433                          * but that should be plenty.
434                          */
435                         for (msgnum = mp->lowsel; msgnum <= mp->hghsel && i < sizeof(vec) - 1;
436                                                 msgnum++)
437                                 if (is_selected (mp, msgnum))
438                                         vec[i++] = getcpy (m_name (msgnum));
439                         vec[i] = NULL;
440
441                         execvp (mhlproc, vec);
442                         fprintf (stderr, "unable to exec ");
443                         perror (mhlproc);
444                         _exit (-1);
445
446                 default:
447                         close (pd[1]);
448                         cpydata (pd[0], out, vec[0], file);
449                         close (pd[0]);
450                         pidXwait(child_id, mhlproc);
451                         break;
452         }
453 }
454
455
456 /*
457  * Copy the messages into the draft.  The messages are
458  * not filtered through the mhlproc.  Do dashstuffing if
459  * necessary.
460  */
461
462 static void
463 copy_draft (int out, char *digest, char *file, int volume, int issue, int dashstuff)
464 {
465         int fd,i, msgcnt, msgnum;
466         int len, buflen;
467         register char *bp, *msgnam;
468         char buffer[BUFSIZ];
469
470         msgcnt = 1;
471         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
472                 if (is_selected (mp, msgnum)) {
473                         if (digest) {
474                                 strncpy (buffer, msgnum == mp->lowsel ?
475                                         delim3 : delim4, sizeof(buffer));
476                         } else {
477                                 /* Get buffer ready to go */
478                                 bp = buffer;
479                                 buflen = sizeof(buffer);
480
481                                 strncpy (bp, "\n-------", buflen);
482                                 len = strlen (bp);
483                                 bp += len;
484                                 buflen -= len;
485
486                                 if (msgnum == mp->lowsel) {
487                                         snprintf (bp, buflen, " Forwarded Message%s",
488                                                 mp->numsel > 1 ? "s" : "");
489                                 } else {
490                                         snprintf (bp, buflen, " Message %d", msgcnt);
491                                 }
492                                 len = strlen (bp);
493                                 bp += len;
494                                 buflen -= len;
495
496                                 strncpy (bp, "\n\n", buflen);
497                         }
498                         write (out, buffer, strlen (buffer));
499
500                         if ((fd = open (msgnam = m_name (msgnum), O_RDONLY)) == NOTOK) {
501                                 admonish (msgnam, "unable to read message");
502                                 continue;
503                         }
504
505                         /*
506                          * Copy the message.  Add RFC934 quoting (dashstuffing)
507                          * unless given the -nodashstuffing flag.
508                          */
509                         if (dashstuff >= 0)
510                                 cpydgst (fd, out, msgnam, file);
511                         else
512                                 cpydata (fd, out, msgnam, file);
513
514                         close (fd);
515                         msgcnt++;
516                 }
517         }
518
519         if (digest) {
520                 strncpy (buffer, delim4, sizeof(buffer));
521         } else {
522                 snprintf (buffer, sizeof(buffer), "\n------- End of Forwarded Message%s\n\n",
523                                 mp->numsel > 1 ? "s" : "");
524         }
525         write (out, buffer, strlen (buffer));
526
527         if (digest) {
528                 snprintf (buffer, sizeof(buffer), "End of %s Digest [Volume %d Issue %d]\n",
529                                 digest, volume, issue);
530                 i = strlen (buffer);
531                 for (bp = buffer + i; i > 1; i--)
532                         *bp++ = '*';
533                 *bp++ = '\n';
534                 *bp = 0;
535                 write (out, buffer, strlen (buffer));
536         }
537 }
538
539
540 /*
541  * Create a mhbuild composition file for forwarding message.
542  */
543
544 static void
545 copy_mime_draft (int out)
546 {
547         int msgnum;
548         char buffer[BUFSIZ];
549
550         snprintf (buffer, sizeof(buffer), "#forw [forwarded message%s] +%s",
551                 mp->numsel == 1 ? "" : "s", mp->foldpath);
552         write (out, buffer, strlen (buffer));
553         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
554                 if (is_selected (mp, msgnum)) {
555                         snprintf (buffer, sizeof(buffer), " %s", m_name (msgnum));
556                         write (out, buffer, strlen (buffer));
557                 }
558         write (out, "\n", 1);
559 }
560
561
562 static int
563 build_form (char *form, char *digest, int volume, int issue)
564 {
565         int in;
566         int fmtsize;
567         register char *nfs;
568         char *line, tmpfil[BUFSIZ];
569         FILE *tmp;
570         register struct comp *cptr;
571         struct format *fmt;
572         int dat[5];
573         char *cp = NULL;
574
575         /* Get new format string */
576         nfs = new_fs (form, NULL, NULL);
577         fmtsize = strlen (nfs) + 256;
578
579         /* Compile format string */
580         fmt_compile (nfs, &fmt);
581
582         FINDCOMP (cptr, "digest");
583         if (cptr)
584                 cptr->c_text = digest;
585         FINDCOMP (cptr, "date");
586         if (cptr)
587                 cptr->c_text = getcpy(dtimenow (0));
588
589         dat[0] = issue;
590         dat[1] = volume;
591         dat[2] = 0;
592         dat[3] = fmtsize;
593         dat[4] = 0;
594
595         cp = m_mktemp2(NULL, invo_name, NULL, &tmp);
596         if (cp == NULL) adios("forw", "unable to create temporary file");
597         strncpy (tmpfil, cp, sizeof(tmpfil));
598         unlink (tmpfil);
599         if ((in = dup (fileno (tmp))) == NOTOK)
600                 adios ("dup", "unable to");
601
602         line = mh_xmalloc ((unsigned) fmtsize);
603         fmt_scan (fmt, line, fmtsize, dat);
604         fputs (line, tmp);
605         free (line);
606         if (fclose (tmp))
607                 adios (tmpfil, "error writing");
608
609         lseek (in, (off_t) 0, SEEK_SET);
610         return in;
611 }