Logo Search packages:      
Sourcecode: af version File versions  Download package

sendmail.c

/* Sendmail.c - Af routines to send mail outwards.
   Copyright (C) 1990 - 2003 Malc Arnold.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include "af.h"
#include "keyseq.h"
#include "functions.h"
#include "sendmail.h"
#include "variable.h"
#include "io.h"
#include "mime.h"
#include "misc.h"
#include STRING_HDR

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */

#ifdef __STDC__
#include <stdarg.h>
#else /* ! __STDC__ */
#include <varargs.h>
#endif /* ! __STDC__ */

/****************************************************************************/
/* RCS info */

#ifndef lint
static char *RcsId = "$Id: sendmail.c,v 2.6 2003/10/27 23:16:20 malc Exp $";
static char *SendmailId = SENDMAILID;
#endif /* ! lint */

/****************************************************************************/
/* Global function declarations */

extern char *xmalloc(), *xrealloc(), *xstrdup(), *vstrcat();
extern char *strerror(), *get_vtext(), *utos(), *get_header();
extern char *c_contype(), *c_encoding(), *content_type();
extern char *get_temp_file();
extern int wait(), strcasecmp(), strncasecmp(), confirm();
extern int select_key(), shellout(), get_vval();
extern int write_composition(), save_composition();
extern int edit_composition(), compose_mail();
extern int attach_messages();
extern unsigned sleep(), save_atimer(), restore_atimer();
extern void free(), _exit(), typeout(), showtext(), msg();
extern void emsgl(), cmsg(), show_decoded_text();
extern void reset_signals(), add_posting_headers();
extern void free_composition();
extern COMPOSITION *init_composition();

#ifdef MTA_IS_SMTP
extern int smtp_deliver();
extern char *get_addr();
#endif /* MTA_IS_SMTP */

#ifndef MTA_MMDF_FORMAT
extern char *get_addr();
#endif /* ! MTA_MMDF_FORMAT */

#ifdef MTA_NEEDS_ARGS
extern char **addr_args();
#endif /* MTA_NEEDS_ARGS */

/* Local function declarations */

static char **mailargs();
static int do_send(), exec_mta(), post();
static unsigned mail_size();
static void summarise_mail(), list_mail();
static void spell_check_mail();

#ifndef MTA_IS_SMTP
#ifdef __STDC__
static char **argvec(char *, char *, ...);
#else /* ! __STDC__ */
static char **argvec();
#endif /* ! __STDC__ */
#endif /* ! MTA_IS_SMTP */

#ifdef MTA_NEEDS_ARGS
static char *set_addrs();
#endif /* MTA_NEEDS_ARGS */

/****************************************************************************/
/* Import the system error number */

extern int errno;

/****************************************************************************/
/* Import the user quit flag from commands.c */

extern int user_quit;

/****************************************************************************/
int send_mail(to, cc, bcc, subject, contype, multipart)
char *to, *cc, *bcc, *subject, *contype;
int multipart;
{
      /* Simple send out of edited file */

      return(do_send(NULL, to, cc, bcc, subject, contype, NULL,
                   NULL, SM_SEND | ((multipart) ? SM_MPART : 0)));
}
/****************************************************************************/
int send_file(filnam, to, ask_ctype)
char *filnam, *to;
int ask_ctype;
{
      /* Simple send out of (edited) file */

      char *contype;

      /* First see if we can derive a content-type from the file name */

      contype = content_type(filnam);
      ask_ctype = (ask_ctype || contype != NULL);

      /* Now actually send the mail */

      return(do_send(filnam, to, NULL, NULL, NULL, contype, NULL,
                   NULL, SM_SEND | ((ask_ctype) ? SM_MIME : 0)));
}
/****************************************************************************/
int send_reply(to, cc, orig_msg)
char *to, *cc;
MESSAGE *orig_msg;
{
      /* Send mail in reply to some source */

      return(do_send(NULL, to, cc, NULL, orig_msg->subject,
                   NULL, orig_msg, NULL, SM_REPLY));
}
/****************************************************************************/
int send_forward(to, orig_msg)
char *to;
MESSAGE *orig_msg;
{
      /* Send existing message, possibly editing */

      return(do_send(NULL, to, NULL, NULL, orig_msg->subject,
                   NULL, orig_msg, NULL, SM_FORWARD));
}
/****************************************************************************/
int send_bounce(to, orig_msg)
char *to;
MESSAGE *orig_msg;
{
      /* Bounce mail - send with original headers */

      return(do_send(NULL, to, NULL, NULL, NULL,
                   NULL, orig_msg, NULL, SM_BOUNCE));
}
/****************************************************************************/
int send_attached(to, subject, attachments, all_headers)
char *to, *subject;
MESSAGE **attachments;
int all_headers;
{
      /* Send existing messages as an attachment */

      return(do_send(NULL, to, NULL, NULL, subject, NULL, NULL, attachments,
                   SM_ATTACH | ((all_headers) ? SM_ALL_HEADERS : 0)));
}
/****************************************************************************/
int quiet_mail(to, cc, bcc, subject, contype)
char *to, *cc, *bcc, *subject, *contype;
{
      /* Send from standard input with no interaction */

      return(do_send(NULL, to, cc, bcc, subject,
                   contype, NULL, NULL, SM_SILENT));
}
/****************************************************************************/
static int do_send(filnam, to, cc, bcc, subject, contype,
               orig_msg, attachments, mail_flags)
