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