/*++
/* NAME
/*	dsn_buf 3
/* SUMMARY
/*	delivery status buffer
/* SYNOPSIS
/*	#include <dsn_buf.h>
/*
/*	typedef struct {
/* .in +4
/*		/* Convenience member */
/*		DSN	dsn;		/* light-weight, dsn(3) */
/*		/* Formal members... */
/*		VSTRING *status;	/* RFC 3463 */
/*		VSTRING *action;	/* RFC 3464 */
/*		VSTRING	*mtype;		/* dns */
/*		VSTRING	*mname;		/* host or domain */
/*		VSTRING	*dtype;		/* smtp, x-unix */
/*		VSTRING *dtext;		/* RFC 2821, sysexits.h */
/*		/* Informal members... */
/*		VSTRING	*reason;	/* informal text */
/* .in -4
/*	} DSN_BUF;
/*
/*	DSN_BUF	*dsb_create(void)
/*
/*	DSN_BUF	*dsb_update(dsb, status, action, mtype, mname, dtype,
/*				dtext, reason_fmt, ...)
/*	DSN_BUF	*dsb;
/*	const char *status;
/*	const char *action;
/*	const char *mtype;
/*	const char *mname;
/*	const char *dtype;
/*	const char *dtext;
/*	const char *reason_fmt;
/*
/*	DSN_BUF	*dsb_simple(dsb, status, reason_fmt, ...)
/*	DSN_BUF	*dsb;
/*	const char *status;
/*	const char *reason_fmt;
/*
/*	DSN_BUF	*dsb_unix(dsb, status, dtext, reason_fmt, ...)
/*	DSN_BUF	*dsb;
/*	const char *status;
/*	const char *reason_fmt;
/*
/*	DSN_BUF	*dsb_formal(dsb, status, action, mtype, mname, dtype,
/*				dtext)
/*	DSN_BUF	*dsb;
/*	const char *status;
/*	const char *action;
/*	const char *mtype;
/*	const char *mname;
/*	const char *dtype;
/*	const char *dtext;
/*
/*	DSN_BUF	*dsb_status(dsb, status)
/*	DSN_BUF	*dsb;
/*	const char *status;
/*
/*	void	dsb_reset(dsb)
/*	DSN_BUF	*dsb;
/*
/*	void	dsb_free(dsb)
/*	DSN_BUF	*dsb;
/*
/*	DSN	*DSN_FROM_DSN_BUF(dsb)
/*	DSN_BUF	*dsb;
/* DESCRIPTION
/*	This module implements a simple to update delivery status
/*	buffer for Postfix-internal use. Typically it is filled in
/*	the course of delivery attempt, and then formatted into a
/*	DSN structure for external notification.
/*
/*	dsb_create() creates initialized storage for formal RFC 3464
/*	attributes, and human-readable informal text.
/*
/*	dsb_update() updates all fields.
/*
/*	dsb_simple() updates the status and informal text, and resets all
/*	other fields to defaults.
/*
/*	dsb_unix() updates the status, diagnostic code, diagnostic
/*	text, and informal text, sets the diagnostic type to UNIX,
/*	and resets all other fields to defaults.
/*
/*	dsb_formal() updates all fields except the informal text.
/*
/*	dsb_status() updates the status field, and resets all
/*	formal fields to defaults.
/*
/*	dsb_reset() resets all fields in a DSN_BUF structure without
/*	deallocating memory.
/*
/*	dsb_free() recycles the storage that was allocated by
/*	dsb_create(), and so on.
/*
/*	DSN_FROM_DSN_BUF() populates the DSN member with a shallow
/*	copy of the contents of the formal and informal fields, and
/*	returns a pointer to the DSN member. This is typically used
/*	for external reporting.
/*
/*	Arguments:
/* .IP dsb
/*	Delivery status buffer.
/* .IP status
/*	RFC 3463 "enhanced" status code.
/* .IP action
/*	RFC 3464 action code; specify DSB_DEF_ACTION to derive the
/*	action from the status value. The only values that really
/*	matter here are "expanded" and "relayed"; all other values
/*	are already implied by the context.
/* .IP mtype
/*	The remote MTA type.
/*	The only valid type is DSB_MTYPE_DNS.  The macro DSB_SKIP_RMTA
/*	conveniently expands into a null argument list for the
/*	remote MTA type and name.
/* .IP mname
/*	Remote MTA name.
/* .IP dtype
/*	The reply type.
/*	DSB_DTYPE_SMTP or DSB_DTYPE_UNIX.  The macro DSB_SKIP_REPLY
/*	conveniently expands into a null argument list for the reply
/*	type and text.
/* .IP dtext
/*	The reply text. The reply text is reset when dtype is
/*	DSB_SKIP_REPLY.
/* .IP reason_fmt
/*	The informal reason format.
/* SEE ALSO
/*	msg(3) diagnostics interface
/* DIAGNOSTICS
/*	Fatal: out of memory.
/* 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 <stdlib.h>			/* 44BSD stdarg.h uses abort() */
#include <stdarg.h>
#include <string.h>

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <vstring.h>

/* Global library. */

#include <dsn_buf.h>

/* Application-specific. */

#define STR(x)	vstring_str(x)

/* dsb_create - create delivery status buffer */

DSN_BUF *dsb_create(void)
{
    DSN_BUF *dsb;

    /*
     * Some fields aren't needed until we want to report an error.
     */
    dsb = (DSN_BUF *) mymalloc(sizeof(*dsb));
    dsb->status = vstring_alloc(10);
    dsb->action = vstring_alloc(10);
    dsb->mtype = vstring_alloc(10);
    dsb->mname = vstring_alloc(100);
    dsb->dtype = vstring_alloc(10);
    dsb->dtext = vstring_alloc(100);
    dsb->reason = vstring_alloc(100);

    return (dsb);
}

