2 ** send.c -- send a composed message
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.
13 #include <h/signals.h>
19 #ifdef TIME_WITH_SYS_TIME
20 # include <sys/time.h>
23 # ifdef TM_IN_SYS_TIME
24 # include <sys/time.h>
30 int debugsw = 0; /* global */
36 char *annotext = NULL;
37 char *distfile = NULL;
41 /* name of temp file for body content */
42 static char body_file_name[MAXPATHLEN + 1];
43 /* name of mhbuild composition temporary file */
44 static char composition_file_name[MAXPATHLEN + 1];
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 */
52 ** external prototypes
54 char *getusername(void);
59 static int sendsbr(char **, int, char *, struct stat *, int);
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 sendaux(char **, int, char *, struct stat *);
67 static int attach(char *);
68 static void clean_up_temporary_files(void);
69 static int get_line(void);
70 static void make_mime_composition_file_entry(char *);
73 static struct swit switches[] = {
75 { "alias aliasfile", 0 },
79 { "filter filterfile", 0 },
111 main(int argc, char **argv)
113 int msgp = 0, nfiles = 0, distsw = 0, vecp = 1;
115 char *cp, *maildir = NULL;
116 char buf[BUFSIZ], **ap, **argp, **arguments;
117 char *msgs[MAXARGS], *vec[MAXARGS];
118 char *files[MAXARGS];
123 setlocale(LC_ALL, "");
125 invo_name = mhbasename(argv[0]);
127 /* read user profile/context */
130 arguments = getarguments(invo_name, argc, argv, 1);
133 vec[vecp++] = "-library";
134 vec[vecp++] = getcpy(toabsdir("+"));
136 while ((cp = *argp++)) {
138 switch (smatch(++cp, switches)) {
140 ambigsw(cp, switches);
143 adios(NULL, "-%s unknown\n", cp);
146 snprintf(buf, sizeof(buf),
147 "%s [file] [switches]",
149 print_help(buf, switches, 1);
152 print_version(invo_name);
179 debugsw++; /* fall */
191 if (!(cp = *argp++) || *cp == '-')
192 adios(NULL, "missing argument to %s",
200 files[nfiles++] = cp;
208 ** check for "Aliasfile:" profile entry
210 if ((cp = context_find("Aliasfile"))) {
213 for (ap = brkstring(dp = getcpy(cp), " ", "\n"); ap && *ap;
215 vec[vecp++] = "-alias";
216 vec[vecp++] = getcpy(etcpath(*ap));
220 if (!msgp && !nfiles)
221 msgs[msgp++] = seq_cur;
222 maildir = toabsdir(draftfolder);
224 if (chdir(maildir) == NOTOK)
225 adios(maildir, "unable to change directory to");
227 /* read folder and create message structure */
228 if (!(mp = folder_read(draftfolder)))
229 adios(NULL, "unable to read draft folder %s", draftfolder);
231 /* check for empty folder */
233 adios(NULL, "no messages in draft folder %s", draftfolder);
235 /* parse all the message ranges/sequences and set SELECTED */
236 for (msgnum = 0; msgnum < msgp; msgnum++) {
237 if (!m_convert(mp, msgs[msgnum])) {
241 seq_setprev(mp); /* set the previous-sequence */
243 for (msgp = 0, msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
244 if (is_selected(mp, msgnum)) {
245 files[nfiles++] = getcpy(m_name(msgnum));
246 unset_exists(mp, msgnum);
250 mp->msgflags |= SEQMOD;
253 if (!(cp = getenv("SIGNATURE")) || !*cp)
254 if ((cp = context_find("signature")) && *cp)
255 m_putenv("SIGNATURE", cp);
257 for (msgnum = 0; msgnum < nfiles; msgnum++)
258 if (stat(files[msgnum], &st) == NOTOK)
259 adios(files[msgnum], "unable to stat draft file");
261 if ((annotext = getenv("mhannotate")) == NULL || *annotext == 0)
263 if (annotext && ((cp = getenv("mhinplace")) != NULL && *cp != 0))
265 if ((altmsg = getenv("mhaltmsg")) == NULL || *altmsg == 0)
266 altmsg = NULL; /* used by dist interface - see below */
268 if ((cp = getenv("mhdist")) && *cp && (distsw = atoi (cp)) && altmsg) {
269 vec[vecp++] = "-dist";
270 distfile = getcpy(m_mktemp2(altmsg, invo_name, NULL, NULL));
271 if (link(altmsg, distfile) == NOTOK) {
273 adios(distfile, "unable to link %s to",
276 distfile = getcpy(m_mktemp2(NULL, invo_name,
282 if ((in = open(altmsg, O_RDONLY)) == NOTOK)
283 adios(altmsg, "unable to open");
285 if ((out = creat(distfile,
286 (int) st.st_mode & 0777))
288 adios(distfile, "unable to write");
289 cpydata(in, out, altmsg, distfile);
298 if (altmsg == NULL || stat(altmsg, &st) == NOTOK) {
307 vec[0] = mhbasename(postproc);
310 for (msgnum = 0; msgnum < nfiles; msgnum++) {
311 switch (sendsbr(vec, vecp, files[msgnum], &st, 1)) {
321 context_save(); /* save the context file */
328 ** send message back-end
331 sendsbr(char **vec, int vecp, char *drft, struct stat *st, int rename_drft)
335 char *original_draft; /* name of original draft file */
336 char *p; /* string pointer for building file name */
339 ** Save the original name of the draft file. The name of the
340 ** draft file is changed to a temporary file containing the built
341 ** MIME message if there are attachments. We need the original
342 ** name so that it can be renamed after the message is sent.
345 original_draft = drft;
348 ** Convert the draft to a MIME message.
349 ** Use the mhbuild composition file for the draft if there was
350 ** a successful conversion because that now contains the MIME
351 ** message. A nice side effect of this is that it leaves the
352 ** original draft file untouched so that it can be retrieved
353 ** and modified if desired.
355 switch (attach(drft)) {
357 drft = composition_file_name;
368 switch (setjmp(env)) {
370 status = sendaux(vec, vecp, drft, st) ? NOTOK : OK;
371 /* rename the original draft */
372 if (rename_drft && status == OK &&
373 rename(original_draft, strncpy(buffer,
374 m_backup(original_draft), sizeof(buffer)))
376 advise(buffer, "unable to rename %s to", drft);
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.
395 if (drft == composition_file_name) {
396 clean_up_temporary_files();
398 if (strlen(composition_file_name) >=
399 sizeof (composition_file_name) - 6)
400 advise(NULL, "unable to remove original composition file.");
403 if ((p = strrchr(composition_file_name, '/')) == NULL)
404 p = composition_file_name;
408 strcpy(body_file_name, p);
410 strcpy(p, body_file_name);
413 unlink(composition_file_name);
421 attach(char *draft_file_name)
423 char buf[MAXPATHLEN + 6]; /* miscellaneous buffer */
424 int c; /* current character for body copy */
425 int has_attachment; /* draft has at least one attachment */
426 int has_body; /* draft has a message body */
427 int non_ascii; /* msg body contains non-ASCII chars */
428 int length; /* length of attachment header field name */
429 char *p; /* miscellaneous string pointer */
431 /* Open up the draft file. */
432 if ((draft_file = fopen(draft_file_name, "r")) == (FILE *)0)
433 adios(NULL, "can't open draft file `%s'.",
437 ** Allocate a buffer to hold the header components as they're read in.
438 ** This buffer might need to be quite large, so we grow it as needed.
440 field = (char *)mh_xmalloc(field_size = 256);
443 ** Scan the draft file for an attachment header field name.
444 ** The existence of one indicates that the
445 ** draft has attachments. Bail out if there are no attachments
446 ** because we're done. Read to the end of the headers even if
447 ** we have no attachments.
449 length = strlen(attach_hdr);
453 while (get_line() != EOF && *field != '\0' && *field != '-') {
454 if (strncasecmp(field, attach_hdr, length) == 0 &&
455 field[length] == ':') {
461 ** Look for at least one non-blank line in the body of the
462 ** message which indicates content in the body.
463 ** Check if body contains at least one non-blank (= not empty)
464 ** and if it contains any non-ASCII chars (= need MIME).
469 while (get_line() != EOF) {
470 for (p = field; *p != '\0'; p++) {
471 if (*p != ' ' && *p != '\t') {
474 if (*p > 127 || *p < 0) {
478 if (has_body && non_ascii)
483 ** Bail out if there are no attachments and only ASCII text.
484 ** This means we don't need to convert it to MIME.
486 if (!has_attachment && non_ascii==0) {
494 /* Make names for the temporary files. */
495 strncpy(body_file_name,
496 m_mktemp(toabsdir(invo_name), NULL, NULL),
497 sizeof (body_file_name));
498 strncpy(composition_file_name,
499 m_mktemp(toabsdir(invo_name), NULL, NULL),
500 sizeof (composition_file_name));
503 body_file = fopen(body_file_name, "w");
505 composition_file = fopen(composition_file_name, "w");
507 if ((has_body && !body_file) || !composition_file) {
508 clean_up_temporary_files();
509 adios(NULL, "unable to open all of the temporary files.");
513 ** Start at the beginning of the draft file. Copy all
514 ** non-attachment header fields to the temporary composition file.
515 ** Then add the dashed line separator.
518 while (get_line() != EOF && *field && *field != '-') {
519 if (strncasecmp(field, attach_hdr, length) != 0 ||
520 field[length] != ':') {
521 fprintf(composition_file, "%s\n", field);
524 fputs("--------\n", composition_file);
527 /* Copy the message body to the temporary file. */
528 while ((c = getc(draft_file)) != EOF) {
533 /* Add a mhbuild MIME composition file line for the body */
534 /* charset will be discovered/guessed by buildmimeproc */
535 fprintf(composition_file, "#text/plain %s\n", body_file_name);
539 ** Now, go back to the beginning of the draft file and look for
540 ** header fields that specify attachments. Add a mhbuild MIME
541 ** composition file for each.
544 while (get_line() != EOF && *field && *field != '-') {
545 if (strncasecmp(field, attach_hdr, length) == 0 &&
546 field[length] == ':') {
547 for (p = field+length+1; *p==' ' || *p=='\t'; p++) {
551 /* forwarded message */
552 fprintf(composition_file, "#forw [forwarded message(s)] %s\n", p);
554 make_mime_composition_file_entry(p);
559 fclose(composition_file);
562 ** We're ready to roll! Run mhbuild on the composition file.
563 ** Note that mhbuild is in the context as buildmimeproc.
565 sprintf(buf, "%s %s", buildmimeproc, composition_file_name);
567 if (system(buf) != 0) {
568 clean_up_temporary_files();
576 clean_up_temporary_files(void)
578 unlink(body_file_name);
579 unlink(composition_file_name);
587 int c; /* current character */
588 int n; /* number of bytes in buffer */
589 char *p; /* buffer pointer */
592 ** Get a line from the input file, growing the field buffer as
593 ** needed. We do this so that we can fit an entire line in the
594 ** buffer making it easy to do a string comparison on both the
595 ** field name and the field body which might be a long path name.
598 for (n = 0, p = field; (c = getc(draft_file)) != EOF; *p++ = c) {
599 if (c == '\n' && (c = getc(draft_file)) != ' ' && c != '\t') {
600 ungetc(c, draft_file);
605 if (++n >= field_size - 1) {
606 field = (char *)mh_xrealloc((void *)field, field_size += 256);
612 /* NUL-terminate the field. */
619 make_mime_composition_file_entry(char *file_name)
624 char content_type[BUFSIZ];
626 char *cmd = mimetypequeryproc;
628 for (np = m_defs; np; np = np->n_next) {
629 if (strcasecmp(np->n_name, mimetypequery)==0) {
634 snprintf(cmdbuf, sizeof cmdbuf, "%s %s", cmd, file_name);
636 if (!(fp = popen(cmdbuf, "r"))) {
637 clean_up_temporary_files();
638 adios(NULL, "unable to determine content type with `%s'",
641 if (fgets(content_type, sizeof content_type, fp) &&
642 (cp = strrchr(content_type, '\n'))) {
645 strcpy(content_type, "application/octet-stream");
646 admonish(NULL, "problems with `%s', using fall back type `%s'",
647 cmdbuf, content_type);
651 /* TODO: don't use access(2) because it checks for ruid, not euid */
652 if (access(file_name, R_OK) != 0) {
653 clean_up_temporary_files();
654 adios(NULL, "unable to access file `%s'", file_name);
657 fprintf(composition_file, "#%s; name=\"%s\" <>{attachment}",
659 (!(cp = strrchr(file_name, '/'))) ? file_name : cp + 1);
661 fprintf(composition_file, " %s\n", file_name);
667 ** Annotate original message, and
668 ** call `postproc' to send message.
671 sendaux(char **vec, int vecp, char *drft, struct stat *st)
674 int i, status, fd, fd2;
677 fd = pushsw ? tmp_fd() : NOTOK;
681 if (annotext && (fd2 = tmp_fd()) == NOTOK) {
682 admonish(NULL, "unable to create file for annotation list");
684 if (distfile && distout(drft, distfile, backup) == NOTOK)
688 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
693 /* oops -- fork error */
694 adios("fork", "unable to");
695 break; /* NOT REACHED */
699 ** child process -- send it
701 ** If fd is ok, then we are pushing and fd points to temp
702 ** file, so capture anything on stdout and stderr there.
705 dup2(fd, fileno(stdout));
706 dup2(fd, fileno(stderr));
709 execvp(postproc, vec);
710 fprintf(stderr, "unable to exec ");
713 break; /* NOT REACHED */
717 ** parent process -- wait for it
719 if ((status = pidwait(child_id, NOTOK)) == OK) {
720 if (annotext && fd2 != NOTOK)
724 ** If postproc failed, and we have good fd (which
725 ** means we pushed), then mail error message
726 ** (and possibly the draft) back to the user.
732 advise(NULL, "message not delivered to anyone");
734 if (annotext && fd2 != NOTOK)
738 if (rename(backup, drft) == NOTOK)
739 advise(drft, "unable to rename %s to",
751 ** Mail error notification (and possibly a copy of the
752 ** message) back to the user, using the mailproc
755 alert(char *file, int out)
761 for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
766 /* oops -- fork error */
767 advise("fork", "unable to");
770 /* child process -- send it */
771 SIGNAL(SIGHUP, SIG_IGN);
772 SIGNAL(SIGINT, SIG_IGN);
773 SIGNAL(SIGQUIT, SIG_IGN);
774 SIGNAL(SIGTERM, SIG_IGN);
776 if ((in = open(file, O_RDONLY)) == NOTOK) {
777 admonish(file, "unable to re-open");
779 lseek(out, (off_t) 0, SEEK_END);
780 strncpy(buf, "\nMessage not delivered to anyone.\n", sizeof(buf));
781 write(out, buf, strlen(buf));
782 strncpy(buf, "\n------- Unsent Draft\n\n", sizeof(buf));
783 write(out, buf, strlen(buf));
784 cpydgst(in, out, file, "temporary file");
786 strncpy(buf, "\n------- End of Unsent Draft\n", sizeof(buf));
787 write(out, buf, strlen(buf));
788 if (rename(file, strncpy(buf, m_backup(file), sizeof(buf))) == NOTOK)
789 admonish(buf, "unable to rename %s to", file);
792 lseek(out, (off_t) 0, SEEK_SET);
793 dup2(out, fileno(stdin));
795 /* create subject for error notification */
796 snprintf(buf, sizeof(buf), "send failed on %s",
797 forwsw ? "enclosed draft" : file);
799 execlp(mailproc, mhbasename(mailproc), getusername(),
800 "-subject", buf, NULL);
801 fprintf(stderr, "unable to exec ");
805 default: /* no waiting... */
817 tfile = m_mktemp2(NULL, invo_name, &fd, NULL);
818 if (tfile == NULL) return NOTOK;
822 advise(NULL, "temporary file %s selected", tfile);
824 if (unlink(tfile) == NOTOK)
825 advise(tfile, "unable to remove");
832 anno(int fd, struct stat *st)
836 static char *cwd = NULL;
839 if (altmsg && (stat(altmsg, &st2) == NOTOK ||
840 st->st_mtime != st2.st_mtime ||
841 st->st_dev != st2.st_dev ||
842 st->st_ino != st2.st_ino)) {
844 admonish(NULL, "$mhaltmsg mismatch");
848 child_id = debugsw ? NOTOK : fork();
850 case NOTOK: /* oops */
852 advise(NULL, "unable to fork, so doing annotations by hand...");
858 /* block a few signals */
860 sigaddset(&set, SIGHUP);
861 sigaddset(&set, SIGINT);
862 sigaddset(&set, SIGQUIT);
863 sigaddset(&set, SIGTERM);
864 SIGPROCMASK(SIG_BLOCK, &set, &oset);
870 /* reset the signal mask */
871 SIGPROCMASK(SIG_SETMASK, &oset, &set);
876 default: /* no waiting... */
886 int fd2, fd3, msgnum;
887 char *cp, *folder, *maildir;
888 char buffer[BUFSIZ], **ap;
892 if ((folder = getenv("mhfolder")) == NULL || *folder == 0) {
894 admonish(NULL, "$mhfolder not set");
897 maildir = toabsdir(folder);
898 if (chdir(maildir) == NOTOK) {
900 admonish(maildir, "unable to change directory to");
903 if (!(mp = folder_read(folder))) {
905 admonish(NULL, "unable to read folder %s", folder);
909 /* check for empty folder */
910 if (mp->nummsg == 0) {
912 admonish(NULL, "no messages in %s", folder);
916 if ((cp = getenv("mhmessages")) == NULL || *cp == 0) {
918 admonish(NULL, "$mhmessages not set");
921 if (!debugsw /* MOBY HACK... */
923 && (fd3 = open("/dev/null", O_RDWR)) != NOTOK
924 && (fd2 = dup(fileno(stderr))) != NOTOK) {
925 dup2(fd3, fileno(stderr));
929 for (ap = brkstring(cp = getcpy(cp), " ", NULL); *ap; ap++)
933 dup2(fd2, fileno(stderr));
934 if (mp->numsel == 0) {
936 admonish(NULL, "no messages to annotate");
940 lseek(fd, (off_t) 0, SEEK_SET);
941 if ((fp = fdopen(fd, "r")) == NULL) {
943 admonish(NULL, "unable to fdopen annotation list");
947 while (fgets(buffer, sizeof(buffer), fp) != NULL)
948 cp = add(buffer, cp);
952 advise(NULL, "annotate%s with %s: \"%s\"",
953 inplace ? " inplace" : "", annotext, cp);
954 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
955 if (is_selected(mp, msgnum)) {
957 advise(NULL, "annotate message %d", msgnum);
958 annotate(m_name(msgnum), annotext, cp, inplace,
966 folder_free(mp); /* free folder/message structure */
971 armed_done(int status)
973 longjmp(env, status ? status : NOTOK);