Logo Search packages:      
Sourcecode: mailutils version File versions

send.c

/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 2003, 2005 Free Software Foundation, Inc.

   GNU Mailutils 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.

   GNU Mailutils 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 GNU Mailutils; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  */

/* MH send command */

#include <mh.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <pwd.h>

const char *program_version = "send (" PACKAGE_STRING ")";
/* TRANSLATORS: Please, preserve the vertical tabulation (^K character)
   in this message */
static char doc[] = N_("GNU MH send\v\
Options marked with `*' are not yet implemented.\n\
Use -help to obtain the list of traditional MH options.");
static char args_doc[] = N_("file [file...]");


/* GNU options */
static struct argp_option options[] = {
  {"alias",         ARG_ALIAS,         N_("FILE"), 0,
   N_("Specify additional alias file") },
  {"draft",         ARG_DRAFT,         NULL, 0,
   N_("Use prepared draft") },
  {"draftfolder",   ARG_DRAFTFOLDER,   N_("FOLDER"), 0,
   N_("Specify the folder for message drafts") },
  {"draftmessage",  ARG_DRAFTMESSAGE,  NULL, 0,
   N_("Treat the arguments as a list of messages from the draftfolder") },
  {"nodraftfolder", ARG_NODRAFTFOLDER, NULL, 0,
   N_("Undo the effect of the last --draftfolder option") },
  {"filter",        ARG_FILTER,        N_("FILE"), 0,
  N_("* Use filter FILE to preprocess the body of the message") },
  {"nofilter",      ARG_NOFILTER,      NULL, 0,
   N_("* Undo the effect of the last --filter option") },
  {"format",        ARG_FORMAT,        N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("* Reformat To: and Cc: addresses") },
  {"noformat",      ARG_NOFORMAT,      NULL, OPTION_HIDDEN, "" },
  {"forward",       ARG_FORWARD,       N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("* In case of failure forward the draft along with the failure notice to the sender") },
  {"noforward",     ARG_NOFORWARD,     NULL, OPTION_HIDDEN, "" },
  {"mime",          ARG_MIME,          N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("* Use MIME encapsulation") },
  {"nomime",        ARG_NOMIME,        NULL, OPTION_HIDDEN, "" },
  {"msgid",         ARG_MSGID,         N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("Add Message-ID: field") },
  {"nomsgid",       ARG_NOMSGID,       NULL, OPTION_HIDDEN, ""},
  {"push",          ARG_PUSH,          N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("Run in the backround.") },
  {"nopush",        ARG_NOPUSH,        NULL, OPTION_HIDDEN, "" },
  {"split",         ARG_SPLIT,         N_("SECONDS"), 0,
   N_("* Split the draft into several partial messages and send them with SECONDS interval") },
  {"verbose",       ARG_VERBOSE,       N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("Print the transcript of interactions with the transport system") },
  {"noverbose",     ARG_NOVERBOSE,     NULL, OPTION_HIDDEN, "" },
  {"watch",         ARG_WATCH,         N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("Monitor the delivery of mail") },
  {"nowatch",       ARG_NOWATCH,       NULL, OPTION_HIDDEN, "" },
  {"width",         ARG_WIDTH,         N_("NUMBER"), 0,
   N_("* Make header fields no longer than NUMBER columns") },
  {"license", ARG_LICENSE, 0,      0,
   N_("Display software license"), -1},
  { 0 }
};

/* Traditional MH options */
struct mh_option mh_option[] = {
  {"alias",         1, 0, "aliasfile" },
  {"draft",         5, 0, NULL },
  {"draftfolder",   6, 0, "folder" },
  {"draftmessage",  6, 0, "message"},
  {"nodraftfolder", 3, 0, NULL },
  {"filter",        2, 0, "filterfile"},
  {"nofilter",      3, 0, NULL },
  {"format",        4, MH_OPT_BOOL, NULL},
  {"forward",       4, MH_OPT_BOOL, NULL},
  {"mime",          2, MH_OPT_BOOL, NULL},
  {"msgid",         2, MH_OPT_BOOL, NULL},
  {"push",          1, MH_OPT_BOOL, NULL},
  {"split",         1, 0, "seconds"},
  {"verbose",       1, MH_OPT_BOOL, NULL},
  {"watch",         2, MH_OPT_BOOL, NULL},
  {"width",         2, 0, NULL },
  { 0 }
};

