Removed the split feature of send.
[mmh] / uip / sendsbr.c
1 /*
2  * sendsbr.c -- routines to help WhatNow/Send along
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 <h/signals.h>
11 #include <setjmp.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 #include <h/mime.h>
15 #include <h/tws.h>
16 #include <h/utils.h>
17
18 #ifdef TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # ifdef TM_IN_SYS_TIME
23 #  include <sys/time.h>
24 # else
25 #  include <time.h>
26 # endif
27 #endif
28
29 int debugsw = 0;  /* global */
30 int forwsw  = 1;
31 int inplace = 1;
32 int pushsw  = 0;
33 int unique  = 0;
34 int verbsw  = 0;
35
36 char *altmsg   = NULL;  /*  .. */
37 char *annotext = NULL;
38 char *distfile = NULL;
39
40 static jmp_buf env;
41
42 static char body_file_name[MAXPATHLEN + 1];  /* name of temporary file for body content */
43 static char composition_file_name[MAXPATHLEN + 1];  /* name of mhbuild composition temporary file */
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  * external prototypes
52  */
53 int sendsbr (char **, int, char *, struct stat *, int, char *, int);
54 char *getusername (void);
55
56 /*
57  * static prototypes
58  */
59 static void armed_done (int) NORETURN;
60 static void alert (char *, int);
61 static int tmp_fd (void);
62 static void anno (int, struct stat *);
63 static void annoaux (int);
64 static int sendaux (char **, int, char *, struct stat *);
65
66 static int attach(char *, char *, int);
67 static void clean_up_temporary_files(void);
68 static int get_line(void);
69 static void make_mime_composition_file_entry(char *, int);
70
71
72 /*
73  * Entry point into (back-end) routines to send message.
74  */
75
76 int
77 sendsbr (char **vec, int vecp, char *drft, struct stat *st,
78         int rename_drft, char *attachment_header_field_name, int attachformat)
79 {
80         int status;
81         char buffer[BUFSIZ], file[BUFSIZ];
82         char *original_draft;  /* name of original draft file */
83         char *p;  /* string pointer for building file name */
84
85         /*
86          * Save the original name of the draft file.  The name of the
87          * draft file is changed to a temporary file containing the built
88          * MIME message if there are attachments.  We need the original
89          * name so that it can be renamed after the message is sent.
90          */
91
92         original_draft = drft;
93
94         /*
95          * There might be attachments if a header field name for
96          * attachments is supplied.  Convert the draft to a MIME message.
97          * Use the mhbuild composition file for the draft if there was
98          * a successful conversion because that now contains the MIME
99          * message.  A nice side effect of this is that it leaves the
100          * original draft file untouched so that it can be retrieved
101          * and modified if desired.
102          */
103
104         if (attachment_header_field_name != (char *)0) {
105                 switch (attach(attachment_header_field_name, drft, attachformat)) {
106                 case OK:
107                         drft = composition_file_name;
108                         break;
109
110                 case NOTOK:
111                         return (NOTOK);
112
113                 case DONE:
114                         break;
115                 }
116         }
117
118         done=armed_done;
119         switch (setjmp (env)) {
120         case OK:
121                 /*
122                  * If given -push and -unique (which is undocumented), then
123                  * rename the draft file.  I'm not quite sure why.
124                  */
125                 if (pushsw && unique) {
126                         char *cp = m_mktemp2(drft, invo_name, NULL, NULL);
127                         if (cp == NULL) {
128                                 adios ("sendsbr", "unable to create temporary file");
129                         }
130                         if (rename (drft, strncpy(file, cp, sizeof(file))) == NOTOK)
131                                 adios (file, "unable to rename %s to", drft);
132                         drft = file;
133                 }
134
135                 status = sendaux (vec, vecp, drft, st) ? NOTOK : OK;
136
137                 /* rename the original draft */
138                 if (rename_drft && status == OK &&
139                                 rename (original_draft, strncpy (buffer, m_backup (original_draft), sizeof(buffer))) == NOTOK)
140                         advise (buffer, "unable to rename %s to", drft);
141                 break;
142
143         default:
144                 status = DONE;
145                 break;
146         }
147
148         done=exit;
149         if (distfile)
150                 unlink (distfile);
151
152         /*
153          * Get rid of any temporary files that we created for attachments.
154          * Also get rid of the renamed composition file that mhbuild
155          * leaves as a turd.  It looks confusing, but we use the body
156          * file name to help build the renamed composition file name.
157          */
158
159         if (drft == composition_file_name) {
160                 clean_up_temporary_files();
161
162                 if (strlen(composition_file_name) >= sizeof (composition_file_name) - 6)
163                         advise((char *)0, "unable to remove original composition file.");
164
165                 else {
166                         if ((p = strrchr(composition_file_name, '/')) == (char *)0)
167                                 p = composition_file_name;
168                         else
169                                 p++;
170
171                         (void)strcpy(body_file_name, p);
172                         *p++ = ',';
173                         (void)strcpy(p, body_file_name);
174                         (void)strcat(p, ".orig");
175
176                         (void)unlink(composition_file_name);
177                 }
178         }
179
180         return status;
181 }
182
183 static int
184 attach(char *attachment_header_field_name, char *draft_file_name,
185         int attachformat)
186 {
187         char buf[MAXPATHLEN + 6];  /* miscellaneous buffer */
188         int c;  /* current character for body copy */
189         int has_attachment;  /* draft has at least one attachment */
190         int has_body;  /* draft has a message body */
191         int length;  /* length of attachment header field name */
192         char *p;  /* miscellaneous string pointer */
193
194         /*
195          * Open up the draft file.
196          */
197
198         if ((draft_file = fopen(draft_file_name, "r")) == (FILE *)0)
199                 adios((char *)0, "can't open draft file `%s'.", draft_file_name);
200
201         /*
202          *  Allocate a buffer to hold the header components as they're read in.
203          *  This buffer might need to be quite large, so we grow it as needed.
204          */
205
206         field = (char *)mh_xmalloc(field_size = 256);
207
208         /*
209          * Scan the draft file for a header field name that matches the
210          * -attach argument.  The existence of one indicates that the
211          * draft has attachments.  Bail out if there are no attachments
212          * because we're done.  Read to the end of the headers even if
213          * we have no attachments.
214          */
215
216         length = strlen(attachment_header_field_name);
217
218         has_attachment = 0;
219
220         while (get_line() != EOF && *field != '\0' && *field != '-')
221                 if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':')
222                         has_attachment = 1;
223
224         if (has_attachment == 0)
225                 return (DONE);
226
227         /*
228          * We have at least one attachment.  Look for at least one
229          * non-blank line in the body of the message which indicates
230          * content in the body.
231          */
232
233         has_body = 0;
234
235         while (get_line() != EOF) {
236                 for (p = field; *p != '\0'; p++) {
237                         if (*p != ' ' && *p != '\t') {
238                                 has_body = 1;
239                                 break;
240                         }
241                 }
242
243                 if (has_body)
244                         break;
245         }
246
247         /*
248          * Make names for the temporary files.
249          */
250
251         (void)strncpy(body_file_name,
252                 m_mktemp(m_maildir(invo_name), NULL, NULL),
253                 sizeof (body_file_name));
254         (void)strncpy(composition_file_name,
255                 m_mktemp(m_maildir(invo_name), NULL, NULL),
256                 sizeof (composition_file_name));
257
258         if (has_body)
259                 body_file = fopen(body_file_name, "w");
260
261         composition_file = fopen(composition_file_name, "w");
262
263         if ((has_body && body_file == (FILE *)0) || composition_file == (FILE *)0) {
264                 clean_up_temporary_files();
265                 adios((char *)0, "unable to open all of the temporary files.");
266         }
267
268         /*
269          * Start at the beginning of the draft file.  Copy all
270          * non-attachment header fields to the temporary composition file.
271          * Then add the dashed line separator.
272          */
273
274         rewind(draft_file);
275
276         while (get_line() != EOF && *field != '\0' && *field != '-')
277                 if (strncasecmp(field, attachment_header_field_name, length) != 0 || field[length] != ':')
278                         (void)fprintf(composition_file, "%s\n", field);
279
280         (void)fputs("--------\n", composition_file);
281
282         /*
283          * Copy the message body to a temporary file.
284          */
285
286         if (has_body) {
287                 while ((c = getc(draft_file)) != EOF)
288                                 putc(c, body_file);
289
290                 (void)fclose(body_file);
291         }
292
293         /*
294          * Add a mhbuild MIME composition file line for the body if
295          * there was one.
296          */
297
298         if (has_body)
299                 make_mime_composition_file_entry(body_file_name, attachformat);
300
301         /*
302          * Now, go back to the beginning of the draft file and look for
303          * header fields that specify attachments.  Add a mhbuild MIME
304          * composition file for each.
305          */
306
307         rewind(draft_file);
308
309         while (get_line() != EOF && *field != '\0' && *field != '-') {
310                 if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':') {
311                         for (p = field + length + 1; *p == ' ' || *p == '\t'; p++)
312                                 ;
313
314                         make_mime_composition_file_entry(p, attachformat);
315                 }
316         }
317
318         (void)fclose(composition_file);
319
320         /*
321          * We're ready to roll!  Run mhbuild on the composition file.
322          * Note that mhbuild is in the context as buildmimeproc.
323          */
324
325         (void)sprintf(buf, "%s %s", buildmimeproc, composition_file_name);
326
327         if (system(buf) != 0) {
328                 clean_up_temporary_files();
329                 return (NOTOK);
330         }
331
332         return (OK);
333 }
334
335 static void
336 clean_up_temporary_files(void)
337 {
338         (void)unlink(body_file_name);
339         (void)unlink(composition_file_name);
340
341         return;
342 }
343
344 static int
345 get_line(void)
346 {
347         int c;  /* current character */
348         int n;  /* number of bytes in buffer */
349         char *p;  /* buffer pointer */
350
351         /*
352          * Get a line from the input file, growing the field buffer as
353          * needed.  We do this so that we can fit an entire line in the
354          * buffer making it easy to do a string comparison on both the
355          * field name and the field body which might be a long path name.
356          */
357
358         for (n = 0, p = field; (c = getc(draft_file)) != EOF; *p++ = c) {
359                 if (c == '\n' && (c = getc(draft_file)) != ' ' && c != '\t') {
360                         (void)ungetc(c, draft_file);
361                         c = '\n';
362                         break;
363                 }
364
365                 if (++n >= field_size - 1) {
366                         field = (char *)mh_xrealloc((void *)field, field_size += 256);
367
368                         p = field + n - 1;
369                 }
370         }
371
372         /*
373          * NUL-terminate the field..
374          */
375
376         *p = '\0';
377
378         return (c);
379 }
380
381 static void
382 make_mime_composition_file_entry(char *file_name, int attachformat)
383 {
384         int binary;  /* binary character found flag */
385         int c;  /* current character */
386         char cmd[MAXPATHLEN + 6];  /* file command buffer */
387         char *content_type;  /* mime content type */
388         FILE *fp;  /* content and pipe file pointer */
389         struct node *np;  /* context scan node pointer */
390         char *p;  /* miscellaneous string pointer */
391         struct stat st;  /* file status buffer */
392
393         content_type = (char *)0;
394
395         /*
396          * Check the file name for a suffix.  Scan the context for that
397          * suffix on a mhshow-suffix- entry.  We use these entries to
398          * be compatible with mhshow, and there's no reason to make the
399          * user specify each suffix twice.  Context entries of the form
400          * "mhshow-suffix-contenttype" in the name have the suffix in
401          * the field, including the dot.
402          */
403
404         if ((p = strrchr(file_name, '.')) != (char *)0) {
405                 for (np = m_defs; np; np = np->n_next) {
406                         if (strncasecmp(np->n_name, "mhshow-suffix-", 14) == 0 && mh_strcasecmp(p, np->n_field) == 0) {
407                                 content_type = np->n_name + 14;
408                                 break;
409                         }
410                 }
411         }
412
413         /*
414          * No content type was found, either because there was no matching
415          * entry in the context or because the file name has no suffix.
416          * Open the file and check for non-ASCII characters.  Choose the
417          * content type based on this check.
418          */
419
420         if (content_type == (char *)0) {
421                 if ((fp = fopen(file_name, "r")) == (FILE *)0) {
422                         clean_up_temporary_files();
423                         adios((char *)0, "unable to access file \"%s\"", file_name);
424                 }
425
426                 binary = 0;
427
428                 while ((c = getc(fp)) != EOF) {
429                         if (c > 127 || c < 0) {
430                                 binary = 1;
431                                 break;
432                         }
433                 }
434
435                 (void)fclose(fp);
436
437                 content_type = binary ? "application/octet-stream" : "text/plain";
438         }
439
440         /*
441          * Make sure that the attachment file exists and is readable.
442          * Append a mhbuild directive to the draft file.  This starts with
443          * the content type.  Append a file name attribute and a private
444          * x-unix-mode attribute.  Also append a description obtained
445          * (if possible) by running the "file" command on the file.
446          */
447
448         if (stat(file_name, &st) == -1 || access(file_name, R_OK) != 0) {
449                 clean_up_temporary_files();
450                 adios((char *)0, "unable to access file \"%s\"", file_name);
451         }
452
453         switch (attachformat) {
454         case 0:
455                 /* Insert name, file mode, and Content-Id. */
456                 (void)fprintf(composition_file, "#%s; name=\"%s\"; x-unix-mode=0%.3ho",
457                         content_type, ((p = strrchr(file_name, '/')) == (char *)0) ? file_name : p + 1, (unsigned short)(st.st_mode & 0777));
458
459                 if (strlen(file_name) > MAXPATHLEN) {
460                         clean_up_temporary_files();
461                         adios((char *)0, "attachment file name `%s' too long.", file_name);
462                 }
463
464                 (void)sprintf(cmd, "file '%s'", file_name);
465
466                 if ((fp = popen(cmd, "r")) != (FILE *)0 && fgets(cmd, sizeof (cmd), fp) != (char *)0) {
467                         *strchr(cmd, '\n') = '\0';
468
469                         /*
470                          *  The output of the "file" command is of the form
471                          *
472                          *       file: description
473                          *
474                          *  Strip off the "file:" and subsequent white space.
475                          */
476
477                         for (p = cmd; *p != '\0'; p++) {
478                                 if (*p == ':') {
479                                         for (p++; *p != '\0'; p++) {
480                                                 if (*p != '\t')
481                                                         break;
482                                         }
483                                         break;
484                                 }
485                         }
486
487                         if (*p != '\0')
488                                 /* Insert Content-Description. */
489                                 (void)fprintf(composition_file, " [ %s ]", p);
490
491                         (void)pclose(fp);
492                 }
493
494                 break;
495         case 1:
496                 if (stringdex (m_maildir(invo_name), file_name) == 0) {
497                         /*
498                          * Content had been placed by send into a temp file.
499                          * Don't generate Content-Disposition header, because
500                          * it confuses Microsoft Outlook, Build 10.0.6626, at
501                          * least.
502                          */
503                         (void) fprintf (composition_file, "#%s <>", content_type);
504                 } else {
505                         /* Suppress Content-Id, insert simple Content-Disposition. */
506                         (void) fprintf (composition_file,
507                                 "#%s; name=\"%s\" <>{attachment}",
508                                 content_type,
509                                 ((p = strrchr(file_name, '/')) == (char *)0) ?
510                                 file_name : p + 1);
511                 }
512
513                 break;
514         case 2:
515                 if (stringdex (m_maildir(invo_name), file_name) == 0) {
516                         /*
517                          * Content had been placed by send into a temp file.
518                          * Don't generate Content-Disposition header, because
519                          * it confuses Microsoft Outlook, Build 10.0.6626, at
520                          * least.
521                          */
522                         (void) fprintf (composition_file, "#%s <>", content_type);
523                 } else {
524                         /*
525                          * Suppress Content-Id, insert Content-Disposition
526                          * with modification date.
527                          */
528                         (void) fprintf (composition_file,
529                                 "#%s; name=\"%s\" <>{attachment; modification-date=\"%s\"}",
530                                 content_type,
531                                 ((p = strrchr(file_name, '/')) == (char *)0) ?
532                                 file_name : p + 1, dtime (&st.st_mtime, 0));
533                 }
534
535                 break;
536         default:
537                 adios ((char *)0, "unsupported attachformat %d", attachformat);
538         }
539
540         /*
541          * Finish up with the file name.
542          */
543
544         (void)fprintf(composition_file, " %s\n", file_name);
545
546         return;
547 }
548
549 /*
550  * Annotate original message, and
551  * call `postproc' to send message.
552  */
553
554 static int
555 sendaux (char **vec, int vecp, char *drft, struct stat *st)
556 {
557         pid_t child_id;
558         int i, status, fd, fd2;
559         char backup[BUFSIZ], buf[BUFSIZ];
560
561         fd = pushsw ? tmp_fd () : NOTOK;
562         fd2 = NOTOK;
563
564         vec[vecp++] = drft;
565         if (annotext) {
566                 if ((fd2 = tmp_fd ()) != NOTOK) {
567                         vec[vecp++] = "-idanno";
568                         snprintf (buf, sizeof(buf), "%d", fd2);
569                         vec[vecp++] = buf;
570                 } else {
571                         admonish (NULL, "unable to create file for annotation list");
572                 }
573         }
574         if (distfile && distout (drft, distfile, backup) == NOTOK)
575                 done (1);
576         vec[vecp] = NULL;
577
578         for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
579                 sleep (5);
580
581         switch (child_id) {
582         case -1:
583                 /* oops -- fork error */
584                 adios ("fork", "unable to");
585                 break;  /* NOT REACHED */
586
587         case 0:
588                 /*
589                  * child process -- send it
590                  *
591                  * If fd is ok, then we are pushing and fd points to temp
592                  * file, so capture anything on stdout and stderr there.
593                  */
594                 if (fd != NOTOK) {
595                         dup2 (fd, fileno (stdout));
596                         dup2 (fd, fileno (stderr));
597                         close (fd);
598                 }
599                 execvp (postproc, vec);
600                 fprintf (stderr, "unable to exec ");
601                 perror (postproc);
602                 _exit (-1);
603                 break;  /* NOT REACHED */
604
605         default:
606                 /*
607                  * parent process -- wait for it
608                  */
609                 if ((status = pidwait(child_id, NOTOK)) == OK) {
610                         if (annotext && fd2 != NOTOK)
611                                 anno (fd2, st);
612                 } else {
613                         /*
614                          * If postproc failed, and we have good fd (which means
615                          * we pushed), then mail error message (and possibly the
616                          * draft) back to the user.
617                          */
618                         if (fd != NOTOK) {
619                                 alert (drft, fd);
620                                 close (fd);
621                         } else {
622                                 advise (NULL, "message not delivered to anyone");
623                         }
624                         if (annotext && fd2 != NOTOK)
625                                 close (fd2);
626                         if (distfile) {
627                                 unlink (drft);
628                                 if (rename (backup, drft) == NOTOK)
629                                         advise (drft, "unable to rename %s to", backup);
630                         }
631                 }
632                 break;
633         }
634
635         return status;
636 }
637
638
639 /*
640  * Mail error notification (and possibly a copy of the
641  * message) back to the user, using the mailproc
642  */
643
644 static void
645 alert (char *file, int out)
646 {
647         pid_t child_id;
648         int i, in;
649         char buf[BUFSIZ];
650
651         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
652                 sleep (5);
653
654         switch (child_id) {
655                 case NOTOK:
656                         /* oops -- fork error */
657                         advise ("fork", "unable to");
658
659                 case OK:
660                         /* child process -- send it */
661                         SIGNAL (SIGHUP, SIG_IGN);
662                         SIGNAL (SIGINT, SIG_IGN);
663                         SIGNAL (SIGQUIT, SIG_IGN);
664                         SIGNAL (SIGTERM, SIG_IGN);
665                         if (forwsw) {
666                                 if ((in = open (file, O_RDONLY)) == NOTOK) {
667                                         admonish (file, "unable to re-open");
668                                 } else {
669                                         lseek (out, (off_t) 0, SEEK_END);
670                                         strncpy (buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
671                                         write (out, buf, strlen (buf));
672                                         strncpy (buf, "\n------- Unsent Draft\n\n", sizeof(buf));
673                                         write (out, buf, strlen (buf));
674                                         cpydgst (in, out, file, "temporary file");
675                                         close (in);
676                                         strncpy (buf, "\n------- End of Unsent Draft\n", sizeof(buf));
677                                         write (out, buf, strlen (buf));
678                                         if (rename (file, strncpy (buf, m_backup (file), sizeof(buf))) == NOTOK)
679                                                 admonish (buf, "unable to rename %s to", file);
680                                 }
681                         }
682                         lseek (out, (off_t) 0, SEEK_SET);
683                         dup2 (out, fileno (stdin));
684                         close (out);
685                         /* create subject for error notification */
686                         snprintf (buf, sizeof(buf), "send failed on %s",
687                                                 forwsw ? "enclosed draft" : file);
688
689                         execlp (mailproc, r1bindex (mailproc, '/'), getusername (),
690                                         "-subject", buf, NULL);
691                         fprintf (stderr, "unable to exec ");
692                         perror (mailproc);
693                         _exit (-1);
694
695                 default:  /* no waiting... */
696                         break;
697         }
698 }
699
700
701 static int
702 tmp_fd (void)
703 {
704         int fd;
705         char *tfile = NULL;
706
707         tfile = m_mktemp2(NULL, invo_name, &fd, NULL);
708         if (tfile == NULL) return NOTOK;
709         fchmod(fd, 0600);
710
711         if (debugsw)
712                 advise (NULL, "temporary file %s selected", tfile);
713         else
714                 if (unlink (tfile) == NOTOK)
715                         advise (tfile, "unable to remove");
716
717         return fd;
718 }
719
720
721 static void
722 anno (int fd, struct stat *st)
723 {
724         pid_t child_id;
725         sigset_t set, oset;
726         static char *cwd = NULL;
727         struct stat st2;
728
729         if (altmsg &&
730                         (stat (altmsg, &st2) == NOTOK
731                                 || st->st_mtime != st2.st_mtime
732                                 || st->st_dev != st2.st_dev
733                                 || st->st_ino != st2.st_ino)) {
734                 if (debugsw)
735                         admonish (NULL, "$mhaltmsg mismatch");
736                 return;
737         }
738
739         child_id = debugsw ? NOTOK : fork ();
740         switch (child_id) {
741                 case NOTOK:  /* oops */
742                         if (!debugsw)
743                                 advise (NULL,
744                                         "unable to fork, so doing annotations by hand...");
745                         if (cwd == NULL)
746                                 cwd = getcpy (pwd ());
747
748                 case OK:
749                         /* block a few signals */
750                         sigemptyset (&set);
751                         sigaddset (&set, SIGHUP);
752                         sigaddset (&set, SIGINT);
753                         sigaddset (&set, SIGQUIT);
754                         sigaddset (&set, SIGTERM);
755                         SIGPROCMASK (SIG_BLOCK, &set, &oset);
756
757                         annoaux (fd);
758                         if (child_id == OK)
759                                 _exit (0);
760
761                         /* reset the signal mask */
762                         SIGPROCMASK (SIG_SETMASK, &oset, &set);
763
764                         chdir (cwd);
765                         break;
766
767                 default:  /* no waiting... */
768                         close (fd);
769                         break;
770         }
771 }
772
773
774 static void
775 annoaux (int fd)
776 {
777         int fd2, fd3, msgnum;
778         char *cp, *folder, *maildir;
779         char buffer[BUFSIZ], **ap;
780         FILE *fp;
781         struct msgs *mp;
782
783         if ((folder = getenv ("mhfolder")) == NULL || *folder == 0) {
784                 if (debugsw)
785                         admonish (NULL, "$mhfolder not set");
786                 return;
787         }
788         maildir = m_maildir (folder);
789         if (chdir (maildir) == NOTOK) {
790                 if (debugsw)
791                         admonish (maildir, "unable to change directory to");
792                 return;
793         }
794         if (!(mp = folder_read (folder))) {
795                 if (debugsw)
796                         admonish (NULL, "unable to read folder %s", folder);
797                 return;
798         }
799
800         /* check for empty folder */
801         if (mp->nummsg == 0) {
802                 if (debugsw)
803                         admonish (NULL, "no messages in %s", folder);
804                 goto oops;
805         }
806
807         if ((cp = getenv ("mhmessages")) == NULL || *cp == 0) {
808                 if (debugsw)
809                         admonish (NULL, "$mhmessages not set");
810                 goto oops;
811         }
812         if (!debugsw  /* MOBY HACK... */
813                         && pushsw
814                         && (fd3 = open ("/dev/null", O_RDWR)) != NOTOK
815                         && (fd2 = dup (fileno (stderr))) != NOTOK) {
816                 dup2 (fd3, fileno (stderr));
817                 close (fd3);
818         }
819         else
820                 fd2 = NOTOK;
821         for (ap = brkstring (cp = getcpy (cp), " ", NULL); *ap; ap++)
822                 m_convert (mp, *ap);
823         free (cp);
824         if (fd2 != NOTOK)
825                 dup2 (fd2, fileno (stderr));
826         if (mp->numsel == 0) {
827                 if (debugsw)
828                         admonish (NULL, "no messages to annotate");
829                 goto oops;
830         }
831
832         lseek (fd, (off_t) 0, SEEK_SET);
833         if ((fp = fdopen (fd, "r")) == NULL) {
834                 if (debugsw)
835                         admonish (NULL, "unable to fdopen annotation list");
836                 goto oops;
837         }
838         cp = NULL;
839         while (fgets (buffer, sizeof(buffer), fp) != NULL)
840                 cp = add (buffer, cp);
841         fclose (fp);
842
843         if (debugsw)
844                 advise (NULL, "annotate%s with %s: \"%s\"",
845                                 inplace ? " inplace" : "", annotext, cp);
846         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
847                 if (is_selected(mp, msgnum)) {
848                         if (debugsw)
849                                 advise (NULL, "annotate message %d", msgnum);
850                         annotate (m_name (msgnum), annotext, cp, inplace, 1, -2, 0);
851                 }
852         }
853
854         free (cp);
855
856 oops:
857         folder_free (mp);  /* free folder/message structure */
858 }
859
860
861 static void
862 armed_done (int status)
863 {
864         longjmp (env, status ? status : NOTOK);
865
866         exit (status);
867 }