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