/*++
/* NAME
/*	abounce 3
/* SUMMARY
/*	asynchronous bounce/defer/trace service client
/* SYNOPSIS
/*	#include <abounce.h>
/*
/*	void	abounce_flush(flags, queue, id, encoding, sender,
/*				dsn_envid, dsn_ret, callback, context)
/*	int	flags;
/*	const char *queue;
/*	const char *id;
/*	const char *encoding;
/*	const char *sender;
/*	const char *dsn_envid;
/*	int	dsn_ret;
/*	void	(*callback)(int status, char *context);
/*	char	*context;
/*
/*	void	abounce_flush_verp(flags, queue, id, encoding, sender,
/*				dsn_envid, dsn_ret, verp, callback, context)
/*	int	flags;
/*	const char *queue;
/*	const char *id;
/*	const char *encoding;
/*	const char *sender;
/*	const char *dsn_envid;
/*	int	dsn_ret;
/*	const char *verp;
/*	void	(*callback)(int status, char *context);
/*	char	*context;
/*
/*	void	adefer_flush(flags, queue, id, encoding, sender,
/*				dsn_envid, dsn_ret, callback, context)
/*	int	flags;
/*	const char *queue;
/*	const char *id;
/*	const char *encoding;
/*	const char *sender;
/*	const char *dsn_envid;
/*	int	dsn_ret;
/*	void	(*callback)(int status, char *context);
/*	char	*context;
/*
/*	void	adefer_flush_verp(flags, queue, id, encoding, sender,
/*				dsn_envid, dsn_ret, verp, callback, context)
/*	int	flags;
/*	const char *queue;
/*	const char *id;
/*	const char *encoding;
/*	const char *sender;
/*	const char *dsn_envid;
/*	int	dsn_ret;
/*	const char *verp;
/*	void	(*callback)(int status, char *context);
/*	char	*context;
/*
/*	void	adefer_warn(flags, queue, id, encoding, sender,
/*				dsn_envid, dsn_ret, callback, context)
/*	int	flags;
/*	const char *queue;
/*	const char *id;
/*	const char *encoding;
/*	const char *sender;
/*	const char *dsn_envid;
/*	int	dsn_ret;
/*	void	(*callback)(int status, char *context);
/*	char	*context;
/*
/*	void	atrace_flush(flags, queue, id, encoding, sender,
/*				dsn_envid, dsn_ret, callback, context)
/*	int	flags;
/*	const char *queue;
/*	const char *id;
/*	const char *encoding;
/*	const char *sender;
/*	const char *dsn_envid;
/*	int	dsn_ret;
/*	void	(*callback)(int status, char *context);
/*	char	*context;
/* DESCRIPTION
/*	This module implements an asynchronous interface to the
/*	bounce/defer/trace service for submitting sender notifications
/*	without waiting for completion of the request.
/*
/*	abounce_flush() bounces the specified message to
/*	the specified sender, including the bounce log that was
/*	built with bounce_append().
/*
/*	abounce_flush_verp() is like abounce_flush() but sends
/*	one VERP style notification per undeliverable recipient.
/*
/*	adefer_flush() bounces the specified message to
/*	the specified sender, including the defer log that was
/*	built with defer_append().
/*	adefer_flush() requests that the deferred recipients are deleted
/*	from the original queue file.
/*
/*	adefer_flush_verp() is like adefer_flush() but sends
/*	one VERP style notification per undeliverable recipient.
/*
/*	adefer_warn() sends a "mail is delayed" notification to
/*	the specified sender, including the defer log that was
/*	built with defer_append().
/*
/*	atrace_flush() returns the specified message to the specified
/*	sender, including the message delivery record log that was
/*	built with vtrace_append().
/*
/*	Arguments:
/* .IP flags
/*	The bitwise OR of zero or more of the following (specify
/*	BOUNCE_FLAG_NONE to request no special processing):
/* .RS
/* .IP BOUNCE_FLAG_CLEAN
/*	Delete the bounce log in case of an error (as in: pretend
/*	that we never even tried to bounce this message).
/* .IP BOUNCE_FLAG_DELRCPT
/*	When specified with a flush operation, request that
/*	recipients be deleted from the queue file.
/*
/*	Note: the bounce daemon ignores this request when the
/*	recipient queue file offset is <= 0.
/* .IP BOUNCE_FLAG_COPY
/*	Request that a postmaster copy is sent.
/* .RE
/* .IP queue
/*	The message queue name of the original message file.
/* .IP id
/*	The message queue id if the original message file. The bounce log
/*	file has the same name as the original message file.
/* .IP encoding
/*	The body content encoding: MAIL_ATTR_ENC_{7BIT,8BIT,NONE}.
/* .IP sender
/*	The sender envelope address.
/* .IP dsn_envid
/*	Optional DSN envelope ID.
/* .IP ret
/*	Optional DSN return full/headers option.
/* .IP verp
/*	VERP delimiter characters.
/* .IP callback
/*	Name of a routine that receives the notification status as
/*	documented for bounce_flush() or defer_flush().
/* .IP context
/*	Application-specific context that is passed through to the
/*	callback routine. Use proper casts or the world will come
/*	to an end.
/* DIAGNOSTICS
/*	In case of success, these functions log the action, and return a
/*	zero result via the callback routine. Otherwise, the functions
/*	return a non-zero result via the callback routine, and when
/*	BOUNCE_FLAG_CLEAN is disabled, log that message delivery is deferred.
/* 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>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <events.h>
#include <vstream.h>

/* Global library. */