static int use_draft;            /* Use the prepared draft */
static char *draft_folder;       /* Use this draft folder */
static int reformat_recipients;  /* --format option */
static int forward_notice;       /* Forward the failure notice to the sender,
                            --forward flag */
static int mime_encaps;          /* Use MIME encapsulation */
static int append_msgid;         /* Append Message-ID: header */
static int background;           /* Operate in the background */

static int split_message;            /* Split the message */
static unsigned long split_interval; /* Interval in seconds between sending two
                              successive partial messages */

static int verbose;              /* Produce verbose diagnostics */
static int watch;                /* Watch the delivery process */
static unsigned width = 76;      /* Maximum width of header fields */

#define WATCH(c) do {\
  if (watch)\
    watch_printf c;\
} while (0)

static int
opt_handler (int key, char *arg, void *unused, struct argp_state *state)
{
  char *p;
  
  switch (key)
    {
    case ARG_ALIAS:
      mh_alias_read (arg, 1);
      break;
      
    case ARG_DRAFT:
      use_draft = 1;
      break;
      
    case ARG_DRAFTFOLDER:
      draft_folder = arg;
      break;
      
    case ARG_NODRAFTFOLDER:
      draft_folder = NULL;
      break;
      
    case ARG_DRAFTMESSAGE:
      if (!draft_folder)
      draft_folder = mh_global_profile_get ("Draft-Folder",
                                    mu_path_folder_dir);
      break;
      
    case ARG_FILTER:
    case ARG_NOFILTER:
      return 1;
      
    case ARG_FORMAT:
      reformat_recipients = is_true(arg);
      break;
      
    case ARG_NOFORMAT:
      reformat_recipients = 0;
      break;
      
    case ARG_FORWARD:
      forward_notice = is_true(arg);
      break;
      
    case ARG_NOFORWARD:
      forward_notice = 0;
      break;
      
    case ARG_MIME:
      mime_encaps = is_true(arg);
      break;
      
    case ARG_NOMIME:
      mime_encaps = 0;
      break;
      
    case ARG_MSGID:
      append_msgid = is_true(arg);
      break;
      
    case ARG_NOMSGID:
      append_msgid = 0;
      break;
      
    case ARG_PUSH:
      background = is_true(arg);
      break;
      
    case ARG_NOPUSH:
      background = 0;
      break;
      
    case ARG_SPLIT:
      split_message = 1;
      split_interval = strtoul(arg, &p, 10);
      if (*p)
      {
        argp_error (state, _("Invalid number"));
        exit (1);
      }
      break;
      
    case ARG_VERBOSE:
      verbose = is_true(arg);
      break;
      
    case ARG_NOVERBOSE:
      verbose = 0;
      break;
      
    case ARG_WATCH:
      watch = is_true(arg);
      break;
      
    case ARG_NOWATCH:
      watch = 0;
      break;
      
    case ARG_WIDTH:
      width = strtoul(arg, &p, 10);
      if (*p)
      {
        argp_error (state, _("Invalid number"));
        exit (1);
      }
      break;
      
    case ARG_LICENSE:
      mh_license (argp_program_version);
      break;

    default:
      return 1;
    }
  return 0;
}

static void
watch_printf (const char *fmt, ...)
{
  va_list ap;

  va_start (ap, fmt);
  vfprintf (stderr, fmt, ap);
  fprintf (stderr, "\n");
  va_end (ap);
}

static list_t mesg_list;
static mh_context_t *mts_profile;

