/*
 * Copyright (c) 2003-2012
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * Simple outgoing email agent with API.
 *
 * 1. Support for DKIM is not yet implemented but is planned.
 * 2. So far the only mailer that has been tested is sendmail.
 *
 * RFC 822, RFC 2822
 */

/*
 * Email authentication and validation
 *
 * RFC4406 - Sender ID: Authenticating E-Mail
 * RFC4408 - Sender Policy Framework (SPF) for Authorizing Use of Domains
 *           in E-Mail, Version 1 
 * RFC4686 - Analysis of Threats Motivating DomainKeys Identified Mail (DKIM)
 * RFC4871 - DomainKeys Identified Mail (DKIM) Signatures (www.dkim.org)
 * RFC5016 - Requirements for a DomainKeys Identified Mail (DKIM) Signing
 *           Practices Protocol
 * RFC5451 - Message Header Field for Indicating Message Authentication Status
 * RFC5585 - DomainKeys Identified Mail (DKIM) Service Overview
 * RFC5617 - DomainKeys Identified Mail (DKIM) Author Domain Signing Practices
 *           (ADSP)
 * RFC5672 - RFC 4871 DomainKeys Identified Mail (DKIM) Signatures -- Update
 *
 * Yahoo! DomainKeys Patent License Agreement v1.2
 *   http://domainkeys.sourceforge.net/license/patentlicense1-2.html
 *
 * See:
 *   http://testing.dkim.org
 *   http://www.dkim.org/specs/draft-allman-dkim-base-01.html
 *   http://www.faqs.org/rfcs/rfc4871.txt
 *   http://domainkeys.sourceforge.net
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: email.c 2586 2012-03-15 16:21:40Z brachman $";
#endif

#include "dacs_config.h"
#include "dacs_api.h"
#include "dacs.h"
#include "email.h"

static MAYBE_UNUSED char *log_module_name = "email";

#ifndef DEFAULT_FROM_ADDR
#define DEFAULT_FROM_ADDR	"<nobody@example.com>"
#endif

#ifndef DEFAULT_SUBJECT
#define DEFAULT_SUBJECT		"DACS Mailer Test"
#endif

#ifndef BOUNDARY_SPEC
#define BOUNDARY_SPEC		"0-9"
#endif

#ifndef BOUNDARY_RAND_LEN
#define BOUNDARY_RAND_LEN	20
#endif

static int verbose_flag = 0;

#ifndef PROG

/*
 * Initialization for creating and sending an email message.
 * PROG_PATH is the full pathname of the program that will transport the
 * formatted message; the mailer (or message transport agent (MTA)) is
 * expected to extract the recipients' email addresses from the To/Cc/Bcc
 * headers (sendmail does this with its -t flag).
 * ALG is a canonicalization algorithm to use with DKIM processing.
 */
Email_message *
email_init(char *prog_path)
{
  Email_message *em;

  em = ALLOC(Email_message);
  em->mailer = ALLOC(Email_mailer);
  em->mailer->prog = prog_path;
  em->mailer->argv = dsvec_init(NULL, sizeof(char *));
  em->mailer->command_args = dsvec_init(NULL, sizeof(Email_command_arg *));
  em->mailer->env = dsvec_init(NULL, sizeof(char *));
  em->headers = dsvec_init(NULL, sizeof(Email_header *));
  em->body = ds_init(NULL);
  em->mesg = ds_init(NULL);
  em->dkim = NULL;
  em->finalized = 0;

  return(em);
}

Email_dkim *
email_dkim(Canon_alg alg)
{
  Email_dkim *dkim;

  dkim = ALLOC(Email_dkim);
  dkim->canon_alg = alg;
  dkim->canon_headers = ds_init(NULL);
  dkim->canon_body = ds_init(NULL);
  dkim->canon_mesg = ds_init(NULL);

  return(dkim);
}

/*
 * Create a new data structure for a MIME header named NAME with value VALUE.
 */
Email_header *
email_new_header(char *name, char *value)
{
  Email_header *eh;

  eh = ALLOC(Email_header);
  eh->name = strdup(name);
  eh->value = strdup(value);

  return(eh);
}

/*
 * Create a copy of MIME header OLD_EH.
 */
Email_header *
email_dup_header(Email_header *old_eh)
{
  Email_header *eh;

  eh = ALLOC(Email_header);
  eh->name = strdup(old_eh->name);
  eh->value = strdup(old_eh->value);

  return(eh);
}

/*
 * Append a command line flag to the list of arguments.
 * A flag can be multi-part (e.g., "-o foo.out").
 * NFIELDS is followed by that many strings.
 */
Email_command_arg *
email_add_mailer_arg(Email_message *em, unsigned int nfields, ...)
{
  int i;
  char *a;
  Email_command_arg *arg;
  va_list ap;

  if (em->finalized || nfields == 0)
	return(NULL);

  arg = ALLOC(Email_command_arg);
  arg->nfields = nfields;
  arg->fields = dsvec_init(NULL, sizeof(char *));

  va_start(ap, nfields);
  for (i = 0; i < nfields; i++) {
	a = va_arg(ap, char *);
	dsvec_add_ptr(arg->fields, a);
  }
  va_end(ap);

  dsvec_add_ptr(em->mailer->command_args, arg);

  return(arg);
}

/*
 * Append a header named NAME with value VALUE to the list of headers.
 * It assumes that duplicate names are not allowed.
 */
