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