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