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 HAVE_SYS_TIME_H
20 # include <sys/time.h>
24 int debugsw = 0; /* global */
27 char *annotext = NULL;
28 char *distfile = NULL;
32 /* name of temp file for body content */
33 static char body_file_name[MAXPATHLEN + 1];
34 /* name of mhbuild composition temporary file */
35 static char composition_file_name[MAXPATHLEN + 1];
36 static int field_size; /* size of header field buffer */
37 static char *field; /* header field buffer */
38 static FILE *draft_file; /* draft file pointer */
39 static FILE *body_file; /* body file pointer */
40 static FILE *composition_file; /* composition file pointer */
45 static int sendsbr(char **, int, char *, struct stat *);
46 static void armed_done(int) NORETURN;
47 static void anno(struct stat *);
48 static int sendaux(char **, int, char *, struct stat *);
49 static int attach(char *);
50 static void clean_up_temporary_files(void);
51 static int get_line(void);
52 static void make_mime_composition_file_entry(char *);
55 static struct swit switches[] = {
57 { "alias aliasfile", 0 },
77 main(int argc, char **argv)
79 int msgp = 0, nfiles = 0, distsw = 0, vecp = 1;
83 char *cp, *maildir = NULL;
84 char buf[BUFSIZ], **ap, **argp, **arguments;
85 char *msgs[MAXARGS], *vec[MAXARGS];
91 setlocale(LC_ALL, "");
92 invo_name = mhbasename(argv[0]);
94 /* read user profile/context */
97 arguments = getarguments(invo_name, argc, argv, 1);
100 vec[vecp++] = "-library";
101 vec[vecp++] = getcpy(toabsdir("+"));
103 while ((cp = *argp++)) {
105 switch (smatch(++cp, switches)) {
107 ambigsw(cp, switches);
110 adios(NULL, "-%s unknown\n", cp);
113 snprintf(buf, sizeof(buf),
114 "%s [file] [switches]",
116 print_help(buf, switches, 1);
119 print_version(invo_name);
132 debugsw++; /* fall */
140 if (!(cp = *argp++) || *cp == '-') {
141 adios(NULL, "missing argument to %s",
150 files[nfiles++] = cp;
157 /* check for "Aliasfile:" profile entry */
158 if ((cp = context_find("Aliasfile"))) {
161 for (ap=brkstring(dp=getcpy(cp), " ", "\n"); ap && *ap; ap++) {
162 vec[vecp++] = "-alias";
163 vec[vecp++] = getcpy(etcpath(*ap));
167 if (!msgp && !nfiles) {
168 msgs[msgp++] = seq_cur;
170 maildir = toabsdir(draftfolder);
172 if (chdir(maildir) == NOTOK) {
173 adios(maildir, "unable to change directory to");
176 if (!(mp = folder_read(draftfolder))) {
177 adios(NULL, "unable to read draft folder %s", draftfolder);
179 if (mp->nummsg == 0) {
180 adios(NULL, "no messages in draft folder %s", draftfolder);
182 /* parse all the message ranges/sequences and set SELECTED */
183 for (msgnum = 0; msgnum < msgp; msgnum++) {
184 if (!m_convert(mp, msgs[msgnum])) {
190 for (msgp = 0, msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
191 if (is_selected(mp, msgnum)) {
192 files[nfiles++] = getcpy(m_name(msgnum));
193 unset_exists(mp, msgnum);
197 mp->msgflags |= SEQMOD;
200 if (!(cp = getenv("SIGNATURE")) || !*cp) {
201 if ((cp = context_find("signature")) && *cp) {
202 m_putenv("SIGNATURE", cp);
206 for (msgnum = 0; msgnum < nfiles; msgnum++) {
207 if (stat(files[msgnum], &st) == NOTOK) {
208 adios(files[msgnum], "unable to stat draft file");
212 if (!(annotext = getenv("mhannotate")) || !*annotext) {
215 if (!(altmsg = getenv("mhaltmsg")) || !*altmsg) {
216 altmsg = NULL; /* used by dist interface - see below */
219 if ((cp = getenv("mhdist")) && *cp && (distsw = atoi(cp)) && altmsg) {
220 vec[vecp++] = "-dist";
221 if ((in = open(altmsg, O_RDONLY)) == NOTOK) {
222 adios(altmsg, "unable to open for reading");
225 distfile = getcpy(m_mktemp2(NULL, invo_name, NULL, NULL));
226 if ((out = creat(distfile, (int)st2.st_mode & 0777))==NOTOK) {
227 adios(distfile, "unable to open for writing");
229 cpydata(in, out, altmsg, distfile);
236 if (!altmsg || stat(altmsg, &st) == NOTOK) {
243 for (n=3; n<OPEN_MAX; n++) {
247 for (msgnum = 0; msgnum < nfiles; msgnum++) {
248 switch (sendsbr(vec, vecp, files[msgnum], &st)) {
265 ** message sending back-end
268 sendsbr(char **vec, int vecp, char *drft, struct stat *st)
272 char *original_draft;
273 char *p; /* string pointer for building file name */
276 ** Save the original name of the draft file. The name of the
277 ** draft file is changed to a temporary file containing the built
278 ** MIME message if there are attachments. We need the original
279 ** name so that it can be renamed after the message is sent.
281 original_draft = drft;
284 ** Convert the draft to a MIME message.
285 ** Use the mhbuild composition file for the draft if there was
286 ** a successful conversion because that now contains the MIME
287 ** message. A nice side effect of this is that it leaves the
288 ** original draft file untouched so that it can be retrieved
289 ** and modified if desired.
291 switch (attach(drft)) {
293 drft = composition_file_name;
304 switch (setjmp(env)) {
306 status = sendaux(vec, vecp, drft, st) ? NOTOK : OK;
307 /* rename the original draft */
309 strncpy(buffer, m_backup(original_draft),
311 if (rename(original_draft, buffer) == NOTOK) {
312 advise(buffer, "unable to rename %s to", drft);
328 ** Get rid of any temporary files that we created for attachments.
329 ** Also get rid of the renamed composition file that mhbuild
330 ** leaves as a turd. It looks confusing, but we use the body
331 ** file name to help build the renamed composition file name.
333 if (drft == composition_file_name) {
334 clean_up_temporary_files();
336 if (strlen(composition_file_name) >=
337 sizeof (composition_file_name) - 6) {
338 advise(NULL, "unable to remove original composition file.");
340 if (!(p = strrchr(composition_file_name, '/'))) {
341 p = composition_file_name;
345 strcpy(body_file_name, p);
347 strcpy(p, body_file_name);
350 unlink(composition_file_name);
358 attach(char *draft_file_name)
360 char buf[MAXPATHLEN + 6];
364 int non_ascii; /* msg body contains non-ASCII chars */
365 int length; /* length of attachment header field name */
368 if (!(draft_file = fopen(draft_file_name, "r"))) {
369 adios(NULL, "can't open draft file `%s'.", draft_file_name);
372 /* We'll grow the buffer as needed. */
373 field = (char *)mh_xmalloc(field_size = 256);
376 ** Scan the draft file for an attachment header field name.
378 length = strlen(attach_hdr);
380 while (get_line() != EOF && *field != '\0' && *field != '-') {
381 if (strncasecmp(field, attach_hdr, length)==0 &&
382 field[length] == ':') {
388 ** Look for at least one non-blank line in the body of the
389 ** message which indicates content in the body.
390 ** Check if body contains at least one non-blank (= not empty)
391 ** and if it contains any non-ASCII chars (= need MIME).
395 while (get_line() != EOF) {
396 for (p = field; *p != '\0'; p++) {
397 if (*p != ' ' && *p != '\t') {
400 if (*p > 127 || *p < 0) {
404 if (has_body && non_ascii) {
405 break; /* that's been already enough information */
409 if (!has_attachment && non_ascii==0) {
410 /* We don't need to convert it to MIME. */
418 /* Make names for the temporary files. */
419 strncpy(body_file_name,
420 m_mktemp(toabsdir(invo_name), NULL, NULL),
421 sizeof (body_file_name));
422 strncpy(composition_file_name,
423 m_mktemp(toabsdir(invo_name), NULL, NULL),
424 sizeof (composition_file_name));
427 body_file = fopen(body_file_name, "w");
429 composition_file = fopen(composition_file_name, "w");
431 if ((has_body && !body_file) || !composition_file) {
432 clean_up_temporary_files();
433 adios(NULL, "unable to open all of the temporary files.");
436 /* Copy non-attachment header fields to the temp composition file. */
438 while (get_line() != EOF && *field && *field != '-') {
439 if (strncasecmp(field, attach_hdr, length) != 0 ||
440 field[length] != ':') {
441 fprintf(composition_file, "%s\n", field);
444 fputs("--------\n", composition_file);
447 /* Copy the message body to the temporary file. */
448 while ((c = getc(draft_file)) != EOF) {
453 /* Add a mhbuild MIME composition file line for the body */
454 /* charset will be discovered/guessed by mhbuild */
455 fprintf(composition_file, "#text/plain %s\n", body_file_name);
459 ** Now, go back to the beginning of the draft file and look for
460 ** header fields that specify attachments. Add a mhbuild MIME
461 ** composition file for each.
464 while (get_line() != EOF && *field && *field != '-') {
465 if (strncasecmp(field, attach_hdr, length) == 0 &&
466 field[length] == ':') {
467 for (p = field+length+1; *p==' ' || *p=='\t'; p++) {
471 /* forwarded message */
472 fprintf(composition_file, "#forw [forwarded message(s)] %s\n", p);
474 /* regular attachment */
475 make_mime_composition_file_entry(p);
479 fclose(composition_file);
481 /* We're ready to roll! */
482 sprintf(buf, "mhbuild %s", composition_file_name);
483 if (system(buf) != 0) {
485 clean_up_temporary_files();
493 clean_up_temporary_files(void)
495 unlink(body_file_name);
496 unlink(composition_file_name);
504 int c; /* current character */
505 int n; /* number of bytes in buffer */
509 ** Get a line from the input file, growing the field buffer as
510 ** needed. We do this so that we can fit an entire line in the
511 ** buffer making it easy to do a string comparison on both the
512 ** field name and the field body which might be a long path name.
514 for (n = 0, p = field; (c = getc(draft_file)) != EOF; *p++ = c) {
515 if (c == '\n' && (c = getc(draft_file)) != ' ' && c != '\t') {
516 ungetc(c, draft_file);
520 if (++n >= field_size - 1) {
521 field = (char *)mh_xrealloc(field, field_size += 256);
531 make_mime_composition_file_entry(char *file_name)
536 char content_type[BUFSIZ];
538 char *cmd = mimetypequeryproc;
540 for (np = m_defs; np; np = np->n_next) {
541 if (strcasecmp(np->n_name, mimetypequery)==0) {
546 snprintf(cmdbuf, sizeof cmdbuf, "%s %s", cmd, file_name);
548 if (!(fp = popen(cmdbuf, "r"))) {
549 clean_up_temporary_files();
550 adios(NULL, "unable to determine content type with `%s'",
553 if (fgets(content_type, sizeof content_type, fp) &&
554 (cp = strrchr(content_type, '\n'))) {
557 strcpy(content_type, "application/octet-stream");
558 admonish(NULL, "problems with `%s', using fall back type `%s'",
559 cmdbuf, content_type);
563 /* TODO: don't use access(2) because it checks for ruid, not euid */
564 if (access(file_name, R_OK) != 0) {
565 clean_up_temporary_files();
566 adios(NULL, "unable to access file `%s'", file_name);
569 cp = (!(cp = strrchr(file_name, '/'))) ? file_name : cp + 1;
570 fprintf(composition_file, "#%s; name=\"%s\" <> [%s] {attachment}",
571 content_type, cp, cp);
573 fprintf(composition_file, " %s\n", file_name);
579 ** The back-end of the message sending back-end.
580 ** Annotate original message, and call `spost' to send message.
583 sendaux(char **vec, int vecp, char *drft, struct stat *st)
590 if (distfile && distout(drft, distfile, backup) == NOTOK) {
595 switch (child_id = fork()) {
597 /* oops -- fork error */
598 adios("fork", "unable to");
599 break; /* NOT REACHED */
602 /* child process -- send it */
604 fprintf(stderr, "unable to exec ");
607 break; /* NOT REACHED */
610 /* parent process -- wait for it */
611 if ((status = pidwait(child_id, NOTOK)) == OK) {
617 advise(NULL, "message not delivered to anyone");
620 if (rename(backup, drft) == NOTOK) {
621 advise(drft, "unable to rename %s to",
634 anno(struct stat *st)
640 if (altmsg && (stat(altmsg, &st2) == NOTOK ||
641 st->st_mtime != st2.st_mtime ||
642 st->st_dev != st2.st_dev ||
643 st->st_ino != st2.st_ino)) {
645 admonish(NULL, "$mhaltmsg mismatch");
650 if (!(folder = getenv("mhfolder")) || !*folder) {
652 admonish(NULL, "$mhfolder not set");
656 if (!(msgs = getenv("mhmessages")) || !*msgs) {
658 admonish(NULL, "$mhmessages not set");
663 advise(NULL, "annotate as `%s': %s %s", annotext,
666 snprintf(buf, sizeof buf, "anno -comp '%s' '+%s' %s",
667 annotext, folder, msgs);
668 if (system(buf) != 0) {
669 advise(NULL, "unable to annotate");
675 armed_done(int status)
677 longjmp(env, status ? status : NOTOK);