Dsvec *
email_add_header(Email_message *em, Email_header *new_eh)
{
  int i;
  Email_header *eh;

  if (em->finalized)
	return(NULL);

  for (i = 0; i < dsvec_len(em->headers); i++) {
	eh = (Email_header *) dsvec_ptr_index(em->headers, i);
	if (strcaseeq(eh->name, new_eh->name)) {
	  /* Duplicate! */
	  return(NULL);
	}
  }

  dsvec_add_ptr(em->headers, new_eh);

  return(em->headers);
}

/*
 * Search for a header named NAME in message EM.
 */
Email_header *
email_lookup_header(Email_message *em, char *name)
{
  int in;
  Email_header *eh;

  if ((in = dsvec_find(em->headers, (void *) name, NULL)) == 0)
	return(NULL);
  eh = dsvec_ptr_index(em->headers, in - 1);

  return(eh);
}

/*
 * Add a NAME header to EM that consists of comma-separated elements of DSV.
 * Duplicate values are allowed.
 */
static void
set_header(Email_message *em, char *name, Dsvec *dsv)
{
  int i;
  char *val;
  Ds ds;

  if (dsv == NULL || dsvec_len(dsv) == 0)
	return;

  ds_init(&ds);
  for (i = 0; i < dsvec_len(dsv); i++) {
	val = (char *) dsvec_ptr_index(dsv, i);
	ds_asprintf(&ds, "%s%s", val, (i == dsvec_len(dsv) - 1) ? "" : ", ");
  }

  email_add_header(em, email_new_header(name, ds_buf(&ds)));
}

#ifdef NOTDEF
/*
 * If a header named NAME exists, either set it to VALUE (VALUE != NULL) or
 * delete it (VALUE == NULL); if the header does not exist, add it if
 * VALUE is not NULL, otherwise its an error.
 */
Dsvec *
email_change_header(Email_message *em, char *name, char *value)
{

}

Dsvec *
email_delete_header(Email_message *em, char *name)
{

}

Dsvec *
email_delete_header(Email_message *em, char *name)
{

}

#endif

/*
 * Append an environment variable NAME with value VALUE to the list
 * of environment variables, which are made available to the mailer.
 */
char *
email_add_env(Email_message *em, char *name, char *value)
{
  char *env_str;

  if (em->finalized)
	return(NULL);

  env_str = ds_xprintf("%s=%s", name, value);
  dsvec_add_ptr(em->mailer->env, env_str);

  return(env_str);
}

char *
email_make_boundary(char *spec, size_t len)
{
  char *boundary, *bspec, *r;
  size_t blen;

  if ((bspec = spec) == NULL)
	bspec = BOUNDARY_SPEC;
  if ((blen = len) == 0)
	blen = BOUNDARY_RAND_LEN;

  r = crypto_make_random_string_from_spec(bspec, blen, 0);
  /* RFC 2045 S6.7 */
  boundary = ds_xprintf("_----------=_%s", r);

  return(boundary);
}

/*
 * Append text STR to the body of the message.
 * If DO_TRANSFORM is non-zero, pass the body through the DACS transform
 * filter, interpolating variable values in KWV (which are in the DACS
 * namespace).
 * The resulting text is not null-terminated (in case additional text is
 * going to be appended).
 */
int
email_append_to_body(Email_message *em, char *str, int do_transform, Kwv *kwv)
{
  char *body_str, *errmsg;

  if (em->finalized)
	return(-1);

  if (do_transform) {
	Transform_config *tc;

	tc = transform_init(NULL);
	tc->acls = NULL;
	tc->docs = NULL;
	var_ns_new(&tc->env->namespaces, "DACS", kwv);
	dsio_set(tc->ds_in, NULL, str, 0, 0);
	if (transform(tc, "", NULL, &errmsg) == -1) {
	  if (errmsg != NULL)
		fprintf(stderr, "During message body transform: %s\n", errmsg);
	  return(-1);
	}
	body_str = ds_buf(tc->ds_out);
  }
  else
	body_str = str;

  ds_append(em->body, body_str);
  /* NOT null-terminated */

  return(0);
}

/*
 * Set the body of message EM to BODY, deleting a trailing null byte.
 */
int
email_set_body(Email_message *em, Ds *body)
{

  if (em->finalized)
	return(-1);

  em->body = ds_dup(body);
  ds_zapnull(em->body);

  return(0);
}

/*
 * Canonicalize message EM's MIME header HEADER (in preparation for computing
 * a signature).
 */
static int
canon_header(Email_message *em, Email_header *header)
{
  Email_dkim *dkim;

  if ((dkim = em->dkim) == NULL)
	return(0);

  switch (dkim->canon_alg) {
  case CANON_SIMPLE:
  case CANON_NONE:
	ds_asprintf(dkim->canon_headers, "%s: %s\r\n", header->name, header->value);
	break;

  case CANON_RELAXED:
	/*
	 * RFC 4871 (3.4.2)
	 * Apply the following steps in order:
	 *
	 * o Convert all header field names (not the header field values) to
	 *   lowercase.  For example, convert "SUBJect: AbC" to "subject: AbC".
	 *
	 * o Unfold all header field continuation lines as described in
	 *   [RFC2822]; in particular, lines with terminators embedded in
	 *   continued header field values (that is, CRLF sequences followed by
	 *   WSP) MUST be interpreted without the CRLF.  Implementations MUST
	 *   NOT remove the CRLF at the end of the header field value.
	 *
	 * o Convert all sequences of one or more WSP characters to a single SP
	 *   character.  WSP characters here include those before and after a
	 *   line folding boundary.
	 *
	 * o Delete all WSP characters at the end of each unfolded header field
	 *   value.
	 *
	 * o Delete any WSP characters remaining before and after the colon
	 *   separating the header field name from the header field value.  The
	 *   colon separator MUST be retained.
	 */

	/* XXX */
	ds_asprintf(dkim->canon_headers, "%s:%s\r\n",
				strtolower(header->name), header->value);
	break;

  default:
	/* XXX */
	break;
  }

  return(0);
}

