Rearranged whitespace (and comments) in all the code!
[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 splitsw = -1;
34 int unique  = 0;
35 int verbsw  = 0;
36
37 char *altmsg   = NULL;  /*  .. */
38 char *annotext = NULL;
39 char *distfile = NULL;
40
41 static jmp_buf env;
42
43 static char body_file_name[MAXPATHLEN + 1];  /* name of temporary file for body content */
44 static char composition_file_name[MAXPATHLEN + 1];  /* name of mhbuild composition temporary file */
45 static int field_size;  /* size of header field buffer */
46 static char *field;  /* header field buffer */
47 static FILE *draft_file;  /* draft file pointer */
48 static FILE *body_file;  /* body file pointer */
49 static FILE *composition_file;  /* composition file pointer */
50
51 /*
52  * external prototypes
53  */
54 int sendsbr (char **, int, char *, struct stat *, int, char *, int);
55 char *getusername (void);
56
57 /*
58  * static prototypes
59  */
60 static void armed_done (int) NORETURN;
61 static void alert (char *, int);
62 static int tmp_fd (void);
63 static void anno (int, struct stat *);
64 static void annoaux (int);
65 static int splitmsg (char **, int, char *, struct stat *, int);
66 static int sendaux (char **, int, char *, struct stat *);
67
68 static int attach(char *, char *, int);
69 static void clean_up_temporary_files(void);
70 static int get_line(void);
71 static void make_mime_composition_file_entry(char *, int);
72
73
74 /*
75  * Entry point into (back-end) routines to send message.
76  */
77
78 int
79 sendsbr (char **vec, int vecp, char *drft, struct stat *st,
80         int rename_drft, char *attachment_header_field_name, int attachformat)
81 {
82         int status;
83         char buffer[BUFSIZ], file[BUFSIZ];
84         struct stat sts;
85         char *original_draft;  /* name of original draft file */
86         char *p;  /* string pointer for building file name */
87
88         /*
89          * Save the original name of the draft file.  The name of the
90          * draft file is changed to a temporary file containing the built
91          * MIME message if there are attachments.  We need the original
92          * name so that it can be renamed after the message is sent.
93          */
94
95         original_draft = drft;
96
97         /*
98          * There might be attachments if a header field name for
99          * attachments is supplied.  Convert the draft to a MIME message.
100          * Use the mhbuild composition file for the draft if there was
101          * a successful conversion because that now contains the MIME
102          * message.  A nice side effect of this is that it leaves the
103          * original draft file untouched so that it can be retrieved
104          * and modified if desired.
105          */
106
107         if (attachment_header_field_name != (char *)0) {
108                 switch (attach(attachment_header_field_name, drft, attachformat)) {
109                 case OK:
110                         drft = composition_file_name;
111                         break;
112
113                 case NOTOK:
114                         return (NOTOK);
115
116                 case DONE:
117                         break;
118                 }
119         }
120
121         done=armed_done;
122         switch (setjmp (env)) {
123         case OK:
124                 /*
125                  * If given -push and -unique (which is undocumented), then
126                  * rename the draft file.  I'm not quite sure why.
127                  */
128                 if (pushsw && unique) {
129                         char *cp = m_mktemp2(drft, invo_name, NULL, NULL);
130                         if (cp == NULL) {
131                                 adios ("sendsbr", "unable to create temporary file");
132                         }
133                         if (rename (drft, strncpy(file, cp, sizeof(file))) == NOTOK)
134                                 adios (file, "unable to rename %s to", drft);
135                         drft = file;
136                 }
137
138                 /*
139                  * Check if we need to split the message into
140                  * multiple messages of type "message/partial".
141                  */
142                 if (splitsw >= 0 && !distfile && stat (drft, &sts) != NOTOK
143                                 && sts.st_size >= CPERMSG) {
144                         status = splitmsg (vec, vecp, drft, st, splitsw) ? NOTOK : OK;
145                 } else {
146                         status = sendaux (vec, vecp, drft, st) ? NOTOK : OK;
147                 }
148
149                 /* rename the original draft */
150                 if (rename_drft && status == OK &&
151                                 rename (original_draft, strncpy (buffer, m_backup (original_draft), sizeof(buffer))) == NOTOK)
152                         advise (buffer, "unable to rename %s to", drft);
153                 break;
154
155         default:
156                 status = DONE;
157                 break;
158         }
159
160         done=exit;
161         if (distfile)
162                 unlink (distfile);
163
164         /*
165          * Get rid of any temporary files that we created for attachments.
166          * Also get rid of the renamed composition file that mhbuild
167          * leaves as a turd.  It looks confusing, but we use the body
168          * file name to help build the renamed composition file name.
169          */
170
171         if (drft == composition_file_name) {
172                 clean_up_temporary_files();
173
174                 if (strlen(composition_file_name) >= sizeof (composition_file_name) - 6)
175                         advise((char *)0, "unable to remove original composition file.");
176
177                 else {
178                         if ((p = strrchr(composition_file_name, '/')) == (char *)0)
179                                 p = composition_file_name;
180                         else
181                                 p++;
182
183                         (void)strcpy(body_file_name, p);
184                         *p++ = ',';
185                         (void)strcpy(p, body_file_name);
186                         (void)strcat(p, ".orig");
187
188                         (void)unlink(composition_file_name);
189                 }
190         }
191
192         return status;
193 }
194
195 static int
196 attach(char *attachment_header_field_name, char *draft_file_name,
197         int attachformat)
198 {
199         char buf[MAXPATHLEN + 6];  /* miscellaneous buffer */
200         int c;  /* current character for body copy */
201         int has_attachment;  /* draft has at least one attachment */
202         int has_body;  /* draft has a message body */
203         int length;  /* length of attachment header field name */
204         char *p;  /* miscellaneous string pointer */
205
206         /*
207          * Open up the draft file.
208          */
209
210         if ((draft_file = fopen(draft_file_name, "r")) == (FILE *)0)
211                 adios((char *)0, "can't open draft file `%s'.", draft_file_name);
212
213         /*
214          *  Allocate a buffer to hold the header components as they're read in.
215          *  This buffer might need to be quite large, so we grow it as needed.
216          */
217
218         field = (char *)mh_xmalloc(field_size = 256);
219
220         /*
221          * Scan the draft file for a header field name that matches the
222          * -attach argument.  The existence of one indicates that the
223          * draft has attachments.  Bail out if there are no attachments
224          * because we're done.  Read to the end of the headers even if
225          * we have no attachments.
226          */
227
228         length = strlen(attachment_header_field_name);
229
230         has_attachment = 0;
231
232         while (get_line() != EOF && *field != '\0' && *field != '-')
233                 if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':')
234                         has_attachment = 1;
235
236         if (has_attachment == 0)
237                 return (DONE);
238
239         /*
240          * We have at least one attachment.  Look for at least one
241          * non-blank line in the body of the message which indicates
242          * content in the body.
243          */
244
245         has_body = 0;
246
247         while (get_line() != EOF) {
248                 for (p = field; *p != '\0'; p++) {
249                         if (*p != ' ' && *p != '\t') {
250                                 has_body = 1;
251                                 break;
252                         }
253                 }
254
255                 if (has_body)
256                         break;
257         }
258
259         /*
260          * Make names for the temporary files.
261          */
262
263         (void)strncpy(body_file_name,
264                 m_mktemp(m_maildir(invo_name), NULL, NULL),
265                 sizeof (body_file_name));
266         (void)strncpy(composition_file_name,
267                 m_mktemp(m_maildir(invo_name), NULL, NULL),
268                 sizeof (composition_file_name));
269
270         if (has_body)
271                 body_file = fopen(body_file_name, "w");
272
273         composition_file = fopen(composition_file_name, "w");
274
275         if ((has_body && body_file == (FILE *)0) || composition_file == (FILE *)0) {
276                 clean_up_temporary_files();
277                 adios((char *)0, "unable to open all of the temporary files.");
278         }
279
280         /*
281          * Start at the beginning of the draft file.  Copy all
282          * non-attachment header fields to the temporary composition file.
283          * Then add the dashed line separator.
284          */
285
286         rewind(draft_file);
287
288         while (get_line() != EOF && *field != '\0' && *field != '-')
289                 if (strncasecmp(field, attachment_header_field_name, length) != 0 || field[length] != ':')
290                         (void)fprintf(composition_file, "%s\n", field);
291
292         (void)fputs("--------\n", composition_file);
293
294         /*
295          * Copy the message body to a temporary file.
296          */
297
298         if (has_body) {
299                 while ((c = getc(draft_file)) != EOF)
300                                 putc(c, body_file);
301
302                 (void)fclose(body_file);
303         }
304
305         /*
306          * Add a mhbuild MIME composition file line for the body if
307          * there was one.
308          */
309
310         if (has_body)
311                 make_mime_composition_file_entry(body_file_name, attachformat);
312
313         /*
314          * Now, go back to the beginning of the draft file and look for
315          * header fields that specify attachments.  Add a mhbuild MIME
316          * composition file for each.
317          */
318
319         rewind(draft_file);
320
321         while (get_line() != EOF && *field != '\0' && *field != '-') {
322                 if (strncasecmp(field, attachment_header_field_name, length) == 0 && field[length] == ':') {
323                         for (p = field + length + 1; *p == ' ' || *p == '\t'; p++)
324                                 ;
325
326                         make_mime_composition_file_entry(p, attachformat);
327                 }
328         }
329
330         (void)fclose(composition_file);
331
332         /*
333          * We're ready to roll!  Run mhbuild on the composition file.
334          * Note that mhbuild is in the context as buildmimeproc.
335          */
336
337         (void)sprintf(buf, "%s %s", buildmimeproc, composition_file_name);
338
339         if (system(buf) != 0) {
340                 clean_up_temporary_files();
341                 return (NOTOK);
342         }
343
344         return (OK);
345 }
346
347 static void
348 clean_up_temporary_files(void)
349 {
350         (void)unlink(body_file_name);
351         (void)unlink(composition_file_name);
352
353         return;
354 }
355
356 static int
357 get_line(void)
358 {
359         int c;  /* current character */
360         int n;  /* number of bytes in buffer */
361         char *p;  /* buffer pointer */
362
363         /*
364          * Get a line from the input file, growing the field buffer as
365          * needed.  We do this so that we can fit an entire line in the
366          * buffer making it easy to do a string comparison on both the
367          * field name and the field body which might be a long path name.
368          */
369
370         for (n = 0, p = field; (c = getc(draft_file)) != EOF; *p++ = c) {
371                 if (c == '\n' && (c = getc(draft_file)) != ' ' && c != '\t') {
372                         (void)ungetc(c, draft_file);
373                         c = '\n';
374                         break;
375                 }
376
377                 if (++n >= field_size - 1) {
378                         field = (char *)mh_xrealloc((void *)field, field_size += 256);
379
380                         p = field + n - 1;
381                 }
382         }
383
384         /*
385          * NUL-terminate the field..
386          */
387
388         *p = '\0';
389
390         return (c);
391 }
392
393 static void
394 make_mime_composition_file_entry(char *file_name, int attachformat)
395 {
396         int binary;  /* binary character found flag */
397         int c;  /* current character */
398         char cmd[MAXPATHLEN + 6];  /* file command buffer */
399         char *content_type;  /* mime content type */
400         FILE *fp;  /* content and pipe file pointer */
401         struct node *np;  /* context scan node pointer */
402         char *p;  /* miscellaneous string pointer */
403         struct stat st;  /* file status buffer */
404
405         content_type = (char *)0;
406
407         /*
408          * Check the file name for a suffix.  Scan the context for that
409          * suffix on a mhshow-suffix- entry.  We use these entries to
410          * be compatible with mhshow, and there's no reason to make the
411          * user specify each suffix twice.  Context entries of the form
412          * "mhshow-suffix-contenttype" in the name have the suffix in
413          * the field, including the dot.
414          */
415
416         if ((p = strrchr(file_name, '.')) != (char *)0) {
417                 for (np = m_defs; np; np = np->n_next) {
418                         if (strncasecmp(np->n_name, "mhshow-suffix-", 14) == 0 && mh_strcasecmp(p, np->n_field) == 0) {
419                                 content_type = np->n_name + 14;
420                                 break;
421                         }
422                 }
423         }
424
425         /*
426          * No content type was found, either because there was no matching
427          * entry in the context or because the file name has no suffix.
428          * Open the file and check for non-ASCII characters.  Choose the
429          * content type based on this check.
430          */
431
432         if (content_type == (char *)0) {
433                 if ((fp = fopen(file_name, "r")) == (FILE *)0) {
434                         clean_up_temporary_files();
435                         adios((char *)0, "unable to access file \"%s\"", file_name);
436                 }
437
438                 binary = 0;
439
440                 while ((c = getc(fp)) != EOF) {
441                         if (c > 127 || c < 0) {
442                                 binary = 1;
443                                 break;
444                         }
445                 }
446
447                 (void)fclose(fp);
448
449                 content_type = binary ? "application/octet-stream" : "text/plain";
450         }
451
452         /*
453          * Make sure that the attachment file exists and is readable.
454          * Append a mhbuild directive to the draft file.  This starts with
455          * the content type.  Append a file name attribute and a private
456          * x-unix-mode attribute.  Also append a description obtained
457          * (if possible) by running the "file" command on the file.
458          */
459
460         if (stat(file_name, &st) == -1 || access(file_name, R_OK) != 0) {
461                 clean_up_temporary_files();
462                 adios((char *)0, "unable to access file \"%s\"", file_name);
463         }
464
465         switch (attachformat) {
466         case 0:
467                 /* Insert name, file mode, and Content-Id. */
468                 (void)fprintf(composition_file, "#%s; name=\"%s\"; x-unix-mode=0%.3ho",
469                         content_type, ((p = strrchr(file_name, '/')) == (char *)0) ? file_name : p + 1, (unsigned short)(st.st_mode & 0777));
470
471                 if (strlen(file_name) > MAXPATHLEN) {
472                         clean_up_temporary_files();
473                         adios((char *)0, "attachment file name `%s' too long.", file_name);
474                 }
475
476                 (void)sprintf(cmd, "file '%s'", file_name);
477
478                 if ((fp = popen(cmd, "r")) != (FILE *)0 && fgets(cmd, sizeof (cmd), fp) != (char *)0) {
479                         *strchr(cmd, '\n') = '\0';
480
481                         /*
482                          *  The output of the "file" command is of the form
483                          *
484                          *       file: description
485                          *
486                          *  Strip off the "file:" and subsequent white space.
487                          */
488
489                         for (p = cmd; *p != '\0'; p++) {
490                                 if (*p == ':') {
491                                         for (p++; *p != '\0'; p++) {
492                                                 if (*p != '\t')
493                                                         break;
494                                         }
495                                         break;
496                                 }
497                         }
498
499                         if (*p != '\0')
500                                 /* Insert Content-Description. */
501                                 (void)fprintf(composition_file, " [ %s ]", p);
502
503                         (void)pclose(fp);
504                 }
505
506                 break;
507         case 1:
508                 if (stringdex (m_maildir(invo_name), file_name) == 0) {
509                         /*
510                          * Content had been placed by send into a temp file.
511                          * Don't generate Content-Disposition header, because
512                          * it confuses Microsoft Outlook, Build 10.0.6626, at
513                          * least.
514                          */
515                         (void) fprintf (composition_file, "#%s <>", content_type);
516                 } else {
517                         /* Suppress Content-Id, insert simple Content-Disposition. */
518                         (void) fprintf (composition_file,
519                                 "#%s; name=\"%s\" <>{attachment}",
520                                 content_type,
521                                 ((p = strrchr(file_name, '/')) == (char *)0) ?
522                                 file_name : p + 1);
523                 }
524
525                 break;
526         case 2:
527                 if (stringdex (m_maildir(invo_name), file_name) == 0) {
528                         /*
529                          * Content had been placed by send into a temp file.
530                          * Don't generate Content-Disposition header, because
531                          * it confuses Microsoft Outlook, Build 10.0.6626, at
532                          * least.
533                          */
534                         (void) fprintf (composition_file, "#%s <>", content_type);
535                 } else {
536                         /*
537                          * Suppress Content-Id, insert Content-Disposition
538                          * with modification date.
539                          */
540                         (void) fprintf (composition_file,
541                                 "#%s; name=\"%s\" <>{attachment; modification-date=\"%s\"}",
542                                 content_type,
543                                 ((p = strrchr(file_name, '/')) == (char *)0) ?
544                                 file_name : p + 1, dtime (&st.st_mtime, 0));
545                 }
546
547                 break;
548         default:
549                 adios ((char *)0, "unsupported attachformat %d", attachformat);
550         }
551
552         /*
553          * Finish up with the file name.
554          */
555
556         (void)fprintf(composition_file, " %s\n", file_name);
557
558         return;
559 }
560
561 /*
562  * Split large message into several messages of
563  * type "message/partial" and send them.
564  */
565
566 static int
567 splitmsg (char **vec, int vecp, char *drft, struct stat *st, int delay)
568 {
569         int compnum, nparts, partno, state, status;
570         long pos, start;
571         time_t clock;
572         char *cp, *dp, buffer[BUFSIZ], msgid[BUFSIZ];
573         char subject[BUFSIZ];
574         char name[NAMESZ], partnum[BUFSIZ];
575         FILE *in;
576
577         if ((in = fopen (drft, "r")) == NULL)
578                 adios (drft, "unable to open for reading");
579
580         cp = dp = NULL;
581         start = 0L;
582
583         /*
584          * Scan through the message and examine the various header fields,
585          * as well as locate the beginning of the message body.
586          */
587         for (compnum = 1, state = FLD;;) {
588                 switch (state = m_getfld (state, name, buffer, sizeof(buffer), in)) {
589                         case FLD:
590                         case FLDPLUS:
591                         case FLDEOF:
592                                 compnum++;
593
594                                 /*
595                                  * This header field is discarded.
596                                  */
597                                 if (!mh_strcasecmp (name, "Message-ID")) {
598                                         while (state == FLDPLUS)
599                                                 state = m_getfld (state, name, buffer, sizeof(buffer), in);
600                                 } else if (uprf (name, XXX_FIELD_PRF)
601                                         || !mh_strcasecmp (name, VRSN_FIELD)
602                                         || !mh_strcasecmp (name, "Subject")
603                                         || !mh_strcasecmp (name, "Encrypted")) {
604                                         /*
605                                          * These header fields are copied
606                                          * to the enclosed header of the
607                                          * first message in the collection
608                                          * of message/partials.  For the
609                                          * "Subject" header field,
610                                          * we also record it, so that
611                                          * a modified version of it,
612                                          * can be copied to the header
613                                          * of each messsage/partial in
614                                          * the collection.
615                                          */
616                                         if (!mh_strcasecmp (name, "Subject")) {
617                                                 size_t sublen;
618
619                                                 strncpy (subject, buffer, BUFSIZ);
620                                                 sublen = strlen (subject);
621                                                 if (sublen > 0 && subject[sublen - 1] == '\n')
622                                                         subject[sublen - 1] = '\0';
623                                         }
624
625                                         dp = add (concat (name, ":", buffer, NULL), dp);
626                                         while (state == FLDPLUS) {
627                                                 state = m_getfld (state, name, buffer, sizeof(buffer), in);
628                                                 dp = add (buffer, dp);
629                                         }
630                                 } else {
631                                         /*
632                                          * These header fields are
633                                          * copied to the header of
634                                          * each message/partial in
635                                          * the collection.
636                                          */
637                                         cp = add (concat (name, ":", buffer, NULL), cp);
638                                         while (state == FLDPLUS) {
639                                                 state = m_getfld (state, name, buffer, sizeof(buffer), in);
640                                                 cp = add (buffer, cp);
641                                         }
642                                 }
643
644                                 if (state != FLDEOF) {
645                                         start = ftell (in) + 1;
646                                         continue;
647                                 }
648                                 /* else fall... */
649
650                    case BODY:
651                    case BODYEOF:
652                    case FILEEOF:
653                                 break;
654
655                    case LENERR:
656                    case FMTERR:
657                                 adios (NULL, "message format error in component #%d", compnum);
658
659                    default:
660                                 adios (NULL, "getfld () returned %d", state);
661                 }
662
663                 break;
664         }
665         if (cp == NULL)
666                 adios (NULL, "headers missing from draft");
667
668         nparts = 1;
669         pos = start;
670         while (fgets (buffer, sizeof(buffer) - 1, in)) {
671                 long len;
672
673                 if ((pos += (len = strlen (buffer))) > CPERMSG) {
674                         nparts++;
675                         pos = len;
676                 }
677         }
678
679         /* Only one part, nothing to split */
680         if (nparts == 1) {
681                 free (cp);
682                 if (dp)
683                         free (dp);
684
685                 fclose (in);
686                 return sendaux (vec, vecp, drft, st);
687         }
688
689         if (!pushsw) {
690                 printf ("Sending as %d Partial Messages\n", nparts);
691                 fflush (stdout);
692         }
693         status = OK;
694
695         vec[vecp++] = "-partno";
696         vec[vecp++] = partnum;
697         if (delay == 0)
698                 vec[vecp++] = "-queued";
699
700         time (&clock);
701         snprintf (msgid, sizeof(msgid), "<%d.%ld@%s>",
702                                 (int) getpid(), (long) clock, LocalName());
703
704         fseek (in, start, SEEK_SET);
705         for (partno = 1; partno <= nparts; partno++) {
706                 char tmpdrf[BUFSIZ];
707                 FILE *out;
708
709                 char *cp = m_mktemp2(drft, invo_name, NULL, &out);
710                 if (cp == NULL) {
711                         adios (drft, "unable to create temporary file for");
712                 }
713                 strncpy(tmpdrf, cp, sizeof(tmpdrf));
714                 chmod (tmpdrf, 0600);
715
716                 /*
717                  * Output the header fields
718                  */
719                 fputs (cp, out);
720                 fprintf (out, "Subject: %s (part %d of %d)\n", subject, partno, nparts);
721                 fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
722                 fprintf (out, "%s: message/partial; id=\"%s\";\n", TYPE_FIELD, msgid);
723                 fprintf (out, "\tnumber=%d; total=%d\n", partno, nparts);
724                 fprintf (out, "%s: part %d of %d\n\n", DESCR_FIELD, partno, nparts);
725
726                 /*
727                  * If this is the first in the collection, output the
728                  * header fields we are encapsulating at the beginning
729                  * of the body of the first message.
730                  */
731                 if (partno == 1) {
732                         if (dp)
733                                 fputs (dp, out);
734                         fprintf (out, "Message-ID: %s\n", msgid);
735                         fprintf (out, "\n");
736                 }
737
738                 pos = 0;
739                 for (;;) {
740                         long len;
741
742                         if (!fgets (buffer, sizeof(buffer) - 1, in)) {
743                                 if (partno == nparts)
744                                         break;
745                                 adios (NULL, "premature eof");
746                         }
747
748                         if ((pos += (len = strlen (buffer))) > CPERMSG) {
749                                 fseek (in, -len, SEEK_CUR);
750                                 break;
751                         }
752
753                         fputs (buffer, out);
754                 }
755
756                 if (fflush (out))
757                         adios (tmpdrf, "error writing to");
758
759                 fclose (out);
760
761                 if (!pushsw && verbsw) {
762                         printf ("\n");
763                         fflush (stdout);
764                 }
765
766                 /* Pause here, if a delay is specified */
767                 if (delay > 0 && 1 < partno && partno <= nparts) {
768                         if (!pushsw) {
769                                 printf ("pausing %d seconds before sending part %d...\n",
770                                                 delay, partno);
771                                 fflush (stdout);
772                         }
773                         sleep ((unsigned int) delay);
774                 }
775
776                 snprintf (partnum, sizeof(partnum), "%d", partno);
777                 status = sendaux (vec, vecp, tmpdrf, st);
778                 unlink (tmpdrf);
779                 if (status != OK)
780                         break;
781
782                 /*
783                  * This is so sendaux will only annotate
784                  * the altmsg the first time it is called.
785                  */
786                 annotext = NULL;
787         }
788
789         free (cp);
790         if (dp)
791                 free (dp);
792
793         fclose (in);  /* close the draft */
794         return status;
795 }
796
797
798 /*
799  * Annotate original message, and
800  * call `postproc' to send message.
801  */
802
803 static int
804 sendaux (char **vec, int vecp, char *drft, struct stat *st)
805 {
806         pid_t child_id;
807         int i, status, fd, fd2;
808         char backup[BUFSIZ], buf[BUFSIZ];
809
810         fd = pushsw ? tmp_fd () : NOTOK;
811         fd2 = NOTOK;
812
813         vec[vecp++] = drft;
814         if (annotext) {
815                 if ((fd2 = tmp_fd ()) != NOTOK) {
816                         vec[vecp++] = "-idanno";
817                         snprintf (buf, sizeof(buf), "%d", fd2);
818                         vec[vecp++] = buf;
819                 } else {
820                         admonish (NULL, "unable to create file for annotation list");
821                 }
822         }
823         if (distfile && distout (drft, distfile, backup) == NOTOK)
824                 done (1);
825         vec[vecp] = NULL;
826
827         for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
828                 sleep (5);
829
830         switch (child_id) {
831         case -1:
832                 /* oops -- fork error */
833                 adios ("fork", "unable to");
834                 break;  /* NOT REACHED */
835
836         case 0:
837                 /*
838                  * child process -- send it
839                  *
840                  * If fd is ok, then we are pushing and fd points to temp
841                  * file, so capture anything on stdout and stderr there.
842                  */
843                 if (fd != NOTOK) {
844                         dup2 (fd, fileno (stdout));
845                         dup2 (fd, fileno (stderr));
846                         close (fd);
847                 }
848                 execvp (postproc, vec);
849                 fprintf (stderr, "unable to exec ");
850                 perror (postproc);
851                 _exit (-1);
852                 break;  /* NOT REACHED */
853
854         default:
855                 /*
856                  * parent process -- wait for it
857                  */
858                 if ((status = pidwait(child_id, NOTOK)) == OK) {
859                         if (annotext && fd2 != NOTOK)
860                                 anno (fd2, st);
861                 } else {
862                         /*
863                          * If postproc failed, and we have good fd (which means
864                          * we pushed), then mail error message (and possibly the
865                          * draft) back to the user.
866                          */
867                         if (fd != NOTOK) {
868                                 alert (drft, fd);
869                                 close (fd);
870                         } else {
871                                 advise (NULL, "message not delivered to anyone");
872                         }
873                         if (annotext && fd2 != NOTOK)
874                                 close (fd2);
875                         if (distfile) {
876                                 unlink (drft);
877                                 if (rename (backup, drft) == NOTOK)
878                                         advise (drft, "unable to rename %s to", backup);
879                         }
880                 }
881                 break;
882         }
883
884         return status;
885 }
886
887
888 /*
889  * Mail error notification (and possibly a copy of the
890  * message) back to the user, using the mailproc
891  */
892
893 static void
894 alert (char *file, int out)
895 {
896         pid_t child_id;
897         int i, in;
898         char buf[BUFSIZ];
899
900         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
901                 sleep (5);
902
903         switch (child_id) {
904                 case NOTOK:
905                         /* oops -- fork error */
906                         advise ("fork", "unable to");
907
908                 case OK:
909                         /* child process -- send it */
910                         SIGNAL (SIGHUP, SIG_IGN);
911                         SIGNAL (SIGINT, SIG_IGN);
912                         SIGNAL (SIGQUIT, SIG_IGN);
913                         SIGNAL (SIGTERM, SIG_IGN);
914                         if (forwsw) {
915                                 if ((in = open (file, O_RDONLY)) == NOTOK) {
916                                         admonish (file, "unable to re-open");
917                                 } else {
918                                         lseek (out, (off_t) 0, SEEK_END);
919                                         strncpy (buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
920                                         write (out, buf, strlen (buf));
921                                         strncpy (buf, "\n------- Unsent Draft\n\n", sizeof(buf));
922                                         write (out, buf, strlen (buf));
923                                         cpydgst (in, out, file, "temporary file");
924                                         close (in);
925                                         strncpy (buf, "\n------- End of Unsent Draft\n", sizeof(buf));
926                                         write (out, buf, strlen (buf));
927                                         if (rename (file, strncpy (buf, m_backup (file), sizeof(buf))) == NOTOK)
928                                                 admonish (buf, "unable to rename %s to", file);
929                                 }
930                         }
931                         lseek (out, (off_t) 0, SEEK_SET);
932                         dup2 (out, fileno (stdin));
933                         close (out);
934                         /* create subject for error notification */
935                         snprintf (buf, sizeof(buf), "send failed on %s",
936                                                 forwsw ? "enclosed draft" : file);
937
938                         execlp (mailproc, r1bindex (mailproc, '/'), getusername (),
939                                         "-subject", buf, NULL);
940                         fprintf (stderr, "unable to exec ");
941                         perror (mailproc);
942                         _exit (-1);
943
944                 default:  /* no waiting... */
945                         break;
946         }
947 }
948
949
950 static int
951 tmp_fd (void)
952 {
953         int fd;
954         char *tfile = NULL;
955
956         tfile = m_mktemp2(NULL, invo_name, &fd, NULL);
957         if (tfile == NULL) return NOTOK;
958         fchmod(fd, 0600);
959
960         if (debugsw)
961                 advise (NULL, "temporary file %s selected", tfile);
962         else
963                 if (unlink (tfile) == NOTOK)
964                         advise (tfile, "unable to remove");
965
966         return fd;
967 }
968
969
970 static void
971 anno (int fd, struct stat *st)
972 {
973         pid_t child_id;
974         sigset_t set, oset;
975         static char *cwd = NULL;
976         struct stat st2;
977
978         if (altmsg &&
979                         (stat (altmsg, &st2) == NOTOK
980                                 || st->st_mtime != st2.st_mtime
981                                 || st->st_dev != st2.st_dev
982                                 || st->st_ino != st2.st_ino)) {
983                 if (debugsw)
984                         admonish (NULL, "$mhaltmsg mismatch");
985                 return;
986         }
987
988         child_id = debugsw ? NOTOK : fork ();
989         switch (child_id) {
990                 case NOTOK:  /* oops */
991                         if (!debugsw)
992                                 advise (NULL,
993                                         "unable to fork, so doing annotations by hand...");
994                         if (cwd == NULL)
995                                 cwd = getcpy (pwd ());
996
997                 case OK:
998                         /* block a few signals */
999                         sigemptyset (&set);
1000                         sigaddset (&set, SIGHUP);
1001                         sigaddset (&set, SIGINT);
1002                         sigaddset (&set, SIGQUIT);
1003                         sigaddset (&set, SIGTERM);
1004                         SIGPROCMASK (SIG_BLOCK, &set, &oset);
1005
1006                         annoaux (fd);
1007                         if (child_id == OK)
1008                                 _exit (0);
1009
1010                         /* reset the signal mask */
1011                         SIGPROCMASK (SIG_SETMASK, &oset, &set);
1012
1013                         chdir (cwd);
1014                         break;
1015
1016                 default:  /* no waiting... */
1017                         close (fd);
1018                         break;
1019         }
1020 }
1021
1022
1023 static void
1024 annoaux (int fd)
1025 {
1026         int fd2, fd3, msgnum;
1027         char *cp, *folder, *maildir;
1028         char buffer[BUFSIZ], **ap;
1029         FILE *fp;
1030         struct msgs *mp;
1031
1032         if ((folder = getenv ("mhfolder")) == NULL || *folder == 0) {
1033                 if (debugsw)
1034                         admonish (NULL, "$mhfolder not set");
1035                 return;
1036         }
1037         maildir = m_maildir (folder);
1038         if (chdir (maildir) == NOTOK) {
1039                 if (debugsw)
1040                         admonish (maildir, "unable to change directory to");
1041                 return;
1042         }
1043         if (!(mp = folder_read (folder))) {
1044                 if (debugsw)
1045                         admonish (NULL, "unable to read folder %s", folder);
1046                 return;
1047         }
1048
1049         /* check for empty folder */
1050         if (mp->nummsg == 0) {
1051                 if (debugsw)
1052                         admonish (NULL, "no messages in %s", folder);
1053                 goto oops;
1054         }
1055
1056         if ((cp = getenv ("mhmessages")) == NULL || *cp == 0) {
1057                 if (debugsw)
1058                         admonish (NULL, "$mhmessages not set");
1059                 goto oops;
1060         }
1061         if (!debugsw  /* MOBY HACK... */
1062                         && pushsw
1063                         && (fd3 = open ("/dev/null", O_RDWR)) != NOTOK
1064                         && (fd2 = dup (fileno (stderr))) != NOTOK) {
1065                 dup2 (fd3, fileno (stderr));
1066                 close (fd3);
1067         }
1068         else
1069                 fd2 = NOTOK;
1070         for (ap = brkstring (cp = getcpy (cp), " ", NULL); *ap; ap++)
1071                 m_convert (mp, *ap);
1072         free (cp);
1073         if (fd2 != NOTOK)
1074                 dup2 (fd2, fileno (stderr));
1075         if (mp->numsel == 0) {
1076                 if (debugsw)
1077                         admonish (NULL, "no messages to annotate");
1078                 goto oops;
1079         }
1080
1081         lseek (fd, (off_t) 0, SEEK_SET);
1082         if ((fp = fdopen (fd, "r")) == NULL) {
1083                 if (debugsw)
1084                         admonish (NULL, "unable to fdopen annotation list");
1085                 goto oops;
1086         }
1087         cp = NULL;
1088         while (fgets (buffer, sizeof(buffer), fp) != NULL)
1089                 cp = add (buffer, cp);
1090         fclose (fp);
1091
1092         if (debugsw)
1093                 advise (NULL, "annotate%s with %s: \"%s\"",
1094                                 inplace ? " inplace" : "", annotext, cp);
1095         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
1096                 if (is_selected(mp, msgnum)) {
1097                         if (debugsw)
1098                                 advise (NULL, "annotate message %d", msgnum);
1099                         annotate (m_name (msgnum), annotext, cp, inplace, 1, -2, 0);
1100                 }
1101         }
1102
1103         free (cp);
1104
1105 oops:
1106         folder_free (mp);  /* free folder/message structure */
1107 }
1108
1109
1110 static void
1111 armed_done (int status)
1112 {
1113         longjmp (env, status ? status : NOTOK);
1114
1115         exit (status);
1116 }