Annotations will always be done inplace from now on.
[mmh] / uip / send.c
1 /*
2 ** send.c -- send a composed message
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 <errno.h>
12 #include <signal.h>
13 #include <h/signals.h>
14 #include <setjmp.h>
15 #include <h/mime.h>
16 #include <h/tws.h>
17 #include <h/utils.h>
18
19 #ifdef TIME_WITH_SYS_TIME
20 # include <sys/time.h>
21 # include <time.h>
22 #else
23 # ifdef TM_IN_SYS_TIME
24 #  include <sys/time.h>
25 # else
26 #  include <time.h>
27 # endif
28 #endif
29
30 int debugsw = 0;  /* global */
31 int forwsw  = 1;
32 int pushsw  = 0;
33 int verbsw  = 0;
34 char *altmsg   = NULL;
35 char *annotext = NULL;
36 char *distfile = NULL;
37
38 static jmp_buf env;
39
40 /* name of temp file for body content */
41 static char body_file_name[MAXPATHLEN + 1];
42 /* name of mhbuild composition temporary file */
43 static char composition_file_name[MAXPATHLEN + 1]; 
44 static int field_size;  /* size of header field buffer */
45 static char *field;  /* header field buffer */
46 static FILE *draft_file;  /* draft file pointer */
47 static FILE *body_file;  /* body file pointer */
48 static FILE *composition_file;  /* composition file pointer */
49
50 /*
51 ** static prototypes
52 */
53 static int sendsbr(char **, int, char *, struct stat *, int);
54 static void armed_done(int) NORETURN;
55 static void alert(char *, int);
56 static int tmp_fd(void);
57 static void anno(struct stat *);
58 static void annoaux(void);
59 static int sendaux(char **, int, char *, struct stat *);
60 static int attach(char *);
61 static void clean_up_temporary_files(void);
62 static int get_line(void);
63 static void make_mime_composition_file_entry(char *);
64
65
66 static struct swit switches[] = {
67 #define ALIASW  0
68         { "alias aliasfile", 0 },
69 #define DEBUGSW  1
70         { "debug", -5 },
71 #define FILTSW  2
72         { "filter filterfile", 0 },
73 #define NFILTSW  3
74         { "nofilter", 0 },
75 #define FRMTSW  4
76         { "format", 0 },
77 #define NFRMTSW  5
78         { "noformat", 0 },
79 #define FORWSW  6
80         { "forward", 0 },
81 #define NFORWSW  7
82         { "noforward", 0 },
83 #define PUSHSW  8
84         { "push", 0 },
85 #define NPUSHSW  9
86         { "nopush", 0 },
87 #define VERBSW  10
88         { "verbose", 0 },
89 #define NVERBSW  11
90         { "noverbose", 0 },
91 #define WATCSW  12
92         { "watch", 0 },
93 #define NWATCSW  13
94         { "nowatch", 0 },
95 #define VERSIONSW  14
96         { "version", 0 },
97 #define HELPSW  15
98         { "help", 0 },
99         { NULL, 0 }
100 };
101
102
103 int
104 main(int argc, char **argv)
105 {
106         int msgp = 0, nfiles = 0, distsw = 0, vecp = 1;
107         int msgnum, status;
108         int in, out;
109         char *cp, *maildir = NULL;
110         char buf[BUFSIZ], **ap, **argp, **arguments;
111         char *msgs[MAXARGS], *vec[MAXARGS];
112         char *files[MAXARGS];
113         struct msgs *mp;
114         struct stat st;
115         struct stat st2;
116
117
118 #ifdef LOCALE
119         setlocale(LC_ALL, "");
120 #endif
121         invo_name = mhbasename(argv[0]);
122
123         /* read user profile/context */
124         context_read();
125
126         arguments = getarguments(invo_name, argc, argv, 1);
127         argp = arguments;
128
129         vec[vecp++] = "-library";
130         vec[vecp++] = getcpy(toabsdir("+"));
131
132         while ((cp = *argp++)) {
133                 if (*cp == '-') {
134                         switch (smatch(++cp, switches)) {
135                         case AMBIGSW:
136                                 ambigsw(cp, switches);
137                                 done(1);
138                         case UNKWNSW:
139                                 adios(NULL, "-%s unknown\n", cp);
140
141                         case HELPSW:
142                                 snprintf(buf, sizeof(buf),
143                                                 "%s [file] [switches]",
144                                                 invo_name);
145                                 print_help(buf, switches, 1);
146                                 done(1);
147                         case VERSIONSW:
148                                 print_version(invo_name);
149                                 done(1);
150
151                         case PUSHSW:
152                                 pushsw++;
153                                 continue;
154                         case NPUSHSW:
155                                 pushsw = 0;
156                                 continue;
157
158                         case FORWSW:
159                                 forwsw++;
160                                 continue;
161                         case NFORWSW:
162                                 forwsw = 0;
163                                 continue;
164
165                         case VERBSW:
166                                 verbsw++;
167                                 vec[vecp++] = --cp;
168                                 continue;
169                         case NVERBSW:
170                                 verbsw = 0;
171                                 vec[vecp++] = --cp;
172                                 continue;
173
174                         case DEBUGSW:
175                                 debugsw++;  /* fall */
176                         case NFILTSW:
177                         case FRMTSW:
178                         case NFRMTSW:
179                         case WATCSW:
180                         case NWATCSW:
181                                 vec[vecp++] = --cp;
182                                 continue;
183
184                         case ALIASW:
185                         case FILTSW:
186                                 vec[vecp++] = --cp;
187                                 if (!(cp = *argp++) || *cp == '-') {
188                                         adios(NULL, "missing argument to %s",
189                                                         argp[-2]);
190                                 }
191                                 vec[vecp++] = cp;
192                                 continue;
193
194                         }
195                 } else {
196                         if (*cp == '/') {
197                                 files[nfiles++] = cp;
198                         } else {
199                                 msgs[msgp++] = cp;
200                         }
201                 }
202         }
203
204         /* check for "Aliasfile:" profile entry */
205         if ((cp = context_find("Aliasfile"))) {
206                 char *dp = NULL;
207
208                 for (ap=brkstring(dp=getcpy(cp), " ", "\n"); ap && *ap; ap++) {
209                         vec[vecp++] = "-alias";
210                         vec[vecp++] = getcpy(etcpath(*ap));
211                 }
212         }
213
214         if (!msgp && !nfiles) {
215                 msgs[msgp++] = seq_cur;
216         }
217         maildir = toabsdir(draftfolder);
218
219         if (chdir(maildir) == NOTOK) {
220                 adios(maildir, "unable to change directory to");
221         }
222
223         if (!(mp = folder_read(draftfolder))) {
224                 adios(NULL, "unable to read draft folder %s", draftfolder);
225         }
226         if (mp->nummsg == 0) {
227                 adios(NULL, "no messages in draft folder %s", draftfolder);
228         }
229         /* parse all the message ranges/sequences and set SELECTED */
230         for (msgnum = 0; msgnum < msgp; msgnum++) {
231                 if (!m_convert(mp, msgs[msgnum])) {
232                         done(1);
233                 }
234         }
235         seq_setprev(mp);
236
237         for (msgp = 0, msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
238                 if (is_selected(mp, msgnum)) {
239                         files[nfiles++] = getcpy(m_name(msgnum));
240                         unset_exists(mp, msgnum);
241                 }
242         }
243
244         mp->msgflags |= SEQMOD;
245         seq_save(mp);
246
247         if (!(cp = getenv("SIGNATURE")) || !*cp) {
248                 if ((cp = context_find("signature")) && *cp) {
249                         m_putenv("SIGNATURE", cp);
250                 }
251         }
252
253         for (msgnum = 0; msgnum < nfiles; msgnum++) {
254                 if (stat(files[msgnum], &st) == NOTOK) {
255                         adios(files[msgnum], "unable to stat draft file");
256                 }
257         }
258
259         if (!(annotext = getenv("mhannotate")) || !*annotext) {
260                 annotext = NULL;
261         }
262         if (!(altmsg = getenv("mhaltmsg")) || !*altmsg) {
263                 altmsg = NULL;  /* used by dist interface - see below */
264         }
265
266         if ((cp = getenv("mhdist")) && *cp && (distsw = atoi(cp)) && altmsg) {
267                 vec[vecp++] = "-dist";
268                 distfile = getcpy(m_mktemp2(altmsg, invo_name, NULL, NULL));
269                 if (link(altmsg, distfile) == NOTOK) {
270                         if (errno != EXDEV) {
271                                 adios(distfile, "unable to link %s to",
272                                                 altmsg);
273                         }
274                         free(distfile);
275                         distfile = getcpy(m_mktemp2(NULL, invo_name,
276                                         NULL, NULL));
277                         if ((in = open(altmsg, O_RDONLY)) == NOTOK) {
278                                 adios(altmsg, "unable to open");
279                         }
280                         fstat(in, &st2);
281                         if ((out = creat(distfile, (int)st2.st_mode & 0777)) ==
282                                         NOTOK) {
283                                 adios(distfile, "unable to write");
284                         }
285                         cpydata(in, out, altmsg, distfile);
286                         close(in);
287                         close(out);
288                 }
289         } else {
290                 distfile = NULL;
291         }
292
293         if (!altmsg || stat(altmsg, &st) == NOTOK) {
294                 st.st_mtime = 0;
295                 st.st_dev = 0;
296                 st.st_ino = 0;
297         }
298         if (pushsw) {
299                 push();
300         }
301         status = 0;
302         vec[0] = mhbasename(postproc);
303         closefds(3);
304
305         for (msgnum = 0; msgnum < nfiles; msgnum++) {
306                 switch (sendsbr(vec, vecp, files[msgnum], &st, 1)) {
307                 case DONE:
308                         done(++status);
309                 case NOTOK:
310                         status++;  /* fall */
311                 case OK:
312                         break;
313                 }
314         }
315
316         context_save();
317         done(status);
318         return 1;
319 }
320
321
322 /*
323 ** message sending back-end
324 */
325 static int
326 sendsbr(char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
327 {
328         int status;
329         char buffer[BUFSIZ];
330         char *original_draft;
331         char *p;  /* string pointer for building file name */
332
333         /*
334         ** Save the original name of the draft file.  The name of the
335         ** draft file is changed to a temporary file containing the built
336         ** MIME message if there are attachments.  We need the original
337         ** name so that it can be renamed after the message is sent.
338         */
339         original_draft = drft;
340
341         /*
342         ** Convert the draft to a MIME message.
343         ** Use the mhbuild composition file for the draft if there was
344         ** a successful conversion because that now contains the MIME
345         ** message.  A nice side effect of this is that it leaves the
346         ** original draft file untouched so that it can be retrieved
347         ** and modified if desired.
348         */
349         switch (attach(drft)) {
350         case OK:
351                 drft = composition_file_name;
352                 break;
353
354         case NOTOK:
355                 return (NOTOK);
356
357         case DONE:
358                 break;
359         }
360
361         done=armed_done;
362         switch (setjmp(env)) {
363         case OK:
364                 status = sendaux(vec, vecp, drft, st) ? NOTOK : OK;
365                 /* rename the original draft */
366                 if (rename_drft && status == OK &&
367                                 rename(original_draft, strncpy(buffer,
368                                 m_backup(original_draft), sizeof(buffer)))
369                                 == NOTOK) {
370                         advise(buffer, "unable to rename %s to", drft);
371                 }
372                 break;
373
374         default:
375                 status = DONE;
376                 break;
377         }
378
379         done=exit;
380         if (distfile) {
381                 unlink(distfile);
382         }
383
384         /*
385         ** Get rid of any temporary files that we created for attachments.
386         ** Also get rid of the renamed composition file that mhbuild
387         ** leaves as a turd.  It looks confusing, but we use the body
388         ** file name to help build the renamed composition file name.
389         */
390         if (drft == composition_file_name) {
391                 clean_up_temporary_files();
392
393                 if (strlen(composition_file_name) >=
394                                 sizeof (composition_file_name) - 6) {
395                         advise(NULL, "unable to remove original composition file.");
396                 } else {
397                         if (!(p = strrchr(composition_file_name, '/'))) {
398                                 p = composition_file_name;
399                         } else {
400                                 p++;
401                         }
402                         strcpy(body_file_name, p);
403                         *p++ = ',';
404                         strcpy(p, body_file_name);
405                         strcat(p, ".orig");
406
407                         unlink(composition_file_name);
408                 }
409         }
410
411         return status;
412 }
413
414 static int
415 attach(char *draft_file_name)
416 {
417         char buf[MAXPATHLEN + 6];
418         int c;
419         int has_attachment;
420         int has_body;
421         int non_ascii; /* msg body contains non-ASCII chars */
422         int length;  /* length of attachment header field name */
423         char *p;
424
425         if (!(draft_file = fopen(draft_file_name, "r"))) {
426                 adios(NULL, "can't open draft file `%s'.", draft_file_name);
427         }
428
429         /* We'll grow the buffer as needed. */
430         field = (char *)mh_xmalloc(field_size = 256);
431
432         /*
433         ** Scan the draft file for an attachment header field name.
434         */
435         length = strlen(attach_hdr);
436         has_attachment = 0;
437         while (get_line() != EOF && *field != '\0' && *field != '-') {
438                 if (strncasecmp(field, attach_hdr, length)==0 &&
439                                 field[length] == ':') {
440                         has_attachment = 1;
441                 }
442         }
443
444         /*
445         ** Look for at least one non-blank line in the body of the
446         ** message which indicates content in the body.
447         ** Check if body contains at least one non-blank (= not empty)
448         ** and if it contains any non-ASCII chars (= need MIME).
449         */
450         has_body = 0;
451         non_ascii = 0;
452         while (get_line() != EOF) {
453                 for (p = field; *p != '\0'; p++) {
454                         if (*p != ' ' && *p != '\t') {
455                                 has_body = 1;
456                         }
457                         if (*p > 127 || *p < 0) {
458                                 non_ascii = 1;
459                         }
460                 }
461                 if (has_body && non_ascii) {
462                         break;  /* that's been already enough information */
463                 }
464         }
465
466         if (!has_attachment && non_ascii==0) {
467                 /* We don't need to convert it to MIME. */
468                 return DONE;
469         }
470
471         /*
472         ** Else: mimify
473         */
474
475         /* Make names for the temporary files.  */
476         strncpy(body_file_name,
477                         m_mktemp(toabsdir(invo_name), NULL, NULL),
478                         sizeof (body_file_name));
479         strncpy(composition_file_name,
480                         m_mktemp(toabsdir(invo_name), NULL, NULL),
481                         sizeof (composition_file_name));
482
483         if (has_body) {
484                 body_file = fopen(body_file_name, "w");
485         }
486         composition_file = fopen(composition_file_name, "w");
487
488         if ((has_body && !body_file) || !composition_file) {
489                 clean_up_temporary_files();
490                 adios(NULL, "unable to open all of the temporary files.");
491         }
492
493         /* Copy non-attachment header fields to the temp composition file. */
494         rewind(draft_file);
495         while (get_line() != EOF && *field && *field != '-') {
496                 if (strncasecmp(field, attach_hdr, length) != 0 ||
497                                 field[length] != ':') {
498                         fprintf(composition_file, "%s\n", field);
499                 }
500         }
501         fputs("--------\n", composition_file);
502
503         if (has_body) {
504                 /* Copy the message body to the temporary file. */
505                 while ((c = getc(draft_file)) != EOF) {
506                         putc(c, body_file);
507                 }
508                 fclose(body_file);
509
510                 /* Add a mhbuild MIME composition file line for the body */
511                 /* charset will be discovered/guessed by buildmimeproc */
512                 fprintf(composition_file, "#text/plain %s\n", body_file_name);
513         }
514
515         /*
516         ** Now, go back to the beginning of the draft file and look for
517         ** header fields that specify attachments.  Add a mhbuild MIME
518         ** composition file for each.
519         */
520         rewind(draft_file);
521         while (get_line() != EOF && *field && *field != '-') {
522                 if (strncasecmp(field, attach_hdr, length) == 0 &&
523                                 field[length] == ':') {
524                         for (p = field+length+1; *p==' ' || *p=='\t'; p++) {
525                                 continue;
526                         }
527                         if (*p == '+') {
528                                 /* forwarded message */
529                                 fprintf(composition_file, "#forw [forwarded message(s)] %s\n", p);
530                         } else {
531                                 /* regular attachment */
532                                 make_mime_composition_file_entry(p);
533                         }
534                 }
535         }
536         fclose(composition_file);
537
538         /*
539         ** We're ready to roll!  Run mhbuild on the composition file.
540         ** Note that mhbuild is in the context as buildmimeproc.
541         */
542         sprintf(buf, "%s %s", buildmimeproc, composition_file_name);
543
544         if (system(buf) != 0) {
545                 /* some problem */
546                 clean_up_temporary_files();
547                 return (NOTOK);
548         }
549
550         return (OK);
551 }
552
553 static void
554 clean_up_temporary_files(void)
555 {
556         unlink(body_file_name);
557         unlink(composition_file_name);
558
559         return;
560 }
561
562 static int
563 get_line(void)
564 {
565         int c;  /* current character */
566         int n;  /* number of bytes in buffer */
567         char *p;
568
569         /*
570         ** Get a line from the input file, growing the field buffer as
571         ** needed.  We do this so that we can fit an entire line in the
572         ** buffer making it easy to do a string comparison on both the
573         ** field name and the field body which might be a long path name.
574         */
575         for (n = 0, p = field; (c = getc(draft_file)) != EOF; *p++ = c) {
576                 if (c == '\n' && (c = getc(draft_file)) != ' ' && c != '\t') {
577                         ungetc(c, draft_file);
578                         c = '\n';
579                         break;
580                 }
581                 if (++n >= field_size - 1) {
582                         field = (char *)mh_xrealloc(field, field_size += 256);
583                         p = field + n - 1;
584                 }
585         }
586         *p = '\0';
587
588         return (c);
589 }
590
591 static void
592 make_mime_composition_file_entry(char *file_name)
593 {
594         FILE *fp;
595         struct node *np;
596         char *cp;
597         char content_type[BUFSIZ];
598         char cmdbuf[BUFSIZ];
599         char *cmd = mimetypequeryproc;
600
601         for (np = m_defs; np; np = np->n_next) {
602                 if (strcasecmp(np->n_name, mimetypequery)==0) {
603                         cmd = np->n_field;
604                         break;
605                 }
606         }
607         snprintf(cmdbuf, sizeof cmdbuf, "%s %s", cmd, file_name);
608
609         if (!(fp = popen(cmdbuf, "r"))) {
610                 clean_up_temporary_files();
611                 adios(NULL, "unable to determine content type with `%s'",
612                                 cmdbuf);
613         }
614         if (fgets(content_type, sizeof content_type, fp) &&
615                         (cp = strrchr(content_type, '\n'))) {
616                 *cp = '\0';
617         } else {
618                 strcpy(content_type, "application/octet-stream");
619                 admonish(NULL, "problems with `%s', using fall back type `%s'",
620                                 cmdbuf, content_type);
621         }
622         pclose(fp);
623
624         /* TODO: don't use access(2) because it checks for ruid, not euid */
625         if (access(file_name, R_OK) != 0) {
626                 clean_up_temporary_files();
627                 adios(NULL, "unable to access file `%s'", file_name);
628         }
629
630         fprintf(composition_file, "#%s; name=\"%s\" <>{attachment}",
631                 content_type,
632                 (!(cp = strrchr(file_name, '/'))) ? file_name : cp + 1);
633
634         fprintf(composition_file, " %s\n", file_name);
635
636         return;
637 }
638
639 /*
640 ** The back-end of the message sending back-end.
641 ** Annotate original message, and call `postproc' to send message.
642 */
643 static int
644 sendaux(char **vec, int vecp, char *drft, struct stat *st)
645 {
646         pid_t child_id;
647         int i, status, fd;
648         char backup[BUFSIZ];
649
650         /*
651         ** fd collects the output of postproc, and is used for the
652         ** failure notice if we need to send one in alert().
653         */
654         fd = pushsw ? tmp_fd() : NOTOK;
655
656         vec[vecp++] = drft;
657         if (distfile && distout(drft, distfile, backup) == NOTOK) {
658                 done(1);
659         }
660         vec[vecp] = NULL;
661
662         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) {
663                 sleep(5);
664         }
665         switch (child_id) {
666         case -1:
667                 /* oops -- fork error */
668                 adios("fork", "unable to");
669                 break;  /* NOT REACHED */
670
671         case 0:
672                 /*
673                 ** child process -- send it
674                 **
675                 ** If fd is ok, then we are pushing and fd points to temp
676                 ** file, so capture anything on stdout and stderr there.
677                 */
678                 if (fd != NOTOK) {
679                         dup2(fd, fileno(stdout));
680                         dup2(fd, fileno(stderr));
681                         close(fd);
682                 }
683                 execvp(postproc, vec);
684                 fprintf(stderr, "unable to exec ");
685                 perror(postproc);
686                 _exit(-1);
687                 break;  /* NOT REACHED */
688
689         default:
690                 /* parent process -- wait for it */
691                 if ((status = pidwait(child_id, NOTOK)) == OK) {
692                         if (annotext) {
693                                 anno(st);
694                         }
695                 } else {
696                         /*
697                         ** If postproc failed, and we have good fd (which
698                         ** means we pushed), then mail error message
699                         ** (and possibly the draft) back to the user.
700                         */
701                         if (fd != NOTOK) {
702                                 alert(drft, fd);
703                                 close(fd);
704                         } else {
705                                 advise(NULL, "message not delivered to anyone");
706                         }
707                         if (distfile) {
708                                 unlink(drft);
709                                 if (rename(backup, drft) == NOTOK) {
710                                         advise(drft, "unable to rename %s to",
711                                                         backup);
712                                 }
713                         }
714                 }
715                 break;
716         }
717
718         return status;
719 }
720
721
722 /*
723 ** Mail error notification (and possibly a copy of the
724 ** message) back to the user, using the mailproc
725 */
726 static void
727 alert(char *file, int out)
728 {
729         pid_t child_id;
730         int i, in;
731         char buf[BUFSIZ];
732
733         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) {
734                 sleep(5);
735         }
736
737         switch (child_id) {
738         case NOTOK:
739                 /* oops -- fork error */
740                 advise("fork", "unable to");
741
742         case OK:
743                 /* child process -- send it */
744                 SIGNAL(SIGHUP, SIG_IGN);
745                 SIGNAL(SIGINT, SIG_IGN);
746                 SIGNAL(SIGQUIT, SIG_IGN);
747                 SIGNAL(SIGTERM, SIG_IGN);
748                 if (forwsw) {
749                         if ((in = open(file, O_RDONLY)) == NOTOK) {
750                                 admonish(file, "unable to re-open");
751                         } else {
752                                 lseek(out, (off_t) 0, SEEK_END);
753                                 strncpy(buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
754                                 write(out, buf, strlen(buf));
755                                 strncpy(buf, "\n------- Unsent Draft\n\n", sizeof(buf));
756                                 write(out, buf, strlen(buf));
757                                 cpydgst(in, out, file, "temporary file");
758                                 close(in);
759                                 strncpy(buf, "\n------- End of Unsent Draft\n", sizeof(buf));
760                                 write(out, buf, strlen(buf));
761                                 if (rename(file, strncpy(buf, m_backup(file), sizeof(buf))) == NOTOK) {
762                                         admonish(buf, "unable to rename %s to", file);
763                                 }
764                         }
765                 }
766                 lseek(out, (off_t) 0, SEEK_SET);
767                 dup2(out, fileno(stdin));
768                 close(out);
769                 /* create subject for error notification */
770                 snprintf(buf, sizeof(buf), "send failed on %s",
771                                 forwsw ? "enclosed draft" : file);
772
773                 execlp(mailproc, mhbasename(mailproc), getusername(),
774                                 "-subject", buf, NULL);
775                 fprintf(stderr, "unable to exec ");
776                 perror(mailproc);
777                 _exit(-1);
778
779         default:  /* no waiting... */
780                 break;
781         }
782 }
783
784
785 static int
786 tmp_fd(void)
787 {
788         int fd;
789         char *tfile = NULL;
790
791         tfile = m_mktemp2(NULL, invo_name, &fd, NULL);
792         if (tfile == NULL) return NOTOK;
793         fchmod(fd, 0600);
794
795         if (debugsw) {
796                 advise(NULL, "temporary file %s selected", tfile);
797         } else {
798                 if (unlink(tfile) == NOTOK) {
799                         advise(tfile, "unable to remove");
800                 }
801         }
802
803         return fd;
804 }
805
806
807 static void
808 anno(struct stat *st)
809 {
810         pid_t child_id;
811         sigset_t set, oset;
812         static char *cwd = NULL;
813         struct stat st2;
814
815         if (altmsg && (stat(altmsg, &st2) == NOTOK ||
816                         st->st_mtime != st2.st_mtime ||
817                         st->st_dev != st2.st_dev ||
818                         st->st_ino != st2.st_ino)) {
819                 if (debugsw) {
820                         admonish(NULL, "$mhaltmsg mismatch");
821                 }
822                 return;
823         }
824
825         child_id = debugsw ? NOTOK : fork();
826         switch (child_id) {
827         case NOTOK:  /* oops */
828                 if (!debugsw) {
829                         advise(NULL, "unable to fork, so doing annotations by hand...");
830                 }
831                 if (!cwd) {
832                         cwd = getcpy(pwd());
833                 }
834                 /* fall */
835
836         case OK:
837                 /* block a few signals */
838                 sigemptyset(&set);
839                 sigaddset(&set, SIGHUP);
840                 sigaddset(&set, SIGINT);
841                 sigaddset(&set, SIGQUIT);
842                 sigaddset(&set, SIGTERM);
843                 SIGPROCMASK(SIG_BLOCK, &set, &oset);
844
845                 annoaux();
846                 if (child_id == OK) {
847                         _exit(0);
848                 }
849
850                 /* reset the signal mask */
851                 SIGPROCMASK(SIG_SETMASK, &oset, &set);
852
853                 chdir(cwd);
854                 break;
855
856         default:  /* no waiting... */
857                 break;
858         }
859 }
860
861
862 static void
863 annoaux(void)
864 {
865         int fd2, fd3, msgnum;
866         char *cp, *folder, *maildir;
867         char **ap;
868         struct msgs *mp;
869
870         if (!(folder = getenv("mhfolder")) || !*folder) {
871                 if (debugsw) {
872                         admonish(NULL, "$mhfolder not set");
873                 }
874                 return;
875         }
876         maildir = toabsdir(folder);
877         if (chdir(maildir) == NOTOK) {
878                 if (debugsw) {
879                         admonish(maildir, "unable to change directory to");
880                 }
881                 return;
882         }
883         if (!(mp = folder_read(folder))) {
884                 if (debugsw) {
885                         admonish(NULL, "unable to read folder %s", folder);
886                 }
887                 return;
888         }
889
890         if (mp->nummsg == 0) {
891                 if (debugsw) {
892                         admonish(NULL, "no messages in %s", folder);
893                 }
894                 goto oops;
895         }
896         if ((cp = getenv("mhmessages")) == NULL || *cp == 0) {
897                 if (debugsw) {
898                         admonish(NULL, "$mhmessages not set");
899                 }
900                 goto oops;
901         }
902         if (!debugsw  /* MOBY HACK... */
903                         && pushsw
904                         && (fd3 = open("/dev/null", O_RDWR)) != NOTOK
905                         && (fd2 = dup(fileno(stderr))) != NOTOK) {
906                 dup2(fd3, fileno(stderr));
907                 close(fd3);
908         } else {
909                 fd2 = NOTOK;
910         }
911         for (ap = brkstring(cp = getcpy(cp), " ", NULL); *ap; ap++) {
912                 m_convert(mp, *ap);
913         }
914         free(cp);
915         if (fd2 != NOTOK) {
916                 dup2(fd2, fileno(stderr));
917         }
918         if (mp->numsel == 0) {
919                 if (debugsw) {
920                         admonish(NULL, "no messages to annotate");
921                 }
922                 goto oops;
923         }
924
925         if (debugsw) {
926                 advise(NULL, "annotate as `%s'", annotext);
927         }
928         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
929                 if (is_selected(mp, msgnum)) {
930                         if (debugsw) {
931                                 advise(NULL, "annotate message %d", msgnum);
932                         }
933                         annotate(m_name(msgnum), annotext, NULL, 1, -2, 0);
934                 }
935         }
936
937 oops:
938         folder_free(mp);
939 }
940
941
942 static void
943 armed_done(int status)
944 {
945         longjmp(env, status ? status : NOTOK);
946
947         exit(status);
948 }