/*
 * Canonicalize all of the MIME headers of message EM.
 */
static void
canon_headers(Email_message *em)
{
  int i;

  for (i = 0; i < dsvec_len(em->headers); i++) {
	Email_header *header;

	header = (Email_header *) dsvec_ptr_index(em->headers, i);
	canon_header(em, header);
  }

}

/*
 * Append the canonicalized body to DS.
 */
static Ds *
canon_body(Email_message *em)
{
  int delete_crlf;
  char *p;
  Ds *canon_ds;
  Email_dkim *dkim;

  if ((dkim = em->dkim) == NULL)
	return(NULL);

  /*
   * 1. Split the body into lines
   * 2. Terminate lines with CRLF
   * 3. Convert into "network normal form"
   */

  canon_ds = dkim->canon_body;
  for (p = ds_buf(em->body); *p != '\0'; p++) {
	if (*p == '\n') {
	  ds_appendc(canon_ds, (int) '\r');
	  ds_appendc(canon_ds, (int) '\n');
	}
	else if (*p == '\r' && *(p + 1) == '\n') {
	  ds_appendc(canon_ds, (int) '\r');
	  ds_appendc(canon_ds, (int) '\n');
	  p++;
	}
	else
	  ds_appendc(canon_ds, (int) *p);
  }

  switch (dkim->canon_alg) {
  case CANON_NONE:
	ds_appendc(canon_ds, (int) '\0');
	break;

  case CANON_SIMPLE:
	/*
	 * RFC 4871 (S 3.4.3)
	 * Ignore all empty lines at the end of the message body.  An empty line
	 * is a line of zero length after removal of the line terminator.
	 * If there is no body or no trailing CRLF on the message body, a CRLF
	 * is added.  A completely empty or missing body is canonicalized as a
	 * single "CRLF"; that is, the canonicalized length will be 2 octets.
	 *
	 * Replace zero or more consecutive CRLF sequences at the end of the
	 * body with a single instance.
	 */
	ds_appendc(canon_ds, (int) '\r');
	ds_appendc(canon_ds, (int) '\n');
	ds_appendc(canon_ds, (int) '\0');

	delete_crlf = 0;
	if (ds_len(canon_ds) > 4) {
	  p = ds_buf(canon_ds) + ds_len(canon_ds) - 5;
	  while (p >= ds_buf(canon_ds) && *p == '\r' && *(p + 1) == '\n') {
		delete_crlf++;
		p -= 2;
	  }
	}

	if (delete_crlf) {
	  size_t chop_count;

	  chop_count = (delete_crlf - 1) * 2 + 3;
	  ds_chop(canon_ds, chop_count);
	  ds_appendc(canon_ds, (int) '\0');
	}

	break;

  case CANON_RELAXED:
	/*
	 * RFC 4871 (S 3.4.4)
	 * o Ignore all whitespace at the end of lines.  Implementations MUST
	 *   NOT remove the CRLF at the end of the line.
	 *
	 * o Reduce all sequences of WSP within a line to a single SP character.
	 *
	 * o Ignore all empty lines at the end of the message body.  "Empty
	 *   line" is defined in Section 3.4.3.
	 */
	/* XXX */
	return(NULL);

  default:
	return(NULL);
  }

  return(canon_ds);
}

/*
 * Generate a message by assembling the headers and body.
 * RFC 4871 (S 5.5):
 * Signers SHOULD choose canonicalization algorithms based on the types
 * of messages they process and their aversion to risk.  For example,
 * e-commerce sites sending primarily purchase receipts, which are not
 * expected to be processed by mailing lists or other software likely to
 * modify messages, will generally prefer "simple" canonicalization.
 * Sites sending primarily person-to-person email will likely prefer to
 * be more resilient to modification during transport by using "relaxed"
 * canonicalization.
 *
 * (S 3.7):
 * Note that canonicalization (Section 3.4) is only used to prepare the
 * email for signing or verifying; it does not affect the transmitted
 * email in any way.
 */
static int
canonicalize_message(Email_message *em)
{
  int i;
  Ds *ds;

  if (em->dkim == NULL)
	return(0);

  canon_headers(em);

  /* Separate last header line from start of message body. */
  ds = ds_set(NULL, "\r\n");

  canon_body(em);

  ds_dsappend(em->dkim->canon_mesg, 1, 3, em->dkim->canon_headers, ds,
			  em->dkim->canon_body);
  ds_appendc(em->dkim->canon_mesg, (int) '\0');  

  return(0);
}

/*
 * Return the DKIM-Signature header for canonicalized message EM.
 * RFC 4871 (S 5.5):
 * The following header fields SHOULD be included in the signature, if
 * they are present in the message being signed:
 *   o From (REQUIRED in all signatures)
 *   o Sender, Reply-To
 *   o Subject
 *   o Date, Message-ID
 *   o To, Cc
 *   o MIME-Version
 *   o Content-Type, Content-Transfer-Encoding, Content-ID, Content-Description
 *   o Resent-Date, Resent-From, Resent-Sender, Resent-To, Resent-Cc,
 *     Resent-Message-ID
 *   o In-Reply-To, References
 *   o List-Id, List-Help, List-Unsubscribe, List-Subscribe, List-Post,
 *     List-Owner, List-Archive
 * The following header fields SHOULD NOT be included in the signature:
 *   o Return-Path
 *   o Received
 *   o Comments, Keywords
 *   o Bcc, Resent-Bcc
 *   o DKIM-Signature
 * Optional header fields (those not mentioned above) normally SHOULD
 * NOT be included in the signature,
 *
 * (S 3.7):
 * The signer/verifier MUST compute two hashes, one over the body of the
 * message and one over the selected header fields of the message.
 */