char *filnam, *to, *cc, *bcc, *subject, *contype;
MESSAGE *orig_msg, **attachments;
int mail_flags;
{
      /* Form a mail message, and send it after confirmation */

      char *prompt, *spellcheck;
      int status;
      COMPOSITION *comp;

      /* Initialise the composition */

      if ((comp = init_composition(to, cc, bcc, subject, contype, filnam,
                             orig_msg, mail_flags)) == NULL) {
            /* The user quit the composition or we got an error */

            return(FALSE);
      }

      /* Do any initial edit of the outgoing text and get the headers */

      if (!comp->bounce && !comp->file && !comp->silent
          && !edit_composition(comp, get_vval(V_EDIT_IHDRS), TRUE)) {
            /* Composition failed somehow */

            free_composition(comp);
            return(FALSE);
      }

      /* Attach any specified messages now */

      if (attachments != NULL &&
          !attach_messages(comp, attachments,
                       (mail_flags & SM_ALL_HEADERS))) {
            /* Failed to attach the messages */

            free_composition(comp);
            return(FALSE);
      }

      /* Post silent mail now */

      if (comp->silent) {
            (void) post(comp, FALSE);
            free_composition(comp);
            return(TRUE);
      }

      /* Get the interactive spelling checker to use */

      spellcheck = get_vtext(V_SPELLCHECK);

      /* Summarise the composition's headers */

      summarise_mail(comp);

      /* Form the prompt for the user */

      prompt = vstrcat("Send, Edit, Attachments, ", (spellcheck == NULL) ?
                   "" : "Check Spelling, ", "List or Forget?", NULL);

      /* Loop until the user selects send or forget */

      for (;;) {
            /* Now handle the user response */

            switch (select_key(prompt, (spellcheck != NULL) ?
                           SC_MAIL_OPTS : MAIL_OPTS, TRUE)) {
            case SEND_MESSAGE:
                  /* Send the mail to the MTA */

                  status = post(comp, TRUE);
                  free(prompt);
                  free_composition(comp);
                  return(status);

            case EDIT_MESSAGE:
                  /* Edit the outgoing message again */

                  if (!edit_composition(comp, V_TRUE, TRUE)) {
                        free_composition(comp);
                        return(FALSE);
                  }
                  summarise_mail(comp);
                  break;

            case ATTACHMENTS:
                  /* Drop into compose mode on the message */

                  if (!compose_mail(comp)) {
                        free_composition(comp);
                        return(FALSE);
                  }
                  summarise_mail(comp);
                  break;

            case CHECK_SPELLING:
                  /* Spell check the outgoing message */

                  if (spellcheck != NULL) {
                        spell_check_mail(comp, spellcheck);
                        summarise_mail(comp);
                  }
                  break;

            case LIST_MESSAGE:
                  /* List the message to typeout */

                  list_mail(comp);
                  summarise_mail(comp);
                  break;

            case FORGET_MESSAGE:
                  /* Forget about the message */

                  msg("(Mail forgotten)");

                  /* Fall through into quit case */

            case EOF:
                  free_composition(comp);
                  return(FALSE);
            }
      }
      /*NOTREACHED*/
}
/****************************************************************************/
static void summarise_mail(comp)
COMPOSITION *comp;
{
      /* Summarise the mail; to be sent via showtext */

      HEADER *h;

      /* First let the user know what they can see */

      showtext("Summary of message headers: \n\n");

      /* Then list the headers via showtext */

      for (h = comp->headers; !user_quit && h != NULL; h = h->next) {
            /* Should this header be listed? */

            if (h->edit && h->text != NULL) {
                  /* Show the header */

                  showtext(h->name);
                  showtext(" ");
                  showtext(h->text);
                  showtext("\n");
            }
      }

      /* End the text and reset the quit flag */

      showtext(NULL);
      user_quit = FALSE;
      return;
}
/****************************************************************************/
static void list_mail(comp)
COMPOSITION *comp;
{
      /* List the mail to be sent to typeout */

      char *ctype, *enc, *slash;
      int textual;
      HEADER *h;

      /* Find out if the file is textual, and how it's encoded */

      ctype = c_contype(get_header(comp, CONTENT_TYPE));
      slash = (ctype != NULL) ? strchr(ctype, '/') : NULL;
      textual = (ctype == NULL || slash == NULL ||
               !strncasecmp(ctype, TEXT_TYPE, slash - ctype));
      enc = c_encoding(get_header(comp, C_T_ENCODING));
      enc = (enc != NULL || textual) ? enc : xstrdup(BASE64);
      free(ctype);

      /* First list the headers */

      for (h = comp->headers; !user_quit && h != NULL; h = h->next) {
            /* Show the header if it is set */

            if (h->text != NULL) {
                  /* Show the header line */

                  typeout(h->name);
                  typeout(" ");
                  typeout(h->text);
                  typeout("\n");
            }
      }

      /* Now list the decoded message body */

      if (!user_quit) {
            /* Show a blank line and then the decoded body */
            typeout("\n");
            show_decoded_text(comp->body, enc, textual);
      }
      free(enc);

      /* End the typeout and reset the quit flag */

      typeout(NULL);
      user_quit = FALSE;
      return;
}
/****************************************************************************/
static void spell_check_mail(comp, spellcheck)
COMPOSITION *comp;
char *spellcheck;
{
      /* Interactively spell-check a message */

      char *tfile, *cmd;
      ATIMER tbuf;

      /* Get the name of a temporary file to use */

      if ((tfile = tempnam(TFILEDIR, TFILEPFX)) == NULL) {
            emsgl("Can't create temporary file: ", strerror(errno), NULL);
      }

      /* Write the decoded message to a file and spell check it */

      if (!save_composition(comp, tfile, CS_BODY | CS_EDIT)) {
            /* Couldn't write the temp file, update the display */

            summarise_mail(comp);

            /* And pause so the user can see the message */

            (void) save_atimer(&tbuf);
            (void) sleep(ECHO_DELAY);
            (void) restore_atimer(&tbuf);

            /* Clean up and fail */

            (void) unlink(tfile);
            free(tfile);
            return;
      }

      /* Build the command to run the spell checker */

      cmd = vstrcat(spellcheck, " ", tfile, NULL);

      /* Now run the interactive spell checker */

      if (!shellout(cmd, FALSE, FALSE, NULL, NULL)) {
            /* Spell check failed, update the display */

            summarise_mail(comp);

            /* And pause so the user can see the message */

            (void) save_atimer(&tbuf);
            (void) sleep(ECHO_DELAY);
            (void) restore_atimer(&tbuf);
      }

      /* Clean up and return */

      (void) unlink(tfile);
      free(tfile);
      free(cmd);
      return;
}
/****************************************************************************/
static int post(comp, verbose)
COMPOSITION *comp;
int verbose;
{
      /* Send mail via the MTA */

      char **argv, **p;
      char *ofolder, *prompt;
      int status, othreshold;
      unsigned mlines;

      /* Get the folder and threshold for saving outbound messages */

      ofolder = get_vtext(V_OUTBOUND);
      othreshold = get_vval(V_THRESHOLD);
      mlines = mail_size(comp);

      /* Check if we want to save a long message */

      if (ofolder != NULL && (othreshold > 0) && othreshold < mlines) {
            prompt = vstrcat("Save ", utos(mlines), "-line message to ",
                         ofolder, "? ", NULL);

            /* Get confirmation for the save if possible */

            if (!verbose || !confirm(prompt, FALSE)) {
                  /* Check if the user quit */

                  if (verbose && user_quit) {
                        return(FALSE);
                  }
                  ofolder = NULL;
            }
            free(prompt);
      }

      /* Tell the user what we're doing */

      msg("Sending mail...");

      /* Set up the command we're going to execute */

      argv = mailargs(comp);

      /* Add any headers needed before we send */

      add_posting_headers(comp);

      /* Now submit the mail by the required method */

#ifdef MTA_IS_SMTP
      /* Submit the mail to a remote host via SMTP */

      status = smtp_deliver(comp, get_vtext(V_SMTP_HOST),
                        get_addr(), argv);
#else /* ! MTA_IS_SMTP */
      /* Exec the MTA and pipe the mail into it */

      status = exec_mta(comp, argv);
#endif /* MTA_IS_SMTP */

      /* Save the message to the outbound folder if required */

      if (status && (ofolder == NULL
                   || save_composition(comp, ofolder, CS_MBOX))) {
            cmsg(" Done");
      }

      /* Free the argument vector and return */

      for (p = argv; *p != NULL; p++) {
            free(*p);
      }
      free(argv);
      return(status);
}
/****************************************************************************/
static int exec_mta(comp, argv)
COMPOSITION *comp;
char **argv;
{
      /* Actually send the mail, by spawning the MTA */

      int fds[2], fd;
      int status = 0;
      FILE *fp;

      /* Set up the file descriptors for the pipe */

      if (pipe(fds) < 0) {
            emsgl("Mail delivery failed: ", strerror(errno), NULL);
            return(FALSE);
      }

      /* Now we fork */

      switch(fork()) {
      case -1:                                  /* Failed */
            emsgl("Mail delivery failed: ", strerror(errno), NULL);
            return(FALSE);

      case 0:                                         /* Child */
            /* Make stdin the read end of the pipe */

            (void) close(fds[1]);
            (void) dup2(fds[0], 0);

            /* Redirect stdout & stderr to /dev/null if possible */

            if ((fd = open(BITBUCKET, O_WRONLY, 0)) >= 0) {
                  /* Redirect stdout and stderr */

                  (void) dup2(fd, 1);
                  (void) dup2(fd, 2);
            }

            /* Reset the signal mask and handlers */

            reset_signals();

            /* Exec the MTA or exit if we have a disaster */

            (void) execv(argv[0], argv);
            _exit(1);

      default:                                  /* Parent */
            /* Open a file pointer from the write descriptor */

            (void) close(fds[0]);
            fp = fdopen(fds[1], "w");

            /* Write the message headers and body to the pipe */

            (void) write_composition(comp, fp, NULL, CS_SAVE);

            /* Close the pipe and wait for the child */

            (void) fclose(fp);
            (void) wait(&status);

            /* And return success */

            return(TRUE);
      }
      /*NOTREACHED*/
}
/****************************************************************************/
static unsigned mail_size(comp)
COMPOSITION *comp;
{
      /* Return the number of lines the composition's body */

      unsigned lines = 0;
      TEXTLINE *t;

      /* Count the lines in the body */

      for (t = comp->body; t != NULL; t = t->next) {
            lines++;
      }

      /* And return the number of lines */

      return(lines);
}
/****************************************************************************/
/*ARGSUSED*/
static char **mailargs(comp)
COMPOSITION *comp;
{
      /*
       * Form the shell command line to exec MAILPROG.
       * If MTA_NEEDS_ARGS is set then we need to put
       * the list of destinations on the command line,
       * otherwise we just call MAILPROG.
       */

      char **args = NULL;

#ifdef MTA_NEEDS_ARGS
      char *users;
#endif /* MTA_NEEDS_ARGS */

#ifdef MTA_IS_SMTP
      /* Initialise the arguments to empty */

      args = (char **) xmalloc(sizeof(char *));
      args[0] = NULL;
#else /* ! MTA_IS_SMTP */
      /* Build the basic argument vector from MAILPROG */

      args = argvec(MTA, NULL);
#endif /* ! MTA_IS_SMTP */

#ifdef MTA_NEEDS_ARGS
      /* Find the headers specifying destination */

      users = set_addrs(comp);

      /* Add the destinations to the argument vector */

      args = addr_args(args, users);
      free(users);
#endif /* MTA_NEEDS_ARGS */

      /* Return the argument list for the delivery */

      return(args);
}
/****************************************************************************/
#ifndef MTA_IS_SMTP
/*VARARGS 1*/
#ifdef __STDC__
static char **argvec(char *prog, char *arg, ...)
#else /* ! __STDC__ */
static char **argvec(prog, va_alist)
char *prog;
va_dcl
#endif /* ! __STDC__ */
{
      /*
       * Build an argument vector for prog and the arguments.
       * The calling sequence is :
       *    argvec(prog, arg1, arg2, ... , NULL)
       *
       * All arguments must be strings.
       *
       * NB: This function does not handle quoting or backslash escapes.
       */

      char *line, **argv = NULL, *space;
      int argno;
      va_list arglist;

#ifndef __STDC__
      char *arg;
#endif /* ! __STDC__ */

      /* Initialise vararg handling */

#ifdef __STDC__
      va_start(arglist, arg);
#else /* ! __STDC__ */
      va_start(arglist);
      arg = va_arg(arglist, char *);
#endif /* ! __STDC__ */
      
      /* Copy prog into line */

      line = xmalloc(strlen(prog) + 1);
      (void) strcpy(line, prog);

      /* Now loop through the arguments, adding them to line */

      while (arg != NULL) {
            /* Add this argument to the line */

            line = xrealloc(line, strlen(line) + strlen(arg) + 2);
            (void) strcat(line, " ");
            (void) strcat(line, arg);

            /* Move to the next argument */

            arg = va_arg(arglist, char *);
      }

      /* Clean up the varargs handling */

      va_end(arglist);

      /* Now build the argument vector */

      arg = line;
      argno = 0;

      while (*arg != '\0') {
            /* Find the next space in the line */

            for (space = arg; *space != '\0'; space++) {
                  if (isspace(*space)) {
                        *space++ = '\0';

                        /* Skip multiple whitespace characters */

                        while (isspace(*space)) {
                              space++;
                        }

                        break;
                  }
            }

            /* Now (re)allocate the argument vector */

            if (argv == NULL) {
                  argv = (char **) xmalloc((argno + 2) *
                                     sizeof(char *));
            } else {
                  argv = (char **) xrealloc(argv, (argno + 2)
                                      * sizeof(char *));
            }

            /* Copy the argument into the vector */

            argv[argno] = xmalloc(strlen(arg) + 1);
            (void) strcpy(argv[argno], arg);

            argno++;
            arg = space;
      }

      /* Add the terminator to the vector */

      argv[argno] = NULL;

      /* Clean up and return the vector */

      free(line);
      return(argv);
}
#endif /* ! MTA_IS_SMTP */
/****************************************************************************/
#ifdef MTA_NEEDS_ARGS
static char *set_addrs(comp)
COMPOSITION *comp;
{
      /* Form a list of destinations from composition's headers */

      char *users = NULL, *new_users;
      int resent = FALSE, new_resent;
      HEADER *h;

      /* Loop through the headers handling each one */

      for (h = comp->headers; h != NULL; h = h->next) {
            /* Initialise for this header */

            new_users = NULL;
            new_resent = FALSE;

            /* Is it a Resent-To: or To: header? */

            if (!strcasecmp(h->name, RESENT_TO)) {
                  new_users = h->text;
                  new_resent = TRUE;
            } else if (!resent && !strcasecmp(h->name, TO)) {
                  new_users = h->text;
                  new_resent = FALSE;

#ifdef NO_MTA_CC
            /* The MTA doesn't handle Cc or Bcc, do it ourselves */

            } else if (!strcasecmp(h->name, RESENT_CC)
                     || !strcasecmp(h->name, RESENT_BCC)) {
                  new_users = h->text;
                  new_resent = TRUE;
            } else if (!resent && (!strcasecmp(h->name, CC) ||
                               !strcasecmp(h->name, BCC))) {
                  new_users = h->text;
                  new_resent = FALSE;
#endif /* NO_MTA_CC */
            }

            /* If new_users is non-null then add them */

            if (new_users != NULL) {
                  /*
                   * Discard the existing user list if the new header
                   * found is a Resent- header and the previous ones
                   * weren't.
                   */

                  if (!resent && new_resent) {
                        if (users != NULL) {
                              free(users);
                              users = NULL;
                        }
                        resent = TRUE;
                  }

                  /* Now add the address to users */

                  if (users == NULL) {
                        users = xstrdup(new_users);
                  } else {
                        users = xrealloc(users, strlen(users) +
                                     strlen(new_users) + 2);
                        (void) strcat(users, " ");
                        (void) strcat(users, new_users);
                  }
            }
      }

      return(users);
}
#endif /* MTA_NEEDS_ARGS */
/****************************************************************************/

Generated by  Doxygen 1.6.0   Back to index