#include <mail_params.h>
#include <mail_proto.h>
#include <abounce.h>

/* Application-specific. */

 /*
  * Each bounce/defer flush/warn request is implemented by sending the
  * request to the bounce/defer server, and by creating a pseudo thread that
  * suspends itself until the server replies (or dies). Upon wakeup, the
  * pseudo thread delivers the request completion status to the application
  * and destroys itself. The structure below maintains all the necessary
  * request state while the pseudo thread is suspended.
  */
typedef struct {
    int     command;			/* bounce request type */
    int     flags;			/* bounce options */
    char   *id;				/* queue ID for logging */
    ABOUNCE_FN callback;		/* application callback */
    char   *context;			/* application context */
    VSTREAM *fp;			/* server I/O handle */
} ABOUNCE;

 /*
  * Encapsulate common code.
  */
#define ABOUNCE_EVENT_ENABLE(fd, callback, context, timeout) do { \
	event_enable_read((fd), (callback), (context)); \
	event_request_timer((callback), (context), (timeout)); \
    } while (0)

#define ABOUNCE_EVENT_DISABLE(fd, callback, context) do { \
	event_cancel_timer((callback), (context)); \
	event_disable_readwrite(fd); \
    } while (0)

 /*
  * If we set the reply timeout too short, then we make the problem worse by
  * increasing overload. With 1000s timeout mail will keep flowing, but there
  * will be a large number of blocked bounce processes, and some resource is
  * likely to run out.
  */
#define ABOUNCE_TIMEOUT	1000

/* abounce_done - deliver status to application and clean up pseudo thread */

static void abounce_done(ABOUNCE *ap, int status)
{
    (void) vstream_fclose(ap->fp);
    if (status != 0 && (ap->flags & BOUNCE_FLAG_CLEAN) == 0)
	msg_info("%s: status=deferred (%s failed)", ap->id,
		 ap->command == BOUNCE_CMD_FLUSH ? "bounce" :
		 ap->command == BOUNCE_CMD_WARN ? "delay warning" :
		 ap->command == BOUNCE_CMD_VERP ? "verp" :
		 ap->command == BOUNCE_CMD_TRACE ? "trace" :
		 "whatever");
    ap->callback(status, ap->context);
    myfree(ap->id);
    myfree((char *) ap);
}

/* abounce_event - resume pseudo thread after server reply event */

static void abounce_event(int event, char *context)
{
    ABOUNCE *ap = (ABOUNCE *) context;
    int     status;

    ABOUNCE_EVENT_DISABLE(vstream_fileno(ap->fp), abounce_event, context);
    abounce_done(ap, (event != EVENT_TIME
		      && attr_scan(ap->fp, ATTR_FLAG_STRICT,
				   ATTR_TYPE_INT, MAIL_ATTR_STATUS, &status,
				   ATTR_TYPE_END) == 1) ? status : -1);
}

/* abounce_request_verp - suspend pseudo thread until server reply event */

static void abounce_request_verp(const char *class, const char *service,
				         int command, int flags,
				         const char *queue, const char *id,
				         const char *encoding,
				         const char *sender,
				         const char *dsn_envid,
				         int dsn_ret,
				         const char *verp,
				         ABOUNCE_FN callback,
				         char *context)
{
    ABOUNCE *ap;

    /*
     * Save pseudo thread state. Connect to the server. Send the request and
     * suspend the pseudo thread until the server replies (or dies).
     */
    ap = (ABOUNCE *) mymalloc(sizeof(*ap));
    ap->command = command;
    ap->flags = flags;
    ap->id = mystrdup(id);
    ap->callback = callback;
    ap->context = context;
    ap->fp = mail_connect_wait(class, service);

    if (attr_print(ap->fp, ATTR_FLAG_NONE,
		   ATTR_TYPE_INT, MAIL_ATTR_NREQ, command,
		   ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags,
		   ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue,
		   ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
		   ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding,
		   ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender,
		   ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, dsn_envid,
		   ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, dsn_ret,
		   ATTR_TYPE_STR, MAIL_ATTR_VERPDL, verp,
		   ATTR_TYPE_END) == 0
	&& vstream_fflush(ap->fp) == 0) {
	ABOUNCE_EVENT_ENABLE(vstream_fileno(ap->fp), abounce_event, 
			     (char *) ap, ABOUNCE_TIMEOUT);
    } else {
	abounce_done(ap, -1);
    }
}