static char *
dkim_message(Email_message *em, Canon_alg ct, RSA *priv_key, size_t ptlen)
{
  char *encoded_sig;
  size_t len;
  Ds *sig;

  if (!em->finalized)
	return(NULL);

  sig = crypto_sign_buf(em->dkim->canon_mesg, priv_key);
  mime_encode_base64((unsigned char *) ds_buf(sig), ds_len(sig), &encoded_sig);

  return(encoded_sig);
}

/*
 * After all headers and the body have been specified, terminate the
 * data structures and validate the message.
 * If not provided, standard headers (Date, Content-type, etc.) are added
 * with default values.
 * No more headers can be added after finalization, and the body cannot be
 * changed.
 * A copy of the formatted, ready-to-send message is put in EM->mesg.
 */
int
email_finalize(Email_message *em, Email_dkim *dkim)
{
  int i, j;
  Email_header *eh;

  /* Terminate the message body, if any */
  if (ds_len(em->body) > 0)
	ds_appendc(em->body, (int) '\0');

  /* When necessary, add some default headers. */
  if (dsvec_len(em->headers) > 0) {
	/* RFC 822 S5 */
	if ((eh = email_lookup_header(em, "Date")) == NULL) {
	  eh = email_new_header("Date", make_rfc822_date_string(NULL, 1, 0));
	  email_add_header(em, eh);
	}

	if ((eh = email_lookup_header(em, "Content-type")) == NULL) {
	  eh = email_new_header("Content-type", "text/plain; charset=\"us-ascii\"");
	  email_add_header(em, eh);
	}

	/* RFC 2045 S6 */
	if ((eh = email_lookup_header(em, "Content-transfer-encoding")) == NULL) {
	  eh = email_new_header("Content-transfer-encoding", "7bit");
	  email_add_header(em, eh);
	}

	/* RFC 822 S4.7.5 */
	if ((eh = email_lookup_header(em, "X-mailer")) == NULL) {
	  eh = email_new_header("X-mailer",
							ds_xprintf("DACS %s", dacs_version_release));
	  email_add_header(em, eh);
	}
  }

  /* Generate an argv vector. */
  dsvec_add_ptr(em->mailer->argv, em->mailer->prog);
  for (i = 0; i < dsvec_len(em->mailer->command_args); i++) {
	char *s;
	Email_command_arg *arg;

	arg = (Email_command_arg *) dsvec_ptr_index(em->mailer->command_args, i);
	for (j = 0; j < arg->nfields; j++) {
	  s = (char *) dsvec_ptr_index(arg->fields, j);
	  dsvec_add_ptr(em->mailer->argv, s);
	}
  }
  dsvec_add_ptr(em->mailer->argv, NULL);

  /* No environment for the mailer... */
  dsvec_add_ptr(em->mailer->env, NULL);

  /* XXX Order headers?  RFC 822 S4.1 */

  /* XXX Do syntactic validation... */

  for (i = 0; i < dsvec_len(em->headers); i++) {
	Email_header *header;

	header = (Email_header *) dsvec_ptr_index(em->headers, i);
	ds_asprintf(em->mesg, "%s: %s\r\n", header->name, header->value);
  }

  if (ds_len(em->body) > 0) {
	/* Separate last header line from start of message body. */
	ds_asprintf(em->mesg, "\r\n");

	ds_dsappend(em->mesg, 1, 1, em->body);
	ds_appendc(em->mesg, (int) '\0');
  }

  if (ds_len(em->body) > 0)
	canonicalize_message(em);

  em->finalized = 1;

  return(0);
}

/*
 * Invoke the mailer to send a message.  The mailer is expected to read the
 * message from its stdin and send it to each recipient specified in a
 * To/Cc/Bcc header.
 * If email_finalize() has not already been called, it will be done now.
 * Return -1 if the operation fails, 0 otherwise.
 */
int
email_send(Email_message *em)
{
  int error_fd, rc, read_fd, status, write_fd;
  pid_t pid, wpid;
  Ds ds;

  if (!em->finalized)
	email_finalize(em, NULL);

  rc = filterthru((char **) dsvec_base(em->mailer->argv),
				  (char **) dsvec_base(em->mailer->env),
				  &read_fd, &write_fd, &error_fd, &pid);
  if (rc == -1) {
	fprintf(stderr, "filterthru() failed, cannot send email in email_send()\n");
	return(-1);
  }

  /* Send the submitted message, not the canonicalized one. */
  rc = write_buffer(write_fd, ds_buf(em->mesg), ds_len(em->mesg) - 1);
  if (rc == -1) {
    fprintf(stderr, "write_buffer() failure in email_send()");
	return(-1);
  }
  close(write_fd);

  if ((wpid = waitpid(pid, &status, 0)) != pid) {
	fprintf(stderr, "Error during waitpid call in email_send()\n");
	return(-1);
  }
  if (!WIFEXITED(status)) {
	fprintf(stderr, "Abnormal mailer termination in email_send()\n");
	return(-1);
  }
  if (WEXITSTATUS(status) != 0) {
	fprintf(stderr, "Abnormal mailer exit in email_send()\n");
	return(-1);
  }

  return(0);
}