int
check_file (char *name)
{
  message_t msg;

  msg = mh_file_to_message (draft_folder, name);
  if (!msg)
    return 1;
  if (!mesg_list && list_create (&mesg_list))
    {
      mh_error (_("Cannot create message list"));
      return 1;
    }
  
  return list_append (mesg_list, msg);
}

void
read_mts_profile ()
{
  char *p;

  p = mh_expand_name (MHLIBDIR, "mtstailor", 0);
  if (!p)
    {
      char *home = mu_get_homedir ();
      if (!home)
      abort (); /* shouldn't happen */
      asprintf (&p, "%s/%s", home, ".mtstailor");
      free (home);
    }
  mts_profile = mh_context_create (p, 1);
  mh_context_read (mts_profile);
}


mailer_t
open_mailer ()
{
  char *url = mh_context_get_value (mts_profile,
                            "url",
                            "sendmail:/usr/sbin/sendmail");
  mailer_t mailer;
  int status;
    
  WATCH ((_("Creating mailer %s"), url));
  status = mailer_create (&mailer, url);
  if (status)
    {
      mh_error(_("Cannot create mailer `%s'"), url);
      return NULL;
    }

  if (verbose)
    {
      mu_debug_t debug = NULL;
      mailer_get_debug (mailer, &debug);
      mu_debug_set_level (debug, MU_DEBUG_TRACE | MU_DEBUG_PROT);
    }

  WATCH ((_("Opening mailer %s"), url));
  status = mailer_open (mailer, MU_STREAM_RDWR);
  if (status)
    {
      mh_error(_("Cannot open mailer `%s'"), url);
      return NULL;
    }
  return mailer;
}

static void
create_message_id (header_t hdr)
{
  char *p = mh_create_message_id (0);
  header_set_value (hdr, MU_HEADER_MESSAGE_ID, p, 1);
  free (p);
}

static char *
get_sender_personal ()
{
  char *s = mh_global_profile_get ("signature", getenv ("SIGNATURE"));
  if (!s)
    {
      struct passwd *pw = getpwuid (getuid ());
      if (pw && pw->pw_gecos[0])
      {
        char *p = strchr (pw->pw_gecos, ',');
        if (p)
          *p = 0;
        s = pw->pw_gecos;
      }
    }
  return s;
}

static void
set_address_header (header_t hdr, char *name, address_t addr)
{
  size_t s = address_format_string (addr, NULL, 0);
  char *value = xmalloc (s + 1);
  address_format_string (addr, value, s);
  header_set_value (hdr, name, value, 1);
  free (value);
}

void
expand_aliases (message_t msg)
{
  header_t hdr;
  size_t i, num;
  char *buf;
  address_t addr_to = NULL,
            addr_cc = NULL,
            addr_bcc = NULL;
  
  message_get_header (msg, &hdr);
  header_get_field_count (hdr, &num);
  for (i = 1; i <= num; i++)
    {
      if (header_aget_field_name (hdr, i, &buf) == 0)
      {
        if (strcasecmp (buf, MU_HEADER_TO) == 0
            || strcasecmp (buf, MU_HEADER_CC) == 0
            || strcasecmp (buf, MU_HEADER_BCC) == 0)
          {
            char *value;
            address_t addr = NULL;
            int incl;
            
            header_aget_field_value_unfold (hdr, i, &value);
            
            mh_alias_expand (value, &addr, &incl);
            free (value);
            if (strcasecmp (buf, MU_HEADER_TO) == 0)
            address_union (&addr_to, addr);
            else if (strcasecmp (buf, MU_HEADER_CC) == 0)
            address_union (&addr_cc, addr);
            else if (strcasecmp (buf, MU_HEADER_BCC) == 0)
            address_union (&addr_bcc, addr);
          }
        free (buf);
      }
    }

  if (addr_to)
    {
      set_address_header (hdr, MU_HEADER_TO, addr_to);
      address_destroy (&addr_to);
    }

  if (addr_cc)
    {
      set_address_header (hdr, MU_HEADER_CC, addr_cc);
      address_destroy (&addr_cc);
    }

  if (addr_bcc)
    {
      set_address_header (hdr, MU_HEADER_BCC, addr_bcc);
      address_destroy (&addr_bcc);
    }
}