/* abounce_flush_verp - asynchronous bounce flush */

void    abounce_flush_verp(int flags, const char *queue, const char *id,
			           const char *encoding, const char *sender,
			           const char *dsn_envid, int dsn_ret,
			           const char *verp, ABOUNCE_FN callback,
			           char *context)
{
    abounce_request_verp(MAIL_CLASS_PRIVATE, var_bounce_service,
			 BOUNCE_CMD_VERP, flags, queue, id, encoding,
		       sender, dsn_envid, dsn_ret, verp, callback, context);
}

/* adefer_flush_verp - asynchronous defer flush */

void    adefer_flush_verp(int flags, const char *queue, const char *id,
			          const char *encoding, const char *sender,
			          const char *dsn_envid, int dsn_ret,
			          const char *verp, ABOUNCE_FN callback,
			          char *context)
{
    flags |= BOUNCE_FLAG_DELRCPT;
    abounce_request_verp(MAIL_CLASS_PRIVATE, var_defer_service,
			 BOUNCE_CMD_VERP, flags, queue, id, encoding,
		       sender, dsn_envid, dsn_ret, verp, callback, context);
}

/* abounce_request - suspend pseudo thread until server reply event */

static void abounce_request(const char *class, const char *service,
			            int command, int flags,
			            const char *queue, const char *id,
			            const char *encoding, const char *sender,
			            const char *dsn_envid, int dsn_ret,
			            ABOUNCE_FN callback, char *context)
{
    ABOUNCE *ap;

    /*
     * Save pseudo thread state. Connect to the server. Send the request and
     * suspend the pseudo thread until the server replies (or dies).
     */
    ap = (ABOUNCE *) mymalloc(sizeof(*ap));
    ap->command = command;
    ap->flags = flags;
    ap->id = mystrdup(id);
    ap->callback = callback;
    ap->context = context;
    ap->fp = mail_connect_wait(class, service);

    if (attr_print(ap->fp, ATTR_FLAG_NONE,
		   ATTR_TYPE_INT, MAIL_ATTR_NREQ, command,
		   ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags,
		   ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue,
		   ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
		   ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding,
		   ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender,
		   ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, dsn_envid,
		   ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, dsn_ret,
		   ATTR_TYPE_END) == 0
	&& vstream_fflush(ap->fp) == 0) {
	ABOUNCE_EVENT_ENABLE(vstream_fileno(ap->fp), abounce_event, 
			     (char *) ap, ABOUNCE_TIMEOUT);
    } else {
	abounce_done(ap, -1);
    }
}

/* abounce_flush - asynchronous bounce flush */

void    abounce_flush(int flags, const char *queue, const char *id,
		              const char *encoding, const char *sender,
		              const char *dsn_envid, int dsn_ret,
		              ABOUNCE_FN callback, char *context)
{
    abounce_request(MAIL_CLASS_PRIVATE, var_bounce_service, BOUNCE_CMD_FLUSH,
		    flags, queue, id, encoding, sender, dsn_envid, dsn_ret,
		    callback, context);
}

/* adefer_flush - asynchronous defer flush */

void    adefer_flush(int flags, const char *queue, const char *id,
		             const char *encoding, const char *sender,
		             const char *dsn_envid, int dsn_ret,
		             ABOUNCE_FN callback, char *context)
{
    flags |= BOUNCE_FLAG_DELRCPT;
    abounce_request(MAIL_CLASS_PRIVATE, var_defer_service, BOUNCE_CMD_FLUSH,
		    flags, queue, id, encoding, sender, dsn_envid, dsn_ret,
		    callback, context);
}

/* adefer_warn - send copy of defer log to sender as warning bounce */

void    adefer_warn(int flags, const char *queue, const char *id,
		            const char *encoding, const char *sender,
		            const char *dsn_envid, int dsn_ret,
		            ABOUNCE_FN callback, char *context)
{
    abounce_request(MAIL_CLASS_PRIVATE, var_defer_service, BOUNCE_CMD_WARN,
		    flags, queue, id, encoding, sender, dsn_envid, dsn_ret,
		    callback, context);
}

/* atrace_flush - asynchronous trace flush */

void    atrace_flush(int flags, const char *queue, const char *id,
		             const char *encoding, const char *sender,
		             const char *dsn_envid, int dsn_ret,
		             ABOUNCE_FN callback, char *context)
{
    abounce_request(MAIL_CLASS_PRIVATE, var_trace_service, BOUNCE_CMD_TRACE,
		    flags, queue, id, encoding, sender, dsn_envid, dsn_ret,
		    callback, context);
}