/* dsb_free - destroy storage */

void    dsb_free(DSN_BUF *dsb)
{
    vstring_free(dsb->status);
    vstring_free(dsb->action);
    vstring_free(dsb->mtype);
    vstring_free(dsb->mname);
    vstring_free(dsb->dtype);
    vstring_free(dsb->dtext);
    vstring_free(dsb->reason);
    myfree((void *) dsb);
}

 /*
  * Initial versions of this code represented unavailable inputs with null
  * pointers, which produced fragile and hard to maintain code. The current
  * code uses empty strings instead of null pointers.
  * 
  * For safety we keep the test for null pointers in input. It's cheap.
  */
#define DSB_TRUNCATE(s) \
    do { VSTRING_RESET(s); VSTRING_TERMINATE(s); } while (0)

#define NULL_OR_EMPTY(s) ((s) == 0 || *(s) == 0)

#define DSB_ACTION(dsb, stat, act) \
    vstring_strcpy((dsb)->action, !NULL_OR_EMPTY(act) ? (act) : "")

#define DSB_MTA(dsb, type, name) do { \
    if (NULL_OR_EMPTY(type) || NULL_OR_EMPTY(name)) { \
	DSB_TRUNCATE((dsb)->mtype); \
	DSB_TRUNCATE((dsb)->mname); \
    } else { \
	vstring_strcpy((dsb)->mtype, (type)); \
	vstring_strcpy((dsb)->mname, (name)); \
    } \
} while (0)

#define DSB_DIAG(dsb, type, text) do { \
    if (NULL_OR_EMPTY(type) || NULL_OR_EMPTY(text)) { \
	DSB_TRUNCATE((dsb)->dtype); \
	DSB_TRUNCATE((dsb)->dtext); \
    } else { \
	vstring_strcpy((dsb)->dtype, (type)); \
	vstring_strcpy((dsb)->dtext, (text)); \
    } \
} while (0)

/* dsb_update - update formal attributes and informal text */

DSN_BUF *dsb_update(DSN_BUF *dsb, const char *status, const char *action,
		            const char *mtype, const char *mname,
		            const char *dtype, const char *dtext,
		            const char *format,...)
{
    va_list ap;

    vstring_strcpy(dsb->status, status);
    DSB_ACTION(dsb, status, action);
    DSB_MTA(dsb, mtype, mname);
    DSB_DIAG(dsb, dtype, dtext);
    va_start(ap, format);
    vstring_vsprintf(dsb->reason, format, ap);
    va_end(ap);

    return (dsb);
}

/* vdsb_simple - update status and informal text, va_list form */

DSN_BUF *vdsb_simple(DSN_BUF *dsb, const char *status, const char *format,
		             va_list ap)
{
    vstring_strcpy(dsb->status, status);
    DSB_TRUNCATE(dsb->action);
    DSB_TRUNCATE(dsb->mtype);
    DSB_TRUNCATE(dsb->mname);
    DSB_TRUNCATE(dsb->dtype);
    DSB_TRUNCATE(dsb->dtext);
    vstring_vsprintf(dsb->reason, format, ap);

    return (dsb);
}

/* dsb_simple - update status and informal text */

DSN_BUF *dsb_simple(DSN_BUF *dsb, const char *status, const char *format,...)
{
    va_list ap;

    va_start(ap, format);
    (void) vdsb_simple(dsb, status, format, ap);
    va_end(ap);
    return (dsb);
}

/* dsb_unix - update status, UNIX diagnostic and informal text */

DSN_BUF *dsb_unix(DSN_BUF *dsb, const char *status,
		          const char *dtext, const char *format,...)
{
    va_list ap;

    vstring_strcpy(dsb->status, status);
    DSB_TRUNCATE(dsb->action);
    DSB_TRUNCATE(dsb->mtype);
    DSB_TRUNCATE(dsb->mname);
    vstring_strcpy(dsb->dtype, DSB_DTYPE_UNIX);
    vstring_strcpy(dsb->dtext, dtext);
    va_start(ap, format);
    vstring_vsprintf(dsb->reason, format, ap);
    va_end(ap);

    return (dsb);
}

/* dsb_formal - update the formal fields */

DSN_BUF *dsb_formal(DSN_BUF *dsb, const char *status, const char *action,
		            const char *mtype, const char *mname,
		            const char *dtype, const char *dtext)
{
    vstring_strcpy(dsb->status, status);
    DSB_ACTION(dsb, status, action);
    DSB_MTA(dsb, mtype, mname);
    DSB_DIAG(dsb, dtype, dtext);
    return (dsb);
}

/* dsb_status - update the status, reset other formal fields */

DSN_BUF *dsb_status(DSN_BUF *dsb, const char *status)
{
    vstring_strcpy(dsb->status, status);
    DSB_TRUNCATE(dsb->action);
    DSB_TRUNCATE(dsb->mtype);
    DSB_TRUNCATE(dsb->mname);
    DSB_TRUNCATE(dsb->dtype);
    DSB_TRUNCATE(dsb->dtext);
    return (dsb);
}

/* dsb_reset - reset all fields */

void    dsb_reset(DSN_BUF *dsb)
{
    DSB_TRUNCATE(dsb->status);
    DSB_TRUNCATE(dsb->action);
    DSB_TRUNCATE(dsb->mtype);
    DSB_TRUNCATE(dsb->mname);
    DSB_TRUNCATE(dsb->dtype);
    DSB_TRUNCATE(dsb->dtext);
    DSB_TRUNCATE(dsb->reason);
}
