]> diplodocus.org Git - nmh/blob - uip/sendsbr.c
Document changes to mhbuild and send.
[nmh] / uip / sendsbr.c
1
2 /*
3 * sendsbr.c -- routines to help WhatNow/Send along
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
8 */
9
10 #include <h/mh.h>
11 #include <h/signals.h>
12 #include <setjmp.h>
13 #include <fcntl.h>
14 #include <h/mime.h>
15 #include <h/tws.h>
16 #include <h/utils.h>
17 #include <h/mts.h>
18
19 #ifdef HAVE_SYS_TIME_H
20 # include <sys/time.h>
21 #endif
22 #include <time.h>
23
24 int debugsw = 0; /* global */
25 int forwsw = 1;
26 int inplace = 1;
27 int pushsw = 0;
28 int splitsw = -1;
29 int unique = 0;
30 int verbsw = 0;
31
32 char *altmsg = NULL; /* .. */
33 char *annotext = NULL;
34 char *distfile = NULL;
35
36 static jmp_buf env;
37
38 /*
39 * static prototypes
40 */
41 static void armed_done (int) NORETURN;
42 static void alert (char *, int);
43 static int tmp_fd (void);
44 static void anno (int, struct stat *);
45 static void annoaux (int);
46 static int splitmsg (char **, int, char *, char *, struct stat *, int);
47 static int sendaux (char **, int, char *, char *, struct stat *);
48
49
50 /*
51 * Entry point into (back-end) routines to send message.
52 */
53
54 int
55 sendsbr (char **vec, int vecp, char *program, char *drft, struct stat *st,
56 int rename_drft)
57 {
58 int status, i;
59 pid_t child;
60 char buffer[BUFSIZ], file[BUFSIZ];
61 struct stat sts;
62 char **buildvec, *buildprogram;
63
64 /*
65 * Run the mimebuildproc (which is by default mhbuild) on the message
66 * with the addition of the "-auto" flag
67 */
68
69 switch (child = fork()) {
70 case NOTOK:
71 adios("fork", "unable to");
72 break;
73
74 case OK:
75 buildvec = argsplit(buildmimeproc, &buildprogram, &i);
76 buildvec[i++] = "-auto";
77 if (distfile)
78 buildvec[i++] = "-dist";
79 buildvec[i++] = drft;
80 buildvec[i] = NULL;
81 execvp(buildprogram, buildvec);
82 fprintf(stderr, "unable to exec ");
83 perror(buildmimeproc);
84 _exit(-1);
85 break;
86
87 default:
88 if (pidXwait(child, buildmimeproc))
89 return NOTOK;
90 break;
91 }
92
93 done=armed_done;
94 switch (setjmp (env)) {
95 case OK:
96 /*
97 * If given -push and -unique (which is undocumented), then
98 * rename the draft file. I'm not quite sure why.
99 */
100 if (pushsw && unique) {
101 char *cp = m_mktemp2(drft, invo_name, NULL, NULL);
102 if (cp == NULL) {
103 adios ("sendsbr", "unable to create temporary file");
104 }
105 if (rename (drft, strncpy(file, cp, sizeof(file))) == NOTOK)
106 adios (file, "unable to rename %s to", drft);
107 drft = file;
108 }
109
110 /*
111 * Check if we need to split the message into
112 * multiple messages of type "message/partial".
113 */
114 if (splitsw >= 0 && !distfile && stat (drft, &sts) != NOTOK
115 && sts.st_size >= CPERMSG) {
116 status = splitmsg (vec, vecp, program, drft, st, splitsw) ? NOTOK : OK;
117 } else {
118 status = sendaux (vec, vecp, program, drft, st) ? NOTOK : OK;
119 }
120
121 /* rename the original draft */
122 if (rename_drft && status == OK &&
123 rename (drft, strncpy (buffer, m_backup (drft), sizeof(buffer))) == NOTOK)
124 advise (buffer, "unable to rename %s to", drft);
125 break;
126
127 default:
128 status = DONE;
129 break;
130 }
131
132 done=exit;
133 if (distfile)
134 unlink (distfile);
135
136 return status;
137 }
138
139 /*
140 * Split large message into several messages of
141 * type "message/partial" and send them.
142 */
143
144 static int
145 splitmsg (char **vec, int vecp, char *program, char *drft,
146 struct stat *st, int delay)
147 {
148 int compnum, nparts, partno, state, status;
149 long pos, start;
150 time_t clock;
151 char *cp, *dp, buffer[BUFSIZ], msgid[BUFSIZ];
152 char subject[BUFSIZ];
153 char name[NAMESZ], partnum[BUFSIZ];
154 FILE *in;
155 m_getfld_state_t gstate = 0;
156
157 if ((in = fopen (drft, "r")) == NULL)
158 adios (drft, "unable to open for reading");
159
160 cp = dp = NULL;
161 start = 0L;
162
163 /*
164 * Scan through the message and examine the various header fields,
165 * as well as locate the beginning of the message body.
166 */
167 m_getfld_track_filepos (&gstate, in);
168 for (compnum = 1;;) {
169 int bufsz = sizeof buffer;
170 switch (state = m_getfld (&gstate, name, buffer, &bufsz, in)) {
171 case FLD:
172 case FLDPLUS:
173 compnum++;
174
175 /*
176 * This header field is discarded.
177 */
178 if (!strcasecmp (name, "Message-ID")) {
179 while (state == FLDPLUS) {
180 bufsz = sizeof buffer;
181 state = m_getfld (&gstate, name, buffer, &bufsz, in);
182 }
183 } else if (uprf (name, XXX_FIELD_PRF)
184 || !strcasecmp (name, VRSN_FIELD)
185 || !strcasecmp (name, "Subject")
186 || !strcasecmp (name, "Encrypted")) {
187 /*
188 * These header fields are copied to the enclosed
189 * header of the first message in the collection
190 * of message/partials. For the "Subject" header
191 * field, we also record it, so that a modified
192 * version of it, can be copied to the header
193 * of each messsage/partial in the collection.
194 */
195 if (!strcasecmp (name, "Subject")) {
196 size_t sublen;
197
198 strncpy (subject, buffer, BUFSIZ);
199 sublen = strlen (subject);
200 if (sublen > 0 && subject[sublen - 1] == '\n')
201 subject[sublen - 1] = '\0';
202 }
203
204 dp = add (concat (name, ":", buffer, NULL), dp);
205 while (state == FLDPLUS) {
206 bufsz = sizeof buffer;
207 state = m_getfld (&gstate, name, buffer, &bufsz, in);
208 dp = add (buffer, dp);
209 }
210 } else {
211 /*
212 * These header fields are copied to the header of
213 * each message/partial in the collection.
214 */
215 cp = add (concat (name, ":", buffer, NULL), cp);
216 while (state == FLDPLUS) {
217 bufsz = sizeof buffer;
218 state = m_getfld (&gstate, name, buffer, &bufsz, in);
219 cp = add (buffer, cp);
220 }
221 }
222
223 start = ftell (in) + 1;
224 continue;
225
226 case BODY:
227 case FILEEOF:
228 break;
229
230 case LENERR:
231 case FMTERR:
232 adios (NULL, "message format error in component #%d", compnum);
233
234 default:
235 adios (NULL, "getfld () returned %d", state);
236 }
237
238 break;
239 }
240 m_getfld_state_destroy (&gstate);
241 if (cp == NULL)
242 adios (NULL, "headers missing from draft");
243
244 nparts = 1;
245 pos = start;
246 while (fgets (buffer, sizeof(buffer) - 1, in)) {
247 long len;
248
249 if ((pos += (len = strlen (buffer))) > CPERMSG) {
250 nparts++;
251 pos = len;
252 }
253 }
254
255 /* Only one part, nothing to split */
256 if (nparts == 1) {
257 free (cp);
258 if (dp)
259 free (dp);
260
261 fclose (in);
262 return sendaux (vec, vecp, program, drft, st);
263 }
264
265 if (!pushsw) {
266 printf ("Sending as %d Partial Messages\n", nparts);
267 fflush (stdout);
268 }
269 status = OK;
270
271 vec[vecp++] = "-partno";
272 vec[vecp++] = partnum;
273 if (delay == 0)
274 vec[vecp++] = "-queued";
275
276 time (&clock);
277 snprintf (msgid, sizeof(msgid), "%s", message_id (clock, 0));
278
279 fseek (in, start, SEEK_SET);
280 for (partno = 1; partno <= nparts; partno++) {
281 char tmpdrf[BUFSIZ];
282 FILE *out;
283
284 char *cp = m_mktemp2(drft, invo_name, NULL, &out);
285 if (cp == NULL) {
286 adios (drft, "unable to create temporary file for");
287 }
288 strncpy(tmpdrf, cp, sizeof(tmpdrf));
289 chmod (tmpdrf, 0600);
290
291 /*
292 * Output the header fields
293 */
294 fputs (cp, out);
295 fprintf (out, "Subject: %s (part %d of %d)\n", subject, partno, nparts);
296 fprintf (out, "%s: %s\n", VRSN_FIELD, VRSN_VALUE);
297 fprintf (out, "%s: message/partial; id=\"%s\";\n", TYPE_FIELD, msgid);
298 fprintf (out, "\tnumber=%d; total=%d\n", partno, nparts);
299 fprintf (out, "%s: part %d of %d\n\n", DESCR_FIELD, partno, nparts);
300
301 /*
302 * If this is the first in the collection, output the
303 * header fields we are encapsulating at the beginning
304 * of the body of the first message.
305 */
306 if (partno == 1) {
307 if (dp)
308 fputs (dp, out);
309 fprintf (out, "Message-ID: %s\n", msgid);
310 fprintf (out, "\n");
311 }
312
313 pos = 0;
314 for (;;) {
315 long len;
316
317 if (!fgets (buffer, sizeof(buffer) - 1, in)) {
318 if (partno == nparts)
319 break;
320 adios (NULL, "premature eof");
321 }
322
323 if ((pos += (len = strlen (buffer))) > CPERMSG) {
324 fseek (in, -len, SEEK_CUR);
325 break;
326 }
327
328 fputs (buffer, out);
329 }
330
331 if (fflush (out))
332 adios (tmpdrf, "error writing to");
333
334 fclose (out);
335
336 if (!pushsw && verbsw) {
337 printf ("\n");
338 fflush (stdout);
339 }
340
341 /* Pause here, if a delay is specified */
342 if (delay > 0 && 1 < partno && partno <= nparts) {
343 if (!pushsw) {
344 printf ("pausing %d seconds before sending part %d...\n",
345 delay, partno);
346 fflush (stdout);
347 }
348 sleep ((unsigned int) delay);
349 }
350
351 snprintf (partnum, sizeof(partnum), "%d", partno);
352 status = sendaux (vec, vecp, program, tmpdrf, st);
353 unlink (tmpdrf);
354 if (status != OK)
355 break;
356
357 /*
358 * This is so sendaux will only annotate
359 * the altmsg the first time it is called.
360 */
361 annotext = NULL;
362 }
363
364 free (cp);
365 if (dp)
366 free (dp);
367
368 fclose (in); /* close the draft */
369 return status;
370 }
371
372
373 /*
374 * Annotate original message, and
375 * call `postproc' (which is passed down in "program") to send message.
376 */
377
378 static int
379 sendaux (char **vec, int vecp, char *program, char *drft, struct stat *st)
380 {
381 pid_t child_id;
382 int i, status, fd, fd2;
383 char backup[BUFSIZ], buf[BUFSIZ];
384
385 fd = pushsw ? tmp_fd () : NOTOK;
386 fd2 = NOTOK;
387
388 vec[vecp++] = drft;
389 if (annotext) {
390 if ((fd2 = tmp_fd ()) != NOTOK) {
391 vec[vecp++] = "-idanno";
392 snprintf (buf, sizeof(buf), "%d", fd2);
393 vec[vecp++] = buf;
394 } else {
395 admonish (NULL, "unable to create file for annotation list");
396 }
397 }
398 if (distfile && distout (drft, distfile, backup) == NOTOK)
399 done (1);
400 vec[vecp] = NULL;
401
402 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
403 sleep (5);
404
405 switch (child_id) {
406 case -1:
407 /* oops -- fork error */
408 adios ("fork", "unable to");
409 break; /* NOT REACHED */
410
411 case 0:
412 /*
413 * child process -- send it
414 *
415 * If fd is ok, then we are pushing and fd points to temp
416 * file, so capture anything on stdout and stderr there.
417 */
418 if (fd != NOTOK) {
419 dup2 (fd, fileno (stdout));
420 dup2 (fd, fileno (stderr));
421 close (fd);
422 }
423 execvp (program, vec);
424 fprintf (stderr, "unable to exec ");
425 perror (postproc);
426 _exit (-1);
427
428 default:
429 /*
430 * parent process -- wait for it
431 */
432 if ((status = pidwait(child_id, NOTOK)) == OK) {
433 if (annotext && fd2 != NOTOK)
434 anno (fd2, st);
435 } else {
436 /*
437 * If postproc failed, and we have good fd (which means
438 * we pushed), then mail error message (and possibly the
439 * draft) back to the user.
440 */
441 if (fd != NOTOK) {
442 alert (drft, fd);
443 close (fd);
444 } else {
445 advise (NULL, "message not delivered to anyone");
446 }
447 if (annotext && fd2 != NOTOK)
448 close (fd2);
449 if (distfile) {
450 unlink (drft);
451 if (rename (backup, drft) == NOTOK)
452 advise (drft, "unable to rename %s to", backup);
453 }
454 }
455 break;
456 }
457
458 return status;
459 }
460
461
462 /*
463 * Mail error notification (and possibly a copy of the
464 * message) back to the user, using the mailproc
465 */
466
467 static void
468 alert (char *file, int out)
469 {
470 pid_t child_id;
471 int i, in, argp;
472 char buf[BUFSIZ];
473 char *program;
474 char **arglist;
475
476 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
477 sleep (5);
478
479 switch (child_id) {
480 case NOTOK:
481 /* oops -- fork error */
482 advise ("fork", "unable to");
483
484 case OK:
485 /* child process -- send it */
486 SIGNAL (SIGHUP, SIG_IGN);
487 SIGNAL (SIGINT, SIG_IGN);
488 SIGNAL (SIGQUIT, SIG_IGN);
489 SIGNAL (SIGTERM, SIG_IGN);
490 if (forwsw) {
491 if ((in = open (file, O_RDONLY)) == NOTOK) {
492 admonish (file, "unable to re-open");
493 } else {
494 lseek (out, (off_t) 0, SEEK_END);
495 strncpy (buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
496 write (out, buf, strlen (buf));
497 strncpy (buf, "\n------- Unsent Draft\n\n", sizeof(buf));
498 write (out, buf, strlen (buf));
499 cpydgst (in, out, file, "temporary file");
500 close (in);
501 strncpy (buf, "\n------- End of Unsent Draft\n", sizeof(buf));
502 write (out, buf, strlen (buf));
503 if (rename (file, strncpy (buf, m_backup (file), sizeof(buf))) == NOTOK)
504 admonish (buf, "unable to rename %s to", file);
505 }
506 }
507 lseek (out, (off_t) 0, SEEK_SET);
508 dup2 (out, fileno (stdin));
509 close (out);
510 /* create subject for error notification */
511 snprintf (buf, sizeof(buf), "send failed on %s",
512 forwsw ? "enclosed draft" : file);
513
514 arglist = argsplit(mailproc, &program, &argp);
515
516 arglist[argp++] = getusername();
517 arglist[argp++] = "-subject";
518 arglist[argp++] = buf;
519 arglist[argp] = NULL;
520
521 execvp (program, arglist);
522 fprintf (stderr, "unable to exec ");
523 perror (mailproc);
524 _exit (-1);
525
526 default: /* no waiting... */
527 break;
528 }
529 }
530
531
532 static int
533 tmp_fd (void)
534 {
535 int fd;
536 char *tfile = NULL;
537
538 tfile = m_mktemp2(NULL, invo_name, &fd, NULL);
539 if (tfile == NULL) return NOTOK;
540 fchmod(fd, 0600);
541
542 if (debugsw)
543 advise (NULL, "temporary file %s selected", tfile);
544 else
545 if (unlink (tfile) == NOTOK)
546 advise (tfile, "unable to remove");
547
548 return fd;
549 }
550
551
552 static void
553 anno (int fd, struct stat *st)
554 {
555 pid_t child_id;
556 sigset_t set, oset;
557 static char *cwd = NULL;
558 struct stat st2;
559
560 if (altmsg &&
561 (stat (altmsg, &st2) == NOTOK
562 || st->st_mtime != st2.st_mtime
563 || st->st_dev != st2.st_dev
564 || st->st_ino != st2.st_ino)) {
565 if (debugsw)
566 admonish (NULL, "$mhaltmsg mismatch");
567 return;
568 }
569
570 child_id = debugsw ? NOTOK : fork ();
571 switch (child_id) {
572 case NOTOK: /* oops */
573 if (!debugsw)
574 advise (NULL,
575 "unable to fork, so doing annotations by hand...");
576 if (cwd == NULL)
577 cwd = getcpy (pwd ());
578
579 case OK:
580 /* block a few signals */
581 sigemptyset (&set);
582 sigaddset (&set, SIGHUP);
583 sigaddset (&set, SIGINT);
584 sigaddset (&set, SIGQUIT);
585 sigaddset (&set, SIGTERM);
586 sigprocmask (SIG_BLOCK, &set, &oset);
587
588 annoaux (fd);
589 if (child_id == OK)
590 _exit (0);
591
592 /* reset the signal mask */
593 sigprocmask (SIG_SETMASK, &oset, &set);
594
595 chdir (cwd);
596 break;
597
598 default: /* no waiting... */
599 close (fd);
600 break;
601 }
602 }
603
604
605 static void
606 annoaux (int fd)
607 {
608 int fd2, fd3, msgnum;
609 char *cp, *folder, *maildir;
610 char buffer[BUFSIZ], **ap;
611 FILE *fp;
612 struct msgs *mp;
613
614 if ((folder = getenv ("mhfolder")) == NULL || *folder == 0) {
615 if (debugsw)
616 admonish (NULL, "$mhfolder not set");
617 return;
618 }
619 maildir = m_maildir (folder);
620 if (chdir (maildir) == NOTOK) {
621 if (debugsw)
622 admonish (maildir, "unable to change directory to");
623 return;
624 }
625 if (!(mp = folder_read (folder, 0))) {
626 if (debugsw)
627 admonish (NULL, "unable to read folder %s", folder);
628 return;
629 }
630
631 /* check for empty folder */
632 if (mp->nummsg == 0) {
633 if (debugsw)
634 admonish (NULL, "no messages in %s", folder);
635 goto oops;
636 }
637
638 if ((cp = getenv ("mhmessages")) == NULL || *cp == 0) {
639 if (debugsw)
640 admonish (NULL, "$mhmessages not set");
641 goto oops;
642 }
643 if (!debugsw /* MOBY HACK... */
644 && pushsw
645 && (fd3 = open ("/dev/null", O_RDWR)) != NOTOK
646 && (fd2 = dup (fileno (stderr))) != NOTOK) {
647 dup2 (fd3, fileno (stderr));
648 close (fd3);
649 }
650 else
651 fd2 = NOTOK;
652 for (ap = brkstring (cp = getcpy (cp), " ", NULL); *ap; ap++)
653 m_convert (mp, *ap);
654 free (cp);
655 if (fd2 != NOTOK)
656 dup2 (fd2, fileno (stderr));
657 if (mp->numsel == 0) {
658 if (debugsw)
659 admonish (NULL, "no messages to annotate");
660 goto oops;
661 }
662
663 lseek (fd, (off_t) 0, SEEK_SET);
664 if ((fp = fdopen (fd, "r")) == NULL) {
665 if (debugsw)
666 admonish (NULL, "unable to fdopen annotation list");
667 goto oops;
668 }
669 cp = NULL;
670 while (fgets (buffer, sizeof(buffer), fp) != NULL)
671 cp = add (buffer, cp);
672 fclose (fp);
673
674 if (debugsw)
675 advise (NULL, "annotate%s with %s: \"%s\"",
676 inplace ? " inplace" : "", annotext, cp);
677 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
678 if (is_selected(mp, msgnum)) {
679 if (debugsw)
680 advise (NULL, "annotate message %d", msgnum);
681 annotate (m_name (msgnum), annotext, cp, inplace, 1, -2, 0);
682 }
683 }
684
685 free (cp);
686
687 oops:
688 folder_free (mp); /* free folder/message structure */
689 }
690
691
692 static void
693 armed_done (int status)
694 {
695 longjmp (env, status ? status : NOTOK);
696 }