void
fix_fcc (message_t msg)
{
  header_t hdr;
  char *val;
  
  message_get_header (msg, &hdr);
  if (header_aget_value (hdr, MU_HEADER_FCC, &val) == 0
      && strchr ("+%~/=", val[0]) == NULL)
    {
      val = realloc (val, strlen (val) + 2);
      memmove (val + 1, val, strlen (val) + 1);
      val[0] = '+';
      header_set_value (hdr, MU_HEADER_FCC, val, 1);
      free (val);
    }  
}

int
_action_send (void *item, void *data)
{
  message_t msg = item;
  int rc;
  mailer_t mailer;
  header_t hdr;
  size_t n;

  WATCH ((_("Getting message")));

  if (message_get_header (msg, &hdr) == 0)
    {
      char date[80];
      time_t t = time (NULL);
      struct tm *tm = localtime (&t);
      
      strftime (date, sizeof date, "%a, %d %b %Y %H:%M:%S %Z", tm);
      header_set_value (hdr, MU_HEADER_DATE, date, 1);

      if (header_get_value (hdr, MU_HEADER_FROM, NULL, 0, &n))
      {
        char *from;
        char *email = mu_get_user_email (NULL);
        char *pers = get_sender_personal ();
        if (pers)
          {
            asprintf (&from, "\"%s\" <%s>", pers, email);
            free (email);
          }
        else
          from = email;

        header_set_value (hdr, MU_HEADER_FROM, from, 1);
        free (from);
      }
        
      if (append_msgid
        && header_get_value (hdr, MU_HEADER_MESSAGE_ID, NULL, 0, &n))
      create_message_id (hdr);
    }

  expand_aliases (msg);
  fix_fcc (msg);
  
  mailer = open_mailer ();
  if (!mailer)
    return 1;

  WATCH ((_("Sending message")));
  rc = mailer_send_message (mailer, msg, NULL, NULL);
  if (rc)
    {
      mh_error(_("Cannot send message: %s"), mu_strerror (rc));
      return 1;
    }

  WATCH ((_("Destroying the mailer")));
  mailer_close (mailer);
  mailer_destroy (&mailer);
  
  return 0;
}

static int
send (int argc, char **argv)
{
  int i, rc;
  char *p;
  
  /* Verify all arguments */
  for (i = 0; i < argc; i++)
    if (check_file (argv[i]))
      return 1;

  /* Process the mtstailor file and detach from the console if
     required */
  read_mts_profile ();
  
  if (background && daemon (0, 0) < 0)
    {
      mh_error(_("Cannot switch to background: %s"), mu_strerror (errno));
      return 1;
    }

  /* Prepend url specifier to the folder dir. We won't need this
     when the default format becomes configurable */
  asprintf (&p, "mh:%s", mu_path_folder_dir);
  mu_path_folder_dir = p;
  
  /* Finally, do the work */
  rc = list_do (mesg_list, _action_send, NULL);
  return rc;
}
        
int
main (int argc, char **argv)
{
  int index;
  
  mu_init_nls ();
  
  mu_argp_init (program_version, NULL);
  mh_argp_parse (&argc, &argv, 0, options, mh_option, args_doc, doc,
             opt_handler, NULL, &index);

  mh_read_aliases ();
  
  argc -= index;
  argv += index;

  if (argc == 0)
    {
      struct stat st;
      static char *xargv[2];
      xargv[0] = mh_draft_name ();

      if (stat (xargv[0], &st))
      {
        mh_error(_("cannot stat %s: %s"), xargv[0], mu_strerror (errno));
        return 1;
      }

      if (!use_draft && !mh_usedraft (xargv[0]))
      exit (0);
      xargv[1] = NULL;
      argv = xargv;
      argc = 1;
    }

  return send(argc, argv);  
}

Generated by  Doxygen 1.6.0   Back to index