text: /*++
  /* NAME
  /*	post_mail 3
  /* SUMMARY
  /*	convenient mail posting interface
  /* SYNOPSIS
  /*	#include <post_mail.h>
  /*
  /*	VSTREAM	*post_mail_fopen(sender, recipient, cleanup_flags, trace_flags)
  /*	const char *sender;
  /*	const char *recipient;
  /*	int	cleanup_flags;
  /*	int	trace_flags;
  /*
  /*	VSTREAM	*post_mail_fopen_nowait(sender, recipient,
  /*					cleanup_flags, trace_flags)
  /*	const char *sender;
  /*	const char *recipient;
  /*	int	cleanup_flags;
  /*	int	trace_flags;
  /*
  /*	void	post_mail_fopen_async(sender, recipient,
  /*					cleanup_flags, trace_flags,
  /*					notify, context)
  /*	const char *sender;
  /*	const char *recipient;
  /*	int	cleanup_flags;
  /*	int	trace_flags;
  /*	void	(*notify)(VSTREAM *stream, char *context);
  /*	char	*context;
  /*
  /*	int	post_mail_fprintf(stream, format, ...)
  /*	VSTREAM	*stream;
  /*	const char *format;
  /*
  /*	int	post_mail_fputs(stream, str)
  /*	VSTREAM	*stream;
  /*	const char *str;
  /*
  /*	int	post_mail_buffer(stream, buf, len)
  /*	VSTREAM	*stream;
  /*	const char *buffer;
  /*
  /*	int	POST_MAIL_BUFFER(stream, buf)
  /*	VSTREAM	*stream;
  /*	VSTRING	*buffer;
  /*
  /*	int	post_mail_fclose(stream)
  /*	VSTREAM	*STREAM;
  /* DESCRIPTION
  /*	This module provides a convenient interface for the most
  /*	common case of sending one message to one recipient. It
  /*	allows the application to concentrate on message content,
  /*	without having to worry about queue file structure details.
  /*
  /*	post_mail_fopen() opens a connection to the cleanup service
  /*	and waits until the service is available, does some option
  /*	negotiation, generates message envelope records, and generates
  /*	Received: and Date: message headers.  The result is a stream
  /*	handle that can be used for sending message records.
  /*
  /*	post_mail_fopen_nowait() tries to contact the cleanup service
  /*	only once, and does not wait until the cleanup service is
  /*	available.  Otherwise it is identical to post_mail_fopen().
  /*
  /*	post_mail_fopen_async() contacts the cleanup service and
  /*	invokes the caller-specified notify routine, with the
  /*	open stream and the caller-specified context when the
  /*	service responds, or with a null stream and the caller-specified
  /*	context when the request could not be completed. It is the
  /*	responsability of the application to close an open stream.
  /*
  /*	post_mail_fprintf() formats message content (header or body)
  /*	and sends it to the cleanup service.
  /*
  /*	post_mail_fputs() sends pre-formatted content (header or body)
  /*	to the cleanup service.
  /*
  /*	post_mail_buffer() sends a pre-formatted buffer to the
  /*	cleanup service.
  /*
  /*	POST_MAIL_BUFFER() is a wrapper for post_mail_buffer() that
  /*	evaluates its buffer argument more than once.
  /*
  /*	post_mail_fclose() completes the posting of a message.
  /*
  /*	Arguments:
  /* .IP sender
  /*	The sender envelope address. It is up to the application
  /*	to produce From: headers.
  /* .IP recipient
  /*	The recipient envelope address. It is up to the application
  /*	to produce To: headers.
  /* .IP cleanup_flags
  /*	The binary OR of zero or more of the options defined in
  /*	\fB<cleanup_user.h>\fR.
  /* .IP trace_flags
  /*	Message tracing flags as specified in \fB<deliver_request.h>\fR.
  /* .IP via
  /*	The name of the service responsible for posting this message.
  /* .IP stream
  /*	A stream opened by mail_post_fopen().
  /* .IP notify
  /*	Application call-back routine.
  /* .IP context
  /*	Application call-back context.
  /* DIAGNOSTICS
  /*	post_mail_fopen_nowait() returns a null pointer when the
  /*	cleanup service is not available immediately.
  /*
  /*	post_mail_fopen_async() returns a null pointer when the
  /*	attempt to contact the cleanup service fails immediately.
  /*
  /*	post_mail_fprintf(), post_mail_fputs() post_mail_fclose(),
  /*	and post_mail_buffer() return the binary OR of the error
  /*	status codes defined in \fI<cleanup_user.h>\fR.
  /*
  /*	Fatal errors: cleanup initial handshake errors. This means
  /*	the client and server speak incompatible protocols.
  /* SEE ALSO
  /*	cleanup_user(3h) cleanup options and results
  /*	cleanup_strerror(3) translate results to text
  /*	cleanup(8) cleanup service
  /* LICENSE
  /* .ad
  /* .fi
  /*	The Secure Mailer license must be distributed with this software.
  /* AUTHOR(S)
  /*	Wietse Venema
  /*	IBM T.J. Watson Research
  /*	P.O. Box 704
  /*	Yorktown Heights, NY 10598, USA
  /*--*/
  
  /* System library. */
  
  #include <sys_defs.h>
  #include <time.h>
  #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
  #include <stdarg.h>
  #include <string.h>
  
  /* Utility library. */
  
  #include <msg.h>
  #include <vstream.h>
  #include <vstring.h>
  #include <mymalloc.h>
  #include <events.h>
  
  /* Global library. */
  
  #include <mail_params.h>
  #include <record.h>
  #include <rec_type.h>
  #include <mail_proto.h>
  #include <cleanup_user.h>
  #include <post_mail.h>
  #include <mail_date.h>
  
   /*
    * Call-back state for asynchronous connection requests.
    */
  typedef struct {
      char   *sender;
      char   *recipient;
      int     cleanup_flags;
      int     trace_flags;
      POST_MAIL_NOTIFY notify;
      void   *context;
      VSTREAM *stream;
  } POST_MAIL_STATE;
  
  /* post_mail_init - initial negotiations */
  
  static void post_mail_init(VSTREAM *stream, const char *sender,
  			           const char *recipient,
  			           int cleanup_flags, int trace_flags)
  {
      VSTRING *id = vstring_alloc(100);
      long    now = time((time_t *) 0);
      const char *date = mail_date(now);
  
      /*
       * Negotiate with the cleanup service. Give up if we can't agree.
       */
      if (attr_scan(stream, ATTR_FLAG_STRICT,
  		  ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
  		  ATTR_TYPE_END) != 1
  	|| attr_print(stream, ATTR_FLAG_NONE,
  		      ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, cleanup_flags,
  		      ATTR_TYPE_END) != 0)
  	msg_fatal("unable to contact the %s service", var_cleanup_service);
  
      /*
       * Generate a minimal envelope section. The cleanup service will add a
       * size record.
       */
      rec_fprintf(stream, REC_TYPE_TIME, "%ld", (long) now);
      rec_fprintf(stream, REC_TYPE_ATTR, "%s=%s",
  		MAIL_ATTR_ORIGIN, MAIL_ATTR_ORG_LOCAL);
      rec_fprintf(stream, REC_TYPE_ATTR, "%s=%d",
  		MAIL_ATTR_TRACE_FLAGS, trace_flags);
      rec_fputs(stream, REC_TYPE_FROM, sender);
      rec_fputs(stream, REC_TYPE_RCPT, recipient);
      rec_fputs(stream, REC_TYPE_MESG, "");
  
      /*
       * Do the Received: and Date: header lines. This allows us to shave a few
       * cycles by using the expensive date conversion result for both.
       */
      post_mail_fprintf(stream, "Received: by %s (%s)",
  		      var_myhostname, var_mail_name);
      post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date);
      post_mail_fprintf(stream, "Date: %s", date);
      vstring_free(id);
  }
  
  /* post_mail_fopen - prepare for posting a message */
  
  VSTREAM *post_mail_fopen(const char *sender, const char *recipient,
  			         int cleanup_flags, int trace_flags)
  {
      VSTREAM *stream;
  
      stream = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service);
      post_mail_init(stream, sender, recipient, cleanup_flags, trace_flags);
      return (stream);
  }
  
  /* post_mail_fopen_nowait - prepare for posting a message */
  
  VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient,
  				        int cleanup_flags, int trace_flags)
  {
      VSTREAM *stream;
  
      if ((stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service,
  			       BLOCKING)) != 0)
  	post_mail_init(stream, sender, recipient, cleanup_flags, trace_flags);
      return (stream);
  }
  
  /* post_mail_open_event - handle asynchronous connection events */
  
  static void post_mail_open_event(int event, char *context)
  {
      POST_MAIL_STATE *state = (POST_MAIL_STATE *) context;
      char   *myname = "post_mail_open_event";
  
      switch (event) {
  
  	/*
  	 * Connection established. Request notification when the server sends
  	 * the initial response. This intermediate case is necessary for some
  	 * versions of LINUX and perhaps Solaris, where UNIX-domain
  	 * connect(2) blocks until the server performs an accept(2).
  	 */
      case EVENT_WRITE:
  	if (msg_verbose)
  	    msg_info("%s: write event", myname);
  	event_disable_readwrite(vstream_fileno(state->stream));
  	non_blocking(vstream_fileno(state->stream), BLOCKING);
  	event_enable_read(vstream_fileno(state->stream),
  			  post_mail_open_event, (char *) state);
  	return;
  
  	/*
  	 * Initial server reply. Stop the watchdog timer, disable further
  	 * read events that end up calling this function, and notify the
  	 * requestor.
  	 */
      case EVENT_READ:
  	if (msg_verbose)
  	    msg_info("%s: read event", myname);
  	event_cancel_timer(post_mail_open_event, context);
  	event_disable_readwrite(vstream_fileno(state->stream));
  	post_mail_init(state->stream, state->sender,
  		       state->recipient, state->cleanup_flags,
  		       state->trace_flags);
  	myfree(state->sender);
  	myfree(state->recipient);
  	state->notify(state->stream, state->context);
  	myfree((char *) state);
  	return;
  
  	/*
  	 * No connection or no initial reply within a conservative time
  	 * limit. The system is broken and we give up.
  	 */
      case EVENT_TIME:
  	if (state->stream) {
  	    msg_warn("timeout connecting to service: %s", var_cleanup_service);
  	    event_disable_readwrite(vstream_fileno(state->stream));
  	    vstream_fclose(state->stream);
  	} else {
  	    msg_warn("connect to service: %s: %m", var_cleanup_service);
  	}
  	myfree(state->sender);
  	myfree(state->recipient);
  	state->notify((VSTREAM *) 0, state->context);
  	myfree((char *) state);
  	return;
  
  	/*
  	 * Some exception.
  	 */
      case EVENT_XCPT:
  	msg_warn("error connecting to service: %s", var_cleanup_service);
  	event_cancel_timer(post_mail_open_event, context);
  	event_disable_readwrite(vstream_fileno(state->stream));
  	vstream_fclose(state->stream);
  	myfree(state->sender);
  	myfree(state->recipient);
  	state->notify((VSTREAM *) 0, state->context);
  	myfree((char *) state);
  	return;
  
  	/*
  	 * Broken software or hardware.
  	 */
      default:
  	msg_panic("%s: unknown event type %d", myname, event);
      }
  }
  
  /* post_mail_fopen_async - prepare for posting a message */
  
  void    post_mail_fopen_async(const char *sender, const char *recipient,
  			              int cleanup_flags, int trace_flags,
  			              void (*notify) (VSTREAM *, void *),
  			              void *context)
  {
      VSTREAM *stream;
      POST_MAIL_STATE *state;
  
      stream = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, NON_BLOCKING);
      state = (POST_MAIL_STATE *) mymalloc(sizeof(*state));
      state->sender = mystrdup(sender);
      state->recipient = mystrdup(recipient);
      state->cleanup_flags = cleanup_flags;
      state->trace_flags = trace_flags;
      state->notify = notify;
      state->context = context;
      state->stream = stream;
  
      /*
       * To keep interfaces as simple as possible we report all errors via the
       * same interface as all successes.
       */
      if (stream != 0) {
  	event_enable_write(vstream_fileno(stream), post_mail_open_event,
  			   (void *) state);
  	event_request_timer(post_mail_open_event, (void *) state,
  			    var_daemon_timeout);
      } else {
  	event_request_timer(post_mail_open_event, (void *) state, 0);
      }
  }
  
  /* post_mail_fprintf - format and send message content */
  
  int     post_mail_fprintf(VSTREAM *cleanup, const char *format,...)
  {
      int     status;
      va_list ap;
  
      va_start(ap, format);
      status = rec_vfprintf(cleanup, REC_TYPE_NORM, format, ap);
      va_end(ap);
      return (status != REC_TYPE_NORM ? CLEANUP_STAT_WRITE : 0);
  }
  
  /* post_mail_buffer - send pre-formatted buffer */
  
  int     post_mail_buffer(VSTREAM *cleanup, const char *buf, int len)
  {
      return (rec_put(cleanup, REC_TYPE_NORM, buf, len) != REC_TYPE_NORM ?
  	    CLEANUP_STAT_WRITE : 0);
  }
  
  /* post_mail_fputs - send pre-formatted message content */
  
  int     post_mail_fputs(VSTREAM *cleanup, const char *str)
  {
      int     len = str ? strlen(str) : 0;
  
      return (rec_put(cleanup, REC_TYPE_NORM, str, len) != REC_TYPE_NORM ?
  	    CLEANUP_STAT_WRITE : 0);
  }
  
  /* post_mail_fclose - finish posting of message */
  
  int     post_mail_fclose(VSTREAM *cleanup)
  {
      int     status = 0;
  
      /*
       * Send the message end marker only when there were no errors.
       */
      if (vstream_ferror(cleanup) != 0) {
  	status = CLEANUP_STAT_WRITE;
      } else {
  	rec_fputs(cleanup, REC_TYPE_XTRA, "");
  	rec_fputs(cleanup, REC_TYPE_END, "");
  	if (vstream_fflush(cleanup)
  	    || attr_scan(cleanup, ATTR_FLAG_MISSING,
  			 ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status,
  			 ATTR_TYPE_END) != 1)
  	    status = CLEANUP_STAT_WRITE;
      }
      (void) vstream_fclose(cleanup);
      return (status);
  }
  