9ad2877119e9105686f07c350952fa539fb5bfb1
[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]", invo_name);
131                                         print_help(buf, switches, 1);
132                                         done(1);
133                                 case VERSIONSW:
134                                         print_version(invo_name);
135                                         done(1);
136
137                                 case ANNOSW:
138                                         anot++;
139                                         continue;
140                                 case NANNOSW:
141                                         anot = 0;
142                                         continue;
143
144                                 case EDITRSW:
145                                         if (!(ed = *argp++) || *ed == '-')
146                                                 adios(NULL, "missing argument to %s", argp[-2]);
147                                         nedit = 0;
148                                         continue;
149                                 case NEDITSW:
150                                         nedit++;
151                                         continue;
152
153                                 case WHATSW:
154                                         if (!(whatnowproc = *argp++) || *whatnowproc == '-')
155                                                 adios(NULL, "missing argument to %s", argp[-2]);
156                                         nwhat = 0;
157                                         continue;
158 #ifdef MHE
159                                 case BILDSW:
160                                         buildsw++;  /* fall... */
161 #endif /* MHE */
162                                 case NWHATSW:
163                                         nwhat++;
164                                         continue;
165
166                                 case FILESW:
167                                         if (file)
168                                                 adios(NULL, "only one file at a time!");
169                                         if (!(cp = *argp++) || *cp == '-')
170                                                 adios(NULL, "missing argument to %s", argp[-2]);
171                                         file = path(cp, TFILE);
172                                         continue;
173                                 case FILTSW:
174                                         if (!(cp = *argp++) || *cp == '-')
175                                                 adios(NULL, "missing argument to %s", argp[-2]);
176                                         filter = getcpy(etcpath(cp));
177                                         mime = 0;
178                                         continue;
179                                 case FORMSW:
180                                         if (!(form = *argp++) || *form == '-')
181                                                 adios(NULL, "missing argument to %s", argp[-2]);
182                                         continue;
183
184                                 case FRMTSW:
185                                         filter = getcpy(etcpath(mhlforward));
186                                         continue;
187                                 case NFRMTSW:
188                                         filter = NULL;
189                                         continue;
190
191                                 case INPLSW:
192                                         inplace++;
193                                         continue;
194                                 case NINPLSW:
195                                         inplace = 0;
196                                         continue;
197
198                                 case MIMESW:
199                                         mime++;
200                                         filter = NULL;
201                                         continue;
202                                 case NMIMESW:
203                                         mime = 0;
204                                         continue;
205
206                                 case DGSTSW:
207                                         if (!(digest = *argp++) || *digest == '-')
208                                                 adios(NULL, "missing argument to %s", argp[-2]);
209                                         mime = 0;
210                                         continue;
211                                 case ISSUESW:
212                                         if (!(cp = *argp++) || *cp == '-')
213                                                 adios(NULL, "missing argument to %s", argp[-2]);
214                                         if ((issue = atoi(cp)) < 1)
215                                                 adios(NULL, "bad argument %s %s", argp[-2], cp);
216                                         continue;
217                                 case VOLUMSW:
218                                         if (!(cp = *argp++) || *cp == '-')
219                                                 adios(NULL, "missing argument to %s", argp[-2]);
220                                         if ((volume = atoi(cp)) < 1)
221                                                 adios(NULL, "bad argument %s %s", argp[-2], cp);
222                                         continue;
223
224                                 case BITSTUFFSW:
225                                         dashstuff = 1;  /* trinary logic */
226                                         continue;
227                                 case NBITSTUFFSW:
228                                         dashstuff = -1;  /* trinary logic */
229                                         continue;
230                         }
231                 }
232                 if (*cp == '+' || *cp == '@') {
233                         if (folder)
234                                 adios(NULL, "only one folder at a time!");
235                         else
236                                 folder = pluspath(cp);
237                 } else {
238                         msgs[msgp++] = cp;
239                 }
240         }
241
242         cwd = getcpy(pwd());
243
244         if (!context_find("path"))
245                 free(path("./", TFOLDER));
246         if (file && (msgp || folder))
247                 adios(NULL, "can't mix files and folders/msgs");
248
249 #ifdef MHE
250         strncpy(drft, buildsw ? m_maildir("draft")
251                 : m_draft("new"), sizeof(drft));
252 #else
253         strncpy(drft, m_draft("new"), sizeof(drft));
254 #endif /* MHE */
255
256         if (file) {
257                 /*
258                 ** Forwarding a file.
259                 */
260                 anot = 0;  /* don't want to annotate a file */
261         } else {
262                 /*
263                 ** Forwarding a message.
264                 */
265                 if (!msgp)
266                         msgs[msgp++] = "cur";
267                 if (!folder)
268                         folder = getfolder(1);
269                 maildir = m_maildir(folder);
270
271                 if (chdir(maildir) == NOTOK)
272                         adios(maildir, "unable to change directory to");
273
274                 /* read folder and create message structure */
275                 if (!(mp = folder_read(folder)))
276                         adios(NULL, "unable to read folder %s", folder);
277
278                 /* check for empty folder */
279                 if (mp->nummsg == 0)
280                         adios(NULL, "no messages in %s", folder);
281
282                 /* parse all the message ranges/sequences and set SELECTED */
283                 for (msgnum = 0; msgnum < msgp; msgnum++)
284                         if (!m_convert(mp, msgs[msgnum]))
285                                 done(1);
286                 seq_setprev(mp);  /* set the previous sequence */
287         }
288
289         if (filter && access(filter, R_OK) == NOTOK)
290                 adios(filter, "unable to read");
291
292         /*
293         ** Open form (component) file.
294         */
295         if (digest) {
296                 if (issue == 0) {
297                         snprintf(buf, sizeof(buf), IFORMAT, digest);
298                         if (volume == 0 && (cp = context_find(buf))
299                                         && ((issue = atoi(cp)) < 0))
300                                 issue = 0;
301                         issue++;
302                 }
303                 if (volume == 0)
304                         snprintf(buf, sizeof(buf), VFORMAT, digest);
305                 if ((cp = context_find(buf)) == NULL ||
306                                 (volume = atoi(cp)) <= 0)
307                         volume = 1;
308                 if (!form)
309                         form = digestcomps;
310                 in = build_form(form, digest, volume, issue);
311         } else
312                 in = open_form(&form, forwcomps);
313
314         if ((out = creat(drft, m_gmprot())) == NOTOK)
315                 adios(drft, "unable to create");
316
317         /*
318         ** copy the components into the draft
319         */
320         cpydata(in, out, form, drft);
321         close(in);
322
323         if (file) {
324                 /* just copy the file into the draft */
325                 if ((in = open(file, O_RDONLY)) == NOTOK)
326                         adios(file, "unable to open");
327                 cpydata(in, out, file, drft);
328                 close(in);
329                 close(out);
330         } else {
331                 /*
332                 ** If filter file is defined, then format the
333                 ** messages into the draft using mhlproc.
334                 */
335                 if (filter)
336                         mhl_draft(out, digest, volume, issue, drft, filter,
337                                         dashstuff);
338                 else if (mime)
339                         copy_mime_draft(out);
340                 else
341                         copy_draft(out, digest, drft, volume, issue,
342                                         dashstuff);
343                 close(out);
344
345                 if (digest) {
346                         snprintf(buf, sizeof(buf), IFORMAT, digest);
347                         snprintf(value, sizeof(value), "%d", issue);
348                         context_replace(buf, getcpy(value));
349                         snprintf(buf, sizeof(buf), VFORMAT, digest);
350                         snprintf(value, sizeof(value), "%d", volume);
351                         context_replace(buf, getcpy(value));
352                 }
353
354                 context_replace(pfolder, folder);  /* update current folder */
355                 seq_setcur(mp, mp->lowsel);  /* update current message */
356                 seq_save(mp);  /* synchronize sequences */
357                 context_save();  /* save the context file */
358         }
359
360         if (nwhat)
361                 done(0);
362         what_now(ed, nedit, NOUSE, drft, NULL, 0, mp,
363                 anot ? "Forwarded" : NULL, inplace, cwd);
364         done(1);
365         return 1;
366 }
367
368
369 /*
370 ** Filter the messages you are forwarding, into the
371 ** draft calling the mhlproc, and reading its output
372 ** from a pipe.
373 */
374
375 static void
376 mhl_draft(int out, char *digest, int volume, int issue,
377         char *file, char *filter, int dashstuff)
378 {
379         pid_t child_id;
380         int i, msgnum, pd[2];
381         char *vec[MAXARGS];
382         char buf1[BUFSIZ];
383         char buf2[BUFSIZ];
384
385         if (pipe(pd) == NOTOK)
386                 adios("pipe", "unable to create");
387
388         vec[0] = r1bindex(mhlproc, '/');
389
390         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
391                 sleep(5);
392         switch (child_id) {
393                 case NOTOK:
394                         adios("fork", "unable to");
395
396                 case OK:
397                         close(pd[0]);
398                         dup2(pd[1], 1);
399                         close(pd[1]);
400
401                         i = 1;
402                         vec[i++] = "-forwall";
403                         vec[i++] = "-form";
404                         vec[i++] = filter;
405
406                         if (digest) {
407                                 vec[i++] = "-digest";
408                                 vec[i++] = digest;
409                                 vec[i++] = "-issue";
410                                 snprintf(buf1, sizeof(buf1), "%d", issue);
411                                 vec[i++] = buf1;
412                                 vec[i++] = "-volume";
413                                 snprintf(buf2, sizeof(buf2), "%d", volume);
414                                 vec[i++] = buf2;
415                         }
416
417                         /*
418                         ** Are we dashstuffing (quoting) the lines that begin
419                         ** with `-'.  We use the mhl default (don't add any
420                         ** flag) unless the user has specified a specific flag.
421                         */
422                         if (dashstuff > 0)
423                                 vec[i++] = "-dashstuffing";
424                         else if (dashstuff < 0)
425                                 vec[i++] = "-nodashstuffing";
426
427                         if (mp->numsel >= MAXARGS - i)
428                                 adios(NULL, "more than %d messages for %s exec", 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
433                         ** size), but that should be plenty.
434                         */
435                         for (msgnum = mp->lowsel;
436                                         msgnum <= mp->hghsel && i < sizeof(vec) - 1;
437                                         msgnum++)
438                                 if (is_selected(mp, msgnum))
439                                         vec[i++] = getcpy(m_name(msgnum));
440                         vec[i] = NULL;
441
442                         execvp(mhlproc, vec);
443                         fprintf(stderr, "unable to exec ");
444                         perror(mhlproc);
445                         _exit(-1);
446
447                 default:
448                         close(pd[1]);
449                         cpydata(pd[0], out, vec[0], file);
450                         close(pd[0]);
451                         pidXwait(child_id, mhlproc);
452                         break;
453         }
454 }
455
456
457 /*
458 ** Copy the messages into the draft.  The messages are
459 ** not filtered through the mhlproc.  Do dashstuffing if
460 ** necessary.
461 */
462
463 static void
464 copy_draft(int out, char *digest, char *file, int volume, int issue,
465                 int dashstuff)
466 {
467         int fd,i, msgcnt, msgnum;
468         int len, buflen;
469         register char *bp, *msgnam;
470         char buffer[BUFSIZ];
471
472         msgcnt = 1;
473         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
474                 if (is_selected(mp, msgnum)) {
475                         if (digest) {
476                                 strncpy(buffer, msgnum == mp->lowsel ?
477                                         delim3 : delim4, sizeof(buffer));
478                         } else {
479                                 /* Get buffer ready to go */
480                                 bp = buffer;
481                                 buflen = sizeof(buffer);
482
483                                 strncpy(bp, "\n-------", buflen);
484                                 len = strlen(bp);
485                                 bp += len;
486                                 buflen -= len;
487
488                                 if (msgnum == mp->lowsel) {
489                                         snprintf(bp, buflen, " Forwarded Message%s", mp->numsel > 1 ? "s" : "");
490                                 } else {
491                                         snprintf(bp, buflen, " Message %d", msgcnt);
492                                 }
493                                 len = strlen(bp);
494                                 bp += len;
495                                 buflen -= len;
496
497                                 strncpy(bp, "\n\n", buflen);
498                         }
499                         write(out, buffer, strlen(buffer));
500
501                         if ((fd = open(msgnam = m_name (msgnum), O_RDONLY))
502                                         == NOTOK) {
503                                 admonish(msgnam, "unable to read message");
504                                 continue;
505                         }
506
507                         /*
508                         ** Copy the message.  Add RFC934 quoting (dashstuffing)
509                         ** unless given the -nodashstuffing flag.
510                         */
511                         if (dashstuff >= 0)
512                                 cpydgst(fd, out, msgnam, file);
513                         else
514                                 cpydata(fd, out, msgnam, file);
515
516                         close(fd);
517                         msgcnt++;
518                 }
519         }
520
521         if (digest) {
522                 strncpy(buffer, delim4, sizeof(buffer));
523         } else {
524                 snprintf(buffer, sizeof(buffer),
525                                 "\n------- End of Forwarded Message%s\n\n",
526                                 mp->numsel > 1 ? "s" : "");
527         }
528         write(out, buffer, strlen(buffer));
529
530         if (digest) {
531                 snprintf(buffer, sizeof(buffer),
532                                 "End of %s Digest [Volume %d Issue %d]\n",
533                                 digest, volume, issue);
534                 i = strlen(buffer);
535                 for (bp = buffer + i; i > 1; i--)
536                         *bp++ = '*';
537                 *bp++ = '\n';
538                 *bp = 0;
539                 write(out, buffer, strlen(buffer));
540         }
541 }
542
543
544 /*
545 ** Create a mhbuild composition file for forwarding message.
546 */
547
548 static void
549 copy_mime_draft(int out)
550 {
551         int msgnum;
552         char buffer[BUFSIZ];
553
554         snprintf(buffer, sizeof(buffer), "#forw [forwarded message%s] +%s",
555                         mp->numsel == 1 ? "" : "s", mp->foldpath);
556         write(out, buffer, strlen(buffer));
557         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
558                 if (is_selected(mp, msgnum)) {
559                         snprintf(buffer, sizeof(buffer), " %s",
560                                         m_name(msgnum));
561                         write(out, buffer, strlen(buffer));
562                 }
563         write(out, "\n", 1);
564 }
565
566
567 static int
568 build_form(char *form, char *digest, int volume, int issue)
569 {
570         int in;
571         int fmtsize;
572         register char *nfs;
573         char *line, tmpfil[BUFSIZ];
574         FILE *tmp;
575         register struct comp *cptr;
576         struct format *fmt;
577         int dat[5];
578         char *cp = NULL;
579
580         /* Get new format string */
581         nfs = new_fs(form, NULL, NULL);
582         fmtsize = strlen(nfs) + 256;
583
584         /* Compile format string */
585         fmt_compile(nfs, &fmt);
586
587         FINDCOMP(cptr, "digest");
588         if (cptr)
589                 cptr->c_text = digest;
590         FINDCOMP(cptr, "date");
591         if (cptr)
592                 cptr->c_text = getcpy(dtimenow(0));
593
594         dat[0] = issue;
595         dat[1] = volume;
596         dat[2] = 0;
597         dat[3] = fmtsize;
598         dat[4] = 0;
599
600         cp = m_mktemp2(NULL, invo_name, NULL, &tmp);
601         if (cp == NULL) adios("forw", "unable to create temporary file");
602         strncpy(tmpfil, cp, sizeof(tmpfil));
603         unlink(tmpfil);
604         if ((in = dup(fileno(tmp))) == NOTOK)
605                 adios("dup", "unable to");
606
607         line = mh_xmalloc((unsigned) fmtsize);
608         fmt_scan(fmt, line, fmtsize, dat);
609         fputs(line, tmp);
610         free(line);
611         if (fclose(tmp))
612                 adios(tmpfil, "error writing");
613
614         lseek(in, (off_t) 0, SEEK_SET);
615         return in;
616 }