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