/*
 * For debugging, display message EM to FP.
 * This should work whether or not the message has been finalized.
 */
int
email_show(FILE *fp, Email_message *em)
{
  int i, j;
  char *s;

  fprintf(fp, "Command Path: %s\n", em->mailer->prog);
  fprintf(fp, "Command Args:\n");
  for (i = 0; i < dsvec_len(em->mailer->command_args); i++) {
	Email_command_arg *arg;

	arg = (Email_command_arg *) dsvec_ptr_index(em->mailer->command_args, i);
	fprintf(fp, "    Arg %d of %d: ", i + 1, arg->nfields);
	for (j = 0; j < arg->nfields; j++) {
	  s = (char *) dsvec_ptr_index(arg->fields, j);
	  fprintf(fp, "%s%s", s, (j == arg->nfields - 1) ? "\n" : " ");
	}
  }

  fprintf(fp, "Command Environment:\n");
  if (dsvec_len(em->mailer->env) == 0
	  || (em->finalized && dsvec_len(em->mailer->env) == 1))
	fprintf(fp, "    (None)\n");
  else {
	for (i = 0; i < dsvec_len(em->mailer->env) - 1; i++) {
	  s = (char *) dsvec_ptr_index(em->mailer->env, i);
	  fprintf(fp, "    %s\n", s);
	}
  }

  fprintf(fp, "Headers:\n");
  for (i = 0; i < dsvec_len(em->headers); i++) {
	Email_header *header;

	header = (Email_header *) dsvec_ptr_index(em->headers, i);
	fprintf(fp, "    %s: %s\n", header->name, header->value);
  }

  if (ds_len(em->body) > 0) {
	size_t len;

	len = ds_len(em->body) - 1;
	fprintf(fp, "Canonicalized body (%u bytes):\n", (unsigned int) len);
	fwrite(ds_buf(em->body), len, 1, fp);
  }

  return(0);
}

/*
 * Create a message to send using MAILER with optional arguments MAILER_ARGS,
 * an ordered list of pointers to strings, each of which is a command line
 * argument.  HEADERS is a list of pointers to Email_header structures.
 * BODY is the body of the message, which may be null-terminated.
 * ALG is the canonization algorithm to apply.
 * If successful, a message descriptor is returned, otherwise NULL.
 * The created message cannot be modified in any way.
 * Use email_send() to pass the message to the program described by MAILER.
 */
Email_message *
email_create(char *mailer, Dsvec *mailer_args, Dsvec *headers, Ds *body,
			 Email_dkim *dkim)
{
  int i;
  char *arg;
  Email_header *eh;
  Email_message *em;

  em = email_init(mailer);

  for (i = 0; i < dsvec_len(mailer_args); i++) {
	arg = (char *) dsvec_ptr_index(mailer_args, i);
	email_add_mailer_arg(em, 1, arg);
  }

  for (i = 0; i < dsvec_len(headers); i++) {
	eh = (Email_header *) dsvec_ptr_index(headers, i);
	email_add_header(em, email_dup_header(eh));
  }

  email_set_body(em, body);

  email_finalize(em, dkim);

  return(em);
}

#ifdef NOTDEF
Email_message *
email_create_simple(char *mailer, Dsvec *mailer_args, Ds *mesg)
{
  int i;
  char *arg;
  Email_header *eh;
  Email_message *em;

  em = email_init(mailer);

  for (i = 0; i < dsvec_len(mailer_args); i++) {
	arg = (char *) dsvec_ptr_index(mailer_args, i);
	email_add_mailer_arg(em, 1, arg);
  }

  em->mesg = mesg;

  email_finalize(em, NULL);

  return(em);
}
#endif

/*
 * Like email_create(), except create a message where the body is obtained
 * by applying the DACS transform function to TEMPLATE_FILE (a full pathname)
 * interpolating variables specified in KWV.  The Content-Type of the message is
 * CONTENT_TYPE; if it is "multipart/alternative", a boundary separator is
 * generated (if not already defined in KWV) and added to the Content-Type
 * header and a variable named "boundary" is set the separator string for
 * interpolation.
 * This is a kludge that performs a useful but limited function.
 */
