5054273b61b762649a13ff5068bdae7dd536b0ce
[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 int sendaux(char **, int, char *, struct stat *);
59 static int attach(char *);
60 static void clean_up_temporary_files(void);
61 static int get_line(void);
62 static void make_mime_composition_file_entry(char *);
63
64
65 static struct swit switches[] = {
66 #define ALIASW  0
67         { "alias aliasfile", 0 },
68 #define DEBUGSW  1
69         { "debug", -5 },
70 #define FORWSW  2
71         { "forward", 0 },
72 #define NFORWSW  3
73         { "noforward", 0 },
74 #define PUSHSW  4
75         { "push", 0 },
76 #define NPUSHSW  5
77         { "nopush", 0 },
78 #define VERBSW  6
79         { "verbose", 0 },
80 #define NVERBSW  7
81         { "noverbose", 0 },
82 #define WATCSW  8
83         { "watch", 0 },
84 #define NWATCSW  9
85         { "nowatch", 0 },
86 #define VERSIONSW  10
87         { "version", 0 },
88 #define HELPSW  11
89         { "help", 0 },
90         { NULL, 0 }
91 };
92
93
94 int
95 main(int argc, char **argv)
96 {
97         int msgp = 0, nfiles = 0, distsw = 0, vecp = 1;
98         int msgnum, status;
99         int in, out;
100         char *cp, *maildir = NULL;
101         char buf[BUFSIZ], **ap, **argp, **arguments;
102         char *msgs[MAXARGS], *vec[MAXARGS];
103         char *files[MAXARGS];
104         struct msgs *mp;
105         struct stat st;
106         struct stat st2;
107
108
109 #ifdef LOCALE
110         setlocale(LC_ALL, "");
111 #endif
112         invo_name = mhbasename(argv[0]);
113
114         /* read user profile/context */
115         context_read();
116
117         arguments = getarguments(invo_name, argc, argv, 1);
118         argp = arguments;
119
120         vec[vecp++] = "-library";
121         vec[vecp++] = getcpy(toabsdir("+"));
122
123         while ((cp = *argp++)) {
124                 if (*cp == '-') {
125                         switch (smatch(++cp, switches)) {
126                         case AMBIGSW:
127                                 ambigsw(cp, switches);
128                                 done(1);
129                         case UNKWNSW:
130                                 adios(NULL, "-%s unknown\n", cp);
131
132                         case HELPSW:
133                                 snprintf(buf, sizeof(buf),
134                                                 "%s [file] [switches]",
135                                                 invo_name);
136                                 print_help(buf, switches, 1);
137                                 done(1);
138                         case VERSIONSW:
139                                 print_version(invo_name);
140                                 done(1);
141
142                         case PUSHSW:
143                                 pushsw++;
144                                 continue;
145                         case NPUSHSW:
146                                 pushsw = 0;
147                                 continue;
148
149                         case FORWSW:
150                                 forwsw++;
151                                 continue;
152                         case NFORWSW:
153                                 forwsw = 0;
154                                 continue;
155
156                         case VERBSW:
157                                 verbsw++;
158                                 vec[vecp++] = --cp;
159                                 continue;
160                         case NVERBSW:
161                                 verbsw = 0;
162                                 vec[vecp++] = --cp;
163                                 continue;
164
165                         case DEBUGSW:
166                                 debugsw++;  /* fall */
167                         case WATCSW:
168                         case NWATCSW:
169                                 vec[vecp++] = --cp;
170                                 continue;
171
172                         case ALIASW:
173                                 vec[vecp++] = --cp;
174                                 if (!(cp = *argp++) || *cp == '-') {
175                                         adios(NULL, "missing argument to %s",
176                                                         argp[-2]);
177                                 }
178                                 vec[vecp++] = cp;
179                                 continue;
180
181                         }
182                 } else {
183                         if (*cp == '/') {
184                                 files[nfiles++] = cp;
185                         } else {
186                                 msgs[msgp++] = cp;
187                         }
188                 }
189         }
190
191         /* check for "Aliasfile:" profile entry */
192         if ((cp = context_find("Aliasfile"))) {
193                 char *dp = NULL;
194
195                 for (ap=brkstring(dp=getcpy(cp), " ", "\n"); ap && *ap; ap++) {
196                         vec[vecp++] = "-alias";
197                         vec[vecp++] = getcpy(etcpath(*ap));
198                 }
199         }
200
201         if (!msgp && !nfiles) {
202                 msgs[msgp++] = seq_cur;
203         }
204         maildir = toabsdir(draftfolder);
205
206         if (chdir(maildir) == NOTOK) {
207                 adios(maildir, "unable to change directory to");
208         }
209
210         if (!(mp = folder_read(draftfolder))) {
211                 adios(NULL, "unable to read draft folder %s", draftfolder);
212         }
213         if (mp->nummsg == 0) {
214                 adios(NULL, "no messages in draft folder %s", draftfolder);
215         }
216         /* parse all the message ranges/sequences and set SELECTED */
217         for (msgnum = 0; msgnum < msgp; msgnum++) {
218                 if (!m_convert(mp, msgs[msgnum])) {
219                         done(1);
220                 }
221         }
222         seq_setprev(mp);
223
224         for (msgp = 0, msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
225                 if (is_selected(mp, msgnum)) {
226                         files[nfiles++] = getcpy(m_name(msgnum));
227                         unset_exists(mp, msgnum);
228                 }
229         }
230
231         mp->msgflags |= SEQMOD;
232         seq_save(mp);
233
234         if (!(cp = getenv("SIGNATURE")) || !*cp) {
235                 if ((cp = context_find("signature")) && *cp) {
236                         m_putenv("SIGNATURE", cp);
237                 }
238         }
239
240         for (msgnum = 0; msgnum < nfiles; msgnum++) {
241                 if (stat(files[msgnum], &st) == NOTOK) {
242                         adios(files[msgnum], "unable to stat draft file");
243                 }
244         }
245
246         if (!(annotext = getenv("mhannotate")) || !*annotext) {
247                 annotext = NULL;
248         }
249         if (!(altmsg = getenv("mhaltmsg")) || !*altmsg) {
250                 altmsg = NULL;  /* used by dist interface - see below */
251         }
252
253         if ((cp = getenv("mhdist")) && *cp && (distsw = atoi(cp)) && altmsg) {
254                 vec[vecp++] = "-dist";
255                 if ((in = open(altmsg, O_RDONLY)) == NOTOK) {
256                         adios(altmsg, "unable to open for reading");
257                 }
258                 fstat(in, &st2);
259                 distfile = getcpy(m_mktemp2(NULL, invo_name, NULL, NULL));
260                 if ((out = creat(distfile, (int)st2.st_mode & 0777))==NOTOK) {
261                         adios(distfile, "unable to open for writing");
262                 }
263                 cpydata(in, out, altmsg, distfile);
264                 close(in);
265                 close(out);
266         } else {
267                 distfile = NULL;
268         }
269
270         if (!altmsg || stat(altmsg, &st) == NOTOK) {
271                 st.st_mtime = 0;
272                 st.st_dev = 0;
273                 st.st_ino = 0;
274         }
275         if (pushsw) {
276                 /* push a fork into the background */
277                 pid_t pid;
278
279                 switch (pid = fork()) {
280                 case -1:
281                         /* fork error */
282                         advise(NULL, "unable to fork, so can't push...");
283                         break;
284
285                 default:
286                         /* parent, just exit */
287                         done(0);
288
289                 case 0:
290                         /* child, block a few signals and continue */
291                         SIGNAL(SIGHUP, SIG_IGN);
292                         SIGNAL(SIGINT, SIG_IGN);
293                         SIGNAL(SIGQUIT, SIG_IGN);
294                         SIGNAL(SIGTERM, SIG_IGN);
295 #ifdef SIGTSTP
296                         SIGNAL(SIGTSTP, SIG_IGN);
297                         SIGNAL(SIGTTIN, SIG_IGN);
298                         SIGNAL(SIGTTOU, SIG_IGN);
299 #endif
300                         freopen("/dev/null", "r", stdin);
301                         freopen("/dev/null", "w", stdout);
302                         break;
303                 }
304         }
305         status = 0;
306         vec[0] = "spost";
307         closefds(3);
308
309         for (msgnum = 0; msgnum < nfiles; msgnum++) {
310                 switch (sendsbr(vec, vecp, files[msgnum], &st, 1)) {
311                 case DONE:
312                         done(++status);
313                 case NOTOK:
314                         status++;  /* fall */
315                 case OK:
316                         break;
317                 }
318         }
319
320         context_save();
321         done(status);
322         return 1;
323 }
324
325
326 /*
327 ** message sending back-end
328 */
329 static int
330 sendsbr(char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
331 {
332         int status;
333         char buffer[BUFSIZ];
334         char *original_draft;
335         char *p;  /* string pointer for building file name */
336
337         /*
338         ** Save the original name of the draft file.  The name of the
339         ** draft file is changed to a temporary file containing the built
340         ** MIME message if there are attachments.  We need the original
341         ** name so that it can be renamed after the message is sent.
342         */
343         original_draft = drft;
344
345         /*
346         ** Convert the draft to a MIME message.
347         ** Use the mhbuild composition file for the draft if there was
348         ** a successful conversion because that now contains the MIME
349         ** message.  A nice side effect of this is that it leaves the
350         ** original draft file untouched so that it can be retrieved
351         ** and modified if desired.
352         */
353         switch (attach(drft)) {
354         case OK:
355                 drft = composition_file_name;
356                 break;
357
358         case NOTOK:
359                 return (NOTOK);
360
361         case DONE:
362                 break;
363         }
364
365         done=armed_done;
366         switch (setjmp(env)) {
367         case OK:
368                 status = sendaux(vec, vecp, drft, st) ? NOTOK : OK;
369                 /* rename the original draft */
370                 if (rename_drft && status == OK &&
371                                 rename(original_draft, strncpy(buffer,
372                                 m_backup(original_draft), sizeof(buffer)))
373                                 == NOTOK) {
374                         advise(buffer, "unable to rename %s to", drft);
375                 }
376                 break;
377
378         default:
379                 status = DONE;
380                 break;
381         }
382
383         done=exit;
384         if (distfile) {
385                 unlink(distfile);
386         }
387
388         /*
389         ** Get rid of any temporary files that we created for attachments.
390         ** Also get rid of the renamed composition file that mhbuild
391         ** leaves as a turd.  It looks confusing, but we use the body
392         ** file name to help build the renamed composition file name.
393         */
394         if (drft == composition_file_name) {
395                 clean_up_temporary_files();
396
397                 if (strlen(composition_file_name) >=
398                                 sizeof (composition_file_name) - 6) {
399                         advise(NULL, "unable to remove original composition file.");
400                 } else {
401                         if (!(p = strrchr(composition_file_name, '/'))) {
402                                 p = composition_file_name;
403                         } else {
404                                 p++;
405                         }
406                         strcpy(body_file_name, p);
407                         *p++ = ',';
408                         strcpy(p, body_file_name);
409                         strcat(p, ".orig");
410
411                         unlink(composition_file_name);
412                 }
413         }
414
415         return status;
416 }
417
418 static int
419 attach(char *draft_file_name)
420 {
421         char buf[MAXPATHLEN + 6];
422         int c;
423         int has_attachment;
424         int has_body;
425         int non_ascii; /* msg body contains non-ASCII chars */
426         int length;  /* length of attachment header field name */
427         char *p;
428
429         if (!(draft_file = fopen(draft_file_name, "r"))) {
430                 adios(NULL, "can't open draft file `%s'.", draft_file_name);
431         }
432
433         /* We'll grow the buffer as needed. */
434         field = (char *)mh_xmalloc(field_size = 256);
435
436         /*
437         ** Scan the draft file for an attachment header field name.
438         */
439         length = strlen(attach_hdr);
440         has_attachment = 0;
441         while (get_line() != EOF && *field != '\0' && *field != '-') {
442                 if (strncasecmp(field, attach_hdr, length)==0 &&
443                                 field[length] == ':') {
444                         has_attachment = 1;
445                 }
446         }
447
448         /*
449         ** Look for at least one non-blank line in the body of the
450         ** message which indicates content in the body.
451         ** Check if body contains at least one non-blank (= not empty)
452         ** and if it contains any non-ASCII chars (= need MIME).
453         */
454         has_body = 0;
455         non_ascii = 0;
456         while (get_line() != EOF) {
457                 for (p = field; *p != '\0'; p++) {
458                         if (*p != ' ' && *p != '\t') {
459                                 has_body = 1;
460                         }
461                         if (*p > 127 || *p < 0) {
462                                 non_ascii = 1;
463                         }
464                 }
465                 if (has_body && non_ascii) {
466                         break;  /* that's been already enough information */
467                 }
468         }
469
470         if (!has_attachment && non_ascii==0) {
471                 /* We don't need to convert it to MIME. */
472                 return DONE;
473         }
474
475         /*
476         ** Else: mimify
477         */
478
479         /* Make names for the temporary files.  */
480         strncpy(body_file_name,
481                         m_mktemp(toabsdir(invo_name), NULL, NULL),
482                         sizeof (body_file_name));
483         strncpy(composition_file_name,
484                         m_mktemp(toabsdir(invo_name), NULL, NULL),
485                         sizeof (composition_file_name));
486
487         if (has_body) {
488                 body_file = fopen(body_file_name, "w");
489         }
490         composition_file = fopen(composition_file_name, "w");
491
492         if ((has_body && !body_file) || !composition_file) {
493                 clean_up_temporary_files();
494                 adios(NULL, "unable to open all of the temporary files.");
495         }
496
497         /* Copy non-attachment header fields to the temp composition file. */
498         rewind(draft_file);
499         while (get_line() != EOF && *field && *field != '-') {
500                 if (strncasecmp(field, attach_hdr, length) != 0 ||
501                                 field[length] != ':') {
502                         fprintf(composition_file, "%s\n", field);
503                 }
504         }
505         fputs("--------\n", composition_file);
506
507         if (has_body) {
508                 /* Copy the message body to the temporary file. */
509                 while ((c = getc(draft_file)) != EOF) {
510                         putc(c, body_file);
511                 }
512                 fclose(body_file);
513
514                 /* Add a mhbuild MIME composition file line for the body */
515                 /* charset will be discovered/guessed by mhbuild */
516                 fprintf(composition_file, "#text/plain %s\n", body_file_name);
517         }
518
519         /*
520         ** Now, go back to the beginning of the draft file and look for
521         ** header fields that specify attachments.  Add a mhbuild MIME
522         ** composition file for each.
523         */
524         rewind(draft_file);
525         while (get_line() != EOF && *field && *field != '-') {
526                 if (strncasecmp(field, attach_hdr, length) == 0 &&
527                                 field[length] == ':') {
528                         for (p = field+length+1; *p==' ' || *p=='\t'; p++) {
529                                 continue;
530                         }
531                         if (*p == '+') {
532                                 /* forwarded message */
533                                 fprintf(composition_file, "#forw [forwarded message(s)] %s\n", p);
534                         } else {
535                                 /* regular attachment */
536                                 make_mime_composition_file_entry(p);
537                         }
538                 }
539         }
540         fclose(composition_file);
541
542         /* We're ready to roll! */
543         sprintf(buf, "mhbuild %s", composition_file_name);
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 `spost' 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 status, fd;
648         char backup[BUFSIZ];
649
650         /*
651         ** fd collects the output of spost, 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         switch (child_id = fork()) {
663         case -1:
664                 /* oops -- fork error */
665                 adios("fork", "unable to");
666                 break;  /* NOT REACHED */
667
668         case 0:
669                 /*
670                 ** child process -- send it
671                 **
672                 ** If fd is ok, then we are pushing and fd points to temp
673                 ** file, so capture anything on stdout and stderr there.
674                 */
675                 if (fd != NOTOK) {
676                         dup2(fd, fileno(stdout));
677                         dup2(fd, fileno(stderr));
678                         close(fd);
679                 }
680                 execvp(*vec, vec);
681                 fprintf(stderr, "unable to exec ");
682                 perror(*vec);
683                 _exit(-1);
684                 break;  /* NOT REACHED */
685
686         default:
687                 /* parent process -- wait for it */
688                 if ((status = pidwait(child_id, NOTOK)) == OK) {
689                         if (annotext) {
690                                 anno(st);
691                         }
692                 } else {
693                         /*
694                         ** If spost failed, and we have good fd (which
695                         ** means we pushed), then mail error message
696                         ** (and possibly the draft) back to the user.
697                         */
698                         if (fd != NOTOK) {
699                                 alert(drft, fd);
700                                 close(fd);
701                         } else {
702                                 advise(NULL, "message not delivered to anyone");
703                         }
704                         if (distfile) {
705                                 unlink(drft);
706                                 if (rename(backup, drft) == NOTOK) {
707                                         advise(drft, "unable to rename %s to",
708                                                         backup);
709                                 }
710                         }
711                 }
712                 break;
713         }
714
715         return status;
716 }
717
718
719 /*
720 ** Mail error notification (and possibly a copy of the
721 ** message) back to the user, using mhmail(1).
722 */
723 static void
724 alert(char *file, int out)
725 {
726         pid_t child_id;
727         int in;
728         char buf[BUFSIZ];
729
730         switch (child_id = fork()) {
731         case NOTOK:
732                 /* oops -- fork error */
733                 advise("fork", "unable to");
734
735         case OK:
736                 /* child process -- send it */
737                 SIGNAL(SIGHUP, SIG_IGN);
738                 SIGNAL(SIGINT, SIG_IGN);
739                 SIGNAL(SIGQUIT, SIG_IGN);
740                 SIGNAL(SIGTERM, SIG_IGN);
741                 if (forwsw) {
742                         if ((in = open(file, O_RDONLY)) == NOTOK) {
743                                 admonish(file, "unable to re-open");
744                         } else {
745                                 lseek(out, (off_t) 0, SEEK_END);
746                                 strncpy(buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
747                                 write(out, buf, strlen(buf));
748                                 strncpy(buf, "\n------- Unsent Draft\n\n", sizeof(buf));
749                                 write(out, buf, strlen(buf));
750                                 cpydgst(in, out, file, "temporary file");
751                                 close(in);
752                                 strncpy(buf, "\n------- End of Unsent Draft\n", sizeof(buf));
753                                 write(out, buf, strlen(buf));
754                                 if (rename(file, strncpy(buf, m_backup(file), sizeof(buf))) == NOTOK) {
755                                         admonish(buf, "unable to rename %s to", file);
756                                 }
757                         }
758                 }
759                 lseek(out, (off_t) 0, SEEK_SET);
760                 dup2(out, fileno(stdin));
761                 close(out);
762                 /* create subject for error notification */
763                 snprintf(buf, sizeof(buf), "send failed on %s",
764                                 forwsw ? "enclosed draft" : file);
765                 execlp("mhmail", "mhmail", getusername(),
766                                 "-subject", buf, NULL);
767                 fprintf(stderr, "unable to exec ");
768                 perror("mhmail");
769                 _exit(-1);
770
771         default:  /* no waiting... */
772                 break;
773         }
774 }
775
776
777 static int
778 tmp_fd(void)
779 {
780         int fd;
781         char *tfile = NULL;
782
783         tfile = m_mktemp2(NULL, invo_name, &fd, NULL);
784         if (tfile == NULL) return NOTOK;
785         fchmod(fd, 0600);
786
787         if (debugsw) {
788                 advise(NULL, "temporary file %s selected", tfile);
789         } else {
790                 if (unlink(tfile) == NOTOK) {
791                         advise(tfile, "unable to remove");
792                 }
793         }
794
795         return fd;
796 }
797
798
799 static void
800 anno(struct stat *st)
801 {
802         struct stat st2;
803         char *msgs, *folder;
804         char buf[BUFSIZ];
805
806         if (altmsg && (stat(altmsg, &st2) == NOTOK ||
807                         st->st_mtime != st2.st_mtime ||
808                         st->st_dev != st2.st_dev ||
809                         st->st_ino != st2.st_ino)) {
810                 if (debugsw) {
811                         admonish(NULL, "$mhaltmsg mismatch");
812                 }
813                 return;
814         }
815
816         if (!(folder = getenv("mhfolder")) || !*folder) {
817                 if (debugsw) {
818                         admonish(NULL, "$mhfolder not set");
819                 }
820                 return;
821         }
822         if (!(msgs = getenv("mhmessages")) || !*msgs) {
823                 if (debugsw) {
824                         admonish(NULL, "$mhmessages not set");
825                 }
826                 return;
827         }
828         if (debugsw) {
829                 advise(NULL, "annotate as `%s': %s %s", annotext,
830                                 folder, msgs);
831         }
832         snprintf(buf, sizeof buf, "anno -comp '%s' '+%s' %s",
833                         annotext, folder, msgs);
834         if (system(buf) != 0) {
835                 advise(NULL, "unable to annotate");
836         }
837 }
838
839
840 static void
841 armed_done(int status)
842 {
843         longjmp(env, status ? status : NOTOK);
844
845         exit(status);
846 }