Email_message *
email_create_transformed(char *mailer, Dsvec *mailer_args, Dsvec *headers,
						 char *content_type, char *template_file, Kwv *kwv,
						 Email_dkim *dkim)
{
  int i;
  char *arg, *boundary, *ct, *errmsg;
  Ds *ds_out;
  Email_header *eh;
  Email_message *em;
  FILE *fp;
  Transform_config *tc;

  em = email_init(mailer);

  for (i = 0; i < dsvec_len(mailer_args); i++) {
	arg = (char *) dsvec_ptr_index(mailer_args, i);
	email_add_mailer_arg(em, 1, arg);
  }

  for (i = 0; i < dsvec_len(headers); i++) {
	eh = (Email_header *) dsvec_ptr_index(headers, i);
	email_add_header(em, email_dup_header(eh));
  }

  if (kwv == NULL)
	kwv = kwv_init(8);

  if (streq(content_type, "multipart/alternative")) {
	if ((boundary = kwv_lookup_value(kwv, "boundary")) == NULL) {
	  boundary = email_make_boundary(BOUNDARY_SPEC, BOUNDARY_RAND_LEN);
	  kwv_add(kwv, "boundary", boundary);
	}
	ct = ds_xprintf("multipart/alternative; boundary=\"%s\"", boundary);
  }
  else
	ct = content_type;
  email_add_header(em, email_new_header("Content-Type", ct));

  if ((fp = fopen(template_file, "r")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot open %s", template_file));
	return(NULL);
  }

  ds_out = transform_simple_fp(fp, kwv, &errmsg);
  fclose(fp);
  if (ds_out == NULL) {
	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
	return(NULL);
  }

  email_set_body(em, ds_out);

  email_finalize(em, dkim);

  return(em);
}

/*
 *
 */
static int
test_transformed_email(char *to, char *from, char *subject,
					   char *template_file)
{
#ifndef SENDMAIL_PROG
  return(0);
#else
  int st;
  char *url;
  Dsvec *headers, *mailer_args;
  Email_header *eh;
  Email_message *em;
  Kwv *kwv;

  url = "https://www.example.com/cgi-bin/dacs/dacs_register?foo=3Dbar";

  mailer_args = dsvec_init(NULL, sizeof(char *));
  dsvec_add_ptr(mailer_args, "-t");

  headers = dsvec_init(NULL, sizeof(char *));
  dsvec_add_ptr(headers, email_new_header("From", from));
  dsvec_add_ptr(headers, email_new_header("To", to));
  dsvec_add_ptr(headers, email_new_header("Subject", subject));
  dsvec_add_ptr(headers,
				email_new_header("Content-Transfer-Encoding", "binary"));

  kwv = kwv_init(8);
  kwv_add(kwv, "lifetime", make_approx_relative_date(60));
  kwv_add(kwv, "url", url);
  em = email_create_transformed(SENDMAIL_PROG, mailer_args, headers,
								"multipart/alternative",
								template_file, kwv, CANON_SIMPLE);
  if (em != NULL)
	st = email_send(em);
  else
	st = -1;

  return(st);
#endif
}

static void
usage(void)
{

  fprintf(stderr, "Usage: dacsemail [flags ...]\n");
  fprintf(stderr, "Flags:\n");
  fprintf(stderr, "-bcc <addr>:                 Bcc recipient (repeatable)\n");
  fprintf(stderr, "{-bf|--bodyfile} <path>:     file contains message body\n");
  fprintf(stderr, "{-bs|--bodystring} <string>: the message body\n");
  fprintf(stderr, "-cc <addr>:                  Cc recipient (repeatable)\n");
  fprintf(stderr, "-ct <value>:                 add Content-type header\n");
  fprintf(stderr, "{-f|--from} <from>:          From header\n");
  fprintf(stderr, "{-h|--help}:                 show usage information)\n");
  fprintf(stderr, "-header <name> <value>:      add a header\n");
  fprintf(stderr, "{-mailer|-mta} <path>:       mailer program to use)\n");
  fprintf(stderr, "{-mailer-flags|-mta-flags} <string>: mailer program flags)\n");
  fprintf(stderr, "{-p|--prompt}:               prompt before sending)\n");
  fprintf(stderr, "-save <path>:                save a copy of the message\n");
  fprintf(stderr, "{-s|--subject} <subj>:       Subject header\n");
  fprintf(stderr, "-sender <sender>:            Sender header\n");
  fprintf(stderr, "{-t|--to} <addr>:            recipient (repeatable)\n");
  fprintf(stderr, "-transform:                  filter the body\n");
  fprintf(stderr, "{-v|--verbose}:              verbose output\n");
  fprintf(stderr, "-var <name> <value>:         create a variable\n");
  fprintf(stderr, "\n");
  fprintf(stderr, "If no source for the body is specified, or if the\n");
  fprintf(stderr, "filename \"-\" is given, the body is read from stdin.\n");
  fprintf(stderr, "\n");

  exit(1);
}

int
dacsemail_main(int argc, char **argv, int do_init, void *main_out)
{
  int do_transform, i, rc, do_prompt;
  char *body_buf, *body_str, *body_pathname, *errmsg, *save_str;
  char *from_str, *mailer_prog, *mailer_args, *sender_str, *subject_str;
  Dsvec *bcc_list, *cc_list, *header_list, *to_list;
  Kwv *kwv;
  Email_header *eh;
  Email_message *em;
  Log_desc *ld;

  errmsg = NULL;
  body_str = NULL;
  body_pathname = NULL;
  mailer_prog = NULL;
  mailer_args = NULL;
  from_str = NULL;
  sender_str = NULL;
  save_str = NULL;
  subject_str = NULL;
  to_list = dsvec_init(NULL, sizeof(char *));
  cc_list = dsvec_init(NULL, sizeof(char *));
  bcc_list = dsvec_init(NULL, sizeof(char *));
  header_list = dsvec_init(NULL, sizeof(Email_header *));
  kwv = kwv_init(8);
  kwv_set_mode(kwv, "dn");		/* Disallow duplicates */
  do_prompt = 0;
  do_transform = 0;

  if (dacs_init(DACS_STANDALONE_NOARGS, &argc, &argv, &kwv, &errmsg) == -1) {
    if (errmsg != NULL)
      fprintf(stderr, "%s\n", errmsg);
    usage();
    /*NOTREACHED*/
  }

  ld = log_init(NULL, 0, NULL, "email", LOG_NONE_LEVEL, NULL);
  log_set_level(ld, LOG_WARN_LEVEL);
  log_set_desc(ld, LOG_ENABLED);
  log_set_flags(ld, LOG_MATCH_NORMAL | LOG_MATCH_SENSITIVE);

  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-bcc")) {
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -bcc string";
		goto fail;
	  }
	  dsvec_add_ptr(bcc_list, argv[++i]);
	}
	else if (streq(argv[i], "-bf") || streq(argv[i], "--bodyfile")) {
	  if (body_str != NULL || body_pathname != NULL) {
		errmsg = "Multiple body sources are not permitted";
		goto fail;
	  }
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -bf/--bodyfile string";
		goto fail;
	  }
	  body_pathname = argv[++i];
	}
	else if (streq(argv[i], "-bs") || streq(argv[i], "--bodystring")) {
	  if (body_str != NULL || body_pathname != NULL) {
		errmsg = "Multiple body sources are not permitted";
		goto fail;
	  }
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -bs/--bodystring string";
		goto fail;
	  }
	  body_str = argv[++i];
	}
	else if (streq(argv[i], "-cc")) {
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -cc string";
		goto fail;
	  }
	  dsvec_add_ptr(cc_list, argv[++i]);
	}
	else if (streq(argv[i], "-ct")) {
	  if (argv[++i] == NULL) {
		errmsg = "String expected after -ct flag";
		goto fail;
	  }
	  eh = ALLOC(Email_header);
	  eh->name = "Content-type";
	  if (streq(argv[i], "multipart/alternative")) {
		char *boundary;

		/*
		 * If a variable named "boundary" has not been defined yet, create
		 * one.
		 */
		if ((boundary = kwv_lookup_value(kwv, "boundary")) == NULL) {
		  boundary = email_make_boundary(BOUNDARY_SPEC, BOUNDARY_RAND_LEN);
		  kwv_add(kwv, "boundary", boundary);
		}
		eh->value = ds_xprintf("multipart/alternative; boundary=\"%s\"",
							   boundary);
	  }
	  else
		eh->value = argv[i];
	  dsvec_add_ptr(header_list, eh);
	}
	else if (streq(argv[i], "-f") || streq(argv[i], "--from")) {
	  if (from_str != NULL) {
		errmsg = "Multiple -f/--from arguments are not permitted";
		goto fail;
	  }
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -f/--from string";
		goto fail;
	  }
	  from_str = argv[++i];
	}
	else if (streq(argv[i], "-h") || streq(argv[i], "--help")) {
	  usage();
	  /*NOTREACHED*/
	}
	else if (streq(argv[i], "-header")) {
	  if (argv[++i] == NULL) {
		errmsg = "Header name expected after -header flag";
		goto fail;
	  }
	  eh = ALLOC(Email_header);
	  eh->name = argv[i++];
	  if ((eh->value = argv[i]) == NULL) {
		errmsg = "Header value expected after header name argument";
		goto fail;
	  }
	  dsvec_add_ptr(header_list, eh);
	}
	else if (streq(argv[i], "-mailer") || streq(argv[i], "-mta")) {
	  if (argv[++i] == NULL) {
		errmsg = "Mailer program expected after -mailer/-mta flag";
		goto fail;
	  }
	  if (mailer_prog != NULL) {
		errmsg = "Multiple mailer arguments are not permitted";
		goto fail;
	  }
	  mailer_prog = argv[i];
	}
	else if (streq(argv[i], "-mailer-flags") || streq(argv[i], "-mta-flags")) {
	  if (argv[++i] == NULL) {
		errmsg = "Mailer flags expected after -mailer-flags/-mta-flags";
		goto fail;
	  }
	  if (mailer_args != NULL) {
		errmsg = "Multiple mailer flag arguments are not permitted";
		goto fail;
	  }
	  mailer_args = argv[i];
	}
	else if (streq(argv[i], "-p") || streq(argv[i], "--prompt")) {
	  do_prompt++;
	  verbose_flag++;
	}
	else if (streq(argv[i], "-sender")) {
	  if (sender_str != NULL) {
		errmsg = "Multiple -sender arguments are not permitted";
		goto fail;
	  }
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -sender string";
		goto fail;
	  }
	  sender_str = argv[++i];
	}
	else if (streq(argv[i], "-s") || streq(argv[i], "--subject")) {
	  if (subject_str != NULL) {
		errmsg = "Multiple -s/--subject arguments are not permitted";
		goto fail;
	  }
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -s/--subject string";
		goto fail;
	  }
	  subject_str = argv[++i];
	}
	else if (streq(argv[i], "-save")) {
	  if (save_str != NULL) {
		errmsg = "Multiple -save arguments are not permitted";
		goto fail;
	  }
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -save path";
		goto fail;
	  }
	  save_str = argv[++i];
	}
	else if (streq(argv[i], "-t") || streq(argv[i], "--to")) {
	  if (argv[i + 1] == NULL) {
		errmsg = "Missing -t/--to string";
		goto fail;
	  }
	  dsvec_add_ptr(to_list, argv[++i]);
	}
	else if (streq(argv[i], "-var")) {
	  if (argv[i + 1] == NULL || argv[i + 2] == NULL) {
		errmsg = "Variable name and value must follow -var";
		goto fail;
	  }
	  if (kwv_add(kwv, argv[i + 1], argv[i + 2]) == NULL) {
		errmsg = ds_xprintf("Cannot add variable: %s", argv[i + 1]);
		goto fail;
	  }
	  i += 2;
	}
	else if (streq(argv[i], "-transform"))
	  do_transform++;
	else if (streq(argv[i], "-v") || streq(argv[i], "--verbose"))
	  verbose_flag++;
	else {
	  usage();
	  /*NOTREACHED*/
	}
  }

  if ((dsvec_len(to_list) + dsvec_len(cc_list) + dsvec_len(bcc_list)) == 0) {
	errmsg = "No recipients have been specified, aborting";
	goto fail;
  }

  if (from_str == NULL)
	from_str = DEFAULT_FROM_ADDR;
  if (subject_str == NULL)
	subject_str = DEFAULT_SUBJECT;

  /*
   * If a mailer was specified on the command line, use it; otherwise, if
   * a MAILER_PROG was configured, use that; otherwise, if sendmail was found
   * at configure time, use that; otherwise, we've got nothing to use.
   *
   * If command line arguments for the mailer were specified on the command
   * line, use them with whichever mailer was selected; otherwise, use
   * MAILER_ARGS if they were configured; otherwise, if SENDMAIL_PROG is
   * being used, use SENDMAIL_ARGS if we've got them.
   */
  if (mailer_prog == NULL) {
#if defined(MAILER_PROG)
	mailer_prog = MAILER_PROG;
#elif defined(SENDMAIL_PROG)
	mailer_prog = SENDMAIL_PROG;
#endif
  }

  if (mailer_args == NULL) {
#if defined(MAILER_ARGS)
	mailer_args = MAILER_ARGS;
#elif defined(SENDMAIL_PROG) && defined(SENDMAIL_ARGS)
	mailer_args = SENDMAIL_ARGS;
#endif
  }

  if (mailer_prog == NULL || strlen(mailer_prog) == 0) {
	fprintf(stderr, "No mailer has been configured\n");
	exit(1);
  }

  em = email_init(mailer_prog);

  /*
   * For sendmail, -t means read the message (To, Cc, Bcc) for recipients.
   * Another useful flag is -ODeliveryMode=i which selects synchronous delivery.
   * qmail-inject seems to use the -h flag to use headers for recipients,
   * although this has not been tested.
   */
  if (mailer_args != NULL) {
	int c;
	char **v;
	static Mkargv conf = { 0, 0, " ", NULL, NULL };

	if ((c = mkargv(mailer_args, &conf, &v)) == -1) {
	  errmsg = "Error parsing mailer flags";
	  goto fail;
	}

	for (i = 0; i < c; i++)
	  email_add_mailer_arg(em, 1, v[i]);
  }

  set_header(em, "To", to_list);
  set_header(em, "Cc", cc_list);
  set_header(em, "Bcc", bcc_list);
  email_add_header(em, email_new_header("From", from_str));
  /* RFC 822 S4.4.2 */
  if (sender_str != NULL)
	email_add_header(em, email_new_header("Sender", sender_str));
  email_add_header(em, email_new_header("Subject", subject_str));

  for (i = 0; i < dsvec_len(header_list); i++) {
	eh = (Email_header *) dsvec_ptr_index(header_list, i);
	/* Checks for duplicate. */
	email_add_header(em, email_dup_header(eh));
  }

  if (body_str != NULL)
	body_buf = body_str;
  else if (body_pathname != NULL && !streq(body_pathname, "-")) {
	if (load_file(body_pathname, &body_buf, NULL) == -1) {
	  fprintf(stderr, "Error reading \"%s\".\n", body_pathname);
	  fprintf(stderr, "The message was not sent.\n");
	  exit(1);
	}
  }
  else {
	if (load_file(NULL, &body_buf, NULL) == -1) {
	  fprintf(stderr, "Error reading stdin.\n");
	  fprintf(stderr, "The message was not sent.\n");
	  exit(1);
	}
  }
  if (email_append_to_body(em, body_buf, do_transform, kwv) == -1) {
	fprintf(stderr, "An error occurred while processing the message body.\n");
	fprintf(stderr, "The message was not sent.\n");
	exit(1);
  }

  /* Complete the message but do not send it. */
  email_finalize(em, NULL);

  if (save_str != NULL) {
	FILE *fp;

	/* Save a copy */
	if ((fp = fopen(save_str, "w")) == NULL)
	  fprintf(stderr, "Cannot write to \"%s\": %s\n",
			  save_str, strerror(errno));
	else {
	  size_t len;

	  len = ds_len(em->mesg) - 1;
	  if (fwrite(ds_buf(em->mesg), 1, len, fp) != len)
		fprintf(stderr, "Error writing to \"%s\"\n", save_str);
	  fclose(fp);
	}
  }

  if (verbose_flag || do_prompt)
	email_show(stderr, em);

  if (do_prompt) {
#ifdef NOTDEF
	while (1) {
	  int ch;

	  /* This is intended to discard any existing input. */
	  fflush(stdin);
	  clearerr(stdin);

	  printf("[Type y to continue, n to abort] ");
	  ch = getc(stdin);
	  if (ch == 'y' || ch == 'Y')
		break;
	  if (ch == 'n' || ch == 'N') {
		fprintf(stderr, "Message aborted\n");
		return(0);
	  }

	  /* Flush any other input characters before trying again. */
	  while (!feof(stdin) && !ferror(stdin)) {
		if ((ch = getc(stdin)) == (int) '\n')
		  break;;
	  }
	}
#else
	char *p;
	Ds *ds;

	ds = ds_init(NULL);
	if ((p = ds_prompt(ds, "[Type y to continue, anything else to abort] ",
					   DS_PROMPT_ECHO)) == NULL || !strcaseeq(p, "y")) {
	  fprintf(stderr, "Message aborted.\n");
	  return(0);
	} 
#endif
  }

  /* Give the ready-to-go message to the mailer. */
  if ((rc = email_send(em)) == -1) {
	fprintf(stderr, "Could not send message\n");
	exit(1);
  }

  return(0);

  fail:
  if (errmsg != NULL)
	fprintf(stderr, "%s\n", errmsg);
  usage();

  exit(1);
}

#else

int
main(int argc, char **argv)
{
  int rc;

  if ((rc = dacsemail_main(argc, argv, 1, NULL)) == 0)
	exit(0);

  exit(1);
}
#endif
